@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline-status.js","sourceRoot":"","sources":["../../../../os/rfc/handlers/pipeline-status.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"pipeline-status.js","sourceRoot":"","sources":["../../../../os/rfc/handlers/pipeline-status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export type PipelineStage = "audit" | "enhance" | "plan" | "implement";
|
|
2
|
+
export interface RfcPipelineStageInfo {
|
|
3
|
+
stage: PipelineStage;
|
|
4
|
+
done: boolean;
|
|
5
|
+
/** File or marker that confirms completion, when applicable. */
|
|
6
|
+
evidence?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const TERMINAL_STATUSES: ReadonlySet<string>;
|
|
9
|
+
export interface ParsedDocument {
|
|
10
|
+
frontmatter: Record<string, unknown>;
|
|
11
|
+
body: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function parseFrontmatter(source: string): ParsedDocument;
|
|
14
|
+
export declare function findAuditFile(workspaceRoot: string, docId: string): Promise<string | undefined>;
|
|
15
|
+
export declare function findPlanFile(workspaceRoot: string, docId: string): Promise<string | undefined>;
|
|
16
|
+
export type QueueDocumentKind = "rfc" | "adr";
|
|
17
|
+
export interface ResolvedDocument {
|
|
18
|
+
id: string;
|
|
19
|
+
kind: QueueDocumentKind;
|
|
20
|
+
/** Workspace-relative path to the document file. */
|
|
21
|
+
file: string;
|
|
22
|
+
frontmatter: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve an RFC-XXXX or ADR-XXXX id to its document file and parsed
|
|
26
|
+
* frontmatter. Scans the active dir and archive subdirectories recursively.
|
|
27
|
+
* Returns undefined when the id is malformed or no file matches.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveDocument(workspaceRoot: string, docId: string): Promise<ResolvedDocument | undefined>;
|
|
30
|
+
export interface RfcStageInput {
|
|
31
|
+
status: string;
|
|
32
|
+
enhancedAt?: string;
|
|
33
|
+
implementedAt?: string;
|
|
34
|
+
auditFile?: string;
|
|
35
|
+
planFile?: string;
|
|
36
|
+
}
|
|
37
|
+
/** Compute the four pipeline stages for an RFC from frontmatter + artifacts. */
|
|
38
|
+
export declare function computeRfcPipelineStages(input: RfcStageInput): RfcPipelineStageInfo[];
|
|
39
|
+
/** First incomplete stage for a non-terminal RFC, or null when terminal. */
|
|
40
|
+
export declare function nextPipelineStep(status: string, stages: RfcPipelineStageInfo[]): PipelineStage | null;
|
|
41
|
+
export type QueueItemStatus = "pending" | "in-progress" | "implemented" | "skipped";
|
|
42
|
+
export interface QueueItemReport {
|
|
43
|
+
id: string;
|
|
44
|
+
status: QueueItemStatus;
|
|
45
|
+
/** First incomplete pipeline stage — present only when in-progress. */
|
|
46
|
+
pipelineStep?: PipelineStage;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Derive the queue status of a single document per the RFC-1140 matrix:
|
|
50
|
+
* implemented → implemented; rejected/superseded → skipped; RFC accepted or
|
|
51
|
+
* plan file → in-progress/implement; enhancedAt → in-progress/plan; audit
|
|
52
|
+
* file → in-progress/enhance; otherwise pending. ADR non-terminal → pending.
|
|
53
|
+
*/
|
|
54
|
+
export declare function deriveQueueItemStatus(workspaceRoot: string, docId: string): Promise<QueueItemReport | undefined>;
|
|
55
|
+
export interface QueueReport {
|
|
56
|
+
items: QueueItemReport[];
|
|
57
|
+
/** First item whose derived status is pending or in-progress; null when fully terminal. */
|
|
58
|
+
next: string | null;
|
|
59
|
+
}
|
|
60
|
+
/** Derive the full queue report, preserving manifest order. */
|
|
61
|
+
export declare function deriveQueueReport(workspaceRoot: string, itemIds: string[]): Promise<QueueReport>;
|
|
62
|
+
//# sourceMappingURL=pipeline-status.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline-status.d.ts","sourceRoot":"","sources":["../../src/pipeline-status.ts"],"names":[],"mappings":"AAmCA,MAAM,MAAM,aAAa,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,WAAW,CAAC;AAEvE,MAAM,WAAW,oBAAoB;IACnC,KAAK,EAAE,aAAa,CAAC;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,gEAAgE;IAChE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,eAAO,MAAM,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAIhD,CAAC;AAMH,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACrC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,cAAc,CAS/D;AAMD,wBAAsB,aAAa,CACjC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAU7B;AAED,wBAAsB,YAAY,CAChC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAU7B;AAMD,MAAM,MAAM,iBAAiB,GAAG,KAAK,GAAG,KAAK,CAAC;AAE9C,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,iBAAiB,CAAC;IACxB,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AA+BD;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,gBAAgB,GAAG,SAAS,CAAC,CAuBvC;AAMD,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,gFAAgF;AAChF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,aAAa,GAAG,oBAAoB,EAAE,CAerF;AAED,4EAA4E;AAC5E,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,oBAAoB,EAAE,GAC7B,aAAa,GAAG,IAAI,CAMtB;AAMD,MAAM,MAAM,eAAe,GAAG,SAAS,GAAG,aAAa,GAAG,aAAa,GAAG,SAAS,CAAC;AAEpF,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,eAAe,CAAC;IACxB,uEAAuE;IACvE,YAAY,CAAC,EAAE,aAAa,CAAC;CAC9B;AAED;;;;;GAKG;AACH,wBAAsB,qBAAqB,CACzC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,eAAe,GAAG,SAAS,CAAC,CAoDtC;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,eAAe,EAAE,CAAC;IACzB,2FAA2F;IAC3F,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB;AAED,+DAA+D;AAC/D,wBAAsB,iBAAiB,CACrC,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,MAAM,EAAE,GAChB,OAAO,CAAC,WAAW,CAAC,CAUtB"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Shared pipeline-status resolver (RFC-1140) — derives per-document
|
|
4
|
+
pipeline progress (audit, enhance, plan, implement) from document frontmatter
|
|
5
|
+
and pipeline artifacts. Single source of truth consumed by rfc.pipeline.status,
|
|
6
|
+
queue.validate, and the orchestrator's resume detection. Portable layer: no
|
|
7
|
+
os/ or kernel imports.</purpose>
|
|
8
|
+
<non-goals>
|
|
9
|
+
<item>Do not validate RFC/ADR content — use rfc.validate / adr.validate.</item>
|
|
10
|
+
<item>Do not mutate any files — derivation is read-only.</item>
|
|
11
|
+
<item>Do not store progress state — status is derived at read time, never persisted.</item>
|
|
12
|
+
</non-goals>
|
|
13
|
+
</MODULE_CONTRACT>
|
|
14
|
+
<CHANGE_SUMMARY>
|
|
15
|
+
<item>RFC-1140: initial shared resolver — artifact conventions (audit-*.md,
|
|
16
|
+
plan-*.md, enhancedAt, implementedAt) extracted from
|
|
17
|
+
os/rfc/handlers/pipeline-status.ts; QueueItemStatus derivation matrix added
|
|
18
|
+
for queue.validate.</item>
|
|
19
|
+
<item>RFC-1140: steps 1-4 — shared resolver, queue module, registration
|
|
20
|
+
|
|
21
|
+
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>
|
|
22
|
+
<item>RFC-1140: step 6 — cover shared resolver and queue.validate
|
|
23
|
+
|
|
24
|
+
Add pipeline-status.test.ts (derivation matrix, order, next selection, ADR, skipped) and queue-validate.test.ts (schema errors, unknown/dup ids, id-filename mismatch, empty items, dependsOn warning, JSON shape). Fix queue derivation: pipelineStep is the stage after the last completed stage, not first incomplete — handles non-contiguous artifacts.</item>
|
|
25
|
+
</CHANGE_SUMMARY>
|
|
26
|
+
*/
|
|
27
|
+
import fs from "node:fs/promises";
|
|
28
|
+
import path from "node:path";
|
|
29
|
+
import YAML from "yaml";
|
|
30
|
+
export const TERMINAL_STATUSES = new Set([
|
|
31
|
+
"implemented",
|
|
32
|
+
"rejected",
|
|
33
|
+
"superseded",
|
|
34
|
+
]);
|
|
35
|
+
export function parseFrontmatter(source) {
|
|
36
|
+
const match = source.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/);
|
|
37
|
+
if (!match) {
|
|
38
|
+
return { frontmatter: {}, body: source };
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
frontmatter: (YAML.parse(match[1]) ?? {}),
|
|
42
|
+
body: match[2] ?? "",
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
// ---------------------------------------------------------------------------
|
|
46
|
+
// Artifact finders — the canonical conventions
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
export async function findAuditFile(workspaceRoot, docId) {
|
|
49
|
+
const auditsDir = path.join(workspaceRoot, "docs/audits");
|
|
50
|
+
try {
|
|
51
|
+
const entries = await fs.readdir(auditsDir);
|
|
52
|
+
const prefix = `audit-${docId.toLowerCase()}`;
|
|
53
|
+
const match = entries.find((e) => e.toLowerCase().startsWith(prefix) && e.endsWith(".md"));
|
|
54
|
+
return match ? path.join("docs/audits", match) : undefined;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return undefined;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
export async function findPlanFile(workspaceRoot, docId) {
|
|
61
|
+
const plansDir = path.join(workspaceRoot, "docs/plans");
|
|
62
|
+
try {
|
|
63
|
+
const entries = await fs.readdir(plansDir);
|
|
64
|
+
const prefix = `plan-${docId.toLowerCase()}`;
|
|
65
|
+
const match = entries.find((e) => e.toLowerCase().startsWith(prefix) && e.endsWith(".md"));
|
|
66
|
+
return match ? path.join("docs/plans", match) : undefined;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
const DOC_ID_PATTERN = /^(RFC|ADR)-\d{4}$/i;
|
|
73
|
+
async function scanForDocument(dirPath, relativePrefix, idLower) {
|
|
74
|
+
let entries;
|
|
75
|
+
try {
|
|
76
|
+
entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
for (const entry of entries) {
|
|
82
|
+
const relativePath = relativePrefix ? `${relativePrefix}/${entry.name}` : entry.name;
|
|
83
|
+
if (entry.isDirectory()) {
|
|
84
|
+
const found = await scanForDocument(path.join(dirPath, entry.name), relativePath, idLower);
|
|
85
|
+
if (found)
|
|
86
|
+
return found;
|
|
87
|
+
}
|
|
88
|
+
else if (entry.isFile() &&
|
|
89
|
+
entry.name.endsWith(".md") &&
|
|
90
|
+
entry.name.toLowerCase().startsWith(idLower)) {
|
|
91
|
+
return relativePath;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Resolve an RFC-XXXX or ADR-XXXX id to its document file and parsed
|
|
98
|
+
* frontmatter. Scans the active dir and archive subdirectories recursively.
|
|
99
|
+
* Returns undefined when the id is malformed or no file matches.
|
|
100
|
+
*/
|
|
101
|
+
export async function resolveDocument(workspaceRoot, docId) {
|
|
102
|
+
const idMatch = DOC_ID_PATTERN.exec(docId.trim());
|
|
103
|
+
if (!idMatch)
|
|
104
|
+
return undefined;
|
|
105
|
+
const kind = idMatch[1].toUpperCase() === "ADR" ? "adr" : "rfc";
|
|
106
|
+
const baseDir = kind === "adr" ? "docs/adrs" : "docs/rfcs";
|
|
107
|
+
const idLower = docId.toLowerCase();
|
|
108
|
+
const found = await scanForDocument(path.join(workspaceRoot, baseDir), "", idLower);
|
|
109
|
+
if (!found)
|
|
110
|
+
return undefined;
|
|
111
|
+
let source;
|
|
112
|
+
try {
|
|
113
|
+
source = await fs.readFile(path.join(workspaceRoot, baseDir, found), "utf-8");
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return undefined;
|
|
117
|
+
}
|
|
118
|
+
const parsed = parseFrontmatter(source);
|
|
119
|
+
return {
|
|
120
|
+
id: docId.toUpperCase(),
|
|
121
|
+
kind,
|
|
122
|
+
file: path.join(baseDir, found),
|
|
123
|
+
frontmatter: parsed.frontmatter,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
/** Compute the four pipeline stages for an RFC from frontmatter + artifacts. */
|
|
127
|
+
export function computeRfcPipelineStages(input) {
|
|
128
|
+
return [
|
|
129
|
+
{ stage: "audit", done: !!input.auditFile, evidence: input.auditFile },
|
|
130
|
+
{
|
|
131
|
+
stage: "enhance",
|
|
132
|
+
done: !!input.enhancedAt,
|
|
133
|
+
evidence: input.enhancedAt ? `enhancedAt: ${input.enhancedAt}` : undefined,
|
|
134
|
+
},
|
|
135
|
+
{ stage: "plan", done: !!input.planFile, evidence: input.planFile },
|
|
136
|
+
{
|
|
137
|
+
stage: "implement",
|
|
138
|
+
done: input.status === "implemented" || !!input.implementedAt,
|
|
139
|
+
evidence: input.implementedAt ? `implementedAt: ${input.implementedAt}` : undefined,
|
|
140
|
+
},
|
|
141
|
+
];
|
|
142
|
+
}
|
|
143
|
+
/** First incomplete stage for a non-terminal RFC, or null when terminal. */
|
|
144
|
+
export function nextPipelineStep(status, stages) {
|
|
145
|
+
if (TERMINAL_STATUSES.has(status))
|
|
146
|
+
return null;
|
|
147
|
+
for (const s of stages) {
|
|
148
|
+
if (!s.done)
|
|
149
|
+
return s.stage;
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Derive the queue status of a single document per the RFC-1140 matrix:
|
|
155
|
+
* implemented → implemented; rejected/superseded → skipped; RFC accepted or
|
|
156
|
+
* plan file → in-progress/implement; enhancedAt → in-progress/plan; audit
|
|
157
|
+
* file → in-progress/enhance; otherwise pending. ADR non-terminal → pending.
|
|
158
|
+
*/
|
|
159
|
+
export async function deriveQueueItemStatus(workspaceRoot, docId) {
|
|
160
|
+
const doc = await resolveDocument(workspaceRoot, docId);
|
|
161
|
+
if (!doc)
|
|
162
|
+
return undefined;
|
|
163
|
+
const fm = doc.frontmatter;
|
|
164
|
+
const status = String(fm["status"] ?? "");
|
|
165
|
+
const implementedAt = fm["implementedAt"] ? String(fm["implementedAt"]) : undefined;
|
|
166
|
+
if (status === "implemented" || implementedAt) {
|
|
167
|
+
return { id: doc.id, status: "implemented" };
|
|
168
|
+
}
|
|
169
|
+
if (status === "rejected" || status === "superseded") {
|
|
170
|
+
return { id: doc.id, status: "skipped" };
|
|
171
|
+
}
|
|
172
|
+
// ADR pipeline is create → implement — no intermediate artifacts exist.
|
|
173
|
+
if (doc.kind === "adr") {
|
|
174
|
+
return { id: doc.id, status: "pending" };
|
|
175
|
+
}
|
|
176
|
+
// RFC accepted means the plan step is done by definition.
|
|
177
|
+
if (status === "accepted") {
|
|
178
|
+
return { id: doc.id, status: "in-progress", pipelineStep: "implement" };
|
|
179
|
+
}
|
|
180
|
+
const enhancedAt = fm["enhancedAt"] ? String(fm["enhancedAt"]) : undefined;
|
|
181
|
+
const auditFile = await findAuditFile(workspaceRoot, doc.id);
|
|
182
|
+
const planFile = await findPlanFile(workspaceRoot, doc.id);
|
|
183
|
+
const stages = computeRfcPipelineStages({
|
|
184
|
+
status,
|
|
185
|
+
enhancedAt,
|
|
186
|
+
implementedAt,
|
|
187
|
+
auditFile,
|
|
188
|
+
planFile,
|
|
189
|
+
});
|
|
190
|
+
// Queue semantics: the actionable step is the stage AFTER the last completed
|
|
191
|
+
// one — artifacts may be non-contiguous (e.g. enhancedAt without an audit
|
|
192
|
+
// file), where "first incomplete stage" would wrongly report audit.
|
|
193
|
+
let lastDone = -1;
|
|
194
|
+
for (let i = 0; i < stages.length; i++) {
|
|
195
|
+
if (stages[i].done)
|
|
196
|
+
lastDone = i;
|
|
197
|
+
}
|
|
198
|
+
if (lastDone === -1) {
|
|
199
|
+
return { id: doc.id, status: "pending" };
|
|
200
|
+
}
|
|
201
|
+
const nextStage = stages[lastDone + 1];
|
|
202
|
+
if (!nextStage) {
|
|
203
|
+
return { id: doc.id, status: "pending" };
|
|
204
|
+
}
|
|
205
|
+
return { id: doc.id, status: "in-progress", pipelineStep: nextStage.stage };
|
|
206
|
+
}
|
|
207
|
+
/** Derive the full queue report, preserving manifest order. */
|
|
208
|
+
export async function deriveQueueReport(workspaceRoot, itemIds) {
|
|
209
|
+
const items = [];
|
|
210
|
+
for (const id of itemIds) {
|
|
211
|
+
const report = await deriveQueueItemStatus(workspaceRoot, id);
|
|
212
|
+
// Unresolvable ids are reported by the manifest validator; the resolver
|
|
213
|
+
// skips them here so a partial report stays usable.
|
|
214
|
+
if (report)
|
|
215
|
+
items.push(report);
|
|
216
|
+
}
|
|
217
|
+
const nextItem = items.find((i) => i.status === "pending" || i.status === "in-progress");
|
|
218
|
+
return { items, next: nextItem?.id ?? null };
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=pipeline-status.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pipeline-status.js","sourceRoot":"","sources":["../../src/pipeline-status.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>queue.validate command handler (RFC-1140) — validates a queue
|
|
4
|
+
manifest and reports derived per-item pipeline status plus the next
|
|
5
|
+
actionable item. Read-only; the orchestrator runs this before a queued batch.</purpose>
|
|
6
|
+
<non-goals>
|
|
7
|
+
<item>Do not execute the queue — this command validates and previews only.</item>
|
|
8
|
+
<item>Do not mutate any files.</item>
|
|
9
|
+
</non-goals>
|
|
10
|
+
</MODULE_CONTRACT>
|
|
11
|
+
<CHANGE_SUMMARY>
|
|
12
|
+
<item>RFC-1140: initial queue.validate handler.</item>
|
|
13
|
+
<item>RFC-1140: steps 1-4 — shared resolver, queue module, registration
|
|
14
|
+
|
|
15
|
+
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>
|
|
16
|
+
</CHANGE_SUMMARY>
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
ForgeCommandInput,
|
|
21
|
+
ForgeCommandResult,
|
|
22
|
+
ForgeRuntimeContext,
|
|
23
|
+
} from "../../../src/types.ts";
|
|
24
|
+
import { deriveQueueReport } from "../../../src/pipeline-status.ts";
|
|
25
|
+
import { loadQueueManifest } from "../manifest.ts";
|
|
26
|
+
import type { QueueValidateResult } from "../types.ts";
|
|
27
|
+
|
|
28
|
+
export async function runQueueValidate(
|
|
29
|
+
input: ForgeCommandInput,
|
|
30
|
+
context: ForgeRuntimeContext,
|
|
31
|
+
): Promise<ForgeCommandResult<QueueValidateResult>> {
|
|
32
|
+
const { workspaceRoot, logger, outputFormat } = context;
|
|
33
|
+
|
|
34
|
+
const file = input.flags["file"] as string | undefined;
|
|
35
|
+
if (!file) {
|
|
36
|
+
throw new Error("queue.validate requires --file <path-to-manifest.yaml>");
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const { manifest, errors, warnings } = await loadQueueManifest(workspaceRoot, file);
|
|
40
|
+
|
|
41
|
+
const report =
|
|
42
|
+
errors.length === 0
|
|
43
|
+
? await deriveQueueReport(
|
|
44
|
+
workspaceRoot,
|
|
45
|
+
manifest.items.map((i) => i.id),
|
|
46
|
+
)
|
|
47
|
+
: { items: [], next: null };
|
|
48
|
+
|
|
49
|
+
const status = errors.length === 0 ? "pass" : "fail";
|
|
50
|
+
|
|
51
|
+
if (outputFormat === "pretty") {
|
|
52
|
+
logger.section(`Queue: ${manifest.id || file}`);
|
|
53
|
+
for (const item of report.items) {
|
|
54
|
+
const step = item.pipelineStep ? ` (${item.pipelineStep})` : "";
|
|
55
|
+
logger.info(` ${item.id}: ${item.status}${step}`);
|
|
56
|
+
}
|
|
57
|
+
logger.info(`next: ${report.next ?? "—"}`);
|
|
58
|
+
for (const w of warnings) {
|
|
59
|
+
logger.warn(` ${w.ruleId}: ${w.message}`);
|
|
60
|
+
}
|
|
61
|
+
for (const e of errors) {
|
|
62
|
+
logger.error(` ${e.ruleId}: ${e.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
data: {
|
|
68
|
+
command: "queue.validate",
|
|
69
|
+
status,
|
|
70
|
+
queue: manifest.id,
|
|
71
|
+
file,
|
|
72
|
+
items: report.items,
|
|
73
|
+
next: report.next,
|
|
74
|
+
errors,
|
|
75
|
+
warnings,
|
|
76
|
+
},
|
|
77
|
+
summary:
|
|
78
|
+
status === "pass"
|
|
79
|
+
? `Queue "${manifest.id}": ${report.items.length} item(s), next: ${report.next ?? "none"}`
|
|
80
|
+
: `Queue manifest invalid: ${errors.length} error(s)`,
|
|
81
|
+
exitCode: status === "pass" ? 0 : 1,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Barrel export for the forge queue module (RFC-1140).</purpose>
|
|
4
|
+
<non-goals>
|
|
5
|
+
<item>Do not add logic here — re-exports only.</item>
|
|
6
|
+
</non-goals>
|
|
7
|
+
</MODULE_CONTRACT>
|
|
8
|
+
<CHANGE_SUMMARY>
|
|
9
|
+
<item>RFC-1140: initial barrel export.</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
|
+
</CHANGE_SUMMARY>
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export { createForgeQueueModule } from "./queue.module.ts";
|
|
17
|
+
export { loadQueueManifest } from "./manifest.ts";
|
|
18
|
+
export { queueManifestSchema, QUEUE_ITEM_ID_PATTERN } from "./types.ts";
|
|
19
|
+
export type { QueueManifest, QueueValidateResult } from "./types.ts";
|
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
/*
|
|
2
|
+
<MODULE_CONTRACT>
|
|
3
|
+
<purpose>Queue manifest loading and validation (RFC-1140) — YAML parse, zod
|
|
4
|
+
schema, id↔filename stem check, item id pattern, document resolution across
|
|
5
|
+
active and archive dirs, duplicate detection, and dependsOn order warnings.</purpose>
|
|
6
|
+
<non-goals>
|
|
7
|
+
<item>Do not derive item status — that is src/pipeline-status.ts.</item>
|
|
8
|
+
<item>Do not mutate the manifest file.</item>
|
|
9
|
+
</non-goals>
|
|
10
|
+
</MODULE_CONTRACT>
|
|
11
|
+
<CHANGE_SUMMARY>
|
|
12
|
+
<item>RFC-1140: initial manifest loader and validator.</item>
|
|
13
|
+
<item>RFC-1140: steps 1-4 — shared resolver, queue module, registration
|
|
14
|
+
|
|
15
|
+
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>
|
|
16
|
+
</CHANGE_SUMMARY>
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import fs from "node:fs/promises";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import YAML from "yaml";
|
|
22
|
+
|
|
23
|
+
import type { Diagnostic } from "../../src/types.ts";
|
|
24
|
+
import { resolveDocument } from "../../src/pipeline-status.ts";
|
|
25
|
+
import { QUEUE_ITEM_ID_PATTERN, queueManifestSchema } from "./types.ts";
|
|
26
|
+
import type { QueueManifest } from "./types.ts";
|
|
27
|
+
|
|
28
|
+
export interface LoadedQueueManifest {
|
|
29
|
+
manifest: QueueManifest;
|
|
30
|
+
errors: Diagnostic[];
|
|
31
|
+
warnings: Diagnostic[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function diag(
|
|
35
|
+
ruleId: string,
|
|
36
|
+
severity: "error" | "warning",
|
|
37
|
+
message: string,
|
|
38
|
+
file?: string,
|
|
39
|
+
): Diagnostic {
|
|
40
|
+
return { ruleId, severity, message, file };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Load and validate a queue manifest. Blocking errors: unreadable/invalid
|
|
45
|
+
* YAML, schema violations, id↔filename mismatch, malformed or unresolvable
|
|
46
|
+
* item ids, duplicates. Non-blocking: dependsOn order violations.
|
|
47
|
+
*/
|
|
48
|
+
export async function loadQueueManifest(
|
|
49
|
+
workspaceRoot: string,
|
|
50
|
+
filePath: string,
|
|
51
|
+
): Promise<LoadedQueueManifest> {
|
|
52
|
+
const errors: Diagnostic[] = [];
|
|
53
|
+
const warnings: Diagnostic[] = [];
|
|
54
|
+
const absolutePath = path.isAbsolute(filePath)
|
|
55
|
+
? filePath
|
|
56
|
+
: path.join(workspaceRoot, filePath);
|
|
57
|
+
const displayPath = path.isAbsolute(filePath)
|
|
58
|
+
? path.relative(workspaceRoot, filePath)
|
|
59
|
+
: filePath;
|
|
60
|
+
|
|
61
|
+
let source: string;
|
|
62
|
+
try {
|
|
63
|
+
source = await fs.readFile(absolutePath, "utf-8");
|
|
64
|
+
} catch (e) {
|
|
65
|
+
errors.push(
|
|
66
|
+
diag(
|
|
67
|
+
"QUEUE-01",
|
|
68
|
+
"error",
|
|
69
|
+
`Cannot read queue manifest: ${e instanceof Error ? e.message : String(e)}`,
|
|
70
|
+
displayPath,
|
|
71
|
+
),
|
|
72
|
+
);
|
|
73
|
+
return { manifest: { id: "", createdAt: "", items: [] }, errors, warnings };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let raw: unknown;
|
|
77
|
+
try {
|
|
78
|
+
raw = YAML.parse(source);
|
|
79
|
+
} catch (e) {
|
|
80
|
+
errors.push(
|
|
81
|
+
diag(
|
|
82
|
+
"QUEUE-01",
|
|
83
|
+
"error",
|
|
84
|
+
`YAML parse error: ${e instanceof Error ? e.message : String(e)}`,
|
|
85
|
+
displayPath,
|
|
86
|
+
),
|
|
87
|
+
);
|
|
88
|
+
return { manifest: { id: "", createdAt: "", items: [] }, errors, warnings };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const parsed = queueManifestSchema.safeParse(raw);
|
|
92
|
+
if (!parsed.success) {
|
|
93
|
+
for (const issue of parsed.error.issues) {
|
|
94
|
+
errors.push(
|
|
95
|
+
diag(
|
|
96
|
+
"QUEUE-01",
|
|
97
|
+
"error",
|
|
98
|
+
`Manifest schema violation at ${issue.path.join(".") || "<root>"}: ${issue.message}`,
|
|
99
|
+
displayPath,
|
|
100
|
+
),
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
return { manifest: { id: "", createdAt: "", items: [] }, errors, warnings };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const manifest = parsed.data;
|
|
107
|
+
|
|
108
|
+
// QUEUE-02: manifest id must equal the filename stem.
|
|
109
|
+
const stem = path.basename(absolutePath).replace(/\.(ya?ml)$/i, "");
|
|
110
|
+
if (manifest.id !== stem) {
|
|
111
|
+
errors.push(
|
|
112
|
+
diag(
|
|
113
|
+
"QUEUE-02",
|
|
114
|
+
"error",
|
|
115
|
+
`Manifest id "${manifest.id}" does not match filename stem "${stem}"`,
|
|
116
|
+
displayPath,
|
|
117
|
+
),
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// QUEUE-03/04/05: item id pattern, resolution, duplicates.
|
|
122
|
+
const seen = new Set<string>();
|
|
123
|
+
const resolvedDependsOn = new Map<string, string[]>();
|
|
124
|
+
for (const item of manifest.items) {
|
|
125
|
+
const id = item.id.trim();
|
|
126
|
+
if (!QUEUE_ITEM_ID_PATTERN.test(id)) {
|
|
127
|
+
errors.push(
|
|
128
|
+
diag("QUEUE-03", "error", `Item id "${item.id}" does not match ^(RFC|ADR)-\\d{4}$`, displayPath),
|
|
129
|
+
);
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
const upper = id.toUpperCase();
|
|
133
|
+
if (seen.has(upper)) {
|
|
134
|
+
errors.push(diag("QUEUE-05", "error", `Duplicate item id "${upper}"`, displayPath));
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
seen.add(upper);
|
|
138
|
+
|
|
139
|
+
const doc = await resolveDocument(workspaceRoot, upper);
|
|
140
|
+
if (!doc) {
|
|
141
|
+
errors.push(
|
|
142
|
+
diag(
|
|
143
|
+
"QUEUE-04",
|
|
144
|
+
"error",
|
|
145
|
+
`Item "${upper}" does not resolve to a document in docs/rfcs/ or docs/adrs/ (incl. archive)`,
|
|
146
|
+
displayPath,
|
|
147
|
+
),
|
|
148
|
+
);
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const dependsOn = doc.frontmatter["dependsOn"];
|
|
153
|
+
if (Array.isArray(dependsOn)) {
|
|
154
|
+
resolvedDependsOn.set(
|
|
155
|
+
upper,
|
|
156
|
+
dependsOn.map((d) => String(d).toUpperCase()),
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// QUEUE-06: dependsOn order — an item must not precede its in-queue dependency.
|
|
162
|
+
const order = new Map<string, number>();
|
|
163
|
+
manifest.items.forEach((item, index) => order.set(item.id.trim().toUpperCase(), index));
|
|
164
|
+
for (const [itemId, deps] of resolvedDependsOn) {
|
|
165
|
+
const itemIndex = order.get(itemId);
|
|
166
|
+
if (itemIndex === undefined) continue;
|
|
167
|
+
for (const dep of deps) {
|
|
168
|
+
const depIndex = order.get(dep);
|
|
169
|
+
if (depIndex !== undefined && depIndex > itemIndex) {
|
|
170
|
+
warnings.push(
|
|
171
|
+
diag(
|
|
172
|
+
"QUEUE-06",
|
|
173
|
+
"warning",
|
|
174
|
+
`Item "${itemId}" (position ${itemIndex + 1}) precedes its dependsOn dependency "${dep}" (position ${depIndex + 1})`,
|
|
175
|
+
displayPath,
|
|
176
|
+
),
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return { manifest, errors, warnings };
|
|
183
|
+
}
|