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 +3 -2
- package/package.json +1 -1
- package/templates/cabinet/qa-dimensions-template.yaml +31 -5
- package/templates/engagement/engagement-schema.md +178 -1
- package/templates/scripts/pib-db-lib.mjs +0 -47
- package/templates/scripts/qa-dimensions-validator.cjs +102 -0
- package/templates/skills/cc-upgrade/SKILL.md +18 -0
- package/templates/skills/checklist-discover/SKILL.md +75 -28
- package/templates/skills/orient/phases/checklist-status.md +42 -0
- package/templates/skills/setup-accounts/SKILL.md +502 -0
- package/templates/skills/validate/phases/validators.md +12 -0
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).
|
|
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
|
@@ -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
|
-
- "
|
|
73
|
-
- "
|
|
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
|
|
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
|
-
|
|
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.
|
|
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*`,
|
|
60
|
-
| API surface | Exported functions in `
|
|
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-
|
|
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
|
-
###
|
|
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
|
-
###
|
|
103
|
+
### 5. Present suggestions one at a time
|
|
85
104
|
|
|
86
|
-
For each suggested dimension
|
|
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
|
|
89
|
-
> (
|
|
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", "**/
|
|
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
|
-
|
|
102
|
-
|
|
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
|
|
140
|
+
If the user selects **Accept**: note it — write after all suggestions.
|
|
105
141
|
|
|
106
|
-
If the user
|
|
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
|
-
###
|
|
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
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
|
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 `
|
|
167
|
-
>
|
|
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
|
<!--
|