xcoder-dna 0.0.0-stage → 1.3.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/LICENSE +66 -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 +39 -4
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// xcoder:version 1 | xcoder:task task-8c3c2b | xcoder:updated 2026-08-04T08:26:22.973Z | xcoder:subtask code-st-71e0aa
|
|
2
|
+
/**
|
|
3
|
+
* Sprint Controller — coordinates the full sprint lifecycle.
|
|
4
|
+
*
|
|
5
|
+
* Design contract: `.xcoder/design/sprint-framework-design.md` §4.1
|
|
6
|
+
*/
|
|
7
|
+
import { promises as fs } from "node:fs";
|
|
8
|
+
import { SdlcOrchestrator } from "../sdlc-runtime/sdlc-orchestrator.js";
|
|
9
|
+
import { buildTaskSpec } from "../orchestrator/task-factory.js";
|
|
10
|
+
import { listBacklogItems, updateBacklogItem } from "../backlog/backlog-store.js";
|
|
11
|
+
import { runPoPick } from "../backlog/po-agent.js";
|
|
12
|
+
import { shortId } from "../util/ids.js";
|
|
13
|
+
import { log } from "../util/logger.js";
|
|
14
|
+
import { buildSprintDag } from "./dag-builder.js";
|
|
15
|
+
import { saveSprintRun, loadActiveSprintRun, loadSprintRun, deleteSprintRun, listSprintRuns, } from "./state-store.js";
|
|
16
|
+
import { writeGateRejectionReport, writeRetroReport, } from "./reporter.js";
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// sprintStart
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
export async function sprintStart(opts) {
|
|
21
|
+
const { workspaceRoot: root, llm, concurrency } = opts;
|
|
22
|
+
// 1. Load open backlog items.
|
|
23
|
+
const openItems = await listBacklogItems(root, "open");
|
|
24
|
+
if (openItems.length === 0) {
|
|
25
|
+
throw new Error("No open backlog items available for a sprint.");
|
|
26
|
+
}
|
|
27
|
+
// 2. Use PO Agent to pick the highest-priority eligible item.
|
|
28
|
+
const pickResult = await runPoPick(root, openItems);
|
|
29
|
+
const sprintRunId = `${pickResult.selectedId}-sprint-${shortId("sr")}`;
|
|
30
|
+
log.ok(`PO picked: ${pickResult.selectedId} — ${pickResult.title}`);
|
|
31
|
+
log.dim(`Reasoning: ${pickResult.reasoning}`);
|
|
32
|
+
// 3. Update the item: sprintId, status=in-progress, sprintRunAt.
|
|
33
|
+
const now = new Date().toISOString();
|
|
34
|
+
await updateBacklogItem(root, pickResult.selectedId, {
|
|
35
|
+
sprintId: sprintRunId,
|
|
36
|
+
status: "in-progress",
|
|
37
|
+
sprintRunAt: now,
|
|
38
|
+
});
|
|
39
|
+
log.ok(`${pickResult.selectedId} → in-progress (sprint: ${sprintRunId})`);
|
|
40
|
+
// 4. Build a TaskSpec from the backlog item.
|
|
41
|
+
const taskText = pickResult.title;
|
|
42
|
+
const spec = buildTaskSpec(taskText, {}, {
|
|
43
|
+
budgetUsd: undefined,
|
|
44
|
+
maxHealingAttempts: 2,
|
|
45
|
+
concurrency,
|
|
46
|
+
});
|
|
47
|
+
log.orchestrator(`task ${spec.taskId}: ${spec.goal}`);
|
|
48
|
+
// 5. Build sprint DAG with quality-gate nodes.
|
|
49
|
+
const dag = buildSprintDag(spec);
|
|
50
|
+
// 6. Create and save SprintRun.
|
|
51
|
+
const sprintRun = {
|
|
52
|
+
sprintRunId,
|
|
53
|
+
backlogItemId: pickResult.selectedId,
|
|
54
|
+
taskId: spec.taskId,
|
|
55
|
+
status: "running",
|
|
56
|
+
dag,
|
|
57
|
+
currentStage: "requirements",
|
|
58
|
+
gateResults: [],
|
|
59
|
+
startedAt: now,
|
|
60
|
+
completedAt: null,
|
|
61
|
+
totalTokenUsage: { inputTokens: 0, outputTokens: 0 },
|
|
62
|
+
};
|
|
63
|
+
await saveSprintRun(root, sprintRun);
|
|
64
|
+
// 7. Run via the SdlcOrchestrator with the augmented DAG.
|
|
65
|
+
const orchestrator = new SdlcOrchestrator({
|
|
66
|
+
workspaceRoot: root,
|
|
67
|
+
llm,
|
|
68
|
+
concurrency,
|
|
69
|
+
});
|
|
70
|
+
const taskRun = await orchestrator.run(spec, dag);
|
|
71
|
+
// Accumulate token usage.
|
|
72
|
+
sprintRun.totalTokenUsage = taskRun.totalTokenUsage;
|
|
73
|
+
// 8. Post-orchestrator: check each gate node's result from the orchestrator's DAG.
|
|
74
|
+
const gateFailures = extractGateFailures(taskRun.dag);
|
|
75
|
+
sprintRun.gateResults = gateFailures;
|
|
76
|
+
if (gateFailures.some((g) => !g.passed)) {
|
|
77
|
+
// At least one gate failed — mark item as blocked.
|
|
78
|
+
const firstFailure = gateFailures.find((g) => !g.passed);
|
|
79
|
+
sprintRun.status = "blocked";
|
|
80
|
+
sprintRun.completedAt = new Date().toISOString();
|
|
81
|
+
await saveSprintRun(root, sprintRun);
|
|
82
|
+
await updateBacklogItem(root, pickResult.selectedId, {
|
|
83
|
+
blockedReason: `${firstFailure.role}: ${firstFailure.reason}`,
|
|
84
|
+
});
|
|
85
|
+
await writeGateRejectionReport(root, pickResult.selectedId, sprintRunId, firstFailure);
|
|
86
|
+
log.fail(`Sprint ${sprintRunId} blocked by ${firstFailure.role} gate: ${firstFailure.reason}`);
|
|
87
|
+
return {
|
|
88
|
+
sprintRunId,
|
|
89
|
+
backlogItemId: pickResult.selectedId,
|
|
90
|
+
taskId: spec.taskId,
|
|
91
|
+
outcome: "blocked",
|
|
92
|
+
gateFailures,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
// All gates passed — mark item as done.
|
|
96
|
+
sprintRun.status = "done";
|
|
97
|
+
sprintRun.completedAt = new Date().toISOString();
|
|
98
|
+
await saveSprintRun(root, sprintRun);
|
|
99
|
+
await updateBacklogItem(root, pickResult.selectedId, {
|
|
100
|
+
status: "done",
|
|
101
|
+
taskId: spec.taskId,
|
|
102
|
+
sprintId: null,
|
|
103
|
+
blockedReason: null,
|
|
104
|
+
});
|
|
105
|
+
// Write retrospective.
|
|
106
|
+
await writeRetroReport(root, sprintRun);
|
|
107
|
+
log.ok(`Sprint ${sprintRunId} completed successfully — ${pickResult.selectedId} marked done.`);
|
|
108
|
+
return {
|
|
109
|
+
sprintRunId,
|
|
110
|
+
backlogItemId: pickResult.selectedId,
|
|
111
|
+
taskId: spec.taskId,
|
|
112
|
+
outcome: "done",
|
|
113
|
+
gateFailures,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// sprintStatus
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
export async function sprintStatus(root) {
|
|
120
|
+
const active = await loadActiveSprintRun(root);
|
|
121
|
+
if (!active) {
|
|
122
|
+
log.dim("No active sprint found.");
|
|
123
|
+
return { active: false };
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
active: true,
|
|
127
|
+
sprintRunId: active.sprintRunId,
|
|
128
|
+
backlogItemId: active.backlogItemId,
|
|
129
|
+
currentStage: active.currentStage,
|
|
130
|
+
taskStatus: active.status,
|
|
131
|
+
gateResults: active.gateResults,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// sprintRetro
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
export async function sprintRetro(root, sprintRunId) {
|
|
138
|
+
let run = null;
|
|
139
|
+
if (sprintRunId) {
|
|
140
|
+
run = await loadSprintRun(root, sprintRunId);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
// Default: most recent non-running sprint.
|
|
144
|
+
const all = await listSprintRuns(root);
|
|
145
|
+
const completed = all.filter((r) => r.status === "done" || r.status === "aborted" || r.status === "blocked");
|
|
146
|
+
if (completed.length > 0) {
|
|
147
|
+
completed.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
|
|
148
|
+
run = completed[0];
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
if (!run) {
|
|
152
|
+
throw new Error(sprintRunId
|
|
153
|
+
? `Sprint run "${sprintRunId}" not found.`
|
|
154
|
+
: "No completed sprint runs found for retrospective.");
|
|
155
|
+
}
|
|
156
|
+
const reportPath = await writeRetroReport(root, run);
|
|
157
|
+
// Read back the contents.
|
|
158
|
+
try {
|
|
159
|
+
const contents = await fs.readFile(reportPath, "utf-8");
|
|
160
|
+
return { reportPath, contents };
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
return { reportPath, contents: "" };
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
// ---------------------------------------------------------------------------
|
|
167
|
+
// sprintAbort
|
|
168
|
+
// ---------------------------------------------------------------------------
|
|
169
|
+
export async function sprintAbort(root) {
|
|
170
|
+
const active = await loadActiveSprintRun(root);
|
|
171
|
+
if (!active) {
|
|
172
|
+
throw new Error("No active sprint to abort.");
|
|
173
|
+
}
|
|
174
|
+
// Reset the item.
|
|
175
|
+
await updateBacklogItem(root, active.backlogItemId, {
|
|
176
|
+
sprintId: null,
|
|
177
|
+
blockedReason: null,
|
|
178
|
+
status: "open",
|
|
179
|
+
});
|
|
180
|
+
// Remove the sprint run file.
|
|
181
|
+
await deleteSprintRun(root, active.sprintRunId);
|
|
182
|
+
log.ok(`Sprint ${active.sprintRunId} aborted — ${active.backlogItemId} reverted to open.`);
|
|
183
|
+
return { revertedItemId: active.backlogItemId };
|
|
184
|
+
}
|
|
185
|
+
// ---------------------------------------------------------------------------
|
|
186
|
+
// Helpers
|
|
187
|
+
// ---------------------------------------------------------------------------
|
|
188
|
+
/**
|
|
189
|
+
* Walk the DAG and extract pass/fail results from quality-gate nodes.
|
|
190
|
+
*/
|
|
191
|
+
function extractGateFailures(dag) {
|
|
192
|
+
const results = [];
|
|
193
|
+
for (const node of dag) {
|
|
194
|
+
if (node.stage === "quality_gate" &&
|
|
195
|
+
(node.role === "scrum-master" ||
|
|
196
|
+
node.role === "system-architect" ||
|
|
197
|
+
node.role === "security-lead")) {
|
|
198
|
+
const role = node.role;
|
|
199
|
+
if (node.result?.deliverable) {
|
|
200
|
+
const d = node.result.deliverable;
|
|
201
|
+
results.push({
|
|
202
|
+
role,
|
|
203
|
+
passed: Boolean(d.passed),
|
|
204
|
+
reason: String(d.reason ?? "No reason provided"),
|
|
205
|
+
subtaskId: node.id,
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
// Node didn't produce a result — likely never ran or crashed.
|
|
210
|
+
results.push({
|
|
211
|
+
role,
|
|
212
|
+
passed: false,
|
|
213
|
+
reason: `Gate node ${node.id} did not produce a result (status: ${node.status})`,
|
|
214
|
+
subtaskId: node.id,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
return results;
|
|
220
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sprint DAG Builder — extends the default linear SDLC DAG by inserting
|
|
3
|
+
* quality‑gate SubtaskNodes after specific stages.
|
|
4
|
+
*
|
|
5
|
+
* Pipeline:
|
|
6
|
+
* requirements → [Scrum Master gate] → design → [System Architect gate]
|
|
7
|
+
* → code → test → [Security Lead gate] → done
|
|
8
|
+
*/
|
|
9
|
+
import type { SubtaskNode, TaskSpec } from "../types.js";
|
|
10
|
+
/**
|
|
11
|
+
* Build a sprint DAG from a TaskSpec by inserting quality-gate nodes
|
|
12
|
+
* into the default linear plan at specific checkpoints.
|
|
13
|
+
*/
|
|
14
|
+
export declare function buildSprintDag(spec: TaskSpec): SubtaskNode[];
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// xcoder:version 4 | xcoder:task task-f1ac77 | xcoder:updated 2026-10-17T04:10:51.927Z | xcoder:subtask code-st-1d0003
|
|
2
|
+
/**
|
|
3
|
+
* Sprint DAG Builder — extends the default linear SDLC DAG by inserting
|
|
4
|
+
* quality‑gate SubtaskNodes after specific stages.
|
|
5
|
+
*
|
|
6
|
+
* Pipeline:
|
|
7
|
+
* requirements → [Scrum Master gate] → design → [System Architect gate]
|
|
8
|
+
* → code → test → [Security Lead gate] → done
|
|
9
|
+
*/
|
|
10
|
+
import { buildInitialPlan } from "../orchestrator/task-factory.js";
|
|
11
|
+
import { shortId } from "../util/ids.js";
|
|
12
|
+
import { inferLayers } from "../skills/secure-architect.bylaws.js";
|
|
13
|
+
/**
|
|
14
|
+
* Build a sprint DAG from a TaskSpec by inserting quality-gate nodes
|
|
15
|
+
* into the default linear plan at specific checkpoints.
|
|
16
|
+
*/
|
|
17
|
+
export function buildSprintDag(spec) {
|
|
18
|
+
const baseDag = buildInitialPlan(spec);
|
|
19
|
+
return insertQualityGates(baseDag);
|
|
20
|
+
}
|
|
21
|
+
function insertQualityGates(nodes) {
|
|
22
|
+
const result = [];
|
|
23
|
+
for (const node of nodes) {
|
|
24
|
+
result.push(node);
|
|
25
|
+
// Insert quality gates after specific stages.
|
|
26
|
+
if (node.stage === "requirements") {
|
|
27
|
+
result.push(buildGateNode("scrum-master", node, gateDirectives.scrumMaster));
|
|
28
|
+
}
|
|
29
|
+
else if (node.stage === "design") {
|
|
30
|
+
result.push(buildGateNode("system-architect", node, gateDirectives.systemArchitect));
|
|
31
|
+
}
|
|
32
|
+
else if (node.stage === "test") {
|
|
33
|
+
result.push(buildGateNode("security-lead", node, gateDirectives.securityLead));
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
function buildGateNode(role, reviewed, goal) {
|
|
39
|
+
const id = `gate-${role.replace(/-/g, "-")}-${shortId("st")}`;
|
|
40
|
+
const acceptanceCriteria = [
|
|
41
|
+
{
|
|
42
|
+
id: "AC1",
|
|
43
|
+
type: "schema",
|
|
44
|
+
description: "deliverable contains passed: boolean and reason: string",
|
|
45
|
+
},
|
|
46
|
+
];
|
|
47
|
+
if (role === "security-lead") {
|
|
48
|
+
// Second, independent check: the agent's own `passed` claim is AC1, the
|
|
49
|
+
// bylaws module's verdict over its own findings is AC2. Disagreement
|
|
50
|
+
// between them fails the gate. Scope is frozen here, at build time,
|
|
51
|
+
// because the gate's own goal is static boilerplate and re-inferring it
|
|
52
|
+
// later would silently narrow scope to core + cross-cutting.
|
|
53
|
+
acceptanceCriteria.push({
|
|
54
|
+
id: "AC2",
|
|
55
|
+
type: "bylaw-review",
|
|
56
|
+
description: "every in-scope bylaw has a finding; no failed MUST/MUST_NOT; " +
|
|
57
|
+
"SHOULD deviations carry a named riskOwner",
|
|
58
|
+
bylawLayers: [...inferLayers(reviewed.goal)],
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
id,
|
|
63
|
+
stage: "quality_gate",
|
|
64
|
+
role,
|
|
65
|
+
engine: "react-v1",
|
|
66
|
+
dependsOn: [reviewed.id],
|
|
67
|
+
goal,
|
|
68
|
+
upstreamContext: { reviewedNodeId: reviewed.id, reviewedGoal: reviewed.goal },
|
|
69
|
+
deliverableSpec: role === "security-lead"
|
|
70
|
+
? "A verdict — must be a JSON object { passed: boolean, reason: string, findings: [{ bylawId, status, note?, riskOwner? }] } in the final_answer deliverable, with exactly one finding per in-scope bylaw."
|
|
71
|
+
: "A pass/fail verdict with a reason — must be a JSON object { passed: boolean, reason: string } in the final_answer deliverable.",
|
|
72
|
+
acceptanceCriteria,
|
|
73
|
+
status: "pending",
|
|
74
|
+
healing: { attempts: 0, maxAttempts: 1, log: [] },
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
const gateDirectives = {
|
|
78
|
+
scrumMaster: `
|
|
79
|
+
Review the requirements document produced by the requirements-analyst stage
|
|
80
|
+
immediately before this gate. Validate:
|
|
81
|
+
1. The document exists and is well‑formed (has Goal, Non‑Goals, Constraints,
|
|
82
|
+
Acceptance Criteria sections).
|
|
83
|
+
2. All acceptance criteria are concrete and testable — not hand‑wavy.
|
|
84
|
+
3. Every open question from the requirements stage is either answered or
|
|
85
|
+
explicitly deferred with a stakeholder note.
|
|
86
|
+
Return a JSON object: { "passed": true/false, "reason": "..." }.`.trim(),
|
|
87
|
+
systemArchitect: `
|
|
88
|
+
Review the design document produced by the architect stage immediately
|
|
89
|
+
before this gate. Validate:
|
|
90
|
+
1. Every component named in the requirements has a defined interface.
|
|
91
|
+
2. Every import/reference points to a file that actually exists in the workspace
|
|
92
|
+
index (use the glob tool to verify).
|
|
93
|
+
3. No new pattern‑breaking dependency is introduced — the design must conform
|
|
94
|
+
to existing workspace conventions (check the workspace index for a
|
|
95
|
+
package.json, tsconfig, eslint config, etc.).
|
|
96
|
+
Return a JSON object: { "passed": true/false, "reason": "..." }.`.trim(),
|
|
97
|
+
securityLead: `
|
|
98
|
+
Run the project's security checks:
|
|
99
|
+
1. Execute "npm audit" (or appropriate command from the project's tooling)
|
|
100
|
+
and parse its output. The default threshold for blocking is: any "critical"
|
|
101
|
+
or "high" severity finding blocks the sprint.
|
|
102
|
+
2. Search the workspace for common vulnerability patterns: hard‑coded secrets,
|
|
103
|
+
unvalidated user input reaching eval/system calls, missing auth guards.
|
|
104
|
+
Return a JSON object: { "passed": true/false, "reason": "..." }.`.trim(),
|
|
105
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { GateResult, SprintRun } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Write a gate rejection report for a specific failed gate.
|
|
4
|
+
* Returns the absolute path to the written file.
|
|
5
|
+
*/
|
|
6
|
+
export declare function writeGateRejectionReport(root: string, backlogItemId: string, sprintRunId: string, gate: GateResult): Promise<string>;
|
|
7
|
+
/**
|
|
8
|
+
* Write a sprint retrospective report from a completed or aborted SprintRun.
|
|
9
|
+
* Returns the absolute path to the written file.
|
|
10
|
+
*/
|
|
11
|
+
export declare function writeRetroReport(root: string, run: SprintRun): Promise<string>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
// xcoder:version 1 | xcoder:task task-b190f1 | xcoder:updated 2026-08-03T10:31:20.691Z | xcoder:subtask code-st-167a5d
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { getOutputPaths } from "../util/output-paths.js";
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// Path resolution
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
function reportsDir(root) {
|
|
9
|
+
// The root parameter is preserved for backward compatibility but no longer
|
|
10
|
+
// drives the path — OutputPaths singleton is the single source of truth.
|
|
11
|
+
return getOutputPaths({ cwd: root }).sprintReportsDir();
|
|
12
|
+
}
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
// Gate rejection report
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
/**
|
|
17
|
+
* Write a gate rejection report for a specific failed gate.
|
|
18
|
+
* Returns the absolute path to the written file.
|
|
19
|
+
*/
|
|
20
|
+
export async function writeGateRejectionReport(root, backlogItemId, sprintRunId, gate) {
|
|
21
|
+
const dir = reportsDir(root);
|
|
22
|
+
await fs.mkdir(dir, { recursive: true });
|
|
23
|
+
const fileName = `sprint-${backlogItemId}.md`;
|
|
24
|
+
const filePath = path.join(dir, fileName);
|
|
25
|
+
const content = [
|
|
26
|
+
`# Sprint Gate Rejection — ${backlogItemId}`,
|
|
27
|
+
"",
|
|
28
|
+
`- **Gate:** ${gate.role}`,
|
|
29
|
+
`- **Timestamp:** ${new Date().toISOString()}`,
|
|
30
|
+
`- **Rejection Reason:** ${gate.reason}`,
|
|
31
|
+
`- **Sprint Run ID:** ${sprintRunId}`,
|
|
32
|
+
"",
|
|
33
|
+
].join("\n");
|
|
34
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
35
|
+
return filePath;
|
|
36
|
+
}
|
|
37
|
+
// ---------------------------------------------------------------------------
|
|
38
|
+
// Retrospective report
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
/**
|
|
41
|
+
* Write a sprint retrospective report from a completed or aborted SprintRun.
|
|
42
|
+
* Returns the absolute path to the written file.
|
|
43
|
+
*/
|
|
44
|
+
export async function writeRetroReport(root, run) {
|
|
45
|
+
const dir = reportsDir(root);
|
|
46
|
+
await fs.mkdir(dir, { recursive: true });
|
|
47
|
+
const fileName = `sprint-${run.backlogItemId}-retro.md`;
|
|
48
|
+
const filePath = path.join(dir, fileName);
|
|
49
|
+
const started = new Date(run.startedAt);
|
|
50
|
+
const completed = run.completedAt ? new Date(run.completedAt) : new Date();
|
|
51
|
+
const durationMs = completed.getTime() - started.getTime();
|
|
52
|
+
const duration = humanReadableDuration(durationMs);
|
|
53
|
+
const rows = [
|
|
54
|
+
`# Sprint Retrospective — ${run.backlogItemId}`,
|
|
55
|
+
"",
|
|
56
|
+
`- **Completed Item(s):** ${run.backlogItemId}`,
|
|
57
|
+
`- **Started:** ${run.startedAt}`,
|
|
58
|
+
`- **Completed:** ${run.completedAt ?? "N/A"}`,
|
|
59
|
+
`- **Duration:** ${duration}`,
|
|
60
|
+
`- **Status:** ${run.status}`,
|
|
61
|
+
"",
|
|
62
|
+
"## Gate Results",
|
|
63
|
+
"",
|
|
64
|
+
"| Gate | Passed | Reason |",
|
|
65
|
+
"|------|--------|--------|",
|
|
66
|
+
];
|
|
67
|
+
if (run.gateResults.length > 0) {
|
|
68
|
+
for (const g of run.gateResults) {
|
|
69
|
+
rows.push(`| ${g.role} | ${g.passed ? "✅" : "❌"} | ${g.reason.slice(0, 80)} |`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
rows.push("| (no gates run) | — | — |");
|
|
74
|
+
}
|
|
75
|
+
rows.push("", "## Token Usage", "");
|
|
76
|
+
rows.push(`- **Input tokens:** ${run.totalTokenUsage.inputTokens}`);
|
|
77
|
+
rows.push(`- **Output tokens:** ${run.totalTokenUsage.outputTokens}`);
|
|
78
|
+
rows.push("");
|
|
79
|
+
const content = rows.join("\n");
|
|
80
|
+
await fs.writeFile(filePath, content, "utf-8");
|
|
81
|
+
return filePath;
|
|
82
|
+
}
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Helpers
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
function humanReadableDuration(ms) {
|
|
87
|
+
const seconds = Math.floor(ms / 1000);
|
|
88
|
+
if (seconds < 60)
|
|
89
|
+
return `${seconds}s`;
|
|
90
|
+
const minutes = Math.floor(seconds / 60);
|
|
91
|
+
const remainingSeconds = seconds % 60;
|
|
92
|
+
if (minutes < 60)
|
|
93
|
+
return `${minutes}m ${remainingSeconds}s`;
|
|
94
|
+
const hours = Math.floor(minutes / 60);
|
|
95
|
+
const remainingMinutes = minutes % 60;
|
|
96
|
+
return `${hours}h ${remainingMinutes}m ${remainingSeconds}s`;
|
|
97
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { SprintRun } from "./types.js";
|
|
2
|
+
export declare function saveSprintRun(root: string, run: SprintRun): Promise<void>;
|
|
3
|
+
export declare function loadSprintRun(root: string, sprintRunId: string): Promise<SprintRun | null>;
|
|
4
|
+
/**
|
|
5
|
+
* Loads the most recently started SprintRun that is still active
|
|
6
|
+
* (status "running" or "blocked"), sorted by startedAt descending.
|
|
7
|
+
*/
|
|
8
|
+
export declare function loadActiveSprintRun(root: string): Promise<SprintRun | null>;
|
|
9
|
+
export declare function deleteSprintRun(root: string, sprintRunId: string): Promise<void>;
|
|
10
|
+
export declare function listSprintRuns(root: string): Promise<SprintRun[]>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// xcoder:version 1 | xcoder:task swarm-swarm-is-b22287 | xcoder:updated 2026-08-04T12:10:29.441Z | xcoder:subtask swarm-swarm-is-b22287/0
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { createXcoderPaths } from "../util/xcoder-paths.js";
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
// CRUD operations
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
export async function saveSprintRun(root, run) {
|
|
9
|
+
const paths = createXcoderPaths(root);
|
|
10
|
+
const dir = paths.sprintRunsDir();
|
|
11
|
+
await fs.mkdir(dir, { recursive: true });
|
|
12
|
+
const file = paths.sprintRunFile(run.sprintRunId);
|
|
13
|
+
const tmp = `${file}.tmp`;
|
|
14
|
+
await fs.writeFile(tmp, JSON.stringify(run, null, 2), "utf8");
|
|
15
|
+
await fs.rename(tmp, file);
|
|
16
|
+
}
|
|
17
|
+
export async function loadSprintRun(root, sprintRunId) {
|
|
18
|
+
const file = createXcoderPaths(root).sprintRunFile(sprintRunId);
|
|
19
|
+
try {
|
|
20
|
+
const raw = await fs.readFile(file, "utf-8");
|
|
21
|
+
return JSON.parse(raw);
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Loads the most recently started SprintRun that is still active
|
|
29
|
+
* (status "running" or "blocked"), sorted by startedAt descending.
|
|
30
|
+
*/
|
|
31
|
+
export async function loadActiveSprintRun(root) {
|
|
32
|
+
const runs = await listSprintRuns(root);
|
|
33
|
+
const active = runs.filter((r) => r.status === "running" || r.status === "blocked");
|
|
34
|
+
if (active.length === 0)
|
|
35
|
+
return null;
|
|
36
|
+
active.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
|
|
37
|
+
return active[0];
|
|
38
|
+
}
|
|
39
|
+
export async function deleteSprintRun(root, sprintRunId) {
|
|
40
|
+
const file = createXcoderPaths(root).sprintRunFile(sprintRunId);
|
|
41
|
+
try {
|
|
42
|
+
await fs.unlink(file);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// File doesn't exist — that's fine.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
export async function listSprintRuns(root) {
|
|
49
|
+
const dir = createXcoderPaths(root).sprintRunsDir();
|
|
50
|
+
let entries;
|
|
51
|
+
try {
|
|
52
|
+
entries = await fs.readdir(dir);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
const runs = [];
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
if (!entry.endsWith(".json"))
|
|
60
|
+
continue;
|
|
61
|
+
const filePath = path.join(dir, entry);
|
|
62
|
+
try {
|
|
63
|
+
const raw = await fs.readFile(filePath, "utf-8");
|
|
64
|
+
runs.push(JSON.parse(raw));
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// Skip malformed files.
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return runs;
|
|
71
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sprint framework data model types — see `.xcoder/design/sprint-framework-design.md`.
|
|
3
|
+
*/
|
|
4
|
+
import type { SubtaskNode, TokenUsage } from "../types.js";
|
|
5
|
+
export type SprintRunStatus = "running" | "done" | "aborted" | "blocked";
|
|
6
|
+
export interface GateResult {
|
|
7
|
+
role: "scrum-master" | "system-architect" | "security-lead";
|
|
8
|
+
passed: boolean;
|
|
9
|
+
reason: string;
|
|
10
|
+
/** The subtask node id that ran this gate, for traceability. */
|
|
11
|
+
subtaskId?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface SprintRun {
|
|
14
|
+
sprintRunId: string;
|
|
15
|
+
backlogItemId: string;
|
|
16
|
+
taskId: string;
|
|
17
|
+
status: SprintRunStatus;
|
|
18
|
+
dag: SubtaskNode[];
|
|
19
|
+
currentStage: string;
|
|
20
|
+
gateResults: GateResult[];
|
|
21
|
+
startedAt: string;
|
|
22
|
+
completedAt: string | null;
|
|
23
|
+
totalTokenUsage: TokenUsage;
|
|
24
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** Keyed by workspace-relative posix path. Timestamps in ISO 8601. */
|
|
2
|
+
export type FileVersionsIndex = Record<string, {
|
|
3
|
+
version: number;
|
|
4
|
+
lastTask: string;
|
|
5
|
+
lastSubtask: string;
|
|
6
|
+
lastUpdated: string;
|
|
7
|
+
}>;
|
|
8
|
+
export interface HeaderMeta {
|
|
9
|
+
version: number;
|
|
10
|
+
taskId: string;
|
|
11
|
+
subtaskId: string;
|
|
12
|
+
timestamp: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Reads .xcoder/file-versions.json, parses it, returns the index.
|
|
16
|
+
* Returns an empty index if the file doesn't exist or is malformed.
|
|
17
|
+
*/
|
|
18
|
+
export declare function loadFileVersions(workspaceRoot: string): Promise<FileVersionsIndex>;
|
|
19
|
+
/**
|
|
20
|
+
* Loads the index, looks up relPath, increments version by 1 (or sets to 1
|
|
21
|
+
* if new), updates lastTask/lastSubtask/lastUpdated, saves the index
|
|
22
|
+
* atomically (temp + rename), returns the new HeaderMeta.
|
|
23
|
+
*/
|
|
24
|
+
export declare function incrementFileVersion(workspaceRoot: string, relPath: string, taskId: string, subtaskId: string): Promise<HeaderMeta>;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Paths
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
function versionsPath(workspaceRoot) {
|
|
7
|
+
return path.join(workspaceRoot, ".xcoder", "file-versions.json");
|
|
8
|
+
}
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Public API
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
/**
|
|
13
|
+
* Reads .xcoder/file-versions.json, parses it, returns the index.
|
|
14
|
+
* Returns an empty index if the file doesn't exist or is malformed.
|
|
15
|
+
*/
|
|
16
|
+
export async function loadFileVersions(workspaceRoot) {
|
|
17
|
+
try {
|
|
18
|
+
const raw = await fs.readFile(versionsPath(workspaceRoot), "utf8");
|
|
19
|
+
const parsed = JSON.parse(raw);
|
|
20
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
// Validate entries have the expected shape
|
|
24
|
+
const sanitized = {};
|
|
25
|
+
for (const [k, v] of Object.entries(parsed)) {
|
|
26
|
+
const entry = v;
|
|
27
|
+
if (typeof entry?.version === "number" &&
|
|
28
|
+
typeof entry?.lastTask === "string" &&
|
|
29
|
+
typeof entry?.lastSubtask === "string") {
|
|
30
|
+
sanitized[k] = {
|
|
31
|
+
version: entry.version,
|
|
32
|
+
lastTask: entry.lastTask,
|
|
33
|
+
lastSubtask: entry.lastSubtask,
|
|
34
|
+
lastUpdated: typeof entry.lastUpdated === "string" ? entry.lastUpdated : new Date().toISOString(),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return sanitized;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Loads the index, looks up relPath, increments version by 1 (or sets to 1
|
|
46
|
+
* if new), updates lastTask/lastSubtask/lastUpdated, saves the index
|
|
47
|
+
* atomically (temp + rename), returns the new HeaderMeta.
|
|
48
|
+
*/
|
|
49
|
+
export async function incrementFileVersion(workspaceRoot, relPath, taskId, subtaskId) {
|
|
50
|
+
const index = await loadFileVersions(workspaceRoot);
|
|
51
|
+
const posix = relPath.replace(/\\/g, "/");
|
|
52
|
+
const existing = index[posix];
|
|
53
|
+
const newVersion = existing ? existing.version + 1 : 1;
|
|
54
|
+
const timestamp = new Date().toISOString();
|
|
55
|
+
index[posix] = {
|
|
56
|
+
version: newVersion,
|
|
57
|
+
lastTask: taskId,
|
|
58
|
+
lastSubtask: subtaskId,
|
|
59
|
+
lastUpdated: timestamp,
|
|
60
|
+
};
|
|
61
|
+
await atomicWrite(workspaceRoot, index);
|
|
62
|
+
return { version: newVersion, taskId, subtaskId, timestamp };
|
|
63
|
+
}
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
// Internal: atomic write
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
async function atomicWrite(workspaceRoot, index) {
|
|
68
|
+
const target = versionsPath(workspaceRoot);
|
|
69
|
+
const tmp = target + ".tmp." + Date.now();
|
|
70
|
+
await fs.mkdir(path.dirname(target), { recursive: true });
|
|
71
|
+
await fs.writeFile(tmp, JSON.stringify(index, null, 2), "utf8");
|
|
72
|
+
await fs.rename(tmp, target);
|
|
73
|
+
}
|