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,348 @@
|
|
|
1
|
+
// xcoder:version 9 | xcoder:task task-e6370a | xcoder:updated 2026-08-12T16:55:07.172Z | xcoder:subtask code-st-cd4444
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { promises as fs } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { resolveInWorkspace } from "./patch-tools.js";
|
|
6
|
+
import { upsertEntry } from "./workspace-index.js";
|
|
7
|
+
import { truncateOutput } from "./fs-tools.js";
|
|
8
|
+
import { getCommentSyntax, injectVersionHeader, } from "./file-header-manager.js";
|
|
9
|
+
import { incrementFileVersion } from "../store/file-version-store.js";
|
|
10
|
+
function run(cmd, args, cwd) {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
execFile(cmd, args, { cwd, maxBuffer: 10 * 1024 * 1024, timeout: 60_000 }, (error, stdout, stderr) => {
|
|
13
|
+
resolve({ ok: !error, stdout, stderr });
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Walks up from `startDir` looking for a `.git` directory. Returns the repo
|
|
19
|
+
* root or null. Mirrors the findUp pattern used for repo pinning elsewhere.
|
|
20
|
+
*/
|
|
21
|
+
async function findGitRoot(startDir) {
|
|
22
|
+
let dir = startDir;
|
|
23
|
+
for (let i = 0; i < 20; i++) {
|
|
24
|
+
try {
|
|
25
|
+
await fs.access(path.join(dir, ".git"));
|
|
26
|
+
return dir;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
const parent = path.dirname(dir);
|
|
30
|
+
if (parent === dir)
|
|
31
|
+
return null;
|
|
32
|
+
dir = parent;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const STAT_LINE_RE = /^\s*(\S+)\s+\|\s+(\d+)\s+([+\-*]+)?\s*$/;
|
|
38
|
+
/**
|
|
39
|
+
* Low-token diff summary by default (`git diff --stat`). Pass `rawDiff: true`
|
|
40
|
+
* to get the full unified diff (still capped by truncation). Summary-first is
|
|
41
|
+
* the cheapest-first default (T5).
|
|
42
|
+
*/
|
|
43
|
+
export async function gitDiffTool(root, input = {}) {
|
|
44
|
+
const cwd = path.resolve(root);
|
|
45
|
+
const repoRoot = await findGitRoot(cwd);
|
|
46
|
+
if (!repoRoot) {
|
|
47
|
+
return {
|
|
48
|
+
ok: false,
|
|
49
|
+
code: "not_a_git_repo",
|
|
50
|
+
error: `no git repository found from "${root}" upward — git_diff works inside a git repo. Use git_log/read_file/read_outline on the workspace files instead.`,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
const args = [];
|
|
54
|
+
if (input.rawDiff) {
|
|
55
|
+
args.push("diff");
|
|
56
|
+
if (input.ref && input.ref.trim() !== "")
|
|
57
|
+
args.push(input.ref);
|
|
58
|
+
if (input.path && input.path.trim() !== "") {
|
|
59
|
+
args.push("--", input.path.replace(/\\/g, "/"));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
args.push("diff", "--stat");
|
|
64
|
+
if (input.ref && input.ref.trim() !== "")
|
|
65
|
+
args.push(input.ref);
|
|
66
|
+
if (input.path && input.path.trim() !== "") {
|
|
67
|
+
args.push("--", input.path.replace(/\\/g, "/"));
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const res = await run("git", args, repoRoot);
|
|
71
|
+
if (!res.ok) {
|
|
72
|
+
return { ok: false, code: "git_error", error: `git ${args[0]} failed: ${res.stderr.trim() || "unknown git error"}`, stderr: res.stderr };
|
|
73
|
+
}
|
|
74
|
+
const raw = res.stdout;
|
|
75
|
+
if (input.rawDiff) {
|
|
76
|
+
const capped = truncateOutput(raw, 4000);
|
|
77
|
+
return {
|
|
78
|
+
ok: true,
|
|
79
|
+
summary: capped.text.includes("\n") ? capped.text.split("\n")[0] : capped.text,
|
|
80
|
+
entries: [],
|
|
81
|
+
hasDiff: raw.trim() !== "",
|
|
82
|
+
statsOnly: false,
|
|
83
|
+
rawDiff: capped.text,
|
|
84
|
+
note: capped.truncated ? capped.note : undefined,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Parse the --stat output into entries.
|
|
88
|
+
const lines = raw.split("\n");
|
|
89
|
+
const entries = [];
|
|
90
|
+
const dirs = new Set();
|
|
91
|
+
for (const line of lines) {
|
|
92
|
+
if (line.trim() === "" || line.includes("files changed") || line.includes("file changed"))
|
|
93
|
+
continue;
|
|
94
|
+
const m = STAT_LINE_RE.exec(line);
|
|
95
|
+
if (m) {
|
|
96
|
+
const file = m[1].replace(/\|.*$/, "").trim();
|
|
97
|
+
const added = (m[3]?.match(/\+/g) ?? []).length;
|
|
98
|
+
const removed = (m[3]?.match(/-/g) ?? []).length;
|
|
99
|
+
const status = line.includes("|") ? "modified" : "modified";
|
|
100
|
+
entries.push({ file, added, removed, status });
|
|
101
|
+
}
|
|
102
|
+
else if (/^\s*\S+\s*\|/.test(line)) {
|
|
103
|
+
// A path with a rename `->` or a mode-change line — best-effort capture.
|
|
104
|
+
const file = line.split("|")[0].trim();
|
|
105
|
+
if (file && !file.startsWith(" "))
|
|
106
|
+
entries.push({ file, added: 0, removed: 0, status: "renamed" });
|
|
107
|
+
}
|
|
108
|
+
else if (/^\s*[A-Za-z]:\\.*/.test(line) || line.trim().startsWith("dev/")) {
|
|
109
|
+
dirs.add(line.trim());
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const capped = truncateOutput(raw, 4000);
|
|
113
|
+
return {
|
|
114
|
+
ok: true,
|
|
115
|
+
summary: capped.text,
|
|
116
|
+
entries,
|
|
117
|
+
hasDiff: raw.trim() !== "",
|
|
118
|
+
statsOnly: true,
|
|
119
|
+
note: capped.truncated ? capped.note : undefined,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
export async function gitLogTool(root, input) {
|
|
123
|
+
const cwd = path.resolve(root);
|
|
124
|
+
const repoRoot = await findGitRoot(cwd);
|
|
125
|
+
if (!repoRoot) {
|
|
126
|
+
return {
|
|
127
|
+
ok: false,
|
|
128
|
+
code: "not_a_git_repo",
|
|
129
|
+
error: `no git repository found from "${root}" upward — git_log works inside a git repo. Use read_file/read_outline on the workspace files instead.`,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
const maxCount = Math.min(Math.max(1, input.maxCount ?? 10), 100);
|
|
133
|
+
const args = [
|
|
134
|
+
"log",
|
|
135
|
+
`--max-count=${maxCount}`,
|
|
136
|
+
"--format=%H|%h|%an|%ad|%s",
|
|
137
|
+
"--date=short",
|
|
138
|
+
];
|
|
139
|
+
if (input.path && input.path.trim() !== "") {
|
|
140
|
+
args.push("--", input.path.replace(/\\/g, "/"));
|
|
141
|
+
}
|
|
142
|
+
const res = await run("git", args, repoRoot);
|
|
143
|
+
if (!res.ok) {
|
|
144
|
+
return { ok: false, code: "git_error", error: `git log failed: ${res.stderr.trim() || "unknown git error"}`, stderr: res.stderr };
|
|
145
|
+
}
|
|
146
|
+
const commits = res.stdout
|
|
147
|
+
.split("\n")
|
|
148
|
+
.filter((l) => l.trim() !== "")
|
|
149
|
+
.map((l) => {
|
|
150
|
+
const [hash, shortHash, author, date, ...subjectParts] = l.split("|");
|
|
151
|
+
return { hash, shortHash, author, date, subject: subjectParts.join("|") };
|
|
152
|
+
});
|
|
153
|
+
const result = {
|
|
154
|
+
ok: true,
|
|
155
|
+
commits: commits.slice(0, maxCount),
|
|
156
|
+
truncated: commits.length > maxCount || commits.length === 0 ? false : commits.length === maxCount,
|
|
157
|
+
};
|
|
158
|
+
if (result.truncated && commits.length > maxCount) {
|
|
159
|
+
result.truncationNote = `showing the most recent ${maxCount} commits — pass a larger "maxCount" (up to 100) for more.`;
|
|
160
|
+
}
|
|
161
|
+
return result;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Applies a unified diff to a single file. Inside a git repo this uses
|
|
165
|
+
* `git apply --check` (zero-writes on failure, full check output returned)
|
|
166
|
+
* then `git apply`. Outside a repo it falls back to a small JS hunk parser
|
|
167
|
+
* that splices the same way `line_patch` does (cross-platform — no `patch`
|
|
168
|
+
* binary dependency).
|
|
169
|
+
*/
|
|
170
|
+
export async function applyUnifiedDiffTool(root, input, ctx) {
|
|
171
|
+
let full;
|
|
172
|
+
try {
|
|
173
|
+
full = resolveInWorkspace(root, input.path);
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
return { ok: false, error: e.message, code: "workspace_escape" };
|
|
177
|
+
}
|
|
178
|
+
let content;
|
|
179
|
+
try {
|
|
180
|
+
content = await fs.readFile(full, "utf8");
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return {
|
|
184
|
+
ok: false,
|
|
185
|
+
code: "file_not_found",
|
|
186
|
+
error: `"${input.path}" doesn't exist or can't be read. Use write_file to create it first.`,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
const repoRoot = await findGitRoot(path.dirname(full));
|
|
190
|
+
if (repoRoot) {
|
|
191
|
+
// Work via a temp file so `git apply --check` (zero writes on failure) and
|
|
192
|
+
// `git apply` both read the exact same bytes. Do NOT use `-` as the patch
|
|
193
|
+
// path without piping stdin — execFile would leave git waiting on the pipe.
|
|
194
|
+
const tmp = path.join(repoRoot, ".xcoder", `tmp-apply-${Date.now()}.diff`);
|
|
195
|
+
await fs.mkdir(path.dirname(tmp), { recursive: true });
|
|
196
|
+
await fs.writeFile(tmp, input.diff, "utf8");
|
|
197
|
+
try {
|
|
198
|
+
const checkRes = await run("git", ["apply", "--check", path.relative(repoRoot, tmp)], repoRoot);
|
|
199
|
+
if (!checkRes.ok) {
|
|
200
|
+
return {
|
|
201
|
+
ok: false,
|
|
202
|
+
code: "diff_check_failed",
|
|
203
|
+
error: `git apply --check rejected the diff — no changes were written. Fix the diff or re-read the file and regenerate it.`,
|
|
204
|
+
checkOutput: checkRes.stderr.trim() || checkRes.stdout.trim(),
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
const applyRes = await run("git", ["apply", path.relative(repoRoot, tmp)], repoRoot);
|
|
208
|
+
if (!applyRes.ok) {
|
|
209
|
+
return { ok: false, code: "diff_apply_failed", error: `git apply failed: ${applyRes.stderr.trim() || "unknown error"}` };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
finally {
|
|
213
|
+
await fs.rm(tmp, { force: true }).catch(() => undefined);
|
|
214
|
+
}
|
|
215
|
+
// Header injection + index refresh after git apply (ctx-aware).
|
|
216
|
+
let finalContent;
|
|
217
|
+
try {
|
|
218
|
+
finalContent = await fs.readFile(full, "utf8");
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return { ok: false, code: "diff_apply_failed", error: "file unreadable after apply" };
|
|
222
|
+
}
|
|
223
|
+
let next = finalContent;
|
|
224
|
+
if (ctx) {
|
|
225
|
+
const syntax = getCommentSyntax(input.path);
|
|
226
|
+
if (syntax) {
|
|
227
|
+
const meta = await incrementFileVersion(root, input.path, ctx.taskId, ctx.subtaskId);
|
|
228
|
+
const injected = injectVersionHeader(next, syntax, meta);
|
|
229
|
+
next = injected.content;
|
|
230
|
+
await fs.writeFile(full, next, "utf8");
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
await upsertEntry(root, path.relative(root, full));
|
|
234
|
+
const sha1After = await (await import("./patch-tools.js")).hashFile(full);
|
|
235
|
+
const hunks = (input.diff.match(/^@@/gm) ?? []).length;
|
|
236
|
+
const changes = (input.diff.match(/^[+-](?![+-])/gm) ?? []).length;
|
|
237
|
+
return {
|
|
238
|
+
ok: true,
|
|
239
|
+
relPath: path.relative(root, full).split(path.sep).join("/"),
|
|
240
|
+
hunksApplied: hunks,
|
|
241
|
+
linesChanged: changes,
|
|
242
|
+
sha1After,
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
// ── Non-repo fallback: JS hunk parser + line splice ──
|
|
246
|
+
const parsed = parseUnifiedDiff(input.diff);
|
|
247
|
+
if (!parsed.ok)
|
|
248
|
+
return { ok: false, code: "no_diff_hunks", error: parsed.error, checkOutput: undefined };
|
|
249
|
+
const lines = content.split("\n");
|
|
250
|
+
let changed = 0;
|
|
251
|
+
// Apply hunks bottom-up against ORIGINAL line numbers so earlier hunks
|
|
252
|
+
// don't shift the coordinates of later ones.
|
|
253
|
+
const sortedHunks = [...parsed.hunks].sort((a, b) => b.oldStart - a.oldStart);
|
|
254
|
+
for (const hunk of sortedHunks) {
|
|
255
|
+
if (hunk.oldCount > 0) {
|
|
256
|
+
const end = hunk.oldStart + hunk.oldCount - 1;
|
|
257
|
+
if (hunk.oldStart < 1 || end > lines.length) {
|
|
258
|
+
return {
|
|
259
|
+
ok: false,
|
|
260
|
+
code: "no_diff_hunks",
|
|
261
|
+
error: `hunk covers old lines ${hunk.oldStart}..${end}, but the file only has ${lines.length} lines — the diff is stale.`,
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else if (hunk.oldStart !== 0 && (hunk.oldStart < 1 || hunk.oldStart > lines.length + 1)) {
|
|
266
|
+
return {
|
|
267
|
+
ok: false,
|
|
268
|
+
code: "no_diff_hunks",
|
|
269
|
+
error: `insert hunk at line ${hunk.oldStart} is outside the file's ${lines.length} lines — the diff is stale.`,
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
const before = lines.slice(0, hunk.oldStart === 0 ? 0 : hunk.oldStart - 1);
|
|
273
|
+
const after = lines.slice(hunk.oldStart === 0 ? 0 : hunk.oldStart - 1 + hunk.oldCount);
|
|
274
|
+
lines.length = 0;
|
|
275
|
+
lines.push(...before, ...hunk.lines, ...after);
|
|
276
|
+
changed += hunk.addedCount + hunk.removedCount;
|
|
277
|
+
}
|
|
278
|
+
let next = lines.join("\n");
|
|
279
|
+
if (ctx) {
|
|
280
|
+
const syntax = getCommentSyntax(input.path);
|
|
281
|
+
if (syntax) {
|
|
282
|
+
const meta = await incrementFileVersion(root, input.path, ctx.taskId, ctx.subtaskId);
|
|
283
|
+
const injected = injectVersionHeader(next, syntax, meta);
|
|
284
|
+
next = injected.content;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
try {
|
|
288
|
+
await fs.writeFile(full, next, "utf8");
|
|
289
|
+
await upsertEntry(root, path.relative(root, full));
|
|
290
|
+
const sha1After = await (await import("./patch-tools.js")).hashFile(full);
|
|
291
|
+
return {
|
|
292
|
+
ok: true,
|
|
293
|
+
relPath: path.relative(root, full).split(path.sep).join("/"),
|
|
294
|
+
hunksApplied: parsed.hunks.length,
|
|
295
|
+
linesChanged: changed,
|
|
296
|
+
sha1After,
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
catch (e) {
|
|
300
|
+
return { ok: false, code: "diff_apply_failed", error: e.message };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/** Minimal unified-diff parser for a single file: `@@ -a,b +c,d @@` + ` `, `+`, `-` lines. */
|
|
304
|
+
function parseUnifiedDiff(diff) {
|
|
305
|
+
const hunks = [];
|
|
306
|
+
let current = null;
|
|
307
|
+
for (const rawLine of diff.split("\n")) {
|
|
308
|
+
const line = rawLine.replace(/\r$/, "");
|
|
309
|
+
const hunkHeader = /^@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@/.exec(line);
|
|
310
|
+
if (hunkHeader) {
|
|
311
|
+
if (current)
|
|
312
|
+
hunks.push(current);
|
|
313
|
+
const oldStart = Number(hunkHeader[1]);
|
|
314
|
+
const oldCount = hunkHeader[2] === undefined ? 1 : Number(hunkHeader[2]);
|
|
315
|
+
const newStart = Number(hunkHeader[3]);
|
|
316
|
+
current = {
|
|
317
|
+
oldStart,
|
|
318
|
+
oldCount,
|
|
319
|
+
newStart,
|
|
320
|
+
lines: [],
|
|
321
|
+
addedCount: 0,
|
|
322
|
+
removedCount: 0,
|
|
323
|
+
};
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (!current)
|
|
327
|
+
continue;
|
|
328
|
+
if (line.startsWith("+")) {
|
|
329
|
+
current.lines.push(line.slice(1));
|
|
330
|
+
current.addedCount++;
|
|
331
|
+
}
|
|
332
|
+
else if (line.startsWith("-")) {
|
|
333
|
+
// Removed lines are NOT copied into the replacement; they are covered by
|
|
334
|
+
// the old-side splice extent (oldStart + oldCount).
|
|
335
|
+
current.removedCount++;
|
|
336
|
+
}
|
|
337
|
+
else if (line.startsWith(" ")) {
|
|
338
|
+
current.lines.push(line.slice(1));
|
|
339
|
+
}
|
|
340
|
+
// '\' and 'diff --git' header lines are ignored.
|
|
341
|
+
}
|
|
342
|
+
if (current)
|
|
343
|
+
hunks.push(current);
|
|
344
|
+
if (hunks.length === 0) {
|
|
345
|
+
return { ok: false, error: "no hunks found — the diff must contain @@ hunk headers" };
|
|
346
|
+
}
|
|
347
|
+
return { ok: true, hunks };
|
|
348
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface GithubCreateRepoInput {
|
|
2
|
+
name: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
private?: boolean;
|
|
5
|
+
org?: string;
|
|
6
|
+
credentialRef?: string;
|
|
7
|
+
}
|
|
8
|
+
export interface GithubCreateRepoResult {
|
|
9
|
+
ok: boolean;
|
|
10
|
+
detail: string;
|
|
11
|
+
cloneUrl?: string;
|
|
12
|
+
htmlUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function githubCreateRepo(input: GithubCreateRepoInput): Promise<GithubCreateRepoResult>;
|
|
15
|
+
export interface GithubCloneInput {
|
|
16
|
+
url: string;
|
|
17
|
+
dir: string;
|
|
18
|
+
credentialRef?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function githubClone(root: string, input: GithubCloneInput): Promise<{
|
|
21
|
+
ok: boolean;
|
|
22
|
+
detail: string;
|
|
23
|
+
}>;
|
|
24
|
+
export interface GithubCommitInput {
|
|
25
|
+
cwd?: string;
|
|
26
|
+
message: string;
|
|
27
|
+
addAll?: boolean;
|
|
28
|
+
}
|
|
29
|
+
export declare function githubCommit(root: string, input: GithubCommitInput): Promise<{
|
|
30
|
+
ok: boolean;
|
|
31
|
+
detail: string;
|
|
32
|
+
}>;
|
|
33
|
+
export interface GithubPushInput {
|
|
34
|
+
cwd?: string;
|
|
35
|
+
remote?: string;
|
|
36
|
+
branch?: string;
|
|
37
|
+
credentialRef?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The token is embedded into the remote URL only for the duration of this
|
|
41
|
+
* one push, then the original URL is restored — it is never left sitting
|
|
42
|
+
* in `.git/config`, and it's redacted out of any error text before this
|
|
43
|
+
* function returns.
|
|
44
|
+
*/
|
|
45
|
+
export declare function githubPush(root: string, input: GithubPushInput): Promise<{
|
|
46
|
+
ok: boolean;
|
|
47
|
+
detail: string;
|
|
48
|
+
}>;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { execFile } from "node:child_process";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { resolveCredential, redact } from "./credential.js";
|
|
4
|
+
function run(cmd, args, cwd) {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
execFile(cmd, args, { cwd, maxBuffer: 10 * 1024 * 1024 }, (error, stdout, stderr) => {
|
|
7
|
+
resolve({ ok: !error, stdout, stderr });
|
|
8
|
+
});
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
function resolveInWorkspace(root, relDir) {
|
|
12
|
+
const full = path.resolve(root, relDir ?? ".");
|
|
13
|
+
const normalizedRoot = path.resolve(root) + path.sep;
|
|
14
|
+
if (full + path.sep !== normalizedRoot && !full.startsWith(normalizedRoot)) {
|
|
15
|
+
throw new Error(`"${relDir}" resolves outside the workspace root — refusing.`);
|
|
16
|
+
}
|
|
17
|
+
return full;
|
|
18
|
+
}
|
|
19
|
+
/** Embeds a token into an https remote URL only for the duration of one git invocation. */
|
|
20
|
+
function withEmbeddedToken(url, token) {
|
|
21
|
+
if (!url.startsWith("https://"))
|
|
22
|
+
return url; // ssh remotes use the user's own key, nothing to embed
|
|
23
|
+
return url.replace("https://", `https://x-access-token:${token}@`);
|
|
24
|
+
}
|
|
25
|
+
export async function githubCreateRepo(input) {
|
|
26
|
+
const ref = input.credentialRef ?? "GITHUB_TOKEN";
|
|
27
|
+
let token;
|
|
28
|
+
try {
|
|
29
|
+
token = resolveCredential(ref);
|
|
30
|
+
}
|
|
31
|
+
catch (e) {
|
|
32
|
+
return { ok: false, detail: e.message };
|
|
33
|
+
}
|
|
34
|
+
const endpoint = input.org
|
|
35
|
+
? `https://api.github.com/orgs/${input.org}/repos`
|
|
36
|
+
: `https://api.github.com/user/repos`;
|
|
37
|
+
try {
|
|
38
|
+
const res = await fetch(endpoint, {
|
|
39
|
+
method: "POST",
|
|
40
|
+
headers: {
|
|
41
|
+
Authorization: `Bearer ${token}`,
|
|
42
|
+
Accept: "application/vnd.github+json",
|
|
43
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
44
|
+
"User-Agent": "xcoder-sdlc-cli",
|
|
45
|
+
"Content-Type": "application/json",
|
|
46
|
+
},
|
|
47
|
+
body: JSON.stringify({
|
|
48
|
+
name: input.name,
|
|
49
|
+
description: input.description,
|
|
50
|
+
private: input.private ?? true,
|
|
51
|
+
}),
|
|
52
|
+
});
|
|
53
|
+
const body = (await res.json());
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
return { ok: false, detail: redact(`GitHub API ${res.status}: ${body.message ?? "unknown error"}`, ref) };
|
|
56
|
+
}
|
|
57
|
+
return {
|
|
58
|
+
ok: true,
|
|
59
|
+
detail: `created ${body.html_url}`,
|
|
60
|
+
cloneUrl: body.clone_url,
|
|
61
|
+
htmlUrl: body.html_url,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
return { ok: false, detail: redact(`request failed: ${e.message}`, ref) };
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
export async function githubClone(root, input) {
|
|
69
|
+
let full;
|
|
70
|
+
try {
|
|
71
|
+
full = resolveInWorkspace(root, input.dir);
|
|
72
|
+
}
|
|
73
|
+
catch (e) {
|
|
74
|
+
return { ok: false, detail: e.message };
|
|
75
|
+
}
|
|
76
|
+
let cloneUrl = input.url;
|
|
77
|
+
const ref = input.credentialRef;
|
|
78
|
+
if (ref) {
|
|
79
|
+
try {
|
|
80
|
+
cloneUrl = withEmbeddedToken(input.url, resolveCredential(ref));
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
return { ok: false, detail: e.message };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const result = await run("git", ["clone", cloneUrl, full], path.dirname(full));
|
|
87
|
+
return {
|
|
88
|
+
ok: result.ok,
|
|
89
|
+
detail: redact(result.ok ? `cloned into ${input.dir}` : result.stderr.trim(), ref),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export async function githubCommit(root, input) {
|
|
93
|
+
let full;
|
|
94
|
+
try {
|
|
95
|
+
full = resolveInWorkspace(root, input.cwd);
|
|
96
|
+
}
|
|
97
|
+
catch (e) {
|
|
98
|
+
return { ok: false, detail: e.message };
|
|
99
|
+
}
|
|
100
|
+
if (input.addAll ?? true) {
|
|
101
|
+
const add = await run("git", ["add", "-A"], full);
|
|
102
|
+
if (!add.ok)
|
|
103
|
+
return { ok: false, detail: `git add failed: ${add.stderr.trim()}` };
|
|
104
|
+
}
|
|
105
|
+
const commit = await run("git", ["commit", "-m", input.message], full);
|
|
106
|
+
return { ok: commit.ok, detail: commit.ok ? commit.stdout.trim() : commit.stderr.trim() };
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* The token is embedded into the remote URL only for the duration of this
|
|
110
|
+
* one push, then the original URL is restored — it is never left sitting
|
|
111
|
+
* in `.git/config`, and it's redacted out of any error text before this
|
|
112
|
+
* function returns.
|
|
113
|
+
*/
|
|
114
|
+
export async function githubPush(root, input) {
|
|
115
|
+
let full;
|
|
116
|
+
try {
|
|
117
|
+
full = resolveInWorkspace(root, input.cwd);
|
|
118
|
+
}
|
|
119
|
+
catch (e) {
|
|
120
|
+
return { ok: false, detail: e.message };
|
|
121
|
+
}
|
|
122
|
+
const remote = input.remote ?? "origin";
|
|
123
|
+
const ref = input.credentialRef ?? "GITHUB_TOKEN";
|
|
124
|
+
const getUrl = await run("git", ["remote", "get-url", remote], full);
|
|
125
|
+
if (!getUrl.ok)
|
|
126
|
+
return { ok: false, detail: `could not resolve remote "${remote}": ${getUrl.stderr.trim()}` };
|
|
127
|
+
const originalUrl = getUrl.stdout.trim();
|
|
128
|
+
let token;
|
|
129
|
+
try {
|
|
130
|
+
token = resolveCredential(ref);
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
return { ok: false, detail: e.message };
|
|
134
|
+
}
|
|
135
|
+
const authedUrl = withEmbeddedToken(originalUrl, token);
|
|
136
|
+
try {
|
|
137
|
+
await run("git", ["remote", "set-url", remote, authedUrl], full);
|
|
138
|
+
const args = ["push", remote];
|
|
139
|
+
if (input.branch)
|
|
140
|
+
args.push(input.branch);
|
|
141
|
+
const push = await run("git", args, full);
|
|
142
|
+
return { ok: push.ok, detail: redact(push.ok ? push.stdout.trim() || "pushed" : push.stderr.trim(), ref) };
|
|
143
|
+
}
|
|
144
|
+
finally {
|
|
145
|
+
await run("git", ["remote", "set-url", remote, originalUrl], full); // always restore, even on failure
|
|
146
|
+
}
|
|
147
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { WorkspaceIndex } from "./workspace-index.js";
|
|
2
|
+
export interface GlobToolOptions {
|
|
3
|
+
/**
|
|
4
|
+
* A pre-gated index to answer from. Long-running callers (the orchestrator,
|
|
5
|
+
* grep) pass this so one freshness probe serves many lookups.
|
|
6
|
+
*/
|
|
7
|
+
index?: WorkspaceIndex;
|
|
8
|
+
/** Workspace root the index describes. Defaults to `cwd`. */
|
|
9
|
+
root?: string;
|
|
10
|
+
/** Maximum number of results. */
|
|
11
|
+
limit?: number;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Match `pattern` against the workspace index.
|
|
15
|
+
*
|
|
16
|
+
* The first two parameters are unchanged from the previous real-FS
|
|
17
|
+
* implementation, so existing call sites keep working — they simply get
|
|
18
|
+
* index-backed answers instead of a directory walk.
|
|
19
|
+
*/
|
|
20
|
+
export declare function globTool(pattern: string, cwd?: string, opts?: GlobToolOptions): Promise<string[]>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// xcoder:version 1 | xcoder:task task-e16f85 | xcoder:updated 2026-10-30T18:14:11.042Z | xcoder:subtask code-st-791be4
|
|
2
|
+
// Glob, answered by the workspace index and nothing else.
|
|
3
|
+
//
|
|
4
|
+
// This module used to call `tinyglobby` directly, which meant `glob` walked the
|
|
5
|
+
// real filesystem and could disagree with the index about which files exist —
|
|
6
|
+
// the two answers came from two different sources. Deleting that import is
|
|
7
|
+
// deliberate: there is now no code path in this module that can enumerate the
|
|
8
|
+
// filesystem, so "glob is index-backed" is enforced rather than promised.
|
|
9
|
+
//
|
|
10
|
+
// Consequences, stated plainly: files excluded from the index are excluded
|
|
11
|
+
// here too — files over `MAX_INDEXED_FILE_BYTES`, dotfiles/dotdirs, and
|
|
12
|
+
// anything under `IGNORE_DIRS` (node_modules, dist, .git, .xcoder, …). Results
|
|
13
|
+
// are therefore a strict subset of the old behaviour. That inconsistency was
|
|
14
|
+
// itself the bug: every other discovery tool already treated the index as the
|
|
15
|
+
// source of truth, so glob returning files the index denied merely moved the
|
|
16
|
+
// disagreement downstream.
|
|
17
|
+
import { loadIgnoreRules } from "../indexing/ignoreRules.js";
|
|
18
|
+
import { globIndex } from "./index-glob.js";
|
|
19
|
+
import { ensureFreshIndex } from "./index-freshness.js";
|
|
20
|
+
/**
|
|
21
|
+
* Match `pattern` against the workspace index.
|
|
22
|
+
*
|
|
23
|
+
* The first two parameters are unchanged from the previous real-FS
|
|
24
|
+
* implementation, so existing call sites keep working — they simply get
|
|
25
|
+
* index-backed answers instead of a directory walk.
|
|
26
|
+
*/
|
|
27
|
+
export async function globTool(pattern, cwd = process.cwd(), opts = {}) {
|
|
28
|
+
const root = opts.root ?? cwd;
|
|
29
|
+
const index = opts.index ?? (await ensureFreshIndex(root)).index;
|
|
30
|
+
return globIndex(index, pattern, {
|
|
31
|
+
ignore: loadIgnoreRules(root),
|
|
32
|
+
limit: opts.limit,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { WorkspaceIndex } from "./workspace-index.js";
|
|
2
|
+
export interface GrepMatch {
|
|
3
|
+
file: string;
|
|
4
|
+
line: number;
|
|
5
|
+
text: string;
|
|
6
|
+
}
|
|
7
|
+
export interface GrepOptions {
|
|
8
|
+
/** Number of surrounding context lines to include around each match (default 0). */
|
|
9
|
+
contextLines?: number;
|
|
10
|
+
/**
|
|
11
|
+
* Pre-gated index supplying the candidate file set. When omitted, `globTool`
|
|
12
|
+
* gates one for this call. Either way the *selection* of files is
|
|
13
|
+
* index-authoritative; only their bytes are read from disk.
|
|
14
|
+
*/
|
|
15
|
+
index?: WorkspaceIndex;
|
|
16
|
+
/** Workspace root the index describes. Defaults to `cwd`. */
|
|
17
|
+
root?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare function grepTool(regex: string, globPattern?: string, cwd?: string, opts?: GrepOptions): Promise<GrepMatch[]>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// xcoder:version 4 | xcoder:task task-e16f85 | xcoder:updated 2026-10-30T18:14:23.512Z | xcoder:subtask code-st-791be4
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { globTool } from "./globTool.js";
|
|
5
|
+
import { MAX_INDEXED_FILE_BYTES } from "./workspace-index.js";
|
|
6
|
+
export async function grepTool(regex, globPattern = "**/*", cwd = process.cwd(), opts) {
|
|
7
|
+
const root = opts?.root ?? cwd;
|
|
8
|
+
// Selection is index-authoritative; content still comes from disk.
|
|
9
|
+
const files = await globTool(globPattern, root, { index: opts?.index, root });
|
|
10
|
+
const re = new RegExp(regex);
|
|
11
|
+
const context = Math.max(0, opts?.contextLines ?? 0);
|
|
12
|
+
const matches = [];
|
|
13
|
+
for (const file of files) {
|
|
14
|
+
const full = path.join(root, file);
|
|
15
|
+
let content;
|
|
16
|
+
try {
|
|
17
|
+
// The index already excludes these, but a caller may hand us an index
|
|
18
|
+
// built elsewhere — never read an unbounded file into memory.
|
|
19
|
+
if (fs.statSync(full).size > MAX_INDEXED_FILE_BYTES)
|
|
20
|
+
continue;
|
|
21
|
+
content = fs.readFileSync(full, "utf-8");
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
continue; // binary or unreadable file — skip
|
|
25
|
+
}
|
|
26
|
+
const lines = content.split("\n");
|
|
27
|
+
lines.forEach((line, idx) => {
|
|
28
|
+
if (re.test(line)) {
|
|
29
|
+
matches.push({ file, line: idx + 1, text: line.trim() });
|
|
30
|
+
for (let c = 1; c <= context; c++) {
|
|
31
|
+
const before = lines[idx - c];
|
|
32
|
+
if (before !== undefined) {
|
|
33
|
+
matches.push({ file, line: idx - c + 1, text: before.trim() });
|
|
34
|
+
}
|
|
35
|
+
const after = lines[idx + c];
|
|
36
|
+
if (after !== undefined) {
|
|
37
|
+
matches.push({ file, line: idx + c + 1, text: after.trim() });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
return matches;
|
|
44
|
+
}
|