@punk6529/playbook 0.1.0 → 0.2.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 CHANGED
@@ -1,67 +1,136 @@
1
1
  # @punk6529/playbook
2
2
 
3
- CLI for initializing and updating Playbook workflow assets.
3
+ 结构化工程知识库 CLI —— 初始化、更新和查询项目 Playbook 资产。
4
4
 
5
- ## Install
5
+ ## 安装
6
6
 
7
7
  ```bash
8
8
  npm install -g @punk6529/playbook
9
9
  ```
10
10
 
11
- Or run without global install:
11
+ 或免安装直接运行:
12
12
 
13
13
  ```bash
14
14
  npx @punk6529/playbook --help
15
15
  ```
16
16
 
17
- ## Commands
17
+ ## 命令
18
18
 
19
19
  ```bash
20
20
  playbook init [path] [--tools all|none|codex,claude] [--force]
21
21
  playbook global init [--force]
22
22
  playbook update [path] [--tools all|none|codex,claude] [--force]
23
+ playbook query [tags...] [--limit N] [--scope project|global|both] [--json] [--tags-only]
23
24
  ```
24
25
 
25
- ## Scope Boundaries
26
+ ## 作用范围
26
27
 
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`
28
+ - `playbook init` 项目级:
29
+ - 创建 `docs/playbook/{cases,patterns,checklists}` `docs/playbook/INDEX.json`
30
+ - 根据 `--tools` 参数写入 AI skill 模板:
31
+ - Codex: `.codex/skills/{playbook-query,playbook-case,playbook-advisor}/SKILL.md`
32
+ - Claude: `.claude/skills/{playbook-query,playbook-case,playbook-advisor}/SKILL.md`
33
+ - `playbook global init` 全局级:
34
+ - 创建 `~/.playbook/repo/{cases,patterns,checklists}`
35
+ - 创建 `~/.playbook/repo/INDEX.json` `~/.playbook/repo/tags.yml`
36
+ - 不支持 `--tools`
37
+ - `playbook update` 项目级:
38
+ - 仅更新托管模板文件
39
+ - 不修改用户业务内容(如 case markdown 文件)
40
+ - V1 暂不支持 `playbook global update`
40
41
 
41
- ## Skill Delivery
42
+ ## Skill 交付
42
43
 
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.
44
+ CLI 为支持的 AI 工具(`codex`、`claude`)安装以下 skill:
47
45
 
48
- ## Conflict Policy
46
+ | Skill | 用途 |
47
+ |-------|------|
48
+ | `playbook-query` | 手动查询知识库 |
49
+ | `playbook-case` | 引导式 case 创建 |
50
+ | `playbook-advisor` | 主动知识应用(自动感知 tag,AI 自主查询) |
49
51
 
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.
52
+ CLI 只负责文件交付(`init`/`update`),skill 的运行时执行由宿主 AI 工具处理。
56
53
 
57
- ## Error Handling
54
+ ## 冲突策略
58
55
 
59
- - Unknown command or invalid option fails with actionable guidance.
60
- - Managed file read/write failures include source path and reason.
56
+ - `playbook init` `playbook global init` 默认非破坏性:
57
+ - 有冲突的托管文件会跳过并报告为 `conflict`
58
+ - `playbook update` 采用分级策略:
59
+ - skill 模板:内容不同时自动刷新
60
+ - `docs/playbook/INDEX.json`:保护不覆盖,除非使用 `--force`
61
+ - 使用 `--force` 可强制覆盖受保护的托管文件
61
62
 
62
- ## Packaging Notes
63
+ ## 查询命令
63
64
 
64
- Before publish, verify exact package contents:
65
+ `playbook query` tag 搜索 INDEX.json 条目(OR 逻辑),同时搜索项目和全局范围。
66
+
67
+ ```bash
68
+ playbook query docker env # 匹配 "docker" 或 "env" 的条目
69
+ playbook query # 所有条目
70
+ playbook query --tags-only # 仅输出 tag 概览:docker(3) env(5) ci(2)
71
+ playbook query --scope project # 仅项目范围
72
+ playbook query --limit 0 # 不限数量
73
+ playbook query docker --json # JSON 格式输出
74
+ ```
75
+
76
+ ## 在 AI 中使用
77
+
78
+ `playbook init` 之后,AI 工具中有三个 skill 可用:
79
+
80
+ ### 1. `/playbook-advisor` —— 主动知识顾问
81
+
82
+ 在会话开始时调用一次,AI 会自动加载 tag 感知,后续遇到相关问题时自主查询。
83
+
84
+ **典型使用流程:**
85
+
86
+ ```
87
+ 你:/playbook-advisor
88
+ AI:(自动执行 playbook query --tags-only)
89
+ AI:已加载知识库标签:docker(3) env(5) react(2) ci(1)
90
+
91
+ ...(正常工作中)...
92
+
93
+ AI:(遇到 Docker 端口绑定问题,发现与 "docker" tag 相关)
94
+ AI:(自动执行 playbook query docker)
95
+ AI:(发现 "case-001-docker-bind" 标题相关,读取该 case)
96
+ AI:根据之前的经验,这个问题是因为 Docker 默认绑定 0.0.0.0,需要改为 127.0.0.1...
97
+ ```
98
+
99
+ 特点:用户触发一次,之后 AI 自主判断何时查询,无需人工干预。
100
+
101
+ ### 2. `/playbook-query` —— 手动知识查询
102
+
103
+ 用户主动搜索知识库时使用。
104
+
105
+ ```
106
+ 你:/playbook-query
107
+ AI:请提供要搜索的 tag
108
+ 你:docker
109
+ AI:找到以下条目:
110
+ [project] case-001-docker-bind Docker bind address issue docker,env
111
+ (1 match: 1 project)
112
+ ```
113
+
114
+ ### 3. `/playbook-case` —— 记录新 case
115
+
116
+ 解决问题后,用来记录经验到知识库。
117
+
118
+ ```
119
+ 你:/playbook-case
120
+ AI:请描述遇到的问题...
121
+ 你:Next.js 部署到 Vercel 后 API 路由 404
122
+ AI:(引导你填写 问题/原因/解决/教训 四个部分)
123
+ AI:(生成 case 文件,更新 INDEX.json)
124
+ ```
125
+
126
+ ## 错误处理
127
+
128
+ - 未知命令或无效选项会给出可操作的提示信息
129
+ - 托管文件读写失败会包含源路径和原因
130
+
131
+ ## 打包说明
132
+
133
+ 发布前确认包内容:
65
134
 
66
135
  ```bash
67
136
  npm pack --dry-run
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@punk6529/playbook",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
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,8 +1,9 @@
1
1
  const { CliError } = require("./errors");
2
- const { parseProjectCommandArgs, parseGlobalInitArgs } = require("./options");
2
+ const { parseProjectCommandArgs, parseGlobalInitArgs, parseQueryArgs } = 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
+ const { queryIndex, aggregateTags, formatPlainText, formatJson, formatTagsOnly } = require("./commands/query");
6
7
  const { printSummary } = require("./reporting");
7
8
 
8
9
  function usageText() {
@@ -11,10 +12,12 @@ function usageText() {
11
12
  " playbook init [path] [--tools all|none|codex,claude] [--force]",
12
13
  " playbook global init [--force]",
13
14
  " playbook update [path] [--tools all|none|codex,claude] [--force]",
15
+ " playbook query [tags...] [--limit N] [--scope project|global|both] [--json] [--tags-only]",
14
16
  "",
15
17
  "Notes:",
16
18
  " - `init` and `update` are project-scoped commands.",
17
19
  " - `global init` initializes ~/.playbook/repo and does not support --tools.",
20
+ " - `query` searches INDEX.json by tags and returns matching entries.",
18
21
  " - V1 does not support `playbook global update`.",
19
22
  ].join("\n");
20
23
  }
@@ -62,6 +65,41 @@ function runUpdate(args, io) {
62
65
  return 0;
63
66
  }
64
67
 
68
+ function queryHelpText() {
69
+ return [
70
+ "Usage: playbook query [tags...] [options]",
71
+ "",
72
+ "Search INDEX.json entries by tags (OR logic).",
73
+ "Without tags, returns all entries.",
74
+ "",
75
+ "Options:",
76
+ " --limit N Max results to return (default: 7, 0 = unlimited)",
77
+ " --scope X Search scope: project, global, or both (default: both)",
78
+ " --json Output results as JSON array",
79
+ " --tags-only Output only tag names with case counts",
80
+ " -h, --help Show this help",
81
+ ].join("\n");
82
+ }
83
+
84
+ function runQuery(args, io) {
85
+ const options = parseQueryArgs(args);
86
+ if (options.help) {
87
+ writeStdout(io, queryHelpText());
88
+ return 0;
89
+ }
90
+
91
+ if (options.tagsOnly) {
92
+ const result = aggregateTags(options);
93
+ writeStdout(io, formatTagsOnly(result));
94
+ return 0;
95
+ }
96
+
97
+ const result = queryIndex(options);
98
+ const output = options.json ? formatJson(result) : formatPlainText(result);
99
+ writeStdout(io, output);
100
+ return 0;
101
+ }
102
+
65
103
  function runGlobal(args, io) {
66
104
  const subcommand = args[0];
67
105
  if (!subcommand) {
@@ -107,11 +145,15 @@ async function runCli(argv, io = process) {
107
145
  return runUpdate(argv.slice(1), io);
108
146
  }
109
147
 
148
+ if (command === "query") {
149
+ return runQuery(argv.slice(1), io);
150
+ }
151
+
110
152
  if (command === "global") {
111
153
  return runGlobal(argv.slice(1), io);
112
154
  }
113
155
 
114
- throw new CliError(`Unknown command: ${command}. Supported: init, global, update`);
156
+ throw new CliError(`Unknown command: ${command}. Supported: init, global, update, query`);
115
157
  } catch (error) {
116
158
  if (error instanceof CliError) {
117
159
  writeStderr(io, `Error: ${error.message}`);
@@ -0,0 +1,172 @@
1
+ const fs = require("fs");
2
+ const path = require("path");
3
+ const os = require("os");
4
+
5
+ const DEFAULT_LIMIT = 7;
6
+
7
+ function readIndex(indexPath) {
8
+ try {
9
+ const content = fs.readFileSync(indexPath, "utf8");
10
+ return JSON.parse(content);
11
+ } catch {
12
+ return null;
13
+ }
14
+ }
15
+
16
+ function filterByTags(index, tags) {
17
+ const entries = [];
18
+ for (const [id, entry] of Object.entries(index)) {
19
+ if (tags.length === 0 || entry.tags.some((t) => tags.includes(t))) {
20
+ entries.push({ id, ...entry });
21
+ }
22
+ }
23
+ return entries;
24
+ }
25
+
26
+ function queryIndex(options) {
27
+ const { tags = [], limit = DEFAULT_LIMIT, scope = "both", targetPath = "." } = options;
28
+
29
+ const projectIndexPath = path.resolve(targetPath, "docs/playbook/INDEX.json");
30
+ const globalIndexPath = path.join(os.homedir(), ".playbook/repo/INDEX.json");
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) {
55
+ return { entries: [], found: false };
56
+ }
57
+
58
+ const limited = limit > 0 ? all.slice(0, limit) : all;
59
+
60
+ return { entries: limited, total: all.length, found: true };
61
+ }
62
+
63
+ function formatPlainText(result) {
64
+ if (!result.found) {
65
+ return "No playbook INDEX found.";
66
+ }
67
+
68
+ if (result.entries.length === 0) {
69
+ return "No matching entries.";
70
+ }
71
+
72
+ const lines = result.entries.map(
73
+ (e) => `[${e.scope}]\t${e.id}\t${e.title}\t${e.tags.join(",")}`
74
+ );
75
+
76
+ const displayed = result.entries.length;
77
+ 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
+
85
+ let summary = `(${total} match${total === 1 ? "" : "es"}`;
86
+ if (displayed < total) {
87
+ summary += `, showing ${displayed}`;
88
+ }
89
+ summary += `: ${parts.join(", ")})`;
90
+
91
+ lines.push(summary);
92
+ return lines.join("\n");
93
+ }
94
+
95
+ function aggregateTags(options) {
96
+ const { scope = "both", targetPath = "." } = options;
97
+
98
+ const projectIndexPath = path.resolve(targetPath, "docs/playbook/INDEX.json");
99
+ const globalIndexPath = path.join(os.homedir(), ".playbook/repo/INDEX.json");
100
+
101
+ const tagCounts = {};
102
+ let anyIndexFound = false;
103
+
104
+ function countTags(index) {
105
+ for (const entry of Object.values(index)) {
106
+ for (const tag of entry.tags) {
107
+ tagCounts[tag] = (tagCounts[tag] || 0) + 1;
108
+ }
109
+ }
110
+ }
111
+
112
+ if (scope === "both" || scope === "project") {
113
+ const projectIndex = readIndex(projectIndexPath);
114
+ if (projectIndex) {
115
+ anyIndexFound = true;
116
+ countTags(projectIndex);
117
+ }
118
+ }
119
+
120
+ if (scope === "both" || scope === "global") {
121
+ const globalIndex = readIndex(globalIndexPath);
122
+ if (globalIndex) {
123
+ anyIndexFound = true;
124
+ countTags(globalIndex);
125
+ }
126
+ }
127
+
128
+ if (!anyIndexFound) {
129
+ return { tags: {}, found: false };
130
+ }
131
+
132
+ return { tags: tagCounts, found: true };
133
+ }
134
+
135
+ function formatTagsOnly(result) {
136
+ if (!result.found) {
137
+ return "No playbook INDEX found.";
138
+ }
139
+
140
+ const entries = Object.entries(result.tags);
141
+ if (entries.length === 0) {
142
+ return "No tags found.";
143
+ }
144
+
145
+ return entries.map(([tag, count]) => `${tag}(${count})`).join(" ");
146
+ }
147
+
148
+ function formatJson(result) {
149
+ if (!result.found) {
150
+ return JSON.stringify([]);
151
+ }
152
+
153
+ const output = result.entries.map((e) => ({
154
+ id: e.id,
155
+ scope: e.scope,
156
+ type: e.type,
157
+ title: e.title,
158
+ tags: e.tags,
159
+ path: e.path,
160
+ }));
161
+
162
+ return JSON.stringify(output, null, 2);
163
+ }
164
+
165
+ module.exports = {
166
+ queryIndex,
167
+ aggregateTags,
168
+ formatPlainText,
169
+ formatJson,
170
+ formatTagsOnly,
171
+ DEFAULT_LIMIT,
172
+ };
package/src/manifest.js CHANGED
@@ -45,6 +45,18 @@ const PROJECT_MANAGED_FILES = [
45
45
  tool: "claude",
46
46
  overwriteOnUpdate: true,
47
47
  },
48
+ {
49
+ relativePath: ".codex/skills/playbook-advisor/SKILL.md",
50
+ templatePath: "project/skills/playbook-advisor/SKILL.md",
51
+ tool: "codex",
52
+ overwriteOnUpdate: true,
53
+ },
54
+ {
55
+ relativePath: ".claude/skills/playbook-advisor/SKILL.md",
56
+ templatePath: "project/skills/playbook-advisor/SKILL.md",
57
+ tool: "claude",
58
+ overwriteOnUpdate: true,
59
+ },
48
60
  ];
49
61
 
50
62
  const GLOBAL_SKELETON_DIRS = ["cases", "patterns", "checklists"];
package/src/options.js CHANGED
@@ -106,8 +106,84 @@ function parseGlobalInitArgs(args) {
106
106
  return { help: false, force };
107
107
  }
108
108
 
109
+ function parseQueryArgs(args) {
110
+ let limit = 7;
111
+ let scope = "both";
112
+ let json = false;
113
+ let tagsOnly = false;
114
+ const tags = [];
115
+
116
+ for (let i = 0; i < args.length; i += 1) {
117
+ const arg = args[i];
118
+
119
+ if (arg === "-h" || arg === "--help") {
120
+ return { help: true };
121
+ }
122
+
123
+ if (arg === "--json") {
124
+ json = true;
125
+ continue;
126
+ }
127
+
128
+ if (arg === "--tags-only") {
129
+ tagsOnly = true;
130
+ continue;
131
+ }
132
+
133
+ if (arg === "--limit") {
134
+ const next = args[i + 1];
135
+ if (next === undefined || next.startsWith("-")) {
136
+ throw new CliError("Missing value for --limit");
137
+ }
138
+ limit = parseInt(next, 10);
139
+ if (Number.isNaN(limit) || limit < 0) {
140
+ throw new CliError("--limit must be a non-negative integer");
141
+ }
142
+ i += 1;
143
+ continue;
144
+ }
145
+
146
+ if (arg.startsWith("--limit=")) {
147
+ const value = arg.slice("--limit=".length);
148
+ limit = parseInt(value, 10);
149
+ if (Number.isNaN(limit) || limit < 0) {
150
+ throw new CliError("--limit must be a non-negative integer");
151
+ }
152
+ continue;
153
+ }
154
+
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
+ if (arg.startsWith("-")) {
175
+ throw new CliError(`Unknown option for playbook query: ${arg}`);
176
+ }
177
+
178
+ tags.push(arg);
179
+ }
180
+
181
+ return { help: false, tags, limit, scope, json, tagsOnly };
182
+ }
183
+
109
184
  module.exports = {
110
185
  parseProjectCommandArgs,
111
186
  parseGlobalInitArgs,
187
+ parseQueryArgs,
112
188
  parseToolsValue,
113
189
  };
@@ -0,0 +1,57 @@
1
+ ---
2
+ name: playbook-advisor
3
+ description: Automatically leverage playbook knowledge when encountering problems related to known topics.
4
+ license: MIT
5
+ ---
6
+
7
+ Use this skill to proactively apply playbook knowledge during your work.
8
+
9
+ ## Bootstrap: Load Tag Awareness
10
+
11
+ On activation, immediately run:
12
+ ```bash
13
+ playbook query --tags-only
14
+ ```
15
+
16
+ This outputs all available tags with their case counts, e.g.: `docker(3) react(2) env(5)`
17
+
18
+ Internalize this tag list. It tells you what domains have recorded experience.
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.
21
+
22
+ ## When to Query
23
+
24
+ Query the playbook when you encounter:
25
+ - An error or unexpected behavior related to a known tag
26
+ - Uncertainty about how to approach a task in a domain with known experience
27
+ - A recurring pattern that might have documented guidance
28
+
29
+ **Do NOT query** during routine work with no errors or uncertainty. Only query when there is a specific reason.
30
+
31
+ ## How to Query (Progressive Disclosure)
32
+
33
+ Follow this L1 → L2 flow:
34
+
35
+ ### Step 1 — Title list (L1)
36
+
37
+ Run:
38
+ ```bash
39
+ playbook query <tag>
40
+ ```
41
+
42
+ Review the titles in the output. Only proceed to Step 2 for entries whose titles suggest relevance to your current problem.
43
+
44
+ ### Step 2 — Read specific cases (L2)
45
+
46
+ For relevant entries only:
47
+ - Project entries: read `docs/playbook/<path>`
48
+ - Global entries: read `~/.playbook/repo/<path>`
49
+
50
+ Do NOT read all matched entries. Only read the ones that look relevant based on title.
51
+
52
+ ## Guidelines
53
+
54
+ - Keep queries focused — use specific tags, not broad searches
55
+ - If multiple tags match your situation, query the most specific one first
56
+ - Apply learnings from cases directly to your current work
57
+ - Do not mention the playbook to the user unless they ask about it
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: playbook-query
3
- description: Query project and global playbook knowledge in a concise, index-first way.
3
+ description: Query project and global playbook knowledge using CLI for token-efficient discovery.
4
4
  license: MIT
5
5
  ---
6
6
 
@@ -8,43 +8,39 @@ Use this skill when the user asks to search, filter, or inspect playbook knowled
8
8
 
9
9
  ## Behavior
10
10
 
11
- ### Lookup Sequence
11
+ ### Discovery (L1): Use CLI command
12
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.**
13
+ **IMPORTANT: Do NOT read INDEX.json directly. Use the CLI command to filter entries locally and save tokens.**
14
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
15
+ Run:
16
+ ```bash
17
+ playbook query <tag1> <tag2> ...
18
+ ```
29
19
 
30
- Return results in order of increasing detail and token cost:
20
+ This searches both project and global INDEX.json, returns matching entries (max 7 by default), with project results first.
31
21
 
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.
22
+ Options:
23
+ - `--limit N`change max results (0 = unlimited)
24
+ - `--scope project|global|both`restrict search scope
25
+ - `--json` — output as JSON array
35
26
 
36
- For each match, show:
27
+ Output format (one line per match):
37
28
  ```
38
- [{type}] {title} (tags: {tags}) [{scope}]
29
+ [scope] entry-id title tags
30
+ (N matches: X project, Y global)
39
31
  ```
40
32
 
41
- ### Expanding Entries
33
+ ### Expansion (L2): Read individual case files
34
+
35
+ When the user asks to see details of a specific entry, or when you need full context for a task:
36
+
37
+ - For project entries: read `docs/playbook/<path>` where `<path>` is from the query result
38
+ - For global entries: read `~/.playbook/repo/<path>`
42
39
 
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
40
+ Only expand entries that are actually needed. Do not read all matched entries at once.
45
41
 
46
42
  ### Edge Cases
47
43
 
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
44
+ - If query returns "No playbook INDEX found." tell the user: "No playbook initialized. Run `playbook init` to get started."
45
+ - If query returns "No matching entries." suggest trying different tags or running without tags to see all entries
46
+ - If user query is vague, run `playbook query` without tags to list everything