@punk6529/playbook 0.2.8 → 0.3.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/CHANGELOG.md ADDED
@@ -0,0 +1,70 @@
1
+ # Changelog
2
+
3
+ ## 0.3.0
4
+
5
+ **BREAKING**: 移除项目级知识存储,简化为全局单一作用域
6
+
7
+ - 移除 `promote` 命令及 `playbook-promote` skill
8
+ - 移除 `--scope` 参数(query/show/hit/reindex 全部只操作 `~/.playbook/repo/`)
9
+ - `playbook init` 不再创建 `docs/playbook/` 目录,仅安装 AI skill 模板
10
+ - `playbook update` 仅刷新 skill 模板
11
+ - `playbook reindex` 直接重建全局 INDEX,无需 `--scope global`
12
+ - `playbook root` 直接输出全局路径,无需 `--scope`
13
+ - `playbook-case` skill 直接写入全局知识库,不再询问 scope
14
+ - Skills 从 4 个减为 3 个(advisor、query、case)
15
+ - query 输出不再包含 `[scope]` 前缀和 scope 字段
16
+
17
+ ## 0.2.8
18
+
19
+ - 新增 `playbook root [--scope project|global]` 命令,输出 playbook 目录绝对路径
20
+ - playbook-case skill 写入文件前通过 `playbook root` 获取路径,避免相对路径问题
21
+ - playbook-case skill 新增 Write Procedure 段,明确写入步骤
22
+
23
+ ## 0.2.7
24
+
25
+ - `playbook query --tags-only` 合并 tags.yml 注册表,即使 INDEX 为空也能显示完整 tag 列表
26
+ - playbook-case skill 改用 `playbook query --tags-only` 获取 tag 建议
27
+
28
+ ## 0.2.6
29
+
30
+ - 内容文件新增 YAML front matter(title, tags, created),作为元数据主真相源
31
+ - 新增 `playbook reindex [--scope project|global]` 命令,从文件 front matter 重建 INDEX.json
32
+ - reindex 保留已有 hits 统计,跳过无效 front matter 的文件
33
+ - playbook-case skill 模板更新:生成的 case 文件包含 front matter
34
+
35
+ ## 0.2.5
36
+
37
+ - 新增 `playbook show <entry-id>` 命令,输出案例文件完整内容
38
+ - playbook-advisor 和 playbook-query skill 的 L2 改为调用 `playbook show`,不再要求 AI 猜测文件路径
39
+
40
+ ## 0.2.4
41
+
42
+ - 移除 init 时创建的 example 文件,INDEX.json 初始为空对象
43
+
44
+ ## 0.2.3
45
+
46
+ - 新增 `playbook hit <entry-id>` 命令,记录案例阅读次数
47
+ - `playbook query` 输出增加 `hits:N` 字段
48
+ - playbook-advisor skill 阅读案例后自动调用 hit,并优先推荐高 hits 案例
49
+
50
+ ## 0.2.2
51
+
52
+ - 新增 `playbook promote` 命令,将项目案例移动到全局知识库
53
+ - 支持单个、多个、`--all` 批量提升,`--force` 覆盖冲突
54
+ - 新增 playbook-promote skill 模板
55
+
56
+ ## 0.2.1
57
+
58
+ - README,补充命令使用场景说明
59
+ - 区分用户命令与内部命令
60
+
61
+ ## 0.2.0
62
+
63
+ - 新增 `playbook query` 命令(按 tag 检索、`--json`、`--tags-only`)
64
+ - 新增 playbook-advisor skill(主动顾问模式)
65
+ - 新增 playbook-query、playbook-case skill 模板
66
+
67
+ ## 0.1.0
68
+
69
+ - 初始版本
70
+ - `playbook init`、`playbook global init`、`playbook update`
package/README.md CHANGED
@@ -1,38 +1,37 @@
1
1
  # @punk6529/playbook
2
2
 
3
- 把团队踩过的坑,沉淀成 AI 可读、可查、可复用的工程记忆。
3
+ 把踩过的坑,沉淀成 AI 可读、可查、可复用的工程记忆。
4
4
 
5
- `@punk6529/playbook` 是一个结构化工程知识库 CLI,用于在项目中快速建立并维护 Playbook 资产(cases / patterns / checklists + INDEX)。
5
+ `@punk6529/playbook` 是一个结构化工程知识库 CLI,用于建立并维护全局 Playbook 资产(cases / patterns / checklists + INDEX)。
6
6
 
7
- 它不只是“记笔记工具”,而是把“个人经验”变成“团队可复用资产”的最小基础设施:
7
+ 它不只是"记笔记工具",而是把"个人经验"变成"可复用资产"的最小基础设施:
8
8
 
9
9
  - 解决问题后可立即沉淀为 case,避免同类问题反复排查
10
10
  - AI 可以按 tag 精准检索历史经验,而不是每次从零猜测
11
- - 支持项目级和全局级知识库,单项目沉淀可逐步升级为跨项目最佳实践
11
+ - 全局知识库跨项目共享,所有经验统一管理
12
12
  - 通过结构化索引 + 按需读取,控制上下文体积,降低 token 浪费
13
13
 
14
14
  ## 这个工具适合谁
15
15
 
16
16
  - 希望减少重复踩坑的个人开发者
17
- - 需要把故障处置经验标准化的技术团队
18
- - 在 Codex / Claude 工作流中,希望让 AI“带着历史经验工作”的团队
17
+ - 在 Codex / Claude 工作流中,希望让 AI"带着历史经验工作"的用户
19
18
 
20
19
  ## 你会获得什么
21
20
 
22
- - 一套开箱即用的知识库目录结构(项目级 + 全局级)
21
+ - 一套全局知识库目录结构(`~/.playbook/repo/`)
23
22
  - 三个可直接使用的 AI skills:
24
23
  - `/playbook-advisor`:会话内主动顾问,自动按需查询知识库
25
24
  - `/playbook-query`:手动按标签检索经验
26
25
  - `/playbook-case`:引导式记录新 case,并更新索引
27
- - 一套稳定的 CLI 工作流(init / global init / update / query / promote / show / hit / reindex),便于持续演进而不破坏现有内容
26
+ - 一套稳定的 CLI 工作流(global init / init / update / query / show / hit / reindex),便于持续演进而不破坏现有内容
28
27
 
29
28
  ## 典型工作流
30
29
 
31
- 1. `playbook init` 初始化项目知识库与 AI skills (新项目一次即可)
32
- 2. `playbook global init` 初始化全局知识库(一次即可)
33
- 3. 新会话开始先调用 `/playbook-advisor`,AI 会列出现有 tags;后续遇到问题时,会按相关 tags 查询案例 summary,并选取最相关案例查看详情
34
- 4. 问题解决后,用 `/playbook-case` 沉淀经验并更新索引
35
- 5. 在多个项目间通过全局仓库复用高价值模式
30
+ 1. `playbook global init` 初始化全局知识库(一次即可)
31
+ 2. `playbook init` 在项目中安装 AI skill 模板(每个项目一次)
32
+ 3. 新会话开始先调用 `/playbook-advisor`,AI 会列出现有 tags;后续遇到问题时,会按相关 tags 查询案例 summary,并选取最相关案例查看详情,获取问题解决方案
33
+ 4. 如果遇到新问题,解决后,手动调用 `/playbook-case` 沉淀经验并更新索引
34
+ 5. 所有经验统一存储在全局知识库,自动跨项目复用
36
35
 
37
36
  ## 安装
38
37
 
@@ -50,28 +49,28 @@ npx @punk6529/playbook --help
50
49
 
51
50
  日常使用的命令:
52
51
 
53
- ### `playbook init`
52
+ ### `playbook global init`
54
53
 
55
- 在项目中初始化知识库。新项目首次使用时运行一次。
54
+ 初始化全局知识库。只需运行一次。
56
55
 
57
56
  ```bash
58
- playbook init # 当前目录,所有 AI 工具
59
- playbook init ./my-project # 指定目录
60
- playbook init --tools claude # 仅安装 Claude skill
61
- playbook init --tools none # 不安装任何 AI skill
57
+ playbook global init
62
58
  ```
63
59
 
64
- 创建 `docs/playbook/{cases,patterns,checklists}`、`INDEX.json`,以及 AI skill 模板。
60
+ 创建 `~/.playbook/repo/{cases,patterns,checklists}`、`INDEX.json` `tags.yml`。
65
61
 
66
- ### `playbook global init`
62
+ ### `playbook init`
67
63
 
68
- 初始化全局知识库(跨项目共享的经验)。只需运行一次。
64
+ 在项目中安装 AI skill 模板。每个新项目首次使用时运行一次。
69
65
 
70
66
  ```bash
71
- playbook global init
67
+ playbook init # 当前目录,所有 AI 工具
68
+ playbook init ./my-project # 指定目录
69
+ playbook init --tools claude # 仅安装 Claude skill
70
+ playbook init --tools none # 不安装任何 AI skill
72
71
  ```
73
72
 
74
- 创建 `~/.playbook/repo/{cases,patterns,checklists}`、`INDEX.json``tags.yml`。
73
+ 安装 AI skill 模板到 `.claude/skills/``.codex/skills/`。
75
74
 
76
75
  ### `playbook update`
77
76
 
@@ -79,10 +78,9 @@ playbook global init
79
78
 
80
79
  ```bash
81
80
  playbook update # 更新当前项目
82
- playbook update --force # 强制覆盖(含 INDEX.json)
83
81
  ```
84
82
 
85
- 仅刷新 skill 模板,不修改用户业务内容(case/pattern/checklist 文件)。`INDEX.json` 默认受保护,需 `--force` 才会覆盖。
83
+ 仅刷新 skill 模板,不修改知识库内容。
86
84
 
87
85
  ## 在 AI 中使用
88
86
 
@@ -118,8 +116,8 @@ AI:根据之前的经验,这个问题是因为 Docker 默认绑定 0.0.0.0
118
116
  AI:请提供要搜索的 tag
119
117
  你:docker
120
118
  AI:找到以下条目:
121
- [project] case-001-docker-bind Docker bind address issue docker,env
122
- (1 match: 1 project)
119
+ case-001-docker-bind Docker bind address issue docker,env hits:3
120
+ (1 match)
123
121
  ```
124
122
 
125
123
  ### 3. `/playbook-case` —— 记录新 case
@@ -131,35 +129,21 @@ AI:找到以下条目:
131
129
  AI:请描述遇到的问题...
132
130
  你:Next.js 部署到 Vercel 后 API 路由 404
133
131
  AI:(引导你填写 问题/原因/解决/教训 四个部分)
134
- AI:(生成 case 文件,更新 INDEX.json)
132
+ AI:(生成 case 文件到 ~/.playbook/repo/,更新 INDEX.json)
135
133
  ```
136
134
 
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
-
150
135
  ## 内部命令
151
136
 
152
137
  以下命令主要由 AI skill 内部调用,用户一般不需要直接使用:
153
138
 
154
139
  ### `playbook query`
155
140
 
156
- 按 tag 搜索 INDEX.json 条目(OR 逻辑),同时搜索项目和全局范围。
141
+ 按 tag 搜索全局 INDEX.json 条目(OR 逻辑)。
157
142
 
158
143
  ```bash
159
144
  playbook query docker env # 匹配 "docker" 或 "env" 的条目
160
145
  playbook query # 所有条目
161
146
  playbook query --tags-only # 仅输出 tag 概览:docker(3) env(5) ci(2)
162
- playbook query --scope project # 仅项目范围
163
147
  playbook query --limit 0 # 不限数量
164
148
  playbook query docker --json # JSON 格式输出
165
149
  ```
@@ -174,8 +158,6 @@ playbook query docker --json # JSON 格式输出
174
158
  playbook show case-001-docker-bind # 输出案例文件内容
175
159
  ```
176
160
 
177
- 先查项目 INDEX,再查全局 INDEX,找到后输出对应文件内容到 stdout。
178
-
179
161
  ### `playbook hit`
180
162
 
181
163
  记录一次案例阅读命中,用于追踪案例的实际使用频率。
@@ -188,14 +170,13 @@ AI advisor 每次阅读案例详情后自动调用。命中次数在 `playbook q
188
170
 
189
171
  ### `playbook reindex`
190
172
 
191
- 从文件 front matter 重建 INDEX.json。当 INDEX 被清空、损坏或需要同步时使用。
173
+ 从文件 front matter 重建全局 INDEX.json。当 INDEX 被清空、损坏或需要同步时使用。
192
174
 
193
175
  ```bash
194
- playbook reindex # 重建项目 INDEX
195
- playbook reindex --scope global # 重建全局 INDEX
176
+ playbook reindex # 重建全局 INDEX
196
177
  ```
197
178
 
198
- 扫描 `cases/`、`patterns/`、`checklists/` 下的 `.md` 文件,解析 YAML front matter(title, tags, created),重建 INDEX.json。保留已有的 hits 统计。
179
+ 扫描 `~/.playbook/repo/` 下 `cases/`、`patterns/`、`checklists/` `.md` 文件,解析 YAML front matter(title, tags, created),重建 INDEX.json。保留已有的 hits 统计。
199
180
 
200
181
  ## 错误处理
201
182
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@punk6529/playbook",
3
- "version": "0.2.8",
3
+ "version": "0.3.0",
4
4
  "description": "CLI for initializing and updating Playbook & Case workflows.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -21,6 +21,7 @@
21
21
  "files": [
22
22
  "bin",
23
23
  "src",
24
- "templates"
24
+ "templates",
25
+ "CHANGELOG.md"
25
26
  ]
26
27
  }
package/src/cli.js CHANGED
@@ -2,12 +2,11 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
  const os = require("os");
4
4
  const { CliError } = require("./errors");
5
- const { parseProjectCommandArgs, parseGlobalInitArgs, parseQueryArgs, parsePromoteArgs, parseHitArgs, parseShowArgs, parseReindexArgs } = require("./options");
5
+ const { parseProjectCommandArgs, parseGlobalInitArgs, parseQueryArgs, parseHitArgs, parseShowArgs, parseReindexArgs } = require("./options");
6
6
  const { initProject } = require("./commands/init-project");
7
7
  const { initGlobal } = require("./commands/global-init");
8
8
  const { updateProject } = require("./commands/update-project");
9
9
  const { queryIndex, aggregateTags, formatPlainText, formatJson, formatTagsOnly } = require("./commands/query");
10
- const { promoteEntries } = require("./commands/promote");
11
10
  const { hitEntry } = require("./commands/hit");
12
11
  const { showEntry } = require("./commands/show");
13
12
  const { reindexPlaybook } = require("./commands/reindex");
@@ -19,19 +18,16 @@ function usageText() {
19
18
  " playbook init [path] [--tools all|none|codex,claude] [--force]",
20
19
  " playbook global init [--force]",
21
20
  " playbook update [path] [--tools all|none|codex,claude] [--force]",
22
- " playbook query [tags...] [--limit N] [--scope project|global|both] [--json] [--tags-only]",
23
- " playbook promote <entry-id...> [--force]",
24
- " playbook promote --all [--force]",
21
+ " playbook query [tags...] [--limit N] [--json] [--tags-only]",
25
22
  " playbook hit <entry-id>",
26
23
  " playbook show <entry-id>",
27
- " playbook reindex [--scope project|global]",
28
- " playbook root [--scope project|global]",
24
+ " playbook reindex",
25
+ " playbook root",
29
26
  "",
30
27
  "Notes:",
31
- " - `init` and `update` are project-scoped commands.",
32
- " - `global init` initializes ~/.playbook/repo and does not support --tools.",
33
- " - `query` searches INDEX.json by tags and returns matching entries.",
34
- " - V1 does not support `playbook global update`.",
28
+ " - `global init` initializes ~/.playbook/repo (knowledge storage).",
29
+ " - `init` installs AI skill templates into the current project.",
30
+ " - `query` searches global INDEX.json by tags and returns matching entries.",
35
31
  ].join("\n");
36
32
  }
37
33
 
@@ -82,12 +78,11 @@ function queryHelpText() {
82
78
  return [
83
79
  "Usage: playbook query [tags...] [options]",
84
80
  "",
85
- "Search INDEX.json entries by tags (OR logic).",
81
+ "Search global INDEX.json entries by tags (OR logic).",
86
82
  "Without tags, returns all entries.",
87
83
  "",
88
84
  "Options:",
89
85
  " --limit N Max results to return (default: 7, 0 = unlimited)",
90
- " --scope X Search scope: project, global, or both (default: both)",
91
86
  " --json Output results as JSON array",
92
87
  " --tags-only Output only tag names with case counts",
93
88
  " -h, --help Show this help",
@@ -113,61 +108,11 @@ function runQuery(args, io) {
113
108
  return 0;
114
109
  }
115
110
 
116
- function promoteHelpText() {
117
- return [
118
- "Usage: playbook promote <entry-id...> [options]",
119
- " playbook promote --all [options]",
120
- "",
121
- "Move project entries to global knowledge base.",
122
- "",
123
- "Options:",
124
- " --all Promote all project entries",
125
- " --force Overwrite if entry already exists in global",
126
- " -h, --help Show this help",
127
- ].join("\n");
128
- }
129
-
130
- function runPromote(args, io) {
131
- const options = parsePromoteArgs(args);
132
- if (options.help) {
133
- writeStdout(io, promoteHelpText());
134
- return 0;
135
- }
136
-
137
- const result = promoteEntries(options);
138
-
139
- if (result.message) {
140
- writeStdout(io, result.message);
141
- return 0;
142
- }
143
-
144
- let hasError = false;
145
- for (const r of result.results) {
146
- if (r.action === "promoted") {
147
- writeStdout(io, `[promoted] ${r.entryId}\t${r.title}`);
148
- } else if (r.action === "conflict") {
149
- writeStderr(io, `[conflict] ${r.entryId}\t${r.reason}`);
150
- hasError = true;
151
- } else if (r.action === "error") {
152
- writeStderr(io, `[error] ${r.entryId}\t${r.reason}`);
153
- hasError = true;
154
- }
155
- }
156
-
157
- const promoted = result.results.filter((r) => r.action === "promoted").length;
158
- if (promoted > 0) {
159
- writeStdout(io, `(${promoted} ${promoted === 1 ? "entry" : "entries"} promoted to global)`);
160
- }
161
-
162
- return hasError ? 1 : 0;
163
- }
164
-
165
111
  function hitHelpText() {
166
112
  return [
167
113
  "Usage: playbook hit <entry-id>",
168
114
  "",
169
- "Record a read hit on a playbook entry.",
170
- "Searches project INDEX first, then global INDEX.",
115
+ "Record a read hit on a playbook entry in global INDEX.",
171
116
  "",
172
117
  "Options:",
173
118
  " -h, --help Show this help",
@@ -182,7 +127,7 @@ function runHit(args, io) {
182
127
  }
183
128
 
184
129
  const result = hitEntry(options);
185
- writeStdout(io, `[${result.scope}] ${result.entryId} hits:${result.hits}`);
130
+ writeStdout(io, `${result.entryId} hits:${result.hits}`);
186
131
  return 0;
187
132
  }
188
133
 
@@ -190,8 +135,7 @@ function showHelpText() {
190
135
  return [
191
136
  "Usage: playbook show <entry-id>",
192
137
  "",
193
- "Display the full content of a playbook entry.",
194
- "Searches project INDEX first, then global INDEX.",
138
+ "Display the full content of a playbook entry from global INDEX.",
195
139
  "",
196
140
  "Options:",
197
141
  " -h, --help Show this help",
@@ -212,12 +156,11 @@ function runShow(args, io) {
212
156
 
213
157
  function reindexHelpText() {
214
158
  return [
215
- "Usage: playbook reindex [options]",
159
+ "Usage: playbook reindex",
216
160
  "",
217
- "Rebuild INDEX.json by scanning playbook files and reading front matter.",
161
+ "Rebuild global INDEX.json by scanning playbook files and reading front matter.",
218
162
  "",
219
163
  "Options:",
220
- " --scope X Scope: project or global (default: project)",
221
164
  " -h, --help Show this help",
222
165
  ].join("\n");
223
166
  }
@@ -230,36 +173,23 @@ function runReindex(args, io) {
230
173
  }
231
174
 
232
175
  const result = reindexPlaybook(options);
233
- writeStdout(io, `Reindexed ${result.scope}: ${result.count} ${result.count === 1 ? "entry" : "entries"} found.`);
176
+ writeStdout(io, `Reindexed: ${result.count} ${result.count === 1 ? "entry" : "entries"} found.`);
234
177
  return 0;
235
178
  }
236
179
 
237
180
  function runRoot(args, io) {
238
- let scope = "project";
239
181
  for (const arg of args) {
240
182
  if (arg === "-h" || arg === "--help") {
241
- writeStdout(io, "Usage: playbook root [--scope project|global]\n\nOutput the absolute path of the playbook directory.");
183
+ writeStdout(io, "Usage: playbook root\n\nOutput the absolute path of the global playbook directory.");
242
184
  return 0;
243
185
  }
244
- if (arg === "--scope" || arg.startsWith("--scope=")) {
245
- const value = arg === "--scope" ? args[args.indexOf(arg) + 1] : arg.slice("--scope=".length);
246
- if (value === "global" || value === "project") scope = value;
247
- }
248
186
  }
249
187
 
250
- if (scope === "global") {
251
- const globalRoot = path.join(os.homedir(), ".playbook/repo");
252
- if (!fs.existsSync(globalRoot)) {
253
- throw new CliError("Global playbook not found. Run 'playbook global init' first.");
254
- }
255
- writeStdout(io, globalRoot);
256
- } else {
257
- const projectRoot = path.resolve("docs/playbook");
258
- if (!fs.existsSync(projectRoot)) {
259
- throw new CliError("Project playbook not found. Run 'playbook init' first.");
260
- }
261
- writeStdout(io, projectRoot);
188
+ const globalRoot = path.join(os.homedir(), ".playbook/repo");
189
+ if (!fs.existsSync(globalRoot)) {
190
+ throw new CliError("Global playbook not found. Run 'playbook global init' first.");
262
191
  }
192
+ writeStdout(io, globalRoot);
263
193
  return 0;
264
194
  }
265
195
 
@@ -288,7 +218,7 @@ function runGlobal(args, io) {
288
218
  printSummary("playbook global init completed", result.globalRoot, result.results, (line) =>
289
219
  writeStdout(io, line)
290
220
  );
291
- writeStdout(io, "Next: run `playbook init` inside a project to scaffold project assets.");
221
+ writeStdout(io, "Next: run `playbook init` inside a project to install AI skill templates.");
292
222
  return 0;
293
223
  }
294
224
 
@@ -312,10 +242,6 @@ async function runCli(argv, io = process) {
312
242
  return runQuery(argv.slice(1), io);
313
243
  }
314
244
 
315
- if (command === "promote") {
316
- return runPromote(argv.slice(1), io);
317
- }
318
-
319
245
  if (command === "hit") {
320
246
  return runHit(argv.slice(1), io);
321
247
  }
@@ -336,7 +262,7 @@ async function runCli(argv, io = process) {
336
262
  return runGlobal(argv.slice(1), io);
337
263
  }
338
264
 
339
- throw new CliError(`Unknown command: ${command}. Supported: init, global, update, query, promote, hit, show, reindex, root`);
265
+ throw new CliError(`Unknown command: ${command}. Supported: init, global, update, query, hit, show, reindex, root`);
340
266
  } catch (error) {
341
267
  if (error instanceof CliError) {
342
268
  writeStderr(io, `Error: ${error.message}`);
@@ -17,33 +17,22 @@ function writeIndex(indexPath, data) {
17
17
  }
18
18
 
19
19
  function hitEntry(options) {
20
- const { entryId, targetPath = "." } = options;
20
+ const { entryId } = options;
21
21
 
22
- const projectIndexPath = path.resolve(targetPath, "docs/playbook/INDEX.json");
23
22
  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
23
  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 };
24
+
25
+ if (!globalIndex) {
26
+ throw new CliError("No INDEX.json found. Run 'playbook global init' first.");
39
27
  }
40
28
 
41
- // Not found
42
- if (!projectIndex && !globalIndex) {
43
- throw new CliError("No INDEX.json found. Run 'playbook init' or 'playbook global init' first.");
29
+ if (!globalIndex[entryId]) {
30
+ throw new CliError(`Entry '${entryId}' not found in INDEX.`);
44
31
  }
45
32
 
46
- throw new CliError(`Entry '${entryId}' not found in project or global INDEX.`);
33
+ globalIndex[entryId].hits = (globalIndex[entryId].hits || 0) + 1;
34
+ writeIndex(globalIndexPath, globalIndex);
35
+ return { entryId, hits: globalIndex[entryId].hits };
47
36
  }
48
37
 
49
38
  module.exports = {
@@ -24,37 +24,16 @@ function filterByTags(index, tags) {
24
24
  }
25
25
 
26
26
  function queryIndex(options) {
27
- const { tags = [], limit = DEFAULT_LIMIT, scope = "both", targetPath = "." } = options;
27
+ const { tags = [], limit = DEFAULT_LIMIT } = options;
28
28
 
29
- const projectIndexPath = path.resolve(targetPath, "docs/playbook/INDEX.json");
30
29
  const globalIndexPath = path.join(os.homedir(), ".playbook/repo/INDEX.json");
30
+ const globalIndex = readIndex(globalIndexPath);
31
31
 
32
- let projectEntries = [];
33
- let globalEntries = [];
34
- let anyIndexFound = false;
35
-
36
- if (scope === "both" || scope === "project") {
37
- const projectIndex = readIndex(projectIndexPath);
38
- if (projectIndex) {
39
- anyIndexFound = true;
40
- projectEntries = filterByTags(projectIndex, tags).map((e) => ({ ...e, scope: "project" }));
41
- }
42
- }
43
-
44
- if (scope === "both" || scope === "global") {
45
- const globalIndex = readIndex(globalIndexPath);
46
- if (globalIndex) {
47
- anyIndexFound = true;
48
- globalEntries = filterByTags(globalIndex, tags).map((e) => ({ ...e, scope: "global" }));
49
- }
50
- }
51
-
52
- const all = [...projectEntries, ...globalEntries];
53
-
54
- if (!anyIndexFound) {
32
+ if (!globalIndex) {
55
33
  return { entries: [], found: false };
56
34
  }
57
35
 
36
+ const all = filterByTags(globalIndex, tags);
58
37
  const limited = limit > 0 ? all.slice(0, limit) : all;
59
38
 
60
39
  return { entries: limited, total: all.length, found: true };
@@ -70,23 +49,17 @@ function formatPlainText(result) {
70
49
  }
71
50
 
72
51
  const lines = result.entries.map(
73
- (e) => `[${e.scope}]\t${e.id}\t${e.title}\t${e.tags.join(",")}\thits:${e.hits || 0}`
52
+ (e) => `${e.id}\t${e.title}\t${e.tags.join(",")}\thits:${e.hits || 0}`
74
53
  );
75
54
 
76
55
  const displayed = result.entries.length;
77
56
  const total = result.total;
78
- const projectCount = result.entries.filter((e) => e.scope === "project").length;
79
- const globalCount = result.entries.filter((e) => e.scope === "global").length;
80
-
81
- const parts = [];
82
- if (projectCount > 0) parts.push(`${projectCount} project`);
83
- if (globalCount > 0) parts.push(`${globalCount} global`);
84
57
 
85
58
  let summary = `(${total} match${total === 1 ? "" : "es"}`;
86
59
  if (displayed < total) {
87
60
  summary += `, showing ${displayed}`;
88
61
  }
89
- summary += `: ${parts.join(", ")})`;
62
+ summary += ")";
90
63
 
91
64
  lines.push(summary);
92
65
  return lines.join("\n");
@@ -108,40 +81,23 @@ function readTagsRegistry(tagsYmlPath) {
108
81
  }
109
82
  }
110
83
 
111
- function aggregateTags(options) {
112
- const { scope = "both", targetPath = "." } = options;
113
-
114
- const projectIndexPath = path.resolve(targetPath, "docs/playbook/INDEX.json");
84
+ function aggregateTags() {
115
85
  const globalIndexPath = path.join(os.homedir(), ".playbook/repo/INDEX.json");
116
86
  const tagsYmlPath = path.join(os.homedir(), ".playbook/repo/tags.yml");
117
87
 
118
88
  const tagCounts = {};
119
89
  let anyIndexFound = false;
120
90
 
121
- function countTags(index) {
122
- for (const entry of Object.values(index)) {
91
+ const globalIndex = readIndex(globalIndexPath);
92
+ if (globalIndex) {
93
+ anyIndexFound = true;
94
+ for (const entry of Object.values(globalIndex)) {
123
95
  for (const tag of entry.tags) {
124
96
  tagCounts[tag] = (tagCounts[tag] || 0) + 1;
125
97
  }
126
98
  }
127
99
  }
128
100
 
129
- if (scope === "both" || scope === "project") {
130
- const projectIndex = readIndex(projectIndexPath);
131
- if (projectIndex) {
132
- anyIndexFound = true;
133
- countTags(projectIndex);
134
- }
135
- }
136
-
137
- if (scope === "both" || scope === "global") {
138
- const globalIndex = readIndex(globalIndexPath);
139
- if (globalIndex) {
140
- anyIndexFound = true;
141
- countTags(globalIndex);
142
- }
143
- }
144
-
145
101
  // Merge tags.yml registry — ensures registered tags appear even with count 0
146
102
  const registryTags = readTagsRegistry(tagsYmlPath);
147
103
  if (registryTags) {
@@ -180,7 +136,6 @@ function formatJson(result) {
180
136
 
181
137
  const output = result.entries.map((e) => ({
182
138
  id: e.id,
183
- scope: e.scope,
184
139
  type: e.type,
185
140
  title: e.title,
186
141
  tags: e.tags,
@@ -50,26 +50,12 @@ function scanDir(baseDir) {
50
50
  return entries;
51
51
  }
52
52
 
53
- function reindexPlaybook(options) {
54
- const { scope = "project", targetPath = "." } = options;
55
-
56
- let baseDir;
57
- let indexPath;
58
-
59
- if (scope === "global") {
60
- baseDir = path.join(os.homedir(), ".playbook/repo");
61
- indexPath = path.join(baseDir, "INDEX.json");
62
- } else {
63
- baseDir = path.resolve(targetPath, "docs/playbook");
64
- indexPath = path.join(baseDir, "INDEX.json");
65
- }
53
+ function reindexPlaybook() {
54
+ const baseDir = path.join(os.homedir(), ".playbook/repo");
55
+ const indexPath = path.join(baseDir, "INDEX.json");
66
56
 
67
57
  if (!fs.existsSync(baseDir)) {
68
- throw new CliError(
69
- scope === "global"
70
- ? "Global playbook not found. Run 'playbook global init' first."
71
- : "Project playbook not found. Run 'playbook init' first."
72
- );
58
+ throw new CliError("Global playbook not found. Run 'playbook global init' first.");
73
59
  }
74
60
 
75
61
  // Read existing index to preserve hits
@@ -89,7 +75,6 @@ function reindexPlaybook(options) {
89
75
  fs.writeFileSync(indexPath, JSON.stringify(newEntries, null, 2) + "\n", "utf8");
90
76
 
91
77
  return {
92
- scope,
93
78
  count: Object.keys(newEntries).length,
94
79
  entries: newEntries,
95
80
  };
@@ -13,35 +13,23 @@ function readIndex(indexPath) {
13
13
  }
14
14
 
15
15
  function showEntry(options) {
16
- const { entryId, targetPath = "." } = options;
16
+ const { entryId } = options;
17
17
 
18
- const projectIndexPath = path.resolve(targetPath, "docs/playbook/INDEX.json");
19
18
  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
19
  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 };
20
+
21
+ if (!globalIndex) {
22
+ throw new CliError("No INDEX.json found. Run 'playbook global init' first.");
37
23
  }
38
24
 
39
- // Not found
40
- if (!projectIndex && !globalIndex) {
41
- throw new CliError("No INDEX.json found. Run 'playbook init' or 'playbook global init' first.");
25
+ if (!globalIndex[entryId]) {
26
+ throw new CliError(`Entry '${entryId}' not found in INDEX.`);
42
27
  }
43
28
 
44
- throw new CliError(`Entry '${entryId}' not found in project or global INDEX.`);
29
+ const entryPath = globalIndex[entryId].path;
30
+ const fullPath = path.join(os.homedir(), ".playbook/repo", entryPath);
31
+ const content = readFile(fullPath, entryId);
32
+ return { entryId, content };
45
33
  }
46
34
 
47
35
  function readFile(fullPath, entryId) {
@@ -11,14 +11,6 @@ function resolveGlobalRoot() {
11
11
  return path.join(home, ".playbook", "repo");
12
12
  }
13
13
 
14
- function resolveRuntimeContext(projectRoot = ".") {
15
- return {
16
- projectRoot: path.resolve(projectRoot),
17
- globalRoot: resolveGlobalRoot(),
18
- };
19
- }
20
-
21
14
  module.exports = {
22
- resolveRuntimeContext,
23
15
  resolveGlobalRoot,
24
16
  };
package/src/manifest.js CHANGED
@@ -1,14 +1,6 @@
1
- const PROJECT_SKELETON_DIRS = [
2
- "docs/playbook/cases",
3
- "docs/playbook/patterns",
4
- "docs/playbook/checklists",
5
- ];
1
+ const PROJECT_SKELETON_DIRS = [];
6
2
 
7
3
  const PROJECT_MANAGED_FILES = [
8
- {
9
- relativePath: "docs/playbook/INDEX.json",
10
- templatePath: "project/docs/playbook/INDEX.json",
11
- },
12
4
  {
13
5
  relativePath: ".codex/skills/playbook-query/SKILL.md",
14
6
  templatePath: "project/skills/playbook-query/SKILL.md",
@@ -45,18 +37,6 @@ const PROJECT_MANAGED_FILES = [
45
37
  tool: "claude",
46
38
  overwriteOnUpdate: true,
47
39
  },
48
- {
49
- relativePath: ".codex/skills/playbook-promote/SKILL.md",
50
- templatePath: "project/skills/playbook-promote/SKILL.md",
51
- tool: "codex",
52
- overwriteOnUpdate: true,
53
- },
54
- {
55
- relativePath: ".claude/skills/playbook-promote/SKILL.md",
56
- templatePath: "project/skills/playbook-promote/SKILL.md",
57
- tool: "claude",
58
- overwriteOnUpdate: true,
59
- },
60
40
  ];
61
41
 
62
42
  const GLOBAL_SKELETON_DIRS = ["cases", "patterns", "checklists"];
package/src/options.js CHANGED
@@ -108,7 +108,6 @@ function parseGlobalInitArgs(args) {
108
108
 
109
109
  function parseQueryArgs(args) {
110
110
  let limit = 7;
111
- let scope = "both";
112
111
  let json = false;
113
112
  let tagsOnly = false;
114
113
  const tags = [];
@@ -152,25 +151,6 @@ function parseQueryArgs(args) {
152
151
  continue;
153
152
  }
154
153
 
155
- if (arg === "--scope") {
156
- const next = args[i + 1];
157
- if (!next || !["project", "global", "both"].includes(next)) {
158
- throw new CliError("--scope must be one of: project, global, both");
159
- }
160
- scope = next;
161
- i += 1;
162
- continue;
163
- }
164
-
165
- if (arg.startsWith("--scope=")) {
166
- const value = arg.slice("--scope=".length);
167
- if (!["project", "global", "both"].includes(value)) {
168
- throw new CliError("--scope must be one of: project, global, both");
169
- }
170
- scope = value;
171
- continue;
172
- }
173
-
174
154
  if (arg.startsWith("-")) {
175
155
  throw new CliError(`Unknown option for playbook query: ${arg}`);
176
156
  }
@@ -178,47 +158,7 @@ function parseQueryArgs(args) {
178
158
  tags.push(arg);
179
159
  }
180
160
 
181
- return { help: false, tags, limit, scope, json, tagsOnly };
182
- }
183
-
184
- function parsePromoteArgs(args) {
185
- let force = false;
186
- let all = false;
187
- const entryIds = [];
188
-
189
- for (let i = 0; i < args.length; i += 1) {
190
- const arg = args[i];
191
-
192
- if (arg === "-h" || arg === "--help") {
193
- return { help: true };
194
- }
195
-
196
- if (arg === "--force") {
197
- force = true;
198
- continue;
199
- }
200
-
201
- if (arg === "--all") {
202
- all = true;
203
- continue;
204
- }
205
-
206
- if (arg.startsWith("-")) {
207
- throw new CliError(`Unknown option for playbook promote: ${arg}`);
208
- }
209
-
210
- entryIds.push(arg);
211
- }
212
-
213
- if (!all && entryIds.length === 0) {
214
- throw new CliError("playbook promote requires entry IDs or --all");
215
- }
216
-
217
- if (all && entryIds.length > 0) {
218
- throw new CliError("Cannot use --all with specific entry IDs");
219
- }
220
-
221
- return { help: false, force, all, entryIds };
161
+ return { help: false, tags, limit, json, tagsOnly };
222
162
  }
223
163
 
224
164
  function parseHitArgs(args) {
@@ -260,47 +200,23 @@ function parseShowArgs(args) {
260
200
  }
261
201
 
262
202
  function parseReindexArgs(args) {
263
- let scope = "project";
264
-
265
- for (let i = 0; i < args.length; i += 1) {
266
- const arg = args[i];
267
-
203
+ for (const arg of args) {
268
204
  if (arg === "-h" || arg === "--help") {
269
205
  return { help: true };
270
206
  }
271
207
 
272
- if (arg === "--scope") {
273
- const next = args[i + 1];
274
- if (!next || !["project", "global"].includes(next)) {
275
- throw new CliError("--scope must be one of: project, global");
276
- }
277
- scope = next;
278
- i += 1;
279
- continue;
280
- }
281
-
282
- if (arg.startsWith("--scope=")) {
283
- const value = arg.slice("--scope=".length);
284
- if (!["project", "global"].includes(value)) {
285
- throw new CliError("--scope must be one of: project, global");
286
- }
287
- scope = value;
288
- continue;
289
- }
290
-
291
208
  if (arg.startsWith("-")) {
292
209
  throw new CliError(`Unknown option for playbook reindex: ${arg}`);
293
210
  }
294
211
  }
295
212
 
296
- return { help: false, scope };
213
+ return { help: false };
297
214
  }
298
215
 
299
216
  module.exports = {
300
217
  parseProjectCommandArgs,
301
218
  parseGlobalInitArgs,
302
219
  parseQueryArgs,
303
- parsePromoteArgs,
304
220
  parseHitArgs,
305
221
  parseShowArgs,
306
222
  parseReindexArgs,
@@ -17,7 +17,7 @@ This outputs all available tags with their case counts, e.g.: `docker(3) react(2
17
17
 
18
18
  Internalize this tag list. It tells you what domains have recorded experience.
19
19
 
20
- If the output is "No playbook INDEX found." — inform the user that no playbook knowledge is available and suggest running `playbook init` to get started. Stop here.
20
+ If the output is "No playbook INDEX found." — inform the user that no playbook knowledge is available and suggest running `playbook global init` to get started. Stop here.
21
21
 
22
22
  ## When to Query
23
23
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: playbook-case
3
- description: Draft and inspect playbook case entries with explicit scope and tags.
3
+ description: Draft and inspect playbook case entries in global knowledge base.
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -8,7 +8,7 @@ Use this skill when the user asks to create, review, or inspect case entries.
8
8
 
9
9
  ## Behavior
10
10
 
11
- - Keep scope explicit (`project` or `global`).
11
+ - All entries are stored in the global knowledge base (`~/.playbook/repo/`).
12
12
  - Keep draft generation non-destructive by default.
13
13
  - For new cases, enforce this guided flow:
14
14
  1. If the user has not provided a concrete problem statement, ask first:
@@ -18,14 +18,10 @@ Use this skill when the user asks to create, review, or inspect case entries.
18
18
  playbook query --tags-only
19
19
  ```
20
20
  Suggest relevant existing tags from the output. Ask user to choose existing tags or explicitly confirm a new tag.
21
- 3. If scope is not confirmed, ask for scope confirmation:
22
- - default is `project`
23
- - use `global` only when user explicitly confirms
24
- 4. Only after required inputs are complete, produce the draft package.
21
+ 3. Only after required inputs are complete, produce the draft package.
25
22
  - Required inputs before any draft output:
26
23
  - problem statement
27
24
  - tag decision (existing tags or confirmed new tag)
28
- - scope confirmation
29
25
  - Hard gate: while any required input is missing, do not output case ID, suggested path, markdown draft, or INDEX update suggestion.
30
26
  - For new cases, once inputs are complete, produce a complete draft package:
31
27
  - case ID (format: `{type}-{NNN}-{slug}`, e.g. `case-001-oauth-token-race`)
@@ -94,20 +90,16 @@ The front matter is the source of truth for metadata. INDEX.json is rebuilt from
94
90
 
95
91
  ### File Paths
96
92
 
97
- Write files to the correct location based on scope:
98
-
99
- - **Project scope:** case file → `<project-root>/docs/playbook/<path>`, INDEX → `<project-root>/docs/playbook/INDEX.json`
100
- - **Global scope:** case file → `~/.playbook/repo/<path>`, INDEX → `~/.playbook/repo/INDEX.json`
101
-
102
- **IMPORTANT:** Always use absolute paths when writing files. Do NOT use relative paths — the current working directory may not be the project root.
93
+ All files are stored in the global playbook directory.
103
94
 
104
95
  Get the playbook directory first:
105
96
  ```bash
106
- playbook root # project scope
107
- playbook root --scope global # global scope
97
+ playbook root
108
98
  ```
109
99
  Then use the output to construct absolute paths, e.g. `<root>/cases/case-001-xxx.md` and `<root>/INDEX.json`.
110
100
 
101
+ **IMPORTANT:** Always use absolute paths when writing files. Do NOT use relative paths — the current working directory may not be the project root.
102
+
111
103
  ### Draft Package Output
112
104
 
113
105
  When producing the draft package, output:
@@ -132,6 +124,6 @@ Add this entry to INDEX.json:
132
124
 
133
125
  After user confirms the draft:
134
126
 
135
- 1. Write the case file to the scope-specific path
136
- 2. Read the current INDEX.json, merge the new entry, write back
127
+ 1. Write the case file to `~/.playbook/repo/<path>`
128
+ 2. Read the current INDEX.json at `~/.playbook/repo/INDEX.json`, merge the new entry, write back
137
129
  3. Run `playbook hit <entry-id>` is NOT needed — hit is only for reads
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: playbook-query
3
- description: Query project and global playbook knowledge using CLI for token-efficient discovery.
3
+ description: Query global playbook knowledge using CLI for token-efficient discovery.
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -17,17 +17,16 @@ Run:
17
17
  playbook query <tag1> <tag2> ...
18
18
  ```
19
19
 
20
- This searches both project and global INDEX.json, returns matching entries (max 7 by default), with project results first.
20
+ This searches the global INDEX.json and returns matching entries (max 7 by default).
21
21
 
22
22
  Options:
23
23
  - `--limit N` — change max results (0 = unlimited)
24
- - `--scope project|global|both` — restrict search scope
25
24
  - `--json` — output as JSON array
26
25
 
27
26
  Output format (one line per match):
28
27
  ```
29
- [scope] entry-id title tags
30
- (N matches: X project, Y global)
28
+ entry-id title tags hits:N
29
+ (N matches)
31
30
  ```
32
31
 
33
32
  ### Expansion (L2): Show entry content
@@ -42,6 +41,6 @@ This outputs the full content of the entry file. Only expand entries that are ac
42
41
 
43
42
  ### Edge Cases
44
43
 
45
- - If query returns "No playbook INDEX found." — tell the user: "No playbook initialized. Run `playbook init` to get started."
44
+ - If query returns "No playbook INDEX found." — tell the user: "No playbook initialized. Run `playbook global init` to get started."
46
45
  - If query returns "No matching entries." — suggest trying different tags or running without tags to see all entries
47
46
  - If user query is vague, run `playbook query` without tags to list everything
@@ -1,96 +0,0 @@
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 promoteEntry(entryId, projectIndex, globalIndex, projectBase, globalBase, force) {
20
- const entry = projectIndex[entryId];
21
- if (!entry) {
22
- return { action: "error", entryId, reason: `Entry '${entryId}' not found in project INDEX` };
23
- }
24
-
25
- if (globalIndex[entryId] && !force) {
26
- return {
27
- action: "conflict",
28
- entryId,
29
- reason: `Entry '${entryId}' already exists in global INDEX (use --force to overwrite)`,
30
- };
31
- }
32
-
33
- const srcFile = path.join(projectBase, entry.path);
34
- const destFile = path.join(globalBase, entry.path);
35
-
36
- // Write to global first (safety: write before delete)
37
- try {
38
- fs.mkdirSync(path.dirname(destFile), { recursive: true });
39
- fs.copyFileSync(srcFile, destFile);
40
- } catch (err) {
41
- return { action: "error", entryId, reason: `Failed to copy file to global: ${err.message}` };
42
- }
43
-
44
- globalIndex[entryId] = entry;
45
-
46
- // Now safe to remove from project
47
- delete projectIndex[entryId];
48
- try {
49
- fs.unlinkSync(srcFile);
50
- } catch {
51
- // File may not exist, that's ok — INDEX entry already removed
52
- }
53
-
54
- return { action: "promoted", entryId, title: entry.title };
55
- }
56
-
57
- function promoteEntries(options) {
58
- const { entryIds = [], all = false, force = false, targetPath = "." } = options;
59
-
60
- const projectBase = path.resolve(targetPath, "docs/playbook");
61
- const globalBase = path.join(os.homedir(), ".playbook/repo");
62
-
63
- const projectIndexPath = path.join(projectBase, "INDEX.json");
64
- const globalIndexPath = path.join(globalBase, "INDEX.json");
65
-
66
- const projectIndex = readIndex(projectIndexPath);
67
- if (!projectIndex) {
68
- throw new CliError("Project INDEX.json not found. Run 'playbook init' first.");
69
- }
70
-
71
- const globalIndex = readIndex(globalIndexPath);
72
- if (!globalIndex) {
73
- throw new CliError("Global INDEX.json not found. Run 'playbook global init' first.");
74
- }
75
-
76
- const ids = all ? Object.keys(projectIndex) : entryIds;
77
-
78
- if (all && ids.length === 0) {
79
- return { results: [], message: "No entries in project INDEX to promote." };
80
- }
81
-
82
- const results = [];
83
- for (const id of ids) {
84
- results.push(promoteEntry(id, projectIndex, globalIndex, projectBase, globalBase, force));
85
- }
86
-
87
- // Write updated indices
88
- writeIndex(projectIndexPath, projectIndex);
89
- writeIndex(globalIndexPath, globalIndex);
90
-
91
- return { results };
92
- }
93
-
94
- module.exports = {
95
- promoteEntries,
96
- };
@@ -1,32 +0,0 @@
1
- ---
2
- name: playbook-promote
3
- description: Guide when to promote project-level playbook entries to global knowledge base.
4
- license: MIT
5
- ---
6
-
7
- Use this skill to suggest promoting valuable project experiences to the global knowledge base for cross-project reuse.
8
-
9
- ## When to Suggest Promote
10
-
11
- Suggest `playbook promote <entry-id>` to the user when:
12
- - A project case was successfully used to resolve a problem and the lesson is not project-specific
13
- - A pattern or checklist has proven useful and would benefit other projects
14
- - The user explicitly asks about sharing knowledge across projects
15
-
16
- ## When NOT to Suggest
17
-
18
- Do not suggest promote when:
19
- - The entry references project-specific configuration, paths, or dependencies
20
- - The entry is still being refined or hasn't been validated
21
- - The user is focused on other work (don't interrupt)
22
-
23
- ## How to Promote
24
-
25
- ```bash
26
- playbook promote <entry-id> # promote a single entry
27
- playbook promote <id1> <id2> # promote multiple entries
28
- playbook promote --all # promote all project entries
29
- playbook promote <entry-id> --force # overwrite if already exists in global
30
- ```
31
-
32
- The command moves entries from project to global: copies the file, updates both INDEX.json files, and removes the project copy.