@warpgogol/forge 5.3.4 → 6.2.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 +40 -10
- 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 +328 -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 +95 -15
- 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/core/handlers/file-size-lint.d.ts.map +1 -1
- package/dist/os/core/handlers/file-size-lint.js +2 -0
- package/dist/os/core/handlers/file-size-lint.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-convention.d.ts.map +1 -1
- package/dist/os/naming/naming-convention.js +11 -1
- package/dist/os/naming/naming-convention.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 +30 -8
- 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 +25 -6
- package/dist/os/rfc/rfc.module.js.map +1 -1
- package/dist/os/rfc/types.d.ts +12 -2
- package/dist/os/rfc/types.d.ts.map +1 -1
- package/dist/os/rfc/types.js +2 -2
- 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 +134 -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 +66 -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 +42 -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/doctor.d.ts.map +1 -1
- package/dist/src/onboarding/doctor.js +2 -3
- package/dist/src/onboarding/doctor.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/dist/src/validators/skill-validate.d.ts.map +1 -1
- package/dist/src/validators/skill-validate.js +2 -4
- package/dist/src/validators/skill-validate.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 +51 -10
- package/os/compass/handlers/compass-docs-validate.ts +361 -0
- package/os/compass/handlers/summary-record.ts +106 -15
- 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/compass-policy.test.ts +7 -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/core/handlers/file-size-lint.ts +2 -0
- package/os/exploration/exploration.module.ts +3 -0
- package/os/naming/naming-convention.ts +11 -1
- 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 +37 -8
- package/os/rfc/rfc-read-only-no-side-effects.test.ts +1 -0
- package/os/rfc/rfc.module.ts +27 -6
- package/os/rfc/types.ts +18 -3
- package/os/rfc/verification-emit.test.ts +305 -0
- package/os/rfc/verification-evidence.ts +149 -24
- package/os/rfc/verification-refresh.test.ts +222 -1
- package/os/rfc/verification-refresh.ts +74 -38
- package/os/session/handlers/save.ts +37 -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/doctor.ts +2 -3
- 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/src/validators/skill-validate.ts +2 -4
- 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,11 @@ 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>RFC-1249: wire werkstatt.commands.validate into packages.check + retire CMD-OUTPUT debt</item>
|
|
17
22
|
</CHANGE_SUMMARY>
|
|
18
23
|
*/
|
|
19
24
|
|
|
@@ -24,14 +29,14 @@ import { parse as yamlParse } from "yaml";
|
|
|
24
29
|
|
|
25
30
|
import { runProbe } from "./acceptance.ts";
|
|
26
31
|
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
32
|
import {
|
|
31
33
|
captureGitContext,
|
|
32
34
|
getKernelVersion,
|
|
35
|
+
mapWithConcurrency,
|
|
36
|
+
resolveConcurrency,
|
|
33
37
|
VERIFICATION_DIR,
|
|
34
38
|
buildEvidenceEnvelope,
|
|
39
|
+
serializeEvidenceEnvelope,
|
|
35
40
|
} from "./verification-evidence.ts";
|
|
36
41
|
import { RFC_DIR } from "./types.ts";
|
|
37
42
|
import type {
|
|
@@ -57,7 +62,10 @@ export async function runRfcVerificationRefresh(
|
|
|
57
62
|
const rfcDirPath = join(workspaceRoot, RFC_DIR);
|
|
58
63
|
const targetId = input.flags["id"] as string | undefined;
|
|
59
64
|
const allMode = input.flags["all"] === true;
|
|
60
|
-
|
|
65
|
+
// RFC-1237: consumeCommonFlags strips --dry-run into context.dryRun before
|
|
66
|
+
// dispatch — the input.flags read stays for direct/programmatic callers.
|
|
67
|
+
const dryRun = context.dryRun === true || input.flags["dry-run"] === true;
|
|
68
|
+
const concurrency = resolveConcurrency(input.flags["concurrency"], (m) => logger.warn(m));
|
|
61
69
|
|
|
62
70
|
if (!targetId && !allMode) {
|
|
63
71
|
return {
|
|
@@ -82,19 +90,24 @@ export async function runRfcVerificationRefresh(
|
|
|
82
90
|
const gitContext = await captureGitContext(workspaceRoot);
|
|
83
91
|
const kernelVersion = await getKernelVersion(workspaceRoot);
|
|
84
92
|
|
|
85
|
-
|
|
93
|
+
const processFile = async (
|
|
94
|
+
fileName: string,
|
|
95
|
+
): Promise<{
|
|
96
|
+
refreshedEntry?: NonNullable<RfcVerificationRefreshResult["refreshed"]>[number];
|
|
97
|
+
skippedEntry?: NonNullable<RfcVerificationRefreshResult["skipped"]>[number];
|
|
98
|
+
diagnostic?: Diagnostic;
|
|
99
|
+
}> => {
|
|
86
100
|
const parsedFile = await readAndParseRfc(rfcDirPath, fileName);
|
|
87
|
-
if (!parsedFile)
|
|
88
|
-
if ("error" in parsedFile)
|
|
101
|
+
if (!parsedFile) return {};
|
|
102
|
+
if ("error" in parsedFile) return {};
|
|
89
103
|
const fm = parsedFile.parsed.frontmatter;
|
|
90
104
|
const rfcId = String(fm["id"] ?? "");
|
|
91
105
|
const status = String(fm["status"] ?? "");
|
|
92
106
|
|
|
93
|
-
if (targetId && rfcId.toLowerCase() !== targetId.toLowerCase())
|
|
107
|
+
if (targetId && rfcId.toLowerCase() !== targetId.toLowerCase()) return {};
|
|
94
108
|
|
|
95
109
|
if (status !== "implemented") {
|
|
96
|
-
|
|
97
|
-
continue;
|
|
110
|
+
return { skippedEntry: { rfcId, reason: "not implemented" } };
|
|
98
111
|
}
|
|
99
112
|
|
|
100
113
|
const slug = rfcId.toLowerCase();
|
|
@@ -113,15 +126,9 @@ export async function runRfcVerificationRefresh(
|
|
|
113
126
|
// file doesn't exist or can't be parsed
|
|
114
127
|
}
|
|
115
128
|
|
|
116
|
-
if (!existingEnvelope) {
|
|
117
|
-
skipped.push({ rfcId, reason: "no evidence envelope" });
|
|
118
|
-
continue;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
129
|
const acceptance = fm["acceptance"];
|
|
122
|
-
if (!Array.isArray(acceptance) || acceptance.length === 0) {
|
|
123
|
-
|
|
124
|
-
continue;
|
|
130
|
+
if (!existingEnvelope || !Array.isArray(acceptance) || acceptance.length === 0) {
|
|
131
|
+
return { skippedEntry: { rfcId, reason: "no evidence envelope" } };
|
|
125
132
|
}
|
|
126
133
|
|
|
127
134
|
const probes = acceptance as AcceptanceProbe[];
|
|
@@ -156,33 +163,49 @@ export async function runRfcVerificationRefresh(
|
|
|
156
163
|
envelope.lastRefreshedAt = now;
|
|
157
164
|
|
|
158
165
|
if (!dryRun) {
|
|
159
|
-
|
|
160
|
-
|
|
166
|
+
// RFC-1237: emit-canonical serialization — emit owns these envelopes;
|
|
167
|
+
// refresh owns content freshness, never file identity (OWN-DUP-01).
|
|
168
|
+
// Port write routes through recording IO under kernel --dry-run.
|
|
169
|
+
await io.writeFile(evidenceAbsPath, serializeEvidenceEnvelope(envelope));
|
|
161
170
|
}
|
|
162
171
|
|
|
163
172
|
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
|
-
}
|
|
173
|
+
const previousOverall = existingEnvelope.overall;
|
|
180
174
|
|
|
181
175
|
if (outputFormat === "pretty") {
|
|
176
|
+
const drift = previousOverall !== envelope.overall ? `, was ${previousOverall}` : "";
|
|
182
177
|
logger.info(
|
|
183
|
-
`[refresh] ${rfcId} → ${evidenceRelPath} (${envelope.overall}, ${probeRecords.length} probes${dryRun ? ", dry-run" : ""})`,
|
|
178
|
+
`[refresh] ${rfcId} → ${evidenceRelPath} (${envelope.overall}, ${probeRecords.length} probes${drift}${dryRun ? ", dry-run" : ""})`,
|
|
184
179
|
);
|
|
185
180
|
}
|
|
181
|
+
|
|
182
|
+
return {
|
|
183
|
+
refreshedEntry: {
|
|
184
|
+
rfcId,
|
|
185
|
+
file: evidenceRelPath,
|
|
186
|
+
overall: envelope.overall,
|
|
187
|
+
previousOverall,
|
|
188
|
+
probesTotal: probeRecords.length,
|
|
189
|
+
probesFailed,
|
|
190
|
+
},
|
|
191
|
+
...(envelope.overall === "fail"
|
|
192
|
+
? {
|
|
193
|
+
diagnostic: {
|
|
194
|
+
ruleId: "RFC-REFRESH-01",
|
|
195
|
+
severity: "error",
|
|
196
|
+
file: evidenceRelPath,
|
|
197
|
+
message: `${rfcId}: refresh overall is "fail" — ${probesFailed}/${probeRecords.length} probe(s) failed${previousOverall === "pass" ? " (regressed: was pass)" : ""}.`,
|
|
198
|
+
} satisfies Diagnostic,
|
|
199
|
+
}
|
|
200
|
+
: {}),
|
|
201
|
+
};
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const results = await mapWithConcurrency(allFiles, concurrency, processFile);
|
|
205
|
+
for (const r of results) {
|
|
206
|
+
if (r.refreshedEntry) refreshed.push(r.refreshedEntry);
|
|
207
|
+
if (r.skippedEntry) skipped.push(r.skippedEntry);
|
|
208
|
+
if (r.diagnostic) diagnostics.push(r.diagnostic);
|
|
186
209
|
}
|
|
187
210
|
|
|
188
211
|
const passed = refreshed.filter((r) => r.overall === "pass").length;
|
|
@@ -190,6 +213,18 @@ export async function runRfcVerificationRefresh(
|
|
|
190
213
|
const hasFailures = failed > 0;
|
|
191
214
|
const hasSkippedNoEnvelope = skipped.some((s) => s.reason === "no evidence envelope");
|
|
192
215
|
const status: RfcVerificationRefreshResult["status"] = hasFailures ? "fail" : "pass";
|
|
216
|
+
const delta = {
|
|
217
|
+
recovered: refreshed
|
|
218
|
+
.filter((r) => r.previousOverall === "fail" && r.overall === "pass")
|
|
219
|
+
.map((r) => r.rfcId),
|
|
220
|
+
regressed: refreshed
|
|
221
|
+
.filter((r) => r.previousOverall === "pass" && r.overall === "fail")
|
|
222
|
+
.map((r) => r.rfcId),
|
|
223
|
+
};
|
|
224
|
+
const deltaSummary =
|
|
225
|
+
delta.recovered.length + delta.regressed.length > 0
|
|
226
|
+
? `, ${delta.regressed.length} regressed, ${delta.recovered.length} recovered`
|
|
227
|
+
: "";
|
|
193
228
|
|
|
194
229
|
return {
|
|
195
230
|
data: {
|
|
@@ -198,6 +233,7 @@ export async function runRfcVerificationRefresh(
|
|
|
198
233
|
refreshed,
|
|
199
234
|
skipped,
|
|
200
235
|
diagnostics,
|
|
236
|
+
delta,
|
|
201
237
|
summary: {
|
|
202
238
|
total: refreshed.length,
|
|
203
239
|
passed,
|
|
@@ -206,6 +242,6 @@ export async function runRfcVerificationRefresh(
|
|
|
206
242
|
},
|
|
207
243
|
},
|
|
208
244
|
exitCode: hasFailures || hasSkippedNoEnvelope ? 1 : 0,
|
|
209
|
-
summary: `rfc.verification.refresh: ${refreshed.length} refreshed, ${skipped.length} skipped`,
|
|
245
|
+
summary: `rfc.verification.refresh: ${refreshed.length} refreshed, ${skipped.length} skipped${deltaSummary}`,
|
|
210
246
|
};
|
|
211
247
|
}
|
|
@@ -19,6 +19,7 @@ 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>RFC-1249: wire werkstatt.commands.validate into packages.check + retire CMD-OUTPUT debt</item>
|
|
22
23
|
</CHANGE_SUMMARY>
|
|
23
24
|
*/
|
|
24
25
|
|
|
@@ -196,7 +197,9 @@ export async function runSessionSave(
|
|
|
196
197
|
} else {
|
|
197
198
|
try {
|
|
198
199
|
const entries = await fs.readdir(rawDirPath);
|
|
199
|
-
|
|
200
|
+
// Only *.atif files are processed — claimed intermediates
|
|
201
|
+
// (*.atif.claim-<pid>) and in-progress writer temp files are skipped.
|
|
202
|
+
rawFiles = entries.filter((e) => e.isFile && e.name.endsWith(".atif")).map((e) => e.name);
|
|
200
203
|
} catch {
|
|
201
204
|
rawFiles = [];
|
|
202
205
|
}
|
|
@@ -234,9 +237,25 @@ export async function runSessionSave(
|
|
|
234
237
|
|
|
235
238
|
for (const rawFileName of rawFiles) {
|
|
236
239
|
const rawFilePath = path.join(rawDirPath, rawFileName);
|
|
240
|
+
// Atomic claim: rename before reading so two concurrent session.save runs
|
|
241
|
+
// (parallel agent sessions) can't process the same file — the losing
|
|
242
|
+
// rename sees ENOENT and skips. Dry-run stays read-only by design.
|
|
243
|
+
let claimedPath = rawFilePath;
|
|
244
|
+
if (!dryRun) {
|
|
245
|
+
claimedPath = `${rawFilePath}.claim-${process.pid}`;
|
|
246
|
+
try {
|
|
247
|
+
await fs.rename(rawFilePath, claimedPath);
|
|
248
|
+
} catch (err) {
|
|
249
|
+
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
250
|
+
skipped.push({ rawFile: rawFileName, reason: "claimed by another process" });
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
throw err;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
237
256
|
let rawContent: string;
|
|
238
257
|
try {
|
|
239
|
-
rawContent = await fs.readFile(
|
|
258
|
+
rawContent = await fs.readFile(claimedPath);
|
|
240
259
|
} catch (err) {
|
|
241
260
|
if ((err as NodeJS.ErrnoException).code === "ENOENT") {
|
|
242
261
|
skipped.push({ rawFile: rawFileName, reason: "already processed by another process" });
|
|
@@ -279,12 +298,18 @@ export async function runSessionSave(
|
|
|
279
298
|
// Delete raw file even when skipping — otherwise .atif files accumulate
|
|
280
299
|
if (!dryRun && !keepRaw) {
|
|
281
300
|
try {
|
|
282
|
-
await trashPath(
|
|
301
|
+
await trashPath(claimedPath);
|
|
283
302
|
} catch (err) {
|
|
284
303
|
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
285
304
|
throw err;
|
|
286
305
|
}
|
|
287
306
|
}
|
|
307
|
+
} else if (!dryRun && claimedPath !== rawFilePath) {
|
|
308
|
+
try {
|
|
309
|
+
await fs.rename(claimedPath, rawFilePath);
|
|
310
|
+
} catch {
|
|
311
|
+
// best-effort restore
|
|
312
|
+
}
|
|
288
313
|
}
|
|
289
314
|
continue;
|
|
290
315
|
} catch {
|
|
@@ -320,15 +345,22 @@ export async function runSessionSave(
|
|
|
320
345
|
);
|
|
321
346
|
}
|
|
322
347
|
|
|
323
|
-
// Delete raw file unless --keep-raw
|
|
348
|
+
// Delete raw file unless --keep-raw (then restore the original name —
|
|
349
|
+
// the claim suffix is an internal detail, not part of the file's name)
|
|
324
350
|
if (!keepRaw) {
|
|
325
351
|
try {
|
|
326
|
-
await trashPath(
|
|
352
|
+
await trashPath(claimedPath);
|
|
327
353
|
} catch (err) {
|
|
328
354
|
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
|
|
329
355
|
throw err;
|
|
330
356
|
}
|
|
331
357
|
}
|
|
358
|
+
} else if (claimedPath !== rawFilePath) {
|
|
359
|
+
try {
|
|
360
|
+
await fs.rename(claimedPath, rawFilePath);
|
|
361
|
+
} catch {
|
|
362
|
+
// best-effort restore — the claimed name remains, still recoverable
|
|
363
|
+
}
|
|
332
364
|
}
|
|
333
365
|
}
|
|
334
366
|
|
|
@@ -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. " +
|