@warpgogol/forge 5.1.2 → 5.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/AGENTS.md +15 -2
- package/bin/cli.ts +4 -0
- package/dist/bin/cli.js +4 -0
- package/dist/bin/cli.js.map +1 -1
- package/dist/os/adr/handlers/implement-stamp.d.ts.map +1 -1
- package/dist/os/adr/handlers/implement-stamp.js +32 -2
- package/dist/os/adr/handlers/implement-stamp.js.map +1 -1
- package/dist/os/adr/types.d.ts +2 -1
- package/dist/os/adr/types.d.ts.map +1 -1
- package/dist/os/naming/naming-convention.d.ts.map +1 -1
- package/dist/os/naming/naming-convention.js +2 -0
- package/dist/os/naming/naming-convention.js.map +1 -1
- package/dist/os/queue/handlers/queue-validate.d.ts +4 -0
- package/dist/os/queue/handlers/queue-validate.d.ts.map +1 -0
- package/dist/os/queue/handlers/queue-validate.js +62 -0
- package/dist/os/queue/handlers/queue-validate.js.map +1 -0
- package/dist/os/queue/index.d.ts +5 -0
- package/dist/os/queue/index.d.ts.map +1 -0
- package/dist/os/queue/index.js +18 -0
- package/dist/os/queue/index.js.map +1 -0
- package/dist/os/queue/manifest.d.ts +14 -0
- package/dist/os/queue/manifest.d.ts.map +1 -0
- package/dist/os/queue/manifest.js +110 -0
- package/dist/os/queue/manifest.js.map +1 -0
- package/dist/os/queue/queue.module.d.ts +3 -0
- package/dist/os/queue/queue.module.d.ts.map +1 -0
- package/dist/os/queue/queue.module.js +56 -0
- package/dist/os/queue/queue.module.js.map +1 -0
- package/dist/os/queue/types.d.ts +25 -0
- package/dist/os/queue/types.d.ts.map +1 -0
- package/dist/os/queue/types.js +28 -0
- package/dist/os/queue/types.js.map +1 -0
- package/dist/os/rfc/frontmatter-io.d.ts.map +1 -1
- package/dist/os/rfc/frontmatter-io.js +1 -0
- package/dist/os/rfc/frontmatter-io.js.map +1 -1
- package/dist/os/rfc/handlers/pipeline-status.d.ts +2 -7
- package/dist/os/rfc/handlers/pipeline-status.d.ts.map +1 -1
- package/dist/os/rfc/handlers/pipeline-status.js +15 -57
- package/dist/os/rfc/handlers/pipeline-status.js.map +1 -1
- package/dist/os/rfc/handlers/validate-rules.d.ts.map +1 -1
- package/dist/os/rfc/handlers/validate-rules.js +6 -1
- package/dist/os/rfc/handlers/validate-rules.js.map +1 -1
- package/dist/os/werkstatt/handlers/workspace-deps.d.ts.map +1 -1
- package/dist/os/werkstatt/handlers/workspace-deps.js +4 -3
- package/dist/os/werkstatt/handlers/workspace-deps.js.map +1 -1
- package/dist/src/onboarding/create.d.ts.map +1 -1
- package/dist/src/onboarding/create.js +5 -2
- package/dist/src/onboarding/create.js.map +1 -1
- package/dist/src/onboarding/doctor.d.ts.map +1 -1
- package/dist/src/onboarding/doctor.js +15 -0
- package/dist/src/onboarding/doctor.js.map +1 -1
- package/dist/src/onboarding/init.d.ts +1 -0
- package/dist/src/onboarding/init.d.ts.map +1 -1
- package/dist/src/onboarding/init.js.map +1 -1
- package/dist/src/onboarding/npm-token-check.d.ts +40 -0
- package/dist/src/onboarding/npm-token-check.d.ts.map +1 -0
- package/dist/src/onboarding/npm-token-check.js +104 -0
- package/dist/src/onboarding/npm-token-check.js.map +1 -0
- package/dist/src/onboarding/scaffold-project.d.ts.map +1 -1
- package/dist/src/onboarding/scaffold-project.js +10 -0
- package/dist/src/onboarding/scaffold-project.js.map +1 -1
- package/dist/src/pipeline-status.d.ts +62 -0
- package/dist/src/pipeline-status.d.ts.map +1 -0
- package/dist/src/pipeline-status.js +220 -0
- package/dist/src/pipeline-status.js.map +1 -0
- package/dist/src/profiles/profile-schema.d.ts +2 -0
- package/dist/src/profiles/profile-schema.d.ts.map +1 -1
- package/dist/src/profiles/profile-schema.js +1 -0
- package/dist/src/profiles/profile-schema.js.map +1 -1
- package/dist/src/profiles/stack-profile.d.ts +1 -0
- package/dist/src/profiles/stack-profile.d.ts.map +1 -1
- package/dist/src/profiles/stack-profile.js +2 -0
- package/dist/src/profiles/stack-profile.js.map +1 -1
- package/os/adr/handlers/implement-stamp.ts +34 -2
- package/os/adr/types.ts +3 -1
- package/os/naming/naming-convention.ts +2 -0
- package/os/queue/handlers/queue-validate.ts +83 -0
- package/os/queue/index.ts +19 -0
- package/os/queue/manifest.ts +183 -0
- package/os/queue/queue-validate.test.ts +177 -0
- package/os/queue/queue.module.ts +59 -0
- package/os/queue/types.ts +49 -0
- package/os/rfc/frontmatter-io.ts +1 -0
- package/os/rfc/handlers/pipeline-status.ts +24 -67
- package/os/rfc/handlers/validate-rules.ts +8 -1
- package/os/werkstatt/handlers/workspace-deps.ts +4 -3
- package/package.json +17 -1
- package/profiles/site-workshop.yaml +654 -0
- package/skills/fo/fo-idea-create-rfc/acceptance-criteria-standard.md +1 -1
- package/skills/fo/fo-idea-i-just-want-to-see-the-result/SKILL.md +46 -1
- package/src/onboarding/create.ts +5 -2
- package/src/onboarding/doctor-token-check.test.ts +124 -0
- package/src/onboarding/doctor.ts +17 -0
- package/src/onboarding/init.ts +1 -0
- package/src/onboarding/npm-token-check.ts +140 -0
- package/src/onboarding/scaffold-project.ts +11 -0
- package/src/onboarding/site-workshop-profile.test.ts +84 -0
- package/src/pipeline-status.test.ts +192 -0
- package/src/pipeline-status.ts +319 -0
- package/src/profiles/profile-schema.ts +2 -0
- package/src/profiles/stack-profile.ts +2 -0
- package/src/tests/adr-implement-stamp.test.ts +150 -0
- package/src/tests/profile-schema.test.ts +1 -1
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { test, expect, describe, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
|
|
6
|
+
import type { ForgeRuntimeContext } from "../../src/types.ts";
|
|
7
|
+
import { runQueueValidate } from "./handlers/queue-validate.ts";
|
|
8
|
+
import { loadQueueManifest } from "./manifest.ts";
|
|
9
|
+
|
|
10
|
+
let tmpDir: string;
|
|
11
|
+
|
|
12
|
+
beforeEach(async () => {
|
|
13
|
+
tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "queue-validate-test-"));
|
|
14
|
+
await fs.mkdir(path.join(tmpDir, "docs/rfcs"), { recursive: true });
|
|
15
|
+
await fs.mkdir(path.join(tmpDir, "docs/adrs"), { recursive: true });
|
|
16
|
+
await fs.mkdir(path.join(tmpDir, "docs/queues"), { recursive: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(async () => {
|
|
20
|
+
await fs.rm(tmpDir, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
function testContext(): ForgeRuntimeContext {
|
|
24
|
+
const logs: string[] = [];
|
|
25
|
+
return {
|
|
26
|
+
workspaceRoot: tmpDir,
|
|
27
|
+
dryRun: false,
|
|
28
|
+
outputFormat: "json",
|
|
29
|
+
logger: {
|
|
30
|
+
section: (m: string) => logs.push(m),
|
|
31
|
+
info: (m: string) => logs.push(m),
|
|
32
|
+
warn: (m: string) => logs.push(m),
|
|
33
|
+
error: (m: string) => logs.push(m),
|
|
34
|
+
success: (m: string) => logs.push(m),
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function writeRfc(id: string, frontmatter = "status: draft\n"): Promise<void> {
|
|
40
|
+
await fs.writeFile(
|
|
41
|
+
path.join(tmpDir, `docs/rfcs/${id.toLowerCase()}-test.md`),
|
|
42
|
+
`---\nid: ${id}\n${frontmatter}---\n# ${id}\n`,
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function writeManifest(name: string, body: string): Promise<string> {
|
|
47
|
+
const rel = `docs/queues/${name}.yaml`;
|
|
48
|
+
await fs.writeFile(path.join(tmpDir, rel), body);
|
|
49
|
+
return rel;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe("loadQueueManifest — validation rules (RFC-1140)", () => {
|
|
53
|
+
test("valid manifest loads with no errors", async () => {
|
|
54
|
+
await writeRfc("RFC-1300");
|
|
55
|
+
const file = await writeManifest(
|
|
56
|
+
"block-a",
|
|
57
|
+
"id: block-a\ncreatedAt: 2026-09-23\nitems:\n - id: RFC-1300\n",
|
|
58
|
+
);
|
|
59
|
+
const { errors } = await loadQueueManifest(tmpDir, file);
|
|
60
|
+
expect(errors).toEqual([]);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("id↔filename mismatch → QUEUE-02 blocking error", async () => {
|
|
64
|
+
await writeRfc("RFC-1301");
|
|
65
|
+
const file = await writeManifest(
|
|
66
|
+
"block-b",
|
|
67
|
+
"id: different-id\ncreatedAt: 2026-09-23\nitems:\n - id: RFC-1301\n",
|
|
68
|
+
);
|
|
69
|
+
const { errors } = await loadQueueManifest(tmpDir, file);
|
|
70
|
+
expect(errors.some((e) => e.ruleId === "QUEUE-02")).toBe(true);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test("malformed item id → QUEUE-03", async () => {
|
|
74
|
+
const file = await writeManifest(
|
|
75
|
+
"block-c",
|
|
76
|
+
"id: block-c\ncreatedAt: 2026-09-23\nitems:\n - id: RFC-13\n",
|
|
77
|
+
);
|
|
78
|
+
const { errors } = await loadQueueManifest(tmpDir, file);
|
|
79
|
+
expect(errors.some((e) => e.ruleId === "QUEUE-03")).toBe(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("unresolvable item id → QUEUE-04", async () => {
|
|
83
|
+
const file = await writeManifest(
|
|
84
|
+
"block-d",
|
|
85
|
+
"id: block-d\ncreatedAt: 2026-09-23\nitems:\n - id: RFC-9999\n",
|
|
86
|
+
);
|
|
87
|
+
const { errors } = await loadQueueManifest(tmpDir, file);
|
|
88
|
+
expect(errors.some((e) => e.ruleId === "QUEUE-04")).toBe(true);
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
test("duplicate item id → QUEUE-05", async () => {
|
|
92
|
+
await writeRfc("RFC-1305");
|
|
93
|
+
const file = await writeManifest(
|
|
94
|
+
"block-e",
|
|
95
|
+
"id: block-e\ncreatedAt: 2026-09-23\nitems:\n - id: RFC-1305\n - id: RFC-1305\n",
|
|
96
|
+
);
|
|
97
|
+
const { errors } = await loadQueueManifest(tmpDir, file);
|
|
98
|
+
expect(errors.some((e) => e.ruleId === "QUEUE-05")).toBe(true);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test("dependsOn order violation → QUEUE-06 warning, not blocking", async () => {
|
|
102
|
+
await writeRfc("RFC-1310");
|
|
103
|
+
await writeRfc("RFC-1311", "status: draft\ndependsOn:\n - RFC-1310\n");
|
|
104
|
+
const file = await writeManifest(
|
|
105
|
+
"block-f",
|
|
106
|
+
"id: block-f\ncreatedAt: 2026-09-23\nitems:\n - id: RFC-1311\n - id: RFC-1310\n",
|
|
107
|
+
);
|
|
108
|
+
const { errors, warnings } = await loadQueueManifest(tmpDir, file);
|
|
109
|
+
expect(errors).toEqual([]);
|
|
110
|
+
expect(warnings.some((w) => w.ruleId === "QUEUE-06")).toBe(true);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("empty items is valid", async () => {
|
|
114
|
+
const file = await writeManifest("block-g", "id: block-g\ncreatedAt: 2026-09-23\nitems: []\n");
|
|
115
|
+
const { errors } = await loadQueueManifest(tmpDir, file);
|
|
116
|
+
expect(errors).toEqual([]);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe("runQueueValidate — command contract (RFC-1140)", () => {
|
|
121
|
+
test("throws without --file", async () => {
|
|
122
|
+
await expect(
|
|
123
|
+
runQueueValidate({ argv: [], flags: {} }, testContext()),
|
|
124
|
+
).rejects.toThrow("--file");
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test("valid manifest → pass, items + next in JSON output", async () => {
|
|
128
|
+
await writeRfc("RFC-1400", "status: implemented\n");
|
|
129
|
+
await writeRfc("RFC-1401");
|
|
130
|
+
const file = await writeManifest(
|
|
131
|
+
"block-h",
|
|
132
|
+
"id: block-h\ncreatedAt: 2026-09-23\nitems:\n - id: RFC-1400\n - id: RFC-1401\n",
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const result = await runQueueValidate(
|
|
136
|
+
{ argv: [], flags: { file } },
|
|
137
|
+
testContext(),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
expect(result?.data?.status).toBe("pass");
|
|
141
|
+
expect(result?.data?.queue).toBe("block-h");
|
|
142
|
+
expect(result?.data?.items).toHaveLength(2);
|
|
143
|
+
expect(result?.data?.items[0]?.status).toBe("implemented");
|
|
144
|
+
expect(result?.data?.items[1]?.status).toBe("pending");
|
|
145
|
+
expect(result?.data?.next).toBe("RFC-1401");
|
|
146
|
+
expect(result?.exitCode).toBe(0);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("invalid manifest → fail + exitCode 1", async () => {
|
|
150
|
+
const file = await writeManifest(
|
|
151
|
+
"block-i",
|
|
152
|
+
"id: block-i\ncreatedAt: 2026-09-23\nitems:\n - id: RFC-9999\n",
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
const result = await runQueueValidate(
|
|
156
|
+
{ argv: [], flags: { file } },
|
|
157
|
+
testContext(),
|
|
158
|
+
);
|
|
159
|
+
|
|
160
|
+
expect(result?.data?.status).toBe("fail");
|
|
161
|
+
expect(result?.exitCode).toBe(1);
|
|
162
|
+
expect(result?.data?.errors.length).toBeGreaterThan(0);
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test("empty items → pass with next null", async () => {
|
|
166
|
+
const file = await writeManifest("block-j", "id: block-j\ncreatedAt: 2026-09-23\nitems: []\n");
|
|
167
|
+
|
|
168
|
+
const result = await runQueueValidate(
|
|
169
|
+
{ argv: [], flags: { file } },
|
|
170
|
+
testContext(),
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
expect(result?.data?.status).toBe("pass");
|
|
174
|
+
expect(result?.data?.items).toEqual([]);
|
|
175
|
+
expect(result?.data?.next).toBeNull();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Register the queue.validate command with the forge kernel registry (RFC-1140).</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not implement handler logic here — delegate to handlers/queue-validate.ts.</item>
|
|
6
|
+
</non-goals>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-1140: initial forgeQueueModule registering queue.validate.</item>
|
|
10
|
+
<item>RFC-1140: steps 1-4 — shared resolver, queue module, registration
|
|
11
|
+
|
|
12
|
+
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>
|
|
13
|
+
<item>RFC-1140: queue.validate missing contract/rules metadata (DNA-91)
|
|
14
|
+
|
|
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
|
+
</CHANGE_SUMMARY>
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type { ForgeModule } from "../../src/forge-module.ts";
|
|
20
|
+
|
|
21
|
+
export async function createForgeQueueModule(): Promise<ForgeModule> {
|
|
22
|
+
const { runQueueValidate } = await import("./handlers/queue-validate.ts");
|
|
23
|
+
return {
|
|
24
|
+
name: "forge-queue",
|
|
25
|
+
version: "0.1.0",
|
|
26
|
+
runtime: "autonomous",
|
|
27
|
+
declarations: [],
|
|
28
|
+
commands: [
|
|
29
|
+
{
|
|
30
|
+
name: "queue.validate",
|
|
31
|
+
description:
|
|
32
|
+
"Validate a queue manifest (docs/queues/*.yaml) and report derived " +
|
|
33
|
+
"per-item pipeline status plus the next actionable item. Read-only — " +
|
|
34
|
+
"the pipeline orchestrator runs this before starting a queued batch (RFC-1140).",
|
|
35
|
+
scope: "workspace",
|
|
36
|
+
contract: "queue",
|
|
37
|
+
rules: ["QUEUE-01", "QUEUE-02", "QUEUE-03", "QUEUE-04", "QUEUE-05", "QUEUE-06"],
|
|
38
|
+
mutatesState: false,
|
|
39
|
+
reads: [
|
|
40
|
+
"docs/queues/*.yaml",
|
|
41
|
+
"docs/rfcs/**/*.md",
|
|
42
|
+
"docs/adrs/**/*.md",
|
|
43
|
+
"docs/audits/*.md",
|
|
44
|
+
"docs/plans/*.md",
|
|
45
|
+
],
|
|
46
|
+
cacheable: false,
|
|
47
|
+
flags: {
|
|
48
|
+
file: {
|
|
49
|
+
kind: "string",
|
|
50
|
+
required: true,
|
|
51
|
+
description: "Path to the queue manifest YAML file.",
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
execute: runQueueValidate,
|
|
55
|
+
},
|
|
56
|
+
],
|
|
57
|
+
pipelines: [],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Queue manifest contract (RFC-1140) — the durable ordered document
|
|
4
|
+
block consumed by queue.validate and the pipeline orchestrator's queue mode.</purpose>
|
|
5
|
+
<non-goals>
|
|
6
|
+
<item>Do not store per-item progress state — status is derived, never persisted.</item>
|
|
7
|
+
</non-goals>
|
|
8
|
+
</MODULE_CONTRACT>
|
|
9
|
+
<CHANGE_SUMMARY>
|
|
10
|
+
<item>RFC-1140: initial QueueManifest schema and queue.validate result types.</item>
|
|
11
|
+
<item>RFC-1140: steps 1-4 — shared resolver, queue module, registration
|
|
12
|
+
|
|
13
|
+
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>
|
|
14
|
+
</CHANGE_SUMMARY>
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { z } from "zod";
|
|
18
|
+
|
|
19
|
+
import type { Diagnostic } from "../../src/types.ts";
|
|
20
|
+
import type { QueueItemReport } from "../../src/pipeline-status.ts";
|
|
21
|
+
|
|
22
|
+
export const QUEUE_ITEM_ID_PATTERN = /^(RFC|ADR)-\d{4}$/;
|
|
23
|
+
|
|
24
|
+
export const queueManifestSchema = z.object({
|
|
25
|
+
id: z.string().min(1),
|
|
26
|
+
createdAt: z.string().min(1),
|
|
27
|
+
items: z
|
|
28
|
+
.array(
|
|
29
|
+
z.object({
|
|
30
|
+
id: z.string().min(1),
|
|
31
|
+
note: z.string().optional(),
|
|
32
|
+
}),
|
|
33
|
+
)
|
|
34
|
+
.default([]),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export type QueueManifest = z.infer<typeof queueManifestSchema>;
|
|
38
|
+
|
|
39
|
+
export interface QueueValidateResult {
|
|
40
|
+
command: "queue.validate";
|
|
41
|
+
status: "pass" | "fail";
|
|
42
|
+
queue: string;
|
|
43
|
+
file: string;
|
|
44
|
+
items: QueueItemReport[];
|
|
45
|
+
/** First item whose derived status is pending or in-progress; null when fully terminal. */
|
|
46
|
+
next: string | null;
|
|
47
|
+
errors: Diagnostic[];
|
|
48
|
+
warnings: Diagnostic[];
|
|
49
|
+
}
|
package/os/rfc/frontmatter-io.ts
CHANGED
|
@@ -56,6 +56,7 @@ export async function listRfcFiles(rfcDirPath: string): Promise<string[]> {
|
|
|
56
56
|
entry.isFile() &&
|
|
57
57
|
entry.name.endsWith(".md") &&
|
|
58
58
|
!entry.name.startsWith("rfc-0000") &&
|
|
59
|
+
!entry.name.endsWith(".generated.md") &&
|
|
59
60
|
entry.name !== "README.md"
|
|
60
61
|
) {
|
|
61
62
|
results.push(relativePath);
|
|
@@ -8,16 +8,21 @@
|
|
|
8
8
|
</non-goals>
|
|
9
9
|
</MODULE_CONTRACT>
|
|
10
10
|
<CHANGE_SUMMARY>
|
|
11
|
+
<item>RFC-1140: refactored onto the shared resolver in src/pipeline-status.ts —
|
|
12
|
+
artifact finders, TERMINAL_STATUSES, and stage computation now imported from
|
|
13
|
+
the portable layer so queue.validate and the orchestrator share one derivation.</item>
|
|
11
14
|
<item>RFC-1097: step 6 — compass.migrate codemod run
|
|
12
15
|
|
|
13
16
|
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>
|
|
14
17
|
<item>RFC-1097: sweep — werkstatt-engine clean
|
|
15
18
|
|
|
16
19
|
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>
|
|
20
|
+
<item>RFC-1140: steps 1-4 — shared resolver, queue module, registration
|
|
21
|
+
|
|
22
|
+
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>
|
|
17
23
|
</CHANGE_SUMMARY>
|
|
18
24
|
*/
|
|
19
25
|
|
|
20
|
-
import fs from "node:fs/promises";
|
|
21
26
|
import path from "node:path";
|
|
22
27
|
|
|
23
28
|
import { listRfcFiles, readAndParseRfc } from "../frontmatter-io.ts";
|
|
@@ -26,17 +31,18 @@ import type {
|
|
|
26
31
|
ForgeCommandResult,
|
|
27
32
|
ForgeRuntimeContext,
|
|
28
33
|
} from "../../../src/types.ts";
|
|
34
|
+
import {
|
|
35
|
+
TERMINAL_STATUSES,
|
|
36
|
+
computeRfcPipelineStages,
|
|
37
|
+
findAuditFile,
|
|
38
|
+
findPlanFile,
|
|
39
|
+
nextPipelineStep,
|
|
40
|
+
} from "../../../src/pipeline-status.ts";
|
|
41
|
+
import type { PipelineStage, RfcPipelineStageInfo } from "../../../src/pipeline-status.ts";
|
|
29
42
|
import type { RfcStatus } from "../types.ts";
|
|
30
43
|
import { RFC_DIR } from "../types.ts";
|
|
31
44
|
|
|
32
|
-
export type PipelineStage
|
|
33
|
-
|
|
34
|
-
export interface RfcPipelineStageInfo {
|
|
35
|
-
stage: PipelineStage;
|
|
36
|
-
done: boolean;
|
|
37
|
-
/** File or marker that confirms completion, when applicable. */
|
|
38
|
-
evidence?: string;
|
|
39
|
-
}
|
|
45
|
+
export type { PipelineStage, RfcPipelineStageInfo };
|
|
40
46
|
|
|
41
47
|
export interface RfcPipelineEntry {
|
|
42
48
|
id: string;
|
|
@@ -55,32 +61,6 @@ export interface RfcPipelineStatusResult {
|
|
|
55
61
|
entries: RfcPipelineEntry[];
|
|
56
62
|
}
|
|
57
63
|
|
|
58
|
-
const TERMINAL_STATUSES: ReadonlySet<string> = new Set(["implemented", "rejected", "superseded"]);
|
|
59
|
-
|
|
60
|
-
async function findAuditFile(workspaceRoot: string, rfcId: string): Promise<string | undefined> {
|
|
61
|
-
const auditsDir = path.join(workspaceRoot, "docs/audits");
|
|
62
|
-
try {
|
|
63
|
-
const entries = await fs.readdir(auditsDir);
|
|
64
|
-
const prefix = `audit-${rfcId.toLowerCase()}`;
|
|
65
|
-
const match = entries.find((e) => e.toLowerCase().startsWith(prefix) && e.endsWith(".md"));
|
|
66
|
-
return match ? path.join("docs/audits", match) : undefined;
|
|
67
|
-
} catch {
|
|
68
|
-
return undefined;
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function findPlanFile(workspaceRoot: string, rfcId: string): Promise<string | undefined> {
|
|
73
|
-
const plansDir = path.join(workspaceRoot, "docs/plans");
|
|
74
|
-
try {
|
|
75
|
-
const entries = await fs.readdir(plansDir);
|
|
76
|
-
const prefix = `plan-${rfcId.toLowerCase()}`;
|
|
77
|
-
const match = entries.find((e) => e.toLowerCase().startsWith(prefix) && e.endsWith(".md"));
|
|
78
|
-
return match ? path.join("docs/plans", match) : undefined;
|
|
79
|
-
} catch {
|
|
80
|
-
return undefined;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
64
|
export async function runRfcPipelineStatus(
|
|
85
65
|
input: ForgeCommandInput,
|
|
86
66
|
context: ForgeRuntimeContext,
|
|
@@ -116,38 +96,15 @@ export async function runRfcPipelineStatus(
|
|
|
116
96
|
const auditFile = await findAuditFile(workspaceRoot, id);
|
|
117
97
|
const planFile = await findPlanFile(workspaceRoot, id);
|
|
118
98
|
|
|
119
|
-
const stages: RfcPipelineStageInfo[] =
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
evidence: enhancedAt ? `enhancedAt: ${enhancedAt}` : undefined,
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
stage: "plan",
|
|
132
|
-
done: !!planFile,
|
|
133
|
-
evidence: planFile,
|
|
134
|
-
},
|
|
135
|
-
{
|
|
136
|
-
stage: "implement",
|
|
137
|
-
done: status === "implemented" || !!implementedAt,
|
|
138
|
-
evidence: implementedAt ? `implementedAt: ${implementedAt}` : undefined,
|
|
139
|
-
},
|
|
140
|
-
];
|
|
141
|
-
|
|
142
|
-
let nextStep: PipelineStage | null = null;
|
|
143
|
-
if (!isTerminal) {
|
|
144
|
-
for (const s of stages) {
|
|
145
|
-
if (!s.done) {
|
|
146
|
-
nextStep = s.stage;
|
|
147
|
-
break;
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
99
|
+
const stages: RfcPipelineStageInfo[] = computeRfcPipelineStages({
|
|
100
|
+
status,
|
|
101
|
+
enhancedAt,
|
|
102
|
+
implementedAt,
|
|
103
|
+
auditFile,
|
|
104
|
+
planFile,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
const nextStep: PipelineStage | null = isTerminal ? null : nextPipelineStep(status, stages);
|
|
151
108
|
|
|
152
109
|
entries.push({
|
|
153
110
|
id,
|
|
@@ -404,7 +404,14 @@ async function checkImplementationCommitDrift(
|
|
|
404
404
|
createdAt: string,
|
|
405
405
|
currentStatus: string,
|
|
406
406
|
): Promise<{ found: boolean; commitCount: number }> {
|
|
407
|
-
|
|
407
|
+
// Terminal statuses are exempt: implemented is the target state, and
|
|
408
|
+
// superseded/rejected RFCs may legitimately carry implement: commits from
|
|
409
|
+
// work that landed before the status transition (RFC-0769 lifecycle).
|
|
410
|
+
if (
|
|
411
|
+
currentStatus === "implemented" ||
|
|
412
|
+
currentStatus === "superseded" ||
|
|
413
|
+
currentStatus === "rejected"
|
|
414
|
+
) {
|
|
408
415
|
return { found: false, commitCount: 0 };
|
|
409
416
|
}
|
|
410
417
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/*
|
|
2
2
|
<MODULE_CONTRACT>
|
|
3
|
-
<purpose>Lazy dynamic-import bridge for @warpgogol/werkstatt-
|
|
3
|
+
<purpose>Lazy dynamic-import bridge for @warpgogol/werkstatt-shared/node/fs and @warpgogol/werkstatt-shared/fingerprint. These workspace packages are not published to npm — when forge is installed standalone from npm, they are unavailable and handlers that depend on them must degrade gracefully.</purpose>
|
|
4
4
|
<non-goals>
|
|
5
5
|
<item>Do not re-export types from @warpgogol/* — only runtime functions.</item>
|
|
6
6
|
<item>Do not use static imports — that would break npm installs without workspace deps.</item>
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
<CHANGE_SUMMARY>
|
|
10
10
|
<item>RFC-0682: extract dynamic-import helper so forge publishes without workspace:* deps.</item>
|
|
11
11
|
<item>RFC-0940: moved from os/core/handlers/ to os/werkstatt/handlers/ — this module imports @warpgogol/werkstatt-engine and belongs in the adapter directory.</item>
|
|
12
|
+
<item>2026-09-22: fingerprint import switched from @warpgogol/werkstatt-engine/fingerprint to @warpgogol/werkstatt-shared/fingerprint (canonical home since RFC-1104) — breaks the forge↔engine package cycle reported by turbo.</item>
|
|
12
13
|
</CHANGE_SUMMARY>
|
|
13
14
|
*/
|
|
14
15
|
|
|
@@ -40,7 +41,7 @@ export async function loadWorkspaceDeps(): Promise<WorkspaceDeps> {
|
|
|
40
41
|
const shareMod = await import("@warpgogol/werkstatt-shared/node/fs");
|
|
41
42
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
42
43
|
// @ts-ignore — workspace dep, may not be installed when forge is used standalone from npm
|
|
43
|
-
const fpMod = await import("@warpgogol/werkstatt-
|
|
44
|
+
const fpMod = await import("@warpgogol/werkstatt-shared/fingerprint");
|
|
44
45
|
cached = {
|
|
45
46
|
collectFiles: shareMod.collectFiles as CollectFilesFn,
|
|
46
47
|
byteHashFile: fpMod.byteHashFile as ByteHashFileFn,
|
|
@@ -48,7 +49,7 @@ export async function loadWorkspaceDeps(): Promise<WorkspaceDeps> {
|
|
|
48
49
|
return cached;
|
|
49
50
|
} catch (err) {
|
|
50
51
|
loadError =
|
|
51
|
-
`@warpgogol/werkstatt-
|
|
52
|
+
`@warpgogol/werkstatt-shared/fingerprint and @warpgogol/werkstatt-shared/node/fs are required for this command but not installed. ` +
|
|
52
53
|
`When using forge standalone from npm, install them separately or use forge within the warpgogol workspace. ` +
|
|
53
54
|
`Error: ${(err as Error).message}`;
|
|
54
55
|
throw new Error(loadError);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@warpgogol/forge",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -147,6 +147,14 @@
|
|
|
147
147
|
"./os/plugin": {
|
|
148
148
|
"types": "./os/plugin/plugin.module.ts",
|
|
149
149
|
"default": "./os/plugin/plugin.module.ts"
|
|
150
|
+
},
|
|
151
|
+
"./os/queue": {
|
|
152
|
+
"types": "./os/queue/index.ts",
|
|
153
|
+
"default": "./os/queue/index.ts"
|
|
154
|
+
},
|
|
155
|
+
"./os/queue-module": {
|
|
156
|
+
"types": "./os/queue/queue.module.ts",
|
|
157
|
+
"default": "./os/queue/queue.module.ts"
|
|
150
158
|
}
|
|
151
159
|
},
|
|
152
160
|
"files": [
|
|
@@ -324,6 +332,14 @@
|
|
|
324
332
|
"./os/plugin": {
|
|
325
333
|
"types": "./dist/os/plugin/plugin.module.d.ts",
|
|
326
334
|
"default": "./dist/os/plugin/plugin.module.js"
|
|
335
|
+
},
|
|
336
|
+
"./os/queue": {
|
|
337
|
+
"types": "./dist/os/queue/index.d.ts",
|
|
338
|
+
"default": "./dist/os/queue/index.js"
|
|
339
|
+
},
|
|
340
|
+
"./os/queue-module": {
|
|
341
|
+
"types": "./dist/os/queue/queue.module.d.ts",
|
|
342
|
+
"default": "./dist/os/queue/queue.module.js"
|
|
327
343
|
}
|
|
328
344
|
}
|
|
329
345
|
},
|