claude-code-session-manager 0.88.1 → 0.89.0
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/dist/assets/{AgentLibrary-kQF8_wAw.js → AgentLibrary-tuj-j1kQ.js} +1 -1
- package/dist/assets/{DataModel-CY_mMnsr.js → DataModel-DOim5SXW.js} +1 -1
- package/dist/assets/{History-Sji2brb0.js → History-DylcasUk.js} +1 -1
- package/dist/assets/{Hooks-CtqpO-p5.js → Hooks--2xoag9a.js} +1 -1
- package/dist/assets/{HostBilko-DN0ydOCr.js → HostBilko-oaw8paAh.js} +1 -1
- package/dist/assets/{Library-CoiLZTKh.js → Library-DzKA09Iy.js} +1 -1
- package/dist/assets/{ListDetail-CmqiQ_io.js → ListDetail-CEdcO80v.js} +1 -1
- package/dist/assets/{MarkdownEditor-HPoNeKhT.js → MarkdownEditor-BMyo0F7s.js} +1 -1
- package/dist/assets/{McpServers-Bj1X3_NT.js → McpServers-BB7ayW7W.js} +1 -1
- package/dist/assets/{Memory-1PKjsM3b.js → Memory-BOdYvrL4.js} +1 -1
- package/dist/assets/{Panel-I1s6ghPo.js → Panel-Yf3SxsRv.js} +1 -1
- package/dist/assets/{Permissions-BhiiZjHQ.js → Permissions-BYCpHNy5.js} +1 -1
- package/dist/assets/{Plugins-gv3_fkPX.js → Plugins-B0lXiYqV.js} +2 -2
- package/dist/assets/{ProvenanceBadge-BGcgBd8V.js → ProvenanceBadge-T7x8LWcV.js} +1 -1
- package/dist/assets/{SaveBar-BGr9eZ4g.js → SaveBar-BdVb4DbR.js} +1 -1
- package/dist/assets/{Scheduler-Bdo_awPq.js → Scheduler-DlOCWioV.js} +1 -1
- package/dist/assets/{ScopeSwitcher-ukl0qWaA.js → ScopeSwitcher-WVhmNsL5.js} +1 -1
- package/dist/assets/{Settings-DJkFj22G.js → Settings-Bt4dVsl6.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-0t1Qblp6.js → SkillReferenceGraph-R-0YykV8.js} +1 -1
- package/dist/assets/{Skills-CeVdBGE-.js → Skills-DXV_LdJP.js} +1 -1
- package/dist/assets/{SystemPrompt-CfKvNHi3.js → SystemPrompt-DiQRo_WF.js} +1 -1
- package/dist/assets/{TagLibrary-DAzNaUPd.js → TagLibrary-B9OuqU6M.js} +1 -1
- package/dist/assets/{TiptapBody--S_rGXoA.js → TiptapBody-HKKmB0AE.js} +1 -1
- package/dist/assets/{Toggle-Dszm0xnp.js → Toggle-CBggPonH.js} +1 -1
- package/dist/assets/{index-Dp4Rc--q.js → index-BcJqLDiY.js} +3 -3
- package/dist/assets/{settingsSchema-BLbssMYo.js → settingsSchema-BYE9DVw_.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +1 -1
- package/src/main/__tests__/scheduler-integration-failure-stamp.test.cjs +41 -0
- package/src/main/__tests__/scheduler-mechanical-recovery.test.cjs +23 -0
- package/src/main/build-info.json +4 -4
- package/src/main/lib/__tests__/gitWorktree.test.cjs +172 -0
- package/src/main/lib/gitWorktree.cjs +195 -3
- package/src/main/lib/rcaReport.cjs +8 -1
- package/src/main/scheduler.cjs +89 -14
package/src/main/scheduler.cjs
CHANGED
|
@@ -4797,6 +4797,25 @@ As the LAST LINE of your final result text, emit exactly one of:
|
|
|
4797
4797
|
Print PASS only once the commit above has actually landed.`;
|
|
4798
4798
|
}
|
|
4799
4799
|
|
|
4800
|
+
/**
|
|
4801
|
+
* Stamps integrateBranch's failure subtype (`failureKind`) and conflicted
|
|
4802
|
+
* paths onto a job row at finalize; clears both when there is no failure so
|
|
4803
|
+
* a re-run never carries a stale subtype. Plain JSON values only.
|
|
4804
|
+
*/
|
|
4805
|
+
function stampIntegrationFailure(row, integration) {
|
|
4806
|
+
if (integration && integration.failureKind) {
|
|
4807
|
+
row.integrationFailureKind = integration.failureKind;
|
|
4808
|
+
if (Array.isArray(integration.conflictedPaths)) row.integrationConflictPaths = integration.conflictedPaths;
|
|
4809
|
+
else delete row.integrationConflictPaths;
|
|
4810
|
+
if (integration.baseHeadSha) row.integrationBaseHeadSha = integration.baseHeadSha;
|
|
4811
|
+
else delete row.integrationBaseHeadSha;
|
|
4812
|
+
} else {
|
|
4813
|
+
delete row.integrationFailureKind;
|
|
4814
|
+
delete row.integrationConflictPaths;
|
|
4815
|
+
delete row.integrationBaseHeadSha;
|
|
4816
|
+
}
|
|
4817
|
+
}
|
|
4818
|
+
|
|
4800
4819
|
/**
|
|
4801
4820
|
* Mechanical recovery (PRD 1130). isFixPlanBeyondDepthCap (below) is the
|
|
4802
4821
|
* ONLY gate on re-investigating a fix-plan job at investigationDepth >= 2 —
|
|
@@ -4809,14 +4828,21 @@ Print PASS only once the commit above has actually landed.`;
|
|
|
4809
4828
|
* auto-fix it authors no plan and spawns no model; it is pure git.
|
|
4810
4829
|
*
|
|
4811
4830
|
* The closed set of mechanically-resolvable verdicts starts at exactly
|
|
4812
|
-
* 'worktree_integration_failed'
|
|
4813
|
-
*
|
|
4814
|
-
*
|
|
4815
|
-
*
|
|
4816
|
-
*
|
|
4817
|
-
*
|
|
4818
|
-
*
|
|
4819
|
-
*
|
|
4831
|
+
* 'worktree_integration_failed', which covers two subtypes (job.
|
|
4832
|
+
* integrationFailureKind): `blocking_paths` — PRD 1125 taught integrateBranch
|
|
4833
|
+
* to parse git's "would be overwritten by merge" stderr, verify the blocking
|
|
4834
|
+
* paths are byte-identical to the branch, discard the proven duplicates and
|
|
4835
|
+
* retry once, so a re-call can genuinely succeed once the dirty files are
|
|
4836
|
+
* gone — and `content_conflict`, which PRD 1125 explicitly does NOT handle
|
|
4837
|
+
* (parseBlockingMergePaths returns null for it). Only pure-addition text
|
|
4838
|
+
* conflicts are auto-resolved (mc-02, gitWorktree.cjs, kill switch
|
|
4839
|
+
* SM_PURE_ADDITION_MERGE_DISABLE=1) — and that already ran inside the
|
|
4840
|
+
* original failed integration. Re-calling integrateBranch for a content
|
|
4841
|
+
* conflict that survived it, against an unmoved base HEAD, fails
|
|
4842
|
+
* deterministically, so the selector skips it (WITHOUT spending the one
|
|
4843
|
+
* attempt) until HEAD moves — a sibling merge may make the retry succeed.
|
|
4844
|
+
* A job parked with this verdict has its `sm-job/<slug>` branch preserved
|
|
4845
|
+
* (cleanupJobWorktree's `keepBranch: !integration.ok`).
|
|
4820
4846
|
*
|
|
4821
4847
|
* Bounded to exactly one attempt via job.mechanicalRecoveryAttempted,
|
|
4822
4848
|
* stamped in the SAME mutate as the outcome (performMechanicalRecovery,
|
|
@@ -4828,11 +4854,41 @@ Print PASS only once the commit above has actually landed.`;
|
|
|
4828
4854
|
*/
|
|
4829
4855
|
const MECHANICALLY_RESOLVABLE_VERDICTS = new Set(['worktree_integration_failed']);
|
|
4830
4856
|
|
|
4831
|
-
|
|
4857
|
+
/**
|
|
4858
|
+
* True when a retry is provably futile: a content_conflict whose base HEAD is
|
|
4859
|
+
* the same as at failure time. Unknown state (no stamp, null current HEAD)
|
|
4860
|
+
* is NOT futile — falls back to attempting the retry.
|
|
4861
|
+
*/
|
|
4862
|
+
function isMechanicalRecoveryFutile(job, currentHeadSha) {
|
|
4863
|
+
return job.integrationFailureKind === 'content_conflict'
|
|
4864
|
+
&& !!job.integrationBaseHeadSha
|
|
4865
|
+
&& !!currentHeadSha
|
|
4866
|
+
&& job.integrationBaseHeadSha === currentHeadSha;
|
|
4867
|
+
}
|
|
4868
|
+
|
|
4869
|
+
/**
|
|
4870
|
+
* Resolves the current HEAD once per distinct cwd for a pass. Returns a
|
|
4871
|
+
* `(job) => sha|null` lookup over `jobs`; the memo lives only as long as the
|
|
4872
|
+
* returned closure. O(distinct cwds) git calls, not O(jobs).
|
|
4873
|
+
*/
|
|
4874
|
+
async function resolveHeadShasForPass(jobs) {
|
|
4875
|
+
const byCwd = new Map();
|
|
4876
|
+
for (const j of jobs || []) {
|
|
4877
|
+
if (!j || j.status !== 'needs_review' || j.integrationFailureKind !== 'content_conflict') continue;
|
|
4878
|
+
const cwd = j.cwd || DEFAULT_PROJECT_CWD;
|
|
4879
|
+
if (!byCwd.has(cwd)) byCwd.set(cwd, gitHead(cwd));
|
|
4880
|
+
}
|
|
4881
|
+
const resolved = new Map();
|
|
4882
|
+
for (const [cwd, p] of byCwd) resolved.set(cwd, await p);
|
|
4883
|
+
return (j) => (j && resolved.get(j.cwd || DEFAULT_PROJECT_CWD)) || null;
|
|
4884
|
+
}
|
|
4885
|
+
|
|
4886
|
+
function selectMechanicalRecoveryTarget(job, currentHeadSha = null) {
|
|
4832
4887
|
if (process.env.SM_MECHANICAL_RECOVERY_DISABLE === '1') return null;
|
|
4833
4888
|
if (!job || job.status !== 'needs_review') return null;
|
|
4834
4889
|
if (!MECHANICALLY_RESOLVABLE_VERDICTS.has(job.verifierVerdict)) return null;
|
|
4835
4890
|
if (job.mechanicalRecoveryAttempted === true) return null;
|
|
4891
|
+
if (isMechanicalRecoveryFutile(job, currentHeadSha)) return null;
|
|
4836
4892
|
const cwd = job.cwd || DEFAULT_PROJECT_CWD;
|
|
4837
4893
|
return { slug: job.slug, cwd, branch: jobWorktree.branchNameFor(job.slug), carriedPaths: job.carriedPaths || [] };
|
|
4838
4894
|
}
|
|
@@ -6044,7 +6100,7 @@ async function spawnInvestigation(failedJob, runDir, { deadChild = null } = {})
|
|
|
6044
6100
|
// Mechanical recovery (PRD 1130): same first-refusal treatment — a job
|
|
6045
6101
|
// eligible for a pure-git retry must never also get a cold-read fix-plan
|
|
6046
6102
|
// PRD authored in the same pass.
|
|
6047
|
-
if (selectMechanicalRecoveryTarget(failedJob)) {
|
|
6103
|
+
if (selectMechanicalRecoveryTarget(failedJob, failedJob.integrationFailureKind === 'content_conflict' ? await gitHead(failedJob.cwd || DEFAULT_PROJECT_CWD) : null)) {
|
|
6048
6104
|
console.log(`[scheduler] skip investigation: ${failedJob.slug} is mechanical-recovery eligible`);
|
|
6049
6105
|
return { deferred: false };
|
|
6050
6106
|
}
|
|
@@ -6859,6 +6915,7 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null) {
|
|
|
6859
6915
|
let res;
|
|
6860
6916
|
let worktreeLeftoverDirty = [];
|
|
6861
6917
|
let worktreeIntegrationFailure = null;
|
|
6918
|
+
let worktreeIntegrationDetail = null;
|
|
6862
6919
|
// Set only when integrateJobBranch's stderr-parsing auto-resolve fired
|
|
6863
6920
|
// (PRD 1125) — surfaced on the job row so the Queue UI can say the merge
|
|
6864
6921
|
// self-healed rather than silently looking like an ordinary merge.
|
|
@@ -6919,6 +6976,7 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null) {
|
|
|
6919
6976
|
}
|
|
6920
6977
|
if (!integration.ok) {
|
|
6921
6978
|
worktreeIntegrationFailure = integration.reason;
|
|
6979
|
+
worktreeIntegrationDetail = integration;
|
|
6922
6980
|
console.error(`[scheduler] ${job.slug}: worktree branch integration FAILED (${integration.reason}) — branch ${worktree.branch} preserved in ${guardCwd} for manual recovery`);
|
|
6923
6981
|
} else if (integration.integrated) {
|
|
6924
6982
|
console.log(`[scheduler] ${job.slug}: worktree branch ${worktree.branch} integrated into ${guardCwd}${integration.mergeCommit ? ' (merge commit)' : ' (fast-forward)'}`);
|
|
@@ -7205,7 +7263,9 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null) {
|
|
|
7205
7263
|
if (worktreeIntegrationFailure) {
|
|
7206
7264
|
verifyResult = {
|
|
7207
7265
|
verdict: 'worktree_integration_failed',
|
|
7208
|
-
reason:
|
|
7266
|
+
reason: worktreeIntegrationDetail && worktreeIntegrationDetail.failureKind === 'content_conflict'
|
|
7267
|
+
? `Integration blocked by a content conflict in ${(worktreeIntegrationDetail.conflictedPaths || []).join(', ') || 'unknown paths'} — branch ${jobWorktree.branchNameFor(job.slug)} preserved; needs a manual merge.`
|
|
7268
|
+
: `worktree branch integration failed: ${worktreeIntegrationFailure} — branch preserved for manual merge`,
|
|
7209
7269
|
downgradeTo: 'needs_review',
|
|
7210
7270
|
};
|
|
7211
7271
|
}
|
|
@@ -7320,6 +7380,9 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null) {
|
|
|
7320
7380
|
let mechanicalRecoveryTarget = null;
|
|
7321
7381
|
let terminalNotifySnapshot = null;
|
|
7322
7382
|
const newlyCompletedPrds = [];
|
|
7383
|
+
// Base HEAD for the mechanical-recovery futility check — read here, never inside mutate() (no I/O).
|
|
7384
|
+
const finalizeHeadSha = worktreeIntegrationDetail && worktreeIntegrationDetail.failureKind === 'content_conflict'
|
|
7385
|
+
? await gitHead(guardCwd) : null;
|
|
7323
7386
|
await mutate((s) => {
|
|
7324
7387
|
const i2 = s.jobs.findIndex((x) => x.slug === job.slug);
|
|
7325
7388
|
// A job already moved off 'running' by someone else (namely
|
|
@@ -7511,6 +7574,7 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null) {
|
|
|
7511
7574
|
} else if (!['blocked_by_foreign_wip_streak', 'budget_exceeded'].includes(s.jobs[i2].verifierVerdict)) {
|
|
7512
7575
|
delete s.jobs[i2].verifierVerdict;
|
|
7513
7576
|
}
|
|
7577
|
+
stampIntegrationFailure(s.jobs[i2], worktreeIntegrationFailure ? worktreeIntegrationDetail : null);
|
|
7514
7578
|
transitionJob(s.jobs[i2], effectiveStatus, { reason: finalizeReason, source: 'spawnJob:finalize' });
|
|
7515
7579
|
s.jobs[i2].finishedAt = new Date().toISOString();
|
|
7516
7580
|
s.jobs[i2].exitCode = res.exitCode;
|
|
@@ -7620,7 +7684,7 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null) {
|
|
|
7620
7684
|
// depth-cap check, so it must never fall through to either.
|
|
7621
7685
|
// Snapshot only (no I/O inside mutate()); the actual git retry
|
|
7622
7686
|
// happens outside mutate(), below.
|
|
7623
|
-
const mTarget = selectMechanicalRecoveryTarget(s.jobs[i2]);
|
|
7687
|
+
const mTarget = selectMechanicalRecoveryTarget(s.jobs[i2], finalizeHeadSha);
|
|
7624
7688
|
if (mTarget) {
|
|
7625
7689
|
mechanicalRecoveryJob = { ...s.jobs[i2] };
|
|
7626
7690
|
mechanicalRecoveryTarget = mTarget;
|
|
@@ -10828,9 +10892,17 @@ async function reverifyNeedsReview() {
|
|
|
10828
10892
|
// check in spawnJob missed because the app restarted in between). Depth
|
|
10829
10893
|
// never disqualifies it, so it runs regardless of investigationDepth.
|
|
10830
10894
|
{
|
|
10895
|
+
const headFor = await resolveHeadShasForPass(queueForResumeAndAutofix.jobs);
|
|
10831
10896
|
for (const job of queueForResumeAndAutofix.jobs) {
|
|
10832
|
-
const
|
|
10833
|
-
|
|
10897
|
+
const head = headFor(job);
|
|
10898
|
+
const target = selectMechanicalRecoveryTarget(job, head);
|
|
10899
|
+
if (!target) {
|
|
10900
|
+
if (job.status === 'needs_review' && job.mechanicalRecoveryAttempted !== true
|
|
10901
|
+
&& MECHANICALLY_RESOLVABLE_VERDICTS.has(job.verifierVerdict) && isMechanicalRecoveryFutile(job, head)) {
|
|
10902
|
+
console.log(`[scheduler] mechanical-recovery: skip ${job.slug} — content conflict, base HEAD unchanged since failure`);
|
|
10903
|
+
}
|
|
10904
|
+
continue;
|
|
10905
|
+
}
|
|
10834
10906
|
console.log(`[scheduler] mechanical-recovery: needs_review ${job.slug} → re-integrating ${target.branch}`);
|
|
10835
10907
|
performMechanicalRecovery(job, target).catch((e) => {
|
|
10836
10908
|
console.error('[scheduler] performMechanicalRecovery error', job.slug, e);
|
|
@@ -12598,6 +12670,9 @@ module.exports = {
|
|
|
12598
12670
|
buildClaudeSpawnArgs,
|
|
12599
12671
|
spawnResumeRecovery,
|
|
12600
12672
|
selectMechanicalRecoveryTarget,
|
|
12673
|
+
isMechanicalRecoveryFutile,
|
|
12674
|
+
resolveHeadShasForPass,
|
|
12675
|
+
stampIntegrationFailure,
|
|
12601
12676
|
performMechanicalRecovery,
|
|
12602
12677
|
MECHANICALLY_RESOLVABLE_VERDICTS,
|
|
12603
12678
|
selectLeftoverQuarantineTarget,
|