@abhinav2203/codeflow-core 0.1.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/README.md +31 -0
- package/dist/analyzer/build.d.ts +2 -0
- package/dist/analyzer/build.js +90 -0
- package/dist/analyzer/index.d.ts +2 -0
- package/dist/analyzer/index.js +2 -0
- package/dist/analyzer/repo.d.ts +4 -0
- package/dist/analyzer/repo.js +451 -0
- package/dist/conflicts/index.d.ts +2 -0
- package/dist/conflicts/index.js +63 -0
- package/dist/export/index.d.ts +2 -0
- package/dist/export/index.js +398 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/internal/codegen.d.ts +5 -0
- package/dist/internal/codegen.js +224 -0
- package/dist/internal/phases.d.ts +17 -0
- package/dist/internal/phases.js +124 -0
- package/dist/internal/plan.d.ts +2 -0
- package/dist/internal/plan.js +67 -0
- package/dist/internal/prd.d.ts +9 -0
- package/dist/internal/prd.js +220 -0
- package/dist/internal/utils.d.ts +13 -0
- package/dist/internal/utils.js +103 -0
- package/dist/schema/index.d.ts +4448 -0
- package/dist/schema/index.js +720 -0
- package/dist/storage/store-paths.d.ts +10 -0
- package/dist/storage/store-paths.js +27 -0
- package/dist/store-paths.d.ts +1 -0
- package/dist/store-paths.js +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { generateNodeCode, getNodeStubPath, isCodeBearingNode } from "../internal/codegen";
|
|
4
|
+
import { createRunPlan } from "../internal/plan";
|
|
5
|
+
import { getCodeBearingNodes, getDefaultExecutionTarget } from "../internal/phases";
|
|
6
|
+
import { slugify } from "../internal/utils";
|
|
7
|
+
const ensureDir = async (dirPath) => {
|
|
8
|
+
await fs.mkdir(dirPath, { recursive: true });
|
|
9
|
+
};
|
|
10
|
+
const formatField = (field) => `${field.name}: ${field.type}${field.description ? ` - ${field.description}` : ""}`;
|
|
11
|
+
const formatList = (items) => (items.length ? items.map((item) => `- ${item}`).join("\n") : "- None");
|
|
12
|
+
const formatFields = (fields) => fields.length ? fields.map((field) => `- ${formatField(field)}`).join("\n") : "- None";
|
|
13
|
+
const layoutNodesForCanvas = (graph) => graph.nodes.map((node, index) => ({
|
|
14
|
+
id: node.id,
|
|
15
|
+
type: "text",
|
|
16
|
+
x: 80 + (index % 3) * 380,
|
|
17
|
+
y: 80 + Math.floor(index / 3) * 220,
|
|
18
|
+
width: 300,
|
|
19
|
+
height: 140,
|
|
20
|
+
text: `# ${node.name}\n\nKind: ${node.kind}\n${node.summary}`
|
|
21
|
+
}));
|
|
22
|
+
const buildCanvas = (graph) => ({
|
|
23
|
+
nodes: layoutNodesForCanvas(graph),
|
|
24
|
+
edges: graph.edges.map((edge) => ({
|
|
25
|
+
id: `${edge.kind}:${edge.from}:${edge.to}`,
|
|
26
|
+
fromNode: edge.from,
|
|
27
|
+
fromSide: "right",
|
|
28
|
+
toNode: edge.to,
|
|
29
|
+
toSide: "left",
|
|
30
|
+
label: edge.label ?? edge.kind
|
|
31
|
+
}))
|
|
32
|
+
});
|
|
33
|
+
const buildNodeDoc = (node) => {
|
|
34
|
+
const inputs = formatFields(node.contract.inputs);
|
|
35
|
+
const outputs = formatFields(node.contract.outputs);
|
|
36
|
+
const responsibilities = formatList(node.contract.responsibilities);
|
|
37
|
+
const attributes = formatFields(node.contract.attributes);
|
|
38
|
+
const dependencies = formatList(node.contract.dependencies);
|
|
39
|
+
const sideEffects = formatList(node.contract.sideEffects);
|
|
40
|
+
const errors = formatList(node.contract.errors);
|
|
41
|
+
const calls = node.contract.calls.length
|
|
42
|
+
? node.contract.calls
|
|
43
|
+
.map((call) => `- ${call.target}${call.kind ? ` [${call.kind}]` : ""}${call.description ? ` - ${call.description}` : ""}`)
|
|
44
|
+
.join("\n")
|
|
45
|
+
: "- None";
|
|
46
|
+
const methods = node.contract.methods.length
|
|
47
|
+
? node.contract.methods
|
|
48
|
+
.map((method) => {
|
|
49
|
+
const methodCalls = method.calls.length
|
|
50
|
+
? method.calls
|
|
51
|
+
.map((call) => `${call.target}${call.kind ? ` [${call.kind}]` : ""}${call.description ? ` - ${call.description}` : ""}`)
|
|
52
|
+
.join("; ")
|
|
53
|
+
: "None";
|
|
54
|
+
return [
|
|
55
|
+
`- ${method.name}`,
|
|
56
|
+
` Signature: ${method.signature ?? "N/A"}`,
|
|
57
|
+
` Summary: ${method.summary}`,
|
|
58
|
+
` Inputs: ${method.inputs.length ? method.inputs.map(formatField).join("; ") : "None"}`,
|
|
59
|
+
` Outputs: ${method.outputs.length ? method.outputs.map(formatField).join("; ") : "None"}`,
|
|
60
|
+
` Side effects: ${method.sideEffects.length ? method.sideEffects.join("; ") : "None"}`,
|
|
61
|
+
` Calls: ${methodCalls}`
|
|
62
|
+
].join("\n");
|
|
63
|
+
})
|
|
64
|
+
.join("\n")
|
|
65
|
+
: "- None";
|
|
66
|
+
const notes = formatList(node.contract.notes);
|
|
67
|
+
const stubLink = node.kind === "module" ? "N/A" : `stubs/${slugify(node.kind)}-${slugify(node.name)}.${node.kind === "ui-screen" ? "tsx" : "ts"}`;
|
|
68
|
+
return `# ${node.name}
|
|
69
|
+
|
|
70
|
+
Kind: ${node.kind}
|
|
71
|
+
|
|
72
|
+
Summary:
|
|
73
|
+
${node.summary}
|
|
74
|
+
|
|
75
|
+
Responsibilities:
|
|
76
|
+
${responsibilities}
|
|
77
|
+
|
|
78
|
+
Signature:
|
|
79
|
+
${node.signature ?? "N/A"}
|
|
80
|
+
|
|
81
|
+
Inputs:
|
|
82
|
+
${inputs}
|
|
83
|
+
|
|
84
|
+
Outputs:
|
|
85
|
+
${outputs}
|
|
86
|
+
|
|
87
|
+
Attributes / State:
|
|
88
|
+
${attributes}
|
|
89
|
+
|
|
90
|
+
Methods:
|
|
91
|
+
${methods}
|
|
92
|
+
|
|
93
|
+
Dependencies:
|
|
94
|
+
${dependencies}
|
|
95
|
+
|
|
96
|
+
Calls:
|
|
97
|
+
${calls}
|
|
98
|
+
|
|
99
|
+
Side effects:
|
|
100
|
+
${sideEffects}
|
|
101
|
+
|
|
102
|
+
Errors:
|
|
103
|
+
${errors}
|
|
104
|
+
|
|
105
|
+
Notes:
|
|
106
|
+
${notes}
|
|
107
|
+
|
|
108
|
+
Obsidian:
|
|
109
|
+
- [[index]]
|
|
110
|
+
- Stub path: ${stubLink}
|
|
111
|
+
- Phase status: ${node.status ?? "spec_only"}
|
|
112
|
+
`;
|
|
113
|
+
};
|
|
114
|
+
const buildPhaseManifest = (graph) => ({
|
|
115
|
+
phase: graph.phase ?? "spec",
|
|
116
|
+
exportedAt: new Date().toISOString(),
|
|
117
|
+
nodes: graph.nodes.map((node) => ({
|
|
118
|
+
id: node.id,
|
|
119
|
+
name: node.name,
|
|
120
|
+
kind: node.kind,
|
|
121
|
+
status: node.status ?? "spec_only",
|
|
122
|
+
specPath: getNodeStubPath(node),
|
|
123
|
+
hasSpecDraft: Boolean(node.specDraft),
|
|
124
|
+
hasImplementationDraft: Boolean(node.implementationDraft),
|
|
125
|
+
hasVerification: Boolean(node.lastVerification)
|
|
126
|
+
}))
|
|
127
|
+
});
|
|
128
|
+
const buildIntegrationEntrypoint = (graph) => {
|
|
129
|
+
if (graph.phase !== "integration") {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
const targetNode = getDefaultExecutionTarget(graph);
|
|
133
|
+
const targetPath = targetNode ? getNodeStubPath(targetNode) : null;
|
|
134
|
+
if (!targetNode || !targetPath) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const rootExport = targetNode.kind === "ui-screen" ? "default" : undefined;
|
|
138
|
+
const codeBearingImports = getCodeBearingNodes(graph)
|
|
139
|
+
.map((node, index) => {
|
|
140
|
+
const stubPath = getNodeStubPath(node);
|
|
141
|
+
if (!stubPath) {
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
binding: node.id === targetNode.id ? "integrationRoot" : `integrationNode${index + 1}`,
|
|
146
|
+
importPath: `./${stubPath.replace(/\.(ts|tsx)$/, "")}`
|
|
147
|
+
};
|
|
148
|
+
})
|
|
149
|
+
.filter((entry) => Boolean(entry));
|
|
150
|
+
const rootNamedExport = rootExport ??
|
|
151
|
+
targetNode.name
|
|
152
|
+
.split(".")
|
|
153
|
+
.pop()
|
|
154
|
+
?.replace(/[^A-Za-z0-9_$]+/g, " ")
|
|
155
|
+
.trim()
|
|
156
|
+
.replace(/(?:^\w|[A-Z]|\b\w)/g, (chunk, index) => (index === 0 ? chunk.toLowerCase() : chunk.toUpperCase()))
|
|
157
|
+
.replace(/\s+/g, "");
|
|
158
|
+
if (!rootNamedExport) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
return [
|
|
162
|
+
`import * as integrationRoot from "${codeBearingImports.find((entry) => entry.binding === "integrationRoot")?.importPath ?? `./${targetPath.replace(/\.(ts|tsx)$/, "")}`}";`,
|
|
163
|
+
...codeBearingImports
|
|
164
|
+
.filter((entry) => entry.binding !== "integrationRoot")
|
|
165
|
+
.map((entry) => `import * as ${entry.binding} from "${entry.importPath}";\nvoid ${entry.binding};`),
|
|
166
|
+
"",
|
|
167
|
+
"export async function runIntegration(input: unknown) {",
|
|
168
|
+
` const runner = integrationRoot["${rootNamedExport}"] as ((...args: any[]) => any) | undefined;`,
|
|
169
|
+
' if (typeof runner !== "function") {',
|
|
170
|
+
' throw new Error("Integration root export is not callable.");',
|
|
171
|
+
" }",
|
|
172
|
+
" const args = Array.isArray(input) ? input : [input];",
|
|
173
|
+
" return runner(...args);",
|
|
174
|
+
"}"
|
|
175
|
+
].join("\n");
|
|
176
|
+
};
|
|
177
|
+
const classifyNodeArtifact = (node, graph, draftOverride) => {
|
|
178
|
+
if (draftOverride) {
|
|
179
|
+
const isValidated = node.lastVerification?.status === "success";
|
|
180
|
+
return {
|
|
181
|
+
content: draftOverride,
|
|
182
|
+
validationState: isValidated ? "validated" : "draft",
|
|
183
|
+
maturity: isValidated ? "production" : "preview",
|
|
184
|
+
provenance: "deterministic",
|
|
185
|
+
notes: ["Using the current editor draft captured at export time."]
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
if (node.implementationDraft) {
|
|
189
|
+
const isValidated = node.lastVerification?.status === "success";
|
|
190
|
+
return {
|
|
191
|
+
content: node.implementationDraft,
|
|
192
|
+
validationState: isValidated ? "validated" : "draft",
|
|
193
|
+
maturity: isValidated ? "production" : "preview",
|
|
194
|
+
provenance: "deterministic",
|
|
195
|
+
notes: ["Using the node implementation draft stored in the blueprint."]
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
if (node.specDraft) {
|
|
199
|
+
return {
|
|
200
|
+
content: node.specDraft,
|
|
201
|
+
validationState: "draft",
|
|
202
|
+
maturity: "preview",
|
|
203
|
+
provenance: "deterministic",
|
|
204
|
+
notes: ["Using the node specification draft because no implementation draft exists."]
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
content: generateNodeCode(node, graph),
|
|
209
|
+
validationState: "scaffold",
|
|
210
|
+
maturity: "scaffold",
|
|
211
|
+
provenance: "deterministic",
|
|
212
|
+
notes: ["Generated deterministic scaffold content from the blueprint contract."]
|
|
213
|
+
};
|
|
214
|
+
};
|
|
215
|
+
export const exportBlueprintArtifacts = async (graph, outputDir, executionReport, codeDrafts) => {
|
|
216
|
+
const runPlan = createRunPlan(graph);
|
|
217
|
+
const baseDir = outputDir && outputDir.trim()
|
|
218
|
+
? path.resolve(outputDir)
|
|
219
|
+
: path.resolve(process.cwd(), "artifacts", slugify(graph.projectName));
|
|
220
|
+
const docsDir = path.join(baseDir, "docs");
|
|
221
|
+
const stubsDir = path.join(baseDir, "stubs");
|
|
222
|
+
const blueprintPath = path.join(baseDir, "blueprint.json");
|
|
223
|
+
const canvasPath = path.join(baseDir, "system.canvas");
|
|
224
|
+
const ownershipPath = path.join(baseDir, "ownership.json");
|
|
225
|
+
const obsidianIndexPath = path.join(baseDir, "obsidian-index.md");
|
|
226
|
+
const phaseManifestPath = path.join(baseDir, "phase-manifest.json");
|
|
227
|
+
const integrationEntrypointPath = path.join(baseDir, "integration-entrypoint.ts");
|
|
228
|
+
const artifactManifestPath = path.join(baseDir, "artifact-manifest.json");
|
|
229
|
+
const exportedAt = new Date().toISOString();
|
|
230
|
+
const artifacts = [];
|
|
231
|
+
await ensureDir(docsDir);
|
|
232
|
+
await ensureDir(stubsDir);
|
|
233
|
+
await fs.writeFile(blueprintPath, `${JSON.stringify(graph, null, 2)}\n`, "utf8");
|
|
234
|
+
await fs.writeFile(canvasPath, `${JSON.stringify(buildCanvas(graph), null, 2)}\n`, "utf8");
|
|
235
|
+
await fs.writeFile(phaseManifestPath, `${JSON.stringify(buildPhaseManifest(graph), null, 2)}\n`, "utf8");
|
|
236
|
+
artifacts.push({
|
|
237
|
+
relativePath: path.relative(baseDir, blueprintPath),
|
|
238
|
+
artifactType: "blueprint",
|
|
239
|
+
validationState: "validated",
|
|
240
|
+
provenance: "deterministic",
|
|
241
|
+
maturity: "production",
|
|
242
|
+
generatedAt: exportedAt,
|
|
243
|
+
notes: ["Serialized blueprint graph."]
|
|
244
|
+
});
|
|
245
|
+
artifacts.push({
|
|
246
|
+
relativePath: path.relative(baseDir, canvasPath),
|
|
247
|
+
artifactType: "canvas",
|
|
248
|
+
validationState: "validated",
|
|
249
|
+
provenance: "deterministic",
|
|
250
|
+
maturity: "production",
|
|
251
|
+
generatedAt: exportedAt,
|
|
252
|
+
notes: ["Canvas projection of the blueprint graph."]
|
|
253
|
+
});
|
|
254
|
+
const summaryDoc = `# ${graph.projectName}
|
|
255
|
+
|
|
256
|
+
Generated at: ${graph.generatedAt}
|
|
257
|
+
Mode: ${graph.mode}
|
|
258
|
+
Current phase: ${graph.phase ?? "spec"}
|
|
259
|
+
|
|
260
|
+
Nodes:
|
|
261
|
+
${graph.nodes.map((node) => `- ${node.kind}: ${node.name}`).join("\n")}
|
|
262
|
+
|
|
263
|
+
Workflows:
|
|
264
|
+
${graph.workflows.length ? graph.workflows.map((workflow) => `- ${workflow.name}: ${workflow.steps.join(" -> ")}`).join("\n") : "- None"}
|
|
265
|
+
|
|
266
|
+
Execution phases:
|
|
267
|
+
${runPlan.batches.length
|
|
268
|
+
? runPlan.batches
|
|
269
|
+
.map((batch) => {
|
|
270
|
+
const taskTitles = batch.taskIds
|
|
271
|
+
.map((taskId) => runPlan.tasks.find((task) => task.id === taskId)?.title)
|
|
272
|
+
.filter(Boolean)
|
|
273
|
+
.join("; ");
|
|
274
|
+
return `- Phase ${batch.index + 1}: ${taskTitles || "No tasks"}`;
|
|
275
|
+
})
|
|
276
|
+
.join("\n")
|
|
277
|
+
: "- None"}
|
|
278
|
+
`;
|
|
279
|
+
await fs.writeFile(path.join(docsDir, "index.md"), summaryDoc, "utf8");
|
|
280
|
+
artifacts.push({
|
|
281
|
+
relativePath: path.relative(baseDir, path.join(docsDir, "index.md")),
|
|
282
|
+
artifactType: "documentation",
|
|
283
|
+
validationState: "validated",
|
|
284
|
+
provenance: "deterministic",
|
|
285
|
+
maturity: "production",
|
|
286
|
+
generatedAt: exportedAt,
|
|
287
|
+
notes: ["Top-level export summary."]
|
|
288
|
+
});
|
|
289
|
+
await fs.writeFile(obsidianIndexPath, `# ${graph.projectName} Vault Index
|
|
290
|
+
|
|
291
|
+
## Core Links
|
|
292
|
+
- [[docs/index]]
|
|
293
|
+
- [[system.canvas]]
|
|
294
|
+
|
|
295
|
+
## Nodes
|
|
296
|
+
${graph.nodes.map((node) => `- [[docs/${slugify(node.kind)}-${slugify(node.name)}]]`).join("\n")}
|
|
297
|
+
`, "utf8");
|
|
298
|
+
artifacts.push({
|
|
299
|
+
relativePath: path.relative(baseDir, obsidianIndexPath),
|
|
300
|
+
artifactType: "documentation",
|
|
301
|
+
validationState: "validated",
|
|
302
|
+
provenance: "deterministic",
|
|
303
|
+
maturity: "production",
|
|
304
|
+
generatedAt: exportedAt,
|
|
305
|
+
notes: ["Obsidian index for exported documentation."]
|
|
306
|
+
});
|
|
307
|
+
for (const node of graph.nodes) {
|
|
308
|
+
const docPath = path.join(docsDir, `${slugify(node.kind)}-${slugify(node.name)}.md`);
|
|
309
|
+
await fs.writeFile(docPath, buildNodeDoc(node), "utf8");
|
|
310
|
+
artifacts.push({
|
|
311
|
+
nodeId: node.id,
|
|
312
|
+
nodeName: node.name,
|
|
313
|
+
nodeKind: node.kind,
|
|
314
|
+
relativePath: path.relative(baseDir, docPath),
|
|
315
|
+
artifactType: "documentation",
|
|
316
|
+
validationState: "validated",
|
|
317
|
+
provenance: "deterministic",
|
|
318
|
+
maturity: "production",
|
|
319
|
+
generatedAt: exportedAt,
|
|
320
|
+
notes: ["Per-node architecture documentation."]
|
|
321
|
+
});
|
|
322
|
+
if (!isCodeBearingNode(node)) {
|
|
323
|
+
continue;
|
|
324
|
+
}
|
|
325
|
+
const artifact = classifyNodeArtifact(node, graph, codeDrafts?.[node.id]);
|
|
326
|
+
const stubContent = artifact.content;
|
|
327
|
+
if (!stubContent) {
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
const extension = node.kind === "ui-screen" ? "tsx" : "ts";
|
|
331
|
+
const stubPath = path.join(stubsDir, `${slugify(node.kind)}-${slugify(node.name)}.${extension}`);
|
|
332
|
+
await fs.writeFile(stubPath, stubContent, "utf8");
|
|
333
|
+
artifacts.push({
|
|
334
|
+
nodeId: node.id,
|
|
335
|
+
nodeName: node.name,
|
|
336
|
+
nodeKind: node.kind,
|
|
337
|
+
relativePath: path.relative(baseDir, stubPath),
|
|
338
|
+
artifactType: "code",
|
|
339
|
+
validationState: artifact.validationState,
|
|
340
|
+
provenance: artifact.provenance,
|
|
341
|
+
maturity: artifact.maturity,
|
|
342
|
+
generatedAt: exportedAt,
|
|
343
|
+
notes: artifact.notes
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
if (executionReport) {
|
|
347
|
+
await fs.writeFile(ownershipPath, `${JSON.stringify(executionReport.ownership, null, 2)}\n`, "utf8");
|
|
348
|
+
artifacts.push({
|
|
349
|
+
relativePath: path.relative(baseDir, ownershipPath),
|
|
350
|
+
artifactType: "ownership",
|
|
351
|
+
validationState: "validated",
|
|
352
|
+
provenance: "deterministic",
|
|
353
|
+
maturity: "production",
|
|
354
|
+
generatedAt: exportedAt,
|
|
355
|
+
notes: ["Ownership records for managed regions."]
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
const integrationEntrypoint = buildIntegrationEntrypoint(graph);
|
|
359
|
+
if (integrationEntrypoint) {
|
|
360
|
+
await fs.writeFile(integrationEntrypointPath, integrationEntrypoint, "utf8");
|
|
361
|
+
artifacts.push({
|
|
362
|
+
relativePath: path.relative(baseDir, integrationEntrypointPath),
|
|
363
|
+
artifactType: "integration",
|
|
364
|
+
validationState: "draft",
|
|
365
|
+
provenance: "deterministic",
|
|
366
|
+
maturity: "preview",
|
|
367
|
+
generatedAt: exportedAt,
|
|
368
|
+
notes: ["Generated integration runner entrypoint."]
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
await fs.writeFile(artifactManifestPath, `${JSON.stringify({ exportedAt, artifacts }, null, 2)}\n`, "utf8");
|
|
372
|
+
const artifactSummary = artifacts.reduce((summary, artifact) => {
|
|
373
|
+
summary.total += 1;
|
|
374
|
+
if (artifact.validationState === "validated") {
|
|
375
|
+
summary.validated += 1;
|
|
376
|
+
}
|
|
377
|
+
else if (artifact.validationState === "draft") {
|
|
378
|
+
summary.draft += 1;
|
|
379
|
+
}
|
|
380
|
+
else {
|
|
381
|
+
summary.scaffold += 1;
|
|
382
|
+
}
|
|
383
|
+
return summary;
|
|
384
|
+
}, { total: 0, validated: 0, draft: 0, scaffold: 0 });
|
|
385
|
+
return {
|
|
386
|
+
rootDir: baseDir,
|
|
387
|
+
blueprintPath,
|
|
388
|
+
canvasPath,
|
|
389
|
+
docsDir,
|
|
390
|
+
stubsDir,
|
|
391
|
+
artifactManifestPath,
|
|
392
|
+
artifactSummary,
|
|
393
|
+
phaseManifestPath,
|
|
394
|
+
integrationEntrypointPath: integrationEntrypoint ? integrationEntrypointPath : undefined,
|
|
395
|
+
ownershipPath: executionReport ? ownershipPath : undefined,
|
|
396
|
+
obsidianIndexPath
|
|
397
|
+
};
|
|
398
|
+
};
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { BlueprintGraph, BlueprintNode } from "../schema/index";
|
|
2
|
+
export declare const isCodeBearingNode: (node: BlueprintNode) => boolean;
|
|
3
|
+
export declare const getNodeStubPath: (node: BlueprintNode) => string | null;
|
|
4
|
+
export declare const getNodeRuntimeExport: (node: BlueprintNode) => string | null;
|
|
5
|
+
export declare const generateNodeCode: (node: BlueprintNode, graph: BlueprintGraph) => string | null;
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { emptyContract } from "../schema/index";
|
|
2
|
+
export const isCodeBearingNode = (node) => node.kind !== "module";
|
|
3
|
+
const normalizeContract = (contract) => ({
|
|
4
|
+
...emptyContract(),
|
|
5
|
+
...contract
|
|
6
|
+
});
|
|
7
|
+
const sanitizeIdentifier = (value) => {
|
|
8
|
+
const cleaned = value
|
|
9
|
+
.replace(/[^A-Za-z0-9_$]+/g, " ")
|
|
10
|
+
.trim()
|
|
11
|
+
.replace(/(?:^\w|[A-Z]|\b\w)/g, (chunk, index) => index === 0 ? chunk.toLowerCase() : chunk.toUpperCase())
|
|
12
|
+
.replace(/\s+/g, "");
|
|
13
|
+
return cleaned || "generatedNode";
|
|
14
|
+
};
|
|
15
|
+
const toPascalCase = (value) => {
|
|
16
|
+
const camel = sanitizeIdentifier(value);
|
|
17
|
+
return camel.charAt(0).toUpperCase() + camel.slice(1);
|
|
18
|
+
};
|
|
19
|
+
export const getNodeStubPath = (node) => {
|
|
20
|
+
if (!isCodeBearingNode(node)) {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
const extension = node.kind === "ui-screen" ? "tsx" : "ts";
|
|
24
|
+
return `stubs/${node.kind.replace(/[^A-Za-z0-9]+/g, "-").toLowerCase()}-${node.name
|
|
25
|
+
.replace(/[^A-Za-z0-9]+/g, "-")
|
|
26
|
+
.toLowerCase()}.${extension}`;
|
|
27
|
+
};
|
|
28
|
+
export const getNodeRuntimeExport = (node) => {
|
|
29
|
+
if (node.kind === "function" || node.kind === "api") {
|
|
30
|
+
return sanitizeIdentifier(node.name.split(".").pop() ?? node.name);
|
|
31
|
+
}
|
|
32
|
+
if (node.kind === "class") {
|
|
33
|
+
return toPascalCase(node.name);
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
};
|
|
37
|
+
const formatField = (field) => `${field.name}: ${field.type}${field.description ? ` - ${field.description}` : ""}`;
|
|
38
|
+
const formatCommentSection = (title, lines) => lines.length ? [` * ${title}:`, ...lines.map((line) => ` * - ${line}`)] : [];
|
|
39
|
+
const buildDocComment = (node) => {
|
|
40
|
+
const contract = normalizeContract(node.contract);
|
|
41
|
+
const lines = [
|
|
42
|
+
"/**",
|
|
43
|
+
` * ${node.summary}`,
|
|
44
|
+
" * @codeflowMaturity scaffold",
|
|
45
|
+
" * @codeflowValidation scaffold",
|
|
46
|
+
...formatCommentSection("Responsibilities", contract.responsibilities),
|
|
47
|
+
...formatCommentSection("Inputs", contract.inputs.map(formatField)),
|
|
48
|
+
...formatCommentSection("Outputs", contract.outputs.map(formatField)),
|
|
49
|
+
...formatCommentSection("Calls", contract.calls.map((call) => `${call.target}${call.kind ? ` [${call.kind}]` : ""}${call.description ? ` - ${call.description}` : ""}`)),
|
|
50
|
+
...formatCommentSection("Errors", contract.errors),
|
|
51
|
+
` * @blueprintId ${node.id}`,
|
|
52
|
+
" */"
|
|
53
|
+
];
|
|
54
|
+
return `${lines.join("\n")}\n`;
|
|
55
|
+
};
|
|
56
|
+
const unwrapPromiseType = (value) => {
|
|
57
|
+
const match = value.trim().match(/^Promise<(.+)>$/);
|
|
58
|
+
return match ? match[1].trim() : null;
|
|
59
|
+
};
|
|
60
|
+
const buildReturnExpression = (returnType) => {
|
|
61
|
+
const normalizedType = returnType.trim();
|
|
62
|
+
if (!normalizedType || normalizedType === "void") {
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
const promisedType = unwrapPromiseType(normalizedType);
|
|
66
|
+
if (promisedType) {
|
|
67
|
+
const innerExpression = buildReturnExpression(promisedType) ?? "undefined";
|
|
68
|
+
return `Promise.resolve(${innerExpression})`;
|
|
69
|
+
}
|
|
70
|
+
if (normalizedType === "string") {
|
|
71
|
+
return '""';
|
|
72
|
+
}
|
|
73
|
+
if (normalizedType === "number" || normalizedType === "bigint") {
|
|
74
|
+
return "0";
|
|
75
|
+
}
|
|
76
|
+
if (normalizedType === "boolean") {
|
|
77
|
+
return "false";
|
|
78
|
+
}
|
|
79
|
+
if (normalizedType === "null") {
|
|
80
|
+
return "null";
|
|
81
|
+
}
|
|
82
|
+
if (normalizedType === "unknown" || normalizedType === "any") {
|
|
83
|
+
return "undefined";
|
|
84
|
+
}
|
|
85
|
+
if (normalizedType.startsWith("Array<") ||
|
|
86
|
+
normalizedType.startsWith("ReadonlyArray<") ||
|
|
87
|
+
normalizedType.endsWith("[]")) {
|
|
88
|
+
return `[] as ${normalizedType}`;
|
|
89
|
+
}
|
|
90
|
+
return `undefined as unknown as ${normalizedType}`;
|
|
91
|
+
};
|
|
92
|
+
const buildScaffoldNotice = (node) => {
|
|
93
|
+
const notes = [
|
|
94
|
+
`const scaffoldStatus = "CodeFlow scaffold for ${node.id}"`,
|
|
95
|
+
"console.warn(scaffoldStatus)"
|
|
96
|
+
];
|
|
97
|
+
return notes.map((line) => ` ${line};`);
|
|
98
|
+
};
|
|
99
|
+
const buildChecklistComment = (node) => {
|
|
100
|
+
const contract = normalizeContract(node.contract);
|
|
101
|
+
return [
|
|
102
|
+
...contract.responsibilities.map((item) => `// TODO: ${item}`),
|
|
103
|
+
...contract.calls.map((call) => `// TODO: integrate ${call.target}${call.description ? ` (${call.description})` : ""}`),
|
|
104
|
+
...contract.errors.map((error) => `// TODO: handle ${error}`)
|
|
105
|
+
];
|
|
106
|
+
};
|
|
107
|
+
const buildFunctionCode = (node) => {
|
|
108
|
+
const contract = normalizeContract(node.contract);
|
|
109
|
+
const functionName = sanitizeIdentifier(node.name.split(".").pop() ?? node.name);
|
|
110
|
+
const inputList = contract.inputs
|
|
111
|
+
.map((field) => `${sanitizeIdentifier(field.name)}: ${field.type || "unknown"}`)
|
|
112
|
+
.join(", ");
|
|
113
|
+
const returnType = contract.outputs[0]?.type || "void";
|
|
114
|
+
const checklist = buildChecklistComment(node);
|
|
115
|
+
const scaffoldNotice = buildScaffoldNotice(node);
|
|
116
|
+
return `${buildDocComment(node)}export function ${functionName}(${inputList}): ${returnType} {
|
|
117
|
+
${scaffoldNotice.join("\n")}
|
|
118
|
+
${checklist.length ? ` ${checklist.join("\n ")}\n` : ""} throw new Error("CodeFlow scaffold: implementation required for ${node.id}");
|
|
119
|
+
}
|
|
120
|
+
`;
|
|
121
|
+
};
|
|
122
|
+
const buildApiCode = (node) => {
|
|
123
|
+
const functionName = sanitizeIdentifier(node.name.replace(/\s+/g, " "));
|
|
124
|
+
const checklist = buildChecklistComment(node);
|
|
125
|
+
return `${buildDocComment(node)}export async function ${functionName}(request: Request): Promise<Response> {
|
|
126
|
+
const body = await request.json().catch(() => null);
|
|
127
|
+
${buildScaffoldNotice(node).join("\n")}
|
|
128
|
+
${checklist.length ? ` ${checklist.join("\n ")}\n` : ""} return Response.json(
|
|
129
|
+
{
|
|
130
|
+
ok: false,
|
|
131
|
+
blueprintId: "${node.id}",
|
|
132
|
+
route: "${node.name}",
|
|
133
|
+
maturity: "scaffold",
|
|
134
|
+
received: body
|
|
135
|
+
},
|
|
136
|
+
{ status: 501 }
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
`;
|
|
140
|
+
};
|
|
141
|
+
const buildUiScreenCode = (node) => {
|
|
142
|
+
const contract = normalizeContract(node.contract);
|
|
143
|
+
const componentName = toPascalCase(node.name);
|
|
144
|
+
const attributeNotes = contract.attributes.length
|
|
145
|
+
? contract.attributes.map((attribute) => ` <li>${attribute.name}: ${attribute.type}</li>`).join("\n")
|
|
146
|
+
: ' <li>No state model defined yet.</li>';
|
|
147
|
+
const responsibilityNotes = contract.responsibilities.length
|
|
148
|
+
? contract.responsibilities.map((item) => ` <li>${item}</li>`).join("\n")
|
|
149
|
+
: " <li>Implementation responsibilities will appear here once the screen is wired.</li>";
|
|
150
|
+
return `${buildDocComment(node)}export default function ${componentName}(): JSX.Element {
|
|
151
|
+
return (
|
|
152
|
+
<main data-codeflow-maturity="scaffold">
|
|
153
|
+
<h1>${node.name}</h1>
|
|
154
|
+
<p>${node.summary}</p>
|
|
155
|
+
<p>This screen is a scaffold artifact. Replace the placeholder structure with real UI before shipping.</p>
|
|
156
|
+
<section>
|
|
157
|
+
<h2>Responsibilities</h2>
|
|
158
|
+
<ul>
|
|
159
|
+
${responsibilityNotes}
|
|
160
|
+
</ul>
|
|
161
|
+
</section>
|
|
162
|
+
<section>
|
|
163
|
+
<h2>State / attributes</h2>
|
|
164
|
+
<ul>
|
|
165
|
+
${attributeNotes}
|
|
166
|
+
</ul>
|
|
167
|
+
</section>
|
|
168
|
+
</main>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
`;
|
|
172
|
+
};
|
|
173
|
+
const buildClassCode = (node, graph) => {
|
|
174
|
+
const contract = normalizeContract(node.contract);
|
|
175
|
+
const className = toPascalCase(node.name);
|
|
176
|
+
const ownedMethods = graph.nodes.filter((candidate) => candidate.ownerId === node.id && candidate.kind === "function");
|
|
177
|
+
const attributes = contract.attributes.length
|
|
178
|
+
? contract.attributes
|
|
179
|
+
.map((attribute) => ` ${sanitizeIdentifier(attribute.name)}: ${attribute.type};`)
|
|
180
|
+
.join("\n")
|
|
181
|
+
: " // TODO: add class attributes from the blueprint.";
|
|
182
|
+
const methods = (ownedMethods.length ? ownedMethods : [])
|
|
183
|
+
.map((methodNode) => {
|
|
184
|
+
const methodName = sanitizeIdentifier(methodNode.name.split(".").pop() ?? methodNode.name);
|
|
185
|
+
const methodContract = normalizeContract(methodNode.contract);
|
|
186
|
+
const inputList = methodContract.inputs
|
|
187
|
+
.map((field) => `${sanitizeIdentifier(field.name)}: ${field.type || "unknown"}`)
|
|
188
|
+
.join(", ");
|
|
189
|
+
const returnType = methodContract.outputs[0]?.type || "void";
|
|
190
|
+
return ` ${buildDocComment(methodNode)
|
|
191
|
+
.trimEnd()
|
|
192
|
+
.split("\n")
|
|
193
|
+
.map((line) => (line.startsWith(" *") || line.startsWith("/**") || line.startsWith(" */") ? ` ${line}` : line))
|
|
194
|
+
.join("\n")}
|
|
195
|
+
${methodName}(${inputList}): ${returnType} {
|
|
196
|
+
throw new Error("CodeFlow scaffold: implementation required for ${methodNode.id}");
|
|
197
|
+
}`;
|
|
198
|
+
})
|
|
199
|
+
.join("\n\n");
|
|
200
|
+
return `${buildDocComment(node)}export class ${className} {
|
|
201
|
+
${attributes}
|
|
202
|
+
|
|
203
|
+
${methods || " // TODO: add methods from the blueprint contract."}
|
|
204
|
+
}
|
|
205
|
+
`;
|
|
206
|
+
};
|
|
207
|
+
export const generateNodeCode = (node, graph) => {
|
|
208
|
+
if (!isCodeBearingNode(node)) {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
if (node.kind === "function") {
|
|
212
|
+
return buildFunctionCode(node);
|
|
213
|
+
}
|
|
214
|
+
if (node.kind === "api") {
|
|
215
|
+
return buildApiCode(node);
|
|
216
|
+
}
|
|
217
|
+
if (node.kind === "ui-screen") {
|
|
218
|
+
return buildUiScreenCode(node);
|
|
219
|
+
}
|
|
220
|
+
if (node.kind === "class") {
|
|
221
|
+
return buildClassCode(node, graph);
|
|
222
|
+
}
|
|
223
|
+
return null;
|
|
224
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { BlueprintGraph, BlueprintNode, BlueprintPhase, NodeVerification, ExecutionStep, RuntimeExecutionResult } from "../schema/index";
|
|
2
|
+
export declare const getCodeBearingNodes: (graph: BlueprintGraph) => BlueprintNode[];
|
|
3
|
+
export declare const withSpecDrafts: (graph: BlueprintGraph) => BlueprintGraph;
|
|
4
|
+
export declare const canCompleteSpecPhase: (graph: BlueprintGraph) => boolean;
|
|
5
|
+
export declare const canEnterImplementationPhase: (graph: BlueprintGraph) => boolean;
|
|
6
|
+
export declare const canEnterIntegrationPhase: (graph: BlueprintGraph) => boolean;
|
|
7
|
+
export declare const setGraphPhase: (graph: BlueprintGraph, phase: BlueprintPhase) => BlueprintGraph;
|
|
8
|
+
export declare const updateNodeStatus: (graph: BlueprintGraph, nodeId: string, updater: (node: BlueprintNode) => BlueprintNode) => BlueprintGraph;
|
|
9
|
+
export declare const markNodeImplemented: (graph: BlueprintGraph, nodeId: string, implementationDraft: string) => BlueprintGraph;
|
|
10
|
+
export declare const createNodeVerification: (result: RuntimeExecutionResult, verifiedAt?: string) => NodeVerification;
|
|
11
|
+
export declare const createNodeVerificationFromStep: (step: Pick<ExecutionStep, "status" | "stdout" | "stderr" | "completedAt">, exitCode?: number | null) => NodeVerification;
|
|
12
|
+
export declare const markNodeVerified: (graph: BlueprintGraph, nodeId: string, result: RuntimeExecutionResult) => BlueprintGraph;
|
|
13
|
+
export declare const getDefaultExecutionTarget: (graph: BlueprintGraph) => BlueprintNode | null;
|
|
14
|
+
export declare const markGraphConnected: (graph: BlueprintGraph) => BlueprintGraph;
|
|
15
|
+
export declare const applyExecutionResultToGraph: (graph: BlueprintGraph, result: RuntimeExecutionResult, options: {
|
|
16
|
+
integrationRun: boolean;
|
|
17
|
+
}) => BlueprintGraph;
|