@punk6529/playbook 0.0.0 → 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.
- package/README.md +65 -2
- package/bin/playbook.js +13 -0
- package/package.json +14 -6
- package/src/cli.js +129 -0
- package/src/commands/global-init.js +31 -0
- package/src/commands/init-project.js +30 -0
- package/src/commands/update-project.js +30 -0
- package/src/constants.js +5 -0
- package/src/core/context.js +24 -0
- package/src/errors.js +11 -0
- package/src/file-ops.js +91 -0
- package/src/index.js +5 -0
- package/src/manifest.js +73 -0
- package/src/options.js +113 -0
- package/src/reporting.js +42 -0
- package/src/template-store.js +22 -0
- package/templates/global/INDEX.json +9 -0
- package/templates/global/tags.yml +34 -0
- package/templates/project/docs/playbook/INDEX.json +23 -0
- package/templates/project/docs/playbook/cases/_example-001-delete-me.md +24 -0
- package/templates/project/docs/playbook/checklists/_example-001-delete-me.md +10 -0
- package/templates/project/docs/playbook/patterns/_example-001-delete-me.md +15 -0
- package/templates/project/skills/playbook-case/SKILL.md +86 -0
- package/templates/project/skills/playbook-query/SKILL.md +50 -0
- package/index.js +0 -1
package/README.md
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
1
|
# @punk6529/playbook
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
CLI for initializing and updating Playbook workflow assets.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @punk6529/playbook
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or run without global install:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npx @punk6529/playbook --help
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Commands
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
playbook init [path] [--tools all|none|codex,claude] [--force]
|
|
21
|
+
playbook global init [--force]
|
|
22
|
+
playbook update [path] [--tools all|none|codex,claude] [--force]
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Scope Boundaries
|
|
26
|
+
|
|
27
|
+
- `playbook init` is project-scoped:
|
|
28
|
+
- creates `docs/playbook/{cases,patterns,checklists}` and `docs/playbook/INDEX.json`
|
|
29
|
+
- writes managed skill templates based on `--tools`:
|
|
30
|
+
- Codex: `.codex/skills/{playbook-query,playbook-case}/SKILL.md`
|
|
31
|
+
- Claude: `.claude/skills/{playbook-query,playbook-case}/SKILL.md`
|
|
32
|
+
- `playbook global init` is global-scoped:
|
|
33
|
+
- creates `~/.playbook/repo/{cases,patterns,checklists}`
|
|
34
|
+
- creates `~/.playbook/repo/INDEX.json` and `~/.playbook/repo/tags.yml`
|
|
35
|
+
- does not support `--tools`
|
|
36
|
+
- `playbook update` is project-scoped:
|
|
37
|
+
- updates only managed project templates
|
|
38
|
+
- does not modify user business content (for example case markdown files)
|
|
39
|
+
- V1 does not include `playbook global update`
|
|
40
|
+
|
|
41
|
+
## Skill Delivery
|
|
42
|
+
|
|
43
|
+
- This release installs namespaced skills for supported tools (`codex`, `claude`):
|
|
44
|
+
- `playbook-query`
|
|
45
|
+
- `playbook-case`
|
|
46
|
+
- CLI responsibility is asset delivery only (`init`/`update`). Runtime skill execution is handled by the host AI tool.
|
|
47
|
+
|
|
48
|
+
## Conflict Policy
|
|
49
|
+
|
|
50
|
+
- `playbook init` and `playbook global init` are non-destructive by default:
|
|
51
|
+
- conflicting managed files are skipped and reported as `conflict`
|
|
52
|
+
- `playbook update` uses split behavior:
|
|
53
|
+
- managed skill templates are refreshed by default when content differs
|
|
54
|
+
- `docs/playbook/INDEX.json` remains conflict-safe unless `--force` is provided
|
|
55
|
+
- Use `--force` to explicitly overwrite conflicting protected managed files.
|
|
56
|
+
|
|
57
|
+
## Error Handling
|
|
58
|
+
|
|
59
|
+
- Unknown command or invalid option fails with actionable guidance.
|
|
60
|
+
- Managed file read/write failures include source path and reason.
|
|
61
|
+
|
|
62
|
+
## Packaging Notes
|
|
63
|
+
|
|
64
|
+
Before publish, verify exact package contents:
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
npm pack --dry-run
|
|
68
|
+
```
|
package/bin/playbook.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { runCli } = require("../src/cli");
|
|
4
|
+
|
|
5
|
+
async function main() {
|
|
6
|
+
const exitCode = await runCli(process.argv.slice(2), process);
|
|
7
|
+
process.exitCode = exitCode;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
main().catch((error) => {
|
|
11
|
+
process.stderr.write(`Unexpected error: ${error.message}\n`);
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
});
|
package/package.json
CHANGED
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@punk6529/playbook",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for initializing and updating Playbook & Case workflows.",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"playbook": "bin/playbook.js"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
5
12
|
"scripts": {
|
|
6
|
-
"test": "
|
|
13
|
+
"test": "node --test"
|
|
7
14
|
},
|
|
8
|
-
"keywords": [],
|
|
15
|
+
"keywords": ["playbook", "knowledge-base", "cli", "ai-skills", "case-management"],
|
|
9
16
|
"author": "",
|
|
10
17
|
"license": "MIT",
|
|
11
|
-
"description": "Placeholder package. Real implementation coming soon.",
|
|
12
18
|
"publishConfig": {
|
|
13
19
|
"access": "public"
|
|
14
20
|
},
|
|
15
21
|
"files": [
|
|
16
|
-
"
|
|
22
|
+
"bin",
|
|
23
|
+
"src",
|
|
24
|
+
"templates"
|
|
17
25
|
]
|
|
18
26
|
}
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
const { CliError } = require("./errors");
|
|
2
|
+
const { parseProjectCommandArgs, parseGlobalInitArgs } = require("./options");
|
|
3
|
+
const { initProject } = require("./commands/init-project");
|
|
4
|
+
const { initGlobal } = require("./commands/global-init");
|
|
5
|
+
const { updateProject } = require("./commands/update-project");
|
|
6
|
+
const { printSummary } = require("./reporting");
|
|
7
|
+
|
|
8
|
+
function usageText() {
|
|
9
|
+
return [
|
|
10
|
+
"Usage:",
|
|
11
|
+
" playbook init [path] [--tools all|none|codex,claude] [--force]",
|
|
12
|
+
" playbook global init [--force]",
|
|
13
|
+
" playbook update [path] [--tools all|none|codex,claude] [--force]",
|
|
14
|
+
"",
|
|
15
|
+
"Notes:",
|
|
16
|
+
" - `init` and `update` are project-scoped commands.",
|
|
17
|
+
" - `global init` initializes ~/.playbook/repo and does not support --tools.",
|
|
18
|
+
" - V1 does not support `playbook global update`.",
|
|
19
|
+
].join("\n");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function writeStdout(io, line) {
|
|
23
|
+
io.stdout.write(`${line}\n`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function writeStderr(io, line) {
|
|
27
|
+
io.stderr.write(`${line}\n`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function ensureNoHelpRequest(options, io) {
|
|
31
|
+
if (options.help) {
|
|
32
|
+
writeStdout(io, usageText());
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function runInit(args, io) {
|
|
40
|
+
const options = parseProjectCommandArgs(args, "playbook init");
|
|
41
|
+
if (ensureNoHelpRequest(options, io)) {
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const result = initProject(options);
|
|
46
|
+
printSummary("playbook init completed", result.projectRoot, result.results, (line) =>
|
|
47
|
+
writeStdout(io, line)
|
|
48
|
+
);
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function runUpdate(args, io) {
|
|
53
|
+
const options = parseProjectCommandArgs(args, "playbook update");
|
|
54
|
+
if (ensureNoHelpRequest(options, io)) {
|
|
55
|
+
return 0;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const result = updateProject(options);
|
|
59
|
+
printSummary("playbook update completed", result.projectRoot, result.results, (line) =>
|
|
60
|
+
writeStdout(io, line)
|
|
61
|
+
);
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function runGlobal(args, io) {
|
|
66
|
+
const subcommand = args[0];
|
|
67
|
+
if (!subcommand) {
|
|
68
|
+
throw new CliError("Missing subcommand for `playbook global`. Supported: init");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (subcommand === "update") {
|
|
72
|
+
throw new CliError(
|
|
73
|
+
"Unsupported command: `playbook global update` (V1). Use `playbook global init` or `playbook update`."
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (subcommand !== "init") {
|
|
78
|
+
throw new CliError(`Unknown command: playbook global ${subcommand}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const options = parseGlobalInitArgs(args.slice(1));
|
|
82
|
+
if (ensureNoHelpRequest(options, io)) {
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const result = initGlobal(options);
|
|
87
|
+
printSummary("playbook global init completed", result.globalRoot, result.results, (line) =>
|
|
88
|
+
writeStdout(io, line)
|
|
89
|
+
);
|
|
90
|
+
writeStdout(io, "Next: run `playbook init` inside a project to scaffold project assets.");
|
|
91
|
+
return 0;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function runCli(argv, io = process) {
|
|
95
|
+
try {
|
|
96
|
+
if (argv.length === 0 || argv[0] === "-h" || argv[0] === "--help") {
|
|
97
|
+
writeStdout(io, usageText());
|
|
98
|
+
return 0;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const command = argv[0];
|
|
102
|
+
if (command === "init") {
|
|
103
|
+
return runInit(argv.slice(1), io);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (command === "update") {
|
|
107
|
+
return runUpdate(argv.slice(1), io);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (command === "global") {
|
|
111
|
+
return runGlobal(argv.slice(1), io);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
throw new CliError(`Unknown command: ${command}. Supported: init, global, update`);
|
|
115
|
+
} catch (error) {
|
|
116
|
+
if (error instanceof CliError) {
|
|
117
|
+
writeStderr(io, `Error: ${error.message}`);
|
|
118
|
+
return error.exitCode;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
writeStderr(io, `Unexpected error: ${error.message}`);
|
|
122
|
+
return 1;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
runCli,
|
|
128
|
+
usageText,
|
|
129
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const { resolveGlobalRoot } = require("../core/context");
|
|
3
|
+
const { GLOBAL_SKELETON_DIRS, GLOBAL_MANAGED_FILES } = require("../manifest");
|
|
4
|
+
const { readTemplate } = require("../template-store");
|
|
5
|
+
const { ensureDirectory, writeManagedFile } = require("../file-ops");
|
|
6
|
+
|
|
7
|
+
function initGlobal(options) {
|
|
8
|
+
const globalRoot = resolveGlobalRoot();
|
|
9
|
+
const results = [];
|
|
10
|
+
|
|
11
|
+
for (const relativeDirectory of GLOBAL_SKELETON_DIRS) {
|
|
12
|
+
const directoryPath = path.join(globalRoot, relativeDirectory);
|
|
13
|
+
results.push(ensureDirectory(directoryPath));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
for (const managedFile of GLOBAL_MANAGED_FILES) {
|
|
17
|
+
const targetPath = path.join(globalRoot, managedFile.relativePath);
|
|
18
|
+
const content = readTemplate(managedFile.templatePath);
|
|
19
|
+
results.push(writeManagedFile(targetPath, content, options.force));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
globalRoot,
|
|
24
|
+
results,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
initGlobal,
|
|
30
|
+
resolveGlobalRoot,
|
|
31
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const { PROJECT_SKELETON_DIRS, getProjectManagedFiles } = require("../manifest");
|
|
3
|
+
const { readTemplate } = require("../template-store");
|
|
4
|
+
const { ensureDirectory, writeManagedFile } = require("../file-ops");
|
|
5
|
+
|
|
6
|
+
function initProject(options) {
|
|
7
|
+
const projectRoot = path.resolve(options.targetPath);
|
|
8
|
+
const results = [];
|
|
9
|
+
|
|
10
|
+
for (const relativeDirectory of PROJECT_SKELETON_DIRS) {
|
|
11
|
+
const directoryPath = path.join(projectRoot, relativeDirectory);
|
|
12
|
+
results.push(ensureDirectory(directoryPath));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const managedFiles = getProjectManagedFiles(options.tools);
|
|
16
|
+
for (const managedFile of managedFiles) {
|
|
17
|
+
const targetPath = path.join(projectRoot, managedFile.relativePath);
|
|
18
|
+
const content = readTemplate(managedFile.templatePath);
|
|
19
|
+
results.push(writeManagedFile(targetPath, content, options.force));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
projectRoot,
|
|
24
|
+
results,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
initProject,
|
|
30
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
const { getProjectManagedFiles } = require("../manifest");
|
|
3
|
+
const { readTemplate } = require("../template-store");
|
|
4
|
+
const { writeManagedFile } = require("../file-ops");
|
|
5
|
+
|
|
6
|
+
function updateProject(options) {
|
|
7
|
+
const projectRoot = path.resolve(options.targetPath);
|
|
8
|
+
const results = [];
|
|
9
|
+
|
|
10
|
+
const managedFiles = getProjectManagedFiles(options.tools);
|
|
11
|
+
for (const managedFile of managedFiles) {
|
|
12
|
+
const targetPath = path.join(projectRoot, managedFile.relativePath);
|
|
13
|
+
const content = readTemplate(managedFile.templatePath);
|
|
14
|
+
const defaultOverwrite = managedFile.overwriteOnUpdate === true;
|
|
15
|
+
const effectiveForce = options.force || defaultOverwrite;
|
|
16
|
+
const overwriteReason = !options.force && defaultOverwrite
|
|
17
|
+
? "overwritten by default managed skill refresh"
|
|
18
|
+
: undefined;
|
|
19
|
+
results.push(writeManagedFile(targetPath, content, effectiveForce, overwriteReason));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return {
|
|
23
|
+
projectRoot,
|
|
24
|
+
results,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
updateProject,
|
|
30
|
+
};
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const os = require("os");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { CliError } = require("../errors");
|
|
4
|
+
|
|
5
|
+
function resolveGlobalRoot() {
|
|
6
|
+
const home = os.homedir();
|
|
7
|
+
if (!home) {
|
|
8
|
+
throw new CliError("Cannot resolve home directory for global playbook path");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return path.join(home, ".playbook", "repo");
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function resolveRuntimeContext(projectRoot = ".") {
|
|
15
|
+
return {
|
|
16
|
+
projectRoot: path.resolve(projectRoot),
|
|
17
|
+
globalRoot: resolveGlobalRoot(),
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
resolveRuntimeContext,
|
|
23
|
+
resolveGlobalRoot,
|
|
24
|
+
};
|
package/src/errors.js
ADDED
package/src/file-ops.js
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { CliError } = require("./errors");
|
|
4
|
+
|
|
5
|
+
function withFsError(action, targetPath, error) {
|
|
6
|
+
throw new CliError(`Failed to ${action} '${targetPath}': ${error.code || error.message}`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function ensureDirectory(directoryPath) {
|
|
10
|
+
const existed = fs.existsSync(directoryPath);
|
|
11
|
+
|
|
12
|
+
if (existed) {
|
|
13
|
+
return {
|
|
14
|
+
action: "skipped",
|
|
15
|
+
path: directoryPath,
|
|
16
|
+
reason: "directory already exists",
|
|
17
|
+
type: "directory",
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
fs.mkdirSync(directoryPath, { recursive: true });
|
|
23
|
+
return {
|
|
24
|
+
action: "added",
|
|
25
|
+
path: directoryPath,
|
|
26
|
+
reason: "directory created",
|
|
27
|
+
type: "directory",
|
|
28
|
+
};
|
|
29
|
+
} catch (error) {
|
|
30
|
+
withFsError("create directory", directoryPath, error);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function writeManagedFile(targetPath, content, force, overwriteReason) {
|
|
35
|
+
try {
|
|
36
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
37
|
+
} catch (error) {
|
|
38
|
+
withFsError("prepare parent directory for", targetPath, error);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let exists = false;
|
|
42
|
+
let currentContent = "";
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
exists = fs.existsSync(targetPath);
|
|
46
|
+
if (exists) {
|
|
47
|
+
currentContent = fs.readFileSync(targetPath, "utf8");
|
|
48
|
+
}
|
|
49
|
+
} catch (error) {
|
|
50
|
+
withFsError("read file", targetPath, error);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!exists) {
|
|
54
|
+
try {
|
|
55
|
+
fs.writeFileSync(targetPath, content, "utf8");
|
|
56
|
+
return { action: "added", path: targetPath, reason: "file created", type: "file" };
|
|
57
|
+
} catch (error) {
|
|
58
|
+
withFsError("write file", targetPath, error);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (currentContent === content) {
|
|
63
|
+
return { action: "skipped", path: targetPath, reason: "unchanged", type: "file" };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!force) {
|
|
67
|
+
return {
|
|
68
|
+
action: "conflict",
|
|
69
|
+
path: targetPath,
|
|
70
|
+
reason: "content differs (rerun with --force to overwrite)",
|
|
71
|
+
type: "file",
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
fs.writeFileSync(targetPath, content, "utf8");
|
|
77
|
+
return {
|
|
78
|
+
action: "updated",
|
|
79
|
+
path: targetPath,
|
|
80
|
+
reason: overwriteReason || "overwritten by --force",
|
|
81
|
+
type: "file",
|
|
82
|
+
};
|
|
83
|
+
} catch (error) {
|
|
84
|
+
withFsError("overwrite file", targetPath, error);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = {
|
|
89
|
+
ensureDirectory,
|
|
90
|
+
writeManagedFile,
|
|
91
|
+
};
|
package/src/index.js
ADDED
package/src/manifest.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
const PROJECT_SKELETON_DIRS = [
|
|
2
|
+
"docs/playbook/cases",
|
|
3
|
+
"docs/playbook/patterns",
|
|
4
|
+
"docs/playbook/checklists",
|
|
5
|
+
];
|
|
6
|
+
|
|
7
|
+
const PROJECT_MANAGED_FILES = [
|
|
8
|
+
{
|
|
9
|
+
relativePath: "docs/playbook/INDEX.json",
|
|
10
|
+
templatePath: "project/docs/playbook/INDEX.json",
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
relativePath: "docs/playbook/cases/_example-001-delete-me.md",
|
|
14
|
+
templatePath: "project/docs/playbook/cases/_example-001-delete-me.md",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
relativePath: "docs/playbook/patterns/_example-001-delete-me.md",
|
|
18
|
+
templatePath: "project/docs/playbook/patterns/_example-001-delete-me.md",
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
relativePath: "docs/playbook/checklists/_example-001-delete-me.md",
|
|
22
|
+
templatePath: "project/docs/playbook/checklists/_example-001-delete-me.md",
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
relativePath: ".codex/skills/playbook-query/SKILL.md",
|
|
26
|
+
templatePath: "project/skills/playbook-query/SKILL.md",
|
|
27
|
+
tool: "codex",
|
|
28
|
+
overwriteOnUpdate: true,
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
relativePath: ".codex/skills/playbook-case/SKILL.md",
|
|
32
|
+
templatePath: "project/skills/playbook-case/SKILL.md",
|
|
33
|
+
tool: "codex",
|
|
34
|
+
overwriteOnUpdate: true,
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
relativePath: ".claude/skills/playbook-query/SKILL.md",
|
|
38
|
+
templatePath: "project/skills/playbook-query/SKILL.md",
|
|
39
|
+
tool: "claude",
|
|
40
|
+
overwriteOnUpdate: true,
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
relativePath: ".claude/skills/playbook-case/SKILL.md",
|
|
44
|
+
templatePath: "project/skills/playbook-case/SKILL.md",
|
|
45
|
+
tool: "claude",
|
|
46
|
+
overwriteOnUpdate: true,
|
|
47
|
+
},
|
|
48
|
+
];
|
|
49
|
+
|
|
50
|
+
const GLOBAL_SKELETON_DIRS = ["cases", "patterns", "checklists"];
|
|
51
|
+
|
|
52
|
+
const GLOBAL_MANAGED_FILES = [
|
|
53
|
+
{
|
|
54
|
+
relativePath: "INDEX.json",
|
|
55
|
+
templatePath: "global/INDEX.json",
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
relativePath: "tags.yml",
|
|
59
|
+
templatePath: "global/tags.yml",
|
|
60
|
+
},
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
function getProjectManagedFiles(tools) {
|
|
64
|
+
return PROJECT_MANAGED_FILES.filter((file) => !file.tool || tools.includes(file.tool));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = {
|
|
68
|
+
PROJECT_SKELETON_DIRS,
|
|
69
|
+
PROJECT_MANAGED_FILES,
|
|
70
|
+
GLOBAL_SKELETON_DIRS,
|
|
71
|
+
GLOBAL_MANAGED_FILES,
|
|
72
|
+
getProjectManagedFiles,
|
|
73
|
+
};
|
package/src/options.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
const { CliError } = require("./errors");
|
|
2
|
+
const { SUPPORTED_TOOLS } = require("./constants");
|
|
3
|
+
|
|
4
|
+
function parseToolsValue(rawValue) {
|
|
5
|
+
if (!rawValue) {
|
|
6
|
+
throw new CliError("Missing value for --tools");
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const normalized = rawValue.trim().toLowerCase();
|
|
10
|
+
if (normalized === "all") {
|
|
11
|
+
return SUPPORTED_TOOLS.slice();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
if (normalized === "none") {
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const values = normalized
|
|
19
|
+
.split(",")
|
|
20
|
+
.map((item) => item.trim())
|
|
21
|
+
.filter(Boolean);
|
|
22
|
+
|
|
23
|
+
if (values.length === 0) {
|
|
24
|
+
throw new CliError("Invalid --tools value. Use all, none, or codex,claude");
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const invalid = values.filter((value) => !SUPPORTED_TOOLS.includes(value));
|
|
28
|
+
if (invalid.length > 0) {
|
|
29
|
+
throw new CliError(
|
|
30
|
+
`Unsupported tool(s): ${invalid.join(", ")}. Supported: ${SUPPORTED_TOOLS.join(", ")}`
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return SUPPORTED_TOOLS.filter((tool) => values.includes(tool));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseProjectCommandArgs(args, commandName) {
|
|
38
|
+
let force = false;
|
|
39
|
+
let tools = SUPPORTED_TOOLS.slice();
|
|
40
|
+
const positionals = [];
|
|
41
|
+
|
|
42
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
43
|
+
const arg = args[i];
|
|
44
|
+
|
|
45
|
+
if (arg === "--force") {
|
|
46
|
+
force = true;
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (arg === "--tools") {
|
|
51
|
+
const next = args[i + 1];
|
|
52
|
+
tools = parseToolsValue(next);
|
|
53
|
+
i += 1;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (arg.startsWith("--tools=")) {
|
|
58
|
+
const value = arg.slice("--tools=".length);
|
|
59
|
+
tools = parseToolsValue(value);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (arg === "-h" || arg === "--help") {
|
|
64
|
+
return { help: true };
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (arg.startsWith("-")) {
|
|
68
|
+
throw new CliError(`Unknown option for ${commandName}: ${arg}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
positionals.push(arg);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (positionals.length > 1) {
|
|
75
|
+
throw new CliError(`Too many path arguments for ${commandName}`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
help: false,
|
|
80
|
+
force,
|
|
81
|
+
tools,
|
|
82
|
+
targetPath: positionals[0] || ".",
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function parseGlobalInitArgs(args) {
|
|
87
|
+
let force = false;
|
|
88
|
+
|
|
89
|
+
for (const arg of args) {
|
|
90
|
+
if (arg === "--force") {
|
|
91
|
+
force = true;
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (arg === "-h" || arg === "--help") {
|
|
96
|
+
return { help: true };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (arg === "--tools" || arg.startsWith("--tools=")) {
|
|
100
|
+
throw new CliError("`playbook global init` does not support --tools");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
throw new CliError(`Unknown option for playbook global init: ${arg}`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return { help: false, force };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = {
|
|
110
|
+
parseProjectCommandArgs,
|
|
111
|
+
parseGlobalInitArgs,
|
|
112
|
+
parseToolsValue,
|
|
113
|
+
};
|
package/src/reporting.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
|
|
3
|
+
function formatPath(basePath, absolutePath) {
|
|
4
|
+
const relative = path.relative(basePath, absolutePath);
|
|
5
|
+
return relative || ".";
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function countByAction(results) {
|
|
9
|
+
const counters = {
|
|
10
|
+
added: 0,
|
|
11
|
+
updated: 0,
|
|
12
|
+
skipped: 0,
|
|
13
|
+
conflict: 0,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
for (const result of results) {
|
|
17
|
+
if (Object.prototype.hasOwnProperty.call(counters, result.action)) {
|
|
18
|
+
counters[result.action] += 1;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return counters;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function printSummary(title, basePath, results, writeLine) {
|
|
26
|
+
writeLine(`\n${title}`);
|
|
27
|
+
|
|
28
|
+
for (const result of results) {
|
|
29
|
+
const displayPath = formatPath(basePath, result.path);
|
|
30
|
+
const reasonSuffix = result.reason ? ` (${result.reason})` : "";
|
|
31
|
+
writeLine(`- [${result.action}] ${displayPath}${reasonSuffix}`);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const counts = countByAction(results);
|
|
35
|
+
writeLine(
|
|
36
|
+
`Summary: added=${counts.added}, updated=${counts.updated}, skipped=${counts.skipped}, conflict=${counts.conflict}`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = {
|
|
41
|
+
printSummary,
|
|
42
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const { CliError } = require("./errors");
|
|
4
|
+
|
|
5
|
+
const TEMPLATE_ROOT = path.resolve(__dirname, "..", "templates");
|
|
6
|
+
|
|
7
|
+
function readTemplate(templatePath) {
|
|
8
|
+
const absolutePath = path.join(TEMPLATE_ROOT, templatePath);
|
|
9
|
+
|
|
10
|
+
try {
|
|
11
|
+
return fs.readFileSync(absolutePath, "utf8");
|
|
12
|
+
} catch (error) {
|
|
13
|
+
throw new CliError(
|
|
14
|
+
`Failed to read template '${templatePath}': ${error.code || error.message}`
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
module.exports = {
|
|
20
|
+
TEMPLATE_ROOT,
|
|
21
|
+
readTemplate,
|
|
22
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
tags:
|
|
2
|
+
- id: auth
|
|
3
|
+
desc: Authentication and authorization issues.
|
|
4
|
+
aliases: [login, oauth, permission]
|
|
5
|
+
- id: web
|
|
6
|
+
desc: Frontend and browser behavior.
|
|
7
|
+
aliases: [frontend, browser, ui]
|
|
8
|
+
- id: db
|
|
9
|
+
desc: Database schema, query, and migration issues.
|
|
10
|
+
aliases: [database, sql, migration]
|
|
11
|
+
- id: env
|
|
12
|
+
desc: Environment variables and local setup problems.
|
|
13
|
+
aliases: [environment, dotenv, config]
|
|
14
|
+
- id: proxy
|
|
15
|
+
desc: Networking, proxy, and request forwarding issues.
|
|
16
|
+
aliases: [gateway, reverse-proxy, tunnel]
|
|
17
|
+
- id: ci
|
|
18
|
+
desc: Continuous integration and pipeline failures.
|
|
19
|
+
aliases: [pipeline, github-actions, build]
|
|
20
|
+
- id: ios
|
|
21
|
+
desc: iOS build, signing, and simulator issues.
|
|
22
|
+
aliases: [xcode, swift, mobile]
|
|
23
|
+
- id: testing
|
|
24
|
+
desc: Test failures, flaky tests, and test infra.
|
|
25
|
+
aliases: [test, qa, flaky]
|
|
26
|
+
- id: workflow
|
|
27
|
+
desc: Engineering process and development workflow issues.
|
|
28
|
+
aliases: [process, collaboration, handoff]
|
|
29
|
+
- id: api
|
|
30
|
+
desc: API contract, integration, and compatibility issues.
|
|
31
|
+
aliases: [endpoint, contract, integration]
|
|
32
|
+
- id: cli
|
|
33
|
+
desc: Command-line interface behavior and ergonomics.
|
|
34
|
+
aliases: [command, terminal, shell]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"case-001-example-node-version-mismatch": {
|
|
3
|
+
"type": "case",
|
|
4
|
+
"title": "Deployment failed due to Node.js version mismatch",
|
|
5
|
+
"tags": ["env", "ci"],
|
|
6
|
+
"path": "cases/_example-001-delete-me.md",
|
|
7
|
+
"created": "2026-01-01"
|
|
8
|
+
},
|
|
9
|
+
"pattern-001-example-pin-runtime-versions": {
|
|
10
|
+
"type": "pattern",
|
|
11
|
+
"title": "Pin runtime versions before upgrading dependencies",
|
|
12
|
+
"tags": ["env", "workflow"],
|
|
13
|
+
"path": "patterns/_example-001-delete-me.md",
|
|
14
|
+
"created": "2026-01-01"
|
|
15
|
+
},
|
|
16
|
+
"checklist-001-example-pre-publish": {
|
|
17
|
+
"type": "checklist",
|
|
18
|
+
"title": "Pre-publish checklist",
|
|
19
|
+
"tags": ["workflow", "ci"],
|
|
20
|
+
"path": "checklists/_example-001-delete-me.md",
|
|
21
|
+
"created": "2026-01-01"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Deployment failed due to Node.js version mismatch
|
|
2
|
+
|
|
3
|
+
> Delete this example file and create real cases using the playbook-case skill.
|
|
4
|
+
|
|
5
|
+
## Problem
|
|
6
|
+
|
|
7
|
+
Production deployment failed silently. The build succeeded locally but crashed on startup in CI with `SyntaxError: Unexpected token ??=`.
|
|
8
|
+
|
|
9
|
+
## Context
|
|
10
|
+
|
|
11
|
+
- Local dev environment: Node 20
|
|
12
|
+
- CI runner: Node 16 (inherited from base image, not pinned)
|
|
13
|
+
- The `??=` operator (logical nullish assignment) requires Node 15+
|
|
14
|
+
- No `engines` field in package.json to catch this earlier
|
|
15
|
+
|
|
16
|
+
## Solution
|
|
17
|
+
|
|
18
|
+
1. Added `"engines": { "node": ">=20" }` to package.json
|
|
19
|
+
2. Updated CI base image to `node:20-slim`
|
|
20
|
+
3. Added `engine-strict=true` to `.npmrc` so mismatches fail fast on `npm install`
|
|
21
|
+
|
|
22
|
+
## Takeaway
|
|
23
|
+
|
|
24
|
+
Always pin the Node.js version in `engines` and enforce it. Silent version mismatches cause the worst kind of debugging — everything works locally.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Pre-publish checklist
|
|
2
|
+
|
|
3
|
+
> Delete this example file and create real checklists using the playbook-case skill.
|
|
4
|
+
|
|
5
|
+
- [ ] Run `npm pack --dry-run` and verify included files
|
|
6
|
+
- [ ] Check `files` field in package.json matches intended contents
|
|
7
|
+
- [ ] Verify version number is correct and not already published
|
|
8
|
+
- [ ] Run full test suite: `npm test`
|
|
9
|
+
- [ ] Check for accidentally included secrets or credentials
|
|
10
|
+
- [ ] Confirm README is up to date with current API/usage
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Pin runtime versions before upgrading dependencies
|
|
2
|
+
|
|
3
|
+
> Delete this example file and create real patterns using the playbook-case skill.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
Apply this pattern whenever you upgrade a major runtime (Node.js, Python, Ruby) or a core dependency that has runtime version requirements.
|
|
8
|
+
|
|
9
|
+
## Pattern
|
|
10
|
+
|
|
11
|
+
1. **Pin the current version first** — add `engines` field (Node), `python_requires` (Python), or equivalent before making any changes
|
|
12
|
+
2. **Upgrade in CI first** — update the CI base image/config before changing local dev setup
|
|
13
|
+
3. **Run full test suite against new version** — don't rely on local smoke tests
|
|
14
|
+
4. **Update lockfile** — regenerate `package-lock.json` / `yarn.lock` under the new runtime
|
|
15
|
+
5. **Communicate** — update README and onboarding docs with new version requirement
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: playbook-case
|
|
3
|
+
description: Draft and inspect playbook case entries with explicit scope and tags.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Use this skill when the user asks to create, review, or inspect case entries.
|
|
8
|
+
|
|
9
|
+
## Behavior
|
|
10
|
+
|
|
11
|
+
- Keep scope explicit (`project` or `global`).
|
|
12
|
+
- Keep draft generation non-destructive by default.
|
|
13
|
+
- For new cases, enforce this guided flow:
|
|
14
|
+
1. If the user has not provided a concrete problem statement, ask first:
|
|
15
|
+
"你想把之前遇到的哪个问题沉淀为 case?请一句话描述。"
|
|
16
|
+
2. After problem statement is available, suggest tags from registry/index context first:
|
|
17
|
+
- Prefer project index: `docs/playbook/INDEX.json`
|
|
18
|
+
- Use global index only when relevant: `~/.playbook/repo/INDEX.json`
|
|
19
|
+
- Ask user to choose existing tags or explicitly confirm a new tag
|
|
20
|
+
3. If scope is not confirmed, ask for scope confirmation:
|
|
21
|
+
- default is `project`
|
|
22
|
+
- use `global` only when user explicitly confirms
|
|
23
|
+
4. Only after required inputs are complete, produce the draft package.
|
|
24
|
+
- Required inputs before any draft output:
|
|
25
|
+
- problem statement
|
|
26
|
+
- tag decision (existing tags or confirmed new tag)
|
|
27
|
+
- scope confirmation
|
|
28
|
+
- Hard gate: while any required input is missing, do not output case ID, suggested path, markdown draft, or INDEX update suggestion.
|
|
29
|
+
- For new cases, once inputs are complete, produce a complete draft package:
|
|
30
|
+
- case ID (format: `{type}-{NNN}-{slug}`, e.g. `case-001-oauth-token-race`)
|
|
31
|
+
- suggested path (e.g. `cases/case-001-oauth-token-race.md`)
|
|
32
|
+
- markdown draft
|
|
33
|
+
- INDEX.json update: the exact JSON entry to add
|
|
34
|
+
|
|
35
|
+
## INDEX.json Schema
|
|
36
|
+
|
|
37
|
+
INDEX.json is a flat object keyed by entry ID. Each entry has this structure:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"case-001-oauth-token-race": {
|
|
42
|
+
"type": "case",
|
|
43
|
+
"title": "OAuth token refresh race condition under concurrency",
|
|
44
|
+
"tags": ["auth", "api"],
|
|
45
|
+
"path": "cases/case-001-oauth-token-race.md",
|
|
46
|
+
"created": "2026-02-10"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Fields
|
|
52
|
+
|
|
53
|
+
| Field | Type | Description |
|
|
54
|
+
|-------|------|-------------|
|
|
55
|
+
| `type` | `"case"` \| `"pattern"` \| `"checklist"` | Entry classification |
|
|
56
|
+
| `title` | string | Human-readable one-line summary |
|
|
57
|
+
| `tags` | string[] | Tag IDs from registry (`tags.yml` or INDEX context) |
|
|
58
|
+
| `path` | string | Relative path to the content file |
|
|
59
|
+
| `created` | string | Creation date in YYYY-MM-DD format |
|
|
60
|
+
|
|
61
|
+
### Entry ID Convention
|
|
62
|
+
|
|
63
|
+
Format: `{type}-{NNN}-{slug}`
|
|
64
|
+
- `type`: one of `case`, `pattern`, `checklist`
|
|
65
|
+
- `NNN`: zero-padded 3-digit sequence number (check existing entries to determine next number)
|
|
66
|
+
- `slug`: lowercase hyphenated descriptor derived from the title
|
|
67
|
+
|
|
68
|
+
### Draft Package Output
|
|
69
|
+
|
|
70
|
+
When producing the draft package, output the INDEX entry as a ready-to-merge JSON snippet:
|
|
71
|
+
|
|
72
|
+
```
|
|
73
|
+
**Entry ID:** case-001-oauth-token-race
|
|
74
|
+
**Path:** cases/case-001-oauth-token-race.md
|
|
75
|
+
**INDEX update:**
|
|
76
|
+
Add this entry to INDEX.json:
|
|
77
|
+
{
|
|
78
|
+
"case-001-oauth-token-race": {
|
|
79
|
+
"type": "case",
|
|
80
|
+
"title": "...",
|
|
81
|
+
"tags": ["..."],
|
|
82
|
+
"path": "cases/case-001-oauth-token-race.md",
|
|
83
|
+
"created": "YYYY-MM-DD"
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
```
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: playbook-query
|
|
3
|
+
description: Query project and global playbook knowledge in a concise, index-first way.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Use this skill when the user asks to search, filter, or inspect playbook knowledge.
|
|
8
|
+
|
|
9
|
+
## Behavior
|
|
10
|
+
|
|
11
|
+
### Lookup Sequence
|
|
12
|
+
|
|
13
|
+
**IMPORTANT: Only read the specific files listed below by their exact path. Do NOT use broad file search, glob, or grep across the filesystem — this can trigger macOS privacy prompts.**
|
|
14
|
+
|
|
15
|
+
1. **Project first**: Read `docs/playbook/INDEX.json` (relative to project root)
|
|
16
|
+
2. **Global fallback**: If no matches found, or user asks for broader results, read `~/.playbook/repo/INDEX.json` (this exact path only)
|
|
17
|
+
3. Merge results if both scopes have relevant entries. Label scope in output.
|
|
18
|
+
|
|
19
|
+
### Matching
|
|
20
|
+
|
|
21
|
+
- Parse INDEX.json: each key is an entry ID, each value has `type`, `title`, `tags`, `path`, `created`
|
|
22
|
+
- Match user query against:
|
|
23
|
+
- `tags` array (primary match — compare with tag IDs and aliases from `tags.yml`)
|
|
24
|
+
- `title` text (secondary match — keyword overlap)
|
|
25
|
+
- `type` filter (if user specifies "show me patterns" or "any checklists for...")
|
|
26
|
+
- If no matches found in either scope, say so clearly
|
|
27
|
+
|
|
28
|
+
### Progressive Disclosure
|
|
29
|
+
|
|
30
|
+
Return results in order of increasing detail and token cost:
|
|
31
|
+
|
|
32
|
+
1. **Checklists first** — actionable, cheapest to show. Display inline if short.
|
|
33
|
+
2. **Patterns next** — show title + tags summary. Offer to expand.
|
|
34
|
+
3. **Cases last** — show title + tags summary only. Expand full content only when user explicitly asks.
|
|
35
|
+
|
|
36
|
+
For each match, show:
|
|
37
|
+
```
|
|
38
|
+
[{type}] {title} (tags: {tags}) [{scope}]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Expanding Entries
|
|
42
|
+
|
|
43
|
+
- When user asks to see details, read the content file at the entry's `path` (relative to `docs/playbook/` for project, `~/.playbook/repo/` for global)
|
|
44
|
+
- Show the full markdown content of the referenced file
|
|
45
|
+
|
|
46
|
+
### Edge Cases
|
|
47
|
+
|
|
48
|
+
- If INDEX.json is empty (`{}`), tell the user: "No entries yet. Use playbook-case to create your first entry."
|
|
49
|
+
- If a referenced file at `path` doesn't exist, report it as a broken reference
|
|
50
|
+
- If user query is vague, show all entries grouped by type
|
package/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
module.exports = {};
|