create-claude-cabinet 0.37.0 → 0.38.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 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/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'],
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', 'scripts/qa-dimensions-validator.cjs', 'skills/orient/phases/checklist-status.md'],
490
490
  },
491
491
  'compliance': {
492
492
  name: 'Compliance Stack (rules + enforcement)',
@@ -595,7 +595,7 @@ const MODULES = {
595
595
  },
596
596
  engagement: {
597
597
  name: 'Engagement management (with secure credential handoff)',
598
- description: 'Ongoing client-engagement management built on pib-db: per-recipient packets (rendered projections of the work backlog), role-gated billing, client feedback flowing back as events, and secure credential handoff (encrypted capture via OS dialog, pluggable email/MCP/file transport). Seven skills across consultant and client sides. Requires work-tracking (the pib-db adapter is the engine\'s data source).',
598
+ description: 'Ongoing client-engagement management built on pib-db: per-recipient packets (rendered projections of the work backlog), role-gated billing, client feedback flowing back as events, and secure credential handoff (encrypted capture via OS dialog, pluggable email/MCP/file transport). Three primary skills across consultant and client sides, plus redirect stubs. Requires work-tracking (the pib-db adapter is the engine\'s data source).',
599
599
  mandatory: false,
600
600
  default: false,
601
601
  lean: false,
@@ -613,6 +613,7 @@ const MODULES = {
613
613
  'skills/engagement-add',
614
614
  'skills/engagement-status',
615
615
  'skills/engagement-sync',
616
+ 'skills/setup-accounts',
616
617
  'skills/guide',
617
618
  'engagement',
618
619
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-cabinet",
3
- "version": "0.37.0",
3
+ "version": "0.38.0",
4
4
  "description": "Claude Cabinet — opinionated process scaffolding for Claude Code projects",
5
5
  "bin": {
6
6
  "create-claude-cabinet": "bin/create-claude-cabinet.js"
@@ -42,11 +42,17 @@
42
42
  # The examples below are illustrative. Delete or replace them.
43
43
 
44
44
  dimensions:
45
+ # ── Replace the example paths below with your project's real paths. ──
46
+ # The dimension *concepts* are broadly useful; the glob patterns are
47
+ # placeholders. Run /checklist-discover to auto-detect your project's
48
+ # paths and generate a tailored qa-dimensions.yaml.
49
+
45
50
  data-coherence:
46
51
  paths:
47
- - "**/pib-db*.mjs"
48
52
  - "**/*schema*"
49
53
  - "**/*migration*"
54
+ - "**/*.sql"
55
+ - "**/models/**"
50
56
  severity: high
51
57
  checks:
52
58
  - tag: run
@@ -58,8 +64,10 @@ dimensions:
58
64
 
59
65
  api-drift:
60
66
  paths:
67
+ - "**/routes*"
68
+ - "**/api/**"
69
+ - "src/**"
61
70
  - "**/index.mjs"
62
- - "lib/*.js"
63
71
  severity: high
64
72
  checks:
65
73
  - tag: run
@@ -67,11 +75,29 @@ dimensions:
67
75
  - tag: review
68
76
  check: "Check downstream consumers of any changed or removed export."
69
77
 
78
+ test-staleness:
79
+ paths:
80
+ - "**/test*/**"
81
+ - "**/*.test.*"
82
+ - "**/*.spec.*"
83
+ - "**/factories/**"
84
+ - "**/fixtures/**"
85
+ severity: high
86
+ checks:
87
+ - tag: run
88
+ check: "Run the test suite if any test, factory, or fixture file changed."
89
+ - tag: review
90
+ check: "If a model's validations or associations changed, verify factories still produce valid records."
91
+ - tag: review
92
+ check: "If behavior changed, check whether existing specs assert on the old behavior."
93
+
70
94
  knowledge-layer:
71
95
  paths:
72
- - "templates/skills/**"
73
- - "templates/engagement/**"
96
+ - "**/docs/**"
97
+ - "**/*guide*"
98
+ - "**/README*"
99
+ - "**/CHANGELOG*"
74
100
  severity: moderate
75
101
  checks:
76
102
  - tag: review
77
- check: "If user-facing behavior or vocabulary changed, check whether app-guide.md needs updating."
103
+ check: "If user-facing behavior or vocabulary changed, check whether docs or guides need updating."
@@ -438,12 +438,13 @@ not a filter boundary.
438
438
 
439
439
  ## 10. Skills
440
440
 
441
- Two primary skills, plus redirect stubs for backward compatibility:
441
+ Three primary skills, plus redirect stubs for backward compatibility:
442
442
 
443
443
  | Skill | Side | Subcommands |
444
444
  | ----- | ---- | ----------- |
445
445
  | `/collab-client` | Client | (default) full review, `progress`, `message`, `delegate`, `help` |
446
446
  | `/collab-consultant` | Consultant | (default) dashboard, `create`, `sync`, `add`, `edit`, `message`, `help` |
447
+ | `/setup-accounts` | Client | Interactive account-setup walkthrough with self/delegate/later modes |
447
448
 
448
449
  The 9 old `/engagement-*` skills are redirect stubs pointing to `/collab-*`.
449
450
 
@@ -494,6 +495,182 @@ edit the guide file directly.
494
495
 
495
496
  ---
496
497
 
498
+ ## 12. Setup-walkthrough item fields
499
+
500
+ Checklist items in `engagement.yaml` sections support optional fields
501
+ used by the `/setup-accounts` skill for guided account-creation
502
+ walkthroughs. These fields extend the base item schema (key, prompt,
503
+ kind, help, options, visibility) — all are optional and the skill
504
+ degrades gracefully without them.
505
+
506
+ | Field | Type | Purpose |
507
+ | ----- | ---- | ------- |
508
+ | `signup_url` | string | Landing/signup page URL — opened in the client's default browser at item entry |
509
+ | `billing` | string or object | Cost context. String: `"$20/mo (your app server)"`. Object: `{ cost, payment_required, at_scale }` |
510
+ | `permissions` | string or object | Access scope. String: `"Member role"`. Object: `{ role, can, cannot, revoke }` |
511
+ | `time_estimate` | string | How long the setup takes — e.g., "10 minutes" |
512
+ | `screenshots` | string[] | URLs (typically Google Drive shareable links) to reference screenshots |
513
+ | `delegate_instructions` | string | Pre-written delegation email body text |
514
+ | `defer_consequence` | string | What won't work without this item — shown when client defers |
515
+ | `depends_on` | string | Flat dependency — item key that must be answered before this item appears |
516
+
517
+ `content_version` is an **engagement-level** field (under the
518
+ `engagement:` block), not a per-item field. Bumped when the content
519
+ changes materially; used as a sync guard between root and plugin copies.
520
+
521
+ ### `billing` field formats
522
+
523
+ **String (simple):**
524
+ ```yaml
525
+ billing: "$20/mo Pro plan (includes 8GB RAM, covers expected traffic)"
526
+ ```
527
+
528
+ **Object (structured):**
529
+ ```yaml
530
+ billing:
531
+ cost: "$20/month minimum (Pro plan)"
532
+ payment_required: "Yes — credit card required at signup"
533
+ at_scale: "Expect $20-35/month for typical traffic"
534
+ ```
535
+
536
+ The skill renders string billing in the overview table directly. For
537
+ object billing, it uses `cost` in the overview table and shows
538
+ `payment_required` and `at_scale` in the per-item detail view.
539
+
540
+ ### `permissions` field formats
541
+
542
+ **String (simple):**
543
+ ```yaml
544
+ permissions: "Member role — can deploy and view logs, cannot delete the project"
545
+ ```
546
+
547
+ **Object (structured):**
548
+ ```yaml
549
+ permissions:
550
+ role: "Member"
551
+ can: "deploy, view logs, manage environment variables"
552
+ cannot: "delete the project, manage billing"
553
+ revoke: "Remove from People page after project handoff"
554
+ ```
555
+
556
+ ### `depends_on` field
557
+
558
+ Flat sequential dependency. The item is hidden from the walkthrough
559
+ until its dependency has an answer in state. Different from the nested
560
+ `visibility: { depends_on, value_in }` format — flat `depends_on`
561
+ gates on any answer (not a specific value).
562
+
563
+ ```yaml
564
+ - key: railway_github_access
565
+ prompt: "Connect GitHub to Railway"
566
+ kind: confirm
567
+ depends_on: railway_account
568
+ ```
569
+
570
+ ### `signup_url` field
571
+
572
+ The landing or signup page for the service being created. When present,
573
+ the `/setup-accounts` skill opens this URL in the client's **default
574
+ browser** (via the OS open command) at item entry — before the step
575
+ loop begins. This is a navigation aid only; the skill never types,
576
+ clicks, submits, or auto-fills in the browser.
577
+
578
+ ```yaml
579
+ signup_url: "https://railway.com"
580
+ ```
581
+
582
+ ### `help` field formats
583
+
584
+ The `help` field doubles as step-by-step instructions. Two formats are
585
+ supported:
586
+
587
+ **String form (original):** A block scalar with numbered lines. The
588
+ skill parses numbered lines as individual walkthrough steps.
589
+
590
+ ```yaml
591
+ help: |
592
+ 1. Go to railway.com (not railway.app)
593
+ 2. Click "Sign in" and enter your email
594
+ 3. Check your inbox for a 6-digit code
595
+ ```
596
+
597
+ **Structured list form:** Each entry is a step object with required
598
+ `text` and optional `url`. Per-step `url` is opened in the client's
599
+ default browser when that step is presented.
600
+
601
+ ```yaml
602
+ help:
603
+ - text: "Click 'Sign in' (not 'Sign Up') and enter your email"
604
+ - text: "Check your inbox for a 6-digit code and enter it"
605
+ - text: "Accept the Terms of Service (two-step)"
606
+ - text: "Go to People in the sidebar"
607
+ url: "https://railway.com/project/settings/members"
608
+ - text: "Click Invite → enter consultant email → set role to Member"
609
+ ```
610
+
611
+ Per-step `url` works best for post-login destinations with stable
612
+ addressable pages (People/Invite, API-tokens, Billing settings). Steps
613
+ within a wizard flow (e.g. "enter the 6-digit code") typically have no
614
+ addressable URL and should omit `url`. Auth-gated deep-links may
615
+ redirect to login if the client hasn't signed in yet — this is expected
616
+ and the skill handles it gracefully (the client signs in, then arrives
617
+ at the target page).
618
+
619
+ ### Browser navigation (optional)
620
+
621
+ URLs from `signup_url` and per-step `url` fields open in the client's
622
+ **default browser** via the OS open command (`open` on macOS,
623
+ `xdg-open` on Linux, `start ""` on Windows). This is never an
624
+ automation-controlled browser — the client uses their own browser with
625
+ their own cookies, fingerprint, and login state. No Playwright, no
626
+ MCP server, no Chromium download, no client-side dependency.
627
+
628
+ If the open command is unavailable or errors, the skill falls back to
629
+ displaying the URL as a clickable link — zero breakage.
630
+
631
+ The skill only opens pages. It never types, clicks, submits, or
632
+ auto-fills in the browser. The client performs every interaction. This
633
+ constraint is intentional: automation-controlled browsers on third-party
634
+ signup pages trigger anti-bot detection (Cloudflare Turnstile,
635
+ reCAPTCHA) and risk leaking on-screen secrets into the conversation
636
+ transcript via page snapshots.
637
+
638
+ ### Full example
639
+
640
+ ```yaml
641
+ - key: railway_account
642
+ prompt: "Create a Railway hosting account"
643
+ kind: credential
644
+ signup_url: "https://railway.com"
645
+ help:
646
+ - text: "Click 'Sign in' (not 'Sign Up') and enter your email"
647
+ - text: "Check your inbox for a 6-digit code and enter it"
648
+ - text: "Accept the Terms of Service (two-step)"
649
+ - text: "Go to People in the sidebar"
650
+ url: "https://railway.com/project/settings/members"
651
+ - text: "Click Invite → enter consultant email → set role to Member"
652
+ billing:
653
+ cost: "$20/month minimum (Pro plan)"
654
+ payment_required: "Yes — credit card required at signup"
655
+ at_scale: "Expect $20-35/month for typical traffic"
656
+ permissions:
657
+ role: "Member"
658
+ can: "deploy, view logs, manage environment variables"
659
+ cannot: "delete the project, manage billing"
660
+ time_estimate: "10 minutes"
661
+ screenshots:
662
+ - "https://drive.google.com/file/d/abc123/view"
663
+ - "https://drive.google.com/file/d/def456/view"
664
+ delegate_instructions: |
665
+ Create a Railway account at railway.com. Use your work email.
666
+ After signup, go to People in the sidebar and invite
667
+ oren.michael.magid@gmail.com as a Member.
668
+ Give the API token to Ed directly — do not email it.
669
+ defer_consequence: "The web application cannot be deployed without hosting."
670
+ ```
671
+
672
+ ---
673
+
497
674
  ## Known limitations
498
675
 
499
676
  - **Single machine, single session.** The engagement store is one local
@@ -99,53 +99,6 @@ export function init(db, { schemaPath }) {
99
99
  return { message: `Database initialized` };
100
100
  }
101
101
 
102
- // ---------------------------------------------------------------------------
103
- // Data migration — extract client-facing copy from notes into columns
104
- // ---------------------------------------------------------------------------
105
- const CLIENT_COPY_RE = /<!--\s*client-facing\s*\n([\s\S]*?)-->/;
106
- const CC_GENERATED_RE = /<!--\s*cc-generated:(\S+)\s+status:(\S+)\s*-->/;
107
-
108
- export function migrateClientCopy(db) {
109
- const rows = db.prepare(
110
- "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"
111
- ).all();
112
- const projRows = db.prepare(
113
- "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"
114
- ).all();
115
-
116
- let migrated = 0;
117
- const update = db.prepare(
118
- "UPDATE actions SET client_title = ?, client_body = ?, client_generated_at = ?, client_generated_status = ? WHERE fid = ?"
119
- );
120
- const updateProj = db.prepare(
121
- "UPDATE projects SET client_title = ?, client_body = ?, client_generated_at = ?, client_generated_status = ? WHERE fid = ?"
122
- );
123
-
124
- for (const { fid, notes } of [...rows, ...projRows]) {
125
- if (!notes) continue;
126
- const copyMatch = notes.match(CLIENT_COPY_RE);
127
- const genMatch = notes.match(CC_GENERATED_RE);
128
- if (!copyMatch && !genMatch) continue;
129
-
130
- let title = null, body = null, genAt = null, genStatus = null;
131
-
132
- if (copyMatch) {
133
- const lines = copyMatch[1].split('\n').map(l => l.trim()).filter(Boolean);
134
- title = lines[0] || null;
135
- body = lines.slice(1).join('\n').trim() || null;
136
- }
137
- if (genMatch) {
138
- genAt = genMatch[1] || null;
139
- genStatus = genMatch[2] || null;
140
- }
141
-
142
- const stmt = rows.some(r => r.fid === fid) ? update : updateProj;
143
- stmt.run(title, body, genAt, genStatus, fid);
144
- migrated++;
145
- }
146
- return { migrated };
147
- }
148
-
149
102
  // ---------------------------------------------------------------------------
150
103
  // Query — run arbitrary SQL
151
104
  // ---------------------------------------------------------------------------
@@ -0,0 +1,102 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ // Structural validator for qa-dimensions.yaml.
5
+ // Checks: file parses, has dimensions: map, each dimension has
6
+ // paths (list, >=1), severity (high|moderate|info), checks (list, >=1).
7
+ // No npm YAML parser — hand-parses the constrained schema.
8
+
9
+ const { readFileSync, existsSync } = require('node:fs');
10
+ const { join } = require('node:path');
11
+
12
+ const candidates = [
13
+ join(process.cwd(), '.claude', 'cabinet', 'qa-dimensions.yaml'),
14
+ ];
15
+
16
+ const file = candidates.find(existsSync);
17
+ if (!file) {
18
+ // No yaml = no error. Checklist engine is opt-in.
19
+ process.exit(0);
20
+ }
21
+
22
+ const lines = readFileSync(file, 'utf-8').split('\n');
23
+ const errors = [];
24
+
25
+ let foundDimensions = false;
26
+ let currentDim = null;
27
+ const dimensions = {};
28
+
29
+ for (let i = 0; i < lines.length; i++) {
30
+ const line = lines[i];
31
+ const num = i + 1;
32
+
33
+ if (line.match(/^dimensions:\s*$/)) {
34
+ foundDimensions = true;
35
+ continue;
36
+ }
37
+
38
+ if (!foundDimensions) continue;
39
+
40
+ // Top-level dimension name (2-space indent, ends with colon)
41
+ const dimMatch = line.match(/^ ([a-z][a-z0-9_-]+):\s*$/);
42
+ if (dimMatch) {
43
+ currentDim = dimMatch[1];
44
+ dimensions[currentDim] = { paths: 0, severity: null, checks: 0, line: num };
45
+ continue;
46
+ }
47
+
48
+ if (!currentDim) continue;
49
+
50
+ // paths list item (6-space indent + dash)
51
+ if (line.match(/^ - /)) {
52
+ const parent = lines.slice(0, i).reverse().find(l => l.match(/^ (paths|checks):/));
53
+ if (parent && parent.includes('paths:')) {
54
+ dimensions[currentDim].paths++;
55
+ }
56
+ if (parent && parent.includes('checks:')) {
57
+ dimensions[currentDim].checks++;
58
+ }
59
+ }
60
+
61
+ // severity field
62
+ const sevMatch = line.match(/^ severity:\s*(\S+)/);
63
+ if (sevMatch) {
64
+ const val = sevMatch[1];
65
+ if (!['high', 'moderate', 'info'].includes(val)) {
66
+ errors.push(`Line ${num}: severity "${val}" must be high|moderate|info`);
67
+ }
68
+ dimensions[currentDim].severity = val;
69
+ }
70
+
71
+ // check item with tag
72
+ const tagMatch = line.match(/^ - tag:\s*(\S+)/);
73
+ if (tagMatch) {
74
+ const val = tagMatch[1];
75
+ if (!['run', 'review'].includes(val)) {
76
+ errors.push(`Line ${num}: tag "${val}" must be run|review`);
77
+ }
78
+ }
79
+ }
80
+
81
+ if (!foundDimensions) {
82
+ errors.push('Missing top-level "dimensions:" key');
83
+ }
84
+
85
+ const dimNames = Object.keys(dimensions);
86
+ if (foundDimensions && dimNames.length === 0) {
87
+ errors.push('"dimensions:" map is empty — at least one dimension required');
88
+ }
89
+
90
+ for (const [name, d] of Object.entries(dimensions)) {
91
+ if (d.paths === 0) errors.push(`Dimension "${name}" (line ${d.line}): needs at least one path`);
92
+ if (!d.severity) errors.push(`Dimension "${name}" (line ${d.line}): missing severity`);
93
+ if (d.checks === 0) errors.push(`Dimension "${name}" (line ${d.line}): needs at least one check`);
94
+ }
95
+
96
+ if (errors.length > 0) {
97
+ console.error(`qa-dimensions.yaml: ${errors.length} error(s)`);
98
+ errors.forEach(e => console.error(` ${e}`));
99
+ process.exit(1);
100
+ }
101
+
102
+ console.log(`qa-dimensions.yaml: ${dimNames.length} dimensions, all valid.`);
@@ -409,6 +409,24 @@ If the upstream schema has new columns or tables:
409
409
  If the upgrade added modules the project hadn't installed before,
410
410
  walk through what they do and whether to keep them.
411
411
 
412
+ #### Plugin Staleness Check
413
+
414
+ If the project has a plugin (detected by `plugin.json` in the project
415
+ root, `.claude-plugin/plugin.json`, or a plugin repo reference in
416
+ `_briefing.md` or `engagement.yaml`), surface a reminder:
417
+
418
+ > This project has a plugin. CC skill patterns may have changed in this
419
+ > upgrade — check whether plugin skills need updating to match.
420
+
421
+ If the plugin path or repo is discoverable (e.g., from `plugin.json`
422
+ or cc-registry), name it specifically:
423
+
424
+ > Plugin at `[path or repo]` has N skills built on CC conventions.
425
+ > Review them for compatibility with the changes above.
426
+
427
+ This is advisory only — don't block the upgrade. The user decides
428
+ whether to check the plugin now or later.
429
+
412
430
  #### patterns-project.md Deduplication
413
431
 
414
432
  After installing updated cabinet member SKILL.md files, check for
@@ -46,7 +46,26 @@ Copy `.claude/cabinet/qa-dimensions-template.yaml` to
46
46
  tell the user to run `npx create-claude-cabinet` with the planning
47
47
  module first.
48
48
 
49
- ### 2. Scan for structural signals
49
+ ### 2. Review template dimensions
50
+
51
+ Before scanning, read the template's existing dimensions. For each
52
+ dimension in the template, evaluate whether the **concept** applies to
53
+ this project — even if the specific paths need updating:
54
+
55
+ 1. Read `qa-dimensions-template.yaml` and list its dimensions.
56
+ 2. For each template dimension, scan for files matching the concept
57
+ (not just the template's paths). Example: `knowledge-layer` watches
58
+ `**/docs/**` and `**/README*` in the template, but this project may
59
+ have equivalent files in `deliverables/`, `correspondence/`, or `wiki/`.
60
+ 3. If the concept applies with different paths, carry it forward as a
61
+ proposal with updated paths (mark it as "adapted from template").
62
+ 4. If the concept genuinely doesn't apply (no matching files at all),
63
+ propose it for Skip so the user sees the decision explicitly.
64
+
65
+ **Never silently drop a template dimension.** The template's accumulated
66
+ wisdom should be evaluated, not replaced sight-unseen.
67
+
68
+ ### 3. Scan for structural signals
50
69
 
51
70
  Analyze the codebase systematically. For each signal category below,
52
71
  look for real files — don't guess or hallucinate. Read directory listings,
@@ -56,20 +75,20 @@ check imports, look at actual file contents.
56
75
 
57
76
  | Signal | Files to look for | Suggested dimension |
58
77
  |--------|------------------|-------------------|
59
- | Database/schema | `**/schema*`, `**/migration*`, `**/pib-db*`, `**/*.sql`, ORM models | `data-coherence` — migration safety, referential integrity, schema changes |
60
- | API surface | Exported functions in `lib/`, `src/api/`, route files, MCP server definitions | `api-drift` — export surface changes, downstream consumer impact |
78
+ | Database/schema | `**/schema*`, `**/migration*`, `**/*.sql`, `**/models/**`, ORM models | `data-coherence` — migration safety, referential integrity, schema changes |
79
+ | API surface | Exported functions in `src/`, route files, API controllers, MCP server definitions | `api-drift` — export surface changes, downstream consumer impact |
61
80
  | Crypto/auth/security | `**/crypto*`, `**/auth*`, `**/secure*`, JWT/token handling, key files | `security-review` — credential handling, encryption correctness, auth flow changes |
62
81
  | UI components | `**/components/**`, `*.tsx`, `*.vue`, `*.svelte`, template files | `render-correctness` — visual regressions, prop changes, layout breaks |
63
82
  | Configuration | `**/config*`, `**/.env*`, `**/settings*`, deployment configs | `config-drift` — env parity, missing variables, deployment config sync |
64
83
  | User-facing docs | App guides, README, help text, onboarding content | `knowledge-layer` — documentation staleness when behavior changes |
65
- | Test infrastructure | `**/test*`, `**/*.test.*`, `**/*.spec.*`, fixture files | `test-health` — fixture staleness, coverage gaps for changed code |
84
+ | Test infrastructure | `**/test*`, `**/*.test.*`, `**/*.spec.*`, fixture/factory files | `test-staleness` — factory validity after model changes, specs asserting stale behavior |
66
85
  | Build/deploy | `Dockerfile`, `railway.toml`, CI configs, build scripts | `deploy-safety` — build breaks, deployment config changes, infra drift |
67
86
 
68
87
  Don't force-fit every category. If the codebase has no crypto code,
69
88
  don't suggest a security-review dimension. Only suggest dimensions
70
89
  backed by real files you found.
71
90
 
72
- ### 3. For each discovered signal, look deeper
91
+ ### 4. For each discovered signal, look deeper
73
92
 
74
93
  Don't stop at filenames. For the files you find:
75
94
 
@@ -81,33 +100,50 @@ Don't stop at filenames. For the files you find:
81
100
  - **Count and characterize** the files. "8 schema files across 3
82
101
  directories" is a stronger signal than "1 schema file."
83
102
 
84
- ### 4. Present suggestions one at a time
103
+ ### 5. Present suggestions one at a time
85
104
 
86
- For each suggested dimension, present it conversationally:
105
+ For each suggested dimension (including those adapted from the template
106
+ in step 2), first output the evidence and proposed config as text:
87
107
 
88
- > I found 8 files matching `**/schema*` and `**/*.sql` across 3 directories
89
- > (scripts/, templates/engagement/, templates/scripts/). This suggests a
90
- > **data-coherence** dimension.
108
+ > I found 6 files matching `**/schema*` and `**/*.sql` across 2 directories
109
+ > (db/, app/models/). This suggests a **data-coherence** dimension.
91
110
  >
92
- > Suggested paths: `["**/schema*", "**/*.sql", "**/pib-db*.mjs"]`
111
+ > Suggested paths: `["**/schema*", "**/*.sql", "**/models/**"]`
93
112
  > Suggested severity: `high`
94
113
  > Suggested checks:
95
114
  > - `[run] Run schema validation if schema or migration files changed`
96
115
  > - `[review] Verify migration handles existing data, not just fresh installs`
97
116
  > - `[review] Check referential integrity for any new foreign keys`
98
- >
99
- > **Accept / Edit / Skip?**
100
117
 
101
- If the user says **Edit**: ask what to change (paths, severity, checks),
102
- apply their changes, re-present for confirmation.
118
+ Then use **AskUserQuestion** with a single-select question for the
119
+ user's decision:
120
+
121
+ ```
122
+ AskUserQuestion({
123
+ questions: [{
124
+ question: "Accept the <dimension-name> dimension as proposed?",
125
+ header: "Dimension",
126
+ options: [
127
+ { label: "Accept", description: "Add this dimension as shown above" },
128
+ { label: "Edit", description: "Modify paths, severity, or checks before adding" },
129
+ { label: "Skip", description: "Don't include this dimension" }
130
+ ],
131
+ multiSelect: false
132
+ }]
133
+ })
134
+ ```
135
+
136
+ If the user selects **Edit**: ask what to change (paths, severity,
137
+ checks), apply their changes, re-present for confirmation with
138
+ another AskUserQuestion.
103
139
 
104
- If the user says **Accept**: note it — write after all suggestions.
140
+ If the user selects **Accept**: note it — write after all suggestions.
105
141
 
106
- If the user says **Skip**: move to the next suggestion.
142
+ If the user selects **Skip**: move to the next suggestion.
107
143
 
108
144
  **Never batch.** One dimension at a time.
109
145
 
110
- ### 5. Write the yaml
146
+ ### 6. Write the yaml
111
147
 
112
148
  If the user skipped ALL suggestions (zero accepted), don't write.
113
149
  Say: "No dimensions accepted. The template is at
@@ -146,25 +182,36 @@ Read `.claude/cabinet/qa-dimensions.yaml`. Summarize what's there:
146
182
 
147
183
  ### 2. Offer two paths
148
184
 
149
- > Want me to:
150
- > 1. **Check for gaps** — scan the codebase for signals not covered
151
- > by existing dimensions
152
- > 2. **Review existing** — walk through each dimension and check if
153
- > paths and checks are still accurate
154
- >
155
- > Or name a specific dimension to edit.
185
+ Use **AskUserQuestion** to let the user choose:
186
+
187
+ ```
188
+ AskUserQuestion({
189
+ questions: [{
190
+ question: "How would you like to review your checklist?",
191
+ header: "Review mode",
192
+ options: [
193
+ { label: "Check for gaps", description: "Scan the codebase for signals not covered by existing dimensions" },
194
+ { label: "Review existing", description: "Walk through each dimension and check if paths and checks are still accurate" }
195
+ ],
196
+ multiSelect: false
197
+ }]
198
+ })
199
+ ```
200
+
201
+ If the user selects "Other" and names a specific dimension, jump to
202
+ that dimension's review directly.
156
203
 
157
204
  ### 3a. Gap analysis
158
205
 
159
- Run the same structural scan as scaffold mode (step 2), but filter out
206
+ Run the same structural scan as scaffold mode (step 3), but filter out
160
207
  signals already covered by existing dimensions. For each uncovered
161
208
  signal, suggest a new dimension using the same one-at-a-time flow.
162
209
 
163
210
  Also check: do existing glob patterns still match files that exist
164
211
  today? Are there new files in matched directories that the patterns miss?
165
212
 
166
- > Your `api-drift` dimension watches `lib/*.js` but I also found
167
- > exports in `src/api/routes.mjs` and `templates/scripts/pib-db-lib.mjs`.
213
+ > Your `api-drift` dimension watches `src/**` but I also found
214
+ > route definitions in `app/routes/` and exported helpers in `utils/`.
168
215
  > Want to add those paths?
169
216
 
170
217
  ### 3b. Existing dimension review
@@ -0,0 +1,42 @@
1
+ # Checklist Status
2
+
3
+ **Runs:** after work-scan, before briefing. Lightweight status line only.
4
+
5
+ ## What this phase does
6
+
7
+ Surfaces the change-impact checklist state during orient so the user
8
+ knows whether the engine is active without having to check files.
9
+
10
+ ## Logic
11
+
12
+ 1. Check if `.claude/cabinet/qa-dimensions.yaml` exists.
13
+
14
+ 2. If **absent**, check if `.claude/cabinet/qa-dimensions-template.yaml`
15
+ exists:
16
+ - **Template exists, yaml absent** → the checklist engine is installed
17
+ but not configured. Surface a one-time nudge in the briefing's
18
+ **Attention Items** section:
19
+
20
+ > Checklist engine installed but not configured. Run
21
+ > `/checklist-discover` to set up QA dimensions for your project.
22
+
23
+ This condition is self-resolving: once the user runs
24
+ `/checklist-discover`, the yaml is created and the nudge stops.
25
+ - **Neither exists** → skip silently. The planning module isn't
26
+ installed or the template wasn't copied.
27
+
28
+ 3. If **present**: count the dimensions and total checks. Output one
29
+ line in the briefing's **Project State** section:
30
+
31
+ > Checklist active: N dimensions, M checks total.
32
+
33
+ If the file exists but is malformed (no `dimensions:` key or empty),
34
+ output a warning instead:
35
+
36
+ > ⚠ qa-dimensions.yaml exists but appears malformed. Run `/validate`
37
+ > to diagnose.
38
+
39
+ ## What this phase does NOT do
40
+
41
+ - It does not validate structure beyond presence — /validate does that.
42
+ - It does not list individual dimensions. Keep it to one line.
@@ -0,0 +1,502 @@
1
+ ---
2
+ name: setup-accounts
3
+ description: |
4
+ Interactive account-setup walkthrough for any engagement. Reads
5
+ engagement.yaml sections, walks the client through creating accounts
6
+ step by step with self/delegate/later modes, structured error recovery,
7
+ credential capture via secure OS dialog, and delegation via Gmail MCP.
8
+ Progress persists across sessions. Use when: "setup accounts",
9
+ "/setup-accounts", "create my accounts", "walk me through the setup".
10
+ manual: true
11
+ argument-hint: "section key — e.g., 'go_live', or empty for all account-setup items"
12
+ ---
13
+
14
+ # /setup-accounts — Interactive Account Setup Walkthrough
15
+
16
+ ## Purpose
17
+
18
+ Walk a client through creating third-party service accounts needed for
19
+ their engagement. Each item gets a guided walkthrough with three
20
+ choices: do it yourself (step-by-step), delegate to someone else (Gmail
21
+ draft), or defer (with consequences explained). Credentials are captured
22
+ via a secure OS dialog and never enter the conversation.
23
+
24
+ The skill is content-agnostic — it reads engagement.yaml for the items
25
+ and their instructions. Project-specific content (which accounts, what
26
+ steps, billing details) lives in the YAML, not here.
27
+
28
+ ## Locate Engagement Directory
29
+
30
+ Search for `engagement.yaml` starting from the current working directory:
31
+ 1. `./engagement.yaml`
32
+ 2. `./engagement/engagement.yaml`
33
+ 3. Walk up parent directories (max 3 levels), checking both patterns
34
+
35
+ Store the resolved directory (the directory containing the found
36
+ `engagement.yaml`) as `ENGAGEMENT_DIR`. All relative paths in
37
+ engagement.yaml resolve from there.
38
+
39
+ If engagement.yaml is not found, tell the user and stop.
40
+
41
+ ## Load State
42
+
43
+ Read checklist state from `ENGAGEMENT_DIR/setup-accounts-state.json`
44
+ using the `loadState` / `createState` pattern from
45
+ `engagement-checklist.mjs`. If no state file exists, create one.
46
+
47
+ ## Select Items
48
+
49
+ Parse engagement.yaml. Collect items from the section specified in
50
+ `$ARGUMENTS`, or from all sections if no argument given.
51
+
52
+ **Invalid section key guard:** If `$ARGUMENTS` specifies a section key
53
+ that doesn't exist in engagement.yaml, list the available section keys
54
+ and stop: "Section '[key]' not found. Available sections: [list]. Run
55
+ `/setup-accounts` with no argument to walk through all items."
56
+
57
+ ### Dependency Resolution
58
+
59
+ Items may declare dependencies in two formats:
60
+
61
+ 1. **Nested visibility** (`visibility: { depends_on, value_in }`) —
62
+ handled by `computeVisibility()` from `engagement-checklist.mjs`.
63
+ 2. **Flat depends_on** (`depends_on: <key>`) — a sequential ordering
64
+ hint not read by `computeVisibility()`.
65
+
66
+ Apply both filters in order:
67
+ 1. Run `computeVisibility()` with current state answers to filter
68
+ nested-visibility items.
69
+ 2. For remaining items with flat `depends_on`: check whether the
70
+ dependency key has an answer in state. If not, exclude the item
71
+ from this session's walkthrough (it will appear once its dependency
72
+ is completed). Show a note in the overview table: "Waiting on
73
+ [dependency item prompt]".
74
+
75
+ The skill operates on items of any `kind` — the walkthrough UX applies
76
+ to `credential`, `provide`, `confirm`, and `decide` items alike.
77
+
78
+ **Empty result guard:** If the filtered item set is empty (no items
79
+ match the section or all are gated by dependencies), tell the user and
80
+ stop.
81
+
82
+ ## Present Overview Table
83
+
84
+ Show all items in a single overview table before starting the
85
+ walkthrough:
86
+
87
+ ```
88
+ ## Account Setup — [engagement title]
89
+
90
+ | # | Service | Status | Est. Time | Cost |
91
+ |---|---------|--------|-----------|------|
92
+ | 1 | Railway hosting | ⬜ Not started | 10 min | $20/mo (your app server) |
93
+ | 2 | Postmark email | ⬜ Not started | 15 min | Free → $15/mo at scale |
94
+ | 3 | Sentry errors | ✅ Done | — | $0 |
95
+ | 4 | Cloudflare DNS | 🔄 Delegated to Sydney | — | $0 |
96
+ | 5 | GitHub access | ⏳ Waiting on Railway | — | — |
97
+
98
+ 5 items: 2 remaining, 1 done, 1 delegated, 1 waiting
99
+ ```
100
+
101
+ **Status icons:**
102
+ - ⬜ Not started
103
+ - ✅ Done (or handled by consultant)
104
+ - 🔄 Delegated to [name]
105
+ - ⏸️ Deferred
106
+ - 🚫 Blocked
107
+ - ⏳ Waiting on [dependency]
108
+
109
+ **Cost column:** Use the item's `billing` field if present. If `billing`
110
+ is a string, display it directly. If `billing` is an object (with `cost`,
111
+ `payment_required`, `at_scale` sub-keys), render the `cost` value in the
112
+ table and show `payment_required` and `at_scale` in the per-item detail.
113
+ If no `billing` field on any item, omit the Cost column entirely.
114
+
115
+ **Time column:** Use the item's `time_estimate` field if present.
116
+
117
+ **Permissions** are shown in the per-item detail view (Step 1), not in
118
+ the overview table.
119
+
120
+ ### Item Selection (for lists > 5 items)
121
+
122
+ If there are more than 5 incomplete items, present the overview table
123
+ first, then ask which items the user wants to tackle this session
124
+ rather than walking through all of them sequentially:
125
+
126
+ Use `AskUserQuestion`:
127
+ - header: "This session"
128
+ - question: "Which items do you want to work on now? You can always
129
+ come back for the rest."
130
+ - multiSelect: true
131
+ - options: one per incomplete item (label: item prompt, description:
132
+ cost/time summary)
133
+
134
+ For 5 or fewer incomplete items, proceed directly to the per-item
135
+ walkthrough without asking.
136
+
137
+ ## Per-Item Walkthrough
138
+
139
+ For each selected incomplete item, in order:
140
+
141
+ ### Step 1: Present the Item
142
+
143
+ ```
144
+ ### Item 1 of 4: Railway Hosting Account
145
+
146
+ Railway runs your web application. Here's what we need to set up.
147
+
148
+ **Cost:** $20/month (includes 8GB RAM, covers your expected traffic)
149
+ **Payment required:** Yes — credit card needed at signup
150
+ **At scale:** Expect $20-35/month for your traffic levels
151
+ **Permissions needed:** Member role — can deploy and view logs, cannot delete the project
152
+ **Time:** ~10 minutes
153
+ ```
154
+
155
+ Show `billing` fields (rendering object sub-keys if present),
156
+ `permissions` fields (rendering object sub-keys if present), and
157
+ `time_estimate` if present.
158
+
159
+ ### Step 2: Ask How to Proceed
160
+
161
+ The base menu always has exactly 3 options. For items that are
162
+ **blocked or have evidence of prior completion**, show a different
163
+ 3-option menu instead.
164
+
165
+ **Base menu** (not started, deferred, or first attempt):
166
+
167
+ Use `AskUserQuestion`:
168
+ - header: "Mode"
169
+ - question: "How would you like to handle [item prompt]?"
170
+ - options:
171
+ - label: "I'll do it now", description: "Walk me through the steps"
172
+ - label: "Someone else will handle this", description: "Draft instructions for a delegate"
173
+ - label: "Skip for now", description: "Come back to this later"
174
+
175
+ **Recovery menu** (item is blocked, or user says it's already done,
176
+ or consultant may have handled it):
177
+
178
+ Use `AskUserQuestion`:
179
+ - header: "Mode"
180
+ - question: "[Item] was previously [blocked at step N / started].
181
+ How would you like to proceed?"
182
+ - options:
183
+ - label: "Try again", description: "Re-attempt the walkthrough from where I left off"
184
+ - label: "Already handled", description: "I or my consultant already took care of this"
185
+ - label: "Skip for now", description: "Come back to this later"
186
+
187
+ If the user selects "Already handled", ask a follow-up to determine
188
+ Path D vs Path E:
189
+
190
+ Use `AskUserQuestion`:
191
+ - header: "Who"
192
+ - question: "Who completed this?"
193
+ - options:
194
+ - label: "I did it myself", description: "I'll provide the credential/info now"
195
+ - label: "My consultant handled it", description: "Mark it as done"
196
+
197
+ ### Path A: "I'll do it now"
198
+
199
+ **Open signup page (if available):** If the item has a `signup_url`
200
+ field, open it in the client's default browser before starting the
201
+ step loop. See **Browser Navigation (optional)** below for the open
202
+ command and fallback behavior.
203
+
204
+ **Parse the `help` field into steps.** Two formats are supported:
205
+
206
+ - **String form:** Parse numbered lines into steps (each line's text
207
+ becomes the step content). This is the original format.
208
+ - **Structured list form:** `help` is an array of `{ text, url? }`
209
+ objects. Each entry's `text` is the step content. If `url` is
210
+ present, open it in the client's browser when the step is presented
211
+ (before the AskUserQuestion).
212
+
213
+ Present one step at a time with progress:
214
+
215
+ ```
216
+ **Step 2 of 6:** Click "Sign in" (not "Sign Up") and enter your email.
217
+ Railway will send a 6-digit code to your inbox.
218
+
219
+ [screenshot: Railway sign-in page]
220
+ ```
221
+
222
+ **Screenshots:** If the item has a `screenshots` field (array of URLs),
223
+ reference the relevant screenshot at each step. If Google Drive MCP
224
+ tools are available (e.g., `mcp__claude_ai_Google_Drive__read_file_content`),
225
+ use them to fetch the screenshot by file ID extracted from the URL.
226
+ Otherwise, display the URL as a clickable link.
227
+
228
+ **After each step**, ask:
229
+
230
+ Use `AskUserQuestion`:
231
+ - header: "Step"
232
+ - question: "Step [N] of [M]: [step description summary]"
233
+ - options:
234
+ - label: "Done, next step", description: "Move to the next step"
235
+ - label: "This isn't working", description: "I need help with this step"
236
+
237
+ **If "This isn't working":**
238
+
239
+ 1. Show the relevant screenshot if available.
240
+
241
+ 2. Ask a structured diagnostic:
242
+
243
+ Use `AskUserQuestion`:
244
+ - header: "Symptom"
245
+ - question: "What do you see on your screen right now?"
246
+ - options:
247
+ - label: "Page looks different", description: "The page doesn't match what I expected"
248
+ - label: "Error message", description: "I see an error on the page"
249
+ - label: "Can't find it", description: "The button or link isn't there"
250
+
251
+ (The harness auto-adds an "Other" option for free-text input.)
252
+
253
+ 3. Based on the answer, provide targeted guidance.
254
+
255
+ 4. If still stuck after one round of diagnosis, offer:
256
+
257
+ Use `AskUserQuestion`:
258
+ - header: "Next"
259
+ - question: "Still stuck. What would you like to do?"
260
+ - options:
261
+ - label: "Message my consultant", description: "I'll draft a message describing the issue"
262
+ - label: "Skip for now", description: "Come back to this later"
263
+ - label: "Try again", description: "Let me retry this step"
264
+
265
+ **If "Message my consultant":**
266
+
267
+ Read `transport.consultant` from engagement.yaml for the recipient
268
+ email address.
269
+
270
+ Draft via Gmail MCP (`create_draft` tool — search available MCP
271
+ tools for one matching `Gmail` and `create_draft`). Set the `to`
272
+ field to the `transport.consultant` email. Include the step number,
273
+ what the client sees, the diagnostic answers, and the screenshot
274
+ URL if available.
275
+
276
+ Tell the client explicitly: **"I've saved a draft in your Gmail
277
+ — please review and send it."**
278
+
279
+ **If Gmail MCP is unavailable:** Display the full message text
280
+ and say: "Copy this and email it to [consultant name] at
281
+ [transport.consultant email]."
282
+
283
+ Mark item as `blocked`:
284
+ `recordAnswer(state, key, { mode: 'blocked', step_number: N, symptom: '<diagnostic answer>', blocked_at: new Date().toISOString() })`
285
+ Then call `saveState()`.
286
+
287
+ **If "Skip for now":**
288
+ Mark as deferred (not blocked — the client chose to skip, they
289
+ weren't stuck):
290
+ `recordAnswer(state, key, { mode: 'deferred', step_number: N, deferred_at: new Date().toISOString() })`
291
+ Then call `saveState()`.
292
+
293
+ **After the last step**, if the item's `kind` is `credential`:
294
+
295
+ Prep the client: "I'm about to show a secure input dialog — this is
296
+ a separate window from our conversation, so the value stays private.
297
+ Paste the [token/key/credential] you just copied."
298
+
299
+ Invoke `capture-and-encrypt.mjs` via Bash. First resolve the paths:
300
+
301
+ 1. Read the public key relative path from `engagement.public_key`
302
+ (v2 format) or `meta.public_key` (v1 format) in engagement.yaml.
303
+ 2. Join it to the resolved ENGAGEMENT_DIR to get the absolute path.
304
+
305
+ Then run (substituting the resolved absolute paths, quoted):
306
+ ```bash
307
+ node "/absolute/path/to/engagement/capture-and-encrypt.mjs" \
308
+ --prompt "Paste your [service] [credential type]" \
309
+ --public-key "/absolute/path/to/engagement/consultant.pub.jwk"
310
+ ```
311
+
312
+ **Handle each exit code:**
313
+ - **Exit 0 (success):** The script writes a base64-encoded envelope to
314
+ stdout. Capture the stdout output, base64-decode it, parse the JSON,
315
+ and extract the `envelope_id` field. Then call:
316
+ `recordCredentialSent(state, key, envelopeId)`
317
+ Confirm to the client: "Credential captured and encrypted securely."
318
+ - **Exit 2 (user cancelled):** "No problem — you can capture this
319
+ later." Leave the item incomplete.
320
+ - **Exit 3 (dialog unavailable):** The secure input dialog is not
321
+ available on this device. Tell the client: "Secure input isn't
322
+ available on this device. You can capture this credential from a
323
+ Mac, or your consultant can help you enter it securely." Mark as
324
+ blocked with `{ mode: 'blocked', step_number: 'credential_capture', symptom: 'dialog_unavailable' }`.
325
+ - **Exit 4 (encryption failed):** Log the error. Mark as needing retry
326
+ with `{ mode: 'blocked', step_number: 'credential_capture', symptom: 'encryption_error' }`.
327
+
328
+ For non-credential items, record completion:
329
+ - `kind: confirm` → `recordAnswer(state, key, { mode: 'confirmed', confirmed_at: new Date().toISOString() })`
330
+ - `kind: provide` → `recordAnswer(state, key, { mode: 'provided', value: '<user input>', provided_at: new Date().toISOString() })`
331
+ - `kind: decide` → `recordAnswer(state, key, { mode: 'decided', value: '<selected option>', decided_at: new Date().toISOString() })`
332
+
333
+ ### Path B: "Someone else will handle this"
334
+
335
+ 1. Ask: "Who should handle this?" — collect name and email.
336
+
337
+ 2. Draft a delegation email. Read `transport.consultant` from
338
+ engagement.yaml for the consultant's contact info.
339
+
340
+ Search available MCP tools for one matching `Gmail` and
341
+ `create_draft`. Use it to create the draft:
342
+
343
+ **Subject:** `[Engagement title] — [Service] account setup`
344
+
345
+ **Body:** Use the item's `delegate_instructions` field if present.
346
+ Otherwise, compose from the item's `help` field:
347
+ - What account to create and why
348
+ - Step-by-step instructions (from `help`)
349
+ - Screenshots (Drive links from `screenshots` field if present)
350
+ - What to give back: "Once you've created the account, give the
351
+ [credential] to [client name] directly — they'll enter it
352
+ securely." **Never ask the delegate to email credentials.**
353
+ - "If something isn't working, contact [transport.consultant email]"
354
+
355
+ 3. Tell the client: **"I've saved a draft in your Gmail — please
356
+ review it and send it to [name]."**
357
+
358
+ 4. **If Gmail MCP is unavailable:** Display the full email text and say
359
+ "Copy this and email it to [name] at [email]."
360
+
361
+ 5. Record:
362
+ `recordAnswer(state, key, { mode: 'delegated', delegate_name: '<name>', delegate_email: '<email>', delegated_at: new Date().toISOString() })`
363
+
364
+ ### Path C: "Skip for now"
365
+
366
+ 1. Explain the consequence of deferring this item — what won't work
367
+ without it. Use the `defer_consequence` field if present. Otherwise,
368
+ infer the impact from the item's `prompt` and `help` fields.
369
+
370
+ 2. Confirm: "Got it, we'll come back to this. Moving on."
371
+
372
+ 3. Record:
373
+ `recordAnswer(state, key, { mode: 'deferred', deferred_at: new Date().toISOString() })`
374
+
375
+ ### Path D: "I did it myself" (recovery — via "Already handled")
376
+
377
+ 1. Ask: "Do you have the [credential/info] handy?"
378
+
379
+ 2. If `kind: credential`: run the secure capture flow (same as Path A
380
+ final step — prep message, invoke capture-and-encrypt.mjs, capture
381
+ stdout, extract envelope_id, call recordCredentialSent).
382
+
383
+ 3. If `kind: provide`: ask for the value directly.
384
+ Record: `recordAnswer(state, key, { mode: 'provided', value: '<input>', provided_at: new Date().toISOString() })`
385
+
386
+ 3b. If `kind: decide`: ask the user to choose from the item's `options`.
387
+ Record: `recordAnswer(state, key, { mode: 'decided', value: '<selected option>', decided_at: new Date().toISOString() })`
388
+
389
+ 4. If `kind: confirm`: mark as confirmed.
390
+ Record: `recordAnswer(state, key, { mode: 'confirmed', confirmed_at: ... })`
391
+
392
+ ### Path E: "My consultant handled it" (recovery — via "Already handled")
393
+
394
+ 1. Confirm: "Noted — marking this as done."
395
+ 2. Record:
396
+ `recordAnswer(state, key, { mode: 'handled_by_consultant', noted_at: new Date().toISOString() })`
397
+
398
+ ## Save State After Each Item
399
+
400
+ Call `saveState()` after every item completion, delegation, deferral,
401
+ or block. State must survive session interruption — if the client
402
+ closes mid-walkthrough, re-entry picks up where they left off.
403
+
404
+ ## Re-Entry Behavior
405
+
406
+ When the skill is invoked and state already exists:
407
+
408
+ 1. Show the updated overview table (reflects current state).
409
+ 2. Summarize current status: "You've completed N items, N are
410
+ delegated, N are deferred, N are blocked."
411
+ 3. Offer: "Want to continue with the remaining items?"
412
+ 4. Resume from the first incomplete item.
413
+
414
+ Items previously marked `blocked` get special treatment — use the
415
+ recovery menu (Step 2) which offers "Try again":
416
+ "Last time, [Service] was blocked at step N. Want to try again?"
417
+
418
+ Items previously marked `deferred` get a gentle nudge, then the
419
+ **base menu** (not the recovery menu):
420
+ "You deferred [Service] last time. Ready to tackle it now?"
421
+
422
+ ## Summary
423
+
424
+ After all items are addressed (or the client chooses to stop):
425
+
426
+ ```
427
+ ## Setup Summary
428
+
429
+ ✅ Done (3): Railway, Sentry, GA4
430
+ 🔄 Delegated (1): Cloudflare → Sydney
431
+ ⏸️ Deferred (1): GTM (not needed until marketing hire)
432
+ 🚫 Blocked (1): Postmark (waiting for account approval)
433
+
434
+ **Monthly cost:** ~$35/month ($20 Railway + $15 Postmark)
435
+ [Only if billing fields were present on items]
436
+
437
+ **Next steps:**
438
+ - Check your Gmail for the Cloudflare delegation draft — send it to Sydney
439
+ - Postmark: your consultant will follow up about the account approval
440
+ - GTM: revisit when you're ready to add marketing tags
441
+ ```
442
+
443
+ ## Browser Navigation (optional)
444
+
445
+ When an item has a `signup_url` or a step has a `url`, open it in the
446
+ client's **default browser** using the OS open command. This is a
447
+ navigation aid only — the skill never types, clicks, submits, or
448
+ auto-fills in the browser. The client performs every interaction.
449
+
450
+ **Detect the open command:**
451
+ - macOS: `open "<url>"`
452
+ - Linux: `xdg-open "<url>"`
453
+ - Windows: `start "" "<url>"`
454
+
455
+ Detect the platform from `process.platform` (or `uname`) and pick the
456
+ command. If the command errors or the platform is unrecognized, fall
457
+ back to displaying the URL as a clickable link in the conversation.
458
+ Do not block or warn — just continue with text-only.
459
+
460
+ **When to open:**
461
+ - **Item entry (Path A):** If `signup_url` is present, open it before
462
+ the step loop.
463
+ - **Per step:** If the current step object has a `url`, open it when
464
+ the step is presented (before the AskUserQuestion).
465
+ - **Paths B, C, D, E:** Do not open URLs — these paths don't involve
466
+ guided step-by-step interaction.
467
+
468
+ **Why not an automation browser:** An automation-controlled browser
469
+ (Playwright, Puppeteer) sets `navigator.webdriver=true`, triggering
470
+ anti-bot detection on signup pages (Cloudflare Turnstile, reCAPTCHA).
471
+ Page snapshots leak on-screen API keys into the conversation transcript
472
+ — defeating the secure-dialog design. Opening the client's own browser
473
+ avoids both risks.
474
+
475
+ ## Rules
476
+
477
+ - **Never** accept or display credential plaintext in the conversation.
478
+ - **Never** ask a delegate to email credentials. Credentials go through
479
+ the client via the secure OS dialog, always.
480
+ - **Never** type, click, submit, or auto-fill in the client's browser.
481
+ The skill only opens pages. The client performs every interaction.
482
+ - **Never** use browser automation tools (`browser_snapshot`,
483
+ `browser_click`, `browser_type`, `@playwright/mcp`, or similar) in
484
+ this skill. URLs open via the OS open command only.
485
+ - **Never** mention other skills in the walkthrough. This is the
486
+ client's only interface.
487
+ - **Always** prep the client before the secure input dialog appears.
488
+ - **Always** tell the client explicitly to send Gmail drafts — drafts
489
+ are not sent automatically.
490
+ - **Always** save state after each item. Session interruption must not
491
+ lose progress.
492
+ - **Always** quote file paths when invoking Bash commands.
493
+ - **Always** read `transport.consultant` from engagement.yaml for
494
+ consultant email — never fabricate an address.
495
+ - Handle missing optional fields gracefully — billing, permissions,
496
+ screenshots, delegate_instructions, time_estimate, signup_url, and
497
+ defer_consequence are all optional. The skill works without them,
498
+ just with less context.
499
+ - Handle both string and object formats for billing and permissions
500
+ fields — both are valid.
501
+ - Handle both string and structured list formats for the `help` field
502
+ — both are valid. See Path A for parsing details.
@@ -135,6 +135,18 @@ protocol, its checkpoints have either been re-inlined or dropped — both
135
135
  are drift. Skips silently if the protocol file isn't present (e.g., a
136
136
  project that hasn't adopted the split yet).
137
137
 
138
+ ### qa-dimensions
139
+
140
+ ```bash
141
+ node scripts/qa-dimensions-validator.cjs
142
+ ```
143
+
144
+ Catches malformed qa-dimensions.yaml: missing `dimensions:` key, empty
145
+ map, dimensions without paths or severity or checks, invalid severity
146
+ values (must be high|moderate|info), invalid tags (must be run|review).
147
+ Exits 0 silently if qa-dimensions.yaml doesn't exist (the checklist
148
+ engine is opt-in).
149
+
138
150
  ## Example Validators (commented — enable for your project)
139
151
 
140
152
  <!--