claude-code-session-manager 0.95.0 → 0.97.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/{DataModel-DZK9QZpV.js → DataModel-D93FoaOm.js} +1 -1
- package/dist/assets/History-CXxxqNP1.js +2 -0
- package/dist/assets/{Hooks-CmK6l0uf.js → Hooks-CzeYFQjq.js} +3 -3
- package/dist/assets/{HostBilko-D8TIx-Bx.js → HostBilko-nESrTRIg.js} +1 -1
- package/dist/assets/{Library-DTxpOjx8.js → Library-D2zPkwOC.js} +1 -1
- package/dist/assets/{MarkdownEditor-BPJyrevq.js → MarkdownEditor-DbinP78A.js} +1 -1
- package/dist/assets/{McpServers-B_rvIYDs.js → McpServers-Bu_sVGLr.js} +2 -2
- package/dist/assets/Memory-BgiI6LZ_.js +8 -0
- package/dist/assets/{Permissions-DhMMp27n.js → Permissions-C1MMzZRm.js} +3 -3
- package/dist/assets/Plugins-Dhu6wAZi.js +2 -0
- package/dist/assets/{ProvenanceBadge-ptlItdCK.js → ProvenanceBadge-C3AcWKMG.js} +1 -1
- package/dist/assets/{SaveBar-CSbNSl2W.js → SaveBar-plwYm0iO.js} +1 -1
- package/dist/assets/Scheduler-BVYTeh39.js +16 -0
- package/dist/assets/{ScopeSwitcher-BJof-GWH.js → ScopeSwitcher-DFy0vv_o.js} +1 -1
- package/dist/assets/Settings-DIhGgdfs.js +3 -0
- package/dist/assets/{SkillReferenceGraph-CGl4B89-.js → SkillReferenceGraph-DsI7hRPB.js} +1 -1
- package/dist/assets/Skills-3E748UfB.js +3 -0
- package/dist/assets/{SystemPrompt-DKj-q39J.js → SystemPrompt-BqmfUJOd.js} +1 -1
- package/dist/assets/TagLibrary-CPz6fO8Q.js +1 -0
- package/dist/assets/{TiptapBody-BbcJL9az.js → TiptapBody-D9EoBQ1P.js} +1 -1
- package/dist/assets/{Toggle-DAslvnnv.js → Toggle-BHAmZNDn.js} +1 -1
- package/dist/assets/index-tn5JIVPj.js +3079 -0
- package/dist/assets/{settingsSchema-pzp0TgvN.js → settingsSchema-Crqy_aNz.js} +1 -1
- package/dist/index.html +1 -1
- package/package.json +8 -2
- package/plugins/CLAUDE.md +3 -3
- package/plugins/session-manager-dev/skills/develop/SKILL.md +90 -67
- package/plugins/session-manager-dev/skills/develop/standards.md +5 -11
- package/plugins/session-manager-dev/skills/requesting-code-review/SKILL.md +5 -15
- package/scripts/README.md +1 -0
- package/scripts/hooks/guard-destructive-git.cjs +9 -374
- package/scripts/hooks/guard-inline-implementation.cjs +9 -128
- package/scripts/hooks/guard-prd-writes.cjs +9 -74
- package/scripts/hooks/guard-self-schedule.cjs +9 -61
- package/scripts/hooks/lib/guard-destructive-git-policy.cjs +410 -0
- package/scripts/hooks/lib/guard-inline-implementation-policy.cjs +159 -0
- package/scripts/hooks/lib/guard-prd-writes-policy.cjs +100 -0
- package/scripts/hooks/lib/guard-self-schedule-policy.cjs +89 -0
- package/scripts/scheduler-mcp-server.cjs +8 -1
- package/src/main/__tests__/configWriteBoundaryOwners.test.cjs +59 -0
- package/src/main/__tests__/exchanges.test.cjs +41 -96
- package/src/main/__tests__/exchangesPromptId.test.cjs +16 -25
- package/src/main/__tests__/files-reject-credentials.test.cjs +1 -1
- package/src/main/__tests__/health-credentials.test.cjs +81 -0
- package/src/main/__tests__/ipcSchemas-dependsOn.test.cjs +39 -0
- package/src/main/__tests__/memoryAggregate.test.cjs +40 -1
- package/src/main/__tests__/opsErrorLogTelemetryTap.test.cjs +1 -1
- package/src/main/__tests__/planValidator.test.cjs +125 -0
- package/src/main/__tests__/prdCreate.test.cjs +3 -1
- package/src/main/__tests__/prdSizing.test.cjs +106 -0
- package/src/main/__tests__/queue-starvation-dispatch-driver.test.cjs +46 -0
- package/src/main/__tests__/reconcileFlatPrdSweep.test.cjs +10 -0
- package/src/main/__tests__/scheduler-looks-done.test.cjs +40 -0
- package/src/main/__tests__/scheduler-notify-originating-tab.test.cjs +81 -0
- package/src/main/__tests__/scheduler-shard-quarantine.test.cjs +6 -1
- package/src/main/__tests__/schedulerStateSidecarRestore.test.cjs +110 -0
- package/src/main/__tests__/seedAgentPersonas.test.cjs +19 -1
- package/src/main/__tests__/seedValidatorPersona.test.cjs +116 -0
- package/src/main/__tests__/telemetryClient.test.cjs +2 -2
- package/src/main/__tests__/telemetryContract.test.cjs +2 -2
- package/src/main/__tests__/usageSingleFlight.test.cjs +10 -0
- package/src/main/__tests__/validationSentinels.test.cjs +84 -0
- package/src/main/build-info.json +4 -4
- package/src/main/config.cjs +26 -14
- package/src/main/exchanges.cjs +26 -21
- package/src/main/health.cjs +66 -4
- package/src/main/index.cjs +1 -1
- package/src/main/ipcSchemas.cjs +15 -7
- package/src/main/lib/__tests__/crashTelemetry.test.cjs +1 -1
- package/src/main/lib/__tests__/credentials-futile-refresh.test.cjs +115 -0
- package/src/main/lib/__tests__/epicWorktreeProjectConfig.test.cjs +85 -27
- package/src/main/lib/__tests__/gitWorktree.test.cjs +21 -27
- package/src/main/lib/__tests__/guardShims.test.cjs +10 -3
- package/src/main/lib/__tests__/loadGate.test.cjs +44 -1
- package/src/main/lib/__tests__/opsOwnership.test.cjs +14 -0
- package/src/main/lib/__tests__/telemetryBacklog.test.cjs +4 -4
- package/src/main/lib/__tests__/telemetryConsent.test.cjs +2 -2
- package/src/main/lib/__tests__/telemetryCountersMetadataColumn.test.cjs +1 -1
- package/src/main/lib/agentModelResolve.cjs +1 -0
- package/src/main/lib/credentials.cjs +62 -0
- package/src/main/lib/cwdClassify.cjs +15 -0
- package/src/main/lib/definitionOfDone.cjs +9 -13
- package/src/main/lib/effectiveModelInfo.cjs +2 -9
- package/src/main/lib/epicWorktreeProjectConfig.cjs +75 -31
- package/src/main/lib/loadGate.cjs +29 -5
- package/src/main/lib/opsOwnership.cjs +23 -0
- package/src/main/lib/planValidator.cjs +80 -0
- package/src/main/lib/prdCreate.cjs +8 -4
- package/src/main/lib/prdSizing.cjs +83 -0
- package/src/main/lib/runLogRetention.cjs +2 -8
- package/src/main/lib/telemetryBacklog.cjs +9 -3
- package/src/main/lib/telemetryClient.cjs +12 -3
- package/src/main/lib/telemetrySettings.cjs +8 -2
- package/src/main/lib/validationSentinels.cjs +29 -0
- package/src/main/memoryAggregate.cjs +36 -11
- package/src/main/otelSettings.cjs +8 -2
- package/src/main/scheduler.cjs +215 -31
- package/src/main/seedAgentPersonas.cjs +1 -1
- package/src/main/sessionsStore.cjs +8 -2
- package/src/main/supervisor.cjs +3 -3
- package/src/main/templates/PRD_AUTHORING.md +9 -9
- package/src/main/voiceSettings.cjs +8 -2
- package/src/preload/api.d.ts +3 -3
- package/src/preload/index.cjs +5 -5
- package/src/seed/agents/dev-lead.md +24 -15
- package/src/seed/agents/validator.md +33 -0
- package/dist/assets/History-BDyJUGtR.js +0 -2
- package/dist/assets/Memory-B-9GlHqe.js +0 -8
- package/dist/assets/Plugins-DKfAlmMY.js +0 -2
- package/dist/assets/Scheduler--rn1QBYc.js +0 -16
- package/dist/assets/Settings-Dsx6XZP5.js +0 -3
- package/dist/assets/Skills-D522Zfol.js +0 -3
- package/dist/assets/TagLibrary-BH7CG7-6.js +0 -1
- package/dist/assets/index-Yf8e7VCB.js +0 -3079
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* prdSizing.cjs — pure PRD-size warning rules (PRD 1403).
|
|
3
|
+
*
|
|
4
|
+
* The measured actual/estimate ratio across scheduler runs sits at 0.24 (p50) with 60% of
|
|
5
|
+
* jobs finishing in <=10 min, yet nothing at the authoring gate flags an oversized PRD before
|
|
6
|
+
* it lands. `sizingWarnings` is the single rule set; prdCreate.cjs's createPrd() surfaces its
|
|
7
|
+
* output in the create-prd response, and scheduler-mcp-server.cjs's scheduler_create_prd
|
|
8
|
+
* handler prints it as a "Sizing warnings:" block. Warnings never block a write.
|
|
9
|
+
*/
|
|
10
|
+
'use strict';
|
|
11
|
+
|
|
12
|
+
const SIZING_LIMITS = {
|
|
13
|
+
estimateMinutes: 15,
|
|
14
|
+
acLines: 8,
|
|
15
|
+
acLineChars: 400,
|
|
16
|
+
bodyChars: 7000,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// Open-ended search-and-fix shape, e.g. "grep the codebase and update every call site." Matches
|
|
20
|
+
// across sentence boundaries within one AC line ([\s\S]*, not [^.]*) — a natural two-sentence
|
|
21
|
+
// phrasing like "Search the codebase for X. Update every call site." is exactly the shape this
|
|
22
|
+
// rule exists to catch, and a period must not be enough to dodge it.
|
|
23
|
+
const OPEN_ENDED_RE = /\b(grep|search|scan|audit|find)\b[\s\S]*\b(and|then)\b[\s\S]*\b(update|fix|adjust|migrate|change)\b/i;
|
|
24
|
+
const TIMEOUT_CMD_RE = /\btimeout\s+\d+/gi;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Pure — no I/O. Returns one human-readable warning per triggered rule, `[]` when nothing
|
|
28
|
+
* is triggered. `input` mirrors createPrd()'s own input shape; missing/non-array fields are
|
|
29
|
+
* treated as empty/absent rather than thrown on, since this is advisory only.
|
|
30
|
+
*/
|
|
31
|
+
function sizingWarnings(input) {
|
|
32
|
+
const { estimateMinutes, goal, implementationNotes } = input || {};
|
|
33
|
+
const acceptanceCriteria = Array.isArray(input?.acceptanceCriteria) ? input.acceptanceCriteria : [];
|
|
34
|
+
const acLines = acceptanceCriteria.filter((line) => typeof line === 'string');
|
|
35
|
+
|
|
36
|
+
const warnings = [];
|
|
37
|
+
|
|
38
|
+
if (typeof estimateMinutes === 'number' && estimateMinutes > SIZING_LIMITS.estimateMinutes) {
|
|
39
|
+
warnings.push(
|
|
40
|
+
`estimateMinutes (${estimateMinutes}) exceeds the ${SIZING_LIMITS.estimateMinutes}-minute sizing limit — split this PRD into smaller work items.`,
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (acceptanceCriteria.length > SIZING_LIMITS.acLines) {
|
|
45
|
+
warnings.push(
|
|
46
|
+
`acceptanceCriteria has ${acceptanceCriteria.length} lines, exceeding the ${SIZING_LIMITS.acLines}-line sizing limit — split into smaller, single-purpose PRDs.`,
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (acLines.some((line) => line.length > SIZING_LIMITS.acLineChars)) {
|
|
51
|
+
warnings.push(
|
|
52
|
+
`One or more acceptance criteria lines exceed ${SIZING_LIMITS.acLineChars} characters — break the line up into smaller, single-purpose criteria.`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (acLines.some((line) => OPEN_ENDED_RE.test(line))) {
|
|
57
|
+
warnings.push(
|
|
58
|
+
'One or more acceptance criteria lines describe an open-ended search-and-fix task (e.g. "grep ... and update ...") — scope it to a concrete, bounded change instead.',
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (acLines.some((line) => {
|
|
63
|
+
const matches = line.match(TIMEOUT_CMD_RE);
|
|
64
|
+
return matches && matches.length >= 2;
|
|
65
|
+
})) {
|
|
66
|
+
warnings.push(
|
|
67
|
+
'One or more acceptance criteria lines run two or more `timeout <n>` commands — split into separate criteria, one command each.',
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const bodyChars = String(goal || '').length
|
|
72
|
+
+ String(implementationNotes || '').length
|
|
73
|
+
+ acLines.join('\n').length;
|
|
74
|
+
if (bodyChars > SIZING_LIMITS.bodyChars) {
|
|
75
|
+
warnings.push(
|
|
76
|
+
`Combined goal + implementation notes + acceptance criteria is ${bodyChars} characters, exceeding the ${SIZING_LIMITS.bodyChars}-character sizing limit — split into smaller PRDs.`,
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return warnings;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
module.exports = { SIZING_LIMITS, sizingWarnings };
|
|
@@ -43,13 +43,8 @@
|
|
|
43
43
|
*/
|
|
44
44
|
|
|
45
45
|
const fs = require('node:fs');
|
|
46
|
-
const os = require('node:os');
|
|
47
46
|
const path = require('node:path');
|
|
48
|
-
|
|
49
|
-
const DEFAULT_RUNS_DIR = path.join(
|
|
50
|
-
os.homedir(),
|
|
51
|
-
'.claude', 'session-manager', 'scheduled-plans', 'runs'
|
|
52
|
-
);
|
|
47
|
+
const schedulerPaths = require('./schedulerPaths.cjs');
|
|
53
48
|
|
|
54
49
|
// Any status that is not yet a terminal outcome. Mirrors the status literals
|
|
55
50
|
// used throughout scheduler.cjs (see e.g. its DOD_SLUG_RE-adjacent status
|
|
@@ -436,14 +431,13 @@ function applyRetention(runsDir, settings, opts) {
|
|
|
436
431
|
* startup timer, alongside finalizeClosedDays) — never on its own timer.
|
|
437
432
|
*/
|
|
438
433
|
function runBootSweep(opts) {
|
|
439
|
-
const runsDir = (opts && opts.runsDir) ||
|
|
434
|
+
const runsDir = (opts && opts.runsDir) || schedulerPaths.runsDir();
|
|
440
435
|
const queueStore = require('./queueStore.cjs');
|
|
441
436
|
const state = queueStore.readMergedSync();
|
|
442
437
|
return applyRetention(runsDir, state.config || {}, { jobs: state.jobs || [] });
|
|
443
438
|
}
|
|
444
439
|
|
|
445
440
|
module.exports = {
|
|
446
|
-
DEFAULT_RUNS_DIR,
|
|
447
441
|
LIVE_STATUSES,
|
|
448
442
|
isLiveJob,
|
|
449
443
|
liveKeysFromJobs,
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* a conservative, delivery-confirmed offset) at once — see the two-phase
|
|
22
22
|
* design below.
|
|
23
23
|
*
|
|
24
|
-
* TWO-PHASE WATERMARK. Per file, ~/.
|
|
24
|
+
* TWO-PHASE WATERMARK. Per file, ~/.claude/session-manager/telemetry-
|
|
25
25
|
* watermarks.json tracks:
|
|
26
26
|
* - bytesEnqueued: how far we've read + handed lines to telemetryClient.
|
|
27
27
|
* Advances the instant a line has been processed
|
|
@@ -85,8 +85,14 @@ function lastRunSummary() {
|
|
|
85
85
|
return lastSummary ? { ...lastSummary } : null;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
+
let migrated = false;
|
|
88
89
|
function watermarksPath() {
|
|
89
|
-
|
|
90
|
+
const p = path.join(os.homedir(), '.claude', 'session-manager', 'telemetry-watermarks.json');
|
|
91
|
+
if (!migrated) {
|
|
92
|
+
migrated = true;
|
|
93
|
+
config.migrateLegacyHomeFile(path.join(os.homedir(), '.config', 'session-manager', 'telemetry-watermarks.json'), p);
|
|
94
|
+
}
|
|
95
|
+
return p;
|
|
90
96
|
}
|
|
91
97
|
|
|
92
98
|
function resolveDeps(deps = {}) {
|
|
@@ -154,7 +160,7 @@ function entryFor(wm, key) {
|
|
|
154
160
|
|
|
155
161
|
/**
|
|
156
162
|
* Known projects come from the same source of truth the renderer restores
|
|
157
|
-
* tabs from (sessionsStore.cjs's ~/.
|
|
163
|
+
* tabs from (sessionsStore.cjs's ~/.claude/session-manager/tabs.json — TAB =
|
|
158
164
|
* cwd = Main Project), deduped by normalized cwd, with ephemeral (worktree /
|
|
159
165
|
* tmpdir) cwds excluded before normalization even runs, since a worktree cwd
|
|
160
166
|
* would otherwise resolve back to its real project and be scanned twice
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* through the same queue/dedup/backoff machinery but sent one record at a
|
|
8
8
|
* time via sendSingle() rather than sendBatch().
|
|
9
9
|
*
|
|
10
|
-
* Records accumulate durably in ~/.
|
|
10
|
+
* Records accumulate durably in ~/.claude/session-manager/telemetry-queue.jsonl
|
|
11
11
|
* the instant they're accepted, and are only ever sent by flush(reason) — on
|
|
12
12
|
* a deliberate cadence (boot / daily / version-change / quit / manual), never
|
|
13
13
|
* on a short interval. Every record is idempotent by recordId and stamped at
|
|
@@ -111,12 +111,21 @@ function logWarn(message, meta) {
|
|
|
111
111
|
|
|
112
112
|
/**
|
|
113
113
|
* SM_TELEMETRY_SPOOL, when set, overrides the spool directory in place of
|
|
114
|
-
* `~/.
|
|
114
|
+
* `~/.claude/session-manager` — the explicit opt-in a test that genuinely
|
|
115
115
|
* needs to exercise real queue/sent-file I/O uses to prove it isn't about to
|
|
116
116
|
* write into a real user's spool (see isTestEnvironment() below).
|
|
117
117
|
*/
|
|
118
|
+
let spoolMigrated = false;
|
|
118
119
|
function spoolDir() {
|
|
119
|
-
|
|
120
|
+
if (process.env.SM_TELEMETRY_SPOOL) return process.env.SM_TELEMETRY_SPOOL;
|
|
121
|
+
const dir = path.join(os.homedir(), '.claude', 'session-manager');
|
|
122
|
+
if (!spoolMigrated) {
|
|
123
|
+
spoolMigrated = true;
|
|
124
|
+
const oldDir = path.join(os.homedir(), '.config', 'session-manager');
|
|
125
|
+
config.migrateLegacyHomeFile(path.join(oldDir, 'telemetry-queue.jsonl'), path.join(dir, 'telemetry-queue.jsonl'));
|
|
126
|
+
config.migrateLegacyHomeFile(path.join(oldDir, 'telemetry-sent.json'), path.join(dir, 'telemetry-sent.json'));
|
|
127
|
+
}
|
|
128
|
+
return dir;
|
|
120
129
|
}
|
|
121
130
|
|
|
122
131
|
function queuePath() {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* telemetrySettings — persists product-telemetry consent/config + install identity.
|
|
3
3
|
*
|
|
4
|
-
* Storage: ~/.
|
|
4
|
+
* Storage: ~/.claude/session-manager/telemetry.json
|
|
5
5
|
* Shape: {
|
|
6
6
|
* enabled: boolean, // ON by default — hard kill switch is SM_TELEMETRY=0
|
|
7
7
|
* installId: string, // crypto.randomUUID(), minted once, the only identity
|
|
@@ -45,8 +45,14 @@ const DEFAULTS = Object.freeze({
|
|
|
45
45
|
|
|
46
46
|
const KNOWN_KEYS = new Set(Object.keys(DEFAULTS));
|
|
47
47
|
|
|
48
|
+
let migrated = false;
|
|
48
49
|
function storePath() {
|
|
49
|
-
|
|
50
|
+
const p = path.join(os.homedir(), '.claude', 'session-manager', 'telemetry.json');
|
|
51
|
+
if (!migrated) {
|
|
52
|
+
migrated = true;
|
|
53
|
+
config.migrateLegacyHomeFile(path.join(os.homedir(), '.config', 'session-manager', 'telemetry.json'), p);
|
|
54
|
+
}
|
|
55
|
+
return p;
|
|
50
56
|
}
|
|
51
57
|
|
|
52
58
|
function isValid(cfg) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* validationSentinels.cjs — parses the `VALIDATION: <slug> VERIFIED` /
|
|
5
|
+
* `VALIDATION: <slug> REFUTED — <reason>` lines a plan-level validator job
|
|
6
|
+
* (persona `validator`, PRD 1405) emits, one per PRD it checked, at the end
|
|
7
|
+
* of its run. Pure text-in/array-out; no filesystem or scheduler knowledge.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const SENTINEL_RE = /^VALIDATION:\s+(\S+)\s+(VERIFIED|REFUTED)(?:\s*[—–-]+\s*(.*))?\s*$/gm;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* parseValidationSentinels(text) → Array<{ slug, verdict, reason }>
|
|
14
|
+
*
|
|
15
|
+
* `verdict` is lowercased ('verified' | 'refuted'); `reason` is '' when
|
|
16
|
+
* absent. Last matching line per slug wins, but the ORDER of the returned
|
|
17
|
+
* array follows each slug's first appearance in `text`, not its last.
|
|
18
|
+
*/
|
|
19
|
+
function parseValidationSentinels(text) {
|
|
20
|
+
if (!text) return [];
|
|
21
|
+
const bySlug = new Map();
|
|
22
|
+
for (const match of text.matchAll(SENTINEL_RE)) {
|
|
23
|
+
const [, slug, verdict, reason] = match;
|
|
24
|
+
bySlug.set(slug, { slug, verdict: verdict.toLowerCase(), reason: reason || '' });
|
|
25
|
+
}
|
|
26
|
+
return Array.from(bySlug.values());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
module.exports = { parseValidationSentinels };
|
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
* owns) and, via a single cost-gated `claude -p` pass, organizes them into
|
|
9
9
|
* named semantic clusters with `[[wikilink]]`-derived connections.
|
|
10
10
|
*
|
|
11
|
-
* Cache:
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* Cache: <cwd>/session-manager-operations/memory-clusters/clusters.json (one
|
|
12
|
+
* file per project — see memory-clusters/README.md). The `claude -p` call
|
|
13
|
+
* only fires when the caller passes `refresh: true` — this is the cost gate;
|
|
14
|
+
* the renderer wires it to an explicit button.
|
|
14
15
|
*
|
|
15
16
|
* Spawn/capture/timeout pattern mirrors kg.cjs's runClaude (~line 316-351):
|
|
16
17
|
* stdin closed, model pinned, hard timeout that resolves {ok:false} rather
|
|
@@ -26,17 +27,34 @@ const { extractJson } = require('./lib/extractJson.cjs');
|
|
|
26
27
|
const { runClaudeP } = require('./lib/runClaudeP.cjs');
|
|
27
28
|
const { writeJson } = require('./config.cjs');
|
|
28
29
|
const config = require('./config.cjs');
|
|
30
|
+
const { opsPath } = require('./lib/opsOwnership.cjs');
|
|
29
31
|
const { MEMORY_SLUG_RE } = require('./lib/memorySlug.cjs');
|
|
30
32
|
|
|
31
33
|
const HOME = os.homedir();
|
|
32
|
-
const
|
|
34
|
+
const CACHE_WRITER = 'memory-clusters';
|
|
33
35
|
|
|
34
36
|
function memoryDir(workspace) {
|
|
35
37
|
return path.join(HOME, '.claude', 'projects', workspace, 'memory');
|
|
36
38
|
}
|
|
37
39
|
|
|
38
|
-
function
|
|
39
|
-
return
|
|
40
|
+
function clustersCachePath(cwd) {
|
|
41
|
+
return opsPath(cwd, 'memory-clusters', 'clusters.json');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Resolves `cwd` to a real, boundary-checked project root, or null when
|
|
46
|
+
* `cwd` is absent/invalid. Mirrors memoryTool.cjs's stale() cwd handling:
|
|
47
|
+
* an absent/unusable cwd disables the per-project cache entirely rather than
|
|
48
|
+
* throwing — the cache is purely regenerable, so a caller with no cwd (or a
|
|
49
|
+
* cwd this process cannot vouch for) just gets an uncached aggregate.
|
|
50
|
+
*/
|
|
51
|
+
function resolveCwd(cwd) {
|
|
52
|
+
if (typeof cwd !== 'string' || !cwd) return null;
|
|
53
|
+
try {
|
|
54
|
+
return config.validatePath(cwd);
|
|
55
|
+
} catch {
|
|
56
|
+
return null;
|
|
57
|
+
}
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
// System prompt for clustering — sets the role server-side so the CLI treats
|
|
@@ -143,17 +161,19 @@ function parseClusters(rawLlmText, slugs) {
|
|
|
143
161
|
return { clusters, orphans };
|
|
144
162
|
}
|
|
145
163
|
|
|
146
|
-
async function readCache(
|
|
147
|
-
const
|
|
164
|
+
async function readCache(cwd) {
|
|
165
|
+
const realCwd = resolveCwd(cwd);
|
|
166
|
+
if (!realCwd) return null;
|
|
167
|
+
const r = await config.readText(clustersCachePath(realCwd));
|
|
148
168
|
if (!r.exists) return null;
|
|
149
169
|
try { return JSON.parse(r.text); } catch { return null; }
|
|
150
170
|
}
|
|
151
171
|
|
|
152
|
-
async function aggregate({ workspace, refresh }) {
|
|
172
|
+
async function aggregate({ workspace, refresh, cwd }) {
|
|
153
173
|
const ws = typeof workspace === 'string' && workspace ? workspace : encodeCwd(null);
|
|
154
174
|
|
|
155
175
|
if (!refresh) {
|
|
156
|
-
const cached = await readCache(
|
|
176
|
+
const cached = await readCache(cwd);
|
|
157
177
|
if (cached) return { ...cached, cached: true };
|
|
158
178
|
return { workspace: ws, generatedAt: null, clusters: [], orphans: [], cached: false };
|
|
159
179
|
}
|
|
@@ -174,7 +194,11 @@ async function aggregate({ workspace, refresh }) {
|
|
|
174
194
|
}
|
|
175
195
|
|
|
176
196
|
const result = { workspace: ws, generatedAt: Date.now(), clusters, orphans };
|
|
177
|
-
|
|
197
|
+
const realCwd = resolveCwd(cwd);
|
|
198
|
+
if (realCwd) {
|
|
199
|
+
config.addAllowedRoot(realCwd);
|
|
200
|
+
await writeJson(clustersCachePath(realCwd), result, { writer: CACHE_WRITER });
|
|
201
|
+
}
|
|
178
202
|
return { ...result, cached: false };
|
|
179
203
|
}
|
|
180
204
|
|
|
@@ -189,4 +213,5 @@ module.exports = {
|
|
|
189
213
|
aggregate,
|
|
190
214
|
// exported for tests
|
|
191
215
|
parseFrontmatter,
|
|
216
|
+
clustersCachePath,
|
|
192
217
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* otelSettings — persists OTEL telemetry export configuration.
|
|
3
3
|
*
|
|
4
|
-
* Storage: ~/.
|
|
4
|
+
* Storage: ~/.claude/session-manager/otel.json
|
|
5
5
|
* Shape: {
|
|
6
6
|
* enabled: boolean,
|
|
7
7
|
* endpoint: string, // OTLP/HTTP traces endpoint
|
|
@@ -30,8 +30,14 @@ const DEFAULTS = Object.freeze({
|
|
|
30
30
|
schemaVersion: SCHEMA_VERSION,
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
+
let migrated = false;
|
|
33
34
|
function storePath() {
|
|
34
|
-
|
|
35
|
+
const p = path.join(os.homedir(), '.claude', 'session-manager', 'otel.json');
|
|
36
|
+
if (!migrated) {
|
|
37
|
+
migrated = true;
|
|
38
|
+
config.migrateLegacyHomeFile(path.join(os.homedir(), '.config', 'session-manager', 'otel.json'), p);
|
|
39
|
+
}
|
|
40
|
+
return p;
|
|
35
41
|
}
|
|
36
42
|
|
|
37
43
|
function isValid(cfg) {
|