claude-code-session-manager 0.57.0 → 0.58.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/{TiptapBody-B1llI4h-.js → TiptapBody-BEBLdJl_.js} +1 -1
- package/dist/assets/index-DEQzGYa6.js +3227 -0
- package/dist/assets/{index-WUc_GHMf.css → index-DwUffaDq.css} +1 -1
- package/dist/index.html +2 -2
- package/package.json +1 -2
- package/plugins/session-manager-dev/skills/develop/SKILL.md +7 -6
- package/plugins/session-manager-dev/skills/develop/standards.md +1 -1
- package/plugins/session-manager-dev/skills/explain-to-me/SKILL.md +1 -1
- package/plugins/session-manager-dev/skills/find-opportunity/SKILL.md +1 -1
- package/plugins/session-manager-dev/skills/ops-sweep/SKILL.md +16 -25
- package/plugins/session-manager-dev/skills/project-status/SKILL.md +5 -5
- package/scripts/lib/watchdogHelpers.cjs +0 -434
- package/scripts/mint-epic.cjs +6 -6
- package/src/main/__tests__/activeIndexMerge.test.cjs +2 -2
- package/src/main/__tests__/agentModelResolve.test.cjs +119 -0
- package/src/main/__tests__/epicContextDigest.test.cjs +100 -0
- package/src/main/__tests__/epicMint.test.cjs +165 -207
- package/src/main/__tests__/promptSessionSchema.test.cjs +101 -0
- package/src/main/__tests__/promptSessionsCreateEpicHandler.test.cjs +139 -0
- package/src/main/__tests__/queueHistory.test.cjs +14 -0
- package/src/main/__tests__/rcaReport.test.cjs +188 -0
- package/src/main/__tests__/runVerify.test.cjs +109 -6
- package/src/main/__tests__/scheduler-epic-digest.test.cjs +214 -0
- package/src/main/__tests__/scheduler-writeprd-epic-rollback.test.cjs +5 -5
- package/src/main/chatRunner.cjs +10 -1
- package/src/main/index.cjs +2 -4
- package/src/main/ipcSchemas.cjs +47 -2
- package/src/main/lib/agentModelResolve.cjs +97 -0
- package/src/main/lib/auditLog.cjs +2 -2
- package/src/main/lib/epicContextDigest.cjs +78 -0
- package/src/main/lib/epicMint.cjs +81 -164
- package/src/main/lib/promptSessionSchema.cjs +79 -0
- package/src/main/lib/promptSessionsCreateEpic.cjs +68 -0
- package/src/main/lib/queueHistory.cjs +14 -2
- package/src/main/lib/{rcaFeedbackHook.cjs → rcaReport.cjs} +47 -114
- package/src/main/runVerify.cjs +17 -2
- package/src/main/scheduler.cjs +79 -24
- package/src/preload/api.d.ts +25 -48
- package/src/preload/index.cjs +6 -7
- package/dist/assets/index-DdVGnaCd.js +0 -3227
- package/plugins/session-manager-dev/skills/propose-epic/SKILL.md +0 -68
- package/scripts/propose-epic.cjs +0 -56
- package/src/main/__tests__/rcaFeedbackHook.test.cjs +0 -280
- package/src/main/repoAnalyzer.cjs +0 -346
- package/src/main/search.cjs +0 -332
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* rcaReport.cjs — writes a deterministic Root Cause Analysis report into the
|
|
5
|
+
* run directory when a scheduler job is parked in `needs_review`.
|
|
6
6
|
*
|
|
7
7
|
* Mirrors the dodDrainHook.cjs pattern: fire-and-forget, idempotent,
|
|
8
8
|
* kill-switched, never throws to the caller. Unlike the DoD gate this fires
|
|
9
9
|
* per-job at the needs_review transition, not at queue drain.
|
|
10
10
|
*
|
|
11
|
+
* This module creates NOTHING but a markdown file next to the run's own log.
|
|
12
|
+
* It used to file a `proposed` Epic per parked job; Epics are now created in
|
|
13
|
+
* exactly one place — the human pressing New Epic (see epicMint.cjs's
|
|
14
|
+
* SINGLE-CREATOR LAW). Surfacing the parked job back to a human is the
|
|
15
|
+
* scheduler's job (notifyNeedsReview → appendResponseEventIfKnown), which
|
|
16
|
+
* routes it into the Epic that AUTHORED the PRD rather than opening a
|
|
17
|
+
* sibling Epic about it.
|
|
18
|
+
*
|
|
11
19
|
* No LLM calls live here — the RCA body is assembled deterministically from
|
|
12
20
|
* the run's meta.json, log tail, and the original PRD's acceptance-criteria
|
|
13
21
|
* section. When an Opus auto-fix investigation later runs (spawnInvestigation
|
|
@@ -17,34 +25,17 @@
|
|
|
17
25
|
*
|
|
18
26
|
* Kill-switch: SM_RCA_DISABLE=1 (mirrors SM_DOD_DISABLE / SM_SUPERVISOR_DISABLE).
|
|
19
27
|
*
|
|
20
|
-
* Electron-free by design (no require('electron')) so tests stay hermetic
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* runtime, and this lib never touches ipcMain — only the atomic-write /
|
|
24
|
-
* path-validation helpers.
|
|
28
|
+
* Electron-free by design (no require('electron')) so tests stay hermetic —
|
|
29
|
+
* it only reads run artifacts and writes one markdown file into the run dir
|
|
30
|
+
* the caller already owns.
|
|
25
31
|
*/
|
|
26
32
|
|
|
27
33
|
const fs = require('node:fs');
|
|
28
|
-
const os = require('node:os');
|
|
29
34
|
const path = require('node:path');
|
|
30
|
-
const config = require('../config.cjs');
|
|
31
|
-
const { ensureEpic, findJoinableEpic } = require('./epicMint.cjs');
|
|
32
35
|
const { splitFrontmatter } = require('./prdFrontmatter.cjs');
|
|
33
36
|
const { readTail } = require('./fileTail.cjs');
|
|
34
37
|
const { resolvePrdWriteDir } = require('./prdLocations.cjs');
|
|
35
38
|
|
|
36
|
-
// The one project this hook always knows how to reach, used as the fallback
|
|
37
|
-
// destination when a job's own cwd has no feedback inbox.
|
|
38
|
-
const SM_REPO_ROOT = path.join(os.homedir(), 'Projects', 'session-manager');
|
|
39
|
-
|
|
40
|
-
// NEVER write here: ~/.claude/session-manager/feedback/ is the scheduler's
|
|
41
|
-
// auto-PRD intake (src/main/scheduler.cjs's periodic feedback sweep) — it
|
|
42
|
-
// converts anything dropped there into a scheduled PRD automatically. Filing
|
|
43
|
-
// an RCA there would create an RCA → auto-PRD → run → needs_review → RCA
|
|
44
|
-
// cycle with no human in the loop. Both real destinations below are project
|
|
45
|
-
// feedback INBOXES (consumed by /process-feedback), which is a different,
|
|
46
|
-
// human/agent-triaged path.
|
|
47
|
-
|
|
48
39
|
// ─── VERDICT_LABELS — deliberate duplicate ──────────────────────────────────
|
|
49
40
|
// Mirrors src/renderer/components/tabs/scheduler/sched-primitives.tsx:123-131.
|
|
50
41
|
// The main process cannot import renderer TSX, so this vocabulary is kept in
|
|
@@ -197,41 +188,12 @@ function extractAcceptanceCriteria(prdBody) {
|
|
|
197
188
|
return text || null;
|
|
198
189
|
}
|
|
199
190
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
* Resolve which PROJECT the proposal is filed into.
|
|
204
|
-
* 1. the job's own cwd — Epics live in every project, so this is almost
|
|
205
|
-
* always the answer.
|
|
206
|
-
* 2. else session-manager's own repo, naming the target project in the body.
|
|
207
|
-
*
|
|
208
|
-
* The old version probed for a `session-manager-operations/feedback/`
|
|
209
|
-
* directory and fell back when it was missing; with proposals there is
|
|
210
|
-
* nothing to probe for — the Epic store is created on demand.
|
|
211
|
-
*/
|
|
212
|
-
function resolveDestination(job) {
|
|
213
|
-
const jobCwd = job?.cwd;
|
|
214
|
-
if (jobCwd) {
|
|
215
|
-
return { cwd: jobCwd, allowlistRoot: jobCwd, targetProjectNote: null };
|
|
216
|
-
}
|
|
217
|
-
return {
|
|
218
|
-
cwd: SM_REPO_ROOT,
|
|
219
|
-
allowlistRoot: SM_REPO_ROOT,
|
|
220
|
-
targetProjectNote: "Filed into session-manager's own project (job had no cwd on record).",
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
/** One-line title for the proposal — goalText is what the Epics list shows,
|
|
225
|
-
* so it must stay short; the full RCA markdown rides in openingPrompt. */
|
|
226
|
-
function rcaProposalTitle(job, verdict) {
|
|
191
|
+
/** One-line summary of the parked job — used as the report's title and as the
|
|
192
|
+
* text the scheduler surfaces back into the Epic that authored the PRD. */
|
|
193
|
+
function rcaSummaryLine(job, verdict) {
|
|
227
194
|
return `PRD ${job?.slug ?? 'unknown'} needs review: ${humanVerdict(verdict)}`;
|
|
228
195
|
}
|
|
229
196
|
|
|
230
|
-
function shortRunId(runId) {
|
|
231
|
-
const cleaned = String(runId ?? '').replace(/[^a-zA-Z0-9]/g, '');
|
|
232
|
-
return cleaned.slice(0, 12) || 'norun';
|
|
233
|
-
}
|
|
234
|
-
|
|
235
197
|
function lastNLines(text, n) {
|
|
236
198
|
const lines = (text || '').split('\n');
|
|
237
199
|
return lines.slice(-n).join('\n');
|
|
@@ -261,7 +223,7 @@ function readPrdBody(cwd, slug) {
|
|
|
261
223
|
* Assemble the deterministic RCA markdown body (frontmatter + sections).
|
|
262
224
|
* Pure — no I/O, no LLM.
|
|
263
225
|
*/
|
|
264
|
-
function buildRcaMarkdown({ job, verdict, meta, logTail, acText, failureClass, investigationText
|
|
226
|
+
function buildRcaMarkdown({ job, verdict, meta, logTail, acText, failureClass, investigationText }) {
|
|
265
227
|
const title = `Root cause: ${job.slug} → needs_review (${humanVerdict(verdict)})`;
|
|
266
228
|
const fm = ['---', `title: ${title}`, 'source: scheduler-rca', 'type: rca', 'severity: normal', `prdSlug: ${job.slug}`, `runId: ${job.runId}`, `verdict: ${verdict}`, '---'].join('\n');
|
|
267
229
|
|
|
@@ -277,21 +239,21 @@ function buildRcaMarkdown({ job, verdict, meta, logTail, acText, failureClass, i
|
|
|
277
239
|
`# The PRD's acceptance criteria\n\n${acText || '(acceptance criteria not found — original PRD may have been archived or removed)'}`,
|
|
278
240
|
`# Likely failure class\n\n**${failureClass}**\n\nPrevention: ${prevention}`,
|
|
279
241
|
];
|
|
280
|
-
if (targetProjectNote) sections.push(`# Note\n\n${targetProjectNote}`);
|
|
281
242
|
if (investigationText) sections.push(`## Investigation analysis\n\n${investigationText.trim()}`);
|
|
282
243
|
|
|
283
244
|
return `${fm}\n\n${sections.join('\n\n')}\n`;
|
|
284
245
|
}
|
|
285
246
|
|
|
286
247
|
/**
|
|
287
|
-
*
|
|
248
|
+
* Write (or refresh) the deterministic RCA report for a needs_review job,
|
|
249
|
+
* as `<runDir>/root-cause-<slug>.md` — next to the run's own log, the same
|
|
250
|
+
* place the definition-of-done gate writes its report (dodDrainHook.cjs).
|
|
288
251
|
* Never throws — logs one line on success/skip/error and always resolves.
|
|
289
252
|
*
|
|
290
|
-
* Idempotency:
|
|
291
|
-
* A second call for the same
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
* duplicate. A different runId (new run of the same slug) files a new RCA.
|
|
253
|
+
* Idempotency: one report per run directory per slug, and a run dir is
|
|
254
|
+
* per-(runId). A second call for the same run overwrites it in place, which
|
|
255
|
+
* is exactly what the investigation-enrichment pass wants (it re-renders the
|
|
256
|
+
* same body plus an "Investigation analysis" section) — never a duplicate.
|
|
295
257
|
*
|
|
296
258
|
* @param {object} opts
|
|
297
259
|
* @param {{slug: string, cwd?: string, runId: string, exitCode?: number, error?: string, epicId?: string}} opts.job
|
|
@@ -300,10 +262,9 @@ function buildRcaMarkdown({ job, verdict, meta, logTail, acText, failureClass, i
|
|
|
300
262
|
* @param {Array} [opts.annotations] Non-blocking verifier annotations (currently unused
|
|
301
263
|
* in the body beyond being accepted for future use).
|
|
302
264
|
* @param {string} [opts.investigationText] <RCA> block text from an Opus investigation.
|
|
303
|
-
* @
|
|
304
|
-
* @returns {Promise<{filed: boolean, path?: string, updated?: boolean, reason?: string}>}
|
|
265
|
+
* @returns {Promise<{filed: boolean, path?: string, summary?: string, failureClass?: string, reason?: string}>}
|
|
305
266
|
*/
|
|
306
|
-
async function
|
|
267
|
+
async function writeRcaReport({ job, runDir, verdict, annotations, investigationText } = {}) {
|
|
307
268
|
try {
|
|
308
269
|
if (process.env.SM_RCA_DISABLE === '1') {
|
|
309
270
|
console.log('[rca] skip: SM_RCA_DISABLE=1');
|
|
@@ -327,7 +288,10 @@ async function fileRcaFeedback({ job, runDir, verdict, annotations, investigatio
|
|
|
327
288
|
return { filed: false, reason: 'no-verdict' };
|
|
328
289
|
}
|
|
329
290
|
|
|
330
|
-
|
|
291
|
+
if (!runDir) {
|
|
292
|
+
console.log(`[rca] skip: no run directory for ${job.slug}`);
|
|
293
|
+
return { filed: false, reason: 'no-run-dir' };
|
|
294
|
+
}
|
|
331
295
|
|
|
332
296
|
const meta = readRunMeta(runDir, job.slug);
|
|
333
297
|
const logPath = runDir ? path.join(runDir, `${job.slug}.log`) : null;
|
|
@@ -336,67 +300,36 @@ async function fileRcaFeedback({ job, runDir, verdict, annotations, investigatio
|
|
|
336
300
|
const failureClass = classifyFailure({ verdict, logTail });
|
|
337
301
|
|
|
338
302
|
const markdown = buildRcaMarkdown({
|
|
339
|
-
job, verdict, meta, logTail, acText, failureClass, investigationText,
|
|
340
|
-
targetProjectNote: dest.targetProjectNote, annotations,
|
|
341
|
-
});
|
|
342
|
-
|
|
343
|
-
// File it as a PROPOSED Epic rather than a markdown file in a feedback
|
|
344
|
-
// inbox. The proposal lands directly in the Epics workspace behind an
|
|
345
|
-
// Approve & start gate: nothing runs and nothing is spent until a human
|
|
346
|
-
// approves it. This replaced the feedback-folder intake (and the
|
|
347
|
-
// process-feedback triage pass that existed only to read it back out) —
|
|
348
|
-
// the proposal IS the work item, already carrying its own session and
|
|
349
|
-
// PRD directory. reuseByGoal makes a re-trigger for the same failure
|
|
350
|
-
// join the existing proposal instead of duplicating it, which is what
|
|
351
|
-
// the old live/processed existence checks were for.
|
|
352
|
-
config.addAllowedRoot(dest.allowlistRoot);
|
|
353
|
-
const title = rcaProposalTitle(job, verdict);
|
|
354
|
-
|
|
355
|
-
// job.epicId (present on jobs authored after Epic-gating landed; absent
|
|
356
|
-
// on legacy PRDs) is the known origin Epic this failing job belonged to.
|
|
357
|
-
// Passing it straight through as ensureEpic's `epicId` would hit its
|
|
358
|
-
// explicitEpicId join-only branch, which joins unconditionally — even a
|
|
359
|
-
// 'completed' Epic. findJoinableEpic's preferEpicId strategy is the one
|
|
360
|
-
// that only joins when the Epic is still open ('proposed'/'active'), so
|
|
361
|
-
// resolve it here and only forward it when that strategy actually fires
|
|
362
|
-
// — otherwise fall through to the existing reuseByGoal/mint behavior.
|
|
363
|
-
// Once forwarded, ensureEpic joins it unconditionally by id (no second
|
|
364
|
-
// status check) — the status gate lives entirely in this resolve step.
|
|
365
|
-
let preferredEpicId;
|
|
366
|
-
if (typeof job.epicId === 'string' && job.epicId) {
|
|
367
|
-
const joinable = findJoinableEpic(dest.cwd, { goalText: title, preferEpicId: job.epicId, status: 'proposed' });
|
|
368
|
-
if (joinable && joinable.matchedBy === 'preferEpicId') {
|
|
369
|
-
preferredEpicId = joinable.epicId;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
const { epicId, created } = await ensureEpic(dest.cwd, {
|
|
374
|
-
goalText: title,
|
|
375
|
-
tag: 'bug',
|
|
376
|
-
status: 'proposed',
|
|
377
|
-
reuseByGoal: true,
|
|
378
|
-
openingPrompt: markdown,
|
|
379
|
-
epicId: preferredEpicId,
|
|
380
|
-
source: { producer: 'rca-hook', prdSlug: job.slug, runId: job.runId },
|
|
303
|
+
job, verdict, meta, logTail, acText, failureClass, investigationText, annotations,
|
|
381
304
|
});
|
|
382
305
|
|
|
383
|
-
|
|
384
|
-
|
|
306
|
+
// Raw fs (not config.cjs's writeTextAtomic) for the same reason the rest
|
|
307
|
+
// of this lib uses it: the run directory lives under
|
|
308
|
+
// ~/.claude/session-manager/scheduled-plans/runs/, which is Session-Manager
|
|
309
|
+
// runtime state, not an OWNERS namespace under a project's operations root.
|
|
310
|
+
const reportPath = path.join(runDir, `root-cause-${job.slug}.md`);
|
|
311
|
+
fs.mkdirSync(runDir, { recursive: true });
|
|
312
|
+
const tmp = `${reportPath}.tmp-${process.pid}`;
|
|
313
|
+
fs.writeFileSync(tmp, markdown);
|
|
314
|
+
fs.renameSync(tmp, reportPath);
|
|
315
|
+
|
|
316
|
+
const summary = rcaSummaryLine(job, verdict);
|
|
317
|
+
console.log(`[rca] wrote ${reportPath} (${failureClass})`);
|
|
318
|
+
return { filed: true, path: reportPath, summary, failureClass };
|
|
385
319
|
} catch (e) {
|
|
386
|
-
console.error('[rca] error
|
|
320
|
+
console.error('[rca] error writing RCA report', e?.message ?? String(e));
|
|
387
321
|
return { filed: false, reason: 'error', error: e?.message ?? String(e) };
|
|
388
322
|
}
|
|
389
323
|
}
|
|
390
324
|
|
|
391
325
|
module.exports = {
|
|
392
|
-
|
|
393
|
-
|
|
326
|
+
writeRcaReport,
|
|
327
|
+
rcaSummaryLine,
|
|
394
328
|
// Pure helpers, exported for unit tests.
|
|
395
329
|
humanVerdict,
|
|
396
330
|
classifyFailure,
|
|
397
331
|
extractAcceptanceCriteria,
|
|
398
332
|
extractRcaBlock,
|
|
399
|
-
resolveDestination,
|
|
400
333
|
buildRcaMarkdown,
|
|
401
334
|
FAILURE_CLASSES,
|
|
402
335
|
VERDICT_LABELS,
|
package/src/main/runVerify.cjs
CHANGED
|
@@ -135,8 +135,8 @@ function extractContent(item) {
|
|
|
135
135
|
* Parse the run log into a flat sequence of extracted events.
|
|
136
136
|
*
|
|
137
137
|
* Event shapes:
|
|
138
|
-
* { kind:'tool_use', seq, toolUseId, toolName, description, command }
|
|
139
|
-
* { kind:'tool_result', seq, toolUseId, content, isError }
|
|
138
|
+
* { kind:'tool_use', seq, toolUseId, toolName, description, command, parentToolUseId }
|
|
139
|
+
* { kind:'tool_result', seq, toolUseId, content, isError, parentToolUseId }
|
|
140
140
|
* { kind:'result', seq, subtype, resultText }
|
|
141
141
|
*
|
|
142
142
|
* Non-JSON lines (scheduler metadata "[scheduler] ...") are silently skipped.
|
|
@@ -190,6 +190,7 @@ function parseLog(logPath) {
|
|
|
190
190
|
toolName: item.name ?? '',
|
|
191
191
|
description: item.input?.description ?? '',
|
|
192
192
|
command: item.input?.command ?? '',
|
|
193
|
+
parentToolUseId: obj.parent_tool_use_id ?? null,
|
|
193
194
|
});
|
|
194
195
|
}
|
|
195
196
|
}
|
|
@@ -206,6 +207,7 @@ function parseLog(logPath) {
|
|
|
206
207
|
toolUseId: item.tool_use_id ?? '',
|
|
207
208
|
content: extractContent(item),
|
|
208
209
|
isError: item.is_error === true,
|
|
210
|
+
parentToolUseId: obj.parent_tool_use_id ?? null,
|
|
209
211
|
});
|
|
210
212
|
}
|
|
211
213
|
}
|
|
@@ -783,6 +785,19 @@ async function verifyRun({ runDir, prdPath, queueEntry, allJobs = [], committedD
|
|
|
783
785
|
// seen in 58-web-remote-correctness-batch, 2026-06-10).
|
|
784
786
|
if (isHarnessToolError(ev.content)) continue;
|
|
785
787
|
|
|
788
|
+
// A tool_result carrying a non-null parent_tool_use_id happened INSIDE a Task
|
|
789
|
+
// subagent's own execution, not in the main agent's. Subagents do ordinary
|
|
790
|
+
// exploratory work (greps that exit 1 on no-match, ls on a path that may not
|
|
791
|
+
// exist) whose failures say nothing about whether the parent run succeeded —
|
|
792
|
+
// the same false-positive class the Task exemption below already covers for the
|
|
793
|
+
// content-pattern scan, which sits after this branch and so never reached
|
|
794
|
+
// is_error. The subagent's OWN final Task tool_result has parentToolUseId ===
|
|
795
|
+
// null and is still scanned, so a genuinely failed subagent still surfaces.
|
|
796
|
+
// (Incident: 955-route-epic-create-through-ipc, 2026-08-03 — a fully green,
|
|
797
|
+
// committed run parked in needs_review over a code-reviewer subagent's
|
|
798
|
+
// no-match grep at event 450/499.)
|
|
799
|
+
if (ev.parentToolUseId) continue;
|
|
800
|
+
|
|
786
801
|
// is_error:true in the final 20% of the transcript.
|
|
787
802
|
if (ev.isError && i >= last20pctStart) {
|
|
788
803
|
const desc = toolUseDesc(events, ev.toolUseId);
|
package/src/main/scheduler.cjs
CHANGED
|
@@ -77,7 +77,7 @@ const {
|
|
|
77
77
|
} = require('./lib/schedulerConfig.cjs');
|
|
78
78
|
const { pickForProject, pickNextBatch, DEFAULT_PROJECT_CWD } = require('./lib/schedulerBatch.cjs');
|
|
79
79
|
const { runDefinitionOfDoneOnDrain } = require('./lib/dodDrainHook.cjs');
|
|
80
|
-
const {
|
|
80
|
+
const { writeRcaReport, extractRcaBlock } = require('./lib/rcaReport.cjs');
|
|
81
81
|
const queueHistory = require('./lib/queueHistory.cjs');
|
|
82
82
|
const queueOps = require('./queueOps.cjs');
|
|
83
83
|
// Feedback-auto-PRD sweep — formerly only run by the external scheduler-watchdog
|
|
@@ -87,6 +87,7 @@ const queueOps = require('./queueOps.cjs');
|
|
|
87
87
|
// home-dir layout.
|
|
88
88
|
const { resolvePrdsDirs, resolveArchivedPrdsDirs, resolvePrdWriteDir, listEpicPrdDirs, listArchivedPrdDirs } = require('./lib/prdLocations.cjs');
|
|
89
89
|
const { ensureEpic, appendPrdCreatedEvent, readActiveIndex } = require('./lib/epicMint.cjs');
|
|
90
|
+
const { buildContextDigest } = require('./lib/epicContextDigest.cjs');
|
|
90
91
|
|
|
91
92
|
// ---------- origin session resolution (PRD 832) ----------
|
|
92
93
|
// An Epic IS a tagged claude session — job rows carry the originating
|
|
@@ -1421,8 +1422,8 @@ let heartbeatInterval = null;
|
|
|
1421
1422
|
// (The 5-minute feedback sweep that used to piggyback on this heartbeat is
|
|
1422
1423
|
// gone: it scanned each active project's session-manager-operations/feedback/
|
|
1423
1424
|
// and auto-queued a /process-feedback PRD. Both the folder and that skill are
|
|
1424
|
-
// retired
|
|
1425
|
-
//
|
|
1425
|
+
// retired, and nothing auto-creates work any more — a parked job is surfaced
|
|
1426
|
+
// back into the Epic that authored its PRD instead. See lib/rcaReport.cjs.)
|
|
1426
1427
|
// In-memory set of slugs currently spawned in this process. Prevents
|
|
1427
1428
|
// double-spawn when runDueJobs() is called while jobs are in flight.
|
|
1428
1429
|
const runningSet = new Set();
|
|
@@ -1893,6 +1894,42 @@ async function notifyOriginatingTab(job, {
|
|
|
1893
1894
|
}
|
|
1894
1895
|
}
|
|
1895
1896
|
|
|
1897
|
+
/**
|
|
1898
|
+
* notifyNeedsReview(job, report) → Promise<boolean>
|
|
1899
|
+
*
|
|
1900
|
+
* A job parked in `needs_review` is a question, not a result: something about
|
|
1901
|
+
* the PRD was unclear, unverifiable, or ran past its acceptance criteria.
|
|
1902
|
+
* That question belongs in the conversation that WROTE the PRD, so this
|
|
1903
|
+
* appends a 'response' event to the authoring Epic's own chain (the same
|
|
1904
|
+
* channel notifyOriginatingTab uses for completed/failed, which deliberately
|
|
1905
|
+
* excludes needs_review — isNotifiableTerminalStatus). It resolves the Epic
|
|
1906
|
+
* from the PRD's `sourcePromptId`, falling back to the job's own `epicId`.
|
|
1907
|
+
*
|
|
1908
|
+
* Join-only by construction: appendResponseEventIfKnown refuses an unknown or
|
|
1909
|
+
* non-active session and returns false. Nothing here can create an Epic — if
|
|
1910
|
+
* no open authoring Epic exists, the root-cause report in the run directory
|
|
1911
|
+
* is the whole record and the Scheduler tab is where it surfaces.
|
|
1912
|
+
*/
|
|
1913
|
+
async function notifyNeedsReview(job, report, {
|
|
1914
|
+
parsePrdRaw = prdParser.parsePrdRaw,
|
|
1915
|
+
appendResponseEvent = appendResponseEventIfKnown,
|
|
1916
|
+
} = {}) {
|
|
1917
|
+
try {
|
|
1918
|
+
if (!job || !report || !report.filed) return false;
|
|
1919
|
+
const prd = await parsePrdRaw(prdPathForJob(job)).catch(() => null);
|
|
1920
|
+
const epicId = prd?.sourcePromptId || job.epicId || null;
|
|
1921
|
+
if (!epicId || !job.cwd) {
|
|
1922
|
+
console.log(`[scheduler] notifyNeedsReview: no authoring Epic for ${job.slug}, report only`);
|
|
1923
|
+
return false;
|
|
1924
|
+
}
|
|
1925
|
+
const message = `${report.summary}. Root-cause report: ${report.path}`;
|
|
1926
|
+
return await appendResponseEvent(job.cwd, epicId, message);
|
|
1927
|
+
} catch (e) {
|
|
1928
|
+
console.error('[scheduler] notifyNeedsReview error', job?.slug, e);
|
|
1929
|
+
return false;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1896
1933
|
/** Scan the tail of a job's log for the canonical rate-limit signal. We look
|
|
1897
1934
|
* at the last 16 KB — final result event always lands at the end.
|
|
1898
1935
|
* Uses readTail() so no raw fd lifecycle is needed here. */
|
|
@@ -2109,6 +2146,25 @@ async function executeJob(job, runDir, defaultCwd, onPid) {
|
|
|
2109
2146
|
}
|
|
2110
2147
|
}
|
|
2111
2148
|
|
|
2149
|
+
// Prepend the Epic's own session digest (PRD 950/958) when this job traces
|
|
2150
|
+
// back to a known Epic — additive only, never mutates the PRD body itself.
|
|
2151
|
+
// A missing/unresolved epicId or a digest build failure is a silent no-op:
|
|
2152
|
+
// the PRD's own body must remain sufficient to complete the job on its own.
|
|
2153
|
+
const digestEpicId = job.epicId ?? job.sourcePromptId ?? null;
|
|
2154
|
+
const originSessionId = resolveOriginSessionId(cwd, digestEpicId);
|
|
2155
|
+
let contextDigestApplied = false;
|
|
2156
|
+
if (originSessionId) {
|
|
2157
|
+
try {
|
|
2158
|
+
const digestText = await buildContextDigest({ cwd, epicId: digestEpicId });
|
|
2159
|
+
if (digestText) {
|
|
2160
|
+
prompt = digestText + '\n\n' + prompt;
|
|
2161
|
+
contextDigestApplied = true;
|
|
2162
|
+
}
|
|
2163
|
+
} catch (e) {
|
|
2164
|
+
safeLog(`[scheduler] context digest build failed (job still dispatches without it): ${e?.message ?? e}\n`);
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2112
2168
|
const promptCheck = validatePromptForSpawn(prompt, prdPath);
|
|
2113
2169
|
if (!promptCheck.ok) {
|
|
2114
2170
|
safeLog(`[scheduler] ${promptCheck.error}\n`);
|
|
@@ -2262,7 +2318,7 @@ async function executeJob(job, runDir, defaultCwd, onPid) {
|
|
|
2262
2318
|
sl(`\n[scheduler] ${errMsg}\n`);
|
|
2263
2319
|
// Sync write: inside a Promise executor callback; must flush meta
|
|
2264
2320
|
// before resolve() so the spawnJob mutate() that follows sees it.
|
|
2265
|
-
config.writeJsonSync(metaPath, { slug: job.slug, cwd, sessionId, exitCode: -1, error: errMsg, startedAt, finishedAt: Date.now(), durationMs, schedulerBootedAt: SCHEDULER_BOOTED_AT, schedulerCodeSha: SCHEDULER_CODE_SHA });
|
|
2321
|
+
config.writeJsonSync(metaPath, { slug: job.slug, cwd, sessionId, exitCode: -1, error: errMsg, startedAt, finishedAt: Date.now(), durationMs, schedulerBootedAt: SCHEDULER_BOOTED_AT, schedulerCodeSha: SCHEDULER_CODE_SHA, originSessionId, contextDigestApplied });
|
|
2266
2322
|
resolve({ exitCode: -1, durationMs, error: errMsg, sessionId });
|
|
2267
2323
|
return;
|
|
2268
2324
|
}
|
|
@@ -2293,6 +2349,7 @@ async function executeJob(job, runDir, defaultCwd, onPid) {
|
|
|
2293
2349
|
startedAt, finishedAt: Date.now(), durationMs,
|
|
2294
2350
|
agentResultSubtype, mappedFromSignal: mappedToSuccess ? signal || `code=${exitCode}` : null,
|
|
2295
2351
|
schedulerBootedAt: SCHEDULER_BOOTED_AT, schedulerCodeSha: SCHEDULER_CODE_SHA,
|
|
2352
|
+
originSessionId, contextDigestApplied,
|
|
2296
2353
|
});
|
|
2297
2354
|
resolve({ exitCode: effectiveCode, durationMs, rateLimited, networkError, sessionId });
|
|
2298
2355
|
},
|
|
@@ -2610,19 +2667,19 @@ async function spawnInvestigation(failedJob, runDir) {
|
|
|
2610
2667
|
return;
|
|
2611
2668
|
}
|
|
2612
2669
|
sl(`\n[scheduler] investigation exit code=${exitCode}\n`);
|
|
2613
|
-
// Fold the investigation's <RCA> summary into the
|
|
2614
|
-
//
|
|
2670
|
+
// Fold the investigation's <RCA> summary into the root-cause report already
|
|
2671
|
+
// written for this job (needs_review jobs only — writeRcaReport no-ops when
|
|
2615
2672
|
// failedJob has no verifierVerdict, e.g. plain 'failed' jobs never got one).
|
|
2616
2673
|
const investigationText = extractRcaBlock(readTail(investigationLogPath, 64 * 1024));
|
|
2617
2674
|
if (investigationText) {
|
|
2618
|
-
|
|
2675
|
+
writeRcaReport({
|
|
2619
2676
|
job: failedJob,
|
|
2620
2677
|
runDir,
|
|
2621
2678
|
verdict: failedJob.verifierVerdict,
|
|
2622
2679
|
annotations: failedJob.verifierAnnotations,
|
|
2623
2680
|
investigationText,
|
|
2624
2681
|
}).catch((e) => {
|
|
2625
|
-
console.error('[scheduler]
|
|
2682
|
+
console.error('[scheduler] writeRcaReport (investigation enrichment) error', failedJob.slug, e);
|
|
2626
2683
|
});
|
|
2627
2684
|
}
|
|
2628
2685
|
if (fs.existsSync(fixPath)) {
|
|
@@ -3006,14 +3063,16 @@ async function spawnJob(job, runId, runDir, defaultCwd) {
|
|
|
3006
3063
|
if (needsReviewRcaSnapshot) {
|
|
3007
3064
|
// Fire-and-forget, mirroring the DoD drain hook: never blocks the status
|
|
3008
3065
|
// transition, never throws to this caller.
|
|
3009
|
-
|
|
3066
|
+
writeRcaReport({
|
|
3010
3067
|
job: needsReviewRcaSnapshot,
|
|
3011
3068
|
runDir,
|
|
3012
3069
|
verdict: needsReviewRcaSnapshot.verifierVerdict,
|
|
3013
3070
|
annotations: needsReviewRcaSnapshot.verifierAnnotations,
|
|
3014
|
-
})
|
|
3015
|
-
|
|
3016
|
-
|
|
3071
|
+
})
|
|
3072
|
+
.then((report) => notifyNeedsReview(needsReviewRcaSnapshot, report))
|
|
3073
|
+
.catch((e) => {
|
|
3074
|
+
console.error('[scheduler] writeRcaReport error', job.slug, e);
|
|
3075
|
+
});
|
|
3017
3076
|
}
|
|
3018
3077
|
|
|
3019
3078
|
if (actuallyFailed && failedJobSnapshot) {
|
|
@@ -4399,12 +4458,9 @@ const remote = {
|
|
|
4399
4458
|
if (candidate && fs.existsSync(candidate)) { dir = d; break; }
|
|
4400
4459
|
}
|
|
4401
4460
|
if (!dir) {
|
|
4402
|
-
// Every PRD must join an EXISTING,
|
|
4403
|
-
//
|
|
4404
|
-
//
|
|
4405
|
-
// /propose-epic + Approve & start) may write PRDs; this write path
|
|
4406
|
-
// is not itself a human-intent gate, so it must not become one by
|
|
4407
|
-
// accident.
|
|
4461
|
+
// Every PRD must join an EXISTING, human-created Epic — ensureEpic
|
|
4462
|
+
// is join-only for every caller but the New Epic UI (epicMint.cjs's
|
|
4463
|
+
// SINGLE-CREATOR LAW), so this can never conjure one.
|
|
4408
4464
|
const { fm } = splitFrontmatter(body);
|
|
4409
4465
|
try {
|
|
4410
4466
|
const epic = await ensureEpic(cwd, {
|
|
@@ -4415,11 +4471,10 @@ const remote = {
|
|
|
4415
4471
|
// a literal-equality join. Any other id (e.g. a PromptTicket.id)
|
|
4416
4472
|
// simply won't match and this call throws below.
|
|
4417
4473
|
epicId: fm.sourcePromptId,
|
|
4418
|
-
|
|
4419
|
-
// Join-only call (mintIfMissing:false, explicit epicId): the
|
|
4474
|
+
// Join-only call (no mintAuthority, explicit epicId): the
|
|
4420
4475
|
// explicitEpicId branch in ensureEpic returns before `source` is
|
|
4421
4476
|
// ever read, so this is a no-op today — kept for symmetry/audit
|
|
4422
|
-
// trail if that join path ever grows a source-touch
|
|
4477
|
+
// trail if that join path ever grows a source-touch.
|
|
4423
4478
|
source: { producer: 'scheduler-dispatch', prdSlug: slug },
|
|
4424
4479
|
});
|
|
4425
4480
|
dir = epic.prdDir;
|
|
@@ -4428,7 +4483,7 @@ const remote = {
|
|
|
4428
4483
|
return {
|
|
4429
4484
|
ok: false,
|
|
4430
4485
|
error: `no existing Epic to join (sourcePromptId=${fm.sourcePromptId ?? 'none'}): ${e?.message ?? 'unknown error'}. `
|
|
4431
|
-
+ 'Create the Epic first via the New Epic UI
|
|
4486
|
+
+ 'Create the Epic first via the New Epic UI, then pass its id as sourcePromptId.',
|
|
4432
4487
|
};
|
|
4433
4488
|
}
|
|
4434
4489
|
}
|
|
@@ -4438,7 +4493,7 @@ const remote = {
|
|
|
4438
4493
|
if (dir === PRDS_DIR) ensureDirs();
|
|
4439
4494
|
}
|
|
4440
4495
|
|
|
4441
|
-
// writePrd only ever JOINS an existing Epic now (
|
|
4496
|
+
// writePrd only ever JOINS an existing Epic now (no mintAuthority
|
|
4442
4497
|
// above) — it can never mint one, so there is no orphaned-mint case left
|
|
4443
4498
|
// to roll back here (contrast the old PRD 825/851 cleanup, removed).
|
|
4444
4499
|
const resolved = safeSlugPathIn(dir, slug);
|
|
@@ -4540,4 +4595,4 @@ function registerAdminRoutes(adminHttp, remoteObj = remote) {
|
|
|
4540
4595
|
});
|
|
4541
4596
|
}
|
|
4542
4597
|
|
|
4543
|
-
module.exports = { registerScheduleHandlers, attachWindow, init, ROOT, PRDS_DIR, writeQueue, reconcile, reconcileSourcePromptId, allocateParallelGroup, selectHistoryJobs, parsePorcelain, FINISH_PROTOCOL, remote, pickNextBatch, pickForProject, reapDeadRunningJobs, pollRecoveryClearSource, memoryLimitedBatchSize, availableForJobs, reverifyNeedsReview, isRescanCandidate, isPromotableOriginal, selectAutoFixTargets, isEligibleForImmediateAutoFix, resolveRunId, isUnresolvableNeedsReview, healTargetForFix, buildInvestigationPrompt, committedInWindow, computeCommittedDuringRun, classifySigtermWithCommit, isFixPlanSlug, isFixPlanBeyondDepthCap, MAX_INVESTIGATION_DEPTH, forceTickOutcome, applyPauseCleared, detectNetworkErrorInLog, detectRateLimitInLog, classifyFailureOutcome, commitGuardVerdict, TRANSIENT_RETRY_CAP, buildScheduleStatePayload, partitionBootOrphans, applyOrphanOutcome, BOOT_ORPHAN_KILL_GRACE_MS, registerAdminRoutes, notifyOriginatingTab, isNotifiableTerminalStatus, extractResultTextFromLog, candidatePrdsDirs, candidateArchivedPrdsDirs, resolveArchivedPrdStatus, prdDirForCwd, prdPathForJob, archivedPrdPathForJob, archivedTwinExists, findPrdDir, runPrdMigration, shouldSkipInvestigationForCleanRun, archiveCompletedPrd, retireCompletedSlugs, SCHEDULER_BOOTED_AT, SCHEDULER_CODE_SHA, resetJobFields, executeJob, prdArchivedSkipResult };
|
|
4598
|
+
module.exports = { registerScheduleHandlers, attachWindow, init, ROOT, PRDS_DIR, writeQueue, reconcile, reconcileSourcePromptId, allocateParallelGroup, selectHistoryJobs, parsePorcelain, FINISH_PROTOCOL, remote, pickNextBatch, pickForProject, reapDeadRunningJobs, pollRecoveryClearSource, memoryLimitedBatchSize, availableForJobs, reverifyNeedsReview, isRescanCandidate, isPromotableOriginal, selectAutoFixTargets, isEligibleForImmediateAutoFix, resolveRunId, isUnresolvableNeedsReview, healTargetForFix, buildInvestigationPrompt, committedInWindow, computeCommittedDuringRun, classifySigtermWithCommit, isFixPlanSlug, isFixPlanBeyondDepthCap, MAX_INVESTIGATION_DEPTH, forceTickOutcome, applyPauseCleared, detectNetworkErrorInLog, detectRateLimitInLog, classifyFailureOutcome, commitGuardVerdict, TRANSIENT_RETRY_CAP, buildScheduleStatePayload, partitionBootOrphans, applyOrphanOutcome, BOOT_ORPHAN_KILL_GRACE_MS, registerAdminRoutes, notifyOriginatingTab, notifyNeedsReview, isNotifiableTerminalStatus, extractResultTextFromLog, candidatePrdsDirs, candidateArchivedPrdsDirs, resolveArchivedPrdStatus, prdDirForCwd, prdPathForJob, archivedPrdPathForJob, archivedTwinExists, findPrdDir, runPrdMigration, shouldSkipInvestigationForCleanRun, archiveCompletedPrd, retireCompletedSlugs, SCHEDULER_BOOTED_AT, SCHEDULER_CODE_SHA, resetJobFields, executeJob, prdArchivedSkipResult };
|
package/src/preload/api.d.ts
CHANGED
|
@@ -699,47 +699,6 @@ export interface FilesDuplicateResult { ok: boolean; path?: string; error?: stri
|
|
|
699
699
|
export interface DocEditResult { ok: boolean; after?: string; error?: string }
|
|
700
700
|
export interface DocEditSessionResult { tabId: string; requestId: string; ok: boolean; after?: string; error?: string }
|
|
701
701
|
|
|
702
|
-
export interface SearchFileEntry {
|
|
703
|
-
name: string;
|
|
704
|
-
path: string;
|
|
705
|
-
isDirectory: false;
|
|
706
|
-
isFile: true;
|
|
707
|
-
}
|
|
708
|
-
export interface SearchFilesResult {
|
|
709
|
-
ok: boolean;
|
|
710
|
-
files: SearchFileEntry[];
|
|
711
|
-
error: string | null;
|
|
712
|
-
usedRipgrep: boolean;
|
|
713
|
-
}
|
|
714
|
-
export interface SearchTextMatch {
|
|
715
|
-
path: string;
|
|
716
|
-
line: number;
|
|
717
|
-
column: number;
|
|
718
|
-
text: string;
|
|
719
|
-
}
|
|
720
|
-
export interface SearchTextResult {
|
|
721
|
-
ok: boolean;
|
|
722
|
-
matches: SearchTextMatch[];
|
|
723
|
-
error: string | null;
|
|
724
|
-
usedRipgrep: boolean;
|
|
725
|
-
}
|
|
726
|
-
|
|
727
|
-
export interface RepoLanguageStats { files: number; lines: number }
|
|
728
|
-
export interface RepoTopDirectory { path: string; fileCount: number; totalLines: number }
|
|
729
|
-
export interface RepoGitStatusSummary { uncommitted: number; branch: string | null }
|
|
730
|
-
export interface RepoAnalyzeResult {
|
|
731
|
-
ok: true;
|
|
732
|
-
cwd: string;
|
|
733
|
-
totalFiles: number;
|
|
734
|
-
totalLines: number;
|
|
735
|
-
languageBreakdown: Record<string, RepoLanguageStats>;
|
|
736
|
-
topDirectories: RepoTopDirectory[];
|
|
737
|
-
gitStatus: RepoGitStatusSummary;
|
|
738
|
-
truncated: boolean;
|
|
739
|
-
durationMs: number;
|
|
740
|
-
}
|
|
741
|
-
export interface RepoAnalyzeError { ok: false; error: string }
|
|
742
|
-
|
|
743
702
|
export interface WatcherInfo {
|
|
744
703
|
watcherId: string;
|
|
745
704
|
tabId: string;
|
|
@@ -1521,13 +1480,6 @@ export interface SessionManagerAPI {
|
|
|
1521
1480
|
| { as: 'revealPath'; path: string }
|
|
1522
1481
|
) => Promise<{ ok: boolean; opener?: string; error?: string }>;
|
|
1523
1482
|
};
|
|
1524
|
-
search: {
|
|
1525
|
-
files: (cwd: string, query?: string, opts?: { limit?: number }) => Promise<SearchFilesResult>;
|
|
1526
|
-
text: (cwd: string, query: string, opts?: { limit?: number; caseSensitive?: boolean }) => Promise<SearchTextResult>;
|
|
1527
|
-
};
|
|
1528
|
-
repo: {
|
|
1529
|
-
analyze: (cwd: string) => Promise<RepoAnalyzeResult | RepoAnalyzeError>;
|
|
1530
|
-
};
|
|
1531
1483
|
history: {
|
|
1532
1484
|
aggregate: (req?: HistoryAggregateRequest) => Promise<HistoryAggregateResult>;
|
|
1533
1485
|
scanProjects: () => Promise<SessionScanResult>;
|
|
@@ -1765,6 +1717,12 @@ export interface SessionManagerAPI {
|
|
|
1765
1717
|
* in-memory contribution for the cwd; disk stays main's truth, merged
|
|
1766
1718
|
* inside the same lock epicMint.cjs's ensureEpic serializes through. */
|
|
1767
1719
|
mergeActiveIndex: (payload: PromptSessionsMergeActiveIndexPayload) => Promise<PromptSessionsMergeActiveIndexResult>;
|
|
1720
|
+
/** Mints a brand-new 'proposed' Epic through main's own ensureEpic()
|
|
1721
|
+
* (lib/promptSessionsCreateEpic.cjs) instead of the renderer
|
|
1722
|
+
* hand-constructing the PromptSession record itself. Unused today —
|
|
1723
|
+
* additive step 1 of a two-PRD chain; a later PRD routes
|
|
1724
|
+
* createPromptSession through this. */
|
|
1725
|
+
create: (payload: PromptSessionsCreateEpicPayload) => Promise<PromptSessionsCreateEpicResult>;
|
|
1768
1726
|
};
|
|
1769
1727
|
}
|
|
1770
1728
|
|
|
@@ -1782,6 +1740,25 @@ export interface PromptSessionsMergeActiveIndexResult {
|
|
|
1782
1740
|
events: Record<string, unknown[]>;
|
|
1783
1741
|
}
|
|
1784
1742
|
|
|
1743
|
+
// ─────────────────────────────────── PromptSessions create-epic (main-side ensureEpic)
|
|
1744
|
+
export interface PromptSessionsCreateEpicPayload {
|
|
1745
|
+
cwd: string;
|
|
1746
|
+
goalText: string;
|
|
1747
|
+
tag?: 'feature' | 'bug' | 'discussion' | 'build' | 'project-home-builder' | 'bilko-host-publisher';
|
|
1748
|
+
agentType?: string;
|
|
1749
|
+
source?: {
|
|
1750
|
+
producer: 'new-epic-ui' | 'scheduler-dispatch';
|
|
1751
|
+
prdSlug?: string;
|
|
1752
|
+
runId?: string;
|
|
1753
|
+
sourceTabId?: string;
|
|
1754
|
+
};
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
export interface PromptSessionsCreateEpicResult {
|
|
1758
|
+
epicId: string;
|
|
1759
|
+
session: Record<string, unknown>;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1785
1762
|
// ─────────────────────────────────── PromptSession event-appended broadcast
|
|
1786
1763
|
|
|
1787
1764
|
export interface PromptSessionEventAppendedPayload {
|
package/src/preload/index.cjs
CHANGED
|
@@ -258,13 +258,6 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
258
258
|
shell: {
|
|
259
259
|
open: (opts) => ipcRenderer.invoke('shell:open', opts),
|
|
260
260
|
},
|
|
261
|
-
search: {
|
|
262
|
-
files: (cwd, query, opts) => ipcRenderer.invoke('search:files', { cwd, query, opts }),
|
|
263
|
-
text: (cwd, query, opts) => ipcRenderer.invoke('search:text', { cwd, query, opts }),
|
|
264
|
-
},
|
|
265
|
-
repo: {
|
|
266
|
-
analyze: (cwd) => ipcRenderer.invoke('repo:analyze', { cwd }),
|
|
267
|
-
},
|
|
268
261
|
schedule: {
|
|
269
262
|
state: () => ipcRenderer.invoke('schedule:state'),
|
|
270
263
|
sessionSlots: () => ipcRenderer.invoke('schedule:session-slots'),
|
|
@@ -493,5 +486,11 @@ contextBridge.exposeInMainWorld('api', {
|
|
|
493
486
|
* renderer-side read-merge-write. Payload carries only this renderer's
|
|
494
487
|
* own in-memory contribution for the cwd; disk stays main's truth. */
|
|
495
488
|
mergeActiveIndex: (payload) => ipcRenderer.invoke('promptSessions:merge-active-index', payload),
|
|
489
|
+
/** Mints a brand-new 'proposed' Epic through main's own ensureEpic()
|
|
490
|
+
* (lib/promptSessionsCreateEpic.cjs) instead of the renderer
|
|
491
|
+
* hand-constructing the PromptSession record itself. Unused today —
|
|
492
|
+
* additive step 1 of a two-PRD chain; a later PRD routes
|
|
493
|
+
* createPromptSession through this. */
|
|
494
|
+
create: (payload) => ipcRenderer.invoke('promptSessions:create-epic', payload),
|
|
496
495
|
},
|
|
497
496
|
});
|