create-claude-cabinet 0.11.0 → 0.11.2
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 +5 -3
- package/lib/cli.js +119 -3
- package/package.json +1 -1
- package/templates/EXTENSIONS.md +2 -2
- package/templates/README.md +191 -450
- package/templates/briefing/_briefing-template.md +14 -7
- package/templates/cabinet/directives-project.yaml +32 -0
- package/templates/cabinet/prompt-guide.md +14 -3
- package/templates/skills/cabinet-record-keeper/SKILL.md +46 -13
- package/templates/skills/debrief/SKILL.md +57 -40
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, 27 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 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** — 27 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
|
|
@@ -37,7 +37,9 @@ say `/onboard`. It'll interview you about your project and set everything
|
|
|
37
37
|
up based on your answers.
|
|
38
38
|
|
|
39
39
|
**New to this?** See [GETTING-STARTED.md](GETTING-STARTED.md) for a
|
|
40
|
-
step-by-step walkthrough.
|
|
40
|
+
step-by-step walkthrough. Then [WORKFLOW-GUIDE.md](WORKFLOW-GUIDE.md)
|
|
41
|
+
for how to use everything — when to plan, when to audit, what the
|
|
42
|
+
cabinet does for you, and how the system grows with your project.
|
|
41
43
|
|
|
42
44
|
### For developers
|
|
43
45
|
|
package/lib/cli.js
CHANGED
|
@@ -27,6 +27,9 @@ function parseFrontmatter(content) {
|
|
|
27
27
|
let nestedMap = null; // The nested map being built
|
|
28
28
|
let nestedSubKey = null; // Current sub-key within the nested map
|
|
29
29
|
let nestedSubValue = '';
|
|
30
|
+
let blockScalarKey = null; // Key when parsing a block scalar (description: >)
|
|
31
|
+
let blockScalarValue = '';
|
|
32
|
+
let blockScalarStyle = null; // '>' for folded, '|' for literal
|
|
30
33
|
|
|
31
34
|
function flushNested() {
|
|
32
35
|
if (nestedSubKey && nestedMap) {
|
|
@@ -50,6 +53,26 @@ function parseFrontmatter(content) {
|
|
|
50
53
|
}
|
|
51
54
|
|
|
52
55
|
for (const line of match[1].split('\n')) {
|
|
56
|
+
// Inside a block scalar (description: > or description: |)?
|
|
57
|
+
if (blockScalarKey) {
|
|
58
|
+
if (/^[\s]/.test(line) && line.trim() !== '') {
|
|
59
|
+
const sep = blockScalarStyle === '|' ? '\n' : ' ';
|
|
60
|
+
blockScalarValue += (blockScalarValue ? sep : '') + line.trim();
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
// Blank line inside block scalar — for '|' style, preserve as newline
|
|
64
|
+
if (line.trim() === '' && blockScalarStyle === '|') {
|
|
65
|
+
blockScalarValue += '\n';
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
// Not indented or blank line in '>' mode — end of block scalar
|
|
69
|
+
fm[blockScalarKey] = blockScalarValue.trim();
|
|
70
|
+
blockScalarKey = null;
|
|
71
|
+
blockScalarValue = '';
|
|
72
|
+
blockScalarStyle = null;
|
|
73
|
+
// Fall through to parse this line
|
|
74
|
+
}
|
|
75
|
+
|
|
53
76
|
// Inside a nested map?
|
|
54
77
|
if (nestedKey) {
|
|
55
78
|
// Nested sub-key (indented key: value)
|
|
@@ -63,6 +86,16 @@ function parseFrontmatter(content) {
|
|
|
63
86
|
nestedSubValue = subKv[2].replace(/^[>|]\s*$/, '');
|
|
64
87
|
continue;
|
|
65
88
|
}
|
|
89
|
+
// First indented line doesn't look like a sub-key — this is a block scalar, not a map
|
|
90
|
+
if (!nestedSubKey && /^[\s]/.test(line) && line.trim() !== '') {
|
|
91
|
+
// Convert from nested map mode to block scalar mode
|
|
92
|
+
blockScalarKey = nestedKey;
|
|
93
|
+
blockScalarStyle = '>'; // Default to folded
|
|
94
|
+
blockScalarValue = line.trim();
|
|
95
|
+
nestedKey = null;
|
|
96
|
+
nestedMap = null;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
66
99
|
// Continuation of nested sub-value (deeply indented)
|
|
67
100
|
if (nestedSubKey && /^ /.test(line)) {
|
|
68
101
|
nestedSubValue += ' ' + line.trim();
|
|
@@ -94,11 +127,12 @@ function parseFrontmatter(content) {
|
|
|
94
127
|
const val = kvMatch[2].trim();
|
|
95
128
|
// Empty value after colon = start of nested map or block scalar
|
|
96
129
|
if (val === '' || val === '>' || val === '|') {
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
//
|
|
130
|
+
// Could be a nested map (directives:) or a block scalar (description: >).
|
|
131
|
+
// Start as nested map; if first indented line has no sub-key, fall back
|
|
132
|
+
// to block scalar mode.
|
|
100
133
|
nestedKey = kvMatch[1];
|
|
101
134
|
nestedMap = {};
|
|
135
|
+
blockScalarStyle = (val === '|') ? '|' : '>'; // Remember style hint
|
|
102
136
|
currentKey = null;
|
|
103
137
|
} else {
|
|
104
138
|
currentKey = kvMatch[1];
|
|
@@ -107,6 +141,9 @@ function parseFrontmatter(content) {
|
|
|
107
141
|
}
|
|
108
142
|
}
|
|
109
143
|
// Flush remaining
|
|
144
|
+
if (blockScalarKey) {
|
|
145
|
+
fm[blockScalarKey] = blockScalarValue.trim();
|
|
146
|
+
}
|
|
110
147
|
if (nestedSubKey && nestedMap) {
|
|
111
148
|
nestedMap[nestedSubKey] = nestedSubValue.trim();
|
|
112
149
|
}
|
|
@@ -126,6 +163,70 @@ function generateSkillIndex(projectDir) {
|
|
|
126
163
|
const skillsDir = path.join(projectDir, '.claude', 'skills');
|
|
127
164
|
if (!fs.existsSync(skillsDir)) return 0;
|
|
128
165
|
|
|
166
|
+
// Read project directive overlay if it exists
|
|
167
|
+
const projectDirectives = {};
|
|
168
|
+
const directivesFile = path.join(projectDir, '.claude', 'cabinet', 'directives-project.yaml');
|
|
169
|
+
if (fs.existsSync(directivesFile)) {
|
|
170
|
+
const dContent = fs.readFileSync(directivesFile, 'utf8');
|
|
171
|
+
// Parse simple YAML: top-level keys are member names, nested keys are
|
|
172
|
+
// standing-mandate (list) and directives (map)
|
|
173
|
+
let currentMember = null;
|
|
174
|
+
let inDirectives = false;
|
|
175
|
+
let currentDirectiveKey = null;
|
|
176
|
+
let currentDirectiveValue = '';
|
|
177
|
+
for (const line of dContent.split('\n')) {
|
|
178
|
+
if (/^\s*#/.test(line) || line.trim() === '') continue;
|
|
179
|
+
const indent = line.length - line.trimStart().length;
|
|
180
|
+
const content = line.trim();
|
|
181
|
+
|
|
182
|
+
// Flush previous directive value
|
|
183
|
+
if (currentDirectiveKey && indent <= 4 && !/^\s/.test(line.charAt(0)) || (indent <= 4 && currentDirectiveKey)) {
|
|
184
|
+
if (currentMember && currentDirectiveKey) {
|
|
185
|
+
if (!projectDirectives[currentMember]) projectDirectives[currentMember] = {};
|
|
186
|
+
if (!projectDirectives[currentMember].directives) projectDirectives[currentMember].directives = {};
|
|
187
|
+
projectDirectives[currentMember].directives[currentDirectiveKey] = currentDirectiveValue.trim();
|
|
188
|
+
}
|
|
189
|
+
currentDirectiveKey = null;
|
|
190
|
+
currentDirectiveValue = '';
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (indent === 0) {
|
|
194
|
+
// Top-level: member name
|
|
195
|
+
const m = content.match(/^(cabinet-[\w-]+):\s*$/);
|
|
196
|
+
if (m) {
|
|
197
|
+
currentMember = m[1];
|
|
198
|
+
projectDirectives[currentMember] = projectDirectives[currentMember] || {};
|
|
199
|
+
inDirectives = false;
|
|
200
|
+
}
|
|
201
|
+
} else if (indent === 2 && currentMember) {
|
|
202
|
+
const kv = content.match(/^([\w-]+):\s*(.*)$/);
|
|
203
|
+
if (kv) {
|
|
204
|
+
if (kv[1] === 'standing-mandate') {
|
|
205
|
+
// Parse [item1, item2] or item1, item2
|
|
206
|
+
const val = kv[2].replace(/^\[|\]$/g, '');
|
|
207
|
+
projectDirectives[currentMember].standingMandate = val.split(/,\s*/).map(s => s.trim()).filter(Boolean);
|
|
208
|
+
inDirectives = false;
|
|
209
|
+
} else if (kv[1] === 'directives') {
|
|
210
|
+
inDirectives = true;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} else if (indent === 4 && inDirectives && currentMember) {
|
|
214
|
+
const kv = content.match(/^([\w-]+):\s*(.*)$/);
|
|
215
|
+
if (kv) {
|
|
216
|
+
currentDirectiveKey = kv[1];
|
|
217
|
+
currentDirectiveValue = kv[2].replace(/^[>|]\s*$/, '');
|
|
218
|
+
}
|
|
219
|
+
} else if (indent >= 6 && currentDirectiveKey) {
|
|
220
|
+
currentDirectiveValue += ' ' + content;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
// Flush last directive
|
|
224
|
+
if (currentMember && currentDirectiveKey) {
|
|
225
|
+
if (!projectDirectives[currentMember].directives) projectDirectives[currentMember].directives = {};
|
|
226
|
+
projectDirectives[currentMember].directives[currentDirectiveKey] = currentDirectiveValue.trim();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
129
230
|
const entries = [];
|
|
130
231
|
const dirs = fs.readdirSync(skillsDir, { withFileTypes: true });
|
|
131
232
|
for (const dir of dirs) {
|
|
@@ -165,6 +266,21 @@ function generateSkillIndex(projectDir) {
|
|
|
165
266
|
entry.directives = fm.directives;
|
|
166
267
|
}
|
|
167
268
|
|
|
269
|
+
// Merge project directive overlay (if any)
|
|
270
|
+
const overlay = projectDirectives[dir.name];
|
|
271
|
+
if (overlay) {
|
|
272
|
+
// Append project standing mandates (union, no duplicates)
|
|
273
|
+
if (overlay.standingMandate) {
|
|
274
|
+
const existing = entry.standingMandate || [];
|
|
275
|
+
const merged = [...new Set([...existing, ...overlay.standingMandate])];
|
|
276
|
+
entry.standingMandate = merged;
|
|
277
|
+
}
|
|
278
|
+
// Merge project directives (project wins on conflict)
|
|
279
|
+
if (overlay.directives) {
|
|
280
|
+
entry.directives = { ...(entry.directives || {}), ...overlay.directives };
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
168
284
|
entries.push(entry);
|
|
169
285
|
}
|
|
170
286
|
|
package/package.json
CHANGED
package/templates/EXTENSIONS.md
CHANGED
|
@@ -226,8 +226,8 @@ command queue processing (needed by any project with an async work queue).
|
|
|
226
226
|
|
|
227
227
|
## Writing Domain-Specific Cabinet Members
|
|
228
228
|
|
|
229
|
-
CC ships
|
|
230
|
-
cabinet members. Here are three examples showing how to write your own.
|
|
229
|
+
CC ships 27 generic cabinet members. Your project can add domain-specific
|
|
230
|
+
cabinet members on top of these. Here are three examples showing how to write your own.
|
|
231
231
|
|
|
232
232
|
### Example 1: GTD (encoding domain expertise)
|
|
233
233
|
|