tuncss-plan-kit 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,43 @@
1
+ import path from "path";
2
+ import { fileURLToPath } from "url";
3
+
4
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
5
+ const skillsDir = path.resolve(__dirname, "../../skills");
6
+
7
+ const WRAPPERS = {
8
+ brainstorm: {
9
+ description: "Turn an idea into an approved spec",
10
+ template:
11
+ "Use the `brainstorm` skill to handle the following request:\n\n$ARGUMENTS\n",
12
+ },
13
+ "plan-universal": {
14
+ description: "Turn an approved spec into an executable implementation plan",
15
+ template:
16
+ "Use the `plan-universal` skill to handle the following request:\n\n$ARGUMENTS\n",
17
+ },
18
+ "handoff-plan": {
19
+ description:
20
+ "Generate a paste-ready handoff message for another LLM agent to execute the plan",
21
+ template:
22
+ "Use the `handoff-plan` skill to handle the following request:\n\n$ARGUMENTS\n",
23
+ },
24
+ };
25
+
26
+ export const TuncssPlanKitPlugin = async () => ({
27
+ config: async (config) => {
28
+ config.skills = config.skills || {};
29
+ config.skills.paths = config.skills.paths || [];
30
+ if (!config.skills.paths.includes(skillsDir)) {
31
+ config.skills.paths.push(skillsDir);
32
+ }
33
+ config.command = config.command || {};
34
+ for (const [name, def] of Object.entries(WRAPPERS)) {
35
+ if (!config.command[name]) {
36
+ config.command[name] = {
37
+ template: def.template,
38
+ description: def.description,
39
+ };
40
+ }
41
+ }
42
+ },
43
+ });
package/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # tuncss-plan-kit
2
+
3
+ Three skills for spec-driven development, installable into Claude Code, Codex CLI, and OpenCode:
4
+
5
+ - **`/brainstorm`** — turn an idea into an approved spec (`docs/specs/`)
6
+ - **`/plan-universal`** — turn a spec into an executable plan (`docs/plans/`)
7
+ - **`/handoff-plan`** — generate a paste-ready briefing for another LLM agent to execute the plan (`docs/handoffs/`)
8
+
9
+ No agents, no routing, no TDD ceremony. Just three skills that get you from idea → spec → plan → handoff.
10
+
11
+ ## Install
12
+
13
+ In your project directory:
14
+
15
+ ```bash
16
+ npx tuncss-plan-kit init
17
+ ```
18
+
19
+ The installer auto-detects which platform(s) the project uses and writes the right files.
20
+
21
+ | Detected | Means |
22
+ |---|---|
23
+ | `.claude/` or `CLAUDE.md` | Claude Code |
24
+ | `.codex/` | Codex CLI |
25
+ | `.opencode/` | OpenCode |
26
+ | `AGENTS.md` (alone) | Both Codex and OpenCode (they share `AGENTS.md`) |
27
+
28
+ If nothing is detected, pass an explicit target:
29
+
30
+ ```bash
31
+ npx tuncss-plan-kit init --target=claude
32
+ npx tuncss-plan-kit init --target=claude,codex
33
+ npx tuncss-plan-kit init --target=all
34
+ ```
35
+
36
+ Re-running is safe. Skill and command files are overwritten only with `--force`. Instruction-file marker blocks are always replaced in place — your other content survives.
37
+
38
+ Restart your coding agent after install so it picks up the new skills and slash commands.
39
+
40
+ ## What gets written where
41
+
42
+ | Platform | Skills | Commands | Instructions |
43
+ |---|---|---|---|
44
+ | Claude Code | `.claude/skills/<n>/SKILL.md` | *(none — skills auto-expose as slash)* | `CLAUDE.md` |
45
+ | Codex CLI | `.agents/skills/<n>/SKILL.md` | `.codex/prompts/<n>.md` | `AGENTS.md` |
46
+ | OpenCode | `.opencode/skills/<n>/SKILL.md` | `.opencode/commands/<n>.md` | `AGENTS.md` |
47
+
48
+ Claude Code automatically exposes any skill named `foo` as `/foo`, so the kit doesn't write wrapper command files for it. Codex and OpenCode don't auto-expose, so wrappers are written there to give you the same `/brainstorm`, `/plan-universal`, `/handoff-plan` UX everywhere.
49
+
50
+ With `--global` the same files go to user-wide locations (`~/.claude/`, `~/.agents/`, `~/.codex/`).
51
+
52
+ **OpenCode `--global` is supported via an npm-plugin route**: the kit installs itself into `~/.config/opencode/node_modules/`, registers itself in `~/.config/opencode/opencode.json`'s `plugin` array, and drops command wrappers into `~/.config/opencode/commands/`. After install, restart OpenCode — skills appear in every project. (For Claude and Codex, `--global` is a plain file copy.)
53
+
54
+ Project-local is still the default for all three — recommended unless you specifically want the kit available everywhere.
55
+
56
+ ## Workflow
57
+
58
+ ```
59
+ You: /brainstorm I want a CLI that ...
60
+ Agent: ↓ brainstorming skill
61
+ asks one question at a time, proposes 2-3 approaches, presents
62
+ the design section by section, writes spec to docs/specs/
63
+ You: (review and approve)
64
+
65
+ You: /plan-universal
66
+ Agent: ↓ writing-plans skill
67
+ writes plan to docs/plans/ with execution contract at the top,
68
+ tasks shaped as Targets / Model Tier / Implementation Notes /
69
+ Done When / Verification
70
+
71
+ You: do TASK-01
72
+ Agent: reads only TASK-01's block, stays inside its Targets, stops for
73
+ approval when done
74
+
75
+ — or —
76
+
77
+ You: /handoff-plan
78
+ Agent: ↓ handoff skill
79
+ writes a short briefing to docs/handoffs/ that you can paste
80
+ into another agent (or feed it the file path)
81
+ ```
82
+
83
+ ## What's in a plan
84
+
85
+ Every plan starts with this contract:
86
+
87
+ > 1. Read **only** that task's block. Do not preview other tasks.
88
+ > 2. Stay strictly inside its **Targets** — do not edit files outside that list.
89
+ > 3. Follow the **Implementation Notes**; do not invent extra scope.
90
+ > 4. When **Done When** and **Verification** are satisfied, **stop and report**. Wait for approval.
91
+ > 5. If verification fails, report and stop. Do not attempt fixes outside the task's Targets.
92
+
93
+ Tasks are tagged with model tiers (T1 Fast / T2 Balanced / T3 Power / T4 Reasoning) so you can route execution to the cheapest model that can do the job.
94
+
95
+ ## Options
96
+
97
+ ```
98
+ npx tuncss-plan-kit init [--target=<list>] [--global] [--force]
99
+ ```
100
+
101
+ | Flag | Effect |
102
+ |------|--------|
103
+ | `--target=<list>` | Comma-separated. Values: `claude`, `codex`, `opencode`, `all`. Auto-detected if omitted. |
104
+ | `--global` | Install to user-wide locations instead of the current project. |
105
+ | `--force` | Overwrite existing skill/command files without warning. |
106
+
107
+ ## Why this exists
108
+
109
+ Existing kits ship dozens of agents and skills you'll never use, but every one of them sits in your context and burns tokens each turn. `tuncss-plan-kit` ships three files that cover the only loop most projects need: design → plan → execute (here or elsewhere). That's it.
110
+
111
+ ## License
112
+
113
+ MIT
package/bin/cli.js ADDED
@@ -0,0 +1,426 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from "fs";
4
+ import path from "path";
5
+ import os from "os";
6
+ import { spawnSync } from "child_process";
7
+ import { fileURLToPath } from "url";
8
+
9
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
10
+ const PKG_ROOT = path.resolve(__dirname, "..");
11
+ const PKG_NAME = "tuncss-plan-kit";
12
+
13
+ const SKILLS = ["brainstorm", "plan-universal", "handoff-plan"];
14
+ const COMMANDS = ["brainstorm", "plan-universal", "handoff-plan"];
15
+ const MARKER_START = "<!-- tuncss-plan-kit:start -->";
16
+ const MARKER_END = "<!-- tuncss-plan-kit:end -->";
17
+
18
+ const PLATFORMS = {
19
+ claude: {
20
+ label: "claude",
21
+ project: {
22
+ skillsDir: ".claude/skills",
23
+ commandsDir: null,
24
+ instructionsFile: "CLAUDE.md",
25
+ },
26
+ global: (home) => ({
27
+ skillsDir: path.join(home, ".claude", "skills"),
28
+ commandsDir: null,
29
+ instructionsFile: path.join(home, ".claude", "CLAUDE.md"),
30
+ }),
31
+ commandsSrc: null,
32
+ skipCommands: true,
33
+ },
34
+ codex: {
35
+ label: "codex",
36
+ project: {
37
+ skillsDir: ".agents/skills",
38
+ commandsDir: ".codex/prompts",
39
+ instructionsFile: "AGENTS.md",
40
+ },
41
+ global: (home) => ({
42
+ skillsDir: path.join(home, ".agents", "skills"),
43
+ commandsDir: path.join(home, ".codex", "prompts"),
44
+ instructionsFile: path.join(home, ".codex", "AGENTS.md"),
45
+ }),
46
+ commandsSrc: "codex",
47
+ },
48
+ opencode: {
49
+ label: "opencode",
50
+ project: {
51
+ skillsDir: ".opencode/skills",
52
+ commandsDir: ".opencode/commands",
53
+ instructionsFile: "AGENTS.md",
54
+ },
55
+ // Global install for OpenCode uses the npm-plugin route, not file-drop.
56
+ // See installOpenCodeGlobal().
57
+ global: null,
58
+ commandsSrc: "opencode",
59
+ },
60
+ };
61
+
62
+ const SUPPORTED = Object.keys(PLATFORMS);
63
+
64
+ function parseArgs(argv) {
65
+ const args = { command: null, target: null, global: false, force: false };
66
+ for (const a of argv) {
67
+ if (!args.command && !a.startsWith("--")) {
68
+ args.command = a;
69
+ } else if (a.startsWith("--target=")) {
70
+ args.target = a
71
+ .slice("--target=".length)
72
+ .split(",")
73
+ .map((s) => s.trim())
74
+ .filter(Boolean);
75
+ } else if (a === "--global") {
76
+ args.global = true;
77
+ } else if (a === "--force") {
78
+ args.force = true;
79
+ } else if (a === "--help" || a === "-h") {
80
+ args.command = "help";
81
+ }
82
+ }
83
+ return args;
84
+ }
85
+
86
+ function printHelp() {
87
+ console.log(`tuncss-plan-kit
88
+
89
+ Usage:
90
+ npx tuncss-plan-kit init [--target=<list>] [--global] [--force]
91
+
92
+ Commands:
93
+ init Install the three skills (brainstorm, plan-universal, handoff-plan)
94
+ into the target platform(s).
95
+
96
+ Options:
97
+ --target Comma-separated platforms to install for. Supported:
98
+ claude, codex, opencode, all
99
+ If omitted, auto-detects from the current directory.
100
+ --global Install to user-wide locations.
101
+ For OpenCode this uses the npm-plugin route (installs the package
102
+ into ~/.config/opencode/node_modules/ and registers it in
103
+ opencode.json's plugin array).
104
+ --force Overwrite existing skill/command files without warning.
105
+ (Instruction-file marker blocks are always idempotent.)
106
+
107
+ Examples:
108
+ npx tuncss-plan-kit init
109
+ npx tuncss-plan-kit init --target=claude
110
+ npx tuncss-plan-kit init --target=claude,codex
111
+ npx tuncss-plan-kit init --target=all --global
112
+ `);
113
+ }
114
+
115
+ function exists(p) {
116
+ try {
117
+ fs.accessSync(p);
118
+ return true;
119
+ } catch {
120
+ return false;
121
+ }
122
+ }
123
+
124
+ function detectTargets(cwd) {
125
+ const found = new Set();
126
+ if (exists(path.join(cwd, ".claude")) || exists(path.join(cwd, "CLAUDE.md"))) {
127
+ found.add("claude");
128
+ }
129
+ if (exists(path.join(cwd, ".codex"))) found.add("codex");
130
+ if (exists(path.join(cwd, ".opencode"))) found.add("opencode");
131
+ if (exists(path.join(cwd, "AGENTS.md"))) {
132
+ if (!found.has("codex") && !found.has("opencode")) {
133
+ found.add("codex");
134
+ found.add("opencode");
135
+ }
136
+ }
137
+ return [...found];
138
+ }
139
+
140
+ function ensureDir(dir) {
141
+ fs.mkdirSync(dir, { recursive: true });
142
+ }
143
+
144
+ function copyFile(src, dest, force) {
145
+ if (exists(dest) && !force) {
146
+ const a = fs.readFileSync(src, "utf8");
147
+ const b = fs.readFileSync(dest, "utf8");
148
+ if (a === b) return { status: "unchanged", dest };
149
+ fs.writeFileSync(dest, a);
150
+ return { status: "updated", dest };
151
+ }
152
+ ensureDir(path.dirname(dest));
153
+ fs.writeFileSync(dest, fs.readFileSync(src, "utf8"));
154
+ return { status: "written", dest };
155
+ }
156
+
157
+ function escapeRegex(s) {
158
+ return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
159
+ }
160
+
161
+ function upsertMarkerBlock(filePath, blockBody) {
162
+ const block = `${MARKER_START}\n${blockBody.trim()}\n${MARKER_END}`;
163
+ let existing = "";
164
+ if (exists(filePath)) existing = fs.readFileSync(filePath, "utf8");
165
+ if (existing.includes(MARKER_START) && existing.includes(MARKER_END)) {
166
+ const re = new RegExp(
167
+ `${escapeRegex(MARKER_START)}[\\s\\S]*?${escapeRegex(MARKER_END)}`,
168
+ "m"
169
+ );
170
+ const next = existing.replace(re, block);
171
+ if (next === existing) return { status: "unchanged" };
172
+ ensureDir(path.dirname(filePath));
173
+ fs.writeFileSync(filePath, next);
174
+ return { status: "block-updated" };
175
+ }
176
+ const sep =
177
+ existing.length === 0 ? "" : existing.endsWith("\n") ? "\n" : "\n\n";
178
+ ensureDir(path.dirname(filePath));
179
+ fs.writeFileSync(filePath, existing + sep + block + "\n");
180
+ return { status: existing.length === 0 ? "file-created" : "block-appended" };
181
+ }
182
+
183
+ function loadInstructionsBlock() {
184
+ const tplPath = path.join(PKG_ROOT, "templates", "instructions-block.md");
185
+ const tpl = fs.readFileSync(tplPath, "utf8");
186
+ return tpl.replace(MARKER_START, "").replace(MARKER_END, "").trim();
187
+ }
188
+
189
+ function relPath(p, base) {
190
+ const r = path.relative(base, p);
191
+ return r.split(path.sep).join("/");
192
+ }
193
+
194
+ function statusTag(status) {
195
+ if (status === "unchanged") return "·";
196
+ if (status === "updated" || status === "block-updated") return "↻";
197
+ return "✓";
198
+ }
199
+
200
+ function installPlatform({ platform, isGlobal, force, baseRoot, sharedState }) {
201
+ const cfg = PLATFORMS[platform];
202
+ const paths = isGlobal
203
+ ? cfg.global(os.homedir())
204
+ : {
205
+ skillsDir: path.join(baseRoot, cfg.project.skillsDir),
206
+ commandsDir: cfg.project.commandsDir
207
+ ? path.join(baseRoot, cfg.project.commandsDir)
208
+ : null,
209
+ instructionsFile: path.join(baseRoot, cfg.project.instructionsFile),
210
+ };
211
+ const displayBase = isGlobal ? os.homedir() : baseRoot;
212
+
213
+ const lines = [];
214
+
215
+ for (const skill of SKILLS) {
216
+ const src = path.join(PKG_ROOT, "skills", skill, "SKILL.md");
217
+ const dest = path.join(paths.skillsDir, skill, "SKILL.md");
218
+ const r = copyFile(src, dest, force);
219
+ lines.push(` ${statusTag(r.status)} ${relPath(r.dest, displayBase)}`);
220
+ }
221
+
222
+ if (!cfg.skipCommands) {
223
+ for (const cmd of COMMANDS) {
224
+ const src = path.join(PKG_ROOT, "commands", cfg.commandsSrc, `${cmd}.md`);
225
+ const dest = path.join(paths.commandsDir, `${cmd}.md`);
226
+ const r = copyFile(src, dest, force);
227
+ lines.push(` ${statusTag(r.status)} ${relPath(r.dest, displayBase)}`);
228
+ }
229
+ } else {
230
+ lines.push(
231
+ ` · (skill names auto-expose as slash commands; no wrapper files needed)`
232
+ );
233
+ }
234
+
235
+ if (sharedState.writtenInstructionFiles.has(paths.instructionsFile)) {
236
+ lines.push(
237
+ ` · ${relPath(paths.instructionsFile, displayBase)} (already updated this run)`
238
+ );
239
+ } else {
240
+ const r = upsertMarkerBlock(
241
+ paths.instructionsFile,
242
+ sharedState.instructionsBlock
243
+ );
244
+ sharedState.writtenInstructionFiles.add(paths.instructionsFile);
245
+ lines.push(
246
+ ` ${statusTag(r.status)} ${relPath(paths.instructionsFile, displayBase)} (${r.status})`
247
+ );
248
+ }
249
+
250
+ return lines;
251
+ }
252
+
253
+ function readJsonOrDefault(filePath, fallback) {
254
+ if (!exists(filePath)) return fallback;
255
+ try {
256
+ return JSON.parse(fs.readFileSync(filePath, "utf8"));
257
+ } catch {
258
+ return fallback;
259
+ }
260
+ }
261
+
262
+ function writeJson(filePath, obj) {
263
+ ensureDir(path.dirname(filePath));
264
+ fs.writeFileSync(filePath, JSON.stringify(obj, null, 2) + "\n");
265
+ }
266
+
267
+ function isRunningFromInstalledPackage() {
268
+ // If our package root contains /node_modules/, we were installed as a dep
269
+ // (e.g. via `npm install -g` or `npx`). Otherwise we're being run from
270
+ // source (dev mode).
271
+ return PKG_ROOT.split(path.sep).includes("node_modules");
272
+ }
273
+
274
+ function dependencySpec() {
275
+ // When running from a published install, use a version range so npm
276
+ // installs from the registry. When running from source (dev), point to
277
+ // our absolute path with file: so changes are picked up without publish.
278
+ if (isRunningFromInstalledPackage()) {
279
+ return `^${readJsonOrDefault(path.join(PKG_ROOT, "package.json"), { version: "0.1.0" }).version || "0.1.0"}`;
280
+ }
281
+ return "file:" + PKG_ROOT.split(path.sep).join("/");
282
+ }
283
+
284
+ function installOpenCodeGlobal() {
285
+ const home = os.homedir();
286
+ const ocDir = path.join(home, ".config", "opencode");
287
+ ensureDir(ocDir);
288
+ const lines = [];
289
+
290
+ // 1. package.json — add or update tuncss-plan-kit dependency
291
+ const pkgPath = path.join(ocDir, "package.json");
292
+ const pkg = readJsonOrDefault(pkgPath, {});
293
+ pkg.dependencies = pkg.dependencies || {};
294
+ const spec = dependencySpec();
295
+ const prevSpec = pkg.dependencies[PKG_NAME];
296
+ pkg.dependencies[PKG_NAME] = spec;
297
+ writeJson(pkgPath, pkg);
298
+ lines.push(
299
+ ` ${prevSpec === spec ? "·" : prevSpec ? "↻" : "✓"} ${relPath(pkgPath, home)} (dep: ${PKG_NAME}@${spec})`
300
+ );
301
+
302
+ // 2. npm install in ocDir
303
+ const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm";
304
+ const npmRes = spawnSync(npmCmd, ["install", "--silent", "--no-audit", "--no-fund"], {
305
+ cwd: ocDir,
306
+ stdio: "inherit",
307
+ shell: process.platform === "win32",
308
+ });
309
+ if (npmRes.status !== 0) {
310
+ throw new Error(
311
+ `npm install failed in ${ocDir} (exit code ${npmRes.status}). Fix the error above and re-run.`
312
+ );
313
+ }
314
+ lines.push(` ✓ npm install ran in ${relPath(ocDir, home)}`);
315
+
316
+ // 3. command wrappers — file-drop into ~/.config/opencode/commands/
317
+ // (the plugin handles skills via config injection, but command discovery
318
+ // appears to require file-drop)
319
+ const cmdDir = path.join(ocDir, "commands");
320
+ for (const cmd of COMMANDS) {
321
+ const src = path.join(PKG_ROOT, "commands", "opencode", `${cmd}.md`);
322
+ const dest = path.join(cmdDir, `${cmd}.md`);
323
+ const r = copyFile(src, dest, false);
324
+ lines.push(` ${statusTag(r.status)} ${relPath(r.dest, home)}`);
325
+ }
326
+
327
+ // 4. opencode.json — add plugin reference (idempotent)
328
+ const ocJsonPath = path.join(ocDir, "opencode.json");
329
+ const ocJson = readJsonOrDefault(ocJsonPath, {
330
+ $schema: "https://opencode.ai/config.json",
331
+ });
332
+ ocJson.plugin = ocJson.plugin || [];
333
+ // Normalize to strings only (we don't use the [string, object] tuple form)
334
+ const pluginPath = `~/.config/opencode/node_modules/${PKG_NAME}`;
335
+ const alreadyHas = ocJson.plugin.some(
336
+ (p) => p === pluginPath || (Array.isArray(p) && p[0] === pluginPath)
337
+ );
338
+ if (!alreadyHas) {
339
+ ocJson.plugin.push(pluginPath);
340
+ }
341
+ writeJson(ocJsonPath, ocJson);
342
+ lines.push(
343
+ ` ${alreadyHas ? "·" : "✓"} ${relPath(ocJsonPath, home)} (plugin: ${pluginPath})`
344
+ );
345
+
346
+ return lines;
347
+ }
348
+
349
+ function init(args) {
350
+ const cwd = process.cwd();
351
+
352
+ let targets = args.target;
353
+ if (targets && targets.includes("all")) {
354
+ targets = SUPPORTED.slice();
355
+ }
356
+ if (!targets) {
357
+ targets = detectTargets(cwd);
358
+ if (targets.length === 0) {
359
+ console.error(
360
+ "No supported platform detected in this directory.\n" +
361
+ "Specify one explicitly, e.g.: npx tuncss-plan-kit init --target=claude\n" +
362
+ "Or install for all three: npx tuncss-plan-kit init --target=all"
363
+ );
364
+ process.exit(1);
365
+ }
366
+ }
367
+
368
+ const unsupported = targets.filter((t) => !SUPPORTED.includes(t));
369
+ if (unsupported.length > 0) {
370
+ console.error(
371
+ `Unsupported target(s): ${unsupported.join(", ")}.\n` +
372
+ `Supported: ${SUPPORTED.join(", ")}, all.`
373
+ );
374
+ process.exit(1);
375
+ }
376
+
377
+ targets = SUPPORTED.filter((p) => targets.includes(p));
378
+
379
+ console.log(`tuncss-plan-kit init`);
380
+ console.log(` scope: ${args.global ? "global (user-wide)" : "project (./)"}`);
381
+ console.log(` target: ${targets.join(", ")}`);
382
+ console.log("");
383
+
384
+ const sharedState = {
385
+ instructionsBlock: loadInstructionsBlock(),
386
+ writtenInstructionFiles: new Set(),
387
+ };
388
+
389
+ for (const platform of targets) {
390
+ console.log(`[${platform}]`);
391
+ if (platform === "opencode" && args.global) {
392
+ const lines = installOpenCodeGlobal();
393
+ for (const l of lines) console.log(l);
394
+ } else {
395
+ const lines = installPlatform({
396
+ platform,
397
+ isGlobal: args.global,
398
+ force: args.force,
399
+ baseRoot: cwd,
400
+ sharedState,
401
+ });
402
+ for (const l of lines) console.log(l);
403
+ }
404
+ console.log("");
405
+ }
406
+
407
+ console.log(
408
+ "Done. Restart your coding agent(s) to pick up new skills and slash commands."
409
+ );
410
+ }
411
+
412
+ function main() {
413
+ const args = parseArgs(process.argv.slice(2));
414
+ if (!args.command || args.command === "help") {
415
+ printHelp();
416
+ process.exit(args.command ? 0 : 1);
417
+ }
418
+ if (args.command === "init") {
419
+ return init(args);
420
+ }
421
+ console.error(`Unknown command: ${args.command}`);
422
+ printHelp();
423
+ process.exit(1);
424
+ }
425
+
426
+ main();
@@ -0,0 +1 @@
1
+ Use the `brainstorm` skill to handle the user's request.
@@ -0,0 +1 @@
1
+ Use the `handoff-plan` skill to handle the user's request.
@@ -0,0 +1 @@
1
+ Use the `plan-universal` skill to handle the user's request.
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Turn an idea into an approved spec
3
+ ---
4
+
5
+ Use the `brainstorm` skill to handle the following request:
6
+
7
+ $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Generate a paste-ready handoff message for another LLM agent to execute the plan
3
+ ---
4
+
5
+ Use the `handoff-plan` skill to handle the following request:
6
+
7
+ $ARGUMENTS
@@ -0,0 +1,7 @@
1
+ ---
2
+ description: Turn an approved spec into an executable implementation plan
3
+ ---
4
+
5
+ Use the `plan-universal` skill to handle the following request:
6
+
7
+ $ARGUMENTS
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "tuncss-plan-kit",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": ".opencode/plugins/tuncss-plan-kit.js",
6
+ "description": "Three-skill kit for spec-driven development: brainstorm an idea into a spec, turn the spec into an executable plan, hand the plan off to another LLM agent.",
7
+ "bin": {
8
+ "tuncss-plan-kit": "bin/cli.js"
9
+ },
10
+ "files": [
11
+ "bin/",
12
+ "skills/",
13
+ "commands/",
14
+ "templates/",
15
+ ".opencode/",
16
+ "README.md"
17
+ ],
18
+ "keywords": [
19
+ "claude-code",
20
+ "claude",
21
+ "skills",
22
+ "brainstorming",
23
+ "planning",
24
+ "spec-driven",
25
+ "llm-agents"
26
+ ],
27
+ "author": "tuncss",
28
+ "license": "MIT",
29
+ "engines": {
30
+ "node": ">=18"
31
+ },
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "https://github.com/tuncss/tuncss-plan-kit"
35
+ }
36
+ }
@@ -0,0 +1,132 @@
1
+ ---
2
+ name: brainstorm
3
+ description: Use before any feature, component, or behavior change. Turns an idea into an approved design before any code is written.
4
+ ---
5
+
6
+ # Brainstorming
7
+
8
+ Turn an idea into a design the user approves, then hand off to plan-universal. No code, no scaffolding, no implementation skill until the design is approved.
9
+
10
+ <HARD-GATE>
11
+ Do NOT write code, scaffold, edit files for the feature, or invoke an implementation skill until you have presented a design and the user has approved it. This applies to every project regardless of size.
12
+ </HARD-GATE>
13
+
14
+ ## "This is too simple to need a design"
15
+
16
+ It isn't. A todo list, a one-file utility, a config tweak — all go through this. Simple-looking projects are where unexamined assumptions cost the most rework. The design can be three sentences for a trivial change. You still present it, you still get approval.
17
+
18
+ ## Checklist
19
+
20
+ Work through these in order. Don't skip ahead.
21
+
22
+ 1. Explore project context — relevant files, recent commits, any existing docs
23
+ 2. Assess scope — if the request is actually several independent projects, decompose before going deeper
24
+ 3. Ask clarifying questions — one per message, multiple-choice when you can
25
+ 4. Propose 2-3 approaches — trade-offs and your recommendation
26
+ 5. Present the design section by section, getting approval after each
27
+ 6. Write the spec to `docs/specs/YYYY-MM-DD-<topic>-design.md`
28
+ 7. Self-review the spec — placeholders, contradictions, ambiguity, scope
29
+ 8. Wait for the user to review the written spec
30
+ 9. Hand off — ask the user to run `/plan-universal` (or implement directly only if trivial; see Hand-off)
31
+
32
+ ## Scope assessment
33
+
34
+ Before any clarifying questions, look at the request as a whole. If it describes multiple independent subsystems ("a platform with chat, billing, file storage, and analytics"), don't refine details — that's wasted effort on something that needs to be decomposed first.
35
+
36
+ When the request is too large for a single spec:
37
+ - Name the independent pieces and how they relate
38
+ - Suggest a build order
39
+ - Brainstorm only the first sub-project through this flow
40
+ - Each sub-project gets its own spec → plan → implementation cycle
41
+
42
+ ## Asking clarifying questions
43
+
44
+ - One question per message. If a topic needs more, break it into multiple turns.
45
+ - Prefer multiple-choice. Open-ended is fine when the space is genuinely open.
46
+ - Focus on purpose, constraints, and what success looks like.
47
+ - Don't ask about anything you can derive from reading the code.
48
+
49
+ **If the user dumps answers in bulk** (numbered list answering several questions at once, or "just go ahead with X, Y, Z"), do NOT take it as permission to skip the gate. Acknowledge the answers, then ask 1-2 follow-ups on what those answers leave open — trade-offs, edge cases, or the next decision their choices imply ("LocalStorage confirmed — should we handle data clearing or schema versioning?"). Only move to approaches once those are resolved.
50
+
51
+ ## Proposing approaches
52
+
53
+ Once you understand the goal, lay out 2-3 ways to solve it. Each gets its trade-offs in plain language. Lead with the one you'd pick and say why. Don't hide your recommendation behind false neutrality — but make it easy for the user to override.
54
+
55
+ ## Presenting the design
56
+
57
+ Present in sections. Scale each section to its complexity:
58
+ - A few sentences for something straightforward
59
+ - Up to ~300 words when it's nuanced
60
+
61
+ After each section, ask if it looks right before moving on. Cover what's actually relevant: architecture, components, data flow, error handling, testing. Skip what doesn't apply.
62
+
63
+ If something doesn't fit together, go back and clarify. The point of these gates is to catch confusion before it lands in the spec.
64
+
65
+ ## Designing for isolation
66
+
67
+ Break the system into small units that each have one purpose, talk to each other through clear interfaces, and can be understood and tested on their own.
68
+
69
+ For each unit, you should be able to answer:
70
+ - What does it do?
71
+ - How do you use it?
72
+ - What does it depend on?
73
+
74
+ If a consumer has to read the internals to use a unit, the boundary is wrong. If you can't change internals without breaking consumers, the boundary is wrong. Smaller, well-bounded units are also easier to work with later — edits get more reliable when files are focused.
75
+
76
+ ## Working inside an existing codebase
77
+
78
+ - Read the surrounding code first. Follow the patterns already there.
79
+ - If existing code in the area has real problems that affect this work (an oversized file, tangled responsibilities, unclear boundaries), include the targeted improvement in the design — the way a careful developer cleans up the room they're working in.
80
+ - Don't bundle unrelated refactoring. Stay on what serves the goal.
81
+
82
+ ## YAGNI
83
+
84
+ Cut anything the request doesn't need yet. Future-proofing, config options "just in case", an abstraction for a hypothetical second consumer — all out, unless the user has actually named the second consumer.
85
+
86
+ ## Writing the spec
87
+
88
+ After every section is approved, write the spec to `docs/specs/YYYY-MM-DD-<topic>-design.md` (override if the user has set a different location). Create `docs/specs/` if it doesn't exist. Commit the file.
89
+
90
+ The spec is the document a future implementer reads. It captures decisions, not your reasoning trail. Keep it tight.
91
+
92
+ ## Spec self-review
93
+
94
+ Re-read with fresh eyes. Fix issues inline; no second review pass.
95
+
96
+ 1. **Placeholders** — any "TBD", "TODO", or vague requirement? Resolve them.
97
+ 2. **Internal consistency** — do sections contradict each other? Does the architecture match the feature description?
98
+ 3. **Scope** — is this still a single implementation plan, or did it grow into something that needs decomposing?
99
+ 4. **Ambiguity** — could a requirement be read two different ways? Pick one and make it explicit.
100
+
101
+ ## User review gate
102
+
103
+ After your self-review, ask the user to read the written spec:
104
+
105
+ > Spec written and committed to `<path>`. Please review it and let me know if you want changes before we move to the implementation plan.
106
+
107
+ Wait for their response. If they ask for changes, make them and re-run the self-review. Only move on once they approve.
108
+
109
+ ## Hand-off
110
+
111
+ After the spec is approved, **do NOT start implementing**. The skill ends here. The next step is `/plan-universal`, which turns the spec into an executable plan with model-tier hints and per-task verification.
112
+
113
+ End your final turn with this message to the user (paraphrase, but keep all four parts):
114
+
115
+ > Spec is locked at `<path>`. Want me to write the implementation plan via `/plan-universal`? Or, if this is small enough — one file, no new public API, no schema or migration changes, no new dependency — say "implement directly" and I'll do it now.
116
+
117
+ Then **stop and wait** for the user's choice. Default is `/plan-universal`. Implement directly **only** when:
118
+ - The user explicitly says so (the phrase "implement directly" or equivalent)
119
+ - **AND** the change meets every trivial criterion above
120
+
121
+ If you find yourself thinking "the spec is small, I'll just knock it out" — that's the drift this gate exists to catch. Stop. Hand off.
122
+
123
+ Do not invoke any other skill from this skill.
124
+
125
+ ## Key principles
126
+
127
+ - One question at a time
128
+ - Multiple choice when you can
129
+ - YAGNI hard
130
+ - Always explore 2-3 approaches before settling
131
+ - Approve as you go — don't drop a wall of design and ask "thoughts?"
132
+ - Be willing to back up when something doesn't fit
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: handoff-plan
3
+ description: Use after writing a plan when the user wants a short briefing message to paste into another LLM agent (Codex, Cursor, Copilot, etc.) so it can pick up execution. Triggered by /handoff-plan.
4
+ ---
5
+
6
+ # Handoff Plan
7
+
8
+ Produce a short, paste-ready briefing message that another LLM agent can use to pick up an existing plan in this repo. The receiving agent is assumed to have filesystem access — the message points at files rather than inlining them.
9
+
10
+ **Announce at start:** "Generating handoff message."
11
+
12
+ ## Inputs
13
+
14
+ - **If the user passes a plan path** (e.g. `/handoff-plan docs/plans/2026-05-13-foo.md`), use that file.
15
+ - **Otherwise** pick the most recently modified `*.md` in `docs/plans/`. If `docs/plans/` doesn't exist or is empty, stop and tell the user to run `/plan-universal` first.
16
+
17
+ If a spec matching the plan's slug exists in `docs/specs/`, include its path too. Best-effort match by filename slug; don't fabricate a path — omit the spec line if there's no clear match.
18
+
19
+ ## What to extract from the plan
20
+
21
+ Read the plan file and pull:
22
+ - **Goal** — the one-line goal from the header
23
+ - **Tech / dependencies** — the tech line from the header
24
+ - **Task list** — every `### TASK-NN: <name>` heading (just the numbers and names, not the bodies)
25
+
26
+ ## Repo context
27
+
28
+ Get a one-line project descriptor:
29
+ - Prefer `package.json` `name` + `description`
30
+ - Fall back to the first non-empty line of `README.md`
31
+ - One short sentence — no marketing language
32
+
33
+ ## Output
34
+
35
+ Write the message to `docs/handoffs/YYYY-MM-DD-<feature-slug>.md` (date = today, slug = same slug as the plan file). Create `docs/handoffs/` if missing.
36
+
37
+ After writing, print only a single confirmation line to chat:
38
+
39
+ > Handoff written to `docs/handoffs/<filename>.md`.
40
+
41
+ Do not echo the message contents — the user will open the file.
42
+
43
+ ## Message template
44
+
45
+ ````text
46
+ You're picking up an implementation plan in this repo.
47
+
48
+ **Project:** <project name> — <one-line description>
49
+
50
+ **Plan:** `<path/to/plan.md>`
51
+ **Spec:** `<path/to/spec.md>` ← omit this line entirely if no spec found
52
+
53
+ **Goal:** <goal line from plan>
54
+
55
+ **Tech:** <tech line from plan>
56
+
57
+ **Tasks:**
58
+ - TASK-01: <name>
59
+ - TASK-02: <name>
60
+ - ...
61
+
62
+ **How to execute (full execution contract is at the top of the plan file):**
63
+ 1. When I ask for a task ("do TASK-03"), read **only** that task's block in the plan.
64
+ 2. Stay strictly inside its **Targets** — don't edit files outside that list.
65
+ 3. Follow the **Implementation Notes**; don't invent extra scope.
66
+ 4. When **Done When** and **Verification** are satisfied, **stop and report**. Wait for my approval before moving on.
67
+ 5. If verification fails, report and stop. Don't attempt fixes outside the task's Targets.
68
+
69
+ Start by reading `<plan path>` end-to-end, then wait for me to ask for the first task. Don't begin TASK-01 until I ask.
70
+ ````
71
+
72
+ ## Rules
73
+
74
+ - Don't summarize task bodies. The receiving agent reads the plan file itself.
75
+ - Don't reformat the execution contract beyond the 5 numbered rules above. They are the contract; the plan file is the source of truth.
76
+ - Keep the message under ~50 lines. If you're tempted to add more context, you're inlining the plan — stop.
77
+ - Don't include this skill's name, your model name, or any Claude-specific framing in the output. The receiver doesn't need to know how the message was generated.
@@ -0,0 +1,138 @@
1
+ ---
2
+ name: plan-universal
3
+ description: Use when you have an approved spec or a clear multi-step task and need to write the implementation plan before any code is written. Invoked via /plan-universal, usually after the brainstorm skill.
4
+ ---
5
+
6
+ # Writing Plans
7
+
8
+ Turn a spec into a plan an engineer can execute task by task without re-reading the spec. Assume they're capable but have zero context on this codebase or problem domain — every task must stand on its own.
9
+
10
+ **Announce at start:** "Writing the implementation plan."
11
+
12
+ **Save plans to:** `docs/plans/YYYY-MM-DD-<feature-name>.md` (override if the user has set a different location). Create `docs/plans/` if it doesn't exist.
13
+
14
+ ## Scope check
15
+
16
+ If the spec covers multiple independent subsystems, that should have been caught during brainstorming. If it slipped through, stop and propose splitting it into one plan per subsystem before writing tasks. Each plan should produce working, testable software on its own.
17
+
18
+ ## File structure first
19
+
20
+ Before defining tasks, map every file the plan will create or modify and what each is responsible for. Decomposition decisions get locked in here, not inside individual tasks.
21
+
22
+ - One clear responsibility per file. Files that change together live together; split by responsibility, not technical layer.
23
+ - Smaller, focused files are easier to edit reliably than large ones doing many things.
24
+ - In an existing codebase, follow the patterns already there. If a file you're modifying has grown unwieldy and the work touches it heavily, including a focused split in the plan is reasonable. Don't bundle unrelated restructuring.
25
+
26
+ This map is what makes the task list coherent. Each task should produce changes that make sense as a self-contained unit.
27
+
28
+ ## Plan document structure
29
+
30
+ Every plan starts with this header:
31
+
32
+ ````markdown
33
+ # <Feature Name> — Implementation Plan
34
+
35
+ <!-- EXECUTION CONTRACT — read before touching any task -->
36
+ > When the user asks for a specific task (e.g. "do TASK-03"):
37
+ > 1. Read **only** that task's block. Do not preview other tasks.
38
+ > 2. Stay strictly inside its **Targets** — do not edit files outside that list.
39
+ > 3. Follow the **Implementation Notes**; do not invent extra scope.
40
+ > 4. When **Done When** and **Verification** are satisfied, **stop and report**. Wait for approval before moving to the next task.
41
+ > 5. If verification fails, report the failure and stop. Do not attempt fixes outside the task's Targets.
42
+
43
+ **Goal:** <one sentence>
44
+
45
+ **Architecture:** <2-3 sentences on the approach>
46
+
47
+ **Tech / dependencies:** <key libraries, runtimes, services>
48
+
49
+ **File map:**
50
+ - `path/to/a.ts` — <responsibility>
51
+ - `path/to/b.ts` — <responsibility>
52
+ - `tests/...` — <what's covered, if anything>
53
+
54
+ ---
55
+ ````
56
+
57
+ ## Model tiers
58
+
59
+ Every task gets a recommended tier. These are the cost/capability brackets for the model that should execute it:
60
+
61
+ - **T1 — Fast:** trivial edits, renames, formatting, single-file boilerplate
62
+ - **T2 — Balanced:** standard feature work in one component, contained logic
63
+ - **T3 — Power:** multi-file changes, non-trivial logic, refactors with consequence
64
+ - **T4 — Reasoning:** architecture decisions, gnarly debugging, cross-cutting design
65
+
66
+ When in doubt, pick the lower tier. Upgrades are cheap; over-spending isn't.
67
+
68
+ ## Task structure
69
+
70
+ Every task uses this shape:
71
+
72
+ ````markdown
73
+ ### TASK-01: <short name>
74
+
75
+ **Targets:**
76
+ - `exact/path/to/file.ts` (create | modify | delete)
77
+ - `exact/path/to/other.ts` (modify)
78
+
79
+ **Model Tier:** T2 <!-- T1 Fast | T2 Balanced | T3 Power | T4 Reasoning -->
80
+
81
+ **Implementation Notes:**
82
+ - What this task does, in plain language
83
+ - Any non-obvious decision and why
84
+ - Concrete code, types, function signatures, or commands the engineer needs — not "implement the handler" but the actual handler shape
85
+ - If a public interface from an earlier task is consumed here, restate its signature; don't make the reader page back
86
+
87
+ **Done When:**
88
+ - Bullet list of observable outcomes
89
+ - E.g. "endpoint returns 200 with `{ id, status }` body for valid input"
90
+ - E.g. "type `Foo` exported from `src/foo.ts`"
91
+
92
+ **Verification:**
93
+ - Manual: <commands the engineer runs and what they should see>
94
+ - Automated (optional): <test files, scripts, or `npm test -- foo` commands and expected output, only if automated coverage genuinely belongs here>
95
+ ````
96
+
97
+ Tasks are self-contained because the executor reads exactly one block per turn (see the Execution Contract). If TASK-07 needs the shape of something defined in TASK-02, restate it in TASK-07 — don't make the reader scroll.
98
+
99
+ ## Granularity
100
+
101
+ Each task should be a self-contained slice that produces something testable. Not microsteps like "write the failing test" / "make it pass" — that's noise. A task is roughly: a feature surface, a module, an endpoint, a screen, a migration. Split when:
102
+ - Targets cross unrelated areas
103
+ - The verification step would need multiple unrelated checks
104
+ - The Implementation Notes start branching ("either X or Y depending on…")
105
+
106
+ Merge when a task is so small it has no meaningful Done When of its own.
107
+
108
+ ## Tests are not mandatory
109
+
110
+ Don't dictate TDD or per-task test coverage. Add Automated Verification only when an automated check genuinely belongs in that task (a regression test for a known-bug fix, a contract test for a new public API). For most tasks, **Done When** + Manual Verification is enough. Let the executor judge whether more coverage pays for itself.
111
+
112
+ ## No placeholders
113
+
114
+ These are **plan failures**. Never write them:
115
+ - "TBD", "TODO", "implement later", "fill in details"
116
+ - "Add appropriate error handling" / "validate input" / "handle edge cases" — name the cases
117
+ - "Write tests for the above" without the actual test names and what they assert
118
+ - "Similar to TASK-N" — repeat what's needed; the executor reads tasks out of order
119
+ - Steps that describe *what* without showing *how* — if a task changes code, show the code shape, the type, or the exact command
120
+ - References to types, functions, or files not defined in any task or in the file map
121
+
122
+ ## Self-review
123
+
124
+ After the plan is written, re-read it against the spec with fresh eyes. Fix issues inline; no second review pass.
125
+
126
+ 1. **Spec coverage** — go through each requirement in the spec. Can you point to the task that implements it? Add tasks for any gap.
127
+ 2. **Placeholder scan** — anything from the "No placeholders" list? Fix.
128
+ 3. **Name and type consistency** — a function called `clearLayers()` in TASK-03 but `clearFullLayers()` in TASK-07 is a bug. Same for types, file paths, env vars, table names.
129
+ 4. **Targets isolation** — does any task's Targets list overlap awkwardly with another in a way that will force out-of-order edits? If so, resequence or merge.
130
+ 5. **Verification reality** — every Done When has a corresponding Verification step that an engineer can actually run.
131
+
132
+ ## After the plan
133
+
134
+ Save the plan, commit it, and tell the user:
135
+
136
+ > Plan saved to `<path>` and committed. To execute, ask for tasks one at a time (e.g. "do TASK-01") — I'll stay inside that task's Targets and stop for approval before moving on, per the execution contract at the top of the plan. Or, if you want to hand this off to another LLM agent, run `/handoff-plan`.
137
+
138
+ Do not start implementing in the same turn. Wait for the user to request the first task.
@@ -0,0 +1,11 @@
1
+ <!-- tuncss-plan-kit:start -->
2
+ ## Plan Kit
3
+
4
+ This project uses tuncss-plan-kit. Three slash commands are available:
5
+
6
+ - `/brainstorm` — turn an idea into an approved spec (writes to `docs/specs/`)
7
+ - `/plan-universal` — turn a spec into an executable plan (writes to `docs/plans/`)
8
+ - `/handoff-plan` — generate a paste-ready handoff for another LLM agent (writes to `docs/handoffs/`)
9
+
10
+ Plans contain an execution contract at the top. When asked for a specific task ("do TASK-03"), read only that task's block, stay inside its Targets, stop and report when Done When + Verification are satisfied.
11
+ <!-- tuncss-plan-kit:end -->