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,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Issue-splitting heuristics — deterministic TS port.
|
|
3
|
+
*
|
|
4
|
+
* SOURCE: the `multi-agent-orchestrator` custom mode's Step 2 "Decide the
|
|
5
|
+
* split". The four
|
|
6
|
+
* heuristics are reused unchanged in spirit:
|
|
7
|
+
*
|
|
8
|
+
* 1. Isolate anything touching a hot path alone (request handling,
|
|
9
|
+
* database sessions/migrations, auth, encryption, anything the
|
|
10
|
+
* codebase's own conventions flag as high blast radius) — never
|
|
11
|
+
* bundle a hot-path change with unrelated work, and never put two
|
|
12
|
+
* independent hot-path changes in the same worktree if avoidable.
|
|
13
|
+
* 2. Group same-shape mechanical work together (e.g. multiple "split
|
|
14
|
+
* this file" issues, multiple "add test coverage" issues) — they
|
|
15
|
+
* share a risk profile and review checklist, and batching them
|
|
16
|
+
* means one worker/PR/review cycle instead of several.
|
|
17
|
+
* 3. Never let a worktree exceed roughly a day of sequential work for
|
|
18
|
+
* one agent — if a single issue is enormous, it can be its own
|
|
19
|
+
* worktree; don't pad it with more work.
|
|
20
|
+
* 4. Prefer 2-4 worktrees over 5+ in most cases.
|
|
21
|
+
*
|
|
22
|
+
* This module turns those judgment calls into a small deterministic
|
|
23
|
+
* algorithm so the same decisions can be made headlessly, re-verified in
|
|
24
|
+
* unit tests, and printed by `orchestrate --dry-run` before anything is
|
|
25
|
+
* spawned. It is pure (no fs, no network) and fully unit-testable.
|
|
26
|
+
*
|
|
27
|
+
* Output naming follows the task-file convention:
|
|
28
|
+
* plans/parallel-tasks/<name>-issue<numbers>.md
|
|
29
|
+
* e.g. `w1-issue27.md`, `w2-issue29-36.md`.
|
|
30
|
+
*/
|
|
31
|
+
|
|
32
|
+
/** One open GitHub issue (`gh issue view --json ...` shape). */
|
|
33
|
+
export interface SplitIssue {
|
|
34
|
+
number: number
|
|
35
|
+
title: string
|
|
36
|
+
body?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** A proposed worktree group, one harness worker will own it. */
|
|
40
|
+
export interface WorktreeSpec {
|
|
41
|
+
/** Worktree name, e.g. `w1`. */
|
|
42
|
+
name: string
|
|
43
|
+
/** Issue numbers assigned to this worktree, ascending. */
|
|
44
|
+
issues: number[]
|
|
45
|
+
/** Task file name under plans/parallel-tasks/ (conventional naming). */
|
|
46
|
+
taskFile: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Hot-path keywords from heuristic 1 (matched against title + body). */
|
|
50
|
+
const HOT_PATH_RE = /(request|handler|migration|database|session|auth|encryption|crypto|password|token)/i
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Same-shape mechanical-work patterns from heuristic 2 (matched against the
|
|
54
|
+
* title). The key is the "shape" used to group issues; all issues whose title
|
|
55
|
+
* matches the same pattern share a risk profile + review checklist.
|
|
56
|
+
*/
|
|
57
|
+
const SAME_SHAPE_PATTERNS: Array<{ key: string; re: RegExp }> = [
|
|
58
|
+
// "split" + "decompose" are the same mechanical shape — grouped
|
|
59
|
+
// #29 "Split the 10 multi-class files" with #36 "Decompose remaining 25
|
|
60
|
+
// files" in one worktree (w2-issue29-36.md) for exactly this reason.
|
|
61
|
+
{ key: "split", re: /(split|decompose)/i },
|
|
62
|
+
{ key: "coverage", re: /coverage/i },
|
|
63
|
+
{ key: "test", re: /test/i },
|
|
64
|
+
{ key: "docs", re: /docs/i },
|
|
65
|
+
{ key: "refactor", re: /refactor/i },
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
/** Issues with no recognized mechanical shape land here. */
|
|
69
|
+
const GENERIC_KEY = "generic"
|
|
70
|
+
|
|
71
|
+
/** Max issues in a generic (non-same-shape) group — heuristic 3 (~1 day). */
|
|
72
|
+
export const GENERIC_GROUP_CAP = 2
|
|
73
|
+
|
|
74
|
+
/** Max issues in a same-shape group — mechanical batches can be larger. */
|
|
75
|
+
export const SAME_SHAPE_GROUP_CAP = 3
|
|
76
|
+
|
|
77
|
+
/** Prefer at most this many groups (heuristic 4: 2-4, never 5+). */
|
|
78
|
+
export const MAX_GROUPS = 4
|
|
79
|
+
|
|
80
|
+
function isHotPath(issue: SplitIssue): boolean {
|
|
81
|
+
const haystack = `${issue.title}\n${issue.body ?? ""}`
|
|
82
|
+
return HOT_PATH_RE.test(haystack)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* The mechanical "shape" of an issue (heuristic 2's grouping key): the first
|
|
87
|
+
* same-shape pattern matched against the TITLE (split/coverage/test/docs/
|
|
88
|
+
* refactor), or "generic" when none matches. Pure title-based — the same key
|
|
89
|
+
* used to batch issues into worktrees. Exported because it doubles as the
|
|
90
|
+
* similarity signal for cost estimation (cost-estimate.ts, issue #16): a NEW
|
|
91
|
+
* issue whose shape matches past ones inherits their recorded cost profile.
|
|
92
|
+
*/
|
|
93
|
+
export function shapeOf(issue: SplitIssue): string {
|
|
94
|
+
for (const { key, re } of SAME_SHAPE_PATTERNS) {
|
|
95
|
+
if (re.test(issue.title)) {
|
|
96
|
+
return key
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return GENERIC_KEY
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* The full shape taxonomy used for cost estimation: "hot" when the issue
|
|
104
|
+
* touches a hot path (title OR body — the same scan that isolates hot-path
|
|
105
|
+
* issues into their own worktree), else shapeOf's mechanical shape. A hot
|
|
106
|
+
* issue keeps its hot label even when its title also matches a mechanical
|
|
107
|
+
* pattern: hot-path work has its own risk/cost profile, and split.ts treats
|
|
108
|
+
* it as its own category for isolation purposes.
|
|
109
|
+
*/
|
|
110
|
+
export function issueShape(issue: SplitIssue): string {
|
|
111
|
+
return isHotPath(issue) ? "hot" : shapeOf(issue)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Pre-flight issue-size check (issue #53). The two worst cost/iteration
|
|
116
|
+
* outliers this repo has seen all
|
|
117
|
+
* share one trait: the issue body asks for several substantial, largely
|
|
118
|
+
* independent pieces of work in a single shot. This is the crude, free,
|
|
119
|
+
* deterministic signal for that — the issue text itself explicitly asks for
|
|
120
|
+
* "does the issue body have 3+ top-level numbered/bulleted sections" as an
|
|
121
|
+
* acceptable first cut. Warning-only by design: a crude heuristic can false-
|
|
122
|
+
* positive, so it never aborts a round, only warns loudly before dispatch.
|
|
123
|
+
*/
|
|
124
|
+
|
|
125
|
+
/** Flag an issue whose body reads like at least this many independent pieces. */
|
|
126
|
+
export const ISSUE_SIZE_WARN_THRESHOLD = 3
|
|
127
|
+
|
|
128
|
+
/** An issue flagged by the pre-flight size check, with the detected count. */
|
|
129
|
+
export interface IssueSizeWarning {
|
|
130
|
+
number: number
|
|
131
|
+
title: string
|
|
132
|
+
/** Number of top-level numbered/bulleted sections detected in the body. */
|
|
133
|
+
sections: number
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Count top-level numbered/bulleted list items in a markdown body — the crude
|
|
138
|
+
* "N independent pieces" signal. Top-level = at most 3 leading spaces (the
|
|
139
|
+
* CommonMark container rule); nested list items are indented deeper and are
|
|
140
|
+
* deliberately excluded. Also counts headings carrying an explicit ordinal
|
|
141
|
+
* ("## 1. Client session cards"). Pure line scan: prose without list markers
|
|
142
|
+
* scores 0, so inline numbers ("Python 3.10", "$1.50", "#17") never
|
|
143
|
+
* false-positive.
|
|
144
|
+
*/
|
|
145
|
+
export function topLevelSectionCount(body: string | undefined): number {
|
|
146
|
+
if (!body) {
|
|
147
|
+
return 0
|
|
148
|
+
}
|
|
149
|
+
let count = 0
|
|
150
|
+
for (const line of body.split("\n")) {
|
|
151
|
+
if (
|
|
152
|
+
/^ {0,3}\d{1,3}[.)]\s+\S/.test(line) ||
|
|
153
|
+
/^ {0,3}[-*+]\s+\S/.test(line) ||
|
|
154
|
+
/^#{1,6} {0,3}\d{1,3}[.)]?\s+\S/.test(line)
|
|
155
|
+
) {
|
|
156
|
+
count++
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return count
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Issues whose body reads like `ISSUE_SIZE_WARN_THRESHOLD`+ independent pieces
|
|
164
|
+
* of work — the exact shape that burned iteration caps and budget on real
|
|
165
|
+
* rounds before dispatch-time detection existed.
|
|
166
|
+
*/
|
|
167
|
+
export function issueSizeWarnings(issues: SplitIssue[]): IssueSizeWarning[] {
|
|
168
|
+
const warnings: IssueSizeWarning[] = []
|
|
169
|
+
for (const issue of issues) {
|
|
170
|
+
const sections = topLevelSectionCount(issue.body)
|
|
171
|
+
if (sections >= ISSUE_SIZE_WARN_THRESHOLD) {
|
|
172
|
+
warnings.push({ number: issue.number, title: issue.title, sections })
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return warnings
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function capFor(shape: string): number {
|
|
179
|
+
return shape === GENERIC_KEY ? GENERIC_GROUP_CAP : SAME_SHAPE_GROUP_CAP
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/** Internal group shape used while deciding; not part of the public spec. */
|
|
183
|
+
interface InternalGroup {
|
|
184
|
+
shape: string
|
|
185
|
+
issues: number[]
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Sort issue-number lists ascending (stable, deterministic). */
|
|
189
|
+
function sortedIssues(numbers: number[]): number[] {
|
|
190
|
+
return [...numbers].sort((a, b) => a - b)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function minIssue(group: InternalGroup): number {
|
|
194
|
+
return group.issues.length > 0 ? Math.min(...group.issues) : Number.MAX_SAFE_INTEGER
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Split issues into worktree groups using the orchestration heuristics.
|
|
199
|
+
*
|
|
200
|
+
* - Every hot-path issue is isolated alone (heuristic 1).
|
|
201
|
+
* - Non-hot-path issues are batched by mechanical shape, capped at
|
|
202
|
+
* `capFor(shape)` issues per group (heuristics 2 + 3).
|
|
203
|
+
* - If that yields more than `MAX_GROUPS` groups, the smallest groups are
|
|
204
|
+
* merged (same-shape pairs first, then the smallest remaining) until the
|
|
205
|
+
* round fits in 2-4 worktrees (heuristic 4).
|
|
206
|
+
*
|
|
207
|
+
* @param issues issues to split (order-independent; output is sorted)
|
|
208
|
+
* @param opts.occupiedNames worktree names already in use in the TARGET repo
|
|
209
|
+
* (e.g. a still-running round's `.worktrees/w1`) — naming skips over
|
|
210
|
+
* these instead of colliding, so a second `orchestrate` invocation
|
|
211
|
+
* against the same repo can run alongside an in-flight round instead
|
|
212
|
+
* of failing the pre-spawn stale-worktree check. See cli.ts, which
|
|
213
|
+
* populates this from the repo's actual `.worktrees/` listing.
|
|
214
|
+
* @returns worktree specs in ascending min-issue order, named around the
|
|
215
|
+
* first free wN slots with convention-named task files
|
|
216
|
+
*/
|
|
217
|
+
export function splitIssues(
|
|
218
|
+
issues: SplitIssue[],
|
|
219
|
+
opts: { occupiedNames?: ReadonlySet<string> } = {},
|
|
220
|
+
): WorktreeSpec[] {
|
|
221
|
+
if (issues.length === 0) {
|
|
222
|
+
return []
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const hot: SplitIssue[] = []
|
|
226
|
+
const rest: SplitIssue[] = []
|
|
227
|
+
for (const issue of issues) {
|
|
228
|
+
;(isHotPath(issue) ? hot : rest).push(issue)
|
|
229
|
+
}
|
|
230
|
+
hot.sort((a, b) => a.number - b.number)
|
|
231
|
+
rest.sort((a, b) => a.number - b.number)
|
|
232
|
+
|
|
233
|
+
// Heuristic 1: each hot-path issue alone.
|
|
234
|
+
const groups: InternalGroup[] = hot.map((issue) => ({ shape: "hot", issues: [issue.number] }))
|
|
235
|
+
|
|
236
|
+
// Heuristics 2 + 3: batch same-shape work, cap non-same-shape at ~2.
|
|
237
|
+
const buckets = new Map<string, InternalGroup[]>()
|
|
238
|
+
for (const issue of rest) {
|
|
239
|
+
const shape = shapeOf(issue)
|
|
240
|
+
const list = buckets.get(shape) ?? []
|
|
241
|
+
let bucket = list[list.length - 1]
|
|
242
|
+
if (!bucket || bucket.issues.length >= capFor(shape)) {
|
|
243
|
+
bucket = { shape, issues: [] }
|
|
244
|
+
list.push(bucket)
|
|
245
|
+
buckets.set(shape, list)
|
|
246
|
+
}
|
|
247
|
+
bucket.issues.push(issue.number)
|
|
248
|
+
}
|
|
249
|
+
for (const list of buckets.values()) {
|
|
250
|
+
groups.push(...list)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Deterministic group ordering by smallest issue number.
|
|
254
|
+
groups.sort((a, b) => minIssue(a) - minIssue(b))
|
|
255
|
+
|
|
256
|
+
// Heuristic 4: never exceed MAX_GROUPS. Merge smallest groups, preferring
|
|
257
|
+
// same-shape pairs so merged work still shares one review checklist.
|
|
258
|
+
while (groups.length > MAX_GROUPS) {
|
|
259
|
+
groups.sort((a, b) => {
|
|
260
|
+
const bySize = a.issues.length - b.issues.length
|
|
261
|
+
if (bySize !== 0) {
|
|
262
|
+
return bySize
|
|
263
|
+
}
|
|
264
|
+
return minIssue(a) - minIssue(b)
|
|
265
|
+
})
|
|
266
|
+
const smallest = groups[0]
|
|
267
|
+
const sameShape = groups
|
|
268
|
+
.slice(1)
|
|
269
|
+
.find((g) => g.shape === smallest.shape && g.shape !== "hot")
|
|
270
|
+
const target = sameShape ?? groups[1]
|
|
271
|
+
target.issues = sortedIssues([...target.issues, ...smallest.issues])
|
|
272
|
+
groups.splice(0, 1)
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// Final sort by min issue number, then assign names + task files. Skips
|
|
276
|
+
// over any name already reserved via opts.occupiedNames (a still-running
|
|
277
|
+
// round in the same repo) — reserves each assigned name as it goes so two
|
|
278
|
+
// groups from THIS call never double-book the same slot either.
|
|
279
|
+
groups.sort((a, b) => minIssue(a) - minIssue(b))
|
|
280
|
+
const reserved = new Set(opts.occupiedNames ?? [])
|
|
281
|
+
let nextSlot = 1
|
|
282
|
+
return groups.map((group) => {
|
|
283
|
+
while (reserved.has(`w${nextSlot}`)) {
|
|
284
|
+
nextSlot++
|
|
285
|
+
}
|
|
286
|
+
const name = `w${nextSlot}`
|
|
287
|
+
reserved.add(name)
|
|
288
|
+
nextSlot++
|
|
289
|
+
const issues = sortedIssues(group.issues)
|
|
290
|
+
return {
|
|
291
|
+
name,
|
|
292
|
+
issues,
|
|
293
|
+
taskFile: `${name}-issue${issues.join("-")}.md`,
|
|
294
|
+
}
|
|
295
|
+
})
|
|
296
|
+
}
|