@launchsecure/launch-kit 0.0.33 → 0.0.35

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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@launchsecure/launch-kit",
3
- "version": "0.0.33",
4
- "description": "LaunchSecure toolkit — launch-pod (pipeline), launch-chart (project graph MCP), launch-deck (visual playground MCP), launch-kit-beacon (feedback Web Component), launch-recall (file-watcher backup).",
3
+ "version": "0.0.35",
4
+ "description": "LaunchSecure toolkit — launch-sequencer (pipeline runner + terminal bridge), launch-radar (feedback webhook receiver), launch-chart (project graph MCP), launch-deck (visual playground MCP), launch-kit-beacon (feedback Web Component), launch-recall (file-watcher backup). launch-pod is the container image these run inside.",
5
5
  "license": "MIT",
6
6
  "author": "LaunchSecure - AutomateWithUs",
7
7
  "homepage": "https://automatewith.us",
@@ -48,13 +48,16 @@
48
48
  },
49
49
  "bin": {
50
50
  "launch-kit": "./dist/server/init-entry.js",
51
- "launch-pod": "./dist/server/cli.js",
51
+ "launch-sequencer": "./dist/server/cli.js",
52
+ "launch-radar": "./dist/server/launch-radar-entry.js",
52
53
  "launch-chart": "./dist/server/graph-mcp-entry.js",
53
54
  "launch-deck": "./dist/server/deck-mcp-entry.js",
54
55
  "launch-recall": "./dist/server/recall-entry.js",
55
56
  "launch-orbit": "./dist/server/orbit-entry.js",
56
57
  "launch-course": "./dist/server/course-entry.js",
57
- "launch-beacon": "./dist/server/beacon-monitor-entry.js"
58
+ "launch-beacon": "./dist/server/beacon-monitor-entry.js",
59
+ "launch-rover": "./dist/server/rover-entry.js",
60
+ "launch-bot": "./dist/server/launch-bot-entry.js"
58
61
  },
59
62
  "files": [
60
63
  "dist",
@@ -113,7 +116,7 @@
113
116
  "build:council-client": "vite build --config vite.council.config.ts",
114
117
  "build:client": "vite build",
115
118
  "build:chart-client": "vite build --config vite.chart.config.ts",
116
- "build:server": "esbuild src/server/cli.ts src/server/fb-wizard.ts src/server/graph-mcp-entry.ts src/server/chart-serve.ts src/server/deck-mcp-entry.ts src/server/deck-serve.ts src/server/council-entry.ts src/server/council-serve.ts src/server/recall-entry.ts src/server/init-entry.ts src/server/orbit-entry.ts src/server/course-entry.ts src/server/beacon-monitor-entry.ts src/server/parse-worker-entry.ts src/server/radar-teardown-entry.ts src/server/radar-docker-init-entry.ts --bundle --platform=node --target=node18 --outdir=dist/server --external:node-pty --external:ws --external:typescript --external:web-tree-sitter --external:tree-sitter-typescript --external:cloudflared --external:pg --external:pg-native --external:pgsql-parser --external:libpg-query && rm -rf dist/server/public && cp -r ../claude-code-web/src/public dist/server/public && rm -rf dist/server/graph/queries && mkdir -p dist/server/graph && cp -r src/server/graph/queries dist/server/graph/queries",
119
+ "build:server": "esbuild src/server/cli.ts src/server/fb-wizard.ts src/server/graph-mcp-entry.ts src/server/chart-serve.ts src/server/deck-mcp-entry.ts src/server/deck-serve.ts src/server/council-entry.ts src/server/council-serve.ts src/server/recall-entry.ts src/server/init-entry.ts src/server/orbit-entry.ts src/server/course-entry.ts src/server/beacon-monitor-entry.ts src/server/parse-worker-entry.ts src/server/radar-teardown-entry.ts src/server/radar-docker-init-entry.ts src/server/launch-radar-entry.ts src/server/rover-entry.ts src/server/launch-bot-entry.ts --bundle --platform=node --target=node18 --outdir=dist/server --external:node-pty --external:ws --external:typescript --external:web-tree-sitter --external:tree-sitter-typescript --external:cloudflared --external:pg --external:pg-native --external:pgsql-parser --external:libpg-query && rm -rf dist/server/public && cp -r ../claude-code-web/src/public dist/server/public && rm -rf dist/server/graph/queries && mkdir -p dist/server/graph && cp -r src/server/graph/queries dist/server/graph/queries",
117
120
  "dev:client": "vite",
118
121
  "dev:deck-serve": "cd ../.. && tsx watch packages/cli/src/server/deck-mcp-entry.ts serve",
119
122
  "dev:chart": "pnpm build:server && pnpm build:chart-client && node dist/server/graph-mcp-entry.js serve",
@@ -63,11 +63,11 @@ Skip this step if no handles were found in commit messages — don't pull the fu
63
63
 
64
64
  ### 6. Release detection
65
65
 
66
- A commit qualifies the post for the `release` tag if ANY of the following are true:
67
- - `package.json` `version` field changed in `git diff @{push}..HEAD -- package.json`
68
- - A migration file under `prisma/migrations/` was added or `prisma/schema.prisma` changed
69
- - A deploy/publish was mentioned in commit subjects (regex: `\b(publish|release|deploy|bump)\b`)
70
- - A new bin or export was added to a package's `package.json`
66
+ A commit qualifies the post for the `release` tag if ANY of the following are true. These signals are **stack-detected**, not hardcoded — match against whatever the project actually uses, and a signal that doesn't apply to this stack simply doesn't fire:
67
+ - The project's **version** changed — detect from the stack's version source: `package.json` `version` (Node), `pyproject.toml`/`setup.py`/`__version__` (Python), `Cargo.toml` `version` (Rust), `<Version>` in a `*.csproj` (.NET), a gemspec/`version.rb` (Ruby), `version` in `pubspec.yaml` (Dart), or a new `vX.Y.Z` git tag pushed in this window (language-agnostic). Diff the relevant file over `@{push}..HEAD`.
68
+ - A **migration** was added — any file under the project's migrations directory (detected as in `/kit:ship`: `prisma/migrations/`, `supabase/migrations/`, `db/migrate/`, `alembic/versions/`, `drizzle/`, `src/main/resources/db/migration/`, etc.), or the schema/DDL source changed. Skip this signal when the project has no migrations.
69
+ - A deploy/publish was mentioned in commit subjects (regex: `\b(publish|release|deploy|bump)\b`).
70
+ - A new **public entry point** was added a bin/CLI command, package export, or exported library symbol (e.g. a new `bin`/`exports` entry in `package.json`, a new console-script in `pyproject.toml`, a new exported package in Go, etc.).
71
71
 
72
72
  If any are true, set `addReleaseTag = true`. Otherwise `false`.
73
73
 
@@ -102,7 +102,7 @@ Pending / in-progress:
102
102
 
103
103
  ----
104
104
 
105
- <one-line PSA or deploy-safety note. Examples: "TS clean, no schema/migration changes, safe to deploy." or "Includes prisma migration <name> — run migrate-with-backup.sh before deploy.">
105
+ <one-line PSA or deploy-safety note, phrased for the project's stack. Examples: "Typecheck clean, no schema/migration changes, safe to deploy." or "Includes a <migration-tool> migration <name> — take a DB backup before deploy." or "Bumps to vX.Y.Z — publishes on merge.">
106
106
 
107
107
  Thanks
108
108
  ```
@@ -22,6 +22,12 @@ allowed-tools:
22
22
 
23
23
  Source-code analysis using launch-chart as the default tool. The chart indexes ~1,200 typed nodes across 4 layers (ui / api / db / static) with cross-layer edges and AST-level deep fields — almost everything grep/glob/Read would surface is queryable here, faster and cheaper in tokens.
24
24
 
25
+ ## Input
26
+
27
+ Run the analysis for the user query → **$ARGUMENTS**
28
+
29
+ This is the target to analyse — a question, a node name (file / table / route / hook), or a module. Route it through the chart tools below. If `$ARGUMENTS` is empty, the user invoked the skill bare; ask them what they want analysed (or infer it from the surrounding conversation) before querying.
30
+
25
31
  ## Discipline (non-negotiable)
26
32
 
27
33
  1. **Chart-first.** Default to launch-chart MCP for any code-structure or behavior query. Do NOT reach for grep / glob / Read first.
@@ -1,50 +1,65 @@
1
1
  ---
2
- description: Create or update a repo-backed Brief in the LaunchSecure Comm Hub per the LS Briefs framework Discovery-plane document (docCategory=features) authored as a comment that's also synced to a markdown file in the repo. Use to capture an idea before promoting it to an Epic.
2
+ description: Create or update a repo-backed Brief a long-form document committed as a markdown file under doc/<category>/. The server syncs it to a Comm Hub comment; you don't post it yourself. Use to capture a feature, architecture decision, requirement, or playbook in the repo.
3
3
  ---
4
4
 
5
5
  # /kit:brief
6
6
 
7
- A **Brief** is an LS Discovery-plane artefact: a long-form description of a problem/opportunity, authored as a comment in the project's Comm Hub AND backed by a markdown file in the repo (`docs/requirements/features/<slug>.md` by convention). It's the structured alternative to a free-form Discussion when the idea has enough shape to write down. Briefs get promoted to Epics on the Roadmap once they're DECIDED.
7
+ A **Brief** is a structured long-form document that lives **both** as a markdown file in the repo at `doc/<category>/<slug>.md` **and** as a comment in the LaunchSecure Comm Hub. You write the file; the LS server's file-backed sync materializes the matching Comm Hub comment (favoring server file convergence). See `doc/features/briefs.md`, `doc/playbook/runbooks/write-a-brief.md`, and `architecture/systems/briefs-discovery.md`.
8
+
9
+ **You write the file, the server does the sync.** Do NOT call `communication_write` / `communication_update` yourself — the brief becomes a Comm Hub comment automatically via the `discussion` handler once the file lands on the default branch (stamped with the category's `docCategory`, icon, and color). The only allowed Comm Hub touch from this skill is reading a `--from` discussion seed, and that's read-only.
10
+
11
+ ## Categories
12
+
13
+ A Brief is **category-scoped** — the category determines its folder, icon, color, and where readers find it. Pick the one matching the question the reader is asking:
14
+
15
+ | Reader is asking… | Category | Folder |
16
+ |---|---|---|
17
+ | "What are we building / what does LS offer?" | `features` | `doc/features/` |
18
+ | "How does this system work / why built this way?" | `architecture` | `doc/architecture/` |
19
+ | "What rule must I follow?" | `requirement` | `doc/requirement/` |
20
+ | "How do I do X?" | `playbook` | `doc/playbook/` |
21
+ | "Something happened, I want to remember" | `incident` | `doc/incident/` |
22
+ | "Half-formed personal thought" | `ideas` | `doc/ideas/` (personal — see below) |
23
+
24
+ `features`, `architecture`, `requirement`, `playbook`, `shipping-logs` are framework defaults; `ideas` and `incident` are this project's custom categories (`.launch-secure.config`). Other projects may differ — read `.launch-secure.config` `docCategories[]` if unsure. **Do not default to `ideas`** — that's the personal-scratch category, not a dumping ground for real briefs. Shipping-log briefs are written by `/kit:ship`, not this skill.
8
25
 
9
26
  Parse `$ARGUMENTS`:
10
27
  - **subcommand** (required) — `new` | `update` | `list` | `show`.
11
- - For **new**: `<title>` (required), `--from=discussion:<id>` to seed body from an existing discussion thread, `--module=<slug>` to tag.
12
- - For **update**: `<brief-id-or-slug>` and either a body description (re-generates body) or `--append=<text>` to add a section.
13
- - For **list**: `[--module=<slug>] [--status=open|decided|declined]`.
14
- - For **show**: `<brief-id-or-slug>`.
28
+ - For **new**: `<title>` (required), `--category=<name>` (the category from the table; if omitted, infer from the title/intent and confirm before writing), `--from=discussion:<id>` to seed body from an existing discussion thread, `--dir=<path>` to override the resolved folder.
29
+ - For **update**: `<slug-or-path>` and either a body description (re-generates body) or `--append=<text>` to add a section.
30
+ - For **list**: `[--category=<name>]` to filter to one category, else lists briefs across all category folders. `[--dir=<path>]` to scan a specific dir.
31
+ - For **show**: `<slug-or-path>`.
15
32
 
16
33
  Examples:
17
- - `/kit:brief new "Channels for Comm Hub" --module=communication`
18
- - `/kit:brief new "Roadmap nav" --from=discussion:cmt_abc123`
19
- - `/kit:brief list --status=open`
20
- - `/kit:brief show channels-for-comms`
21
- - `/kit:brief update channels-for-comms --append="Resolved: use the Slack metaphor not Teams."`
34
+ - `/kit:brief new "Comm Hub channels" --category=features`
35
+ - `/kit:brief new "Roadmap nav" --from=discussion:cmt_abc123 --category=architecture`
36
+ - `/kit:brief list`
37
+ - `/kit:brief list --category=requirement`
38
+ - `/kit:brief show launch-watch`
39
+ - `/kit:brief update comm-hub-channels --append="Resolved: use the Slack metaphor not Teams."`
22
40
 
23
- ## Preflight
24
-
25
- 1. Confirm `mcp__launch-secure__ping` works (the cloud LS MCP must be wired). If only `mcp__local-launch-secure__*` is wired, ask the user whether to post to local or cloud — default per project memory is CLOUD.
26
- 2. For `new`, sanity-check that no existing brief with the same slug already exists — call `mcp__launch-secure__communication_read` filtered by `resourceType=comment` and search the title. If a match is found, ask whether to update instead.
41
+ ## Location
27
42
 
28
- ## The Brief shape
43
+ - **Folder**: `doc/<category>/` at the repo root, where `<category>` is resolved from `--category` (or inferred + confirmed).
44
+ - **Filename**: `<slug>.md`, where the slug is the kebab-cased title (e.g. "launch-watch — unified monitor" → `launch-watch.md`; keep it short — derive from the leading words, drop the subtitle after an em-dash).
45
+ - Override the folder with `--dir=<path>` only if the user asks.
29
46
 
30
- Per the LS Briefs framework (`docs/requirements/work-items/roadmap-and-hierarchy.md`):
47
+ ## Preflight
31
48
 
32
- - **resourceType**: `comment` (Briefs are comments)
33
- - **docCategory**: `features` (this is what file-backed sync watches)
34
- - **title**: the human-friendly name; the slug is derived
35
- - **body**: the long-form markdown sections below
36
- - **file_links**: not used here; the FileBackedEntity link is created by LS server-side on first save
37
- - **tags**: optional module tag, plus a `brief` tag for filtering
49
+ 1. Resolve the repo root (the working directory is the repo root in this project).
50
+ 2. Resolve the category (from `--category`, or infer from the title and confirm with the user). Map it to `doc/<category>/`.
51
+ 3. For **new**, check `doc/<category>/<slug>.md` does not already exist. If it does, surface it and ask whether to `update` instead — do NOT overwrite silently. Also check the slug isn't already used under a *different* category before creating a duplicate.
52
+ 4. No `communication_write`. This skill writes the filesystem (+ optionally reads a `--from` seed); the server handles the Comm Hub side.
38
53
 
39
- ### Body template
54
+ ## Body template
40
55
 
41
- When generating a NEW brief, use this skeleton (markdown body):
56
+ When generating a NEW brief, write this skeleton as the file contents (plain markdown):
42
57
 
43
58
  ```markdown
44
59
  # <Title>
45
60
 
46
- **Status**: PROPOSED
47
- **Owner**: <user-mention or "unassigned">
61
+ **Status**: PROPOSED
62
+ **Owner**: <name or "unassigned">
48
63
  **Module**: <module slug or "-">
49
64
 
50
65
  ## Problem
@@ -69,44 +84,52 @@ When generating a NEW brief, use this skeleton (markdown body):
69
84
 
70
85
  ## References
71
86
 
72
- - <link to existing code via launch-chart node id (use mcp__launch-chart__read_graph to find the right nodes; cite them as `lib/foo/bar.ts:42`)>
73
- - <related comments by id>
87
+ - <link to existing code, cited as `lib/foo/bar.ts:42` — use launch-chart (read_graph / grep_nodes) to find the right nodes; don't invent file paths>
74
88
  ```
75
89
 
76
- The body is plain markdown — Comm Hub renders it. Use launch-chart to add real code references (don't invent file paths).
77
-
78
90
  ## Write paths
79
91
 
80
92
  ### new
81
93
 
82
- 1. Build the body from the template + the user's description (or the `--from=discussion:<id>` seed — fetch via `communication_read` and weave the relevant points into the Problem/Proposed-direction sections).
83
- 2. Call `mcp__launch-secure__communication_write` with:
84
- - `title: <user-supplied>`
85
- - `body: <generated-markdown>`
86
- - `resource_type: "comment"`
87
- - `fields: { docCategory: "features", briefStatus: "PROPOSED" }`
88
- - `tag_ids: <module tag id + "brief" tag id>` (call `mcp__launch-secure__tags_list` first to resolve names → ids; if either tag doesn't exist, surface the missing tag(s) and proceed without)
89
- 3. Print the returned comment id + URL.
90
- 4. Tell the user: "LS server-side file-backed sync will materialize `docs/requirements/features/<slug>.md` on the next default-branch push that includes this brief — pull the branch to see it locally."
94
+ 1. Resolve the category and folder (see Preflight).
95
+ 2. Build the body from the template + the user's description (or the `--from=discussion:<id>` seed — fetch via `mcp__launch-secure__communication_read` and weave the relevant points into Problem / Proposed-direction; reading a seed discussion is fine, but the Brief itself is still written as a file, never posted back).
96
+ 3. Use launch-chart (`read_graph`, `grep_nodes`) to populate the References section with real code paths — don't invent them.
97
+ 4. **Write the file** to `doc/<category>/<slug>.md` with the Write tool.
98
+ 5. Print the path written and remind the user it materializes as a Comm Hub comment on the next default-branch push. Do NOT commit or push unless the user explicitly asks — leave it as a working-tree change for them to review and commit.
91
99
 
92
100
  ### update
93
101
 
94
- 1. Resolve `<brief-id-or-slug>` via `communication_read(resourceType: "comment", search: <slug>)`. If multiple match, list them and stop.
95
- 2. If `--append=<text>`, fetch the current body, append the text under a `## Update <ISO-date>` heading, and call `mcp__launch-secure__communication_update` with the new body.
96
- 3. If a fresh description was passed, regenerate the body from the template + new description, ask the user to confirm (show the diff briefly), then update.
102
+ 1. Resolve `<slug-or-path>` with `node "$CLAUDE_PLUGIN_ROOT/skills/brief/briefs.mjs" show <slug>` (it prints the resolved path on its `── <path> ──` header, or exits non-zero with candidates / an ambiguity list). If nothing matches, surface the candidates and stop; if ambiguous, ask which category.
103
+ 2. If `--append=<text>`, Read the resolved file, append the text under a `## Update <ISO-date>` heading, and Write it back.
104
+ 3. If a fresh description was passed, regenerate the body from the template + new description, show the user the diff briefly, then Write it back on confirmation.
97
105
 
98
106
  ### list
99
107
 
100
- `mcp__launch-secure__communication_read` filtered by `resourceType=comment`, narrow on `docCategory=features` if the API supports it (otherwise pull a wider set and filter client-side by tag/status). Render one line per brief: `id status title module updated`.
108
+ Run the bundled scanner do NOT open and parse the brief files yourself:
109
+
110
+ ```
111
+ node "$CLAUDE_PLUGIN_ROOT/skills/brief/briefs.mjs" list [category]
112
+ ```
113
+
114
+ It walks every `doc/<category>/` folder once and prints an aligned `CATEGORY SLUG STATUS TITLE` table (pass a category to filter). Surface its output as-is; only read individual files if the user then asks to `show` one.
101
115
 
102
116
  ### show
103
117
 
104
- Fetch via `communication_read` and print the body as-is, no transformation.
118
+ Run the bundled scanner — it resolves the slug across all category folders and prints the file with a `── <path> ──` header:
119
+
120
+ ```
121
+ node "$CLAUDE_PLUGIN_ROOT/skills/brief/briefs.mjs" show <slug-or-path>
122
+ ```
123
+
124
+ Exit 1 = no match (it prints near-matches), exit 2 = ambiguous across categories (it lists them — re-run with the full path). Don't hand-glob `doc/` for the file.
105
125
 
106
126
  ## Constraints
107
127
 
108
- - **Plain markdown body.** Per project memory `feedback_comms_plain_text.md`, comms posts are plain no HTML, no emojis unless the user wrote them.
109
- - **One brief, one comment.** Don't fragment a brief across multiple comments append via `--append` to keep the document continuous.
110
- - **Cloud LS by default.** Per memory `feedback_post_to_cloud_ls_default.md`. Local LS only when the user explicitly says "local".
128
+ - **You write a file; the server creates the comment.** Do NOT call `communication_write` / `communication_update`. Reading a `--from` discussion seed is the only allowed Comm Hub touch, and it's read-only.
129
+ - **Pick the right category.** The folder, icon, and color all follow from it. Don't default to `ideas`.
130
+ - **Plain markdown.** No HTML, no emojis unless the user wrote them.
131
+ - **One brief, one file.** Don't fragment a brief across multiple files — append via `--append` to keep the document continuous.
132
+ - **Don't commit or push on your own.** Write the file to the working tree and stop; committing/pushing is the user's call (per project conventions on no unauthorized pushes).
133
+ - **`doc/ideas/` is the user's personal scratch.** Write an `ideas` brief there only when the user explicitly asks for one, and do NOT reorganize, reclassify, or migrate other files in that directory. Promotion out of `doc/ideas/` is user-driven only.
111
134
  - **Cite real code.** Use launch-chart node ids in the References section — don't invent file paths.
112
- - **Don't auto-promote to Epic.** A Brief → Epic is a deliberate human action (project memory: bucket transitions are deliberate, not derived). Surface a hint at the end: "When DECIDED, run `/work-items create epic --from-brief=<id>` to promote."
135
+ - **Don't auto-promote to Epic.** A Brief → Epic is a deliberate human action. Surface a hint at the end: "When DECIDED, promote this to an Epic on the Roadmap."
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+ // briefs.mjs — list/show repo-backed Briefs without the agent reading every file.
3
+ //
4
+ // A Brief is a markdown file at doc/<category>/<slug>.md (see the /kit:brief
5
+ // SKILL.md). This script scans the doc/ tree once and emits structured output so
6
+ // the skill never has to open + parse N files itself.
7
+ //
8
+ // Usage (run from the repo root — process.cwd() must contain doc/):
9
+ // node briefs.mjs list [category] # table: CATEGORY SLUG STATUS TITLE
10
+ // node briefs.mjs show <slug-or-path> # print one brief's contents
11
+ //
12
+ // Exit codes: 0 ok · 1 not found · 2 ambiguous · 3 usage error.
13
+
14
+ import { readFileSync, readdirSync, existsSync, statSync } from 'node:fs';
15
+ import { join, relative, basename, sep } from 'node:path';
16
+
17
+ const DOC_ROOT = join(process.cwd(), 'doc');
18
+
19
+ /** Recursively collect every *.md under doc/, tagged with its top-level category. */
20
+ function collectBriefs(filterCategory) {
21
+ if (!existsSync(DOC_ROOT)) {
22
+ return { error: `no doc/ directory at ${DOC_ROOT}` };
23
+ }
24
+ const out = [];
25
+ const walk = (dir) => {
26
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
27
+ const full = join(dir, entry.name);
28
+ if (entry.isDirectory()) {
29
+ walk(full);
30
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
31
+ const rel = relative(DOC_ROOT, full); // e.g. features/foo.md, playbook/runbooks/bar.md
32
+ const category = rel.split(sep)[0];
33
+ if (filterCategory && category !== filterCategory) continue;
34
+ out.push({ category, slug: basename(entry.name, '.md'), rel, full });
35
+ }
36
+ }
37
+ };
38
+ walk(DOC_ROOT);
39
+ out.sort((a, b) => a.category.localeCompare(b.category) || a.slug.localeCompare(b.slug));
40
+ return { briefs: out };
41
+ }
42
+
43
+ /**
44
+ * Pull a brief's title + status without loading it into the model. Looks in both
45
+ * YAML frontmatter (`title:` / `status:`, used by the PUBLISHED briefs) and the
46
+ * markdown body (`# Title` / `**Status**:`, used by template-generated ones).
47
+ * Frontmatter wins for the title; the `**Status**:` line wins for status.
48
+ */
49
+ function parseMeta(file) {
50
+ let fmTitle = '', fmStatus = '', h1 = '', bodyStatus = '';
51
+ try {
52
+ const lines = readFileSync(file, 'utf8').split('\n');
53
+ let inFm = false, fmDone = false;
54
+ for (let i = 0; i < lines.length; i++) {
55
+ const line = lines[i];
56
+ if (i === 0 && line.trim() === '---') { inFm = true; continue; }
57
+ if (inFm) {
58
+ if (line.trim() === '---') { inFm = false; fmDone = true; continue; }
59
+ const t = line.match(/^title:\s*(.+?)\s*$/i);
60
+ if (t && !fmTitle) fmTitle = t[1].replace(/^["']|["']$/g, '');
61
+ const s = line.match(/^status:\s*(.+?)\s*$/i);
62
+ if (s && !fmStatus) fmStatus = s[1].replace(/^["']|["']$/g, '');
63
+ continue;
64
+ }
65
+ if (!h1) {
66
+ const m = line.match(/^#\s+(.+?)\s*$/);
67
+ if (m) { h1 = m[1]; continue; }
68
+ }
69
+ if (!bodyStatus) {
70
+ const m = line.match(/^\*\*Status\*\*:\s*(.+?)\s*$/i);
71
+ if (m) bodyStatus = m[1];
72
+ }
73
+ if ((fmTitle || h1) && (fmStatus || bodyStatus) && fmDone) break;
74
+ }
75
+ } catch { /* unreadable → fall through to defaults */ }
76
+ return {
77
+ title: fmTitle || h1 || '(untitled)',
78
+ status: bodyStatus || fmStatus || '-',
79
+ };
80
+ }
81
+
82
+ /** First clause of a status, capped — keeps the table aligned when a file writes a sentence. */
83
+ function shortStatus(s) {
84
+ const first = s.split(/[.(—]/)[0].trim() || s.trim();
85
+ return first.length > 18 ? first.slice(0, 17) + '…' : first;
86
+ }
87
+
88
+ function pad(s, n) { return s + ' '.repeat(Math.max(0, n - s.length)); }
89
+
90
+ function cmdList(category) {
91
+ const { error, briefs } = collectBriefs(category);
92
+ if (error) { console.error(error); process.exit(1); }
93
+ if (briefs.length === 0) {
94
+ console.log(category ? `(no briefs in doc/${category}/)` : '(no briefs under doc/)');
95
+ return;
96
+ }
97
+ const rows = briefs.map((b) => {
98
+ const { title, status } = parseMeta(b.full);
99
+ return { category: b.category, slug: b.slug, status: shortStatus(status), title };
100
+ });
101
+ const w = (k) => Math.max(k.length, ...rows.map((r) => r[k].length));
102
+ const wc = w('category'), ws = w('slug'), wst = w('status');
103
+ console.log(`${pad('CATEGORY', wc)} ${pad('SLUG', ws)} ${pad('STATUS', wst)} TITLE`);
104
+ for (const r of rows) {
105
+ console.log(`${pad(r.category, wc)} ${pad(r.slug, ws)} ${pad(r.status, wst)} ${r.title}`);
106
+ }
107
+ console.log(`\n${rows.length} brief${rows.length === 1 ? '' : 's'}.`);
108
+ }
109
+
110
+ function cmdShow(target) {
111
+ if (!target) { console.error('show: need a <slug-or-path>'); process.exit(3); }
112
+ // Direct path hit (relative to cwd or absolute).
113
+ for (const p of [target, join(process.cwd(), target)]) {
114
+ if (existsSync(p) && statSync(p).isFile()) {
115
+ console.log(`── ${relative(process.cwd(), p) || p} ──\n`);
116
+ console.log(readFileSync(p, 'utf8'));
117
+ return;
118
+ }
119
+ }
120
+ // Otherwise resolve a slug across categories.
121
+ const { error, briefs } = collectBriefs();
122
+ if (error) { console.error(error); process.exit(1); }
123
+ const slug = basename(target, '.md');
124
+ const matches = briefs.filter((b) => b.slug === slug);
125
+ if (matches.length === 0) {
126
+ console.error(`no brief matching "${slug}".`);
127
+ const near = briefs.filter((b) => b.slug.includes(slug)).slice(0, 8);
128
+ if (near.length) {
129
+ console.error('did you mean:');
130
+ for (const n of near) console.error(` ${n.category}/${n.slug}`);
131
+ }
132
+ process.exit(1);
133
+ }
134
+ if (matches.length > 1) {
135
+ console.error(`"${slug}" is ambiguous — matches ${matches.length} briefs:`);
136
+ for (const m of matches) console.error(` ${m.rel}`);
137
+ console.error('re-run show with the full path.');
138
+ process.exit(2);
139
+ }
140
+ const hit = matches[0];
141
+ console.log(`── ${hit.rel} ──\n`);
142
+ console.log(readFileSync(hit.full, 'utf8'));
143
+ }
144
+
145
+ const [cmd, ...rest] = process.argv.slice(2);
146
+ switch (cmd) {
147
+ case 'list': cmdList(rest[0]); break;
148
+ case 'show': cmdShow(rest[0]); break;
149
+ default:
150
+ console.error('usage: node briefs.mjs list [category] | show <slug-or-path>');
151
+ process.exit(3);
152
+ }
@@ -1,54 +1,77 @@
1
1
  ---
2
- description: Investigate a bug, error message, or unexpected behavior using launch-chart first (structural + variable queries), falling back to grep/Read only when the chart can't answer. When the chart gap is the reason for the fallback, files a feedback comment to LaunchSecure so the gap is monitored and fixed.
2
+ description: Investigate a bug, error message, or unexpected behavior. Leads with runtime evidence from the launch-beacon monitor ("what was happening when it broke") when a session exists, then traces the code with launch-chart (structural + variable queries), falling back to grep/Read only when the chart can't answer. Fuses runtime + static signals into a ranked diagnosis. Files a feedback comment when a chart gap forces the fallback. Does NOT edit code.
3
3
  ---
4
4
 
5
5
  # /kit:debug
6
6
 
7
- A guided investigation workflow that defaults to chart-MCP queries and only reaches for grep/Read when the chart can't answer — and when that happens, files a one-line feedback comment in LS so the gap doesn't go unnoticed.
7
+ A guided investigation workflow. It works two evidence sources, in order:
8
+
9
+ 1. **Runtime** — the launch-beacon monitor's captured events (the error itself, plus the clicks / fetches / route changes leading up to it). This is what *actually happened* in the running app. Beacon was built for exactly this.
10
+ 2. **Static** — launch-chart structural + variable queries to find *where the code is* and trace the dependency chain toward the cause.
11
+
12
+ It defaults to beacon-first when there's a live session and the symptom is runtime-shaped, then chart, and only reaches for grep/Read when the chart can't answer — filing a one-line gap report in LS when that happens.
8
13
 
9
14
  Parse `$ARGUMENTS`:
10
15
  - **symptom** (required) — the error message, broken behavior, or question to investigate. Quoted is fine: `/kit:debug "tags don't render on the work-item drawer"`.
11
- - **--start=<node>** — start the investigation from a specific file/function/table id (skip the search step).
16
+ - **--start=<node>** — start the static investigation from a specific file/function/table id (skip the chart search step). Does not skip beacon.
12
17
  - **--layer=<id>** — scope chart queries to one layer (ui / api / db / static).
13
18
  - **--no-grep-fallback** — refuse the grep fallback entirely (strict MCP-only).
19
+ - **--no-beacon** — skip the runtime-evidence step; go straight to static chart analysis. Use for pure structural questions, or when no monitor is running and you don't want the check.
20
+ - **--worktree=<slug>** / **--project_root=<path>** — forwarded to every beacon MCP call (orbit worktree slug / explicit project root), same semantics as `/kit:beacon-pulse`.
14
21
 
15
22
  Examples:
16
23
  - `/kit:debug tags don't render on the work-item drawer`
17
24
  - `/kit:debug "TypeError: Cannot read property 'status' of undefined" --layer=ui`
18
25
  - `/kit:debug --start=src/server/comms/build-feed.ts`
26
+ - `/kit:debug "checkout button does nothing" --worktree=beacon_rewrite`
19
27
 
20
28
  ## Investigation loop
21
29
 
22
- Run this loop until the user has enough to act, OR until a fallback was filed (then surface the fallback):
30
+ Run Step 0, then the static loop (Steps 1–4), until the user has enough to act, OR a fallback was filed (then surface it).
31
+
32
+ ### Step 0 — runtime evidence (launch-beacon)
33
+
34
+ Skip this step if `--no-beacon` was passed, or if the symptom is a purely structural question with no runtime component ("where is X defined", "what depends on Y", "how is auth wired"). Otherwise — for any symptom that describes something *happening* (an error message, a broken interaction, a wrong result in the running app) — start here:
35
+
36
+ 1. Call `mcp__local-launch-beacon__beacon_failures` with `limit: 5` (plus `worktree` / `project_root` if supplied). The most recent session is targeted implicitly. A "failure" is broader than `kind=error`: window errors, unhandled rejections, fetch/xhr ≥ 400 or thrown, and clicks where overlay interception blocked the intended target.
37
+ 2. **Match the symptom to a failure.** Pick the failure whose message/route best matches the symptom. If several plausibly match, surface the top 2–3 (with `seq`, `kind`, `ts`, message) and ask which — or take the most recent.
38
+ 3. **Pull the lead-up.** Call `mcp__local-launch-beacon__beacon_correlate` with `seq: <failure.seq>`, `before: 10`, `after: 0` (forward the same root args). This is the chain of events that produced the failure — the click that set bad state, the fetch that 500'd, the route change that mounted the wrong component. If a context event's summary is too thin (truncated stack, missing response body, selector chain), call `mcp__local-launch-beacon__beacon_event` with that `seq` for the full JSON — only when needed.
39
+ 4. **Form a runtime hypothesis** and carry it into the static phase. Example: *"beacon shows `GET /api/work-items/42/tags` returned 500 two events before the render error — so the empty-tags symptom is likely server-side, not the drawer component."* This tells the chart phase **where to start** (the failing endpoint), turning a blind structural search into a targeted one.
40
+
41
+ **Graceful degradation (beacon is additive, never a hard dependency):**
42
+ - `{error: "no session found"}` or empty failures → say so in one line (`"No beacon monitor session found — proceeding with static analysis only. (Start one with: npx launch-beacon monitor)"`) and go to Step 1. Do NOT treat this as a failure of the debug run.
43
+ - Before giving up: if the user passed no `--worktree`/`--project_root` and `launch-orbit` has worktrees for this repo, suggest re-running with `--worktree=<slug>` (the session NDJSON may live in a worktree outside the MCP's watch root).
44
+ - If the beacon MCP isn't wired at all, note it once and continue static-only.
45
+ - For a deeper runtime drill (more preceding events, a different failure), point the user at `/kit:beacon-pulse` rather than re-deriving it here.
23
46
 
24
- ### Step 1 — locate the entry point
47
+ ### Step 1 — locate the entry point (static)
25
48
 
26
- If `--start` was given, jump to step 2. Otherwise, use chart to find candidate nodes:
49
+ If `--start` was given, jump to Step 2. If Step 0 produced a concrete node (an endpoint, component, or file from the failing event), use THAT as the entry point — skip the blind search. Otherwise, use chart to find candidates:
27
50
 
28
- 1. Extract key terms from the symptom (component name, identifier, error class). Skip noise words.
29
- 2. Call `mcp__launch-chart__read_graph(search: <term>, type: <best-guess-type>, layer: <layer?>)`. If `type` is uncertain, omit it and filter the response by relevance.
30
- 3. If `read_graph` returns 0 candidates, broaden: try without `type`, then without `layer`. Surface the top 3 candidates to the user as ranked options; if the user can pick, jump to step 2 with their pick.
51
+ 1. Extract key terms from the symptom (component name, identifier, error class) — and from the beacon failure if Step 0 ran (the failing route/selector is a strong term). Skip noise words.
52
+ 2. Call `mcp__launch-chart__read_graph(search: <term>, type: <best-guess-type>, layer: <layer?>)`. If `type` is uncertain, omit it and filter by relevance.
53
+ 3. If `read_graph` returns 0 candidates, broaden: drop `type`, then `layer`. Surface the top 3 to the user; if they can pick, jump to Step 2 with their pick.
31
54
 
32
55
  **Chart-gap detection (entry-point search):**
33
- - If `read_graph` returns 0 candidates AND a quick `grep` confirms the term exists somewhere in source → file a chart-gap report per the protocol below, then proceed via the grep fallback for this query only.
34
- - If `detect_project_stack` lists a layer (e.g. `api`) but `read_graph(layer:"api")` returns 0 nodes → file a chart-gap report (parser misconfig).
56
+ - `read_graph` returns 0 AND a quick `grep` confirms the term exists in source → file a chart-gap report (below), then proceed via grep fallback for this query only.
57
+ - `detect_project_stack` lists a layer (e.g. `api`) but `read_graph(layer:"api")` returns 0 nodes → file a chart-gap report (parser misconfig).
35
58
 
36
- ### Step 2 — understand the node
59
+ ### Step 2 — understand the node (static)
37
60
 
38
61
  Once the entry point is known, query its structure and behavior:
39
62
 
40
63
  - **Structural** — `mcp__launch-chart__read_graph(node_id: <id>, hops: 1, include_edges: true)` for what it renders/imports and what depends on it.
41
- - **Variable / state** — `mcp__launch-chart__inspect_node(node_id: <id>, fields: ["stateVars"])` or `(filter: <key-term>)` for what state it holds, what conditions exist. Per project CLAUDE.md, ALWAYS start with `fields` or `filter` to keep the response small.
64
+ - **Variable / state** — `mcp__launch-chart__inspect_node(node_id: <id>, fields: ["stateVars"])` or `(filter: <key-term>)` for held state and conditions. Per project CLAUDE.md, ALWAYS start with `fields` or `filter` to keep the response small.
42
65
 
43
- Compose a hypothesis from these two reads. Example: "WorkItemDrawer fetches tags via `useWorkItemTags`, which calls `GET /api/work-items/:id/tags` let's see what that returns."
66
+ Compose a hypothesis. If Step 0 ran, **test the runtime hypothesis against the code** here e.g. confirm the failing endpoint's handler actually can return the 500 beacon saw.
44
67
 
45
- ### Step 3 — follow the trail
68
+ ### Step 3 — follow the trail (static)
46
69
 
47
- Walk the dependency edges toward the suspected cause (API endpoint, DB table, util fn, etc.). At each hop, use `read_graph(node_id, hops:1)` to find the next link. When you hit a leaf or run out of edges, narrate the chain back to the user with file:line refs from the node ids.
70
+ Walk dependency edges toward the suspected cause (endpoint, table, util fn). At each hop, `read_graph(node_id, hops:1)` to find the next link. When you hit a leaf, narrate the chain back with file:line refs from the node ids. If Step 0 gave a runtime chain, align the two — the static call path should explain the observed runtime sequence.
48
71
 
49
72
  ### Step 4 — propose, don't fix
50
73
 
51
- End with a one-paragraph diagnosis + 2-3 candidate causes ranked by likelihood. Do NOT propose an edit unless the user asks for one — debug is investigation, not implementation. Suggest the next move: "Want me to read the route handler and confirm?" or "Want `/kit:diagram sequence`-ing the call chain?"
74
+ End with a one-paragraph diagnosis + 23 candidate causes ranked by likelihood. **When both sources contributed, say which evidence is runtime vs static** — e.g. *"Runtime (beacon): the tags fetch 500'd. Static (chart): the handler dereferences `org.id` with no null-guard when the session has no org — the likely cause."* A diagnosis backed by an observed failure + the matching code path is far stronger than either alone. Do NOT propose an edit unless asked — debug is investigation, not implementation. Suggest the next move: "Want me to read the handler and confirm?" or "Want to `/kit:diagram sequence` the call chain?"
52
75
 
53
76
  ## Grep fallback
54
77
 
@@ -78,8 +101,10 @@ Then continue with the grep fallback for THIS query. Don't file again in the sam
78
101
 
79
102
  ## Constraints
80
103
 
81
- - **Chart-first.** Default to MCP queries; grep only when the chart cannot answer.
104
+ - **Runtime-first, then static.** When a beacon session exists and the symptom is runtime-shaped, lead with beacon — it converts a blind structural search into a targeted one. Beacon is additive evidence: its absence never aborts the run, it just drops you to static-only with a one-line note.
105
+ - **Don't reimplement beacon skills.** Step 0 does a lightweight `beacon_failures` + `beacon_correlate`; for deeper runtime analysis defer to `/kit:beacon-pulse` (failure + N preceding) and `/kit:beacon-scan` (events by kind).
106
+ - **Chart-first for the static phase.** Default to MCP queries; grep only when the chart cannot answer.
82
107
  - **One gap report per invocation.** Don't spam comments.
83
108
  - **No implementation.** This skill diagnoses; it does not edit. If the user wants the fix, they invoke `/fix:*` or hand the diagnosis back to you in chat.
84
- - **Cite by node id.** Every claim links to a chart node id (file:line via `inspect_node` lines) so the user can verify.
85
- - **Surface the fallback verbatim.** When grep is used, the message that grep was used (and why) must appear in the output — silent fallback hides the gap.
109
+ - **Cite your evidence.** Static claims link to a chart node id (file:line via `inspect_node` lines); runtime claims cite the beacon `seq` + event kind. The user can verify either.
110
+ - **Surface fallbacks verbatim.** When grep is used, or when beacon came up empty, the message (and why) must appear in the output — silent fallback hides the gap.