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,32 @@
|
|
|
1
|
+
// xcoder:version 1 | xcoder:task task-c49600 | xcoder:updated 2026-08-13T13:51:25.757Z | xcoder:subtask code-st-6d79ef
|
|
2
|
+
import { LlmClient } from "./client.js";
|
|
3
|
+
import { loadLlmConfigFile, resolveLlmConfig, } from "../config/loadConfig.js";
|
|
4
|
+
/**
|
|
5
|
+
* Sole production constructor path for LLM clients. Resolves the effective
|
|
6
|
+
* per-role configuration from `.xcoder/config/llm.yaml` (or built-in
|
|
7
|
+
* DeepSeek defaults) and returns a transport ready for that role.
|
|
8
|
+
*
|
|
9
|
+
* Precedence in `resolveLlmConfig`: role block → `default` block → built-in
|
|
10
|
+
* DeepSeek; `XCODER_MODEL` applies to `model` only.
|
|
11
|
+
*/
|
|
12
|
+
export function createLlmClient(role, overrides = {}) {
|
|
13
|
+
const file = loadLlmConfigFile();
|
|
14
|
+
const config = resolveLlmConfig(role, file);
|
|
15
|
+
const opts = configToClientOptions(config);
|
|
16
|
+
return new LlmClient({
|
|
17
|
+
...opts,
|
|
18
|
+
model: overrides.model ?? opts.model,
|
|
19
|
+
maxTokens: overrides.maxTokens ?? opts.maxTokens,
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
function configToClientOptions(config) {
|
|
23
|
+
return {
|
|
24
|
+
provider: config.provider ?? "deepseek",
|
|
25
|
+
baseUrl: config.base_url,
|
|
26
|
+
endpoint: config.endpoint,
|
|
27
|
+
model: config.model,
|
|
28
|
+
maxTokens: config.max_tokens,
|
|
29
|
+
temperature: config.temperature,
|
|
30
|
+
apiKeyEnv: config.api_key_env,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export interface DefectLogEntry {
|
|
2
|
+
timestamp: string;
|
|
3
|
+
defectId: string;
|
|
4
|
+
event: "discovered" | "fix_attempt_start" | "fix_attempt_result" | "resolved" | "closed" | "reopened";
|
|
5
|
+
detail: string;
|
|
6
|
+
metadata?: Record<string, unknown>;
|
|
7
|
+
}
|
|
8
|
+
export interface MigrationEntry {
|
|
9
|
+
timestamp: string;
|
|
10
|
+
oldPath: string;
|
|
11
|
+
newPath: string;
|
|
12
|
+
defectId: string;
|
|
13
|
+
status: "migrated" | "skipped" | "failed";
|
|
14
|
+
reason: string;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Appends one JSON line to `.log/defect-rca-check.log`. Creates the
|
|
18
|
+
* directory and file if they don't exist. Follows the 1MB rollover
|
|
19
|
+
* pattern from `logs.ts`.
|
|
20
|
+
*/
|
|
21
|
+
export declare function logDefectEvent(workspaceRoot: string, entry: DefectLogEntry): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Reads and parses the NDJSON log file. If `defectId` is provided,
|
|
24
|
+
* filters to events for that defect only. Used by the orchestrator to
|
|
25
|
+
* reconstruct the RCA check status.
|
|
26
|
+
*/
|
|
27
|
+
export declare function readDefectLog(workspaceRoot: string, defectId?: string): Promise<DefectLogEntry[]>;
|
|
28
|
+
/**
|
|
29
|
+
* Writes migration audit records to `.log/migration.log` in NDJSON format.
|
|
30
|
+
*/
|
|
31
|
+
export declare function logMigration(workspaceRoot: string, entries: MigrationEntry[]): Promise<void>;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Path resolution
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
const MAX_BYTES = 1_000_000; // 1MB rollover threshold
|
|
7
|
+
function logFilePath(workspaceRoot) {
|
|
8
|
+
const logDir = process.env.XCODER_LOG_DIR ?? ".log";
|
|
9
|
+
return path.join(workspaceRoot, logDir, "defect-rca-check.log");
|
|
10
|
+
}
|
|
11
|
+
function migrationLogPath(workspaceRoot) {
|
|
12
|
+
const logDir = process.env.XCODER_LOG_DIR ?? ".log";
|
|
13
|
+
return path.join(workspaceRoot, logDir, "migration.log");
|
|
14
|
+
}
|
|
15
|
+
// ---------------------------------------------------------------------------
|
|
16
|
+
// Internal helpers
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
/**
|
|
19
|
+
* Appends a line to a file, with a 1MB rollover pattern matching `logs.ts`.
|
|
20
|
+
*/
|
|
21
|
+
async function appendWithRollover(filePath, line) {
|
|
22
|
+
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
23
|
+
const entry = line.endsWith("\n") ? line : line + "\n";
|
|
24
|
+
let currentSize = 0;
|
|
25
|
+
try {
|
|
26
|
+
currentSize = (await fs.stat(filePath)).size;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
currentSize = 0;
|
|
30
|
+
}
|
|
31
|
+
if (currentSize + Buffer.byteLength(entry, "utf8") > MAX_BYTES) {
|
|
32
|
+
const rolled = filePath.replace(/(\.[^.]+)$/, ".1$1");
|
|
33
|
+
try {
|
|
34
|
+
await fs.rm(rolled, { force: true });
|
|
35
|
+
await fs.rename(filePath, rolled);
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
// best-effort; if the rename fails just keep appending to the current file
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
await fs.appendFile(filePath, entry, "utf8");
|
|
42
|
+
}
|
|
43
|
+
function ts() {
|
|
44
|
+
return new Date().toISOString();
|
|
45
|
+
}
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Public API
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
/**
|
|
50
|
+
* Appends one JSON line to `.log/defect-rca-check.log`. Creates the
|
|
51
|
+
* directory and file if they don't exist. Follows the 1MB rollover
|
|
52
|
+
* pattern from `logs.ts`.
|
|
53
|
+
*/
|
|
54
|
+
export async function logDefectEvent(workspaceRoot, entry) {
|
|
55
|
+
await appendWithRollover(logFilePath(workspaceRoot), JSON.stringify(entry));
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Reads and parses the NDJSON log file. If `defectId` is provided,
|
|
59
|
+
* filters to events for that defect only. Used by the orchestrator to
|
|
60
|
+
* reconstruct the RCA check status.
|
|
61
|
+
*/
|
|
62
|
+
export async function readDefectLog(workspaceRoot, defectId) {
|
|
63
|
+
const file = logFilePath(workspaceRoot);
|
|
64
|
+
let raw = "";
|
|
65
|
+
try {
|
|
66
|
+
raw = await fs.readFile(file, "utf-8");
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
const entries = [];
|
|
72
|
+
for (const line of raw.split("\n")) {
|
|
73
|
+
const trimmed = line.trim();
|
|
74
|
+
if (!trimmed)
|
|
75
|
+
continue;
|
|
76
|
+
try {
|
|
77
|
+
const parsed = JSON.parse(trimmed);
|
|
78
|
+
if (parsed &&
|
|
79
|
+
typeof parsed === "object" &&
|
|
80
|
+
typeof parsed.timestamp === "string" &&
|
|
81
|
+
typeof parsed.defectId === "string" &&
|
|
82
|
+
typeof parsed.event === "string") {
|
|
83
|
+
if (!defectId || parsed.defectId === defectId) {
|
|
84
|
+
entries.push(parsed);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// Skip malformed lines.
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return entries;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Writes migration audit records to `.log/migration.log` in NDJSON format.
|
|
96
|
+
*/
|
|
97
|
+
export async function logMigration(workspaceRoot, entries) {
|
|
98
|
+
for (const entry of entries) {
|
|
99
|
+
await appendWithRollover(migrationLogPath(workspaceRoot), JSON.stringify(entry));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { Role, SdlcStage, SubtaskNode, TaskSpec } from "../types.js";
|
|
2
|
+
import type { DefectsIndex } from "./defect-store.js";
|
|
3
|
+
/**
|
|
4
|
+
* Parsed representation of a defect RCA file written by a tester.
|
|
5
|
+
* The file format must follow the Markdown contract defined in the
|
|
6
|
+
* design: a `# DEFECT-NNNN` title line followed by `## Observed
|
|
7
|
+
* Behaviour`, `## Expected Behaviour`, `## Reproduction Steps`,
|
|
8
|
+
* `## Root Cause Hypothesis`, `## Affected Files`, and `## Severity`.
|
|
9
|
+
*/
|
|
10
|
+
export interface DefectFile {
|
|
11
|
+
id: string;
|
|
12
|
+
path: string;
|
|
13
|
+
discoveredBy: string;
|
|
14
|
+
observedBehaviour: string;
|
|
15
|
+
expectedBehaviour: string;
|
|
16
|
+
reproductionSteps: string;
|
|
17
|
+
rootCauseHypothesis: string;
|
|
18
|
+
affectedFiles: string[];
|
|
19
|
+
severity: "low" | "medium" | "high" | "critical";
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The shape of `upstreamContext.defect` that a `fix_defect` node carries.
|
|
23
|
+
*/
|
|
24
|
+
export interface DefectContext {
|
|
25
|
+
defectId: string;
|
|
26
|
+
defectPath: string;
|
|
27
|
+
rca: {
|
|
28
|
+
observedBehaviour: string;
|
|
29
|
+
expectedBehaviour: string;
|
|
30
|
+
rootCauseHypothesis: string;
|
|
31
|
+
affectedFiles: string[];
|
|
32
|
+
severity: "low" | "medium" | "high" | "critical";
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Context passed to `defectManagerDecide` — this interface is retained
|
|
37
|
+
* for backwards compatibility but the decision logic now lives in
|
|
38
|
+
* `SdlcOrchestrator.handleResult`.
|
|
39
|
+
* @deprecated Use direct store/logger operations instead.
|
|
40
|
+
*/
|
|
41
|
+
export interface DefectActionContext {
|
|
42
|
+
dag: SubtaskNode[];
|
|
43
|
+
newDefects: DefectFile[];
|
|
44
|
+
/** Any fix_defect node in the DAG that was tasked with this defect
|
|
45
|
+
* and has now completed (passed or failed). */
|
|
46
|
+
completedFixNodes: {
|
|
47
|
+
nodeId: string;
|
|
48
|
+
defectPath: string;
|
|
49
|
+
passed: boolean;
|
|
50
|
+
}[];
|
|
51
|
+
/** The workspace root, needed for file-existence checks. */
|
|
52
|
+
workspaceRoot: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Scans the defects directory for files matching `defect[0-9]{4}.md`.
|
|
56
|
+
* The directory is resolved via `createXcoderPaths` (respects XCODER_DIR),
|
|
57
|
+
* or `XCODER_DEFECT_DIR` env var as override. A file is "new" if its id
|
|
58
|
+
* is not in the index at all, OR if the index entry has `status: 'closed'`
|
|
59
|
+
* and the file has been modified since `lastUpdated` (reopened). Returns
|
|
60
|
+
* parsed DefectFile array sorted by ID ascending.
|
|
61
|
+
*/
|
|
62
|
+
export declare function scanForDefects(workspaceRoot: string, index: DefectsIndex): Promise<DefectFile[]>;
|
|
63
|
+
/**
|
|
64
|
+
* Determines which role should receive a `fix_defect` node for the given
|
|
65
|
+
* defect. The v1 rule is simple: look at the tester's immediate upstream
|
|
66
|
+
* node in the DAG. If it was a `code` stage, route to `programmer`; if it
|
|
67
|
+
* was a `refactor` stage, route to `refactorer`.
|
|
68
|
+
*
|
|
69
|
+
* In the orchestrator integration, the caller (handleResult) knows which
|
|
70
|
+
* tester node discovered the defect, so it can look up that node's
|
|
71
|
+
* `dependsOn` and pass the upstream stage directly.
|
|
72
|
+
*/
|
|
73
|
+
export declare function classifyDefectRouting(upstreamStage: SdlcStage | undefined): Role;
|
|
74
|
+
/**
|
|
75
|
+
* Constructs a self-contained `fix_defect` `SubtaskNode` with the defect
|
|
76
|
+
* path and RCA inlined into `upstreamContext`, ready to be injected into
|
|
77
|
+
* the DAG.
|
|
78
|
+
*/
|
|
79
|
+
export declare function buildFixDefectNode(defect: DefectFile, role: Role, spec: TaskSpec, dependsOn: string[]): SubtaskNode;
|
|
80
|
+
/**
|
|
81
|
+
* Builds the goal text for a `fix_defect` node. When a defect path is
|
|
82
|
+
* present in upstreamContext, the goal becomes: "Confirm the defect
|
|
83
|
+
* described in {defectPath} no longer reproduces, via a real test run."
|
|
84
|
+
*
|
|
85
|
+
* Exported so task-factory.ts can reuse it for its own `buildGoalText`.
|
|
86
|
+
*/
|
|
87
|
+
export declare function buildFixDefectGoal(defectPath: string): string;
|
|
88
|
+
/**
|
|
89
|
+
* Called by the orchestrator when a fix_defect passes verification.
|
|
90
|
+
* Updates the `_defects.json` entry status to "resolved", sets
|
|
91
|
+
* `resolvedAt` and `resolvedBy`, logs the lifecycle event, and
|
|
92
|
+
* optionally archives the RCA file to `.xcoder/defects/archive/`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function resolveDefect(workspaceRoot: string, defectId: string, nodeId: string): Promise<void>;
|
|
95
|
+
/**
|
|
96
|
+
* Called by the orchestrator when a fix_defect node is created. Updates
|
|
97
|
+
* the `_defects.json` entry to add a `FixAttempt` record and sets status
|
|
98
|
+
* to `in_progress`. Logs the lifecycle event.
|
|
99
|
+
*/
|
|
100
|
+
export declare function recordFixAttempt(workspaceRoot: string, defectId: string, nodeId: string, attemptNumber: number): Promise<void>;
|
|
101
|
+
/**
|
|
102
|
+
* The Defect Manager's main entry point — retains the original signature
|
|
103
|
+
* for backwards compatibility. The orchestrator now handles the decision
|
|
104
|
+
* logic directly in `handleResult` since it has direct access to the
|
|
105
|
+
* store and logger.
|
|
106
|
+
*
|
|
107
|
+
* @deprecated Decision logic is now inlined in `SdlcOrchestrator.handleResult`.
|
|
108
|
+
*/
|
|
109
|
+
export declare function defectManagerDecide(ctx: DefectActionContext): {
|
|
110
|
+
type: "replan";
|
|
111
|
+
patch: {
|
|
112
|
+
addNodes: SubtaskNode[];
|
|
113
|
+
};
|
|
114
|
+
} | {
|
|
115
|
+
type: "escalate";
|
|
116
|
+
subtaskId: string;
|
|
117
|
+
reason: string;
|
|
118
|
+
} | null;
|
|
@@ -0,0 +1,338 @@
|
|
|
1
|
+
import { shortId } from "../util/ids.js";
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { loadDefectsIndex, upsertDefect } from "./defect-store.js";
|
|
5
|
+
import { logDefectEvent } from "./defect-logger.js";
|
|
6
|
+
import { createXcoderPaths } from "../util/xcoder-paths.js";
|
|
7
|
+
// ---------------------------------------------------------------------------
|
|
8
|
+
// Scanning
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
/** Pattern that defect files must match: defect followed by exactly four digits, then .md. */
|
|
11
|
+
const DEFECT_FILE_RE = /^defect(\d{4})\.md$/;
|
|
12
|
+
/**
|
|
13
|
+
* Scans the defects directory for files matching `defect[0-9]{4}.md`.
|
|
14
|
+
* The directory is resolved via `createXcoderPaths` (respects XCODER_DIR),
|
|
15
|
+
* or `XCODER_DEFECT_DIR` env var as override. A file is "new" if its id
|
|
16
|
+
* is not in the index at all, OR if the index entry has `status: 'closed'`
|
|
17
|
+
* and the file has been modified since `lastUpdated` (reopened). Returns
|
|
18
|
+
* parsed DefectFile array sorted by ID ascending.
|
|
19
|
+
*/
|
|
20
|
+
export async function scanForDefects(workspaceRoot, index) {
|
|
21
|
+
const scanDir = process.env.XCODER_DEFECT_DIR ?? createXcoderPaths(workspaceRoot).defectsDir();
|
|
22
|
+
const defectsDir = path.resolve(workspaceRoot, scanDir);
|
|
23
|
+
let entries = [];
|
|
24
|
+
try {
|
|
25
|
+
entries = await fs.readdir(defectsDir);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// No defects directory or it's unreadable — nothing to scan.
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
const newDefects = [];
|
|
32
|
+
for (const entry of entries) {
|
|
33
|
+
const match = entry.match(DEFECT_FILE_RE);
|
|
34
|
+
if (!match)
|
|
35
|
+
continue;
|
|
36
|
+
const numericId = match[1];
|
|
37
|
+
const defectId = `DEFECT-${numericId}`;
|
|
38
|
+
const fullPath = path.join(defectsDir, entry);
|
|
39
|
+
const relativePath = path.relative(workspaceRoot, fullPath).replace(/\\/g, "/");
|
|
40
|
+
// Check index: skip if already known and not reopened.
|
|
41
|
+
const existing = index[defectId];
|
|
42
|
+
if (existing && existing.status !== "closed") {
|
|
43
|
+
// Already tracked and not closed — skip.
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (existing && existing.status === "closed") {
|
|
47
|
+
// Check if file was modified since it was resolved (reopened).
|
|
48
|
+
try {
|
|
49
|
+
const stat = await fs.stat(fullPath);
|
|
50
|
+
const fileMtime = stat.mtime.toISOString();
|
|
51
|
+
if (fileMtime <= existing.lastUpdated) {
|
|
52
|
+
// File hasn't been touched since resolution — still closed.
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
// File modified after resolution — treat as reopened.
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// File unreadable — skip.
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const content = await fs.readFile(fullPath, "utf-8");
|
|
64
|
+
const defect = parseDefectFile(content, relativePath, numericId);
|
|
65
|
+
if (defect)
|
|
66
|
+
newDefects.push(defect);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// File unreadable — skip it.
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
newDefects.sort((a, b) => a.id.localeCompare(b.id));
|
|
73
|
+
return newDefects;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Parses the contents of a single defect Markdown file into a `DefectFile`.
|
|
77
|
+
* Returns `null` if the file doesn't conform to the minimum required
|
|
78
|
+
* structure (a `# DEFECT-` title and at least one recognised section).
|
|
79
|
+
*/
|
|
80
|
+
function parseDefectFile(content, relativePath, numericId) {
|
|
81
|
+
const titleMatch = content.match(/^#\s+DEFECT-(\d+)/m);
|
|
82
|
+
const id = titleMatch ? `DEFECT-${titleMatch[1]}` : `DEFECT-${numericId}`;
|
|
83
|
+
// Extract sections by heading — case-insensitive, `##` prefix.
|
|
84
|
+
const sections = extractSections(content);
|
|
85
|
+
const discoveredBy = sections["discovered by"] ??
|
|
86
|
+
sections["**discovered by:**"] ??
|
|
87
|
+
extractInlineMeta(content, /\*\*Discovered by:\*\*\s*(.*)/i) ??
|
|
88
|
+
"unknown";
|
|
89
|
+
const severityRaw = (sections["severity"] ??
|
|
90
|
+
extractInlineMeta(content, /\*\*Severity:\*\*\s*(.*)/i) ??
|
|
91
|
+
"medium").trim().toLowerCase();
|
|
92
|
+
const severity = ["low", "medium", "high", "critical"].includes(severityRaw)
|
|
93
|
+
? severityRaw
|
|
94
|
+
: "medium";
|
|
95
|
+
const observedBehaviour = sections["observed behaviour"] ?? sections["observed behavior"] ?? "";
|
|
96
|
+
const expectedBehaviour = sections["expected behaviour"] ?? sections["expected behavior"] ?? "";
|
|
97
|
+
const reproductionSteps = sections["reproduction steps"] ?? "";
|
|
98
|
+
const rootCauseHypothesis = sections["root cause hypothesis"] ?? "";
|
|
99
|
+
const affectedFilesRaw = sections["affected files"] ?? "";
|
|
100
|
+
const affectedFiles = affectedFilesRaw
|
|
101
|
+
.split(/\n/)
|
|
102
|
+
.map((line) => line.trim())
|
|
103
|
+
.filter((line) => line.startsWith("- ") || line.startsWith("*"))
|
|
104
|
+
.map((line) => line.replace(/^[-*]\s+/, "").replace(/`/g, "").trim())
|
|
105
|
+
.filter(Boolean);
|
|
106
|
+
// Must have at least the core RCA fields populated.
|
|
107
|
+
if (!observedBehaviour && !expectedBehaviour && !rootCauseHypothesis)
|
|
108
|
+
return null;
|
|
109
|
+
return {
|
|
110
|
+
id,
|
|
111
|
+
path: relativePath,
|
|
112
|
+
discoveredBy,
|
|
113
|
+
observedBehaviour,
|
|
114
|
+
expectedBehaviour,
|
|
115
|
+
reproductionSteps,
|
|
116
|
+
rootCauseHypothesis,
|
|
117
|
+
affectedFiles,
|
|
118
|
+
severity,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Extracts sections from Markdown content keyed by `## Heading` lines.
|
|
123
|
+
* Headings are normalised to lowercase for lookup. Body runs until the
|
|
124
|
+
* next `##` heading or EOF.
|
|
125
|
+
*/
|
|
126
|
+
function extractSections(content) {
|
|
127
|
+
const result = {};
|
|
128
|
+
const lines = content.split("\n");
|
|
129
|
+
let currentHeading = null;
|
|
130
|
+
let currentBody = [];
|
|
131
|
+
for (const line of lines) {
|
|
132
|
+
const headingMatch = line.match(/^##\s+(.+)/);
|
|
133
|
+
if (headingMatch) {
|
|
134
|
+
if (currentHeading) {
|
|
135
|
+
result[currentHeading.toLowerCase()] = currentBody.join("\n").trim();
|
|
136
|
+
}
|
|
137
|
+
currentHeading = headingMatch[1].trim();
|
|
138
|
+
currentBody = [];
|
|
139
|
+
}
|
|
140
|
+
else if (currentHeading) {
|
|
141
|
+
currentBody.push(line);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (currentHeading) {
|
|
145
|
+
result[currentHeading.toLowerCase()] = currentBody.join("\n").trim();
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
/** Extracts a value from an inline bold-meta line like `**Key:** value`. */
|
|
150
|
+
function extractInlineMeta(content, regex) {
|
|
151
|
+
const match = content.match(regex);
|
|
152
|
+
return match ? match[1].trim() : null;
|
|
153
|
+
}
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Routing
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
/**
|
|
158
|
+
* Determines which role should receive a `fix_defect` node for the given
|
|
159
|
+
* defect. The v1 rule is simple: look at the tester's immediate upstream
|
|
160
|
+
* node in the DAG. If it was a `code` stage, route to `programmer`; if it
|
|
161
|
+
* was a `refactor` stage, route to `refactorer`.
|
|
162
|
+
*
|
|
163
|
+
* In the orchestrator integration, the caller (handleResult) knows which
|
|
164
|
+
* tester node discovered the defect, so it can look up that node's
|
|
165
|
+
* `dependsOn` and pass the upstream stage directly.
|
|
166
|
+
*/
|
|
167
|
+
export function classifyDefectRouting(upstreamStage) {
|
|
168
|
+
if (upstreamStage === "code")
|
|
169
|
+
return "programmer";
|
|
170
|
+
if (upstreamStage === "refactor")
|
|
171
|
+
return "refactorer";
|
|
172
|
+
// Fallback: if unsure, route to programmer (the safer default — new code is more likely).
|
|
173
|
+
return "programmer";
|
|
174
|
+
}
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
// Node construction
|
|
177
|
+
// ---------------------------------------------------------------------------
|
|
178
|
+
/**
|
|
179
|
+
* Constructs a self-contained `fix_defect` `SubtaskNode` with the defect
|
|
180
|
+
* path and RCA inlined into `upstreamContext`, ready to be injected into
|
|
181
|
+
* the DAG.
|
|
182
|
+
*/
|
|
183
|
+
export function buildFixDefectNode(defect, role, spec, dependsOn) {
|
|
184
|
+
const nodeId = `fix_defect-${shortId("df")}`;
|
|
185
|
+
const defectContext = {
|
|
186
|
+
defectId: defect.id,
|
|
187
|
+
defectPath: defect.path,
|
|
188
|
+
rca: {
|
|
189
|
+
observedBehaviour: defect.observedBehaviour,
|
|
190
|
+
expectedBehaviour: defect.expectedBehaviour,
|
|
191
|
+
rootCauseHypothesis: defect.rootCauseHypothesis,
|
|
192
|
+
affectedFiles: defect.affectedFiles,
|
|
193
|
+
severity: defect.severity,
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
const goal = buildFixDefectGoal(defect.path);
|
|
197
|
+
return {
|
|
198
|
+
id: nodeId,
|
|
199
|
+
stage: "fix_defect",
|
|
200
|
+
role,
|
|
201
|
+
engine: "react-v1",
|
|
202
|
+
dependsOn,
|
|
203
|
+
goal,
|
|
204
|
+
upstreamContext: { defect: defectContext },
|
|
205
|
+
deliverableSpec: "Confirmation the original defect no longer reproduces, via a real test run.",
|
|
206
|
+
acceptanceCriteria: [
|
|
207
|
+
{
|
|
208
|
+
id: "AC1",
|
|
209
|
+
type: "command",
|
|
210
|
+
description: "A targeted re-run confirms the original defect no longer reproduces.",
|
|
211
|
+
},
|
|
212
|
+
],
|
|
213
|
+
status: "pending",
|
|
214
|
+
healing: {
|
|
215
|
+
attempts: 0,
|
|
216
|
+
maxAttempts: spec.constraints.maxHealingAttempts,
|
|
217
|
+
log: [],
|
|
218
|
+
},
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Builds the goal text for a `fix_defect` node. When a defect path is
|
|
223
|
+
* present in upstreamContext, the goal becomes: "Confirm the defect
|
|
224
|
+
* described in {defectPath} no longer reproduces, via a real test run."
|
|
225
|
+
*
|
|
226
|
+
* Exported so task-factory.ts can reuse it for its own `buildGoalText`.
|
|
227
|
+
*/
|
|
228
|
+
export function buildFixDefectGoal(defectPath) {
|
|
229
|
+
return `Confirm the defect described in ${defectPath} no longer reproduces, via a real test run.`;
|
|
230
|
+
}
|
|
231
|
+
// ---------------------------------------------------------------------------
|
|
232
|
+
// Defect lifecycle helpers
|
|
233
|
+
// ---------------------------------------------------------------------------
|
|
234
|
+
/**
|
|
235
|
+
* Called by the orchestrator when a fix_defect passes verification.
|
|
236
|
+
* Updates the `_defects.json` entry status to "resolved", sets
|
|
237
|
+
* `resolvedAt` and `resolvedBy`, logs the lifecycle event, and
|
|
238
|
+
* optionally archives the RCA file to `.xcoder/defects/archive/`.
|
|
239
|
+
*/
|
|
240
|
+
export async function resolveDefect(workspaceRoot, defectId, nodeId) {
|
|
241
|
+
const index = await loadDefectsIndex(workspaceRoot);
|
|
242
|
+
const record = index[defectId];
|
|
243
|
+
if (!record)
|
|
244
|
+
return;
|
|
245
|
+
const now = new Date().toISOString();
|
|
246
|
+
record.status = "resolved";
|
|
247
|
+
record.resolvedAt = now;
|
|
248
|
+
record.resolvedBy = nodeId;
|
|
249
|
+
record.lastUpdated = now;
|
|
250
|
+
// Mark the latest fix attempt as passed.
|
|
251
|
+
if (record.fixAttempts.length > 0) {
|
|
252
|
+
const last = record.fixAttempts[record.fixAttempts.length - 1];
|
|
253
|
+
last.completedAt = now;
|
|
254
|
+
last.passed = true;
|
|
255
|
+
last.notes = `Resolved by ${nodeId}`;
|
|
256
|
+
}
|
|
257
|
+
await upsertDefect(workspaceRoot, record);
|
|
258
|
+
// Log lifecycle event.
|
|
259
|
+
await logDefectEvent(workspaceRoot, {
|
|
260
|
+
timestamp: now,
|
|
261
|
+
defectId,
|
|
262
|
+
event: "resolved",
|
|
263
|
+
detail: `Defect ${defectId} resolved by ${nodeId}`,
|
|
264
|
+
metadata: { fixNodeId: nodeId },
|
|
265
|
+
});
|
|
266
|
+
// Optionally archive the RCA file.
|
|
267
|
+
try {
|
|
268
|
+
const scanDir = process.env.XCODER_DEFECT_DIR ?? createXcoderPaths(workspaceRoot).defectsDir();
|
|
269
|
+
const archiveDir = path.join(path.resolve(workspaceRoot, scanDir), "archive");
|
|
270
|
+
const srcPath = path.join(workspaceRoot, record.path);
|
|
271
|
+
const basename = path.basename(record.path);
|
|
272
|
+
const destPath = path.join(archiveDir, basename);
|
|
273
|
+
await fs.mkdir(archiveDir, { recursive: true });
|
|
274
|
+
await fs.rename(srcPath, destPath);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
// Archive is best-effort — file may already have been moved or deleted.
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Called by the orchestrator when a fix_defect node is created. Updates
|
|
282
|
+
* the `_defects.json` entry to add a `FixAttempt` record and sets status
|
|
283
|
+
* to `in_progress`. Logs the lifecycle event.
|
|
284
|
+
*/
|
|
285
|
+
export async function recordFixAttempt(workspaceRoot, defectId, nodeId, attemptNumber) {
|
|
286
|
+
const index = await loadDefectsIndex(workspaceRoot);
|
|
287
|
+
const record = index[defectId];
|
|
288
|
+
if (!record)
|
|
289
|
+
return;
|
|
290
|
+
const now = new Date().toISOString();
|
|
291
|
+
record.status = "in_progress";
|
|
292
|
+
record.lastUpdated = now;
|
|
293
|
+
record.fixAttempts.push({
|
|
294
|
+
attemptNumber,
|
|
295
|
+
fixNodeId: nodeId,
|
|
296
|
+
startedAt: now,
|
|
297
|
+
completedAt: undefined,
|
|
298
|
+
passed: false,
|
|
299
|
+
notes: undefined,
|
|
300
|
+
});
|
|
301
|
+
await upsertDefect(workspaceRoot, record);
|
|
302
|
+
// Log lifecycle event.
|
|
303
|
+
await logDefectEvent(workspaceRoot, {
|
|
304
|
+
timestamp: now,
|
|
305
|
+
defectId,
|
|
306
|
+
event: "fix_attempt_start",
|
|
307
|
+
detail: `Fix attempt ${attemptNumber} started by ${nodeId}`,
|
|
308
|
+
metadata: { fixNodeId: nodeId, attemptNumber },
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
// ---------------------------------------------------------------------------
|
|
312
|
+
// Decision function
|
|
313
|
+
// ---------------------------------------------------------------------------
|
|
314
|
+
/**
|
|
315
|
+
* The Defect Manager's main entry point — retains the original signature
|
|
316
|
+
* for backwards compatibility. The orchestrator now handles the decision
|
|
317
|
+
* logic directly in `handleResult` since it has direct access to the
|
|
318
|
+
* store and logger.
|
|
319
|
+
*
|
|
320
|
+
* @deprecated Decision logic is now inlined in `SdlcOrchestrator.handleResult`.
|
|
321
|
+
*/
|
|
322
|
+
export function defectManagerDecide(ctx) {
|
|
323
|
+
for (const completed of ctx.completedFixNodes) {
|
|
324
|
+
if (!completed.passed) {
|
|
325
|
+
return {
|
|
326
|
+
type: "escalate",
|
|
327
|
+
subtaskId: completed.nodeId,
|
|
328
|
+
reason: `Unresolved defect ${completed.defectPath} after healing exhaustion`,
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
if (ctx.newDefects.length === 0)
|
|
333
|
+
return null;
|
|
334
|
+
return {
|
|
335
|
+
type: "replan",
|
|
336
|
+
patch: { addNodes: [] },
|
|
337
|
+
};
|
|
338
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DefectRecord } from "./defect-store.js";
|
|
2
|
+
/**
|
|
3
|
+
* One-time auto-detection and migration of a legacy `defects/` directory
|
|
4
|
+
* into `.xcoder/defects/`. Runs once per workspace lifetime (guarded by a
|
|
5
|
+
* `.xcoder/.migrated` sentinel file).
|
|
6
|
+
*
|
|
7
|
+
* If the sentinel is absent, checks for a `defects/` directory. If found,
|
|
8
|
+
* moves all `defect*.md` files to `.xcoder/defects/`, writes a
|
|
9
|
+
* `_defects.json` with `status: 'open'` for each, writes
|
|
10
|
+
* `.log/migration.log`, creates the sentinel, and returns the migrated
|
|
11
|
+
* records. If `defects/` doesn't exist, still creates the sentinel to
|
|
12
|
+
* avoid future checks.
|
|
13
|
+
*/
|
|
14
|
+
export declare function migrateLegacyDefects(workspaceRoot: string): Promise<DefectRecord[]>;
|