create-claude-cabinet 0.34.1 → 0.36.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/package.json +1 -1
- package/templates/cabinet/skill-best-practices.md +7 -0
- package/templates/cabinet/skill-output-conventions.md +153 -0
- package/templates/engagement/engagement-schema.md +19 -4
- package/templates/engagement/engagement.mjs +41 -6
- package/templates/skills/cabinet-roster-check/SKILL.md +14 -0
- package/templates/skills/engagement/SKILL.md +40 -10
- package/templates/skills/engagement-add/SKILL.md +13 -2
- package/templates/skills/engagement-create/SKILL.md +27 -8
- package/templates/skills/engagement-sync/SKILL.md +47 -13
- package/templates/skills/orient/SKILL.md +25 -4
- package/templates/skills/triage-audit/SKILL.md +17 -2
package/package.json
CHANGED
|
@@ -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.
|
|
@@ -187,6 +187,14 @@ One or more "why this matters" lines for the client.
|
|
|
187
187
|
blocks dispatch until it is supplied (the internal `text` is never
|
|
188
188
|
shown to a client verbatim).
|
|
189
189
|
|
|
190
|
+
**Literal `\n` normalization.** When notes are written via MCP tools
|
|
191
|
+
that JSON-encode the string, newlines sometimes arrive as literal
|
|
192
|
+
two-character `\n` sequences (backslash + n) instead of real newlines.
|
|
193
|
+
`parseClientCopy` normalizes these before parsing — titles won't contain
|
|
194
|
+
stray `\n` characters. Status suffixes like "— complete" or "— shipped"
|
|
195
|
+
are also stripped from titles since the bucket placement already
|
|
196
|
+
communicates status.
|
|
197
|
+
|
|
190
198
|
The copy block may be human-written or generated-then-approved.
|
|
191
199
|
`/engagement-sync` step 3.5 can auto-draft copy from structured action
|
|
192
200
|
metadata (via `buildGenerationSource`), with the consultant reviewing
|
|
@@ -240,6 +248,9 @@ transport:
|
|
|
240
248
|
type: email # the CHANNEL (email | mcp | file)
|
|
241
249
|
consultant: "oren@example.com" # where client feedback is sent
|
|
242
250
|
|
|
251
|
+
engagement_notes: # persistent notes shown at top of every packet
|
|
252
|
+
- "All email logic is built and tested on staging — real delivery activates once Postmark is configured."
|
|
253
|
+
|
|
243
254
|
sections: # the credential checklist = one section
|
|
244
255
|
- key: go_live
|
|
245
256
|
title: "Go-Live Credentials"
|
|
@@ -274,8 +285,10 @@ A **packet** is a per-recipient projection of engagement state. Fields:
|
|
|
274
285
|
engagement, packet_id, recipient, role, generated_at,
|
|
275
286
|
needs_you: [ { ref, title, why, kind, options } ],
|
|
276
287
|
in_flight, shipped, delegated,
|
|
277
|
-
billing,
|
|
278
|
-
messages, new_since_last, generated_label
|
|
288
|
+
billing, # present ONLY if roles[role].billing === true
|
|
289
|
+
messages, new_since_last, generated_label,
|
|
290
|
+
engagement_notes, # persistent consultant-authored notes (from config)
|
|
291
|
+
checklist_summary # [ { type, count } ] when engagement.yaml has sections
|
|
279
292
|
```
|
|
280
293
|
|
|
281
294
|
- The client **never** sees `action_fid` — items are referenced by an
|
|
@@ -306,13 +319,15 @@ The pure render engine (`engagement.mjs`, imports nothing — the adapter
|
|
|
306
319
|
boundary) pins these behaviors so Phase 3/4 can build on them without
|
|
307
320
|
reopening Phase 1:
|
|
308
321
|
|
|
309
|
-
- **`renderPacket` returns `{ packet, refmap, notClientReady }`.**
|
|
322
|
+
- **`renderPacket` returns `{ packet, refmap, notClientReady, allDropped }`.**
|
|
310
323
|
`packet` is the client projection, built without `action_fid`/`_refmap`
|
|
311
324
|
ever added (leaking is impossible by construction, not by a strip step).
|
|
312
325
|
`refmap` is the consultant-side `ref → action_fid` map. The caller writes
|
|
313
326
|
`{ ...packet, _refmap: refmap }` to `<recipient>-sent.json` and delivers
|
|
314
327
|
`packet` alone. `notClientReady` lists fids of client-visible actions
|
|
315
|
-
dropped because they lacked a client-facing copy block.
|
|
328
|
+
dropped because they lacked a client-facing copy block. `allDropped` is
|
|
329
|
+
`true` when ALL visible items were dropped (likely a missing-notes-column
|
|
330
|
+
query bug) — the caller should halt dispatch, not send an empty packet.
|
|
316
331
|
- **Not-client-ready items are dropped, never rendered with internal text.**
|
|
317
332
|
A `client-visible` action with no `<!-- client-facing -->` block is
|
|
318
333
|
excluded from the packet (and reported in `notClientReady`) — the engine
|
|
@@ -16,6 +16,9 @@
|
|
|
16
16
|
const KNOWN_TAG_PREFIXES = ['audience', 'scope', 'assignee', 'needs'];
|
|
17
17
|
const KNOWN_BARE_TAGS = ['client-visible'];
|
|
18
18
|
|
|
19
|
+
// Trailing status phrases that are redundant with bucket placement.
|
|
20
|
+
const STATUS_SUFFIX_RE = /\s*[—–-]\s*(?:complete|completed|shipped|done|planned|in progress|started|delivered)\s*(?:\(.*\))?\s*$/i;
|
|
21
|
+
|
|
19
22
|
// pib-db status → plain English shown to clients (never the raw enum).
|
|
20
23
|
const STATUS_LABELS = {
|
|
21
24
|
open: 'Not started',
|
|
@@ -82,8 +85,9 @@ export function loadEngagement(config) {
|
|
|
82
85
|
|
|
83
86
|
const transport = config.transport || { type: 'email', consultant: null };
|
|
84
87
|
const sections = Array.isArray(config.sections) ? config.sections : [];
|
|
88
|
+
const engagement_notes = config.engagement_notes || null;
|
|
85
89
|
|
|
86
|
-
return { engagement, billing, recipients, roles, transport, sections };
|
|
90
|
+
return { engagement, billing, recipients, roles, transport, sections, engagement_notes };
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
export function normalizeRecipient(r) {
|
|
@@ -171,8 +175,11 @@ export function parseClientCopy(notes) {
|
|
|
171
175
|
const close = notes.indexOf('-->', bodyStart);
|
|
172
176
|
if (close === -1) return null;
|
|
173
177
|
|
|
174
|
-
const
|
|
175
|
-
if (!
|
|
178
|
+
const raw = notes.slice(bodyStart, close).trim();
|
|
179
|
+
if (!raw) return null;
|
|
180
|
+
// Normalize literal two-char \n sequences (from double-escaping during
|
|
181
|
+
// JSON-encoded MCP writes) to real newlines before splitting.
|
|
182
|
+
const inner = raw.replace(/\\n/g, '\n');
|
|
176
183
|
const lines = inner.split('\n').map(l => l.trim()).filter(Boolean);
|
|
177
184
|
if (lines.length === 0) return null;
|
|
178
185
|
|
|
@@ -361,7 +368,8 @@ export function renderPacket(config, actions, events, billing, recipient, prevPa
|
|
|
361
368
|
throw new Error(`renderPacket: stableRef collision — ${refmap[ref]} and ${a.fid} both hash to ${ref}`);
|
|
362
369
|
}
|
|
363
370
|
refmap[ref] = a.fid;
|
|
364
|
-
const
|
|
371
|
+
const title = copy.client_title.replace(STATUS_SUFFIX_RE, '');
|
|
372
|
+
const item = { ref, title, why: copy.client_why };
|
|
365
373
|
|
|
366
374
|
if (a.completed || a.status === 'done') {
|
|
367
375
|
// Completed wins over needs: a done item is never shown as pending.
|
|
@@ -408,7 +416,8 @@ export function renderPacket(config, actions, events, billing, recipient, prevPa
|
|
|
408
416
|
}
|
|
409
417
|
// Construction-not-strip: explicit field listing only. Never spread from
|
|
410
418
|
// project row. fid/project_fid/name/notes/tags are never assigned.
|
|
411
|
-
const
|
|
419
|
+
const rollupTitle = copy.client_title.replace(STATUS_SUFFIX_RE, '');
|
|
420
|
+
const rollupItem = { title: rollupTitle, why: copy.client_why, status: rollupStatus, feedbackable: false };
|
|
412
421
|
const parsedProjectTags = parseActionTags(p.tags);
|
|
413
422
|
if (p.status === 'done' || (children.length > 0 && children.every(c => c.completed || c.status === 'done'))) {
|
|
414
423
|
shipped.push(rollupItem);
|
|
@@ -419,6 +428,29 @@ export function renderPacket(config, actions, events, billing, recipient, prevPa
|
|
|
419
428
|
}
|
|
420
429
|
}
|
|
421
430
|
|
|
431
|
+
// Silent-empty signal: if there were visible actions but ALL of them landed
|
|
432
|
+
// in notClientReady, the caller likely used a query that omits the notes
|
|
433
|
+
// column (e.g. pib_list_actions instead of pib_query). Signal so the
|
|
434
|
+
// caller can halt dispatch instead of silently sending an empty packet.
|
|
435
|
+
const allDropped = visible.length > 0 && notClientReady.length === visible.length;
|
|
436
|
+
|
|
437
|
+
// Checklist summary: sections parsed from engagement.yaml but NOT rendered as
|
|
438
|
+
// bucket items (those are interactive via /engagement). Include a summary so
|
|
439
|
+
// the preview shows checklist coverage.
|
|
440
|
+
const sections = config.sections || [];
|
|
441
|
+
const checklistSummary = sections.length > 0
|
|
442
|
+
? sections.map(s => {
|
|
443
|
+
const items = Array.isArray(s.items) ? s.items : [];
|
|
444
|
+
return { type: s.type || s.label || 'unknown', count: items.length };
|
|
445
|
+
})
|
|
446
|
+
: null;
|
|
447
|
+
|
|
448
|
+
// Engagement-wide notes: persistent messages from the consultant that appear
|
|
449
|
+
// at the top of every packet (e.g. caveats, disclaimers, contextual notes).
|
|
450
|
+
const engagementNotes = Array.isArray(config.engagement_notes) ? config.engagement_notes
|
|
451
|
+
: typeof config.engagement_notes === 'string' ? [config.engagement_notes]
|
|
452
|
+
: null;
|
|
453
|
+
|
|
422
454
|
// Messages: engagement-level events (target_fid null) plus events whose
|
|
423
455
|
// target action is still live. Events for soft-deleted / unknown actions
|
|
424
456
|
// are dropped (defensive — even if the caller passed them through).
|
|
@@ -452,6 +484,9 @@ export function renderPacket(config, actions, events, billing, recipient, prevPa
|
|
|
452
484
|
new_since_last,
|
|
453
485
|
};
|
|
454
486
|
|
|
487
|
+
if (engagementNotes) packet.engagement_notes = engagementNotes;
|
|
488
|
+
if (checklistSummary) packet.checklist_summary = checklistSummary;
|
|
489
|
+
|
|
455
490
|
// billing only for roles configured to receive it, and only when billing is
|
|
456
491
|
// actually enabled (a config.billing with enabled:false must not produce a
|
|
457
492
|
// "$0.00" section; a computed renderBilling block has no `enabled` field, so
|
|
@@ -465,7 +500,7 @@ export function renderPacket(config, actions, events, billing, recipient, prevPa
|
|
|
465
500
|
// roles, defined role). Defense-in-depth so the dispatch path can't skip it.
|
|
466
501
|
assertPacketInvariants(packet, recipient, roles);
|
|
467
502
|
|
|
468
|
-
return { packet, refmap, notClientReady };
|
|
503
|
+
return { packet, refmap, notClientReady, allDropped };
|
|
469
504
|
}
|
|
470
505
|
|
|
471
506
|
function extractOptions(action) {
|
|
@@ -321,6 +321,20 @@ information-design's mock output. This should be sequential: designer
|
|
|
321
321
|
produces mock, then usability critiques the interaction model using the
|
|
322
322
|
mock as input."
|
|
323
323
|
|
|
324
|
+
**Good (output conventions — prose where AskUserQuestion fits):** "/triage-audit's
|
|
325
|
+
CLI-fallback path prompts the user to type one of Fix / Defer / Reject /
|
|
326
|
+
Question. These are four discrete, mutually-exclusive verdicts that branch
|
|
327
|
+
behavior — the canonical case for the native `AskUserQuestion` tool per
|
|
328
|
+
`skill-output-conventions.md` §2. Rendering them as a prose 'type one of…'
|
|
329
|
+
prompt is a guidance-layer drift. Flag as a finding pointing to that doc."
|
|
330
|
+
Do NOT raise this finding for: free-text gathering (names, reasons), open
|
|
331
|
+
clarifying questions, obvious-default confirms (e.g. orient's "remove the
|
|
332
|
+
deleted project from the registry?" — removal is plainly expected, so prose
|
|
333
|
+
is correct), or variable-length per-item loops of homogeneous items (those
|
|
334
|
+
prefer a batch path, not N dialogs). Match the finding to the doc's §2
|
|
335
|
+
checklist; if any checklist item fails, prose is the right call and there is
|
|
336
|
+
no finding.
|
|
337
|
+
|
|
324
338
|
**Too narrow (belongs to another cabinet member):** "The deploy script has a
|
|
325
339
|
race condition." That's technical-debt or architecture territory.
|
|
326
340
|
|
|
@@ -66,7 +66,14 @@ note before anything else:
|
|
|
66
66
|
Always show the `generated_label` ("as of <date>") as a header so
|
|
67
67
|
staleness is visible even under the threshold.
|
|
68
68
|
|
|
69
|
-
### 3.
|
|
69
|
+
### 3. Engagement notes, messages, then work
|
|
70
|
+
|
|
71
|
+
If the packet carries `engagement_notes` (persistent consultant-authored
|
|
72
|
+
notes), render them first in a distinct block before any messages or items.
|
|
73
|
+
|
|
74
|
+
If the packet carries `checklist_summary`, show a single summary line
|
|
75
|
+
after engagement notes: "Plus N checklist items (X decisions, Y credentials,
|
|
76
|
+
...) — these appear when you run `/engagement` interactively."
|
|
70
77
|
|
|
71
78
|
Render the **messages** feed distinctly from action items — each with an
|
|
72
79
|
explicit sender and timestamp, in order:
|
|
@@ -74,16 +81,23 @@ explicit sender and timestamp, in order:
|
|
|
74
81
|
> **Oren · May 30** — "Quick note: the hosting migration is scheduled
|
|
75
82
|
> for next week."
|
|
76
83
|
|
|
77
|
-
Then render the work
|
|
78
|
-
|
|
79
|
-
- **
|
|
80
|
-
|
|
84
|
+
Then render the work with **visual structure**:
|
|
85
|
+
|
|
86
|
+
- **Needs your attention (N)** (`needs_you`) — lead with these. Use a
|
|
87
|
+
clear heading with the count.
|
|
88
|
+
- **In progress (N)** (`in_flight`) — each item gets an arrow indicator.
|
|
89
|
+
- **Done (N)** (`shipped`) — each item gets a checkmark indicator.
|
|
81
90
|
- **Handled for you** (`delegated`) — place LAST, collapsed to a one-line
|
|
82
91
|
summary ("Sydney is handling 3 items (marketing)"); offer to expand on
|
|
83
92
|
request. It's reassurance, not a to-do.
|
|
84
93
|
|
|
85
|
-
|
|
86
|
-
|
|
94
|
+
**Formatting rules:**
|
|
95
|
+
- Every section gets a header with a count: "**Done (12)**" not just a
|
|
96
|
+
bare list.
|
|
97
|
+
- Items in `new_since_last` are marked with "(new)" after the title.
|
|
98
|
+
- Show `billing` only if the packet carries it (principals only). Format
|
|
99
|
+
billing as a clear table (Date | Hours | Work), with rate and total at
|
|
100
|
+
the bottom — not a raw JSON dump.
|
|
87
101
|
|
|
88
102
|
### 4. Collect responses (respectful batch review)
|
|
89
103
|
|
|
@@ -109,9 +123,25 @@ in `needs_you` was resolved upstream; don't nag about it.)
|
|
|
109
123
|
Then split `needs_you` into two groups:
|
|
110
124
|
|
|
111
125
|
- **Decisions** — items with a populated `options` list (or
|
|
112
|
-
`kind === 'decision'`). These are **never** batch-approved
|
|
113
|
-
one individually
|
|
114
|
-
|
|
126
|
+
`kind === 'decision'`). These are **never** batch-approved — walk each
|
|
127
|
+
one individually with exactly **one `AskUserQuestion` call per decision
|
|
128
|
+
item**. Never bundle two decisions into a single call. Pre-specify the
|
|
129
|
+
call this way:
|
|
130
|
+
- `header`: `"Decision"` (within the 12-char cap).
|
|
131
|
+
- `question`: the item's client-facing prompt/title.
|
|
132
|
+
- `options`: the item's own `options` list, one labeled choice each.
|
|
133
|
+
**If an item has more than 4 options**, AskUserQuestion can't hold
|
|
134
|
+
them — fall back to presenting that item's options as prose and let
|
|
135
|
+
the client answer in their own words.
|
|
136
|
+
- `multiSelect: false` — a single verdict per decision.
|
|
137
|
+
- Do **not** add an "Other" choice; the harness adds it automatically.
|
|
138
|
+
If the client picks "Other" or says something off-menu, handle it
|
|
139
|
+
conversationally and map to the right verdict (see the table below).
|
|
140
|
+
|
|
141
|
+
A real decision always gets explicit engagement. (AskUserQuestion is
|
|
142
|
+
available here because `/engagement` runs in the main session, not a
|
|
143
|
+
subagent. See `.claude/cabinet/skill-output-conventions.md` for the
|
|
144
|
+
conventions behind this.)
|
|
115
145
|
- **Approvals / FYIs** — items with no options. These are eligible for
|
|
116
146
|
the batch default.
|
|
117
147
|
|
|
@@ -32,8 +32,16 @@ Access pib-db via [pib-db-access.md](../../cabinet/pib-db-access.md).
|
|
|
32
32
|
- "What's the item?" (internal `text`)
|
|
33
33
|
- "Who should see it?" — map to `audience:<id>` (and, for a delegate,
|
|
34
34
|
`assignee:<id>` — the **sole** basis for a delegate seeing it).
|
|
35
|
-
-
|
|
36
|
-
|
|
35
|
+
- Collect item type via `AskUserQuestion`:
|
|
36
|
+
- `header`: `"Item type"`
|
|
37
|
+
- `question`: "What kind of item is this?"
|
|
38
|
+
- `options`:
|
|
39
|
+
- Decision — "Client must choose among options you provide"
|
|
40
|
+
- Credential — "Client must hand over a secret (secure dialog)"
|
|
41
|
+
- Neither — "Informational or approval item, no special handling"
|
|
42
|
+
- `multiSelect: false`
|
|
43
|
+
Map: Decision → tag `needs:decision`. Credential → tag
|
|
44
|
+
`needs:credential`. Neither → no needs tag.
|
|
37
45
|
- "What scope?" (optional `scope:<scope>`)
|
|
38
46
|
- To make a **project itself** visible to clients as a roll-up
|
|
39
47
|
(e.g., "E-signing: Done"), tag the project `client-visible` and author
|
|
@@ -50,6 +58,9 @@ Access pib-db via [pib-db-access.md](../../cabinet/pib-db-access.md).
|
|
|
50
58
|
- Always include `client-visible` (an item with no `client-visible`
|
|
51
59
|
tag appears in **no** packet).
|
|
52
60
|
|
|
61
|
+
(See `.claude/cabinet/skill-output-conventions.md` for when structured
|
|
62
|
+
choices apply.)
|
|
63
|
+
|
|
53
64
|
3. **Prompt for the client-facing copy block** (optional — the
|
|
54
65
|
consultant can write it now or let `/engagement-sync` draft it):
|
|
55
66
|
- "Want to write the client-facing copy now, or let `/engagement-sync`
|
|
@@ -53,14 +53,24 @@ id, name, email, and their role."
|
|
|
53
53
|
For each recipient, capture:
|
|
54
54
|
- **id** — short handle (e.g. `ed`, `sydney`), used in tags
|
|
55
55
|
- **name**, **email**
|
|
56
|
-
- **role** —
|
|
57
|
-
|
|
56
|
+
- **role** — collect via `AskUserQuestion`:
|
|
57
|
+
- `header`: `"Role"`
|
|
58
|
+
- `question`: "What role for [name]?"
|
|
59
|
+
- `options`:
|
|
60
|
+
- Principal — "Sees everything, gets billing, defaults to interactive delivery"
|
|
61
|
+
- Delegate — "Sees only assigned items, no billing, defaults to email"
|
|
62
|
+
- `multiSelect: false`
|
|
58
63
|
- **scopes** — topical areas they care about (e.g. `[marketing]`); a
|
|
59
64
|
principal is usually `[all]`
|
|
60
|
-
- **has_claude_code** —
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
65
|
+
- **has_claude_code** — collect via `AskUserQuestion`:
|
|
66
|
+
- `header`: `"Delivery"`
|
|
67
|
+
- `question`: "Does [name] have Claude Code installed?"
|
|
68
|
+
- `options`:
|
|
69
|
+
- Yes — "Interactive /engagement view with rich status"
|
|
70
|
+
- No — "Formatted email summary instead"
|
|
71
|
+
- `multiSelect: false`
|
|
72
|
+
(Principals default to yes, delegates default to no — use as the
|
|
73
|
+
pre-selected suggestion in the question phrasing.)
|
|
64
74
|
|
|
65
75
|
The default `roles` block is:
|
|
66
76
|
```yaml
|
|
@@ -86,8 +96,17 @@ confirmation before proceeding.
|
|
|
86
96
|
"Do you bill time on this engagement?" If yes:
|
|
87
97
|
- **timelog** path (default `./timelog.md`)
|
|
88
98
|
- **rate** (default `190`), **currency** (default `USD`)
|
|
89
|
-
- **period** —
|
|
90
|
-
|
|
99
|
+
- **period** — collect via `AskUserQuestion`:
|
|
100
|
+
- `header`: `"Billing"`
|
|
101
|
+
- `question`: "How should billing be calculated?"
|
|
102
|
+
- `options`:
|
|
103
|
+
- Running total — "Cumulative from engagement start (default)"
|
|
104
|
+
- Monthly — "Reset each calendar month"
|
|
105
|
+
- Since last sync — "Only time since the last packet was sent"
|
|
106
|
+
- `multiSelect: false`
|
|
107
|
+
|
|
108
|
+
(See `.claude/cabinet/skill-output-conventions.md` for when structured
|
|
109
|
+
choices apply.)
|
|
91
110
|
|
|
92
111
|
Billing appears only in `principal`-role packets.
|
|
93
112
|
|
|
@@ -150,15 +150,33 @@ For every `client-visible` action or project **lacking** a `<!-- client-facing -
|
|
|
150
150
|
summary: "N items need new copy (no block). M items have stale copy
|
|
151
151
|
(status changed). Total: N+M items to review."
|
|
152
152
|
|
|
153
|
-
**Batch option for large sets.** If more than 5 items need review,
|
|
154
|
-
|
|
155
|
-
|
|
153
|
+
**Batch option for large sets.** If more than 5 items need review,
|
|
154
|
+
collect the choice via `AskUserQuestion`:
|
|
155
|
+
- `header`: `"Review"`
|
|
156
|
+
- `question`: "N items need copy review. How to proceed?"
|
|
157
|
+
- `options`:
|
|
158
|
+
- Individual — "One at a time, decide each separately"
|
|
159
|
+
- Batch — "Show all drafts at once, confirm/edit by number"
|
|
160
|
+
- `multiSelect: false`
|
|
156
161
|
|
|
157
162
|
**Skip if block exists.** An existing block (hand-written or previously approved) is authoritative. Never overwrite it. Check: `parseClientCopy(notes) !== null → skip`.
|
|
158
163
|
|
|
159
164
|
**Draft from structured data.** Call `buildGenerationSource(item)` (from `engagement.mjs`) to get the structured metadata — status, completion, needs, options. For projects, pre-compute `child_summary` via `computeChildSummary(actions, project.fid)` and attach it to the project object before calling. Claude drafts a `client_title` + `client_why` from this metadata. The internal `text` field is NEVER an input to generation.
|
|
160
165
|
|
|
161
|
-
**Per-item review loop (not batch approve).** For each generated draft,
|
|
166
|
+
**Per-item review loop (not batch approve).** For each generated draft,
|
|
167
|
+
show the consultant the structured source data and the generated title +
|
|
168
|
+
why, then collect the verdict via `AskUserQuestion`:
|
|
169
|
+
- `header`: `"Copy"`
|
|
170
|
+
- `question`: "[item title] — accept this draft?"
|
|
171
|
+
- `options`:
|
|
172
|
+
- Accept — "Use this copy as-is"
|
|
173
|
+
- Edit — "I'll provide revised text"
|
|
174
|
+
- Skip — "Leave without copy (dispatch gate will drop it)"
|
|
175
|
+
- `multiSelect: false`
|
|
176
|
+
|
|
177
|
+
Accepted or edited copy proceeds to write-back. Skipped items remain
|
|
178
|
+
without copy (the dispatch gate drops them). If "Edit" is chosen, ask
|
|
179
|
+
for the revised text conversationally, then write back.
|
|
162
180
|
|
|
163
181
|
**Write-back.** On approval, call `spliceClientCopy(notes, newBlock, markerLine)` (from `engagement.mjs`) to produce the updated notes. `newBlock` is the `<!-- client-facing ... -->` block. `markerLine` is `<!-- cc-generated:<ISO timestamp> status:<current status> -->` — placed OUTSIDE the copy block (never inside, or `parseClientCopy` would include it in `client_why`). Write via `pib_update_action({ fid, notes: splicedNotes })` or `pib_update_project({ fid, notes: splicedNotes })`.
|
|
164
182
|
|
|
@@ -180,12 +198,19 @@ For each recipient: load their previous packet from
|
|
|
180
198
|
`engagement-packets/<recipient>-sent.json` (if any) as `prevPacket`,
|
|
181
199
|
then:
|
|
182
200
|
```
|
|
183
|
-
const { packet, refmap, notClientReady } = renderPacket(config, actions, events, billing, recipient, prevPacket, projects)
|
|
201
|
+
const { packet, refmap, notClientReady, allDropped } = renderPacket(config, actions, events, billing, recipient, prevPacket, projects)
|
|
184
202
|
```
|
|
185
203
|
`notClientReady` reconfirms (defensively) the not-client-ready items the
|
|
186
204
|
engine dropped — they never reach the client (no internal text leaks).
|
|
187
205
|
Fold these into the step-3 block report.
|
|
188
206
|
|
|
207
|
+
**`allDropped` guard:** If `allDropped` is true, **STOP** — do not
|
|
208
|
+
proceed to dispatch. Every visible item was dropped because it lacked
|
|
209
|
+
client-facing copy. This almost always means the query used
|
|
210
|
+
`pib_list_actions` (which omits `notes`) instead of `pib_query` with
|
|
211
|
+
`SELECT *`. Surface: "Empty packet — all N client-visible items are
|
|
212
|
+
missing client-facing copy. Did you use pib_query with SELECT *?"
|
|
213
|
+
|
|
189
214
|
### 5. Hard gate
|
|
190
215
|
|
|
191
216
|
`assertPacketInvariants(packet, recipient, config.roles)`. If it throws
|
|
@@ -211,14 +236,23 @@ For project roll-ups, show: `E-signing (roll-up: 3 done, 1 in progress)`
|
|
|
211
236
|
vs action items: `DNS cutover (action, needs:decision)`. For projects
|
|
212
237
|
with individually-visible children, note the count.
|
|
213
238
|
|
|
214
|
-
Then
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
-
|
|
218
|
-
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
-
|
|
239
|
+
Then collect the dispatch decision via `AskUserQuestion`:
|
|
240
|
+
- `header`: `"Dispatch"`
|
|
241
|
+
- `question`: "Ready to send to N recipients?"
|
|
242
|
+
- `options`:
|
|
243
|
+
- Send — "Archive and dispatch all packets now"
|
|
244
|
+
- Review — "Show me a recipient's packet in detail first"
|
|
245
|
+
- Cancel — "Abort this sync, nothing sent"
|
|
246
|
+
- `multiSelect: false`
|
|
247
|
+
|
|
248
|
+
- **Send** → proceed to archive + dispatch (steps 7–9).
|
|
249
|
+
- **Review** → ask which recipient, render their full packet (needs_you,
|
|
250
|
+
in_flight, shipped, messages, billing), then re-present this same
|
|
251
|
+
AskUserQuestion (loop until Send or Cancel).
|
|
252
|
+
- **Cancel** → abort, nothing sent.
|
|
253
|
+
|
|
254
|
+
(See `.claude/cabinet/skill-output-conventions.md` for when structured
|
|
255
|
+
choices apply.)
|
|
222
256
|
|
|
223
257
|
This confirmation step is the human-in-the-loop gate — the safety checks
|
|
224
258
|
(assertPacketInvariants) catch structural violations, but only the
|
|
@@ -282,7 +282,19 @@ items are returned, skip silently. Otherwise:
|
|
|
282
282
|
(`triggered | still-waiting | needs-info | condition-obsolete`)
|
|
283
283
|
and brief reasoning in notes.
|
|
284
284
|
4. Include any items that evaluated to `triggered` in the briefing's
|
|
285
|
-
**Attention Items** section.
|
|
285
|
+
**Attention Items** section. For each, collect the user's decision
|
|
286
|
+
via `AskUserQuestion`:
|
|
287
|
+
- `header`: `"Triggered"`
|
|
288
|
+
- `question`: "[fid] appears triggered — [trigger summary]. What to do?"
|
|
289
|
+
- `options`:
|
|
290
|
+
- Reopen — "Change status back to open, ready to work"
|
|
291
|
+
- Leave deferred — "Keep it deferred, not ready yet"
|
|
292
|
+
- Mark obsolete — "Trigger condition no longer relevant"
|
|
293
|
+
- `multiSelect: false`
|
|
294
|
+
|
|
295
|
+
Map: Reopen → status to open; Leave deferred → no-op; Mark obsolete
|
|
296
|
+
→ `pib_mark_trigger_checked` with `condition-obsolete`.
|
|
297
|
+
(See `skill-output-conventions.md`.)
|
|
286
298
|
|
|
287
299
|
**Cost control:** Cap this phase at 30 seconds total. If N > 10 items,
|
|
288
300
|
evaluate only the 5 least-recently-checked.
|
|
@@ -344,9 +356,18 @@ done
|
|
|
344
356
|
Also check `git worktree list` for active worktrees whose branches
|
|
345
357
|
have diverged from main.
|
|
346
358
|
|
|
347
|
-
Surface as advisory
|
|
348
|
-
|
|
349
|
-
|
|
359
|
+
Surface as advisory, then collect the decision via `AskUserQuestion`:
|
|
360
|
+
- `header`: `"Branch"`
|
|
361
|
+
- `question`: "Branch `[name]` has N commits ahead of main. What to do?"
|
|
362
|
+
- `options`:
|
|
363
|
+
- Merge — "Merge into main now"
|
|
364
|
+
- Continue — "Leave it, still working on it"
|
|
365
|
+
- Discard — "Delete the branch (destructive)"
|
|
366
|
+
- `multiSelect: false`
|
|
367
|
+
|
|
368
|
+
Map: Merge → `git merge [branch]` (surface conflicts if any). Continue →
|
|
369
|
+
no-op. Discard → warn "permanently deletes N commits" and require
|
|
370
|
+
explicit second confirmation before `git branch -D`.
|
|
350
371
|
|
|
351
372
|
**Known platform limitation:** The Claude Code Agent tool with
|
|
352
373
|
`isolation: "worktree"` branches from the remote tracking ref, not
|
|
@@ -136,8 +136,23 @@ assumption, evidence, question, and your commentary. The user needs to
|
|
|
136
136
|
see everything to make good decisions.
|
|
137
137
|
|
|
138
138
|
**Fallback (no browser available):** Present findings in the conversation
|
|
139
|
-
grouped by cabinet member, severity-ordered within each group.
|
|
140
|
-
|
|
139
|
+
grouped by cabinet member, severity-ordered within each group. For each
|
|
140
|
+
finding, show its full context (title, description, assumption, evidence,
|
|
141
|
+
question, and your commentary), then collect the verdict via one
|
|
142
|
+
`AskUserQuestion` call:
|
|
143
|
+
|
|
144
|
+
- `header`: `"Verdict"`
|
|
145
|
+
- `question`: the finding's title
|
|
146
|
+
- `options`:
|
|
147
|
+
- Fix — "Real finding, approve for action"
|
|
148
|
+
- Defer — "Real but not now — revisit later"
|
|
149
|
+
- Reject — "Not a real problem for this project"
|
|
150
|
+
- Question — "Need more information before deciding"
|
|
151
|
+
- `multiSelect: false`
|
|
152
|
+
|
|
153
|
+
One call per finding, walked individually within each group. (See
|
|
154
|
+
`.claude/cabinet/skill-output-conventions.md` for when this pattern
|
|
155
|
+
applies.)
|
|
141
156
|
|
|
142
157
|
### 3. Apply Verdicts (core)
|
|
143
158
|
|