@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 +104 -35
- package/package.json +1 -1
- package/src/cli.js +44 -2
- package/src/commands/query.js +172 -0
- package/src/manifest.js +12 -0
- package/src/options.js +76 -0
- package/templates/project/skills/playbook-advisor/SKILL.md +57 -0
- package/templates/project/skills/playbook-query/SKILL.md +25 -29
package/README.md
CHANGED
|
@@ -1,67 +1,136 @@
|
|
|
1
1
|
# @punk6529/playbook
|
|
2
2
|
|
|
3
|
-
CLI
|
|
3
|
+
结构化工程知识库 CLI —— 初始化、更新和查询项目 Playbook 资产。
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## 安装
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npm install -g @punk6529/playbook
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
或免安装直接运行:
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
14
|
npx @punk6529/playbook --help
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
##
|
|
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
|
-
##
|
|
26
|
+
## 作用范围
|
|
26
27
|
|
|
27
|
-
- `playbook init`
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
- Codex: `.codex/skills/{playbook-query,playbook-case}/SKILL.md`
|
|
31
|
-
- Claude: `.claude/skills/{playbook-query,playbook-case}/SKILL.md`
|
|
32
|
-
- `playbook global init`
|
|
33
|
-
-
|
|
34
|
-
-
|
|
35
|
-
-
|
|
36
|
-
- `playbook update`
|
|
37
|
-
-
|
|
38
|
-
-
|
|
39
|
-
- V1
|
|
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
|
|
42
|
+
## Skill 交付
|
|
42
43
|
|
|
43
|
-
|
|
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
|
-
|
|
46
|
+
| Skill | 用途 |
|
|
47
|
+
|-------|------|
|
|
48
|
+
| `playbook-query` | 手动查询知识库 |
|
|
49
|
+
| `playbook-case` | 引导式 case 创建 |
|
|
50
|
+
| `playbook-advisor` | 主动知识应用(自动感知 tag,AI 自主查询) |
|
|
49
51
|
|
|
50
|
-
|
|
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
|
-
##
|
|
54
|
+
## 冲突策略
|
|
58
55
|
|
|
59
|
-
-
|
|
60
|
-
-
|
|
56
|
+
- `playbook init` 和 `playbook global init` 默认非破坏性:
|
|
57
|
+
- 有冲突的托管文件会跳过并报告为 `conflict`
|
|
58
|
+
- `playbook update` 采用分级策略:
|
|
59
|
+
- skill 模板:内容不同时自动刷新
|
|
60
|
+
- `docs/playbook/INDEX.json`:保护不覆盖,除非使用 `--force`
|
|
61
|
+
- 使用 `--force` 可强制覆盖受保护的托管文件
|
|
61
62
|
|
|
62
|
-
##
|
|
63
|
+
## 查询命令
|
|
63
64
|
|
|
64
|
-
|
|
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
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
|
|
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
|
-
###
|
|
11
|
+
### Discovery (L1): Use CLI command
|
|
12
12
|
|
|
13
|
-
**IMPORTANT:
|
|
13
|
+
**IMPORTANT: Do NOT read INDEX.json directly. Use the CLI command to filter entries locally and save tokens.**
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
20
|
+
This searches both project and global INDEX.json, returns matching entries (max 7 by default), with project results first.
|
|
31
21
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
27
|
+
Output format (one line per match):
|
|
37
28
|
```
|
|
38
|
-
[
|
|
29
|
+
[scope] entry-id title tags
|
|
30
|
+
(N matches: X project, Y global)
|
|
39
31
|
```
|
|
40
32
|
|
|
41
|
-
###
|
|
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
|
-
|
|
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.
|
|
49
|
-
- If
|
|
50
|
-
- If user query is vague,
|
|
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
|