@punk6529/playbook 0.2.3 → 0.2.5
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 -0
- package/package.json +1 -1
- package/src/cli.js +32 -2
- package/src/commands/show.js +57 -0
- package/src/manifest.js +0 -12
- package/src/options.js +20 -0
- package/templates/global/INDEX.json +1 -9
- package/templates/project/docs/playbook/INDEX.json +1 -23
- package/templates/project/skills/playbook-advisor/SKILL.md +5 -4
- package/templates/project/skills/playbook-query/SKILL.md +5 -4
- package/templates/project/docs/playbook/cases/_example-001-delete-me.md +0 -24
- package/templates/project/docs/playbook/checklists/_example-001-delete-me.md +0 -10
- package/templates/project/docs/playbook/patterns/_example-001-delete-me.md +0 -15
package/README.md
CHANGED
|
@@ -166,6 +166,16 @@ playbook query docker --json # JSON 格式输出
|
|
|
166
166
|
|
|
167
167
|
这个命令是 AI skill 的底层工具。`/playbook-advisor` 和 `/playbook-query` skill 在内部调用它来搜索知识库,节省 token 开销。
|
|
168
168
|
|
|
169
|
+
### `playbook show`
|
|
170
|
+
|
|
171
|
+
输出指定案例的完整内容。AI skill 用它查看案例详情(L2),无需知道文件路径。
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
playbook show case-001-docker-bind # 输出案例文件内容
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
先查项目 INDEX,再查全局 INDEX,找到后输出对应文件内容到 stdout。
|
|
178
|
+
|
|
169
179
|
### `playbook hit`
|
|
170
180
|
|
|
171
181
|
记录一次案例阅读命中,用于追踪案例的实际使用频率。
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
const { CliError } = require("./errors");
|
|
2
|
-
const { parseProjectCommandArgs, parseGlobalInitArgs, parseQueryArgs, parsePromoteArgs, parseHitArgs } = require("./options");
|
|
2
|
+
const { parseProjectCommandArgs, parseGlobalInitArgs, parseQueryArgs, parsePromoteArgs, parseHitArgs, parseShowArgs } = require("./options");
|
|
3
3
|
const { initProject } = require("./commands/init-project");
|
|
4
4
|
const { initGlobal } = require("./commands/global-init");
|
|
5
5
|
const { updateProject } = require("./commands/update-project");
|
|
6
6
|
const { queryIndex, aggregateTags, formatPlainText, formatJson, formatTagsOnly } = require("./commands/query");
|
|
7
7
|
const { promoteEntries } = require("./commands/promote");
|
|
8
8
|
const { hitEntry } = require("./commands/hit");
|
|
9
|
+
const { showEntry } = require("./commands/show");
|
|
9
10
|
const { printSummary } = require("./reporting");
|
|
10
11
|
|
|
11
12
|
function usageText() {
|
|
@@ -18,6 +19,7 @@ function usageText() {
|
|
|
18
19
|
" playbook promote <entry-id...> [--force]",
|
|
19
20
|
" playbook promote --all [--force]",
|
|
20
21
|
" playbook hit <entry-id>",
|
|
22
|
+
" playbook show <entry-id>",
|
|
21
23
|
"",
|
|
22
24
|
"Notes:",
|
|
23
25
|
" - `init` and `update` are project-scoped commands.",
|
|
@@ -178,6 +180,30 @@ function runHit(args, io) {
|
|
|
178
180
|
return 0;
|
|
179
181
|
}
|
|
180
182
|
|
|
183
|
+
function showHelpText() {
|
|
184
|
+
return [
|
|
185
|
+
"Usage: playbook show <entry-id>",
|
|
186
|
+
"",
|
|
187
|
+
"Display the full content of a playbook entry.",
|
|
188
|
+
"Searches project INDEX first, then global INDEX.",
|
|
189
|
+
"",
|
|
190
|
+
"Options:",
|
|
191
|
+
" -h, --help Show this help",
|
|
192
|
+
].join("\n");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function runShow(args, io) {
|
|
196
|
+
const options = parseShowArgs(args);
|
|
197
|
+
if (options.help) {
|
|
198
|
+
writeStdout(io, showHelpText());
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
const result = showEntry(options);
|
|
203
|
+
writeStdout(io, result.content);
|
|
204
|
+
return 0;
|
|
205
|
+
}
|
|
206
|
+
|
|
181
207
|
function runGlobal(args, io) {
|
|
182
208
|
const subcommand = args[0];
|
|
183
209
|
if (!subcommand) {
|
|
@@ -235,11 +261,15 @@ async function runCli(argv, io = process) {
|
|
|
235
261
|
return runHit(argv.slice(1), io);
|
|
236
262
|
}
|
|
237
263
|
|
|
264
|
+
if (command === "show") {
|
|
265
|
+
return runShow(argv.slice(1), io);
|
|
266
|
+
}
|
|
267
|
+
|
|
238
268
|
if (command === "global") {
|
|
239
269
|
return runGlobal(argv.slice(1), io);
|
|
240
270
|
}
|
|
241
271
|
|
|
242
|
-
throw new CliError(`Unknown command: ${command}. Supported: init, global, update, query, promote, hit`);
|
|
272
|
+
throw new CliError(`Unknown command: ${command}. Supported: init, global, update, query, promote, hit, show`);
|
|
243
273
|
} catch (error) {
|
|
244
274
|
if (error instanceof CliError) {
|
|
245
275
|
writeStderr(io, `Error: ${error.message}`);
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const { CliError } = require("../errors");
|
|
5
|
+
|
|
6
|
+
function readIndex(indexPath) {
|
|
7
|
+
try {
|
|
8
|
+
const content = fs.readFileSync(indexPath, "utf8");
|
|
9
|
+
return JSON.parse(content);
|
|
10
|
+
} catch {
|
|
11
|
+
return null;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function showEntry(options) {
|
|
16
|
+
const { entryId, targetPath = "." } = options;
|
|
17
|
+
|
|
18
|
+
const projectIndexPath = path.resolve(targetPath, "docs/playbook/INDEX.json");
|
|
19
|
+
const globalIndexPath = path.join(os.homedir(), ".playbook/repo/INDEX.json");
|
|
20
|
+
|
|
21
|
+
// Try project first
|
|
22
|
+
const projectIndex = readIndex(projectIndexPath);
|
|
23
|
+
if (projectIndex && projectIndex[entryId]) {
|
|
24
|
+
const entryPath = projectIndex[entryId].path;
|
|
25
|
+
const fullPath = path.resolve(targetPath, "docs/playbook", entryPath);
|
|
26
|
+
const content = readFile(fullPath, entryId);
|
|
27
|
+
return { entryId, scope: "project", content };
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Fall back to global
|
|
31
|
+
const globalIndex = readIndex(globalIndexPath);
|
|
32
|
+
if (globalIndex && globalIndex[entryId]) {
|
|
33
|
+
const entryPath = globalIndex[entryId].path;
|
|
34
|
+
const fullPath = path.join(os.homedir(), ".playbook/repo", entryPath);
|
|
35
|
+
const content = readFile(fullPath, entryId);
|
|
36
|
+
return { entryId, scope: "global", content };
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Not found
|
|
40
|
+
if (!projectIndex && !globalIndex) {
|
|
41
|
+
throw new CliError("No INDEX.json found. Run 'playbook init' or 'playbook global init' first.");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
throw new CliError(`Entry '${entryId}' not found in project or global INDEX.`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function readFile(fullPath, entryId) {
|
|
48
|
+
try {
|
|
49
|
+
return fs.readFileSync(fullPath, "utf8");
|
|
50
|
+
} catch {
|
|
51
|
+
throw new CliError(`File not found for entry '${entryId}': ${fullPath}`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = {
|
|
56
|
+
showEntry,
|
|
57
|
+
};
|
package/src/manifest.js
CHANGED
|
@@ -9,18 +9,6 @@ const PROJECT_MANAGED_FILES = [
|
|
|
9
9
|
relativePath: "docs/playbook/INDEX.json",
|
|
10
10
|
templatePath: "project/docs/playbook/INDEX.json",
|
|
11
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
12
|
{
|
|
25
13
|
relativePath: ".codex/skills/playbook-query/SKILL.md",
|
|
26
14
|
templatePath: "project/skills/playbook-query/SKILL.md",
|
package/src/options.js
CHANGED
|
@@ -240,11 +240,31 @@ function parseHitArgs(args) {
|
|
|
240
240
|
return { help: false, entryId };
|
|
241
241
|
}
|
|
242
242
|
|
|
243
|
+
function parseShowArgs(args) {
|
|
244
|
+
for (const arg of args) {
|
|
245
|
+
if (arg === "-h" || arg === "--help") {
|
|
246
|
+
return { help: true };
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const entryId = args[0];
|
|
251
|
+
if (!entryId || entryId.startsWith("-")) {
|
|
252
|
+
throw new CliError("playbook show requires an entry ID");
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
if (args.length > 1) {
|
|
256
|
+
throw new CliError("playbook show accepts exactly one entry ID");
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return { help: false, entryId };
|
|
260
|
+
}
|
|
261
|
+
|
|
243
262
|
module.exports = {
|
|
244
263
|
parseProjectCommandArgs,
|
|
245
264
|
parseGlobalInitArgs,
|
|
246
265
|
parseQueryArgs,
|
|
247
266
|
parsePromoteArgs,
|
|
248
267
|
parseHitArgs,
|
|
268
|
+
parseShowArgs,
|
|
249
269
|
parseToolsValue,
|
|
250
270
|
};
|
|
@@ -1,23 +1 @@
|
|
|
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
|
-
}
|
|
1
|
+
{}
|
|
@@ -43,11 +43,12 @@ Review the titles in the output. Only proceed to Step 2 for entries whose titles
|
|
|
43
43
|
|
|
44
44
|
### Step 2 — Read specific cases (L2)
|
|
45
45
|
|
|
46
|
-
For relevant entries only:
|
|
47
|
-
|
|
48
|
-
|
|
46
|
+
For relevant entries only, run:
|
|
47
|
+
```bash
|
|
48
|
+
playbook show <entry-id>
|
|
49
|
+
```
|
|
49
50
|
|
|
50
|
-
Do NOT read all matched entries. Only
|
|
51
|
+
This outputs the full content of the case file. Do NOT read all matched entries. Only show the ones that look relevant based on title.
|
|
51
52
|
|
|
52
53
|
When multiple entries have similar titles, prefer entries with higher `hits` count — they have been used more often and are likely more effective.
|
|
53
54
|
|
|
@@ -30,14 +30,15 @@ Output format (one line per match):
|
|
|
30
30
|
(N matches: X project, Y global)
|
|
31
31
|
```
|
|
32
32
|
|
|
33
|
-
### Expansion (L2):
|
|
33
|
+
### Expansion (L2): Show entry content
|
|
34
34
|
|
|
35
35
|
When the user asks to see details of a specific entry, or when you need full context for a task:
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
```bash
|
|
38
|
+
playbook show <entry-id>
|
|
39
|
+
```
|
|
39
40
|
|
|
40
|
-
Only expand entries that are actually needed. Do not
|
|
41
|
+
This outputs the full content of the entry file. Only expand entries that are actually needed. Do not show all matched entries at once.
|
|
41
42
|
|
|
42
43
|
### Edge Cases
|
|
43
44
|
|
|
@@ -1,24 +0,0 @@
|
|
|
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.
|
|
@@ -1,10 +0,0 @@
|
|
|
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
|
|
@@ -1,15 +0,0 @@
|
|
|
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
|