claude-code-session-manager 0.121.0 → 0.122.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.
Files changed (76) hide show
  1. package/dist/assets/{AgentLibrary-FO2ei2wB.js → AgentLibrary-BmJ8hIPw.js} +1 -1
  2. package/dist/assets/{Button-CeIOXUPU.js → Button-BS4kmz4p.js} +1 -1
  3. package/dist/assets/{DataModel-D0p__Hil.js → DataModel-DZHBZrfL.js} +1 -1
  4. package/dist/assets/History-pg00b2Gd.js +2 -0
  5. package/dist/assets/{Hooks-DkTnRHyT.js → Hooks-BTEs0UZY.js} +1 -1
  6. package/dist/assets/{Library-l7KS6pkE.js → Library-DuaJ-VSn.js} +1 -1
  7. package/dist/assets/{ListDetail-DDw8fuRT.js → ListDetail-Cud72s-O.js} +1 -1
  8. package/dist/assets/{McpServers-UUNIxk_3.js → McpServers-Dy-wnG_X.js} +1 -1
  9. package/dist/assets/{Memory-CNYUoQ8C.js → Memory-Bs1T1Jo7.js} +1 -1
  10. package/dist/assets/{Panel-Bb54soc2.js → Panel-8UhLXEOW.js} +1 -1
  11. package/dist/assets/{Permissions-D-dNdV3f.js → Permissions-BfwzvmBS.js} +1 -1
  12. package/dist/assets/Plugins-McjyvvKh.js +2 -0
  13. package/dist/assets/{ProvenanceBadge-DEAkDNjA.js → ProvenanceBadge-D0Ov_6WJ.js} +1 -1
  14. package/dist/assets/{RemoteMachines-xCEgvTGU.js → RemoteMachines-gOlFTvWq.js} +1 -1
  15. package/dist/assets/{SaveBar-BSEs7ecA.js → SaveBar-Cd6hJR6K.js} +1 -1
  16. package/dist/assets/Scheduler-BZXKqYJP.js +16 -0
  17. package/dist/assets/{ScopeSwitcher-DcaFyAV-.js → ScopeSwitcher-C24ysV1K.js} +1 -1
  18. package/dist/assets/{Settings-vcdWy_Jx.js → Settings-DWnN2qxn.js} +1 -1
  19. package/dist/assets/{SkillReferenceGraph-CLrJWTPd.js → SkillReferenceGraph-BsWuiTbw.js} +1 -1
  20. package/dist/assets/{Skills-CYM4rBEC.js → Skills-6pHn6glu.js} +1 -1
  21. package/dist/assets/{SystemPrompt-BWNIu-iY.js → SystemPrompt-DT0LBH-K.js} +1 -1
  22. package/dist/assets/{TagLibrary-CYdwHK6m.js → TagLibrary-DbJ0zbYL.js} +1 -1
  23. package/dist/assets/{TiptapBody-Kt90lHrD.js → TiptapBody-C4kIvIMH.js} +1 -1
  24. package/dist/assets/{Toggle-D1OXWYOz.js → Toggle-pP19zlyk.js} +1 -1
  25. package/dist/assets/{ViewTabs-BeQR3CKh.js → ViewTabs-CpzN8e5I.js} +1 -1
  26. package/dist/assets/{historyTotals-B0qafrRo.js → historyTotals-Caq78bb5.js} +1 -1
  27. package/dist/assets/{index-Bq9uH08R.js → index-Bjtguv6I.js} +396 -396
  28. package/dist/assets/{settingsSchema-0ZksSAPB.js → settingsSchema-CC2ySu3d.js} +1 -1
  29. package/dist/assets/{skillFrontmatter-C0nh8yfa.js → skillFrontmatter-hDhNo8jE.js} +1 -1
  30. package/dist/assets/{useAgentsDirWatch-DPkoBybS.js → useAgentsDirWatch-BuOtY25U.js} +1 -1
  31. package/dist/assets/{useRemotePeers-BDJMUihy.js → useRemotePeers-CNQmRBpt.js} +1 -1
  32. package/dist/index.html +1 -1
  33. package/package.json +1 -1
  34. package/plugins/session-manager-dev/skills/develop/SKILL.md +7 -7
  35. package/plugins/session-manager-dev/skills/develop/standards.md +4 -10
  36. package/scripts/scheduler-mcp-server.cjs +2 -2
  37. package/src/main/build-info.json +4 -4
  38. package/src/main/chatRunner.cjs +13 -37
  39. package/src/main/docEdit.cjs +1 -0
  40. package/src/main/index.cjs +12 -13
  41. package/src/main/ipcSchemas.cjs +2 -2
  42. package/src/main/lib/classifyPromptTicket.cjs +1 -0
  43. package/src/main/lib/claude/effectiveModelInfo.cjs +9 -21
  44. package/src/main/lib/claude/personaToolScope.cjs +7 -1
  45. package/src/main/lib/claude/runClaudeP.cjs +1 -0
  46. package/src/main/lib/epic/delegationReadiness.cjs +75 -8
  47. package/src/main/lib/mcpToolCatalog.cjs +0 -1
  48. package/src/main/lib/persona-macro/shippedPersonaSeeds.cjs +11 -0
  49. package/src/main/lib/prd/prdCreate.cjs +3 -1
  50. package/src/main/lib/prd/prdSizing.cjs +1 -8
  51. package/src/main/lib/runSeeders.cjs +21 -0
  52. package/src/main/lib/scheduler/loopEfficiency.cjs +109 -0
  53. package/src/main/lib/scheduler/queueHistory.cjs +44 -1
  54. package/src/main/lib/scheduler/queueStore.cjs +4 -72
  55. package/src/main/lib/scheduler/rcaReport.cjs +1 -1
  56. package/src/main/lib/scheduler/refireCap.cjs +40 -0
  57. package/src/main/lib/scheduler/reviewNotice.cjs +1 -1
  58. package/src/main/lib/scheduler/schedulerConfig.cjs +1 -2
  59. package/src/main/lib/scheduler/schedulerPaths.cjs +0 -2
  60. package/src/main/lib/scheduler/sessionSlots.cjs +1 -1
  61. package/src/main/lib/telemetry/auditLog.cjs +65 -14
  62. package/src/main/loopEfficiencyHandlers.cjs +160 -0
  63. package/src/main/queueOps.cjs +4 -9
  64. package/src/main/runVerify.cjs +22 -454
  65. package/src/main/scheduler/reviewLadder.cjs +12 -2
  66. package/src/main/scheduler.cjs +103 -177
  67. package/src/main/seedDevPlugin.cjs +49 -22
  68. package/src/main/seedStatus.cjs +3 -2
  69. package/src/main/supervisor.cjs +1 -0
  70. package/src/main/templates/PRD_AUTHORING.md +16 -31
  71. package/src/preload/api.d.ts +26 -6
  72. package/src/preload/index.cjs +1 -0
  73. package/src/seed/agents/dev-lead.md +2 -9
  74. package/dist/assets/History-DVqQ5azN.js +0 -2
  75. package/dist/assets/Plugins-DhcIXRhQ.js +0 -2
  76. package/dist/assets/Scheduler-BIKJIU-C.js +0 -16
@@ -7,8 +7,8 @@
7
7
  * state a project's single-writer law needs to arbitrate, and it must
8
8
  * survive even if the project's ops root doesn't exist yet.
9
9
  *
10
- * Rotation: on the first append in a new calendar month (UTC) the live file is
11
- * gzipped to audit-log-<YYYY-MM>.jsonl.gz beside it (YYYY-MM = the month of the
10
+ * Rotation: on the first append in a new calendar month (UTC), or once the live file passes
11
+ * 32 MB, the live file is renamed synchronously and then stream-gzipped (async) to audit-log-<YYYY-MM>.jsonl.gz beside it (YYYY-MM = the month of the
12
12
  * file's last write) and a fresh live file starts; the newest `auditLogMonths` rolled
13
13
  * files are kept. The live file stays plain JSONL, so readers are unaffected.
14
14
  *
@@ -23,6 +23,7 @@
23
23
  const fs = require('node:fs');
24
24
  const path = require('node:path');
25
25
  const zlib = require('node:zlib');
26
+ const { pipeline } = require('node:stream/promises');
26
27
 
27
28
  const { auditLogPath } = require('../scheduler/schedulerPaths.cjs');
28
29
 
@@ -55,23 +56,73 @@ const ROLLED_RE = /^audit-log-\d{4}-\d{2}\.jsonl\.gz$/;
55
56
 
56
57
  function monthKey(d) { return d.toISOString().slice(0, 7); }
57
58
 
58
- /** Roll the live file if its last write was in an earlier month than `now`; prune old rolls. */
59
- function rotateIfNewMonth(target, now) {
59
+ const MAX_LIVE_BYTES = 32 * 1024 * 1024;
60
+ let rollSeq = 0;
61
+ const inflight = new Set(); // renamed files currently being compressed
62
+ let chain = Promise.resolve();
63
+ const settling = new Set(); // promises of in-progress compressions
64
+
65
+ function escapeRe(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
66
+
67
+ function pruneRolled(dir) {
68
+ const rolls = fs.readdirSync(dir).filter((f) => ROLLED_RE.test(f)).sort();
69
+ for (const f of rolls.slice(0, Math.max(0, rolls.length - maxRolled()))) {
70
+ try { fs.unlinkSync(path.join(dir, f)); } catch { /* best-effort */ }
71
+ }
72
+ }
73
+
74
+ /** Stream-gzip `pending` onto the month archive (append: members concatenate), then unlink it. */
75
+ async function compressPending(pending, rolled, dir) {
76
+ let before = 0;
77
+ try { before = fs.statSync(rolled).size; } catch { /* new archive */ }
78
+ try {
79
+ await pipeline(fs.createReadStream(pending), zlib.createGzip(), fs.createWriteStream(rolled, { flags: 'a' }));
80
+ } catch (e) {
81
+ // Drop any partial gzip member so the archive stays valid; the pending file is kept for retry.
82
+ try { if (fs.existsSync(rolled)) fs.truncateSync(rolled, before); } catch { /* best-effort */ }
83
+ throw e;
84
+ }
85
+ fs.unlinkSync(pending);
86
+ try { pruneRolled(dir); } catch { /* best-effort */ }
87
+ }
88
+
89
+ function startCompress(pending, rolled, dir) {
90
+ if (inflight.has(pending)) return;
91
+ inflight.add(pending);
92
+ // Serialized: two members appended to one archive concurrently would interleave.
93
+ const p = chain.then(() => compressPending(pending, rolled, dir))
94
+ .catch((e) => { console.error('[auditLog] roll compress failed; will retry', e?.message ?? String(e)); })
95
+ .finally(() => { inflight.delete(pending); settling.delete(p); });
96
+ chain = p;
97
+ settling.add(p);
98
+ }
99
+
100
+ /** Resolves once every in-flight roll compression has finished (success or failure). Test seam. */
101
+ async function settleRolls() {
102
+ while (settling.size) await Promise.all([...settling]);
103
+ }
104
+
105
+ /**
106
+ * Roll the live file if its last write was in an earlier month than `now`, or it
107
+ * exceeds `maxBytes`. The rename is synchronous; compression is async. Leftover
108
+ * renamed files from a failed compress are retried here.
109
+ */
110
+ function rotateIfNeeded(target, now, maxBytes) {
60
111
  let st;
61
112
  try { st = fs.statSync(target); } catch { return; }
62
113
  if (!st.isFile() || st.size === 0) return;
63
114
  const oldMonth = monthKey(st.mtime);
64
- if (oldMonth === monthKey(now)) return;
115
+ if (oldMonth === monthKey(now) && st.size <= maxBytes) return;
65
116
  const dir = path.dirname(target);
66
117
  const base = path.basename(target, path.extname(target));
67
- const rolled = path.join(dir, `${base}-${oldMonth}.jsonl.gz`);
68
- // appendFileSync: gzip members concatenate, so a pre-existing roll for that month is extended.
69
- fs.appendFileSync(rolled, zlib.gzipSync(fs.readFileSync(target)));
70
- fs.unlinkSync(target);
71
- const rolls = fs.readdirSync(dir).filter((f) => ROLLED_RE.test(f)).sort();
72
- for (const f of rolls.slice(0, Math.max(0, rolls.length - maxRolled()))) {
73
- try { fs.unlinkSync(path.join(dir, f)); } catch { /* best-effort */ }
118
+ const pending = path.join(dir, `${base}-${oldMonth}.jsonl.rolling-${Date.now().toString(36)}${(rollSeq++).toString(36)}`);
119
+ fs.renameSync(target, pending);
120
+ const pendingRe = new RegExp(`^${escapeRe(base)}-(\\d{4}-\\d{2})\\.jsonl\\.rolling-[a-z0-9]+$`);
121
+ for (const f of fs.readdirSync(dir)) {
122
+ const m = pendingRe.exec(f);
123
+ if (m) startCompress(path.join(dir, f), path.join(dir, `${base}-${m[1]}.jsonl.gz`), dir);
74
124
  }
125
+ try { pruneRolled(dir); } catch { /* best-effort */ }
75
126
  }
76
127
 
77
128
  /**
@@ -92,7 +143,7 @@ function appendAuditEvent(kind, fields = {}, opts = {}) {
92
143
  const target = opts.target ?? auditLogPath();
93
144
  fs.mkdirSync(path.dirname(target), { recursive: true });
94
145
  try {
95
- rotateIfNewMonth(target, now);
146
+ rotateIfNeeded(target, now, opts.maxBytes ?? MAX_LIVE_BYTES);
96
147
  } catch (e) {
97
148
  console.error('[auditLog] rotation failed', e?.message ?? String(e));
98
149
  }
@@ -127,4 +178,4 @@ function readTail(maxBytes, target = auditLogPath()) {
127
178
  }
128
179
  }
129
180
 
130
- module.exports = { appendAuditEvent, auditLogPath, readTail };
181
+ module.exports = { appendAuditEvent, settleRolls, MAX_LIVE_BYTES, auditLogPath, readTail };
@@ -0,0 +1,160 @@
1
+ /**
2
+ * loopEfficiencyHandlers.cjs — `history:loop-efficiency` IPC. Gathers this
3
+ * usage week's run records + validator verdicts from the runs dir and feeds
4
+ * the pure calculator in lib/scheduler/loopEfficiency.cjs.
5
+ *
6
+ * Complexity O(runs in window); run logs are immutable after finalize, so
7
+ * validator verdicts are memoized per log path.
8
+ */
9
+
10
+ 'use strict';
11
+
12
+ const fsp = require('fs/promises');
13
+ const path = require('path');
14
+ const { ipcMain } = require('electron');
15
+ const { computeLoopEfficiency } = require('./lib/scheduler/loopEfficiency.cjs');
16
+ const { parseValidationSentinels } = require('./lib/scheduler/validationSentinels.cjs');
17
+
18
+ const WEEK_MS = 7 * 24 * 3600 * 1000;
19
+ const LOG_TAIL_BYTES = 1024 * 1024;
20
+ const USAGE_TIMEOUT_MS = 5000;
21
+ const VALIDATOR_SLUG_RE = /(^|-)validate-/;
22
+
23
+ /** @type {Map<string, Array<{ slug: string, verdict: string, reason: string }>>} */
24
+ const validationMemo = new Map();
25
+
26
+ /** `2026-10-23T01-26-37-026Z` -> epoch ms (NaN if not a run dir name). */
27
+ function runIdToMs(runId) {
28
+ const m = /^(\d{4}-\d{2}-\d{2})T(\d{2})-(\d{2})-(\d{2})-(\d{3})Z$/.exec(runId);
29
+ return m ? Date.parse(`${m[1]}T${m[2]}:${m[3]}:${m[4]}.${m[5]}Z`) : NaN;
30
+ }
31
+
32
+ async function readJson(p) {
33
+ try { return JSON.parse(await fsp.readFile(p, 'utf8')); } catch { return null; }
34
+ }
35
+
36
+ /** Last 1 MB of a file as text ('' on failure). */
37
+ async function readTail(p) {
38
+ let fh;
39
+ try {
40
+ fh = await fsp.open(p, 'r');
41
+ const { size } = await fh.stat();
42
+ const len = Math.min(size, LOG_TAIL_BYTES);
43
+ const buf = Buffer.alloc(len);
44
+ await fh.read(buf, 0, len, size - len);
45
+ return buf.toString('utf8');
46
+ } catch {
47
+ return '';
48
+ } finally {
49
+ if (fh) await fh.close().catch(() => {});
50
+ }
51
+ }
52
+
53
+ async function validationsForLog(logPath) {
54
+ const hit = validationMemo.get(logPath);
55
+ if (hit) return hit;
56
+ let out = [];
57
+ const tail = await readTail(logPath);
58
+ const lines = tail.split('\n');
59
+ for (let i = lines.length - 1; i >= 0; i--) {
60
+ if (!lines[i].includes('"type":"result"')) continue;
61
+ try {
62
+ const obj = JSON.parse(lines[i]);
63
+ if (typeof obj.result === 'string') out = parseValidationSentinels(obj.result);
64
+ } catch { /* torn line — treat as no verdicts */ }
65
+ break;
66
+ }
67
+ validationMemo.set(logPath, out);
68
+ return out;
69
+ }
70
+
71
+ /** Never throws; resolves null on any failure or after 5 s. */
72
+ async function defaultUsage(fetchUsage) {
73
+ let timer;
74
+ try {
75
+ const fetchFn = fetchUsage || require('./usage.cjs').fetchUsage;
76
+ const r = await Promise.race([
77
+ fetchFn(),
78
+ new Promise((resolve) => { timer = setTimeout(() => resolve(null), USAGE_TIMEOUT_MS); }),
79
+ ]);
80
+ if (!r) return null;
81
+ // 'ok-stale' = last cached payload served while the API fails / rate-limits.
82
+ if (r.kind === 'ok' || r.kind === 'ok-stale') return r.data && r.data.usage ? r.data.usage : null;
83
+ return null;
84
+ } catch {
85
+ return null;
86
+ } finally {
87
+ if (timer) clearTimeout(timer);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * @param {{ runsRoot?: string, nowMs?: number, usage?: any }} [opts]
93
+ * `usage` is the usage payload (`{ seven_day: { utilization, resets_at } }`) or null.
94
+ */
95
+ async function gatherLoopEfficiency({ runsRoot, nowMs, usage } = {}) {
96
+ const root = runsRoot || require('./lib/scheduler/schedulerPaths.cjs').runsDir();
97
+ const now = typeof nowMs === 'number' ? nowMs : Date.now();
98
+
99
+ let fromMs = now - WEEK_MS;
100
+ let weeklyUtilizationPct = null;
101
+ const sd = usage && usage.seven_day;
102
+ if (sd && sd.resets_at && typeof sd.utilization === 'number') {
103
+ const reset = typeof sd.resets_at === 'number' ? sd.resets_at : Date.parse(sd.resets_at);
104
+ if (Number.isFinite(reset)) {
105
+ fromMs = reset - WEEK_MS;
106
+ weeklyUtilizationPct = sd.utilization;
107
+ }
108
+ }
109
+
110
+ const runs = [];
111
+ const validations = [];
112
+ let dirs = [];
113
+ try { dirs = await fsp.readdir(root); } catch { /* no runs dir */ }
114
+
115
+ for (const runId of dirs) {
116
+ const t = runIdToMs(runId);
117
+ if (!Number.isFinite(t) || t < fromMs) continue;
118
+ const runDir = path.join(root, runId);
119
+ let files;
120
+ try { files = await fsp.readdir(runDir); } catch { continue; }
121
+ for (const f of files) {
122
+ if (!f.endsWith('.meta.json')) continue;
123
+ const meta = await readJson(path.join(runDir, f));
124
+ if (!meta || typeof meta !== 'object') continue;
125
+ // Slug comes from the filename, never meta content (no path traversal via meta.slug).
126
+ const slug = f.slice(0, -'.meta.json'.length);
127
+ const vd = await readJson(path.join(runDir, `${slug}.verdicts.json`));
128
+ const startedAtMs = typeof meta.startedAt === 'number' ? meta.startedAt : t;
129
+ const cwd = String(meta.cwd == null ? '' : meta.cwd);
130
+ runs.push({
131
+ cwd,
132
+ slug,
133
+ startedAtMs,
134
+ costUsd: meta.totalCostUsd,
135
+ verdict: vd && typeof vd.verdict === 'string' ? vd.verdict : null,
136
+ });
137
+ if (VALIDATOR_SLUG_RE.test(slug)) {
138
+ for (const v of await validationsForLog(path.join(runDir, `${slug}.log`))) {
139
+ validations.push({
140
+ cwd,
141
+ slug: v.slug,
142
+ verdict: v.verdict === 'verified' ? 'VERIFIED' : 'REFUTED',
143
+ atMs: startedAtMs,
144
+ });
145
+ }
146
+ }
147
+ }
148
+ }
149
+
150
+ return computeLoopEfficiency({ runs, validations, fromMs, toMs: now, weeklyUtilizationPct });
151
+ }
152
+
153
+ function registerLoopEfficiencyHandlers() {
154
+ ipcMain.handle('history:loop-efficiency', async () => {
155
+ const usage = await defaultUsage();
156
+ return gatherLoopEfficiency({ nowMs: Date.now(), usage });
157
+ });
158
+ }
159
+
160
+ module.exports = { gatherLoopEfficiency, registerLoopEfficiencyHandlers, defaultUsage };
@@ -18,7 +18,8 @@
18
18
  * - `^until ` at the start of a logical line → fizzpop-style poll hang
19
19
  * - `^while true` (case insensitive) → unbounded while loop
20
20
  * - `for .* in $(seq 1 [5-9][0-9][0-9]+)` → unbounded large seq
21
- * - missing frontmatter title / cwd / estimateMinutes
21
+ * - missing frontmatter title / cwd (estimateMinutes is optional; if present
22
+ * it must be a positive integer)
22
23
  * - cwd doesn't exist on disk (fs.accessSync)
23
24
  * - `--no-verify` or `--no-gpg-sign` → hook-skip flags
24
25
  *
@@ -200,14 +201,8 @@ function lintParsed(slug, raw) {
200
201
  }
201
202
  }
202
203
  }
203
- if (fm.estimateMinutes === undefined || !String(fm.estimateMinutes).trim()) {
204
- findings.push({
205
- rule: 'missing-estimate',
206
- line: 1,
207
- snippet: 'frontmatter "estimateMinutes" is required',
208
- severity: 'warn',
209
- });
210
- } else {
204
+ // estimateMinutes is optional (budget.cjs floors a missing one); validate only when present.
205
+ if (fm.estimateMinutes !== undefined && String(fm.estimateMinutes).trim()) {
211
206
  const v = Number(fm.estimateMinutes);
212
207
  if (!Number.isInteger(v) || v <= 0) {
213
208
  findings.push({