xcoder-dna 0.0.0-stage → 1.3.1
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/LICENSE +21 -0
- package/README.md +87 -2
- package/dist/agents/ai-chat-debate.d.ts +36 -0
- package/dist/agents/ai-chat-debate.js +300 -0
- package/dist/agents/ai-chat-report-writer.d.ts +7 -0
- package/dist/agents/ai-chat-report-writer.js +100 -0
- package/dist/agents/duplicate-detector.d.ts +41 -0
- package/dist/agents/duplicate-detector.js +65 -0
- package/dist/agents/genesis-agent.d.ts +61 -0
- package/dist/agents/genesis-agent.js +340 -0
- package/dist/agents/samurai-agent.d.ts +53 -0
- package/dist/agents/samurai-agent.js +279 -0
- package/dist/agents/sub-agent.d.ts +56 -0
- package/dist/agents/sub-agent.js +353 -0
- package/dist/backlog/backlog-requirement-validator.d.ts +10 -0
- package/dist/backlog/backlog-requirement-validator.js +60 -0
- package/dist/backlog/backlog-store.d.ts +29 -0
- package/dist/backlog/backlog-store.js +148 -0
- package/dist/backlog/po-agent.d.ts +16 -0
- package/dist/backlog/po-agent.js +121 -0
- package/dist/backlog/types.d.ts +25 -0
- package/dist/backlog/types.js +4 -0
- package/dist/bench/run.d.ts +11 -0
- package/dist/bench/run.js +185 -0
- package/dist/bench/types.d.ts +50 -0
- package/dist/bench/types.js +1 -0
- package/dist/cli/swarm.d.ts +10 -0
- package/dist/cli/swarm.js +100 -0
- package/dist/cli/task.d.ts +14 -0
- package/dist/cli/task.js +121 -0
- package/dist/cli/wbc-arg.d.ts +6 -0
- package/dist/cli/wbc-arg.js +40 -0
- package/dist/cli/wbc.d.ts +45 -0
- package/dist/cli/wbc.js +98 -0
- package/dist/commands/license.d.ts +9 -0
- package/dist/commands/license.js +140 -0
- package/dist/commands/release.d.ts +44 -0
- package/dist/commands/release.js +180 -0
- package/dist/commands/test.d.ts +50 -0
- package/dist/commands/test.js +385 -0
- package/dist/config/loadConfig.d.ts +82 -0
- package/dist/config/loadConfig.js +240 -0
- package/dist/config/secure-mode.d.ts +29 -0
- package/dist/config/secure-mode.js +63 -0
- package/dist/config/user-config.d.ts +39 -0
- package/dist/config/user-config.js +202 -0
- package/dist/engine/reasoning-engine.d.ts +21 -0
- package/dist/engine/reasoning-engine.js +59 -0
- package/dist/events/bus.d.ts +22 -0
- package/dist/events/bus.js +18 -0
- package/dist/index.d.ts +79 -0
- package/dist/index.js +77 -0
- package/dist/indexing/ignoreRules.d.ts +1 -0
- package/dist/indexing/ignoreRules.js +53 -0
- package/dist/instance-swarm/instance-swarm-orchestrator.d.ts +57 -0
- package/dist/instance-swarm/instance-swarm-orchestrator.js +204 -0
- package/dist/instance-swarm/protocol-name-resolver.d.ts +23 -0
- package/dist/instance-swarm/protocol-name-resolver.js +64 -0
- package/dist/license/gate.d.ts +31 -0
- package/dist/license/gate.js +188 -0
- package/dist/license/http-client.d.ts +43 -0
- package/dist/license/http-client.js +195 -0
- package/dist/license/manager.d.ts +39 -0
- package/dist/license/manager.js +175 -0
- package/dist/license/receipt.d.ts +27 -0
- package/dist/license/receipt.js +108 -0
- package/dist/license/state-codec.d.ts +72 -0
- package/dist/license/state-codec.js +218 -0
- package/dist/license/state-paths.d.ts +17 -0
- package/dist/license/state-paths.js +64 -0
- package/dist/license/store.d.ts +44 -0
- package/dist/license/store.js +223 -0
- package/dist/license/trust-anchor.d.ts +24 -0
- package/dist/license/trust-anchor.js +74 -0
- package/dist/license/types.d.ts +34 -0
- package/dist/license/types.js +19 -0
- package/dist/llm/budget.d.ts +23 -0
- package/dist/llm/budget.js +58 -0
- package/dist/llm/client.d.ts +53 -0
- package/dist/llm/client.js +122 -0
- package/dist/llm/factory.d.ts +14 -0
- package/dist/llm/factory.js +32 -0
- package/dist/orchestrator/defect-logger.d.ts +31 -0
- package/dist/orchestrator/defect-logger.js +101 -0
- package/dist/orchestrator/defect-manager.d.ts +118 -0
- package/dist/orchestrator/defect-manager.js +338 -0
- package/dist/orchestrator/defect-migration.d.ts +14 -0
- package/dist/orchestrator/defect-migration.js +174 -0
- package/dist/orchestrator/defect-store.d.ts +48 -0
- package/dist/orchestrator/defect-store.js +76 -0
- package/dist/orchestrator/healing.d.ts +14 -0
- package/dist/orchestrator/healing.js +11 -0
- package/dist/orchestrator/interactive-steering.d.ts +64 -0
- package/dist/orchestrator/interactive-steering.js +107 -0
- package/dist/orchestrator/interrupt-handler.d.ts +30 -0
- package/dist/orchestrator/interrupt-handler.js +70 -0
- package/dist/orchestrator/lesson-manager.d.ts +25 -0
- package/dist/orchestrator/lesson-manager.js +122 -0
- package/dist/orchestrator/progress-summary.d.ts +15 -0
- package/dist/orchestrator/progress-summary.js +106 -0
- package/dist/orchestrator/reasoning-score.d.ts +49 -0
- package/dist/orchestrator/reasoning-score.js +117 -0
- package/dist/orchestrator/swarm.d.ts +36 -0
- package/dist/orchestrator/swarm.js +77 -0
- package/dist/orchestrator/task-factory.d.ts +15 -0
- package/dist/orchestrator/task-factory.js +176 -0
- package/dist/orchestrator/validation-gate.d.ts +23 -0
- package/dist/orchestrator/validation-gate.js +155 -0
- package/dist/protocol/react-contract.d.ts +136 -0
- package/dist/protocol/react-contract.js +698 -0
- package/dist/protocol/wks-protocol-manager.d.ts +22 -0
- package/dist/protocol/wks-protocol-manager.js +72 -0
- package/dist/sdlc-runtime/conversation-runner.d.ts +8 -0
- package/dist/sdlc-runtime/conversation-runner.js +64 -0
- package/dist/sdlc-runtime/dag-utils.d.ts +15 -0
- package/dist/sdlc-runtime/dag-utils.js +67 -0
- package/dist/sdlc-runtime/defect-integration.d.ts +7 -0
- package/dist/sdlc-runtime/defect-integration.js +99 -0
- package/dist/sdlc-runtime/execution-loop.d.ts +38 -0
- package/dist/sdlc-runtime/execution-loop.js +274 -0
- package/dist/sdlc-runtime/lesson-recorder.d.ts +9 -0
- package/dist/sdlc-runtime/lesson-recorder.js +27 -0
- package/dist/sdlc-runtime/plan-initializer.d.ts +47 -0
- package/dist/sdlc-runtime/plan-initializer.js +135 -0
- package/dist/sdlc-runtime/progress-persister.d.ts +10 -0
- package/dist/sdlc-runtime/progress-persister.js +34 -0
- package/dist/sdlc-runtime/protocol-loader.d.ts +10 -0
- package/dist/sdlc-runtime/protocol-loader.js +21 -0
- package/dist/sdlc-runtime/reporting.d.ts +24 -0
- package/dist/sdlc-runtime/reporting.js +68 -0
- package/dist/sdlc-runtime/sdlc-orchestrator.d.ts +59 -0
- package/dist/sdlc-runtime/sdlc-orchestrator.js +170 -0
- package/dist/sdlc-runtime/sdlc-skill.d.ts +44 -0
- package/dist/sdlc-runtime/sdlc-skill.js +148 -0
- package/dist/sdlc-runtime/steering-handler.d.ts +14 -0
- package/dist/sdlc-runtime/steering-handler.js +51 -0
- package/dist/sdlc-runtime/trace-recorder.d.ts +10 -0
- package/dist/sdlc-runtime/trace-recorder.js +7 -0
- package/dist/security/index.d.ts +16 -0
- package/dist/security/index.js +12 -0
- package/dist/security/input-screen.d.ts +30 -0
- package/dist/security/input-screen.js +75 -0
- package/dist/security/tool-call-guard.d.ts +16 -0
- package/dist/security/tool-call-guard.js +163 -0
- package/dist/security/untrusted-content.d.ts +34 -0
- package/dist/security/untrusted-content.js +63 -0
- package/dist/skills/advanced-dfir/SKILL.md +85 -0
- package/dist/skills/advanced-pentest-lab/SKILL.md +88 -0
- package/dist/skills/analyst/SKILL.md +55 -0
- package/dist/skills/architect/SKILL.md +54 -0
- package/dist/skills/aws/SKILL.md +92 -0
- package/dist/skills/azure/SKILL.md +90 -0
- package/dist/skills/bylaw-context.d.ts +45 -0
- package/dist/skills/bylaw-context.js +61 -0
- package/dist/skills/bylaw-review.d.ts +20 -0
- package/dist/skills/bylaw-review.js +101 -0
- package/dist/skills/conversation/SKILL.md +29 -0
- package/dist/skills/design-patterns/SKILL.md +190 -0
- package/dist/skills/devops/SKILL.md +55 -0
- package/dist/skills/directives.d.ts +20 -0
- package/dist/skills/directives.js +313 -0
- package/dist/skills/docker/SKILL.md +76 -0
- package/dist/skills/docker-expert/SKILL.md +56 -0
- package/dist/skills/enterprise-systems-analyst/SKILL.md +83 -0
- package/dist/skills/enterprise-systems-analyst/references/crm.md +41 -0
- package/dist/skills/enterprise-systems-analyst/references/erp.md +40 -0
- package/dist/skills/enterprise-systems-analyst/references/financial-systems.md +35 -0
- package/dist/skills/enterprise-systems-analyst/references/hcm.md +38 -0
- package/dist/skills/enterprise-systems-analyst/references/mrp.md +37 -0
- package/dist/skills/enterprise-systems-analyst/references/scm.md +32 -0
- package/dist/skills/ethical-hacking-methodology/SKILL.md +119 -0
- package/dist/skills/externalSkillLoader.d.ts +25 -0
- package/dist/skills/externalSkillLoader.js +65 -0
- package/dist/skills/externalSkillRegistry.d.ts +33 -0
- package/dist/skills/externalSkillRegistry.js +94 -0
- package/dist/skills/filesystem-management/SKILL.md +61 -0
- package/dist/skills/first-run-seed.d.ts +26 -0
- package/dist/skills/first-run-seed.js +75 -0
- package/dist/skills/git-vcs/SKILL.md +84 -0
- package/dist/skills/kafka/SKILL.md +89 -0
- package/dist/skills/kubernetes/SKILL.md +93 -0
- package/dist/skills/kubernetes-expert/SKILL.md +54 -0
- package/dist/skills/openshift/SKILL.md +93 -0
- package/dist/skills/pentester/SKILL.md +59 -0
- package/dist/skills/performance-tester/SKILL.md +57 -0
- package/dist/skills/playwright-ui-tester/SKILL.md +67 -0
- package/dist/skills/programmer/SKILL.md +58 -0
- package/dist/skills/project-scaffolding/SKILL.md +133 -0
- package/dist/skills/qa-engineer/SKILL.md +91 -0
- package/dist/skills/rca/SKILL.md +58 -0
- package/dist/skills/redhat/SKILL.md +92 -0
- package/dist/skills/rosa/SKILL.md +80 -0
- package/dist/skills/scrum-framework/SKILL.md +74 -0
- package/dist/skills/scrum-framework/references/artifacts_and_quality.md +79 -0
- package/dist/skills/scrum-framework/references/roles_and_events.md +105 -0
- package/dist/skills/scrum-master-agent/SKILL.md +74 -0
- package/dist/skills/scrum-master-agent/references/behaviors.md +56 -0
- package/dist/skills/sdlc-skill.d.ts +1 -0
- package/dist/skills/sdlc-skill.js +4 -0
- package/dist/skills/secops/SKILL.md +52 -0
- package/dist/skills/secure-architect.bylaws.d.ts +68 -0
- package/dist/skills/secure-architect.bylaws.js +525 -0
- package/dist/skills/seed-skills.d.ts +48 -0
- package/dist/skills/seed-skills.js +213 -0
- package/dist/skills/skill-authoring/SKILL.md +123 -0
- package/dist/skills/skill-header.d.ts +51 -0
- package/dist/skills/skill-header.js +163 -0
- package/dist/skills/skill-path-guard.d.ts +41 -0
- package/dist/skills/skill-path-guard.js +95 -0
- package/dist/skills/skillRouter.d.ts +51 -0
- package/dist/skills/skillRouter.js +90 -0
- package/dist/skills/skills-root.d.ts +64 -0
- package/dist/skills/skills-root.js +168 -0
- package/dist/skills/software-architect/SKILL.md +77 -0
- package/dist/skills/software-engineer/SKILL.md +74 -0
- package/dist/skills/solid-principles/SKILL.md +94 -0
- package/dist/skills/task-planning/SKILL.md +115 -0
- package/dist/skills/tester/SKILL.md +54 -0
- package/dist/skills/ubuntu/SKILL.md +87 -0
- package/dist/skills/ui-ux-design/SKILL.md +114 -0
- package/dist/skills/workspace-context/SKILL.md +103 -0
- package/dist/sprint/controller.d.ts +37 -0
- package/dist/sprint/controller.js +220 -0
- package/dist/sprint/dag-builder.d.ts +14 -0
- package/dist/sprint/dag-builder.js +105 -0
- package/dist/sprint/reporter.d.ts +11 -0
- package/dist/sprint/reporter.js +97 -0
- package/dist/sprint/state-store.d.ts +10 -0
- package/dist/sprint/state-store.js +71 -0
- package/dist/sprint/types.d.ts +24 -0
- package/dist/sprint/types.js +4 -0
- package/dist/store/file-version-store.d.ts +24 -0
- package/dist/store/file-version-store.js +73 -0
- package/dist/store/task-store.d.ts +90 -0
- package/dist/store/task-store.js +166 -0
- package/dist/task-ingest/architect-detailer.d.ts +43 -0
- package/dist/task-ingest/architect-detailer.js +141 -0
- package/dist/task-ingest/detail-schema.d.ts +9 -0
- package/dist/task-ingest/detail-schema.js +195 -0
- package/dist/task-ingest/detailed-run.d.ts +36 -0
- package/dist/task-ingest/detailed-run.js +108 -0
- package/dist/task-ingest/extract-id.d.ts +9 -0
- package/dist/task-ingest/extract-id.js +24 -0
- package/dist/task-ingest/index.d.ts +23 -0
- package/dist/task-ingest/index.js +14 -0
- package/dist/task-ingest/ingest.d.ts +45 -0
- package/dist/task-ingest/ingest.js +163 -0
- package/dist/task-ingest/list-ingested.d.ts +13 -0
- package/dist/task-ingest/list-ingested.js +30 -0
- package/dist/task-ingest/pending-filter.d.ts +30 -0
- package/dist/task-ingest/pending-filter.js +59 -0
- package/dist/task-ingest/scanner.d.ts +56 -0
- package/dist/task-ingest/scanner.js +216 -0
- package/dist/task-ingest/source-reader.d.ts +25 -0
- package/dist/task-ingest/source-reader.js +93 -0
- package/dist/task-ingest/spec-builder.d.ts +34 -0
- package/dist/task-ingest/spec-builder.js +89 -0
- package/dist/task-ingest/wbs-extract.d.ts +50 -0
- package/dist/task-ingest/wbs-extract.js +226 -0
- package/dist/task-ingest/wbs-parser.d.ts +36 -0
- package/dist/task-ingest/wbs-parser.js +170 -0
- package/dist/tools/apiTestTool.d.ts +37 -0
- package/dist/tools/apiTestTool.js +128 -0
- package/dist/tools/ast-tools.d.ts +183 -0
- package/dist/tools/ast-tools.js +853 -0
- package/dist/tools/browse-tool.d.ts +105 -0
- package/dist/tools/browse-tool.js +394 -0
- package/dist/tools/credential.d.ts +17 -0
- package/dist/tools/credential.js +32 -0
- package/dist/tools/dependency-graph.d.ts +23 -0
- package/dist/tools/dependency-graph.js +164 -0
- package/dist/tools/edit-tool.d.ts +34 -0
- package/dist/tools/edit-tool.js +95 -0
- package/dist/tools/file-header-manager.d.ts +27 -0
- package/dist/tools/file-header-manager.js +153 -0
- package/dist/tools/fs-tools.d.ts +154 -0
- package/dist/tools/fs-tools.js +337 -0
- package/dist/tools/git-tools.d.ts +79 -0
- package/dist/tools/git-tools.js +348 -0
- package/dist/tools/github-tool.d.ts +48 -0
- package/dist/tools/github-tool.js +147 -0
- package/dist/tools/globTool.d.ts +20 -0
- package/dist/tools/globTool.js +34 -0
- package/dist/tools/grepTool.d.ts +19 -0
- package/dist/tools/grepTool.js +44 -0
- package/dist/tools/index-freshness.d.ts +58 -0
- package/dist/tools/index-freshness.js +190 -0
- package/dist/tools/index-glob.d.ts +48 -0
- package/dist/tools/index-glob.js +175 -0
- package/dist/tools/load-skill-tool.d.ts +15 -0
- package/dist/tools/load-skill-tool.js +114 -0
- package/dist/tools/patch-tools.d.ts +58 -0
- package/dist/tools/patch-tools.js +153 -0
- package/dist/tools/playwright-tool.d.ts +66 -0
- package/dist/tools/playwright-tool.js +121 -0
- package/dist/tools/project-detect.d.ts +20 -0
- package/dist/tools/project-detect.js +87 -0
- package/dist/tools/prompt-guidance.d.ts +22 -0
- package/dist/tools/prompt-guidance.js +23 -0
- package/dist/tools/scheduleTool.d.ts +19 -0
- package/dist/tools/scheduleTool.js +181 -0
- package/dist/tools/search-tools.d.ts +27 -0
- package/dist/tools/search-tools.js +78 -0
- package/dist/tools/sed-tool.d.ts +66 -0
- package/dist/tools/sed-tool.js +402 -0
- package/dist/tools/shell-result.d.ts +11 -0
- package/dist/tools/shell-result.js +1 -0
- package/dist/tools/shell-tool.d.ts +45 -0
- package/dist/tools/shell-tool.js +141 -0
- package/dist/tools/siteCrawlerTool.d.ts +32 -0
- package/dist/tools/siteCrawlerTool.js +205 -0
- package/dist/tools/tool-registry.d.ts +67 -0
- package/dist/tools/tool-registry.js +596 -0
- package/dist/tools/validate-tool.d.ts +23 -0
- package/dist/tools/validate-tool.js +201 -0
- package/dist/tools/workspace-index.d.ts +46 -0
- package/dist/tools/workspace-index.js +173 -0
- package/dist/types.d.ts +222 -0
- package/dist/types.js +7 -0
- package/dist/updater/index.d.ts +14 -0
- package/dist/updater/index.js +120 -0
- package/dist/util/banner.d.ts +25 -0
- package/dist/util/banner.js +140 -0
- package/dist/util/command-suggester.d.ts +6 -0
- package/dist/util/command-suggester.js +25 -0
- package/dist/util/confirm-interrupt.d.ts +10 -0
- package/dist/util/confirm-interrupt.js +31 -0
- package/dist/util/continue-trigger.d.ts +11 -0
- package/dist/util/continue-trigger.js +20 -0
- package/dist/util/env-info.d.ts +18 -0
- package/dist/util/env-info.js +37 -0
- package/dist/util/file-access-log.d.ts +35 -0
- package/dist/util/file-access-log.js +140 -0
- package/dist/util/footer-manager.d.ts +124 -0
- package/dist/util/footer-manager.js +195 -0
- package/dist/util/footer-renderer.d.ts +67 -0
- package/dist/util/footer-renderer.js +319 -0
- package/dist/util/footer-state.d.ts +34 -0
- package/dist/util/footer-state.js +18 -0
- package/dist/util/footer-text.d.ts +51 -0
- package/dist/util/footer-text.js +74 -0
- package/dist/util/ids.d.ts +1 -0
- package/dist/util/ids.js +4 -0
- package/dist/util/levenshtein.d.ts +6 -0
- package/dist/util/levenshtein.js +34 -0
- package/dist/util/logger.d.ts +10 -0
- package/dist/util/logger.js +41 -0
- package/dist/util/logs.d.ts +26 -0
- package/dist/util/logs.js +88 -0
- package/dist/util/output-handler.d.ts +89 -0
- package/dist/util/output-handler.js +193 -0
- package/dist/util/output-interceptor.d.ts +18 -0
- package/dist/util/output-interceptor.js +49 -0
- package/dist/util/output-paths.d.ts +43 -0
- package/dist/util/output-paths.js +121 -0
- package/dist/util/package-json.d.ts +6 -0
- package/dist/util/package-json.js +36 -0
- package/dist/util/process-kill.d.ts +27 -0
- package/dist/util/process-kill.js +148 -0
- package/dist/util/protocol-error-logger.d.ts +33 -0
- package/dist/util/protocol-error-logger.js +73 -0
- package/dist/util/redact.d.ts +25 -0
- package/dist/util/redact.js +103 -0
- package/dist/util/scroll-region-guard.d.ts +33 -0
- package/dist/util/scroll-region-guard.js +63 -0
- package/dist/util/spinner.d.ts +15 -0
- package/dist/util/spinner.js +49 -0
- package/dist/util/terminal-output.d.ts +34 -0
- package/dist/util/terminal-output.js +1 -0
- package/dist/util/terminal-silent.d.ts +6 -0
- package/dist/util/terminal-silent.js +20 -0
- package/dist/util/terminal.d.ts +56 -0
- package/dist/util/terminal.js +124 -0
- package/dist/util/text-sanitize.d.ts +8 -0
- package/dist/util/text-sanitize.js +45 -0
- package/dist/util/thinking-spinner.d.ts +27 -0
- package/dist/util/thinking-spinner.js +189 -0
- package/dist/util/tool-error-logger.d.ts +33 -0
- package/dist/util/tool-error-logger.js +77 -0
- package/dist/util/tty.d.ts +13 -0
- package/dist/util/tty.js +16 -0
- package/dist/util/xcoder-paths.d.ts +56 -0
- package/dist/util/xcoder-paths.js +63 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +4 -0
- package/dist/worker-agent/to-subtask-node.d.ts +35 -0
- package/dist/worker-agent/to-subtask-node.js +41 -0
- package/dist/worker-agent/worker-orchestrator.d.ts +104 -0
- package/dist/worker-agent/worker-orchestrator.js +201 -0
- package/package.json +38 -3
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
// xcoder:version 1 | xcoder:task task-cee5a1 | xcoder:updated 2026-10-23T12:52:47.742Z | xcoder:subtask code-st-f13216
|
|
2
|
+
// C1 — discover `.md` task sources in a directory and hand each survivor to
|
|
3
|
+
// `ingestTask()`.
|
|
4
|
+
//
|
|
5
|
+
// This module is orchestration only: it never parses markdown, never derives an
|
|
6
|
+
// id, and never writes. Taskhood is delegated to `parseWbs` (at least one work
|
|
7
|
+
// item) so the rule cannot drift from `task add --from`; every byte on disk
|
|
8
|
+
// still goes through the single already-tested ingest path.
|
|
9
|
+
import { promises as fs } from "node:fs";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import { loadTaskRun } from "../store/task-store.js";
|
|
12
|
+
import { createXcoderPaths } from "../util/xcoder-paths.js";
|
|
13
|
+
import { deriveTaskId, ingestTask } from "./ingest.js";
|
|
14
|
+
import { parseWbs } from "./wbs-parser.js";
|
|
15
|
+
import { readSource } from "./source-reader.js";
|
|
16
|
+
/**
|
|
17
|
+
* Mirrors `ingest.ts`'s private `PROGRESS_STATUSES`.
|
|
18
|
+
*
|
|
19
|
+
* Duplicated rather than exported so `ingest.ts` (on the design's no-change
|
|
20
|
+
* list) stays untouched. `scanner.test.ts` locks the two together by
|
|
21
|
+
* asserting that a dry run agrees with the real, persisted outcome for the
|
|
22
|
+
* same directory.
|
|
23
|
+
*/
|
|
24
|
+
const PROGRESS_STATUSES = new Set(["done", "in_progress", "validating", "healing"]);
|
|
25
|
+
function hasProgress(run) {
|
|
26
|
+
return (run.dag ?? []).some((n) => PROGRESS_STATUSES.has(n.status));
|
|
27
|
+
}
|
|
28
|
+
function toPosix(p) {
|
|
29
|
+
return p.split(path.sep).join("/");
|
|
30
|
+
}
|
|
31
|
+
function isMarkdownCandidate(name) {
|
|
32
|
+
return name.length > 3 && !name.startsWith(".") && /\.md$/i.test(name);
|
|
33
|
+
}
|
|
34
|
+
function skipped(sourcePath, reason, code, message) {
|
|
35
|
+
return { sourcePath, status: "skipped", reason, code, message };
|
|
36
|
+
}
|
|
37
|
+
function failed(sourcePath, code, message) {
|
|
38
|
+
return { sourcePath, status: "failed", code, message };
|
|
39
|
+
}
|
|
40
|
+
function dirFailure(relDir, code, message) {
|
|
41
|
+
return {
|
|
42
|
+
ok: false,
|
|
43
|
+
result: {
|
|
44
|
+
dir: relDir,
|
|
45
|
+
scanned: 0,
|
|
46
|
+
created: [],
|
|
47
|
+
unchanged: [],
|
|
48
|
+
skipped: [],
|
|
49
|
+
failed: [],
|
|
50
|
+
dirError: { code, message },
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Confine the requested directory to the workspace (lexically *and* through
|
|
56
|
+
* symlinks), then confirm it is a directory. This is a second layer of defence
|
|
57
|
+
* over `readSource`, which independently confines every individual file.
|
|
58
|
+
*/
|
|
59
|
+
async function resolveScanDir(root, dir) {
|
|
60
|
+
const absDir = path.resolve(root, dir ?? createXcoderPaths(root).tasksDir());
|
|
61
|
+
const rel = path.relative(root, absDir);
|
|
62
|
+
const escapes = path.isAbsolute(rel) || rel === ".." || rel.startsWith(`..${path.sep}`) || rel.startsWith("..");
|
|
63
|
+
const relDir = toPosix(rel);
|
|
64
|
+
if (escapes) {
|
|
65
|
+
return dirFailure(relDir, "outside-workspace", `scan directory resolves outside the workspace (${absDir})`);
|
|
66
|
+
}
|
|
67
|
+
const realRoot = (await fs.realpath(root).catch(() => root)) ?? root;
|
|
68
|
+
const realDir = await fs.realpath(absDir).catch(() => null);
|
|
69
|
+
if (realDir === null) {
|
|
70
|
+
return dirFailure(relDir, "not-found", `scan directory not found: ${absDir}`);
|
|
71
|
+
}
|
|
72
|
+
const relFromRoot = path.relative(realRoot, realDir);
|
|
73
|
+
if (path.isAbsolute(relFromRoot) ||
|
|
74
|
+
relFromRoot === ".." ||
|
|
75
|
+
relFromRoot.startsWith(`..${path.sep}`)) {
|
|
76
|
+
return dirFailure(relDir, "outside-workspace", `scan directory resolves outside the workspace via a symlink (${realDir})`);
|
|
77
|
+
}
|
|
78
|
+
const stat = await fs.stat(realDir).catch(() => null);
|
|
79
|
+
if (!stat || !stat.isDirectory()) {
|
|
80
|
+
return dirFailure(relDir, "not-a-directory", `not a directory: ${absDir}`);
|
|
81
|
+
}
|
|
82
|
+
return { ok: true, absDir, relDir };
|
|
83
|
+
}
|
|
84
|
+
/** Map a real `ingestTask` result onto a scan outcome. */
|
|
85
|
+
function fromIngest(sourcePath, result) {
|
|
86
|
+
if (result.ok) {
|
|
87
|
+
return {
|
|
88
|
+
sourcePath,
|
|
89
|
+
taskId: result.taskId,
|
|
90
|
+
status: result.created ? "created" : "unchanged",
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
switch (result.code) {
|
|
94
|
+
case "no-items":
|
|
95
|
+
return skipped(sourcePath, "not-a-task", result.code, result.message);
|
|
96
|
+
case "has-progress":
|
|
97
|
+
return skipped(sourcePath, "has-progress", result.code, result.message);
|
|
98
|
+
case "id-collision":
|
|
99
|
+
return skipped(sourcePath, "id-collision", result.code, result.message);
|
|
100
|
+
default:
|
|
101
|
+
return failed(sourcePath, result.code, result.message);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Predict what `ingestTask` would do, without writing.
|
|
106
|
+
*
|
|
107
|
+
* Reuses `readSource`, `parseWbs` and `deriveTaskId` so the discovery rules are
|
|
108
|
+
* identical to the real path; only the persistence step is elided.
|
|
109
|
+
*/
|
|
110
|
+
async function predict(sourcePath, root, force) {
|
|
111
|
+
const source = await readSource(root, sourcePath);
|
|
112
|
+
if (!source.ok)
|
|
113
|
+
return failed(sourcePath, source.code, source.message);
|
|
114
|
+
const stem = path.basename(sourcePath, path.extname(sourcePath));
|
|
115
|
+
const parsed = parseWbs(source.value.bytes.toString("utf8"), stem);
|
|
116
|
+
if (!parsed.ok) {
|
|
117
|
+
if (parsed.code === "no-items")
|
|
118
|
+
return skipped(sourcePath, "not-a-task", parsed.code, parsed.message);
|
|
119
|
+
return failed(sourcePath, parsed.code, parsed.message);
|
|
120
|
+
}
|
|
121
|
+
// Same collision fallback as `ingestTask`.
|
|
122
|
+
let taskId = deriveTaskId(sourcePath, 8);
|
|
123
|
+
const primary = await loadTaskRun(root, taskId);
|
|
124
|
+
if (primary && primary.spec.inputs.sourcePath !== sourcePath) {
|
|
125
|
+
taskId = deriveTaskId(sourcePath, 12);
|
|
126
|
+
}
|
|
127
|
+
const existing = await loadTaskRun(root, taskId);
|
|
128
|
+
if (!existing)
|
|
129
|
+
return { sourcePath, taskId, status: "created" };
|
|
130
|
+
if (existing.spec.inputs.sourcePath !== sourcePath) {
|
|
131
|
+
return skipped(sourcePath, "id-collision", "id-collision", `derived id ${taskId} already belongs to a different source`);
|
|
132
|
+
}
|
|
133
|
+
if (hasProgress(existing) && !force) {
|
|
134
|
+
return skipped(sourcePath, "has-progress", "has-progress", `${taskId} already has completed or in-flight subtasks — re-run with --force to replace it`);
|
|
135
|
+
}
|
|
136
|
+
return { sourcePath, taskId, status: "unchanged" };
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Scan `dir` (default: the task store directory) for `.md` task sources and
|
|
140
|
+
* ingest each one.
|
|
141
|
+
*
|
|
142
|
+
* Never throws for a single bad file — every per-file problem is isolated into
|
|
143
|
+
* `failed` so one unreadable source cannot abort the batch.
|
|
144
|
+
*/
|
|
145
|
+
export async function scanMarkdownTasks(opts) {
|
|
146
|
+
const root = path.resolve(opts.workspaceRoot);
|
|
147
|
+
const resolved = await resolveScanDir(root, opts.dir);
|
|
148
|
+
if (!resolved.ok)
|
|
149
|
+
return resolved.result;
|
|
150
|
+
const { absDir, relDir } = resolved;
|
|
151
|
+
let entries;
|
|
152
|
+
try {
|
|
153
|
+
entries = await fs.readdir(absDir, { withFileTypes: true });
|
|
154
|
+
}
|
|
155
|
+
catch (e) {
|
|
156
|
+
return {
|
|
157
|
+
dir: relDir,
|
|
158
|
+
scanned: 0,
|
|
159
|
+
created: [],
|
|
160
|
+
unchanged: [],
|
|
161
|
+
skipped: [],
|
|
162
|
+
failed: [],
|
|
163
|
+
dirError: { code: "not-found", message: `could not read ${absDir}: ${e.message}` },
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
// Any `<stem>.json` beside a `<stem>.md` makes that markdown a derived
|
|
167
|
+
// progress summary, not a task source (D2). This guard runs before anything
|
|
168
|
+
// is read, so a progress mirror can never be re-ingested as a phantom task.
|
|
169
|
+
const present = new Set(entries.map((e) => e.name));
|
|
170
|
+
const candidates = entries
|
|
171
|
+
.filter((e) => (e.isFile() || e.isSymbolicLink()) && isMarkdownCandidate(e.name))
|
|
172
|
+
.map((e) => e.name)
|
|
173
|
+
.sort();
|
|
174
|
+
const result = {
|
|
175
|
+
dir: relDir,
|
|
176
|
+
scanned: candidates.length,
|
|
177
|
+
created: [],
|
|
178
|
+
unchanged: [],
|
|
179
|
+
skipped: [],
|
|
180
|
+
failed: [],
|
|
181
|
+
};
|
|
182
|
+
const force = opts.force === true;
|
|
183
|
+
for (const name of candidates) {
|
|
184
|
+
const stem = name.slice(0, -3);
|
|
185
|
+
const sourcePath = toPosix(path.relative(root, path.join(absDir, name)));
|
|
186
|
+
if (present.has(`${stem}.json`)) {
|
|
187
|
+
result.skipped.push(skipped(sourcePath, "progress-summary", "progress-summary", `${stem}.json sits beside it in the same directory — derived progress summary, not a task source`));
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
const outcome = opts.dryRun
|
|
192
|
+
? await predict(sourcePath, root, force)
|
|
193
|
+
: fromIngest(sourcePath, await ingestTask({
|
|
194
|
+
workspaceRoot: root,
|
|
195
|
+
from: sourcePath,
|
|
196
|
+
force,
|
|
197
|
+
now: opts.now,
|
|
198
|
+
concurrency: opts.concurrency,
|
|
199
|
+
}));
|
|
200
|
+
if (outcome.status === "created")
|
|
201
|
+
result.created.push(outcome);
|
|
202
|
+
else if (outcome.status === "unchanged")
|
|
203
|
+
result.unchanged.push(outcome);
|
|
204
|
+
else if (outcome.status === "skipped")
|
|
205
|
+
result.skipped.push(outcome);
|
|
206
|
+
else
|
|
207
|
+
result.failed.push(outcome);
|
|
208
|
+
}
|
|
209
|
+
catch (e) {
|
|
210
|
+
// Per-file failure isolation (D7): one unexpected throw never aborts the
|
|
211
|
+
// batch. `saveTaskRun` is atomic, so no partial `.tmp` survives either.
|
|
212
|
+
result.failed.push(failed(sourcePath, "internal-error", e.message));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return result;
|
|
216
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export interface SourceFile {
|
|
2
|
+
/** Resolved path, guaranteed to be inside the workspace. */
|
|
3
|
+
absPath: string;
|
|
4
|
+
/** Workspace-relative POSIX path — the idempotency key. */
|
|
5
|
+
relPathPosix: string;
|
|
6
|
+
bytes: Buffer;
|
|
7
|
+
/** Hex sha256 of the exact bytes ingested. */
|
|
8
|
+
sha256: string;
|
|
9
|
+
}
|
|
10
|
+
export type ReadSourceErrorCode = "not-found" | "outside-workspace" | "too-large" | "not-a-file";
|
|
11
|
+
export type ReadSourceResult = {
|
|
12
|
+
ok: true;
|
|
13
|
+
value: SourceFile;
|
|
14
|
+
} | {
|
|
15
|
+
ok: false;
|
|
16
|
+
code: ReadSourceErrorCode;
|
|
17
|
+
message: string;
|
|
18
|
+
};
|
|
19
|
+
/** Hard cap so ingestion can't be used to read a giant file into memory. */
|
|
20
|
+
export declare const MAX_SOURCE_BYTES: number;
|
|
21
|
+
/**
|
|
22
|
+
* Resolve `from` against `workspaceRoot`, refuse anything that escapes the
|
|
23
|
+
* workspace (including through a symlink), enforce the size cap, and read.
|
|
24
|
+
*/
|
|
25
|
+
export declare function readSource(workspaceRoot: string, from: string): Promise<ReadSourceResult>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// xcoder:version 1 | xcoder:task task-a91099 | xcoder:updated 2026-10-23T10:52:34.657Z | xcoder:subtask code-st-03748c
|
|
2
|
+
// C1 — resolve, confine, and read a user-supplied markdown/WBS source file.
|
|
3
|
+
//
|
|
4
|
+
// The source file is HOSTILE input (prompt-injection bylaw PRM-01): it may
|
|
5
|
+
// carry ANSI escapes, bidi overrides, or instructions aimed at a future model
|
|
6
|
+
// turn. This module's only job is to hand back bounded, path-confined bytes;
|
|
7
|
+
// nothing downstream may ever treat those bytes as an instruction.
|
|
8
|
+
import { promises as fs } from "node:fs";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import crypto from "node:crypto";
|
|
11
|
+
/** Hard cap so ingestion can't be used to read a giant file into memory. */
|
|
12
|
+
export const MAX_SOURCE_BYTES = 1024 * 1024; // 1 MiB
|
|
13
|
+
function within(child, parent) {
|
|
14
|
+
const rel = path.relative(parent, child);
|
|
15
|
+
return rel.length > 0 && !rel.startsWith(".." + path.sep) && rel !== ".." && !path.isAbsolute(rel);
|
|
16
|
+
}
|
|
17
|
+
async function realpathOrNull(p) {
|
|
18
|
+
try {
|
|
19
|
+
return await fs.realpath(p);
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve `from` against `workspaceRoot`, refuse anything that escapes the
|
|
27
|
+
* workspace (including through a symlink), enforce the size cap, and read.
|
|
28
|
+
*/
|
|
29
|
+
export async function readSource(workspaceRoot, from) {
|
|
30
|
+
const root = path.resolve(workspaceRoot);
|
|
31
|
+
const abs = path.resolve(root, from);
|
|
32
|
+
// 1. Lexical confinement: reject absolute escapes, `..`, and the root itself.
|
|
33
|
+
const rel = path.relative(root, abs);
|
|
34
|
+
if (rel.length === 0) {
|
|
35
|
+
return { ok: false, code: "not-a-file", message: `"${from}" resolves to the workspace root, not a file` };
|
|
36
|
+
}
|
|
37
|
+
if (path.isAbsolute(rel) || rel.startsWith("..")) {
|
|
38
|
+
return {
|
|
39
|
+
ok: false,
|
|
40
|
+
code: "outside-workspace",
|
|
41
|
+
message: `"${from}" resolves outside the workspace (${abs})`,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
// 2. Physical confinement: a symlink inside the workspace may still point out.
|
|
45
|
+
const realRoot = (await realpathOrNull(root)) ?? root;
|
|
46
|
+
const realAbs = await realpathOrNull(abs);
|
|
47
|
+
if (realAbs === null) {
|
|
48
|
+
return { ok: false, code: "not-found", message: `source file not found: ${abs}` };
|
|
49
|
+
}
|
|
50
|
+
if (!within(realAbs, realRoot)) {
|
|
51
|
+
return {
|
|
52
|
+
ok: false,
|
|
53
|
+
code: "outside-workspace",
|
|
54
|
+
message: `"${from}" resolves outside the workspace via a symlink (${realAbs})`,
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
// 3. Must be a regular file; size checked BEFORE reading.
|
|
58
|
+
let stat;
|
|
59
|
+
try {
|
|
60
|
+
stat = await fs.stat(realAbs);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return { ok: false, code: "not-found", message: `source file not found: ${abs}` };
|
|
64
|
+
}
|
|
65
|
+
if (!stat.isFile()) {
|
|
66
|
+
return { ok: false, code: "not-a-file", message: `not a regular file: ${abs}` };
|
|
67
|
+
}
|
|
68
|
+
if (stat.size > MAX_SOURCE_BYTES) {
|
|
69
|
+
return {
|
|
70
|
+
ok: false,
|
|
71
|
+
code: "too-large",
|
|
72
|
+
message: `source file is ${stat.size} bytes, over the ${MAX_SOURCE_BYTES}-byte limit: ${abs}`,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
let bytes;
|
|
76
|
+
try {
|
|
77
|
+
bytes = await fs.readFile(realAbs);
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
return { ok: false, code: "not-found", message: `could not read ${abs}: ${e.message}` };
|
|
81
|
+
}
|
|
82
|
+
// 4. Re-check after read — the file may have grown between stat and read.
|
|
83
|
+
if (bytes.length > MAX_SOURCE_BYTES) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
code: "too-large",
|
|
87
|
+
message: `source file is ${bytes.length} bytes, over the ${MAX_SOURCE_BYTES}-byte limit: ${abs}`,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
const sha256 = crypto.createHash("sha256").update(bytes).digest("hex");
|
|
91
|
+
const relPathPosix = rel.split(path.sep).join("/");
|
|
92
|
+
return { ok: true, value: { absPath: abs, relPathPosix, bytes, sha256 } };
|
|
93
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { SdlcStage, SubtaskNode, TaskSpec } from "../types.js";
|
|
2
|
+
import type { ParsedWbs, WbsItem } from "./wbs-parser.js";
|
|
3
|
+
export interface BuildInput {
|
|
4
|
+
parsed: ParsedWbs;
|
|
5
|
+
source: {
|
|
6
|
+
relPathPosix: string;
|
|
7
|
+
sha256: string;
|
|
8
|
+
};
|
|
9
|
+
/** Supplied by C4 so this function stays pure. */
|
|
10
|
+
taskId: string;
|
|
11
|
+
/** Supplied by C4 so re-ingest can preserve the original timestamp. */
|
|
12
|
+
createdAt: string;
|
|
13
|
+
/** Default concurrency, from the CLI. */
|
|
14
|
+
concurrency: number;
|
|
15
|
+
maxHealingAttempts?: number;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Keyword-to-stage table. Data, not logic — first match wins, applied to the
|
|
19
|
+
* lower-cased item text. Extend here without touching the builder.
|
|
20
|
+
*/
|
|
21
|
+
export declare const KEYWORD_STAGE_TABLE: ReadonlyArray<{
|
|
22
|
+
pattern: RegExp;
|
|
23
|
+
stage: SdlcStage;
|
|
24
|
+
}>;
|
|
25
|
+
export declare function stageForItem(item: WbsItem): SdlcStage;
|
|
26
|
+
/**
|
|
27
|
+
* Build the TaskSpec and the DAG. Uses the existing `getStageRole`,
|
|
28
|
+
* `deliverableFor` and `acceptanceCriteriaFor` helpers so the ingested task
|
|
29
|
+
* speaks exactly the same vocabulary as a free-text one.
|
|
30
|
+
*/
|
|
31
|
+
export declare function buildIngestedRun(input: BuildInput): {
|
|
32
|
+
spec: TaskSpec;
|
|
33
|
+
dag: SubtaskNode[];
|
|
34
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// xcoder:version 2 | xcoder:task task-a91099 | xcoder:updated 2026-10-23T10:55:14.056Z | xcoder:subtask code-st-03748c
|
|
2
|
+
// C3 — deterministic, PURE mapping from a parsed WBS to a valid TaskRun.
|
|
3
|
+
//
|
|
4
|
+
// No I/O, no clock, no randomness, no LLM. Every input that varies (taskId,
|
|
5
|
+
// createdAt) is supplied by C4 so re-ingesting an unchanged source produces a
|
|
6
|
+
// byte-identical record.
|
|
7
|
+
import crypto from "node:crypto";
|
|
8
|
+
import { getStageRole } from "../sdlc-runtime/sdlc-skill.js";
|
|
9
|
+
import { deliverableFor, acceptanceCriteriaFor } from "../orchestrator/task-factory.js";
|
|
10
|
+
/**
|
|
11
|
+
* Keyword-to-stage table. Data, not logic — first match wins, applied to the
|
|
12
|
+
* lower-cased item text. Extend here without touching the builder.
|
|
13
|
+
*/
|
|
14
|
+
export const KEYWORD_STAGE_TABLE = [
|
|
15
|
+
{ pattern: /\b(requirements?|specs?|acceptance criteri(?:on|a)|user stor(?:y|ies))\b/i, stage: "requirements" },
|
|
16
|
+
{ pattern: /\b(designs?|architects?|architectural|blueprints?|schemas?|contracts?)\b/i, stage: "design" },
|
|
17
|
+
{ pattern: /\b(tests?|suites?|coverage|vitest|playwright)\b/i, stage: "test" },
|
|
18
|
+
{ pattern: /\b(docs?|readme|readmes?|guides?|changelogs?|document(?:s|ation)?|documenting)\b/i, stage: "document" },
|
|
19
|
+
{ pattern: /\b(deploys?|deploying|deployment|releases?|installers?|publish(?:es|ing)?)\b/i, stage: "deploy" },
|
|
20
|
+
{ pattern: /\b(fix(?:es|ing)?|bugs?|defects?|regressions?|crashes?|crash)\b/i, stage: "fix_defect" },
|
|
21
|
+
];
|
|
22
|
+
export function stageForItem(item) {
|
|
23
|
+
if (item.stage)
|
|
24
|
+
return item.stage;
|
|
25
|
+
for (const entry of KEYWORD_STAGE_TABLE) {
|
|
26
|
+
if (entry.pattern.test(item.text))
|
|
27
|
+
return entry.stage;
|
|
28
|
+
}
|
|
29
|
+
return "requirements";
|
|
30
|
+
}
|
|
31
|
+
/** stage-<first 6 hex of sha256(relPath + "#" + index)> — stable across runs. */
|
|
32
|
+
function deterministicNodeId(stage, relPathPosix, index) {
|
|
33
|
+
const digest = crypto.createHash("sha256").update(`${relPathPosix}#${index}`).digest("hex");
|
|
34
|
+
return `${stage}-${digest.slice(0, 6)}`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build the TaskSpec and the DAG. Uses the existing `getStageRole`,
|
|
38
|
+
* `deliverableFor` and `acceptanceCriteriaFor` helpers so the ingested task
|
|
39
|
+
* speaks exactly the same vocabulary as a free-text one.
|
|
40
|
+
*/
|
|
41
|
+
export function buildIngestedRun(input) {
|
|
42
|
+
const { parsed, source, taskId, createdAt, concurrency } = input;
|
|
43
|
+
const maxHealingAttempts = input.maxHealingAttempts ?? 2;
|
|
44
|
+
const spec = {
|
|
45
|
+
taskId,
|
|
46
|
+
goal: parsed.goal,
|
|
47
|
+
// Deliberately ONLY freeText + sourcePath. code / design / url /
|
|
48
|
+
// defectDetail / credentialRef are never populated from file content —
|
|
49
|
+
// that is what would let a hostile document steer intake classification
|
|
50
|
+
// or a credential lookup.
|
|
51
|
+
inputs: { freeText: parsed.goal, sourcePath: source.relPathPosix },
|
|
52
|
+
constraints: { maxHealingAttempts, concurrency },
|
|
53
|
+
createdAt,
|
|
54
|
+
provenance: {
|
|
55
|
+
sourcePath: source.relPathPosix,
|
|
56
|
+
sourceSha256: source.sha256,
|
|
57
|
+
ingestedAt: createdAt,
|
|
58
|
+
ingestVersion: 1,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
const dag = [];
|
|
62
|
+
let previousId = null;
|
|
63
|
+
for (const item of parsed.items) {
|
|
64
|
+
const stage = stageForItem(item);
|
|
65
|
+
const id = deterministicNodeId(stage, source.relPathPosix, item.index);
|
|
66
|
+
let dependsOn;
|
|
67
|
+
if (typeof item.after === "number" && item.after >= 1 && item.after <= dag.length) {
|
|
68
|
+
dependsOn = [dag[item.after - 1].id];
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
dependsOn = previousId ? [previousId] : [];
|
|
72
|
+
}
|
|
73
|
+
dag.push({
|
|
74
|
+
id,
|
|
75
|
+
stage,
|
|
76
|
+
role: getStageRole(stage, spec.inputs),
|
|
77
|
+
engine: "react-v1",
|
|
78
|
+
dependsOn,
|
|
79
|
+
goal: item.text,
|
|
80
|
+
upstreamContext: {},
|
|
81
|
+
deliverableSpec: deliverableFor(stage),
|
|
82
|
+
acceptanceCriteria: acceptanceCriteriaFor(stage),
|
|
83
|
+
status: dag.length === 0 ? "ready" : "pending",
|
|
84
|
+
healing: { attempts: 0, maxAttempts: maxHealingAttempts, log: [] },
|
|
85
|
+
});
|
|
86
|
+
previousId = id;
|
|
87
|
+
}
|
|
88
|
+
return { spec, dag };
|
|
89
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { SdlcStage } from "../types.js";
|
|
2
|
+
import { type ArchitectDetailer } from "./architect-detailer.js";
|
|
3
|
+
export interface ExtractOptions {
|
|
4
|
+
workspaceRoot: string;
|
|
5
|
+
/** Exactly what the user typed after --wbc. Confined by readSource(). */
|
|
6
|
+
from: string;
|
|
7
|
+
dryRun?: boolean;
|
|
8
|
+
force?: boolean;
|
|
9
|
+
/** Omit for the LLM detailer; pass null for deterministic content. */
|
|
10
|
+
detailer?: ArchitectDetailer | null;
|
|
11
|
+
/** Default 50, hard max MAX_ITEMS (200). */
|
|
12
|
+
maxItems?: number;
|
|
13
|
+
/** Default 3, clamped 1..8. */
|
|
14
|
+
concurrency?: number;
|
|
15
|
+
now?: () => Date;
|
|
16
|
+
}
|
|
17
|
+
export type ExtractErrorCode = "not-found" | "outside-workspace" | "too-large" | "not-a-file" | "bad-front-matter" | "no-items" | "too-many-items" | "bad-options" | "too-many-pending" | "architect-failed" | "architect-schema-invalid" | "has-progress" | "id-collision" | "write-failed";
|
|
18
|
+
export interface ExtractedTask {
|
|
19
|
+
taskId: string;
|
|
20
|
+
line: number;
|
|
21
|
+
order: number;
|
|
22
|
+
/** false => an identical record was already on disk ("unchanged"). */
|
|
23
|
+
created: boolean;
|
|
24
|
+
supersededBy?: string;
|
|
25
|
+
nodeCount: number;
|
|
26
|
+
stages: SdlcStage[];
|
|
27
|
+
nextCommand: string;
|
|
28
|
+
}
|
|
29
|
+
export type ExtractResult = {
|
|
30
|
+
ok: true;
|
|
31
|
+
sourcePath: string;
|
|
32
|
+
sourceSha256: string;
|
|
33
|
+
dryRun: boolean;
|
|
34
|
+
counts: {
|
|
35
|
+
pending: number;
|
|
36
|
+
checked: number;
|
|
37
|
+
created: number;
|
|
38
|
+
unchanged: number;
|
|
39
|
+
superseded: number;
|
|
40
|
+
};
|
|
41
|
+
tasks: ExtractedTask[];
|
|
42
|
+
} | {
|
|
43
|
+
ok: false;
|
|
44
|
+
exitCode: 1;
|
|
45
|
+
code: ExtractErrorCode;
|
|
46
|
+
message: string;
|
|
47
|
+
line?: number;
|
|
48
|
+
taskId?: string;
|
|
49
|
+
};
|
|
50
|
+
export declare function extractWbsTasks(opts: ExtractOptions): Promise<ExtractResult>;
|