@punk6529/playbook 0.2.2 → 0.2.3

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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@punk6529/playbook",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
4
4
  "description": "CLI for initializing and updating Playbook & Case workflows.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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
+ };
@@ -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/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
  };
@@ -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