headlesscode 1.0.2

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.
Files changed (232) hide show
  1. package/ATTRIBUTION.md +53 -0
  2. package/CODE_OF_CONDUCT.md +130 -0
  3. package/CONTRIBUTING.md +107 -0
  4. package/LICENSE +202 -0
  5. package/README.md +486 -0
  6. package/SECURITY.md +211 -0
  7. package/bin/headlesscode.mjs +83 -0
  8. package/package.json +63 -0
  9. package/shared/prompts/review-mode-prompt-short.md +93 -0
  10. package/shared/prompts/review-mode-prompt.md +281 -0
  11. package/shared/rules-code/rules.md +22 -0
  12. package/shared/stacks/cpp/rules.md +30 -0
  13. package/shared/stacks/fastapi/rules.md +30 -0
  14. package/shared/stacks/javascript/rules.md +37 -0
  15. package/shared/stacks/postgresql/rules.md +31 -0
  16. package/shared/stacks/python/rules.md +35 -0
  17. package/shared/stacks/react/rules.md +11 -0
  18. package/shared/stacks/typescript/rules.md +10 -0
  19. package/src/budget/budget.ts +221 -0
  20. package/src/budget/concurrency.ts +126 -0
  21. package/src/budget/cost.ts +309 -0
  22. package/src/budget/index.ts +8 -0
  23. package/src/checkpoints/cli.ts +256 -0
  24. package/src/checkpoints/service.ts +227 -0
  25. package/src/cli.ts +1535 -0
  26. package/src/cloud/docker-provider.ts +334 -0
  27. package/src/cloud/provider.ts +300 -0
  28. package/src/codeintel/call-graph.ts +78 -0
  29. package/src/codeintel/find-references.ts +123 -0
  30. package/src/codeintel/go-to-definition.ts +193 -0
  31. package/src/codeintel/handlers.ts +190 -0
  32. package/src/codeintel/import-graph.ts +173 -0
  33. package/src/codeintel/outline.ts +180 -0
  34. package/src/codeintel/position.ts +77 -0
  35. package/src/codeintel/program.ts +350 -0
  36. package/src/codeintel/rename-symbol.ts +213 -0
  37. package/src/codeintel/tools.ts +280 -0
  38. package/src/codemap/build.ts +135 -0
  39. package/src/codemap/cli.ts +190 -0
  40. package/src/codemap/extract.ts +339 -0
  41. package/src/codemap/files.ts +236 -0
  42. package/src/codemap/fingerprint.ts +65 -0
  43. package/src/codemap/flows.ts +62 -0
  44. package/src/codemap/html.ts +451 -0
  45. package/src/codemap/lock.ts +80 -0
  46. package/src/codemap/types.ts +101 -0
  47. package/src/codesearch/airunner-embedder.ts +185 -0
  48. package/src/codesearch/chunk.ts +339 -0
  49. package/src/codesearch/cli.ts +223 -0
  50. package/src/codesearch/embedder.ts +332 -0
  51. package/src/codesearch/files.ts +280 -0
  52. package/src/codesearch/index.ts +469 -0
  53. package/src/codesearch/ollama-embedder.ts +205 -0
  54. package/src/codesearch/search.ts +141 -0
  55. package/src/codesearch/types.ts +100 -0
  56. package/src/config/mode-models.ts +218 -0
  57. package/src/dashboard/aggregate.ts +364 -0
  58. package/src/dashboard/chat-thread.ts +141 -0
  59. package/src/dashboard/checkpoints.ts +124 -0
  60. package/src/dashboard/cli.ts +193 -0
  61. package/src/dashboard/codemap.ts +44 -0
  62. package/src/dashboard/files.ts +121 -0
  63. package/src/dashboard/page.ts +2803 -0
  64. package/src/dashboard/self-improvement-metrics.ts +282 -0
  65. package/src/dashboard/server.ts +1103 -0
  66. package/src/dashboard/session-launch.ts +310 -0
  67. package/src/dashboard/timeline.ts +273 -0
  68. package/src/dashboard/tool-exec.ts +107 -0
  69. package/src/dashboard/trend-cli.ts +141 -0
  70. package/src/dashboard/trend.ts +413 -0
  71. package/src/decision-proxy/cli.ts +261 -0
  72. package/src/decision-proxy/proxy.ts +569 -0
  73. package/src/deploy/gate-cli.ts +147 -0
  74. package/src/deploy/gate.ts +254 -0
  75. package/src/engine/condense.ts +512 -0
  76. package/src/engine/events.ts +428 -0
  77. package/src/engine/handoff.ts +71 -0
  78. package/src/engine/lazy-tools.ts +160 -0
  79. package/src/engine/local-explore.ts +653 -0
  80. package/src/engine/logger.ts +96 -0
  81. package/src/engine/loop.ts +5517 -0
  82. package/src/engine/parser.ts +347 -0
  83. package/src/engine/prompt.ts +860 -0
  84. package/src/engine/reports.ts +47 -0
  85. package/src/engine/stacks.ts +448 -0
  86. package/src/engine/types.ts +291 -0
  87. package/src/engine/usage.ts +186 -0
  88. package/src/github/app-auth.ts +161 -0
  89. package/src/github/cli.ts +448 -0
  90. package/src/github/installations.ts +133 -0
  91. package/src/github/pr.ts +321 -0
  92. package/src/github/provision.ts +118 -0
  93. package/src/github/push.ts +122 -0
  94. package/src/index-util.ts +50 -0
  95. package/src/index.ts +81 -0
  96. package/src/init/cli.ts +248 -0
  97. package/src/init/gitignore.ts +74 -0
  98. package/src/llm/ollama.ts +308 -0
  99. package/src/llm/openrouter.ts +868 -0
  100. package/src/llm/preflight.ts +367 -0
  101. package/src/llm/transcript-capture.ts +84 -0
  102. package/src/memory/embed.ts +110 -0
  103. package/src/memory/index.ts +22 -0
  104. package/src/memory/local.ts +259 -0
  105. package/src/memory/summarizer.ts +283 -0
  106. package/src/memory/types.ts +153 -0
  107. package/src/memory/uwuchat.ts +157 -0
  108. package/src/migrate/cli.ts +115 -0
  109. package/src/orchestrator/analyze-cli.ts +104 -0
  110. package/src/orchestrator/auto-split.ts +206 -0
  111. package/src/orchestrator/cleanup.ts +1003 -0
  112. package/src/orchestrator/cli.ts +3571 -0
  113. package/src/orchestrator/cost-estimate.ts +564 -0
  114. package/src/orchestrator/cost-history-cli.ts +242 -0
  115. package/src/orchestrator/cost-history.ts +397 -0
  116. package/src/orchestrator/git-sync.ts +250 -0
  117. package/src/orchestrator/index.ts +153 -0
  118. package/src/orchestrator/log-analysis.ts +0 -0
  119. package/src/orchestrator/merge-check.ts +108 -0
  120. package/src/orchestrator/pipeline.ts +411 -0
  121. package/src/orchestrator/resume.ts +1940 -0
  122. package/src/orchestrator/reviewer.ts +503 -0
  123. package/src/orchestrator/split.ts +296 -0
  124. package/src/orchestrator/state.ts +542 -0
  125. package/src/orchestrator/status.ts +697 -0
  126. package/src/orchestrator/verification-gate.ts +134 -0
  127. package/src/orchestrator/watch.ts +898 -0
  128. package/src/permissions/commands.ts +1083 -0
  129. package/src/permissions/config.ts +241 -0
  130. package/src/permissions/index.ts +12 -0
  131. package/src/permissions/protected-files.ts +96 -0
  132. package/src/permissions/store-protection.ts +272 -0
  133. package/src/project-store.ts +648 -0
  134. package/src/projects/cli.ts +382 -0
  135. package/src/qa/qa.ts +487 -0
  136. package/src/tools/browser/handler.ts +346 -0
  137. package/src/tools/browser/service.ts +406 -0
  138. package/src/tools/browser/smoke.ts +78 -0
  139. package/src/tools/browser/tool.ts +99 -0
  140. package/src/tools/executor.ts +2575 -0
  141. package/src/tools/language-detect.ts +183 -0
  142. package/src/tools/output-summarizer.ts +369 -0
  143. package/src/tools/run-tests.ts +302 -0
  144. package/src/tools/set-indentation-tool.ts +49 -0
  145. package/src/tools/test-selection.ts +160 -0
  146. package/src/vendor/tests/smoke.ts +103 -0
  147. package/src/vendor/zoo-code/VENDOR-NOTES.md +213 -0
  148. package/src/vendor/zoo-code/shim/anthropic.ts +71 -0
  149. package/src/vendor/zoo-code/shim/openai.d.ts +60 -0
  150. package/src/vendor/zoo-code/shim/os-name.ts +18 -0
  151. package/src/vendor/zoo-code/shim/strip-bom.ts +14 -0
  152. package/src/vendor/zoo-code/shim/vscode.ts +76 -0
  153. package/src/vendor/zoo-code/src/core/config/CustomModesManager.ts +1015 -0
  154. package/src/vendor/zoo-code/src/core/diff/strategies/multi-search-replace.ts +670 -0
  155. package/src/vendor/zoo-code/src/core/prompts/sections/capabilities.ts +46 -0
  156. package/src/vendor/zoo-code/src/core/prompts/sections/custom-instructions.ts +559 -0
  157. package/src/vendor/zoo-code/src/core/prompts/sections/index.ts +10 -0
  158. package/src/vendor/zoo-code/src/core/prompts/sections/markdown-formatting.ts +7 -0
  159. package/src/vendor/zoo-code/src/core/prompts/sections/modes.ts +35 -0
  160. package/src/vendor/zoo-code/src/core/prompts/sections/objective.ts +13 -0
  161. package/src/vendor/zoo-code/src/core/prompts/sections/rules.ts +95 -0
  162. package/src/vendor/zoo-code/src/core/prompts/sections/skills.ts +105 -0
  163. package/src/vendor/zoo-code/src/core/prompts/sections/system-info.ts +30 -0
  164. package/src/vendor/zoo-code/src/core/prompts/sections/tool-use-guidelines.ts +9 -0
  165. package/src/vendor/zoo-code/src/core/prompts/sections/tool-use.ts +7 -0
  166. package/src/vendor/zoo-code/src/core/prompts/system.ts +176 -0
  167. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/access_mcp_resource.ts +41 -0
  168. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_diff.ts +40 -0
  169. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_patch.ts +61 -0
  170. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/ask_followup_question.ts +62 -0
  171. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/attempt_completion.ts +33 -0
  172. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/codebase_search.ts +43 -0
  173. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/converters.ts +109 -0
  174. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit.ts +48 -0
  175. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit_file.ts +72 -0
  176. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/execute_command.ts +54 -0
  177. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/generate_image.ts +51 -0
  178. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/index.ts +75 -0
  179. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/list_files.ts +41 -0
  180. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/mcp_server.ts +75 -0
  181. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/new_task.ts +39 -0
  182. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_command_output.ts +81 -0
  183. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_file.ts +169 -0
  184. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/run_slash_command.ts +31 -0
  185. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_files.ts +50 -0
  186. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_replace.ts +51 -0
  187. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/skill.ts +33 -0
  188. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/switch_mode.ts +31 -0
  189. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/update_todo_list.ts +54 -0
  190. package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/write_to_file.ts +40 -0
  191. package/src/vendor/zoo-code/src/core/prompts/types.ts +12 -0
  192. package/src/vendor/zoo-code/src/i18n/index.ts +19 -0
  193. package/src/vendor/zoo-code/src/integrations/misc/extract-text.ts +81 -0
  194. package/src/vendor/zoo-code/src/services/checkpoints/RepoPerTaskCheckpointService.ts +15 -0
  195. package/src/vendor/zoo-code/src/services/checkpoints/ShadowCheckpointService.ts +553 -0
  196. package/src/vendor/zoo-code/src/services/checkpoints/excludes.ts +212 -0
  197. package/src/vendor/zoo-code/src/services/checkpoints/index.ts +3 -0
  198. package/src/vendor/zoo-code/src/services/checkpoints/types.ts +35 -0
  199. package/src/vendor/zoo-code/src/services/code-index/manager.ts +19 -0
  200. package/src/vendor/zoo-code/src/services/mcp/McpHub.ts +36 -0
  201. package/src/vendor/zoo-code/src/services/roo-config/index.ts +441 -0
  202. package/src/vendor/zoo-code/src/services/search/file-search.ts +143 -0
  203. package/src/vendor/zoo-code/src/services/skills/SkillsManager.ts +20 -0
  204. package/src/vendor/zoo-code/src/shared/globalFileNames.ts +9 -0
  205. package/src/vendor/zoo-code/src/shared/language.ts +43 -0
  206. package/src/vendor/zoo-code/src/shared/modes.ts +257 -0
  207. package/src/vendor/zoo-code/src/shared/tools.ts +385 -0
  208. package/src/vendor/zoo-code/src/utils/fs.ts +39 -0
  209. package/src/vendor/zoo-code/src/utils/globalContext.ts +22 -0
  210. package/src/vendor/zoo-code/src/utils/json-schema.ts +16 -0
  211. package/src/vendor/zoo-code/src/utils/logging.ts +21 -0
  212. package/src/vendor/zoo-code/src/utils/mcp-name.ts +190 -0
  213. package/src/vendor/zoo-code/src/utils/object.ts +18 -0
  214. package/src/vendor/zoo-code/src/utils/path.ts +94 -0
  215. package/src/vendor/zoo-code/src/utils/shell.ts +376 -0
  216. package/src/vendor/zoo-code/src/utils/text-normalization.ts +99 -0
  217. package/src/vendor/zoo-code/types/global-settings.ts +19 -0
  218. package/src/vendor/zoo-code/types/index.ts +22 -0
  219. package/src/vendor/zoo-code/types/message.ts +375 -0
  220. package/src/vendor/zoo-code/types/mode.ts +241 -0
  221. package/src/vendor/zoo-code/types/todo.ts +19 -0
  222. package/src/vendor/zoo-code/types/tool-params.ts +116 -0
  223. package/src/vendor/zoo-code/types/tool.ts +67 -0
  224. package/src/vendor/zoo-code/types/vscode.ts +84 -0
  225. package/src/vision/describe.ts +242 -0
  226. package/src/vision/tool.ts +91 -0
  227. package/src/watcher/cli.ts +369 -0
  228. package/src/watcher/github.ts +304 -0
  229. package/src/watcher/index.ts +59 -0
  230. package/src/watcher/state.ts +254 -0
  231. package/src/watcher/watch.ts +562 -0
  232. package/tsconfig.json +18 -0
package/README.md ADDED
@@ -0,0 +1,486 @@
1
+ # headlesscode
2
+
3
+ A small, purpose-built, genuinely headless coding-agent harness. It reuses what
4
+ makes [Zoo Code](https://github.com/Zoo-Code-Org/Zoo-Code) (an Apache-2.0 Roo
5
+ Code fork) good — its system prompts, native tool schemas, and
6
+ mode/rules configuration format — without carrying along the VS Code GUI
7
+ dependency.
8
+
9
+ This repo implements:
10
+
11
+ - The **headless runtime engine** — vendoring the portable Apache-2.0 core and
12
+ the headless runtime (OpenRouter client, tool executor, tool-call parser,
13
+ orchestration loop, CLI).
14
+ - A **headless orchestration layer** — split an issue into worker groups, run
15
+ each as a harness subprocess in its own git worktree, review, and merge
16
+ (see [`docs/phase2-orchestration.md`](./docs/phase2-orchestration.md)).
17
+ - A **memory subsystem** — per-project knowledge facts + rolling session
18
+ summaries, with local semantic recall and a pluggable `MemoryStore`
19
+ contract for a remote backend.
20
+ - **Headless QA** — run the target repo's `qa-agent` mode as a second harness
21
+ session (see [`docs/phase4-qa.md`](./docs/phase4-qa.md)) and a
22
+ human-approval deploy gate in front of the repo's `deploy-production.sh`
23
+ (see [`docs/phase4-deploy-gate.md`](./docs/phase4-deploy-gate.md)).
24
+ - A **GitHub issue watcher** (see
25
+ [`docs/phase5-issue-watcher.md`](./docs/phase5-issue-watcher.md)) — file an
26
+ issue with a target label and a poll loop automatically splits and spawns it
27
+ through the existing orchestration pipeline, with durable idempotency so a
28
+ restart never double-spawns.
29
+ - **Cloud-scaling guardrails** — a hard concurrent-session cap, a per-session
30
+ cost/time/iteration budget, and a `CloudProvider` abstraction (container/VM
31
+ per issue behind the same lifecycle as the local worktree flow) with an
32
+ evaluation-only cloud-provider sketch (see
33
+ [`docs/phase6-cloud.md`](./docs/phase6-cloud.md)). No live cloud resources
34
+ are launched; the caps/budgets land before any scaling.
35
+
36
+ > ⚠️ **Security warning: default-allow arbitrary command execution.**
37
+ > `headlesscode` is a headless coding agent: by default it runs **arbitrary
38
+ > shell commands with your full user privileges** (no sandbox, no approval
39
+ > prompts) and can read and modify your files — including `~/.ssh`, `~/.aws`,
40
+ > and any other credentials your user can access. Run it only on machines and
41
+ > with tasks you trust, and isolate it (container/VM/dedicated user) whenever
42
+ > untrusted content is involved. See [`SECURITY.md`](./SECURITY.md) for the
43
+ > full disclosure and the optional permissions layer that provides defense in
44
+ > depth (not a security boundary).
45
+
46
+ ## Purpose
47
+
48
+ Drive a coding agent headlessly against real git repos: read a target repo's
49
+ `.roomodes` and `.roo/rules-<slug>/` files, build a system prompt from mode +
50
+ rules, call an LLM API (OpenRouter in Phase 1) with the tool schema, execute
51
+ tool calls via plain `fs`/`child_process` (no `vscode.*` anywhere), and loop
52
+ until completion. Non-interactive by design.
53
+
54
+ ## Quick start
55
+
56
+ `headlesscode` is not yet published to the npm registry, so install it from a
57
+ checkout:
58
+
59
+ ```bash
60
+ git clone https://github.com/Capsize-Games/headlesscode.git
61
+ cd headlesscode
62
+ npm install
63
+
64
+ # Required (except for --dry-run):
65
+ export HEADLESSCODE_OPENROUTER_API_KEY=sk-or-...
66
+
67
+ # Optional:
68
+ export OPENROUTER_MODEL=deepseek/deepseek-v4-flash-0731 # default model
69
+ export OPENROUTER_HTTP_REFERER=https://example.com # OpenRouter app header
70
+ export OPENROUTER_APP_TITLE="headlesscode" # OpenRouter X-Title header
71
+ export HEADLESSCODE_WORKSPACE_ROOT=/path/to/target/repo # default workspace root
72
+
73
+ # Run a task against a target repo, straight from the checkout:
74
+ node bin/headlesscode.mjs --task "Fix the bug in src/index.ts" --workspace /path/to/target/repo
75
+ ```
76
+
77
+ ### Systemwide `headlesscode` command
78
+
79
+ To avoid re-typing `node <checkout>/bin/headlesscode.mjs` from every project,
80
+ install a `headlesscode` command onto your `PATH` once:
81
+
82
+ ```bash
83
+ scripts/install-cli.sh
84
+ ```
85
+
86
+ This writes a wrapper to `~/.local/bin/headlesscode` (override with
87
+ `HEADLESSCODE_BIN_DIR`) that runs this checkout's `src/cli.ts` via its local
88
+ `tsx`, without `cd`-ing — so `--repo`/`--workspace` still default to whatever
89
+ directory you're standing in when you invoke it. Re-run the script any time
90
+ after `git pull` to point it at a moved checkout; the wrapper itself doesn't
91
+ need updating for ordinary code changes.
92
+
93
+ ```bash
94
+ # from any repo, no HEADLESSCODE_ROOT plumbing needed:
95
+ headlesscode orchestrate --repo . --issue 42
96
+ ```
97
+
98
+ The rest of this README uses the plain `headlesscode` form for brevity —
99
+ substitute `node <checkout>/bin/headlesscode.mjs` if you haven't run
100
+ `scripts/install-cli.sh` yet.
101
+
102
+ ## The `--dry-run` flow (no API key needed)
103
+
104
+ `--dry-run` builds the full system prompt and validates configuration loading
105
+ without calling the LLM — useful for CI and for checking that a target repo's
106
+ `.roomodes` / `.roo/rules-<slug>/` / `AGENTS.md` are picked up:
107
+
108
+ ```bash
109
+ headlesscode --dry-run --mode code --workspace /path/to/target/repo
110
+ ```
111
+
112
+ It prints the assembled system prompt plus a summary line (mode, custom modes
113
+ loaded, exposed tools, prompt size). Exit code 0 means prompt building + mode /
114
+ rules loading succeeded; non-zero means a config error.
115
+
116
+ ## Real-usage example against a target repo
117
+
118
+ ```bash
119
+ export HEADLESSCODE_OPENROUTER_API_KEY=sk-or-...
120
+ export HEADLESSCODE_WORKSPACE_ROOT=~/Projects/some-target-repo
121
+
122
+ # Point the agent at an already-scoped issue, in an isolated worktree:
123
+ headlesscode \
124
+ --mode code \
125
+ --task "Implement issue #29: add retry logic to the HTTP client (see .roo/rules for project conventions)." \
126
+ --workspace ~/Projects/some-target-repo \
127
+ --max-iterations 50 \
128
+ --log-file ./headlesscode-session.log
129
+ ```
130
+
131
+ The agent reads files, runs commands, writes code, and finishes by calling
132
+ `attempt_completion` (or by giving a final text answer). The final result is
133
+ printed to stdout. Exit code 0 = success; 1 = task failed (max iterations or
134
+ consecutive-mistake limit); 2 = usage/config error (e.g. missing
135
+ `HEADLESSCODE_OPENROUTER_API_KEY`).
136
+
137
+ ## Registering a new project
138
+
139
+ To use headlesscode against a project that isn't set up as a headlesscode
140
+ project yet, register it in one step:
141
+
142
+ ```bash
143
+ headlesscode init --workspace ~/Projects/your-project
144
+ ```
145
+
146
+ This detects the project's stack(s) (which drives per-session instruction
147
+ selection), makes sure the project's `.gitignore` excludes `.headlesscode/`
148
+ session artifacts, and builds the codebase-search index and the codemap — no
149
+ manual `index`/`codemap`/`.gitignore` steps needed. The index step calls the
150
+ embedding API and costs real money unless you pass `--skip-index` or
151
+ `--embedding-backend ollama`. See `headlesscode init --help` for the full
152
+ options.
153
+
154
+ ## CLI reference
155
+
156
+ ```
157
+ headlesscode --task "<task text>" [options]
158
+ headlesscode --task-file <path> [options]
159
+ headlesscode --dry-run [options]
160
+ headlesscode orchestrate --repo <path> --issue <n> [--issue <n> ...] [options]
161
+ headlesscode watch --owner <o> --repo <path> --label <name> [options]
162
+
163
+ --mode <slug> Mode to run in (built-in or from .roomodes). Default: code
164
+ --task <text> The task description for the agent
165
+ --task-file <path> Read the task from a file (relative to workspace)
166
+ --workspace <root> Workspace root (default: $HEADLESSCODE_WORKSPACE_ROOT or cwd)
167
+ --model <id> OpenRouter model id (default: $OPENROUTER_MODEL or deepseek/deepseek-v4-flash-0731)
168
+ --max-iterations <n> Loop iteration cap (default: 50)
169
+ --consecutive-error-limit <n> Consecutive mistakes before giving up (default: 3)
170
+ --max-cost-usd <n> Phase 6: per-session cost cap in USD (decimal). Default
171
+ $HEADLESSCODE_MAX_COST_USD; off when neither is set
172
+ --max-duration-ms <n> Phase 6: per-session wall-clock cap in ms. Default
173
+ $HEADLESSCODE_MAX_DURATION_MS; off when neither is set.
174
+ A tripped cap aborts with reason "budget"
175
+ --log-file <path> Also append structured logs to this file
176
+ --memory-dir <path> Phase 3 memory: store facts + session summaries under <path>
177
+ (enabled; default $HEADLESSCODE_MEMORY_DIR or
178
+ <workspace>/.headlesscode/memory). Memory is OFF unless set.
179
+ --no-memory Explicitly disable memory even if HEADLESSCODE_MEMORY_DIR is set
180
+ --allowed-commands <list> Comma-separated command prefixes the agent may run.
181
+ Default: $HEADLESSCODE_ALLOWED_COMMANDS, else
182
+ .headlesscode/permissions.json, else empty (=
183
+ allow everything except --denied-commands; see
184
+ SECURITY.md)
185
+ --denied-commands <list> Comma-separated command prefixes that are ALWAYS
186
+ refused (deny wins over allow; dangerous shell
187
+ substitutions are always blocked regardless).
188
+ Default: $HEADLESSCODE_DENIED_COMMANDS, else
189
+ .headlesscode/permissions.json, else empty
190
+ --protected-files <list> Comma-separated glob patterns of files the agent may
191
+ not write. Default: $HEADLESSCODE_PROTECTED_FILES,
192
+ else .headlesscode/permissions.json, else
193
+ ".env,.env.*,*.pem,*.key,id_rsa*"
194
+ --allow-protected-writes Escape hatch: permit writes to protected files
195
+ (default: OFF). Also settable via
196
+ "allowProtectedWrites": true in
197
+ .headlesscode/permissions.json
198
+ --dry-run Build the system prompt + validate config, then exit (no API key)
199
+ --version / --help
200
+
201
+ orchestrate subcommand (Phase 2 — parallel worktrees, headless workers):
202
+ --repo <path> Target repo root (required)
203
+ --issue <n> Issue number to include (repeatable)
204
+ --issues-json <file> Read issues from a JSON array of {number,title,body}
205
+ (used when gh is unavailable, or for tests)
206
+ --file-issues With --issues-json: file a REAL GitHub issue for
207
+ each synthetic entry (gh issue create --repo
208
+ <origin-owner>/<origin-repo>), swap in the real
209
+ number returned, and print one confirmation line
210
+ per created issue. A real, visible write to
211
+ GitHub — opt-in, never automatic. Requires
212
+ --issues-json
213
+ --batch <name> Batch id in the state file (default round-<date>)
214
+ --review-mode <slug> Mode slug for review sessions (default deepseek-reviewer)
215
+ --no-review Spawn + watch only; skip the reviewer
216
+ --qa Phase 4: run a headless QA session (--mode qa-agent)
217
+ on each group after its review passes; record
218
+ qa {status,verdict,evidence} in the state file
219
+ --qa-mode <slug> Mode slug for QA sessions (default qa-agent; the
220
+ target repo's .roomodes + .roo/rules-<slug>/ are
221
+ spliced automatically)
222
+ --deploy Phase 4: after all groups done + reviewed + QA passed,
223
+ run the human-approval deploy gate
224
+ (scripts/deploy-gate.sh) — a hard stop that never runs
225
+ the repo's deploy-production.sh without explicit human
226
+ approval (interactive on a TTY, token/file otherwise)
227
+ --deploy-args <str> Deploy args forwarded to the deploy script after the
228
+ gate approves (space-separated flags; also DEPLOY_ARGS env)
229
+ --poll-interval-ms <n> Watcher poll interval (default 5000)
230
+ --max-concurrent-sessions <n> Phase 6 global cap on concurrent sessions across
231
+ processes (default $HEADLESSCODE_MAX_CONCURRENT_
232
+ SESSIONS or 3). At/over the cap this run ABORTS
233
+ with a clear message, exit 1
234
+ --dry-run Print the split plan + spawn commands, spawn nothing
235
+
236
+ watch subcommand (Phase 5 — GitHub issue watcher, poll-based intake):
237
+ --owner <o> GitHub owner (required)
238
+ --repo <path> Local clone of the target repo (required; worktrees
239
+ are spawned under <path>/.worktrees/). The GitHub repo
240
+ name defaults to the directory basename (--gh-repo overrides)
241
+ --label <name> The label that triggers processing, e.g. needs-agent
242
+ --poll-interval-ms <n> Sweep interval in continuous mode (default 60000)
243
+ --run-once One sweep then exit 0 (or 1 if any spawn failed)
244
+ --max-per-sweep <n> Max NEW issues spawned per sweep (default 5); the rest
245
+ stay 'pending' in state and are picked up next sweep
246
+ --max-concurrent-sessions <n> Phase 6 GLOBAL cap on concurrent sessions across
247
+ processes (default $HEADLESSCODE_MAX_CONCURRENT_
248
+ SESSIONS or 3). Interplay: maxPerSweep bounds one
249
+ sweep's burst; this bounds the total fleet — issues
250
+ beyond it stay 'pending' until slots free up
251
+ --state-file <path> Durable idempotency state (default
252
+ <repo>/.worktrees/.watcher-state.json)
253
+ --mode <slug> / --memory-dir <path>
254
+ Forwarded to the spawner (ORCHESTRATOR_MODE /
255
+ HEADLESSCODE_MEMORY_DIR)
256
+ --qa / --deploy Pass-through: recorded per batch for the follow-up
257
+ orchestrate completion run
258
+ --dry-run Sweep + print the spawn plan, spawn nothing, write no
259
+ state (still needs a GitHub token for listIssues)
260
+ --retry-failed Retry previously-failed spawns next sweep
261
+ ```
262
+
263
+ ## Architecture
264
+
265
+ - [`src/llm/openrouter.ts`](./src/llm/openrouter.ts) — OpenRouter
266
+ chat-completions client using native `fetch` (no axios/node-fetch/openai
267
+ SDK). Reads `HEADLESSCODE_OPENROUTER_API_KEY`, optional `OPENROUTER_HTTP_REFERER` /
268
+ `OPENROUTER_APP_TITLE`; model default `deepseek/deepseek-v4-flash-0731` (env
269
+ `OPENROUTER_MODEL` overrides). Non-2xx → typed `OpenRouterError` with status +
270
+ body excerpt; supports `AbortSignal` timeouts.
271
+ - [`src/tools/executor.ts`](./src/tools/executor.ts) — headless executor for
272
+ `read_file`, `write_to_file`, `execute_command`, `list_files` (plain
273
+ `fs`/`child_process`), plus `attempt_completion` / `ask_followup_question`
274
+ handlers and "not implemented" stubs for every other vendored tool schema.
275
+ All file operations are resolved relative to the workspace root and rejected
276
+ if they escape it (path-traversal guard via `path.resolve` + containment
277
+ check). Command results are truncated (~30k chars) to keep context bounded.
278
+ - [`src/tools/output-summarizer.ts`](./src/tools/output-summarizer.ts) —
279
+ OPT-IN local summarization of oversized `execute_command` output: when
280
+ `HEADLESSCODE_LOCAL_SUMMARIZATION=1`, a result that would exceed the 30k
281
+ char cap is compressed by a local Ollama chat model before reaching the
282
+ cloud model, with a `[Output summarized by local model…]` transparency
283
+ header. OFF by default; on any failure it falls back to today's exact blunt
284
+ truncation (never an error, never a hang). Endpoint/model configurable via
285
+ `HEADLESSCODE_OLLAMA_URL` (default `http://localhost:11434`) and
286
+ `HEADLESSCODE_SUMMARIZATION_MODEL` (default `qwen3:8b`). Deliberately
287
+ limited to command output — file/diff content always stays verbatim.
288
+ - [`src/engine/parser.ts`](./src/engine/parser.ts) — OpenAI function-calling
289
+ parser: JSON.parses `tool_calls[].function.arguments` with a best-effort
290
+ partial-JSON fallback; parse failures are marked and fed back as errors.
291
+ - [`src/engine/prompt.ts`](./src/engine/prompt.ts) — wraps the vendored
292
+ `SYSTEM_PROMPT` builder: loads project `.roomodes` (same zod schema as Zoo
293
+ Code's `CustomModesManager`), passes the workspace as `cwd` so `.roo/rules-*`
294
+ / `AGENTS.md` splice in, and selects the mode's exposed tools.
295
+ - [`src/engine/loop.ts`](./src/engine/loop.ts) — `HeadlessSession`, the
296
+ orchestration loop. System + user → LLM → assistant (with tool_calls) → parse
297
+ → execute → `tool` role message → repeat. Terminates on `attempt_completion`
298
+ (its `args.result` is the final answer) or a text-only reply; fails bounded
299
+ on max iterations or `consecutiveErrorLimit` consecutive mistakes (tool
300
+ errors / parse errors / identical repeated calls). History truncation is a
301
+ Phase 1 placeholder: system + first user always kept, sliding window of the
302
+ last ~40 messages. The loop accepts an injected `llmClient` (DI) so tests use
303
+ a fake; the CLI wires `OpenRouterClient`.
304
+ - [`src/engine/logger.ts`](./src/engine/logger.ts) — structured logger
305
+ (timestamped lines to stdout/stderr, optional file).
306
+ - [`src/memory/`](./src/memory/index.ts) — memory subsystem:
307
+ - [`src/memory/types.ts`](./src/memory/types.ts) — the `MemoryFact` /
308
+ `SessionSummary` schema (per-project scoped, kinds
309
+ `convention|decision|failure|knowledge` where "things that didn't work"
310
+ are `failure`) and the two contracts: `MemoryStore` (the pluggable
311
+ storage boundary) and `Embedder`. Hard data-isolation requirement
312
+ documented: the harness knowledge is a dedicated schema, never reachable
313
+ through any customer tenant route.
314
+ - [`src/memory/local.ts`](./src/memory/local.ts) — `LocalMemoryStore`: the
315
+ fully working file backend (`facts/<project>.jsonl` +
316
+ `sessions/<project>.jsonl`, append-only, idempotent `addFact` by content
317
+ hash, `queryRecall` = keyword matches (high weight) + local-embedder
318
+ cosine similarity, deterministic ordering).
319
+ - [`src/memory/embed.ts`](./src/memory/embed.ts) — `createLocalEmbedder()`:
320
+ zero-dependency, deterministic lexical-hash embedder (lowercase word +
321
+ char-bigram tokens → fixed-dim L2-normalized vector). Placeholder for a
322
+ real local embedding model behind the same `Embedder` interface.
323
+ - [`src/memory/summarizer.ts`](./src/memory/summarizer.ts) —
324
+ `extractSessionSummary` (deterministic; files/commands derived from the
325
+ tool history, facts via keyword heuristics) + `buildRollingSummary`
326
+ (compact markdown recap of the last N sessions, so a session never needs
327
+ the infinite raw history).
328
+ - [`src/memory/uwuchat.ts`](./src/memory/uwuchat.ts) — a remote
329
+ `MemoryStore` implementation stub for a future hosted memory API. Throws
330
+ "not implemented" until its base-URL/token env vars are set.
331
+ - Memory is wired into `HeadlessSession` as an **opt-in** config (`memory`
332
+ + `project`); when unset the loop behaves exactly as before. When set, the
333
+ loop injects a `## PROJECT MEMORY` section (recalled facts + rolling
334
+ recap) into the first user message and records the session + extracted
335
+ facts afterwards — and memory failures are always non-fatal.
336
+ - [`src/orchestrator/`](./src/orchestrator/index.ts) — Phase 2 orchestration
337
+ layer: `split.ts` (issue-splitting heuristics, deterministic +
338
+ unit-tested), `state.ts` (`.worktrees/.orchestrator-state.json` read/write),
339
+ `reviewer.ts` (adversarial fresh-context review run with a read-only
340
+ executor), `watch.ts` (completion polling of `.harness.done` markers + stall
341
+ guard), and `cli.ts` (the `orchestrate` subcommand — split → spawn via
342
+ `scripts/spawn-parallel-worktrees.sh` → watch → review → QA → deploy gate).
343
+ - [`src/qa/qa.ts`](./src/qa/qa.ts) — Phase 4 headless QA: `runQa()` runs a
344
+ second harness session against a worktree in the target repo's `qa-agent`
345
+ mode (auto-spliced from `.roomodes` + `.roo/rules-qa-agent/`), with a
346
+ generic checklist fallback when the repo has no such mode. Read + command
347
+ tools only (no `write_to_file`) — QA verifies and reports, it never edits.
348
+ Verdict parsing is fail-closed (`pass`/`fail`/`error`, default `fail`).
349
+ - [`src/deploy/gate.ts`](./src/deploy/gate.ts) + [`src/deploy/gate-cli.ts`](./src/deploy/gate-cli.ts) —
350
+ Phase 4 human-approval deploy gate: the pure, unit-tested decision function
351
+ `decideApproval` (interactive y/N, one-time approval file, or
352
+ `DEPLOY_APPROVAL_TOKEN` matching `<repo>/.deploy-approval`; never
353
+ auto-approves) plus a thin CLI the bash wrapper calls.
354
+ - [`src/watcher/`](./src/watcher/index.ts) — Phase 5 GitHub issue watcher:
355
+ [`github.ts`](./src/watcher/github.ts) (native-fetch GitHub REST client with
356
+ label filter, PR filtering, pagination, `GITHUB_API_BASE_URL` override for
357
+ tests/mocks), [`state.ts`](./src/watcher/state.ts) (durable idempotency
358
+ state file — write-ahead `spawned` → `done`/`failed`, `pending` for capped
359
+ issues, restart-safe), [`watch.ts`](./src/watcher/watch.ts) (the poll loop:
360
+ list by label → split → spawn via the existing bash spawner, bounded by
361
+ `maxPerSweep` and the Phase 6 global cap), and [`cli.ts`](./src/watcher/cli.ts)
362
+ (the `watch` subcommand — continuous or `--run-once`, `--dry-run`,
363
+ `--retry-failed`).
364
+ - [`src/budget/`](./src/budget/index.ts) — Phase 6 guardrails:
365
+ [`cost.ts`](./src/budget/cost.ts) (model pricing table + `estimateCost`,
366
+ `HEADLESSCODE_PRICING_JSON` override, conservative fallback for unlisted
367
+ models), [`budget.ts`](./src/budget/budget.ts) (`SessionBudget` +
368
+ `BudgetTracker`: `tick()` before each LLM call, `record()` after with usage
369
+ tokens, `check()` snapshot, `BudgetExceededError`), and
370
+ [`concurrency.ts`](./src/budget/concurrency.ts) (`ConcurrencyLimiter` —
371
+ fail-fast in-process semaphore — plus `activeSessionCount` reading the
372
+ durable orchestrator/watcher state files for a cross-process view). Wired
373
+ into `HeadlessSession` (`budget` config, `budgetUsage` on results), the base
374
+ CLI (`--max-cost-usd` / `--max-duration-ms`), `orchestrate` (aborts at the
375
+ cap), the watcher (defers cap-exceeding issues to `pending`), and
376
+ `run-worker.sh`/`run-qa.sh` (env forwarding).
377
+ - [`src/cloud/`](./src/cloud/provider.ts) — Phase 6 ephemeral compute
378
+ abstraction: the `CloudProvider` lifecycle interface
379
+ (`spawnWorktreeSession` → `waitReady` → `runHarness` → `collectResults` →
380
+ `teardown`) with `LocalProcessProvider` as the current local behavior behind
381
+ it (reuses `spawn-parallel-worktrees.sh` + `run-worker.sh`), so a
382
+ container/VM-per-issue backend slots in without touching the orchestration
383
+ layer. A cloud-provider sketch is documented (evaluation only — no
384
+ live setup; see [`docs/phase6-cloud.md`](./docs/phase6-cloud.md)).
385
+ - [`src/cli.ts`](./src/cli.ts) — the `headlesscode` bin entry (+ `orchestrate`
386
+ and `watch` subcommand dispatch).
387
+ - [`scripts/run-worker.sh`](./scripts/run-worker.sh) — launches one headless
388
+ harness worker per worktree (pid, log, exit code, `.harness.done` marker).
389
+ - [`scripts/run-qa.sh`](./scripts/run-qa.sh) — Phase 4 QA wrapper mirroring
390
+ run-worker.sh: launches one harness QA session per worktree (`.qa-task.md`,
391
+ `.qa.pid`, `qa.log`, `.qa.exit`, `.qa.done/`).
392
+ - [`scripts/deploy-gate.sh`](./scripts/deploy-gate.sh) — Phase 4 gate wrapper:
393
+ path safety, deployment summary, interactive + token/file approval, then
394
+ (and only then) invokes the repo's `scripts/deploy-production.sh` with
395
+ forwarded deploy args. Exit 3 = human DENIED (hard stop).
396
+ - [`scripts/spawn-parallel-worktrees.sh`](./scripts/spawn-parallel-worktrees.sh)
397
+ — spawns one git worktree + harness worker per group (worktree/.env/branch
398
+ conventions, `run-worker.sh` + state-file writes, no GUI involved).
399
+
400
+ ### Phase 1 tool filtering decision
401
+
402
+ The loop exposes to the model exactly the tools the executor can actually run
403
+ for the selected mode: the intersection of the vendored mode tool groups
404
+ (`getToolsForMode`) with the Phase 1 executable set
405
+ (`read_file`, `write_to_file`, `execute_command`, `list_files`,
406
+ `attempt_completion`, `ask_followup_question`). Stub-only tools (`apply_diff`,
407
+ `search_files`, …) stay registered in the executor purely as a safety net
408
+ (clear "not implemented" error) but are NOT advertised to the model, so it
409
+ doesn't waste turns calling them.
410
+
411
+ ## Development
412
+
413
+ ```bash
414
+ npm run typecheck # npx tsc --noEmit (whole repo incl. vendored core)
415
+ npm run smoke # vendored prompt builder smoke test (no network)
416
+ npm test # unit tests with fake LLM clients (no network/key)
417
+ bash scripts/e2e/run.sh # Phase 1 integration (mock OpenRouter)
418
+ bash scripts/e2e-phase2/run.sh # Phase 2 integration (spawn + watch + review)
419
+ bash scripts/e2e-phase4/run.sh # Phase 4 integration (QA + deploy gate, fake deploy)
420
+ bash scripts/e2e-phase5/run.sh # Phase 5 integration (watcher vs fake GitHub server,
421
+ # stubbed spawner: state transitions + no double-spawn)
422
+ bash scripts/e2e-phase6/run.sh # Phase 6 integration (budget abort via mock OpenRouter
423
+ # + concurrency-cap sweep via fake GitHub + stubbed spawner)
424
+ npm run cli -- --dry-run --workspace . # build this repo's system prompt
425
+ ```
426
+
427
+ The engine tests (`src/engine/__tests__/loop.test.ts`) run the full loop with a
428
+ fake `LlmClient` injected via the `HeadlessSession` constructor — no network,
429
+ no API key required. Phase 2 adds `src/orchestrator/__tests__/` (split
430
+ heuristics, state round-trip, reviewer verdict parsing), and the e2e scripts
431
+ drive the real CLI through a local mock OpenRouter server, including a
432
+ 2-worktree parallel spawn, completion-marker polling, and a read-only review
433
+ invocation. Phase 5 adds `src/watcher/__tests__/` (github client with an
434
+ injected fetch, watcher-state idempotency semantics, and the watch loop with
435
+ an injected gh client + spawner covering spawn/idempotency/cap/failure/
436
+ dry-run/abort) and `scripts/e2e-phase5/run.sh` (watcher against a fake GitHub
437
+ server with a stubbed spawner).
438
+
439
+ ## Phase status
440
+
441
+ - ✅ Phase 1 Subtask 1 — vendored portable Zoo Code core
442
+ ([`src/vendor/zoo-code/`](./src/vendor/zoo-code/), read-only dependency).
443
+ - ✅ Phase 1 Subtask 2 — runtime engine (OpenRouter client, tool executor,
444
+ parser, orchestration loop, CLI, tests).
445
+ - ✅ Phase 2 — headless orchestration layer: drop-in
446
+ `spawn-parallel-worktrees.sh` + `run-worker.sh` (harness subprocess per
447
+ worktree, `.harness.done` completion markers), issue-splitting heuristics
448
+ port, `.orchestrator-state.json` state management, headless reviewer, and
449
+ the `orchestrate` CLI subcommand (see
450
+ [`docs/phase2-orchestration.md`](./docs/phase2-orchestration.md)).
451
+ - ✅ Phase 3 — memory subsystem: per-project knowledge facts + rolling session
452
+ summaries (`src/memory/`), local deterministic embedder for semantic recall,
453
+ opt-in `HeadlessSession`/CLI wiring (`--memory-dir`, `--no-memory`), and a
454
+ pluggable `MemoryStore` contract with a remote-backend client stub.
455
+ - ✅ Phase 4 — QA + deploy gate: headless QA runs the target repo's `qa-agent`
456
+ mode (`--qa` / `--qa-mode`), verdict parsing is fail-closed, results land in
457
+ the state file's per-group `qa` field; the human-approval deploy gate
458
+ (`--deploy`, `scripts/deploy-gate.sh` + `src/deploy/gate.ts`) is a hard stop
459
+ in front of `deploy-production.sh` that never auto-approves (see
460
+ [`docs/phase4-qa.md`](./docs/phase4-qa.md) and
461
+ [`docs/phase4-deploy-gate.md`](./docs/phase4-deploy-gate.md)).
462
+ - ✅ Phase 5 — GitHub issue watcher: poll-based intake (`watch` subcommand) —
463
+ detect issues by label via the GitHub REST API (`GH_TOKEN`), fan each out
464
+ through `splitIssues` + the existing `spawn-parallel-worktrees.sh`, track
465
+ idempotency durably (`.worktrees/.watcher-state.json`, write-ahead
466
+ ordering, `pending` cap deferral, restart-safe), optional `--dry-run` /
467
+ `--run-once` / `--retry-failed`; webhook upgrade designed but not built as
468
+ a server (see [`docs/phase5-issue-watcher.md`](./docs/phase5-issue-watcher.md)).
469
+ - ✅ Phase 6 — cloud scaling, guardrails-first: per-session cost/time/iteration
470
+ budget (`src/budget/`, `--max-cost-usd` / `--max-duration-ms`, budgetUsage on
471
+ results, worker env forwarding) + a hard concurrent-session cap
472
+ (`HEADLESSCODE_MAX_CONCURRENT_SESSIONS`, orchestrate aborts / watcher defers
473
+ to `pending`) + the `CloudProvider` abstraction with `LocalProcessProvider`
474
+ and an evaluation-only cloud-provider sketch (see
475
+ [`docs/phase6-cloud.md`](./docs/phase6-cloud.md)). No live cloud launched;
476
+ a container/VM-per-issue backend slots in behind the same interface.
477
+ - ⏳ Phase 3 (remaining) — token-based condensation.
478
+
479
+ ## Attribution
480
+
481
+ This project contains Apache-2.0-licensed code derived from
482
+ [Zoo Code](https://github.com/Zoo-Code-Org/Zoo-Code)
483
+ (`Zoo-Code-Org/Zoo-Code`, commit `ca9b60f`), itself a Roo Code fork. Prompt
484
+ text, tool schemas, and mode/rules loading logic are reused under the terms of
485
+ the Apache License 2.0. See [`LICENSE`](./LICENSE) and
486
+ [`ATTRIBUTION.md`](./ATTRIBUTION.md).