create-claude-cabinet 0.35.0 → 0.37.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/lib/cli.js +4 -1
- package/lib/engagement-setup.js +119 -11
- package/package.json +1 -1
- package/templates/cabinet/qa-dimensions-template.yaml +77 -0
- package/templates/cabinet/skill-best-practices.md +7 -0
- package/templates/cabinet/skill-output-conventions.md +153 -0
- package/templates/engagement/__tests__/engagement.test.mjs +334 -29
- package/templates/engagement/__tests__/pibdb-adapter.test.mjs +19 -0
- package/templates/engagement/app-guide-template.md +82 -0
- package/templates/engagement/engagement-schema.md +298 -321
- package/templates/engagement/engagement.mjs +74 -18
- package/templates/engagement/pib-db-patches/pib-db-lib.mjs +91 -28
- package/templates/engagement/pib-db-patches/pib-db-schema.sql +3 -0
- package/templates/engagement/pibdb-adapter.mjs +9 -0
- package/templates/engagement/sql-constants.mjs +27 -0
- package/templates/scripts/pib-db-lib.mjs +69 -5
- package/templates/skills/cabinet-roster-check/SKILL.md +14 -0
- package/templates/skills/checklist-discover/SKILL.md +217 -0
- package/templates/skills/collab-client/SKILL.md +188 -0
- package/templates/skills/collab-consultant/SKILL.md +219 -0
- package/templates/skills/debrief/SKILL.md +13 -2
- package/templates/skills/debrief/phases/checklist-feedback.md +116 -0
- package/templates/skills/engagement/SKILL.md +3 -222
- package/templates/skills/engagement-add/SKILL.md +2 -92
- package/templates/skills/engagement-create/SKILL.md +2 -197
- package/templates/skills/engagement-edit/SKILL.md +2 -115
- package/templates/skills/engagement-help/SKILL.md +5 -174
- package/templates/skills/engagement-message/SKILL.md +3 -75
- package/templates/skills/engagement-progress/SKILL.md +3 -51
- package/templates/skills/engagement-status/SKILL.md +3 -125
- package/templates/skills/engagement-sync/SKILL.md +2 -424
- package/templates/skills/execute/SKILL.md +14 -0
- package/templates/skills/execute/phases/post-impl-checklist.md +137 -0
- package/templates/skills/guide/SKILL.md +98 -0
- package/templates/skills/orient/SKILL.md +25 -4
- package/templates/skills/triage-audit/SKILL.md +17 -2
package/lib/cli.js
CHANGED
|
@@ -486,7 +486,7 @@ const MODULES = {
|
|
|
486
486
|
mandatory: false,
|
|
487
487
|
default: true,
|
|
488
488
|
lean: true,
|
|
489
|
-
templates: ['skills/plan', 'skills/execute', 'skills/generate-plan-groups', 'skills/execute-group', 'workflows/execute-group-implement.js', 'workflows/execute-group-complete.js', 'skills/investigate', 'cabinet/checkpoint-protocol.md'],
|
|
489
|
+
templates: ['skills/plan', 'skills/execute', 'skills/execute/phases/post-impl-checklist.md', 'skills/debrief/phases/checklist-feedback.md', 'skills/checklist-discover', 'skills/generate-plan-groups', 'skills/execute-group', 'workflows/execute-group-implement.js', 'workflows/execute-group-complete.js', 'skills/investigate', 'cabinet/checkpoint-protocol.md', 'cabinet/qa-dimensions-template.yaml'],
|
|
490
490
|
},
|
|
491
491
|
'compliance': {
|
|
492
492
|
name: 'Compliance Stack (rules + enforcement)',
|
|
@@ -602,6 +602,8 @@ const MODULES = {
|
|
|
602
602
|
requires: ['work-tracking'],
|
|
603
603
|
postInstall: 'engagement-setup',
|
|
604
604
|
templates: [
|
|
605
|
+
'skills/collab-client',
|
|
606
|
+
'skills/collab-consultant',
|
|
605
607
|
'skills/engagement',
|
|
606
608
|
'skills/engagement-progress',
|
|
607
609
|
'skills/engagement-help',
|
|
@@ -611,6 +613,7 @@ const MODULES = {
|
|
|
611
613
|
'skills/engagement-add',
|
|
612
614
|
'skills/engagement-status',
|
|
613
615
|
'skills/engagement-sync',
|
|
616
|
+
'skills/guide',
|
|
614
617
|
'engagement',
|
|
615
618
|
],
|
|
616
619
|
},
|
package/lib/engagement-setup.js
CHANGED
|
@@ -5,11 +5,16 @@
|
|
|
5
5
|
* Dispatched from cli.js's postInstall pipeline. Two responsibilities:
|
|
6
6
|
*
|
|
7
7
|
* 1. FILE OVERLAY — copy the engagement-inclusive pib-db files over the
|
|
8
|
-
* base versions.
|
|
9
|
-
* the installed file
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* base versions. For pib-db-lib.mjs, the decision is marker-aware:
|
|
9
|
+
* we check whether the installed file already contains engagement code
|
|
10
|
+
* (not just its SCHEMA_VERSION number). This handles three cases:
|
|
11
|
+
* a. Installed file has engagement code + version >= patch → skip
|
|
12
|
+
* b. Installed file lacks engagement code + version <= patch → copy
|
|
13
|
+
* c. Installed file lacks engagement code + version > patch → BASE
|
|
14
|
+
* AHEAD: the base work-tracking module advanced past the patch
|
|
15
|
+
* without a coordinated engagement patch bump. Copying stale
|
|
16
|
+
* patch code over a newer base would drop base migrations.
|
|
17
|
+
* Emit a warning and skip both overlay and schema-ensure.
|
|
13
18
|
*
|
|
14
19
|
* 2. SCHEMA ENSURE — after the overlay, open pib.db and call migrate()
|
|
15
20
|
* so existing DBs advance to the patch's schema version. Also runs an
|
|
@@ -18,8 +23,19 @@
|
|
|
18
23
|
* engagement — the v5 migration entry is gated out, so this direct
|
|
19
24
|
* exec is the only path to creating the table).
|
|
20
25
|
*
|
|
21
|
-
*
|
|
22
|
-
* The engagement patch
|
|
26
|
+
* SCHEMA VERSIONING COORDINATION RULES:
|
|
27
|
+
* - The engagement patch's SCHEMA_VERSION must always be >= the base
|
|
28
|
+
* work-tracking SCHEMA_VERSION (the patch is a superset).
|
|
29
|
+
* - When base advances, the patch must advance in the same CC release.
|
|
30
|
+
* - Migrations shared by both base and patch use column-existence
|
|
31
|
+
* checks or IF NOT EXISTS to be idempotent.
|
|
32
|
+
* - A base-ahead state (base version > patch version) is an error
|
|
33
|
+
* indicating an uncoordinated upgrade. The overlay refuses to copy
|
|
34
|
+
* and skips schema-ensure to avoid mismatched lib/DB state.
|
|
35
|
+
*
|
|
36
|
+
* The base pib-db templates (owned by work-tracking) ship schema v1-v6.
|
|
37
|
+
* The engagement patch ships v1-v8 (v5=engagement_events, v6=projects.tags,
|
|
38
|
+
* v7=client-facing copy columns, v8=engagement_events.visibility).
|
|
23
39
|
*/
|
|
24
40
|
|
|
25
41
|
const fs = require('fs');
|
|
@@ -27,12 +43,17 @@ const path = require('path');
|
|
|
27
43
|
|
|
28
44
|
const FILES = ['pib-db-lib.mjs', 'pib-db-mcp-server.mjs', 'pib-db-schema.sql', 'pib-db.mjs'];
|
|
29
45
|
const VERSION_RE = /export const SCHEMA_VERSION\s*=\s*(\d+)/;
|
|
46
|
+
const ENGAGEMENT_MARKER_RE = /engagement_events/;
|
|
30
47
|
|
|
31
48
|
function parseSchemaVersion(content) {
|
|
32
49
|
const m = content.match(VERSION_RE);
|
|
33
50
|
return m ? parseInt(m[1], 10) : 0;
|
|
34
51
|
}
|
|
35
52
|
|
|
53
|
+
function hasEngagementCode(content) {
|
|
54
|
+
return ENGAGEMENT_MARKER_RE.test(content);
|
|
55
|
+
}
|
|
56
|
+
|
|
36
57
|
function setupEngagement({ dryRun, projectDir } = {}) {
|
|
37
58
|
const results = [];
|
|
38
59
|
const scriptsDir = path.join(projectDir, 'scripts');
|
|
@@ -43,7 +64,7 @@ function setupEngagement({ dryRun, projectDir } = {}) {
|
|
|
43
64
|
return { results };
|
|
44
65
|
}
|
|
45
66
|
|
|
46
|
-
// --- Step 1: File overlay (
|
|
67
|
+
// --- Step 1: File overlay (marker-aware for pib-db-lib.mjs) ---
|
|
47
68
|
|
|
48
69
|
const patchLibPath = path.join(patchDir, 'pib-db-lib.mjs');
|
|
49
70
|
let patchVersion = 0;
|
|
@@ -52,6 +73,7 @@ function setupEngagement({ dryRun, projectDir } = {}) {
|
|
|
52
73
|
}
|
|
53
74
|
|
|
54
75
|
let filesUpdated = false;
|
|
76
|
+
let baseAhead = false;
|
|
55
77
|
for (const file of FILES) {
|
|
56
78
|
const target = path.join(scriptsDir, file);
|
|
57
79
|
const source = path.join(patchDir, file);
|
|
@@ -67,9 +89,22 @@ function setupEngagement({ dryRun, projectDir } = {}) {
|
|
|
67
89
|
}
|
|
68
90
|
|
|
69
91
|
if (file === 'pib-db-lib.mjs') {
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
92
|
+
const installedContent = fs.readFileSync(target, 'utf8');
|
|
93
|
+
const installedVersion = parseSchemaVersion(installedContent);
|
|
94
|
+
const isEngagementInclusive = hasEngagementCode(installedContent);
|
|
95
|
+
|
|
96
|
+
if (isEngagementInclusive && installedVersion >= patchVersion) {
|
|
97
|
+
results.push(`${file}: engagement-inclusive v${installedVersion} >= patch v${patchVersion} — skipped`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!isEngagementInclusive && installedVersion > patchVersion) {
|
|
102
|
+
baseAhead = true;
|
|
103
|
+
results.push(
|
|
104
|
+
`${file}: BASE AHEAD — installed base v${installedVersion} > patch v${patchVersion}. ` +
|
|
105
|
+
`The engagement patch must be updated to match. Overlay skipped to avoid dropping base migrations. ` +
|
|
106
|
+
`Fix: bump the engagement patch SCHEMA_VERSION to >= ${installedVersion} in the same CC release.`
|
|
107
|
+
);
|
|
73
108
|
continue;
|
|
74
109
|
}
|
|
75
110
|
} else {
|
|
@@ -89,6 +124,13 @@ function setupEngagement({ dryRun, projectDir } = {}) {
|
|
|
89
124
|
}
|
|
90
125
|
|
|
91
126
|
// --- Step 2: Schema ensure (migrate + idempotent engagement_events) ---
|
|
127
|
+
// Skipped entirely when base-ahead is detected — running migrations
|
|
128
|
+
// against a mismatched library/DB pair would under-migrate the DB.
|
|
129
|
+
|
|
130
|
+
if (baseAhead) {
|
|
131
|
+
results.push('schema-ensure: skipped — base-ahead state detected (see overlay warning above)');
|
|
132
|
+
return { results };
|
|
133
|
+
}
|
|
92
134
|
|
|
93
135
|
const dbPath = path.join(projectDir, 'pib.db');
|
|
94
136
|
if (!dryRun && fs.existsSync(dbPath) && fs.existsSync(path.join(scriptsDir, 'pib-db-lib.mjs'))) {
|
|
@@ -122,6 +164,12 @@ function setupEngagement({ dryRun, projectDir } = {}) {
|
|
|
122
164
|
// later adds engagement. Their v5 migration entry (engagement_events)
|
|
123
165
|
// is gated out by user_version, and the execSync migrate above won't
|
|
124
166
|
// create it either. This direct exec is the only path.
|
|
167
|
+
//
|
|
168
|
+
// SOURCE OF TRUTH: templates/engagement/sql-constants.mjs
|
|
169
|
+
// This inline copy exists because engagement-setup.js is CJS and
|
|
170
|
+
// can't import() ESM synchronously. The drift-guard test
|
|
171
|
+
// (test/pib-db-engagement/sql-constants-drift.test.js) verifies
|
|
172
|
+
// this stays in sync with sql-constants.mjs.
|
|
125
173
|
try {
|
|
126
174
|
db.exec(`CREATE TABLE IF NOT EXISTS engagement_events (
|
|
127
175
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
@@ -133,6 +181,7 @@ function setupEngagement({ dryRun, projectDir } = {}) {
|
|
|
133
181
|
author TEXT NOT NULL,
|
|
134
182
|
verdict TEXT CHECK(verdict IS NULL OR verdict IN ('approve','object','comment','none')),
|
|
135
183
|
body TEXT CHECK(body IS NULL OR length(body) <= 10000),
|
|
184
|
+
visibility TEXT NOT NULL DEFAULT 'internal' CHECK(visibility IN ('client','internal')),
|
|
136
185
|
addressed INTEGER NOT NULL DEFAULT 0 CHECK(addressed IN (0,1)),
|
|
137
186
|
created_at TEXT NOT NULL CHECK(created_at GLOB '????-??-??T*'),
|
|
138
187
|
CHECK(kind NOT IN ('client_feedback','approval')
|
|
@@ -141,10 +190,69 @@ function setupEngagement({ dryRun, projectDir } = {}) {
|
|
|
141
190
|
db.exec("CREATE INDEX IF NOT EXISTS idx_engagement_events_eng ON engagement_events(engagement, created_at DESC)");
|
|
142
191
|
db.exec("CREATE INDEX IF NOT EXISTS idx_engagement_events_tgt ON engagement_events(target_fid, created_at DESC)");
|
|
143
192
|
db.exec("CREATE INDEX IF NOT EXISTS idx_engagement_events_dedup ON engagement_events(packet_id, target_fid, verdict)");
|
|
193
|
+
// Defensive idempotent ALTER: an engagement_events table created by a
|
|
194
|
+
// prior install (before the visibility column existed) won't get the
|
|
195
|
+
// column from CREATE IF NOT EXISTS. Add it here; swallow "duplicate
|
|
196
|
+
// column" when it's already present.
|
|
197
|
+
try {
|
|
198
|
+
db.exec("ALTER TABLE engagement_events ADD COLUMN visibility TEXT NOT NULL DEFAULT 'internal' CHECK(visibility IN ('client','internal'))");
|
|
199
|
+
} catch (e) {
|
|
200
|
+
if (!/duplicate column/i.test(e.message || '')) throw e;
|
|
201
|
+
}
|
|
144
202
|
results.push('schema-ensure: engagement_events table ensured (idempotent)');
|
|
145
203
|
} catch (e) {
|
|
146
204
|
results.push(`schema-ensure: engagement_events ensure failed — ${e.message}`);
|
|
147
205
|
}
|
|
206
|
+
|
|
207
|
+
// Data migration — extract client-facing copy from notes into columns.
|
|
208
|
+
// Idempotent: skips rows where any client column is already populated.
|
|
209
|
+
// Uses synchronous DB operations (better-sqlite3) so no async needed.
|
|
210
|
+
try {
|
|
211
|
+
const hasClientCols = db.prepare("PRAGMA table_info(actions)").all()
|
|
212
|
+
.some(c => c.name === 'client_title');
|
|
213
|
+
if (hasClientCols) {
|
|
214
|
+
const actionRows = db.prepare(
|
|
215
|
+
"SELECT fid, notes FROM actions WHERE notes LIKE '%client-facing%' AND client_title IS NULL AND client_body IS NULL AND client_generated_at IS NULL"
|
|
216
|
+
).all();
|
|
217
|
+
const projRows = db.prepare(
|
|
218
|
+
"SELECT fid, notes FROM projects WHERE notes LIKE '%client-facing%' AND client_title IS NULL AND client_body IS NULL AND client_generated_at IS NULL"
|
|
219
|
+
).all();
|
|
220
|
+
|
|
221
|
+
const copyRe = /<!--\s*client-facing\s*\n([\s\S]*?)-->/;
|
|
222
|
+
const genRe = /<!--\s*cc-generated:(\S+)\s+status:(\S+)\s*-->/;
|
|
223
|
+
const updateA = db.prepare("UPDATE actions SET client_title = ?, client_body = ?, client_generated_at = ?, client_generated_status = ? WHERE fid = ?");
|
|
224
|
+
const updateP = db.prepare("UPDATE projects SET client_title = ?, client_body = ?, client_generated_at = ?, client_generated_status = ? WHERE fid = ?");
|
|
225
|
+
|
|
226
|
+
let migrated = 0;
|
|
227
|
+
for (const { fid, notes } of [...actionRows, ...projRows]) {
|
|
228
|
+
if (!notes) continue;
|
|
229
|
+
const copyMatch = notes.match(copyRe);
|
|
230
|
+
const genMatch = notes.match(genRe);
|
|
231
|
+
if (!copyMatch && !genMatch) continue;
|
|
232
|
+
|
|
233
|
+
let title = null, body = null, genAt = null, genStatus = null;
|
|
234
|
+
if (copyMatch) {
|
|
235
|
+
const lines = copyMatch[1].split('\n').map(l => l.trim()).filter(Boolean);
|
|
236
|
+
title = lines[0] || null;
|
|
237
|
+
body = lines.slice(1).join('\n').trim() || null;
|
|
238
|
+
}
|
|
239
|
+
if (genMatch) {
|
|
240
|
+
genAt = genMatch[1] || null;
|
|
241
|
+
genStatus = genMatch[2] || null;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const stmt = actionRows.some(r => r.fid === fid) ? updateA : updateP;
|
|
245
|
+
stmt.run(title, body, genAt, genStatus, fid);
|
|
246
|
+
migrated++;
|
|
247
|
+
}
|
|
248
|
+
if (migrated > 0) {
|
|
249
|
+
results.push(`data-migration: migrated client-facing copy for ${migrated} row(s)`);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
} catch (e) {
|
|
253
|
+
results.push(`data-migration: client copy migration failed — ${e.message}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
148
256
|
db.close();
|
|
149
257
|
} catch (e) {
|
|
150
258
|
if (/Cannot find module|MODULE_NOT_FOUND/.test(e.message || '')) {
|
package/package.json
CHANGED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Change-Impact QA Dimensions — starter template.
|
|
2
|
+
#
|
|
3
|
+
# To ACTIVATE the change-impact checklist, copy this file to:
|
|
4
|
+
# .claude/cabinet/qa-dimensions.yaml
|
|
5
|
+
# and customize the dimensions for your project. The /execute
|
|
6
|
+
# post-impl-checklist phase looks for `qa-dimensions.yaml` (no
|
|
7
|
+
# `-template` suffix) and stays silent until that file exists.
|
|
8
|
+
#
|
|
9
|
+
# How it works: after implementation, the phase reads the git diff,
|
|
10
|
+
# matches each changed file against every dimension's `paths` globs,
|
|
11
|
+
# and surfaces the matched dimensions' checks as context for the
|
|
12
|
+
# pre-commit cabinet sweep (Checkpoint 3). QA is the primary consumer.
|
|
13
|
+
#
|
|
14
|
+
# ── Schema ────────────────────────────────────────────────────────
|
|
15
|
+
# dimensions: # top-level map; keys are dimension names
|
|
16
|
+
# <dimension-name>:
|
|
17
|
+
# paths: # list of glob patterns (REQUIRED, >=1)
|
|
18
|
+
# - "glob/pattern/**"
|
|
19
|
+
# severity: high # high | moderate | info (REQUIRED)
|
|
20
|
+
# checks: # list of checks (REQUIRED, >=1)
|
|
21
|
+
# - tag: run # run | review
|
|
22
|
+
# check: "text" # what to verify
|
|
23
|
+
#
|
|
24
|
+
# ── Glob matching rules (canonical — the phase follows these) ───────
|
|
25
|
+
# * A leading "./" is stripped from both pattern and path before
|
|
26
|
+
# matching. Diff paths are repo-relative with no "./".
|
|
27
|
+
# * "*" matches within ONE path segment (no "/"). "src/api/*"
|
|
28
|
+
# matches "src/api/foo.js" but NOT "src/api/v2/foo.js".
|
|
29
|
+
# * "**" matches across segments (subtree). "src/api/**" matches
|
|
30
|
+
# "src/api/foo.js" AND "src/api/v2/foo.js".
|
|
31
|
+
# * A trailing "/" means "this directory and below": "src/api/" is
|
|
32
|
+
# treated as "src/api/**".
|
|
33
|
+
# * A bare extension glob like "*.md" is UNROOTED — it matches that
|
|
34
|
+
# extension at any depth. For root-only, use an explicit prefix:
|
|
35
|
+
# "README.md" or "docs/*.md" instead of bare "*.md".
|
|
36
|
+
#
|
|
37
|
+
# ── severity meanings ───────────────────────────────────────────────
|
|
38
|
+
# high — a miss here ships a real bug; sweep should treat as blocking
|
|
39
|
+
# moderate — worth checking; sweep treats as advisory
|
|
40
|
+
# info — reminder/nudge; never blocking
|
|
41
|
+
#
|
|
42
|
+
# The examples below are illustrative. Delete or replace them.
|
|
43
|
+
|
|
44
|
+
dimensions:
|
|
45
|
+
data-coherence:
|
|
46
|
+
paths:
|
|
47
|
+
- "**/pib-db*.mjs"
|
|
48
|
+
- "**/*schema*"
|
|
49
|
+
- "**/*migration*"
|
|
50
|
+
severity: high
|
|
51
|
+
checks:
|
|
52
|
+
- tag: run
|
|
53
|
+
check: "Run schema validation if any schema or migration file changed."
|
|
54
|
+
- tag: review
|
|
55
|
+
check: "Verify referential integrity for any new foreign keys or cross-store references."
|
|
56
|
+
- tag: review
|
|
57
|
+
check: "Confirm any migration handles existing rows, not just fresh installs."
|
|
58
|
+
|
|
59
|
+
api-drift:
|
|
60
|
+
paths:
|
|
61
|
+
- "**/index.mjs"
|
|
62
|
+
- "lib/*.js"
|
|
63
|
+
severity: high
|
|
64
|
+
checks:
|
|
65
|
+
- tag: run
|
|
66
|
+
check: "Grep the changed files for exported symbols; confirm the public surface is intentional."
|
|
67
|
+
- tag: review
|
|
68
|
+
check: "Check downstream consumers of any changed or removed export."
|
|
69
|
+
|
|
70
|
+
knowledge-layer:
|
|
71
|
+
paths:
|
|
72
|
+
- "templates/skills/**"
|
|
73
|
+
- "templates/engagement/**"
|
|
74
|
+
severity: moderate
|
|
75
|
+
checks:
|
|
76
|
+
- tag: review
|
|
77
|
+
check: "If user-facing behavior or vocabulary changed, check whether app-guide.md needs updating."
|
|
@@ -40,6 +40,13 @@ slash commands for CC maintenance.
|
|
|
40
40
|
|
|
41
41
|
These hold for every SKILL.md regardless of type.
|
|
42
42
|
|
|
43
|
+
**Runtime output formatting lives in a sibling doc.** This doc covers
|
|
44
|
+
*write-time* authoring. For *runtime* user-facing output — when a running
|
|
45
|
+
skill should present choices via the native `AskUserQuestion` tool versus
|
|
46
|
+
prose versus a markdown table, plus the AskUserQuestion schema facts
|
|
47
|
+
authors must get right — see `skill-output-conventions.md`. Follow that
|
|
48
|
+
pointer whenever a skill prompts the user to make a decision.
|
|
49
|
+
|
|
43
50
|
### Body under 500 lines
|
|
44
51
|
|
|
45
52
|
The SKILL.md **body** — content after the closing `---` of
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# Skill Output Conventions
|
|
2
|
+
|
|
3
|
+
How skills format **user-facing output** and **interactive prompts** at
|
|
4
|
+
runtime. The companion to `skill-best-practices.md`.
|
|
5
|
+
|
|
6
|
+
## 1. Purpose & Scope
|
|
7
|
+
|
|
8
|
+
`skill-best-practices.md` governs **write-time** authoring rules — file
|
|
9
|
+
structure, naming, frontmatter, length. This doc governs **runtime**
|
|
10
|
+
interaction: how a skill, while executing, presents choices and
|
|
11
|
+
information to the user. `output-contract.md` is a third, separate
|
|
12
|
+
concern — the JSON cabinet members emit during audits, not user-facing
|
|
13
|
+
output.
|
|
14
|
+
|
|
15
|
+
Stating the boundary keeps the three docs from drifting together: if a
|
|
16
|
+
rule is about *how a SKILL.md is written*, it belongs in
|
|
17
|
+
skill-best-practices; if it's about *what the running skill shows the
|
|
18
|
+
user*, it belongs here.
|
|
19
|
+
|
|
20
|
+
## 2. When to Use AskUserQuestion
|
|
21
|
+
|
|
22
|
+
`AskUserQuestion` is the native interactive primitive — a structured
|
|
23
|
+
menu of 2-4 options the user picks from. Use it when **all** of these
|
|
24
|
+
hold:
|
|
25
|
+
|
|
26
|
+
- The choice is among **discrete options** (not free text).
|
|
27
|
+
- The answer **branches the skill's behavior** (it's a real decision).
|
|
28
|
+
- There are **2-4 mutually-exclusive choices** (or a small set if
|
|
29
|
+
multiSelect).
|
|
30
|
+
- It is **not an obvious-default confirm** — a yes/no where one answer
|
|
31
|
+
is plainly expected (see §5).
|
|
32
|
+
- The item count is **bounded and small** — not a variable-length loop
|
|
33
|
+
(see §4).
|
|
34
|
+
|
|
35
|
+
If any answer is "no," use prose (§5) or a table (§6) instead.
|
|
36
|
+
|
|
37
|
+
## 3. Schema Facts Authors Must Get Right
|
|
38
|
+
|
|
39
|
+
Verified against the live schema. Get these wrong and the call fails or
|
|
40
|
+
behaves unexpectedly.
|
|
41
|
+
|
|
42
|
+
- **1-4 questions per call; 2-4 options per question.** Outside this
|
|
43
|
+
range fails validation.
|
|
44
|
+
- **`header` is REQUIRED** — a short chip/tag, **max 12 chars** (e.g.
|
|
45
|
+
`Branch`, `Verdict`, `Decision`). Omitting it fails validation. This
|
|
46
|
+
is the most common authoring mistake.
|
|
47
|
+
- **Cost is per-session, not per-call.** AskUserQuestion is a deferred
|
|
48
|
+
tool: the first use in a session loads its schema (one ToolSearch
|
|
49
|
+
round-trip, ~200 tokens); every call after that is free. Don't
|
|
50
|
+
under-use it to "save" a cost that's already paid.
|
|
51
|
+
- **`multiSelect` is required.** `false` for mutually-exclusive choices
|
|
52
|
+
(the norm — one answer). `true` only when the user may legitimately
|
|
53
|
+
pick more than one.
|
|
54
|
+
- **"Other" is auto-added** by the harness. Never add an "Other" /
|
|
55
|
+
"Something else" option manually — it duplicates.
|
|
56
|
+
- **No reliable preview/comparison field.** The official schema has no
|
|
57
|
+
dependable preview surface; don't build conventions on preview
|
|
58
|
+
behavior or expect side-by-side rendering.
|
|
59
|
+
- **Unavailable in Task-spawned subagents.** Agents launched via the
|
|
60
|
+
Task tool (execute-group worktree agents, Workflow agents,
|
|
61
|
+
`context:fork`) cannot call AskUserQuestion — they must use prose.
|
|
62
|
+
This warning outlives any current wiring: a skill that runs in the
|
|
63
|
+
main session today may be called from a subagent tomorrow.
|
|
64
|
+
- **Pre-specify each call site** in the SKILL.md prose — exact `header`,
|
|
65
|
+
`question`, and option labels/descriptions. In a long skill body the
|
|
66
|
+
model otherwise drifts to a prose question instead of emitting the
|
|
67
|
+
structured call. (This is an authoring practice — `skill-best-practices.md`
|
|
68
|
+
is the write-time home for it — surfaced here because it's what makes a
|
|
69
|
+
runtime AskUserQuestion call fire reliably.)
|
|
70
|
+
|
|
71
|
+
## 4. Bounded-List Caveat
|
|
72
|
+
|
|
73
|
+
AskUserQuestion is for **decisions**, not **batch processing**. A
|
|
74
|
+
variable-length loop of homogeneous items — reviewing 12 copy edits,
|
|
75
|
+
approving 30 line items — should not become 30 sequential dialogs. Past
|
|
76
|
+
~5 items, prefer a single batch path (present all, take one combined
|
|
77
|
+
response) over N prompts. The fatigue of N near-identical dialogs
|
|
78
|
+
outweighs the structure they add.
|
|
79
|
+
|
|
80
|
+
The test: are these **genuine, distinct decisions** (→ AskUserQuestion,
|
|
81
|
+
one per item up to a small cap) or **homogeneous review items** (→ batch
|
|
82
|
+
path)?
|
|
83
|
+
|
|
84
|
+
## 5. When to Use Prose
|
|
85
|
+
|
|
86
|
+
Use plain conversational prose for:
|
|
87
|
+
|
|
88
|
+
- **Free-text gathering** — names, reasons, descriptions, anything not
|
|
89
|
+
a fixed menu.
|
|
90
|
+
- **Clarifications and reasons** — "why did you defer this?"
|
|
91
|
+
- **Obvious-default confirms** — a yes/no where one answer is plainly
|
|
92
|
+
expected and the other is rare. A structured menu over-formalizes it;
|
|
93
|
+
a prose "Want me to X? (I'll skip if not)" is lighter.
|
|
94
|
+
|
|
95
|
+
## 6. When to Use Markdown Tables
|
|
96
|
+
|
|
97
|
+
Use a table for **rosters, status, or any data with ≥2 columns** —
|
|
98
|
+
where the value is in *comparing rows*, not *choosing one*. The `/menu`
|
|
99
|
+
skill (skills × description) and `/cabinet` (member × domain) are
|
|
100
|
+
canonical examples. A table presents; AskUserQuestion decides. Don't use
|
|
101
|
+
a table to offer a choice, and don't use AskUserQuestion to display a
|
|
102
|
+
list the user isn't picking from.
|
|
103
|
+
|
|
104
|
+
## 7. Option-Block Format
|
|
105
|
+
|
|
106
|
+
When presenting options as prose (subagent context, or >4 options), the
|
|
107
|
+
canonical format is in `templates/skills/onboard/phases/options.md` —
|
|
108
|
+
name, "what it is," "good for," "trade-off" per option. Do not duplicate
|
|
109
|
+
that format here; reference it.
|
|
110
|
+
|
|
111
|
+
## 8. Section Structure & Tone
|
|
112
|
+
|
|
113
|
+
`options.md` is also canonical for tone: **present, don't prescribe.**
|
|
114
|
+
"Here are your options" not "I recommend X." Ground choices in the
|
|
115
|
+
user's actual situation. This applies whether the choice is rendered via
|
|
116
|
+
AskUserQuestion or prose — the primitive changes, the posture doesn't.
|
|
117
|
+
|
|
118
|
+
## 9. Calibration Examples
|
|
119
|
+
|
|
120
|
+
**Before/after — engagement decision items** (the Tier 1 conversion):
|
|
121
|
+
|
|
122
|
+
> Before: "Present the options conversationally and let the client
|
|
123
|
+
> choose."
|
|
124
|
+
> After: For each decision item, one AskUserQuestion call — `header:
|
|
125
|
+
> "Decision"`, `question:` the item's client-facing prompt, `options:`
|
|
126
|
+
> the item's own authored option list, `multiSelect: false`. One call
|
|
127
|
+
> per item; never batched.
|
|
128
|
+
|
|
129
|
+
Why: the items already carry discrete, consultant-authored options, and
|
|
130
|
+
this is the consultant↔client boundary where ambiguity is most expensive
|
|
131
|
+
and the client can't ask for real-time clarification.
|
|
132
|
+
|
|
133
|
+
**Before/after — triage-audit fallback verdict:**
|
|
134
|
+
|
|
135
|
+
> Before: a prose prompt listing "Fix / Defer / Reject / Question" and
|
|
136
|
+
> asking the user to type one.
|
|
137
|
+
> After: AskUserQuestion — `header: "Verdict"`, four options, one per
|
|
138
|
+
> finding, `multiSelect: false`.
|
|
139
|
+
|
|
140
|
+
Why: four discrete mutually-exclusive verdicts that branch behavior —
|
|
141
|
+
this scores well on all five §2 criteria.
|
|
142
|
+
|
|
143
|
+
**EXCLUDED — orient registry-orphan prompt** (do NOT convert):
|
|
144
|
+
|
|
145
|
+
> "Your old project 'deal-v1' seems to have been deleted — want me to
|
|
146
|
+
> remove it from the registry?"
|
|
147
|
+
|
|
148
|
+
This looks like a Remove/Keep choice but is an **obvious-default
|
|
149
|
+
confirm**: the path no longer exists, so removal is plainly expected and
|
|
150
|
+
"keep" is the rare exception. A structured two-option menu over-formalizes
|
|
151
|
+
a routine yes/no. Leave it prose. This is the boundary that stops
|
|
152
|
+
AskUserQuestion from being applied to every binary question in the
|
|
153
|
+
codebase.
|