@launchsecure/launch-kit 0.0.34 → 0.0.36
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/cli.js +277 -33
- package/dist/server/init-entry.js +741 -230
- package/dist/server/launch-bot-entry.js +4078 -0
- package/dist/server/orbit-entry.js +969 -136
- package/dist/server/radar-docker-init-entry.js +326 -32
- package/dist/server/rover-entry.js +624 -124
- package/package.json +4 -3
- package/scaffolds/ls-marketplace/plugins/kit/skills/brief/SKILL.md +53 -22
- package/scaffolds/ls-marketplace/plugins/kit/skills/brief/briefs.mjs +152 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/kickoff/SKILL.md +167 -0
- package/scaffolds/ls-marketplace/plugins/kit/skills/orbit/SKILL.md +41 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@launchsecure/launch-kit",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.36",
|
|
4
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",
|
|
@@ -56,7 +56,8 @@
|
|
|
56
56
|
"launch-orbit": "./dist/server/orbit-entry.js",
|
|
57
57
|
"launch-course": "./dist/server/course-entry.js",
|
|
58
58
|
"launch-beacon": "./dist/server/beacon-monitor-entry.js",
|
|
59
|
-
"launch-rover": "./dist/server/rover-entry.js"
|
|
59
|
+
"launch-rover": "./dist/server/rover-entry.js",
|
|
60
|
+
"launch-bot": "./dist/server/launch-bot-entry.js"
|
|
60
61
|
},
|
|
61
62
|
"scripts": {
|
|
62
63
|
"build": "pnpm build:client && pnpm build:chart-client && pnpm build:deck-client && pnpm build:council-client && pnpm build:beacon && pnpm build:server",
|
|
@@ -68,7 +69,7 @@
|
|
|
68
69
|
"build:council-client": "vite build --config vite.council.config.ts",
|
|
69
70
|
"build:client": "vite build",
|
|
70
71
|
"build:chart-client": "vite build --config vite.chart.config.ts",
|
|
71
|
-
"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 --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",
|
|
72
|
+
"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",
|
|
72
73
|
"dev:client": "vite",
|
|
73
74
|
"dev:deck-serve": "cd ../.. && tsx watch packages/cli/src/server/deck-mcp-entry.ts serve",
|
|
74
75
|
"dev:chart": "pnpm build:server && pnpm build:chart-client && node dist/server/graph-mcp-entry.js serve",
|
|
@@ -1,38 +1,55 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Create or update a repo-backed Brief — a long-form
|
|
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 a
|
|
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
8
|
|
|
9
|
-
**
|
|
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.
|
|
10
25
|
|
|
11
26
|
Parse `$ARGUMENTS`:
|
|
12
27
|
- **subcommand** (required) — `new` | `update` | `list` | `show`.
|
|
13
|
-
- For **new**: `<title>` (required), `--from=discussion:<id>` to seed body from an existing discussion thread, `--dir=<path>` to override the
|
|
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.
|
|
14
29
|
- For **update**: `<slug-or-path>` and either a body description (re-generates body) or `--append=<text>` to add a section.
|
|
15
|
-
- For **list**: `[--dir=<path>]`
|
|
30
|
+
- For **list**: `[--category=<name>]` to filter to one category, else lists briefs across all category folders. `[--dir=<path>]` to scan a specific dir.
|
|
16
31
|
- For **show**: `<slug-or-path>`.
|
|
17
32
|
|
|
18
33
|
Examples:
|
|
19
|
-
- `/kit:brief new "
|
|
20
|
-
- `/kit:brief new "Roadmap nav" --from=discussion:cmt_abc123`
|
|
34
|
+
- `/kit:brief new "Comm Hub channels" --category=features`
|
|
35
|
+
- `/kit:brief new "Roadmap nav" --from=discussion:cmt_abc123 --category=architecture`
|
|
21
36
|
- `/kit:brief list`
|
|
37
|
+
- `/kit:brief list --category=requirement`
|
|
22
38
|
- `/kit:brief show launch-watch`
|
|
23
|
-
- `/kit:brief update
|
|
39
|
+
- `/kit:brief update comm-hub-channels --append="Resolved: use the Slack metaphor not Teams."`
|
|
24
40
|
|
|
25
41
|
## Location
|
|
26
42
|
|
|
27
|
-
- **
|
|
43
|
+
- **Folder**: `doc/<category>/` at the repo root, where `<category>` is resolved from `--category` (or inferred + confirmed).
|
|
28
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).
|
|
29
|
-
- Override with `--dir=<path>` only if the user asks.
|
|
45
|
+
- Override the folder with `--dir=<path>` only if the user asks.
|
|
30
46
|
|
|
31
47
|
## Preflight
|
|
32
48
|
|
|
33
49
|
1. Resolve the repo root (the working directory is the repo root in this project).
|
|
34
|
-
2.
|
|
35
|
-
3.
|
|
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.
|
|
36
53
|
|
|
37
54
|
## Body template
|
|
38
55
|
|
|
@@ -74,31 +91,45 @@ When generating a NEW brief, write this skeleton as the file contents (plain mar
|
|
|
74
91
|
|
|
75
92
|
### new
|
|
76
93
|
|
|
77
|
-
1.
|
|
78
|
-
2.
|
|
79
|
-
3.
|
|
80
|
-
4.
|
|
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.
|
|
81
99
|
|
|
82
100
|
### update
|
|
83
101
|
|
|
84
|
-
1. Resolve `<slug-or-path>`
|
|
85
|
-
2. If `--append=<text>`, Read the file, append the text under a `## Update <ISO-date>` heading, and Write it back.
|
|
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.
|
|
86
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.
|
|
87
105
|
|
|
88
106
|
### list
|
|
89
107
|
|
|
90
|
-
|
|
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.
|
|
91
115
|
|
|
92
116
|
### show
|
|
93
117
|
|
|
94
|
-
|
|
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.
|
|
95
125
|
|
|
96
126
|
## Constraints
|
|
97
127
|
|
|
98
|
-
- **
|
|
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`.
|
|
99
130
|
- **Plain markdown.** No HTML, no emojis unless the user wrote them.
|
|
100
131
|
- **One brief, one file.** Don't fragment a brief across multiple files — append via `--append` to keep the document continuous.
|
|
101
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).
|
|
102
|
-
- **`doc/ideas/` is the user's personal scratch.** Write
|
|
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.
|
|
103
134
|
- **Cite real code.** Use launch-chart node ids in the References section — don't invent file paths.
|
|
104
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
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Cold-start a session — read the last HANDOFF snapshot, the newest shipping log, the git summary, any in-flight orbits/worktrees, and (if LS is connected) your open work items, reconcile them, then categorize all pending work (resume in-flight · review/PR feedback · bugs · high-priority · low-hanging fruit · stale/dead · tech-debt/deferred, plus a non-pickable blocked list) and let you choose a list to work from before drilling into a specific item. Read-only; the complement to /kit:handoff. Does NOT branch, commit, or write files unless you confirm a next step.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
# /kit:kickoff
|
|
6
|
+
|
|
7
|
+
Start a session the way `/kit:handoff` ends one. Handoff **writes** the current-state snapshot and a single kickoff prompt at the end of a session; kickoff **reads** that snapshot back at the start of the next one, cross-checks it against git and your work items, and turns "what was I doing / what's next" into a concrete pick.
|
|
8
|
+
|
|
9
|
+
The loop is: `/kit:handoff` → (new session) → `/kit:kickoff` → choose a slice → work → `/kit:handoff` → `/kit:ship`. One session per feature; kickoff is the first thing you run in the fresh session.
|
|
10
|
+
|
|
11
|
+
This skill is **read-only by default**. It does not branch, commit, push, or write files. Its output is: a lay-of-the-land board, the pending work **sorted into categorized lists** (resume in-flight · low-hanging fruit · high-priority · stale/dead · tech-debt/deferred), a two-stage choice (pick a *list*, then a *specific item*), and — once you've chosen — a ready-to-use kickoff prompt for that item plus an offer to set up the branch/orbit as a *separate, confirmed* next step.
|
|
12
|
+
|
|
13
|
+
Parse `$ARGUMENTS`:
|
|
14
|
+
|
|
15
|
+
- (empty) — full orient: gather all sources, show the board + categorized lists, ask which list to work from, then drill into a specific item.
|
|
16
|
+
- **--brief** — show the lay-of-the-land board + the category headline counts only (e.g. "🍒 Low-hanging: 4 · 🔥 High-priority: 3 · 🪦 Stale: 6"). Skip the interactive choice. For "just remind me where I was."
|
|
17
|
+
- **--list=<name>** — skip the first-stage menu; go straight to the named list (`resume` / `review` / `bugs` / `priority` / `fruit` / `stale` / `debt`) and present its filtered items to pick from.
|
|
18
|
+
- **--pick=<text>** — skip both menus; treat `<text>` as the chosen item and go straight to producing its kickoff prompt + setup offer.
|
|
19
|
+
- **--all** — when assembling work-item candidates, include items not assigned to you (whole-board backlog), not just your own. Default is assigned-to-me.
|
|
20
|
+
|
|
21
|
+
## Preflight
|
|
22
|
+
|
|
23
|
+
1. `git rev-parse --is-inside-work-tree` — abort if not a git repo.
|
|
24
|
+
2. Resolve the **base branch** (same logic as `/kit:handoff` and `/kit:ship`):
|
|
25
|
+
- `git symbolic-ref refs/remotes/origin/HEAD --short` (strip `origin/`). Fall back to `origin/main` / `origin/master` if unset.
|
|
26
|
+
3. Record current branch (`git rev-parse --abbrev-ref HEAD`) and git email (`git config user.email`) — the email is the work-item handle.
|
|
27
|
+
4. Detect LS connectivity: check for `.launch-secure.cred.config` at the repo root. If absent, kickoff still runs — it just **skips the work-items source** and notes "LS not connected; candidates from HANDOFF + git only." Do NOT abort (unlike `/kit:standup`, kickoff is useful offline).
|
|
28
|
+
|
|
29
|
+
## Step 1 — Gather the sources (don't show yet)
|
|
30
|
+
|
|
31
|
+
Pull from five layers in parallel. Each is independently optional; a missing one is noted, not fatal.
|
|
32
|
+
|
|
33
|
+
**1a. The HANDOFF snapshot** — read `doc/handoff/HANDOFF.md` if it exists. Extract its four live sections: **Where we are**, **What's next** (the priority-ordered 1–3), **Open threads**, and the embedded **Kickoff prompt**. This is the human-curated "what's next," highest signal. If the file doesn't exist, note "no HANDOFF yet — first kickoff" and lean on git + work items.
|
|
34
|
+
|
|
35
|
+
**1b. The last ship** — read the newest file under `doc/shipping-logs/` (date-prefixed `YYYY-MM-DD-…`, so the lexically-last name is newest). This is the authoritative "what was *actually* last updated/shipped" — HANDOFF is current-state only and does not carry per-ship detail. Capture its title + the slice it shipped. If the dir is empty, skip.
|
|
36
|
+
|
|
37
|
+
**1c. Git summary** — the real on-disk state, to reconcile against what HANDOFF claims:
|
|
38
|
+
- `git log <base>..HEAD --oneline` — commits on this branch not yet in base.
|
|
39
|
+
- `git log -n 8 --oneline <base>` — recent baseline history (what landed lately).
|
|
40
|
+
- `git status --porcelain` — uncommitted work. Filter out generated noise (`.launchsecure/graphs/*.json`, `node_modules`, `dist`, `.next`). What remains is genuine in-flight work — a **"resume this"** candidate.
|
|
41
|
+
- Current branch + whether it's ahead/behind base.
|
|
42
|
+
|
|
43
|
+
> ⚠️ Everything in 1c is scoped to the **current** worktree only. In-flight work in other worktrees/orbits is invisible here — 1e covers it. Do not present a "resume in-flight" list without running 1e first.
|
|
44
|
+
|
|
45
|
+
**1d. Work items (only if LS connected)** — build the backlog candidates:
|
|
46
|
+
- `mcp__launch-secure__work_items_list({ assignee_email: "<git email>", status: "IN_PROGRESS" })` — things you already started; strongest "resume" candidates.
|
|
47
|
+
- `mcp__launch-secure__work_items_list({ assignee_email: "<git email>", status: "TODO" })` — your queued backlog.
|
|
48
|
+
- With `--all`, also fetch the same statuses without `assignee_email` to include unassigned/team items.
|
|
49
|
+
- Capture id + title + status for each. Cap at ~8 total; if more, keep the top 8 and note "(N more in LS)".
|
|
50
|
+
|
|
51
|
+
> Use the hosted `launch-secure` MCP by default. Only use `local-launch-secure` if the active course points there (see `/kit:course`). Don't ask for org/project slugs — the `.mcp.json` headers default to the current project.
|
|
52
|
+
|
|
53
|
+
**1e. Orbits / other worktrees** — the in-flight work that 1c structurally can't see. A feature usually lives in its own orbit (isolated worktree + forked DB), so the current tree being clean does NOT mean nothing is in flight. Enumerate + classify them:
|
|
54
|
+
- **Primary: `mcp__launch-orbit__orbit_check({ all: true })`** (if launch-orbit is wired) — sweeps every orbit registered for THIS repo and returns, per orbit, a `recommendation` + two verdicts. This replaces the hand-rolled dirty/ahead/merged math — orbit_check already runs `git status`, the merged-vs-base check, AND the DB/data drop-safety checks, then tells you which orbits still hold work vs. are safe to drop:
|
|
55
|
+
- **not `dropSafe`** (uncommitted changes or commits ahead of base) → a **🔧 Resume in-flight** candidate. Pull the "N dirty, M ahead" label from its `git.clean` / `git.merged` check summaries, and use the report's `actions[]` for what's needed to land it.
|
|
56
|
+
- **`dropSafe`** (merged + clean, nothing to lose) → a **🪦 Stale / dead** candidate ("orbit ‹slug› merged + clean — safe to drop"). Note: `healthy:false` here is usually just the DB being down, not real work — don't promote it to resume on that alone.
|
|
57
|
+
- `mcp__launch-orbit__orbit_list` — use for the stable slug + `age` metadata to label candidates and sort 🔧 Resume in-flight (freshest first).
|
|
58
|
+
- `git worktree list` — also surfaces **bare** worktrees created outside launch-orbit (these won't appear in orbit_check/orbit_list). For each such non-orbit worktree other than the current one, fall back to raw git: `git -C <path> status --porcelain` (uncommitted, filter the generated noise from 1c) + `git -C <path> rev-list --count <base>..HEAD` (commits ahead). Same rule: dirty/ahead → resume; clean + 0-ahead → stale/dead.
|
|
59
|
+
- `orbit_check` / `orbit_list` span **all** projects on the machine — keep only orbits whose `path` is under this repo's root.
|
|
60
|
+
- **Fallback (launch-orbit not wired):** classify every worktree from `git worktree list` with the raw-git pair above — still works, just without the DB/data drop-safety signal.
|
|
61
|
+
|
|
62
|
+
## Step 2 — Reconcile + flag drift
|
|
63
|
+
|
|
64
|
+
Before showing anything, cross-check the sources against each other. This is the honesty pass — the inverse of what `/kit:standup` does for shipped work.
|
|
65
|
+
|
|
66
|
+
- **Stale "What's next":** if a HANDOFF "What's next" item appears already done in git (matching commits in `<base>..HEAD`, or named in the newest shipping log), flag it: _"HANDOFF lists ‹X› as next, but it appears shipped (commit ‹sha›) — likely stale."_ Don't silently drop it; surface the contradiction.
|
|
67
|
+
- **Dedup across sources:** a HANDOFF "What's next" item and an LS work item often refer to the same slice (match on id like `LS-…`/`#…`, or on title similarity). Collapse them into one candidate that carries both handles.
|
|
68
|
+
- **Resolved open threads:** if an "Open thread" looks addressed by a recent commit, note it as "possibly resolved — confirm."
|
|
69
|
+
- **Branch sanity:** if the current branch already has commits ahead of base AND there's an in-progress work item, the most likely intent is "continue the current branch," not "start something new" — rank accordingly.
|
|
70
|
+
|
|
71
|
+
## Step 3 — Categorize the pending work into lists
|
|
72
|
+
|
|
73
|
+
Pool every candidate from the reconciled sources (HANDOFF "What's next" + "Open threads", LS work items, uncommitted git work, follow-up flags grepped from recent commit bodies — same patterns `/kit:standup` uses: `TODO`, `follow-up`, `deferred`, `not patched`, `revert`, `next pass`, plus `broken`/`bug`/`failing`/`blocked`/`waiting on`). Dedup (Step 2).
|
|
74
|
+
|
|
75
|
+
**First, pull out the blocked items** — anything tagged `blocked`/`waiting`, marked depending on an unfinished item, or whose commit/thread says "waiting on ‹X›". These go to the non-pickable **🚧 Blocked** list (Step 4) so you see *why* they aren't moving; they are removed from all buckets below.
|
|
76
|
+
|
|
77
|
+
Then sort each remaining item into **one** bucket — most-specific bucket wins, in this precedence:
|
|
78
|
+
|
|
79
|
+
1. **🔧 Resume in-flight** — uncommitted local work in the current tree (1c) + **dirty/ahead orbits & other worktrees (1e)** + `IN_PROGRESS` work items. The strongest "continue what you started" signal; always wins if present. Sort orbit/worktree candidates by recency (`age` / most-recently-touched first) — the freshest dirty orbit is usually what you were last on, even if it isn't the current tree.
|
|
80
|
+
2. **👀 Review / PR feedback** — work waiting on *your* action on something already in motion: open PRs you authored or are assigned to review, unresolved review comments, `needs-review`/`changes-requested` tags. Time-sensitive (someone's blocked on you), so it outranks fresh work.
|
|
81
|
+
3. **🐛 Bugs / broken** — defects: work items of type `bug` or tagged `bug`/`defect`/`regression`, and `broken`/`failing`/`revert`/`not patched` follow-up flags from commit bodies. Distinct from feature backlog regardless of priority.
|
|
82
|
+
4. **🔥 High-priority** — items the project marks urgent: a work-item priority/severity field at its top value, tags like `p0`/`p1`/`urgent`, **and** everything in HANDOFF "What's next" (human-curated = high priority by definition), minus anything Step 2 flagged stale.
|
|
83
|
+
5. **🍒 Low-hanging fruit** — quick wins: work items with a small estimate/size field or `quick`/`chore`/`good-first`/`small` tags, single-file follow-up flags from commit bodies, and one-line open threads. When no size signal exists, infer from scope words in the title ("rename", "typo", "bump", "add flag", "copy") — but say it's inferred.
|
|
84
|
+
6. **🪦 Stale / dead** — pending too long: `TODO`/`IN_PROGRESS` work items whose `updatedAt` is older than ~30 days (tune to the project's pace), and open threads that have survived multiple handoffs. These are the "why is this still here" items.
|
|
85
|
+
7. **🧹 Tech-debt / deferred** — items tagged `tech-debt`/`debt`/`deferred`/`backlog`, explicit deferrals recorded in HANDOFF, and `deferred`/`rolled back` follow-up flags from commit bodies.
|
|
86
|
+
|
|
87
|
+
Each item lands in exactly one bucket. Within a bucket, sort by the most relevant key (priority desc, then age; for stale, oldest first; for fruit, smallest first). An empty bucket is simply omitted everywhere downstream.
|
|
88
|
+
|
|
89
|
+
## Step 4 — Show the lay of the land
|
|
90
|
+
|
|
91
|
+
One compact block: orientation header, then each **non-empty** bucket with up to **5** items and a `+N more` tail. No preamble.
|
|
92
|
+
|
|
93
|
+
```
|
|
94
|
+
📍 Where we are
|
|
95
|
+
<HANDOFF "Where we are", trimmed to 2–4 lines. If no HANDOFF: a 1-line synthesis from git.>
|
|
96
|
+
🚢 Last shipped: <newest shipping-log title + date> — <one line>
|
|
97
|
+
⚠️ Drift: <Step 2 flags, one line each. Omit if none.>
|
|
98
|
+
|
|
99
|
+
🔧 Resume in-flight (N)
|
|
100
|
+
- <item — handle/branch/file · short why>
|
|
101
|
+
- <orbit ‹slug› [branch] · N dirty, M ahead · touched <age>>
|
|
102
|
+
…up to 5… (+N more)
|
|
103
|
+
|
|
104
|
+
👀 Review / PR feedback (N)
|
|
105
|
+
- <item — PR#/LS-id · what's waiting on you>
|
|
106
|
+
…up to 5… (+N more)
|
|
107
|
+
|
|
108
|
+
🐛 Bugs / broken (N)
|
|
109
|
+
- <item — LS-id · short why>
|
|
110
|
+
…up to 5… (+N more)
|
|
111
|
+
|
|
112
|
+
🔥 High-priority (N)
|
|
113
|
+
- <item — LS-id · short why>
|
|
114
|
+
…up to 5… (+N more)
|
|
115
|
+
|
|
116
|
+
🍒 Low-hanging fruit (N)
|
|
117
|
+
- <item — handle · est/size or "inferred quick">
|
|
118
|
+
…up to 5… (+N more)
|
|
119
|
+
|
|
120
|
+
🪦 Stale / dead (N)
|
|
121
|
+
- <item — LS-id · "untouched 47d">
|
|
122
|
+
…up to 5… (+N more)
|
|
123
|
+
|
|
124
|
+
🧹 Tech-debt / deferred (N)
|
|
125
|
+
- <item — handle · source>
|
|
126
|
+
…up to 5… (+N more)
|
|
127
|
+
|
|
128
|
+
🚧 Blocked (N) — not pickable, shown for awareness
|
|
129
|
+
- <item — handle · waiting on ‹what›>
|
|
130
|
+
…up to 5… (+N more)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Readable in under a minute. The 🚧 Blocked list is informational only — never offered as a pick. If `--brief`, stop after the headline counts line (don't expand the bucket items).
|
|
134
|
+
|
|
135
|
+
## Step 5 — Two-stage choice: pick a list, then an item
|
|
136
|
+
|
|
137
|
+
**Stage A — which list.** Call **AskUserQuestion** ("Which list do you want to work from?"). Options are the **non-empty pickable buckets** (🚧 Blocked is never an option), capped at 4 (the tool's max) — there are up to 7 pickable buckets, so choose the 4 most useful for this session by precedence + item count + freshness, and surface the rest in option descriptions ("…also N stale, N tech-debt — say the word to see them"). Thin slow-moving buckets may be folded into one option ("Stale & deferred"). Each option's description carries the bucket's count + a one-line flavor of what's inside. "Other" (auto-added) lets the user name any list (including a folded/overflow one) or item directly. Skip Stage A if `--list=` was passed.
|
|
138
|
+
|
|
139
|
+
**Stage B — which item.** Once a list is chosen, show that bucket's **full** filtered list (not just the top 5 — now we're focused on one category, so depth is fine), numbered, each with its handle + the one-line why. Ask the user to pick one (a second `AskUserQuestion` with up to 4 of the top items, or just let them reply with a number/name if the list is long). Skip Stage B if `--pick=` was passed.
|
|
140
|
+
|
|
141
|
+
## Step 6 — Set up the chosen item
|
|
142
|
+
|
|
143
|
+
Once a specific item is chosen:
|
|
144
|
+
|
|
145
|
+
1. **Produce its kickoff prompt.** If the chosen slice matches the one HANDOFF already wrote a "Kickoff prompt for the next session" for, **reuse that verbatim** in a fenced block. Otherwise synthesize one in the **same shape** handoff uses (Starting **‹item — Title›** · read CLAUDE.md + HANDOFF + relevant Briefs · then the 1–6 design-shape questions: Surface, Fields/inputs, Eligibility, Data path, feature-specific, Out-of-scope). Make it specific to the chosen slice, not generic.
|
|
146
|
+
2. **Offer the setup as a confirmed next step — do not do it unprompted.** Tailor the offer to what was picked:
|
|
147
|
+
- **Picked an existing orbit/worktree (1e candidate):** it already exists — offer to switch into it (`mcp__launch-orbit__orbit_switch` to activate, then work in its `path`), NOT to create a branch. Never re-create an orbit that's already live.
|
|
148
|
+
- **Picked a fresh slice:** offer to branch off the base (`git checkout <base> && git pull && git checkout -b <slice-branch>`) **or** create an isolated orbit (`/kit:orbit`) if the slice wants a forked DB.
|
|
149
|
+
Wait for an explicit "yes" / branch name before running any git or orbit command — this is a state mutation and falls under the per-action confirmation rule.
|
|
150
|
+
3. If the chosen slice is a `TODO` work item and the user confirms starting it, offer (don't auto-run) to move it to `IN_PROGRESS` via `mcp__launch-secure__work_item_move` / `work_item_update`.
|
|
151
|
+
|
|
152
|
+
## Edge cases
|
|
153
|
+
|
|
154
|
+
- **No HANDOFF.md yet** (first kickoff in a repo) — don't fabricate a "Where we are." Synthesize the board from git + last shipping log + work items, and tell the user "no HANDOFF snapshot yet — run `/kit:handoff` at the end of this session to create one." Kickoff degrades to a git+LS orientation.
|
|
155
|
+
- **LS not connected** — skip Step 1d entirely; candidates come from HANDOFF + git. Note it once on the board.
|
|
156
|
+
- **Clean tree, nothing in flight, empty backlog** — say so plainly ("nothing in flight; HANDOFF's next is ‹X›") and just present HANDOFF's "What's next" as the candidates. Don't invent work.
|
|
157
|
+
- **HANDOFF wildly out of sync with git** (many stale "next" items already shipped) — lead with the drift block and suggest the previous session likely forgot `/kit:handoff`; offer to reconstruct current-state from git, but don't write HANDOFF here (that's `/kit:handoff`'s job).
|
|
158
|
+
- **Mid-branch resume** (current branch already ahead of base) — surface "continue this branch" at the top of the 🔧 Resume in-flight list, and skip the branch-creation offer in Step 6 unless the user picks a different item.
|
|
159
|
+
- **In-flight work lives in an orbit, not the current tree** (the common case — current tree clean but a dirty/ahead orbit exists per 1e) — do NOT report "nothing in flight." Lead 🔧 Resume in-flight with the freshest dirty/ahead orbit, and in Step 6 offer `orbit_switch` into it rather than a new branch.
|
|
160
|
+
- **Everything lands in one bucket** (e.g. all candidates are stale) — that's fine; show the single list and skip Stage A (no choice to make), going straight to picking an item.
|
|
161
|
+
|
|
162
|
+
## Notes for the assistant
|
|
163
|
+
|
|
164
|
+
- Kickoff is **read + orient + choose**. It is the mirror of `/kit:handoff` (write) — keep the boundary clean: kickoff does not write `HANDOFF.md`, does not commit, does not push. The only mutations it ever performs are the explicitly-confirmed branch/orbit/work-item-move in Step 6.
|
|
165
|
+
- The single most useful thing kickoff adds over just re-reading HANDOFF is **reconciliation** (Step 2): HANDOFF is hand-maintained and goes stale. Always cross-check it against git before presenting its "What's next" as gospel.
|
|
166
|
+
- Prefer commit scopes, work-item ids, and shipping-log titles as candidate handles — they're stable and clickable. Don't pull the full chart/graph for this; kickoff is a fast orientation, not an analysis (`/kit:analyse` is for structure).
|
|
167
|
+
- Respect the course: read/write LS through whichever server the active course points at, never cross prod/local.
|
|
@@ -10,19 +10,22 @@ Parse `$ARGUMENTS` — first token is the subcommand:
|
|
|
10
10
|
|
|
11
11
|
- **create <branch>** — `[--base=<ref>] [--profile=<name>]` — fork resources per `orbit.json`, return path + envFile.
|
|
12
12
|
- **list** — list every orbit registered on this machine.
|
|
13
|
-
- **switch <branch>** —
|
|
13
|
+
- **switch <branch>** — activate an orbit: atomically repoint the `current` anchor at its worktree, then operate under that anchor.
|
|
14
|
+
- **deactivate** — clear the `current` anchor so work targets the main checkout again.
|
|
14
15
|
- **doctor** — surface registry inconsistencies.
|
|
15
|
-
- **check <branch>
|
|
16
|
+
- **check <branch>** — `[--all] [--deep] [--base=<ref>] [--json]` — orbit **cleanliness** report: DROP-SAFE + HEALTHY verdicts, recommendation + concrete actions. (Different from `check-merge` — this is "is it safe to delete / sane to keep working in", not "will it merge".)
|
|
17
|
+
- **check-merge <branch> --target=<target-branch>** — run pre-merge gates WITHOUT merging.
|
|
16
18
|
- **merge <branch> --target=<target-branch>** — `[--cleanup=full|none] [--skip-gate=<id>,…]` — run gates + merge.
|
|
17
|
-
- **drop <branch>** — `[--no-backup]` — tear down (backup, drop DB, remove worktree, deregister).
|
|
19
|
+
- **drop <branch>** — `[--no-backup] [--force]` — tear down (drop-safety guard, then backup, drop DB, remove worktree, deregister).
|
|
18
20
|
|
|
19
21
|
Examples:
|
|
20
22
|
- `/kit:orbit create feat-channel-rename --profile=fe`
|
|
21
|
-
- `/kit:orbit check feat-channel-rename --
|
|
23
|
+
- `/kit:orbit check feat-channel-rename` · `/kit:orbit check --all`
|
|
24
|
+
- `/kit:orbit check-merge feat-channel-rename --target=implementation`
|
|
22
25
|
- `/kit:orbit merge feat-channel-rename --target=implementation`
|
|
23
26
|
- `/kit:orbit doctor`
|
|
24
27
|
|
|
25
|
-
## Preflight (runs on create / check / merge)
|
|
28
|
+
## Preflight (runs on create / check-merge / merge)
|
|
26
29
|
|
|
27
30
|
1. Confirm `mcp__launch-orbit__orbit_doctor` returns no critical inconsistencies for THIS project. If it does, surface them and stop — the operator probably wants `/kit:orbit drop <stale-branch>` first.
|
|
28
31
|
2. Confirm `orbit.json` exists at the repo root. If missing, tell the user to add one (point at the orbit docs) and stop.
|
|
@@ -43,13 +46,40 @@ Examples:
|
|
|
43
46
|
|
|
44
47
|
### switch
|
|
45
48
|
|
|
46
|
-
`mcp__launch-orbit__orbit_switch(branch)`
|
|
49
|
+
`mcp__launch-orbit__orbit_switch(branch)` **activates** the orbit — it atomically repoints `<project>/.launchsecure/orbit/current` at the orbit's worktree and writes `active.json`, then returns `anchor` + `nextAction`. This is a real, deterministic switch: one stable path now resolves into the active orbit.
|
|
50
|
+
|
|
51
|
+
After switching, target the anchor for everything (this is the whole point — one path, not the raw worktree path):
|
|
52
|
+
- **files**: Read / Edit / Write under `.launchsecure/orbit/current/<path>`.
|
|
53
|
+
- **chart**: pass `project_root: ".launchsecure/orbit/current"`.
|
|
54
|
+
- **commands**: run the returned `nextAction` (`cd ".launchsecure/orbit/current"`) once, then all Bash runs in the orbit.
|
|
55
|
+
|
|
56
|
+
Switching to a *different* orbit just repoints the anchor — re-run `nextAction` afterward, because a shell that already `cd`'d resolved the old target at cd-time (files and chart follow the symlink live; only the shell needs the re-cd).
|
|
57
|
+
|
|
58
|
+
### deactivate
|
|
59
|
+
|
|
60
|
+
`mcp__launch-orbit__orbit_deactivate()` removes the `current` anchor + `active.json` for this project. Use when done in the orbit so subsequent work targets the main checkout. No-op if nothing is active. (Dropping the active orbit clears the anchor automatically.)
|
|
47
61
|
|
|
48
62
|
### doctor
|
|
49
63
|
|
|
50
64
|
`mcp__launch-orbit__orbit_doctor` → list every issue with a one-line fix (e.g. `orphan-registration` → `/kit:orbit drop <branch>`).
|
|
51
65
|
|
|
52
|
-
### check
|
|
66
|
+
### check (cleanliness)
|
|
67
|
+
|
|
68
|
+
`mcp__launch-orbit__orbit_check(branch)` (or `all: true` to sweep this repo's orbits; `deep: true` adds the prisma schema-drift check) → render the two-verdict report:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
orbit feat-channel-rename [feat-channel-rename] DROP-SAFE: NO HEALTHY: YES
|
|
72
|
+
recommendation: KEEP — unmerged or uncommitted work would be lost on drop.
|
|
73
|
+
Drop-safety: ✗ uncommitted changes · ✗ 3 commits ahead · ✓ no orbit-only rows
|
|
74
|
+
Working-health: ✓ registry · ⚠ env drift · ✓ DB reachable · ✓ ports free
|
|
75
|
+
Actions:
|
|
76
|
+
→ Commit or stash the uncommitted changes …
|
|
77
|
+
→ Merge feat-channel-rename into implementation …
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Use it to answer "can I drop this / is it sane to keep working in" — e.g. before cleaning up stale orbits, or at session start. The report carries a `recommendation` + `actions[]`; surface those verbatim. DB checks skip gracefully if the cluster is down.
|
|
81
|
+
|
|
82
|
+
### check-merge (pre-merge gates)
|
|
53
83
|
|
|
54
84
|
`mcp__launch-orbit__orbit_check_mergeable(branch, target)` → render the gate report:
|
|
55
85
|
|
|
@@ -64,7 +94,7 @@ result: BLOCKED — fix build-lint then retry.
|
|
|
64
94
|
|
|
65
95
|
### merge
|
|
66
96
|
|
|
67
|
-
1. Run `check` first (same report). If `passed: false`, stop with the report — don't attempt the merge.
|
|
97
|
+
1. Run `check-merge` first (same report). If `passed: false`, stop with the report — don't attempt the merge.
|
|
68
98
|
2. If `passed: true`, call `mcp__launch-orbit__orbit_merge(branch, target, cleanup, skipGates)` and surface the result.
|
|
69
99
|
3. Default `cleanup: "full"` (orbit is dropped post-merge after pg_dump). The operator must pass `--cleanup=none` to keep the orbit around.
|
|
70
100
|
4. After a successful merge, recommend `/kit:standup` to surface the change in the next standup, and `/kit:diagram` if it touched the schema.
|
|
@@ -73,11 +103,13 @@ result: BLOCKED — fix build-lint then retry.
|
|
|
73
103
|
|
|
74
104
|
`mcp__launch-orbit__orbit_drop(branch, backup: !args.noBackup)` → idempotent. Surface `backups[]` paths.
|
|
75
105
|
|
|
106
|
+
**Drop-safety guard (default on).** `orbit_drop` runs the drop-safety checks first and THROWS if the orbit still has uncommitted/unmerged work or orbit-only DB data. When that happens, surface the blockers and DON'T pass `force: true` reflexively — relay the verdict, let the operator decide. Only pass `force: true` after they explicitly confirm "drop anyway, I'll lose that work". (Tip: run `/kit:orbit check <branch>` first to see the full report and the recommended actions.)
|
|
107
|
+
|
|
76
108
|
## Conventions encoded here
|
|
77
109
|
|
|
78
110
|
- **No bare `--target=master`.** Default merge target is `implementation` per project CLAUDE.md ("implementation branch is parent for all impl branches"). If the operator types `--target=master`, double-check by asking once.
|
|
79
111
|
- **No `--skip-gate` without a reason in the next message.** Surface a banner: `"Skipping gates: <ids>. Reason?"` — let the operator answer in chat; do NOT proceed silently.
|
|
80
|
-
- **Always run `check` before `merge`.** The skill enforces this — even if the operator runs `merge` directly, the skill runs `check` first.
|
|
112
|
+
- **Always run `check-merge` before `merge`.** The skill enforces this — even if the operator runs `merge` directly, the skill runs `check-merge` first.
|
|
81
113
|
- **Layer awareness.** Pre-flight always prints `detect_project_stack` layers so the operator sees parity (or absence) between chart layers and orbit gate scope.
|
|
82
114
|
|
|
83
115
|
## Constraints
|