create-claude-cabinet 0.37.0 → 0.39.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/README.md +4 -4
- package/lib/cli.js +66 -3
- package/lib/engagement-server-setup.js +189 -0
- package/lib/mux-setup.js +189 -0
- package/lib/settings-merge.js +56 -1
- package/package.json +1 -1
- package/templates/cabinet/committees.yaml +1 -0
- package/templates/cabinet/qa-dimensions-template.yaml +31 -5
- package/templates/cabinet/watchtower-contracts.md +87 -0
- package/templates/engagement/capture-and-encrypt.mjs +14 -5
- package/templates/engagement/engagement-schema.md +271 -8
- package/templates/engagement/engagement-transport.mjs +99 -2
- package/templates/engagement/sql-constants.mjs +1 -1
- package/templates/engagement-server/Dockerfile +12 -0
- package/templates/engagement-server/engage-server.mjs +211 -0
- package/templates/engagement-server/package.json +12 -0
- package/templates/engagement-server/railway.toml +10 -0
- package/templates/engagement-server/schema.sql +46 -0
- package/templates/engagement-server/server.mjs +412 -0
- package/templates/hooks/watchtower-session-end.sh +74 -0
- package/templates/hooks/watchtower-session-start.sh +41 -0
- package/templates/mux/bin/mux +1119 -0
- package/templates/mux/config/_mux +70 -0
- package/templates/mux/config/context-help.py +49 -0
- package/templates/mux/config/dashboard-wrapper.sh +16 -0
- package/templates/mux/config/dashboard.py +122 -0
- package/templates/mux/config/dx-wrapper.sh +12 -0
- package/templates/mux/config/help.txt +38 -0
- package/templates/mux/config/manage-dx.py +105 -0
- package/templates/mux/config/manage-notes.py +73 -0
- package/templates/mux/config/mux-server.py +295 -0
- package/templates/mux/config/mux.bash +43 -0
- package/templates/mux/config/muxlib.py +395 -0
- package/templates/mux/config/show-dx.py +89 -0
- package/templates/mux/config/show-notes.py +87 -0
- package/templates/mux/config/sidebar-wrapper.sh +56 -0
- package/templates/mux/config/status-indicators.sh +44 -0
- package/templates/mux/config/status-popup.py +200 -0
- package/templates/mux/config/status-set.sh +44 -0
- package/templates/mux/config/trail-add.sh +49 -0
- package/templates/mux/config/trail-popup.py +210 -0
- package/templates/scripts/pib-db-lib.mjs +0 -47
- package/templates/scripts/qa-dimensions-validator.cjs +102 -0
- package/templates/scripts/watchtower-build-context.mjs +248 -0
- package/templates/scripts/watchtower-lib.mjs +78 -0
- package/templates/scripts/watchtower-queue.mjs +265 -0
- package/templates/scripts/watchtower-ring1-runner.sh +77 -0
- package/templates/scripts/watchtower-ring1.mjs +669 -0
- package/templates/scripts/watchtower-ring2-runner.sh +92 -0
- package/templates/scripts/watchtower-ring2.mjs +1066 -0
- package/templates/scripts/watchtower-ring3-close.mjs +972 -0
- package/templates/scripts/watchtower-validate.mjs +134 -0
- package/templates/skills/briefing/SKILL.md +242 -0
- package/templates/skills/cabinet-elegance/SKILL.md +284 -0
- package/templates/skills/cc-upgrade/SKILL.md +18 -0
- package/templates/skills/checklist-discover/SKILL.md +75 -28
- package/templates/skills/collab-client/SKILL.md +181 -46
- package/templates/skills/collab-consultant/SKILL.md +313 -75
- package/templates/skills/decisions/SKILL.md +164 -0
- package/templates/skills/orient/phases/checklist-status.md +42 -0
- package/templates/skills/orient/phases/dx-captures.md +52 -0
- package/templates/skills/setup-accounts/SKILL.md +584 -0
- package/templates/skills/validate/phases/validators.md +12 -0
- package/templates/skills/watchtower/SKILL.md +252 -0
- package/templates/watchtower/com.claude-cabinet.watchtower-ring1.plist +38 -0
- package/templates/watchtower/com.claude-cabinet.watchtower-ring2-fast.plist +39 -0
- package/templates/watchtower/com.claude-cabinet.watchtower-ring2-slow.plist +39 -0
- package/templates/watchtower/config.json.template +25 -0
- package/templates/watchtower/queue/items/item.json.schema +78 -0
- package/templates/watchtower/state/projects/project.md.template +13 -0
- package/templates/watchtower/state/summary.md.template +18 -0
- package/templates/watchtower/watchtower-ring1.service +16 -0
- package/templates/watchtower/watchtower-ring1.timer +15 -0
- package/templates/watchtower/watchtower-ring2-fast.service +16 -0
- package/templates/watchtower/watchtower-ring2-fast.timer +15 -0
- package/templates/watchtower/watchtower-ring2-slow.service +16 -0
- package/templates/watchtower/watchtower-ring2-slow.timer +15 -0
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Claude Cabinet
|
|
2
2
|
|
|
3
3
|
A cabinet of expert advisors for your Claude Code project. One command
|
|
4
|
-
gives Claude a memory,
|
|
4
|
+
gives Claude a memory, 32 domain experts, a planning process, and the
|
|
5
5
|
habit of starting sessions informed and ending them properly.
|
|
6
6
|
|
|
7
7
|
Built by a guy who'd rather talk to Claude than write code. Most of it
|
|
@@ -12,7 +12,7 @@ was built by Claude. I just complained until it (mostly) worked.
|
|
|
12
12
|
Your project gets a cabinet — specialist advisors who each own a domain
|
|
13
13
|
and weigh in when their expertise matters:
|
|
14
14
|
|
|
15
|
-
- **Cabinet members** —
|
|
15
|
+
- **Cabinet members** — 32 domain experts (security, accessibility,
|
|
16
16
|
architecture, QA, etc.) who review your project and surface what
|
|
17
17
|
you'd miss alone
|
|
18
18
|
- **Briefings** — project context members read before weighing in
|
|
@@ -78,7 +78,7 @@ left off.
|
|
|
78
78
|
|
|
79
79
|
### The Cabinet (included in lean)
|
|
80
80
|
|
|
81
|
-
|
|
81
|
+
32 expert cabinet members who each own a domain and stay in their lane.
|
|
82
82
|
**Speed-freak** watches performance. **Boundary-man** catches edge cases.
|
|
83
83
|
**Record-keeper** flags when docs drift from code. **Workflow-cop**
|
|
84
84
|
evaluates whether your process actually works. Each member has a
|
|
@@ -240,7 +240,7 @@ source code.
|
|
|
240
240
|
```
|
|
241
241
|
.claude/
|
|
242
242
|
├── skills/ # orient, debrief, plan, execute, audit, etc.
|
|
243
|
-
│ └── cabinet-*/ #
|
|
243
|
+
│ └── cabinet-*/ # 32 cabinet member definitions
|
|
244
244
|
├── cabinet/ # committees, lifecycle, composition patterns
|
|
245
245
|
│ # (incl. pib-db-access.md, pib-db-triggers.md)
|
|
246
246
|
├── briefing/ # project briefing templates
|
package/lib/cli.js
CHANGED
|
@@ -4,12 +4,14 @@ const fs = require('fs');
|
|
|
4
4
|
const os = require('os');
|
|
5
5
|
const crypto = require('crypto');
|
|
6
6
|
const { copyTemplates } = require('./copy');
|
|
7
|
-
const { mergeSettings, healUserSettings } = require('./settings-merge');
|
|
7
|
+
const { mergeSettings, healUserSettings, mergeWatchtowerHooks } = require('./settings-merge');
|
|
8
8
|
const { create: createMetadata, read: readMetadata } = require('./metadata');
|
|
9
9
|
const { setupDb } = require('./db-setup');
|
|
10
10
|
const { setupVerifyRuntime } = require('./verify-setup');
|
|
11
11
|
const { setupSiteAuditRuntime } = require('./site-audit-setup');
|
|
12
12
|
const { setupEngagement } = require('./engagement-setup');
|
|
13
|
+
const { setupMux } = require('./mux-setup');
|
|
14
|
+
const { setupEngagementServer } = require('./engagement-server-setup');
|
|
13
15
|
const { reset } = require('./reset');
|
|
14
16
|
|
|
15
17
|
const VERSION = require('../package.json').version;
|
|
@@ -486,7 +488,7 @@ const MODULES = {
|
|
|
486
488
|
mandatory: false,
|
|
487
489
|
default: true,
|
|
488
490
|
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'],
|
|
491
|
+
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
492
|
},
|
|
491
493
|
'compliance': {
|
|
492
494
|
name: 'Compliance Stack (rules + enforcement)',
|
|
@@ -543,6 +545,7 @@ const MODULES = {
|
|
|
543
545
|
'skills/cabinet-ui-experimentalist', 'skills/cabinet-user-advocate',
|
|
544
546
|
'skills/cabinet-vision',
|
|
545
547
|
'skills/cabinet-narrative-architect', 'skills/cabinet-interactive-storyteller',
|
|
548
|
+
'skills/cabinet-elegance',
|
|
546
549
|
'scripts/merge-findings.js', 'scripts/load-triage-history.js',
|
|
547
550
|
'scripts/triage-server.mjs', 'scripts/triage-ui.html',
|
|
548
551
|
'scripts/finding-schema.json', 'scripts/resolve-committees.cjs',
|
|
@@ -595,7 +598,7 @@ const MODULES = {
|
|
|
595
598
|
},
|
|
596
599
|
engagement: {
|
|
597
600
|
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).
|
|
601
|
+
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
602
|
mandatory: false,
|
|
600
603
|
default: false,
|
|
601
604
|
lean: false,
|
|
@@ -613,10 +616,63 @@ const MODULES = {
|
|
|
613
616
|
'skills/engagement-add',
|
|
614
617
|
'skills/engagement-status',
|
|
615
618
|
'skills/engagement-sync',
|
|
619
|
+
'skills/setup-accounts',
|
|
616
620
|
'skills/guide',
|
|
617
621
|
'engagement',
|
|
618
622
|
],
|
|
619
623
|
},
|
|
624
|
+
watchtower: {
|
|
625
|
+
name: 'Watchtower (continuous background state)',
|
|
626
|
+
description: 'Replaces orient/debrief with continuous background processing. Three rings (mechanical cron, Claude intelligence, session-aware), ambient state injection via SessionStart hook, and an asynchronous decision queue. Sessions start informed with minimal context cost.',
|
|
627
|
+
mandatory: false,
|
|
628
|
+
default: false,
|
|
629
|
+
lean: false,
|
|
630
|
+
templates: [
|
|
631
|
+
'scripts/watchtower-validate.mjs',
|
|
632
|
+
'cabinet/watchtower-contracts.md',
|
|
633
|
+
'scripts/watchtower-lib.mjs',
|
|
634
|
+
'scripts/watchtower-queue.mjs',
|
|
635
|
+
'skills/decisions',
|
|
636
|
+
'hooks/watchtower-session-start.sh',
|
|
637
|
+
'scripts/watchtower-build-context.mjs',
|
|
638
|
+
'scripts/watchtower-ring1.mjs',
|
|
639
|
+
'scripts/watchtower-ring1-runner.sh',
|
|
640
|
+
'skills/watchtower',
|
|
641
|
+
'watchtower/com.claude-cabinet.watchtower-ring1.plist',
|
|
642
|
+
'watchtower/watchtower-ring1.service',
|
|
643
|
+
'watchtower/watchtower-ring1.timer',
|
|
644
|
+
'scripts/watchtower-ring2.mjs',
|
|
645
|
+
'scripts/watchtower-ring2-runner.sh',
|
|
646
|
+
'watchtower/com.claude-cabinet.watchtower-ring2-fast.plist',
|
|
647
|
+
'watchtower/com.claude-cabinet.watchtower-ring2-slow.plist',
|
|
648
|
+
'watchtower/watchtower-ring2-fast.service',
|
|
649
|
+
'watchtower/watchtower-ring2-fast.timer',
|
|
650
|
+
'watchtower/watchtower-ring2-slow.service',
|
|
651
|
+
'watchtower/watchtower-ring2-slow.timer',
|
|
652
|
+
'hooks/watchtower-session-end.sh',
|
|
653
|
+
'scripts/watchtower-ring3-close.mjs',
|
|
654
|
+
'skills/briefing',
|
|
655
|
+
],
|
|
656
|
+
},
|
|
657
|
+
mux: {
|
|
658
|
+
name: 'Mux (tmux project manager)',
|
|
659
|
+
description: 'Multi-project terminal manager. Desks (tmux sessions), auto-worktrees, trail logging, sticky notes, portal color-switching, and an MCP server for Claude. Installs to user-level paths (~/.local/bin/, ~/.config/mux/).',
|
|
660
|
+
mandatory: false,
|
|
661
|
+
default: false,
|
|
662
|
+
lean: false,
|
|
663
|
+
postInstall: 'mux-setup',
|
|
664
|
+
templates: ['skills/orient/phases/dx-captures.md'],
|
|
665
|
+
},
|
|
666
|
+
'engagement-server': {
|
|
667
|
+
name: 'Engagement Server',
|
|
668
|
+
description: 'Central multi-engagement API server. Deploys once to Railway/Fly, serves all client engagements. User-level infrastructure.',
|
|
669
|
+
mandatory: false,
|
|
670
|
+
default: false,
|
|
671
|
+
lean: false,
|
|
672
|
+
requires: ['engagement', 'work-tracking'],
|
|
673
|
+
postInstall: 'engagement-server-setup',
|
|
674
|
+
templates: [],
|
|
675
|
+
},
|
|
620
676
|
};
|
|
621
677
|
|
|
622
678
|
/** Recursively collect all relative file paths under a directory. */
|
|
@@ -1224,6 +1280,11 @@ async function run() {
|
|
|
1224
1280
|
if (selectedModules.includes('hooks') && !flags.dryRun) {
|
|
1225
1281
|
const settingsPath = mergeSettings(projectDir, { includeDb });
|
|
1226
1282
|
console.log(` ⚙️ Merged hooks into ${path.relative(projectDir, settingsPath)}`);
|
|
1283
|
+
|
|
1284
|
+
if (selectedModules.includes('watchtower')) {
|
|
1285
|
+
mergeWatchtowerHooks(settingsPath);
|
|
1286
|
+
console.log(' ⚙️ Registered watchtower SessionStart/SessionEnd hooks');
|
|
1287
|
+
}
|
|
1227
1288
|
}
|
|
1228
1289
|
|
|
1229
1290
|
// --- Heal user-level ~/.claude/settings.json ---
|
|
@@ -1275,6 +1336,8 @@ async function run() {
|
|
|
1275
1336
|
'verify-setup': setupVerifyRuntime,
|
|
1276
1337
|
'site-audit-setup': setupSiteAuditRuntime,
|
|
1277
1338
|
'engagement-setup': setupEngagement,
|
|
1339
|
+
'mux-setup': setupMux,
|
|
1340
|
+
'engagement-server-setup': setupEngagementServer,
|
|
1278
1341
|
};
|
|
1279
1342
|
for (const moduleKey of selectedModules) {
|
|
1280
1343
|
const mod = MODULES[moduleKey];
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* engagement-server-setup.js — install engagement server to user-level paths.
|
|
3
|
+
*
|
|
4
|
+
* Follows the mux-setup.js pattern: user-level infrastructure managed by CC,
|
|
5
|
+
* tracked via global manifest at ~/.claude-cabinet/global-manifest.json.
|
|
6
|
+
*
|
|
7
|
+
* Installs to ~/.claude-cabinet/engagement-server/. The user deploys to
|
|
8
|
+
* Railway (or similar) separately.
|
|
9
|
+
*
|
|
10
|
+
* Version semantics:
|
|
11
|
+
* - First install: copy all managed files, create data dir, write .cc-version
|
|
12
|
+
* - Same version: skip with log
|
|
13
|
+
* - Newer CC version: upgrade managed files, preserve data dir
|
|
14
|
+
* - Older CC version than installed: skip (don't downgrade)
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const os = require('os');
|
|
20
|
+
const crypto = require('crypto');
|
|
21
|
+
|
|
22
|
+
const CC_HOME = path.join(os.homedir(), '.claude-cabinet');
|
|
23
|
+
const GLOBAL_MANIFEST_PATH = path.join(CC_HOME, 'global-manifest.json');
|
|
24
|
+
const INSTALL_DIR = path.join(CC_HOME, 'engagement-server');
|
|
25
|
+
const VERSION_FILE = path.join(INSTALL_DIR, '.cc-version');
|
|
26
|
+
|
|
27
|
+
const MANAGED_FILES = [
|
|
28
|
+
'server.mjs',
|
|
29
|
+
'engage-server.mjs',
|
|
30
|
+
'schema.sql',
|
|
31
|
+
'package.json',
|
|
32
|
+
'Dockerfile',
|
|
33
|
+
'railway.toml',
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
const DATA_DIRS = [
|
|
37
|
+
path.join(INSTALL_DIR, 'data'),
|
|
38
|
+
path.join(INSTALL_DIR, 'migrations'),
|
|
39
|
+
];
|
|
40
|
+
|
|
41
|
+
function sha256(content) {
|
|
42
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function compareVersions(a, b) {
|
|
46
|
+
const pa = a.split('.').map(Number);
|
|
47
|
+
const pb = b.split('.').map(Number);
|
|
48
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
49
|
+
const va = pa[i] || 0;
|
|
50
|
+
const vb = pb[i] || 0;
|
|
51
|
+
if (va > vb) return 1;
|
|
52
|
+
if (va < vb) return -1;
|
|
53
|
+
}
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function readGlobalManifest() {
|
|
58
|
+
if (!fs.existsSync(GLOBAL_MANIFEST_PATH)) return { files: {} };
|
|
59
|
+
try {
|
|
60
|
+
return JSON.parse(fs.readFileSync(GLOBAL_MANIFEST_PATH, 'utf8'));
|
|
61
|
+
} catch {
|
|
62
|
+
return { files: {} };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function writeGlobalManifest(manifest) {
|
|
67
|
+
fs.mkdirSync(path.dirname(GLOBAL_MANIFEST_PATH), { recursive: true });
|
|
68
|
+
const tmp = GLOBAL_MANIFEST_PATH + '.tmp';
|
|
69
|
+
fs.writeFileSync(tmp, JSON.stringify(manifest, null, 2));
|
|
70
|
+
fs.renameSync(tmp, GLOBAL_MANIFEST_PATH);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function readInstalledVersion() {
|
|
74
|
+
if (!fs.existsSync(VERSION_FILE)) return null;
|
|
75
|
+
try {
|
|
76
|
+
return fs.readFileSync(VERSION_FILE, 'utf8').trim();
|
|
77
|
+
} catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function writeVersion(version) {
|
|
83
|
+
fs.mkdirSync(path.dirname(VERSION_FILE), { recursive: true });
|
|
84
|
+
fs.writeFileSync(VERSION_FILE, version + '\n');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @param {Object} opts
|
|
89
|
+
* @param {boolean} [opts.dryRun]
|
|
90
|
+
* @param {string} [opts.projectDir]
|
|
91
|
+
* @returns {{ results: string[], status: string }}
|
|
92
|
+
*/
|
|
93
|
+
function setupEngagementServer(opts = {}) {
|
|
94
|
+
const dryRun = !!opts.dryRun;
|
|
95
|
+
const results = [];
|
|
96
|
+
|
|
97
|
+
const ccVersion = require('../package.json').version;
|
|
98
|
+
const templateDir = path.resolve(__dirname, '..', 'templates', 'engagement-server');
|
|
99
|
+
|
|
100
|
+
if (!fs.existsSync(templateDir)) {
|
|
101
|
+
throw new Error(`engagement-server-setup: ${templateDir} not found.`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const installedVersion = readInstalledVersion();
|
|
105
|
+
|
|
106
|
+
if (installedVersion) {
|
|
107
|
+
const cmp = compareVersions(ccVersion, installedVersion);
|
|
108
|
+
if (cmp === 0) {
|
|
109
|
+
results.push(`engagement-server ${installedVersion} already installed — skipping`);
|
|
110
|
+
return { results, status: 'skipped' };
|
|
111
|
+
}
|
|
112
|
+
if (cmp < 0) {
|
|
113
|
+
results.push(`engagement-server ${installedVersion} is newer than CC ${ccVersion} — skipping (won't downgrade)`);
|
|
114
|
+
return { results, status: 'skipped' };
|
|
115
|
+
}
|
|
116
|
+
results.push(`Upgrading engagement-server from ${installedVersion} to ${ccVersion}`);
|
|
117
|
+
} else {
|
|
118
|
+
results.push(`Installing engagement-server ${ccVersion}`);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Create data directories (preserved across upgrades)
|
|
122
|
+
for (const dir of DATA_DIRS) {
|
|
123
|
+
if (!fs.existsSync(dir)) {
|
|
124
|
+
if (dryRun) {
|
|
125
|
+
results.push(` [dry-run] mkdir -p ${dir}`);
|
|
126
|
+
} else {
|
|
127
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Copy managed files
|
|
133
|
+
const manifest = readGlobalManifest();
|
|
134
|
+
let copiedCount = 0;
|
|
135
|
+
|
|
136
|
+
for (const file of MANAGED_FILES) {
|
|
137
|
+
const srcPath = path.join(templateDir, file);
|
|
138
|
+
const destPath = path.join(INSTALL_DIR, file);
|
|
139
|
+
|
|
140
|
+
if (!fs.existsSync(srcPath)) {
|
|
141
|
+
results.push(` ⚠ Template missing: ${file}`);
|
|
142
|
+
continue;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const content = fs.readFileSync(srcPath);
|
|
146
|
+
const hash = sha256(content);
|
|
147
|
+
|
|
148
|
+
if (manifest.files[destPath] === hash) {
|
|
149
|
+
continue;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (dryRun) {
|
|
153
|
+
results.push(` [dry-run] ${file} → ${destPath}`);
|
|
154
|
+
} else {
|
|
155
|
+
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
156
|
+
fs.writeFileSync(destPath, content);
|
|
157
|
+
manifest.files[destPath] = hash;
|
|
158
|
+
}
|
|
159
|
+
copiedCount++;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!dryRun) {
|
|
163
|
+
manifest.version = ccVersion;
|
|
164
|
+
manifest.installedAt = new Date().toISOString();
|
|
165
|
+
writeGlobalManifest(manifest);
|
|
166
|
+
writeVersion(ccVersion);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (copiedCount > 0) {
|
|
170
|
+
results.push(` Copied ${copiedCount} file(s) to ${INSTALL_DIR}`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!installedVersion) {
|
|
174
|
+
results.push('');
|
|
175
|
+
results.push(' Next steps:');
|
|
176
|
+
results.push(` cd ${INSTALL_DIR}`);
|
|
177
|
+
results.push(' npm install');
|
|
178
|
+
results.push(' railway init # or railway link');
|
|
179
|
+
results.push(' railway volume add -m /app/data');
|
|
180
|
+
results.push(' railway up --detach');
|
|
181
|
+
} else {
|
|
182
|
+
results.push('');
|
|
183
|
+
results.push(` Re-deploy: cd ${INSTALL_DIR} && railway up --detach`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
return { results, status: installedVersion ? 'upgraded' : 'installed' };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = { setupEngagementServer };
|
package/lib/mux-setup.js
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mux-setup.js — install mux files to user-level paths.
|
|
3
|
+
*
|
|
4
|
+
* First user-level module in CC. Unlike project-scoped templates that
|
|
5
|
+
* copy to .claude/ and scripts/, mux installs to ~/.local/bin/,
|
|
6
|
+
* ~/.config/mux/, and ~/.local/share/mux/. Tracked via a global
|
|
7
|
+
* manifest at ~/.claude-cabinet/global-manifest.json.
|
|
8
|
+
*
|
|
9
|
+
* Version semantics:
|
|
10
|
+
* - First install: copy all managed files, write .cc-version
|
|
11
|
+
* - Same version: skip with log
|
|
12
|
+
* - Newer CC version: upgrade managed files, preserve data dirs
|
|
13
|
+
* - Older CC version than installed: skip (don't downgrade)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('fs');
|
|
17
|
+
const path = require('path');
|
|
18
|
+
const os = require('os');
|
|
19
|
+
const crypto = require('crypto');
|
|
20
|
+
|
|
21
|
+
const CC_HOME = path.join(os.homedir(), '.claude-cabinet');
|
|
22
|
+
const GLOBAL_MANIFEST_PATH = path.join(CC_HOME, 'global-manifest.json');
|
|
23
|
+
const MUX_VERSION_FILE = path.join(os.homedir(), '.config', 'mux', '.cc-version');
|
|
24
|
+
|
|
25
|
+
const MANAGED_FILES = [
|
|
26
|
+
{ src: 'bin/mux', dest: path.join(os.homedir(), '.local', 'bin', 'mux'), mode: 0o755 },
|
|
27
|
+
{ src: 'config/muxlib.py', dest: path.join(os.homedir(), '.config', 'mux', 'muxlib.py') },
|
|
28
|
+
{ src: 'config/trail-add.sh', dest: path.join(os.homedir(), '.config', 'mux', 'trail-add.sh'), mode: 0o755 },
|
|
29
|
+
{ src: 'config/status-set.sh', dest: path.join(os.homedir(), '.config', 'mux', 'status-set.sh'), mode: 0o755 },
|
|
30
|
+
{ src: 'config/trail-popup.py', dest: path.join(os.homedir(), '.config', 'mux', 'trail-popup.py') },
|
|
31
|
+
{ src: 'config/status-popup.py', dest: path.join(os.homedir(), '.config', 'mux', 'status-popup.py') },
|
|
32
|
+
{ src: 'config/dashboard.py', dest: path.join(os.homedir(), '.config', 'mux', 'dashboard.py') },
|
|
33
|
+
{ src: 'config/dashboard-wrapper.sh', dest: path.join(os.homedir(), '.config', 'mux', 'dashboard-wrapper.sh'), mode: 0o755 },
|
|
34
|
+
{ src: 'config/sidebar-wrapper.sh', dest: path.join(os.homedir(), '.config', 'mux', 'sidebar-wrapper.sh'), mode: 0o755 },
|
|
35
|
+
{ src: 'config/status-indicators.sh', dest: path.join(os.homedir(), '.config', 'mux', 'status-indicators.sh'), mode: 0o755 },
|
|
36
|
+
{ src: 'config/context-help.py', dest: path.join(os.homedir(), '.config', 'mux', 'context-help.py') },
|
|
37
|
+
{ src: 'config/show-notes.py', dest: path.join(os.homedir(), '.config', 'mux', 'show-notes.py') },
|
|
38
|
+
{ src: 'config/manage-notes.py', dest: path.join(os.homedir(), '.config', 'mux', 'manage-notes.py') },
|
|
39
|
+
{ src: 'config/manage-dx.py', dest: path.join(os.homedir(), '.config', 'mux', 'manage-dx.py') },
|
|
40
|
+
{ src: 'config/show-dx.py', dest: path.join(os.homedir(), '.config', 'mux', 'show-dx.py') },
|
|
41
|
+
{ src: 'config/mux-server.py', dest: path.join(os.homedir(), '.config', 'mux', 'mux-server.py') },
|
|
42
|
+
{ src: 'config/help.txt', dest: path.join(os.homedir(), '.config', 'mux', 'help.txt') },
|
|
43
|
+
{ src: 'config/_mux', dest: path.join(os.homedir(), '.config', 'mux', '_mux') },
|
|
44
|
+
{ src: 'config/mux.bash', dest: path.join(os.homedir(), '.config', 'mux', 'mux.bash') },
|
|
45
|
+
];
|
|
46
|
+
|
|
47
|
+
const DATA_DIRS = [
|
|
48
|
+
path.join(os.homedir(), '.local', 'share', 'mux', 'trails'),
|
|
49
|
+
path.join(os.homedir(), '.local', 'share', 'mux', 'status'),
|
|
50
|
+
path.join(os.homedir(), '.config', 'mux', 'notes'),
|
|
51
|
+
path.join(os.homedir(), '.config', 'mux', 'dx'),
|
|
52
|
+
path.join(os.homedir(), '.config', 'mux', 'pending-prompts'),
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
function sha256(content) {
|
|
56
|
+
return crypto.createHash('sha256').update(content).digest('hex');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function compareVersions(a, b) {
|
|
60
|
+
const pa = a.split('.').map(Number);
|
|
61
|
+
const pb = b.split('.').map(Number);
|
|
62
|
+
for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
|
|
63
|
+
const va = pa[i] || 0;
|
|
64
|
+
const vb = pb[i] || 0;
|
|
65
|
+
if (va > vb) return 1;
|
|
66
|
+
if (va < vb) return -1;
|
|
67
|
+
}
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readGlobalManifest() {
|
|
72
|
+
if (!fs.existsSync(GLOBAL_MANIFEST_PATH)) return { files: {} };
|
|
73
|
+
try {
|
|
74
|
+
return JSON.parse(fs.readFileSync(GLOBAL_MANIFEST_PATH, 'utf8'));
|
|
75
|
+
} catch {
|
|
76
|
+
return { files: {} };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function writeGlobalManifest(manifest) {
|
|
81
|
+
fs.mkdirSync(path.dirname(GLOBAL_MANIFEST_PATH), { recursive: true });
|
|
82
|
+
const tmp = GLOBAL_MANIFEST_PATH + '.tmp';
|
|
83
|
+
fs.writeFileSync(tmp, JSON.stringify(manifest, null, 2));
|
|
84
|
+
fs.renameSync(tmp, GLOBAL_MANIFEST_PATH);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function readInstalledVersion() {
|
|
88
|
+
if (!fs.existsSync(MUX_VERSION_FILE)) return null;
|
|
89
|
+
try {
|
|
90
|
+
return fs.readFileSync(MUX_VERSION_FILE, 'utf8').trim();
|
|
91
|
+
} catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function writeVersion(version) {
|
|
97
|
+
fs.mkdirSync(path.dirname(MUX_VERSION_FILE), { recursive: true });
|
|
98
|
+
fs.writeFileSync(MUX_VERSION_FILE, version + '\n');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* @param {Object} opts
|
|
103
|
+
* @param {boolean} [opts.dryRun]
|
|
104
|
+
* @param {string} [opts.projectDir] — consumer project dir (unused for mux)
|
|
105
|
+
* @returns {{ results: string[], status: string }}
|
|
106
|
+
*/
|
|
107
|
+
function setupMux(opts = {}) {
|
|
108
|
+
const dryRun = !!opts.dryRun;
|
|
109
|
+
const results = [];
|
|
110
|
+
|
|
111
|
+
const ccVersion = require('../package.json').version;
|
|
112
|
+
const templateDir = path.resolve(__dirname, '..', 'templates', 'mux');
|
|
113
|
+
|
|
114
|
+
if (!fs.existsSync(templateDir)) {
|
|
115
|
+
throw new Error(`mux-setup: ${templateDir} not found.`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const installedVersion = readInstalledVersion();
|
|
119
|
+
|
|
120
|
+
if (installedVersion) {
|
|
121
|
+
const cmp = compareVersions(ccVersion, installedVersion);
|
|
122
|
+
if (cmp === 0) {
|
|
123
|
+
results.push(`mux ${installedVersion} already installed — skipping`);
|
|
124
|
+
return { results, status: 'skipped' };
|
|
125
|
+
}
|
|
126
|
+
if (cmp < 0) {
|
|
127
|
+
results.push(`mux ${installedVersion} is newer than CC ${ccVersion} — skipping (won't downgrade)`);
|
|
128
|
+
return { results, status: 'skipped' };
|
|
129
|
+
}
|
|
130
|
+
results.push(`Upgrading mux from ${installedVersion} to ${ccVersion}`);
|
|
131
|
+
} else {
|
|
132
|
+
results.push(`Installing mux ${ccVersion}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Create data directories
|
|
136
|
+
for (const dir of DATA_DIRS) {
|
|
137
|
+
if (!fs.existsSync(dir)) {
|
|
138
|
+
if (dryRun) {
|
|
139
|
+
results.push(` [dry-run] mkdir -p ${dir}`);
|
|
140
|
+
} else {
|
|
141
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Copy managed files
|
|
147
|
+
const manifest = readGlobalManifest();
|
|
148
|
+
let copiedCount = 0;
|
|
149
|
+
|
|
150
|
+
for (const file of MANAGED_FILES) {
|
|
151
|
+
const srcPath = path.join(templateDir, file.src);
|
|
152
|
+
if (!fs.existsSync(srcPath)) {
|
|
153
|
+
results.push(` ⚠ Template missing: ${file.src}`);
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const content = fs.readFileSync(srcPath);
|
|
158
|
+
const hash = sha256(content);
|
|
159
|
+
|
|
160
|
+
if (manifest.files[file.dest] === hash) {
|
|
161
|
+
continue; // file unchanged
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (dryRun) {
|
|
165
|
+
results.push(` [dry-run] ${file.src} → ${file.dest}`);
|
|
166
|
+
} else {
|
|
167
|
+
fs.mkdirSync(path.dirname(file.dest), { recursive: true });
|
|
168
|
+
fs.writeFileSync(file.dest, content);
|
|
169
|
+
if (file.mode) {
|
|
170
|
+
fs.chmodSync(file.dest, file.mode);
|
|
171
|
+
}
|
|
172
|
+
manifest.files[file.dest] = hash;
|
|
173
|
+
}
|
|
174
|
+
copiedCount++;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!dryRun) {
|
|
178
|
+
manifest.version = ccVersion;
|
|
179
|
+
manifest.installedAt = new Date().toISOString();
|
|
180
|
+
writeGlobalManifest(manifest);
|
|
181
|
+
writeVersion(ccVersion);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
results.push(` ${copiedCount} file${copiedCount !== 1 ? 's' : ''} installed to user paths`);
|
|
185
|
+
|
|
186
|
+
return { results, status: installedVersion ? 'upgraded' : 'installed' };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
module.exports = { setupMux };
|
package/lib/settings-merge.js
CHANGED
|
@@ -82,6 +82,31 @@ const DEFAULT_HOOKS = {
|
|
|
82
82
|
],
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
+
const WATCHTOWER_HOOKS = {
|
|
86
|
+
SessionStart: [
|
|
87
|
+
{
|
|
88
|
+
matcher: '',
|
|
89
|
+
hooks: [
|
|
90
|
+
{
|
|
91
|
+
type: 'command',
|
|
92
|
+
command: '$HOME/.claude-cabinet/watchtower/hooks/watchtower-session-start.sh',
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
SessionEnd: [
|
|
98
|
+
{
|
|
99
|
+
matcher: '',
|
|
100
|
+
hooks: [
|
|
101
|
+
{
|
|
102
|
+
type: 'command',
|
|
103
|
+
command: '$HOME/.claude-cabinet/watchtower/hooks/watchtower-session-end.sh',
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
};
|
|
109
|
+
|
|
85
110
|
// Legacy hook script names that should be stripped on any merge.
|
|
86
111
|
// Centralizes cleanup so a user who skips --migrate-memory but runs
|
|
87
112
|
// any other CC operation still gets omega-era hooks pruned.
|
|
@@ -209,4 +234,34 @@ function healUserSettings() {
|
|
|
209
234
|
return removed;
|
|
210
235
|
}
|
|
211
236
|
|
|
212
|
-
|
|
237
|
+
/**
|
|
238
|
+
* Merge watchtower-specific hooks into project settings.
|
|
239
|
+
* Called from the watchtower module's install path in cli.js — only
|
|
240
|
+
* registers SessionStart/SessionEnd hooks when watchtower is installed.
|
|
241
|
+
*/
|
|
242
|
+
function mergeWatchtowerHooks(settingsPath) {
|
|
243
|
+
if (!fs.existsSync(settingsPath)) return;
|
|
244
|
+
const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
245
|
+
if (!settings.hooks) settings.hooks = {};
|
|
246
|
+
|
|
247
|
+
for (const [event, newHooks] of Object.entries(WATCHTOWER_HOOKS)) {
|
|
248
|
+
if (!settings.hooks[event]) {
|
|
249
|
+
settings.hooks[event] = newHooks;
|
|
250
|
+
} else {
|
|
251
|
+
for (const newHook of newHooks) {
|
|
252
|
+
const hookKey = h => h.command || h.prompt || '';
|
|
253
|
+
const existingKeys = settings.hooks[event].flatMap(h =>
|
|
254
|
+
h.hooks.map(hh => hookKey(hh))
|
|
255
|
+
);
|
|
256
|
+
const newKeys = newHook.hooks.map(h => hookKey(h));
|
|
257
|
+
if (!newKeys.every(k => existingKeys.includes(k))) {
|
|
258
|
+
settings.hooks[event].push(newHook);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
module.exports = { mergeSettings, healUserSettings, mergeWatchtowerHooks, DEFAULT_HOOKS, WATCHTOWER_HOOKS, LEGACY_HOOK_COMMANDS };
|
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."
|