@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
package/README.md
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
<img src="terminal.svg" alt="BASE terminal" width="740"/>
|
|
3
|
+
</div>
|
|
4
|
+
|
|
5
|
+
<div align="center">
|
|
6
|
+
|
|
7
|
+
[](https://www.npmjs.com/package/@chrisai/base)
|
|
8
|
+
[](https://nodejs.org)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](https://claude.ai/code)
|
|
11
|
+
|
|
12
|
+
</div>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## What is BASE?
|
|
17
|
+
|
|
18
|
+
BASE keeps your Claude Code workspace from becoming a mess. It scaffolds structure, tracks workspace health, surfaces the right context automatically, and tells you when things go stale — so you spend time building, not maintaining.
|
|
19
|
+
|
|
20
|
+
**The core pattern:** structured JSON files (data surfaces) + lightweight Python hooks that inject them into Claude's context every session. Claude always knows what's active, what's queued, and where satellites stand — without you having to say it.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npx @chrisai/base --global --workspace
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
| Flag | What it does |
|
|
31
|
+
|------|-------------|
|
|
32
|
+
| `--global` | Install commands + framework to `~/.claude` |
|
|
33
|
+
| `--workspace` | Install workspace layer (`.base/`) in current directory |
|
|
34
|
+
| `--local` | Install commands to `./.claude` instead of global |
|
|
35
|
+
| `--config-dir <path>` | Custom Claude config directory |
|
|
36
|
+
| `--workspace-dir <path>` | Target a specific workspace path |
|
|
37
|
+
|
|
38
|
+
**Common flows:**
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Full install — global commands + current workspace
|
|
42
|
+
npx @chrisai/base --global --workspace
|
|
43
|
+
|
|
44
|
+
# Already have global? Just wire a new workspace
|
|
45
|
+
npx @chrisai/base --workspace
|
|
46
|
+
|
|
47
|
+
# Global only — set up each workspace later with /base:scaffold
|
|
48
|
+
npx @chrisai/base --global
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## What Gets Installed
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
~/.claude/ ← --global
|
|
57
|
+
├── commands/base/ 11 slash commands
|
|
58
|
+
├── skills/base/ Entry point (base.md)
|
|
59
|
+
└── base-framework/
|
|
60
|
+
├── tasks/ pulse, groom, audit, scaffold...
|
|
61
|
+
├── templates/ workspace.json, STATE.md, surfaces
|
|
62
|
+
├── context/ base-principles.md
|
|
63
|
+
├── frameworks/ audit-strategies.md
|
|
64
|
+
└── hooks/ Session hooks (scaffold source)
|
|
65
|
+
|
|
66
|
+
./.base/ ← --workspace
|
|
67
|
+
├── workspace.json Manifest: surfaces, satellites, groom config
|
|
68
|
+
├── data/
|
|
69
|
+
│ ├── active.json Active projects surface
|
|
70
|
+
│ └── backlog.json Backlog surface
|
|
71
|
+
├── hooks/ Surface injection hooks
|
|
72
|
+
├── base-mcp/ BASE MCP server
|
|
73
|
+
└── carl-mcp/ CARL MCP server
|
|
74
|
+
|
|
75
|
+
./.claude/
|
|
76
|
+
├── hooks/
|
|
77
|
+
│ ├── base-pulse-check.py Drift detection (every session)
|
|
78
|
+
│ ├── psmm-injector.py Per-session meta memory
|
|
79
|
+
│ └── satellite-detection.py PAUL project auto-registration
|
|
80
|
+
└── settings.json Hook registrations (merged)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## Commands
|
|
86
|
+
|
|
87
|
+
After install, open Claude Code and run `/base:scaffold` to complete setup.
|
|
88
|
+
|
|
89
|
+
| Command | Description |
|
|
90
|
+
|---------|------------|
|
|
91
|
+
| `/base:scaffold` | Set up BASE in a new or existing workspace |
|
|
92
|
+
| `/base:pulse` | Daily workspace health briefing |
|
|
93
|
+
| `/base:groom` | Weekly maintenance cycle |
|
|
94
|
+
| `/base:audit` | Deep workspace optimization |
|
|
95
|
+
| `/base:status` | Quick health check |
|
|
96
|
+
| `/base:history` | Workspace evolution timeline |
|
|
97
|
+
| `/base:audit-claude-md` | Audit CLAUDE.md, generate recommended version |
|
|
98
|
+
| `/base:carl-hygiene` | CARL domain maintenance |
|
|
99
|
+
| `/base:surface create` | Create a new data surface (guided) |
|
|
100
|
+
| `/base:surface convert` | Convert a markdown file to a data surface |
|
|
101
|
+
| `/base:surface list` | Show all registered data surfaces |
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## Data Surfaces
|
|
106
|
+
|
|
107
|
+
The core primitive. A data surface is a structured JSON file + a Python hook that injects it into Claude's context every session. Any persistent data you want Claude to passively know about becomes a surface.
|
|
108
|
+
|
|
109
|
+
```
|
|
110
|
+
workspace.json registers it → hook reads it → Claude knows it
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
**Built-in surfaces:**
|
|
114
|
+
- `active.json` — Current projects, status, blockers, deadlines
|
|
115
|
+
- `backlog.json` — Future work queue, ideas, deferred items
|
|
116
|
+
|
|
117
|
+
**Create your own:**
|
|
118
|
+
```
|
|
119
|
+
/base:surface create
|
|
120
|
+
```
|
|
121
|
+
Guided schema builder. Point it at any data you want surfaced — contacts, clients, API keys, anything. BASE generates the JSON schema, the hook, and wires it automatically.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## PAUL Satellite Integration
|
|
126
|
+
|
|
127
|
+
BASE auto-detects [PAUL](https://github.com/ChristopherKahler/paul) projects in your workspace. Every session, `satellite-detection.py` scans for `.paul/paul.json` files and registers any new projects in `workspace.json`. Weekly groom cycles check satellite health: stale loops, abandoned phases, overdue milestones.
|
|
128
|
+
|
|
129
|
+
No manual registration. It just works.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Ecosystem
|
|
134
|
+
|
|
135
|
+
BASE is part of a three-layer workspace system:
|
|
136
|
+
|
|
137
|
+
| System | Role |
|
|
138
|
+
|--------|------|
|
|
139
|
+
| **BASE** | Workspace lifecycle — surfaces, grooming, drift detection |
|
|
140
|
+
| **CARL** | Dynamic rules engine — just-in-time rule injection |
|
|
141
|
+
| **PAUL** | Project orchestration — Plan → Apply → Unify loop |
|
|
142
|
+
|
|
143
|
+
Each is independent. Use one, some, or all.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## Requirements
|
|
148
|
+
|
|
149
|
+
- Node.js ≥ 16.7.0
|
|
150
|
+
- [Claude Code](https://claude.ai/code)
|
|
151
|
+
- Python 3 (for hooks)
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## License
|
|
156
|
+
|
|
157
|
+
MIT — [Chris Kahler](https://github.com/ChristopherKahler)
|
package/bin/install.js
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const readline = require('readline');
|
|
7
|
+
const { execSync } = require('child_process');
|
|
8
|
+
|
|
9
|
+
// Colors
|
|
10
|
+
const cyan = '\x1b[36m';
|
|
11
|
+
const green = '\x1b[32m';
|
|
12
|
+
const yellow = '\x1b[33m';
|
|
13
|
+
const dim = '\x1b[2m';
|
|
14
|
+
const reset = '\x1b[0m';
|
|
15
|
+
|
|
16
|
+
// Get version from package.json
|
|
17
|
+
const pkg = require('../package.json');
|
|
18
|
+
|
|
19
|
+
const banner = `
|
|
20
|
+
${cyan} ██████╗ █████╗ ███████╗███████╗
|
|
21
|
+
██╔══██╗██╔══██╗██╔════╝██╔════╝
|
|
22
|
+
██████╔╝███████║███████╗█████╗
|
|
23
|
+
██╔══██╗██╔══██║╚════██║██╔══╝
|
|
24
|
+
██████╔╝██║ ██║███████║███████╗
|
|
25
|
+
╚═════╝ ╚═╝ ╚═╝╚══════╝╚══════╝${reset}
|
|
26
|
+
|
|
27
|
+
BASE Framework ${dim}v${pkg.version}${reset}
|
|
28
|
+
Builder's Automated State Engine for Claude Code
|
|
29
|
+
`;
|
|
30
|
+
|
|
31
|
+
// Parse args
|
|
32
|
+
const args = process.argv.slice(2);
|
|
33
|
+
const hasGlobal = args.includes('--global') || args.includes('-g');
|
|
34
|
+
const hasLocal = args.includes('--local') || args.includes('-l');
|
|
35
|
+
const hasWorkspace = args.includes('--workspace') || args.includes('-w');
|
|
36
|
+
|
|
37
|
+
// Parse --config-dir argument
|
|
38
|
+
function parseConfigDirArg() {
|
|
39
|
+
const configDirIndex = args.findIndex(arg => arg === '--config-dir' || arg === '-c');
|
|
40
|
+
if (configDirIndex !== -1) {
|
|
41
|
+
const nextArg = args[configDirIndex + 1];
|
|
42
|
+
if (!nextArg || nextArg.startsWith('-')) {
|
|
43
|
+
console.error(` ${yellow}--config-dir requires a path argument${reset}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
return nextArg;
|
|
47
|
+
}
|
|
48
|
+
const configDirArg = args.find(arg => arg.startsWith('--config-dir=') || arg.startsWith('-c='));
|
|
49
|
+
if (configDirArg) {
|
|
50
|
+
return configDirArg.split('=')[1];
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const explicitConfigDir = parseConfigDirArg();
|
|
55
|
+
|
|
56
|
+
// Parse --workspace-dir argument
|
|
57
|
+
function parseWorkspaceDirArg() {
|
|
58
|
+
const idx = args.findIndex(arg => arg === '--workspace-dir');
|
|
59
|
+
if (idx !== -1) {
|
|
60
|
+
const nextArg = args[idx + 1];
|
|
61
|
+
if (!nextArg || nextArg.startsWith('-')) {
|
|
62
|
+
console.error(` ${yellow}--workspace-dir requires a path argument${reset}`);
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
return nextArg;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
const explicitWorkspaceDir = parseWorkspaceDirArg();
|
|
70
|
+
const hasHelp = args.includes('--help') || args.includes('-h');
|
|
71
|
+
|
|
72
|
+
console.log(banner);
|
|
73
|
+
|
|
74
|
+
// Show help if requested
|
|
75
|
+
if (hasHelp) {
|
|
76
|
+
console.log(` ${yellow}Usage:${reset} npx base-framework [options]
|
|
77
|
+
|
|
78
|
+
${yellow}Options:${reset}
|
|
79
|
+
${cyan}-g, --global${reset} Install commands globally (to ~/.claude)
|
|
80
|
+
${cyan}-l, --local${reset} Install commands locally (to ./.claude)
|
|
81
|
+
${cyan}-w, --workspace${reset} Install workspace layer (.base/ in current directory)
|
|
82
|
+
${cyan}-c, --config-dir <path>${reset} Specify custom Claude config directory
|
|
83
|
+
${cyan}--workspace-dir <path>${reset} Specify workspace root (default: cwd)
|
|
84
|
+
${cyan}-h, --help${reset} Show this help message
|
|
85
|
+
|
|
86
|
+
${yellow}Examples:${reset}
|
|
87
|
+
${dim}# Full install: global commands + workspace layer${reset}
|
|
88
|
+
npx base-framework --global --workspace
|
|
89
|
+
|
|
90
|
+
${dim}# Global commands only (no workspace data)${reset}
|
|
91
|
+
npx base-framework --global
|
|
92
|
+
|
|
93
|
+
${dim}# Workspace layer only (assumes commands already installed)${reset}
|
|
94
|
+
npx base-framework --workspace
|
|
95
|
+
|
|
96
|
+
${dim}# Install to current project only${reset}
|
|
97
|
+
npx base-framework --local --workspace
|
|
98
|
+
|
|
99
|
+
${yellow}What gets installed:${reset}
|
|
100
|
+
${cyan}Commands (--global or --local):${reset}
|
|
101
|
+
commands/base/ - Slash commands (/base:surface-create, etc.)
|
|
102
|
+
skills/base/ - Skill framework (tasks, templates, context)
|
|
103
|
+
|
|
104
|
+
${cyan}Workspace (--workspace):${reset}
|
|
105
|
+
.base/data/ - Data surface directory
|
|
106
|
+
.base/hooks/ - Surface injection hooks
|
|
107
|
+
.base/base-mcp/ - BASE MCP server (npm install auto-runs)
|
|
108
|
+
.base/carl-mcp/ - CARL MCP server (npm install auto-runs)
|
|
109
|
+
.mcp.json - MCP server registration (merged)
|
|
110
|
+
`);
|
|
111
|
+
process.exit(0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Expand ~ to home directory
|
|
116
|
+
*/
|
|
117
|
+
function expandTilde(filePath) {
|
|
118
|
+
if (filePath && filePath.startsWith('~/')) {
|
|
119
|
+
return path.join(os.homedir(), filePath.slice(2));
|
|
120
|
+
}
|
|
121
|
+
return filePath;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Recursively copy directory
|
|
126
|
+
*/
|
|
127
|
+
function copyDir(srcDir, destDir) {
|
|
128
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
129
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
130
|
+
for (const entry of entries) {
|
|
131
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
132
|
+
const destPath = path.join(destDir, entry.name);
|
|
133
|
+
if (entry.isDirectory()) {
|
|
134
|
+
copyDir(srcPath, destPath);
|
|
135
|
+
} else {
|
|
136
|
+
fs.copyFileSync(srcPath, destPath);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Install commands and skill framework
|
|
143
|
+
*/
|
|
144
|
+
function installCommands(isGlobal) {
|
|
145
|
+
const src = path.join(__dirname, '..');
|
|
146
|
+
const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.CLAUDE_CONFIG_DIR);
|
|
147
|
+
const defaultGlobalDir = configDir || path.join(os.homedir(), '.claude');
|
|
148
|
+
const claudeDir = isGlobal ? defaultGlobalDir : path.join(process.cwd(), '.claude');
|
|
149
|
+
|
|
150
|
+
const locationLabel = isGlobal
|
|
151
|
+
? claudeDir.replace(os.homedir(), '~')
|
|
152
|
+
: claudeDir.replace(process.cwd(), '.');
|
|
153
|
+
|
|
154
|
+
console.log(` Installing commands to ${cyan}${locationLabel}${reset}\n`);
|
|
155
|
+
|
|
156
|
+
// Copy commands
|
|
157
|
+
const commandsSrc = path.join(src, 'src', 'commands');
|
|
158
|
+
const commandsDest = path.join(claudeDir, 'commands', 'base');
|
|
159
|
+
copyDir(commandsSrc, commandsDest);
|
|
160
|
+
console.log(` ${green}+${reset} commands/base/ (3 slash commands)`);
|
|
161
|
+
|
|
162
|
+
// Copy skill entry point
|
|
163
|
+
const skillSrc = path.join(src, 'src', 'skill');
|
|
164
|
+
const skillDest = path.join(claudeDir, 'skills', 'base');
|
|
165
|
+
copyDir(skillSrc, skillDest);
|
|
166
|
+
console.log(` ${green}+${reset} skills/base/ (entry point + MCP package sources)`);
|
|
167
|
+
|
|
168
|
+
// Copy MCP package sources into skill (for scaffold reference)
|
|
169
|
+
const packagesSrc = path.join(src, 'src', 'packages');
|
|
170
|
+
const packagesDest = path.join(claudeDir, 'skills', 'base', 'packages');
|
|
171
|
+
copyDir(packagesSrc, packagesDest);
|
|
172
|
+
|
|
173
|
+
// Copy BASE framework (tasks, templates, context, frameworks)
|
|
174
|
+
const frameworkSrc = path.join(src, 'src', 'framework');
|
|
175
|
+
const frameworkDest = path.join(claudeDir, 'base-framework');
|
|
176
|
+
copyDir(frameworkSrc, frameworkDest);
|
|
177
|
+
console.log(` ${green}+${reset} base-framework/ (tasks, templates, context, frameworks)`);
|
|
178
|
+
|
|
179
|
+
// Copy session hooks to global base-framework/hooks/ (source for scaffold)
|
|
180
|
+
const sessionHookNames = ['base-pulse-check.py', 'psmm-injector.py', 'satellite-detection.py'];
|
|
181
|
+
const hooksFrameworkDest = path.join(claudeDir, 'base-framework', 'hooks');
|
|
182
|
+
fs.mkdirSync(hooksFrameworkDest, { recursive: true });
|
|
183
|
+
const hooksSrcDir = path.join(src, 'src', 'hooks');
|
|
184
|
+
for (const hookFile of sessionHookNames) {
|
|
185
|
+
const hookSrcPath = path.join(hooksSrcDir, hookFile);
|
|
186
|
+
if (fs.existsSync(hookSrcPath)) {
|
|
187
|
+
fs.copyFileSync(hookSrcPath, path.join(hooksFrameworkDest, hookFile));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
console.log(` ${green}+${reset} base-framework/hooks/ (session hooks for scaffold)`);
|
|
191
|
+
|
|
192
|
+
console.log(`\n ${green}Commands installed.${reset}\n`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Install workspace layer (.base/)
|
|
197
|
+
*/
|
|
198
|
+
function installWorkspace() {
|
|
199
|
+
const src = path.join(__dirname, '..');
|
|
200
|
+
const workspaceDir = expandTilde(explicitWorkspaceDir) || process.cwd();
|
|
201
|
+
const baseDir = path.join(workspaceDir, '.base');
|
|
202
|
+
|
|
203
|
+
console.log(` Installing workspace layer to ${cyan}${baseDir.replace(os.homedir(), '~')}${reset}\n`);
|
|
204
|
+
|
|
205
|
+
// Create .base directories
|
|
206
|
+
fs.mkdirSync(path.join(baseDir, 'data'), { recursive: true });
|
|
207
|
+
fs.mkdirSync(path.join(baseDir, 'hooks'), { recursive: true });
|
|
208
|
+
console.log(` ${green}+${reset} .base/data/`);
|
|
209
|
+
console.log(` ${green}+${reset} .base/hooks/`);
|
|
210
|
+
|
|
211
|
+
// Copy MCP servers
|
|
212
|
+
const baseMcpSrc = path.join(src, 'src', 'packages', 'base-mcp');
|
|
213
|
+
const baseMcpDest = path.join(baseDir, 'base-mcp');
|
|
214
|
+
copyDir(baseMcpSrc, baseMcpDest);
|
|
215
|
+
console.log(` ${green}+${reset} .base/base-mcp/`);
|
|
216
|
+
|
|
217
|
+
const carlMcpSrc = path.join(src, 'src', 'packages', 'carl-mcp');
|
|
218
|
+
const carlMcpDest = path.join(baseDir, 'carl-mcp');
|
|
219
|
+
copyDir(carlMcpSrc, carlMcpDest);
|
|
220
|
+
console.log(` ${green}+${reset} .base/carl-mcp/`);
|
|
221
|
+
|
|
222
|
+
// Copy all hooks from single src/hooks/ directory
|
|
223
|
+
// Surface hooks (_template, active-hook, backlog-hook) → .base/hooks/
|
|
224
|
+
// Session hooks (base-pulse-check, psmm-injector) → .claude/hooks/
|
|
225
|
+
const allHooksSrc = path.join(src, 'src', 'hooks');
|
|
226
|
+
const surfaceHooks = ['_template.py', 'active-hook.py', 'backlog-hook.py'];
|
|
227
|
+
const sessionHooks = ['base-pulse-check.py', 'psmm-injector.py', 'satellite-detection.py'];
|
|
228
|
+
|
|
229
|
+
const entries = fs.readdirSync(allHooksSrc);
|
|
230
|
+
for (const file of entries) {
|
|
231
|
+
const srcPath = path.join(allHooksSrc, file);
|
|
232
|
+
if (surfaceHooks.includes(file)) {
|
|
233
|
+
fs.copyFileSync(srcPath, path.join(baseDir, 'hooks', file));
|
|
234
|
+
} else if (sessionHooks.includes(file)) {
|
|
235
|
+
const claudeHooksDir = path.join(workspaceDir, '.claude', 'hooks');
|
|
236
|
+
fs.mkdirSync(claudeHooksDir, { recursive: true });
|
|
237
|
+
fs.copyFileSync(srcPath, path.join(claudeHooksDir, file));
|
|
238
|
+
} else {
|
|
239
|
+
// Unknown hooks default to .base/hooks/
|
|
240
|
+
fs.copyFileSync(srcPath, path.join(baseDir, 'hooks', file));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
console.log(` ${green}+${reset} .base/hooks/ (${surfaceHooks.length} surface hooks)`);
|
|
244
|
+
console.log(` ${green}+${reset} .claude/hooks/ (${sessionHooks.length} session hooks: pulse, PSMM, satellite)`);
|
|
245
|
+
|
|
246
|
+
// npm install for MCP servers
|
|
247
|
+
console.log(`\n Installing MCP dependencies...`);
|
|
248
|
+
try {
|
|
249
|
+
execSync('npm install', { cwd: baseMcpDest, stdio: 'pipe' });
|
|
250
|
+
console.log(` ${green}+${reset} base-mcp dependencies installed`);
|
|
251
|
+
} catch (e) {
|
|
252
|
+
console.log(` ${yellow}!${reset} base-mcp npm install failed — run manually in .base/base-mcp/`);
|
|
253
|
+
}
|
|
254
|
+
try {
|
|
255
|
+
execSync('npm install', { cwd: carlMcpDest, stdio: 'pipe' });
|
|
256
|
+
console.log(` ${green}+${reset} carl-mcp dependencies installed`);
|
|
257
|
+
} catch (e) {
|
|
258
|
+
console.log(` ${yellow}!${reset} carl-mcp npm install failed — run manually in .base/carl-mcp/`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Merge MCP registrations into .mcp.json
|
|
262
|
+
const mcpJsonPath = path.join(workspaceDir, '.mcp.json');
|
|
263
|
+
let mcpConfig = { mcpServers: {} };
|
|
264
|
+
if (fs.existsSync(mcpJsonPath)) {
|
|
265
|
+
try {
|
|
266
|
+
mcpConfig = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf-8'));
|
|
267
|
+
} catch (e) { /* start fresh */ }
|
|
268
|
+
}
|
|
269
|
+
mcpConfig.mcpServers['carl-mcp'] = {
|
|
270
|
+
type: 'stdio',
|
|
271
|
+
command: 'node',
|
|
272
|
+
args: ['./.base/carl-mcp/index.js']
|
|
273
|
+
};
|
|
274
|
+
mcpConfig.mcpServers['base-mcp'] = {
|
|
275
|
+
type: 'stdio',
|
|
276
|
+
command: 'node',
|
|
277
|
+
args: ['./.base/base-mcp/index.js']
|
|
278
|
+
};
|
|
279
|
+
fs.writeFileSync(mcpJsonPath, JSON.stringify(mcpConfig, null, 2));
|
|
280
|
+
console.log(` ${green}+${reset} .mcp.json (carl-mcp + base-mcp registered)`);
|
|
281
|
+
|
|
282
|
+
console.log(`\n ${green}Workspace layer installed.${reset}`);
|
|
283
|
+
console.log(` Run ${cyan}/base:scaffold${reset} to complete workspace setup.\n`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Prompt for install location
|
|
288
|
+
*/
|
|
289
|
+
function promptLocation() {
|
|
290
|
+
const rl = readline.createInterface({
|
|
291
|
+
input: process.stdin,
|
|
292
|
+
output: process.stdout
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
const configDir = expandTilde(explicitConfigDir) || expandTilde(process.env.CLAUDE_CONFIG_DIR);
|
|
296
|
+
const globalPath = configDir || path.join(os.homedir(), '.claude');
|
|
297
|
+
const globalLabel = globalPath.replace(os.homedir(), '~');
|
|
298
|
+
|
|
299
|
+
console.log(` ${yellow}What would you like to install?${reset}
|
|
300
|
+
|
|
301
|
+
${cyan}1${reset}) Full install ${dim}(commands to ${globalLabel} + workspace layer to .base/)${reset}
|
|
302
|
+
${cyan}2${reset}) Commands only ${dim}(${globalLabel})${reset}
|
|
303
|
+
${cyan}3${reset}) Workspace only ${dim}(.base/ in current directory)${reset}
|
|
304
|
+
`);
|
|
305
|
+
|
|
306
|
+
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
|
307
|
+
rl.close();
|
|
308
|
+
const choice = answer.trim() || '1';
|
|
309
|
+
if (choice === '1' || choice === '2') {
|
|
310
|
+
installCommands(true);
|
|
311
|
+
}
|
|
312
|
+
if (choice === '1' || choice === '3') {
|
|
313
|
+
installWorkspace();
|
|
314
|
+
}
|
|
315
|
+
if (choice !== '1' && choice !== '2' && choice !== '3') {
|
|
316
|
+
installCommands(true);
|
|
317
|
+
installWorkspace();
|
|
318
|
+
}
|
|
319
|
+
console.log(` ${green}Done!${reset} Launch Claude Code and run ${cyan}/base:surface-list${reset}.\n`);
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Main
|
|
324
|
+
if (hasHelp) {
|
|
325
|
+
// Already handled above
|
|
326
|
+
} else if (hasGlobal || hasLocal || hasWorkspace) {
|
|
327
|
+
if (hasGlobal && hasLocal) {
|
|
328
|
+
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
if (hasGlobal || hasLocal) {
|
|
332
|
+
installCommands(hasGlobal);
|
|
333
|
+
}
|
|
334
|
+
if (hasWorkspace) {
|
|
335
|
+
installWorkspace();
|
|
336
|
+
}
|
|
337
|
+
console.log(` ${green}Done!${reset} Launch Claude Code and run ${cyan}/base:surface-list${reset}.\n`);
|
|
338
|
+
} else {
|
|
339
|
+
promptLocation();
|
|
340
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chrisai/base",
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "Builder's Automated State Engine — workspace orchestration framework for Claude Code",
|
|
5
|
+
"bin": {
|
|
6
|
+
"base-framework": "bin/install.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"src/commands",
|
|
11
|
+
"src/skill",
|
|
12
|
+
"src/framework",
|
|
13
|
+
"src/packages",
|
|
14
|
+
"src/hooks",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"keywords": [
|
|
18
|
+
"claude",
|
|
19
|
+
"claude-code",
|
|
20
|
+
"ai",
|
|
21
|
+
"workspace",
|
|
22
|
+
"orchestration",
|
|
23
|
+
"base",
|
|
24
|
+
"data-surfaces",
|
|
25
|
+
"mcp"
|
|
26
|
+
],
|
|
27
|
+
"author": "Chris Kahler",
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/ChristopherKahler/base.git"
|
|
32
|
+
},
|
|
33
|
+
"bugs": {
|
|
34
|
+
"url": "https://github.com/ChristopherKahler/base/issues"
|
|
35
|
+
},
|
|
36
|
+
"homepage": "https://github.com/ChristopherKahler/base#readme",
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=16.7.0"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: base:audit-claude-md
|
|
3
|
+
description: Audit CLAUDE.md and generate recommended version
|
|
4
|
+
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<objective>
|
|
8
|
+
Audit the project's CLAUDE.md for completeness, accuracy, and alignment with workspace state. Generate a recommended version.
|
|
9
|
+
|
|
10
|
+
**When to use:** "audit claude md", "check my claude.md", after major workspace changes.
|
|
11
|
+
</objective>
|
|
12
|
+
|
|
13
|
+
<execution_context>
|
|
14
|
+
@.claude/skills/base/tasks/audit-claude-md.md
|
|
15
|
+
</execution_context>
|
|
16
|
+
|
|
17
|
+
<context>
|
|
18
|
+
$ARGUMENTS
|
|
19
|
+
|
|
20
|
+
@CLAUDE.md
|
|
21
|
+
</context>
|
|
22
|
+
|
|
23
|
+
<process>
|
|
24
|
+
Follow task: @.claude/skills/base/tasks/audit-claude-md.md
|
|
25
|
+
</process>
|
|
26
|
+
|
|
27
|
+
<success_criteria>
|
|
28
|
+
- [ ] Current CLAUDE.md analyzed
|
|
29
|
+
- [ ] Gaps and outdated sections identified
|
|
30
|
+
- [ ] Recommended version generated or changes suggested
|
|
31
|
+
</success_criteria>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: base:audit
|
|
3
|
+
description: Deep workspace optimization
|
|
4
|
+
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, Agent, AskUserQuestion]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<objective>
|
|
8
|
+
Deep workspace audit — comprehensive optimization across all areas with actionable recommendations.
|
|
9
|
+
|
|
10
|
+
**When to use:** "audit my workspace", "deep clean", monthly optimization.
|
|
11
|
+
</objective>
|
|
12
|
+
|
|
13
|
+
<execution_context>
|
|
14
|
+
@.claude/skills/base/tasks/audit.md
|
|
15
|
+
@.claude/skills/base/context/base-principles.md
|
|
16
|
+
@.claude/skills/base/frameworks/audit-strategies.md
|
|
17
|
+
</execution_context>
|
|
18
|
+
|
|
19
|
+
<context>
|
|
20
|
+
$ARGUMENTS
|
|
21
|
+
|
|
22
|
+
@.base/workspace.json
|
|
23
|
+
</context>
|
|
24
|
+
|
|
25
|
+
<process>
|
|
26
|
+
Follow task: @.claude/skills/base/tasks/audit.md
|
|
27
|
+
</process>
|
|
28
|
+
|
|
29
|
+
<success_criteria>
|
|
30
|
+
- [ ] All areas audited with strategy-specific checks
|
|
31
|
+
- [ ] Findings categorized and prioritized
|
|
32
|
+
- [ ] Actionable recommendations presented
|
|
33
|
+
</success_criteria>
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: base:carl-hygiene
|
|
3
|
+
description: CARL domain maintenance and rule review
|
|
4
|
+
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<objective>
|
|
8
|
+
CARL rule lifecycle management — review staleness, dedup, staging pipeline, domain health.
|
|
9
|
+
|
|
10
|
+
**When to use:** "carl hygiene", "review carl rules", "clean up carl".
|
|
11
|
+
</objective>
|
|
12
|
+
|
|
13
|
+
<execution_context>
|
|
14
|
+
@.claude/skills/base/tasks/carl-hygiene.md
|
|
15
|
+
</execution_context>
|
|
16
|
+
|
|
17
|
+
<context>
|
|
18
|
+
$ARGUMENTS
|
|
19
|
+
|
|
20
|
+
@.carl/manifest
|
|
21
|
+
@.base/workspace.json
|
|
22
|
+
</context>
|
|
23
|
+
|
|
24
|
+
<process>
|
|
25
|
+
Follow task: @.claude/skills/base/tasks/carl-hygiene.md
|
|
26
|
+
</process>
|
|
27
|
+
|
|
28
|
+
<success_criteria>
|
|
29
|
+
- [ ] All domains reviewed for staleness
|
|
30
|
+
- [ ] Duplicate/conflicting rules identified
|
|
31
|
+
- [ ] Staging pipeline processed
|
|
32
|
+
- [ ] carl_hygiene.last_run updated in workspace.json
|
|
33
|
+
</success_criteria>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: base:groom
|
|
3
|
+
description: Weekly workspace maintenance cycle
|
|
4
|
+
allowed-tools: [Read, Write, Edit, Glob, Grep, Bash, AskUserQuestion]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<objective>
|
|
8
|
+
Structured weekly maintenance — review each workspace area, update statuses, archive stale items, reduce drift.
|
|
9
|
+
|
|
10
|
+
**When to use:** Weekly maintenance, "groom my workspace", "run grooming".
|
|
11
|
+
</objective>
|
|
12
|
+
|
|
13
|
+
<execution_context>
|
|
14
|
+
@.claude/skills/base/tasks/groom.md
|
|
15
|
+
@.claude/skills/base/context/base-principles.md
|
|
16
|
+
@.claude/skills/base/frameworks/audit-strategies.md
|
|
17
|
+
</execution_context>
|
|
18
|
+
|
|
19
|
+
<context>
|
|
20
|
+
$ARGUMENTS
|
|
21
|
+
|
|
22
|
+
@.base/workspace.json
|
|
23
|
+
@.base/STATE.md
|
|
24
|
+
</context>
|
|
25
|
+
|
|
26
|
+
<process>
|
|
27
|
+
Follow task: @.claude/skills/base/tasks/groom.md
|
|
28
|
+
</process>
|
|
29
|
+
|
|
30
|
+
<success_criteria>
|
|
31
|
+
- [ ] All workspace areas reviewed
|
|
32
|
+
- [ ] Stale items addressed
|
|
33
|
+
- [ ] STATE.md updated with groom results
|
|
34
|
+
- [ ] Drift score recalculated
|
|
35
|
+
</success_criteria>
|