@warpgogol/forge 5.3.4 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/bin/cli.ts +7 -2
- package/dist/bin/cli.js +7 -2
- package/dist/bin/cli.js.map +1 -1
- package/dist/os/adr/adr.module.d.ts.map +1 -1
- package/dist/os/adr/adr.module.js +2 -0
- package/dist/os/adr/adr.module.js.map +1 -1
- package/dist/os/audit/audit.module.d.ts.map +1 -1
- package/dist/os/audit/audit.module.js +2 -0
- package/dist/os/audit/audit.module.js.map +1 -1
- package/dist/os/compass/compass.module.d.ts.map +1 -1
- package/dist/os/compass/compass.module.js +47 -15
- package/dist/os/compass/compass.module.js.map +1 -1
- package/dist/os/compass/handlers/compass-audit-handler.d.ts +2 -0
- package/dist/os/compass/handlers/compass-audit-handler.d.ts.map +1 -1
- package/dist/os/compass/handlers/compass-audit-handler.js +39 -9
- package/dist/os/compass/handlers/compass-audit-handler.js.map +1 -1
- package/dist/os/compass/handlers/compass-docs-validate.d.ts +13 -0
- package/dist/os/compass/handlers/compass-docs-validate.d.ts.map +1 -0
- package/dist/os/compass/handlers/compass-docs-validate.js +326 -0
- package/dist/os/compass/handlers/compass-docs-validate.js.map +1 -0
- package/dist/os/compass/handlers/summary-record.d.ts +11 -2
- package/dist/os/compass/handlers/summary-record.d.ts.map +1 -1
- package/dist/os/compass/handlers/summary-record.js +93 -5
- package/dist/os/compass/handlers/summary-record.js.map +1 -1
- package/dist/os/core/core.module.d.ts.map +1 -1
- package/dist/os/core/core.module.js +124 -73
- package/dist/os/core/core.module.js.map +1 -1
- package/dist/os/exploration/exploration.module.d.ts.map +1 -1
- package/dist/os/exploration/exploration.module.js +3 -0
- package/dist/os/exploration/exploration.module.js.map +1 -1
- package/dist/os/naming/naming.module.d.ts.map +1 -1
- package/dist/os/naming/naming.module.js +9 -2
- package/dist/os/naming/naming.module.js.map +1 -1
- package/dist/os/notes/notes.module.d.ts.map +1 -1
- package/dist/os/notes/notes.module.js +17 -6
- package/dist/os/notes/notes.module.js.map +1 -1
- package/dist/os/plan/plan.module.d.ts.map +1 -1
- package/dist/os/plan/plan.module.js +2 -0
- package/dist/os/plan/plan.module.js.map +1 -1
- package/dist/os/plugin/plugin.module.d.ts.map +1 -1
- package/dist/os/plugin/plugin.module.js +9 -2
- package/dist/os/plugin/plugin.module.js.map +1 -1
- package/dist/os/program/program.module.d.ts.map +1 -1
- package/dist/os/program/program.module.js +19 -4
- package/dist/os/program/program.module.js.map +1 -1
- package/dist/os/queue/handlers/queue-validate.d.ts.map +1 -1
- package/dist/os/queue/handlers/queue-validate.js +154 -7
- package/dist/os/queue/handlers/queue-validate.js.map +1 -1
- package/dist/os/queue/manifest.d.ts +17 -1
- package/dist/os/queue/manifest.d.ts.map +1 -1
- package/dist/os/queue/manifest.js +127 -6
- package/dist/os/queue/manifest.js.map +1 -1
- package/dist/os/queue/queue.module.d.ts.map +1 -1
- package/dist/os/queue/queue.module.js +32 -4
- package/dist/os/queue/queue.module.js.map +1 -1
- package/dist/os/queue/types.d.ts +100 -1
- package/dist/os/queue/types.d.ts.map +1 -1
- package/dist/os/queue/types.js +44 -0
- package/dist/os/queue/types.js.map +1 -1
- package/dist/os/rfc/handlers/implement-stamp.d.ts.map +1 -1
- package/dist/os/rfc/handlers/implement-stamp.js +28 -5
- package/dist/os/rfc/handlers/implement-stamp.js.map +1 -1
- package/dist/os/rfc/rfc.module.d.ts.map +1 -1
- package/dist/os/rfc/rfc.module.js +27 -3
- package/dist/os/rfc/rfc.module.js.map +1 -1
- package/dist/os/rfc/types.d.ts +11 -0
- package/dist/os/rfc/types.d.ts.map +1 -1
- package/dist/os/rfc/types.js +1 -0
- package/dist/os/rfc/types.js.map +1 -1
- package/dist/os/rfc/verification-evidence.d.ts +21 -0
- package/dist/os/rfc/verification-evidence.d.ts.map +1 -1
- package/dist/os/rfc/verification-evidence.js +140 -23
- package/dist/os/rfc/verification-evidence.js.map +1 -1
- package/dist/os/rfc/verification-refresh.d.ts.map +1 -1
- package/dist/os/rfc/verification-refresh.js +72 -37
- package/dist/os/rfc/verification-refresh.js.map +1 -1
- package/dist/os/session/handlers/save.d.ts.map +1 -1
- package/dist/os/session/handlers/save.js +46 -5
- package/dist/os/session/handlers/save.js.map +1 -1
- package/dist/os/session/session.module.d.ts.map +1 -1
- package/dist/os/session/session.module.js +5 -0
- package/dist/os/session/session.module.js.map +1 -1
- package/dist/os/spec/live-spec-merge.d.ts.map +1 -1
- package/dist/os/spec/live-spec-merge.js +55 -134
- package/dist/os/spec/live-spec-merge.js.map +1 -1
- package/dist/os/spec/live-spec-rebuild.d.ts +32 -0
- package/dist/os/spec/live-spec-rebuild.d.ts.map +1 -0
- package/dist/os/spec/live-spec-rebuild.js +272 -0
- package/dist/os/spec/live-spec-rebuild.js.map +1 -0
- package/dist/os/spec/live-spec-shared.d.ts +62 -0
- package/dist/os/spec/live-spec-shared.d.ts.map +1 -0
- package/dist/os/spec/live-spec-shared.js +315 -0
- package/dist/os/spec/live-spec-shared.js.map +1 -0
- package/dist/os/spec/live-spec-types.d.ts +17 -1
- package/dist/os/spec/live-spec-types.d.ts.map +1 -1
- package/dist/os/spec/live-spec-types.js +3 -0
- package/dist/os/spec/live-spec-types.js.map +1 -1
- package/dist/os/spec/live-spec-validate.d.ts.map +1 -1
- package/dist/os/spec/live-spec-validate.js +105 -2
- package/dist/os/spec/live-spec-validate.js.map +1 -1
- package/dist/os/spec/spec-materialize.d.ts +2 -0
- package/dist/os/spec/spec-materialize.d.ts.map +1 -1
- package/dist/os/spec/spec-materialize.js +45 -5
- package/dist/os/spec/spec-materialize.js.map +1 -1
- package/dist/os/spec/spec-schema.d.ts +19 -0
- package/dist/os/spec/spec-schema.d.ts.map +1 -1
- package/dist/os/spec/spec-schema.js +30 -0
- package/dist/os/spec/spec-schema.js.map +1 -1
- package/dist/os/spec/spec-status.d.ts +4 -0
- package/dist/os/spec/spec-status.d.ts.map +1 -1
- package/dist/os/spec/spec-status.js +19 -1
- package/dist/os/spec/spec-status.js.map +1 -1
- package/dist/os/spec/spec-validate.d.ts.map +1 -1
- package/dist/os/spec/spec-validate.js +55 -8
- package/dist/os/spec/spec-validate.js.map +1 -1
- package/dist/os/spec/spec.module.d.ts.map +1 -1
- package/dist/os/spec/spec.module.js +84 -8
- package/dist/os/spec/spec.module.js.map +1 -1
- package/dist/os/werkstatt/handlers/werkstatt-operation-validate.d.ts.map +1 -1
- package/dist/os/werkstatt/handlers/werkstatt-operation-validate.js +10 -4
- package/dist/os/werkstatt/handlers/werkstatt-operation-validate.js.map +1 -1
- package/dist/os/werkstatt/werkstatt.module.d.ts.map +1 -1
- package/dist/os/werkstatt/werkstatt.module.js +13 -4
- package/dist/os/werkstatt/werkstatt.module.js.map +1 -1
- package/dist/os/workflow/workflow.module.d.ts.map +1 -1
- package/dist/os/workflow/workflow.module.js +13 -5
- package/dist/os/workflow/workflow.module.js.map +1 -1
- package/dist/src/cli-flags.d.ts.map +1 -1
- package/dist/src/cli-flags.js +25 -5
- package/dist/src/cli-flags.js.map +1 -1
- package/dist/src/config/forge-config.d.ts +3 -0
- package/dist/src/config/forge-config.d.ts.map +1 -1
- package/dist/src/config/forge-config.js +2 -0
- package/dist/src/config/forge-config.js.map +1 -1
- package/dist/src/onboarding/init.d.ts.map +1 -1
- package/dist/src/onboarding/init.js +8 -3
- package/dist/src/onboarding/init.js.map +1 -1
- package/dist/src/pipeline-status.d.ts +14 -1
- package/dist/src/pipeline-status.d.ts.map +1 -1
- package/dist/src/pipeline-status.js +28 -8
- package/dist/src/pipeline-status.js.map +1 -1
- package/dist/src/types.d.ts +7 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +6 -0
- package/dist/src/types.js.map +1 -1
- package/dist/src/utils/generated-marker.d.ts +8 -0
- package/dist/src/utils/generated-marker.d.ts.map +1 -1
- package/dist/src/utils/generated-marker.js +25 -0
- package/dist/src/utils/generated-marker.js.map +1 -1
- package/os/adr/adr.module.ts +2 -0
- package/os/audit/audit.module.ts +2 -0
- package/os/compass/compass.module.ts +49 -15
- package/os/compass/handlers/compass-audit-handler.ts +50 -9
- package/os/compass/handlers/compass-docs-validate.ts +359 -0
- package/os/compass/handlers/summary-record.ts +104 -5
- package/os/compass/handlers/tests/compass-audit-record.test.ts +2 -1
- package/os/compass/handlers/tests/compass-audit-validate.test.ts +92 -3
- package/os/compass/handlers/tests/compass-docs-validate.test.ts +203 -0
- package/os/compass/handlers/tests/compass-ledger-scope.test.ts +2 -1
- package/os/compass/handlers/tests/summary-record.test.ts +127 -0
- package/os/core/core.module.ts +147 -75
- package/os/core/docs-archive.test.ts +136 -0
- package/os/exploration/exploration.module.ts +3 -0
- package/os/naming/naming.module.ts +9 -2
- package/os/notes/notes.module.ts +17 -6
- package/os/plan/plan-0000-template.md +150 -0
- package/os/plan/plan.module.ts +2 -0
- package/os/plugin/plugin.module.ts +9 -2
- package/os/program/program.module.ts +19 -4
- package/os/queue/decision-ledger.test.ts +163 -0
- package/os/queue/handlers/queue-validate.ts +190 -8
- package/os/queue/manifest.ts +201 -7
- package/os/queue/queue-validate.test.ts +303 -0
- package/os/queue/queue.module.ts +32 -4
- package/os/queue/types.ts +56 -1
- package/os/rfc/handlers/implement-stamp.ts +35 -5
- package/os/rfc/rfc.module.ts +29 -3
- package/os/rfc/types.ts +17 -1
- package/os/rfc/verification-emit.test.ts +305 -0
- package/os/rfc/verification-evidence.ts +155 -24
- package/os/rfc/verification-refresh.test.ts +222 -1
- package/os/rfc/verification-refresh.ts +80 -38
- package/os/session/handlers/save.ts +41 -5
- package/os/session/session.module.ts +5 -0
- package/os/spec/live-spec-idempotency.pbt.test.ts +222 -0
- package/os/spec/live-spec-list-show-validate.test.ts +10 -6
- package/os/spec/live-spec-merge.test.ts +187 -7
- package/os/spec/live-spec-merge.ts +71 -155
- package/os/spec/live-spec-rebuild.test.ts +317 -0
- package/os/spec/live-spec-rebuild.ts +356 -0
- package/os/spec/live-spec-shared.ts +378 -0
- package/os/spec/live-spec-types.ts +21 -1
- package/os/spec/live-spec-validate.test.ts +317 -0
- package/os/spec/live-spec-validate.ts +106 -2
- package/os/spec/spec-materialize.test.ts +192 -0
- package/os/spec/spec-materialize.ts +48 -4
- package/os/spec/spec-schema.ts +49 -0
- package/os/spec/spec-status.ts +24 -0
- package/os/spec/spec-validate.test.ts +66 -0
- package/os/spec/spec-validate.ts +62 -8
- package/os/spec/spec.module.ts +85 -8
- package/os/werkstatt/handlers/werkstatt-operation-validate.ts +10 -4
- package/os/werkstatt/werkstatt.module.ts +13 -4
- package/os/workflow/workflow.module.ts +13 -5
- package/package.json +1 -1
- package/skills/_shared/fo-pipeline-conventions.md +126 -1
- package/skills/fo/fo-doc-audit/SKILL.md +4 -4
- package/skills/fo/fo-explore/SKILL.md +2 -1
- package/skills/fo/fo-fix/SKILL.md +11 -3
- package/skills/fo/fo-handoff/SKILL.md +4 -6
- package/skills/fo/fo-idea-create-rfc/SKILL.md +2 -1
- package/skills/fo/fo-idea-enhance/SKILL.md +23 -4
- package/skills/fo/fo-idea-i-just-want-to-see-the-result/SKILL.md +86 -22
- package/skills/fo/fo-idea-implement/SKILL.md +53 -10
- package/skills/fo/fo-idea-plan/SKILL.md +28 -6
- package/skills/fo/fo-review/SKILL.md +69 -8
- package/skills/fo/fo-session-retro/SKILL.md +3 -2
- package/skills/fo/fo-step-commit/SKILL.md +2 -0
- package/skills/shared/grilling/SKILL.md +12 -0
- package/skills/shared/writing-great-skills/GLOSSARY.md +2 -0
- package/skills/shared/writing-great-skills/SKILL.md +2 -1
- package/src/cli-flags.ts +23 -5
- package/src/config/forge-config.ts +4 -0
- package/src/onboarding/init.ts +8 -3
- package/src/pipeline-status.ts +32 -8
- package/src/tests/cli-flags.test.ts +20 -0
- package/src/tests/cli-output.test.ts +13 -7
- package/src/tests/fixtures/agents-generate-business-before.txt +7 -7
- package/src/tests/implement-stamp.test.ts +38 -0
- package/src/tests/session-handlers.test.ts +33 -0
- package/src/types.ts +13 -1
- package/src/utils/generated-marker.ts +28 -0
- package/skills/fo/fo-idea-implement/ADR-FLOW.md +0 -179
- package/skills/fo/fo-review/AXES.md +0 -70
|
@@ -117,6 +117,309 @@ describe("loadQueueManifest — validation rules (RFC-1140)", () => {
|
|
|
117
117
|
});
|
|
118
118
|
});
|
|
119
119
|
|
|
120
|
+
async function writeLedger(manifestName: string, body: string): Promise<void> {
|
|
121
|
+
await fs.writeFile(path.join(tmpDir, `docs/queues/${manifestName}.decisions.yaml`), body);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const LEDGER_HEADER = (id: string) => `id: ${id}.decisions\nqueue: ${id}\ncreatedAt: 2026-10-09\n`;
|
|
125
|
+
|
|
126
|
+
describe("runQueueValidate — decision ledger (RFC-1250)", () => {
|
|
127
|
+
test("implement-stage item with open entry → QUEUE-07 + exitCode 1 (AC-2)", async () => {
|
|
128
|
+
await writeRfc("RFC-1500", "status: accepted\n");
|
|
129
|
+
const file = await writeManifest(
|
|
130
|
+
"block-q7",
|
|
131
|
+
"id: block-q7\ncreatedAt: 2026-10-09\nitems:\n - id: RFC-1500\n",
|
|
132
|
+
);
|
|
133
|
+
await writeLedger(
|
|
134
|
+
"block-q7",
|
|
135
|
+
`${LEDGER_HEADER("block-q7")}items:\n - id: Q-1\n doc: RFC-1500\n stage: plan\n question: which scope?\n resolutionPath: none\n status: open\n`,
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
139
|
+
|
|
140
|
+
expect(result?.data?.status).toBe("fail");
|
|
141
|
+
expect(result?.exitCode).toBe(1);
|
|
142
|
+
expect(result?.data?.errors.some((e) => e.ruleId === "QUEUE-07")).toBe(true);
|
|
143
|
+
expect(result?.data?.items[0]?.decisions?.open).toBe(1);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("no QUEUE-07 when entries are answered/auto-resolved/deferred (AC-3)", async () => {
|
|
147
|
+
await writeRfc("RFC-1501", "status: accepted\n");
|
|
148
|
+
const file = await writeManifest(
|
|
149
|
+
"block-q7b",
|
|
150
|
+
"id: block-q7b\ncreatedAt: 2026-10-09\nitems:\n - id: RFC-1501\n",
|
|
151
|
+
);
|
|
152
|
+
await writeLedger(
|
|
153
|
+
"block-q7b",
|
|
154
|
+
`${LEDGER_HEADER("block-q7b")}items:\n - id: Q-1\n doc: RFC-1501\n stage: plan\n question: a\n resolutionPath: none\n status: answered\n answer: scope A\n - id: Q-2\n doc: RFC-1501\n stage: implement\n question: b\n resolutionPath: convention\n status: auto-resolved\n answer: option B\n - id: Q-3\n doc: RFC-1501\n stage: review\n question: c\n resolutionPath: none\n status: deferred\n`,
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
158
|
+
|
|
159
|
+
expect(result?.data?.errors.some((e) => e.ruleId === "QUEUE-07")).toBe(false);
|
|
160
|
+
expect(result?.data?.status).toBe("pass");
|
|
161
|
+
const d = result?.data?.items[0]?.decisions;
|
|
162
|
+
expect(d?.open).toBe(0);
|
|
163
|
+
expect(d?.deferred).toBe(1);
|
|
164
|
+
expect(d?.autoResolved).toBe(1);
|
|
165
|
+
expect(result?.data?.decisions).toEqual({
|
|
166
|
+
open: 0,
|
|
167
|
+
answered: 1,
|
|
168
|
+
deferred: 1,
|
|
169
|
+
autoResolved: 1,
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
test("no ledger sibling → no QUEUE-07 (AC-4)", async () => {
|
|
174
|
+
await writeRfc("RFC-1502", "status: accepted\n");
|
|
175
|
+
const file = await writeManifest(
|
|
176
|
+
"block-q7c",
|
|
177
|
+
"id: block-q7c\ncreatedAt: 2026-10-09\nitems:\n - id: RFC-1502\n",
|
|
178
|
+
);
|
|
179
|
+
|
|
180
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
181
|
+
|
|
182
|
+
expect(result?.data?.status).toBe("pass");
|
|
183
|
+
expect(result?.data?.errors).toEqual([]);
|
|
184
|
+
expect(result?.data?.items[0]?.decisions).toBeUndefined();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
test("open entries on pre-implement stages stay legal — no QUEUE-07", async () => {
|
|
188
|
+
await writeRfc("RFC-1503"); // status: draft, no artifacts → pending
|
|
189
|
+
const file = await writeManifest(
|
|
190
|
+
"block-q7d",
|
|
191
|
+
"id: block-q7d\ncreatedAt: 2026-10-09\nitems:\n - id: RFC-1503\n",
|
|
192
|
+
);
|
|
193
|
+
await writeLedger(
|
|
194
|
+
"block-q7d",
|
|
195
|
+
`${LEDGER_HEADER("block-q7d")}items:\n - id: Q-1\n doc: RFC-1503\n stage: enhance\n question: q\n resolutionPath: none\n status: open\n`,
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
199
|
+
|
|
200
|
+
expect(result?.data?.errors.some((e) => e.ruleId === "QUEUE-07")).toBe(false);
|
|
201
|
+
expect(result?.data?.status).toBe("pass");
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
test("pending ADR with open entry → QUEUE-07", async () => {
|
|
205
|
+
await fs.writeFile(
|
|
206
|
+
path.join(tmpDir, "docs/adrs/adr-1504-test.md"),
|
|
207
|
+
"---\nid: ADR-1504\nstatus: proposed\n---\n# ADR-1504\n",
|
|
208
|
+
);
|
|
209
|
+
const file = await writeManifest(
|
|
210
|
+
"block-q7e",
|
|
211
|
+
"id: block-q7e\ncreatedAt: 2026-10-09\nitems:\n - id: ADR-1504\n",
|
|
212
|
+
);
|
|
213
|
+
await writeLedger(
|
|
214
|
+
"block-q7e",
|
|
215
|
+
`${LEDGER_HEADER("block-q7e")}items:\n - id: Q-1\n doc: ADR-1504\n stage: implement\n question: which adapter?\n resolutionPath: none\n status: open\n`,
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
219
|
+
|
|
220
|
+
expect(result?.data?.errors.some((e) => e.ruleId === "QUEUE-07")).toBe(true);
|
|
221
|
+
expect(result?.exitCode).toBe(1);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test("next skips deferred and parked items, dependsOn cascades (AC-5)", async () => {
|
|
225
|
+
await writeRfc("RFC-1510", "status: accepted\n");
|
|
226
|
+
await writeRfc("RFC-1511", "status: draft\ndependsOn:\n - RFC-1510\n");
|
|
227
|
+
await writeRfc("RFC-1512");
|
|
228
|
+
const file = await writeManifest(
|
|
229
|
+
"block-park",
|
|
230
|
+
"id: block-park\ncreatedAt: 2026-10-09\nitems:\n - id: RFC-1510\n - id: RFC-1511\n - id: RFC-1512\n",
|
|
231
|
+
);
|
|
232
|
+
// RFC-1510 operator-deferred → parked; RFC-1511 dependsOn it → cascade-parked;
|
|
233
|
+
// next must be RFC-1512.
|
|
234
|
+
await writeLedger(
|
|
235
|
+
"block-park",
|
|
236
|
+
`${LEDGER_HEADER("block-park")}items:\n - id: Q-1\n doc: RFC-1510\n stage: implement\n question: park it\n resolutionPath: none\n status: deferred\n`,
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
240
|
+
|
|
241
|
+
expect(result?.data?.next).toBe("RFC-1512");
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("open+parked entry parks the item — no QUEUE-07, next skips, batch stays green", async () => {
|
|
245
|
+
await writeRfc("RFC-1520", "status: accepted\n");
|
|
246
|
+
await writeRfc("RFC-1521");
|
|
247
|
+
const file = await writeManifest(
|
|
248
|
+
"block-park2",
|
|
249
|
+
"id: block-park2\ncreatedAt: 2026-10-09\nitems:\n - id: RFC-1520\n - id: RFC-1521\n",
|
|
250
|
+
);
|
|
251
|
+
await writeLedger(
|
|
252
|
+
"block-park2",
|
|
253
|
+
`${LEDGER_HEADER("block-park2")}items:\n - id: Q-1\n doc: RFC-1520\n stage: implement\n question: destructive op\n resolutionPath: none\n status: open\n parked: true\n`,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
257
|
+
|
|
258
|
+
// A parked open is already execution-gated — QUEUE-07 must not fire on
|
|
259
|
+
// it (the park is the containment); the item just drops out of `next`.
|
|
260
|
+
expect(result?.data?.status).toBe("pass");
|
|
261
|
+
expect(result?.data?.errors.some((e) => e.ruleId === "QUEUE-07")).toBe(false);
|
|
262
|
+
expect(result?.data?.next).toBe("RFC-1521");
|
|
263
|
+
expect(result?.data?.items[0]?.decisions?.open).toBe(1);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("un-parked open on implementable item blocks it — QUEUE-07 + next skips it", async () => {
|
|
267
|
+
await writeRfc("RFC-1530", "status: accepted\n");
|
|
268
|
+
await writeRfc("RFC-1531");
|
|
269
|
+
const file = await writeManifest(
|
|
270
|
+
"block-q7-skip",
|
|
271
|
+
"id: block-q7-skip\ncreatedAt: 2026-10-09\nitems:\n - id: RFC-1530\n - id: RFC-1531\n",
|
|
272
|
+
);
|
|
273
|
+
await writeLedger(
|
|
274
|
+
"block-q7-skip",
|
|
275
|
+
`${LEDGER_HEADER("block-q7-skip")}items:\n - id: Q-1\n doc: RFC-1530\n stage: plan\n question: pending answer\n resolutionPath: none\n status: open\n`,
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
279
|
+
|
|
280
|
+
expect(result?.data?.status).toBe("fail");
|
|
281
|
+
expect(result?.data?.errors.some((e) => e.ruleId === "QUEUE-07")).toBe(true);
|
|
282
|
+
// QUEUE-07-blocked items are not executable — `next` must not name one.
|
|
283
|
+
expect(result?.data?.next).toBe("RFC-1531");
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
test("ledger bound to a different manifest → QUEUE-02 error", async () => {
|
|
287
|
+
await writeRfc("RFC-1540", "status: accepted\n");
|
|
288
|
+
const file = await writeManifest(
|
|
289
|
+
"block-bind",
|
|
290
|
+
"id: block-bind\ncreatedAt: 2026-10-09\nitems:\n - id: RFC-1540\n",
|
|
291
|
+
);
|
|
292
|
+
await writeLedger(
|
|
293
|
+
"block-bind",
|
|
294
|
+
`id: block-bind.decisions\nqueue: other-queue\ncreatedAt: 2026-10-09\nitems: []\n`,
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
298
|
+
|
|
299
|
+
expect(result?.data?.status).toBe("fail");
|
|
300
|
+
expect(result?.data?.errors.some((e) => e.ruleId === "QUEUE-02")).toBe(true);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("ledger id mismatching filename stem → QUEUE-02 error", async () => {
|
|
304
|
+
await writeRfc("RFC-1541", "status: accepted\n");
|
|
305
|
+
const file = await writeManifest(
|
|
306
|
+
"block-stem",
|
|
307
|
+
"id: block-stem\ncreatedAt: 2026-10-09\nitems:\n - id: RFC-1541\n",
|
|
308
|
+
);
|
|
309
|
+
await writeLedger(
|
|
310
|
+
"block-stem",
|
|
311
|
+
`id: wrong-id\nqueue: block-stem\ncreatedAt: 2026-10-09\nitems: []\n`,
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
315
|
+
|
|
316
|
+
expect(result?.data?.errors.some((e) => e.ruleId === "QUEUE-02")).toBe(true);
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
test("duplicate decision id → QUEUE-05 error", async () => {
|
|
320
|
+
await writeRfc("RFC-1542", "status: draft\n");
|
|
321
|
+
const file = await writeManifest(
|
|
322
|
+
"block-dup",
|
|
323
|
+
"id: block-dup\ncreatedAt: 2026-10-09\nitems:\n - id: RFC-1542\n",
|
|
324
|
+
);
|
|
325
|
+
await writeLedger(
|
|
326
|
+
"block-dup",
|
|
327
|
+
`${LEDGER_HEADER("block-dup")}items:\n - id: Q-1\n doc: RFC-1542\n stage: enhance\n question: a\n resolutionPath: none\n - id: Q-1\n doc: RFC-1542\n stage: plan\n question: b\n resolutionPath: none\n`,
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
331
|
+
|
|
332
|
+
expect(result?.data?.status).toBe("fail");
|
|
333
|
+
expect(result?.data?.errors.some((e) => e.ruleId === "QUEUE-05")).toBe(true);
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
test("answered entry without answer → QUEUE-08 warning, not blocking", async () => {
|
|
337
|
+
await writeRfc("RFC-1543", "status: accepted\n");
|
|
338
|
+
const file = await writeManifest(
|
|
339
|
+
"block-noans",
|
|
340
|
+
"id: block-noans\ncreatedAt: 2026-10-09\nitems:\n - id: RFC-1543\n",
|
|
341
|
+
);
|
|
342
|
+
await writeLedger(
|
|
343
|
+
"block-noans",
|
|
344
|
+
`${LEDGER_HEADER("block-noans")}items:\n - id: Q-1\n doc: RFC-1543\n stage: plan\n question: a\n resolutionPath: none\n status: answered\n`,
|
|
345
|
+
);
|
|
346
|
+
|
|
347
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
348
|
+
|
|
349
|
+
expect(result?.data?.status).toBe("pass");
|
|
350
|
+
expect(result?.data?.warnings.some((w) => w.ruleId === "QUEUE-08")).toBe(true);
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
test("decision targeting a foreign doc → QUEUE-08 warning", async () => {
|
|
354
|
+
await writeRfc("RFC-1544", "status: accepted\n");
|
|
355
|
+
const file = await writeManifest(
|
|
356
|
+
"block-foreign",
|
|
357
|
+
"id: block-foreign\ncreatedAt: 2026-10-09\nitems:\n - id: RFC-1544\n",
|
|
358
|
+
);
|
|
359
|
+
await writeLedger(
|
|
360
|
+
"block-foreign",
|
|
361
|
+
`${LEDGER_HEADER("block-foreign")}items:\n - id: Q-1\n doc: RFC-9998\n stage: plan\n question: a\n resolutionPath: none\n status: open\n`,
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
365
|
+
|
|
366
|
+
expect(result?.data?.status).toBe("pass");
|
|
367
|
+
expect(result?.data?.warnings.some((w) => w.ruleId === "QUEUE-08")).toBe(true);
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
test("QUEUE-07-blocked item cascades exclusion to dependsOn dependents", async () => {
|
|
371
|
+
await writeRfc("RFC-1550", "status: accepted\n");
|
|
372
|
+
await writeRfc("RFC-1551", "status: draft\ndependsOn:\n - RFC-1550\n");
|
|
373
|
+
await writeRfc("RFC-1552");
|
|
374
|
+
const file = await writeManifest(
|
|
375
|
+
"block-q7-cascade",
|
|
376
|
+
"id: block-q7-cascade\ncreatedAt: 2026-10-09\nitems:\n - id: RFC-1550\n - id: RFC-1551\n - id: RFC-1552\n",
|
|
377
|
+
);
|
|
378
|
+
await writeLedger(
|
|
379
|
+
"block-q7-cascade",
|
|
380
|
+
`${LEDGER_HEADER("block-q7-cascade")}items:\n - id: Q-1\n doc: RFC-1550\n stage: plan\n question: unresolved\n resolutionPath: none\n status: open\n`,
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
384
|
+
|
|
385
|
+
expect(result?.data?.status).toBe("fail");
|
|
386
|
+
expect(result?.data?.errors.some((e) => e.ruleId === "QUEUE-07")).toBe(true);
|
|
387
|
+
// RFC-1551 is pending and has no open decisions, but it depends on a
|
|
388
|
+
// QUEUE-07-blocked item — it must not execute before its dependency.
|
|
389
|
+
expect(result?.data?.next).toBe("RFC-1552");
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test("broken manifest + valid ledger → no spurious QUEUE-02/QUEUE-08 noise", async () => {
|
|
393
|
+
await writeRfc("RFC-1560", "status: accepted\n");
|
|
394
|
+
const file = await writeManifest("block-broken", "id: [\n"); // unparseable YAML
|
|
395
|
+
await writeLedger(
|
|
396
|
+
"block-broken",
|
|
397
|
+
`${LEDGER_HEADER("block-broken")}items:\n - id: Q-1\n doc: RFC-1560\n stage: plan\n question: q\n resolutionPath: none\n status: open\n`,
|
|
398
|
+
);
|
|
399
|
+
|
|
400
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
401
|
+
|
|
402
|
+
expect(result?.data?.status).toBe("fail");
|
|
403
|
+
expect(result?.data?.errors.some((e) => e.ruleId === "QUEUE-02")).toBe(false);
|
|
404
|
+
expect(result?.data?.warnings.some((w) => w.ruleId === "QUEUE-08")).toBe(false);
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
test("unreadable ledger (non-ENOENT) → QUEUE-01 error, never fails open", async () => {
|
|
408
|
+
await writeRfc("RFC-1545", "status: accepted\n");
|
|
409
|
+
const file = await writeManifest(
|
|
410
|
+
"block-eisdir",
|
|
411
|
+
"id: block-eisdir\ncreatedAt: 2026-10-09\nitems:\n - id: RFC-1545\n",
|
|
412
|
+
);
|
|
413
|
+
// A directory at the ledger path → EISDIR on read: not legal absence.
|
|
414
|
+
await fs.mkdir(path.join(tmpDir, "docs/queues/block-eisdir.decisions.yaml"));
|
|
415
|
+
|
|
416
|
+
const result = await runQueueValidate({ argv: [], flags: { file } }, testContext());
|
|
417
|
+
|
|
418
|
+
expect(result?.data?.status).toBe("fail");
|
|
419
|
+
expect(result?.data?.errors.some((e) => e.ruleId === "QUEUE-01")).toBe(true);
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
120
423
|
describe("runQueueValidate — command contract (RFC-1140)", () => {
|
|
121
424
|
test("throws without --file", async () => {
|
|
122
425
|
await expect(runQueueValidate({ argv: [], flags: {} }, testContext())).rejects.toThrow(
|
package/os/queue/queue.module.ts
CHANGED
|
@@ -13,6 +13,20 @@ Extract pipeline-status derivation into packages/forge/src/pipeline-status.ts, r
|
|
|
13
13
|
<item>RFC-1140: queue.validate missing contract/rules metadata (DNA-91)
|
|
14
14
|
|
|
15
15
|
fo-review REVIEW-CODE-2026-09-23-01 finding: declare contract queue and emitted ruleIds QUEUE-01..06 so validator.inventory.generate does not fail closed. Manifest regenerated.</item>
|
|
16
|
+
<item>RFC-1250: review wave — QUEUE-07 gates un-parked opens only, ledger binding checks, fail-closed loader
|
|
17
|
+
|
|
18
|
+
REVIEW-RFC-1250-01 findings: QUEUE-07 no longer fires on parked entries
|
|
19
|
+
(the park is the containment — a parked queue stays resumable and the
|
|
20
|
+
decision window arbitrates it); loader fails open only on ENOENT —
|
|
21
|
+
other read errors are QUEUE-01; ledger gains queue/id-stem binding
|
|
22
|
+
(QUEUE-02), Q-N uniqueness (QUEUE-05), and QUEUE-08 hygiene warnings
|
|
23
|
+
(missing answers, foreign doc ids); next excludes QUEUE-07-blocked
|
|
24
|
+
items; top-level decision totals added. Orchestrator pre-flight treats
|
|
25
|
+
QUEUE-07 as the window agenda — structural errors still stop the batch;
|
|
26
|
+
maturation skips parked/deferred items; uncovered imperative ask sites
|
|
27
|
+
gain collect riders (ADR code-trace, NC markers, audit-verdict guard).</item>
|
|
28
|
+
<item>RFC-1250: re-review wave — blocked dependsOn cascade, manifest-failure gating, generated artifacts</item>
|
|
29
|
+
<history>RFC-1250</history>
|
|
16
30
|
</CHANGE_SUMMARY>
|
|
17
31
|
*/
|
|
18
32
|
|
|
@@ -29,12 +43,26 @@ export async function createForgeQueueModule(): Promise<ForgeModule> {
|
|
|
29
43
|
{
|
|
30
44
|
name: "queue.validate",
|
|
31
45
|
description:
|
|
32
|
-
"Validate a queue manifest (docs/queues/*.yaml) and
|
|
33
|
-
"per-item pipeline status plus the
|
|
34
|
-
"
|
|
46
|
+
"Validate a queue manifest (docs/queues/*.yaml) and its decision " +
|
|
47
|
+
"ledger sibling, report derived per-item pipeline status plus the " +
|
|
48
|
+
"next actionable item. QUEUE-07 blocks implementable items carrying " +
|
|
49
|
+
"un-parked open decisions (RFC-1250); QUEUE-08 warns on ledger " +
|
|
50
|
+
"hygiene issues (missing answers, foreign doc ids). Read-only — " +
|
|
51
|
+
"the pipeline orchestrator runs this before a queued batch and the " +
|
|
52
|
+
"decision window consumes its diagnostics (RFC-1140). " +
|
|
53
|
+
"Required flags: `--file`.",
|
|
35
54
|
scope: "workspace",
|
|
36
55
|
contract: "queue",
|
|
37
|
-
rules: [
|
|
56
|
+
rules: [
|
|
57
|
+
"QUEUE-01",
|
|
58
|
+
"QUEUE-02",
|
|
59
|
+
"QUEUE-03",
|
|
60
|
+
"QUEUE-04",
|
|
61
|
+
"QUEUE-05",
|
|
62
|
+
"QUEUE-06",
|
|
63
|
+
"QUEUE-07",
|
|
64
|
+
"QUEUE-08",
|
|
65
|
+
],
|
|
38
66
|
mutatesState: false,
|
|
39
67
|
reads: [
|
|
40
68
|
"docs/queues/*.yaml",
|
package/os/queue/types.ts
CHANGED
|
@@ -7,10 +7,23 @@ block consumed by queue.validate and the pipeline orchestrator's queue mode.</pu
|
|
|
7
7
|
</non-goals>
|
|
8
8
|
</MODULE_CONTRACT>
|
|
9
9
|
<CHANGE_SUMMARY>
|
|
10
|
+
<item>RFC-1250: decision-ledger schemas — DecisionLedger, DecisionEntry, DecisionStatus, policies.</item>
|
|
10
11
|
<item>RFC-1140: initial QueueManifest schema and queue.validate result types.</item>
|
|
11
12
|
<item>RFC-1140: steps 1-4 — shared resolver, queue module, registration
|
|
12
13
|
|
|
13
14
|
Extract pipeline-status derivation into packages/forge/src/pipeline-status.ts, refactor rfc.pipeline.status onto it, add os/queue module with queue.validate command, register in WORKSHOP_MODULE_MAP.forge + bin/cli.ts + package.json exports.</item>
|
|
15
|
+
<item>RFC-1250: review wave — QUEUE-07 gates un-parked opens only, ledger binding checks, fail-closed loader
|
|
16
|
+
|
|
17
|
+
REVIEW-RFC-1250-01 findings: QUEUE-07 no longer fires on parked entries
|
|
18
|
+
(the park is the containment — a parked queue stays resumable and the
|
|
19
|
+
decision window arbitrates it); loader fails open only on ENOENT —
|
|
20
|
+
other read errors are QUEUE-01; ledger gains queue/id-stem binding
|
|
21
|
+
(QUEUE-02), Q-N uniqueness (QUEUE-05), and QUEUE-08 hygiene warnings
|
|
22
|
+
(missing answers, foreign doc ids); next excludes QUEUE-07-blocked
|
|
23
|
+
items; top-level decision totals added. Orchestrator pre-flight treats
|
|
24
|
+
QUEUE-07 as the window agenda — structural errors still stop the batch;
|
|
25
|
+
maturation skips parked/deferred items; uncovered imperative ask sites
|
|
26
|
+
gain collect riders (ADR code-trace, NC markers, audit-verdict guard).</item>
|
|
14
27
|
</CHANGE_SUMMARY>
|
|
15
28
|
*/
|
|
16
29
|
|
|
@@ -21,6 +34,46 @@ import type { QueueItemReport } from "../../src/pipeline-status.ts";
|
|
|
21
34
|
|
|
22
35
|
export const QUEUE_ITEM_ID_PATTERN = /^(RFC|ADR)-\d{4}$/;
|
|
23
36
|
|
|
37
|
+
export const DECISION_STATUS = ["open", "answered", "auto-resolved", "deferred"] as const;
|
|
38
|
+
export type DecisionStatus = (typeof DECISION_STATUS)[number];
|
|
39
|
+
|
|
40
|
+
export const decisionOptionSchema = z.object({
|
|
41
|
+
label: z.string().min(1),
|
|
42
|
+
consequence: z.string().optional(),
|
|
43
|
+
recommended: z.boolean().optional(),
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
export const decisionEntrySchema = z.object({
|
|
47
|
+
id: z.string().regex(/^Q-\d+$/),
|
|
48
|
+
doc: z.string().regex(QUEUE_ITEM_ID_PATTERN),
|
|
49
|
+
stage: z.enum(["idea", "enhance", "plan", "implement", "review"]),
|
|
50
|
+
question: z.string().min(1),
|
|
51
|
+
resolutionPath: z.enum(["codebase", "convention", "profile", "policy", "none"]),
|
|
52
|
+
options: z.array(decisionOptionSchema).default([]),
|
|
53
|
+
status: z.enum(DECISION_STATUS).default("open"),
|
|
54
|
+
answer: z.string().optional(),
|
|
55
|
+
answeredAt: z.string().optional(),
|
|
56
|
+
parked: z.boolean().optional(),
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
export const decisionPolicySchema = z.object({
|
|
60
|
+
id: z.string().min(1),
|
|
61
|
+
question: z.string().min(1),
|
|
62
|
+
answer: z.string().min(1),
|
|
63
|
+
appliesTo: z.union([z.literal("*"), z.array(z.string())]),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
export const decisionLedgerSchema = z.object({
|
|
67
|
+
id: z.string().min(1),
|
|
68
|
+
queue: z.string().min(1),
|
|
69
|
+
createdAt: z.string().min(1),
|
|
70
|
+
policies: z.array(decisionPolicySchema).default([]),
|
|
71
|
+
items: z.array(decisionEntrySchema).default([]),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
export type DecisionEntry = z.infer<typeof decisionEntrySchema>;
|
|
75
|
+
export type DecisionLedger = z.infer<typeof decisionLedgerSchema>;
|
|
76
|
+
|
|
24
77
|
export const queueManifestSchema = z.object({
|
|
25
78
|
id: z.string().min(1),
|
|
26
79
|
createdAt: z.string().min(1),
|
|
@@ -42,8 +95,10 @@ export interface QueueValidateResult {
|
|
|
42
95
|
queue: string;
|
|
43
96
|
file: string;
|
|
44
97
|
items: QueueItemReport[];
|
|
45
|
-
/** First item
|
|
98
|
+
/** First actionable item — pending/in-progress, not parked or QUEUE-07-blocked; null when none. */
|
|
46
99
|
next: string | null;
|
|
100
|
+
/** Aggregate decision counts across the ledger (present when a sibling ledger exists). */
|
|
101
|
+
decisions?: { open: number; answered: number; deferred: number; autoResolved: number };
|
|
47
102
|
errors: Diagnostic[];
|
|
48
103
|
warnings: Diagnostic[];
|
|
49
104
|
}
|
|
@@ -21,6 +21,7 @@ Mechanical v1 to v2 header migration across the workspace: 942 files rewritten
|
|
|
21
21
|
<item>RFC-1097: sweep — packages/forge + services clean
|
|
22
22
|
|
|
23
23
|
Sweep batch 2: real KEY_DECISIONS on 10 files, expanded purposes (CONTRACT-02/PURPOSE-02), headers on mission/index + gen-upstreams, sanitizeItemText in summary.record (literal Compass tags corrupted history), excludedPaths for wrangler types, test-fixtures testPattern. forge+services now 0 diagnostics under --mode error.</item>
|
|
24
|
+
<item>Session-retro 2026-10-08: auto-detect picks the sole implement/feat-prefixed candidate among multi-commit references (RFC-IMP-03 no longer blocks full pipelines); ambiguous sets keep blocking with annotated candidate list + earliest-candidate suggestion.</item>
|
|
24
25
|
<history>RFC-0268, RFC-0476, RFC-0756</history>
|
|
25
26
|
</CHANGE_SUMMARY>
|
|
26
27
|
*/
|
|
@@ -58,6 +59,11 @@ const VERIFICATION_DIR = join(RFC_DIR, "verification");
|
|
|
58
59
|
|
|
59
60
|
// ─── Git helpers ─────────────────────────────────────────────────────────────
|
|
60
61
|
|
|
62
|
+
// Pipeline convention: the implementation commit's subject carries an
|
|
63
|
+
// `implement:` (or `feat:`) conventional prefix — audit/enhance/plan/docs/
|
|
64
|
+
// review/fix/chore commits referencing the same RFC id never claim it.
|
|
65
|
+
const IMPLEMENTATION_SUBJECT_RE = /^(implement|feat)(\([^)]*\))?!?:/i;
|
|
66
|
+
|
|
61
67
|
function execGit(workspaceRoot: string, args: string[]): Promise<string> {
|
|
62
68
|
return new Promise((resolve) => {
|
|
63
69
|
execFile("git", args, { cwd: workspaceRoot, timeout: 10000 }, (err, stdout) => {
|
|
@@ -347,11 +353,35 @@ export async function runRfcImplementStamp(
|
|
|
347
353
|
message: `No commit referencing ${targetId} found in git history. Pass --implementation-commit <SHA> explicitly.`,
|
|
348
354
|
});
|
|
349
355
|
} else {
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
356
|
+
// Auto-resolve when exactly one candidate looks like an implementation
|
|
357
|
+
// commit — a multi-commit RFC pipeline (audit → plan → implement → docs →
|
|
358
|
+
// review) references the id at every step, but only one step implements.
|
|
359
|
+
const implCandidates = detected.multiple.filter((c) =>
|
|
360
|
+
IMPLEMENTATION_SUBJECT_RE.test(c.message),
|
|
361
|
+
);
|
|
362
|
+
if (implCandidates.length === 1) {
|
|
363
|
+
implementationCommit = implCandidates[0]!.sha;
|
|
364
|
+
logger.info(
|
|
365
|
+
`[rfc.implement.stamp] auto-selected implementation commit ${implCandidates[0]!.sha} (sole implement/feat-prefixed candidate)`,
|
|
366
|
+
);
|
|
367
|
+
} else {
|
|
368
|
+
const candidateList = detected.multiple
|
|
369
|
+
.map(
|
|
370
|
+
(c) =>
|
|
371
|
+
` ${c.sha} — ${c.message}${IMPLEMENTATION_SUBJECT_RE.test(c.message) ? " [implementation-shaped]" : ""}`,
|
|
372
|
+
)
|
|
373
|
+
.join("\n");
|
|
374
|
+
// git log lists newest first — the earliest implementation-shaped
|
|
375
|
+
// commit is the most likely canonical pick for a step-wise wave.
|
|
376
|
+
const earliest = implCandidates[implCandidates.length - 1];
|
|
377
|
+
const suggestion = earliest
|
|
378
|
+
? `\nLikely: ${earliest.sha} (earliest implementation-shaped commit).`
|
|
379
|
+
: "";
|
|
380
|
+
violations.push({
|
|
381
|
+
rule: "RFC-IMP-03",
|
|
382
|
+
message: `Multiple commits reference ${targetId}:\n${candidateList}\nPass --implementation-commit <SHA> to specify which one.${suggestion}`,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
355
385
|
}
|
|
356
386
|
}
|
|
357
387
|
|
package/os/rfc/rfc.module.ts
CHANGED
|
@@ -7,8 +7,6 @@
|
|
|
7
7
|
</non-goals>
|
|
8
8
|
</MODULE_CONTRACT>
|
|
9
9
|
<CHANGE_SUMMARY>
|
|
10
|
-
<item>RFC-0260: declare typed `flags` schemas for the whole rfc.* command family; unknown/malformed flags now fail with KERNEL-FLAG-01/02/03 instead of being silently ignored.</item>
|
|
11
|
-
<item>RFC-0795: add `--batch` flag to `rfc.list` for filtering by batch slug.</item>
|
|
12
10
|
<item>RFC-1097: step 6 — compass.migrate codemod run
|
|
13
11
|
|
|
14
12
|
Mechanical v1 to v2 header migration across the workspace: 942 files rewritten — CHANGE_SUMMARY windows collapsed into history, forbidden v1 blocks stripped, KEY_DECISIONS seeded from @ai-invariant comments (5 files) or TODO placeholders (103 files), blocks reordered to canonical order.</item>
|
|
@@ -16,6 +14,12 @@ Mechanical v1 to v2 header migration across the workspace: 942 files rewritten
|
|
|
16
14
|
|
|
17
15
|
Sweep batch 4: 73 Compass headers on headerless engine files (certification, component-runtime, isolation, evolution, testing), real KEY_DECISIONS on 75 files (kernel, cache, dht, swim, gitmesh, runtime), ~80 purpose expansions (CONTRACT-02/PURPOSE-02), non-goals on 13 CONTRACT-03 files, CS-07 history literal fix repo-wide (253 files). Policy: .template.ts/.template.astro excludedPaths. werkstatt-engine now 0 diagnostics.</item>
|
|
18
16
|
<item>RFC-1173: declare mutatesState on all kernel commands — collectDeclarationDiagnostics emits error-severity MUTATES-STATE-DECLARED, command.manifest.validate is the blocking consumer in packages.check, sweep declares the flag on every command definition (factories hardcode false for read-only check specs)</item>
|
|
17
|
+
<item>RFC-1231: rfc.verification.refresh gains acceptsAllFlag: true — the declared --all flag is now reachable via werkstatt run (consumed selector re-injected as flags.all after the gate).</item>
|
|
18
|
+
<item>RFC-1237: honor dry-run via context.dryRun across kernel commands; verification envelopes keep emit-canonical ownership</item>
|
|
19
|
+
<item>Session-retro 2026-10-08: rfc.verification.emit/refresh declare
|
|
20
|
+
`concurrency` (1-16, default 1) — bounded parallelism across RFCs for sweep
|
|
21
|
+
runs while probe order within each RFC stays sequential.</item>
|
|
22
|
+
<history>RFC-0260, RFC-0795</history>
|
|
19
23
|
</CHANGE_SUMMARY>
|
|
20
24
|
*/
|
|
21
25
|
|
|
@@ -114,6 +118,7 @@ export async function createForgeRfcModule(): Promise<ForgeModule> {
|
|
|
114
118
|
},
|
|
115
119
|
{
|
|
116
120
|
name: "rfc.next-id",
|
|
121
|
+
coverage: "operator" as const,
|
|
117
122
|
mutatesState: false,
|
|
118
123
|
description:
|
|
119
124
|
"Return the next free RFC number (max existing + 1) by scanning docs/rfcs/ recursively including archive/.",
|
|
@@ -212,6 +217,7 @@ export async function createForgeRfcModule(): Promise<ForgeModule> {
|
|
|
212
217
|
},
|
|
213
218
|
{
|
|
214
219
|
name: "rfc.graph",
|
|
220
|
+
coverage: "operator" as const,
|
|
215
221
|
mutatesState: false,
|
|
216
222
|
description:
|
|
217
223
|
"Print one RFC's relationship neighbours (supersedes/supersededBy/amends/" +
|
|
@@ -249,7 +255,8 @@ export async function createForgeRfcModule(): Promise<ForgeModule> {
|
|
|
249
255
|
description:
|
|
250
256
|
"RFC-0330: execute acceptance probes for target RFC(s) and write per-RFC verification " +
|
|
251
257
|
"evidence artifacts to docs/rfcs/verification/*.generated.yaml. Requires --id <rfc-id> " +
|
|
252
|
-
"or --status <status>. Reuses runProbe from RFC-0268. Writes via
|
|
258
|
+
"or --status <status>. Reuses runProbe from RFC-0268. Writes via the WorkspaceIO port " +
|
|
259
|
+
"(io.writeFile); --dry-run runs probes and reports without writing (RFC-1237).",
|
|
253
260
|
scope: "workspace",
|
|
254
261
|
mutatesState: true,
|
|
255
262
|
writes: ["docs/rfcs/verification/*.generated.yaml"],
|
|
@@ -262,6 +269,15 @@ export async function createForgeRfcModule(): Promise<ForgeModule> {
|
|
|
262
269
|
kind: "string",
|
|
263
270
|
description: "Target every RFC with this status (e.g. implemented).",
|
|
264
271
|
},
|
|
272
|
+
"dry-run": {
|
|
273
|
+
kind: "boolean",
|
|
274
|
+
description: "Run probes and report results without writing envelope files.",
|
|
275
|
+
},
|
|
276
|
+
concurrency: {
|
|
277
|
+
kind: "string",
|
|
278
|
+
description:
|
|
279
|
+
"Bounded parallelism across RFCs for sweep runs (default 1, capped at 16 — invalid values warn and fall back). Probe workers spawn subprocesses.",
|
|
280
|
+
},
|
|
265
281
|
},
|
|
266
282
|
execute: runRfcVerificationEmit,
|
|
267
283
|
},
|
|
@@ -274,6 +290,7 @@ export async function createForgeRfcModule(): Promise<ForgeModule> {
|
|
|
274
290
|
"probes[] with fresh results. Requires --id <rfc-id> or --all. Use --dry-run to " +
|
|
275
291
|
"run probes without writing envelope files.",
|
|
276
292
|
scope: "workspace",
|
|
293
|
+
acceptsAllFlag: true,
|
|
277
294
|
mutatesState: true,
|
|
278
295
|
writes: ["docs/rfcs/verification/*.generated.yaml"],
|
|
279
296
|
// Secondary writer — rfc.verification.emit owns these envelopes;
|
|
@@ -297,6 +314,11 @@ export async function createForgeRfcModule(): Promise<ForgeModule> {
|
|
|
297
314
|
kind: "boolean",
|
|
298
315
|
description: "Run probes and report results without writing envelope files.",
|
|
299
316
|
},
|
|
317
|
+
concurrency: {
|
|
318
|
+
kind: "string",
|
|
319
|
+
description:
|
|
320
|
+
"Bounded parallelism across RFCs for sweep runs (default 1, capped at 16 — invalid values warn and fall back). Probe workers spawn subprocesses.",
|
|
321
|
+
},
|
|
300
322
|
},
|
|
301
323
|
execute: runRfcVerificationRefresh,
|
|
302
324
|
},
|
|
@@ -321,6 +343,7 @@ export async function createForgeRfcModule(): Promise<ForgeModule> {
|
|
|
321
343
|
},
|
|
322
344
|
{
|
|
323
345
|
name: "rfc.dna.trace.generate",
|
|
346
|
+
coverage: "operator" as const,
|
|
324
347
|
modulePath: "packages/forge/os/rfc/rfc.module.ts",
|
|
325
348
|
description:
|
|
326
349
|
"RFC-0331: generate docs/rfcs/dna-trace.generated.yaml — the machine-readable " +
|
|
@@ -360,6 +383,7 @@ export async function createForgeRfcModule(): Promise<ForgeModule> {
|
|
|
360
383
|
},
|
|
361
384
|
{
|
|
362
385
|
name: "rfc.supersede.propose",
|
|
386
|
+
coverage: "operator" as const,
|
|
363
387
|
description:
|
|
364
388
|
"RFC-0334: escalate a blocked implementation by creating a draft superseding RFC " +
|
|
365
389
|
"with the conflict stated and TODO sections for the proposed alternative.",
|
|
@@ -391,6 +415,7 @@ export async function createForgeRfcModule(): Promise<ForgeModule> {
|
|
|
391
415
|
},
|
|
392
416
|
{
|
|
393
417
|
name: "rfc.archive",
|
|
418
|
+
coverage: "operator" as const,
|
|
394
419
|
description:
|
|
395
420
|
"Move terminal-status RFC files (implemented, rejected, superseded) into " +
|
|
396
421
|
"docs/rfcs/archive/<status>/ subdirectories. Bidirectional: moves non-terminal " +
|
|
@@ -417,6 +442,7 @@ export async function createForgeRfcModule(): Promise<ForgeModule> {
|
|
|
417
442
|
},
|
|
418
443
|
{
|
|
419
444
|
name: "rfc.pipeline.status",
|
|
445
|
+
coverage: "operator" as const,
|
|
420
446
|
mutatesState: false,
|
|
421
447
|
description:
|
|
422
448
|
"Report the pipeline status of RFCs — which steps (audit, enhance, plan, implement) " +
|
package/os/rfc/types.ts
CHANGED
|
@@ -21,6 +21,7 @@ Mechanical v1 to v2 header migration across the workspace: 942 files rewritten
|
|
|
21
21
|
<item>RFC-1097: sweep — packages/forge + services clean
|
|
22
22
|
|
|
23
23
|
Sweep batch 2: real KEY_DECISIONS on 10 files, expanded purposes (CONTRACT-02/PURPOSE-02), headers on mission/index + gen-upstreams, sanitizeItemText in summary.record (literal Compass tags corrupted history), excludedPaths for wrangler types, test-fixtures testPattern. forge+services now 0 diagnostics under --mode error.</item>
|
|
24
|
+
<item>Session-retro 2026-10-08: VerificationDelta + previousOverall on emit/refresh result entries — sweep-level recovered/regressed transition reporting.</item>
|
|
24
25
|
<history>RFC-0465, RFC-0478, RFC-0480</history>
|
|
25
26
|
</CHANGE_SUMMARY>
|
|
26
27
|
***************************************************************/
|
|
@@ -338,12 +339,25 @@ export interface VerificationEvidence {
|
|
|
338
339
|
filesModified?: string[];
|
|
339
340
|
}
|
|
340
341
|
|
|
342
|
+
export interface VerificationDelta {
|
|
343
|
+
/** Envelopes that flipped fail → pass since the committed version. */
|
|
344
|
+
recovered: string[];
|
|
345
|
+
/** Envelopes that flipped pass → fail since the committed version. */
|
|
346
|
+
regressed: string[];
|
|
347
|
+
}
|
|
348
|
+
|
|
341
349
|
export interface RfcVerificationEmitResult {
|
|
342
350
|
command: "rfc.verification.emit";
|
|
343
351
|
status: "pass" | "fail";
|
|
344
|
-
emitted: Array<{
|
|
352
|
+
emitted: Array<{
|
|
353
|
+
rfcId: string;
|
|
354
|
+
file: string;
|
|
355
|
+
overall: "pass" | "fail";
|
|
356
|
+
previousOverall?: "pass" | "fail";
|
|
357
|
+
}>;
|
|
345
358
|
skipped: Array<{ rfcId: string; reason: "no-probes" }>;
|
|
346
359
|
diagnostics: Diagnostic[];
|
|
360
|
+
delta?: VerificationDelta;
|
|
347
361
|
}
|
|
348
362
|
|
|
349
363
|
export interface RfcVerificationRefreshResult {
|
|
@@ -353,11 +367,13 @@ export interface RfcVerificationRefreshResult {
|
|
|
353
367
|
rfcId: string;
|
|
354
368
|
file: string;
|
|
355
369
|
overall: "pass" | "fail";
|
|
370
|
+
previousOverall?: "pass" | "fail";
|
|
356
371
|
probesTotal: number;
|
|
357
372
|
probesFailed: number;
|
|
358
373
|
}>;
|
|
359
374
|
skipped: Array<{ rfcId: string; reason: "no evidence envelope" | "not implemented" }>;
|
|
360
375
|
diagnostics: Diagnostic[];
|
|
376
|
+
delta?: VerificationDelta;
|
|
361
377
|
summary: { total: number; passed: number; failed: number; skipped: number };
|
|
362
378
|
} // ─── Validation ──────────────────────────────────────────────────────────────
|
|
363
379
|
export interface RfcValidationViolation {
|