create-claude-cabinet 0.14.2 → 0.16.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.
Files changed (29) hide show
  1. package/lib/cli.js +27 -2
  2. package/lib/settings-merge.js +7 -6
  3. package/package.json +1 -1
  4. package/templates/cabinet/pib-db-access.md +56 -0
  5. package/templates/mcp/pib-db.json +11 -0
  6. package/templates/scripts/pib-db-lib.mjs +281 -0
  7. package/templates/scripts/pib-db-mcp-server.mjs +292 -0
  8. package/templates/scripts/pib-db.mjs +38 -249
  9. package/templates/skills/audit/SKILL.md +29 -2
  10. package/templates/skills/cabinet/SKILL.md +10 -0
  11. package/templates/skills/cabinet-anthropic-insider/SKILL.md +360 -0
  12. package/templates/skills/cabinet-cc-health/SKILL.md +4 -3
  13. package/templates/skills/cabinet-organized-mind/SKILL.md +24 -4
  14. package/templates/skills/cc-feedback/SKILL.md +9 -1
  15. package/templates/skills/cc-publish/SKILL.md +8 -1
  16. package/templates/skills/debrief/SKILL.md +9 -0
  17. package/templates/skills/debrief/phases/audit-pattern-capture.md +2 -4
  18. package/templates/skills/debrief/phases/close-work.md +18 -14
  19. package/templates/skills/debrief/phases/upstream-feedback.md +10 -2
  20. package/templates/skills/execute/SKILL.md +11 -0
  21. package/templates/skills/execute-plans/SKILL.md +15 -11
  22. package/templates/skills/investigate/SKILL.md +7 -0
  23. package/templates/skills/orient/SKILL.md +45 -36
  24. package/templates/skills/plan/SKILL.md +31 -2
  25. package/templates/skills/plan/phases/overlap-check.md +8 -4
  26. package/templates/skills/plan/phases/work-tracker.md +6 -2
  27. package/templates/skills/triage-audit/SKILL.md +17 -7
  28. package/templates/skills/triage-audit/phases/apply-verdicts.md +14 -5
  29. package/templates/skills/triage-audit/phases/load-findings.md +8 -5
package/lib/cli.js CHANGED
@@ -252,6 +252,9 @@ function generateSkillIndex(projectDir) {
252
252
  type: isCabinet ? 'cabinet' : 'workflow',
253
253
  };
254
254
 
255
+ // Argument hint (for skill index consumers)
256
+ if (fm['argument-hint']) entry.argumentHint = fm['argument-hint'];
257
+
255
258
  // Invocability flags
256
259
  if (fm['disable-model-invocation'] === 'true') entry.manual = true;
257
260
  if (fm['user-invocable'] === 'false') entry.userInvocable = false;
@@ -361,7 +364,7 @@ const MODULES = {
361
364
  mandatory: false,
362
365
  default: true,
363
366
  lean: false,
364
- templates: ['scripts/pib-db.mjs', 'scripts/pib-db-schema.sql', 'scripts/work-tracker-server.mjs', 'scripts/work-tracker-ui.html', 'skills/work-tracker'],
367
+ templates: ['scripts/pib-db.mjs', 'scripts/pib-db-lib.mjs', 'scripts/pib-db-mcp-server.mjs', 'scripts/pib-db-schema.sql', 'scripts/work-tracker-server.mjs', 'scripts/work-tracker-ui.html', 'skills/work-tracker'],
365
368
  needsDb: true,
366
369
  },
367
370
  'planning': {
@@ -391,7 +394,8 @@ const MODULES = {
391
394
  'cabinet', 'briefing',
392
395
  'skills/cabinet-accessibility', 'skills/cabinet-anti-confirmation',
393
396
  'skills/cabinet-architecture', 'skills/cabinet-boundary-man',
394
- 'skills/cabinet-cc-health', 'skills/cabinet-data-integrity',
397
+ 'skills/cabinet-anthropic-insider', 'skills/cabinet-cc-health',
398
+ 'skills/cabinet-data-integrity',
395
399
  'skills/cabinet-debugger', 'skills/cabinet-historian',
396
400
  'skills/cabinet-organized-mind', 'skills/cabinet-process-therapist',
397
401
  'skills/cabinet-qa', 'skills/cabinet-record-keeper',
@@ -891,6 +895,27 @@ async function run() {
891
895
  console.log(` ⚙️ Merged hooks into ${path.relative(projectDir, settingsPath)}`);
892
896
  }
893
897
 
898
+ // --- Merge pib-db MCP server into .mcp.json ---
899
+ if (selectedModules.includes('work-tracking') && !flags.dryRun) {
900
+ try {
901
+ const mcpTemplatePath = path.join(templateRoot, 'mcp', 'pib-db.json');
902
+ if (fs.existsSync(mcpTemplatePath)) {
903
+ const mcpConfig = JSON.parse(fs.readFileSync(mcpTemplatePath, 'utf8'));
904
+ const mcpJsonPath = path.join(projectDir, '.mcp.json');
905
+ let existing = {};
906
+ if (fs.existsSync(mcpJsonPath)) {
907
+ existing = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf8'));
908
+ }
909
+ if (!existing.mcpServers) existing.mcpServers = {};
910
+ Object.assign(existing.mcpServers, mcpConfig.mcpServers);
911
+ fs.writeFileSync(mcpJsonPath, JSON.stringify(existing, null, 2) + '\n');
912
+ console.log(` 🔌 Merged pib-db MCP server into .mcp.json`);
913
+ }
914
+ } catch (err) {
915
+ console.log(` ⚠ MCP config merge failed: ${err.message}`);
916
+ }
917
+ }
918
+
894
919
  // --- Set up database ---
895
920
  if (includeDb && selectedModules.includes('work-tracking') && !flags.dryRun) {
896
921
  try {
@@ -112,14 +112,15 @@ function mergeSettings(projectDir, { includeDb = true, includeMemory = false } =
112
112
  if (!settings.hooks[event]) {
113
113
  settings.hooks[event] = newHooks;
114
114
  } else {
115
- // Add hooks that don't already exist (check by command path)
115
+ // Add hooks that don't already exist (check by command path or prompt text)
116
116
  for (const newHook of newHooks) {
117
- const existingCommands = settings.hooks[event].flatMap(h =>
118
- h.hooks.map(hh => hh.command)
117
+ const hookKey = h => h.command || h.prompt || '';
118
+ const existingKeys = settings.hooks[event].flatMap(h =>
119
+ h.hooks.map(hh => hookKey(hh))
119
120
  );
120
- const newCommands = newHook.hooks.map(h => h.command);
121
- const alreadyExists = newCommands.every(cmd =>
122
- existingCommands.includes(cmd)
121
+ const newKeys = newHook.hooks.map(h => hookKey(h));
122
+ const alreadyExists = newKeys.every(k =>
123
+ existingKeys.includes(k)
123
124
  );
124
125
  if (!alreadyExists) {
125
126
  settings.hooks[event].push(newHook);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-claude-cabinet",
3
- "version": "0.14.2",
3
+ "version": "0.16.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"
@@ -0,0 +1,56 @@
1
+ # pib-db Access Protocol
2
+
3
+ How to interact with the process infrastructure database (pib-db).
4
+
5
+ ## Preference Order
6
+
7
+ 1. **MCP tools (preferred):** If `pib_*` MCP tools are available (check
8
+ by attempting to use them), use them directly. They return structured
9
+ JSON — no parsing needed.
10
+
11
+ 2. **CLI fallback:** If MCP tools are not available, use the CLI:
12
+ ```bash
13
+ node scripts/pib-db.mjs <command> [args]
14
+ ```
15
+
16
+ Skills should reference this document instead of embedding their own
17
+ fallback logic. The access method is determined once at the start of the
18
+ skill execution:
19
+
20
+ ```
21
+ Check: are pib_* MCP tools available?
22
+ YES → use pib_list_projects, pib_create_action, etc.
23
+ NO → use node scripts/pib-db.mjs list-projects, etc.
24
+ ```
25
+
26
+ ## Available Operations
27
+
28
+ | MCP Tool | CLI Equivalent | Description |
29
+ | ---------------------- | --------------------------------------- | ------------------------------ |
30
+ | pib_create_project | create-project "name" --area X | Create a project |
31
+ | pib_list_projects | list-projects | List active projects |
32
+ | pib_create_action | create-action "text" --notes X | Create an action (work item) |
33
+ | pib_list_actions | list-actions [--status X] | List actions |
34
+ | pib_update_action | update-action fid --status X | Update action fields |
35
+ | pib_complete_action | complete-action fid | Mark action done |
36
+ | pib_ingest_findings | ingest-findings run-dir | Ingest audit findings |
37
+ | pib_triage | triage finding-id status [notes] | Triage a finding |
38
+ | pib_triage_history | triage-history | Get suppression list |
39
+ | pib_query | query "SQL" | Run arbitrary SQL |
40
+
41
+ ## Surface Area Validation
42
+
43
+ `pib_create_action` (and the CLI `create-action`) require that notes
44
+ contain a `## Surface Area` section with at least one `- files:` or
45
+ `- dirs:` line. This ensures every action clearly defines what it
46
+ touches.
47
+
48
+ Example notes format:
49
+ ```
50
+ Implement the new feature.
51
+
52
+ ## Surface Area
53
+ - files: src/components/Widget.js
54
+ - files: src/utils/helpers.js
55
+ - dirs: tests/components/
56
+ ```
@@ -0,0 +1,11 @@
1
+ {
2
+ "mcpServers": {
3
+ "pib-db": {
4
+ "command": "node",
5
+ "args": ["scripts/pib-db-mcp-server.mjs"],
6
+ "env": {
7
+ "PIB_DB_PATH": "./pib.db"
8
+ }
9
+ }
10
+ }
11
+ }
@@ -0,0 +1,281 @@
1
+ // Process-in-a-Box shared library
2
+ //
3
+ // All database operations as importable functions.
4
+ // Both the CLI (pib-db.mjs) and MCP server (pib-db-mcp-server.mjs)
5
+ // import from here. Schema changes update one place.
6
+ //
7
+ // Every function takes (db, params) and returns a result object.
8
+ // None of them do console.log — callers decide how to present output.
9
+
10
+ import { existsSync, readFileSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { randomUUID } from 'node:crypto';
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Helpers
16
+ // ---------------------------------------------------------------------------
17
+ function generateFid(prefix) {
18
+ return `${prefix}:${randomUUID().replace(/-/g, '').slice(0, 8)}`;
19
+ }
20
+
21
+ function today() {
22
+ return new Date().toISOString().slice(0, 10);
23
+ }
24
+
25
+ // ---------------------------------------------------------------------------
26
+ // Init — create tables from schema
27
+ // ---------------------------------------------------------------------------
28
+ export function init(db, { schemaPath }) {
29
+ const schema = readFileSync(schemaPath, 'utf-8');
30
+ db.exec(schema);
31
+
32
+ // Migrate existing DBs — add columns that may not exist yet
33
+ const migrations = [
34
+ { table: 'actions', column: 'status', sql: "ALTER TABLE actions ADD COLUMN status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open','in-progress','blocked','deferred','done'))" },
35
+ { table: 'actions', column: 'tags', sql: "ALTER TABLE actions ADD COLUMN tags TEXT NOT NULL DEFAULT ''" },
36
+ ];
37
+ for (const m of migrations) {
38
+ const cols = db.prepare(`PRAGMA table_info(${m.table})`).all();
39
+ if (!cols.some(c => c.name === m.column)) {
40
+ try { db.exec(m.sql); } catch { /* column may already exist */ }
41
+ }
42
+ }
43
+
44
+ return { message: `Database initialized` };
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Query — run arbitrary SQL
49
+ // ---------------------------------------------------------------------------
50
+ export function query(db, { sql }) {
51
+ if (sql.trim().toUpperCase().startsWith('SELECT')) {
52
+ const rows = db.prepare(sql).all();
53
+ return { rows };
54
+ } else {
55
+ db.exec(sql);
56
+ return { message: 'Done.' };
57
+ }
58
+ }
59
+
60
+ // ---------------------------------------------------------------------------
61
+ // Actions
62
+ // ---------------------------------------------------------------------------
63
+
64
+ /**
65
+ * Validate that notes contain a ## Surface Area section with at least one
66
+ * - files: or - dirs: line. Returns null if valid, or an error object if not.
67
+ */
68
+ function validateSurfaceArea(notes) {
69
+ if (!notes) {
70
+ return {
71
+ error: 'missing_surface_area',
72
+ message: 'Action notes must contain a ## Surface Area section.',
73
+ suggestedFormat: [
74
+ '## Surface Area',
75
+ '- files: path/to/file.js',
76
+ '- files: path/to/other.js',
77
+ '- dirs: src/components/',
78
+ ].join('\n'),
79
+ };
80
+ }
81
+
82
+ const hasSection = /^## Surface Area/m.test(notes);
83
+ if (!hasSection) {
84
+ return {
85
+ error: 'missing_surface_area',
86
+ message: 'Action notes must contain a ## Surface Area section.',
87
+ suggestedFormat: [
88
+ '## Surface Area',
89
+ '- files: path/to/file.js',
90
+ '- files: path/to/other.js',
91
+ '- dirs: src/components/',
92
+ ].join('\n'),
93
+ };
94
+ }
95
+
96
+ // Extract everything after ## Surface Area until the next ## or end
97
+ const sectionMatch = notes.match(/^## Surface Area\s*\n([\s\S]*?)(?=\n## |\n*$)/m);
98
+ const sectionBody = sectionMatch ? sectionMatch[1] : '';
99
+ const hasEntry = /^- (?:files|dirs):/m.test(sectionBody);
100
+ if (!hasEntry) {
101
+ return {
102
+ error: 'empty_surface_area',
103
+ message: '## Surface Area section must contain at least one "- files:" or "- dirs:" line.',
104
+ suggestedFormat: [
105
+ '## Surface Area',
106
+ '- files: path/to/file.js',
107
+ '- dirs: src/components/',
108
+ ].join('\n'),
109
+ };
110
+ }
111
+
112
+ return null; // valid
113
+ }
114
+
115
+ export function createAction(db, { text, area, projectFid, due, notes }) {
116
+ const validationError = validateSurfaceArea(notes);
117
+ if (validationError) {
118
+ return { error: validationError };
119
+ }
120
+
121
+ const fid = generateFid('act');
122
+ db.prepare(`
123
+ INSERT INTO actions (fid, text, area, project_fid, due, notes, created)
124
+ VALUES (?, ?, ?, ?, ?, ?, ?)
125
+ `).run(fid, text, area || null, projectFid || null, due || null, notes || '', today());
126
+ return { fid, text, message: `Created action ${fid}: ${text}` };
127
+ }
128
+
129
+ export function listActions(db, { status, project } = {}) {
130
+ const conditions = ['a.deleted_at IS NULL'];
131
+ const params = [];
132
+
133
+ if (status) {
134
+ conditions.push('a.status = ?');
135
+ params.push(status);
136
+ } else {
137
+ conditions.push('a.completed = 0');
138
+ }
139
+ if (project) {
140
+ conditions.push('a.project_fid = ?');
141
+ params.push(project);
142
+ }
143
+
144
+ const rows = db.prepare(`
145
+ SELECT a.fid, a.text, a.area, a.due, a.flagged, a.status, a.tags, p.name as project
146
+ FROM actions a
147
+ LEFT JOIN projects p ON a.project_fid = p.fid
148
+ WHERE ${conditions.join(' AND ')}
149
+ ORDER BY
150
+ CASE WHEN a.due IS NOT NULL AND a.due <= date('now') THEN 0 ELSE 1 END,
151
+ a.due,
152
+ a.flagged DESC,
153
+ a.created DESC
154
+ `).all(...params);
155
+ return { rows };
156
+ }
157
+
158
+ export function updateAction(db, { fid, status, text, tags, notes, due, flagged }) {
159
+ const sets = [];
160
+ const params = [];
161
+
162
+ if (status !== undefined) { sets.push('status = ?'); params.push(status); }
163
+ if (text !== undefined) { sets.push('text = ?'); params.push(text); }
164
+ if (tags !== undefined) { sets.push('tags = ?'); params.push(tags); }
165
+ if (notes !== undefined) { sets.push('notes = ?'); params.push(notes); }
166
+ if (due !== undefined) { sets.push('due = ?'); params.push(due); }
167
+ if (flagged !== undefined) { sets.push('flagged = ?'); params.push(flagged === 'true' || flagged === '1' || flagged === true ? 1 : 0); }
168
+
169
+ // If marking done, also set completed fields
170
+ if (status === 'done') {
171
+ sets.push('completed = 1', 'completed_at = ?');
172
+ params.push(new Date().toISOString());
173
+ }
174
+
175
+ if (sets.length === 0) {
176
+ return { error: { message: 'No fields to update. Use status, text, tags, notes, due, or flagged.' } };
177
+ }
178
+
179
+ params.push(fid);
180
+ db.prepare(`UPDATE actions SET ${sets.join(', ')} WHERE fid = ?`).run(...params);
181
+ return { fid, message: `Updated ${fid}` };
182
+ }
183
+
184
+ export function completeAction(db, { fid }) {
185
+ db.prepare(`
186
+ UPDATE actions SET completed = 1, completed_at = ?, status = 'done' WHERE fid = ?
187
+ `).run(new Date().toISOString(), fid);
188
+ return { fid, message: `Completed ${fid}` };
189
+ }
190
+
191
+ // ---------------------------------------------------------------------------
192
+ // Projects
193
+ // ---------------------------------------------------------------------------
194
+ export function createProject(db, { name, area, notes, due }) {
195
+ const fid = generateFid('prj');
196
+ db.prepare(`
197
+ INSERT INTO projects (fid, name, area, notes, due, created)
198
+ VALUES (?, ?, ?, ?, ?, ?)
199
+ `).run(fid, name, area || null, notes || '', due || null, today());
200
+ return { fid, name, message: `Created project ${fid}: ${name}` };
201
+ }
202
+
203
+ export function listProjects(db) {
204
+ const rows = db.prepare(`
205
+ SELECT p.fid, p.name, p.area, p.status, p.due,
206
+ (SELECT COUNT(*) FROM actions a WHERE a.project_fid = p.fid AND a.completed = 0 AND a.deleted_at IS NULL) as open_actions
207
+ FROM projects p
208
+ WHERE p.status = 'active' AND p.deleted_at IS NULL
209
+ ORDER BY p.created DESC
210
+ `).all();
211
+ return { rows };
212
+ }
213
+
214
+ // ---------------------------------------------------------------------------
215
+ // Audit — ingest findings from a run directory
216
+ // ---------------------------------------------------------------------------
217
+ export function ingestFindings(db, { runDir }) {
218
+ const summaryPath = join(runDir, 'run-summary.json');
219
+ if (!existsSync(summaryPath)) {
220
+ return { error: { message: `No run-summary.json found in ${runDir}` } };
221
+ }
222
+ const data = JSON.parse(readFileSync(summaryPath, 'utf-8'));
223
+ const runId = data.meta?.runId || `run-${Date.now()}`;
224
+ const timestamp = data.meta?.timestamp || new Date().toISOString();
225
+ const dateStr = timestamp.slice(0, 10);
226
+
227
+ db.prepare(`
228
+ INSERT OR REPLACE INTO audit_runs (id, date, timestamp, trigger, finding_count)
229
+ VALUES (?, ?, ?, ?, ?)
230
+ `).run(runId, dateStr, timestamp, data.meta?.trigger || 'manual', data.findings?.length || 0);
231
+
232
+ const insert = db.prepare(`
233
+ INSERT OR REPLACE INTO audit_findings
234
+ (id, run_id, cabinet_member, severity, title, description, assumption,
235
+ evidence, question, file, line, suggested_fix, auto_fixable, type)
236
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
237
+ `);
238
+
239
+ let count = 0;
240
+ for (const f of (data.findings || [])) {
241
+ insert.run(
242
+ f.id, runId, f['cabinet-member'], f.severity, f.title,
243
+ f.description || null, f.assumption || null, f.evidence || null,
244
+ f.question || null, f.file || null, f.line || null,
245
+ f.suggestedFix || null, f.autoFixable ? 1 : 0, f.type || 'finding'
246
+ );
247
+ count++;
248
+ }
249
+ return { count, runId, message: `Ingested ${count} findings from ${runDir} (run: ${runId})` };
250
+ }
251
+
252
+ // ---------------------------------------------------------------------------
253
+ // Triage
254
+ // ---------------------------------------------------------------------------
255
+ export function triage(db, { findingId, status, notes }) {
256
+ db.prepare(`
257
+ UPDATE audit_findings
258
+ SET triage_status = ?, triage_notes = ?, triaged_at = ?
259
+ WHERE id = ?
260
+ `).run(status, notes || null, new Date().toISOString(), findingId);
261
+ return { findingId, status, message: `Triaged ${findingId} → ${status}` };
262
+ }
263
+
264
+ export function triageHistory(db) {
265
+ const rejected = db.prepare(`
266
+ SELECT id, cabinet_member, title FROM audit_findings
267
+ WHERE triage_status = 'rejected'
268
+ `).all();
269
+
270
+ const deferred = db.prepare(`
271
+ SELECT id, cabinet_member, title FROM audit_findings
272
+ WHERE triage_status = 'deferred'
273
+ `).all();
274
+
275
+ return {
276
+ rejectedIds: rejected.map(r => r.id),
277
+ rejectedFingerprints: rejected.map(r => ({ 'cabinet-member': r.cabinet_member, title: r.title })),
278
+ deferredIds: deferred.map(r => r.id),
279
+ deferredFingerprints: deferred.map(r => ({ 'cabinet-member': r.cabinet_member, title: r.title })),
280
+ };
281
+ }