@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.
- package/README.md +157 -0
- package/bin/install.js +340 -0
- package/package.json +40 -0
- package/src/commands/audit-claude-md.md +31 -0
- package/src/commands/audit.md +33 -0
- package/src/commands/carl-hygiene.md +33 -0
- package/src/commands/groom.md +35 -0
- package/src/commands/history.md +27 -0
- package/src/commands/pulse.md +33 -0
- package/src/commands/scaffold.md +33 -0
- package/src/commands/status.md +28 -0
- package/src/commands/surface-convert.md +35 -0
- package/src/commands/surface-create.md +34 -0
- package/src/commands/surface-list.md +27 -0
- package/src/framework/context/base-principles.md +71 -0
- package/src/framework/frameworks/audit-strategies.md +53 -0
- package/src/framework/frameworks/satellite-registration.md +44 -0
- package/src/framework/tasks/audit-claude-md.md +68 -0
- package/src/framework/tasks/audit.md +64 -0
- package/src/framework/tasks/carl-hygiene.md +160 -0
- package/src/framework/tasks/groom.md +164 -0
- package/src/framework/tasks/history.md +34 -0
- package/src/framework/tasks/pulse.md +83 -0
- package/src/framework/tasks/scaffold.md +167 -0
- package/src/framework/tasks/status.md +35 -0
- package/src/framework/tasks/surface-convert.md +143 -0
- package/src/framework/tasks/surface-create.md +184 -0
- package/src/framework/tasks/surface-list.md +42 -0
- package/src/framework/templates/active-md.md +112 -0
- package/src/framework/templates/backlog-md.md +100 -0
- package/src/framework/templates/state-md.md +48 -0
- package/src/framework/templates/workspace-json.md +50 -0
- package/src/hooks/_template.py +129 -0
- package/src/hooks/active-hook.py +115 -0
- package/src/hooks/backlog-hook.py +107 -0
- package/src/hooks/base-pulse-check.py +206 -0
- package/src/hooks/psmm-injector.py +67 -0
- package/src/hooks/satellite-detection.py +131 -0
- package/src/packages/base-mcp/index.js +108 -0
- package/src/packages/base-mcp/package.json +10 -0
- package/src/packages/base-mcp/tools/surfaces.js +404 -0
- package/src/packages/carl-mcp/index.js +115 -0
- package/src/packages/carl-mcp/package.json +10 -0
- package/src/packages/carl-mcp/tools/decisions.js +269 -0
- package/src/packages/carl-mcp/tools/domains.js +361 -0
- package/src/packages/carl-mcp/tools/psmm.js +204 -0
- package/src/packages/carl-mcp/tools/staging.js +245 -0
- 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>
|