cairn-work 0.6.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
 
@@ -61,11 +61,10 @@ program
61
61
  .description('Check workspace health and fix issues')
62
62
  .action(doctor);
63
63
 
64
- // Update-skill command - refresh agent skill
64
+ // Update-skill command - refresh workspace context
65
65
  program
66
66
  .command('update-skill')
67
- .description('Update agent skill documentation')
68
- .option('--agent <type>', 'Specific agent to update')
67
+ .description('Refresh workspace context files (CLAUDE.md + .cairn/planning.md)')
69
68
  .action(updateSkill);
70
69
 
71
70
  // Update command - check for and install updates
@@ -85,7 +85,7 @@ async function createProject(workspacePath, name, slug, options) {
85
85
  const charter = `---
86
86
  title: ${name}
87
87
  description: ${expandNewlines(options.description) || name}
88
- status: active
88
+ status: in_progress
89
89
  priority: 2
90
90
  created: ${getToday()}
91
91
  due: ${options.due || getDueDate(30)}
@@ -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,24 +4,20 @@ 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
-
17
- // Determine if running non-interactively (agent provided via --agent flag)
18
- const nonInteractive = !!options.agent;
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);
19
16
 
20
17
  // Prompt for workspace path (if not provided)
21
18
  let workspacePath = options.path;
22
19
  if (!workspacePath) {
23
20
  if (nonInteractive) {
24
- // Non-interactive: default to current directory
25
21
  workspacePath = process.cwd();
26
22
  } else {
27
23
  const currentDir = process.cwd();
@@ -54,7 +50,6 @@ export default async function onboard(options) {
54
50
  // Check if already set up
55
51
  if (workspaceExists(workspacePath) && !options.force) {
56
52
  if (nonInteractive) {
57
- // Non-interactive: just proceed with re-configuring the agent
58
53
  console.log(chalk.dim('Workspace already exists at'), workspacePath);
59
54
  } else {
60
55
  const { proceed } = await inquirer.prompt([{
@@ -70,82 +65,30 @@ export default async function onboard(options) {
70
65
  }
71
66
  }
72
67
  }
73
-
74
- // Step 1: Detect agents
75
- let agentType = options.agent;
76
-
77
- if (!agentType) {
78
- const spinner = ora('Detecting AI agents').start();
79
- const { primary, detected } = detectAgents();
80
- spinner.stop();
81
-
82
- if (detected.length > 1) {
83
- console.log(chalk.green('āœ“'), `Detected agents: ${detected.map(getAgentName).join(', ')}`);
84
-
85
- const { selected } = await inquirer.prompt([{
86
- type: 'list',
87
- name: 'selected',
88
- message: 'Multiple agents detected. Which one do you want to configure?',
89
- choices: detected.map(type => ({
90
- name: getAgentName(type),
91
- value: type
92
- }))
93
- }]);
94
-
95
- agentType = selected;
96
- } else if (detected.length === 1) {
97
- agentType = primary;
98
- console.log(chalk.green('āœ“'), `Detected: ${getAgentName(agentType)}`);
99
- } else {
100
- agentType = 'generic';
101
- console.log(chalk.yellow('ℹ'), 'No specific agent detected - using generic setup');
102
- }
103
- }
104
-
105
- // Step 2: Create workspace
68
+
69
+ // Step 1: Create workspace
106
70
  console.log();
107
71
  createWorkspace(workspacePath);
108
72
  createWelcomeFile(workspacePath);
109
-
110
- // Step 3: Set up agent
73
+
74
+ // Step 2: Write context files (CLAUDE.md + .cairn/planning.md)
111
75
  console.log();
112
- const setupSpinner = ora(`Configuring ${getAgentName(agentType)}`).start();
113
-
76
+ const setupSpinner = ora('Writing workspace context').start();
77
+
114
78
  try {
115
- switch (agentType) {
116
- case 'clawdbot':
117
- await setupClawdbot(workspacePath);
118
- setupSpinner.succeed(`${getAgentName(agentType)} configured`);
119
- console.log(getClawdbotInstructions(workspacePath));
120
- break;
121
-
122
- case 'claude-code':
123
- await setupClaudeCode(workspacePath);
124
- setupSpinner.succeed(`${getAgentName(agentType)} configured`);
125
- console.log(getClaudeCodeInstructions(workspacePath));
126
- break;
127
-
128
- case 'cursor':
129
- await setupCursor(workspacePath);
130
- setupSpinner.succeed(`${getAgentName(agentType)} configured`);
131
- console.log(getCursorInstructions(workspacePath));
132
- break;
133
-
134
- case 'generic':
135
- default:
136
- await setupGeneric(workspacePath);
137
- setupSpinner.stop();
138
- console.log(getGenericInstructions(workspacePath));
139
- break;
140
- }
79
+ await setupWorkspaceContext(workspacePath);
80
+ setupSpinner.succeed('Workspace context configured');
141
81
  } catch (error) {
142
82
  setupSpinner.fail('Setup failed');
143
83
  console.error(chalk.red('Error:'), error.message);
144
84
  process.exit(1);
145
85
  }
146
-
86
+
147
87
  // Success!
148
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();
149
92
  console.log(chalk.dim('Next steps:'));
150
93
  console.log(chalk.dim(' 1. Open your workspace and tell your agent:'));
151
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.6.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": {
@@ -61,7 +61,7 @@ Good:
61
61
  Create 3-8 tasks per project. Each task should be:
62
62
  - **Atomic**: Completable in one work session
63
63
  - **Specific**: Clear what "done" means without reading other tasks
64
- - **Ordered**: Set the first task to `status: active`, the rest to `pending`
64
+ - **Ordered**: All tasks default to `status: pending`. Only set `--status next_up` if the user asks you to begin work immediately.
65
65
 
66
66
  Think in phases:
67
67
  1. **Setup/Foundation** — Environment, tooling, project scaffolding
@@ -72,7 +72,7 @@ Think in phases:
72
72
  cairn create task "Task Name" \
73
73
  --project project-slug \
74
74
  --description "What this accomplishes in one sentence" \
75
- --status active \
75
+ --status pending \
76
76
  --due YYYY-MM-DD
77
77
  ```
78
78
 
@@ -183,7 +183,7 @@ render on all routes.
183
183
 
184
184
  When breaking down a project, think about what blocks what:
185
185
 
186
- 1. **Foundation tasks come first** — These set up the project, install tools, create the base structure. Set the first one to `status: active`.
186
+ 1. **Foundation tasks come first** — These set up the project, install tools, create the base structure. All tasks start as `status: pending` by default.
187
187
 
188
188
  2. **Core tasks depend on foundation** — These build the actual features. Set to `status: pending` until foundation tasks are done.
189
189
 
@@ -275,7 +275,7 @@ Here's what a fully fleshed-out project looks like after an agent processes "Cre
275
275
  ---
276
276
  title: Build Marketing Website
277
277
  description: Design and develop a responsive marketing website with homepage, about, contact, and blog
278
- status: active
278
+ status: in_progress
279
279
  priority: 2
280
280
  created: 2026-01-30
281
281
  due: 2026-03-01
@@ -325,7 +325,7 @@ Assumptions:
325
325
  title: Initialize project and tooling
326
326
  description: Scaffold Next.js project with TypeScript, Tailwind, and dev tooling
327
327
  assignee: you
328
- status: active
328
+ status: pending
329
329
  created: 2026-01-30
330
330
  due: 2026-02-03
331
331
  autonomy: draft
@@ -503,7 +503,7 @@ Chrome, Safari, and Firefox latest versions.
503
503
  ---
504
504
  title: Add Dark Mode
505
505
  description: Implement system-aware dark mode with manual toggle across the site
506
- status: active
506
+ status: in_progress
507
507
  priority: 2
508
508
  created: 2026-01-30
509
509
  due: 2026-02-10
@@ -547,7 +547,7 @@ Use `next-themes` or a lightweight custom hook.
547
547
  title: Implement theme system
548
548
  description: Add dark mode infrastructure with Tailwind class strategy and theme provider
549
549
  assignee: you
550
- status: active
550
+ status: pending
551
551
  created: 2026-01-30
552
552
  due: 2026-02-04
553
553
  autonomy: draft
@@ -654,7 +654,7 @@ in both modes.
654
654
 
655
655
  4. **Vague tasks** — "Work on the frontend" is not a task. "Build the homepage with hero section, features grid, and CTA" is a task.
656
656
 
657
- 5. **No sequencing** — All tasks set to `active` at once. Set only the first task (or first parallel set) to `active`, rest to `pending`.
657
+ 5. **No sequencing** — All tasks created with random statuses. Create all tasks as `pending` by default. Only use `next_up` when the user explicitly asks you to begin work.
658
658
 
659
659
  6. **Ignoring the charter** — Jumping straight to tasks without filling in Why, Success Criteria, and Context. The charter is how the human verifies you understood their intent.
660
660