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 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 - full setup with agent detection
28
+ // Onboard command - workspace setup with context files
29
29
  program
30
30
  .command('onboard')
31
- .description('Set up Cairn and configure your AI agent')
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>', 'Specify agent type (clawdbot|claude-code|cursor|generic)')
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 agent skill
64
+ // Update-skill command - refresh workspace context
63
65
  program
64
66
  .command('update-skill')
65
- .description('Update agent skill documentation')
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
@@ -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: active
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
@@ -5,11 +5,8 @@ import { join } from 'path';
5
5
  import chalk from 'chalk';
6
6
  import ora from 'ora';
7
7
 
8
- import { detectAgents, getAgentName } from '../agents/detect.js';
9
- import { verifyClawdbot } from '../agents/clawdbot.js';
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: Agent configuration
44
- const agentSpinner = ora('Checking agent configuration').start();
45
- const { primary, detected } = detectAgents();
46
-
47
- if (detected.length === 0) {
48
- agentSpinner.info('No specific agent detected');
49
- warnings.push({
50
- problem: 'No AI agent auto-detected',
51
- fix: 'Run: cairn onboard --agent <type>'
52
- });
53
- } else {
54
- agentSpinner.succeed(`Detected: ${detected.map(getAgentName).join(', ')}`);
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
  }
@@ -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
- const currentDir = process.cwd();
21
- const cairnDir = join(homedir(), 'cairn');
22
-
23
- const { location } = await inquirer.prompt([{
24
- type: 'list',
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
- if (location === '__custom__') {
35
- const { customPath } = await inquirer.prompt([{
36
- type: 'input',
37
- name: 'customPath',
38
- message: 'Enter the full path:'
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
- workspacePath = customPath;
41
- } else {
42
- workspacePath = location;
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
- const { proceed } = await inquirer.prompt([{
49
- type: 'confirm',
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
- agentType = 'generic';
88
- console.log(chalk.yellow(''), 'No specific agent detected - using generic setup');
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 2: Create workspace
68
+
69
+ // Step 1: Create workspace
93
70
  console.log();
94
71
  createWorkspace(workspacePath);
95
72
  createWelcomeFile(workspacePath);
96
-
97
- // Step 3: Set up agent
73
+
74
+ // Step 2: Write context files (CLAUDE.md + .cairn/planning.md)
98
75
  console.log();
99
- const setupSpinner = ora(`Configuring ${getAgentName(agentType)}`).start();
100
-
76
+ const setupSpinner = ora('Writing workspace context').start();
77
+
101
78
  try {
102
- switch (agentType) {
103
- case 'clawdbot':
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 Agent Skill\n'));
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
- // Determine which agents to update
21
- let agentsToUpdate = [];
22
-
23
- if (options.agent) {
24
- agentsToUpdate = [options.agent];
25
- } else {
26
- const { detected } = detectAgents();
27
- if (detected.length === 0) {
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 skills updated');
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
+ }
@@ -84,7 +84,7 @@ Cairn uses markdown files with YAML frontmatter:
84
84
  ---
85
85
  title: My Project
86
86
  description: What we're building
87
- status: active
87
+ status: in_progress
88
88
  priority: 1
89
89
  owner: me
90
90
  ---
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cairn-work",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "AI-native project management - work with AI agents using markdown files",
5
5
  "type": "module",
6
6
  "bin": {