tuncss-plan-kit 0.3.0 → 0.4.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.
- package/README.md +10 -9
- package/bin/cli.js +12 -137
- package/package.json +1 -3
- package/.opencode/plugins/tuncss-plan-kit.js +0 -48
- package/commands/opencode/brainstorm.md +0 -5
- package/commands/opencode/changelog.md +0 -5
- package/commands/opencode/handoff-plan.md +0 -5
- package/commands/opencode/plan-universal.md +0 -5
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# tuncss-plan-kit
|
|
2
2
|
|
|
3
|
-
Four skills for spec-driven development, installable into Claude Code, Codex CLI,
|
|
3
|
+
Four skills for spec-driven development, installable into Claude Code, Codex CLI, and Antigravity:
|
|
4
4
|
|
|
5
5
|
- **`/brainstorm`** — turn an idea into an approved spec (`docs/specs/`)
|
|
6
6
|
- **`/plan-universal`** — turn a spec into an executable plan (`docs/plans/`)
|
|
@@ -23,9 +23,8 @@ The installer auto-detects which platform(s) the project uses and writes the rig
|
|
|
23
23
|
|---|---|
|
|
24
24
|
| `.claude/` or `CLAUDE.md` | Claude Code |
|
|
25
25
|
| `.codex/` | Codex CLI |
|
|
26
|
-
| `.opencode/` | OpenCode |
|
|
27
26
|
| `.agents/` | Antigravity |
|
|
28
|
-
| `AGENTS.md` (alone) | Codex
|
|
27
|
+
| `AGENTS.md` (alone) | Codex and Antigravity (they share `AGENTS.md`) |
|
|
29
28
|
|
|
30
29
|
If nothing is detected, pass an explicit target:
|
|
31
30
|
|
|
@@ -45,10 +44,9 @@ Restart your coding agent after install so it picks up the new skills and slash
|
|
|
45
44
|
|---|---|---|---|
|
|
46
45
|
| Claude Code | `.claude/skills/<n>/SKILL.md` | *(none — skills auto-expose as slash)* | `CLAUDE.md` |
|
|
47
46
|
| Codex CLI | `.agents/skills/<n>/SKILL.md` | `.codex/prompts/<n>.md` | `AGENTS.md` |
|
|
48
|
-
| OpenCode | `.opencode/skills/<n>/SKILL.md` | `.opencode/commands/<n>.md` | `AGENTS.md` |
|
|
49
47
|
| Antigravity | `.agents/skills/<n>/SKILL.md` | *(none — skills auto-expose as slash)* | `AGENTS.md` |
|
|
50
48
|
|
|
51
|
-
Claude Code and Antigravity automatically expose any skill named `foo` as `/foo`, so the kit doesn't write wrapper command files for them. Codex
|
|
49
|
+
Claude Code and Antigravity automatically expose any skill named `foo` as `/foo`, so the kit doesn't write wrapper command files for them. Codex doesn't auto-expose, so wrappers are written there to give you the same `/brainstorm`, `/plan-universal`, `/handoff-plan`, `/changelog` UX everywhere.
|
|
52
50
|
|
|
53
51
|
Antigravity and Codex share `.agents/skills/`, so installing both writes those files once — the second platform reports them as already written.
|
|
54
52
|
|
|
@@ -56,11 +54,9 @@ Note: `agy changelog` is Antigravity's own built-in subcommand for release notes
|
|
|
56
54
|
|
|
57
55
|
With `--global` the same files go to user-wide locations (`~/.claude/`, `~/.agents/`, `~/.codex/`, `~/.gemini/config/`).
|
|
58
56
|
|
|
59
|
-
**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.)
|
|
60
|
-
|
|
61
57
|
**Antigravity `--global` is a plain file copy to `~/.gemini/config/`** — a single location the desktop app, the `agy` CLI, and the IDE all read, so one install covers them all.
|
|
62
58
|
|
|
63
|
-
Project-local is still the default for all
|
|
59
|
+
Project-local is still the default for all three — recommended unless you specifically want the kit available everywhere.
|
|
64
60
|
|
|
65
61
|
## Workflow
|
|
66
62
|
|
|
@@ -110,7 +106,7 @@ npx tuncss-plan-kit init [--target=<list>] [--global] [--force]
|
|
|
110
106
|
|
|
111
107
|
| Flag | Effect |
|
|
112
108
|
|------|--------|
|
|
113
|
-
| `--target=<list>` | Comma-separated. Values: `claude`, `codex`, `
|
|
109
|
+
| `--target=<list>` | Comma-separated. Values: `claude`, `codex`, `antigravity`, `all`. Auto-detected if omitted. |
|
|
114
110
|
| `--global` | Install to user-wide locations instead of the current project. |
|
|
115
111
|
| `--force` | Overwrite existing skill/command files without warning. |
|
|
116
112
|
|
|
@@ -118,6 +114,11 @@ npx tuncss-plan-kit init [--target=<list>] [--global] [--force]
|
|
|
118
114
|
|
|
119
115
|
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 four files that cover the only loop most projects need: design → plan → execute (here or elsewhere) → record. That's it.
|
|
120
116
|
|
|
117
|
+
## A note on OpenCode
|
|
118
|
+
|
|
119
|
+
OpenCode was supported through v0.3.0. It was dropped in v0.4.0 because nobody
|
|
120
|
+
on the team uses it any more. If you need it, pin `tuncss-plan-kit@0.3.0`.
|
|
121
|
+
|
|
121
122
|
## License
|
|
122
123
|
|
|
123
124
|
MIT
|
package/bin/cli.js
CHANGED
|
@@ -3,12 +3,10 @@
|
|
|
3
3
|
import fs from "fs";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import os from "os";
|
|
6
|
-
import { spawnSync } from "child_process";
|
|
7
6
|
import { fileURLToPath } from "url";
|
|
8
7
|
|
|
9
8
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
10
9
|
const PKG_ROOT = path.resolve(__dirname, "..");
|
|
11
|
-
const PKG_NAME = "tuncss-plan-kit";
|
|
12
10
|
|
|
13
11
|
const SKILLS = ["brainstorm", "plan-universal", "handoff-plan", "changelog"];
|
|
14
12
|
const COMMANDS = ["brainstorm", "plan-universal", "handoff-plan", "changelog"];
|
|
@@ -45,18 +43,6 @@ const PLATFORMS = {
|
|
|
45
43
|
}),
|
|
46
44
|
commandsSrc: "codex",
|
|
47
45
|
},
|
|
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
46
|
// Antigravity reads .agents/ in a workspace and ~/.gemini/config/ globally.
|
|
61
47
|
// One global location serves all three variants (desktop app, agy CLI, IDE).
|
|
62
48
|
// Like Claude Code, it expands skills into slash commands on its own, so no
|
|
@@ -109,17 +95,14 @@ Usage:
|
|
|
109
95
|
npx tuncss-plan-kit init [--target=<list>] [--global] [--force]
|
|
110
96
|
|
|
111
97
|
Commands:
|
|
112
|
-
init Install the
|
|
113
|
-
into the target platform(s).
|
|
98
|
+
init Install the four skills (brainstorm, plan-universal, handoff-plan,
|
|
99
|
+
changelog) into the target platform(s).
|
|
114
100
|
|
|
115
101
|
Options:
|
|
116
102
|
--target Comma-separated platforms to install for. Supported:
|
|
117
|
-
claude, codex,
|
|
103
|
+
claude, codex, antigravity, all
|
|
118
104
|
If omitted, auto-detects from the current directory.
|
|
119
105
|
--global Install to user-wide locations.
|
|
120
|
-
For OpenCode this uses the npm-plugin route (installs the package
|
|
121
|
-
into ~/.config/opencode/node_modules/ and registers it in
|
|
122
|
-
opencode.json's plugin array).
|
|
123
106
|
--force Overwrite existing skill/command files without warning.
|
|
124
107
|
(Instruction-file marker blocks are always idempotent.)
|
|
125
108
|
|
|
@@ -146,16 +129,12 @@ function detectTargets(cwd) {
|
|
|
146
129
|
found.add("claude");
|
|
147
130
|
}
|
|
148
131
|
if (exists(path.join(cwd, ".codex"))) found.add("codex");
|
|
149
|
-
if (exists(path.join(cwd, ".opencode"))) found.add("opencode");
|
|
150
132
|
// Antigravity discovers .agents/ and reads AGENTS.md as rules. Its project
|
|
151
133
|
// paths are a subset of Codex's, so adding it here costs no extra files.
|
|
152
134
|
if (exists(path.join(cwd, ".agents"))) found.add("antigravity");
|
|
153
135
|
if (exists(path.join(cwd, "AGENTS.md"))) {
|
|
154
136
|
found.add("antigravity");
|
|
155
|
-
if (!found.has("codex")
|
|
156
|
-
found.add("codex");
|
|
157
|
-
found.add("opencode");
|
|
158
|
-
}
|
|
137
|
+
if (!found.has("codex")) found.add("codex");
|
|
159
138
|
}
|
|
160
139
|
return [...found];
|
|
161
140
|
}
|
|
@@ -297,105 +276,6 @@ function installPlatform({ platform, isGlobal, force, baseRoot, sharedState }) {
|
|
|
297
276
|
return lines;
|
|
298
277
|
}
|
|
299
278
|
|
|
300
|
-
function readJsonOrDefault(filePath, fallback) {
|
|
301
|
-
if (!exists(filePath)) return fallback;
|
|
302
|
-
try {
|
|
303
|
-
return JSON.parse(fs.readFileSync(filePath, "utf8"));
|
|
304
|
-
} catch {
|
|
305
|
-
return fallback;
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function writeJson(filePath, obj) {
|
|
310
|
-
ensureDir(path.dirname(filePath));
|
|
311
|
-
fs.writeFileSync(filePath, JSON.stringify(obj, null, 2) + "\n");
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
function isRunningFromInstalledPackage() {
|
|
315
|
-
// If our package root contains /node_modules/, we were installed as a dep
|
|
316
|
-
// (e.g. via `npm install -g` or `npx`). Otherwise we're being run from
|
|
317
|
-
// source (dev mode).
|
|
318
|
-
return PKG_ROOT.split(path.sep).includes("node_modules");
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
function dependencySpec() {
|
|
322
|
-
// When running from a published install, use a version range so npm
|
|
323
|
-
// installs from the registry. When running from source (dev), point to
|
|
324
|
-
// our absolute path with file: so changes are picked up without publish.
|
|
325
|
-
if (isRunningFromInstalledPackage()) {
|
|
326
|
-
return `^${readJsonOrDefault(path.join(PKG_ROOT, "package.json"), { version: "0.1.0" }).version || "0.1.0"}`;
|
|
327
|
-
}
|
|
328
|
-
return "file:" + PKG_ROOT.split(path.sep).join("/");
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
function installOpenCodeGlobal() {
|
|
332
|
-
const home = os.homedir();
|
|
333
|
-
const ocDir = path.join(home, ".config", "opencode");
|
|
334
|
-
ensureDir(ocDir);
|
|
335
|
-
const lines = [];
|
|
336
|
-
|
|
337
|
-
// 1. package.json — add or update tuncss-plan-kit dependency
|
|
338
|
-
const pkgPath = path.join(ocDir, "package.json");
|
|
339
|
-
const pkg = readJsonOrDefault(pkgPath, {});
|
|
340
|
-
pkg.dependencies = pkg.dependencies || {};
|
|
341
|
-
const spec = dependencySpec();
|
|
342
|
-
const prevSpec = pkg.dependencies[PKG_NAME];
|
|
343
|
-
pkg.dependencies[PKG_NAME] = spec;
|
|
344
|
-
writeJson(pkgPath, pkg);
|
|
345
|
-
lines.push(
|
|
346
|
-
` ${prevSpec === spec ? "·" : prevSpec ? "↻" : "✓"} ${relPath(pkgPath, home)} (dep: ${PKG_NAME}@${spec})`
|
|
347
|
-
);
|
|
348
|
-
|
|
349
|
-
// 2. npm install in ocDir
|
|
350
|
-
// Pass the full command as a single string with shell: true to avoid the
|
|
351
|
-
// Node DEP0190 warning about unescaped args concatenation. Args are all
|
|
352
|
-
// hardcoded here so there's no injection surface.
|
|
353
|
-
const npmBin = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
354
|
-
const npmRes = spawnSync(`${npmBin} install --silent --no-audit --no-fund`, {
|
|
355
|
-
cwd: ocDir,
|
|
356
|
-
stdio: "inherit",
|
|
357
|
-
shell: true,
|
|
358
|
-
});
|
|
359
|
-
if (npmRes.status !== 0) {
|
|
360
|
-
throw new Error(
|
|
361
|
-
`npm install failed in ${ocDir} (exit code ${npmRes.status}). Fix the error above and re-run.`
|
|
362
|
-
);
|
|
363
|
-
}
|
|
364
|
-
lines.push(` ✓ npm install ran in ${relPath(ocDir, home)}`);
|
|
365
|
-
|
|
366
|
-
// 3. command wrappers — file-drop into ~/.config/opencode/commands/
|
|
367
|
-
// (the plugin handles skills via config injection, but command discovery
|
|
368
|
-
// appears to require file-drop)
|
|
369
|
-
const cmdDir = path.join(ocDir, "commands");
|
|
370
|
-
for (const cmd of COMMANDS) {
|
|
371
|
-
const src = path.join(PKG_ROOT, "commands", "opencode", `${cmd}.md`);
|
|
372
|
-
const dest = path.join(cmdDir, `${cmd}.md`);
|
|
373
|
-
const r = copyFile(src, dest, false);
|
|
374
|
-
lines.push(` ${statusTag(r.status)} ${relPath(r.dest, home)}`);
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
// 4. opencode.json — add plugin reference (idempotent)
|
|
378
|
-
const ocJsonPath = path.join(ocDir, "opencode.json");
|
|
379
|
-
const ocJson = readJsonOrDefault(ocJsonPath, {
|
|
380
|
-
$schema: "https://opencode.ai/config.json",
|
|
381
|
-
});
|
|
382
|
-
ocJson.plugin = ocJson.plugin || [];
|
|
383
|
-
// Normalize to strings only (we don't use the [string, object] tuple form)
|
|
384
|
-
const pluginPath = `~/.config/opencode/node_modules/${PKG_NAME}`;
|
|
385
|
-
const alreadyHas = ocJson.plugin.some(
|
|
386
|
-
(p) => p === pluginPath || (Array.isArray(p) && p[0] === pluginPath)
|
|
387
|
-
);
|
|
388
|
-
if (!alreadyHas) {
|
|
389
|
-
ocJson.plugin.push(pluginPath);
|
|
390
|
-
}
|
|
391
|
-
writeJson(ocJsonPath, ocJson);
|
|
392
|
-
lines.push(
|
|
393
|
-
` ${alreadyHas ? "·" : "✓"} ${relPath(ocJsonPath, home)} (plugin: ${pluginPath})`
|
|
394
|
-
);
|
|
395
|
-
|
|
396
|
-
return lines;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
279
|
function init(args) {
|
|
400
280
|
const cwd = process.cwd();
|
|
401
281
|
|
|
@@ -439,19 +319,14 @@ function init(args) {
|
|
|
439
319
|
|
|
440
320
|
for (const platform of targets) {
|
|
441
321
|
console.log(`[${platform}]`);
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
baseRoot: cwd,
|
|
451
|
-
sharedState,
|
|
452
|
-
});
|
|
453
|
-
for (const l of lines) console.log(l);
|
|
454
|
-
}
|
|
322
|
+
const lines = installPlatform({
|
|
323
|
+
platform,
|
|
324
|
+
isGlobal: args.global,
|
|
325
|
+
force: args.force,
|
|
326
|
+
baseRoot: cwd,
|
|
327
|
+
sharedState,
|
|
328
|
+
});
|
|
329
|
+
for (const l of lines) console.log(l);
|
|
455
330
|
console.log("");
|
|
456
331
|
}
|
|
457
332
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tuncss-plan-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"main": ".opencode/plugins/tuncss-plan-kit.js",
|
|
6
5
|
"description": "Four-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, and record what changed.",
|
|
7
6
|
"bin": {
|
|
8
7
|
"tuncss-plan-kit": "bin/cli.js"
|
|
@@ -12,7 +11,6 @@
|
|
|
12
11
|
"skills/",
|
|
13
12
|
"commands/",
|
|
14
13
|
"templates/",
|
|
15
|
-
".opencode/",
|
|
16
14
|
"README.md"
|
|
17
15
|
],
|
|
18
16
|
"keywords": [
|
|
@@ -1,48 +0,0 @@
|
|
|
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
|
-
// Minimal templates: just pass the user's input through. The skill's
|
|
8
|
-
// `description` frontmatter is what triggers the model to invoke the skill
|
|
9
|
-
// when relevant — we don't force-load it from the wrapper, which would
|
|
10
|
-
// dump the full SKILL.md body into the chat in OpenCode's UI.
|
|
11
|
-
const WRAPPERS = {
|
|
12
|
-
brainstorm: {
|
|
13
|
-
description: "Turn an idea into an approved spec",
|
|
14
|
-
template: "$ARGUMENTS\n",
|
|
15
|
-
},
|
|
16
|
-
"plan-universal": {
|
|
17
|
-
description: "Turn an approved spec into an executable implementation plan",
|
|
18
|
-
template: "$ARGUMENTS\n",
|
|
19
|
-
},
|
|
20
|
-
"handoff-plan": {
|
|
21
|
-
description:
|
|
22
|
-
"Generate a paste-ready handoff message for another LLM agent to execute the plan",
|
|
23
|
-
template: "$ARGUMENTS\n",
|
|
24
|
-
},
|
|
25
|
-
changelog: {
|
|
26
|
-
description: "Record what changed in docs/CHANGELOG.md",
|
|
27
|
-
template: "$ARGUMENTS\n",
|
|
28
|
-
},
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export const TuncssPlanKitPlugin = async () => ({
|
|
32
|
-
config: async (config) => {
|
|
33
|
-
config.skills = config.skills || {};
|
|
34
|
-
config.skills.paths = config.skills.paths || [];
|
|
35
|
-
if (!config.skills.paths.includes(skillsDir)) {
|
|
36
|
-
config.skills.paths.push(skillsDir);
|
|
37
|
-
}
|
|
38
|
-
config.command = config.command || {};
|
|
39
|
-
for (const [name, def] of Object.entries(WRAPPERS)) {
|
|
40
|
-
if (!config.command[name]) {
|
|
41
|
-
config.command[name] = {
|
|
42
|
-
template: def.template,
|
|
43
|
-
description: def.description,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
});
|