claude-code-session-manager 0.39.1 → 0.39.3
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/{TiptapBody-CtuLATFR.js → TiptapBody-B90xy18x.js} +1 -1
- package/dist/assets/{index-CefVSjmm.js → index-BUuhV6vT.js} +496 -494
- package/dist/assets/{index-B6JNrbpD.css → index-DVlD8N1X.css} +2 -2
- package/dist/index.html +2 -2
- package/package.json +3 -3
- package/scripts/lib/activeSessions.cjs +26 -3
- package/scripts/lib/watchdogHelpers.cjs +13 -6
- package/src/main/__tests__/browserView-destroyed-handler.test.cjs +84 -0
- package/src/main/__tests__/prdCreate.test.cjs +6 -0
- package/src/main/__tests__/prdLocations.test.cjs +41 -0
- package/src/main/__tests__/prdMigration.test.cjs +41 -0
- package/src/main/__tests__/promptSessionEvents.test.cjs +106 -0
- package/src/main/__tests__/queueHistory.test.cjs +2 -2
- package/src/main/__tests__/rcaFeedbackHook.test.cjs +108 -0
- package/src/main/__tests__/runVerify.test.cjs +459 -4
- package/src/main/__tests__/scheduler-admin-routes.test.cjs +52 -3
- package/src/main/__tests__/scheduler-archive-completed-prd.test.cjs +102 -0
- package/src/main/__tests__/scheduler-commit-guard-noop.test.cjs +126 -0
- package/src/main/__tests__/scheduler-committed-in-window.test.cjs +58 -3
- package/src/main/__tests__/scheduler-find-prd-dir.test.cjs +42 -0
- package/src/main/__tests__/scheduler-investigation-clean-skip.test.cjs +63 -0
- package/src/main/__tests__/scheduler-meta-code-sha.test.cjs +23 -0
- package/src/main/__tests__/scheduler-notify-originating-tab.test.cjs +68 -0
- package/src/main/__tests__/scheduler-reset-job-fields-guard.test.cjs +77 -0
- package/src/main/__tests__/scheduler-unreadable-queue-guard.test.cjs +62 -0
- package/src/main/browserView.cjs +5 -4
- package/src/main/chatRunner.cjs +90 -7
- package/src/main/config.cjs +9 -0
- package/src/main/ipcSchemas.cjs +1 -1
- package/src/main/lib/__tests__/terminalRunOutcome.test.cjs +118 -0
- package/src/main/lib/prdLocations.cjs +43 -6
- package/src/main/lib/prdMigration.cjs +5 -3
- package/src/main/lib/queueHistory.cjs +1 -1
- package/src/main/lib/rcaFeedbackHook.cjs +55 -5
- package/src/main/lib/terminalRunOutcome.cjs +100 -0
- package/src/main/promptSessionEvents.cjs +87 -0
- package/src/main/runVerify.cjs +195 -3
- package/src/main/scheduler.cjs +436 -80
- package/src/preload/api.d.ts +5 -1
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Main-process counterpart to promptSessions.ts's event chain, used ONLY by
|
|
5
|
+
* the scheduler's PRD-finished notification path (notifyOriginatingTab,
|
|
6
|
+
* scheduler.cjs). The renderer owns the live in-memory PromptSession store
|
|
7
|
+
* and its own persistActiveIndex; this module never touches that store — it
|
|
8
|
+
* reads/writes the SAME on-disk active-index.json directly, read-modify-
|
|
9
|
+
* write, so a scheduler job (which runs in the main process, with no
|
|
10
|
+
* renderer store to append to) can chain a 'response' event onto a known
|
|
11
|
+
* session's tail without routing a synthetic prompt into an unrelated tab
|
|
12
|
+
* (PRD 814).
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const config = require('./config.cjs');
|
|
16
|
+
|
|
17
|
+
function promptSessionActiveIndexPath(cwd) {
|
|
18
|
+
return `${String(cwd).replace(/\/+$/, '')}/session-manager-operations/prompt-sessions/active-index.json`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let seq = 0;
|
|
22
|
+
function mintEventId() {
|
|
23
|
+
seq += 1;
|
|
24
|
+
return `pevt-${Date.now().toString(36)}-${seq}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Serializes read-modify-write cycles per active-index.json path so two
|
|
28
|
+
// PRDs finishing back-to-back for sessions in the same cwd chain onto the
|
|
29
|
+
// tail in issue order instead of racing two independent read-then-write
|
|
30
|
+
// round trips (mirrors promptSessions.ts's own pendingWritesByPath).
|
|
31
|
+
const pendingWritesByPath = new Map();
|
|
32
|
+
|
|
33
|
+
function withPathLock(path, task) {
|
|
34
|
+
const prior = pendingWritesByPath.get(path) || Promise.resolve();
|
|
35
|
+
const settle = () => task();
|
|
36
|
+
const run = prior.then(settle, settle);
|
|
37
|
+
pendingWritesByPath.set(
|
|
38
|
+
path,
|
|
39
|
+
run.then(
|
|
40
|
+
() => undefined,
|
|
41
|
+
() => undefined,
|
|
42
|
+
),
|
|
43
|
+
);
|
|
44
|
+
return run;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* If `sourcePromptId` resolves to a known, still-active PromptSession under
|
|
49
|
+
* `cwd`'s active-index.json, appends a 'response' event chained to that
|
|
50
|
+
* session's current tail and returns true. Returns false (never throws) for
|
|
51
|
+
* every case the caller must fall back on: missing cwd/id, no index file,
|
|
52
|
+
* unknown id, a completed session, an empty event chain, or any I/O error —
|
|
53
|
+
* so notifyOriginatingTab's existing tab-external-ticket path stays the
|
|
54
|
+
* safety net, not a special case.
|
|
55
|
+
*/
|
|
56
|
+
async function appendResponseEventIfKnown(cwd, sourcePromptId, text) {
|
|
57
|
+
if (!cwd || !sourcePromptId) return false;
|
|
58
|
+
const path = promptSessionActiveIndexPath(cwd);
|
|
59
|
+
try {
|
|
60
|
+
return await withPathLock(path, async () => {
|
|
61
|
+
const result = await config.readJson(path);
|
|
62
|
+
if (!result.exists || !result.data) return false;
|
|
63
|
+
const data = result.data;
|
|
64
|
+
const session = data.sessions && data.sessions[sourcePromptId];
|
|
65
|
+
if (!session || session.status !== 'active') return false;
|
|
66
|
+
const events = (data.events && data.events[sourcePromptId]) || [];
|
|
67
|
+
const tail = events.length > 0 ? events[events.length - 1] : null;
|
|
68
|
+
if (!tail) return false;
|
|
69
|
+
const event = {
|
|
70
|
+
id: mintEventId(),
|
|
71
|
+
promptSessionId: sourcePromptId,
|
|
72
|
+
kind: 'response',
|
|
73
|
+
causedByEventId: tail.id,
|
|
74
|
+
at: new Date().toISOString(),
|
|
75
|
+
text,
|
|
76
|
+
};
|
|
77
|
+
data.events[sourcePromptId] = [...events, event];
|
|
78
|
+
await config.writeJson(path, data);
|
|
79
|
+
return true;
|
|
80
|
+
});
|
|
81
|
+
} catch (e) {
|
|
82
|
+
console.error('[promptSessionEvents] appendResponseEventIfKnown error', cwd, sourcePromptId, e);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = { appendResponseEventIfKnown, promptSessionActiveIndexPath };
|
package/src/main/runVerify.cjs
CHANGED
|
@@ -260,16 +260,34 @@ function hasInstallRecovery(events, fromSeq) {
|
|
|
260
260
|
return false;
|
|
261
261
|
}
|
|
262
262
|
|
|
263
|
+
// Matches a leading `sleep <N> &&` or `sleep <N> ;` wrapper (the shell idiom
|
|
264
|
+
// used to poll a still-pending remote job) so a retry that only drops/changes
|
|
265
|
+
// its own timing wrapper still counts as the same underlying command. Narrow
|
|
266
|
+
// on purpose: it strips nothing else, so two genuinely different commands
|
|
267
|
+
// never collapse into a false pairing (incident: RCA 745-pr188-ci-lint-docs-integrity).
|
|
268
|
+
const SLEEP_PREFIX_RE = /^\s*sleep\s+\d+\s*(&&|;)\s*/;
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Strip a leading `sleep <N> (&&|;)` wrapper from a tool_use description so
|
|
272
|
+
* self-recovery comparisons ignore only that timing-poll idiom.
|
|
273
|
+
*/
|
|
274
|
+
function normalizeDescForRecovery(desc) {
|
|
275
|
+
return (desc ?? '').replace(SLEEP_PREFIX_RE, '');
|
|
276
|
+
}
|
|
277
|
+
|
|
263
278
|
/**
|
|
264
279
|
* Check whether the error at `errorSeq` is self-recovered within the next 30
|
|
265
280
|
* events.
|
|
266
281
|
*
|
|
267
282
|
* Recovery heuristic: another tool_use with the same non-empty `description`
|
|
283
|
+
* (after stripping a leading `sleep N &&`/`sleep N;` wrapper from both sides)
|
|
268
284
|
* appears within 30 events of the error, AND its corresponding tool_result is
|
|
269
285
|
* free of error patterns AND is not is_error=true.
|
|
270
286
|
*/
|
|
271
287
|
function isSelfRecovered(events, errorSeq, desc) {
|
|
272
288
|
if (!desc) return false;
|
|
289
|
+
const normalizedDesc = normalizeDescForRecovery(desc);
|
|
290
|
+
if (!normalizedDesc) return false;
|
|
273
291
|
|
|
274
292
|
// Build a quick lookup from tool_use_id → tool_result event.
|
|
275
293
|
const resultByUseId = new Map();
|
|
@@ -281,7 +299,7 @@ function isSelfRecovered(events, errorSeq, desc) {
|
|
|
281
299
|
for (const ev of events) {
|
|
282
300
|
if (ev.seq <= errorSeq) continue;
|
|
283
301
|
if (++seen > 30) break;
|
|
284
|
-
if (ev.kind !== 'tool_use' || ev.description !==
|
|
302
|
+
if (ev.kind !== 'tool_use' || normalizeDescForRecovery(ev.description) !== normalizedDesc) continue;
|
|
285
303
|
const result = resultByUseId.get(ev.toolUseId);
|
|
286
304
|
if (result && !result.isError && !detectPattern(result.content)) return true;
|
|
287
305
|
}
|
|
@@ -512,6 +530,114 @@ function checkMergeablePr({ cwd, prNumber, timeoutMs = GH_CHECK_TIMEOUT_MS, exec
|
|
|
512
530
|
}
|
|
513
531
|
}
|
|
514
532
|
|
|
533
|
+
// ─── prior-run-landed-commit postcondition exemption (re-fired same slug) ───
|
|
534
|
+
|
|
535
|
+
/**
|
|
536
|
+
* Independently re-check, via real git ancestry, whether a commit a PREVIOUS
|
|
537
|
+
* run of this same slug landed is reachable from the current HEAD. Used to
|
|
538
|
+
* distinguish a truthful no-op re-run (the slug's work already shipped in an
|
|
539
|
+
* earlier run, so this run correctly made no new commit) from a lying PASS
|
|
540
|
+
* with no substantiating work at all.
|
|
541
|
+
*
|
|
542
|
+
* Bounded (15s) and fully fail-safe: ANY error (bad SHA, non-git cwd, timeout,
|
|
543
|
+
* `sha` not an ancestor) resolves `false` rather than throwing, so a failure
|
|
544
|
+
* here only ever falls back to today's pass_no_commit behavior — it can never
|
|
545
|
+
* turn a real failure into a false "verified".
|
|
546
|
+
*
|
|
547
|
+
* `execImpl` is injectable (defaults to `child_process.execFileSync`) so unit
|
|
548
|
+
* tests can stub the subprocess call without shelling out to real git.
|
|
549
|
+
*/
|
|
550
|
+
function isAncestorCommit({ cwd, sha, timeoutMs = GH_CHECK_TIMEOUT_MS, execImpl = execFileSync }) {
|
|
551
|
+
if (!sha) return false;
|
|
552
|
+
try {
|
|
553
|
+
execImpl('git', ['merge-base', '--is-ancestor', sha, 'HEAD'], {
|
|
554
|
+
cwd: cwd || process.cwd(),
|
|
555
|
+
timeout: timeoutMs,
|
|
556
|
+
encoding: 'utf8',
|
|
557
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
558
|
+
});
|
|
559
|
+
return true;
|
|
560
|
+
} catch {
|
|
561
|
+
return false;
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
// ─── already-shipped postcondition exemption (original PRD re-runs) ─────────
|
|
566
|
+
|
|
567
|
+
const PRD_DELIVERABLE_PATH_RE = /(?:^|[`\s(])((?:src|scripts|session-manager-operations|test|tests|docs|bin)\/[A-Za-z0-9_./-]*[A-Za-z0-9_-]\.[A-Za-z0-9]+)\b/g;
|
|
568
|
+
|
|
569
|
+
// Repo-root config files a PRD can legitimately name as its only deliverable.
|
|
570
|
+
// Deliberately an exact-name allowlist, not a general root-file pattern:
|
|
571
|
+
// a loose rule would match prose mentions (README.md, queue.json) and grant
|
|
572
|
+
// false exemptions. Incident: PRD 816 named vitest.config.ts as its primary
|
|
573
|
+
// deliverable and it was silently dropped from extraction.
|
|
574
|
+
const PRD_ROOT_CONFIG_DELIVERABLES = [
|
|
575
|
+
'package.json',
|
|
576
|
+
'tsconfig.json',
|
|
577
|
+
'vitest.config.ts',
|
|
578
|
+
'vite.config.ts',
|
|
579
|
+
'playwright.config.ts',
|
|
580
|
+
'eslint.config.js',
|
|
581
|
+
'tailwind.config.js',
|
|
582
|
+
'postcss.config.js',
|
|
583
|
+
];
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* Extract the deliverable file paths a PRD body names — backticked or bare
|
|
587
|
+
* paths under a source dir with a file extension (e.g. `src/main/lib/foo.cjs`,
|
|
588
|
+
* `src/renderer/components/Bar.tsx`, `scripts/baz.cjs`), plus any allowlisted
|
|
589
|
+
* repo-root config filename named as a whole word (e.g. `vitest.config.ts`).
|
|
590
|
+
* Deliberately narrow: only repo-relative paths under a known source root or
|
|
591
|
+
* an exact-name root-config allowlist, no globs, no node_modules, no URLs.
|
|
592
|
+
* O(n) over the PRD body length.
|
|
593
|
+
*/
|
|
594
|
+
function extractPrdDeliverablePaths(prdBody) {
|
|
595
|
+
if (typeof prdBody !== 'string' || !prdBody) return [];
|
|
596
|
+
const seen = new Set();
|
|
597
|
+
const out = [];
|
|
598
|
+
let m;
|
|
599
|
+
PRD_DELIVERABLE_PATH_RE.lastIndex = 0;
|
|
600
|
+
while ((m = PRD_DELIVERABLE_PATH_RE.exec(prdBody)) !== null) {
|
|
601
|
+
const p = m[1];
|
|
602
|
+
if (p.includes('node_modules') || p.includes('..') || seen.has(p)) continue;
|
|
603
|
+
seen.add(p);
|
|
604
|
+
out.push(p);
|
|
605
|
+
}
|
|
606
|
+
for (const name of PRD_ROOT_CONFIG_DELIVERABLES) {
|
|
607
|
+
if (seen.has(name)) continue;
|
|
608
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
609
|
+
const re = new RegExp(`(?:^|[\`\\s(])${escaped}(?:[\`\\s)]|$)`);
|
|
610
|
+
if (re.test(prdBody)) {
|
|
611
|
+
seen.add(name);
|
|
612
|
+
out.push(name);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return out;
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/**
|
|
619
|
+
* Materially check whether every deliverable path a PRD names is already
|
|
620
|
+
* tracked in git — i.e. the PRD's work landed in an EARLIER run/commit and
|
|
621
|
+
* this run correctly had nothing to do. Uses `git ls-files --error-unmatch`
|
|
622
|
+
* (bounded, execImpl injectable for tests). Fails safe in every direction:
|
|
623
|
+
* any error, a non-git cwd, or zero paths returns false, which falls straight
|
|
624
|
+
* through to today's pass_no_commit behavior.
|
|
625
|
+
*/
|
|
626
|
+
function allDeliverablesAlreadyTracked({ cwd, paths, execImpl = execFileSync, timeoutMs = 15_000 }) {
|
|
627
|
+
if (!Array.isArray(paths) || paths.length === 0) return false;
|
|
628
|
+
try {
|
|
629
|
+
execImpl('git', ['ls-files', '--error-unmatch', '--', ...paths], {
|
|
630
|
+
cwd: cwd || process.cwd(),
|
|
631
|
+
timeout: timeoutMs,
|
|
632
|
+
encoding: 'utf8',
|
|
633
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
634
|
+
});
|
|
635
|
+
return true;
|
|
636
|
+
} catch {
|
|
637
|
+
return false;
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
|
|
515
641
|
// ─── main verifier ────────────────────────────────────────────────────────────
|
|
516
642
|
|
|
517
643
|
/**
|
|
@@ -538,9 +664,13 @@ function checkMergeablePr({ cwd, prNumber, timeoutMs = GH_CHECK_TIMEOUT_MS, exec
|
|
|
538
664
|
* subprocess call used by the -merge-main
|
|
539
665
|
* postcondition exemption. Defaults to
|
|
540
666
|
* child_process.execFileSync.
|
|
667
|
+
* @param {string|null} [params.priorLandedCommit] The commit SHA a PREVIOUS run of this
|
|
668
|
+
* same slug landed, if any — supplied by the
|
|
669
|
+
* caller (scheduler.cjs). Used by the
|
|
670
|
+
* pass_no_commit_prior_run_verified exemption.
|
|
541
671
|
* @returns {Promise<{verdict:string, reason:string, downgradeTo:string|null}>}
|
|
542
672
|
*/
|
|
543
|
-
async function verifyRun({ runDir, prdPath, queueEntry, allJobs = [], committedDuringRun = false, allowPreSentinelHeal = false, ghExecImpl }) {
|
|
673
|
+
async function verifyRun({ runDir, prdPath, queueEntry, allJobs = [], committedDuringRun = false, allowPreSentinelHeal = false, ghExecImpl, priorLandedCommit = null }) {
|
|
544
674
|
const { slug } = queueEntry;
|
|
545
675
|
const logPath = path.join(runDir, `${slug}.log`);
|
|
546
676
|
const verdictsPath = path.join(runDir, `${slug}.verdicts.json`);
|
|
@@ -758,7 +888,64 @@ async function verifyRun({ runDir, prdPath, queueEntry, allJobs = [], committedD
|
|
|
758
888
|
}
|
|
759
889
|
}
|
|
760
890
|
}
|
|
761
|
-
|
|
891
|
+
// EXEMPTION: the SAME slug was already executed by a PRIOR run that
|
|
892
|
+
// landed a commit, and that commit is still reachable from HEAD. "I
|
|
893
|
+
// re-checked every AC item, found it all already implemented by an
|
|
894
|
+
// earlier run of this exact PRD, declined to make a no-op commit, and
|
|
895
|
+
// printed a truthful PASS" is correct, idempotent behavior — the
|
|
896
|
+
// systemic false-failure class this exemption exists to catch (distinct
|
|
897
|
+
// from the already-shipped exemption below, which looks at PRD-named
|
|
898
|
+
// file paths rather than a specific prior run's SHA).
|
|
899
|
+
// (Incident: 812-workbench-review-nits-cleanup, 2026-07-31 — re-fired
|
|
900
|
+
// ~24 min after its own prior run landed 00d891c; re-verified,
|
|
901
|
+
// re-ran typecheck + 17 green tests, made no new commit, printed a
|
|
902
|
+
// truthful PASS, and was parked in needs_review anyway.)
|
|
903
|
+
let priorRunVerified = false;
|
|
904
|
+
if (!mergeMainVerified && priorLandedCommit
|
|
905
|
+
&& isAncestorCommit({ cwd: queueEntry?.cwd, sha: priorLandedCommit })) {
|
|
906
|
+
priorRunVerified = true;
|
|
907
|
+
return conclude(
|
|
908
|
+
'pass_no_commit_prior_run_verified',
|
|
909
|
+
`SCHEDULER_VERDICT: PASS with no commit, but a prior run of this slug landed ${priorLandedCommit}, `
|
|
910
|
+
+ 'which is an ancestor of HEAD — the PRD\'s work is already in the tree, so this re-run correctly '
|
|
911
|
+
+ 'made no change',
|
|
912
|
+
null,
|
|
913
|
+
{
|
|
914
|
+
...(annotations.length ? { annotations } : {}),
|
|
915
|
+
sentinel,
|
|
916
|
+
priorLandedCommit,
|
|
917
|
+
},
|
|
918
|
+
);
|
|
919
|
+
}
|
|
920
|
+
// EXEMPTION: an ORIGINAL PRD re-queued after its deliverables already
|
|
921
|
+
// landed. "I checked, the work is already committed and green, nothing
|
|
922
|
+
// to change" is a truthful PASS, not a silent no-op — the same
|
|
923
|
+
// reasoning that exempts fix-plan slugs above. Materially checked
|
|
924
|
+
// against real git state, never inferred from the transcript.
|
|
925
|
+
// (Incident: 655-needs-review-rca-feedback-hook, 2026-07-31 — verified
|
|
926
|
+
// rcaFeedbackHook.cjs shipped in e13168d, ran typecheck + 23 green
|
|
927
|
+
// tests, made no commit, printed a truthful PASS, and was parked
|
|
928
|
+
// anyway.)
|
|
929
|
+
let alreadyShipped = false;
|
|
930
|
+
if (!mergeMainVerified && !priorRunVerified) {
|
|
931
|
+
const deliverablePaths = extractPrdDeliverablePaths(prdBody);
|
|
932
|
+
if (deliverablePaths.length > 0
|
|
933
|
+
&& allDeliverablesAlreadyTracked({ cwd: queueEntry?.cwd, paths: deliverablePaths })) {
|
|
934
|
+
alreadyShipped = true;
|
|
935
|
+
return conclude(
|
|
936
|
+
'pass_no_commit_already_shipped',
|
|
937
|
+
`SCHEDULER_VERDICT: PASS with no commit, but every PRD-named deliverable path is already `
|
|
938
|
+
+ `tracked in git (${deliverablePaths.join(', ')}) — this PRD's work landed in an earlier run`,
|
|
939
|
+
null,
|
|
940
|
+
{
|
|
941
|
+
...(annotations.length ? { annotations } : {}),
|
|
942
|
+
sentinel,
|
|
943
|
+
alreadyTrackedPaths: deliverablePaths,
|
|
944
|
+
},
|
|
945
|
+
);
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
if (!mergeMainVerified && !priorRunVerified && !alreadyShipped) {
|
|
762
949
|
issues.push({
|
|
763
950
|
verdict: 'pass_no_commit',
|
|
764
951
|
reason: 'SCHEDULER_VERDICT: PASS but no commit landed during the run window — the run claims success but produced no code change',
|
|
@@ -830,6 +1017,8 @@ module.exports = {
|
|
|
830
1017
|
// Exposed for unit tests.
|
|
831
1018
|
detectPattern,
|
|
832
1019
|
isHarnessToolError,
|
|
1020
|
+
isSelfRecovered,
|
|
1021
|
+
normalizeDescForRecovery,
|
|
833
1022
|
toolUseName,
|
|
834
1023
|
extractSoakFromBody,
|
|
835
1024
|
parsePrdBodyDepFragments,
|
|
@@ -839,4 +1028,7 @@ module.exports = {
|
|
|
839
1028
|
isMergeMainSlug,
|
|
840
1029
|
extractMergeMainPrNumber,
|
|
841
1030
|
checkMergeablePr,
|
|
1031
|
+
extractPrdDeliverablePaths,
|
|
1032
|
+
allDeliverablesAlreadyTracked,
|
|
1033
|
+
isAncestorCommit,
|
|
842
1034
|
};
|