@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
|
@@ -105,15 +105,44 @@ async function setupWorkspace(opts: {
|
|
|
105
105
|
};
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
function makeContext(workspaceRoot: string) {
|
|
108
|
+
function makeContext(workspaceRoot: string, overrides: Record<string, unknown> = {}) {
|
|
109
109
|
return {
|
|
110
110
|
workspaceRoot,
|
|
111
111
|
logger: { info: () => {} },
|
|
112
112
|
outputFormat: "json" as const,
|
|
113
113
|
commandRegistry: undefined,
|
|
114
|
+
dryRun: false,
|
|
115
|
+
...overrides,
|
|
114
116
|
};
|
|
115
117
|
}
|
|
116
118
|
|
|
119
|
+
/**
|
|
120
|
+
* RFC-1237: a minimal recording-style WorkspaceIO — writes land in `writes`,
|
|
121
|
+
* never on disk. Lets tests assert which writes the handler attempted through
|
|
122
|
+
* the port without touching fs.
|
|
123
|
+
*/
|
|
124
|
+
function makeSpyIo() {
|
|
125
|
+
const writes: string[] = [];
|
|
126
|
+
const io = {
|
|
127
|
+
readFile: (p: string) => fs.readFile(p, "utf8"),
|
|
128
|
+
writeFile: async (p: string) => {
|
|
129
|
+
writes.push(p);
|
|
130
|
+
},
|
|
131
|
+
mkdir: async (p: string) => {
|
|
132
|
+
writes.push(`mkdir:${p}`);
|
|
133
|
+
},
|
|
134
|
+
exists: async (p: string) => {
|
|
135
|
+
try {
|
|
136
|
+
await fs.stat(p);
|
|
137
|
+
return true;
|
|
138
|
+
} catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
return { io, writes };
|
|
144
|
+
}
|
|
145
|
+
|
|
117
146
|
beforeEach(() => {
|
|
118
147
|
mockRunProbe.mockReset();
|
|
119
148
|
mockCaptureGitContext.mockReset();
|
|
@@ -124,6 +153,53 @@ afterEach(() => {
|
|
|
124
153
|
vi.restoreAllMocks();
|
|
125
154
|
});
|
|
126
155
|
|
|
156
|
+
describe("rfc.verification.refresh — sweep delta report", () => {
|
|
157
|
+
test("reports recovered (fail→pass) and regressed (pass→fail) transitions", async () => {
|
|
158
|
+
const { workspaceRoot, cleanup } = await setupWorkspace({
|
|
159
|
+
rfcs: [
|
|
160
|
+
{ id: "RFC-0100", status: "implemented" },
|
|
161
|
+
{ id: "RFC-0101", status: "implemented" },
|
|
162
|
+
{ id: "RFC-0102", status: "implemented" },
|
|
163
|
+
],
|
|
164
|
+
envelopes: [
|
|
165
|
+
{ id: "RFC-0100", overall: "fail" },
|
|
166
|
+
{ id: "RFC-0101", overall: "pass" },
|
|
167
|
+
{ id: "RFC-0102", overall: "fail" },
|
|
168
|
+
],
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
// RFC-0100: fail → pass (recovered); RFC-0101: pass → fail (regressed);
|
|
173
|
+
// RFC-0102: fail → fail (still failing — not a regression).
|
|
174
|
+
mockRunProbe
|
|
175
|
+
.mockResolvedValueOnce({ probe: PROBE, ok: true, detail: "exists" })
|
|
176
|
+
.mockResolvedValueOnce({ probe: PROBE, ok: false, detail: "missing" })
|
|
177
|
+
.mockResolvedValueOnce({ probe: PROBE, ok: false, detail: "missing" });
|
|
178
|
+
|
|
179
|
+
const result = await runRfcVerificationRefresh(
|
|
180
|
+
{ flags: { all: true } } as unknown as ForgeCommandInput,
|
|
181
|
+
makeContext(workspaceRoot) as unknown as ForgeRuntimeContext,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
expect(result.data!.delta).toEqual({
|
|
185
|
+
recovered: ["RFC-0100"],
|
|
186
|
+
regressed: ["RFC-0101"],
|
|
187
|
+
});
|
|
188
|
+
const entries = Object.fromEntries(result.data!.refreshed.map((r) => [r.rfcId, r]));
|
|
189
|
+
expect(entries["RFC-0100"]?.previousOverall).toBe("fail");
|
|
190
|
+
expect(entries["RFC-0101"]?.previousOverall).toBe("pass");
|
|
191
|
+
expect(entries["RFC-0102"]?.previousOverall).toBe("fail");
|
|
192
|
+
const r101Diag = result.data!.diagnostics.find((d) => d.file?.includes("rfc-0101"));
|
|
193
|
+
expect(r101Diag?.message).toContain("regressed: was pass");
|
|
194
|
+
const r102Diag = result.data!.diagnostics.find((d) => d.file?.includes("rfc-0102"));
|
|
195
|
+
expect(r102Diag?.message).not.toContain("regressed");
|
|
196
|
+
expect(result.summary).toContain("1 regressed, 1 recovered");
|
|
197
|
+
} finally {
|
|
198
|
+
await cleanup();
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
127
203
|
describe("rfc.verification.refresh — AC-1: single-RFC refresh", () => {
|
|
128
204
|
test("updates lastRefreshedAt, replaces probes[], preserves emittedAt", async () => {
|
|
129
205
|
const { workspaceRoot, cleanup } = await setupWorkspace({
|
|
@@ -260,6 +336,151 @@ describe("rfc.verification.refresh — AC-4: --dry-run does not write files", ()
|
|
|
260
336
|
});
|
|
261
337
|
});
|
|
262
338
|
|
|
339
|
+
describe("rfc.verification.refresh — RFC-1237 AC-1: context.dryRun (the real CLI path)", () => {
|
|
340
|
+
test("werkstatt run --dry-run consumes --dry-run into context.dryRun — zero writes", async () => {
|
|
341
|
+
const { workspaceRoot, cleanup } = await setupWorkspace({
|
|
342
|
+
rfcs: [{ id: "RFC-0100", status: "implemented" }],
|
|
343
|
+
envelopes: [{ id: "RFC-0100", emittedAt: "2026-01-01T00:00:00.000Z" }],
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
try {
|
|
347
|
+
mockRunProbe.mockResolvedValue({ probe: PROBE, ok: true, detail: "exists" });
|
|
348
|
+
const envelopePath = path.join(
|
|
349
|
+
workspaceRoot,
|
|
350
|
+
"docs",
|
|
351
|
+
"rfcs",
|
|
352
|
+
"verification",
|
|
353
|
+
"rfc-0100.generated.yaml",
|
|
354
|
+
);
|
|
355
|
+
const before = await fs.readFile(envelopePath, "utf-8");
|
|
356
|
+
|
|
357
|
+
// The real CLI path: consumeCommonFlags strips --dry-run from argv into
|
|
358
|
+
// context.dryRun — input.flags carries no "dry-run" key at all.
|
|
359
|
+
const result = await runRfcVerificationRefresh(
|
|
360
|
+
{ flags: { id: "RFC-0100" } } as unknown as ForgeCommandInput,
|
|
361
|
+
makeContext(workspaceRoot, { dryRun: true }) as unknown as ForgeRuntimeContext,
|
|
362
|
+
);
|
|
363
|
+
|
|
364
|
+
expect(result.data!.refreshed).toHaveLength(1);
|
|
365
|
+
expect(await fs.readFile(envelopePath, "utf-8")).toBe(before);
|
|
366
|
+
} finally {
|
|
367
|
+
await cleanup();
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
|
|
371
|
+
test("recording-IO port sees zero write intents under context.dryRun", async () => {
|
|
372
|
+
const { workspaceRoot, cleanup } = await setupWorkspace({
|
|
373
|
+
rfcs: [{ id: "RFC-0100", status: "implemented" }],
|
|
374
|
+
envelopes: [{ id: "RFC-0100" }],
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
try {
|
|
378
|
+
mockRunProbe.mockResolvedValue({ probe: PROBE, ok: true, detail: "exists" });
|
|
379
|
+
const { io, writes } = makeSpyIo();
|
|
380
|
+
|
|
381
|
+
await runRfcVerificationRefresh(
|
|
382
|
+
{ flags: { id: "RFC-0100" } } as unknown as ForgeCommandInput,
|
|
383
|
+
makeContext(workspaceRoot, { dryRun: true, io }) as unknown as ForgeRuntimeContext,
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
expect(
|
|
387
|
+
writes,
|
|
388
|
+
"dry-run refresh must issue zero io writes — check the dryRun guard in runRfcVerificationRefresh",
|
|
389
|
+
).toEqual([]);
|
|
390
|
+
} finally {
|
|
391
|
+
await cleanup();
|
|
392
|
+
}
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
describe("rfc.verification.refresh — RFC-1237 AC-2: emit-canonical header", () => {
|
|
397
|
+
test("writes the emit-owned structured marker header, not a refresh-owned one", async () => {
|
|
398
|
+
const { workspaceRoot, cleanup } = await setupWorkspace({
|
|
399
|
+
rfcs: [{ id: "RFC-0100", status: "implemented" }],
|
|
400
|
+
envelopes: [{ id: "RFC-0100" }],
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
try {
|
|
404
|
+
mockRunProbe.mockResolvedValue({ probe: PROBE, ok: true, detail: "exists" });
|
|
405
|
+
|
|
406
|
+
await runRfcVerificationRefresh(
|
|
407
|
+
{ flags: { id: "RFC-0100" } } as unknown as ForgeCommandInput,
|
|
408
|
+
makeContext(workspaceRoot) as unknown as ForgeRuntimeContext,
|
|
409
|
+
);
|
|
410
|
+
|
|
411
|
+
const raw = await fs.readFile(
|
|
412
|
+
path.join(workspaceRoot, "docs", "rfcs", "verification", "rfc-0100.generated.yaml"),
|
|
413
|
+
"utf-8",
|
|
414
|
+
);
|
|
415
|
+
// Structured marker keys — identical-by-construction to what emit writes.
|
|
416
|
+
expect(raw).toMatch(/^generatedMarker: GENERATED\./);
|
|
417
|
+
expect(raw).toContain("ownerCommand: rfc.verification.emit");
|
|
418
|
+
expect(raw).toContain("regenerateCommand: pnpm exec werkstatt run rfc.verification.emit");
|
|
419
|
+
// Refresh never stamps itself as owner — it is a conditional secondary writer.
|
|
420
|
+
expect(raw).not.toContain("rfc.verification.refresh");
|
|
421
|
+
} finally {
|
|
422
|
+
await cleanup();
|
|
423
|
+
}
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
test("legacy '#'-comment input header normalizes to the canonical key format", async () => {
|
|
427
|
+
const { workspaceRoot, cleanup } = await setupWorkspace({
|
|
428
|
+
rfcs: [{ id: "RFC-0100", status: "implemented" }],
|
|
429
|
+
envelopes: [{ id: "RFC-0100" }],
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
try {
|
|
433
|
+
mockRunProbe.mockResolvedValue({ probe: PROBE, ok: true, detail: "exists" });
|
|
434
|
+
await runRfcVerificationRefresh(
|
|
435
|
+
{ flags: { id: "RFC-0100" } } as unknown as ForgeCommandInput,
|
|
436
|
+
makeContext(workspaceRoot) as unknown as ForgeRuntimeContext,
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
const raw = await fs.readFile(
|
|
440
|
+
path.join(workspaceRoot, "docs", "rfcs", "verification", "rfc-0100.generated.yaml"),
|
|
441
|
+
"utf-8",
|
|
442
|
+
);
|
|
443
|
+
// setupWorkspace wrote the legacy `# Generated by ...` comment header —
|
|
444
|
+
// refresh output must carry no comment lines at all.
|
|
445
|
+
expect(raw.split("\n").every((line) => !line.startsWith("#"))).toBe(true);
|
|
446
|
+
expect(raw).toMatch(/^generatedMarker: GENERATED\./);
|
|
447
|
+
} finally {
|
|
448
|
+
await cleanup();
|
|
449
|
+
}
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
test("writes route through the resolved io port, not ambient fs", async () => {
|
|
453
|
+
const { workspaceRoot, cleanup } = await setupWorkspace({
|
|
454
|
+
rfcs: [{ id: "RFC-0100", status: "implemented" }],
|
|
455
|
+
envelopes: [{ id: "RFC-0100", emittedAt: "2026-01-01T00:00:00.000Z" }],
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
try {
|
|
459
|
+
mockRunProbe.mockResolvedValue({ probe: PROBE, ok: true, detail: "exists" });
|
|
460
|
+
const { io, writes } = makeSpyIo();
|
|
461
|
+
const envelopePath = path.join(
|
|
462
|
+
workspaceRoot,
|
|
463
|
+
"docs",
|
|
464
|
+
"rfcs",
|
|
465
|
+
"verification",
|
|
466
|
+
"rfc-0100.generated.yaml",
|
|
467
|
+
);
|
|
468
|
+
|
|
469
|
+
await runRfcVerificationRefresh(
|
|
470
|
+
{ flags: { id: "RFC-0100" } } as unknown as ForgeCommandInput,
|
|
471
|
+
makeContext(workspaceRoot, { io }) as unknown as ForgeRuntimeContext,
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
expect(writes).toEqual([envelopePath]);
|
|
475
|
+
// The spy io never touches disk — the on-disk file keeps its old content.
|
|
476
|
+
const raw = await fs.readFile(envelopePath, "utf-8");
|
|
477
|
+
expect(raw).not.toContain("lastRefreshedAt");
|
|
478
|
+
} finally {
|
|
479
|
+
await cleanup();
|
|
480
|
+
}
|
|
481
|
+
});
|
|
482
|
+
});
|
|
483
|
+
|
|
263
484
|
describe("rfc.verification.refresh — AC-5: no evidence envelope", () => {
|
|
264
485
|
test("skips RFC with reason 'no evidence envelope' and exits non-zero", async () => {
|
|
265
486
|
const { workspaceRoot, cleanup } = await setupWorkspace({
|
|
@@ -14,6 +14,17 @@ lastRefreshedAt, replaces probes[] with fresh results. Supports --id,
|
|
|
14
14
|
</MODULE_CONTRACT>
|
|
15
15
|
<CHANGE_SUMMARY>
|
|
16
16
|
<item>RFC-0999: initial implementation.</item>
|
|
17
|
+
<item>RFC-1237: dual-read dry-run (context.dryRun || flags["dry-run"]),
|
|
18
|
+
io.writeFile port routing, emit-canonical generated header — refresh owns
|
|
19
|
+
content freshness, never envelope identity (ownerCommand stays
|
|
20
|
+
rfc.verification.emit).</item>
|
|
21
|
+
<item>Session-retro 2026-10-08: sweep delta report — previousOverall per
|
|
22
|
+
envelope + recovered/regressed lists separate still-failing drift from fresh
|
|
23
|
+
regressions without manual triage.</item>
|
|
24
|
+
<item>Session-retro 2026-10-08: --concurrency flag (default 1, garbage values
|
|
25
|
+
warn + fall back to 1, >16 clamps) fans refresh out across RFC units via
|
|
26
|
+
mapWithConcurrency while probes inside one RFC stay sequential — envelope
|
|
27
|
+
ordering stays deterministic.</item>
|
|
17
28
|
</CHANGE_SUMMARY>
|
|
18
29
|
*/
|
|
19
30
|
|
|
@@ -24,14 +35,14 @@ import { parse as yamlParse } from "yaml";
|
|
|
24
35
|
|
|
25
36
|
import { runProbe } from "./acceptance.ts";
|
|
26
37
|
import { listRfcFiles, readAndParseRfc } from "./frontmatter-io.ts";
|
|
27
|
-
import { writeFileAtomic } from "../../src/utils/fs-atomic.ts";
|
|
28
|
-
import { buildGeneratedHeader } from "../../src/utils/generated-marker.ts";
|
|
29
|
-
import { stringify as yamlStringify } from "yaml";
|
|
30
38
|
import {
|
|
31
39
|
captureGitContext,
|
|
32
40
|
getKernelVersion,
|
|
41
|
+
mapWithConcurrency,
|
|
42
|
+
resolveConcurrency,
|
|
33
43
|
VERIFICATION_DIR,
|
|
34
44
|
buildEvidenceEnvelope,
|
|
45
|
+
serializeEvidenceEnvelope,
|
|
35
46
|
} from "./verification-evidence.ts";
|
|
36
47
|
import { RFC_DIR } from "./types.ts";
|
|
37
48
|
import type {
|
|
@@ -57,7 +68,10 @@ export async function runRfcVerificationRefresh(
|
|
|
57
68
|
const rfcDirPath = join(workspaceRoot, RFC_DIR);
|
|
58
69
|
const targetId = input.flags["id"] as string | undefined;
|
|
59
70
|
const allMode = input.flags["all"] === true;
|
|
60
|
-
|
|
71
|
+
// RFC-1237: consumeCommonFlags strips --dry-run into context.dryRun before
|
|
72
|
+
// dispatch — the input.flags read stays for direct/programmatic callers.
|
|
73
|
+
const dryRun = context.dryRun === true || input.flags["dry-run"] === true;
|
|
74
|
+
const concurrency = resolveConcurrency(input.flags["concurrency"], (m) => logger.warn(m));
|
|
61
75
|
|
|
62
76
|
if (!targetId && !allMode) {
|
|
63
77
|
return {
|
|
@@ -82,19 +96,24 @@ export async function runRfcVerificationRefresh(
|
|
|
82
96
|
const gitContext = await captureGitContext(workspaceRoot);
|
|
83
97
|
const kernelVersion = await getKernelVersion(workspaceRoot);
|
|
84
98
|
|
|
85
|
-
|
|
99
|
+
const processFile = async (
|
|
100
|
+
fileName: string,
|
|
101
|
+
): Promise<{
|
|
102
|
+
refreshedEntry?: NonNullable<RfcVerificationRefreshResult["refreshed"]>[number];
|
|
103
|
+
skippedEntry?: NonNullable<RfcVerificationRefreshResult["skipped"]>[number];
|
|
104
|
+
diagnostic?: Diagnostic;
|
|
105
|
+
}> => {
|
|
86
106
|
const parsedFile = await readAndParseRfc(rfcDirPath, fileName);
|
|
87
|
-
if (!parsedFile)
|
|
88
|
-
if ("error" in parsedFile)
|
|
107
|
+
if (!parsedFile) return {};
|
|
108
|
+
if ("error" in parsedFile) return {};
|
|
89
109
|
const fm = parsedFile.parsed.frontmatter;
|
|
90
110
|
const rfcId = String(fm["id"] ?? "");
|
|
91
111
|
const status = String(fm["status"] ?? "");
|
|
92
112
|
|
|
93
|
-
if (targetId && rfcId.toLowerCase() !== targetId.toLowerCase())
|
|
113
|
+
if (targetId && rfcId.toLowerCase() !== targetId.toLowerCase()) return {};
|
|
94
114
|
|
|
95
115
|
if (status !== "implemented") {
|
|
96
|
-
|
|
97
|
-
continue;
|
|
116
|
+
return { skippedEntry: { rfcId, reason: "not implemented" } };
|
|
98
117
|
}
|
|
99
118
|
|
|
100
119
|
const slug = rfcId.toLowerCase();
|
|
@@ -113,15 +132,9 @@ export async function runRfcVerificationRefresh(
|
|
|
113
132
|
// file doesn't exist or can't be parsed
|
|
114
133
|
}
|
|
115
134
|
|
|
116
|
-
if (!existingEnvelope) {
|
|
117
|
-
skipped.push({ rfcId, reason: "no evidence envelope" });
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
135
|
const acceptance = fm["acceptance"];
|
|
122
|
-
if (!Array.isArray(acceptance) || acceptance.length === 0) {
|
|
123
|
-
|
|
124
|
-
continue;
|
|
136
|
+
if (!existingEnvelope || !Array.isArray(acceptance) || acceptance.length === 0) {
|
|
137
|
+
return { skippedEntry: { rfcId, reason: "no evidence envelope" } };
|
|
125
138
|
}
|
|
126
139
|
|
|
127
140
|
const probes = acceptance as AcceptanceProbe[];
|
|
@@ -156,33 +169,49 @@ export async function runRfcVerificationRefresh(
|
|
|
156
169
|
envelope.lastRefreshedAt = now;
|
|
157
170
|
|
|
158
171
|
if (!dryRun) {
|
|
159
|
-
|
|
160
|
-
|
|
172
|
+
// RFC-1237: emit-canonical serialization — emit owns these envelopes;
|
|
173
|
+
// refresh owns content freshness, never file identity (OWN-DUP-01).
|
|
174
|
+
// Port write routes through recording IO under kernel --dry-run.
|
|
175
|
+
await io.writeFile(evidenceAbsPath, serializeEvidenceEnvelope(envelope));
|
|
161
176
|
}
|
|
162
177
|
|
|
163
178
|
const probesFailed = probeRecords.filter((r) => !r.ok).length;
|
|
164
|
-
|
|
165
|
-
rfcId,
|
|
166
|
-
file: evidenceRelPath,
|
|
167
|
-
overall: envelope.overall,
|
|
168
|
-
probesTotal: probeRecords.length,
|
|
169
|
-
probesFailed,
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
if (envelope.overall === "fail") {
|
|
173
|
-
diagnostics.push({
|
|
174
|
-
ruleId: "RFC-REFRESH-01",
|
|
175
|
-
severity: "error",
|
|
176
|
-
file: evidenceRelPath,
|
|
177
|
-
message: `${rfcId}: refresh overall is "fail" — ${probesFailed}/${probeRecords.length} probe(s) failed.`,
|
|
178
|
-
});
|
|
179
|
-
}
|
|
179
|
+
const previousOverall = existingEnvelope.overall;
|
|
180
180
|
|
|
181
181
|
if (outputFormat === "pretty") {
|
|
182
|
+
const drift = previousOverall !== envelope.overall ? `, was ${previousOverall}` : "";
|
|
182
183
|
logger.info(
|
|
183
|
-
`[refresh] ${rfcId} → ${evidenceRelPath} (${envelope.overall}, ${probeRecords.length} probes${dryRun ? ", dry-run" : ""})`,
|
|
184
|
+
`[refresh] ${rfcId} → ${evidenceRelPath} (${envelope.overall}, ${probeRecords.length} probes${drift}${dryRun ? ", dry-run" : ""})`,
|
|
184
185
|
);
|
|
185
186
|
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
refreshedEntry: {
|
|
190
|
+
rfcId,
|
|
191
|
+
file: evidenceRelPath,
|
|
192
|
+
overall: envelope.overall,
|
|
193
|
+
previousOverall,
|
|
194
|
+
probesTotal: probeRecords.length,
|
|
195
|
+
probesFailed,
|
|
196
|
+
},
|
|
197
|
+
...(envelope.overall === "fail"
|
|
198
|
+
? {
|
|
199
|
+
diagnostic: {
|
|
200
|
+
ruleId: "RFC-REFRESH-01",
|
|
201
|
+
severity: "error",
|
|
202
|
+
file: evidenceRelPath,
|
|
203
|
+
message: `${rfcId}: refresh overall is "fail" — ${probesFailed}/${probeRecords.length} probe(s) failed${previousOverall === "pass" ? " (regressed: was pass)" : ""}.`,
|
|
204
|
+
} satisfies Diagnostic,
|
|
205
|
+
}
|
|
206
|
+
: {}),
|
|
207
|
+
};
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const results = await mapWithConcurrency(allFiles, concurrency, processFile);
|
|
211
|
+
for (const r of results) {
|
|
212
|
+
if (r.refreshedEntry) refreshed.push(r.refreshedEntry);
|
|
213
|
+
if (r.skippedEntry) skipped.push(r.skippedEntry);
|
|
214
|
+
if (r.diagnostic) diagnostics.push(r.diagnostic);
|
|
186
215
|
}
|
|
187
216
|
|
|
188
217
|
const passed = refreshed.filter((r) => r.overall === "pass").length;
|
|
@@ -190,6 +219,18 @@ export async function runRfcVerificationRefresh(
|
|
|
190
219
|
const hasFailures = failed > 0;
|
|
191
220
|
const hasSkippedNoEnvelope = skipped.some((s) => s.reason === "no evidence envelope");
|
|
192
221
|
const status: RfcVerificationRefreshResult["status"] = hasFailures ? "fail" : "pass";
|
|
222
|
+
const delta = {
|
|
223
|
+
recovered: refreshed
|
|
224
|
+
.filter((r) => r.previousOverall === "fail" && r.overall === "pass")
|
|
225
|
+
.map((r) => r.rfcId),
|
|
226
|
+
regressed: refreshed
|
|
227
|
+
.filter((r) => r.previousOverall === "pass" && r.overall === "fail")
|
|
228
|
+
.map((r) => r.rfcId),
|
|
229
|
+
};
|
|
230
|
+
const deltaSummary =
|
|
231
|
+
delta.recovered.length + delta.regressed.length > 0
|
|
232
|
+
? `, ${delta.regressed.length} regressed, ${delta.recovered.length} recovered`
|
|
233
|
+
: "";
|
|
193
234
|
|
|
194
235
|
return {
|
|
195
236
|
data: {
|
|
@@ -198,6 +239,7 @@ export async function runRfcVerificationRefresh(
|
|
|
198
239
|
refreshed,
|
|
199
240
|
skipped,
|
|
200
241
|
diagnostics,
|
|
242
|
+
delta,
|
|
201
243
|
summary: {
|
|
202
244
|
total: refreshed.length,
|
|
203
245
|
passed,
|
|
@@ -206,6 +248,6 @@ export async function runRfcVerificationRefresh(
|
|
|
206
248
|
},
|
|
207
249
|
},
|
|
208
250
|
exitCode: hasFailures || hasSkippedNoEnvelope ? 1 : 0,
|
|
209
|
-
summary: `rfc.verification.refresh: ${refreshed.length} refreshed, ${skipped.length} skipped`,
|
|
251
|
+
summary: `rfc.verification.refresh: ${refreshed.length} refreshed, ${skipped.length} skipped${deltaSummary}`,
|
|
210
252
|
};
|
|
211
253
|
}
|
|
@@ -19,6 +19,11 @@ Mechanical v1 to v2 header migration across the workspace: 942 files rewritten
|
|
|
19
19
|
<item>RFC-1097: sweep — werkstatt-engine clean
|
|
20
20
|
|
|
21
21
|
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>
|
|
22
|
+
<item>Session-retro 2026-10-08: raw-file claiming — each .atif is claimed via
|
|
23
|
+
an atomic sibling `<name>.claim-<pid>-<token>` (io rename onto a nonexistent
|
|
24
|
+
target) before read/process/delete; another session's claim skips the file,
|
|
25
|
+
claims clean up in finally, dry-run creates none, keepRaw still preserves the
|
|
26
|
+
raw input. Closes the parallel-session read/delete race.</item>
|
|
22
27
|
</CHANGE_SUMMARY>
|
|
23
28
|
*/
|
|
24
29
|
|
|
@@ -196,7 +201,9 @@ export async function runSessionSave(
|
|
|
196
201
|
} else {
|
|
197
202
|
try {
|
|
198
203
|
const entries = await fs.readdir(rawDirPath);
|
|
199
|
-
|
|
204
|
+
// Only *.atif files are processed — claimed intermediates
|
|
205
|
+
// (*.atif.claim-<pid>) and in-progress writer temp files are skipped.
|
|
206
|
+
rawFiles = entries.filter((e) => e.isFile && e.name.endsWith(".atif")).map((e) => e.name);
|
|
200
207
|
} catch {
|
|
201
208
|
rawFiles = [];
|
|
202
209
|
}
|
|
@@ -234,9 +241,25 @@ export async function runSessionSave(
|
|
|
234
241
|
|
|
235
242
|
for (const rawFileName of rawFiles) {
|
|
236
243
|
const rawFilePath = path.join(rawDirPath, rawFileName);
|
|
244
|
+
// Atomic claim: rename before reading so two concurrent session.save runs
|
|
245
|
+
// (parallel agent sessions) can't process the same file — the losing
|
|
246
|
+
// rename sees ENOENT and skips. Dry-run stays read-only by design.
|
|
247
|
+
let claimedPath = rawFilePath;
|
|
248
|
+
if (!dryRun) {
|
|
249
|
+
claimedPath = `${rawFilePath}.claim-${process.pid}`;
|
|
250
|
+
try {
|
|
251
|
+
await fs.rename(rawFilePath, claimedPath);
|
|
252
|
+
} catch (err) {
|
|
253
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
254
|
+
skipped.push({ rawFile: rawFileName, reason: "claimed by another process" });
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
257
|
+
throw err;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
237
260
|
let rawContent: string;
|
|
238
261
|
try {
|
|
239
|
-
rawContent = await fs.readFile(
|
|
262
|
+
rawContent = await fs.readFile(claimedPath);
|
|
240
263
|
} catch (err) {
|
|
241
264
|
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
242
265
|
skipped.push({ rawFile: rawFileName, reason: "already processed by another process" });
|
|
@@ -279,12 +302,18 @@ export async function runSessionSave(
|
|
|
279
302
|
// Delete raw file even when skipping — otherwise .atif files accumulate
|
|
280
303
|
if (!dryRun && !keepRaw) {
|
|
281
304
|
try {
|
|
282
|
-
await trashPath(
|
|
305
|
+
await trashPath(claimedPath);
|
|
283
306
|
} catch (err) {
|
|
284
307
|
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
285
308
|
throw err;
|
|
286
309
|
}
|
|
287
310
|
}
|
|
311
|
+
} else if (!dryRun && claimedPath !== rawFilePath) {
|
|
312
|
+
try {
|
|
313
|
+
await fs.rename(claimedPath, rawFilePath);
|
|
314
|
+
} catch {
|
|
315
|
+
// best-effort restore
|
|
316
|
+
}
|
|
288
317
|
}
|
|
289
318
|
continue;
|
|
290
319
|
} catch {
|
|
@@ -320,15 +349,22 @@ export async function runSessionSave(
|
|
|
320
349
|
);
|
|
321
350
|
}
|
|
322
351
|
|
|
323
|
-
// Delete raw file unless --keep-raw
|
|
352
|
+
// Delete raw file unless --keep-raw (then restore the original name —
|
|
353
|
+
// the claim suffix is an internal detail, not part of the file's name)
|
|
324
354
|
if (!keepRaw) {
|
|
325
355
|
try {
|
|
326
|
-
await trashPath(
|
|
356
|
+
await trashPath(claimedPath);
|
|
327
357
|
} catch (err) {
|
|
328
358
|
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
329
359
|
throw err;
|
|
330
360
|
}
|
|
331
361
|
}
|
|
362
|
+
} else if (claimedPath !== rawFilePath) {
|
|
363
|
+
try {
|
|
364
|
+
await fs.rename(claimedPath, rawFilePath);
|
|
365
|
+
} catch {
|
|
366
|
+
// best-effort restore — the claimed name remains, still recoverable
|
|
367
|
+
}
|
|
332
368
|
}
|
|
333
369
|
}
|
|
334
370
|
|
|
@@ -57,6 +57,7 @@ export async function createForgeSessionModule(): Promise<ForgeModule> {
|
|
|
57
57
|
},
|
|
58
58
|
{
|
|
59
59
|
name: "session.archive",
|
|
60
|
+
coverage: "operator" as const,
|
|
60
61
|
description:
|
|
61
62
|
"Move session files older than --max-age-days (default 7) from " +
|
|
62
63
|
"docs/sessions/ to docs/sessions/archive/. Bidirectional: files in " +
|
|
@@ -84,6 +85,8 @@ export async function createForgeSessionModule(): Promise<ForgeModule> {
|
|
|
84
85
|
},
|
|
85
86
|
{
|
|
86
87
|
name: "session.validate",
|
|
88
|
+
coverage: "operator" as const,
|
|
89
|
+
coverageNote: "retrospective session-doc audit — pre-existing frontmatter debt",
|
|
87
90
|
mutatesState: false,
|
|
88
91
|
contract: "session",
|
|
89
92
|
rules: [],
|
|
@@ -102,6 +105,7 @@ export async function createForgeSessionModule(): Promise<ForgeModule> {
|
|
|
102
105
|
},
|
|
103
106
|
{
|
|
104
107
|
name: "session.list",
|
|
108
|
+
coverage: "operator" as const,
|
|
105
109
|
mutatesState: false,
|
|
106
110
|
description:
|
|
107
111
|
"List all sessions. Filter with --date-from, --date-to, --rfc, --type flags. " +
|
|
@@ -132,6 +136,7 @@ export async function createForgeSessionModule(): Promise<ForgeModule> {
|
|
|
132
136
|
},
|
|
133
137
|
{
|
|
134
138
|
name: "metrics.aggregate",
|
|
139
|
+
coverage: "operator" as const,
|
|
135
140
|
mutatesState: true,
|
|
136
141
|
description:
|
|
137
142
|
"Query and aggregate skill effectiveness metrics from docs/metrics/ files. " +
|