claude-code-session-manager 0.35.3 → 0.35.5

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/index.html CHANGED
@@ -7,10 +7,10 @@
7
7
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
8
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
9
  <link href="https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,400;0,6..72,500;0,6..72,600;0,6..72,700;1,6..72,400&family=Geist:wght@300;400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
10
- <script type="module" crossorigin src="./assets/index-CHKMzzCM.js"></script>
10
+ <script type="module" crossorigin src="./assets/index-DIVOtBGO.js"></script>
11
11
  <link rel="modulepreload" crossorigin href="./assets/monaco-editor-BW5C4Iv1.js">
12
12
  <link rel="stylesheet" crossorigin href="./assets/monaco-editor-BTnBOi8r.css">
13
- <link rel="stylesheet" crossorigin href="./assets/index-DVqmrWP3.css">
13
+ <link rel="stylesheet" crossorigin href="./assets/index-BHyeZfve.css">
14
14
  </head>
15
15
  <body class="bg-bg text-fg font-sans antialiased">
16
16
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-session-manager",
3
- "version": "0.35.3",
3
+ "version": "0.35.5",
4
4
  "description": "Local cockpit for the Claude Code CLI — multi-tab terminal, full config surface, scheduler, voice dictation, and live observability.",
5
5
  "type": "module",
6
6
  "main": "src/main/index.cjs",
@@ -38,10 +38,19 @@ the only step that runs on the cheaper executor.
38
38
  ## Standards (single source of truth)
39
39
 
40
40
  The engineering standards (Performance, Debugging, API reuse / single source of truth, TDD,
41
- and the executor-facing Execution discipline) live in **`standards.md`** beside this file:
42
- `~/.claude/skills/develop/standards.md`. Read it, hold it while planning, and inline it
43
- verbatim into every PRD you emit (Phase 1 step 4). Never restate or fork its content — one
44
- concept, one implementation.
41
+ and the executor-facing Execution discipline) live in **`standards.md`** beside this file, in
42
+ the same skill directory (`.../skills/develop/standards.md` NOT `~/.claude/skills/develop/`,
43
+ which is a different, non-existent path; resolve it relative to wherever this SKILL.md itself
44
+ was loaded from). **Re-read that file fresh with the Read tool immediately before pasting it
45
+ into each PRD (Phase 1 step 4) — never reuse a copy cached earlier in the same conversation.**
46
+ A long authoring session can span an edit to `standards.md` (including one autonomously applied
47
+ by a prior incident's fix-plan) without the model noticing; pasting a stale in-context copy
48
+ silently ships PRDs missing the latest execution-discipline rules. This is exactly the
49
+ single-source-of-truth violation the standards themselves warn against — don't let it happen to
50
+ the standards block itself. (Incident: PRDs 467/468 authored late in a long session carried a
51
+ standards.md snapshot from before the "You ARE the executor" guard was added earlier that same
52
+ session, so PRD 467's headless run repeated the exact anti-pattern the guard exists to prevent.)
53
+ Never restate or fork its content — one concept, one implementation.
45
54
 
46
55
  For interactive dev work, also apply the `test-driven-development` and `systematic-debugging`
47
56
  skills; the headless PRDs get the distilled core from `standards.md` instead, since they
@@ -195,7 +204,7 @@ single definition of "tracked to done" for both entry paths.
195
204
  ## References (reuse, don't duplicate)
196
205
 
197
206
  - `~/.claude/session-manager/scheduled-plans/PRD_AUTHORING.md` — the §1–§10 safety rules.
198
- - `~/.claude/skills/develop/standards.md` — the engineering + execution-discipline rules inlined into every PRD.
207
+ - `standards.md` beside this file — the engineering + execution-discipline rules inlined into every PRD. Re-read fresh each time (see "Standards" above) — don't reuse a cached copy.
199
208
  - `test-driven-development`, `systematic-debugging` — interactive dev sessions.
200
209
  - `requesting-code-review` — the Phase-2 review gate.
201
210
 
@@ -58,6 +58,7 @@ Data-driven from 400+ scheduler runs: long hangs (not bad code) are the dominant
58
58
  - **Verify before done.** Run the acceptance test command once before declaring success. If it's red, fix it or `exit 1` with the failure — never end the run on a failing test (that trips the verifier's `transcript_errors` downgrade).
59
59
  - **Fail loud, fail fast.** On any step failure, print one diagnostic line and `exit 1`; don't swallow with `|| true` or spin in a silent retry. A `rateLimited` exit-1 is the scheduler's benign auto-pause (auto-resumes next window) — not a failure to engineer around.
60
60
  - **Stay in the AC.** Do not add work past the acceptance checklist ("while we're here" generators/fixtures are the post-AC-overrun incident). Body must be clean UTF-8 — no NUL/control bytes.
61
+ - **You ARE the executor — never re-queue or self-schedule.** A headless PRD run must perform its own acceptance criteria directly. Do NOT invoke `/develop`, `/process-feedback`, or any queue-authoring skill from inside a run — those are interactive main-loop skills that author a *new* PRD and return, so the run exits 0 having done nothing (no commit, no sentinel → `needs_review` with `no_verdict_sentinel`). Do NOT call `ScheduleWakeup`/set a tracking loop either — the process exits when the run ends and nothing re-invokes it. If the PRD's work looks large, decompose and execute it inline within this run; never delegate it back to the queue. (Incident: PRD 460 invoked `/develop`, spawned a duplicate PRD 461, and exited 0 with no work.)
61
62
  - **Negative-assertion checks must exit 0 when clean.** A check that verifies the *absence* of something (a `grep` that should find nothing, "no leftover X", `diff` expecting no change) must return exit 0 on the clean case. A bare `grep` exits **1 on no-match** — so the *success* path surfaces as `is_error=true` and the verifier downgrades a perfect run to `needs_review`. Always invert: `if <detector>; then echo "HALT: <what was found>"; exit 1; fi; echo clean`. Never let the no-match/empty path carry the non-zero exit.
62
63
  - **Recover or annotate every error — don't strand a Traceback in the transcript.** The verifier downgrades an otherwise-perfect run to `needs_review` when a `Traceback`/`Error` appears with *no visible recovery within ~10 lines* (the `transcript_errors` heuristic — the single most common false-positive on green deliverables). Two executor habits cause it: (1) **throwaway probes that error** — an inline `python -c` with a quoting/f-string slip, a wrong kwarg, a bad path. When a probe errors, immediately re-run the corrected version *or* print one line `# expected/handled: <why>` right after, so recovery is adjacent. Don't move on leaving a bare error as the last thing in that step. Prefer a small temp `.py` file over a fragile multi-quote `python -c` one-liner (inline f-string errors are the top source of stranded tracebacks). (2) See the timeout rule below.
63
64
  - **An *expected* bounded-timeout (exit 124) must be annotated, not bare.** `timeout`-capping a genuinely long task you expect to hit the cap (a full-universe ingest, a long scan) is correct — but a bare `Exit code 124` reads as a failure to the verifier. Wrap it so the cap is a success-with-note: `timeout 120 <cmd> || { rc=$?; [ $rc -eq 124 ] && echo "hit time cap — idempotent/partial, rows persist incrementally; OK" || { echo "HALT: <cmd> failed rc=$rc"; exit 1; }; }`. (Distinguish 124 = expected cap from a real non-zero.) For work that legitimately needs longer than a safe cap, run it in the background and poll a bounded number of times rather than capping the foreground command.
@@ -1,28 +1,31 @@
1
1
  ---
2
2
  name: explain-to-me
3
3
  description: >-
4
- Build and maintain HUMAN_LEARN/ — a human-readable, visually-rich HTML
5
- knowledge base at the repo root that explains how this project actually works.
6
- Deep-probes the codebase for a given topic/component (real file:line refs,
7
- real constants, real on-disk state — never invented), then writes or updates a
8
- clean, self-contained HTML — one combined component page (HUMAN_LEARN/index.html)
9
- plus a separate skill-chain map (HUMAN_LEARN/SKILL_MAP.html) for the local-dev
10
- workflow with advanced CSS, flow diagrams, sticky nav, collapsible tables. Use
11
- whenever the user says "/explain-to-me X", "explain X to me", "explain how X
12
- works", "document X for me", "add X to HUMAN_LEARN", "make me a human-readable
13
- page for X", or "update the skill map". Keywords: explain, explain-to-me,
14
- human-learn, HUMAN_LEARN, skill map, document, one-pager, how does X work, visual
15
- explainer, knowledge base.
4
+ Build and maintain session-manager-operations/HUMAN_LEARN/ — a human-readable,
5
+ visually-rich HTML knowledge base at the repo root that explains how this
6
+ project actually works. Deep-probes the codebase for a given topic/component
7
+ (real file:line refs, real constants, real on-disk state — never invented),
8
+ then writes or updates a clean, self-contained HTML — one combined component
9
+ page (session-manager-operations/HUMAN_LEARN/index.html) plus a separate
10
+ skill-chain map (session-manager-operations/HUMAN_LEARN/SKILL_MAP.html) for
11
+ the local-dev workflow with advanced CSS, flow diagrams, sticky nav,
12
+ collapsible tables. Use whenever the user says "/explain-to-me X", "explain X
13
+ to me", "explain how X works", "document X for me", "add X to HUMAN_LEARN",
14
+ "make me a human-readable page for X", or "update the skill map". Keywords:
15
+ explain, explain-to-me, human-learn, HUMAN_LEARN, skill map, document,
16
+ one-pager, how does X work, visual explainer, knowledge base.
16
17
  model: opus
17
18
  ---
18
19
 
19
20
  # explain-to-me
20
21
 
21
22
  Turn a question about this codebase ("explain how the scheduler works") into a
22
- **clean, visually-rich HTML one-pager** in `HUMAN_LEARN/` at the repo root, and
23
- keep that folder's index coherent as it grows. The output is for a *human*
24
- catching up on the project — not for the model. It is the human-facing complement
25
- to the Memory tab (which is Claude's own terse recall store).
23
+ **clean, visually-rich HTML one-pager** in `session-manager-operations/HUMAN_LEARN/`
24
+ at the repo root, and keep that folder's index coherent as it grows. The output
25
+ is for a *human* catching up on the project — not for the model. It is the
26
+ human-facing complement to the Memory tab (which is Claude's own terse recall
27
+ store). All session-manager per-project operations live under
28
+ `session-manager-operations/`.
26
29
 
27
30
  The bar: **clean, explanatory, visually rich, self-contained** (opens directly in
28
31
  a browser — no build, no external network deps). Every claim grounded in the
@@ -53,26 +56,35 @@ If you can't state it in the present tense without a date or a PRD, cut it.
53
56
  ## Where things live
54
57
 
55
58
  - Repo root: `git rev-parse --show-toplevel` (fall back to cwd if not a git repo).
56
- - Knowledge base: `<root>/HUMAN_LEARN/`. Create it if missing.
57
- - **One combined page** — `HUMAN_LEARN/index.html`. Every component explainer is a
58
- `<section>` in this single file, under a sticky section-nav, ordered so the page
59
- reads top-to-bottom as one narrative. A new topic becomes a **new `<section>`**,
60
- never a new file. One navigable page that logically follows itself beats a folder
61
- of thin pages; do not re-introduce per-topic `.html` files.
62
- - **The Skill Map** `HUMAN_LEARN/SKILL_MAP.html`, a **separate, dedicated** page
63
- (NOT a section of index.html) that visualizes the *local-development skill chain*:
64
- the two intakes (interactive human prompt; agent feedback via `/process-feedback`)
65
- converging on `/develop`, which owns PRD authoring + reads `standards.md`, queues onto the
66
- scheduler, and gates with review/verify plus `/my-feedback` outbound. It is the
67
- "how I build on this project" companion to index.html's "how this project works."
68
- index.html links to it from the nav; it links back.
59
+ - Knowledge base: `<root>/session-manager-operations/HUMAN_LEARN/`. Create it
60
+ (and `session-manager-operations/` if needed) if missing.
61
+ - **One combined page** `session-manager-operations/HUMAN_LEARN/index.html`.
62
+ Every component explainer is a `<section>` in this single file, under a sticky
63
+ section-nav, ordered so the page reads top-to-bottom as one narrative. A new
64
+ topic becomes a **new `<section>`**, never a new file. One navigable page that
65
+ logically follows itself beats a folder of thin pages; do not re-introduce
66
+ per-topic `.html` files.
67
+ - **The Skill Map** `session-manager-operations/HUMAN_LEARN/SKILL_MAP.html`, a
68
+ **separate, dedicated** page (NOT a section of index.html) that visualizes the
69
+ *local-development skill chain*: the two intakes (interactive human prompt;
70
+ agent feedback via `/process-feedback`) converging on `/develop`, which owns
71
+ PRD authoring + reads `standards.md`, queues onto the scheduler, and gates with
72
+ review/verify — plus `/my-feedback` outbound. It is the "how I build on this
73
+ project" companion to index.html's "how this project works." index.html links
74
+ to it from the nav; it links back.
69
75
 
70
76
  ## Workflow
71
77
 
72
- 1. **Locate & survey.** Find the repo root + `HUMAN_LEARN/`. If `index.html`
73
- exists, read it to see what's already documented — you are *maintaining* a
74
- knowledge base, not starting fresh. If the topic already has a page/section,
75
- you are updating it, not duplicating it.
78
+ 0. **Self-migrate a legacy root-level folder first.** If `HUMAN_LEARN/` exists
79
+ at the repo root **and** `session-manager-operations/HUMAN_LEARN/` does not,
80
+ relocate it now `mkdir -p session-manager-operations` (if needed), then
81
+ `git mv HUMAN_LEARN session-manager-operations/HUMAN_LEARN` when the repo is
82
+ git-tracked, else a plain `mv`. This converges any project this skill runs
83
+ in to the new layout even if its bulk-relocation PRD hasn't landed yet.
84
+ 1. **Locate & survey.** Find the repo root + `session-manager-operations/HUMAN_LEARN/`.
85
+ If `index.html` exists, read it to see what's already documented — you are
86
+ *maintaining* a knowledge base, not starting fresh. If the topic already has
87
+ a page/section, you are updating it, not duplicating it.
76
88
  2. **Deep-probe the topic.** Read the real code. Gather exact `file:line`
77
89
  references, real constant names + values, and real on-disk state where it
78
90
  matters (run `ls`/`cat`/`jq` to ground claims in what actually exists). For a
@@ -98,8 +110,9 @@ If you can't state it in the present tense without a date or a PRD, cut it.
98
110
  in `~/.claude/skills/`, a changed Role/Never contract, a re-routed step in the
99
111
  chain — refresh `SKILL_MAP.html` (its chain diagram + the role-contract table +
100
112
  the two walkthroughs) so it stays a faithful map of the actual files on disk.
101
- 7. **Tell the user the path** to open (`HUMAN_LEARN/index.html` or
102
- `HUMAN_LEARN/SKILL_MAP.html`, optionally with a `#anchor`). Do not commit unless asked.
113
+ 7. **Tell the user the path** to open (`session-manager-operations/HUMAN_LEARN/index.html`
114
+ or `session-manager-operations/HUMAN_LEARN/SKILL_MAP.html`, optionally with a
115
+ `#anchor`). Do not commit unless asked.
103
116
 
104
117
  ## Grounding rules (non-negotiable)
105
118
 
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: my-feedback
3
3
  description: >-
4
- File a feedback/enhancement request FROM the current project INTO another
4
+ File a feedback or enhancement request FROM the current project INTO another
5
5
  project's inbound feedback folder, following that project's own README
6
6
  convention. The cross-project complement of /process-feedback (which works the
7
7
  *current* project's inbox). Use whenever the user says "/my-feedback to X",
@@ -38,21 +38,25 @@ The invocation is `/my-feedback to <<project>>` (or "send feedback to
38
38
  <project>`."* Then list candidate sibling projects that have an intake folder:
39
39
  ```bash
40
40
  for d in ~/Projects/*/; do
41
- [ -d "$d/feedback" ] && echo " - $(basename "$d") (feedback/)"
41
+ [ -d "$d/session-manager-operations/feedback" ] && echo " - $(basename "$d") (session-manager-operations/feedback/)"
42
42
  done
43
43
  ```
44
44
  and stop.
45
- - Resolve the path: `~/Projects/<project>/feedback/`. This is the **one**
46
- canonical folder name — do not accept or invent variants (`external-feedback/`,
47
- `feedback-inbox/`, etc.) even if a near-miss directory exists; a stray
48
- differently-named folder is drift, not a valid convention (burrow's
49
- `external-feedback/` existed for ~2.5 weeks from exactly this mistake before
50
- being merged back into `feedback/` on 2026-07-10 — don't recreate it, in burrow
51
- or anywhere else). Fuzzy-match a near miss only to confirm it's actually named
52
- `feedback/`, not to accept a differently-named folder as equivalent.
53
- - **If the target has no `feedback/` folder, STOP.** Don't invent one under any
54
- name say the project doesn't accept feedback this way and ask how to proceed
55
- (it may take requests via issues, a different folder, or not at all).
45
+ - Resolve the path: `~/Projects/<project>/session-manager-operations/feedback/`.
46
+ This is the **one** canonical folder name — do not accept or invent variants
47
+ (`external-feedback/`, `feedback-inbox/`, a root-level `feedback` folder, etc.) even
48
+ if a near-miss directory exists; a stray differently-named folder is drift,
49
+ not a valid convention (burrow's `external-feedback/` existed for ~2.5 weeks
50
+ from exactly this mistake before being merged back into `feedback` on
51
+ 2026-07-10 — don't recreate it, in burrow or anywhere else). Fuzzy-match a
52
+ near miss only to confirm it's actually named
53
+ `session-manager-operations/feedback/`, not to accept a differently-named
54
+ folder as equivalent. All session-manager per-project operations live under
55
+ `session-manager-operations/`.
56
+ - **If the target has no `session-manager-operations/feedback/` folder, STOP.**
57
+ Don't invent one under any name — say the project doesn't accept feedback
58
+ this way and ask how to proceed (it may take requests via issues, a
59
+ different folder, or not at all).
56
60
 
57
61
  ## 1. Read the target's README FIRST — it is the authority
58
62
 
@@ -61,9 +65,9 @@ unique per project** (file-naming scheme, required sections, the status-log
61
65
  table, the closing ritual). Read it before writing anything:
62
66
 
63
67
  ```bash
64
- cat ~/Projects/<project>/feedback/README.md
65
- ls ~/Projects/<project>/feedback/ # open items + numbering
66
- ls ~/Projects/<project>/feedback/processed/ 2>/dev/null # closed examples to match
68
+ cat ~/Projects/<project>/session-manager-operations/feedback/README.md
69
+ ls ~/Projects/<project>/session-manager-operations/feedback/ # open items + numbering
70
+ ls ~/Projects/<project>/session-manager-operations/feedback/processed/ 2>/dev/null # closed examples to match
67
71
  ```
68
72
 
69
73
  Read one **processed** example end-to-end to copy the house format exactly — the
@@ -62,9 +62,9 @@ files it points to:
62
62
  all, that itself is a finding — recommend adding it.
63
63
  - **The levers** — the files the section names as the knobs (tiers/config,
64
64
  selection, throughput/cadence/registry, etc.).
65
- - **The feedback intake** — `feedback/` at the repo root (the one canonical
66
- name — do not treat a differently-named folder as equivalent), and its
67
- `README.md` convention.
65
+ - **The feedback intake** — `session-manager-operations/feedback/` at the repo
66
+ root (the one canonical name — do not treat a differently-named folder as
67
+ equivalent), and its `README.md` convention.
68
68
 
69
69
  **If CLAUDE.md declares no North-Star KPI section, STOP** and tell the user this
70
70
  project hasn't declared one — the KPI belongs in CLAUDE.md's mission statement
@@ -228,7 +228,7 @@ Give it the 3 temp files **and** the Step-2 in-flight/cooldown set. It must:
228
228
  failure mode this rule exists to prevent.
229
229
  - **Write ONE consolidated feedback item** (for the this-project-owned half) into
230
230
  this project's intake folder, named by the folder README's convention
231
- (`feedback/<YYYY-MM-DD>-NN-kpi-optimization.md`, next free `NN`), with: title,
231
+ (`session-manager-operations/feedback/<YYYY-MM-DD>-NN-kpi-optimization.md`, next free `NN`), with: title,
232
232
  **From:** `optimize-kpi loop`, date (PT), priority + why, **TL;DR**, **Evidence**
233
233
  (cite the Step-1 scorecard numbers, the **Step-1b usage+log findings** the root
234
234
  cause rests on, *and* the Step-2 prior-lever grade), **Why it matters**
@@ -8,8 +8,9 @@ description: >-
8
8
  execution from there), and fold lessons back into the folder's README so
9
9
  future feedback (written by other agents/projects) gets better. Use whenever the user says "/process-feedback",
10
10
  "review the feedback folder", "work through the feedback", "any open
11
- feedback?", or drops new files into feedback/. Keywords: feedback, intake,
12
- cross-project requests, process feedback, triage feedback.
11
+ feedback?", or drops new files into session-manager-operations/feedback/.
12
+ Keywords: feedback, intake, cross-project requests, process feedback, triage
13
+ feedback.
13
14
  model: opus
14
15
  ---
15
16
 
@@ -21,15 +22,18 @@ an interactive human prompt. It evaluates inbound feedback, dispatches the codea
21
22
  RESOLUTION, archive). It does **not** implement, and it does **not** re-specify how PRDs are
22
23
  tracked — that lives once, in `/develop` Phase 2.
23
24
 
24
- Work the project's intra-project feedback intake (`feedback/` at the repo root —
25
- this is the **one** canonical name; do not create or treat a differently-named
26
- folder like `external-feedback/` as equivalent, even for cross-service requests
27
- that split caused ~2.5 weeks of drifted duplicate tracking in burrow before
28
- being merged back on 2026-07-10). Each file is a request from an
29
- upstream/downstream service in the same stack (e.g. Burrow signal-builder
30
- social-signals-trader) cross-service origin does not mean a different folder,
31
- it's still just `feedback/`. The folder's own `README.md` is the authority on
32
- file conventions read it first; the steps below are the process.
25
+ Work the project's intra-project feedback intake
26
+ (`session-manager-operations/feedback/` at the repo root this is the **one**
27
+ canonical name; do not create or treat a differently-named folder like
28
+ `external-feedback/` as equivalent, even for cross-service requests that
29
+ split caused ~2.5 weeks of drifted duplicate tracking in burrow before being
30
+ merged back on 2026-07-10). Each file is a request from an upstream/downstream
31
+ service in the same stack (e.g. Burrow signal-builder ⇄ social-signals-trader)
32
+ cross-service origin does not mean a different folder, it's still just
33
+ `session-manager-operations/feedback/`. The folder's own `README.md` is the
34
+ authority on file conventions — read it first; the steps below are the
35
+ process. All session-manager per-project operations live under
36
+ `session-manager-operations/`.
33
37
 
34
38
  **Core principle:** this skill *triages and dispatches*; it does not implement.
35
39
  Anything that requires writing code for this project is decomposed and queued as
@@ -55,15 +59,27 @@ prevent.
55
59
 
56
60
  ## Steps
57
61
 
62
+ ### 0a. Self-migrate a legacy root-level folder first
63
+
64
+ Before anything else, check for the pre-migration layout: if a legacy
65
+ `feedback` folder exists at the repo root **and**
66
+ `session-manager-operations/feedback/` does not, relocate it now —
67
+ `mkdir -p session-manager-operations` (if needed), then
68
+ `git mv feedback session-manager-operations/feedback` when the repo is
69
+ git-tracked, else a plain `mv`. This converges any project this skill runs in
70
+ to the new layout even if its bulk-relocation PRD hasn't landed yet. Only then
71
+ continue with step 0.
72
+
58
73
  ### 0. Quick-exit — bail in milliseconds if there's nothing to do
59
74
 
60
75
  **Do this first, cheaply, before reading any code or spawning anything.** This
61
76
  skill is run on a schedule across many projects (the scheduler's feedback sweep),
62
77
  so the empty case must cost almost nothing:
63
78
 
64
- - If `feedback/` doesn't exist report "no feedback intake in `<project>`" and
79
+ - If `session-manager-operations/feedback/` doesn't exist (after the step-0a
80
+ self-migration check) → report "no feedback intake in `<project>`" and
65
81
  **EXIT**. (Don't check for or fall back to an `external-feedback/`-style
66
- variant — `feedback/` is the only canonical name.)
82
+ variant — `session-manager-operations/feedback/` is the only canonical name.)
67
83
  - If the folder exists but holds **no open item** (every file is in `processed/`
68
84
  or marked ✅/archived; nothing open/🆕) → report "no open feedback in
69
85
  `<project>`" and **EXIT immediately**. Do NOT read source, evaluate, or start
@@ -74,7 +90,7 @@ so the empty case must cost almost nothing:
74
90
 
75
91
  ### 1. Read the intake
76
92
 
77
- - Read `feedback/README.md` (conventions + the status log), then every open/🆕
93
+ - Read `session-manager-operations/feedback/README.md` (conventions + the status log), then every open/🆕
78
94
  file still in the inbox. Under this contract a **🛠 (queued) item has already
79
95
  been archived to `processed/`** at disposition time — so anything still sitting
80
96
  in the inbox is genuinely un-dispositioned and needs a pass. You will not find
@@ -95,7 +111,7 @@ drifted. Then classify each ask:
95
111
  RESOLUTION with the reason; never silently skip. Resolved now (no code) →
96
112
  close immediately in step 4.
97
113
  - **Theirs, forward** — the root cause lives in another service. Do NOT reach
98
- across the boundary to hack around it; file a feedback/PRD item in *that*
114
+ across the boundary to hack around it; file a feedback or PRD item in *that*
99
115
  project's intake folder and reference it (use `/my-feedback to <project>`).
100
116
  Resolved now (no code in *this* repo) → close immediately in step 4.
101
117
 
@@ -127,7 +143,7 @@ Record the emitted PRD filenames/ids — you need them for steps 4–6.
127
143
  ### 4. Disposition + archive every item now — this is the definition of done
128
144
 
129
145
  Every open item gets a disposition, a `## RESOLUTION`, and a `git mv` to
130
- `feedback/processed/` **in this pass** — archival is at disposition time, not
146
+ `session-manager-operations/feedback/processed/` **in this pass** — archival is at disposition time, not
131
147
  delivery time. Do NOT leave a queued item in the inbox "until its PRD lands":
132
148
  that is the drift this contract forbids. By disposition:
133
149
 
@@ -0,0 +1,137 @@
1
+ /**
2
+ * chat-mcp-consent-notice.test.cjs — regression for the headless MCP-consent
3
+ * hang: a chat run with stdin closed can never answer an MCP server's own
4
+ * interactive consent flow (e.g. `/design consent`). This drives the real
5
+ * stdout parser with a synthetic stream-json `tool_result` denial line and
6
+ * asserts a `chat:run:notice` event is broadcast — informational, not a
7
+ * terminal event.
8
+ *
9
+ * Run: timeout 120 node --test src/main/__tests__/chat-mcp-consent-notice.test.cjs
10
+ */
11
+
12
+ 'use strict';
13
+
14
+ delete process.env.SM_CHAT_CONCURRENCY;
15
+
16
+ const { test } = require('node:test');
17
+ const assert = require('node:assert/strict');
18
+ const fs = require('node:fs');
19
+ const os = require('node:os');
20
+ const path = require('node:path');
21
+
22
+ function writeStub(lines) {
23
+ const stubPath = path.join(os.tmpdir(), `sm-claude-stub-${process.pid}-${Math.floor(Math.random() * 1e9)}.cjs`);
24
+ const body = lines.map((l) => `process.stdout.write(${JSON.stringify(JSON.stringify(l))} + "\\n");`).join('\n');
25
+ fs.writeFileSync(stubPath, `#!${process.execPath}\n${body}\nprocess.exit(0);\n`, { mode: 0o755 });
26
+ return stubPath;
27
+ }
28
+
29
+ function isTerminal(channel) {
30
+ return (
31
+ channel === 'chat:run:complete' ||
32
+ channel === 'chat:run:needs-input' ||
33
+ channel === 'chat:run:error'
34
+ );
35
+ }
36
+
37
+ test('MCP consent denial in a tool_result surfaces a chat:run:notice event', async () => {
38
+ const stubPath = writeStub([
39
+ {
40
+ type: 'user',
41
+ message: {
42
+ content: [
43
+ {
44
+ type: 'tool_result',
45
+ tool_use_id: 'toolu_1',
46
+ is_error: true,
47
+ content: [
48
+ {
49
+ type: 'text',
50
+ text:
51
+ "Claude Design: write_files The user hasn't granted this — run /design consent " +
52
+ "to grant it (it can't be approved automatically in this permission mode).",
53
+ },
54
+ ],
55
+ },
56
+ ],
57
+ },
58
+ },
59
+ { type: 'result', subtype: 'success', result: 'done anyway' },
60
+ ]);
61
+ process.env.SM_CLAUDE_BIN = stubPath;
62
+ delete require.cache[require.resolve('../chatRunner.cjs')];
63
+ const cr = require('../chatRunner.cjs');
64
+
65
+ const events = [];
66
+ cr.attachWindow({
67
+ isDestroyed: () => false,
68
+ webContents: {
69
+ isDestroyed: () => false,
70
+ send: (channel, payload) => events.push({ channel, payload }),
71
+ },
72
+ });
73
+
74
+ cr.run({ tabId: 'T-notice', sessionId: 'S-notice', prompt: 'do a design thing', cwd: process.cwd(), resume: false });
75
+
76
+ for (let i = 0; i < 60 && !events.some((e) => isTerminal(e.channel)); i++) {
77
+ await new Promise((r) => setTimeout(r, 25));
78
+ }
79
+
80
+ const notice = events.find((e) => e.channel === 'chat:run:notice');
81
+ assert.ok(notice, 'chat:run:notice should be broadcast');
82
+ assert.match(notice.payload.message, /consent/i, 'message mentions consent');
83
+ assert.match(notice.payload.message, /terminal/i, 'message mentions the raw terminal session');
84
+
85
+ // Notice is informational, not terminal — the normal result still fires.
86
+ const terminal = events.filter((e) => isTerminal(e.channel));
87
+ assert.equal(terminal.length, 1, 'exactly one terminal event still fires');
88
+ assert.equal(terminal[0].channel, 'chat:run:complete');
89
+
90
+ try { fs.unlinkSync(stubPath); } catch { /* already gone */ }
91
+ delete process.env.SM_CLAUDE_BIN;
92
+ });
93
+
94
+ test('a normal tool_result with no consent marker does not fire chat:run:notice', async () => {
95
+ const stubPath = writeStub([
96
+ {
97
+ type: 'user',
98
+ message: {
99
+ content: [
100
+ {
101
+ type: 'tool_result',
102
+ tool_use_id: 'toolu_2',
103
+ is_error: false,
104
+ content: [{ type: 'text', text: 'Wrote 3 files successfully.' }],
105
+ },
106
+ ],
107
+ },
108
+ },
109
+ { type: 'result', subtype: 'success', result: 'all good' },
110
+ ]);
111
+ process.env.SM_CLAUDE_BIN = stubPath;
112
+ delete require.cache[require.resolve('../chatRunner.cjs')];
113
+ const cr = require('../chatRunner.cjs');
114
+
115
+ const events = [];
116
+ cr.attachWindow({
117
+ isDestroyed: () => false,
118
+ webContents: {
119
+ isDestroyed: () => false,
120
+ send: (channel, payload) => events.push({ channel, payload }),
121
+ },
122
+ });
123
+
124
+ cr.run({ tabId: 'T-clean', sessionId: 'S-clean', prompt: 'do a normal thing', cwd: process.cwd(), resume: false });
125
+
126
+ for (let i = 0; i < 60 && !events.some((e) => isTerminal(e.channel)); i++) {
127
+ await new Promise((r) => setTimeout(r, 25));
128
+ }
129
+
130
+ assert.ok(
131
+ !events.some((e) => e.channel === 'chat:run:notice'),
132
+ 'no chat:run:notice should fire for a clean tool_result (no false positive)',
133
+ );
134
+
135
+ try { fs.unlinkSync(stubPath); } catch { /* already gone */ }
136
+ delete process.env.SM_CLAUDE_BIN;
137
+ });
@@ -0,0 +1,61 @@
1
+ /**
2
+ * mcpStatus.test.cjs — unit tests for the `claude mcp list` output parser.
3
+ *
4
+ * Run: timeout 120 node --test src/main/__tests__/mcpStatus.test.cjs
5
+ */
6
+
7
+ 'use strict';
8
+
9
+ const { test } = require('node:test');
10
+ const assert = require('node:assert/strict');
11
+ const { parseMcpList } = require('../mcpStatus.cjs');
12
+
13
+ const SAMPLE = `Checking MCP server health…
14
+
15
+ claude.ai Gmail: https://gmailmcp.googleapis.com/mcp/v1 - ✔ Connected
16
+ fetch: uvx mcp-server-fetch - ✔ Connected
17
+ n8n: bash /home/bilko/.config/n8n-mcp/run.sh - ✘ Failed to connect
18
+ session-manager-scheduler: node scripts/scheduler-mcp-server.cjs - ⏸ Pending approval (run \`claude\` to approve)
19
+ claude_design: https://api.anthropic.com/v1/design/mcp (HTTP) - ✔ Connected
20
+ claude.ai Google Drive: https://drivemcp.googleapis.com/mcp/v1 - ! Needs authentication
21
+ `;
22
+
23
+ test('parses the captured claude mcp list sample', () => {
24
+ const servers = parseMcpList(SAMPLE);
25
+ assert.ok(servers.length >= 6, `expected >=6 servers, got ${servers.length}`);
26
+
27
+ const byName = Object.fromEntries(servers.map((s) => [s.name, s]));
28
+
29
+ assert.equal(byName['claude.ai Gmail'].status, 'connected');
30
+ assert.equal(byName['fetch'].status, 'connected');
31
+ assert.equal(byName['n8n'].status, 'failed');
32
+ assert.equal(byName['session-manager-scheduler'].status, 'pending');
33
+ assert.equal(byName['claude_design'].status, 'connected');
34
+ assert.equal(byName['claude_design'].transport, 'http');
35
+ assert.equal(byName['claude.ai Google Drive'].status, 'needs-auth');
36
+ });
37
+
38
+ test('ignores the header and blank lines', () => {
39
+ const servers = parseMcpList(SAMPLE);
40
+ assert.ok(!servers.some((s) => /Checking MCP server health/i.test(s.name)));
41
+ });
42
+
43
+ test('defaults transport to stdio for non-HTTP targets', () => {
44
+ const servers = parseMcpList(SAMPLE);
45
+ const byName = Object.fromEntries(servers.map((s) => [s.name, s]));
46
+ assert.equal(byName['n8n'].transport, 'stdio');
47
+ assert.equal(byName['fetch'].transport, 'stdio');
48
+ });
49
+
50
+ test('returns [] (not a throw) on empty input', () => {
51
+ assert.deepEqual(parseMcpList(''), []);
52
+ });
53
+
54
+ test('returns [] (not a throw) on garbage input', () => {
55
+ assert.deepEqual(parseMcpList('this is not\na valid mcp list output\n12345'), []);
56
+ });
57
+
58
+ test('returns [] on non-string input without throwing', () => {
59
+ assert.deepEqual(parseMcpList(null), []);
60
+ assert.deepEqual(parseMcpList(undefined), []);
61
+ });