claude-code-session-manager 0.75.3 → 0.77.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-CzQqcObq.js → AgentLibrary-B2ie8bbw.js} +2 -2
- package/dist/assets/{DataModel-Bj_WlLz8.js → DataModel-BIJPYw32.js} +1 -1
- package/dist/assets/{History-DnSi_OHm.js → History-CeY6dk9S.js} +2 -2
- package/dist/assets/{Hooks-0BB0dp3S.js → Hooks-BFH2ocKg.js} +2 -2
- package/dist/assets/{HostBilko-DHpwwsLQ.js → HostBilko-36gj9wLz.js} +1 -1
- package/dist/assets/{Library-CaJVqVvi.js → Library-C-hBct39.js} +1 -1
- package/dist/assets/{ListDetail-C1W2HmC2.js → ListDetail-CNq64VWV.js} +1 -1
- package/dist/assets/{MarkdownEditor-5Ob9FW3z.js → MarkdownEditor-Bh3qt5-1.js} +1 -1
- package/dist/assets/{McpServers-JxCSfm1S.js → McpServers-DpGN0oyz.js} +1 -1
- package/dist/assets/{Memory-BDeqlqwH.js → Memory-D59hUjC4.js} +6 -6
- package/dist/assets/{Panel-Dh9ZHuEj.js → Panel-DCgbaoci.js} +1 -1
- package/dist/assets/{Permissions-DXy-CbEY.js → Permissions-DAmQ0DYV.js} +2 -2
- package/dist/assets/{Plugins-_n1Iuc8T.js → Plugins-Dyfgn6Is.js} +2 -2
- package/dist/assets/{ProvenanceBadge-BP_evfxE.js → ProvenanceBadge-BiYhPO1U.js} +1 -1
- package/dist/assets/SaveBar-RV7B6sOh.js +1 -0
- package/dist/assets/Scheduler-BPaNqx1b.js +14 -0
- package/dist/assets/{ScopeSwitcher-CAWzM6RI.js → ScopeSwitcher-P4mdLGNU.js} +1 -1
- package/dist/assets/{Settings-DRRozLyT.js → Settings-BL4vf5aX.js} +1 -1
- package/dist/assets/{SkillReferenceGraph-DGHDWlz4.js → SkillReferenceGraph-BRBDyi1_.js} +1 -1
- package/dist/assets/{Skills-D8L66eiX.js → Skills-BV08gDUH.js} +2 -2
- package/dist/assets/{SystemPrompt-CYtUsonD.js → SystemPrompt-CLftSsDw.js} +1 -1
- package/dist/assets/TagLibrary-Bp8jGsd5.js +1 -0
- package/dist/assets/{TiptapBody-B2hRgbPE.js → TiptapBody-jCpuB6E5.js} +1 -1
- package/dist/assets/{Toggle-BTwsbxam.js → Toggle-D2paA1xf.js} +1 -1
- package/dist/assets/{index-DijufvkJ.js → index-BDRSqBl3.js} +704 -704
- package/dist/assets/{index-CMLnzdZC.css → index-CYhdtisq.css} +1 -1
- package/dist/assets/{settingsSchema-D6wzxAi6.js → settingsSchema-6IOLjZZN.js} +1 -1
- package/dist/index.html +2 -2
- package/package.json +8 -2
- package/plugins/session-manager-dev/skills/develop/standards.md +1 -1
- package/scripts/lib/activeSessions.cjs +116 -6
- package/scripts/project-pages-logic/dist/logic.cjs +4709 -0
- package/scripts/render-project-pages/dist/renderer.cjs +18900 -0
- package/scripts/render-project-pages.cjs +70 -0
- package/scripts/scheduler-mcp-server.cjs +269 -96
- package/scripts/validate-project-pages-summary.cjs +62 -0
- package/src/main/__tests__/agentModelResolve.test.cjs +66 -0
- package/src/main/__tests__/epicStatusMirror.test.cjs +110 -0
- package/src/main/__tests__/health-delegation-chain.test.cjs +106 -0
- package/src/main/__tests__/prdAdminRoutes.test.cjs +295 -0
- package/src/main/__tests__/prdAgentType.test.cjs +103 -0
- package/src/main/__tests__/prdCreate.test.cjs +247 -0
- package/src/main/__tests__/prdFrontmatterAgentType.test.cjs +117 -0
- package/src/main/__tests__/prdFrontmatterQuietMachine.test.cjs +108 -0
- package/src/main/__tests__/projectHomeAdminRoutes.test.cjs +485 -0
- package/src/main/__tests__/projectPages.test.cjs +73 -1
- package/src/main/__tests__/rcaReport.test.cjs +54 -0
- package/src/main/__tests__/runVerify.test.cjs +94 -0
- package/src/main/__tests__/scheduler-autofix-select.test.cjs +58 -3
- package/src/main/__tests__/scheduler-bash-timeout-env.test.cjs +103 -0
- package/src/main/__tests__/scheduler-commit-guard-noop.test.cjs +41 -0
- package/src/main/__tests__/scheduler-effective-concurrency.test.cjs +10 -0
- package/src/main/__tests__/scheduler-foreign-wip-manifest.test.cjs +78 -0
- package/src/main/__tests__/scheduler-inplace-salvage.test.cjs +242 -0
- package/src/main/__tests__/scheduler-investigation-prompt.test.cjs +31 -0
- package/src/main/__tests__/scheduler-launch-failure.test.cjs +201 -0
- package/src/main/__tests__/scheduler-leftover-fields.test.cjs +52 -0
- package/src/main/__tests__/scheduler-looks-done.test.cjs +241 -0
- package/src/main/__tests__/scheduler-prd-persona-spawn.test.cjs +135 -0
- package/src/main/__tests__/scheduler-quiet-machine-lease.test.cjs +222 -0
- package/src/main/__tests__/scheduler-reap-dead-running-jobs.test.cjs +207 -1
- package/src/main/__tests__/scheduler-shared-tree-guard.test.cjs +212 -0
- package/src/main/__tests__/scheduler-stranded-investigation.test.cjs +185 -0
- package/src/main/__tests__/scheduler-worktree-cap-defer.test.cjs +194 -0
- package/src/main/__tests__/seedAgentPersonas.test.cjs +75 -14
- package/src/main/__tests__/seedSchedulerMcp.test.cjs +66 -0
- package/src/main/__tests__/uniquePrdNumbers.test.cjs +14 -5
- package/src/main/bilkoHost.cjs +4 -3
- package/src/main/chatRunner.cjs +6 -1
- package/src/main/config.cjs +25 -33
- package/src/main/health.cjs +153 -2
- package/src/main/index.cjs +64 -5
- package/src/main/ipcSchemas.cjs +69 -1
- package/src/main/lib/__tests__/activeIndexRebuild.test.cjs +179 -0
- package/src/main/lib/__tests__/childWithLog.test.cjs +141 -0
- package/src/main/lib/__tests__/delegationReadiness.test.cjs +391 -42
- package/src/main/lib/__tests__/ephemeralCwd.test.cjs +91 -0
- package/src/main/lib/__tests__/epicWorktreeMint.test.cjs +5 -3
- package/src/main/lib/__tests__/fixChainDepth.test.cjs +40 -0
- package/src/main/lib/__tests__/gitWorktree.test.cjs +290 -5
- package/src/main/lib/__tests__/gitWorktreeSalvage.test.cjs +107 -0
- package/src/main/lib/__tests__/gitWorktreeSalvageDelta.test.cjs +153 -0
- package/src/main/lib/__tests__/jobWorktree.test.cjs +6 -4
- package/src/main/lib/__tests__/landedSinceRun.test.cjs +73 -0
- package/src/main/lib/__tests__/launchFailure.test.cjs +220 -0
- package/src/main/lib/__tests__/loadGate.test.cjs +159 -0
- package/src/main/lib/__tests__/mcpToolCatalog.test.cjs +102 -0
- package/src/main/lib/__tests__/opsOwnership.test.cjs +7 -0
- package/src/main/lib/__tests__/opsRootAbsoluteCwd.test.cjs +151 -0
- package/src/main/lib/__tests__/opsRootResolve.test.cjs +149 -0
- package/src/main/lib/__tests__/prdDeclaredPaths.test.cjs +82 -0
- package/src/main/lib/__tests__/projectRootResolve.test.cjs +148 -0
- package/src/main/lib/__tests__/queueHealth.test.cjs +58 -0
- package/src/main/lib/__tests__/quietMachineLease.test.cjs +39 -0
- package/src/main/lib/__tests__/reaperHelpers.test.cjs +133 -0
- package/src/main/lib/__tests__/schedulerBatchDepends.test.cjs +19 -9
- package/src/main/lib/__tests__/schedulerBatchFairness.test.cjs +213 -0
- package/src/main/lib/__tests__/schedulerBatchLaunchHold.test.cjs +125 -0
- package/src/main/lib/__tests__/schedulerBatchProjectCap.test.cjs +127 -0
- package/src/main/lib/__tests__/schedulerBatchQuietMachine.test.cjs +109 -0
- package/src/main/lib/__tests__/schedulerMcpServerHeadlessRefusal.test.cjs +71 -0
- package/src/main/lib/__tests__/schedulerMcpServerHelp.test.cjs +217 -0
- package/src/main/lib/__tests__/schedulerMcpServerProjectHome.test.cjs +350 -0
- package/src/main/lib/activeIndexMerge.cjs +15 -0
- package/src/main/lib/activeIndexRebuild.cjs +133 -0
- package/src/main/lib/agentModelResolve.cjs +58 -0
- package/src/main/lib/buildTarget.cjs +3 -2
- package/src/main/lib/childWithLog.cjs +69 -2
- package/src/main/lib/claudeBin.cjs +54 -1
- package/src/main/lib/crossProjectFeedback.cjs +8 -1
- package/src/main/lib/definitionOfDone.cjs +3 -2
- package/src/main/lib/delegationReadiness.cjs +514 -26
- package/src/main/lib/ephemeralCwd.cjs +78 -0
- package/src/main/lib/epicDelegationStats.cjs +2 -1
- package/src/main/lib/epicMint.cjs +17 -1
- package/src/main/lib/epicStatusMirror.cjs +95 -0
- package/src/main/lib/epicValidationHook.cjs +2 -1
- package/src/main/lib/epicWorktreeMint.cjs +5 -2
- package/src/main/lib/fixChainDepth.cjs +45 -0
- package/src/main/lib/gitWorktree.cjs +520 -21
- package/src/main/lib/jobWorktree.cjs +2 -0
- package/src/main/lib/landedSinceRun.cjs +55 -0
- package/src/main/lib/launchFailure.cjs +357 -0
- package/src/main/lib/loadGate.cjs +134 -0
- package/src/main/lib/mcpToolCatalog.cjs +370 -0
- package/src/main/lib/opsErrorLog.cjs +12 -1
- package/src/main/lib/opsOwnership.cjs +106 -0
- package/src/main/lib/prdAdminRoutes.cjs +43 -3
- package/src/main/lib/prdAgentType.cjs +84 -0
- package/src/main/lib/prdCreate.cjs +103 -15
- package/src/main/lib/prdDeclaredPaths.cjs +70 -0
- package/src/main/lib/prdFrontmatter.cjs +17 -3
- package/src/main/lib/prdLocations.cjs +13 -6
- package/src/main/lib/projectHomeAdminRoutes.cjs +402 -0
- package/src/main/lib/projectPageSummarySchema.cjs +181 -0
- package/src/main/lib/projectRootResolve.cjs +134 -0
- package/src/main/lib/promptSessionSchema.cjs +7 -0
- package/src/main/lib/queueHealth.cjs +38 -0
- package/src/main/lib/queueStore.cjs +40 -7
- package/src/main/lib/quietMachineLease.cjs +48 -0
- package/src/main/lib/rcaReport.cjs +54 -4
- package/src/main/lib/reaperHelpers.cjs +64 -1
- package/src/main/lib/scheduleJobSchema.cjs +31 -0
- package/src/main/lib/scheduleJobTransitions.cjs +6 -2
- package/src/main/lib/schedulerBatch.cjs +301 -55
- package/src/main/lib/schedulerConfig.cjs +99 -0
- package/src/main/projectBrief.cjs +3 -2
- package/src/main/projectPages.cjs +162 -3
- package/src/main/promptSessionTranscript.cjs +0 -0
- package/src/main/pty.cjs +5 -0
- package/src/main/queueOps.cjs +15 -8
- package/src/main/runVerify.cjs +50 -9
- package/src/main/scheduler/prdParser.cjs +18 -1
- package/src/main/scheduler.cjs +1701 -130
- package/src/main/seedAgentPersonas.cjs +62 -21
- package/src/main/seedSchedulerMcp.cjs +58 -4
- package/src/main/templates/project-pages-catalog.json +741 -0
- package/src/main/templates/project-pages-pipeline.md +417 -0
- package/src/preload/api.d.ts +187 -3
- package/src/preload/index.cjs +9 -0
- package/src/seed/agents/project-home-builder.md +59 -0
- package/dist/assets/SaveBar-D-gCUx4n.js +0 -1
- package/dist/assets/Scheduler-Bpd4OGju.js +0 -14
- package/dist/assets/TagLibrary-E5CLeuVk.js +0 -1
|
@@ -5,7 +5,9 @@
|
|
|
5
5
|
* display (PRD 932). Reads what the `project-home-builder` Epic's own
|
|
6
6
|
* session writes directly with its Write tool
|
|
7
7
|
* (`session-manager-operations/project-pages/output/*.html` + manifest.json)
|
|
8
|
-
* — this module never writes
|
|
8
|
+
* — this module never writes any CONTENT (the one exception, watchOutput()'s
|
|
9
|
+
* `fs.mkdirSync` to ensure the output dir exists before attaching a watcher,
|
|
10
|
+
* creates an empty directory only), matching the corrected "not an
|
|
9
11
|
* OWNERS namespace" storage/ownership note in
|
|
10
12
|
* session-manager-operations/architecture/project-pages-pipeline.md and
|
|
11
13
|
* session-manager-operations/project-pages/README.md. Structure mirrors
|
|
@@ -25,10 +27,34 @@
|
|
|
25
27
|
const { ipcMain } = require('electron');
|
|
26
28
|
const fs = require('node:fs');
|
|
27
29
|
const path = require('node:path');
|
|
30
|
+
const chokidar = require('chokidar');
|
|
28
31
|
const config = require('./config.cjs');
|
|
32
|
+
const { opsPath } = require('./lib/opsOwnership.cjs');
|
|
33
|
+
const { sendIfAlive } = require('./lib/sendToRenderer.cjs');
|
|
34
|
+
|
|
35
|
+
let mainWindow = null;
|
|
36
|
+
function attachWindow(window) {
|
|
37
|
+
mainWindow = window;
|
|
38
|
+
}
|
|
29
39
|
|
|
30
40
|
function outputDir(cwd) {
|
|
31
|
-
return
|
|
41
|
+
return opsPath(cwd, 'project-pages', 'output');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolve `cwd`'s output dir, treating the opsOwnership "ephemeral cwd"
|
|
46
|
+
* throw (a tmpdir/linked-worktree project root — see opsOwnership.cjs's
|
|
47
|
+
* resolveProjectRoot) as "no output dir available" rather than letting it
|
|
48
|
+
* propagate. Both get() and the watcher below go through this so a builder
|
|
49
|
+
* Epic running in a worktree can never crash either path.
|
|
50
|
+
*/
|
|
51
|
+
function tryOutputDir(realCwd) {
|
|
52
|
+
try {
|
|
53
|
+
return { dir: outputDir(realCwd), ephemeral: false };
|
|
54
|
+
} catch (e) {
|
|
55
|
+
if (e.ephemeral) return { dir: null, ephemeral: true };
|
|
56
|
+
throw e;
|
|
57
|
+
}
|
|
32
58
|
}
|
|
33
59
|
|
|
34
60
|
// Shipped build-time asset (see PRD "project-home-hosted-html-spec"'s
|
|
@@ -59,13 +85,24 @@ function defaultOutput() {
|
|
|
59
85
|
// the empty state for every pre-existing project. So brief.html is read
|
|
60
86
|
// tolerantly — present, its text is returned; absent, the field is simply
|
|
61
87
|
// omitted from the payload rather than failing the whole read.
|
|
88
|
+
// Partial-output strategy (see this file's module doc + PRD
|
|
89
|
+
// "project-home-generate-preflight-and-push"): the render route
|
|
90
|
+
// (projectHomeAdminRoutes.cjs's doRender) writes every lens file
|
|
91
|
+
// concurrently and writes manifest.json ONLY AFTER all of them have landed.
|
|
92
|
+
// So manifest.json's existence is already an all-or-nothing commit signal —
|
|
93
|
+
// gating get() on it (below) means a reader can never observe a lens file
|
|
94
|
+
// that arrived without its siblings. No second partial-state representation
|
|
95
|
+
// is needed; this file just relies on that write order.
|
|
62
96
|
const REQUIRED_LENSES = ['home', 'marketing', 'feature', 'architecture'];
|
|
63
97
|
const OPTIONAL_LENSES = ['brief'];
|
|
64
98
|
const LENSES = [...REQUIRED_LENSES, ...OPTIONAL_LENSES];
|
|
65
99
|
|
|
66
100
|
async function get({ cwd }) {
|
|
67
101
|
const realCwd = config.validatePath(cwd);
|
|
68
|
-
const dir =
|
|
102
|
+
const { dir, ephemeral } = tryOutputDir(realCwd);
|
|
103
|
+
if (ephemeral) {
|
|
104
|
+
return { output: defaultOutput() };
|
|
105
|
+
}
|
|
69
106
|
const manifestResult = await config.readJson(path.join(dir, 'manifest.json'));
|
|
70
107
|
if (!manifestResult.exists || !manifestResult.data || manifestResult.parseError) {
|
|
71
108
|
return { output: defaultOutput() };
|
|
@@ -99,14 +136,136 @@ async function get({ cwd }) {
|
|
|
99
136
|
return { output };
|
|
100
137
|
}
|
|
101
138
|
|
|
139
|
+
// ─── Push channel (PRD "project-home-generate-preflight-and-push") ────────
|
|
140
|
+
//
|
|
141
|
+
// project-pages:get above is a one-shot pull, fetched once at mount by
|
|
142
|
+
// ProjectHome.tsx. That leaves a finished (or stuck) builder Epic invisible
|
|
143
|
+
// until the next manual refresh/app restart. This section adds a per-cwd
|
|
144
|
+
// chokidar watcher on the output dir (the same debounced-write library
|
|
145
|
+
// config.cjs already uses for its generic file watcher) that recomputes
|
|
146
|
+
// get() and pushes it to the renderer whenever the dir changes.
|
|
147
|
+
//
|
|
148
|
+
// Map<realCwd, { watcher, refCount }> — refcounted like config.cjs's own
|
|
149
|
+
// watchers Map so more than one mounted component watching the same cwd
|
|
150
|
+
// doesn't spawn a second chokidar instance, and doesn't get torn down by
|
|
151
|
+
// the first component's unmount while a second is still watching.
|
|
152
|
+
const outputWatchers = new Map();
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Watch `cwd`'s output dir. Resolves `{ ok: true }` once watching is actually
|
|
156
|
+
* live (or already watching), or `{ ok: false, reason: 'ephemeral' |
|
|
157
|
+
* 'invalid-cwd' }` when there is nothing safe to watch — callers must not
|
|
158
|
+
* treat that as an error, just as "no live updates for this cwd" (e.g. a
|
|
159
|
+
* worktree cwd). Async: waits for chokidar's own 'ready' before resolving so
|
|
160
|
+
* a write landing immediately after the call is never missed in the window
|
|
161
|
+
* before the underlying fs watch handle is actually registered.
|
|
162
|
+
*/
|
|
163
|
+
async function watchOutput(cwd) {
|
|
164
|
+
let realCwd;
|
|
165
|
+
try {
|
|
166
|
+
realCwd = config.validatePath(cwd);
|
|
167
|
+
} catch {
|
|
168
|
+
return { ok: false, reason: 'invalid-cwd' };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const existing = outputWatchers.get(realCwd);
|
|
172
|
+
if (existing) {
|
|
173
|
+
existing.refCount += 1;
|
|
174
|
+
return { ok: true };
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const { dir, ephemeral } = tryOutputDir(realCwd);
|
|
178
|
+
if (ephemeral) {
|
|
179
|
+
return { ok: false, reason: 'ephemeral' };
|
|
180
|
+
}
|
|
181
|
+
// Ensure the (possibly not-yet-generated) output dir exists before
|
|
182
|
+
// chokidar attaches: watching a path 3 levels deep that doesn't exist yet
|
|
183
|
+
// does not reliably pick up files later written into it (verified — the
|
|
184
|
+
// watcher silently misses the first write without this). This creates an
|
|
185
|
+
// EMPTY directory only, no content, so it does not conflict with this
|
|
186
|
+
// module's read-only contract or the single-writer law (opsOwnership.cjs)
|
|
187
|
+
// — it bypasses config.cjs's writer-tracked write path deliberately,
|
|
188
|
+
// exactly like a project-home-builder Epic's own untracked Write-tool
|
|
189
|
+
// calls into this same folder (see opsOwnership.cjs's 'project-pages'
|
|
190
|
+
// entry).
|
|
191
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
192
|
+
|
|
193
|
+
const push = async () => {
|
|
194
|
+
let result;
|
|
195
|
+
try {
|
|
196
|
+
result = await get({ cwd: realCwd });
|
|
197
|
+
} catch {
|
|
198
|
+
return; // best-effort; the renderer keeps its last-known output
|
|
199
|
+
}
|
|
200
|
+
sendIfAlive(mainWindow, 'project-pages:changed', { cwd: realCwd, output: result.output });
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const watcher = chokidar.watch(dir, {
|
|
204
|
+
ignoreInitial: true,
|
|
205
|
+
persistent: true,
|
|
206
|
+
// manifest.json is the LAST file doRender writes (see REQUIRED_LENSES's
|
|
207
|
+
// comment above) — but the burst of per-lens writes leading up to it
|
|
208
|
+
// still fires several fs events in quick succession. awaitWriteFinish
|
|
209
|
+
// (same 50ms/25ms shape config.cjs's generic watcher uses) collapses
|
|
210
|
+
// that burst into one push instead of flapping the UI once per file.
|
|
211
|
+
awaitWriteFinish: { stabilityThreshold: 50, pollInterval: 25 },
|
|
212
|
+
});
|
|
213
|
+
watcher.on('add', push);
|
|
214
|
+
watcher.on('change', push);
|
|
215
|
+
watcher.on('unlink', push);
|
|
216
|
+
watcher.on('error', (err) => {
|
|
217
|
+
console.warn('[projectPages] watcher error for', dir, err?.message);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
outputWatchers.set(realCwd, { watcher, refCount: 1 });
|
|
221
|
+
await new Promise((resolve) => watcher.once('ready', resolve));
|
|
222
|
+
return { ok: true };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Decrement `cwd`'s watch refcount; closes the underlying watcher at 0. */
|
|
226
|
+
function unwatchOutput(cwd) {
|
|
227
|
+
let realCwd;
|
|
228
|
+
try {
|
|
229
|
+
realCwd = config.validatePath(cwd);
|
|
230
|
+
} catch {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const entry = outputWatchers.get(realCwd);
|
|
234
|
+
if (!entry) return;
|
|
235
|
+
entry.refCount -= 1;
|
|
236
|
+
if (entry.refCount <= 0) {
|
|
237
|
+
entry.watcher.close().catch(() => {});
|
|
238
|
+
outputWatchers.delete(realCwd);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Window close / app quit teardown — mirrors config.cjs's closeAllWatchers. */
|
|
243
|
+
function closeAllOutputWatchers() {
|
|
244
|
+
for (const { watcher } of outputWatchers.values()) {
|
|
245
|
+
watcher.close().catch(() => {});
|
|
246
|
+
}
|
|
247
|
+
outputWatchers.clear();
|
|
248
|
+
}
|
|
249
|
+
|
|
102
250
|
function registerProjectPagesIpc() {
|
|
103
251
|
const { schemas: s, validated: v } = require('./ipcSchemas.cjs');
|
|
104
252
|
ipcMain.handle('project-pages:get', v(s.projectPagesCwd, get));
|
|
253
|
+
ipcMain.handle('project-pages:watch', v(s.projectPagesCwd, ({ cwd }) => watchOutput(cwd)));
|
|
254
|
+
ipcMain.handle('project-pages:unwatch', v(s.projectPagesCwd, ({ cwd }) => {
|
|
255
|
+
unwatchOutput(cwd);
|
|
256
|
+
return { ok: true };
|
|
257
|
+
}));
|
|
105
258
|
}
|
|
106
259
|
|
|
107
260
|
module.exports = {
|
|
261
|
+
attachWindow,
|
|
108
262
|
registerProjectPagesIpc,
|
|
109
263
|
get,
|
|
264
|
+
watchOutput,
|
|
265
|
+
unwatchOutput,
|
|
266
|
+
closeAllOutputWatchers,
|
|
110
267
|
DEFAULT_HOME_PATH,
|
|
111
268
|
readDefaultHomeHtml,
|
|
269
|
+
// Test-only introspection of the refcounted watcher map.
|
|
270
|
+
_outputWatchers: outputWatchers,
|
|
112
271
|
};
|
|
Binary file
|
package/src/main/pty.cjs
CHANGED
|
@@ -156,6 +156,11 @@ class PtyManager {
|
|
|
156
156
|
// back to its originating Epic and minted an unrelated sibling one
|
|
157
157
|
// instead (prdCreate.cjs's resolveSourcePromptIdFromClaudeSession).
|
|
158
158
|
SM_CHAT_SESSION_ID: tabId,
|
|
159
|
+
// The real project cwd (never the worktree spawnCwd resolved below) —
|
|
160
|
+
// forwarded by scheduler-mcp-server.cjs as originProjectRoot so
|
|
161
|
+
// create-prd/open-session/readiness resolve the right project even
|
|
162
|
+
// from a worktree pwd. See projectRootResolve.cjs.
|
|
163
|
+
SM_PROJECT_ROOT: cwd,
|
|
159
164
|
});
|
|
160
165
|
|
|
161
166
|
const shell = process.env.SHELL || '/bin/bash';
|
package/src/main/queueOps.cjs
CHANGED
|
@@ -128,7 +128,7 @@ const INTERACTIVE_AC_PATTERNS = [
|
|
|
128
128
|
];
|
|
129
129
|
|
|
130
130
|
const { splitFrontmatter } = require('./lib/prdFrontmatter.cjs');
|
|
131
|
-
const { resolvePrdsDirs } = require('./lib/prdLocations.cjs');
|
|
131
|
+
const { resolvePrdsDirs, resolvePrdWriteDir, listEpicPrdDirs } = require('./lib/prdLocations.cjs');
|
|
132
132
|
|
|
133
133
|
// PRD 808: PRDS_DIR is now the LEGACY global dir — kept as a migration
|
|
134
134
|
// source and search fallback for not-yet-migrated files. Live PRDs resolve
|
|
@@ -139,9 +139,16 @@ function candidatePrdsDirs() {
|
|
|
139
139
|
return [PRDS_DIR, ...resolvePrdsDirs()];
|
|
140
140
|
}
|
|
141
141
|
|
|
142
|
-
/**
|
|
143
|
-
|
|
144
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Search every candidate PRD dir for `<slug>.md`; returns the containing dir
|
|
144
|
+
* or null. `cwd`, if given, narrows the search to that one project's own PRD
|
|
145
|
+
* dirs (its flat prds/ dir plus its Epic-scoped dirs) instead of scanning
|
|
146
|
+
* every project machine-wide — same narrowing pattern scheduler.cjs's
|
|
147
|
+
* getPrdParsed uses.
|
|
148
|
+
*/
|
|
149
|
+
async function findPrdDir(slug, cwd) {
|
|
150
|
+
const dirs = cwd ? [resolvePrdWriteDir(cwd), ...listEpicPrdDirs(cwd)] : candidatePrdsDirs();
|
|
151
|
+
for (const dir of dirs) {
|
|
145
152
|
try {
|
|
146
153
|
await fsp.access(path.join(dir, `${slug}.md`));
|
|
147
154
|
return dir;
|
|
@@ -311,9 +318,9 @@ function archiveDirForSource(srcDir, ts) {
|
|
|
311
318
|
return path.join(path.dirname(srcDir), 'prds-archived', ts);
|
|
312
319
|
}
|
|
313
320
|
|
|
314
|
-
async function archiveOne(slug, ts) {
|
|
321
|
+
async function archiveOne(slug, ts, cwd) {
|
|
315
322
|
if (!SLUG_RE.test(slug)) return { ok: false, slug, error: 'invalid slug' };
|
|
316
|
-
const srcDir = await findPrdDir(slug);
|
|
323
|
+
const srcDir = await findPrdDir(slug, cwd);
|
|
317
324
|
if (!srcDir) return { ok: false, slug, error: 'not found in any PRDs dir' };
|
|
318
325
|
const src = path.resolve(path.join(srcDir, `${slug}.md`));
|
|
319
326
|
if (!src.startsWith(srcDir + path.sep)) return { ok: false, slug, error: 'path escape (src)' };
|
|
@@ -342,7 +349,7 @@ async function archiveOne(slug, ts) {
|
|
|
342
349
|
*/
|
|
343
350
|
let retireCompletedSlugsFn = async () => {};
|
|
344
351
|
|
|
345
|
-
async function archiveMany(slugs) {
|
|
352
|
+
async function archiveMany(slugs, cwd) {
|
|
346
353
|
if (!Array.isArray(slugs) || slugs.length === 0) {
|
|
347
354
|
return { ok: true, archived: 0, archivedTo: null, results: [] };
|
|
348
355
|
}
|
|
@@ -353,7 +360,7 @@ async function archiveMany(slugs) {
|
|
|
353
360
|
const ts = new Date().toISOString().replace(/[:.]/g, '-');
|
|
354
361
|
const results = [];
|
|
355
362
|
for (const slug of slugs) {
|
|
356
|
-
results.push(await archiveOne(slug, ts));
|
|
363
|
+
results.push(await archiveOne(slug, ts, cwd));
|
|
357
364
|
}
|
|
358
365
|
const archived = results.filter((r) => r.ok).length;
|
|
359
366
|
const archivedSlugs = results.filter((r) => r.ok).map((r) => r.slug);
|
package/src/main/runVerify.cjs
CHANGED
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
* verifyRun({ runDir, prdPath, queueEntry, allJobs }) → Promise<Verdict>
|
|
14
14
|
*
|
|
15
15
|
* Verdict shape:
|
|
16
|
-
* { verdict: 'clean'|'halt'|'deps_unmet'|'transcript_errors'|'verify_unavailable'
|
|
16
|
+
* { verdict: 'clean'|'halt'|'deps_unmet'|'transcript_errors'|'verify_unavailable'
|
|
17
|
+
* |'no_verdict_sentinel'|'abandoned_background_task',
|
|
17
18
|
* reason: string,
|
|
18
19
|
* downgradeTo: 'pending'|'needs_review'|null }
|
|
19
20
|
*
|
|
@@ -23,6 +24,8 @@
|
|
|
23
24
|
* deps_unmet → 'pending' (re-fires when deps clear)
|
|
24
25
|
* transcript_errors → 'needs_review' (requires human flip)
|
|
25
26
|
* verify_unavailable → 'needs_review'
|
|
27
|
+
* no_verdict_sentinel → 'needs_review'
|
|
28
|
+
* abandoned_background_task → 'needs_review'
|
|
26
29
|
*
|
|
27
30
|
* Time complexity: O(N·L) where N = log line count, L = avg content line count.
|
|
28
31
|
* The log is read once with readFileSync (700 KB typical; well within Node.js
|
|
@@ -231,6 +234,28 @@ function parseLog(logPath) {
|
|
|
231
234
|
return { events, resultEvent, error: null, transcriptCommitLanded };
|
|
232
235
|
}
|
|
233
236
|
|
|
237
|
+
// Stable, narrow substrings of the harness's auto-background tool_result text
|
|
238
|
+
// (Bash: "Command did not complete within its 120s timeout and was moved to
|
|
239
|
+
// the background... You will be notified when it completes."). Anchored on
|
|
240
|
+
// short distinctive fragments rather than the whole sentence so harness
|
|
241
|
+
// wording drift degrades to the generic no_verdict_sentinel verdict instead
|
|
242
|
+
// of throwing.
|
|
243
|
+
const BACKGROUND_TASK_MARKERS = ['moved to the background', 'You will be notified when it completes'];
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* True when any tool_result in the transcript carries the harness's
|
|
247
|
+
* auto-background marker text — i.e. a Bash command was auto-backgrounded
|
|
248
|
+
* after its foreground timeout and the run ended waiting for a notification
|
|
249
|
+
* a headless run structurally cannot receive.
|
|
250
|
+
*/
|
|
251
|
+
function hasAbandonedBackgroundTask(events) {
|
|
252
|
+
for (const ev of events) {
|
|
253
|
+
if (ev.kind !== 'tool_result' || !ev.content) continue;
|
|
254
|
+
if (BACKGROUND_TASK_MARKERS.some((marker) => ev.content.includes(marker))) return true;
|
|
255
|
+
}
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
|
|
234
259
|
// ─── self-recovery helpers ────────────────────────────────────────────────────
|
|
235
260
|
|
|
236
261
|
/**
|
|
@@ -905,15 +930,30 @@ async function verifyRun({ runDir, prdPath, queueEntry, allJobs = [], committedD
|
|
|
905
930
|
: undefined;
|
|
906
931
|
|
|
907
932
|
// No pattern hits is not automatically "clean": a run that neither
|
|
908
|
-
// committed anything nor ever emitted the finish-protocol sentinel
|
|
909
|
-
//
|
|
910
|
-
//
|
|
933
|
+
// committed anything nor ever emitted the finish-protocol sentinel did not
|
|
934
|
+
// finish for SOME reason. When the transcript shows the harness's
|
|
935
|
+
// auto-background marker, that reason is knowable and cheap to recover
|
|
936
|
+
// from: the run backgrounded a long Bash command past its foreground
|
|
937
|
+
// timeout and ended waiting for a "you will be notified" callback a
|
|
938
|
+
// headless run can never receive — it did not necessarily fail to do the
|
|
939
|
+
// work, it failed to come back and commit it. That gets its own verdict
|
|
940
|
+
// (abandoned_background_task) so the auto-fix investigation checks the
|
|
941
|
+
// working tree for already-written work first, instead of re-planning the
|
|
942
|
+
// whole PRD from a wrong "stopped on a clarifying question" premise.
|
|
911
943
|
if (sentinel === null && !commitEvidence) {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
944
|
+
if (hasAbandonedBackgroundTask(events)) {
|
|
945
|
+
issues.push({
|
|
946
|
+
verdict: 'abandoned_background_task',
|
|
947
|
+
reason: 'run made no commit and emitted no SCHEDULER_VERDICT sentinel, and the transcript shows a Bash command auto-backgrounded past its foreground timeout — the run likely ended waiting for a background-task notification a headless run cannot receive, not because the work failed; the work is probably already written and just needs to be committed',
|
|
948
|
+
priority: 1,
|
|
949
|
+
});
|
|
950
|
+
} else {
|
|
951
|
+
issues.push({
|
|
952
|
+
verdict: 'no_verdict_sentinel',
|
|
953
|
+
reason: 'run made no commit and emitted no SCHEDULER_VERDICT sentinel — the run ended before the finish protocol for some reason (stopped on a clarifying question, crashed, ran out of turns, or another cause not identifiable from this transcript alone)',
|
|
954
|
+
priority: 1,
|
|
955
|
+
});
|
|
956
|
+
}
|
|
917
957
|
}
|
|
918
958
|
|
|
919
959
|
// A truthful-looking PASS sentinel with no commit is still not "clean":
|
|
@@ -1122,4 +1162,5 @@ module.exports = {
|
|
|
1122
1162
|
allDeliverablesAlreadyTracked,
|
|
1123
1163
|
isAncestorCommit,
|
|
1124
1164
|
isGitRepo,
|
|
1165
|
+
hasAbandonedBackgroundTask,
|
|
1125
1166
|
};
|
|
@@ -19,6 +19,7 @@ const os = require('node:os');
|
|
|
19
19
|
const path = require('node:path');
|
|
20
20
|
const { splitFrontmatter } = require('../lib/prdFrontmatter.cjs');
|
|
21
21
|
const { deriveEpicIdFromPrdPath } = require('../lib/prdLocations.cjs');
|
|
22
|
+
const { reportDanglingAgentTypeOnce } = require('../lib/prdAgentType.cjs');
|
|
22
23
|
|
|
23
24
|
/**
|
|
24
25
|
* Expand a PRD `cwd` value to an absolute path.
|
|
@@ -57,12 +58,19 @@ async function parsePrdRaw(filePath) {
|
|
|
57
58
|
const m = base.match(/^(\d+)-/);
|
|
58
59
|
return m ? Number(m[1]) : null;
|
|
59
60
|
})();
|
|
61
|
+
const cwd = expandCwd(fm.cwd || null);
|
|
62
|
+
|
|
63
|
+
// Read-time tolerance for the agentType FK (throw on write, report on
|
|
64
|
+
// read — prdAgentType.cjs's header): a persona renamed/deleted after this
|
|
65
|
+
// PRD was written must not stop the PRD from loading. Best-effort only —
|
|
66
|
+
// never lets a logging failure affect parsing.
|
|
67
|
+
if (fm.agentType && cwd) reportDanglingAgentTypeOnce(cwd, fm.agentType);
|
|
60
68
|
|
|
61
69
|
return {
|
|
62
70
|
slug: base,
|
|
63
71
|
path: filePath,
|
|
64
72
|
title: fm.title || base,
|
|
65
|
-
cwd
|
|
73
|
+
cwd,
|
|
66
74
|
estimateMinutes: fm.estimateMinutes ? Number(fm.estimateMinutes) || null : null,
|
|
67
75
|
parallelGroup: (fm.parallelGroup ? Number(fm.parallelGroup) || null : null) ?? groupFromName ?? 99,
|
|
68
76
|
// Optional traceability back to the PromptTicket.id (PRD 748) that was
|
|
@@ -99,6 +107,15 @@ async function parsePrdRaw(filePath) {
|
|
|
99
107
|
// quarantines it instead of queuing it to run.
|
|
100
108
|
createdVia: fm.createdVia || null,
|
|
101
109
|
issuedAt: fm.issuedAt || null,
|
|
110
|
+
// WHO executes this PRD (persona name) — distinct from `tag` (work
|
|
111
|
+
// type). Absent on every PRD authored before this field existed; the
|
|
112
|
+
// dependent follow-up PRD (scheduler-launch-as-persona) resolves this
|
|
113
|
+
// into a launch decision, which this module does not do.
|
|
114
|
+
agentType: fm.agentType || null,
|
|
115
|
+
// Opt-in exclusive-lease flag (PRD 1107, quietMachineLease.cjs +
|
|
116
|
+
// schedulerBatch.cjs's pickNextBatch). Only a literal `true` opts in —
|
|
117
|
+
// matches prdFrontmatter.cjs's applyKey semantics for this field.
|
|
118
|
+
quietMachine: fm.quietMachine === 'true',
|
|
102
119
|
body: body.trim(),
|
|
103
120
|
};
|
|
104
121
|
}
|