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,164 @@
|
|
|
1
|
+
// xcoder:version 6 | xcoder:task task-e6370a | xcoder:updated 2026-08-12T16:41:56.324Z | xcoder:subtask code-st-cd4444
|
|
2
|
+
import { promises as fs } from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { getOrBuildIndex, toPosixPath } from "./workspace-index.js";
|
|
5
|
+
import { resolveInWorkspace } from "./patch-tools.js";
|
|
6
|
+
import { scanFile } from "./ast-tools.js";
|
|
7
|
+
const memo = new Map();
|
|
8
|
+
const EXT_IMPORTABLE = new Set([
|
|
9
|
+
".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts",
|
|
10
|
+
".py", ".go", ".java", ".rs", ".rb",
|
|
11
|
+
]);
|
|
12
|
+
function guessScanLanguages(relPath) {
|
|
13
|
+
const lower = relPath.toLowerCase();
|
|
14
|
+
if (lower.endsWith(".ts") || lower.endsWith(".tsx") || lower.endsWith(".mts") || lower.endsWith(".cts"))
|
|
15
|
+
return "typescript";
|
|
16
|
+
if (lower.endsWith(".js") || lower.endsWith(".jsx") || lower.endsWith(".mjs") || lower.endsWith(".cjs"))
|
|
17
|
+
return "javascript";
|
|
18
|
+
if (lower.endsWith(".py"))
|
|
19
|
+
return "python";
|
|
20
|
+
return "none";
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Best-effort import index over `WorkspaceIndex.entries`, memoized by index
|
|
24
|
+
* `builtAt` so repeated calls in one run don't re-scan the tree.
|
|
25
|
+
*/
|
|
26
|
+
async function buildImportIndex(root, index) {
|
|
27
|
+
const cached = memo.get(index.builtAt);
|
|
28
|
+
if (cached)
|
|
29
|
+
return cached;
|
|
30
|
+
const importsByFile = new Map();
|
|
31
|
+
for (const entry of index.entries) {
|
|
32
|
+
if (!EXT_IMPORTABLE.has(path.extname(entry.relPath)))
|
|
33
|
+
continue;
|
|
34
|
+
let content;
|
|
35
|
+
try {
|
|
36
|
+
content = await fs.readFile(path.join(root, entry.relPath), "utf8");
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const lang = guessScanLanguages(entry.relPath);
|
|
42
|
+
let specifiers = [];
|
|
43
|
+
if (lang === "typescript" || lang === "javascript") {
|
|
44
|
+
const scan = scanFile(content, lang);
|
|
45
|
+
if (!("ok" in scan)) {
|
|
46
|
+
specifiers = scan.imports;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
specifiers = regexImports(content);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else if (lang === "python") {
|
|
53
|
+
specifiers = regexImports(content);
|
|
54
|
+
}
|
|
55
|
+
importsByFile.set(entry.relPath, specifiers
|
|
56
|
+
.map((s) => resolveSpecifier(s, entry.relPath, index) ?? s)
|
|
57
|
+
.filter((s) => s !== ""));
|
|
58
|
+
}
|
|
59
|
+
const built = { builtAt: index.builtAt, importsByFile };
|
|
60
|
+
memo.set(index.builtAt, built);
|
|
61
|
+
return built;
|
|
62
|
+
}
|
|
63
|
+
/** Regex fallback for non-TS/JS: Python `import x` / `from y import z`, etc. */
|
|
64
|
+
function regexImports(content) {
|
|
65
|
+
const out = [];
|
|
66
|
+
for (const line of content.split("\n")) {
|
|
67
|
+
const m = /^\s*(?:import|from)\s+([A-Za-z_][A-Za-z0-9_.]*)/.exec(line);
|
|
68
|
+
if (m)
|
|
69
|
+
out.push(m[1]);
|
|
70
|
+
}
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
/** Converts an import specifier to a relative path candidates list (TS/JS only). */
|
|
74
|
+
function resolveSpecifier(specifier, fromFile, index) {
|
|
75
|
+
if (specifier.startsWith(".") || specifier.startsWith("/")) {
|
|
76
|
+
const fromDir = path.posix.dirname(fromFile);
|
|
77
|
+
const candidates = [
|
|
78
|
+
specifier,
|
|
79
|
+
`${specifier}.ts`, `${specifier}.tsx`, `${specifier}.js`, `${specifier}.jsx`,
|
|
80
|
+
`${specifier}/index.ts`, `${specifier}/index.tsx`, `${specifier}/index.js`, `${specifier}/index.jsx`,
|
|
81
|
+
];
|
|
82
|
+
for (const c of candidates) {
|
|
83
|
+
const joined = toPosixPath(path.posix.normalize(path.posix.join(fromDir === "." ? "" : fromDir, c)));
|
|
84
|
+
if (index.entries.some((e) => e.relPath === joined))
|
|
85
|
+
return joined;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return null; // bare specifier = external package
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* get_dependency_graph — imports + importers for one file, best-effort.
|
|
93
|
+
* Never throws: unknown extensions (or unparseable files) return typed
|
|
94
|
+
* `unsupported`, missing files `file_not_found`.
|
|
95
|
+
*/
|
|
96
|
+
export async function getDependencyGraphTool(root, relPath) {
|
|
97
|
+
let full;
|
|
98
|
+
try {
|
|
99
|
+
full = resolveInWorkspace(root, relPath);
|
|
100
|
+
}
|
|
101
|
+
catch (e) {
|
|
102
|
+
return { ok: false, code: "workspace_escape", error: e.message };
|
|
103
|
+
}
|
|
104
|
+
const index = await getOrBuildIndex(root);
|
|
105
|
+
const posix = toPosixPath(relPath).replace(/^\.\//, "");
|
|
106
|
+
if (!index.entries.some((e) => e.relPath === posix)) {
|
|
107
|
+
return {
|
|
108
|
+
ok: false,
|
|
109
|
+
code: "file_not_found",
|
|
110
|
+
error: `"${relPath}" is not in the workspace index. Use write_file to create it, or search the index for the right path.`,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const ext = path.extname(posix).toLowerCase();
|
|
114
|
+
const lang = guessScanLanguages(posix);
|
|
115
|
+
if (lang === "none" && !EXT_IMPORTABLE.has(ext)) {
|
|
116
|
+
return {
|
|
117
|
+
ok: false,
|
|
118
|
+
code: "unsupported",
|
|
119
|
+
error: `get_dependency_graph has no import scanner for "${ext}" files — supported: TS/JS/Python. Fall back to grep/read_file.`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
let content;
|
|
123
|
+
try {
|
|
124
|
+
content = await fs.readFile(full, "utf8");
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
return { ok: false, code: "file_not_found", error: e.message };
|
|
128
|
+
}
|
|
129
|
+
let specifiers = [];
|
|
130
|
+
let parseMethod = "none";
|
|
131
|
+
if (lang === "typescript" || lang === "javascript") {
|
|
132
|
+
const scan = scanFile(content, lang);
|
|
133
|
+
if (!("ok" in scan)) {
|
|
134
|
+
specifiers = scan.imports;
|
|
135
|
+
parseMethod = "tokenizer";
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
specifiers = regexImports(content);
|
|
139
|
+
parseMethod = "regex";
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
specifiers = regexImports(content);
|
|
144
|
+
parseMethod = "regex";
|
|
145
|
+
}
|
|
146
|
+
const imports = specifiers.map((specifier) => ({
|
|
147
|
+
specifier,
|
|
148
|
+
resolved: resolveSpecifier(specifier, posix, index),
|
|
149
|
+
isExternal: !specifier.startsWith(".") && !specifier.startsWith("/"),
|
|
150
|
+
}));
|
|
151
|
+
const note = content.includes("import(") || content.includes("require(")
|
|
152
|
+
? "dynamic imports (import()/require()) are documented, not resolved."
|
|
153
|
+
: undefined;
|
|
154
|
+
// Importers: files whose resolved import set includes this file.
|
|
155
|
+
const impIndex = await buildImportIndex(root, index);
|
|
156
|
+
const importers = [];
|
|
157
|
+
for (const [fromFile, fromImports] of impIndex.importsByFile) {
|
|
158
|
+
if (fromFile === posix)
|
|
159
|
+
continue;
|
|
160
|
+
if (fromImports.includes(posix))
|
|
161
|
+
importers.push(fromFile);
|
|
162
|
+
}
|
|
163
|
+
return { ok: true, relPath: posix, imports, importers, parseMethod: parseMethod, note };
|
|
164
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export declare class WorkspaceEscapeError extends Error {
|
|
2
|
+
}
|
|
3
|
+
export interface EditFileInput {
|
|
4
|
+
path: string;
|
|
5
|
+
old_str: string;
|
|
6
|
+
new_str: string;
|
|
7
|
+
}
|
|
8
|
+
export type EditFileResult = {
|
|
9
|
+
ok: true;
|
|
10
|
+
relPath: string;
|
|
11
|
+
replaced: true;
|
|
12
|
+
} | {
|
|
13
|
+
ok: false;
|
|
14
|
+
error: string;
|
|
15
|
+
code: "file_not_found" | "not_unique" | "no_match" | "workspace_escape" | "io_error";
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* The tool that exists specifically to fix "the programmer can't refactor
|
|
19
|
+
* large files": `write_file` requires reproducing an ENTIRE file's content
|
|
20
|
+
* to change one function, which is exactly where a model truncates,
|
|
21
|
+
* mis-copies, or silently drops unrelated code in a big file. `edit_file`
|
|
22
|
+
* takes a unique anchor (`old_str`) and its replacement — the same
|
|
23
|
+
* str_replace pattern Claude Code's own editor uses — so a correction to
|
|
24
|
+
* one function never risks the other 900 lines.
|
|
25
|
+
*
|
|
26
|
+
* `old_str` must match EXACTLY ONCE. Zero matches and multiple matches are
|
|
27
|
+
* both refused with a specific, actionable error (rather than guessing
|
|
28
|
+
* which occurrence was meant) so the model can widen its anchor with more
|
|
29
|
+
* surrounding context and retry.
|
|
30
|
+
*/
|
|
31
|
+
export declare function editFileTool(root: string, input: EditFileInput, ctx?: {
|
|
32
|
+
taskId: string;
|
|
33
|
+
subtaskId: string;
|
|
34
|
+
}): Promise<EditFileResult>;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { promises as fs } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { upsertEntry } from "./workspace-index.js";
|
|
4
|
+
import { getCommentSyntax, injectVersionHeader, } from "./file-header-manager.js";
|
|
5
|
+
import { incrementFileVersion } from "../store/file-version-store.js";
|
|
6
|
+
export class WorkspaceEscapeError extends Error {
|
|
7
|
+
}
|
|
8
|
+
function resolveInWorkspace(root, relPath) {
|
|
9
|
+
const full = path.resolve(root, relPath);
|
|
10
|
+
const normalizedRoot = path.resolve(root) + path.sep;
|
|
11
|
+
if (full + path.sep !== normalizedRoot && !full.startsWith(normalizedRoot)) {
|
|
12
|
+
throw new WorkspaceEscapeError(`path "${relPath}" resolves outside the workspace root — refusing.`);
|
|
13
|
+
}
|
|
14
|
+
return full;
|
|
15
|
+
}
|
|
16
|
+
function countOccurrences(haystack, needle) {
|
|
17
|
+
if (needle.length === 0)
|
|
18
|
+
return 0;
|
|
19
|
+
let count = 0;
|
|
20
|
+
let idx = 0;
|
|
21
|
+
while ((idx = haystack.indexOf(needle, idx)) !== -1) {
|
|
22
|
+
count++;
|
|
23
|
+
idx += needle.length;
|
|
24
|
+
}
|
|
25
|
+
return count;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The tool that exists specifically to fix "the programmer can't refactor
|
|
29
|
+
* large files": `write_file` requires reproducing an ENTIRE file's content
|
|
30
|
+
* to change one function, which is exactly where a model truncates,
|
|
31
|
+
* mis-copies, or silently drops unrelated code in a big file. `edit_file`
|
|
32
|
+
* takes a unique anchor (`old_str`) and its replacement — the same
|
|
33
|
+
* str_replace pattern Claude Code's own editor uses — so a correction to
|
|
34
|
+
* one function never risks the other 900 lines.
|
|
35
|
+
*
|
|
36
|
+
* `old_str` must match EXACTLY ONCE. Zero matches and multiple matches are
|
|
37
|
+
* both refused with a specific, actionable error (rather than guessing
|
|
38
|
+
* which occurrence was meant) so the model can widen its anchor with more
|
|
39
|
+
* surrounding context and retry.
|
|
40
|
+
*/
|
|
41
|
+
export async function editFileTool(root, input, ctx) {
|
|
42
|
+
let full;
|
|
43
|
+
try {
|
|
44
|
+
full = resolveInWorkspace(root, input.path);
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
return { ok: false, error: e.message, code: "workspace_escape" };
|
|
48
|
+
}
|
|
49
|
+
let content;
|
|
50
|
+
try {
|
|
51
|
+
content = await fs.readFile(full, "utf8");
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
return {
|
|
55
|
+
ok: false,
|
|
56
|
+
code: "file_not_found",
|
|
57
|
+
error: `"${input.path}" doesn't exist. Use write_file to create a new file instead of edit_file.`,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const occurrences = countOccurrences(content, input.old_str);
|
|
61
|
+
if (occurrences === 0) {
|
|
62
|
+
return {
|
|
63
|
+
ok: false,
|
|
64
|
+
code: "no_match",
|
|
65
|
+
error: `old_str was not found in "${input.path}". It must match the file's current content EXACTLY, ` +
|
|
66
|
+
`including whitespace/indentation. Re-read the file (or the relevant range) and copy the anchor precisely.`,
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
if (occurrences > 1) {
|
|
70
|
+
return {
|
|
71
|
+
ok: false,
|
|
72
|
+
code: "not_unique",
|
|
73
|
+
error: `old_str matches ${occurrences} locations in "${input.path}" — it must be unique. ` +
|
|
74
|
+
`Include more surrounding context (a few extra lines above/below) so the anchor pins exactly one spot.`,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
let next = content.replace(input.old_str, input.new_str);
|
|
78
|
+
// Header injection (when context is available and file type supports it)
|
|
79
|
+
if (ctx) {
|
|
80
|
+
const syntax = getCommentSyntax(input.path);
|
|
81
|
+
if (syntax) {
|
|
82
|
+
const meta = await incrementFileVersion(root, input.path, ctx.taskId, ctx.subtaskId);
|
|
83
|
+
const injected = injectVersionHeader(next, syntax, meta);
|
|
84
|
+
next = injected.content;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
await fs.writeFile(full, next, "utf8");
|
|
89
|
+
await upsertEntry(root, path.relative(root, full));
|
|
90
|
+
return { ok: true, relPath: input.path, replaced: true };
|
|
91
|
+
}
|
|
92
|
+
catch (e) {
|
|
93
|
+
return { ok: false, code: "io_error", error: e.message };
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { HeaderMeta } from "../store/file-version-store.js";
|
|
2
|
+
export type CommentSyntax = {
|
|
3
|
+
open: string;
|
|
4
|
+
close?: string;
|
|
5
|
+
};
|
|
6
|
+
export interface InjectResult {
|
|
7
|
+
content: string;
|
|
8
|
+
isNew: boolean;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Returns the comment markers and whether the file type supports headers.
|
|
12
|
+
* Maps extensions: .ts/.js/.tsx/.jsx/.css/.scss/.less → //
|
|
13
|
+
* .py/.sh/.yaml/.yml/.toml/Dockerfile/.env → #
|
|
14
|
+
* .html/.md/.mdx → <!-- --> pair
|
|
15
|
+
* .sql → --
|
|
16
|
+
* .json → null (skipped)
|
|
17
|
+
* Unknown extensions → null (skipped)
|
|
18
|
+
*/
|
|
19
|
+
export declare function getCommentSyntax(filePath: string): CommentSyntax | null;
|
|
20
|
+
/**
|
|
21
|
+
* Builds a version header block and prepends it to content. If content
|
|
22
|
+
* already begins with a xcoder header block, updates the existing one
|
|
23
|
+
* (version+1, new task/subtask/timestamp). Uses regex to detect xcoder
|
|
24
|
+
* headers. Returns new content and a flag indicating if this was a new
|
|
25
|
+
* header or an update.
|
|
26
|
+
*/
|
|
27
|
+
export declare function injectVersionHeader(content: string, syntax: CommentSyntax, meta: HeaderMeta): InjectResult;
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Extension → comment syntax lookup
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
function ext(filePath) {
|
|
5
|
+
const lastDot = filePath.lastIndexOf(".");
|
|
6
|
+
if (lastDot === -1)
|
|
7
|
+
return "";
|
|
8
|
+
return filePath.slice(lastDot).toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
// prettier-ignore
|
|
11
|
+
const LINE_COMMENT_EXTS = new Set([
|
|
12
|
+
".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".mts", ".cts",
|
|
13
|
+
".css", ".scss", ".less",
|
|
14
|
+
".java", ".swift", ".kt", ".kts", ".go", ".rs",
|
|
15
|
+
".php",
|
|
16
|
+
]);
|
|
17
|
+
// prettier-ignore
|
|
18
|
+
const HASH_COMMENT_EXTS = new Set([
|
|
19
|
+
".py", ".pyi", ".pyx",
|
|
20
|
+
".sh", ".bash", ".zsh",
|
|
21
|
+
".yaml", ".yml",
|
|
22
|
+
".toml",
|
|
23
|
+
".rb", ".rbs",
|
|
24
|
+
".pl", ".pm",
|
|
25
|
+
".cfg", ".conf", ".ini",
|
|
26
|
+
".gitignore", ".dockerignore",
|
|
27
|
+
".env", ".env.local", ".env.example",
|
|
28
|
+
]);
|
|
29
|
+
const HTML_COMMENT_EXTS = new Set([".html", ".htm", ".md", ".mdx"]);
|
|
30
|
+
// prettier-ignore
|
|
31
|
+
const SQL_COMMENT_EXTS = new Set([".sql", ".psql"]);
|
|
32
|
+
// Special filenames (no extension or non-standard ext)
|
|
33
|
+
const HASH_COMMENT_NAMES = new Set(["dockerfile", "makefile", "gemfile", "rakefile"]);
|
|
34
|
+
/**
|
|
35
|
+
* Returns the comment markers and whether the file type supports headers.
|
|
36
|
+
* Maps extensions: .ts/.js/.tsx/.jsx/.css/.scss/.less → //
|
|
37
|
+
* .py/.sh/.yaml/.yml/.toml/Dockerfile/.env → #
|
|
38
|
+
* .html/.md/.mdx → <!-- --> pair
|
|
39
|
+
* .sql → --
|
|
40
|
+
* .json → null (skipped)
|
|
41
|
+
* Unknown extensions → null (skipped)
|
|
42
|
+
*/
|
|
43
|
+
export function getCommentSyntax(filePath) {
|
|
44
|
+
const e = ext(filePath);
|
|
45
|
+
const name = filePath.split("/").pop()?.split("\\").pop()?.toLowerCase() ?? "";
|
|
46
|
+
// JSON files: no comment support — skip headers entirely
|
|
47
|
+
if (e === ".json")
|
|
48
|
+
return null;
|
|
49
|
+
// Binary / image / audio / video: skip
|
|
50
|
+
if ([
|
|
51
|
+
".png",
|
|
52
|
+
".jpg",
|
|
53
|
+
".jpeg",
|
|
54
|
+
".gif",
|
|
55
|
+
".svg",
|
|
56
|
+
".ico",
|
|
57
|
+
".webp",
|
|
58
|
+
".bmp",
|
|
59
|
+
".mp3",
|
|
60
|
+
".wav",
|
|
61
|
+
".ogg",
|
|
62
|
+
".mp4",
|
|
63
|
+
".webm",
|
|
64
|
+
".avi",
|
|
65
|
+
".mov",
|
|
66
|
+
".zip",
|
|
67
|
+
".tar",
|
|
68
|
+
".gz",
|
|
69
|
+
".rar",
|
|
70
|
+
".7z",
|
|
71
|
+
".pdf",
|
|
72
|
+
".woff",
|
|
73
|
+
".woff2",
|
|
74
|
+
".ttf",
|
|
75
|
+
".eot",
|
|
76
|
+
".lock",
|
|
77
|
+
].includes(e))
|
|
78
|
+
return null;
|
|
79
|
+
if (LINE_COMMENT_EXTS.has(e))
|
|
80
|
+
return { open: "//" };
|
|
81
|
+
if (HASH_COMMENT_EXTS.has(e) || HASH_COMMENT_NAMES.has(name))
|
|
82
|
+
return { open: "#" };
|
|
83
|
+
if (HTML_COMMENT_EXTS.has(e))
|
|
84
|
+
return { open: "<!--", close: "-->" };
|
|
85
|
+
if (SQL_COMMENT_EXTS.has(e))
|
|
86
|
+
return { open: "--" };
|
|
87
|
+
// Unknown extension: skip headers (conservative)
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
// Header construction / parsing
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
/**
|
|
94
|
+
* Build a single-line xcoder version header using the given comment syntax.
|
|
95
|
+
*
|
|
96
|
+
* Format per syntax:
|
|
97
|
+
* // xcoder:version 3 | xcoder:task TASK-001 | xcoder:updated 2026-01-23... | xcoder:subtask design-st-7bec8f
|
|
98
|
+
* # xcoder:version 3 | xcoder:task TASK-001 | xcoder:updated 2026-01-23... | xcoder:subtask design-st-7bec8f
|
|
99
|
+
* <!-- xcoder:version 3 | xcoder:task TASK-001 | xcoder:updated 2026-01-23... | xcoder:subtask design-st-7bec8f -->
|
|
100
|
+
* -- xcoder:version 3 | xcoder:task TASK-001 | xcoder:updated 2026-01-23... | xcoder:subtask design-st-7bec8f
|
|
101
|
+
*/
|
|
102
|
+
function buildHeaderLine(syntax, meta) {
|
|
103
|
+
const payload = `xcoder:version ${meta.version} | xcoder:task ${meta.taskId} | xcoder:updated ${meta.timestamp} | xcoder:subtask ${meta.subtaskId}`;
|
|
104
|
+
if (syntax.close) {
|
|
105
|
+
return `${syntax.open} ${payload} ${syntax.close}`;
|
|
106
|
+
}
|
|
107
|
+
return `${syntax.open} ${payload}`;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Regex that detects a xcoder version header line in any comment syntax.
|
|
111
|
+
* Matches lines like:
|
|
112
|
+
* // xcoder:version 3 | ...
|
|
113
|
+
* # xcoder:version 1 | ...
|
|
114
|
+
* <!-- xcoder:version 5 | ... -->
|
|
115
|
+
* -- xcoder:version 2 | ...
|
|
116
|
+
*/
|
|
117
|
+
const XCODER_HEADER_RE = /^\s*\S*\s*xcoder:version\s+\d+/m;
|
|
118
|
+
/**
|
|
119
|
+
* Builds a version header block and prepends it to content. If content
|
|
120
|
+
* already begins with a xcoder header block, updates the existing one
|
|
121
|
+
* (version+1, new task/subtask/timestamp). Uses regex to detect xcoder
|
|
122
|
+
* headers. Returns new content and a flag indicating if this was a new
|
|
123
|
+
* header or an update.
|
|
124
|
+
*/
|
|
125
|
+
export function injectVersionHeader(content, syntax, meta) {
|
|
126
|
+
const newLine = buildHeaderLine(syntax, meta);
|
|
127
|
+
// Check if a xcoder header already exists
|
|
128
|
+
const match = content.match(XCODER_HEADER_RE);
|
|
129
|
+
if (match) {
|
|
130
|
+
// Extract the full matched line (up to the next newline)
|
|
131
|
+
const matchIndex = match.index;
|
|
132
|
+
const lineStart = content.lastIndexOf("\n", matchIndex - 1) + 1;
|
|
133
|
+
const lineEnd = content.indexOf("\n", matchIndex);
|
|
134
|
+
const fullLine = lineEnd === -1 ? content.slice(lineStart) : content.slice(lineStart, lineEnd);
|
|
135
|
+
// Replace the old header line with the new one
|
|
136
|
+
const before = content.slice(0, lineStart);
|
|
137
|
+
const after = lineEnd === -1 ? "" : content.slice(lineEnd);
|
|
138
|
+
return { content: before + newLine + after, isNew: false };
|
|
139
|
+
}
|
|
140
|
+
// No existing header: prepend at top
|
|
141
|
+
// If the file starts with a shebang, insert the header line right after it
|
|
142
|
+
if (content.startsWith("#!")) {
|
|
143
|
+
const shebangEnd = content.indexOf("\n");
|
|
144
|
+
if (shebangEnd !== -1) {
|
|
145
|
+
return {
|
|
146
|
+
content: content.slice(0, shebangEnd + 1) + newLine + "\n" + content.slice(shebangEnd + 1),
|
|
147
|
+
isNew: true,
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Prepend the header line at the very top
|
|
152
|
+
return { content: newLine + "\n" + content, isNew: true };
|
|
153
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { type WorkspaceIndex } from "./workspace-index.js";
|
|
2
|
+
export declare class WorkspaceEscapeError extends Error {
|
|
3
|
+
}
|
|
4
|
+
export interface ReadFileResult {
|
|
5
|
+
ok: true;
|
|
6
|
+
content: string;
|
|
7
|
+
relPath: string;
|
|
8
|
+
totalLines?: number;
|
|
9
|
+
/**
|
|
10
|
+
* Full-file sha1 (only present on range reads). Stale-enforced write
|
|
11
|
+
* tools (line_patch) require this — hash the file right before writing.
|
|
12
|
+
*/
|
|
13
|
+
readSha1?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface ReadFileError {
|
|
16
|
+
ok: false;
|
|
17
|
+
error: string;
|
|
18
|
+
code: "file_not_found" | "workspace_escape" | "read_error";
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* The workspace index is the source of truth for "what files exist". If a
|
|
22
|
+
* requested path isn't in the index, this triggers exactly one automatic
|
|
23
|
+
* reindex and retries — covering the common case (a file appeared via some
|
|
24
|
+
* process outside Xcoder SDLC, or the index is simply stale). If it's still not
|
|
25
|
+
* found after that, the caller gets a typed `file_not_found`, not a crash.
|
|
26
|
+
*
|
|
27
|
+
* `startLine`/`endLine` (1-indexed, inclusive) let a large file be read in
|
|
28
|
+
* slices instead of pulling the whole thing into context at once — pair
|
|
29
|
+
* this with `edit_file` for surgical changes to files too big to safely
|
|
30
|
+
* rewrite in full via `write_file`.
|
|
31
|
+
*/
|
|
32
|
+
export declare function readFileTool(root: string, relPath: string, range?: {
|
|
33
|
+
startLine?: number;
|
|
34
|
+
endLine?: number;
|
|
35
|
+
}): Promise<ReadFileResult | ReadFileError>;
|
|
36
|
+
export interface WriteFileResult {
|
|
37
|
+
ok: true;
|
|
38
|
+
relPath: string;
|
|
39
|
+
bytesWritten: number;
|
|
40
|
+
}
|
|
41
|
+
export interface WriteFileError {
|
|
42
|
+
ok: false;
|
|
43
|
+
error: string;
|
|
44
|
+
code: "workspace_escape" | "write_error" | "file_too_large";
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* write_file gate (AC-15): refuse whole-file rewrites above this many lines
|
|
48
|
+
* unless the caller explicitly opts out with `force: true`. The refusal is
|
|
49
|
+
* a steering signal — point the model at the surgical alternatives rather
|
|
50
|
+
* than silently accepting a 900-line reproduce-the-whole-file risk.
|
|
51
|
+
*/
|
|
52
|
+
export declare const WRITE_FILE_LINE_CEILING = 200;
|
|
53
|
+
export declare function writeFileTool(root: string, relPath: string, content: string, ctx?: {
|
|
54
|
+
taskId: string;
|
|
55
|
+
subtaskId: string;
|
|
56
|
+
}, opts?: {
|
|
57
|
+
force?: boolean;
|
|
58
|
+
}): Promise<WriteFileResult | WriteFileError>;
|
|
59
|
+
export interface ListDirResult {
|
|
60
|
+
ok: true;
|
|
61
|
+
entries: string[];
|
|
62
|
+
}
|
|
63
|
+
export declare function listDirTool(index: WorkspaceIndex, relDir: string): Promise<ListDirResult>;
|
|
64
|
+
/**
|
|
65
|
+
* Maximum characters for a single observation returned to the LLM. Every
|
|
66
|
+
* growable new-tool output routes through `truncateOutput` — with an
|
|
67
|
+
* actionable note naming that tool's narrowing parameter, never a silent
|
|
68
|
+
* cutoff (AC-18).
|
|
69
|
+
*/
|
|
70
|
+
export declare const MAX_OBSERVATION_CHARS = 4000;
|
|
71
|
+
export declare function truncateOutput(text: string, maxChars?: number): {
|
|
72
|
+
text: string;
|
|
73
|
+
truncated: boolean;
|
|
74
|
+
note: string;
|
|
75
|
+
};
|
|
76
|
+
export type ReadOutlineResult = {
|
|
77
|
+
ok: true;
|
|
78
|
+
relPath: string;
|
|
79
|
+
language: "typescript" | "javascript" | "text";
|
|
80
|
+
lineCount: number;
|
|
81
|
+
imports: string[];
|
|
82
|
+
symbols: Array<{
|
|
83
|
+
kind: "function" | "class" | "interface" | "type" | "const";
|
|
84
|
+
name: string;
|
|
85
|
+
startLine: number;
|
|
86
|
+
endLine: number;
|
|
87
|
+
signature: string;
|
|
88
|
+
doc?: string;
|
|
89
|
+
}>;
|
|
90
|
+
body: string;
|
|
91
|
+
} | {
|
|
92
|
+
ok: false;
|
|
93
|
+
error: string;
|
|
94
|
+
code: "file_not_found" | "workspace_escape" | "unsupported_language" | "read_error";
|
|
95
|
+
};
|
|
96
|
+
/**
|
|
97
|
+
* Structural skeleton read: imports, symbol signatures, docstrings, bodies
|
|
98
|
+
* collapsed. The default first read for files over ~150 lines — far cheaper
|
|
99
|
+
* than pulling the whole file into context. TS/JS uses the scanner in
|
|
100
|
+
* ast-tools; other text files get a structural fallback (markdown headings,
|
|
101
|
+
* `---` separators, first non-empty lines). Binary → unsupported_language.
|
|
102
|
+
*/
|
|
103
|
+
export declare function readOutlineTool(root: string, relPath: string): Promise<ReadOutlineResult>;
|
|
104
|
+
export interface ReadMultipleFilesInput {
|
|
105
|
+
files: Array<{
|
|
106
|
+
path: string;
|
|
107
|
+
startLine?: number;
|
|
108
|
+
endLine?: number;
|
|
109
|
+
}>;
|
|
110
|
+
maxTotalChars?: number;
|
|
111
|
+
}
|
|
112
|
+
export type ReadMultipleFilesResult = {
|
|
113
|
+
ok: true;
|
|
114
|
+
sections: Array<{
|
|
115
|
+
label: string;
|
|
116
|
+
content: string;
|
|
117
|
+
totalLines: number;
|
|
118
|
+
sliced: boolean;
|
|
119
|
+
}>;
|
|
120
|
+
totalChars: number;
|
|
121
|
+
dropped: number;
|
|
122
|
+
note?: string;
|
|
123
|
+
} | {
|
|
124
|
+
ok: false;
|
|
125
|
+
error: string;
|
|
126
|
+
code: "workspace_escape" | "invalid_files";
|
|
127
|
+
sections?: never;
|
|
128
|
+
};
|
|
129
|
+
/**
|
|
130
|
+
* Batched, labeled section reads in ONE call — the replacement for N
|
|
131
|
+
* separate read_file calls when building cross-file understanding. Parallel
|
|
132
|
+
* (bounded to 8), each section labeled `--- path (lines a-b of N) ---`.
|
|
133
|
+
* Over maxTotalChars (default 12000) tail sections are dropped with an
|
|
134
|
+
* actionable note; never silently swallowed.
|
|
135
|
+
*/
|
|
136
|
+
export declare function readMultipleFilesTool(root: string, input: ReadMultipleFilesInput): Promise<ReadMultipleFilesResult>;
|
|
137
|
+
export type ReadFullFileResult = {
|
|
138
|
+
ok: true;
|
|
139
|
+
relPath: string;
|
|
140
|
+
lineCount: number;
|
|
141
|
+
content: string;
|
|
142
|
+
} | {
|
|
143
|
+
ok: false;
|
|
144
|
+
error: string;
|
|
145
|
+
code: "file_not_found" | "workspace_escape" | "read_error" | "file_too_large";
|
|
146
|
+
};
|
|
147
|
+
/** read_full_file refuses whole-file reads above this line count unless allowLarge is set. */
|
|
148
|
+
export declare const FULL_FILE_LINE_CEILING = 500;
|
|
149
|
+
/**
|
|
150
|
+
* Gated whole-file read: refused above 500 lines unless `allowLarge: true`
|
|
151
|
+
* explicitly opts in. The refusal names the cheaper alternative
|
|
152
|
+
* (read_outline / read_file with a range) instead of silently auto-chunking.
|
|
153
|
+
*/
|
|
154
|
+
export declare function readFullFileTool(root: string, relPath: string, allowLarge?: boolean): Promise<ReadFullFileResult>;
|