@gethmy/agent 1.10.2 → 1.10.4
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/README.md +1 -1
- package/dist/cli.js +349 -166
- package/dist/index.js +350 -167
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -397,8 +397,17 @@ var init_types = __esm(() => {
|
|
|
397
397
|
},
|
|
398
398
|
claude: {
|
|
399
399
|
model: "opus",
|
|
400
|
+
escalateModel: "claude-fable-5",
|
|
401
|
+
escalateAfterAttempts: 2,
|
|
402
|
+
tiers: {
|
|
403
|
+
simple: "claude-haiku-4-5",
|
|
404
|
+
advanced: "claude-sonnet-4-6",
|
|
405
|
+
research: "claude-opus-4-8"
|
|
406
|
+
},
|
|
400
407
|
reviewModel: "sonnet",
|
|
401
|
-
maxTurns:
|
|
408
|
+
maxTurns: 80,
|
|
409
|
+
reviewMaxTurns: 60,
|
|
410
|
+
leanSettingSources: "local,user",
|
|
402
411
|
additionalArgs: []
|
|
403
412
|
},
|
|
404
413
|
worktree: {
|
|
@@ -2007,6 +2016,54 @@ function parseNumstat(raw, maxFiles = MAX_CHANGED_FILES2) {
|
|
|
2007
2016
|
}
|
|
2008
2017
|
return { files, insertions, deletions };
|
|
2009
2018
|
}
|
|
2019
|
+
function summarizeUnifiedDiff(diff) {
|
|
2020
|
+
const files = [];
|
|
2021
|
+
let current = null;
|
|
2022
|
+
let totalAdded = 0;
|
|
2023
|
+
let totalRemoved = 0;
|
|
2024
|
+
for (const line of diff.split(`
|
|
2025
|
+
`)) {
|
|
2026
|
+
if (line.startsWith("diff --git")) {
|
|
2027
|
+
const m = line.match(/ b\/(.+)$/);
|
|
2028
|
+
current = {
|
|
2029
|
+
path: m ? m[1] : line.slice("diff --git ".length),
|
|
2030
|
+
added: 0,
|
|
2031
|
+
removed: 0
|
|
2032
|
+
};
|
|
2033
|
+
files.push(current);
|
|
2034
|
+
continue;
|
|
2035
|
+
}
|
|
2036
|
+
if (!current)
|
|
2037
|
+
continue;
|
|
2038
|
+
if (line.startsWith("+++") || line.startsWith("---"))
|
|
2039
|
+
continue;
|
|
2040
|
+
if (line.startsWith("+")) {
|
|
2041
|
+
current.added++;
|
|
2042
|
+
totalAdded++;
|
|
2043
|
+
} else if (line.startsWith("-")) {
|
|
2044
|
+
current.removed++;
|
|
2045
|
+
totalRemoved++;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
return { files, totalAdded, totalRemoved };
|
|
2049
|
+
}
|
|
2050
|
+
function formatDiffSummary(diff, maxFiles = 100) {
|
|
2051
|
+
const trimmed = diff.trim();
|
|
2052
|
+
if (!trimmed || diff === "(unable to retrieve diff)") {
|
|
2053
|
+
return trimmed ? diff : "(no diff available)";
|
|
2054
|
+
}
|
|
2055
|
+
const { files, totalAdded, totalRemoved } = summarizeUnifiedDiff(diff);
|
|
2056
|
+
if (files.length === 0)
|
|
2057
|
+
return "(no file changes detected in diff)";
|
|
2058
|
+
const shown = files.slice(0, maxFiles);
|
|
2059
|
+
const lines = shown.map((f) => ` ${f.path} | +${f.added} -${f.removed}`);
|
|
2060
|
+
if (files.length > maxFiles) {
|
|
2061
|
+
lines.push(` ... and ${files.length - maxFiles} more file(s)`);
|
|
2062
|
+
}
|
|
2063
|
+
lines.push(` ${files.length} file(s) changed, ${totalAdded} insertion(s)(+), ${totalRemoved} deletion(s)(-)`);
|
|
2064
|
+
return lines.join(`
|
|
2065
|
+
`);
|
|
2066
|
+
}
|
|
2010
2067
|
function captureDiffStat(worktreePath, baseBranch, maxFiles = MAX_CHANGED_FILES2) {
|
|
2011
2068
|
try {
|
|
2012
2069
|
const raw = execFileSync5("git", ["diff", "--numstat", `${baseBranch}...HEAD`], { cwd: worktreePath, encoding: "utf-8", timeout: 30000 });
|
|
@@ -2123,7 +2180,17 @@ async function runDeepReview(worktreePath, config, workerId) {
|
|
|
2123
2180
|
"```"
|
|
2124
2181
|
].join(`
|
|
2125
2182
|
`);
|
|
2126
|
-
const
|
|
2183
|
+
const leanSources = config.claude.leanSettingSources;
|
|
2184
|
+
const output = execFileSync6("claude", [
|
|
2185
|
+
"--print",
|
|
2186
|
+
"--model",
|
|
2187
|
+
"sonnet",
|
|
2188
|
+
"--max-turns",
|
|
2189
|
+
"10",
|
|
2190
|
+
...leanSources ? ["--setting-sources", leanSources] : [],
|
|
2191
|
+
"--",
|
|
2192
|
+
reviewPrompt
|
|
2193
|
+
], {
|
|
2127
2194
|
cwd: worktreePath,
|
|
2128
2195
|
encoding: "utf-8",
|
|
2129
2196
|
timeout: config.verification.timeout,
|
|
@@ -2154,6 +2221,7 @@ function attemptAutoFix(worktreePath, config, errors) {
|
|
|
2154
2221
|
"```"
|
|
2155
2222
|
].join(`
|
|
2156
2223
|
`);
|
|
2224
|
+
const leanSources = config.claude.leanSettingSources;
|
|
2157
2225
|
const args = [
|
|
2158
2226
|
"--print",
|
|
2159
2227
|
"--model",
|
|
@@ -2162,6 +2230,7 @@ function attemptAutoFix(worktreePath, config, errors) {
|
|
|
2162
2230
|
"50",
|
|
2163
2231
|
"--allowedTools",
|
|
2164
2232
|
"Bash,Read,Write,Edit,Glob,Grep",
|
|
2233
|
+
...leanSources ? ["--setting-sources", leanSources] : [],
|
|
2165
2234
|
"--",
|
|
2166
2235
|
fixPrompt
|
|
2167
2236
|
];
|
|
@@ -3368,7 +3437,133 @@ var init_review_completion = __esm(() => {
|
|
|
3368
3437
|
init_worktree();
|
|
3369
3438
|
});
|
|
3370
3439
|
|
|
3371
|
-
//
|
|
3440
|
+
// ../harmony-shared/dist/cardLinks.js
|
|
3441
|
+
var init_cardLinks = () => {};
|
|
3442
|
+
// ../harmony-shared/dist/classification.js
|
|
3443
|
+
function escalateTier(tier) {
|
|
3444
|
+
const i = MODEL_TIERS.indexOf(tier);
|
|
3445
|
+
return MODEL_TIERS[Math.min(i + 1, MODEL_TIERS.length - 1)];
|
|
3446
|
+
}
|
|
3447
|
+
function isModelTier(v) {
|
|
3448
|
+
return typeof v === "string" && MODEL_TIERS.includes(v);
|
|
3449
|
+
}
|
|
3450
|
+
var MODEL_TIERS;
|
|
3451
|
+
var init_classification = __esm(() => {
|
|
3452
|
+
MODEL_TIERS = ["simple", "advanced", "research"];
|
|
3453
|
+
});
|
|
3454
|
+
|
|
3455
|
+
// ../harmony-shared/dist/commentSerializer.js
|
|
3456
|
+
function sanitizeHeaderField(value) {
|
|
3457
|
+
return value.replace(/[\]\r\n|<>]/g, " ").trim() || "—";
|
|
3458
|
+
}
|
|
3459
|
+
function authorLabel(c) {
|
|
3460
|
+
if (c.author_type === "agent")
|
|
3461
|
+
return "AI agent";
|
|
3462
|
+
const raw = c.author?.full_name || "teammate";
|
|
3463
|
+
return sanitizeHeaderField(raw);
|
|
3464
|
+
}
|
|
3465
|
+
function criticalIds(comments) {
|
|
3466
|
+
const keep = new Set;
|
|
3467
|
+
for (const c of comments) {
|
|
3468
|
+
if (c.comment_type === "decision")
|
|
3469
|
+
keep.add(c.id);
|
|
3470
|
+
if (c.supersedes_id) {
|
|
3471
|
+
keep.add(c.id);
|
|
3472
|
+
keep.add(c.supersedes_id);
|
|
3473
|
+
}
|
|
3474
|
+
if (c.confirms_id) {
|
|
3475
|
+
keep.add(c.id);
|
|
3476
|
+
keep.add(c.confirms_id);
|
|
3477
|
+
}
|
|
3478
|
+
}
|
|
3479
|
+
return keep;
|
|
3480
|
+
}
|
|
3481
|
+
function serializeCommentThread(comments, options = {}) {
|
|
3482
|
+
const { heading = "Conversation", includeInstructions = true, activity = [], maxComments } = options;
|
|
3483
|
+
const visible = comments.filter((c) => !c.deleted_at).slice().sort((a, b) => a.created_at.localeCompare(b.created_at));
|
|
3484
|
+
if (visible.length === 0)
|
|
3485
|
+
return "";
|
|
3486
|
+
const indexById = new Map;
|
|
3487
|
+
visible.forEach((c, i) => {
|
|
3488
|
+
indexById.set(c.id, i + 1);
|
|
3489
|
+
});
|
|
3490
|
+
let rendered = visible;
|
|
3491
|
+
let elidedCount = 0;
|
|
3492
|
+
if (maxComments && visible.length > maxComments) {
|
|
3493
|
+
const keep = criticalIds(visible);
|
|
3494
|
+
const recentThreshold = visible.length - maxComments;
|
|
3495
|
+
rendered = visible.filter((c, i) => i >= recentThreshold || keep.has(c.id));
|
|
3496
|
+
elidedCount = visible.length - rendered.length;
|
|
3497
|
+
}
|
|
3498
|
+
const ref = (id) => {
|
|
3499
|
+
const n = indexById.get(id);
|
|
3500
|
+
return n ? `#${n}` : `#${id.slice(0, 8)}`;
|
|
3501
|
+
};
|
|
3502
|
+
const lines = [];
|
|
3503
|
+
if (elidedCount > 0) {
|
|
3504
|
+
lines.push({
|
|
3505
|
+
at: visible[0]?.created_at ?? "",
|
|
3506
|
+
text: `(${elidedCount} earlier comment(s) omitted for brevity)`
|
|
3507
|
+
});
|
|
3508
|
+
}
|
|
3509
|
+
for (const c of rendered) {
|
|
3510
|
+
const tags = [];
|
|
3511
|
+
if (c.edited_at)
|
|
3512
|
+
tags.push("edited");
|
|
3513
|
+
if (c.supersedes_id)
|
|
3514
|
+
tags.push(`supersedes ${ref(c.supersedes_id)}`);
|
|
3515
|
+
if (c.confirms_id)
|
|
3516
|
+
tags.push(`confirms ${ref(c.confirms_id)}`);
|
|
3517
|
+
if (c.resolved_at)
|
|
3518
|
+
tags.push("resolved");
|
|
3519
|
+
const tagStr = tags.length ? ` | ${tags.join(" | ")}` : "";
|
|
3520
|
+
const header = `[${sanitizeHeaderField(ref(c.id))} | ${sanitizeHeaderField(c.author_type)} | ${authorLabel(c)} | ${sanitizeHeaderField(c.comment_type)} | ${sanitizeHeaderField(c.created_at)}${tagStr}]`;
|
|
3521
|
+
const fencedBody = c.body.trim().replaceAll("<", "<").replaceAll(">", ">");
|
|
3522
|
+
lines.push({
|
|
3523
|
+
at: c.created_at,
|
|
3524
|
+
text: `${header}
|
|
3525
|
+
<comment-body>
|
|
3526
|
+
${fencedBody}
|
|
3527
|
+
</comment-body>`
|
|
3528
|
+
});
|
|
3529
|
+
}
|
|
3530
|
+
for (const a of activity) {
|
|
3531
|
+
const actor = a.actor ? `${a.actor} ` : "";
|
|
3532
|
+
lines.push({ at: a.at, text: `· (system) ${a.at} — ${actor}${a.text}` });
|
|
3533
|
+
}
|
|
3534
|
+
lines.sort((a, b) => a.at.localeCompare(b.at));
|
|
3535
|
+
const body = lines.map((l) => l.text).join(`
|
|
3536
|
+
|
|
3537
|
+
`);
|
|
3538
|
+
const instruction = includeInstructions ? `
|
|
3539
|
+
|
|
3540
|
+
${CONFLICT_INSTRUCTION}` : "";
|
|
3541
|
+
return `## ${heading} (oldest → newest)
|
|
3542
|
+
|
|
3543
|
+
${body}${instruction}`;
|
|
3544
|
+
}
|
|
3545
|
+
var CONFLICT_INSTRUCTION;
|
|
3546
|
+
var init_commentSerializer = __esm(() => {
|
|
3547
|
+
CONFLICT_INSTRUCTION = "When two comments conflict, prefer the latest created_at, UNLESS a later " + "comment explicitly confirms or restates the earlier finding. Evaluate " + "substance, not just recency. Cite the comment id(s) you relied on.";
|
|
3548
|
+
});
|
|
3549
|
+
|
|
3550
|
+
// ../harmony-shared/dist/constants.js
|
|
3551
|
+
var TIMINGS;
|
|
3552
|
+
var init_constants = __esm(() => {
|
|
3553
|
+
TIMINGS = {
|
|
3554
|
+
SEARCH_DEBOUNCE: 300,
|
|
3555
|
+
AUTOSAVE_DEBOUNCE: 1000,
|
|
3556
|
+
TOAST_DURATION: 3000,
|
|
3557
|
+
QUERY_STALE_TIME: 1000 * 60 * 5,
|
|
3558
|
+
QUERY_GC_TIME: 1000 * 60 * 60 * 24
|
|
3559
|
+
};
|
|
3560
|
+
});
|
|
3561
|
+
// ../harmony-shared/dist/logger.js
|
|
3562
|
+
var init_logger = () => {};
|
|
3563
|
+
// ../harmony-shared/dist/projectTemplates.js
|
|
3564
|
+
var init_projectTemplates = () => {};
|
|
3565
|
+
|
|
3566
|
+
// ../harmony-shared/dist/reviewMethodology.js
|
|
3372
3567
|
var REVIEW_SYSTEM_PROMPT = `You are a senior code reviewer. Follow this two-pass methodology strictly.
|
|
3373
3568
|
Report findings; do NOT fix them. This is a read-only review.
|
|
3374
3569
|
|
|
@@ -3444,7 +3639,42 @@ For each page affected by the changes:
|
|
|
3444
3639
|
- Use snapshot for navigation — client-side routes may not appear in link lists.
|
|
3445
3640
|
- Check for stale state: navigate away and back — does data refresh correctly?
|
|
3446
3641
|
- Test browser back/forward — does the app handle history correctly?
|
|
3447
|
-
- Watch for hydration errors or layout shifts after dynamic content loads
|
|
3642
|
+
- Watch for hydration errors or layout shifts after dynamic content loads.`, REVIEW_VERDICT_SCHEMA = `{
|
|
3643
|
+
"verdict": "approved" | "rejected",
|
|
3644
|
+
"summary": "Brief overall assessment",
|
|
3645
|
+
"scopeCheck": {
|
|
3646
|
+
"status": "clean" | "drift" | "missing",
|
|
3647
|
+
"notes": "Optional explanation of scope issues"
|
|
3648
|
+
},
|
|
3649
|
+
"findings": [
|
|
3650
|
+
{
|
|
3651
|
+
"severity": "critical" | "major" | "minor",
|
|
3652
|
+
"category": "sql-safety | race-condition | llm-trust | enum-completeness | visual | functional | ux | console | scope | other",
|
|
3653
|
+
"title": "Short title",
|
|
3654
|
+
"description": "Detailed description of the issue",
|
|
3655
|
+
"location": "file:line (if applicable)"
|
|
3656
|
+
}
|
|
3657
|
+
]
|
|
3658
|
+
}`, REVIEW_DECISION_RULES = `- **rejected**: Any \`critical\` finding, unaddressed requirements, or 2+ \`major\` findings.
|
|
3659
|
+
- **approved**: No critical findings, at most 1 major finding with minor findings OK.`;
|
|
3660
|
+
// ../harmony-shared/dist/types.js
|
|
3661
|
+
var init_types2 = () => {};
|
|
3662
|
+
|
|
3663
|
+
// ../harmony-shared/dist/index.js
|
|
3664
|
+
var init_dist = __esm(() => {
|
|
3665
|
+
init_cardLinks();
|
|
3666
|
+
init_classification();
|
|
3667
|
+
init_commentSerializer();
|
|
3668
|
+
init_constants();
|
|
3669
|
+
init_logger();
|
|
3670
|
+
init_projectTemplates();
|
|
3671
|
+
init_types2();
|
|
3672
|
+
});
|
|
3673
|
+
|
|
3674
|
+
// src/review-knowledge.ts
|
|
3675
|
+
var init_review_knowledge = __esm(() => {
|
|
3676
|
+
init_dist();
|
|
3677
|
+
});
|
|
3448
3678
|
|
|
3449
3679
|
// src/review-prompt.ts
|
|
3450
3680
|
function buildReviewSystemPrompt() {
|
|
@@ -3455,15 +3685,13 @@ ${REVIEW_SYSTEM_PROMPT}
|
|
|
3455
3685
|
|
|
3456
3686
|
${QA_VISUAL_CHECKLIST}`;
|
|
3457
3687
|
}
|
|
3458
|
-
function buildReviewUserPrompt(enriched, branchName, worktreePath, previewUrl,
|
|
3688
|
+
function buildReviewUserPrompt(enriched, branchName, worktreePath, previewUrl, diffSummary, baseBranch) {
|
|
3459
3689
|
const { card, labels, subtasks } = enriched;
|
|
3460
3690
|
const labelStr = labels.length > 0 ? labels.map((l) => l.name).join(", ") : "none";
|
|
3461
3691
|
const subtaskStr = subtasks.length > 0 ? subtasks.map((s) => `- [${s.completed ? "x" : " "}] ${s.title}`).join(`
|
|
3462
3692
|
`) : "No subtasks defined.";
|
|
3463
3693
|
const description = card.description?.trim() || "No description provided.";
|
|
3464
|
-
const
|
|
3465
|
-
|
|
3466
|
-
... (diff truncated at 80K characters)` : diff;
|
|
3694
|
+
const diffRange = branchName ? `origin/${baseBranch}..HEAD` : "HEAD";
|
|
3467
3695
|
const branchLine = branchName ? `**Branch**: ${branchName}` : `**Mode**: Local review (no branch — reviewing working tree changes)`;
|
|
3468
3696
|
return `## Card: #${card.short_id} - ${card.title}
|
|
3469
3697
|
**Labels**: ${labelStr}
|
|
@@ -3475,10 +3703,15 @@ ${description}
|
|
|
3475
3703
|
## Subtasks (Acceptance Criteria)
|
|
3476
3704
|
${subtaskStr}
|
|
3477
3705
|
|
|
3478
|
-
##
|
|
3479
|
-
\`\`\`diff
|
|
3480
|
-
${truncatedDiff}
|
|
3706
|
+
## Changed Files (git diff --stat ${diffRange})
|
|
3481
3707
|
\`\`\`
|
|
3708
|
+
${diffSummary}
|
|
3709
|
+
\`\`\`
|
|
3710
|
+
|
|
3711
|
+
The full diff is intentionally NOT inlined here. Inspect the changes yourself —
|
|
3712
|
+
you have Read, Grep, Glob, and read-only Bash:
|
|
3713
|
+
- Run \`git diff ${diffRange}\` (or \`git diff ${diffRange} -- <file>\`) for the full hunks.
|
|
3714
|
+
- Read / Grep the changed files above and trace new values through their consumers.
|
|
3482
3715
|
|
|
3483
3716
|
## Review Steps
|
|
3484
3717
|
|
|
@@ -3509,33 +3742,18 @@ Use the \`/browse\` skill to navigate to ${previewUrl} and apply the visual QA c
|
|
|
3509
3742
|
After completing all steps, output EXACTLY one JSON block (and nothing else after it):
|
|
3510
3743
|
|
|
3511
3744
|
\`\`\`json
|
|
3512
|
-
{
|
|
3513
|
-
"verdict": "approved" | "rejected",
|
|
3514
|
-
"summary": "Brief overall assessment",
|
|
3515
|
-
"scopeCheck": {
|
|
3516
|
-
"status": "clean" | "drift" | "missing",
|
|
3517
|
-
"notes": "Optional explanation of scope issues"
|
|
3518
|
-
},
|
|
3519
|
-
"findings": [
|
|
3520
|
-
{
|
|
3521
|
-
"severity": "critical" | "major" | "minor",
|
|
3522
|
-
"category": "sql-safety | race-condition | llm-trust | enum-completeness | visual | functional | ux | console | scope | other",
|
|
3523
|
-
"title": "Short title",
|
|
3524
|
-
"description": "Detailed description of the issue",
|
|
3525
|
-
"location": "file:line (if applicable)"
|
|
3526
|
-
}
|
|
3527
|
-
]
|
|
3528
|
-
}
|
|
3745
|
+
${REVIEW_VERDICT_SCHEMA}
|
|
3529
3746
|
\`\`\`
|
|
3530
3747
|
|
|
3531
3748
|
**Decision rules:**
|
|
3532
|
-
|
|
3533
|
-
- **approved**: No critical findings, at most 1 major finding with minor findings OK.
|
|
3749
|
+
${REVIEW_DECISION_RULES}
|
|
3534
3750
|
|
|
3535
3751
|
**Do NOT modify any code.** This is a read-only review.
|
|
3536
3752
|
${branchName ? `You are reviewing code in a git worktree at \`${worktreePath}\` on branch \`${branchName}\`.` : `You are reviewing local changes in the repository at \`${worktreePath}\`.`}`;
|
|
3537
3753
|
}
|
|
3538
|
-
var init_review_prompt = () => {
|
|
3754
|
+
var init_review_prompt = __esm(() => {
|
|
3755
|
+
init_review_knowledge();
|
|
3756
|
+
});
|
|
3539
3757
|
|
|
3540
3758
|
// src/run-log.ts
|
|
3541
3759
|
import { createWriteStream, mkdirSync } from "node:fs";
|
|
@@ -4239,6 +4457,7 @@ class ReviewWorker {
|
|
|
4239
4457
|
} catch {
|
|
4240
4458
|
diff = "(unable to retrieve diff)";
|
|
4241
4459
|
}
|
|
4460
|
+
const diffSummary = formatDiffSummary(diff);
|
|
4242
4461
|
const previewUrl = `http://localhost:${port}`;
|
|
4243
4462
|
const enriched = {
|
|
4244
4463
|
card,
|
|
@@ -4248,7 +4467,7 @@ class ReviewWorker {
|
|
|
4248
4467
|
mode: "review"
|
|
4249
4468
|
};
|
|
4250
4469
|
const systemPrompt = buildReviewSystemPrompt();
|
|
4251
|
-
const userPrompt = buildReviewUserPrompt(enriched, this.branchName, cwd, previewUrl,
|
|
4470
|
+
const userPrompt = buildReviewUserPrompt(enriched, this.branchName, cwd, previewUrl, diffSummary, this.config.worktree.baseBranch);
|
|
4252
4471
|
try {
|
|
4253
4472
|
await this.client.recordPromptHistory({
|
|
4254
4473
|
cardId: card.id,
|
|
@@ -4425,6 +4644,7 @@ class ReviewWorker {
|
|
|
4425
4644
|
}
|
|
4426
4645
|
spawnClaude(prompt, systemPrompt, tracker, shortId) {
|
|
4427
4646
|
return new Promise((resolve3, reject) => {
|
|
4647
|
+
const leanSources = this.config.claude.leanSettingSources;
|
|
4428
4648
|
const args = [
|
|
4429
4649
|
"--output-format",
|
|
4430
4650
|
"stream-json",
|
|
@@ -4432,9 +4652,10 @@ class ReviewWorker {
|
|
|
4432
4652
|
"--model",
|
|
4433
4653
|
this.config.claude.reviewModel,
|
|
4434
4654
|
"--max-turns",
|
|
4435
|
-
String(this.config.claude.
|
|
4655
|
+
String(this.config.claude.reviewMaxTurns),
|
|
4436
4656
|
"--allowedTools",
|
|
4437
4657
|
"Bash(readonly),Read,Glob,Grep,Agent,mcp__harmony__*",
|
|
4658
|
+
...leanSources ? ["--setting-sources", leanSources] : [],
|
|
4438
4659
|
...systemPrompt ? ["--append-system-prompt", systemPrompt] : [],
|
|
4439
4660
|
...this.config.claude.additionalArgs,
|
|
4440
4661
|
"--",
|
|
@@ -4580,6 +4801,7 @@ var TAG19 = "review-worker", CANCEL_SIGINT_TIMEOUT = 30000, CANCEL_SIGTERM_TIMEO
|
|
|
4580
4801
|
var init_review_worker = __esm(() => {
|
|
4581
4802
|
init_board_helpers();
|
|
4582
4803
|
init_completion();
|
|
4804
|
+
init_git_diff_stat();
|
|
4583
4805
|
init_log();
|
|
4584
4806
|
init_process_group();
|
|
4585
4807
|
init_progress_tracker();
|
|
@@ -4726,129 +4948,31 @@ var init_unblock = __esm(() => {
|
|
|
4726
4948
|
init_log();
|
|
4727
4949
|
});
|
|
4728
4950
|
|
|
4729
|
-
//
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
return value.replace(/[\]\r\n|<>]/g, " ").trim() || "—";
|
|
4734
|
-
}
|
|
4735
|
-
function authorLabel(c) {
|
|
4736
|
-
if (c.author_type === "agent")
|
|
4737
|
-
return "AI agent";
|
|
4738
|
-
const raw = c.author?.full_name || "teammate";
|
|
4739
|
-
return sanitizeHeaderField(raw);
|
|
4740
|
-
}
|
|
4741
|
-
function criticalIds(comments) {
|
|
4742
|
-
const keep = new Set;
|
|
4743
|
-
for (const c of comments) {
|
|
4744
|
-
if (c.comment_type === "decision")
|
|
4745
|
-
keep.add(c.id);
|
|
4746
|
-
if (c.supersedes_id) {
|
|
4747
|
-
keep.add(c.id);
|
|
4748
|
-
keep.add(c.supersedes_id);
|
|
4749
|
-
}
|
|
4750
|
-
if (c.confirms_id) {
|
|
4751
|
-
keep.add(c.id);
|
|
4752
|
-
keep.add(c.confirms_id);
|
|
4753
|
-
}
|
|
4951
|
+
// src/model-tier.ts
|
|
4952
|
+
function chooseImplementModel(claude, card, attempts) {
|
|
4953
|
+
if (card.model_override) {
|
|
4954
|
+
return { model: card.model_override, escalated: false, source: "override" };
|
|
4754
4955
|
}
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
indexById.set(c.id, i + 1);
|
|
4765
|
-
});
|
|
4766
|
-
let rendered = visible;
|
|
4767
|
-
let elidedCount = 0;
|
|
4768
|
-
if (maxComments && visible.length > maxComments) {
|
|
4769
|
-
const keep = criticalIds(visible);
|
|
4770
|
-
const recentThreshold = visible.length - maxComments;
|
|
4771
|
-
rendered = visible.filter((c, i) => i >= recentThreshold || keep.has(c.id));
|
|
4772
|
-
elidedCount = visible.length - rendered.length;
|
|
4956
|
+
if (isModelTier(card.model_tier)) {
|
|
4957
|
+
const retry = attempts >= claude.escalateAfterAttempts;
|
|
4958
|
+
const tier = retry ? escalateTier(card.model_tier) : card.model_tier;
|
|
4959
|
+
const mapped = claude.tiers?.[tier];
|
|
4960
|
+
return {
|
|
4961
|
+
model: mapped && mapped.length > 0 ? mapped : claude.model,
|
|
4962
|
+
escalated: retry,
|
|
4963
|
+
source: "tier"
|
|
4964
|
+
};
|
|
4773
4965
|
}
|
|
4774
|
-
const
|
|
4775
|
-
|
|
4776
|
-
|
|
4966
|
+
const highPriority = card.priority === "high" || card.priority === "urgent";
|
|
4967
|
+
const escalated = highPriority || attempts >= claude.escalateAfterAttempts;
|
|
4968
|
+
return {
|
|
4969
|
+
model: escalated ? claude.escalateModel : claude.model,
|
|
4970
|
+
escalated,
|
|
4971
|
+
source: "policy"
|
|
4777
4972
|
};
|
|
4778
|
-
const lines = [];
|
|
4779
|
-
if (elidedCount > 0) {
|
|
4780
|
-
lines.push({
|
|
4781
|
-
at: visible[0]?.created_at ?? "",
|
|
4782
|
-
text: `(${elidedCount} earlier comment(s) omitted for brevity)`
|
|
4783
|
-
});
|
|
4784
|
-
}
|
|
4785
|
-
for (const c of rendered) {
|
|
4786
|
-
const tags = [];
|
|
4787
|
-
if (c.edited_at)
|
|
4788
|
-
tags.push("edited");
|
|
4789
|
-
if (c.supersedes_id)
|
|
4790
|
-
tags.push(`supersedes ${ref(c.supersedes_id)}`);
|
|
4791
|
-
if (c.confirms_id)
|
|
4792
|
-
tags.push(`confirms ${ref(c.confirms_id)}`);
|
|
4793
|
-
if (c.resolved_at)
|
|
4794
|
-
tags.push("resolved");
|
|
4795
|
-
const tagStr = tags.length ? ` | ${tags.join(" | ")}` : "";
|
|
4796
|
-
const header = `[${sanitizeHeaderField(ref(c.id))} | ${sanitizeHeaderField(c.author_type)} | ${authorLabel(c)} | ${sanitizeHeaderField(c.comment_type)} | ${sanitizeHeaderField(c.created_at)}${tagStr}]`;
|
|
4797
|
-
const fencedBody = c.body.trim().replaceAll("<", "<").replaceAll(">", ">");
|
|
4798
|
-
lines.push({
|
|
4799
|
-
at: c.created_at,
|
|
4800
|
-
text: `${header}
|
|
4801
|
-
<comment-body>
|
|
4802
|
-
${fencedBody}
|
|
4803
|
-
</comment-body>`
|
|
4804
|
-
});
|
|
4805
|
-
}
|
|
4806
|
-
for (const a of activity) {
|
|
4807
|
-
const actor = a.actor ? `${a.actor} ` : "";
|
|
4808
|
-
lines.push({ at: a.at, text: `· (system) ${a.at} — ${actor}${a.text}` });
|
|
4809
|
-
}
|
|
4810
|
-
lines.sort((a, b) => a.at.localeCompare(b.at));
|
|
4811
|
-
const body = lines.map((l) => l.text).join(`
|
|
4812
|
-
|
|
4813
|
-
`);
|
|
4814
|
-
const instruction = includeInstructions ? `
|
|
4815
|
-
|
|
4816
|
-
${CONFLICT_INSTRUCTION}` : "";
|
|
4817
|
-
return `## ${heading} (oldest → newest)
|
|
4818
|
-
|
|
4819
|
-
${body}${instruction}`;
|
|
4820
4973
|
}
|
|
4821
|
-
var
|
|
4822
|
-
|
|
4823
|
-
CONFLICT_INSTRUCTION = "When two comments conflict, prefer the latest created_at, UNLESS a later " + "comment explicitly confirms or restates the earlier finding. Evaluate " + "substance, not just recency. Cite the comment id(s) you relied on.";
|
|
4824
|
-
});
|
|
4825
|
-
|
|
4826
|
-
// ../harmony-shared/dist/constants.js
|
|
4827
|
-
var TIMINGS;
|
|
4828
|
-
var init_constants = __esm(() => {
|
|
4829
|
-
TIMINGS = {
|
|
4830
|
-
SEARCH_DEBOUNCE: 300,
|
|
4831
|
-
AUTOSAVE_DEBOUNCE: 1000,
|
|
4832
|
-
TOAST_DURATION: 3000,
|
|
4833
|
-
QUERY_STALE_TIME: 1000 * 60 * 5,
|
|
4834
|
-
QUERY_GC_TIME: 1000 * 60 * 60 * 24
|
|
4835
|
-
};
|
|
4836
|
-
});
|
|
4837
|
-
// ../harmony-shared/dist/logger.js
|
|
4838
|
-
var init_logger = () => {};
|
|
4839
|
-
// ../harmony-shared/dist/projectTemplates.js
|
|
4840
|
-
var init_projectTemplates = () => {};
|
|
4841
|
-
// ../harmony-shared/dist/types.js
|
|
4842
|
-
var init_types2 = () => {};
|
|
4843
|
-
|
|
4844
|
-
// ../harmony-shared/dist/index.js
|
|
4845
|
-
var init_dist = __esm(() => {
|
|
4846
|
-
init_cardLinks();
|
|
4847
|
-
init_commentSerializer();
|
|
4848
|
-
init_constants();
|
|
4849
|
-
init_logger();
|
|
4850
|
-
init_projectTemplates();
|
|
4851
|
-
init_types2();
|
|
4974
|
+
var init_model_tier = __esm(() => {
|
|
4975
|
+
init_dist();
|
|
4852
4976
|
});
|
|
4853
4977
|
|
|
4854
4978
|
// src/prompt.ts
|
|
@@ -5162,7 +5286,9 @@ class Worker {
|
|
|
5162
5286
|
this.timedOut = true;
|
|
5163
5287
|
this.cancel();
|
|
5164
5288
|
}, this.config.maxTimeout);
|
|
5165
|
-
await this.spawnClaude(prompt, card, subtasks
|
|
5289
|
+
await this.spawnClaude(prompt, card, subtasks, {
|
|
5290
|
+
model: this.selectImplementModel(card)
|
|
5291
|
+
});
|
|
5166
5292
|
if (this.aborted)
|
|
5167
5293
|
return;
|
|
5168
5294
|
if (this.timeoutTimer) {
|
|
@@ -5288,6 +5414,14 @@ class Worker {
|
|
|
5288
5414
|
this.onDone(this);
|
|
5289
5415
|
}
|
|
5290
5416
|
}
|
|
5417
|
+
selectImplementModel(card) {
|
|
5418
|
+
const attempts = this.stateStore.getCard(card.id)?.attempts ?? 1;
|
|
5419
|
+
const { model, escalated, source } = chooseImplementModel(this.config.claude, card, attempts);
|
|
5420
|
+
if (source !== "policy" || escalated) {
|
|
5421
|
+
log.info(this.tag, `Implement model "${model}" (source=${source}, escalated=${escalated}, attempts=${attempts}, priority=${card.priority ?? "none"}, tier=${card.model_tier ?? "none"})`);
|
|
5422
|
+
}
|
|
5423
|
+
return model;
|
|
5424
|
+
}
|
|
5291
5425
|
async recordOutcome(cardId, outcome) {
|
|
5292
5426
|
try {
|
|
5293
5427
|
const cost = this.lastSessionStats?.cost;
|
|
@@ -5620,6 +5754,7 @@ var init_worker = __esm(() => {
|
|
|
5620
5754
|
init_completion();
|
|
5621
5755
|
init_error_classifier();
|
|
5622
5756
|
init_log();
|
|
5757
|
+
init_model_tier();
|
|
5623
5758
|
init_plan_phase();
|
|
5624
5759
|
init_process_group();
|
|
5625
5760
|
init_progress_tracker();
|
|
@@ -6396,9 +6531,11 @@ function configRows(config, projectName, gitProvider, httpPort) {
|
|
|
6396
6531
|
const reviewEnabled = config.agent.review.enabled;
|
|
6397
6532
|
const poolDesc = reviewEnabled ? `Pool ${config.agent.poolSize} impl + ${config.agent.review.poolSize} review` : `Pool ${config.agent.poolSize} impl`;
|
|
6398
6533
|
const flowDesc = reviewEnabled ? `Pickup ${config.agent.pickupColumns[0]} → ${config.agent.completion.moveToColumn} → ${config.agent.review.moveToColumn}` : `Pickup ${config.agent.pickupColumns.join(", ")}`;
|
|
6534
|
+
const { model, escalateModel, reviewModel } = config.agent.claude;
|
|
6535
|
+
const modelDesc = model === escalateModel ? model : `${model} → ${escalateModel} on retry/high · review ${reviewModel}`;
|
|
6399
6536
|
rows.push({
|
|
6400
6537
|
label: "Model",
|
|
6401
|
-
value: `${
|
|
6538
|
+
value: `${modelDesc} · ${poolDesc} · ${flowDesc}`
|
|
6402
6539
|
});
|
|
6403
6540
|
const tail = [];
|
|
6404
6541
|
if (gitProvider)
|
|
@@ -6669,11 +6806,23 @@ var exports_worktree_gc = {};
|
|
|
6669
6806
|
__export(exports_worktree_gc, {
|
|
6670
6807
|
runWorktreeGc: () => runWorktreeGc,
|
|
6671
6808
|
pruneFailedRemoteBranches: () => pruneFailedRemoteBranches,
|
|
6809
|
+
isTransientGitNetworkError: () => isTransientGitNetworkError,
|
|
6672
6810
|
WorktreeGc: () => WorktreeGc
|
|
6673
6811
|
});
|
|
6674
6812
|
import { execFileSync as execFileSync9 } from "node:child_process";
|
|
6675
6813
|
import { readdirSync, statSync as statSync2 } from "node:fs";
|
|
6676
6814
|
import { resolve as resolve3 } from "node:path";
|
|
6815
|
+
function isTransientGitNetworkError(message) {
|
|
6816
|
+
return TRANSIENT_GIT_NETWORK_ERROR.test(message);
|
|
6817
|
+
}
|
|
6818
|
+
function gitErrorDetail(err) {
|
|
6819
|
+
if (err && typeof err === "object" && "stderr" in err) {
|
|
6820
|
+
const stderr = String(err.stderr ?? "").trim();
|
|
6821
|
+
if (stderr)
|
|
6822
|
+
return stderr;
|
|
6823
|
+
}
|
|
6824
|
+
return err instanceof Error ? err.message : String(err);
|
|
6825
|
+
}
|
|
6677
6826
|
function runWorktreeGc(basePath, store, opts = {}) {
|
|
6678
6827
|
const minAgeMs = opts.minAgeMs ?? 60 * 60 * 1000;
|
|
6679
6828
|
const now = (opts.now ?? Date.now)();
|
|
@@ -6761,13 +6910,16 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
6761
6910
|
try {
|
|
6762
6911
|
execFileSync9("git", ["fetch", "--prune", "origin"], {
|
|
6763
6912
|
cwd: repoRoot,
|
|
6764
|
-
stdio: "pipe"
|
|
6913
|
+
stdio: "pipe",
|
|
6914
|
+
...GIT_NETWORK_EXEC
|
|
6765
6915
|
});
|
|
6766
6916
|
} catch (err) {
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6917
|
+
const detail = gitErrorDetail(err);
|
|
6918
|
+
if (isTransientGitNetworkError(detail)) {
|
|
6919
|
+
log.debug(TAG30, `Remote branch GC skipped — remote unreachable: ${detail}`);
|
|
6920
|
+
return result;
|
|
6921
|
+
}
|
|
6922
|
+
result.errors.push({ ref: "fetch", error: detail });
|
|
6771
6923
|
}
|
|
6772
6924
|
const refPattern = `refs/remotes/origin/${opts.prefix}*`;
|
|
6773
6925
|
let listing = "";
|
|
@@ -6784,7 +6936,9 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
6784
6936
|
});
|
|
6785
6937
|
return result;
|
|
6786
6938
|
}
|
|
6787
|
-
const
|
|
6939
|
+
const clock = opts.now ?? Date.now;
|
|
6940
|
+
const cutoffSecs = clock() / 1000 - opts.retentionDays * 24 * 60 * 60;
|
|
6941
|
+
const sweepDeadline = clock() + GIT_PRUNE_SWEEP_BUDGET_MS;
|
|
6788
6942
|
for (const line of listing.split(`
|
|
6789
6943
|
`)) {
|
|
6790
6944
|
const trimmed = line.trim();
|
|
@@ -6800,17 +6954,24 @@ function pruneFailedRemoteBranches(opts) {
|
|
|
6800
6954
|
result.skipped.push(ref);
|
|
6801
6955
|
continue;
|
|
6802
6956
|
}
|
|
6957
|
+
if (clock() > sweepDeadline) {
|
|
6958
|
+
log.debug(TAG30, `Remote branch GC budget spent — removed ${result.removed.length}, remaining deferred to next tick`);
|
|
6959
|
+
break;
|
|
6960
|
+
}
|
|
6803
6961
|
try {
|
|
6804
6962
|
execFileSync9("git", ["push", "origin", `:refs/heads/${ref}`], {
|
|
6805
6963
|
cwd: repoRoot,
|
|
6806
|
-
stdio: "pipe"
|
|
6964
|
+
stdio: "pipe",
|
|
6965
|
+
...GIT_NETWORK_EXEC
|
|
6807
6966
|
});
|
|
6808
6967
|
result.removed.push(ref);
|
|
6809
6968
|
} catch (err) {
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6969
|
+
const detail = gitErrorDetail(err);
|
|
6970
|
+
if (isTransientGitNetworkError(detail)) {
|
|
6971
|
+
log.debug(TAG30, `Remote branch GC interrupted — remote unreachable: ${detail}`);
|
|
6972
|
+
break;
|
|
6973
|
+
}
|
|
6974
|
+
result.errors.push({ ref, error: detail });
|
|
6814
6975
|
}
|
|
6815
6976
|
}
|
|
6816
6977
|
if (result.removed.length > 0) {
|
|
@@ -6868,10 +7029,32 @@ function getRepoRoot2() {
|
|
|
6868
7029
|
return null;
|
|
6869
7030
|
}
|
|
6870
7031
|
}
|
|
6871
|
-
var TAG30 = "worktree-gc";
|
|
7032
|
+
var TAG30 = "worktree-gc", GIT_NETWORK_TIMEOUT_MS = 30000, GIT_SSH_CONNECT_TIMEOUT_SECS = 10, GIT_PRUNE_SWEEP_BUDGET_MS = 60000, GIT_NETWORK_EXEC, TRANSIENT_GIT_NETWORK_ERROR;
|
|
6872
7033
|
var init_worktree_gc = __esm(() => {
|
|
6873
7034
|
init_log();
|
|
6874
7035
|
init_worktree();
|
|
7036
|
+
GIT_NETWORK_EXEC = {
|
|
7037
|
+
timeout: GIT_NETWORK_TIMEOUT_MS,
|
|
7038
|
+
killSignal: "SIGKILL",
|
|
7039
|
+
env: {
|
|
7040
|
+
...process.env,
|
|
7041
|
+
GIT_SSH_COMMAND: `${process.env.GIT_SSH_COMMAND ?? "ssh"} -o ConnectTimeout=${GIT_SSH_CONNECT_TIMEOUT_SECS} -o BatchMode=yes`,
|
|
7042
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
7043
|
+
}
|
|
7044
|
+
};
|
|
7045
|
+
TRANSIENT_GIT_NETWORK_ERROR = new RegExp([
|
|
7046
|
+
"timed out",
|
|
7047
|
+
"connection refused",
|
|
7048
|
+
"connection reset",
|
|
7049
|
+
"could not resolve host",
|
|
7050
|
+
"temporary failure in name resolution",
|
|
7051
|
+
"ssh: connect to host",
|
|
7052
|
+
"kex_exchange_identification",
|
|
7053
|
+
"network is unreachable",
|
|
7054
|
+
"etimedout",
|
|
7055
|
+
"enotfound",
|
|
7056
|
+
"enetunreach"
|
|
7057
|
+
].join("|"), "i");
|
|
6875
7058
|
});
|
|
6876
7059
|
|
|
6877
7060
|
// src/index.ts
|