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
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Local-branch ↔ origin reconciliation for the orchestration workflow
3
+ * (issue #25).
4
+ *
5
+ * The failure mode this exists to prevent: local `master` silently
6
+ * accumulates commits for an entire session without ever being pushed.
7
+ * Parallel worktrees branch off local `master` / `origin/master` while GitHub
8
+ * PR merges only ever move `origin/master`, so the two diverge and every
9
+ * PR-merge cycle pays for it with re-merge conflict cascades (three separate
10
+ * re-merge-and-retest cycles in one session, zero functional benefit).
11
+ *
12
+ * Natural checkpoints call `syncBranchWithOrigin`:
13
+ * - `orchestrate` before spawning, so a round's branches start from what's
14
+ * actually on GitHub;
15
+ * - `orchestrate cleanup --apply` after a round settles.
16
+ *
17
+ * Both are best-effort auxiliary steps matching the repo's non-fatal failure
18
+ * pattern: a failure (auth, divergence, offline) is collected as a warning
19
+ * for the caller to print loudly — it never aborts the round or fails
20
+ * cleanup. Setting `HEADLESSCODE_ORCHESTRATE_NO_SYNC=1` disables the mutation
21
+ * entirely; only a read-only drift report (with a loud warning past
22
+ * `TRIVIAL_DRIFT_AHEAD`) is produced.
23
+ */
24
+
25
+ import { execFileSync } from "node:child_process"
26
+
27
+ /** Read-only drift report: how far local <branch> is from origin/<branch>. */
28
+ export interface BranchSyncStatus {
29
+ branch: string
30
+ remoteRef: string
31
+ /** Commits on <branch> that origin/<branch> does not have. */
32
+ ahead: number
33
+ /** Commits on origin/<branch> that <branch> does not have. */
34
+ behind: number
35
+ }
36
+
37
+ /** Outcome of one sync attempt; never throws — everything is a note/warning. */
38
+ export interface BranchSyncResult {
39
+ branch: string
40
+ remoteRef: string
41
+ ahead: number
42
+ behind: number
43
+ /** Sync was skipped entirely (no origin remote, detached HEAD, never pushed). */
44
+ skipped: boolean
45
+ skipReason?: string
46
+ fetched: boolean
47
+ pushed: boolean
48
+ fastForwarded: boolean
49
+ notes: string[]
50
+ warnings: string[]
51
+ }
52
+
53
+ /** Unpushed drift above this count is "more than a trivial amount" — the
54
+ * read-only (`HEADLESSCODE_ORCHESTRATE_NO_SYNC`) path warns only past it. */
55
+ export const TRIVIAL_DRIFT_AHEAD = 5
56
+
57
+ /** Setting this env var disables the automatic sync (read-only drift report). */
58
+ export const ORCHESTRATE_SYNC_DISABLED_ENV = "HEADLESSCODE_ORCHESTRATE_NO_SYNC"
59
+
60
+ function git(repo: string, args: string[], timeoutMs: number): string {
61
+ return execFileSync("git", ["-C", repo, ...args], {
62
+ encoding: "utf-8",
63
+ stdio: ["ignore", "pipe", "ignore"],
64
+ timeout: timeoutMs,
65
+ }).trim()
66
+ }
67
+
68
+ /** The repo's checked-out branch ("master"/"main"), or undefined when detached. */
69
+ function resolveHeadBranch(repo: string): string | undefined {
70
+ try {
71
+ const branch = git(repo, ["symbolic-ref", "--short", "HEAD"], 5000)
72
+ return branch === "" ? undefined : branch
73
+ } catch {
74
+ return undefined
75
+ }
76
+ }
77
+
78
+ function hasRemoteOrigin(repo: string): boolean {
79
+ try {
80
+ return git(repo, ["remote"], 5000).split("\n").includes("origin")
81
+ } catch {
82
+ return false
83
+ }
84
+ }
85
+
86
+ function remoteRefExists(repo: string, remoteRef: string): boolean {
87
+ try {
88
+ execFileSync("git", ["-C", repo, "rev-parse", "--verify", "--quiet", remoteRef], { stdio: "ignore", timeout: 5000 })
89
+ return true
90
+ } catch {
91
+ return false
92
+ }
93
+ }
94
+
95
+ function countAheadBehind(repo: string, remoteRef: string, branch: string): { ahead: number; behind: number } {
96
+ const count = (args: string[]): number => {
97
+ try {
98
+ return Number(git(repo, args, 10_000)) || 0
99
+ } catch {
100
+ return 0
101
+ }
102
+ }
103
+ return {
104
+ ahead: count(["rev-list", "--count", `${remoteRef}..${branch}`]),
105
+ behind: count(["rev-list", "--count", `${branch}..${remoteRef}`]),
106
+ }
107
+ }
108
+
109
+ function errMessage(err: unknown): string {
110
+ return err instanceof Error ? err.message : String(err)
111
+ }
112
+
113
+ /**
114
+ * Read-only drift report against the CURRENT (possibly stale) origin ref —
115
+ * no fetch, no mutation. `undefined` when there is nothing to compare
116
+ * against (detached HEAD, no origin remote, branch never pushed).
117
+ */
118
+ export function branchSyncStatus(repo: string, branch?: string): BranchSyncStatus | undefined {
119
+ const head = resolveHeadBranch(repo)
120
+ const target = branch ?? head
121
+ if (!target || !hasRemoteOrigin(repo)) {
122
+ return undefined
123
+ }
124
+ const remoteRef = `origin/${target}`
125
+ if (!remoteRefExists(repo, remoteRef)) {
126
+ return undefined
127
+ }
128
+ const { ahead, behind } = countAheadBehind(repo, remoteRef, target)
129
+ return { branch: target, remoteRef, ahead, behind }
130
+ }
131
+
132
+ /**
133
+ * Fetch origin, push local-only commits, then fast-forward local onto origin
134
+ * (only when local is strictly behind and it is the checked-out branch). A
135
+ * divergence (local ahead AND behind) cannot be auto-reconciled — it is
136
+ * reported as a loud warning. Never throws.
137
+ */
138
+ export function syncBranchWithOrigin(repo: string, branch?: string): BranchSyncResult {
139
+ const head = resolveHeadBranch(repo)
140
+ const target = branch ?? head
141
+ if (!target) {
142
+ return {
143
+ branch: "", remoteRef: "", ahead: 0, behind: 0,
144
+ skipped: true, skipReason: "detached HEAD — cannot determine the branch to sync",
145
+ fetched: false, pushed: false, fastForwarded: false, notes: [], warnings: [],
146
+ }
147
+ }
148
+ const remoteRef = `origin/${target}`
149
+ if (!hasRemoteOrigin(repo)) {
150
+ return {
151
+ branch: target, remoteRef, ahead: 0, behind: 0,
152
+ skipped: true, skipReason: `no git remote named 'origin' — nothing to sync`,
153
+ fetched: false, pushed: false, fastForwarded: false, notes: [], warnings: [],
154
+ }
155
+ }
156
+ if (!remoteRefExists(repo, remoteRef)) {
157
+ return {
158
+ branch: target, remoteRef, ahead: 0, behind: 0,
159
+ skipped: true, skipReason: `${remoteRef} does not exist — this branch was never pushed`,
160
+ fetched: false, pushed: false, fastForwarded: false, notes: [], warnings: [],
161
+ }
162
+ }
163
+
164
+ const notes: string[] = []
165
+ const warnings: string[] = []
166
+
167
+ let fetched = false
168
+ try {
169
+ git(repo, ["fetch", "origin", target], 60_000)
170
+ fetched = true
171
+ } catch (err) {
172
+ return {
173
+ branch: target, remoteRef, ahead: 0, behind: 0, skipped: false,
174
+ fetched: false, pushed: false, fastForwarded: false, notes: [],
175
+ warnings: [`git fetch origin ${target} failed: ${errMessage(err)} — cannot sync local ${target} with ${remoteRef}`],
176
+ }
177
+ }
178
+
179
+ let { ahead, behind } = countAheadBehind(repo, remoteRef, target)
180
+ let pushed = false
181
+ let fastForwarded = false
182
+
183
+ if (ahead > 0) {
184
+ try {
185
+ git(repo, ["push", "origin", target], 60_000)
186
+ pushed = true
187
+ notes.push(`pushed ${ahead} commit(s) to ${remoteRef}`)
188
+ ahead = 0
189
+ } catch (err) {
190
+ warnings.push(`could not push ${ahead} commit(s) to ${remoteRef}: ${errMessage(err)}`)
191
+ }
192
+ }
193
+
194
+ if (behind > 0) {
195
+ if (target !== head) {
196
+ notes.push(`local ${target} is ${behind} commit(s) behind ${remoteRef} — not fast-forwarding (only the checked-out branch is auto-merged)`)
197
+ } else {
198
+ try {
199
+ git(repo, ["merge", "--ff-only", remoteRef], 30_000)
200
+ fastForwarded = true
201
+ notes.push(`fast-forwarded ${target} onto ${remoteRef} (${behind} commit(s))`)
202
+ behind = 0
203
+ } catch (err) {
204
+ warnings.push(`could not fast-forward ${target} onto ${remoteRef}: ${errMessage(err)}`)
205
+ }
206
+ }
207
+ }
208
+
209
+ return { branch: target, remoteRef, ahead, behind, skipped: false, fetched, pushed, fastForwarded, notes, warnings }
210
+ }
211
+
212
+ /** Informational lines describing what the sync did (empty for a silent no-op). */
213
+ export function syncSummaryLines(result: BranchSyncResult): string[] {
214
+ if (result.skipped) {
215
+ return []
216
+ }
217
+ if (result.pushed || result.fastForwarded) {
218
+ const bits: string[] = []
219
+ if (result.pushed) {
220
+ bits.push("pushed local commits")
221
+ }
222
+ if (result.fastForwarded) {
223
+ bits.push("fast-forwarded onto origin")
224
+ }
225
+ return [`${result.branch} synced with ${result.remoteRef} (${bits.join(", ")})`]
226
+ }
227
+ if (result.ahead > 0 || result.behind > 0) {
228
+ return [`${result.branch} NOT fully synced with ${result.remoteRef} (${result.ahead} ahead, ${result.behind} behind)`]
229
+ }
230
+ if (result.warnings.length === 0) {
231
+ return [`${result.branch} is in sync with ${result.remoteRef}`]
232
+ }
233
+ return []
234
+ }
235
+
236
+ /** Loud, actionable warning lines for drift the sync could not fix. */
237
+ export function syncWarningLines(result: BranchSyncResult, repo: string): string[] {
238
+ if (result.skipped) {
239
+ return []
240
+ }
241
+ const lines = [...result.warnings]
242
+ if (result.ahead > 0 || result.behind > 0) {
243
+ lines.push(
244
+ `WARNING: local ${result.branch} is ${result.ahead} commit(s) ahead and ${result.behind} behind ${result.remoteRef} ` +
245
+ `after the sync attempt — unpushed drift like this causes merge-conflict cascades at PR-merge time (issue #25). ` +
246
+ `Reconcile manually: git -C ${repo} fetch origin && git -C ${repo} merge --ff-only origin/${result.branch} && git -C ${repo} push origin ${result.branch}`,
247
+ )
248
+ }
249
+ return lines
250
+ }
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Phase 2 orchestration layer — public exports.
3
+ *
4
+ * Replaces a GUI orchestration flow (xdotool inject + wmctrl wake-up) with
5
+ * headless harness subprocesses, reusing the issue-splitting heuristics and
6
+ * the reviewer checklist.
7
+ */
8
+
9
+ export {
10
+ ISSUE_SIZE_WARN_THRESHOLD,
11
+ issueShape,
12
+ issueSizeWarnings,
13
+ shapeOf,
14
+ splitIssues,
15
+ topLevelSectionCount,
16
+ } from "./split.js"
17
+ export type { IssueSizeWarning, SplitIssue, WorktreeSpec } from "./split.js"
18
+
19
+ export { loadState, loadStateSync, saveState, saveStateSync, updateGroup, defaultState } from "./state.js"
20
+ export type { OrchestratorGroup, OrchestratorState } from "./state.js"
21
+
22
+ export {
23
+ runReview,
24
+ runReviewWithRetries,
25
+ parseReviewResult,
26
+ reviewTools,
27
+ HARNESS_ROOT,
28
+ DEFAULT_REVIEW_PROMPT_PATH,
29
+ } from "./reviewer.js"
30
+ export type { ReviewOptions, ReviewResult } from "./reviewer.js"
31
+
32
+ export { analyzeWorktreeSessions, formatAnalysisReport, listSessionIds } from "./log-analysis.js"
33
+ export type { SessionLogAnalysis, AnalyzeOptions, ToolErrorSample, StallGap, RepeatedCommand } from "./log-analysis.js"
34
+
35
+ export { analyzeCliMain, parseAnalyzeArgs } from "./analyze-cli.js"
36
+
37
+ export {
38
+ costHistoryFilePath,
39
+ appendCostHistoryRecord,
40
+ readCostHistory,
41
+ recordGroupCost,
42
+ sessionCostHistoryFilePath,
43
+ appendSessionCostRecord,
44
+ readSessionCostHistory,
45
+ recordAllSessionCosts,
46
+ } from "./cost-history.js"
47
+ export type { CostHistoryRecord, SessionCostRecord } from "./cost-history.js"
48
+
49
+ export { costHistoryCliMain, parseCostHistoryArgs } from "./cost-history-cli.js"
50
+
51
+ export {
52
+ aggregateByShape,
53
+ buildEstimateSection,
54
+ estimateGroup,
55
+ estimateGroups,
56
+ estimateIssue,
57
+ MIN_SHAPE_SAMPLES,
58
+ } from "./cost-estimate.js"
59
+ export type { GroupEstimate, IssueEstimate, ShapeStats } from "./cost-estimate.js"
60
+
61
+
62
+ export { watchGroups, inspectGroup, tailLog, groupWorktreePath, isPidAlive, DEFAULT_POLL_INTERVAL_MS, DEFAULT_STALL_TIMEOUT_MS } from "./watch.js"
63
+ export type { WatchOptions, WatchSummary } from "./watch.js"
64
+
65
+ export { checkMergedByAncestor, checkMergedByGitHubPr } from "./merge-check.js"
66
+ export type { MergeCheckResult } from "./merge-check.js"
67
+
68
+ export {
69
+ applyCleanup,
70
+ assessGroupCleanup,
71
+ assessGroupCleanupSync,
72
+ checkGroupMerged,
73
+ cleanupMain,
74
+ parseCleanupArgs,
75
+ planCleanup,
76
+ removeHarnessArtifacts,
77
+ removeKnownSafeArtifacts,
78
+ resolveBaseBranch,
79
+ resolveGroupBranch,
80
+ worktreeUncommitted,
81
+ } from "./cleanup.js"
82
+ export type { CleanupDeps, CleanupPlan, CleanupPlanEntry, CleanupRunResult, CleanupStatus, GhWiring } from "./cleanup.js"
83
+
84
+ export {
85
+ branchSyncStatus,
86
+ ORCHESTRATE_SYNC_DISABLED_ENV,
87
+ syncBranchWithOrigin,
88
+ syncSummaryLines,
89
+ syncWarningLines,
90
+ TRIVIAL_DRIFT_AHEAD,
91
+ } from "./git-sync.js"
92
+ export type { BranchSyncResult, BranchSyncStatus } from "./git-sync.js"
93
+
94
+ export {
95
+ buildStatusSummary,
96
+ formatStatusText,
97
+ reconcileGroups,
98
+ verdictLine,
99
+ waitForTerminalState,
100
+ isTerminalStatus,
101
+ TERMINAL_STATUSES,
102
+ DEFAULT_STATUS_POLL_INTERVAL_MS,
103
+ DEFAULT_STATUS_TIMEOUT_MS,
104
+ } from "./status.js"
105
+ export type {
106
+ StatusSummary,
107
+ StatusCounts,
108
+ StatusGroupRow,
109
+ WaitForTerminalOptions,
110
+ WaitForTerminalResult,
111
+ ReconcileHooks,
112
+ ReconcileResult,
113
+ } from "./status.js"
114
+
115
+ // Issue #14: standalone review/rework/resume subcommands (resume.ts).
116
+ export {
117
+ findingsFromCommentBodies,
118
+ fetchFindingsFromIssueComments,
119
+ parseResumeArgs,
120
+ parseReviewArgs,
121
+ parseReworkArgs,
122
+ prHeadBranch,
123
+ rebuildPatchFromMarkers,
124
+ recheckoutWorktree,
125
+ recordSettledCost,
126
+ resumeGroup,
127
+ resumeMain,
128
+ reviewMain,
129
+ reworkMain,
130
+ runQaStep,
131
+ runReviewStep,
132
+ runReworkStep,
133
+ resolveTargetGroups,
134
+ } from "./resume.js"
135
+ export type {
136
+ QaStepOptions,
137
+ QaStepResult,
138
+ RecordCostOptions,
139
+ ResumeCliOptions,
140
+ ResumeGroupOptions,
141
+ ResumeGroupResult,
142
+ ResumeIo,
143
+ ResumeOutcome,
144
+ ResumeTarget,
145
+ ResolveTargetHooks,
146
+ ReviewCliOptions,
147
+ ReviewStepOptions,
148
+ ReviewStepResult,
149
+ ReworkCliOptions,
150
+ ReworkOutcome,
151
+ ReworkStepOptions,
152
+ ReworkStepResult,
153
+ } from "./resume.js"
Binary file
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Deterministic "is this group's work merged?" predicates for `orchestrate
3
+ * cleanup` (see cleanup.ts). Two sources of truth, dispatched by which
4
+ * workflow produced the group — never a heuristic:
5
+ *
6
+ * - checkMergedByAncestor: local merge (`git merge <branch>` /
7
+ * fast-forward into the base branch). `git merge-base --is-ancestor
8
+ * <branchTip> <base>` is provable with total certainty — exit 0 means
9
+ * every commit on the branch is in history. No guessing, no network.
10
+ * - checkMergedByGitHubPr: GitHub PR merge (squash or rebase). GitHub
11
+ * creates a NEW commit whose hash is nothing on the worker's branch, so
12
+ * the ancestor check above would wrongly say "not merged" — the only
13
+ * correct answer for that workflow is GitHub's server-computed `.merged`
14
+ * field (GET /repos/{owner}/{repo}/pulls/{number}).
15
+ *
16
+ * Both checks are conservative by construction: an API error or unexpected
17
+ * git failure is "unknown" (via checkMergedByGitHubPr) or a throw (ancestor
18
+ * check) — never guessed as merged.
19
+ */
20
+
21
+ import { execFileSync } from "node:child_process"
22
+
23
+ import { getPullRequestStatus, OpenPrError } from "../github/pr.js"
24
+
25
+ export type MergeCheckResult =
26
+ | { merged: true; via: "local-ancestor" | "github-pr"; detail: string }
27
+ | { merged: false; via: "local-ancestor" | "github-pr" | "unknown"; detail: string }
28
+
29
+ /**
30
+ * Local-merge check — no network, fully deterministic. Pass the branch names
31
+ * (or commits) directly; git resolves them itself.
32
+ *
33
+ * Exit-code contract (git's documented behavior for
34
+ * `merge-base --is-ancestor`):
35
+ * 0 the branch tip IS an ancestor of baseBranch → merged
36
+ * 1 not an ancestor → not merged
37
+ * anything else (e.g. 128 for a missing ref) → THROW; an unknown git
38
+ * state must never be guessed as merged or not-merged.
39
+ */
40
+ export function checkMergedByAncestor(repo: string, branch: string, baseBranch: string): MergeCheckResult {
41
+ let status: number | null
42
+ try {
43
+ execFileSync("git", ["-C", repo, "merge-base", "--is-ancestor", branch, baseBranch], {
44
+ stdio: "ignore",
45
+ timeout: 10_000,
46
+ })
47
+ status = 0
48
+ } catch (err) {
49
+ status = (err as { status?: number }).status ?? null
50
+ }
51
+ if (status === 0) {
52
+ return {
53
+ merged: true,
54
+ via: "local-ancestor",
55
+ detail: `${branch} tip is an ancestor of ${baseBranch}`,
56
+ }
57
+ }
58
+ if (status === 1) {
59
+ return {
60
+ merged: false,
61
+ via: "local-ancestor",
62
+ detail: `${branch} tip is NOT an ancestor of ${baseBranch}`,
63
+ }
64
+ }
65
+ throw new Error(
66
+ `git merge-base --is-ancestor ${branch} ${baseBranch} failed (exit ${status ?? "unknown"}) — merge status unknown`,
67
+ )
68
+ }
69
+
70
+ /**
71
+ * GitHub PR check — only attempted when the group records a PR number. Asks
72
+ * GitHub directly and trusts the server-computed `.merged` boolean (the only
73
+ * correct answer for squash/rebase merges). An API/network failure fails
74
+ * CLOSED as `{ merged: false, via: "unknown" }` — an unverifiable merge is
75
+ * never treated as merged.
76
+ */
77
+ export async function checkMergedByGitHubPr(options: {
78
+ owner: string
79
+ repo: string
80
+ prNumber: number
81
+ getInstallationToken: () => Promise<string>
82
+ /** API base URL override (tests/mocks; default api.github.com). */
83
+ baseUrl?: string
84
+ /** Injectable fetch for tests (default: global fetch). */
85
+ fetchImpl?: typeof fetch
86
+ }): Promise<MergeCheckResult> {
87
+ const { owner, repo, prNumber } = options
88
+ try {
89
+ const status = await getPullRequestStatus({
90
+ owner,
91
+ repo,
92
+ prNumber,
93
+ getToken: options.getInstallationToken,
94
+ ...(options.baseUrl ? { baseUrl: options.baseUrl } : {}),
95
+ ...(options.fetchImpl ? { fetchImpl: options.fetchImpl } : {}),
96
+ })
97
+ if (status.merged) {
98
+ return { merged: true, via: "github-pr", detail: `PR #${prNumber} merged (state ${status.state})` }
99
+ }
100
+ return { merged: false, via: "github-pr", detail: `PR #${prNumber} not merged (state ${status.state})` }
101
+ } catch (err) {
102
+ const detail =
103
+ err instanceof OpenPrError
104
+ ? `GitHub API error (GET ${err.url}, HTTP ${err.status}): ${err.message}`
105
+ : `GitHub PR check error: ${err instanceof Error ? err.message : String(err)}`
106
+ return { merged: false, via: "unknown", detail }
107
+ }
108
+ }