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.
- package/ATTRIBUTION.md +53 -0
- package/CODE_OF_CONDUCT.md +130 -0
- package/CONTRIBUTING.md +107 -0
- package/LICENSE +202 -0
- package/README.md +486 -0
- package/SECURITY.md +211 -0
- package/bin/headlesscode.mjs +83 -0
- package/package.json +63 -0
- package/shared/prompts/review-mode-prompt-short.md +93 -0
- package/shared/prompts/review-mode-prompt.md +281 -0
- package/shared/rules-code/rules.md +22 -0
- package/shared/stacks/cpp/rules.md +30 -0
- package/shared/stacks/fastapi/rules.md +30 -0
- package/shared/stacks/javascript/rules.md +37 -0
- package/shared/stacks/postgresql/rules.md +31 -0
- package/shared/stacks/python/rules.md +35 -0
- package/shared/stacks/react/rules.md +11 -0
- package/shared/stacks/typescript/rules.md +10 -0
- package/src/budget/budget.ts +221 -0
- package/src/budget/concurrency.ts +126 -0
- package/src/budget/cost.ts +309 -0
- package/src/budget/index.ts +8 -0
- package/src/checkpoints/cli.ts +256 -0
- package/src/checkpoints/service.ts +227 -0
- package/src/cli.ts +1535 -0
- package/src/cloud/docker-provider.ts +334 -0
- package/src/cloud/provider.ts +300 -0
- package/src/codeintel/call-graph.ts +78 -0
- package/src/codeintel/find-references.ts +123 -0
- package/src/codeintel/go-to-definition.ts +193 -0
- package/src/codeintel/handlers.ts +190 -0
- package/src/codeintel/import-graph.ts +173 -0
- package/src/codeintel/outline.ts +180 -0
- package/src/codeintel/position.ts +77 -0
- package/src/codeintel/program.ts +350 -0
- package/src/codeintel/rename-symbol.ts +213 -0
- package/src/codeintel/tools.ts +280 -0
- package/src/codemap/build.ts +135 -0
- package/src/codemap/cli.ts +190 -0
- package/src/codemap/extract.ts +339 -0
- package/src/codemap/files.ts +236 -0
- package/src/codemap/fingerprint.ts +65 -0
- package/src/codemap/flows.ts +62 -0
- package/src/codemap/html.ts +451 -0
- package/src/codemap/lock.ts +80 -0
- package/src/codemap/types.ts +101 -0
- package/src/codesearch/airunner-embedder.ts +185 -0
- package/src/codesearch/chunk.ts +339 -0
- package/src/codesearch/cli.ts +223 -0
- package/src/codesearch/embedder.ts +332 -0
- package/src/codesearch/files.ts +280 -0
- package/src/codesearch/index.ts +469 -0
- package/src/codesearch/ollama-embedder.ts +205 -0
- package/src/codesearch/search.ts +141 -0
- package/src/codesearch/types.ts +100 -0
- package/src/config/mode-models.ts +218 -0
- package/src/dashboard/aggregate.ts +364 -0
- package/src/dashboard/chat-thread.ts +141 -0
- package/src/dashboard/checkpoints.ts +124 -0
- package/src/dashboard/cli.ts +193 -0
- package/src/dashboard/codemap.ts +44 -0
- package/src/dashboard/files.ts +121 -0
- package/src/dashboard/page.ts +2803 -0
- package/src/dashboard/self-improvement-metrics.ts +282 -0
- package/src/dashboard/server.ts +1103 -0
- package/src/dashboard/session-launch.ts +310 -0
- package/src/dashboard/timeline.ts +273 -0
- package/src/dashboard/tool-exec.ts +107 -0
- package/src/dashboard/trend-cli.ts +141 -0
- package/src/dashboard/trend.ts +413 -0
- package/src/decision-proxy/cli.ts +261 -0
- package/src/decision-proxy/proxy.ts +569 -0
- package/src/deploy/gate-cli.ts +147 -0
- package/src/deploy/gate.ts +254 -0
- package/src/engine/condense.ts +512 -0
- package/src/engine/events.ts +428 -0
- package/src/engine/handoff.ts +71 -0
- package/src/engine/lazy-tools.ts +160 -0
- package/src/engine/local-explore.ts +653 -0
- package/src/engine/logger.ts +96 -0
- package/src/engine/loop.ts +5517 -0
- package/src/engine/parser.ts +347 -0
- package/src/engine/prompt.ts +860 -0
- package/src/engine/reports.ts +47 -0
- package/src/engine/stacks.ts +448 -0
- package/src/engine/types.ts +291 -0
- package/src/engine/usage.ts +186 -0
- package/src/github/app-auth.ts +161 -0
- package/src/github/cli.ts +448 -0
- package/src/github/installations.ts +133 -0
- package/src/github/pr.ts +321 -0
- package/src/github/provision.ts +118 -0
- package/src/github/push.ts +122 -0
- package/src/index-util.ts +50 -0
- package/src/index.ts +81 -0
- package/src/init/cli.ts +248 -0
- package/src/init/gitignore.ts +74 -0
- package/src/llm/ollama.ts +308 -0
- package/src/llm/openrouter.ts +868 -0
- package/src/llm/preflight.ts +367 -0
- package/src/llm/transcript-capture.ts +84 -0
- package/src/memory/embed.ts +110 -0
- package/src/memory/index.ts +22 -0
- package/src/memory/local.ts +259 -0
- package/src/memory/summarizer.ts +283 -0
- package/src/memory/types.ts +153 -0
- package/src/memory/uwuchat.ts +157 -0
- package/src/migrate/cli.ts +115 -0
- package/src/orchestrator/analyze-cli.ts +104 -0
- package/src/orchestrator/auto-split.ts +206 -0
- package/src/orchestrator/cleanup.ts +1003 -0
- package/src/orchestrator/cli.ts +3571 -0
- package/src/orchestrator/cost-estimate.ts +564 -0
- package/src/orchestrator/cost-history-cli.ts +242 -0
- package/src/orchestrator/cost-history.ts +397 -0
- package/src/orchestrator/git-sync.ts +250 -0
- package/src/orchestrator/index.ts +153 -0
- package/src/orchestrator/log-analysis.ts +0 -0
- package/src/orchestrator/merge-check.ts +108 -0
- package/src/orchestrator/pipeline.ts +411 -0
- package/src/orchestrator/resume.ts +1940 -0
- package/src/orchestrator/reviewer.ts +503 -0
- package/src/orchestrator/split.ts +296 -0
- package/src/orchestrator/state.ts +542 -0
- package/src/orchestrator/status.ts +697 -0
- package/src/orchestrator/verification-gate.ts +134 -0
- package/src/orchestrator/watch.ts +898 -0
- package/src/permissions/commands.ts +1083 -0
- package/src/permissions/config.ts +241 -0
- package/src/permissions/index.ts +12 -0
- package/src/permissions/protected-files.ts +96 -0
- package/src/permissions/store-protection.ts +272 -0
- package/src/project-store.ts +648 -0
- package/src/projects/cli.ts +382 -0
- package/src/qa/qa.ts +487 -0
- package/src/tools/browser/handler.ts +346 -0
- package/src/tools/browser/service.ts +406 -0
- package/src/tools/browser/smoke.ts +78 -0
- package/src/tools/browser/tool.ts +99 -0
- package/src/tools/executor.ts +2575 -0
- package/src/tools/language-detect.ts +183 -0
- package/src/tools/output-summarizer.ts +369 -0
- package/src/tools/run-tests.ts +302 -0
- package/src/tools/set-indentation-tool.ts +49 -0
- package/src/tools/test-selection.ts +160 -0
- package/src/vendor/tests/smoke.ts +103 -0
- package/src/vendor/zoo-code/VENDOR-NOTES.md +213 -0
- package/src/vendor/zoo-code/shim/anthropic.ts +71 -0
- package/src/vendor/zoo-code/shim/openai.d.ts +60 -0
- package/src/vendor/zoo-code/shim/os-name.ts +18 -0
- package/src/vendor/zoo-code/shim/strip-bom.ts +14 -0
- package/src/vendor/zoo-code/shim/vscode.ts +76 -0
- package/src/vendor/zoo-code/src/core/config/CustomModesManager.ts +1015 -0
- package/src/vendor/zoo-code/src/core/diff/strategies/multi-search-replace.ts +670 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/capabilities.ts +46 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/custom-instructions.ts +559 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/index.ts +10 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/markdown-formatting.ts +7 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/modes.ts +35 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/objective.ts +13 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/rules.ts +95 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/skills.ts +105 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/system-info.ts +30 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/tool-use-guidelines.ts +9 -0
- package/src/vendor/zoo-code/src/core/prompts/sections/tool-use.ts +7 -0
- package/src/vendor/zoo-code/src/core/prompts/system.ts +176 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/access_mcp_resource.ts +41 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_diff.ts +40 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/apply_patch.ts +61 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/ask_followup_question.ts +62 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/attempt_completion.ts +33 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/codebase_search.ts +43 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/converters.ts +109 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit.ts +48 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/edit_file.ts +72 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/execute_command.ts +54 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/generate_image.ts +51 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/index.ts +75 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/list_files.ts +41 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/mcp_server.ts +75 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/new_task.ts +39 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_command_output.ts +81 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/read_file.ts +169 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/run_slash_command.ts +31 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_files.ts +50 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/search_replace.ts +51 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/skill.ts +33 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/switch_mode.ts +31 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/update_todo_list.ts +54 -0
- package/src/vendor/zoo-code/src/core/prompts/tools/native-tools/write_to_file.ts +40 -0
- package/src/vendor/zoo-code/src/core/prompts/types.ts +12 -0
- package/src/vendor/zoo-code/src/i18n/index.ts +19 -0
- package/src/vendor/zoo-code/src/integrations/misc/extract-text.ts +81 -0
- package/src/vendor/zoo-code/src/services/checkpoints/RepoPerTaskCheckpointService.ts +15 -0
- package/src/vendor/zoo-code/src/services/checkpoints/ShadowCheckpointService.ts +553 -0
- package/src/vendor/zoo-code/src/services/checkpoints/excludes.ts +212 -0
- package/src/vendor/zoo-code/src/services/checkpoints/index.ts +3 -0
- package/src/vendor/zoo-code/src/services/checkpoints/types.ts +35 -0
- package/src/vendor/zoo-code/src/services/code-index/manager.ts +19 -0
- package/src/vendor/zoo-code/src/services/mcp/McpHub.ts +36 -0
- package/src/vendor/zoo-code/src/services/roo-config/index.ts +441 -0
- package/src/vendor/zoo-code/src/services/search/file-search.ts +143 -0
- package/src/vendor/zoo-code/src/services/skills/SkillsManager.ts +20 -0
- package/src/vendor/zoo-code/src/shared/globalFileNames.ts +9 -0
- package/src/vendor/zoo-code/src/shared/language.ts +43 -0
- package/src/vendor/zoo-code/src/shared/modes.ts +257 -0
- package/src/vendor/zoo-code/src/shared/tools.ts +385 -0
- package/src/vendor/zoo-code/src/utils/fs.ts +39 -0
- package/src/vendor/zoo-code/src/utils/globalContext.ts +22 -0
- package/src/vendor/zoo-code/src/utils/json-schema.ts +16 -0
- package/src/vendor/zoo-code/src/utils/logging.ts +21 -0
- package/src/vendor/zoo-code/src/utils/mcp-name.ts +190 -0
- package/src/vendor/zoo-code/src/utils/object.ts +18 -0
- package/src/vendor/zoo-code/src/utils/path.ts +94 -0
- package/src/vendor/zoo-code/src/utils/shell.ts +376 -0
- package/src/vendor/zoo-code/src/utils/text-normalization.ts +99 -0
- package/src/vendor/zoo-code/types/global-settings.ts +19 -0
- package/src/vendor/zoo-code/types/index.ts +22 -0
- package/src/vendor/zoo-code/types/message.ts +375 -0
- package/src/vendor/zoo-code/types/mode.ts +241 -0
- package/src/vendor/zoo-code/types/todo.ts +19 -0
- package/src/vendor/zoo-code/types/tool-params.ts +116 -0
- package/src/vendor/zoo-code/types/tool.ts +67 -0
- package/src/vendor/zoo-code/types/vscode.ts +84 -0
- package/src/vision/describe.ts +242 -0
- package/src/vision/tool.ts +91 -0
- package/src/watcher/cli.ts +369 -0
- package/src/watcher/github.ts +304 -0
- package/src/watcher/index.ts +59 -0
- package/src/watcher/state.ts +254 -0
- package/src/watcher/watch.ts +562 -0
- 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
|
+
}
|