@warpgogol/forge 5.1.3 → 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 +1 -0
- package/bin/cli.ts +4 -0
- package/dist/bin/cli.js +4 -0
- package/dist/bin/cli.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/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/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/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/handlers/pipeline-status.ts +24 -67
- package/package.json +17 -1
- package/skills/fo/fo-idea-i-just-want-to-see-the-result/SKILL.md +46 -1
- package/src/pipeline-status.test.ts +192 -0
- package/src/pipeline-status.ts +319 -0
|
@@ -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
|
+
}
|
|
@@ -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,
|
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
|
},
|
|
@@ -8,6 +8,7 @@ dependsOn: ['my-preferences']
|
|
|
8
8
|
languagePolicy: ref(PREFERENCES.md)
|
|
9
9
|
triggers: ["I just want to see the result", "run the full pipeline automatically", "implement this end-to-end without pauses"]
|
|
10
10
|
---
|
|
11
|
+
|
|
11
12
|
<!--
|
|
12
13
|
<MODULE_CONTRACT>
|
|
13
14
|
<purpose>fo-idea-i-just-want-to-see-the-result skill — Orchestrate the full feature pipeline (idea, audit, enhance, plan, implement, review, fix) in one invocation. Accepts a raw idea or RFC/ADR id. Use when the operator wants the complete pipeline.</purpose>
|
|
@@ -19,10 +20,15 @@ triggers: ["I just want to see the result", "run the full pipeline automatically
|
|
|
19
20
|
<item>RFC-1097: sweep — SKILL.md headers + classification fixes
|
|
20
21
|
|
|
21
22
|
Sweep batch 1: add Compass v2 headers to 45 SKILL.md files (purpose derived from frontmatter description). Fix non-skill-markdown exclusion to check filename not workspace-relative path (packages/AGENTS.md escaped it). Add .coverage to ignoredDirs.</item>
|
|
23
|
+
<item>RFC-1140: step 5 — queue-mode section in orchestrator SKILL.md
|
|
24
|
+
|
|
25
|
+
Add Queue mode section (pre-flight queue.validate, loop semantics, failure handling, manifest immutability) to fo-idea-i-just-want-to-see-the-result SKILL.md, sync .agents copy, add forgeQueueModule row to forge AGENTS.md.</item>
|
|
26
|
+
<item>RFC-1140: queue mode — materialize manifest at invocation from pasted doc list
|
|
27
|
+
|
|
28
|
+
When the invocation carries >=2 RFC/ADR ids without a manifest path, the orchestrator builds docs/queues/session-<timestamp>.yaml from the pasted order, runs queue.validate, then processes queue mode. Unresolvable ids are named explicitly.</item>
|
|
22
29
|
</CHANGE_SUMMARY>
|
|
23
30
|
-->
|
|
24
31
|
|
|
25
|
-
|
|
26
32
|
# Full Pipeline — Just Want to See the Result
|
|
27
33
|
|
|
28
34
|
Before starting, read `PREFERENCES.md` at the repository root. If the file is missing or `aiLanguage` is unset, ask the operator once and create the file using the `my-preferences` skill semantics.
|
|
@@ -48,8 +54,47 @@ The operator may provide either:
|
|
|
48
54
|
|
|
49
55
|
- **A raw idea** — natural-language description of a feature, change, or decision. The skill will invoke `fo-idea` as step 0 to create the RFC/ADR first.
|
|
50
56
|
- **An existing RFC/ADR id** — e.g. `RFC-XXXX` or `ADR-XXXX`. The skill skips idea creation and starts the pipeline from the appropriate step.
|
|
57
|
+
- **A queue manifest** — `--queue <path>` or a `docs/queues/*.yaml` path in the invocation text. The skill enters queue mode (see §Queue mode) and processes the manifest's `items[]` in order.
|
|
58
|
+
- **A pasted document list** — >=2 `RFC-XXXX`/`ADR-XXXX` ids in the invocation text (e.g. another agent's ordered implementation plan). The skill materializes a session manifest (see §Queue mode → Manifest materialization) and enters queue mode.
|
|
51
59
|
- **Nothing** — if neither is provided, check session context and IDE for a recently created document. If none found, ask the operator: "Какую идею реализуем? Опишите идею или укажите RFC-XXXX / ADR-XXXX."
|
|
52
60
|
|
|
61
|
+
## Queue mode
|
|
62
|
+
|
|
63
|
+
The orchestrator runs in **queue mode** when the invocation carries `--queue <path>`, a `docs/queues/*.yaml` path, OR a pasted document list (>=2 `RFC-XXXX`/`ADR-XXXX` ids — e.g. another agent's ordered implementation plan).
|
|
64
|
+
|
|
65
|
+
**Manifest materialization (mandatory when no manifest path is given):** Build the manifest from the invocation text before anything else:
|
|
66
|
+
|
|
67
|
+
1. Extract `RFC-\d{4}`/`ADR-\d{4}` tokens in order of appearance; dedupe preserving first-seen order. The pasted order IS the dependency order — preserve it even when the surrounding prose discusses a different grouping.
|
|
68
|
+
2. Write `docs/queues/session-<YYYYMMDD>-<HHmm>.yaml`:
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
id: session-<YYYYMMDD>-<HHmm>
|
|
72
|
+
createdAt: <YYYY-MM-DD>
|
|
73
|
+
items:
|
|
74
|
+
- id: RFC-XXXX
|
|
75
|
+
- id: ADR-XXXX
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
The `id` field MUST equal the filename stem (`queue.validate` enforces this). Leave the file uncommitted — it is a session artifact; the operator may commit it for cross-session durability.
|
|
79
|
+
|
|
80
|
+
3. If the paste mixes ids with prose, ignore the prose — only the id sequence matters. Do not invent items that are not in the text.
|
|
81
|
+
|
|
82
|
+
**Pre-flight (mandatory):** Run `pnpm exec werkstatt run queue.validate --file <manifest> --json`. If the command exits non-zero (schema errors, unknown ids, duplicates, id↔filename mismatch), do NOT start the batch — report the errors and stop. When a pasted list references ids that do not resolve (QUEUE-04), name them explicitly — the operator must create those documents first or fix the list.
|
|
83
|
+
|
|
84
|
+
**Batch plan preview:** Emit the existing batch plan preview per `_shared/fo-pipeline-conventions.md` §Batch plan preview, listing items in manifest order.
|
|
85
|
+
|
|
86
|
+
**Loop semantics:** Process `items[]` in manifest order with no pauses between items:
|
|
87
|
+
|
|
88
|
+
1. **Skip terminal items** — items whose derived status is `implemented` or `skipped` (rejected/superseded frontmatter) are recorded in the batch summary and skipped.
|
|
89
|
+
2. **Resume mid-pipeline items** — an `in-progress` item resumes at its derived `pipelineStep`, not from step 1.
|
|
90
|
+
3. **Run the existing per-doc pipeline** — RFC items run audit → enhance → plan → implement; ADR items run implement only. All skill-internal interactions (grilling) stay inside the invoked skills.
|
|
91
|
+
4. **Batch-item checkpoint** — after each item, emit the context checkpoint per `_shared/fo-pipeline-conventions.md` §Context checkpoint between batch items.
|
|
92
|
+
5. **Clean-tree gate** — before starting the next item, verify the working tree has no uncommitted leftovers from the completed item; warn and stop if dirty.
|
|
93
|
+
|
|
94
|
+
**Failure:** If an item fails after its error checkpoint, stop the batch. The report names the blocked item id and the remaining item count. `blocked` is in-session report language only — never persist it into frontmatter, manifests, or files. Resume = re-invoke with the same manifest; `queue.validate` derives where to continue.
|
|
95
|
+
|
|
96
|
+
**Manifest immutability:** Do not edit `items[]` or the manifest during a queue run. Reordering requires stopping the batch and re-validating.
|
|
97
|
+
|
|
53
98
|
## Process
|
|
54
99
|
|
|
55
100
|
### 0. Idea creation (conditional)
|