@chrisai/base 2.3.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 (48) hide show
  1. package/README.md +157 -0
  2. package/bin/install.js +340 -0
  3. package/package.json +40 -0
  4. package/src/commands/audit-claude-md.md +31 -0
  5. package/src/commands/audit.md +33 -0
  6. package/src/commands/carl-hygiene.md +33 -0
  7. package/src/commands/groom.md +35 -0
  8. package/src/commands/history.md +27 -0
  9. package/src/commands/pulse.md +33 -0
  10. package/src/commands/scaffold.md +33 -0
  11. package/src/commands/status.md +28 -0
  12. package/src/commands/surface-convert.md +35 -0
  13. package/src/commands/surface-create.md +34 -0
  14. package/src/commands/surface-list.md +27 -0
  15. package/src/framework/context/base-principles.md +71 -0
  16. package/src/framework/frameworks/audit-strategies.md +53 -0
  17. package/src/framework/frameworks/satellite-registration.md +44 -0
  18. package/src/framework/tasks/audit-claude-md.md +68 -0
  19. package/src/framework/tasks/audit.md +64 -0
  20. package/src/framework/tasks/carl-hygiene.md +160 -0
  21. package/src/framework/tasks/groom.md +164 -0
  22. package/src/framework/tasks/history.md +34 -0
  23. package/src/framework/tasks/pulse.md +83 -0
  24. package/src/framework/tasks/scaffold.md +167 -0
  25. package/src/framework/tasks/status.md +35 -0
  26. package/src/framework/tasks/surface-convert.md +143 -0
  27. package/src/framework/tasks/surface-create.md +184 -0
  28. package/src/framework/tasks/surface-list.md +42 -0
  29. package/src/framework/templates/active-md.md +112 -0
  30. package/src/framework/templates/backlog-md.md +100 -0
  31. package/src/framework/templates/state-md.md +48 -0
  32. package/src/framework/templates/workspace-json.md +50 -0
  33. package/src/hooks/_template.py +129 -0
  34. package/src/hooks/active-hook.py +115 -0
  35. package/src/hooks/backlog-hook.py +107 -0
  36. package/src/hooks/base-pulse-check.py +206 -0
  37. package/src/hooks/psmm-injector.py +67 -0
  38. package/src/hooks/satellite-detection.py +131 -0
  39. package/src/packages/base-mcp/index.js +108 -0
  40. package/src/packages/base-mcp/package.json +10 -0
  41. package/src/packages/base-mcp/tools/surfaces.js +404 -0
  42. package/src/packages/carl-mcp/index.js +115 -0
  43. package/src/packages/carl-mcp/package.json +10 -0
  44. package/src/packages/carl-mcp/tools/decisions.js +269 -0
  45. package/src/packages/carl-mcp/tools/domains.js +361 -0
  46. package/src/packages/carl-mcp/tools/psmm.js +204 -0
  47. package/src/packages/carl-mcp/tools/staging.js +245 -0
  48. package/src/skill/base.md +111 -0
@@ -0,0 +1,245 @@
1
+ /**
2
+ * CARL Staging — Rule proposal staging pipeline
3
+ * Stage, review, approve/kill rule proposals before they become CARL rules
4
+ */
5
+
6
+ import { readFileSync, writeFileSync, existsSync, appendFileSync } from 'fs';
7
+ import { join } from 'path';
8
+
9
+ const VALID_SOURCES = ['psmm', 'decisions', 'manual'];
10
+
11
+ function debugLog(...args) {
12
+ console.error('[CARL:staging]', new Date().toISOString(), ...args);
13
+ }
14
+
15
+ function getStagingPath(workspacePath) {
16
+ return join(workspacePath, '.base', 'data', 'staging.json');
17
+ }
18
+
19
+ function readStaging(workspacePath) {
20
+ const filepath = getStagingPath(workspacePath);
21
+ if (!existsSync(filepath)) {
22
+ return { proposals: [] };
23
+ }
24
+ try {
25
+ return JSON.parse(readFileSync(filepath, 'utf-8'));
26
+ } catch (error) {
27
+ debugLog('Error reading staging.json:', error.message);
28
+ return { proposals: [] };
29
+ }
30
+ }
31
+
32
+ function writeStaging(workspacePath, data) {
33
+ writeFileSync(getStagingPath(workspacePath), JSON.stringify(data, null, 2), 'utf-8');
34
+ }
35
+
36
+ function nextProposalId(proposals) {
37
+ if (proposals.length === 0) return 'prop-001';
38
+ const nums = proposals.map(p => parseInt(p.id.replace('prop-', ''), 10)).filter(n => !isNaN(n));
39
+ const max = nums.length > 0 ? Math.max(...nums) : 0;
40
+ return `prop-${String(max + 1).padStart(3, '0')}`;
41
+ }
42
+
43
+ // ============================================================
44
+ // TOOL DEFINITIONS
45
+ // ============================================================
46
+
47
+ export const TOOLS = [
48
+ {
49
+ name: "carl_stage_proposal",
50
+ description: "Stage a new CARL rule proposal for review. Sources: psmm, decisions, manual.",
51
+ inputSchema: {
52
+ type: "object",
53
+ properties: {
54
+ domain: { type: "string", description: "Target domain (e.g., 'GLOBAL', 'DEVELOPMENT')" },
55
+ rule_text: { type: "string", description: "The proposed rule text" },
56
+ rationale: { type: "string", description: "Why this rule should exist" },
57
+ source: { type: "string", enum: VALID_SOURCES, description: "Where this proposal came from" }
58
+ },
59
+ required: ["domain", "rule_text", "rationale", "source"]
60
+ }
61
+ },
62
+ {
63
+ name: "carl_get_staged",
64
+ description: "List all pending rule proposals in the staging pipeline.",
65
+ inputSchema: { type: "object", properties: {} }
66
+ },
67
+ {
68
+ name: "carl_approve_proposal",
69
+ description: "Approve a staged proposal — writes the rule to the target domain file and removes from staging.",
70
+ inputSchema: {
71
+ type: "object",
72
+ properties: {
73
+ id: { type: "string", description: "Proposal ID (e.g., 'prop-001')" }
74
+ },
75
+ required: ["id"]
76
+ }
77
+ },
78
+ {
79
+ name: "carl_kill_proposal",
80
+ description: "Delete a staged proposal permanently.",
81
+ inputSchema: {
82
+ type: "object",
83
+ properties: {
84
+ id: { type: "string", description: "Proposal ID to delete" }
85
+ },
86
+ required: ["id"]
87
+ }
88
+ },
89
+ {
90
+ name: "carl_archive_proposal",
91
+ description: "Archive a proposal — keeps it for reference but doesn't activate as a rule.",
92
+ inputSchema: {
93
+ type: "object",
94
+ properties: {
95
+ id: { type: "string", description: "Proposal ID to archive" }
96
+ },
97
+ required: ["id"]
98
+ }
99
+ }
100
+ ];
101
+
102
+ // ============================================================
103
+ // TOOL HANDLERS
104
+ // ============================================================
105
+
106
+ export async function handleTool(name, args, workspacePath) {
107
+ switch (name) {
108
+ case "carl_stage_proposal": return stageProposal(args, workspacePath);
109
+ case "carl_get_staged": return getStaged(workspacePath);
110
+ case "carl_approve_proposal": return approveProposal(args, workspacePath);
111
+ case "carl_kill_proposal": return killProposal(args, workspacePath);
112
+ case "carl_archive_proposal": return archiveProposal(args, workspacePath);
113
+ default: return null;
114
+ }
115
+ }
116
+
117
+ async function stageProposal(args, workspacePath) {
118
+ const { domain, rule_text, rationale, source } = args;
119
+
120
+ if (!VALID_SOURCES.includes(source)) {
121
+ return { success: false, error: `Invalid source: ${source}. Valid: ${VALID_SOURCES.join(', ')}` };
122
+ }
123
+
124
+ debugLog('Staging proposal for domain:', domain);
125
+
126
+ const data = readStaging(workspacePath);
127
+ const id = nextProposalId(data.proposals);
128
+ const date = new Date().toISOString().split('T')[0];
129
+
130
+ const proposal = {
131
+ id,
132
+ proposed: date,
133
+ source,
134
+ domain: domain.toUpperCase(),
135
+ rule_text,
136
+ rationale,
137
+ status: 'pending'
138
+ };
139
+
140
+ data.proposals.push(proposal);
141
+ writeStaging(workspacePath, data);
142
+
143
+ return {
144
+ success: true,
145
+ id,
146
+ domain: proposal.domain,
147
+ message: `Staged ${id} for ${proposal.domain}: "${rule_text.slice(0, 60)}..."`
148
+ };
149
+ }
150
+
151
+ async function getStaged(workspacePath) {
152
+ debugLog('Getting staged proposals');
153
+ const data = readStaging(workspacePath);
154
+
155
+ const pending = data.proposals.filter(p => p.status === 'pending');
156
+ const archived = data.proposals.filter(p => p.status === 'archived');
157
+
158
+ return {
159
+ success: true,
160
+ pending_count: pending.length,
161
+ archived_count: archived.length,
162
+ pending,
163
+ archived
164
+ };
165
+ }
166
+
167
+ async function approveProposal(args, workspacePath) {
168
+ const { id } = args;
169
+ debugLog('Approving proposal:', id);
170
+
171
+ const data = readStaging(workspacePath);
172
+ const idx = data.proposals.findIndex(p => p.id === id && p.status === 'pending');
173
+
174
+ if (idx === -1) {
175
+ return { success: false, error: `Pending proposal not found: ${id}` };
176
+ }
177
+
178
+ const proposal = data.proposals[idx];
179
+ const domain = proposal.domain;
180
+ const domainLower = domain.toLowerCase();
181
+
182
+ // Find the domain file
183
+ const domainFilePath = join(workspacePath, '.carl', domainLower);
184
+
185
+ if (!existsSync(domainFilePath)) {
186
+ return { success: false, error: `Domain file not found: .carl/${domainLower}` };
187
+ }
188
+
189
+ // Read domain file and find next rule number
190
+ const content = readFileSync(domainFilePath, 'utf-8');
191
+ const ruleRegex = new RegExp(`${domain}_RULE_(\\d+)`, 'g');
192
+ const ruleNums = [...content.matchAll(ruleRegex)].map(m => parseInt(m[1], 10));
193
+ const nextNum = ruleNums.length > 0 ? Math.max(...ruleNums) + 1 : 1;
194
+
195
+ // Append the new rule
196
+ const ruleLine = `${domain}_RULE_${nextNum}=${proposal.rule_text}\n`;
197
+ appendFileSync(domainFilePath, ruleLine, 'utf-8');
198
+
199
+ // Remove from staging
200
+ data.proposals.splice(idx, 1);
201
+ writeStaging(workspacePath, data);
202
+
203
+ return {
204
+ success: true,
205
+ id,
206
+ domain,
207
+ rule_number: nextNum,
208
+ message: `Approved ${id} → ${domain}_RULE_${nextNum} written to .carl/${domainLower}`
209
+ };
210
+ }
211
+
212
+ async function killProposal(args, workspacePath) {
213
+ const { id } = args;
214
+ debugLog('Killing proposal:', id);
215
+
216
+ const data = readStaging(workspacePath);
217
+ const idx = data.proposals.findIndex(p => p.id === id);
218
+
219
+ if (idx === -1) {
220
+ return { success: false, error: `Proposal not found: ${id}` };
221
+ }
222
+
223
+ data.proposals.splice(idx, 1);
224
+ writeStaging(workspacePath, data);
225
+
226
+ return { success: true, message: `Deleted proposal ${id}` };
227
+ }
228
+
229
+ async function archiveProposal(args, workspacePath) {
230
+ const { id } = args;
231
+ debugLog('Archiving proposal:', id);
232
+
233
+ const data = readStaging(workspacePath);
234
+ const proposal = data.proposals.find(p => p.id === id && p.status === 'pending');
235
+
236
+ if (!proposal) {
237
+ return { success: false, error: `Pending proposal not found: ${id}` };
238
+ }
239
+
240
+ proposal.status = 'archived';
241
+ proposal.archived_date = new Date().toISOString().split('T')[0];
242
+ writeStaging(workspacePath, data);
243
+
244
+ return { success: true, message: `Archived proposal ${id}` };
245
+ }
@@ -0,0 +1,111 @@
1
+ ---
2
+ name: base
3
+ type: suite
4
+ version: 0.1.0
5
+ category: workspace-orchestration
6
+ description: "Builder's Automated State Engine — workspace lifecycle management for Claude Code. Scaffold, audit, groom, and maintain AI builder workspaces. Manage data surfaces for structured context injection. Use when user mentions workspace setup, cleanup, organization, maintenance, grooming, auditing workspace health, surfaces, or BASE."
7
+ allowed-tools: [Read, Write, Glob, Grep, Edit, Bash, Agent, AskUserQuestion]
8
+ ---
9
+
10
+ <activation>
11
+
12
+ ## What
13
+ BASE (Builder's Automated State Engine) manages the lifecycle of a Claude Code workspace. It scaffolds new workspaces, audits existing ones, runs structured grooming cycles, and maintains workspace health through automated drift detection.
14
+
15
+ ## When to Use
16
+ - User says "base", "workspace", "cleanup", "organize", "audit my workspace", "groom", "surface", "create a surface"
17
+ - User wants to set up a new workspace from scratch
18
+ - User wants to optimize or clean up an existing workspace
19
+ - User asks about workspace health, staleness, or drift
20
+ - Session start hook detects overdue grooming
21
+ - User wants to review workspace evolution history
22
+
23
+ ## Not For
24
+ - Project-level build orchestration (that's PAUL)
25
+ - Session-level rule management (that's CARL)
26
+ - Code quality auditing (that's AEGIS)
27
+ - Skill/tool creation (that's Skillsmith)
28
+
29
+ </activation>
30
+
31
+ <persona>
32
+
33
+ ## Role
34
+ Workspace operations engineer. Knows the territory, tracks what's drifting, enforces maintenance cadence. Tactical, not theoretical.
35
+
36
+ ## Style
37
+ - Direct, structured, checklist-driven
38
+ - Presents health dashboards and drift scores
39
+ - Asks focused questions during grooming (voice-friendly)
40
+ - Never skips areas — systematic coverage
41
+ - Recommends, doesn't dictate
42
+
43
+ ## Expertise
44
+ - Workspace architecture and file organization
45
+ - Context document lifecycle (data surfaces, ACTIVE.md, BACKLOG.md)
46
+ - Tool and configuration management
47
+ - Drift detection and prevention patterns
48
+ - Claude Code ecosystem (PAUL, CARL, AEGIS, Skillsmith integration)
49
+
50
+ </persona>
51
+
52
+ <commands>
53
+
54
+ | Command | Description | Routes To |
55
+ |---------|------------|-----------|
56
+ | `/base:pulse` | Daily activation — workspace health briefing | `@~/.claude/base-framework/tasks/pulse.md` |
57
+ | `/base:groom` | Weekly maintenance cycle | `@~/.claude/base-framework/tasks/groom.md` |
58
+ | `/base:audit` | Deep workspace optimization | `@~/.claude/base-framework/tasks/audit.md` |
59
+ | `/base:scaffold` | Set up BASE in a new workspace | `@~/.claude/base-framework/tasks/scaffold.md` |
60
+ | `/base:status` | Quick health check (one-liner) | `@~/.claude/base-framework/tasks/status.md` |
61
+ | `/base:history` | Workspace evolution timeline | `@~/.claude/base-framework/tasks/history.md` |
62
+ | `/base:audit-claude-md` | Audit CLAUDE.md, generate recommended version | `@~/.claude/base-framework/tasks/audit-claude-md.md` |
63
+ | `/base:carl-hygiene` | CARL domain maintenance and rule review | `@~/.claude/base-framework/tasks/carl-hygiene.md` |
64
+ | `/base:surface create` | Create a new data surface (guided) | `@~/.claude/base-framework/tasks/surface-create.md` |
65
+ | `/base:surface convert` | Convert markdown file to data surface | `@~/.claude/base-framework/tasks/surface-convert.md` |
66
+ | `/base:surface list` | Show all registered surfaces | `@~/.claude/base-framework/tasks/surface-list.md` |
67
+
68
+ </commands>
69
+
70
+ <routing>
71
+
72
+ ## Always Load
73
+ - `@~/.claude/base-framework/context/base-principles.md` — Core workspace management principles
74
+ - `@~/.claude/base-framework/frameworks/audit-strategies.md` — Reusable audit strategy definitions
75
+
76
+ ## Load on Command
77
+ - `@~/.claude/base-framework/tasks/pulse.md` — on `/base:pulse`
78
+ - `@~/.claude/base-framework/tasks/groom.md` — on `/base:groom`
79
+ - `@~/.claude/base-framework/tasks/audit.md` — on `/base:audit`
80
+ - `@~/.claude/base-framework/tasks/scaffold.md` — on `/base:scaffold`
81
+ - `@~/.claude/base-framework/tasks/status.md` — on `/base:status`
82
+ - `@~/.claude/base-framework/tasks/history.md` — on `/base:history`
83
+ - `@~/.claude/base-framework/tasks/carl-hygiene.md` — on `/base:carl-hygiene`
84
+ - `@~/.claude/base-framework/tasks/surface-create.md` — on `/base:surface create`
85
+ - `@~/.claude/base-framework/tasks/surface-convert.md` — on `/base:surface convert`
86
+ - `@~/.claude/base-framework/tasks/surface-list.md` — on `/base:surface list`
87
+
88
+ ## Load on Demand
89
+ - `@~/.claude/base-framework/templates/workspace-json.md` — When generating workspace.json
90
+ - `@~/.claude/base-framework/templates/state-md.md` — When generating STATE.md
91
+ - `@~/.claude/base-framework/frameworks/satellite-registration.md` — When handling PAUL project registration
92
+
93
+ </routing>
94
+
95
+ <greeting>
96
+
97
+ BASE loaded. Builder's Automated State Engine.
98
+
99
+ Available commands:
100
+ - `/base:pulse` — What's the state of my workspace?
101
+ - `/base:groom` — Run weekly maintenance
102
+ - `/base:audit` — Deep optimization session
103
+ - `/base:scaffold` — Set up BASE in a new workspace
104
+ - `/base:status` — Quick health check
105
+ - `/base:history` — Workspace evolution timeline
106
+ - `/base:surface create` — Create a new data surface
107
+ - `/base:surface list` — Show registered surfaces
108
+
109
+ What do you need?
110
+
111
+ </greeting>