@tcanaud/playbook 1.0.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/LICENSE +21 -0
- package/README.md +69 -0
- package/bin/cli.js +60 -0
- package/package.json +31 -0
- package/src/detect.js +118 -0
- package/src/installer.js +142 -0
- package/src/session.js +493 -0
- package/src/updater.js +85 -0
- package/src/validator.js +289 -0
- package/src/worktree.js +120 -0
- package/src/yaml-parser.js +682 -0
- package/templates/commands/playbook.resume.md +143 -0
- package/templates/commands/playbook.run.md +214 -0
- package/templates/core/_index.yaml +15 -0
- package/templates/core/playbook.tpl.yaml +88 -0
- package/templates/playbooks/auto-feature.yaml +100 -0
- package/templates/playbooks/auto-validate.yaml +31 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Thibaud Canaud
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# @tcanaud/playbook
|
|
2
|
+
|
|
3
|
+
YAML-driven orchestration for kai feature workflows — autonomous playbook execution with crash recovery, gates, and git-tracked audit journals.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx @tcanaud/playbook init
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This creates:
|
|
12
|
+
- `.playbooks/` directory with built-in playbooks and template
|
|
13
|
+
- `.claude/commands/playbook.run.md` and `playbook.resume.md` slash commands
|
|
14
|
+
|
|
15
|
+
## CLI Commands
|
|
16
|
+
|
|
17
|
+
| Command | Description |
|
|
18
|
+
|---------|-------------|
|
|
19
|
+
| `npx @tcanaud/playbook init [--yes]` | Scaffold `.playbooks/` and install slash commands |
|
|
20
|
+
| `npx @tcanaud/playbook update` | Refresh commands and built-in playbooks |
|
|
21
|
+
| `npx @tcanaud/playbook start {playbook} {feature}` | Create worktree session for parallel execution |
|
|
22
|
+
| `npx @tcanaud/playbook check {file}` | Validate playbook YAML against schema |
|
|
23
|
+
| `npx @tcanaud/playbook help` | Show usage |
|
|
24
|
+
|
|
25
|
+
## Claude Code Commands
|
|
26
|
+
|
|
27
|
+
After installation, use these in the Claude Code TUI:
|
|
28
|
+
|
|
29
|
+
| Command | Description |
|
|
30
|
+
|---------|-------------|
|
|
31
|
+
| `/playbook.run {playbook} {feature}` | Launch supervisor to orchestrate playbook steps |
|
|
32
|
+
| `/playbook.resume` | Auto-detect and resume an interrupted session |
|
|
33
|
+
|
|
34
|
+
## Built-in Playbooks
|
|
35
|
+
|
|
36
|
+
| Playbook | Steps | Description |
|
|
37
|
+
|----------|-------|-------------|
|
|
38
|
+
| `auto-feature` | 8 | plan → tasks → agreement → implement → agreement check → QA plan → QA run → PR |
|
|
39
|
+
| `auto-validate` | 2 | QA plan → QA run |
|
|
40
|
+
|
|
41
|
+
## Custom Playbooks
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
cp .playbooks/playbooks/playbook.tpl.yaml .playbooks/playbooks/my-workflow.yaml
|
|
45
|
+
# Edit the file, then validate:
|
|
46
|
+
npx @tcanaud/playbook check .playbooks/playbooks/my-workflow.yaml
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Parallel Execution
|
|
50
|
+
|
|
51
|
+
Run two features simultaneously in separate worktrees:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npx @tcanaud/playbook start auto-feature 013-another-feature
|
|
55
|
+
# Follow the printed instructions
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Session Files
|
|
59
|
+
|
|
60
|
+
After a run, session files are in `.playbooks/sessions/{id}/`:
|
|
61
|
+
|
|
62
|
+
- `session.yaml` — manifest (playbook, feature, status, timestamps)
|
|
63
|
+
- `journal.yaml` — step-by-step execution log (status, decision type, duration, human responses)
|
|
64
|
+
|
|
65
|
+
These files are git-tracked and appear in PR diffs for auditability.
|
|
66
|
+
|
|
67
|
+
## License
|
|
68
|
+
|
|
69
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { argv, exit } from "node:process";
|
|
4
|
+
|
|
5
|
+
const command = argv[2];
|
|
6
|
+
const args = argv.slice(3);
|
|
7
|
+
|
|
8
|
+
const HELP = `
|
|
9
|
+
playbook — YAML-driven orchestration for kai feature workflows.
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
npx @tcanaud/playbook init Scaffold .playbooks/ directory and install slash commands
|
|
13
|
+
npx @tcanaud/playbook update Refresh slash commands and built-in playbooks
|
|
14
|
+
npx @tcanaud/playbook start Create a worktree session for parallel execution
|
|
15
|
+
npx @tcanaud/playbook check Validate a playbook YAML file against the schema
|
|
16
|
+
npx @tcanaud/playbook help Show this help message
|
|
17
|
+
|
|
18
|
+
Commands:
|
|
19
|
+
init [--yes] Skip confirmation prompts
|
|
20
|
+
update Refresh commands without touching sessions or custom playbooks
|
|
21
|
+
start {playbook} {feature} Create git worktree + session for parallel playbook execution
|
|
22
|
+
check {file} Validate playbook YAML against schema
|
|
23
|
+
|
|
24
|
+
Claude Code commands (after init):
|
|
25
|
+
/playbook.run {playbook} {feature} Launch supervisor to orchestrate playbook steps
|
|
26
|
+
/playbook.resume Auto-detect and resume an interrupted session
|
|
27
|
+
`;
|
|
28
|
+
|
|
29
|
+
switch (command) {
|
|
30
|
+
case "init": {
|
|
31
|
+
const { install } = await import("../src/installer.js");
|
|
32
|
+
await install(args);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
case "update": {
|
|
36
|
+
const { update } = await import("../src/updater.js");
|
|
37
|
+
await update(args);
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
case "start": {
|
|
41
|
+
const { start } = await import("../src/worktree.js");
|
|
42
|
+
await start(args);
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
case "check": {
|
|
46
|
+
const { check } = await import("../src/validator.js");
|
|
47
|
+
await check(args);
|
|
48
|
+
break;
|
|
49
|
+
}
|
|
50
|
+
case "help":
|
|
51
|
+
case "--help":
|
|
52
|
+
case "-h":
|
|
53
|
+
case undefined:
|
|
54
|
+
console.log(HELP);
|
|
55
|
+
break;
|
|
56
|
+
default:
|
|
57
|
+
console.error(`Unknown command: ${command}`);
|
|
58
|
+
console.log(HELP);
|
|
59
|
+
exit(1);
|
|
60
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@tcanaud/playbook",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "YAML-driven orchestration for kai feature workflows — autonomous playbook execution with crash recovery, gates, and git-tracked audit journals.",
|
|
6
|
+
"bin": {
|
|
7
|
+
"playbook": "./bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18.0.0"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin/",
|
|
14
|
+
"src/",
|
|
15
|
+
"templates/"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"kai",
|
|
19
|
+
"playbook",
|
|
20
|
+
"orchestration",
|
|
21
|
+
"supervisor",
|
|
22
|
+
"workflow",
|
|
23
|
+
"claude-code"
|
|
24
|
+
],
|
|
25
|
+
"author": "Thibaud Canaud",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "https://github.com/tcanaud/playbook.git"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/detect.js
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { resolve, join } from "node:path";
|
|
3
|
+
import { execSync } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
// ── Directory detection ─────────────────────────────────
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Checks if `.playbooks/` exists relative to cwd.
|
|
9
|
+
* @param {string} [cwd] - Directory to check from. Defaults to process.cwd().
|
|
10
|
+
* @returns {string|null} Absolute path to `.playbooks/` if found, null otherwise.
|
|
11
|
+
*/
|
|
12
|
+
export function detectPlaybooksDir(cwd = process.cwd()) {
|
|
13
|
+
const dir = resolve(cwd, ".playbooks");
|
|
14
|
+
return existsSync(dir) ? dir : null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Checks if `.claude/commands/` exists relative to cwd.
|
|
19
|
+
* @param {string} [cwd] - Directory to check from. Defaults to process.cwd().
|
|
20
|
+
* @returns {string|null} Absolute path to `.claude/commands/` if found, null otherwise.
|
|
21
|
+
*/
|
|
22
|
+
export function detectClaudeCommands(cwd = process.cwd()) {
|
|
23
|
+
const dir = resolve(cwd, join(".claude", "commands"));
|
|
24
|
+
return existsSync(dir) ? dir : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// ── Git state ───────────────────────────────────────────
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Returns the absolute path of the git repository root.
|
|
31
|
+
* @returns {string} Trimmed output of `git rev-parse --show-toplevel`.
|
|
32
|
+
* @throws {Error} If not in a git repository or git is unavailable.
|
|
33
|
+
*/
|
|
34
|
+
export function getRepoRoot() {
|
|
35
|
+
try {
|
|
36
|
+
return execSync("git rev-parse --show-toplevel", {
|
|
37
|
+
encoding: "utf8",
|
|
38
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
39
|
+
}).trim();
|
|
40
|
+
} catch (err) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Failed to determine git repository root: ${err.message ?? String(err)}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Determines whether the current directory is a git worktree (not the main working tree).
|
|
49
|
+
*
|
|
50
|
+
* Compares `git rev-parse --show-toplevel` with `git rev-parse --git-common-dir`.
|
|
51
|
+
* In the main working tree, `--git-common-dir` resolves to `{toplevel}/.git`.
|
|
52
|
+
* In a linked worktree, it resolves to the common `.git` directory of the main tree.
|
|
53
|
+
*
|
|
54
|
+
* @returns {boolean} True if current directory is a linked worktree, false otherwise.
|
|
55
|
+
* @throws {Error} If not in a git repository or git is unavailable.
|
|
56
|
+
*/
|
|
57
|
+
export function isWorktree() {
|
|
58
|
+
try {
|
|
59
|
+
const toplevel = execSync("git rev-parse --show-toplevel", {
|
|
60
|
+
encoding: "utf8",
|
|
61
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
62
|
+
}).trim();
|
|
63
|
+
|
|
64
|
+
const gitCommonDir = execSync("git rev-parse --git-common-dir", {
|
|
65
|
+
encoding: "utf8",
|
|
66
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
67
|
+
}).trim();
|
|
68
|
+
|
|
69
|
+
// In the main working tree, git-common-dir is "{toplevel}/.git" (or ".git" relative).
|
|
70
|
+
// Resolve both to absolute paths for a reliable comparison.
|
|
71
|
+
const expectedMainGitDir = join(toplevel, ".git");
|
|
72
|
+
const resolvedCommonDir = resolve(toplevel, gitCommonDir);
|
|
73
|
+
|
|
74
|
+
return resolvedCommonDir !== expectedMainGitDir;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`Failed to determine worktree status: ${err.message ?? String(err)}`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Returns the name of the current git branch.
|
|
84
|
+
* @returns {string} Current branch name (trimmed).
|
|
85
|
+
* @throws {Error} If not in a git repository, in detached HEAD state, or git is unavailable.
|
|
86
|
+
*/
|
|
87
|
+
export function getCurrentBranch() {
|
|
88
|
+
try {
|
|
89
|
+
return execSync("git rev-parse --abbrev-ref HEAD", {
|
|
90
|
+
encoding: "utf8",
|
|
91
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
92
|
+
}).trim();
|
|
93
|
+
} catch (err) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`Failed to determine current branch: ${err.message ?? String(err)}`
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Returns true if the working tree has no uncommitted changes (clean state).
|
|
102
|
+
* Equivalent to checking that `git status --porcelain` produces no output.
|
|
103
|
+
* @returns {boolean} True if working tree is clean, false if there are uncommitted changes.
|
|
104
|
+
* @throws {Error} If not in a git repository or git is unavailable.
|
|
105
|
+
*/
|
|
106
|
+
export function isCleanWorkingTree() {
|
|
107
|
+
try {
|
|
108
|
+
const output = execSync("git status --porcelain", {
|
|
109
|
+
encoding: "utf8",
|
|
110
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
111
|
+
});
|
|
112
|
+
return output.trim() === "";
|
|
113
|
+
} catch (err) {
|
|
114
|
+
throw new Error(
|
|
115
|
+
`Failed to check working tree status: ${err.message ?? String(err)}`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|
package/src/installer.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
copyFileSync,
|
|
5
|
+
readFileSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from "node:fs";
|
|
8
|
+
import { join, dirname } from "node:path";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { createInterface } from "node:readline";
|
|
11
|
+
import { detectPlaybooksDir, detectClaudeCommands } from "./detect.js";
|
|
12
|
+
|
|
13
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const TEMPLATES = join(__dirname, "..", "templates");
|
|
15
|
+
|
|
16
|
+
// ── Helpers ──────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
function ensureDir(dir) {
|
|
19
|
+
if (!existsSync(dir)) {
|
|
20
|
+
mkdirSync(dir, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function copyTemplate(src, dest) {
|
|
25
|
+
ensureDir(dirname(dest));
|
|
26
|
+
copyFileSync(src, dest);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ask(question) {
|
|
30
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
rl.question(question, (answer) => {
|
|
33
|
+
rl.close();
|
|
34
|
+
resolve(answer.trim().toLowerCase());
|
|
35
|
+
});
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ── install ──────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
export async function install(flags = []) {
|
|
42
|
+
const projectRoot = process.cwd();
|
|
43
|
+
const autoYes = flags.includes("--yes");
|
|
44
|
+
|
|
45
|
+
console.log("\n @tcanaud/playbook v1.0.0\n");
|
|
46
|
+
|
|
47
|
+
const playbooksDir = join(projectRoot, ".playbooks");
|
|
48
|
+
const alreadyExists = existsSync(playbooksDir);
|
|
49
|
+
|
|
50
|
+
// ── Confirmation prompt ──────────────────────────────
|
|
51
|
+
if (alreadyExists && !autoYes) {
|
|
52
|
+
const answer = await ask(
|
|
53
|
+
" Playbook system already initialized. Re-install? (y/N) "
|
|
54
|
+
);
|
|
55
|
+
if (answer !== "y" && answer !== "yes") {
|
|
56
|
+
console.log(" Skipping. Use '@tcanaud/playbook update' to refresh commands only.\n");
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── Phase 1/3: Create .playbooks/ directory tree ─────
|
|
62
|
+
console.log(" [1/3] Creating .playbooks/ directory tree...");
|
|
63
|
+
|
|
64
|
+
const dirs = [
|
|
65
|
+
join(playbooksDir, "playbooks"),
|
|
66
|
+
join(playbooksDir, "sessions"),
|
|
67
|
+
join(playbooksDir, "templates"),
|
|
68
|
+
];
|
|
69
|
+
|
|
70
|
+
for (const dir of dirs) {
|
|
71
|
+
if (!existsSync(dir)) {
|
|
72
|
+
mkdirSync(dir, { recursive: true });
|
|
73
|
+
const rel = dir.replace(projectRoot + "/", "");
|
|
74
|
+
console.log(` created .${rel.startsWith(".") ? "" : "/"}${rel}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ── Phase 2/3: Copy built-in playbook files ──────────
|
|
79
|
+
console.log(" [2/3] Installing built-in playbooks...");
|
|
80
|
+
|
|
81
|
+
const playbookFiles = ["auto-feature.yaml", "auto-validate.yaml"];
|
|
82
|
+
|
|
83
|
+
for (const file of playbookFiles) {
|
|
84
|
+
const src = join(TEMPLATES, "playbooks", file);
|
|
85
|
+
const dest = join(playbooksDir, "playbooks", file);
|
|
86
|
+
if (existsSync(src)) {
|
|
87
|
+
copyTemplate(src, dest);
|
|
88
|
+
console.log(` created .playbooks/playbooks/${file}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Copy playbook template file
|
|
93
|
+
const tplSrc = join(TEMPLATES, "core", "playbook.tpl.yaml");
|
|
94
|
+
const tplDest = join(playbooksDir, "playbooks", "playbook.tpl.yaml");
|
|
95
|
+
if (existsSync(tplSrc)) {
|
|
96
|
+
copyTemplate(tplSrc, tplDest);
|
|
97
|
+
console.log(" created .playbooks/playbooks/playbook.tpl.yaml");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Generate _index.yaml from template (replace {{TIMESTAMP}})
|
|
101
|
+
const indexSrc = join(TEMPLATES, "core", "_index.yaml");
|
|
102
|
+
const indexDest = join(playbooksDir, "_index.yaml");
|
|
103
|
+
if (existsSync(indexSrc)) {
|
|
104
|
+
const timestamp = new Date().toISOString();
|
|
105
|
+
const content = readFileSync(indexSrc, "utf8").replace(
|
|
106
|
+
/\{\{TIMESTAMP\}\}/g,
|
|
107
|
+
timestamp
|
|
108
|
+
);
|
|
109
|
+
writeFileSync(indexDest, content, "utf8");
|
|
110
|
+
console.log(" created .playbooks/_index.yaml");
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Phase 3/3: Install Claude Code commands ──────────
|
|
114
|
+
console.log(" [3/3] Installing Claude Code commands...");
|
|
115
|
+
|
|
116
|
+
const claudeCommandsDir = join(projectRoot, ".claude", "commands");
|
|
117
|
+
if (!detectClaudeCommands(projectRoot)) {
|
|
118
|
+
mkdirSync(claudeCommandsDir, { recursive: true });
|
|
119
|
+
console.log(" created .claude/commands/");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const commandFiles = ["playbook.run.md", "playbook.resume.md"];
|
|
123
|
+
|
|
124
|
+
for (const file of commandFiles) {
|
|
125
|
+
const src = join(TEMPLATES, "commands", file);
|
|
126
|
+
const dest = join(claudeCommandsDir, file);
|
|
127
|
+
if (existsSync(src)) {
|
|
128
|
+
copyTemplate(src, dest);
|
|
129
|
+
console.log(` created .claude/commands/${file}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// ── Done ─────────────────────────────────────────────
|
|
134
|
+
console.log();
|
|
135
|
+
console.log(" Done! Playbook system installed.");
|
|
136
|
+
console.log();
|
|
137
|
+
console.log(" Next steps:");
|
|
138
|
+
console.log(" 1. Run /playbook.run {playbook} {feature} to start a supervised workflow");
|
|
139
|
+
console.log(" 2. Run /playbook.resume to recover an interrupted session");
|
|
140
|
+
console.log(" 3. Explore .playbooks/playbooks/ to see built-in playbooks");
|
|
141
|
+
console.log();
|
|
142
|
+
}
|