@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/dist/server/chart-serve.js +167 -2
- package/dist/server/cli.js +286 -50
- package/dist/server/course-entry.js +1 -1
- package/dist/server/graph-mcp-entry.js +180 -4
- package/dist/server/init-entry.js +563 -60
- package/dist/server/launch-bot-entry.js +4078 -0
- package/dist/server/launch-radar-entry.js +45 -0
- package/dist/server/orbit-entry.js +123 -26
- package/dist/server/parse-worker-entry.js +167 -2
- package/dist/server/radar-docker-init-entry.js +496 -39
- package/dist/server/radar-teardown-entry.js +23 -22
- package/dist/server/rover-entry.js +20555 -0
- package/package.json +8 -5
- package/scaffolds/ls-marketplace/plugins/kit/commands/standup.md +6 -6
- package/scaffolds/ls-marketplace/plugins/kit/skills/analyse/SKILL.md +6 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/brief/SKILL.md +72 -49
- package/scaffolds/ls-marketplace/plugins/kit/skills/brief/briefs.mjs +152 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/debug/SKILL.md +45 -20
- package/scaffolds/ls-marketplace/plugins/kit/skills/deploy-check/SKILL.md +76 -67
- package/scaffolds/ls-marketplace/plugins/kit/skills/handoff/SKILL.md +132 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/kickoff/SKILL.md +151 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/orbit/SKILL.md +14 -2
- package/scaffolds/ls-marketplace/plugins/kit/skills/ship/SKILL.md +149 -133
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@launchsecure/launch-kit",
|
|
3
|
-
"version": "0.0.
|
|
4
|
-
"description": "LaunchSecure toolkit — launch-
|
|
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-
|
|
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`
|
|
68
|
-
- A migration file under `prisma/migrations
|
|
69
|
-
- A deploy/publish was mentioned in commit subjects (regex: `\b(publish|release|deploy|bump)\b`)
|
|
70
|
-
- A new
|
|
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: "
|
|
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
|
|
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
|
|
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, `--
|
|
12
|
-
- For **update**: `<
|
|
13
|
-
- For **list**: `[--
|
|
14
|
-
- For **show**: `<
|
|
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 "
|
|
18
|
-
- `/kit:brief new "Roadmap nav" --from=discussion:cmt_abc123`
|
|
19
|
-
- `/kit:brief list
|
|
20
|
-
- `/kit:brief
|
|
21
|
-
- `/kit:brief
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
47
|
+
## Preflight
|
|
31
48
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
|
|
54
|
+
## Body template
|
|
40
55
|
|
|
41
|
-
When generating a NEW brief,
|
|
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**: <
|
|
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
|
|
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.
|
|
83
|
-
2.
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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 `<
|
|
95
|
-
2. If `--append=<text>`,
|
|
96
|
-
3. If a fresh description was passed, regenerate the body from the template + new description,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
- **
|
|
109
|
-
- **
|
|
110
|
-
- **
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
30
|
-
3. If `read_graph` returns 0 candidates, broaden:
|
|
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
|
-
-
|
|
34
|
-
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
74
|
+
End with a one-paragraph diagnosis + 2–3 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
|
-
- **
|
|
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
|
|
85
|
-
- **Surface
|
|
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.
|