@rryando/arcs 3.0.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 +448 -0
- package/dist/cli/arcs-orchestrate-caveman.d.ts +16 -0
- package/dist/cli/arcs-orchestrate-caveman.d.ts.map +1 -0
- package/dist/cli/arcs-orchestrate-caveman.js +109 -0
- package/dist/cli/arcs-orchestrate-caveman.js.map +1 -0
- package/dist/cli/arcs-orchestrate.d.ts +2 -0
- package/dist/cli/arcs-orchestrate.d.ts.map +1 -0
- package/dist/cli/arcs-orchestrate.js +431 -0
- package/dist/cli/arcs-orchestrate.js.map +1 -0
- package/dist/cli/arg-parser.d.ts +20 -0
- package/dist/cli/arg-parser.d.ts.map +1 -0
- package/dist/cli/arg-parser.js +216 -0
- package/dist/cli/arg-parser.js.map +1 -0
- package/dist/cli/brief-renderer.d.ts +32 -0
- package/dist/cli/brief-renderer.d.ts.map +1 -0
- package/dist/cli/brief-renderer.js +67 -0
- package/dist/cli/brief-renderer.js.map +1 -0
- package/dist/cli/bundle-installer.d.ts +77 -0
- package/dist/cli/bundle-installer.d.ts.map +1 -0
- package/dist/cli/bundle-installer.js +328 -0
- package/dist/cli/bundle-installer.js.map +1 -0
- package/dist/cli/command-registry.d.ts +52 -0
- package/dist/cli/command-registry.d.ts.map +1 -0
- package/dist/cli/command-registry.js +68 -0
- package/dist/cli/command-registry.js.map +1 -0
- package/dist/cli/commands/batch.d.ts +2 -0
- package/dist/cli/commands/batch.d.ts.map +1 -0
- package/dist/cli/commands/batch.js +307 -0
- package/dist/cli/commands/batch.js.map +1 -0
- package/dist/cli/commands/brief.d.ts +2 -0
- package/dist/cli/commands/brief.d.ts.map +1 -0
- package/dist/cli/commands/brief.js +146 -0
- package/dist/cli/commands/brief.js.map +1 -0
- package/dist/cli/commands/bundle.d.ts +2 -0
- package/dist/cli/commands/bundle.d.ts.map +1 -0
- package/dist/cli/commands/bundle.js +90 -0
- package/dist/cli/commands/bundle.js.map +1 -0
- package/dist/cli/commands/dependency.d.ts +2 -0
- package/dist/cli/commands/dependency.d.ts.map +1 -0
- package/dist/cli/commands/dependency.js +260 -0
- package/dist/cli/commands/dependency.js.map +1 -0
- package/dist/cli/commands/diagnostics.d.ts +2 -0
- package/dist/cli/commands/diagnostics.d.ts.map +1 -0
- package/dist/cli/commands/diagnostics.js +91 -0
- package/dist/cli/commands/diagnostics.js.map +1 -0
- package/dist/cli/commands/diagram.d.ts +2 -0
- package/dist/cli/commands/diagram.d.ts.map +1 -0
- package/dist/cli/commands/diagram.js +305 -0
- package/dist/cli/commands/diagram.js.map +1 -0
- package/dist/cli/commands/done.d.ts +2 -0
- package/dist/cli/commands/done.d.ts.map +1 -0
- package/dist/cli/commands/done.js +140 -0
- package/dist/cli/commands/done.js.map +1 -0
- package/dist/cli/commands/graph.d.ts +2 -0
- package/dist/cli/commands/graph.d.ts.map +1 -0
- package/dist/cli/commands/graph.js +105 -0
- package/dist/cli/commands/graph.js.map +1 -0
- package/dist/cli/commands/index.d.ts +21 -0
- package/dist/cli/commands/index.d.ts.map +1 -0
- package/dist/cli/commands/index.js +22 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/knowledge-search.d.ts +2 -0
- package/dist/cli/commands/knowledge-search.d.ts.map +1 -0
- package/dist/cli/commands/knowledge-search.js +43 -0
- package/dist/cli/commands/knowledge-search.js.map +1 -0
- package/dist/cli/commands/knowledge.d.ts +2 -0
- package/dist/cli/commands/knowledge.d.ts.map +1 -0
- package/dist/cli/commands/knowledge.js +416 -0
- package/dist/cli/commands/knowledge.js.map +1 -0
- package/dist/cli/commands/loop.d.ts +2 -0
- package/dist/cli/commands/loop.d.ts.map +1 -0
- package/dist/cli/commands/loop.js +192 -0
- package/dist/cli/commands/loop.js.map +1 -0
- package/dist/cli/commands/maintenance.d.ts +2 -0
- package/dist/cli/commands/maintenance.d.ts.map +1 -0
- package/dist/cli/commands/maintenance.js +231 -0
- package/dist/cli/commands/maintenance.js.map +1 -0
- package/dist/cli/commands/next.d.ts +2 -0
- package/dist/cli/commands/next.d.ts.map +1 -0
- package/dist/cli/commands/next.js +116 -0
- package/dist/cli/commands/next.js.map +1 -0
- package/dist/cli/commands/plan.d.ts +2 -0
- package/dist/cli/commands/plan.d.ts.map +1 -0
- package/dist/cli/commands/plan.js +340 -0
- package/dist/cli/commands/plan.js.map +1 -0
- package/dist/cli/commands/project-updates.d.ts +2 -0
- package/dist/cli/commands/project-updates.d.ts.map +1 -0
- package/dist/cli/commands/project-updates.js +243 -0
- package/dist/cli/commands/project-updates.js.map +1 -0
- package/dist/cli/commands/project.d.ts +2 -0
- package/dist/cli/commands/project.d.ts.map +1 -0
- package/dist/cli/commands/project.js +362 -0
- package/dist/cli/commands/project.js.map +1 -0
- package/dist/cli/commands/remember.d.ts +2 -0
- package/dist/cli/commands/remember.d.ts.map +1 -0
- package/dist/cli/commands/remember.js +70 -0
- package/dist/cli/commands/remember.js.map +1 -0
- package/dist/cli/commands/status.d.ts +2 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/status.js +104 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/task.d.ts +6 -0
- package/dist/cli/commands/task.d.ts.map +1 -0
- package/dist/cli/commands/task.js +361 -0
- package/dist/cli/commands/task.js.map +1 -0
- package/dist/cli/commands/utility.d.ts +9 -0
- package/dist/cli/commands/utility.d.ts.map +1 -0
- package/dist/cli/commands/utility.js +302 -0
- package/dist/cli/commands/utility.js.map +1 -0
- package/dist/cli/config.d.ts +42 -0
- package/dist/cli/config.d.ts.map +1 -0
- package/dist/cli/config.js +134 -0
- package/dist/cli/config.js.map +1 -0
- package/dist/cli/dag-commands.d.ts +7 -0
- package/dist/cli/dag-commands.d.ts.map +1 -0
- package/dist/cli/dag-commands.js +152 -0
- package/dist/cli/dag-commands.js.map +1 -0
- package/dist/cli/diagram-renderer.d.ts +26 -0
- package/dist/cli/diagram-renderer.d.ts.map +1 -0
- package/dist/cli/diagram-renderer.js +118 -0
- package/dist/cli/diagram-renderer.js.map +1 -0
- package/dist/cli/help-generator.d.ts +21 -0
- package/dist/cli/help-generator.d.ts.map +1 -0
- package/dist/cli/help-generator.js +100 -0
- package/dist/cli/help-generator.js.map +1 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +95 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/instructions.d.ts +53 -0
- package/dist/cli/instructions.d.ts.map +1 -0
- package/dist/cli/instructions.js +219 -0
- package/dist/cli/instructions.js.map +1 -0
- package/dist/cli/lean-output.d.ts +15 -0
- package/dist/cli/lean-output.d.ts.map +1 -0
- package/dist/cli/lean-output.js +66 -0
- package/dist/cli/lean-output.js.map +1 -0
- package/dist/cli/md-renderer.d.ts +6 -0
- package/dist/cli/md-renderer.d.ts.map +1 -0
- package/dist/cli/md-renderer.js +396 -0
- package/dist/cli/md-renderer.js.map +1 -0
- package/dist/cli/output-envelope.d.ts +15 -0
- package/dist/cli/output-envelope.d.ts.map +1 -0
- package/dist/cli/output-envelope.js +61 -0
- package/dist/cli/output-envelope.js.map +1 -0
- package/dist/cli/setup.d.ts +11 -0
- package/dist/cli/setup.d.ts.map +1 -0
- package/dist/cli/setup.js +415 -0
- package/dist/cli/setup.js.map +1 -0
- package/dist/cli/status-dashboard.d.ts +5 -0
- package/dist/cli/status-dashboard.d.ts.map +1 -0
- package/dist/cli/status-dashboard.js +248 -0
- package/dist/cli/status-dashboard.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/retrieval/bm25.d.ts +23 -0
- package/dist/retrieval/bm25.d.ts.map +1 -0
- package/dist/retrieval/bm25.js +92 -0
- package/dist/retrieval/bm25.js.map +1 -0
- package/dist/retrieval/cross-project-search.d.ts +30 -0
- package/dist/retrieval/cross-project-search.d.ts.map +1 -0
- package/dist/retrieval/cross-project-search.js +109 -0
- package/dist/retrieval/cross-project-search.js.map +1 -0
- package/dist/retrieval/graph-builder.d.ts +6 -0
- package/dist/retrieval/graph-builder.d.ts.map +1 -0
- package/dist/retrieval/graph-builder.js +196 -0
- package/dist/retrieval/graph-builder.js.map +1 -0
- package/dist/retrieval/graph-cache.d.ts +15 -0
- package/dist/retrieval/graph-cache.d.ts.map +1 -0
- package/dist/retrieval/graph-cache.js +66 -0
- package/dist/retrieval/graph-cache.js.map +1 -0
- package/dist/retrieval/graph-invalidate.d.ts +18 -0
- package/dist/retrieval/graph-invalidate.d.ts.map +1 -0
- package/dist/retrieval/graph-invalidate.js +23 -0
- package/dist/retrieval/graph-invalidate.js.map +1 -0
- package/dist/retrieval/graph-retrieval.d.ts +24 -0
- package/dist/retrieval/graph-retrieval.d.ts.map +1 -0
- package/dist/retrieval/graph-retrieval.js +165 -0
- package/dist/retrieval/graph-retrieval.js.map +1 -0
- package/dist/retrieval/graph-traverse.d.ts +3 -0
- package/dist/retrieval/graph-traverse.d.ts.map +1 -0
- package/dist/retrieval/graph-traverse.js +53 -0
- package/dist/retrieval/graph-traverse.js.map +1 -0
- package/dist/retrieval/graph-types.d.ts +37 -0
- package/dist/retrieval/graph-types.d.ts.map +1 -0
- package/dist/retrieval/graph-types.js +12 -0
- package/dist/retrieval/graph-types.js.map +1 -0
- package/dist/retrieval/index-builder.d.ts +21 -0
- package/dist/retrieval/index-builder.d.ts.map +1 -0
- package/dist/retrieval/index-builder.js +94 -0
- package/dist/retrieval/index-builder.js.map +1 -0
- package/dist/retrieval/knowledge-selection.d.ts +7 -0
- package/dist/retrieval/knowledge-selection.d.ts.map +1 -0
- package/dist/retrieval/knowledge-selection.js +80 -0
- package/dist/retrieval/knowledge-selection.js.map +1 -0
- package/dist/retrieval/task-scoped.d.ts +23 -0
- package/dist/retrieval/task-scoped.d.ts.map +1 -0
- package/dist/retrieval/task-scoped.js +51 -0
- package/dist/retrieval/task-scoped.js.map +1 -0
- package/dist/utils/content-assembly.d.ts +44 -0
- package/dist/utils/content-assembly.d.ts.map +1 -0
- package/dist/utils/content-assembly.js +101 -0
- package/dist/utils/content-assembly.js.map +1 -0
- package/dist/utils/dag.d.ts +28 -0
- package/dist/utils/dag.d.ts.map +1 -0
- package/dist/utils/dag.js +68 -0
- package/dist/utils/dag.js.map +1 -0
- package/dist/utils/diagram-generator.d.ts +17 -0
- package/dist/utils/diagram-generator.d.ts.map +1 -0
- package/dist/utils/diagram-generator.js +38 -0
- package/dist/utils/diagram-generator.js.map +1 -0
- package/dist/utils/errors.d.ts +40 -0
- package/dist/utils/errors.d.ts.map +1 -0
- package/dist/utils/errors.js +83 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/file-lock.d.ts +13 -0
- package/dist/utils/file-lock.d.ts.map +1 -0
- package/dist/utils/file-lock.js +80 -0
- package/dist/utils/file-lock.js.map +1 -0
- package/dist/utils/git.d.ts +14 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +56 -0
- package/dist/utils/git.js.map +1 -0
- package/dist/utils/graphify-knowledge.d.ts +6 -0
- package/dist/utils/graphify-knowledge.d.ts.map +1 -0
- package/dist/utils/graphify-knowledge.js +60 -0
- package/dist/utils/graphify-knowledge.js.map +1 -0
- package/dist/utils/graphify.d.ts +49 -0
- package/dist/utils/graphify.d.ts.map +1 -0
- package/dist/utils/graphify.js +306 -0
- package/dist/utils/graphify.js.map +1 -0
- package/dist/utils/json-schemas.d.ts +723 -0
- package/dist/utils/json-schemas.d.ts.map +1 -0
- package/dist/utils/json-schemas.js +147 -0
- package/dist/utils/json-schemas.js.map +1 -0
- package/dist/utils/json.d.ts +15 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +39 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/utils/knowledge-store.d.ts +49 -0
- package/dist/utils/knowledge-store.d.ts.map +1 -0
- package/dist/utils/knowledge-store.js +191 -0
- package/dist/utils/knowledge-store.js.map +1 -0
- package/dist/utils/loop-state.d.ts +51 -0
- package/dist/utils/loop-state.d.ts.map +1 -0
- package/dist/utils/loop-state.js +123 -0
- package/dist/utils/loop-state.js.map +1 -0
- package/dist/utils/paths.d.ts +16 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +66 -0
- package/dist/utils/paths.js.map +1 -0
- package/dist/utils/plan-store.d.ts +46 -0
- package/dist/utils/plan-store.d.ts.map +1 -0
- package/dist/utils/plan-store.js +184 -0
- package/dist/utils/plan-store.js.map +1 -0
- package/dist/utils/project-documents.d.ts +20 -0
- package/dist/utils/project-documents.d.ts.map +1 -0
- package/dist/utils/project-documents.js +10 -0
- package/dist/utils/project-documents.js.map +1 -0
- package/dist/utils/project-memory.d.ts +16 -0
- package/dist/utils/project-memory.d.ts.map +1 -0
- package/dist/utils/project-memory.js +16 -0
- package/dist/utils/project-memory.js.map +1 -0
- package/dist/utils/project-resolver.d.ts +28 -0
- package/dist/utils/project-resolver.d.ts.map +1 -0
- package/dist/utils/project-resolver.js +119 -0
- package/dist/utils/project-resolver.js.map +1 -0
- package/dist/utils/quick-scan.d.ts +13 -0
- package/dist/utils/quick-scan.d.ts.map +1 -0
- package/dist/utils/quick-scan.js +156 -0
- package/dist/utils/quick-scan.js.map +1 -0
- package/dist/utils/schemas.d.ts +13 -0
- package/dist/utils/schemas.d.ts.map +1 -0
- package/dist/utils/schemas.js +6 -0
- package/dist/utils/schemas.js.map +1 -0
- package/dist/utils/slug.d.ts +10 -0
- package/dist/utils/slug.d.ts.map +1 -0
- package/dist/utils/slug.js +18 -0
- package/dist/utils/slug.js.map +1 -0
- package/dist/utils/stdin.d.ts +6 -0
- package/dist/utils/stdin.d.ts.map +1 -0
- package/dist/utils/stdin.js +25 -0
- package/dist/utils/stdin.js.map +1 -0
- package/dist/utils/storage-utils.d.ts +48 -0
- package/dist/utils/storage-utils.d.ts.map +1 -0
- package/dist/utils/storage-utils.js +175 -0
- package/dist/utils/storage-utils.js.map +1 -0
- package/dist/utils/task-store.d.ts +49 -0
- package/dist/utils/task-store.d.ts.map +1 -0
- package/dist/utils/task-store.js +178 -0
- package/dist/utils/task-store.js.map +1 -0
- package/dist/utils/template.d.ts +15 -0
- package/dist/utils/template.d.ts.map +1 -0
- package/dist/utils/template.js +27 -0
- package/dist/utils/template.js.map +1 -0
- package/dist/utils/tool-response.d.ts +37 -0
- package/dist/utils/tool-response.d.ts.map +1 -0
- package/dist/utils/tool-response.js +44 -0
- package/dist/utils/tool-response.js.map +1 -0
- package/dist/utils/workflow-policy.d.ts +50 -0
- package/dist/utils/workflow-policy.d.ts.map +1 -0
- package/dist/utils/workflow-policy.js +111 -0
- package/dist/utils/workflow-policy.js.map +1 -0
- package/dist/utils/workspace-match.d.ts +35 -0
- package/dist/utils/workspace-match.d.ts.map +1 -0
- package/dist/utils/workspace-match.js +58 -0
- package/dist/utils/workspace-match.js.map +1 -0
- package/opencode/arcs/.opencode/plugins/arcs.js +348 -0
- package/opencode/arcs/bundle-runtime.json +68 -0
- package/opencode/arcs/manifest.json +279 -0
- package/opencode/arcs/prompts/arcs-docs.txt +124 -0
- package/opencode/arcs/prompts/arcs-orchestrate-caveman.txt +529 -0
- package/opencode/arcs/prompts/arcs-orchestrate.txt +437 -0
- package/opencode/arcs/prompts/code-reviewer.txt +55 -0
- package/opencode/arcs/prompts/devil-advocate.txt +121 -0
- package/opencode/arcs/prompts/docs-researcher.txt +54 -0
- package/opencode/arcs/prompts/oncall-ops.txt +54 -0
- package/opencode/arcs/prompts/qa-analyst.txt +54 -0
- package/opencode/arcs/prompts/software-engineer.txt +48 -0
- package/opencode/arcs/prompts/system-architect.txt +60 -0
- package/opencode/arcs/prompts/tech-architect.txt +54 -0
- package/opencode/arcs/skills/brainstorming/SKILL.md +144 -0
- package/opencode/arcs/skills/brainstorming/scripts/frame-template.html +214 -0
- package/opencode/arcs/skills/brainstorming/scripts/helper.js +88 -0
- package/opencode/arcs/skills/brainstorming/scripts/server.js +338 -0
- package/opencode/arcs/skills/brainstorming/scripts/start-server.sh +137 -0
- package/opencode/arcs/skills/brainstorming/scripts/stop-server.sh +31 -0
- package/opencode/arcs/skills/brainstorming/spec-document-reviewer-prompt.md +50 -0
- package/opencode/arcs/skills/brainstorming/visual-companion.md +277 -0
- package/opencode/arcs/skills/caveman-commit/SKILL.md +51 -0
- package/opencode/arcs/skills/code-agent/SKILL.md +58 -0
- package/opencode/arcs/skills/deep-pr-review/SKILL.md +191 -0
- package/opencode/arcs/skills/deep-pr-review/graphify-diff.md +102 -0
- package/opencode/arcs/skills/deep-pr-review/review-template.md +69 -0
- package/opencode/arcs/skills/executing-plans/SKILL.md +91 -0
- package/opencode/arcs/skills/init-project/SKILL.md +150 -0
- package/opencode/arcs/skills/quick-dev/SKILL.md +47 -0
- package/opencode/arcs/skills/requesting-code-review/SKILL.md +47 -0
- package/opencode/arcs/skills/requesting-code-review/code-reviewer.md +179 -0
- package/opencode/arcs/skills/subagent-driven-development/SKILL.md +162 -0
- package/opencode/arcs/skills/subagent-driven-development/code-quality-reviewer-prompt.md +53 -0
- package/opencode/arcs/skills/subagent-driven-development/implementer-prompt.md +148 -0
- package/opencode/arcs/skills/subagent-driven-development/spec-reviewer-prompt.md +85 -0
- package/opencode/arcs/skills/systematic-debugging/SKILL.md +174 -0
- package/opencode/arcs/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/opencode/arcs/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/opencode/arcs/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/opencode/arcs/skills/systematic-debugging/find-polluter.sh +63 -0
- package/opencode/arcs/skills/systematic-debugging/phases-reference.md +168 -0
- package/opencode/arcs/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/opencode/arcs/skills/test-driven-development/SKILL.md +72 -0
- package/opencode/arcs/skills/test-driven-development/tdd-rationalizations-and-examples.md +157 -0
- package/opencode/arcs/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/opencode/arcs/skills/to-diagram/SKILL.md +235 -0
- package/opencode/arcs/skills/to-diagram/scripts/manage-diagram.mjs +963 -0
- package/opencode/arcs/skills/writing-plans/SKILL.md +179 -0
- package/opencode/arcs/skills/writing-plans/plan-document-reviewer-prompt.md +52 -0
- package/package.json +60 -0
- package/scripts/arcs-cli.mjs +45 -0
- package/skills/explore-dag.md +57 -0
- package/skills/init-project.md +105 -0
- package/skills/orchestrate.md +94 -0
- package/skills/update-docs.md +72 -0
- package/templates/dependency.md.tmpl +9 -0
- package/templates/knowledge.md.tmpl +15 -0
- package/templates/project-meta.json.tmpl +13 -0
- package/templates/project.md.tmpl +11 -0
- package/templates/task.md.tmpl +9 -0
|
@@ -0,0 +1,963 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// manage-diagram.mjs — Deterministic diagram management for .diagram.mmd files
|
|
3
|
+
// No external dependencies. Node ESM CLI.
|
|
4
|
+
|
|
5
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
6
|
+
|
|
7
|
+
// --- Safe file read ---
|
|
8
|
+
|
|
9
|
+
function safeReadFile(filePath) {
|
|
10
|
+
if (!existsSync(filePath)) {
|
|
11
|
+
process.stderr.write(`Error: File not found: ${filePath}\n`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
return readFileSync(filePath, "utf-8");
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// --- Pure helpers ---
|
|
18
|
+
|
|
19
|
+
function parseNodes(graphLines) {
|
|
20
|
+
const nodes = [];
|
|
21
|
+
const seen = new Set();
|
|
22
|
+
// Match node declarations: ID[label]:::status or ID[label] (no status)
|
|
23
|
+
const nodeRe = /\b(T\d{3,})\[([^\]]+)\](?::::(\w+))?/g;
|
|
24
|
+
for (const line of graphLines) {
|
|
25
|
+
for (const match of line.matchAll(nodeRe)) {
|
|
26
|
+
const [, id, label, status] = match;
|
|
27
|
+
if (!seen.has(id)) {
|
|
28
|
+
nodes.push({ id, label, status: status || "backlog" });
|
|
29
|
+
seen.add(id);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return nodes;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function parseEdges(graphLines) {
|
|
37
|
+
const edges = [];
|
|
38
|
+
const edgeRe = /\b(T\d{3,})(?:\[[^\]]*\](?::::\w+)?)?\s*-->\s*(T\d{3,})/g;
|
|
39
|
+
for (const line of graphLines) {
|
|
40
|
+
for (const match of line.matchAll(edgeRe)) {
|
|
41
|
+
edges.push({ from: match[1], to: match[2] });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return edges;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseMetadataBlocks(lines) {
|
|
48
|
+
const blocks = {};
|
|
49
|
+
let current = null;
|
|
50
|
+
|
|
51
|
+
for (const line of lines) {
|
|
52
|
+
const trimmed = line.trim();
|
|
53
|
+
if (!trimmed.startsWith("%%")) continue;
|
|
54
|
+
|
|
55
|
+
const content = trimmed.slice(2).trim();
|
|
56
|
+
const fieldMatch = content.match(/^(\S+?):\s*(.*)$/);
|
|
57
|
+
if (!fieldMatch) continue;
|
|
58
|
+
|
|
59
|
+
const [, key, value] = fieldMatch;
|
|
60
|
+
|
|
61
|
+
if (key === "node") {
|
|
62
|
+
current = value.trim();
|
|
63
|
+
blocks[current] = { node: current };
|
|
64
|
+
} else if (current) {
|
|
65
|
+
blocks[current][key] = value.trim();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return blocks;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function parsePlanLevelComments(lines) {
|
|
73
|
+
const result = {
|
|
74
|
+
planId: null,
|
|
75
|
+
statusComment: null,
|
|
76
|
+
readyComment: null,
|
|
77
|
+
blockedComment: null,
|
|
78
|
+
nextActionComment: null,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
const trimmed = line.trim();
|
|
83
|
+
if (!trimmed.startsWith("%%")) continue;
|
|
84
|
+
const content = trimmed.slice(2).trim();
|
|
85
|
+
|
|
86
|
+
// Stop at first node metadata block
|
|
87
|
+
if (content.startsWith("node:")) break;
|
|
88
|
+
|
|
89
|
+
if (content.startsWith("plan:")) {
|
|
90
|
+
result.planId = content.slice(5).trim();
|
|
91
|
+
} else if (content.startsWith("status:")) {
|
|
92
|
+
result.statusComment = content.slice(7).trim();
|
|
93
|
+
} else if (content.startsWith("ready:")) {
|
|
94
|
+
result.readyComment = content.slice(6).trim();
|
|
95
|
+
} else if (content.startsWith("blocked:")) {
|
|
96
|
+
result.blockedComment = content.slice(8).trim();
|
|
97
|
+
} else if (content.startsWith("next-action:")) {
|
|
98
|
+
result.nextActionComment = content.slice(12).trim();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return result;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function splitSections(content) {
|
|
106
|
+
const lines = content.split("\n");
|
|
107
|
+
const flowchartIdx = lines.findIndex((l) => /^\s*flowchart\s+TD/i.test(l));
|
|
108
|
+
|
|
109
|
+
if (flowchartIdx === -1) {
|
|
110
|
+
return { headerLines: lines, graphLines: [], flowchartIdx: -1 };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
headerLines: lines.slice(0, flowchartIdx),
|
|
115
|
+
graphLines: lines.slice(flowchartIdx),
|
|
116
|
+
flowchartIdx,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function computeReady(nodes, edges) {
|
|
121
|
+
const nodeMap = new Map(nodes.map((n) => [n.id, n]));
|
|
122
|
+
const ready = [];
|
|
123
|
+
|
|
124
|
+
for (const node of nodes) {
|
|
125
|
+
if (node.status !== "backlog") continue;
|
|
126
|
+
const incomingDeps = edges.filter((e) => e.to === node.id).map((e) => e.from);
|
|
127
|
+
const allDepsDone = incomingDeps.every((depId) => {
|
|
128
|
+
const dep = nodeMap.get(depId);
|
|
129
|
+
return dep && dep.status === "done";
|
|
130
|
+
});
|
|
131
|
+
if (allDepsDone) ready.push(node.id);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return ready;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function computeBlocked(nodes, edges) {
|
|
138
|
+
const nodeMap = new Map(nodes.map((n) => [n.id, n]));
|
|
139
|
+
const blocked = [];
|
|
140
|
+
|
|
141
|
+
for (const node of nodes) {
|
|
142
|
+
// Explicitly blocked status always counts
|
|
143
|
+
if (node.status === "blocked") {
|
|
144
|
+
blocked.push(node.id);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
// Implicitly blocked: backlog node with at least one undone predecessor
|
|
148
|
+
if (node.status !== "backlog") continue;
|
|
149
|
+
const incomingDeps = edges.filter((e) => e.to === node.id).map((e) => e.from);
|
|
150
|
+
if (incomingDeps.length === 0) continue; // No deps = ready, not blocked
|
|
151
|
+
const hasUndone = incomingDeps.some((depId) => {
|
|
152
|
+
const dep = nodeMap.get(depId);
|
|
153
|
+
return dep && dep.status !== "done";
|
|
154
|
+
});
|
|
155
|
+
if (hasUndone) {
|
|
156
|
+
blocked.push(node.id);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return blocked;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function parseReadyNodeIds(readyComment) {
|
|
164
|
+
if (!readyComment) return [];
|
|
165
|
+
const ids = [];
|
|
166
|
+
for (const match of readyComment.matchAll(/T\d{3,}/g)) {
|
|
167
|
+
ids.push(match[0]);
|
|
168
|
+
}
|
|
169
|
+
return ids.sort();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
function parseStatusComment(statusComment) {
|
|
173
|
+
if (!statusComment) return {};
|
|
174
|
+
const result = {};
|
|
175
|
+
for (const match of statusComment.matchAll(/(T\d{3,})=(\w+)/g)) {
|
|
176
|
+
result[match[1]] = match[2];
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// --- Commands ---
|
|
182
|
+
|
|
183
|
+
function requirePlanHeader(content) {
|
|
184
|
+
const lines = content.split("\n");
|
|
185
|
+
let found = false;
|
|
186
|
+
for (const line of lines) {
|
|
187
|
+
const trimmed = line.trim();
|
|
188
|
+
if (trimmed.startsWith("%%")) {
|
|
189
|
+
const c = trimmed.slice(2).trim();
|
|
190
|
+
if (c.startsWith("plan:")) {
|
|
191
|
+
const val = c.slice(5).trim();
|
|
192
|
+
if (val) {
|
|
193
|
+
found = true;
|
|
194
|
+
break;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (!found) {
|
|
200
|
+
process.stderr.write("Error: Diagram must have a non-empty plan header.\n");
|
|
201
|
+
process.exit(1);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function inspect(filePath) {
|
|
206
|
+
const content = safeReadFile(filePath);
|
|
207
|
+
requirePlanHeader(content);
|
|
208
|
+
const { headerLines, graphLines } = splitSections(content);
|
|
209
|
+
|
|
210
|
+
if (graphLines.length === 0) {
|
|
211
|
+
// Check for stateDiagram
|
|
212
|
+
if (content.includes("stateDiagram-v2")) {
|
|
213
|
+
process.stderr.write("Error: stateDiagram-v2 is not supported by this tool.\n");
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
process.stderr.write("Error: No flowchart TD found in file.\n");
|
|
217
|
+
process.exit(1);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const nodes = parseNodes(graphLines);
|
|
221
|
+
const edges = parseEdges(graphLines);
|
|
222
|
+
const metadataBlocks = parseMetadataBlocks(headerLines);
|
|
223
|
+
const planLevel = parsePlanLevelComments(headerLines);
|
|
224
|
+
|
|
225
|
+
const result = {
|
|
226
|
+
planId: planLevel.planId,
|
|
227
|
+
nodes,
|
|
228
|
+
edges,
|
|
229
|
+
metadataBlocks,
|
|
230
|
+
statusComment: planLevel.statusComment,
|
|
231
|
+
readyComment: planLevel.readyComment,
|
|
232
|
+
blockedComment: planLevel.blockedComment,
|
|
233
|
+
nextActionComment: planLevel.nextActionComment,
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function ready(filePath) {
|
|
240
|
+
const content = safeReadFile(filePath);
|
|
241
|
+
requirePlanHeader(content);
|
|
242
|
+
const { graphLines } = splitSections(content);
|
|
243
|
+
|
|
244
|
+
if (graphLines.length === 0) {
|
|
245
|
+
if (content.includes("stateDiagram-v2")) {
|
|
246
|
+
process.stderr.write("Error: stateDiagram-v2 is not supported by this tool.\n");
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
process.stderr.write("Error: No flowchart TD found in file.\n");
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const nodes = parseNodes(graphLines);
|
|
254
|
+
const edges = parseEdges(graphLines);
|
|
255
|
+
const readyNodes = computeReady(nodes, edges);
|
|
256
|
+
|
|
257
|
+
process.stdout.write(`${JSON.stringify(readyNodes, null, 2)}\n`);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function validate(filePath, metadataPath) {
|
|
261
|
+
const content = safeReadFile(filePath);
|
|
262
|
+
|
|
263
|
+
// If --metadata provided, do drift validation
|
|
264
|
+
if (metadataPath) {
|
|
265
|
+
return validateWithMetadata(content, metadataPath);
|
|
266
|
+
}
|
|
267
|
+
const errors = [];
|
|
268
|
+
|
|
269
|
+
// Check for unsupported diagram type
|
|
270
|
+
if (content.includes("stateDiagram-v2")) {
|
|
271
|
+
process.stdout.write(
|
|
272
|
+
`${JSON.stringify(
|
|
273
|
+
{
|
|
274
|
+
ok: false,
|
|
275
|
+
errors: ["Unsupported diagram type: stateDiagram-v2. Only flowchart TD is supported."],
|
|
276
|
+
},
|
|
277
|
+
null,
|
|
278
|
+
2,
|
|
279
|
+
)}\n`,
|
|
280
|
+
);
|
|
281
|
+
process.exit(1);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
const { headerLines, graphLines } = splitSections(content);
|
|
285
|
+
|
|
286
|
+
if (graphLines.length === 0) {
|
|
287
|
+
process.stdout.write(
|
|
288
|
+
`${JSON.stringify({ ok: false, errors: ["No flowchart TD found in file."] }, null, 2)}\n`,
|
|
289
|
+
);
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const nodes = parseNodes(graphLines);
|
|
294
|
+
const edges = parseEdges(graphLines);
|
|
295
|
+
const metadataBlocks = parseMetadataBlocks(headerLines);
|
|
296
|
+
const planLevel = parsePlanLevelComments(headerLines);
|
|
297
|
+
|
|
298
|
+
// Duplicate node IDs in graph
|
|
299
|
+
const nodeIds = [];
|
|
300
|
+
const nodeRe = /\b(T\d{3,})\[([^\]]+)\](?::::(\w+))?/g;
|
|
301
|
+
for (const line of graphLines) {
|
|
302
|
+
for (const match of line.matchAll(nodeRe)) {
|
|
303
|
+
nodeIds.push(match[1]);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
const idCounts = {};
|
|
307
|
+
for (const id of nodeIds) {
|
|
308
|
+
idCounts[id] = (idCounts[id] || 0) + 1;
|
|
309
|
+
}
|
|
310
|
+
for (const [id, count] of Object.entries(idCounts)) {
|
|
311
|
+
if (count > 1) errors.push(`Duplicate node ID in graph: ${id}`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Duplicate metadata blocks
|
|
315
|
+
const metaNodeIds = [];
|
|
316
|
+
for (const line of headerLines) {
|
|
317
|
+
const trimmed = line.trim();
|
|
318
|
+
if (trimmed.startsWith("%%")) {
|
|
319
|
+
const content = trimmed.slice(2).trim();
|
|
320
|
+
const m = content.match(/^node:\s*(.+)$/);
|
|
321
|
+
if (m) metaNodeIds.push(m[1].trim());
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
const metaCounts = {};
|
|
325
|
+
for (const id of metaNodeIds) {
|
|
326
|
+
metaCounts[id] = (metaCounts[id] || 0) + 1;
|
|
327
|
+
}
|
|
328
|
+
for (const [id, count] of Object.entries(metaCounts)) {
|
|
329
|
+
if (count > 1) errors.push(`Duplicate metadata block for node: ${id}`);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// Edges referencing missing nodes
|
|
333
|
+
const nodeIdSet = new Set(nodes.map((n) => n.id));
|
|
334
|
+
for (const edge of edges) {
|
|
335
|
+
if (!nodeIdSet.has(edge.from)) errors.push(`Edge references missing node: ${edge.from}`);
|
|
336
|
+
if (!nodeIdSet.has(edge.to)) errors.push(`Edge references missing node: ${edge.to}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Missing metadata block for graph nodes
|
|
340
|
+
for (const node of nodes) {
|
|
341
|
+
if (!metadataBlocks[node.id]) {
|
|
342
|
+
errors.push(`Missing metadata block for node: ${node.id}`);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Missing required metadata fields (verify is optional per SKILL.md)
|
|
347
|
+
const requiredFields = ["node", "title", "status", "skill", "scope", "acceptance"];
|
|
348
|
+
for (const node of nodes) {
|
|
349
|
+
const block = metadataBlocks[node.id];
|
|
350
|
+
if (block) {
|
|
351
|
+
for (const field of requiredFields) {
|
|
352
|
+
if (!block[field]) {
|
|
353
|
+
errors.push(`Missing required field '${field}' in metadata block for ${node.id}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Metadata blocks not ordered by node ID
|
|
360
|
+
if (metaNodeIds.length > 1) {
|
|
361
|
+
const uniqueMetaIds = [...new Set(metaNodeIds)];
|
|
362
|
+
const sorted = [...uniqueMetaIds].sort();
|
|
363
|
+
if (JSON.stringify(uniqueMetaIds) !== JSON.stringify(sorted)) {
|
|
364
|
+
errors.push("Metadata blocks are not ordered by node ID");
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
// Graph node status differs from metadata block status
|
|
369
|
+
for (const node of nodes) {
|
|
370
|
+
const block = metadataBlocks[node.id];
|
|
371
|
+
if (block?.status && block.status !== node.status) {
|
|
372
|
+
errors.push(
|
|
373
|
+
`Status mismatch for ${node.id}: graph says '${node.status}', metadata says '${block.status}'`,
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Status comment differs from graph node statuses
|
|
379
|
+
if (planLevel.statusComment) {
|
|
380
|
+
const commentStatuses = parseStatusComment(planLevel.statusComment);
|
|
381
|
+
for (const node of nodes) {
|
|
382
|
+
if (commentStatuses[node.id] && commentStatuses[node.id] !== node.status) {
|
|
383
|
+
errors.push(
|
|
384
|
+
`Status comment mismatch for ${node.id}: comment says '${commentStatuses[node.id]}', graph says '${node.status}'`,
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Ready comment differs from computed ready nodes
|
|
391
|
+
if (planLevel.readyComment) {
|
|
392
|
+
const commentReady = parseReadyNodeIds(planLevel.readyComment);
|
|
393
|
+
// Validate that all ready IDs actually exist in the graph
|
|
394
|
+
for (const id of commentReady) {
|
|
395
|
+
if (!nodeIdSet.has(id)) {
|
|
396
|
+
errors.push(`Ready comment references node '${id}' not found in graph`);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
const computedReady = computeReady(nodes, edges).sort();
|
|
400
|
+
if (JSON.stringify(commentReady) !== JSON.stringify(computedReady)) {
|
|
401
|
+
errors.push(
|
|
402
|
+
`Ready comment mismatch: comment says [${commentReady.join(", ")}], computed is [${computedReady.join(", ")}]`,
|
|
403
|
+
);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const ok = errors.length === 0;
|
|
408
|
+
process.stdout.write(`${JSON.stringify({ ok, errors }, null, 2)}\n`);
|
|
409
|
+
if (!ok) process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function validateWithMetadata(content, metadataPath) {
|
|
413
|
+
const metaRaw = safeReadFile(metadataPath);
|
|
414
|
+
let meta;
|
|
415
|
+
try {
|
|
416
|
+
meta = JSON.parse(metaRaw);
|
|
417
|
+
} catch (e) {
|
|
418
|
+
process.stderr.write(`Error: Failed to parse metadata JSON: ${e.message}\n`);
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const errors = [];
|
|
423
|
+
const { headerLines, graphLines } = splitSections(content);
|
|
424
|
+
|
|
425
|
+
if (graphLines.length === 0) {
|
|
426
|
+
process.stdout.write(
|
|
427
|
+
`${JSON.stringify({ ok: false, errors: ["No flowchart TD found in file."] }, null, 2)}\n`,
|
|
428
|
+
);
|
|
429
|
+
process.exit(1);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
const nodes = parseNodes(graphLines);
|
|
433
|
+
const edges = parseEdges(graphLines);
|
|
434
|
+
const planLevel = parsePlanLevelComments(headerLines);
|
|
435
|
+
const metadataBlocks = parseMetadataBlocks(headerLines);
|
|
436
|
+
const nodeMap = new Map(nodes.map((n) => [n.id, n]));
|
|
437
|
+
const metaTaskIds = new Set((meta.tasks || []).map((t) => t.id));
|
|
438
|
+
const diagramNodeIds = new Set(nodes.map((n) => n.id));
|
|
439
|
+
|
|
440
|
+
// 1. Class status mismatch
|
|
441
|
+
for (const task of meta.tasks || []) {
|
|
442
|
+
const node = nodeMap.get(task.id);
|
|
443
|
+
if (node && node.status !== task.status) {
|
|
444
|
+
errors.push(
|
|
445
|
+
`Class status mismatch for ${task.id}: diagram says '${node.status}', metadata says '${task.status}'`,
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// 2. Phantom node
|
|
451
|
+
for (const node of nodes) {
|
|
452
|
+
if (!metaTaskIds.has(node.id)) {
|
|
453
|
+
errors.push(`Phantom node in diagram: ${node.id}`);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// 3. Missing node
|
|
458
|
+
for (const task of meta.tasks || []) {
|
|
459
|
+
if (!diagramNodeIds.has(task.id)) {
|
|
460
|
+
errors.push(`Missing node from diagram: ${task.id}`);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// 4. Topology mismatch
|
|
465
|
+
const metaEdges = new Set();
|
|
466
|
+
for (const task of meta.tasks || []) {
|
|
467
|
+
for (const dep of (task.dependencies || []).sort()) {
|
|
468
|
+
metaEdges.add(`${dep}->${task.id}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
const diagramEdges = new Set(edges.map((e) => `${e.from}->${e.to}`));
|
|
472
|
+
const allEdgeKeys = new Set([...metaEdges, ...diagramEdges]);
|
|
473
|
+
let topoMismatch = false;
|
|
474
|
+
for (const key of allEdgeKeys) {
|
|
475
|
+
if (!metaEdges.has(key) || !diagramEdges.has(key)) {
|
|
476
|
+
topoMismatch = true;
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
if (topoMismatch) {
|
|
481
|
+
errors.push("Topology mismatch: diagram edges differ from metadata dependencies");
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// 5. Stale plan-level comments
|
|
485
|
+
if (planLevel.statusComment) {
|
|
486
|
+
const commentStatuses = parseStatusComment(planLevel.statusComment);
|
|
487
|
+
for (const task of meta.tasks || []) {
|
|
488
|
+
if (commentStatuses[task.id] && commentStatuses[task.id] !== task.status) {
|
|
489
|
+
errors.push(
|
|
490
|
+
`Stale plan-level comments: status comment says ${task.id}=${commentStatuses[task.id]}, metadata says ${task.status}`,
|
|
491
|
+
);
|
|
492
|
+
break;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
// 6. Incomplete metadata in node blocks (verify is optional per SKILL.md)
|
|
498
|
+
const requiredFields = ["node", "title", "status", "skill", "scope", "acceptance"];
|
|
499
|
+
for (const task of meta.tasks || []) {
|
|
500
|
+
const block = metadataBlocks[task.id];
|
|
501
|
+
if (!block) {
|
|
502
|
+
errors.push(`Incomplete metadata: missing node block for ${task.id}`);
|
|
503
|
+
} else {
|
|
504
|
+
for (const field of requiredFields) {
|
|
505
|
+
if (!block[field]) {
|
|
506
|
+
errors.push(`Incomplete metadata: missing '${field}' in node block for ${task.id}`);
|
|
507
|
+
break;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
const ok = errors.length === 0;
|
|
514
|
+
process.stdout.write(`${JSON.stringify({ ok, errors }, null, 2)}\n`);
|
|
515
|
+
if (!ok) process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// --- Metadata schema validation ---
|
|
519
|
+
|
|
520
|
+
const VALID_STATUSES = ["done", "inProgress", "blocked", "backlog"];
|
|
521
|
+
const REQUIRED_TASK_FIELDS = ["title", "status", "skill", "scope", "acceptance"];
|
|
522
|
+
|
|
523
|
+
function validateMetadataSchema(meta) {
|
|
524
|
+
if (!meta.planId || typeof meta.planId !== "string" || !meta.planId.trim()) {
|
|
525
|
+
process.stderr.write("Error: Metadata requires a non-empty 'planId' string.\n");
|
|
526
|
+
process.exit(1);
|
|
527
|
+
}
|
|
528
|
+
if (!Array.isArray(meta.tasks)) {
|
|
529
|
+
process.stderr.write("Error: Metadata requires a 'tasks' array.\n");
|
|
530
|
+
process.exit(1);
|
|
531
|
+
}
|
|
532
|
+
for (let i = 0; i < meta.tasks.length; i++) {
|
|
533
|
+
const task = meta.tasks[i];
|
|
534
|
+
for (const field of REQUIRED_TASK_FIELDS) {
|
|
535
|
+
if (!task[field] || typeof task[field] !== "string" || !task[field].trim()) {
|
|
536
|
+
process.stderr.write(
|
|
537
|
+
`Error: Task ${task.id || `[${i}]`} missing required field '${field}'.\n`,
|
|
538
|
+
);
|
|
539
|
+
process.exit(1);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (!VALID_STATUSES.includes(task.status)) {
|
|
543
|
+
process.stderr.write(
|
|
544
|
+
`Error: Task ${task.id || `[${i}]`} has invalid status '${task.status}'. Must be one of: ${VALID_STATUSES.join(", ")}\n`,
|
|
545
|
+
);
|
|
546
|
+
process.exit(1);
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
// --- Regenerate command ---
|
|
552
|
+
|
|
553
|
+
function regenerate(filePath, metadataPath) {
|
|
554
|
+
const metaRaw = safeReadFile(metadataPath);
|
|
555
|
+
let meta;
|
|
556
|
+
try {
|
|
557
|
+
meta = JSON.parse(metaRaw);
|
|
558
|
+
} catch (e) {
|
|
559
|
+
process.stderr.write(`Error: Failed to parse metadata JSON: ${e.message}\n`);
|
|
560
|
+
process.exit(1);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
validateMetadataSchema(meta);
|
|
564
|
+
|
|
565
|
+
// Determine existing IDs from previous diagram (if exists)
|
|
566
|
+
const existingIds = new Set();
|
|
567
|
+
if (existsSync(filePath)) {
|
|
568
|
+
const prev = readFileSync(filePath, "utf-8");
|
|
569
|
+
const { graphLines } = splitSections(prev);
|
|
570
|
+
const prevNodes = parseNodes(graphLines);
|
|
571
|
+
for (const n of prevNodes) existingIds.add(n.id);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// Assign IDs: preserve existing task IDs, assign new sequential ones for tasks without IDs
|
|
575
|
+
const allUsedIds = new Set(existingIds);
|
|
576
|
+
const tasks = meta.tasks.map((t) => {
|
|
577
|
+
if (t.id) {
|
|
578
|
+
allUsedIds.add(t.id);
|
|
579
|
+
return { ...t };
|
|
580
|
+
}
|
|
581
|
+
return { ...t, id: null }; // will assign below
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
// Assign IDs to tasks without one
|
|
585
|
+
let nextNum = 1;
|
|
586
|
+
for (const task of tasks) {
|
|
587
|
+
if (!task.id) {
|
|
588
|
+
while (allUsedIds.has(`T${String(nextNum).padStart(3, "0")}`)) nextNum++;
|
|
589
|
+
task.id = `T${String(nextNum).padStart(3, "0")}`;
|
|
590
|
+
allUsedIds.add(task.id);
|
|
591
|
+
nextNum++;
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Validate dependencies reference existing task IDs
|
|
596
|
+
const taskIdSet = new Set(tasks.map((t) => t.id));
|
|
597
|
+
for (const task of tasks) {
|
|
598
|
+
for (const dep of task.dependencies || []) {
|
|
599
|
+
if (!taskIdSet.has(dep)) {
|
|
600
|
+
process.stderr.write(`Error: Task dependency references missing task: ${dep}\n`);
|
|
601
|
+
process.exit(1);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Sort tasks by ID for determinism
|
|
607
|
+
tasks.sort((a, b) => a.id.localeCompare(b.id));
|
|
608
|
+
|
|
609
|
+
// Build plan-level comments
|
|
610
|
+
const statusStr = tasks.map((t) => `${t.id}=${t.status}`).join(", ");
|
|
611
|
+
|
|
612
|
+
// Build edges (sorted) for ready/blocked computation
|
|
613
|
+
const edges = [];
|
|
614
|
+
for (const task of tasks) {
|
|
615
|
+
for (const dep of (task.dependencies || []).sort()) {
|
|
616
|
+
edges.push({ from: dep, to: task.id });
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
edges.sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to));
|
|
620
|
+
|
|
621
|
+
const nodesForCompute = tasks.map((t) => ({ id: t.id, label: t.title, status: t.status }));
|
|
622
|
+
const readyNodes = computeReady(nodesForCompute, edges);
|
|
623
|
+
const blockedNodes = computeBlocked(nodesForCompute, edges);
|
|
624
|
+
|
|
625
|
+
const readyStr = readyNodes.length > 0 ? readyNodes.join(", ") : "none";
|
|
626
|
+
const blockedStr = blockedNodes.length > 0 ? blockedNodes.join(", ") : "none";
|
|
627
|
+
const nextAction = readyNodes.length > 0 ? `Start ${readyNodes[0]}` : "No ready tasks";
|
|
628
|
+
|
|
629
|
+
// Build output
|
|
630
|
+
const lines = [];
|
|
631
|
+
lines.push(`%% plan: ${meta.planId}`);
|
|
632
|
+
lines.push(`%% status: ${statusStr}`);
|
|
633
|
+
lines.push(`%% ready: ${readyStr}`);
|
|
634
|
+
lines.push(`%% blocked: ${blockedStr}`);
|
|
635
|
+
lines.push(`%% next-action: ${nextAction}`);
|
|
636
|
+
lines.push("");
|
|
637
|
+
|
|
638
|
+
// Per-node metadata
|
|
639
|
+
for (const task of tasks) {
|
|
640
|
+
lines.push(`%% node: ${task.id}`);
|
|
641
|
+
lines.push(`%% title: ${task.title}`);
|
|
642
|
+
lines.push(`%% status: ${task.status}`);
|
|
643
|
+
lines.push(`%% skill: ${task.skill}`);
|
|
644
|
+
lines.push(`%% scope: ${task.scope}`);
|
|
645
|
+
if (task.files) lines.push(`%% files: ${task.files}`);
|
|
646
|
+
lines.push(`%% acceptance: ${task.acceptance}`);
|
|
647
|
+
if (task.verify) lines.push(`%% verify: ${task.verify}`);
|
|
648
|
+
const deps = (task.dependencies || []).sort();
|
|
649
|
+
if (deps.length > 0) lines.push(`%% blocked-by: ${deps.join(", ")}`);
|
|
650
|
+
if (task.delegate) lines.push(`%% delegate: ${task.delegate}`);
|
|
651
|
+
lines.push("");
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
// Flowchart
|
|
655
|
+
lines.push("flowchart TD");
|
|
656
|
+
lines.push(" classDef done fill:#22c55e,color:#fff");
|
|
657
|
+
lines.push(" classDef inProgress fill:#f59e0b,color:#fff");
|
|
658
|
+
lines.push(" classDef blocked fill:#ef4444,color:#fff");
|
|
659
|
+
lines.push(" classDef backlog fill:#94a3b8,color:#fff");
|
|
660
|
+
lines.push("");
|
|
661
|
+
|
|
662
|
+
// Node declarations + edges
|
|
663
|
+
// Declare all nodes first, then edges with bare IDs
|
|
664
|
+
for (const task of tasks) {
|
|
665
|
+
lines.push(` ${task.id}[${task.title}]:::${task.status}`);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
if (edges.length > 0) {
|
|
669
|
+
lines.push("");
|
|
670
|
+
for (const edge of edges) {
|
|
671
|
+
lines.push(` ${edge.from} --> ${edge.to}`);
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
lines.push("");
|
|
676
|
+
|
|
677
|
+
const output = lines.join("\n");
|
|
678
|
+
writeFileSync(filePath, output);
|
|
679
|
+
|
|
680
|
+
process.stdout.write(
|
|
681
|
+
`${JSON.stringify(
|
|
682
|
+
{ regenerated: true, planId: meta.planId, nodeCount: tasks.length, readyNodes },
|
|
683
|
+
null,
|
|
684
|
+
2,
|
|
685
|
+
)}\n`,
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function status(filePath, nodeId, newStatus) {
|
|
690
|
+
const validStatuses = ["done", "inProgress", "blocked", "backlog"];
|
|
691
|
+
if (!validStatuses.includes(newStatus)) {
|
|
692
|
+
process.stderr.write(
|
|
693
|
+
`Error: Invalid status '${newStatus}'. Must be one of: ${validStatuses.join(", ")}\n`,
|
|
694
|
+
);
|
|
695
|
+
process.exit(1);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
const content = safeReadFile(filePath);
|
|
699
|
+
|
|
700
|
+
if (content.includes("stateDiagram-v2")) {
|
|
701
|
+
process.stderr.write("Error: stateDiagram-v2 is not supported by this tool.\n");
|
|
702
|
+
process.exit(1);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const { headerLines, graphLines } = splitSections(content);
|
|
706
|
+
|
|
707
|
+
if (graphLines.length === 0) {
|
|
708
|
+
process.stderr.write("Error: No flowchart TD found in file.\n");
|
|
709
|
+
process.exit(1);
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// Capture pre-update topology
|
|
713
|
+
const preNodes = parseNodes(graphLines);
|
|
714
|
+
const preEdges = parseEdges(graphLines);
|
|
715
|
+
const preNodeSet = new Set(preNodes.map((n) => n.id));
|
|
716
|
+
const preEdgeSet = new Set(preEdges.map((e) => `${e.from}->${e.to}`));
|
|
717
|
+
|
|
718
|
+
if (!preNodeSet.has(nodeId)) {
|
|
719
|
+
process.stderr.write(`Error: Node '${nodeId}' not found in graph.\n`);
|
|
720
|
+
process.exit(1);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// Update graph lines — change :::status for the target node
|
|
724
|
+
const updatedGraphLines = graphLines.map((line) => {
|
|
725
|
+
// Match node declaration: nodeId[label]:::status — anchored to avoid matching inside labels
|
|
726
|
+
// Uses (?<![\\w]) negative lookbehind instead of \b to prevent matching T001 inside another node's label
|
|
727
|
+
const re = new RegExp(`(?<![\\w])(${nodeId}\\[[^\\]]+\\]):::\\w+`, "g");
|
|
728
|
+
return line.replace(re, `$1:::${newStatus}`);
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
// Verify the graph was actually updated (node must have :::class suffix)
|
|
732
|
+
const graphChanged = graphLines.some((line, i) => line !== updatedGraphLines[i]);
|
|
733
|
+
if (!graphChanged) {
|
|
734
|
+
process.stderr.write(
|
|
735
|
+
`Error: Node '${nodeId}' has no :::class suffix in graph declaration. Cannot update status.\n`,
|
|
736
|
+
);
|
|
737
|
+
process.exit(1);
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
// Update metadata block status
|
|
741
|
+
const updatedHeaderLines = [];
|
|
742
|
+
let inTargetBlock = false;
|
|
743
|
+
for (const line of headerLines) {
|
|
744
|
+
const trimmed = line.trim();
|
|
745
|
+
if (trimmed.startsWith("%%")) {
|
|
746
|
+
const c = trimmed.slice(2).trim();
|
|
747
|
+
const nodeMatch = c.match(/^node:\s*(.+)$/);
|
|
748
|
+
if (nodeMatch) {
|
|
749
|
+
inTargetBlock = nodeMatch[1].trim() === nodeId;
|
|
750
|
+
}
|
|
751
|
+
if (inTargetBlock && c.startsWith("status:")) {
|
|
752
|
+
updatedHeaderLines.push(`%% status: ${newStatus}`);
|
|
753
|
+
continue;
|
|
754
|
+
}
|
|
755
|
+
} else {
|
|
756
|
+
inTargetBlock = false;
|
|
757
|
+
}
|
|
758
|
+
updatedHeaderLines.push(line);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
// Recompute plan-level comments
|
|
762
|
+
const updatedNodes = parseNodes(updatedGraphLines);
|
|
763
|
+
const updatedEdges = parseEdges(updatedGraphLines);
|
|
764
|
+
const readyNodes = computeReady(updatedNodes, updatedEdges);
|
|
765
|
+
const blockedNodes = computeBlocked(updatedNodes, updatedEdges);
|
|
766
|
+
|
|
767
|
+
// Build new status comment
|
|
768
|
+
const statusStr = updatedNodes.map((n) => `${n.id}=${n.status}`).join(", ");
|
|
769
|
+
const readyStr = readyNodes.length > 0 ? readyNodes.join(", ") : "none";
|
|
770
|
+
const blockedStr = blockedNodes.length > 0 ? blockedNodes.join(", ") : "none";
|
|
771
|
+
const nextAction = readyNodes.length > 0 ? `Start ${readyNodes[0]}` : "No ready tasks";
|
|
772
|
+
|
|
773
|
+
// Replace plan-level comments in header
|
|
774
|
+
const result = [];
|
|
775
|
+
let replacedStatus = false,
|
|
776
|
+
replacedReady = false,
|
|
777
|
+
replacedBlocked = false,
|
|
778
|
+
replacedNext = false;
|
|
779
|
+
|
|
780
|
+
const firstNodeIdx = updatedHeaderLines.findIndex((l) => l.trim().startsWith("%% node:"));
|
|
781
|
+
|
|
782
|
+
for (let i = 0; i < updatedHeaderLines.length; i++) {
|
|
783
|
+
const line = updatedHeaderLines[i];
|
|
784
|
+
const trimmed = line.trim();
|
|
785
|
+
if (trimmed.startsWith("%%")) {
|
|
786
|
+
const c = trimmed.slice(2).trim();
|
|
787
|
+
// Plan-level comments come before first node block
|
|
788
|
+
if (c.startsWith("status:") && !replacedStatus) {
|
|
789
|
+
if (i < firstNodeIdx || firstNodeIdx === -1) {
|
|
790
|
+
result.push(`%% status: ${statusStr}`);
|
|
791
|
+
replacedStatus = true;
|
|
792
|
+
continue;
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
if (c.startsWith("ready:") && !replacedReady) {
|
|
796
|
+
result.push(`%% ready: ${readyStr}`);
|
|
797
|
+
replacedReady = true;
|
|
798
|
+
continue;
|
|
799
|
+
}
|
|
800
|
+
if (c.startsWith("blocked:") && !replacedBlocked) {
|
|
801
|
+
// Plan-level blocked
|
|
802
|
+
if (i < firstNodeIdx || firstNodeIdx === -1) {
|
|
803
|
+
result.push(`%% blocked: ${blockedStr}`);
|
|
804
|
+
replacedBlocked = true;
|
|
805
|
+
continue;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
if (c.startsWith("next-action:") && !replacedNext) {
|
|
809
|
+
result.push(`%% next-action: ${nextAction}`);
|
|
810
|
+
replacedNext = true;
|
|
811
|
+
continue;
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
result.push(line);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
const finalContent = [...result, ...updatedGraphLines].join("\n");
|
|
818
|
+
|
|
819
|
+
// Verify topology unchanged
|
|
820
|
+
const postNodes = parseNodes(updatedGraphLines);
|
|
821
|
+
const postEdges = parseEdges(updatedGraphLines);
|
|
822
|
+
const postNodeSet = new Set(postNodes.map((n) => n.id));
|
|
823
|
+
const postEdgeSet = new Set(postEdges.map((e) => `${e.from}->${e.to}`));
|
|
824
|
+
|
|
825
|
+
const nodeSetEqual =
|
|
826
|
+
preNodeSet.size === postNodeSet.size && [...preNodeSet].every((id) => postNodeSet.has(id));
|
|
827
|
+
const edgeSetEqual =
|
|
828
|
+
preEdgeSet.size === postEdgeSet.size && [...preEdgeSet].every((e) => postEdgeSet.has(e));
|
|
829
|
+
|
|
830
|
+
if (!nodeSetEqual || !edgeSetEqual) {
|
|
831
|
+
process.stderr.write("Error: Topology changed during status update. Aborting.\n");
|
|
832
|
+
process.exit(1);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
writeFileSync(filePath, finalContent);
|
|
836
|
+
|
|
837
|
+
const summary = { updated: true, nodeId, status: newStatus, ready: readyNodes };
|
|
838
|
+
process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
|
|
839
|
+
}
|
|
840
|
+
|
|
841
|
+
function sortMetadata(filePath) {
|
|
842
|
+
const content = safeReadFile(filePath);
|
|
843
|
+
|
|
844
|
+
if (content.includes("stateDiagram-v2")) {
|
|
845
|
+
process.stderr.write("Error: stateDiagram-v2 is not supported by this tool.\n");
|
|
846
|
+
process.exit(1);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
const lines = content.split("\n");
|
|
850
|
+
const flowchartIdx = lines.findIndex((l) => /^\s*flowchart\s+TD/i.test(l));
|
|
851
|
+
|
|
852
|
+
if (flowchartIdx === -1) {
|
|
853
|
+
process.stderr.write("Error: No flowchart TD found in file.\n");
|
|
854
|
+
process.exit(1);
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
const headerLines = lines.slice(0, flowchartIdx);
|
|
858
|
+
const graphLines = lines.slice(flowchartIdx);
|
|
859
|
+
|
|
860
|
+
// Separate plan-level comments from node metadata blocks
|
|
861
|
+
const planComments = [];
|
|
862
|
+
const nodeBlocks = []; // array of { id, lines }
|
|
863
|
+
let currentBlock = null;
|
|
864
|
+
|
|
865
|
+
for (const line of headerLines) {
|
|
866
|
+
const trimmed = line.trim();
|
|
867
|
+
if (trimmed.startsWith("%%")) {
|
|
868
|
+
const c = trimmed.slice(2).trim();
|
|
869
|
+
const nodeMatch = c.match(/^node:\s*(.+)$/);
|
|
870
|
+
if (nodeMatch) {
|
|
871
|
+
if (currentBlock) nodeBlocks.push(currentBlock);
|
|
872
|
+
currentBlock = { id: nodeMatch[1].trim(), lines: [line] };
|
|
873
|
+
continue;
|
|
874
|
+
}
|
|
875
|
+
if (currentBlock) {
|
|
876
|
+
currentBlock.lines.push(line);
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
} else if (trimmed === "" && currentBlock) {
|
|
880
|
+
// Empty line after a node block — keep as separator
|
|
881
|
+
currentBlock.lines.push(line);
|
|
882
|
+
nodeBlocks.push(currentBlock);
|
|
883
|
+
currentBlock = null;
|
|
884
|
+
continue;
|
|
885
|
+
} else if (currentBlock) {
|
|
886
|
+
// Non-comment line ends the block
|
|
887
|
+
nodeBlocks.push(currentBlock);
|
|
888
|
+
currentBlock = null;
|
|
889
|
+
}
|
|
890
|
+
|
|
891
|
+
if (!currentBlock) {
|
|
892
|
+
planComments.push(line);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
if (currentBlock) nodeBlocks.push(currentBlock);
|
|
896
|
+
|
|
897
|
+
// Sort node blocks by ID
|
|
898
|
+
nodeBlocks.sort((a, b) => a.id.localeCompare(b.id));
|
|
899
|
+
|
|
900
|
+
// Reassemble
|
|
901
|
+
const sortedHeader = [...planComments, ...nodeBlocks.flatMap((block) => block.lines)];
|
|
902
|
+
|
|
903
|
+
// Ensure blank line between header and flowchart
|
|
904
|
+
const lastHeaderLine = sortedHeader[sortedHeader.length - 1];
|
|
905
|
+
if (lastHeaderLine && lastHeaderLine.trim() !== "") {
|
|
906
|
+
sortedHeader.push("");
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
const finalContent = [...sortedHeader, ...graphLines].join("\n");
|
|
910
|
+
writeFileSync(filePath, finalContent);
|
|
911
|
+
|
|
912
|
+
const sortedIds = nodeBlocks.map((b) => b.id);
|
|
913
|
+
process.stdout.write(`${JSON.stringify({ sorted: true, order: sortedIds }, null, 2)}\n`);
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
// --- CLI ---
|
|
917
|
+
|
|
918
|
+
const [, , command, filePath, ...args] = process.argv;
|
|
919
|
+
|
|
920
|
+
if (!command || !filePath) {
|
|
921
|
+
process.stderr.write("Usage: manage-diagram.mjs <command> <file> [args...]\n");
|
|
922
|
+
process.stderr.write("Commands: inspect, ready, validate, status, sort-metadata, regenerate\n");
|
|
923
|
+
process.exit(1);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
switch (command) {
|
|
927
|
+
case "inspect":
|
|
928
|
+
inspect(filePath);
|
|
929
|
+
break;
|
|
930
|
+
case "ready":
|
|
931
|
+
ready(filePath);
|
|
932
|
+
break;
|
|
933
|
+
case "validate": {
|
|
934
|
+
const metaIdx = args.indexOf("--metadata");
|
|
935
|
+
const metaPath = metaIdx !== -1 ? args[metaIdx + 1] : null;
|
|
936
|
+
validate(filePath, metaPath);
|
|
937
|
+
break;
|
|
938
|
+
}
|
|
939
|
+
case "status":
|
|
940
|
+
if (args.length < 2) {
|
|
941
|
+
process.stderr.write("Usage: manage-diagram.mjs status <file> <nodeId> <newStatus>\n");
|
|
942
|
+
process.exit(1);
|
|
943
|
+
}
|
|
944
|
+
status(filePath, args[0], args[1]);
|
|
945
|
+
break;
|
|
946
|
+
case "sort-metadata":
|
|
947
|
+
sortMetadata(filePath);
|
|
948
|
+
break;
|
|
949
|
+
case "regenerate": {
|
|
950
|
+
const metaIdx = args.indexOf("--metadata");
|
|
951
|
+
if (metaIdx === -1 || !args[metaIdx + 1]) {
|
|
952
|
+
process.stderr.write(
|
|
953
|
+
"Usage: manage-diagram.mjs regenerate <file> --metadata <metadata.json>\n",
|
|
954
|
+
);
|
|
955
|
+
process.exit(1);
|
|
956
|
+
}
|
|
957
|
+
regenerate(filePath, args[metaIdx + 1]);
|
|
958
|
+
break;
|
|
959
|
+
}
|
|
960
|
+
default:
|
|
961
|
+
process.stderr.write(`Unknown command: ${command}\n`);
|
|
962
|
+
process.exit(1);
|
|
963
|
+
}
|