claude-code-session-manager 0.116.1 → 0.118.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-CkS69HnN.js → AgentLibrary-CEI6vsmu.js} +2 -2
- package/dist/assets/{Button-BQxkfIbW.js → Button-BuyzGzO8.js} +1 -1
- package/dist/assets/{DataModel-YVgdJTZF.js → DataModel-CbXI_rEf.js} +1 -1
- package/dist/assets/{History-DneHh4jI.js → History-axy4nJyY.js} +2 -2
- package/dist/assets/{Hooks-Bt4xIjfo.js → Hooks-D5ghOppL.js} +3 -3
- package/dist/assets/{Library-DoP5FdXq.js → Library-wBI4Fwh3.js} +7 -7
- package/dist/assets/{ListDetail-BYxyl13e.js → ListDetail-CY5hhwn9.js} +1 -1
- package/dist/assets/{McpServers-CKm_iZsf.js → McpServers-CwEVZqfu.js} +2 -2
- package/dist/assets/Memory-Beh2r4Xk.js +25 -0
- package/dist/assets/{Panel-Cv8aYElQ.js → Panel-yOYf80ax.js} +1 -1
- package/dist/assets/{Permissions-CFmWwqyV.js → Permissions-DA-SMXO3.js} +3 -3
- package/dist/assets/{Plugins-CuYNgf_d.js → Plugins-qfKt6BxH.js} +2 -2
- package/dist/assets/ProvenanceBadge-DFyBe7Po.js +1 -0
- package/dist/assets/RemoteMachines-AG0K7kqS.js +3 -0
- package/dist/assets/{SaveBar-Cb4Q8TBK.js → SaveBar-35Nw2YsZ.js} +1 -1
- package/dist/assets/{Scheduler-ysJHQvW4.js → Scheduler-BPBAU0TO.js} +8 -8
- package/dist/assets/{ScopeSwitcher-BTOWfifI.js → ScopeSwitcher-BCXNrrrs.js} +1 -1
- package/dist/assets/{Settings-C6fH8diZ.js → Settings-C4yhzfZI.js} +4 -4
- package/dist/assets/{SkillReferenceGraph-esAcniTQ.js → SkillReferenceGraph-Ca5z2avC.js} +1 -1
- package/dist/assets/{Skills-4_vqzrSF.js → Skills-5WWusmKG.js} +1 -1
- package/dist/assets/SystemPrompt-ofVCdHAW.js +1 -0
- package/dist/assets/TagLibrary-rSrpD-Bz.js +1 -0
- package/dist/assets/{TiptapBody-EkZlmD9k.js → TiptapBody-DAYcdcVt.js} +1 -1
- package/dist/assets/{Toggle-CpR97XGM.js → Toggle-BzHdpXZI.js} +1 -1
- package/dist/assets/{ViewTabs-ekJfFek8.js → ViewTabs-D82jJ4VS.js} +1 -1
- package/dist/assets/{historyTotals-Bg0GVcsF.js → historyTotals-DMGFqK5h.js} +1 -1
- package/dist/assets/{index-CZsDDyLy.css → index-DAMBm3UN.css} +1 -1
- package/dist/assets/{index-DQSEUdx0.js → index-DZ5TJKNI.js} +501 -500
- package/dist/assets/{settingsSchema-BG5jZbhn.js → settingsSchema-BfnNE6ne.js} +1 -1
- package/dist/assets/{skillFrontmatter-DF6xI6Wb.js → skillFrontmatter-ED1DjPE1.js} +1 -1
- package/dist/assets/{useAgentsDirWatch-Dox-xCx9.js → useAgentsDirWatch-CbpQIu3P.js} +1 -1
- package/dist/assets/{useRemotePeers-DHo62LbA.js → useRemotePeers-Dy_o0WlY.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -1
- package/plugins/session-manager-dev/skills/develop/SKILL.md +23 -10
- package/scripts/scheduler-mcp-server.cjs +16 -1
- package/src/main/build-info.json +4 -4
- package/src/main/lib/epic/epicSettle.cjs +105 -0
- package/src/main/lib/epic/epicSweepValidator.cjs +74 -0
- package/src/main/lib/epic/epicValidationHook.cjs +171 -141
- package/src/main/lib/feedback-remote/macroAdminRoutes.cjs +2 -1
- package/src/main/lib/git/gitWorktree.cjs +7 -0
- package/src/main/lib/git/worktreeDeps.cjs +77 -0
- package/src/main/lib/mcpToolCatalog.cjs +6 -2
- package/src/main/lib/memoryMdNetwork.cjs +121 -0
- package/src/main/lib/memoryRefResolve.cjs +64 -0
- package/src/main/lib/memoryStale.cjs +27 -11
- package/src/main/lib/persona-macro/macroLibrary.cjs +49 -2
- package/src/main/lib/scheduler/baselineGate.cjs +128 -0
- package/src/main/lib/scheduler/definitionOfDone.cjs +9 -3
- package/src/main/lib/scheduler/needsReviewLedger.cjs +1 -1
- package/src/main/lib/scheduler/reviewNotice.cjs +53 -0
- package/src/main/memoryAssist.cjs +71 -31
- package/src/main/memoryTool.cjs +18 -6
- package/src/main/promptSessionEvents.cjs +6 -2
- package/src/main/runVerify.cjs +39 -3
- package/src/main/scheduler/reviewLadder.cjs +25 -6
- package/src/main/scheduler.cjs +121 -62
- package/src/main/templates/PRD_AUTHORING.md +2 -0
- package/src/preload/api.d.ts +24 -2
- package/dist/assets/Memory-BWMQuAoC.js +0 -18
- package/dist/assets/ProvenanceBadge-BMLVPyHr.js +0 -1
- package/dist/assets/RemoteMachines-D04Mby_W.js +0 -3
- package/dist/assets/SystemPrompt-CdUq4KXx.js +0 -1
- package/dist/assets/TagLibrary-CVFIphBv.js +0 -1
package/src/main/memoryTool.cjs
CHANGED
|
@@ -32,6 +32,7 @@ const config = require('./config.cjs');
|
|
|
32
32
|
|
|
33
33
|
const { MEMORY_SLUG_RE: SLUG_RE } = require('./lib/memorySlug.cjs');
|
|
34
34
|
const { scoreMemories } = require('./lib/memoryStale.cjs');
|
|
35
|
+
const { makeRefResolver, listTrackedFiles } = require('./lib/memoryRefResolve.cjs');
|
|
35
36
|
const { addIndexLine, removeIndexLines, indexHealth } = require('./lib/memoryIndex.cjs');
|
|
36
37
|
const {
|
|
37
38
|
resolveWorkspaceMemoryDir,
|
|
@@ -318,13 +319,24 @@ async function stale({ workspace, cwd, agent }) {
|
|
|
318
319
|
baseDir = null;
|
|
319
320
|
}
|
|
320
321
|
}
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
322
|
+
let existsPath = () => true;
|
|
323
|
+
if (baseDir) {
|
|
324
|
+
const resolveRef = makeRefResolver({ cwd: baseDir, trackedFiles: await listTrackedFiles(baseDir) });
|
|
325
|
+
existsPath = (relPath, roots = []) => {
|
|
326
|
+
const full = path.resolve(baseDir, relPath);
|
|
327
|
+
if (full !== baseDir && !full.startsWith(baseDir + path.sep)) return false;
|
|
328
|
+
const safeRoots = [];
|
|
329
|
+
for (const root of roots) {
|
|
330
|
+
try {
|
|
331
|
+
// Validate the expanded form so `~/x` is checked against the home boundary too.
|
|
332
|
+
const expanded = root === '~' || root.startsWith('~/') ? path.join(os.homedir(), root.slice(1)) : root;
|
|
333
|
+
config.validatePath(expanded);
|
|
334
|
+
safeRoots.push(root);
|
|
335
|
+
} catch { /* skip an out-of-bounds root */ }
|
|
326
336
|
}
|
|
327
|
-
|
|
337
|
+
return resolveRef(relPath, safeRoots);
|
|
338
|
+
};
|
|
339
|
+
}
|
|
328
340
|
|
|
329
341
|
const entries = [];
|
|
330
342
|
for (const name of names) {
|
|
@@ -69,7 +69,9 @@ function mintEventId() {
|
|
|
69
69
|
* meta arg (not a fifth positional parameter) with `validation`: a
|
|
70
70
|
* scheduler check-in is born `'unvalidated'` — never `'verified'` —
|
|
71
71
|
* regardless of the job's self-reported outcome; the authoring Epic's own
|
|
72
|
-
* validation pass is what may later move it to 'verified'/'refuted'.
|
|
72
|
+
* validation pass is what may later move it to 'verified'/'refuted'. `agentType`
|
|
73
|
+
* (the job's agent) and `digestKey` (the settle digest's stable key) ride the
|
|
74
|
+
* same meta arg and are copied the same way. All
|
|
73
75
|
* keys are added to the event object only when present in `meta`, never as
|
|
74
76
|
* `undefined`-valued keys, so events appended without them serialize
|
|
75
77
|
* identically to before.
|
|
@@ -84,7 +86,7 @@ async function appendResponseEventWithReason(cwd, sourcePromptId, text, meta = {
|
|
|
84
86
|
const data = result.data;
|
|
85
87
|
const session = data.sessions && data.sessions[sourcePromptId];
|
|
86
88
|
if (!session) return { ok: false, reason: 'no-session' };
|
|
87
|
-
if (session.status !== 'active') return { ok: false, reason: 'not-active' };
|
|
89
|
+
if (session.status !== 'active' && !(meta && meta.allowInactive === true)) return { ok: false, reason: 'not-active' };
|
|
88
90
|
const events = (data.events && data.events[sourcePromptId]) || [];
|
|
89
91
|
const tail = events.length > 0 ? events[events.length - 1] : null;
|
|
90
92
|
if (!tail) return { ok: false, reason: 'no-events' };
|
|
@@ -99,6 +101,8 @@ async function appendResponseEventWithReason(cwd, sourcePromptId, text, meta = {
|
|
|
99
101
|
if (meta && meta.prdSlug) event.prdSlug = meta.prdSlug;
|
|
100
102
|
if (meta && meta.outcome) event.outcome = meta.outcome;
|
|
101
103
|
if (meta && meta.validation) event.validation = meta.validation;
|
|
104
|
+
if (meta && meta.agentType) event.agentType = meta.agentType;
|
|
105
|
+
if (meta && meta.digestKey) event.digestKey = meta.digestKey;
|
|
102
106
|
data.events[sourcePromptId] = [...events, event];
|
|
103
107
|
await config.writeJson(path, data, { writer: 'scheduler' });
|
|
104
108
|
broadcast(EVENT_APPENDED_CHANNEL, { cwd, promptSessionId: sourcePromptId, event });
|
package/src/main/runVerify.cjs
CHANGED
|
@@ -526,17 +526,17 @@ function checkDeps(queueEntry, allJobs, prdBody) {
|
|
|
526
526
|
// ─── sentinel scanner ─────────────────────────────────────────────────────────
|
|
527
527
|
|
|
528
528
|
/**
|
|
529
|
-
* Scan for a `SCHEDULER_VERDICT: PASS|FAIL|BLOCKED_BY_FOREIGN_WIP` sentinel
|
|
529
|
+
* Scan for a `SCHEDULER_VERDICT: PASS|FAIL|BLOCKED_BY_FOREIGN_WIP|NEEDS_HUMAN` sentinel
|
|
530
530
|
* line in the run output.
|
|
531
531
|
*
|
|
532
532
|
* Checks `resultEvent.resultText` first (the agent's final message), then the
|
|
533
533
|
* last tool_result content. Anchored to line-start so prose mentioning the
|
|
534
534
|
* string in mid-sentence does not match.
|
|
535
535
|
*
|
|
536
|
-
* Returns 'pass', 'fail', 'blocked_by_foreign_wip', or null.
|
|
536
|
+
* Returns 'pass', 'fail', 'blocked_by_foreign_wip', 'needs_human', or null.
|
|
537
537
|
*/
|
|
538
538
|
function scanSentinel(resultEvent, events) {
|
|
539
|
-
const RE = /^SCHEDULER_VERDICT:\s*(PASS|FAIL|BLOCKED_BY_FOREIGN_WIP)\b/m;
|
|
539
|
+
const RE = /^SCHEDULER_VERDICT:\s*(PASS|FAIL|BLOCKED_BY_FOREIGN_WIP|NEEDS_HUMAN)\b/m;
|
|
540
540
|
|
|
541
541
|
if (resultEvent) {
|
|
542
542
|
const m = resultEvent.resultText.match(RE);
|
|
@@ -577,6 +577,28 @@ function scanSentinelReason(resultEvent, events) {
|
|
|
577
577
|
return '';
|
|
578
578
|
}
|
|
579
579
|
|
|
580
|
+
/**
|
|
581
|
+
* One-line question after `SCHEDULER_VERDICT: NEEDS_HUMAN` (same two-source
|
|
582
|
+
* scan order as scanSentinel). Trimmed, capped at 300 chars; '' when absent.
|
|
583
|
+
*/
|
|
584
|
+
function scanHumanQuestion(resultEvent, events) {
|
|
585
|
+
const RE = /^SCHEDULER_VERDICT:\s*NEEDS_HUMAN\b[ \t]*([^\n]*)/m;
|
|
586
|
+
const clip = (m) => m[1].trim().slice(0, 300);
|
|
587
|
+
if (resultEvent && resultEvent.resultText) {
|
|
588
|
+
const m = resultEvent.resultText.match(RE);
|
|
589
|
+
if (m) return clip(m);
|
|
590
|
+
}
|
|
591
|
+
let lastToolResult = null;
|
|
592
|
+
for (const ev of events) {
|
|
593
|
+
if (ev.kind === 'tool_result') lastToolResult = ev;
|
|
594
|
+
}
|
|
595
|
+
if (lastToolResult && lastToolResult.content) {
|
|
596
|
+
const m = lastToolResult.content.match(RE);
|
|
597
|
+
if (m) return clip(m);
|
|
598
|
+
}
|
|
599
|
+
return '';
|
|
600
|
+
}
|
|
601
|
+
|
|
580
602
|
/**
|
|
581
603
|
* Scan for a `FOREIGN_WIP_PATHS: <comma-separated paths>` evidence line —
|
|
582
604
|
* the mandatory companion to a `SCHEDULER_VERDICT: BLOCKED_BY_FOREIGN_WIP`
|
|
@@ -1121,6 +1143,19 @@ async function verifyRun({
|
|
|
1121
1143
|
);
|
|
1122
1144
|
}
|
|
1123
1145
|
|
|
1146
|
+
// The executor needs a decision/action only the human can take. Parks as
|
|
1147
|
+
// needs_review with the question; every automatic retry rung skips it.
|
|
1148
|
+
if (sentinel === 'needs_human') {
|
|
1149
|
+
const humanQuestion = scanHumanQuestion(resultEvent, events);
|
|
1150
|
+
const kept = [...annotations, ...issues.map((i) => ({ verdict: i.verdict, reason: i.reason }))];
|
|
1151
|
+
return conclude(
|
|
1152
|
+
'needs_human',
|
|
1153
|
+
'executor reported SCHEDULER_VERDICT: NEEDS_HUMAN' + (humanQuestion ? ': ' + humanQuestion : ''),
|
|
1154
|
+
'needs_review',
|
|
1155
|
+
{ sentinel, humanQuestion, ...(kept.length ? { annotations: kept } : {}) },
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1124
1159
|
// Ground truth outranks heuristics: an exit-0 run that landed a commit
|
|
1125
1160
|
// ATTRIBUTABLE TO THIS RUN (spawnJob's own guardHeadBefore/headAtExit
|
|
1126
1161
|
// delta, or the repo-wide committedDuringRun window scan) and never
|
|
@@ -1420,6 +1455,7 @@ module.exports = {
|
|
|
1420
1455
|
parseLog,
|
|
1421
1456
|
scanSentinel,
|
|
1422
1457
|
scanSentinelReason,
|
|
1458
|
+
scanHumanQuestion,
|
|
1423
1459
|
scanForeignWipPathsClaim,
|
|
1424
1460
|
isMergeMainSlug,
|
|
1425
1461
|
extractMergeMainPrNumber,
|
|
@@ -34,9 +34,15 @@ const { DEFAULT_PROJECT_CWD } = require('../lib/scheduler/schedulerBatch.cjs');
|
|
|
34
34
|
* Kill-switch: SM_RESUME_RECOVERY_DISABLE=1 restores today's behaviour
|
|
35
35
|
* exactly (always returns null), mirroring SM_RCA_DISABLE/SM_DOD_DISABLE.
|
|
36
36
|
*/
|
|
37
|
+
/** A row parked on a question only the human can answer — no automatic rung may touch it. */
|
|
38
|
+
function isNeedsHuman(job) {
|
|
39
|
+
return !!job && job.verifierVerdict === 'needs_human';
|
|
40
|
+
}
|
|
41
|
+
|
|
37
42
|
function selectResumeRecoveryTarget(job) {
|
|
38
43
|
if (process.env.SM_RESUME_RECOVERY_DISABLE === '1') return null;
|
|
39
44
|
if (!job || job.status !== 'needs_review') return null;
|
|
45
|
+
if (isNeedsHuman(job)) return null;
|
|
40
46
|
if (job.verifierVerdict !== 'uncommitted_changes') return null;
|
|
41
47
|
if (typeof job.sessionId !== 'string' || job.sessionId.length === 0) return null;
|
|
42
48
|
if (job.resumeRecoveryAttempted === true) return null;
|
|
@@ -100,6 +106,7 @@ function isMechanicalRecoveryFutile(job, currentHeadSha) {
|
|
|
100
106
|
function selectMechanicalRecoveryTarget(job, currentHeadSha = null) {
|
|
101
107
|
if (process.env.SM_MECHANICAL_RECOVERY_DISABLE === '1') return null;
|
|
102
108
|
if (!job || job.status !== 'needs_review') return null;
|
|
109
|
+
if (isNeedsHuman(job)) return null;
|
|
103
110
|
if (!MECHANICALLY_RESOLVABLE_VERDICTS.has(job.verifierVerdict)) return null;
|
|
104
111
|
if (job.mechanicalRecoveryAttempted === true) return null;
|
|
105
112
|
if (isMechanicalRecoveryFutile(job, currentHeadSha)) return null;
|
|
@@ -791,6 +798,7 @@ function selectExhaustedNeedsReviewTargets(jobs, now, thresholdMs) {
|
|
|
791
798
|
const targets = [];
|
|
792
799
|
for (const j of jobs ?? []) {
|
|
793
800
|
if (j.status !== 'needs_review') continue;
|
|
801
|
+
if (isNeedsHuman(j)) continue;
|
|
794
802
|
if (!isEligibleForNeedsReviewAutoResolve(j, jobs)) continue;
|
|
795
803
|
if ((j.exhaustedResolveAttempts ?? 0) > NEEDS_REVIEW_RESOLVE_CAP) continue;
|
|
796
804
|
const history = j.statusHistory || [];
|
|
@@ -833,6 +841,7 @@ function selectAutoFixTargets(jobs, { fixSlugExists, resolveJobRunId = resolveRu
|
|
|
833
841
|
const slugsInQueue = new Set(jobs.map((j) => j.slug));
|
|
834
842
|
return jobs.filter((job) => {
|
|
835
843
|
if (job.status !== 'needs_review') return false;
|
|
844
|
+
if (isNeedsHuman(job)) return false;
|
|
836
845
|
// A job parked here because it was blocked by a sibling's foreign WIP
|
|
837
846
|
// three times in a row (never its own regression) has nothing for a
|
|
838
847
|
// fix-plan investigation to diagnose — there is no code defect to
|
|
@@ -926,7 +935,7 @@ function isEligibleForImmediateAutoFix(job, allJobs, fixSlugExists) {
|
|
|
926
935
|
*
|
|
927
936
|
* Returns the first tier-1 row, else the tier-2 row, else null.
|
|
928
937
|
*/
|
|
929
|
-
function selectGateShadowTarget(jobs, headByCwd, { authorityDisabled = false } = {}) {
|
|
938
|
+
function selectGateShadowTarget(jobs, headByCwd, { authorityDisabled = false, baselineDisabled = process.env.SM_BASELINE_GATE_AUTHORITY_DISABLE === '1' } = {}) {
|
|
930
939
|
const eligible = jobs.filter((j) => {
|
|
931
940
|
if (j.status !== 'needs_review') return false;
|
|
932
941
|
const head = headByCwd.get(j.cwd);
|
|
@@ -938,16 +947,25 @@ function selectGateShadowTarget(jobs, headByCwd, { authorityDisabled = false } =
|
|
|
938
947
|
return !j.gateShadow || j.gateShadow.head !== head || j.gateShadow.definitive !== true;
|
|
939
948
|
};
|
|
940
949
|
|
|
941
|
-
|
|
950
|
+
// Baseline authority: a sentinel_fail park with a landed commit and a known
|
|
951
|
+
// base commit may complete when its red gate was already red at that base.
|
|
952
|
+
const baselineEligible = (j) => !baselineDisabled
|
|
953
|
+
&& j.verifierVerdict === 'sentinel_fail'
|
|
954
|
+
&& typeof j.landedCommit === 'string' && j.landedCommit.length > 0
|
|
955
|
+
&& typeof j.guardHeadBefore === 'string' && j.guardHeadBefore.length > 0;
|
|
956
|
+
|
|
957
|
+
if (!authorityDisabled || !baselineDisabled) {
|
|
942
958
|
const ranAtMs = (j) => {
|
|
943
959
|
const t = j.gateShadow ? Date.parse(j.gateShadow.ranAt) : NaN;
|
|
944
960
|
return Number.isFinite(t) ? t : -Infinity; // never-run or unparseable sorts oldest
|
|
945
961
|
};
|
|
946
962
|
const tier1 = eligible.filter((j) => isStale(j)
|
|
947
|
-
&&
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
963
|
+
&& ((!authorityDisabled
|
|
964
|
+
&& j.verifierVerdict
|
|
965
|
+
&& GATE_AUTHORITY_VERDICTS.includes(j.verifierVerdict)
|
|
966
|
+
&& typeof j.landedCommit === 'string'
|
|
967
|
+
&& j.landedCommit.length > 0)
|
|
968
|
+
|| baselineEligible(j)));
|
|
951
969
|
if (tier1.length) {
|
|
952
970
|
const ranked = tier1
|
|
953
971
|
.map((j, i) => ({ j, i, neverRun: !j.gateShadow, ranAt: ranAtMs(j) }))
|
|
@@ -964,6 +982,7 @@ function selectGateShadowTarget(jobs, headByCwd, { authorityDisabled = false } =
|
|
|
964
982
|
}
|
|
965
983
|
|
|
966
984
|
module.exports = {
|
|
985
|
+
isNeedsHuman,
|
|
967
986
|
selectResumeRecoveryTarget,
|
|
968
987
|
MECHANICALLY_RESOLVABLE_VERDICTS,
|
|
969
988
|
isMechanicalRecoveryFutile,
|
package/src/main/scheduler.cjs
CHANGED
|
@@ -79,6 +79,8 @@ const { sweepStrandedJobBranches } = require('./lib/git/branchSweep.cjs');
|
|
|
79
79
|
const { detectRateLimitInLog } = require('./lib/claude/rateLimitDetect.cjs');
|
|
80
80
|
const { stripAppOwnedChurn } = require('./lib/scheduler/jobDirtFilter.cjs');
|
|
81
81
|
const { GATE_AUTHORITY_VERDICTS, decideGateAuthority } = require('./lib/scheduler/gateAuthority.cjs');
|
|
82
|
+
const { decideBaselineAuthority } = require('./lib/scheduler/baselineGate.cjs');
|
|
83
|
+
const { linkDependencyDirs } = require('./lib/git/worktreeDeps.cjs');
|
|
82
84
|
const { resolveBindingRateLimitReset } = require('./lib/claude/rateLimitWindow.cjs');
|
|
83
85
|
const { isResetFresh, bindingWindow, degradedBudget, meterFailureBudget } = require('./lib/claude/usageCircuit.cjs');
|
|
84
86
|
const { computeQueueHealth } = require('./lib/scheduler/queueHealth.cjs');
|
|
@@ -97,7 +99,8 @@ const sessionsStore = require('./sessionsStore.cjs');
|
|
|
97
99
|
const { enqueueExternalPrompt } = require('./chatRunner.cjs');
|
|
98
100
|
const { appendResponseEventIfKnown, appendResponseEventWithReason } = require('./promptSessionEvents.cjs');
|
|
99
101
|
const { buildReviewNotice, selectDueReviewNotices, formatReviewNotice, holdMsFromEnv } = require('./lib/scheduler/reviewNotice.cjs');
|
|
100
|
-
const
|
|
102
|
+
const epicValidationHook = require('./lib/epic/epicValidationHook.cjs');
|
|
103
|
+
const prdCreate = require('./lib/prd/prdCreate.cjs');
|
|
101
104
|
const { parseValidationSentinels } = require('./lib/scheduler/validationSentinels.cjs');
|
|
102
105
|
const { hasDownstreamValidator } = require('./lib/prd/planValidator.cjs');
|
|
103
106
|
const promptSessionTranscript = require('./promptSessionTranscript.cjs');
|
|
@@ -347,12 +350,18 @@ ${verdictStepNum}. VERDICT LINE — end your final report with exactly one of th
|
|
|
347
350
|
SCHEDULER_VERDICT: PASS
|
|
348
351
|
SCHEDULER_VERDICT: FAIL <one-line reason>
|
|
349
352
|
SCHEDULER_VERDICT: BLOCKED_BY_FOREIGN_WIP
|
|
353
|
+
SCHEDULER_VERDICT: NEEDS_HUMAN <one-line question>
|
|
350
354
|
- Print PASS only when the gate is green AND the commit from step ${commitStepNum} landed.
|
|
351
355
|
- Print FAIL when the gate is red or the commit could not land.
|
|
352
356
|
- Never print PASS on a red gate. Why: a false PASS ships broken work that
|
|
353
357
|
no one notices. A true PASS plus a landed commit lets the scheduler ignore
|
|
354
358
|
harmless noise in your transcript, such as grep hits for "Error", an early
|
|
355
359
|
red test run, or debug tracebacks.
|
|
360
|
+
- Print NEEDS_HUMAN only when finishing needs a decision or action only the
|
|
361
|
+
human can take (credentials, an account/dashboard action, a product
|
|
362
|
+
choice, an install on the host) — never for a red gate or a bug you could
|
|
363
|
+
fix. Commit any finished work first. Phrase the question so a yes/no or
|
|
364
|
+
one-line answer unblocks it.
|
|
356
365
|
- Print BLOCKED_BY_FOREIGN_WIP ONLY when your gate failed because of a
|
|
357
366
|
sibling job's unfinished, uncommitted file — never because of your own
|
|
358
367
|
change — AND every failing path is one this prompt already listed as
|
|
@@ -2664,6 +2673,7 @@ function resetJobFields(job, errorMsg, opts = {}) {
|
|
|
2664
2673
|
job.error = errorMsg ?? null;
|
|
2665
2674
|
delete job.runtime;
|
|
2666
2675
|
delete job.verifierVerdict;
|
|
2676
|
+
delete job.humanQuestion;
|
|
2667
2677
|
delete job.uncommittedPaths;
|
|
2668
2678
|
delete job.resumeRecoveryAttempted;
|
|
2669
2679
|
// Same one-attempt-per-episode category as resumeRecoveryAttempted above —
|
|
@@ -3009,6 +3019,29 @@ async function resolveFixPlanPath(failedJob) {
|
|
|
3009
3019
|
return { fixPath, fixSlug, group, livePrdDir };
|
|
3010
3020
|
}
|
|
3011
3021
|
|
|
3022
|
+
/**
|
|
3023
|
+
* defaultScheduleSettle — production wiring for notifyOriginatingTab's
|
|
3024
|
+
* scheduleSettle option: arms the debounced Epic settle runner with live deps.
|
|
3025
|
+
* `remote` is defined later in the module but only read at call time.
|
|
3026
|
+
*/
|
|
3027
|
+
function defaultScheduleSettle({ cwd, epicId }) {
|
|
3028
|
+
const deps = {
|
|
3029
|
+
loadJobs: async () => (await readQueue()).jobs || [],
|
|
3030
|
+
loadHistory: () => queueHistory.readHistory({ limit: 500 }),
|
|
3031
|
+
createPrd: (input) => prdCreate.createPrd(input, remote),
|
|
3032
|
+
resolveBaseSha: async (c, commit) => {
|
|
3033
|
+
try {
|
|
3034
|
+
return String(await execGitAt(c, ['rev-parse', `${commit}^`])).trim() || null;
|
|
3035
|
+
} catch {
|
|
3036
|
+
return null;
|
|
3037
|
+
}
|
|
3038
|
+
},
|
|
3039
|
+
sendPrompt: enqueueExternalPrompt,
|
|
3040
|
+
appendResponseEvent: appendResponseEventIfKnown,
|
|
3041
|
+
};
|
|
3042
|
+
epicValidationHook.scheduleEpicSettle({ cwd, epicId }, (args) => epicValidationHook.runEpicSettle(args, deps));
|
|
3043
|
+
}
|
|
3044
|
+
|
|
3012
3045
|
/**
|
|
3013
3046
|
* notifyOriginatingTab(job) → void
|
|
3014
3047
|
*
|
|
@@ -3047,16 +3080,19 @@ async function notifyOriginatingTab(job, {
|
|
|
3047
3080
|
appendResponseEvent = appendResponseEventIfKnown,
|
|
3048
3081
|
appendTranscriptTurn = promptSessionTranscript.appendTurn,
|
|
3049
3082
|
readResultFromLog = extractResultTextFromLog,
|
|
3050
|
-
|
|
3051
|
-
// PRD 1408: same queue rows spawnJob/tickQueue already use — read fresh
|
|
3052
|
-
// here (not threaded through the terminal-transition snapshot) since this
|
|
3053
|
-
// fires well after dispatch, when the plan may have gained/dropped a
|
|
3054
|
-
// trailing validator row since this job was spawned.
|
|
3055
|
-
loadJobs = async () => (await readQueue()).jobs || [],
|
|
3083
|
+
scheduleSettle = defaultScheduleSettle,
|
|
3056
3084
|
} = {}) {
|
|
3057
3085
|
try {
|
|
3058
3086
|
const prd = await resolveNotifyPrd(job, parsePrdRaw);
|
|
3059
|
-
|
|
3087
|
+
let message = `PRD ${job.slug} finished: ${job.status}. Check Scheduler for details.`;
|
|
3088
|
+
if (job.verifierVerdict === 'pass_preexisting_gate_red') {
|
|
3089
|
+
const failures = Array.isArray(job.preexistingGateFailures) ? job.preexistingGateFailures : [];
|
|
3090
|
+
const shown = failures.slice(0, 10).join('; ');
|
|
3091
|
+
const more = failures.length > 10 ? ` (+${failures.length - 10} more)` : '';
|
|
3092
|
+
message = `PRD ${job.slug} completed; the gate was already red before this PRD`
|
|
3093
|
+
+ (failures.length ? `. Pre-existing failures: ${shown}${more}` : '')
|
|
3094
|
+
+ '. Check Scheduler for details.';
|
|
3095
|
+
}
|
|
3060
3096
|
|
|
3061
3097
|
// The EPIC this check-in belongs to. Deliberately does NOT consider
|
|
3062
3098
|
// `sourceTabId` — that is a Terminal tab id, not an Epic id, and
|
|
@@ -3157,55 +3193,22 @@ async function notifyOriginatingTab(job, {
|
|
|
3157
3193
|
prdSlug: job.slug,
|
|
3158
3194
|
outcome: job.status,
|
|
3159
3195
|
validation: 'unvalidated',
|
|
3196
|
+
...(typeof job.agentType === 'string' ? { agentType: job.agentType } : {}),
|
|
3160
3197
|
}).catch((e) => {
|
|
3161
3198
|
console.error('[scheduler] notifyOriginatingTab appendResponseEvent error', job?.slug, e);
|
|
3162
3199
|
return false;
|
|
3163
3200
|
});
|
|
3164
3201
|
if (routed) {
|
|
3165
|
-
//
|
|
3166
|
-
//
|
|
3167
|
-
//
|
|
3168
|
-
//
|
|
3169
|
-
//
|
|
3170
|
-
//
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
//
|
|
3176
|
-
// PRD 1407: a validator job's own check-in never enqueues a
|
|
3177
|
-
// validation prompt for itself — it IS the plan's validation pass,
|
|
3178
|
-
// not a work-item awaiting one.
|
|
3179
|
-
//
|
|
3180
|
-
// PRD 1408: nor does an ordinary work-item whose plan already ends in
|
|
3181
|
-
// a trailing validator job depending on it — that validator's own
|
|
3182
|
-
// check-in (above) is the single validation pass for the whole plan,
|
|
3183
|
-
// so a per-PRD prompt here would be a second, redundant ask into the
|
|
3184
|
-
// same (Opus/Fable-tier) authoring session.
|
|
3185
|
-
if (job.agentType !== 'validator') {
|
|
3186
|
-
let skipForDownstreamValidator = false;
|
|
3187
|
-
try {
|
|
3188
|
-
skipForDownstreamValidator = hasDownstreamValidator(job, await loadJobs());
|
|
3189
|
-
} catch (e) {
|
|
3190
|
-
console.error('[scheduler] notifyOriginatingTab loadJobs error', job?.slug, e);
|
|
3191
|
-
}
|
|
3192
|
-
if (!skipForDownstreamValidator) {
|
|
3193
|
-
try {
|
|
3194
|
-
enqueueValidation(
|
|
3195
|
-
{
|
|
3196
|
-
cwd: job.cwd || null,
|
|
3197
|
-
epicId,
|
|
3198
|
-
prdSlug: job.slug,
|
|
3199
|
-
prdPath: prd?.path || prdPaths.archivedPrdPathForJob(job) || null,
|
|
3200
|
-
outcome: job.status,
|
|
3201
|
-
eventValidation: 'unvalidated',
|
|
3202
|
-
},
|
|
3203
|
-
{ sendPrompt },
|
|
3204
|
-
);
|
|
3205
|
-
} catch (e) {
|
|
3206
|
-
console.error('[scheduler] notifyOriginatingTab enqueueValidation error', job?.slug, e);
|
|
3207
|
-
}
|
|
3208
|
-
}
|
|
3202
|
+
// The debounced Epic settle runner (lib/epic/epicValidationHook.cjs)
|
|
3203
|
+
// owns all validation messaging now (replaces the per-PRD prompts of
|
|
3204
|
+
// PRDs 986/1407/1408): every successful terminal check-in, a
|
|
3205
|
+
// validator's own included (its finish usually settles the Epic),
|
|
3206
|
+
// just re-arms the per-Epic debounce. A finished PRD never sends a
|
|
3207
|
+
// chat prompt into its authoring Epic by itself.
|
|
3208
|
+
try {
|
|
3209
|
+
scheduleSettle({ cwd: job.cwd || null, epicId });
|
|
3210
|
+
} catch (e) {
|
|
3211
|
+
console.error('[scheduler] notifyOriginatingTab scheduleSettle error', job?.slug, e);
|
|
3209
3212
|
}
|
|
3210
3213
|
return;
|
|
3211
3214
|
}
|
|
@@ -3383,6 +3386,7 @@ async function flushDueReviewNotices({
|
|
|
3383
3386
|
prdSlug: group.jobs[0].slug,
|
|
3384
3387
|
outcome: 'needs_review',
|
|
3385
3388
|
validation: 'unvalidated',
|
|
3389
|
+
allowInactive: true,
|
|
3386
3390
|
});
|
|
3387
3391
|
outcome = typeof result === 'boolean' ? { ok: result, reason: result ? undefined : 'refused' } : result;
|
|
3388
3392
|
} catch (e) {
|
|
@@ -6438,17 +6442,10 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
|
|
|
6438
6442
|
let mechanicalRecoveryTarget = null;
|
|
6439
6443
|
let terminalNotifySnapshot = null;
|
|
6440
6444
|
const newlyCompletedPrds = [];
|
|
6441
|
-
// PRD 1408: captured inside the mutate() below (the one in-memory read of
|
|
6442
|
-
// `s.jobs` this finalize pass already does) so notifyOriginatingTab's own
|
|
6443
|
-
// hasDownstreamValidator check can reuse it instead of a second, redundant
|
|
6444
|
-
// readQueue() — that call federates queue.json shards across every known
|
|
6445
|
-
// project cwd on the machine, not just this job's own project.
|
|
6446
|
-
let jobsSnapshotForNotify = null;
|
|
6447
6445
|
// Base HEAD for the mechanical-recovery futility check — read here, never inside mutate() (no I/O).
|
|
6448
6446
|
const finalizeHeadSha = worktreeIntegrationDetail && worktreeIntegrationDetail.failureKind === 'content_conflict'
|
|
6449
6447
|
? await gitTreeGuard.gitHead(guardCwd) : null;
|
|
6450
6448
|
await mutate((s) => {
|
|
6451
|
-
jobsSnapshotForNotify = s.jobs.slice();
|
|
6452
6449
|
const i2 = s.jobs.findIndex((x) => x.slug === job.slug);
|
|
6453
6450
|
// A job already moved off 'running' by someone else (namely
|
|
6454
6451
|
// remote.cancelJob, PRD 1024 — it SIGTERMs the process then finalizes
|
|
@@ -6637,6 +6634,11 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
|
|
|
6637
6634
|
} else if (!['blocked_by_foreign_wip_streak', 'budget_exceeded'].includes(s.jobs[i2].verifierVerdict)) {
|
|
6638
6635
|
delete s.jobs[i2].verifierVerdict;
|
|
6639
6636
|
}
|
|
6637
|
+
if (verifyResult?.verdict === 'needs_human') {
|
|
6638
|
+
s.jobs[i2].humanQuestion = verifyResult.humanQuestion || '';
|
|
6639
|
+
} else {
|
|
6640
|
+
delete s.jobs[i2].humanQuestion;
|
|
6641
|
+
}
|
|
6640
6642
|
stampIntegrationFailure(s.jobs[i2], worktreeIntegrationFailure ? worktreeIntegrationDetail : null);
|
|
6641
6643
|
transitionJob(s.jobs[i2], effectiveStatus, { reason: finalizeReason, source: 'spawnJob:finalize' });
|
|
6642
6644
|
s.jobs[i2].finishedAt = new Date().toISOString();
|
|
@@ -6860,9 +6862,7 @@ async function spawnJob(job, runId, runDir, defaultCwd, resumeTarget = null, sib
|
|
|
6860
6862
|
});
|
|
6861
6863
|
|
|
6862
6864
|
if (terminalNotifySnapshot) {
|
|
6863
|
-
notifyOriginatingTab(terminalNotifySnapshot
|
|
6864
|
-
loadJobs: async () => jobsSnapshotForNotify || [],
|
|
6865
|
-
}).catch((e) => {
|
|
6865
|
+
notifyOriginatingTab(terminalNotifySnapshot).catch((e) => {
|
|
6866
6866
|
console.error('[scheduler] notifyOriginatingTab error', job.slug, e);
|
|
6867
6867
|
});
|
|
6868
6868
|
}
|
|
@@ -8793,6 +8793,52 @@ async function runGateShadow(job) {
|
|
|
8793
8793
|
gateShadow.authority = decision;
|
|
8794
8794
|
}
|
|
8795
8795
|
|
|
8796
|
+
// Baseline authority: a sentinel_fail park whose gate is red at HEAD may
|
|
8797
|
+
// still complete when every HEAD failure already failed at the job's base
|
|
8798
|
+
// commit. The base run is memoized per HEAD on gateShadow.baseline.
|
|
8799
|
+
let baselineDecision = null;
|
|
8800
|
+
if (
|
|
8801
|
+
job.verifierVerdict === 'sentinel_fail'
|
|
8802
|
+
&& outcome.status === 'red' && definitive
|
|
8803
|
+
&& typeof job.landedCommit === 'string' && job.landedCommit.length > 0
|
|
8804
|
+
&& typeof job.guardHeadBefore === 'string' && job.guardHeadBefore.length > 0
|
|
8805
|
+
&& process.env.SM_BASELINE_GATE_AUTHORITY_DISABLE !== '1'
|
|
8806
|
+
) {
|
|
8807
|
+
const startedAtMs = typeof job.startedAt === 'string' ? Date.parse(job.startedAt) : NaN;
|
|
8808
|
+
let evidenceOk = false;
|
|
8809
|
+
if (Number.isFinite(startedAtMs)) {
|
|
8810
|
+
try { evidenceOk = await resolveLandedCommitEvidence(job.cwd, job.landedCommit, job.startedAt); } catch { evidenceOk = false; }
|
|
8811
|
+
}
|
|
8812
|
+
let ancestorOk = false;
|
|
8813
|
+
try {
|
|
8814
|
+
await execGitAt(job.cwd, ['merge-base', '--is-ancestor', job.landedCommit, headBefore], { timeout: 10_000 });
|
|
8815
|
+
ancestorOk = true;
|
|
8816
|
+
} catch { ancestorOk = false; }
|
|
8817
|
+
const memo = job.gateShadow?.baseline;
|
|
8818
|
+
let baseOutcome = memo && memo.head === headBefore && memo.outcome ? memo.outcome : null;
|
|
8819
|
+
if (!baseOutcome) {
|
|
8820
|
+
const scratch = path.join(os.tmpdir(), `sm-baseline-gate-${process.pid}-${Date.now()}`);
|
|
8821
|
+
let added = false;
|
|
8822
|
+
try {
|
|
8823
|
+
await execGitAt(job.cwd, ['worktree', 'add', '--detach', scratch, job.guardHeadBefore], { timeout: 120_000 });
|
|
8824
|
+
added = true;
|
|
8825
|
+
await linkDependencyDirs({ mainCwd: job.cwd, worktreeCwd: scratch });
|
|
8826
|
+
const r = await runGateSequence(gate.sequence, { cwd: scratch });
|
|
8827
|
+
baseOutcome = r.status === 'busy' ? { status: 'unavailable', reason: 'busy', results: [] } : r;
|
|
8828
|
+
} catch (e) {
|
|
8829
|
+
baseOutcome = { status: 'unavailable', reason: `base-gate-error: ${e && e.message ? e.message : e}`, results: [] };
|
|
8830
|
+
} finally {
|
|
8831
|
+
if (added) {
|
|
8832
|
+
try { await execGitAt(job.cwd, ['worktree', 'remove', '--force', scratch], { timeout: 60_000 }); } catch { /* best-effort */ }
|
|
8833
|
+
}
|
|
8834
|
+
try { fs.rmSync(scratch, { recursive: true, force: true }); } catch { /* best-effort */ }
|
|
8835
|
+
try { await execGitAt(job.cwd, ['worktree', 'prune'], { timeout: 30_000 }); } catch { /* best-effort */ }
|
|
8836
|
+
}
|
|
8837
|
+
}
|
|
8838
|
+
baselineDecision = decideBaselineAuthority({ job, headOutcome: outcome, baseOutcome, evidenceOk, ancestorOk, cleanOk, headStable });
|
|
8839
|
+
gateShadow.baseline = { head: headBefore, base: job.guardHeadBefore, outcome: baseOutcome, ...baselineDecision };
|
|
8840
|
+
}
|
|
8841
|
+
|
|
8796
8842
|
const runId = job.runId || reviewLadder.resolveRunId(job);
|
|
8797
8843
|
if (runId) {
|
|
8798
8844
|
const verdictsPath = path.join(schedulerPaths.runsDir(), runId, `${job.slug}.verdicts.json`);
|
|
@@ -8827,6 +8873,19 @@ async function runGateShadow(job) {
|
|
|
8827
8873
|
continue;
|
|
8828
8874
|
}
|
|
8829
8875
|
j.gateShadow = gateShadow;
|
|
8876
|
+
if (baselineDecision?.complete) {
|
|
8877
|
+
const v = j.verifierVerdict;
|
|
8878
|
+
transitionJob(j, 'completed', {
|
|
8879
|
+
reason: `gate red at ${headShort} but identical to base ${String(job.guardHeadBefore).slice(0, 7)}; verdict ${v} overridden`,
|
|
8880
|
+
source: 'gateAuthority:baseline',
|
|
8881
|
+
});
|
|
8882
|
+
j.error = null;
|
|
8883
|
+
j.verifierVerdict = 'pass_preexisting_gate_red';
|
|
8884
|
+
j.preexistingGateFailures = baselineDecision.preexisting;
|
|
8885
|
+
delete j.looksDone;
|
|
8886
|
+
resolved = { slug: j.slug, cwd: j.cwd, verdict: v };
|
|
8887
|
+
continue;
|
|
8888
|
+
}
|
|
8830
8889
|
if (!decision?.complete) continue;
|
|
8831
8890
|
const v = j.verifierVerdict;
|
|
8832
8891
|
transitionJob(j, 'completed', {
|
|
@@ -233,3 +233,5 @@ Before queueing a new PRD, verify each of these:
|
|
|
233
233
|
- [ ] **§10 Negative assertions:** every "should find nothing" check is inverted to exit 0 on the clean case.
|
|
234
234
|
- [ ] **§11 End green:** the gate runs last; any intentional failure runs early and is captured.
|
|
235
235
|
- [ ] **§12 No stranded probes:** a probe error is corrected or annotated; an expected `timeout` cap branches on exit 124.
|
|
236
|
+
- [ ] **Gate baseline:** every gate command ran green once at HEAD in the main checkout; a red suite is scoped to the PRD's own test files and the PRD notes say why. Why: a gate already red on base parks as `sentinel_fail`.
|
|
237
|
+
- [ ] **No human-only gates:** no gate or criterion needs a human or the passage of time (cron install, live post wait, live payment, dashboard action); those end `SCHEDULER_VERDICT: NEEDS_HUMAN <question>`. Why: no executor can pass them.
|
package/src/preload/api.d.ts
CHANGED
|
@@ -259,6 +259,15 @@ export interface McpCatalogResult {
|
|
|
259
259
|
* same concept, not a free-form string list. */
|
|
260
260
|
export type AgentPersonaTag = 'feature' | 'bug' | 'discussion' | 'build' | 'project-home-builder';
|
|
261
261
|
|
|
262
|
+
/** One declared `{{name}}` argument of a macro prompt (macroLibrary.cjs `MacroInputSchema`). */
|
|
263
|
+
export interface MacroInput {
|
|
264
|
+
name: string;
|
|
265
|
+
label: string;
|
|
266
|
+
default?: string;
|
|
267
|
+
required?: boolean;
|
|
268
|
+
options?: string[];
|
|
269
|
+
}
|
|
270
|
+
|
|
262
271
|
/** One user-defined sidebar HOT KEYS macro (macroLibrary.cjs). `projects` = absolute cwds or `'*'` (every project). */
|
|
263
272
|
export interface Macro {
|
|
264
273
|
id: string;
|
|
@@ -275,6 +284,7 @@ export interface Macro {
|
|
|
275
284
|
builtinVersion?: number;
|
|
276
285
|
createdAt: string;
|
|
277
286
|
updatedAt: string;
|
|
287
|
+
inputs?: MacroInput[];
|
|
278
288
|
}
|
|
279
289
|
|
|
280
290
|
/** Omit `id` to create; set it to update an existing macro. */
|
|
@@ -286,6 +296,8 @@ export interface MacroSaveInput {
|
|
|
286
296
|
prompt: string;
|
|
287
297
|
projects?: string[];
|
|
288
298
|
surface?: 'sessions' | 'project-home';
|
|
299
|
+
/** Omitted keeps the stored list on update; `[]` clears it. */
|
|
300
|
+
inputs?: MacroInput[];
|
|
289
301
|
}
|
|
290
302
|
|
|
291
303
|
/** One global `~/.claude/agents/<name>.md` persona, per the Agent Library nav page. */
|
|
@@ -1347,6 +1359,16 @@ export interface MemoryStaleEntry {
|
|
|
1347
1359
|
danglingLinks: string[];
|
|
1348
1360
|
}
|
|
1349
1361
|
|
|
1362
|
+
export type MemoryFixProposal = {
|
|
1363
|
+
ok: boolean;
|
|
1364
|
+
action: 'update' | 'delete' | 'fold' | 'keep';
|
|
1365
|
+
summary: string;
|
|
1366
|
+
reason: string;
|
|
1367
|
+
content: string;
|
|
1368
|
+
fold: { target: string; anchor: string; insert: string } | null;
|
|
1369
|
+
error?: string;
|
|
1370
|
+
};
|
|
1371
|
+
|
|
1350
1372
|
export interface MemoryStaleResult {
|
|
1351
1373
|
entries: MemoryStaleEntry[];
|
|
1352
1374
|
workspace: string;
|
|
@@ -2131,9 +2153,9 @@ export interface SessionManagerAPI {
|
|
|
2131
2153
|
/** Gated `claude -p` question over the memory folder. */
|
|
2132
2154
|
ask: (input: { question: string; target?: MemoryTarget; contextName?: string }) =>
|
|
2133
2155
|
Promise<{ ok: boolean; answer: string; citations: string[]; error?: string }>;
|
|
2134
|
-
/** Gated `claude -p`
|
|
2156
|
+
/** Gated `claude -p` triage of a memory against the md network: update, delete, fold into a CLAUDE.md, or keep; requires `target.cwd`. */
|
|
2135
2157
|
proposeFix: (input: { name: string; target?: MemoryTarget }) =>
|
|
2136
|
-
Promise<
|
|
2158
|
+
Promise<MemoryFixProposal>;
|
|
2137
2159
|
};
|
|
2138
2160
|
projectBrief: {
|
|
2139
2161
|
/** Read brief.json (or null if none yet) plus cheaply-computed source/drift metadata. Never fires an LLM call. */
|