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,9 @@
|
|
|
1
|
+
import type { SubtaskResult, TaskRun } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Records a lesson learned from a subtask result into .xcoder/lesson.md
|
|
4
|
+
* if the result passed validation and subtask.role is not "conversation".
|
|
5
|
+
* Checks file size before appending to prevent unbounded growth.
|
|
6
|
+
*/
|
|
7
|
+
export declare function recordTaskLesson(workspaceRoot: string, result: SubtaskResult, run: TaskRun, nodeId: string): Promise<void>;
|
|
8
|
+
/** Re-export for convenience */
|
|
9
|
+
export { checkLessonFileSize } from "../orchestrator/lesson-manager.js";
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { checkLessonFileSize, recordLesson } from "../orchestrator/lesson-manager.js";
|
|
2
|
+
/**
|
|
3
|
+
* Records a lesson learned from a subtask result into .xcoder/lesson.md
|
|
4
|
+
* if the result passed validation and subtask.role is not "conversation".
|
|
5
|
+
* Checks file size before appending to prevent unbounded growth.
|
|
6
|
+
*/
|
|
7
|
+
export async function recordTaskLesson(workspaceRoot, result, run, nodeId) {
|
|
8
|
+
if (result.crashed)
|
|
9
|
+
return;
|
|
10
|
+
// Only record for non-conversation roles with a valid self-check.
|
|
11
|
+
if (!result.selfCheck?.passed)
|
|
12
|
+
return;
|
|
13
|
+
// Check file size before recording.
|
|
14
|
+
const ok = await checkLessonFileSize(workspaceRoot);
|
|
15
|
+
if (typeof ok === "string")
|
|
16
|
+
return; // warning string returned, skip
|
|
17
|
+
const entry = {
|
|
18
|
+
source: `sub-agent:${nodeId}`,
|
|
19
|
+
taskId: run.spec.taskId,
|
|
20
|
+
tags: ["lesson"],
|
|
21
|
+
context: result.summary.slice(0, 200),
|
|
22
|
+
lesson: result.summary,
|
|
23
|
+
};
|
|
24
|
+
await recordLesson(workspaceRoot, entry);
|
|
25
|
+
}
|
|
26
|
+
/** Re-export for convenience */
|
|
27
|
+
export { checkLessonFileSize } from "../orchestrator/lesson-manager.js";
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { SubtaskNode, SubtaskStatus, TaskRun, TaskSpec } from "../types.js";
|
|
2
|
+
import type { WksProtocolMap } from "../protocol/wks-protocol-manager.js";
|
|
3
|
+
import type { OrchestratorOptions } from "./sdlc-orchestrator.js";
|
|
4
|
+
/** Shared result from initialization. */
|
|
5
|
+
export interface InitializationResult {
|
|
6
|
+
run: TaskRun;
|
|
7
|
+
isConversation: boolean;
|
|
8
|
+
lessonContext: string | undefined;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Builds or loads the initial DAG for a task run.
|
|
12
|
+
* - For new tasks: builds the initial plan via task-factory
|
|
13
|
+
* - For resume tasks: loads the existing TaskRun from disk
|
|
14
|
+
* - Migrates legacy defects
|
|
15
|
+
* - Reconciles orphaned tasks
|
|
16
|
+
* - Runs initial lesson loading if lessonLearned
|
|
17
|
+
*/
|
|
18
|
+
export declare function initializeRun(spec: TaskSpec, opts: OrchestratorOptions, protocolMap: WksProtocolMap, masterProtocolSection: string | undefined, customDag?: SubtaskNode[]): Promise<InitializationResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Resumes an existing TaskRun from disk, resetting healing budgets
|
|
21
|
+
* on escalated/healing nodes while preserving healing.log.
|
|
22
|
+
*/
|
|
23
|
+
export declare function resumeRun(taskId: string, opts: OrchestratorOptions, protocolMap: WksProtocolMap, masterProtocolSection: string | undefined): Promise<InitializationResult>;
|
|
24
|
+
/**
|
|
25
|
+
* Node statuses that must be returned to an executable state before a resumed
|
|
26
|
+
* run can pick them up. A process that died mid-flight leaves nodes at
|
|
27
|
+
* `in_progress`/`validating`; a blocked dependency leaves `blocked`/`failed`.
|
|
28
|
+
* `recomputeReady()` only ever promotes `pending`, so nothing re-arms these by
|
|
29
|
+
* itself — the execution loop would find zero ready nodes and exit silently.
|
|
30
|
+
*/
|
|
31
|
+
export declare const REARM_ON_RESUME: readonly SubtaskStatus[];
|
|
32
|
+
/**
|
|
33
|
+
* Returns nodes to an executable state for a resumed run. Mutates in place.
|
|
34
|
+
*
|
|
35
|
+
* Rules (by precedence):
|
|
36
|
+
* 1. "done" → untouched. Never re-executed, results kept.
|
|
37
|
+
* 2. "pending"/"ready" → untouched; recomputeReady() promotes pending
|
|
38
|
+
* once its dependencies land.
|
|
39
|
+
* 3. escalated/healing/failed/blocked
|
|
40
|
+
* 4. in_progress/validating (crash-orphaned)
|
|
41
|
+
* → status="ready", healing.attempts=0, healing.log
|
|
42
|
+
* kept so the failure history is not lost.
|
|
43
|
+
* 5. unknown status → left alone (fail-safe; never guessed into ready).
|
|
44
|
+
*
|
|
45
|
+
* @returns number of nodes re-armed.
|
|
46
|
+
*/
|
|
47
|
+
export declare function rearmForResume(dag: SubtaskNode[]): number;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { buildInitialPlan, isConversation } from "../orchestrator/task-factory.js";
|
|
2
|
+
import { loadTaskRun, reconcileOrphanedTasks } from "../store/task-store.js";
|
|
3
|
+
import { getOrBuildIndex } from "../tools/workspace-index.js";
|
|
4
|
+
import { migrateLegacyDefects } from "../orchestrator/defect-migration.js";
|
|
5
|
+
import { loadLessons, checkLessonFileSize } from "../orchestrator/lesson-manager.js";
|
|
6
|
+
import * as term from "../util/terminal.js";
|
|
7
|
+
import { emitXcoderEvent } from "../events/bus.js";
|
|
8
|
+
import { dagSnapshot } from "./dag-utils.js";
|
|
9
|
+
/**
|
|
10
|
+
* Builds or loads the initial DAG for a task run.
|
|
11
|
+
* - For new tasks: builds the initial plan via task-factory
|
|
12
|
+
* - For resume tasks: loads the existing TaskRun from disk
|
|
13
|
+
* - Migrates legacy defects
|
|
14
|
+
* - Reconciles orphaned tasks
|
|
15
|
+
* - Runs initial lesson loading if lessonLearned
|
|
16
|
+
*/
|
|
17
|
+
export async function initializeRun(spec, opts, protocolMap, masterProtocolSection, customDag) {
|
|
18
|
+
await getOrBuildIndex(opts.workspaceRoot);
|
|
19
|
+
// One-time migration of legacy defects/ -> .xcoder/defects/
|
|
20
|
+
try {
|
|
21
|
+
await migrateLegacyDefects(opts.workspaceRoot);
|
|
22
|
+
}
|
|
23
|
+
catch (e) {
|
|
24
|
+
term.orchestratorNote(`defect migration warning: ${e.message}`);
|
|
25
|
+
}
|
|
26
|
+
const orphaned = await reconcileOrphanedTasks(opts.workspaceRoot);
|
|
27
|
+
for (const o of orphaned) {
|
|
28
|
+
term.orchestratorNote(`reconciled orphaned task ${o.taskId} ("${o.goal.slice(0, 50)}...") — was stuck at "running" from a prior process, marked halted`);
|
|
29
|
+
}
|
|
30
|
+
// Load lessons learned if enabled.
|
|
31
|
+
let lessonContext;
|
|
32
|
+
if (opts.lessonLearned) {
|
|
33
|
+
const warning = await checkLessonFileSize(opts.workspaceRoot);
|
|
34
|
+
if (warning) {
|
|
35
|
+
term.orchestratorNote(warning);
|
|
36
|
+
}
|
|
37
|
+
lessonContext = await loadLessons(opts.workspaceRoot);
|
|
38
|
+
}
|
|
39
|
+
if (isConversation(spec)) {
|
|
40
|
+
return { run: null, isConversation: true, lessonContext };
|
|
41
|
+
}
|
|
42
|
+
const dag = customDag ?? buildInitialPlan(spec);
|
|
43
|
+
const run = {
|
|
44
|
+
spec,
|
|
45
|
+
dag,
|
|
46
|
+
status: "running",
|
|
47
|
+
escalations: [],
|
|
48
|
+
orchestratorTrace: [],
|
|
49
|
+
finalDeliverable: undefined,
|
|
50
|
+
totalTokenUsage: { inputTokens: 0, outputTokens: 0 },
|
|
51
|
+
};
|
|
52
|
+
term.taskIntro(spec.taskId, spec.goal);
|
|
53
|
+
emitXcoderEvent({ type: "task:intro", taskId: spec.taskId, data: { goal: spec.goal } });
|
|
54
|
+
term.orchestratorNote(`plan built: ${dag.map((n) => n.id).join(" → ")}`);
|
|
55
|
+
term.orchestratorNote(`logs: ${spec.taskId}`);
|
|
56
|
+
emitXcoderEvent({ type: "task:dag", taskId: spec.taskId, data: dagSnapshot(dag) });
|
|
57
|
+
return { run, isConversation: false, lessonContext };
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Resumes an existing TaskRun from disk, resetting healing budgets
|
|
61
|
+
* on escalated/healing nodes while preserving healing.log.
|
|
62
|
+
*/
|
|
63
|
+
export async function resumeRun(taskId, opts, protocolMap, masterProtocolSection) {
|
|
64
|
+
const run = await loadTaskRun(opts.workspaceRoot, taskId);
|
|
65
|
+
if (!run) {
|
|
66
|
+
throw new Error(`no task "${taskId}" found in ${opts.workspaceRoot} to resume.`);
|
|
67
|
+
}
|
|
68
|
+
if (run.status === "done") {
|
|
69
|
+
term.orchestratorNote(`${taskId} is already done — nothing to resume.`);
|
|
70
|
+
return { run, isConversation: false, lessonContext: undefined };
|
|
71
|
+
}
|
|
72
|
+
await getOrBuildIndex(opts.workspaceRoot);
|
|
73
|
+
const orphaned = await reconcileOrphanedTasks(opts.workspaceRoot);
|
|
74
|
+
for (const o of orphaned) {
|
|
75
|
+
if (o.taskId !== taskId)
|
|
76
|
+
term.orchestratorNote(`also reconciled orphaned task ${o.taskId}`);
|
|
77
|
+
else
|
|
78
|
+
term.orchestratorNote(`${taskId} was still marked "running" (a prior process died) — reconciled to "halted" and resuming.`);
|
|
79
|
+
}
|
|
80
|
+
const resumeCount = rearmForResume(run.dag);
|
|
81
|
+
term.taskIntro(run.spec.taskId, `[resume] ${run.spec.goal}`);
|
|
82
|
+
emitXcoderEvent({ type: "task:intro", taskId: run.spec.taskId, data: { goal: run.spec.goal, resumed: true } });
|
|
83
|
+
term.orchestratorNote(`resuming ${taskId}: ${resumeCount} subtask(s) reset to ready with a fresh healing budget, ${run.dag.filter((n) => n.status === "done").length}/${run.dag.length} already done`);
|
|
84
|
+
run.status = "running";
|
|
85
|
+
run.orchestratorTrace.push({
|
|
86
|
+
thought: "resumed by user request",
|
|
87
|
+
action: null,
|
|
88
|
+
observation: `${resumeCount} subtask(s) re-armed for another attempt; already-done subtasks and their outputs were kept as-is.`,
|
|
89
|
+
});
|
|
90
|
+
emitXcoderEvent({ type: "task:dag", taskId: run.spec.taskId, data: dagSnapshot(run.dag) });
|
|
91
|
+
return { run, isConversation: false, lessonContext: undefined };
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Node statuses that must be returned to an executable state before a resumed
|
|
95
|
+
* run can pick them up. A process that died mid-flight leaves nodes at
|
|
96
|
+
* `in_progress`/`validating`; a blocked dependency leaves `blocked`/`failed`.
|
|
97
|
+
* `recomputeReady()` only ever promotes `pending`, so nothing re-arms these by
|
|
98
|
+
* itself — the execution loop would find zero ready nodes and exit silently.
|
|
99
|
+
*/
|
|
100
|
+
export const REARM_ON_RESUME = [
|
|
101
|
+
"escalated",
|
|
102
|
+
"healing",
|
|
103
|
+
"failed",
|
|
104
|
+
"blocked",
|
|
105
|
+
"in_progress",
|
|
106
|
+
"validating",
|
|
107
|
+
];
|
|
108
|
+
/**
|
|
109
|
+
* Returns nodes to an executable state for a resumed run. Mutates in place.
|
|
110
|
+
*
|
|
111
|
+
* Rules (by precedence):
|
|
112
|
+
* 1. "done" → untouched. Never re-executed, results kept.
|
|
113
|
+
* 2. "pending"/"ready" → untouched; recomputeReady() promotes pending
|
|
114
|
+
* once its dependencies land.
|
|
115
|
+
* 3. escalated/healing/failed/blocked
|
|
116
|
+
* 4. in_progress/validating (crash-orphaned)
|
|
117
|
+
* → status="ready", healing.attempts=0, healing.log
|
|
118
|
+
* kept so the failure history is not lost.
|
|
119
|
+
* 5. unknown status → left alone (fail-safe; never guessed into ready).
|
|
120
|
+
*
|
|
121
|
+
* @returns number of nodes re-armed.
|
|
122
|
+
*/
|
|
123
|
+
export function rearmForResume(dag) {
|
|
124
|
+
let rearmed = 0;
|
|
125
|
+
for (const node of dag) {
|
|
126
|
+
if (node.status === "done" || node.status === "pending" || node.status === "ready")
|
|
127
|
+
continue;
|
|
128
|
+
if (!REARM_ON_RESUME.includes(node.status))
|
|
129
|
+
continue;
|
|
130
|
+
node.status = "ready";
|
|
131
|
+
node.healing.attempts = 0;
|
|
132
|
+
rearmed++;
|
|
133
|
+
}
|
|
134
|
+
return rearmed;
|
|
135
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { TaskRun } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Persists the current DAG state and progress summary to disk.
|
|
4
|
+
* Emits task:dag event, saves TaskRun atomically, and writes progress summary markdown.
|
|
5
|
+
*/
|
|
6
|
+
export declare function saveProgress(workspaceRoot: string, run: TaskRun): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Persists the DAG state (synchronous fallback when event loop may be draining).
|
|
9
|
+
*/
|
|
10
|
+
export declare function saveProgressWithFallback(workspaceRoot: string, run: TaskRun, saveTaskRunSync: (root: string, run: TaskRun) => void): Promise<void>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { saveTaskRun } from "../store/task-store.js";
|
|
2
|
+
import { saveProgressSummary } from "../store/task-store.js";
|
|
3
|
+
import { buildProgressSummaryMarkdown } from "../orchestrator/progress-summary.js";
|
|
4
|
+
import { emitXcoderEvent } from "../events/bus.js";
|
|
5
|
+
import { dagSnapshot } from "./dag-utils.js";
|
|
6
|
+
import * as term from "../util/terminal.js";
|
|
7
|
+
/**
|
|
8
|
+
* Persists the current DAG state and progress summary to disk.
|
|
9
|
+
* Emits task:dag event, saves TaskRun atomically, and writes progress summary markdown.
|
|
10
|
+
*/
|
|
11
|
+
export async function saveProgress(workspaceRoot, run) {
|
|
12
|
+
await saveTaskRun(workspaceRoot, run);
|
|
13
|
+
emitXcoderEvent({ type: "task:dag", taskId: run.spec.taskId, data: dagSnapshot(run.dag) });
|
|
14
|
+
const progressMd = buildProgressSummaryMarkdown(run);
|
|
15
|
+
await saveProgressSummary(workspaceRoot, run.spec.taskId, progressMd);
|
|
16
|
+
term.orchestratorNote(`progress summary written: .xcoder/tasks/${run.spec.taskId}.md`);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Persists the DAG state (synchronous fallback when event loop may be draining).
|
|
20
|
+
*/
|
|
21
|
+
export async function saveProgressWithFallback(workspaceRoot, run, saveTaskRunSync) {
|
|
22
|
+
try {
|
|
23
|
+
await Promise.race([
|
|
24
|
+
saveTaskRun(workspaceRoot, run),
|
|
25
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("save timed out")), 500)),
|
|
26
|
+
]);
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
saveTaskRunSync(workspaceRoot, run);
|
|
30
|
+
}
|
|
31
|
+
emitXcoderEvent({ type: "task:dag", taskId: run.spec.taskId, data: dagSnapshot(run.dag) });
|
|
32
|
+
const progressMd = buildProgressSummaryMarkdown(run);
|
|
33
|
+
await saveProgressSummary(workspaceRoot, run.spec.taskId, progressMd);
|
|
34
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { WksProtocolMap } from "../protocol/wks-protocol-manager.js";
|
|
2
|
+
/**
|
|
3
|
+
* Reads and parses wks_protocol.md from the workspace root.
|
|
4
|
+
* Returns the parsed map and the optional masterProtocolSection.
|
|
5
|
+
* If the file is missing or empty, returns empty map/undefined.
|
|
6
|
+
*/
|
|
7
|
+
export declare function loadProtocol(workspaceRoot: string): Promise<{
|
|
8
|
+
protocolMap: WksProtocolMap;
|
|
9
|
+
masterProtocolSection: string | undefined;
|
|
10
|
+
}>;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { parseWksProtocol } from "../protocol/wks-protocol-manager.js";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
/**
|
|
5
|
+
* Reads and parses wks_protocol.md from the workspace root.
|
|
6
|
+
* Returns the parsed map and the optional masterProtocolSection.
|
|
7
|
+
* If the file is missing or empty, returns empty map/undefined.
|
|
8
|
+
*/
|
|
9
|
+
export async function loadProtocol(workspaceRoot) {
|
|
10
|
+
try {
|
|
11
|
+
const filePath = path.join(workspaceRoot, "wks_protocol.md");
|
|
12
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
13
|
+
const protocolMap = parseWksProtocol(content);
|
|
14
|
+
// "sdlc-orchestrator" is reserved for future LLM-driven orchestrator engines.
|
|
15
|
+
const masterProtocolSection = protocolMap["sdlc-orchestrator"];
|
|
16
|
+
return { protocolMap, masterProtocolSection };
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return { protocolMap: {}, masterProtocolSection: undefined };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SubtaskNode, SubtaskResult, TaskRun, TaskSpec } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pure function: computes the orchestration trace entry, event data, and
|
|
4
|
+
* idempotent defect ID for an iteration-exhausted SubtaskResult. Called
|
|
5
|
+
* by handleResult() during the orchestrator loop; also exported for test.
|
|
6
|
+
*/
|
|
7
|
+
export declare function buildExhaustionTrace(nodeId: string, taskId: string, result: SubtaskResult): {
|
|
8
|
+
trace: {
|
|
9
|
+
thought: string;
|
|
10
|
+
action: null;
|
|
11
|
+
observation: string;
|
|
12
|
+
};
|
|
13
|
+
event: {
|
|
14
|
+
type: "agent:exhausted";
|
|
15
|
+
taskId: string;
|
|
16
|
+
subtaskId: string;
|
|
17
|
+
data: {
|
|
18
|
+
maxIterations: number;
|
|
19
|
+
iterationsUsed: number;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
defectId: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function summarize(dag: SubtaskNode[], run: TaskRun, spec: TaskSpec, workspaceRoot: string): string;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { taskLogDir } from "../util/logs.js";
|
|
2
|
+
/**
|
|
3
|
+
* Pure function: computes the orchestration trace entry, event data, and
|
|
4
|
+
* idempotent defect ID for an iteration-exhausted SubtaskResult. Called
|
|
5
|
+
* by handleResult() during the orchestrator loop; also exported for test.
|
|
6
|
+
*/
|
|
7
|
+
export function buildExhaustionTrace(nodeId, taskId, result) {
|
|
8
|
+
const maxIterations = result.iterations || 200;
|
|
9
|
+
return {
|
|
10
|
+
trace: {
|
|
11
|
+
thought: `${nodeId} exhausted its ${maxIterations} iterations without a final_answer`,
|
|
12
|
+
action: null,
|
|
13
|
+
observation: result.summary,
|
|
14
|
+
},
|
|
15
|
+
event: {
|
|
16
|
+
type: "agent:exhausted",
|
|
17
|
+
taskId,
|
|
18
|
+
subtaskId: nodeId,
|
|
19
|
+
data: { maxIterations, iterationsUsed: result.iterations },
|
|
20
|
+
},
|
|
21
|
+
defectId: `DEFECT-EXH-${nodeId}`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
export function summarize(dag, run, spec, workspaceRoot) {
|
|
25
|
+
const lines = [
|
|
26
|
+
`# ${spec.goal}`,
|
|
27
|
+
"",
|
|
28
|
+
`Status: ${run.status}`,
|
|
29
|
+
`Tokens: ${run.totalTokenUsage.inputTokens} in / ${run.totalTokenUsage.outputTokens} out`,
|
|
30
|
+
`Logs: ${taskLogDir(workspaceRoot, spec.taskId)}`,
|
|
31
|
+
"",
|
|
32
|
+
];
|
|
33
|
+
for (const node of dag) {
|
|
34
|
+
lines.push(`## ${node.id} (${node.role}) — ${node.status}`);
|
|
35
|
+
if (node.result)
|
|
36
|
+
lines.push(node.result.summary);
|
|
37
|
+
if (node.result?.crashed)
|
|
38
|
+
lines.push(`**Crashed** — see sys.log for the stack trace.`);
|
|
39
|
+
if (node.status === "escalated") {
|
|
40
|
+
const esc = run.escalations.find((e) => e.subtaskId === node.id);
|
|
41
|
+
lines.push(`**Escalated:** ${esc?.reason}`);
|
|
42
|
+
}
|
|
43
|
+
lines.push("");
|
|
44
|
+
}
|
|
45
|
+
// --- Files touched section
|
|
46
|
+
const nodesWithFiles = dag.filter((n) => n.result && ((n.result._filesTouchedMeta?.length ?? 0) > 0 || n.result.filesTouched.length > 0));
|
|
47
|
+
if (nodesWithFiles.length > 0) {
|
|
48
|
+
lines.push("## Files touched");
|
|
49
|
+
lines.push("");
|
|
50
|
+
for (const node of nodesWithFiles) {
|
|
51
|
+
lines.push(`### ${node.id}`);
|
|
52
|
+
const meta = node.result._filesTouchedMeta;
|
|
53
|
+
if (meta && meta.length > 0) {
|
|
54
|
+
for (const entry of meta) {
|
|
55
|
+
const label = entry.operation === "created" ? "[created]" : "[modified]";
|
|
56
|
+
lines.push(`- \`${entry.path}\` ${label}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
for (const f of node.result.filesTouched) {
|
|
61
|
+
lines.push(`- \`${f}\` [touched]`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
lines.push("");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return lines.join("\n");
|
|
68
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { SubtaskNode, TaskRun, TaskSpec } from "../types.js";
|
|
2
|
+
import type { InterruptAction } from "../orchestrator/interrupt-handler.js";
|
|
3
|
+
import { LlmClient } from "../llm/client.js";
|
|
4
|
+
import { InteractiveSteeringManager } from "../orchestrator/interactive-steering.js";
|
|
5
|
+
export { dagSnapshot, recomputeReady, inlineIntoDependents } from "./dag-utils.js";
|
|
6
|
+
export { summarize, buildExhaustionTrace } from "./reporting.js";
|
|
7
|
+
export interface OrchestratorOptions {
|
|
8
|
+
workspaceRoot: string;
|
|
9
|
+
/** Orchestrator role client — used by the coordinator/conversation loops. */
|
|
10
|
+
llm: LlmClient;
|
|
11
|
+
/** Swarm/Sub Agent role client — used by dispatchSwarm workers. Defaults to `llm`. */
|
|
12
|
+
subAgentLlm?: LlmClient;
|
|
13
|
+
/** Explicit per-role clients — highest priority when all are provided. */
|
|
14
|
+
roleLlm?: Partial<Record<import("../config/loadConfig.js").AgentRole, LlmClient>>;
|
|
15
|
+
concurrency: number;
|
|
16
|
+
maxRounds?: number;
|
|
17
|
+
lessonLearned?: boolean;
|
|
18
|
+
signal?: AbortSignal;
|
|
19
|
+
quiet?: boolean;
|
|
20
|
+
/**
|
|
21
|
+
* Force a full index rebuild before the run instead of probing it first.
|
|
22
|
+
* Backs the `--reindex` flag; freshness is otherwise automatic, so this is
|
|
23
|
+
* an override rather than the only route to an up-to-date index.
|
|
24
|
+
*/
|
|
25
|
+
reindex?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export declare class SdlcOrchestrator {
|
|
28
|
+
private opts;
|
|
29
|
+
private protocolMap;
|
|
30
|
+
private masterProtocolSection;
|
|
31
|
+
private lessonContext;
|
|
32
|
+
private _abortController;
|
|
33
|
+
private _steering;
|
|
34
|
+
private _currentRun;
|
|
35
|
+
private _haltDeferred;
|
|
36
|
+
private _index?;
|
|
37
|
+
constructor(opts: OrchestratorOptions);
|
|
38
|
+
/**
|
|
39
|
+
* Gate the workspace index once per task run.
|
|
40
|
+
*
|
|
41
|
+
* Every ingress path needs this: the index is the source of truth for which
|
|
42
|
+
* files exist, and it used to be reused no matter how stale it was. The gate
|
|
43
|
+
* is stat-only while the index is still accurate, so the price of
|
|
44
|
+
* correctness here is one filesystem walk, not a full re-hash.
|
|
45
|
+
*
|
|
46
|
+
* It never throws. A cache refresh failure degrades to the best index
|
|
47
|
+
* available rather than aborting the run — the authorization boundary is
|
|
48
|
+
* `resolveInWorkspace()`, which is untouched and still fails closed.
|
|
49
|
+
*/
|
|
50
|
+
private gateIndex;
|
|
51
|
+
/** The index gated for the current run, if a run has started. */
|
|
52
|
+
get index(): import("../tools/workspace-index.js").WorkspaceIndex | undefined;
|
|
53
|
+
get currentRun(): TaskRun | null;
|
|
54
|
+
get steering(): InteractiveSteeringManager;
|
|
55
|
+
run(spec: TaskSpec, customDag?: SubtaskNode[]): Promise<TaskRun>;
|
|
56
|
+
resume(taskId: string): Promise<TaskRun>;
|
|
57
|
+
halt(currentRun: TaskRun): Promise<InterruptAction["summary"]>;
|
|
58
|
+
private _runLoop;
|
|
59
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { ensureFreshIndex } from "../tools/index-freshness.js";
|
|
2
|
+
import { logIndexRefresh } from "../util/file-access-log.js";
|
|
3
|
+
import { saveTaskRunSync } from "../store/task-store.js";
|
|
4
|
+
import { FooterManager } from "../util/footer-manager.js";
|
|
5
|
+
import { isInteractiveStdout } from "../util/tty.js";
|
|
6
|
+
import { InteractiveSteeringManager } from "../orchestrator/interactive-steering.js";
|
|
7
|
+
// Helper modules
|
|
8
|
+
import { loadProtocol } from "./protocol-loader.js";
|
|
9
|
+
import { initializeRun, resumeRun } from "./plan-initializer.js";
|
|
10
|
+
import { runConversation } from "./conversation-runner.js";
|
|
11
|
+
import { executeLoop } from "./execution-loop.js";
|
|
12
|
+
import { saveProgress } from "./progress-persister.js";
|
|
13
|
+
import { haltInternal, checkSteeringInjection } from "./steering-handler.js";
|
|
14
|
+
// Re-exported for consumer compatibility
|
|
15
|
+
export { dagSnapshot, recomputeReady, inlineIntoDependents } from "./dag-utils.js";
|
|
16
|
+
export { summarize, buildExhaustionTrace } from "./reporting.js";
|
|
17
|
+
export class SdlcOrchestrator {
|
|
18
|
+
opts;
|
|
19
|
+
protocolMap = {};
|
|
20
|
+
masterProtocolSection = undefined;
|
|
21
|
+
lessonContext = undefined;
|
|
22
|
+
_abortController = null;
|
|
23
|
+
_steering = new InteractiveSteeringManager();
|
|
24
|
+
_currentRun = null;
|
|
25
|
+
_haltDeferred = null;
|
|
26
|
+
_index;
|
|
27
|
+
constructor(opts) {
|
|
28
|
+
this.opts = opts;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Gate the workspace index once per task run.
|
|
32
|
+
*
|
|
33
|
+
* Every ingress path needs this: the index is the source of truth for which
|
|
34
|
+
* files exist, and it used to be reused no matter how stale it was. The gate
|
|
35
|
+
* is stat-only while the index is still accurate, so the price of
|
|
36
|
+
* correctness here is one filesystem walk, not a full re-hash.
|
|
37
|
+
*
|
|
38
|
+
* It never throws. A cache refresh failure degrades to the best index
|
|
39
|
+
* available rather than aborting the run — the authorization boundary is
|
|
40
|
+
* `resolveInWorkspace()`, which is untouched and still fails closed.
|
|
41
|
+
*/
|
|
42
|
+
async gateIndex(taskId) {
|
|
43
|
+
const result = await ensureFreshIndex(this.opts.workspaceRoot, {
|
|
44
|
+
force: this.opts.reindex === true,
|
|
45
|
+
});
|
|
46
|
+
this._index = result.index;
|
|
47
|
+
if (!this.opts.quiet) {
|
|
48
|
+
try {
|
|
49
|
+
await logIndexRefresh(this.opts.workspaceRoot, result, {
|
|
50
|
+
taskId: taskId ?? "session",
|
|
51
|
+
subtaskId: "-",
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// observability must never break a run
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/** The index gated for the current run, if a run has started. */
|
|
60
|
+
get index() {
|
|
61
|
+
return this._index;
|
|
62
|
+
}
|
|
63
|
+
get currentRun() {
|
|
64
|
+
return this._currentRun;
|
|
65
|
+
}
|
|
66
|
+
get steering() {
|
|
67
|
+
return this._steering;
|
|
68
|
+
}
|
|
69
|
+
// ── run ──────────────────────────────────────────────────────────
|
|
70
|
+
async run(spec, customDag) {
|
|
71
|
+
await this.gateIndex();
|
|
72
|
+
// Load protocol.
|
|
73
|
+
const { protocolMap, masterProtocolSection } = await loadProtocol(this.opts.workspaceRoot);
|
|
74
|
+
this.protocolMap = protocolMap;
|
|
75
|
+
this.masterProtocolSection = masterProtocolSection;
|
|
76
|
+
const init = await initializeRun(spec, this.opts, protocolMap, masterProtocolSection, customDag);
|
|
77
|
+
this.lessonContext = init.lessonContext;
|
|
78
|
+
if (init.isConversation) {
|
|
79
|
+
return runConversation(spec, this.opts, protocolMap, masterProtocolSection);
|
|
80
|
+
}
|
|
81
|
+
const run = init.run;
|
|
82
|
+
await saveProgress(this.opts.workspaceRoot, run);
|
|
83
|
+
return this._runLoop(run);
|
|
84
|
+
}
|
|
85
|
+
// ── resume ───────────────────────────────────────────────────────
|
|
86
|
+
async resume(taskId) {
|
|
87
|
+
await this.gateIndex();
|
|
88
|
+
const { protocolMap, masterProtocolSection } = await loadProtocol(this.opts.workspaceRoot);
|
|
89
|
+
this.protocolMap = protocolMap;
|
|
90
|
+
this.masterProtocolSection = masterProtocolSection;
|
|
91
|
+
const init = await resumeRun(taskId, this.opts, protocolMap, masterProtocolSection);
|
|
92
|
+
if (init.run.status === "done")
|
|
93
|
+
return init.run;
|
|
94
|
+
const run = init.run;
|
|
95
|
+
await saveProgress(this.opts.workspaceRoot, run);
|
|
96
|
+
return this._runLoop(run);
|
|
97
|
+
}
|
|
98
|
+
// ── halt ─────────────────────────────────────────────────────────
|
|
99
|
+
async halt(currentRun) {
|
|
100
|
+
if (!this._abortController) {
|
|
101
|
+
this._abortController = new AbortController();
|
|
102
|
+
}
|
|
103
|
+
this._abortController.abort();
|
|
104
|
+
if (this._currentRun) {
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
this._haltDeferred = { resolve, reject };
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
// Not running — compute halt inline.
|
|
110
|
+
const summary = await haltInternal(currentRun, this.opts, saveTaskRunSync);
|
|
111
|
+
return summary;
|
|
112
|
+
}
|
|
113
|
+
// ── Private: wraps executeLoop with halt/abort lifecycle ────────
|
|
114
|
+
async _runLoop(run) {
|
|
115
|
+
this._currentRun = run;
|
|
116
|
+
this._abortController = new AbortController();
|
|
117
|
+
const signal = this.opts.signal ?? this._abortController.signal;
|
|
118
|
+
// Wire up the orchestration context with callbacks.
|
|
119
|
+
const ctx = {
|
|
120
|
+
opts: this.opts,
|
|
121
|
+
protocolMap: this.protocolMap,
|
|
122
|
+
masterProtocolSection: this.masterProtocolSection,
|
|
123
|
+
lessonContext: this.lessonContext,
|
|
124
|
+
signal,
|
|
125
|
+
steering: this._steering,
|
|
126
|
+
quiet: this.opts.quiet ?? false,
|
|
127
|
+
onSteeringInjection: async (r) => {
|
|
128
|
+
return checkSteeringInjection(r, this.opts, this._steering);
|
|
129
|
+
},
|
|
130
|
+
onSaveProgress: async (r) => {
|
|
131
|
+
await saveProgress(this.opts.workspaceRoot, r);
|
|
132
|
+
},
|
|
133
|
+
};
|
|
134
|
+
try {
|
|
135
|
+
// Initialize footer iff the interactive gate passes.
|
|
136
|
+
// Availability matrix (single source of truth in src/util/tty.ts):
|
|
137
|
+
// TTY attached AND NO_COLOR unset. Quiet run mode mutes painting.
|
|
138
|
+
FooterManager.init({
|
|
139
|
+
tty: isInteractiveStdout(),
|
|
140
|
+
// Raw goal — the renderer owns the ≤50-char title field (footer-text.ts).
|
|
141
|
+
taskTitle: run.spec.goal,
|
|
142
|
+
quiet: this.opts.quiet ?? false,
|
|
143
|
+
});
|
|
144
|
+
const result = await executeLoop(run, ctx);
|
|
145
|
+
// Cleanup.
|
|
146
|
+
this._currentRun = null;
|
|
147
|
+
this._abortController = null;
|
|
148
|
+
// If halt() was called but the task finished before the abort check,
|
|
149
|
+
// still provide a summary.
|
|
150
|
+
if (this._haltDeferred) {
|
|
151
|
+
const dag = result.dag;
|
|
152
|
+
const summary = {
|
|
153
|
+
taskId: result.spec.taskId,
|
|
154
|
+
doneCount: dag.filter((n) => n.status === "done").length,
|
|
155
|
+
totalCount: dag.length,
|
|
156
|
+
resumeCommand: `xcoder resume ${result.spec.taskId}`,
|
|
157
|
+
taskFilePath: `.xcoder/tasks/${result.spec.taskId}.json`,
|
|
158
|
+
};
|
|
159
|
+
this._haltDeferred.resolve(summary);
|
|
160
|
+
this._haltDeferred = null;
|
|
161
|
+
}
|
|
162
|
+
return result;
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
this._currentRun = null;
|
|
166
|
+
this._abortController = null;
|
|
167
|
+
throw e;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { Role, SdlcStage, TaskInputs } from "../types.js";
|
|
2
|
+
export declare const SDLC_STAGES: {
|
|
3
|
+
stage: SdlcStage;
|
|
4
|
+
description: string;
|
|
5
|
+
}[];
|
|
6
|
+
export declare const STAGE_ROLE: Record<SdlcStage, Role>;
|
|
7
|
+
/**
|
|
8
|
+
* Internal roles that exist for traceability and system functions but are
|
|
9
|
+
* not mapped to any DAG stage — they are invoked inline by the orchestrator
|
|
10
|
+
* rather than dispatched as standalone Sub Agents.
|
|
11
|
+
*/
|
|
12
|
+
export declare const INTERNAL_ROLES: Role[];
|
|
13
|
+
/**
|
|
14
|
+
* Explicit intake classification. This exists specifically because "the
|
|
15
|
+
* orchestrator infers where to start" was a hidden judgment call in the
|
|
16
|
+
* original sketch — inconsistent behavior waiting to happen. Every rule
|
|
17
|
+
* here is a plain, inspectable predicate over what inputs are actually
|
|
18
|
+
* present, checked top to bottom, first match wins.
|
|
19
|
+
*/
|
|
20
|
+
export interface IntakeRule {
|
|
21
|
+
name: string;
|
|
22
|
+
when: (inputs: TaskInputs) => boolean;
|
|
23
|
+
startStage: SdlcStage;
|
|
24
|
+
/** The stage sequence this kind of task walks, in order. */
|
|
25
|
+
sequence: SdlcStage[];
|
|
26
|
+
}
|
|
27
|
+
export declare const INTAKE_RULES: IntakeRule[];
|
|
28
|
+
export declare const CONVERSATION_RULE: IntakeRule;
|
|
29
|
+
export declare function classifyIntake(inputs: TaskInputs): IntakeRule;
|
|
30
|
+
export declare function classifyDesignRole(inputs: TaskInputs): Role;
|
|
31
|
+
/**
|
|
32
|
+
* Returns the Role for a given SdlcStage. For the `design` stage, when
|
|
33
|
+
* `inputs` is provided, this delegates to `classifyDesignRole` to decide
|
|
34
|
+
* between `architect` and `business-analyst`. All other stages — and the
|
|
35
|
+
* `design` stage when `inputs` is omitted — use the static default mapping
|
|
36
|
+
* below, preserving backward compatibility with every existing call site.
|
|
37
|
+
*/
|
|
38
|
+
export declare function getStageRole(stage: SdlcStage, inputs?: TaskInputs): Role;
|
|
39
|
+
/**
|
|
40
|
+
* Returns true when the given role is one of the internal (non-DAG-stage)
|
|
41
|
+
* roles such as `defect-manager`. These roles are never dispatched as Sub
|
|
42
|
+
* Agents — they exist for traceability and orchestrator-inline decisions.
|
|
43
|
+
*/
|
|
44
|
+
export declare function isInternalRole(role: Role): boolean;
|