@punk6529/playbook 0.1.0 → 0.2.1
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 +105 -38
- 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,134 @@
|
|
|
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
|
+
|
|
19
|
+
日常使用的命令:
|
|
20
|
+
|
|
21
|
+
### `playbook init`
|
|
22
|
+
|
|
23
|
+
在项目中初始化知识库。新项目首次使用时运行一次。
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
playbook init # 当前目录,所有 AI 工具
|
|
27
|
+
playbook init ./my-project # 指定目录
|
|
28
|
+
playbook init --tools claude # 仅安装 Claude skill
|
|
29
|
+
playbook init --tools none # 不安装任何 AI skill
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
创建 `docs/playbook/{cases,patterns,checklists}`、`INDEX.json`,以及 AI skill 模板。
|
|
33
|
+
|
|
34
|
+
### `playbook global init`
|
|
35
|
+
|
|
36
|
+
初始化全局知识库(跨项目共享的经验)。只需运行一次。
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
playbook global init
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
创建 `~/.playbook/repo/{cases,patterns,checklists}`、`INDEX.json` 和 `tags.yml`。
|
|
43
|
+
|
|
44
|
+
### `playbook update`
|
|
45
|
+
|
|
46
|
+
升级 playbook CLI 版本后,更新项目中的 skill 模板到最新版。
|
|
18
47
|
|
|
19
48
|
```bash
|
|
20
|
-
playbook
|
|
21
|
-
playbook
|
|
22
|
-
playbook update [path] [--tools all|none|codex,claude] [--force]
|
|
49
|
+
playbook update # 更新当前项目
|
|
50
|
+
playbook update --force # 强制覆盖(含 INDEX.json)
|
|
23
51
|
```
|
|
24
52
|
|
|
25
|
-
|
|
53
|
+
仅刷新 skill 模板,不修改用户业务内容(case/pattern/checklist 文件)。`INDEX.json` 默认受保护,需 `--force` 才会覆盖。
|
|
26
54
|
|
|
27
|
-
|
|
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`
|
|
55
|
+
## 内部命令
|
|
40
56
|
|
|
41
|
-
|
|
57
|
+
以下命令主要由 AI skill 内部调用,用户一般不需要直接使用:
|
|
58
|
+
|
|
59
|
+
### `playbook query`
|
|
60
|
+
|
|
61
|
+
按 tag 搜索 INDEX.json 条目(OR 逻辑),同时搜索项目和全局范围。
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
playbook query docker env # 匹配 "docker" 或 "env" 的条目
|
|
65
|
+
playbook query # 所有条目
|
|
66
|
+
playbook query --tags-only # 仅输出 tag 概览:docker(3) env(5) ci(2)
|
|
67
|
+
playbook query --scope project # 仅项目范围
|
|
68
|
+
playbook query --limit 0 # 不限数量
|
|
69
|
+
playbook query docker --json # JSON 格式输出
|
|
70
|
+
```
|
|
42
71
|
|
|
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.
|
|
72
|
+
这个命令是 AI skill 的底层工具。`/playbook-advisor` 和 `/playbook-query` skill 在内部调用它来搜索知识库,节省 token 开销。
|
|
47
73
|
|
|
48
|
-
##
|
|
74
|
+
## 在 AI 中使用
|
|
49
75
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
76
|
+
`playbook init` 之后,AI 工具中有三个 skill 可用:
|
|
77
|
+
|
|
78
|
+
### 1. `/playbook-advisor` —— 主动知识顾问
|
|
79
|
+
|
|
80
|
+
在会话开始时调用一次,AI 会自动加载 tag 感知,后续遇到相关问题时自主查询。
|
|
81
|
+
|
|
82
|
+
**典型使用流程:**
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
你:/playbook-advisor
|
|
86
|
+
AI:(自动执行 playbook query --tags-only)
|
|
87
|
+
AI:已加载知识库标签:docker(3) env(5) react(2) ci(1)
|
|
88
|
+
|
|
89
|
+
...(正常工作中)...
|
|
90
|
+
|
|
91
|
+
AI:(遇到 Docker 端口绑定问题,发现与 "docker" tag 相关)
|
|
92
|
+
AI:(自动执行 playbook query docker)
|
|
93
|
+
AI:(发现 "case-001-docker-bind" 标题相关,读取该 case)
|
|
94
|
+
AI:根据之前的经验,这个问题是因为 Docker 默认绑定 0.0.0.0,需要改为 127.0.0.1...
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
特点:用户触发一次,之后 AI 自主判断何时查询,无需人工干预。
|
|
98
|
+
|
|
99
|
+
### 2. `/playbook-query` —— 手动知识查询
|
|
100
|
+
|
|
101
|
+
用户主动搜索知识库时使用。
|
|
102
|
+
|
|
103
|
+
```
|
|
104
|
+
你:/playbook-query
|
|
105
|
+
AI:请提供要搜索的 tag
|
|
106
|
+
你:docker
|
|
107
|
+
AI:找到以下条目:
|
|
108
|
+
[project] case-001-docker-bind Docker bind address issue docker,env
|
|
109
|
+
(1 match: 1 project)
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### 3. `/playbook-case` —— 记录新 case
|
|
113
|
+
|
|
114
|
+
解决问题后,用来记录经验到知识库。
|
|
115
|
+
|
|
116
|
+
```
|
|
117
|
+
你:/playbook-case
|
|
118
|
+
AI:请描述遇到的问题...
|
|
119
|
+
你:Next.js 部署到 Vercel 后 API 路由 404
|
|
120
|
+
AI:(引导你填写 问题/原因/解决/教训 四个部分)
|
|
121
|
+
AI:(生成 case 文件,更新 INDEX.json)
|
|
122
|
+
```
|
|
56
123
|
|
|
57
|
-
##
|
|
124
|
+
## 错误处理
|
|
58
125
|
|
|
59
|
-
-
|
|
60
|
-
-
|
|
126
|
+
- 未知命令或无效选项会给出可操作的提示信息
|
|
127
|
+
- 托管文件读写失败会包含源路径和原因
|
|
61
128
|
|
|
62
|
-
##
|
|
129
|
+
## 打包说明
|
|
63
130
|
|
|
64
|
-
|
|
131
|
+
发布前确认包内容:
|
|
65
132
|
|
|
66
133
|
```bash
|
|
67
134
|
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
|