cairn-work 0.5.0 → 0.7.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/bin/cairn.js +7 -6
- package/lib/commands/create.js +47 -20
- package/lib/commands/doctor.js +24 -54
- package/lib/commands/onboard.js +57 -101
- package/lib/commands/update-skill.js +13 -51
- package/lib/setup/context.js +70 -0
- package/lib/setup/workspace.js +1 -1
- package/package.json +1 -1
- package/skills/agent-planning.template.md +8 -8
- package/skills/agent-skill.template.md +105 -292
- package/lib/agents/claude-code.js +0 -107
- package/lib/agents/clawdbot.js +0 -96
- package/lib/agents/cursor.js +0 -118
- package/lib/agents/detect.js +0 -60
- package/lib/agents/detect.test.js +0 -108
- package/lib/agents/generic.js +0 -62
package/bin/cairn.js
CHANGED
|
@@ -25,12 +25,12 @@ import doctor from '../lib/commands/doctor.js';
|
|
|
25
25
|
import updateSkill from '../lib/commands/update-skill.js';
|
|
26
26
|
import update from '../lib/commands/update.js';
|
|
27
27
|
|
|
28
|
-
// Onboard command -
|
|
28
|
+
// Onboard command - workspace setup with context files
|
|
29
29
|
program
|
|
30
30
|
.command('onboard')
|
|
31
|
-
.description('Set up Cairn and
|
|
31
|
+
.description('Set up Cairn workspace and agent context')
|
|
32
32
|
.option('--force', 'Force re-onboarding even if already set up')
|
|
33
|
-
.option('--agent <type>', '
|
|
33
|
+
.option('--agent <type>', 'Ignored (kept for backwards compatibility)')
|
|
34
34
|
.option('--path <path>', 'Custom workspace path')
|
|
35
35
|
.action(onboard);
|
|
36
36
|
|
|
@@ -51,6 +51,8 @@ program
|
|
|
51
51
|
.option('--due <date>', 'Due date (YYYY-MM-DD)')
|
|
52
52
|
.option('--description <text>', 'Short description')
|
|
53
53
|
.option('--objective <text>', 'Detailed objective')
|
|
54
|
+
.option('--criteria <text>', 'Success criteria (projects only)')
|
|
55
|
+
.option('--context <text>', 'Background context (projects only)')
|
|
54
56
|
.action(create);
|
|
55
57
|
|
|
56
58
|
// Doctor command - check workspace health
|
|
@@ -59,11 +61,10 @@ program
|
|
|
59
61
|
.description('Check workspace health and fix issues')
|
|
60
62
|
.action(doctor);
|
|
61
63
|
|
|
62
|
-
// Update-skill command - refresh
|
|
64
|
+
// Update-skill command - refresh workspace context
|
|
63
65
|
program
|
|
64
66
|
.command('update-skill')
|
|
65
|
-
.description('
|
|
66
|
-
.option('--agent <type>', 'Specific agent to update')
|
|
67
|
+
.description('Refresh workspace context files (CLAUDE.md + .cairn/planning.md)')
|
|
67
68
|
.action(updateSkill);
|
|
68
69
|
|
|
69
70
|
// Update command - check for and install updates
|
package/lib/commands/create.js
CHANGED
|
@@ -4,6 +4,11 @@ import chalk from 'chalk';
|
|
|
4
4
|
import inquirer from 'inquirer';
|
|
5
5
|
import { resolveWorkspace } from '../setup/workspace.js';
|
|
6
6
|
|
|
7
|
+
function expandNewlines(text) {
|
|
8
|
+
if (!text) return text;
|
|
9
|
+
return text.replace(/\\n/g, '\n');
|
|
10
|
+
}
|
|
11
|
+
|
|
7
12
|
function slugify(text) {
|
|
8
13
|
return text
|
|
9
14
|
.toLowerCase()
|
|
@@ -47,11 +52,28 @@ export default async function create(type, name, options) {
|
|
|
47
52
|
|
|
48
53
|
async function createProject(workspacePath, name, slug, options) {
|
|
49
54
|
const projectPath = join(workspacePath, 'projects', slug);
|
|
50
|
-
|
|
55
|
+
|
|
51
56
|
if (existsSync(projectPath)) {
|
|
52
57
|
console.error(chalk.red('Error:'), `Project already exists: ${slug}`);
|
|
53
58
|
process.exit(1);
|
|
54
59
|
}
|
|
60
|
+
|
|
61
|
+
// Require meaningful content — don't create empty charters
|
|
62
|
+
const missing = [];
|
|
63
|
+
if (!options.description) missing.push('--description');
|
|
64
|
+
if (!options.objective) missing.push('--objective');
|
|
65
|
+
if (!options.criteria) missing.push('--criteria');
|
|
66
|
+
if (!options.context) missing.push('--context');
|
|
67
|
+
if (missing.length > 0) {
|
|
68
|
+
console.error(chalk.red('Error:'), `Missing required flags: ${missing.join(', ')}`);
|
|
69
|
+
console.log(chalk.dim('Projects need real content for every section. Example:'));
|
|
70
|
+
console.log(chalk.cyan(` cairn create project "${name}" \\`));
|
|
71
|
+
console.log(chalk.cyan(` --description "One-line summary" \\`));
|
|
72
|
+
console.log(chalk.cyan(` --objective "Why this matters and what problem it solves" \\`));
|
|
73
|
+
console.log(chalk.cyan(` --criteria "Specific measurable outcomes that define done" \\`));
|
|
74
|
+
console.log(chalk.cyan(` --context "Tech stack, constraints, dependencies, assumptions"`));
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
55
77
|
|
|
56
78
|
// Create folders
|
|
57
79
|
mkdirSync(projectPath, { recursive: true });
|
|
@@ -59,10 +81,11 @@ async function createProject(workspacePath, name, slug, options) {
|
|
|
59
81
|
|
|
60
82
|
// Create charter.md
|
|
61
83
|
const charterPath = join(projectPath, 'charter.md');
|
|
84
|
+
const section = (text) => text ? `\n${expandNewlines(text)}\n` : '\n';
|
|
62
85
|
const charter = `---
|
|
63
86
|
title: ${name}
|
|
64
|
-
description: ${options.description || name}
|
|
65
|
-
status:
|
|
87
|
+
description: ${expandNewlines(options.description) || name}
|
|
88
|
+
status: in_progress
|
|
66
89
|
priority: 2
|
|
67
90
|
created: ${getToday()}
|
|
68
91
|
due: ${options.due || getDueDate(30)}
|
|
@@ -73,19 +96,11 @@ spent: 0
|
|
|
73
96
|
---
|
|
74
97
|
|
|
75
98
|
## Why This Matters
|
|
76
|
-
|
|
77
|
-
${options.objective || '[Describe why this project is important]'}
|
|
78
|
-
|
|
99
|
+
${section(options.objective)}
|
|
79
100
|
## Success Criteria
|
|
80
|
-
|
|
81
|
-
- [ ] [Define what success looks like]
|
|
82
|
-
- [ ] [Add measurable outcomes]
|
|
83
|
-
- [ ] [Specify completion criteria]
|
|
84
|
-
|
|
101
|
+
${section(options.criteria)}
|
|
85
102
|
## Context
|
|
86
|
-
|
|
87
|
-
[Add relevant background, constraints, or dependencies]
|
|
88
|
-
|
|
103
|
+
${section(options.context)}
|
|
89
104
|
## Work Log
|
|
90
105
|
|
|
91
106
|
### ${getToday()} - Created
|
|
@@ -122,16 +137,30 @@ async function createTask(workspacePath, name, slug, options) {
|
|
|
122
137
|
}
|
|
123
138
|
|
|
124
139
|
const taskPath = join(tasksDir, `${slug}.md`);
|
|
125
|
-
|
|
140
|
+
|
|
126
141
|
if (existsSync(taskPath)) {
|
|
127
142
|
console.error(chalk.red('Error:'), `Task already exists: ${slug}`);
|
|
128
143
|
process.exit(1);
|
|
129
144
|
}
|
|
130
|
-
|
|
145
|
+
|
|
146
|
+
// Require meaningful content — don't create empty tasks
|
|
147
|
+
const missing = [];
|
|
148
|
+
if (!options.objective) missing.push('--objective');
|
|
149
|
+
if (!options.description) missing.push('--description');
|
|
150
|
+
if (missing.length > 0) {
|
|
151
|
+
console.error(chalk.red('Error:'), `Missing required flags: ${missing.join(', ')}`);
|
|
152
|
+
console.log(chalk.dim('Tasks need real content. Example:'));
|
|
153
|
+
console.log(chalk.cyan(` cairn create task "${name}" --project ${projectSlug} \\`));
|
|
154
|
+
console.log(chalk.cyan(` --description "One-line summary" \\`));
|
|
155
|
+
console.log(chalk.cyan(` --objective "What needs to happen and what done looks like"`));
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
|
|
131
159
|
// Create task file
|
|
160
|
+
const taskSection = (text) => text ? `\n${expandNewlines(text)}\n` : '\n';
|
|
132
161
|
const task = `---
|
|
133
162
|
title: ${name}
|
|
134
|
-
description: ${options.description || name}
|
|
163
|
+
description: ${expandNewlines(options.description) || name}
|
|
135
164
|
assignee: ${options.assignee || 'you'}
|
|
136
165
|
status: ${options.status || 'pending'}
|
|
137
166
|
created: ${getToday()}
|
|
@@ -142,9 +171,7 @@ artifacts: []
|
|
|
142
171
|
---
|
|
143
172
|
|
|
144
173
|
## Objective
|
|
145
|
-
|
|
146
|
-
${options.objective || '[Describe what needs to be accomplished]'}
|
|
147
|
-
|
|
174
|
+
${taskSection(options.objective)}
|
|
148
175
|
## Work Log
|
|
149
176
|
|
|
150
177
|
### ${getToday()} - Created
|
package/lib/commands/doctor.js
CHANGED
|
@@ -5,11 +5,8 @@ import { join } from 'path';
|
|
|
5
5
|
import chalk from 'chalk';
|
|
6
6
|
import ora from 'ora';
|
|
7
7
|
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import { verifyClaudeCode } from '../agents/claude-code.js';
|
|
11
|
-
import { verifyCursor } from '../agents/cursor.js';
|
|
12
|
-
import { validateWorkspace, createWorkspace, createWelcomeFile, resolveWorkspace } from '../setup/workspace.js';
|
|
8
|
+
import { validateWorkspace, createWelcomeFile, resolveWorkspace } from '../setup/workspace.js';
|
|
9
|
+
import { verifyWorkspaceContext } from '../setup/context.js';
|
|
13
10
|
|
|
14
11
|
export default async function doctor() {
|
|
15
12
|
console.log(chalk.bold.cyan('\n🦮 Cairn Doctor\n'));
|
|
@@ -18,7 +15,7 @@ export default async function doctor() {
|
|
|
18
15
|
const workspacePath = resolveWorkspace() || join(homedir(), 'cairn');
|
|
19
16
|
let issues = [];
|
|
20
17
|
let warnings = [];
|
|
21
|
-
|
|
18
|
+
|
|
22
19
|
// Check 1: Workspace exists
|
|
23
20
|
const spinner = ora('Checking workspace').start();
|
|
24
21
|
if (!existsSync(workspacePath)) {
|
|
@@ -39,51 +36,24 @@ export default async function doctor() {
|
|
|
39
36
|
spinner.succeed('Workspace structure valid');
|
|
40
37
|
}
|
|
41
38
|
}
|
|
42
|
-
|
|
43
|
-
// Check 2:
|
|
44
|
-
const
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// Verify each detected agent
|
|
57
|
-
for (const agent of detected) {
|
|
58
|
-
const verifySpinner = ora(`Verifying ${getAgentName(agent)}`).start();
|
|
59
|
-
let result;
|
|
60
|
-
|
|
61
|
-
switch (agent) {
|
|
62
|
-
case 'clawdbot':
|
|
63
|
-
result = verifyClawdbot();
|
|
64
|
-
break;
|
|
65
|
-
case 'claude-code':
|
|
66
|
-
result = verifyClaudeCode();
|
|
67
|
-
break;
|
|
68
|
-
case 'cursor':
|
|
69
|
-
result = verifyCursor();
|
|
70
|
-
break;
|
|
71
|
-
default:
|
|
72
|
-
result = { success: true, message: 'No verification available' };
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
if (result.success) {
|
|
76
|
-
verifySpinner.succeed(result.message);
|
|
77
|
-
} else {
|
|
78
|
-
verifySpinner.fail(result.message);
|
|
79
|
-
issues.push({
|
|
80
|
-
problem: `${getAgentName(agent)}: ${result.message}`,
|
|
81
|
-
fix: `Run: cairn onboard --agent ${agent} --force`
|
|
82
|
-
});
|
|
83
|
-
}
|
|
39
|
+
|
|
40
|
+
// Check 2: Context files (CLAUDE.md + .cairn/planning.md)
|
|
41
|
+
const contextSpinner = ora('Checking workspace context').start();
|
|
42
|
+
if (existsSync(workspacePath)) {
|
|
43
|
+
const result = verifyWorkspaceContext(workspacePath);
|
|
44
|
+
if (result.success) {
|
|
45
|
+
contextSpinner.succeed(result.message);
|
|
46
|
+
} else {
|
|
47
|
+
contextSpinner.fail(result.message);
|
|
48
|
+
issues.push({
|
|
49
|
+
problem: result.message,
|
|
50
|
+
fix: 'Run: cairn onboard --force --path ' + workspacePath
|
|
51
|
+
});
|
|
84
52
|
}
|
|
53
|
+
} else {
|
|
54
|
+
contextSpinner.skip('Skipped (no workspace)');
|
|
85
55
|
}
|
|
86
|
-
|
|
56
|
+
|
|
87
57
|
// Check 3: README exists
|
|
88
58
|
const readmeSpinner = ora('Checking README').start();
|
|
89
59
|
const readmePath = join(workspacePath, 'README.md');
|
|
@@ -100,11 +70,11 @@ export default async function doctor() {
|
|
|
100
70
|
console.log(chalk.green('✓'), 'README created');
|
|
101
71
|
}
|
|
102
72
|
}
|
|
103
|
-
|
|
73
|
+
|
|
104
74
|
// Summary
|
|
105
75
|
console.log();
|
|
106
76
|
console.log(chalk.bold('Summary:\n'));
|
|
107
|
-
|
|
77
|
+
|
|
108
78
|
if (issues.length === 0 && warnings.length === 0) {
|
|
109
79
|
console.log(chalk.green('✓'), chalk.bold('Everything looks good!'));
|
|
110
80
|
console.log();
|
|
@@ -113,7 +83,7 @@ export default async function doctor() {
|
|
|
113
83
|
console.log();
|
|
114
84
|
return;
|
|
115
85
|
}
|
|
116
|
-
|
|
86
|
+
|
|
117
87
|
if (issues.length > 0) {
|
|
118
88
|
console.log(chalk.red.bold(`${issues.length} issue(s) found:\n`));
|
|
119
89
|
for (const issue of issues) {
|
|
@@ -122,7 +92,7 @@ export default async function doctor() {
|
|
|
122
92
|
console.log();
|
|
123
93
|
}
|
|
124
94
|
}
|
|
125
|
-
|
|
95
|
+
|
|
126
96
|
if (warnings.length > 0) {
|
|
127
97
|
console.log(chalk.yellow.bold(`${warnings.length} warning(s):\n`));
|
|
128
98
|
for (const warning of warnings) {
|
|
@@ -131,7 +101,7 @@ export default async function doctor() {
|
|
|
131
101
|
console.log();
|
|
132
102
|
}
|
|
133
103
|
}
|
|
134
|
-
|
|
104
|
+
|
|
135
105
|
console.log(chalk.dim('Run'), chalk.cyan('cairn --help'), chalk.dim('for more commands.'));
|
|
136
106
|
console.log();
|
|
137
107
|
}
|
package/lib/commands/onboard.js
CHANGED
|
@@ -4,135 +4,91 @@ import chalk from 'chalk';
|
|
|
4
4
|
import inquirer from 'inquirer';
|
|
5
5
|
import ora from 'ora';
|
|
6
6
|
|
|
7
|
-
import { detectAgents, getAgentName } from '../agents/detect.js';
|
|
8
|
-
import { setupClawdbot, getClawdbotInstructions } from '../agents/clawdbot.js';
|
|
9
|
-
import { setupClaudeCode, getClaudeCodeInstructions } from '../agents/claude-code.js';
|
|
10
|
-
import { setupCursor, getCursorInstructions } from '../agents/cursor.js';
|
|
11
|
-
import { setupGeneric, getGenericInstructions } from '../agents/generic.js';
|
|
12
7
|
import { createWorkspace, createWelcomeFile, workspaceExists } from '../setup/workspace.js';
|
|
8
|
+
import { setupWorkspaceContext } from '../setup/context.js';
|
|
13
9
|
|
|
14
10
|
export default async function onboard(options) {
|
|
15
11
|
console.log(chalk.bold.cyan('\n🦮 Cairn Onboarding\n'));
|
|
16
|
-
|
|
12
|
+
|
|
13
|
+
// Accept --agent for backwards compat but ignore it.
|
|
14
|
+
// Non-interactive when --agent or --path is provided.
|
|
15
|
+
const nonInteractive = !!(options.agent || options.path);
|
|
16
|
+
|
|
17
17
|
// Prompt for workspace path (if not provided)
|
|
18
18
|
let workspacePath = options.path;
|
|
19
19
|
if (!workspacePath) {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
name: 'location',
|
|
26
|
-
message: 'Where should Cairn store your project files?',
|
|
27
|
-
choices: [
|
|
28
|
-
{ name: `Here (${currentDir})`, value: currentDir },
|
|
29
|
-
{ name: `~/cairn`, value: cairnDir },
|
|
30
|
-
{ name: 'Somewhere else...', value: '__custom__' }
|
|
31
|
-
]
|
|
32
|
-
}]);
|
|
20
|
+
if (nonInteractive) {
|
|
21
|
+
workspacePath = process.cwd();
|
|
22
|
+
} else {
|
|
23
|
+
const currentDir = process.cwd();
|
|
24
|
+
const cairnDir = join(homedir(), 'cairn');
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
26
|
+
const { location } = await inquirer.prompt([{
|
|
27
|
+
type: 'list',
|
|
28
|
+
name: 'location',
|
|
29
|
+
message: 'Where should Cairn store your project files?',
|
|
30
|
+
choices: [
|
|
31
|
+
{ name: `Here (${currentDir})`, value: currentDir },
|
|
32
|
+
{ name: `~/cairn`, value: cairnDir },
|
|
33
|
+
{ name: 'Somewhere else...', value: '__custom__' }
|
|
34
|
+
]
|
|
39
35
|
}]);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
|
|
37
|
+
if (location === '__custom__') {
|
|
38
|
+
const { customPath } = await inquirer.prompt([{
|
|
39
|
+
type: 'input',
|
|
40
|
+
name: 'customPath',
|
|
41
|
+
message: 'Enter the full path:'
|
|
42
|
+
}]);
|
|
43
|
+
workspacePath = customPath;
|
|
44
|
+
} else {
|
|
45
|
+
workspacePath = location;
|
|
46
|
+
}
|
|
43
47
|
}
|
|
44
48
|
}
|
|
45
|
-
|
|
49
|
+
|
|
46
50
|
// Check if already set up
|
|
47
51
|
if (workspaceExists(workspacePath) && !options.force) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
name: 'proceed',
|
|
51
|
-
message: `Workspace already exists at ${workspacePath}. Re-run onboarding?`,
|
|
52
|
-
default: false
|
|
53
|
-
}]);
|
|
54
|
-
|
|
55
|
-
if (!proceed) {
|
|
56
|
-
console.log(chalk.yellow('\nOnboarding cancelled.'));
|
|
57
|
-
return;
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Step 1: Detect agents
|
|
62
|
-
let agentType = options.agent;
|
|
63
|
-
|
|
64
|
-
if (!agentType) {
|
|
65
|
-
const spinner = ora('Detecting AI agents').start();
|
|
66
|
-
const { primary, detected } = detectAgents();
|
|
67
|
-
spinner.stop();
|
|
68
|
-
|
|
69
|
-
if (detected.length > 1) {
|
|
70
|
-
console.log(chalk.green('✓'), `Detected agents: ${detected.map(getAgentName).join(', ')}`);
|
|
71
|
-
|
|
72
|
-
const { selected } = await inquirer.prompt([{
|
|
73
|
-
type: 'list',
|
|
74
|
-
name: 'selected',
|
|
75
|
-
message: 'Multiple agents detected. Which one do you want to configure?',
|
|
76
|
-
choices: detected.map(type => ({
|
|
77
|
-
name: getAgentName(type),
|
|
78
|
-
value: type
|
|
79
|
-
}))
|
|
80
|
-
}]);
|
|
81
|
-
|
|
82
|
-
agentType = selected;
|
|
83
|
-
} else if (detected.length === 1) {
|
|
84
|
-
agentType = primary;
|
|
85
|
-
console.log(chalk.green('✓'), `Detected: ${getAgentName(agentType)}`);
|
|
52
|
+
if (nonInteractive) {
|
|
53
|
+
console.log(chalk.dim('Workspace already exists at'), workspacePath);
|
|
86
54
|
} else {
|
|
87
|
-
|
|
88
|
-
|
|
55
|
+
const { proceed } = await inquirer.prompt([{
|
|
56
|
+
type: 'confirm',
|
|
57
|
+
name: 'proceed',
|
|
58
|
+
message: `Workspace already exists at ${workspacePath}. Re-run onboarding?`,
|
|
59
|
+
default: false
|
|
60
|
+
}]);
|
|
61
|
+
|
|
62
|
+
if (!proceed) {
|
|
63
|
+
console.log(chalk.yellow('\nOnboarding cancelled.'));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
89
66
|
}
|
|
90
67
|
}
|
|
91
|
-
|
|
92
|
-
// Step
|
|
68
|
+
|
|
69
|
+
// Step 1: Create workspace
|
|
93
70
|
console.log();
|
|
94
71
|
createWorkspace(workspacePath);
|
|
95
72
|
createWelcomeFile(workspacePath);
|
|
96
|
-
|
|
97
|
-
// Step
|
|
73
|
+
|
|
74
|
+
// Step 2: Write context files (CLAUDE.md + .cairn/planning.md)
|
|
98
75
|
console.log();
|
|
99
|
-
const setupSpinner = ora(
|
|
100
|
-
|
|
76
|
+
const setupSpinner = ora('Writing workspace context').start();
|
|
77
|
+
|
|
101
78
|
try {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
await setupClawdbot(workspacePath);
|
|
105
|
-
setupSpinner.succeed(`${getAgentName(agentType)} configured`);
|
|
106
|
-
console.log(getClawdbotInstructions(workspacePath));
|
|
107
|
-
break;
|
|
108
|
-
|
|
109
|
-
case 'claude-code':
|
|
110
|
-
await setupClaudeCode(workspacePath);
|
|
111
|
-
setupSpinner.succeed(`${getAgentName(agentType)} configured`);
|
|
112
|
-
console.log(getClaudeCodeInstructions(workspacePath));
|
|
113
|
-
break;
|
|
114
|
-
|
|
115
|
-
case 'cursor':
|
|
116
|
-
await setupCursor(workspacePath);
|
|
117
|
-
setupSpinner.succeed(`${getAgentName(agentType)} configured`);
|
|
118
|
-
console.log(getCursorInstructions(workspacePath));
|
|
119
|
-
break;
|
|
120
|
-
|
|
121
|
-
case 'generic':
|
|
122
|
-
default:
|
|
123
|
-
await setupGeneric(workspacePath);
|
|
124
|
-
setupSpinner.stop();
|
|
125
|
-
console.log(getGenericInstructions(workspacePath));
|
|
126
|
-
break;
|
|
127
|
-
}
|
|
79
|
+
await setupWorkspaceContext(workspacePath);
|
|
80
|
+
setupSpinner.succeed('Workspace context configured');
|
|
128
81
|
} catch (error) {
|
|
129
82
|
setupSpinner.fail('Setup failed');
|
|
130
83
|
console.error(chalk.red('Error:'), error.message);
|
|
131
84
|
process.exit(1);
|
|
132
85
|
}
|
|
133
|
-
|
|
86
|
+
|
|
134
87
|
// Success!
|
|
135
88
|
console.log(chalk.bold.green('\n🎉 Onboarding complete!\n'));
|
|
89
|
+
console.log(chalk.dim('Workspace:'), chalk.cyan(workspacePath));
|
|
90
|
+
console.log(chalk.dim('Context:'), chalk.cyan('CLAUDE.md'), '+', chalk.cyan('.cairn/planning.md'));
|
|
91
|
+
console.log();
|
|
136
92
|
console.log(chalk.dim('Next steps:'));
|
|
137
93
|
console.log(chalk.dim(' 1. Open your workspace and tell your agent:'));
|
|
138
94
|
console.log(chalk.dim(' "I use Cairn for project management. Run `cairn --help`'));
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
2
|
import ora from 'ora';
|
|
3
3
|
|
|
4
|
-
import { detectAgents, getAgentName } from '../agents/detect.js';
|
|
5
|
-
import { setupClawdbot } from '../agents/clawdbot.js';
|
|
6
|
-
import { setupClaudeCode } from '../agents/claude-code.js';
|
|
7
|
-
import { setupCursor } from '../agents/cursor.js';
|
|
8
4
|
import { resolveWorkspace } from '../setup/workspace.js';
|
|
5
|
+
import { setupWorkspaceContext } from '../setup/context.js';
|
|
9
6
|
|
|
10
7
|
export default async function updateSkill(options) {
|
|
11
|
-
console.log(chalk.bold.cyan('\n🦮 Updating
|
|
8
|
+
console.log(chalk.bold.cyan('\n🦮 Updating Workspace Context\n'));
|
|
12
9
|
|
|
13
10
|
const workspacePath = resolveWorkspace();
|
|
14
11
|
|
|
@@ -16,54 +13,19 @@ export default async function updateSkill(options) {
|
|
|
16
13
|
console.error(chalk.red('Error:'), 'No workspace found. Run:', chalk.cyan('cairn init'));
|
|
17
14
|
process.exit(1);
|
|
18
15
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
console.log(chalk.yellow('⚠'), 'No agents detected');
|
|
29
|
-
console.log(chalk.dim('Specify agent:'), chalk.cyan('cairn update-skill --agent <type>'));
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
agentsToUpdate = detected;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Update each agent
|
|
36
|
-
for (const agent of agentsToUpdate) {
|
|
37
|
-
const spinner = ora(`Updating ${getAgentName(agent)}`).start();
|
|
38
|
-
|
|
39
|
-
try {
|
|
40
|
-
switch (agent) {
|
|
41
|
-
case 'clawdbot':
|
|
42
|
-
await setupClawdbot(workspacePath);
|
|
43
|
-
spinner.succeed(`${getAgentName(agent)} skill updated`);
|
|
44
|
-
break;
|
|
45
|
-
|
|
46
|
-
case 'claude-code':
|
|
47
|
-
await setupClaudeCode(workspacePath);
|
|
48
|
-
spinner.succeed(`${getAgentName(agent)} skill updated`);
|
|
49
|
-
break;
|
|
50
|
-
|
|
51
|
-
case 'cursor':
|
|
52
|
-
await setupCursor(workspacePath);
|
|
53
|
-
spinner.succeed(`${getAgentName(agent)} skill updated`);
|
|
54
|
-
break;
|
|
55
|
-
|
|
56
|
-
default:
|
|
57
|
-
spinner.info(`${getAgentName(agent)} - no auto-update available`);
|
|
58
|
-
}
|
|
59
|
-
} catch (error) {
|
|
60
|
-
spinner.fail(`Failed to update ${getAgentName(agent)}`);
|
|
61
|
-
console.error(chalk.red('Error:'), error.message);
|
|
62
|
-
}
|
|
16
|
+
|
|
17
|
+
const spinner = ora('Refreshing CLAUDE.md and .cairn/planning.md').start();
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
await setupWorkspaceContext(workspacePath);
|
|
21
|
+
spinner.succeed('Workspace context updated');
|
|
22
|
+
} catch (error) {
|
|
23
|
+
spinner.fail('Failed to update workspace context');
|
|
24
|
+
console.error(chalk.red('Error:'), error.message);
|
|
63
25
|
}
|
|
64
|
-
|
|
26
|
+
|
|
65
27
|
console.log();
|
|
66
|
-
console.log(chalk.green('✓'), 'Agent
|
|
28
|
+
console.log(chalk.green('✓'), 'Agent context files refreshed');
|
|
67
29
|
console.log(chalk.dim('Your agents now have the latest Cairn workflow documentation.'));
|
|
68
30
|
console.log();
|
|
69
31
|
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Set up agent-agnostic workspace context.
|
|
11
|
+
* Writes CLAUDE.md to workspace root and .cairn/planning.md.
|
|
12
|
+
* Works with any AI agent that can read files.
|
|
13
|
+
*/
|
|
14
|
+
export async function setupWorkspaceContext(workspacePath) {
|
|
15
|
+
const workspaceRoot = dirname(workspacePath);
|
|
16
|
+
|
|
17
|
+
// Read skill template and replace placeholders
|
|
18
|
+
const skillTemplate = join(__dirname, '../../skills/agent-skill.template.md');
|
|
19
|
+
let skillContent = readFileSync(skillTemplate, 'utf-8');
|
|
20
|
+
skillContent = skillContent
|
|
21
|
+
.replace(/\{\{WORKSPACE_PATH\}\}/g, workspacePath)
|
|
22
|
+
.replace(/\{\{WORKSPACE_ROOT\}\}/g, workspaceRoot);
|
|
23
|
+
|
|
24
|
+
// Write CLAUDE.md to workspace root
|
|
25
|
+
const claudeMdPath = join(workspacePath, 'CLAUDE.md');
|
|
26
|
+
writeFileSync(claudeMdPath, skillContent);
|
|
27
|
+
console.log(chalk.green('✓'), 'CLAUDE.md written to workspace root');
|
|
28
|
+
|
|
29
|
+
// Create .cairn directory
|
|
30
|
+
const cairnDir = join(workspacePath, '.cairn');
|
|
31
|
+
if (!existsSync(cairnDir)) {
|
|
32
|
+
mkdirSync(cairnDir, { recursive: true });
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Read planning template and replace placeholders
|
|
36
|
+
const planningTemplate = join(__dirname, '../../skills/agent-planning.template.md');
|
|
37
|
+
let planningContent = readFileSync(planningTemplate, 'utf-8');
|
|
38
|
+
planningContent = planningContent
|
|
39
|
+
.replace(/\{\{WORKSPACE_PATH\}\}/g, workspacePath)
|
|
40
|
+
.replace(/\{\{WORKSPACE_ROOT\}\}/g, workspaceRoot);
|
|
41
|
+
|
|
42
|
+
// Write .cairn/planning.md
|
|
43
|
+
const planningPath = join(cairnDir, 'planning.md');
|
|
44
|
+
writeFileSync(planningPath, planningContent);
|
|
45
|
+
console.log(chalk.green('✓'), '.cairn/planning.md written');
|
|
46
|
+
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Verify workspace context files exist.
|
|
52
|
+
*/
|
|
53
|
+
export function verifyWorkspaceContext(workspacePath) {
|
|
54
|
+
const claudeMd = join(workspacePath, 'CLAUDE.md');
|
|
55
|
+
const planningMd = join(workspacePath, '.cairn', 'planning.md');
|
|
56
|
+
|
|
57
|
+
const issues = [];
|
|
58
|
+
|
|
59
|
+
if (!existsSync(claudeMd)) {
|
|
60
|
+
issues.push('CLAUDE.md not found in workspace root');
|
|
61
|
+
}
|
|
62
|
+
if (!existsSync(planningMd)) {
|
|
63
|
+
issues.push('.cairn/planning.md not found');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (issues.length > 0) {
|
|
67
|
+
return { success: false, message: issues.join('; ') };
|
|
68
|
+
}
|
|
69
|
+
return { success: true, message: 'Workspace context files verified' };
|
|
70
|
+
}
|
package/lib/setup/workspace.js
CHANGED