@punk6529/playbook 0.2.2 → 0.2.4
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 +24 -1
- package/package.json +1 -1
- package/src/cli.js +32 -2
- package/src/commands/hit.js +51 -0
- package/src/commands/query.js +2 -1
- 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 +11 -0
- 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
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
- `/playbook-advisor`:会话内主动顾问,自动按需查询知识库
|
|
25
25
|
- `/playbook-query`:手动按标签检索经验
|
|
26
26
|
- `/playbook-case`:引导式记录新 case,并更新索引
|
|
27
|
-
- 一套稳定的 CLI 工作流(init / global init / update / query),便于持续演进而不破坏现有内容
|
|
27
|
+
- 一套稳定的 CLI 工作流(init / global init / update / query / promote / hit),便于持续演进而不破坏现有内容
|
|
28
28
|
|
|
29
29
|
## 典型工作流
|
|
30
30
|
|
|
@@ -134,6 +134,19 @@ AI:(引导你填写 问题/原因/解决/教训 四个部分)
|
|
|
134
134
|
AI:(生成 case 文件,更新 INDEX.json)
|
|
135
135
|
```
|
|
136
136
|
|
|
137
|
+
### `playbook promote`
|
|
138
|
+
|
|
139
|
+
将项目级案例提升到全局知识库,让经验跨项目复用。
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
playbook promote case-001-docker-bind # 提升单个条目
|
|
143
|
+
playbook promote case-001 case-002 # 提升多个条目
|
|
144
|
+
playbook promote --all # 提升所有项目条目
|
|
145
|
+
playbook promote case-001 --force # 强制覆盖已存在的全局条目
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
将项目 `docs/playbook/` 下的条目移动到全局 `~/.playbook/repo/`,同时更新两端的 INDEX.json。
|
|
149
|
+
|
|
137
150
|
## 内部命令
|
|
138
151
|
|
|
139
152
|
以下命令主要由 AI skill 内部调用,用户一般不需要直接使用:
|
|
@@ -153,6 +166,16 @@ playbook query docker --json # JSON 格式输出
|
|
|
153
166
|
|
|
154
167
|
这个命令是 AI skill 的底层工具。`/playbook-advisor` 和 `/playbook-query` skill 在内部调用它来搜索知识库,节省 token 开销。
|
|
155
168
|
|
|
169
|
+
### `playbook hit`
|
|
170
|
+
|
|
171
|
+
记录一次案例阅读命中,用于追踪案例的实际使用频率。
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
playbook hit case-001-docker-bind # 记录一次命中
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
AI advisor 每次阅读案例详情后自动调用。命中次数在 `playbook query` 输出中以 `hits:N` 显示,帮助识别高价值案例。
|
|
178
|
+
|
|
156
179
|
## 错误处理
|
|
157
180
|
|
|
158
181
|
- 未知命令或无效选项会给出可操作的提示信息
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
const { CliError } = require("./errors");
|
|
2
|
-
const { parseProjectCommandArgs, parseGlobalInitArgs, parseQueryArgs, parsePromoteArgs } = require("./options");
|
|
2
|
+
const { parseProjectCommandArgs, parseGlobalInitArgs, parseQueryArgs, parsePromoteArgs, parseHitArgs } = 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
|
+
const { hitEntry } = require("./commands/hit");
|
|
8
9
|
const { printSummary } = require("./reporting");
|
|
9
10
|
|
|
10
11
|
function usageText() {
|
|
@@ -16,6 +17,7 @@ function usageText() {
|
|
|
16
17
|
" playbook query [tags...] [--limit N] [--scope project|global|both] [--json] [--tags-only]",
|
|
17
18
|
" playbook promote <entry-id...> [--force]",
|
|
18
19
|
" playbook promote --all [--force]",
|
|
20
|
+
" playbook hit <entry-id>",
|
|
19
21
|
"",
|
|
20
22
|
"Notes:",
|
|
21
23
|
" - `init` and `update` are project-scoped commands.",
|
|
@@ -152,6 +154,30 @@ function runPromote(args, io) {
|
|
|
152
154
|
return hasError ? 1 : 0;
|
|
153
155
|
}
|
|
154
156
|
|
|
157
|
+
function hitHelpText() {
|
|
158
|
+
return [
|
|
159
|
+
"Usage: playbook hit <entry-id>",
|
|
160
|
+
"",
|
|
161
|
+
"Record a read hit on a playbook entry.",
|
|
162
|
+
"Searches project INDEX first, then global INDEX.",
|
|
163
|
+
"",
|
|
164
|
+
"Options:",
|
|
165
|
+
" -h, --help Show this help",
|
|
166
|
+
].join("\n");
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function runHit(args, io) {
|
|
170
|
+
const options = parseHitArgs(args);
|
|
171
|
+
if (options.help) {
|
|
172
|
+
writeStdout(io, hitHelpText());
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const result = hitEntry(options);
|
|
177
|
+
writeStdout(io, `[${result.scope}] ${result.entryId} hits:${result.hits}`);
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
|
|
155
181
|
function runGlobal(args, io) {
|
|
156
182
|
const subcommand = args[0];
|
|
157
183
|
if (!subcommand) {
|
|
@@ -205,11 +231,15 @@ async function runCli(argv, io = process) {
|
|
|
205
231
|
return runPromote(argv.slice(1), io);
|
|
206
232
|
}
|
|
207
233
|
|
|
234
|
+
if (command === "hit") {
|
|
235
|
+
return runHit(argv.slice(1), io);
|
|
236
|
+
}
|
|
237
|
+
|
|
208
238
|
if (command === "global") {
|
|
209
239
|
return runGlobal(argv.slice(1), io);
|
|
210
240
|
}
|
|
211
241
|
|
|
212
|
-
throw new CliError(`Unknown command: ${command}. Supported: init, global, update, query, promote`);
|
|
242
|
+
throw new CliError(`Unknown command: ${command}. Supported: init, global, update, query, promote, hit`);
|
|
213
243
|
} catch (error) {
|
|
214
244
|
if (error instanceof CliError) {
|
|
215
245
|
writeStderr(io, `Error: ${error.message}`);
|
|
@@ -0,0 +1,51 @@
|
|
|
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 writeIndex(indexPath, data) {
|
|
16
|
+
fs.writeFileSync(indexPath, JSON.stringify(data, null, 2) + "\n", "utf8");
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function hitEntry(options) {
|
|
20
|
+
const { entryId, targetPath = "." } = options;
|
|
21
|
+
|
|
22
|
+
const projectIndexPath = path.resolve(targetPath, "docs/playbook/INDEX.json");
|
|
23
|
+
const globalIndexPath = path.join(os.homedir(), ".playbook/repo/INDEX.json");
|
|
24
|
+
|
|
25
|
+
// Try project first
|
|
26
|
+
const projectIndex = readIndex(projectIndexPath);
|
|
27
|
+
if (projectIndex && projectIndex[entryId]) {
|
|
28
|
+
projectIndex[entryId].hits = (projectIndex[entryId].hits || 0) + 1;
|
|
29
|
+
writeIndex(projectIndexPath, projectIndex);
|
|
30
|
+
return { entryId, scope: "project", hits: projectIndex[entryId].hits };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Fall back to global
|
|
34
|
+
const globalIndex = readIndex(globalIndexPath);
|
|
35
|
+
if (globalIndex && globalIndex[entryId]) {
|
|
36
|
+
globalIndex[entryId].hits = (globalIndex[entryId].hits || 0) + 1;
|
|
37
|
+
writeIndex(globalIndexPath, globalIndex);
|
|
38
|
+
return { entryId, scope: "global", hits: globalIndex[entryId].hits };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Not found
|
|
42
|
+
if (!projectIndex && !globalIndex) {
|
|
43
|
+
throw new CliError("No INDEX.json found. Run 'playbook init' or 'playbook global init' first.");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
throw new CliError(`Entry '${entryId}' not found in project or global INDEX.`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = {
|
|
50
|
+
hitEntry,
|
|
51
|
+
};
|
package/src/commands/query.js
CHANGED
|
@@ -70,7 +70,7 @@ function formatPlainText(result) {
|
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
const lines = result.entries.map(
|
|
73
|
-
(e) => `[${e.scope}]\t${e.id}\t${e.title}\t${e.tags.join(",")}`
|
|
73
|
+
(e) => `[${e.scope}]\t${e.id}\t${e.title}\t${e.tags.join(",")}\thits:${e.hits || 0}`
|
|
74
74
|
);
|
|
75
75
|
|
|
76
76
|
const displayed = result.entries.length;
|
|
@@ -157,6 +157,7 @@ function formatJson(result) {
|
|
|
157
157
|
title: e.title,
|
|
158
158
|
tags: e.tags,
|
|
159
159
|
path: e.path,
|
|
160
|
+
hits: e.hits || 0,
|
|
160
161
|
}));
|
|
161
162
|
|
|
162
163
|
return JSON.stringify(output, null, 2);
|
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
|
@@ -221,10 +221,30 @@ function parsePromoteArgs(args) {
|
|
|
221
221
|
return { help: false, force, all, entryIds };
|
|
222
222
|
}
|
|
223
223
|
|
|
224
|
+
function parseHitArgs(args) {
|
|
225
|
+
for (const arg of args) {
|
|
226
|
+
if (arg === "-h" || arg === "--help") {
|
|
227
|
+
return { help: true };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const entryId = args[0];
|
|
232
|
+
if (!entryId || entryId.startsWith("-")) {
|
|
233
|
+
throw new CliError("playbook hit requires an entry ID");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (args.length > 1) {
|
|
237
|
+
throw new CliError("playbook hit accepts exactly one entry ID");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return { help: false, entryId };
|
|
241
|
+
}
|
|
242
|
+
|
|
224
243
|
module.exports = {
|
|
225
244
|
parseProjectCommandArgs,
|
|
226
245
|
parseGlobalInitArgs,
|
|
227
246
|
parseQueryArgs,
|
|
228
247
|
parsePromoteArgs,
|
|
248
|
+
parseHitArgs,
|
|
229
249
|
parseToolsValue,
|
|
230
250
|
};
|
|
@@ -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
|
+
{}
|
|
@@ -49,6 +49,17 @@ For relevant entries only:
|
|
|
49
49
|
|
|
50
50
|
Do NOT read all matched entries. Only read the ones that look relevant based on title.
|
|
51
51
|
|
|
52
|
+
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
|
+
### Step 3 — Record hit
|
|
55
|
+
|
|
56
|
+
After reading a case, run:
|
|
57
|
+
```bash
|
|
58
|
+
playbook hit <entry-id>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
This records usage and helps prioritize effective cases in future queries.
|
|
62
|
+
|
|
52
63
|
## Guidelines
|
|
53
64
|
|
|
54
65
|
- Keep queries focused — use specific tags, not broad searches
|
|
@@ -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
|