/** * * `.peaks/memory/.md` memories with `metadata.type === 'feedback'` * are user-given rules. They are advisory (LLM-readable) until promoted * to at least one enforcement layer (peaks-sop gate / peaks-hooks * PreToolUse / mode-gate hardFloorCategory). This service is the * primitive behind the `peaks feedback promote` and * `peaks feedback check-unpromoted` CLI commands. * * Promotion tracking convention — two parts, and BOTH are required: * * (a) A MARKER, either an HTML comment near the top of the body * (``) or a sibling * `.peaks/memory/.promotion.json` sidecar with * `{ layer: "A" | "B" | "C", ... }`. Written by `peaks feedback * promote` so a single read of the memory file is enough to see the * claimed layer. * * (b) The ARTIFACT that layer implies — see `promotionArtifactChecks`. * which made the gate self-certifying, because the only thing a marker * proves is that `peaks feedback promote` ran. Every layer-A marker in * that no engine reads. * * The comment marker is the SOURCE OF TRUTH for human review; the sidecar is * the source of truth for the scanner. Neither is evidence on its own. */ import { missingArtifacts } from './promotion-artifact-missing.js'; import type { PromotionArtifactCheck, PromotionEvidence } from './promotion-artifact-evidence.js'; export type { PromotionArtifactCheck, PromotionEvidence }; export { missingArtifacts }; export type PromotionLayer = 'A' | 'B' | 'C'; export declare const PROMOTION_LAYERS: readonly PromotionLayer[]; export type PromotionLayerDetail = { layer: PromotionLayer; label: string; /** One-line description of what kind of rule belongs on this layer. */ description: string; }; export declare const PROMOTION_LAYER_DETAILS: readonly PromotionLayerDetail[]; /** * * Before this, a promotion was honored on the marker alone (HTML comment or * sidecar). Both are written by `peaks feedback promote` and neither proves * that anything was enforced: every layer-A promotion in this repo pointed at * gate was therefore self-certifying — it read only what the command it tells * you to run had written. * * A promotion is now honored only when its layer's enforcement surface carries * the artifact. The three layers keep artifacts in three different shapes, so * the check is a small table rather than one rule: * * - A (peaks-sop gate): a SOP manifest at `.peaks/sops//sop.json` AND an * entry for `` in `.peaks/sops/registry.json`. The registry half is not * decoration: `gate-enforce-service.enforceBashCommand` enumerates SOPs via * `readRegistry()`, so an unregistered manifest is off the enforcement path * no matter how valid it is. * - B (peaks-hooks PreToolUse): the `hooks` block of one of the two settings * files in `LAYER_B_SETTINGS` — the `.peaks/.claude-settings-template.json` * snapshot, or this repository's tracked `.claude/settings.json` — must * register the rule. The file always exists, so existence proves nothing: * the evidence is the registration inside it. * - C (mode-gate hardFloorCategory): `src/services/code/mode-gate.ts` must * register the rule in the hard-floor vocabulary, for the same reason. This * is the repo's existing convention: the one real layer-C promotion cites * its memory by path, from the category's doc block. * * assertion, in `promotion-artifact-evidence.ts`. Every check used to be a * `text.includes()` over the whole file, which certified a tree that was * a refusal — an invalid-JSON registry, a template saying "do NOT add a * matcher", a mode-gate line saying the rule is deliberately not a category — * because in each case the rule's name was still in the file's bytes. A name in * a file is not a registration, and a file that cannot be parsed is a finding, * not a permit. */ /** * SOP id used for a feedback memory's layer-A artifact. * * Prefixed because `peaks-*` SOP ids are reserved for the built-in namespace * (`reservedIdReason` in sop-service.ts) and several feedback memories start * with `peaks-`, which would make them unregistrable under their own name. */ export declare function sopIdForFeedback(memoryName: string): string; /** The artifact(s) and the structural evidence each must carry for `layer` to mean anything. */ export declare function promotionArtifactChecks(memoryName: string, layer: PromotionLayer): PromotionArtifactCheck[]; export type FeedbackMemory = { /** File basename (without `.md`). */ name: string; /** Absolute path to the .md file. */ path: string; /** Parsed frontmatter. */ frontmatter: { name?: string; description?: string; kind?: string; }; /** Body text (after the closing `---`). */ body: string; /** Parsed promotion marker (from comment OR sidecar). null when unpromoted. */ promotion: { layer: PromotionLayer; source: 'comment' | 'sidecar'; detail: string; } | null; }; export type UnpromotedFeedbackEntry = { name: string; path: string; reason: string; }; /** * declaration. * * The gate used to know only `has artifact` / `has no artifact`, so a memory that * prescribes no action could never pass: promoting it registers a SOP whose only * gate is "the source file still exists", which asserts nothing about behaviour. * That is a permanent false positive — the old vacuity defect facing the other way. * * The declaration closes it, but it must not become a way to silence the gate. * It differs from the refused grandfather channel (`promotedAt` older than this * rule) in that a grandfather exemption is a property of a memory's AGE: every * legacy memory has it, it says nothing about content, and nobody has to assert * or defend it. This is a bounded claim about the memory's CONTENT: * * 1. The code is drawn from a closed vocabulary — free text cannot be used. * 2. Each code binds to a predicate over the memory's own frontmatter, which * the gate recomputes. The declaration may only RESTATE what the memory * already says; it cannot introduce a new fact. * 3. A reason string is required (the `closedAt` escape hatch beside it has none). * 4. Coexisting with a promotion marker is a contradiction and fails, so the * channel cannot be used to bury a promotion whose artifact is missing. * 5. Exempted memories stay REPORTED via `listPromotionExempt`, so the * unpromoted count never drops silently. */ export declare const NOT_TO_PROMOTE_CODES: readonly ["non-actionable", "closed-slice-note"]; export type NotToPromoteCode = (typeof NOT_TO_PROMOTE_CODES)[number]; export type NotToPromoteRead = { kind: 'none'; } | { kind: 'valid'; code: NotToPromoteCode; reason: string; } | { kind: 'invalid'; reason: string; }; /** * Parse a single `.peaks/memory/.md` into a FeedbackMemory, or * `null` when the file is missing / unreadable / not a feedback memory. * * The "is this a feedback memory?" check is intentionally narrow: only * frontmatter with `metadata.type === 'feedback'` OR top-level * `type: feedback` qualifies. Other memory kinds (project / rule / * decision / reference / convention / module / lesson) are skipped. */ export declare function parseFeedbackMemory(filePath: string): FeedbackMemory | null; /** * Scan `.peaks/memory/*.md` for feedback memories and return those * that are NOT promoted to any enforcement layer. Pure read; the * CLI command emits the warning list + (with `--strict`) a non-zero * exit code. */ export declare function listUnpromotedFeedback(opts: { projectRoot: string; }): UnpromotedFeedbackEntry[]; /** * Read the memory's not-to-promote declaration. `invalid` is returned rather than * `none` when the fields are present but unusable, so the gate fails with a reason * instead of quietly treating a malformed declaration as "no declaration". */ export declare function readNotToPromote(filePath: string): NotToPromoteRead; export type PromotionExemptEntry = { name: string; path: string; code: NotToPromoteCode; reason: string; }; /** * The feedback memories that declare themselves out of the gate. Exposed so the * gate can REPORT them: an exemption nobody can see is the vacuity this whole * channel is required to avoid. Never throws. */ export declare function listPromotionExempt(opts: { projectRoot: string; }): PromotionExemptEntry[]; /** * Generate the code stub for a given layer. Returns a Markdown * snippet the LLM / human can paste into the appropriate file. Pure * function — does NOT touch disk. The CLI command wraps this with * the disk write / envelope emission. */ export declare function generatePromotionStub(opts: { layer: PromotionLayer; feedbackName: string; feedbackBody: string; }): { snippet: string; targetFiles: string[]; }; export type FeedbackPromoteEnvelope = { name: string; feedbackPath: string; layer: PromotionLayer; layerDetail: string; /** * to be the stub's *targets* — paths the command never wrote yet printed as * `Generated files:` — which is the defect the Gate H rework exists to remove. */ generatedFiles: string[]; /** The artifact(s) `layer` requires before the promotion means anything. */ requiredArtifacts: string[]; /** `false` when `requiredArtifacts` are not all present — the marker is then a claim without evidence. */ effective: boolean; snippet: string; promotedAt: string; promotedBy: string; }; /** * Write the promotion marker + sidecar. Also writes the envelope to * `.peaks/_runtime//rd/feedback-promote-.json` for QA * auditing. Returns the envelope so the CLI can emit it. * * The promotion marker is embedded in the memory file (so it * travels with the file across git history); the sidecar is a * machine-readable mirror. Both are written; either alone is * enough for the scanner. */ export declare function promoteFeedback(opts: { feedbackPath: string; layer: PromotionLayer; promotedBy: string; sessionId: string; projectRoot: string; dryRun?: boolean; }): Promise; export declare function isPromotionLayer(value: string): value is PromotionLayer;