cairn-work 0.4.0 → 0.6.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 CHANGED
@@ -5,7 +5,7 @@ AI-native project management where markdown files are the source of truth.
5
5
  ## Quick Start
6
6
 
7
7
  ```bash
8
- npm install -g cairn
8
+ npm install -g cairn-work
9
9
  cairn onboard
10
10
  ```
11
11
 
@@ -27,9 +27,9 @@ Cairn is a project management system designed for working with AI agents. Instea
27
27
  ### Global Install (Recommended)
28
28
 
29
29
  ```bash
30
- npm install -g cairn
30
+ npm install -g cairn-work
31
31
  # or
32
- bun install -g cairn
32
+ bun install -g cairn-work
33
33
  ```
34
34
 
35
35
  ### Test Locally
package/bin/cairn.js CHANGED
@@ -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
@@ -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,9 +81,10 @@ 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}
87
+ description: ${expandNewlines(options.description) || name}
65
88
  status: active
66
89
  priority: 2
67
90
  created: ${getToday()}
@@ -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
@@ -14,47 +14,60 @@ import { createWorkspace, createWelcomeFile, workspaceExists } from '../setup/wo
14
14
  export default async function onboard(options) {
15
15
  console.log(chalk.bold.cyan('\n🦮 Cairn Onboarding\n'));
16
16
 
17
+ // Determine if running non-interactively (agent provided via --agent flag)
18
+ const nonInteractive = !!options.agent;
19
+
17
20
  // Prompt for workspace path (if not provided)
18
21
  let workspacePath = options.path;
19
22
  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
- }]);
23
+ if (nonInteractive) {
24
+ // Non-interactive: default to current directory
25
+ workspacePath = process.cwd();
26
+ } else {
27
+ const currentDir = process.cwd();
28
+ const cairnDir = join(homedir(), 'cairn');
33
29
 
34
- if (location === '__custom__') {
35
- const { customPath } = await inquirer.prompt([{
36
- type: 'input',
37
- name: 'customPath',
38
- message: 'Enter the full path:'
30
+ const { location } = await inquirer.prompt([{
31
+ type: 'list',
32
+ name: 'location',
33
+ message: 'Where should Cairn store your project files?',
34
+ choices: [
35
+ { name: `Here (${currentDir})`, value: currentDir },
36
+ { name: `~/cairn`, value: cairnDir },
37
+ { name: 'Somewhere else...', value: '__custom__' }
38
+ ]
39
39
  }]);
40
- workspacePath = customPath;
41
- } else {
42
- workspacePath = location;
40
+
41
+ if (location === '__custom__') {
42
+ const { customPath } = await inquirer.prompt([{
43
+ type: 'input',
44
+ name: 'customPath',
45
+ message: 'Enter the full path:'
46
+ }]);
47
+ workspacePath = customPath;
48
+ } else {
49
+ workspacePath = location;
50
+ }
43
51
  }
44
52
  }
45
-
53
+
46
54
  // Check if already set up
47
55
  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;
56
+ if (nonInteractive) {
57
+ // Non-interactive: just proceed with re-configuring the agent
58
+ console.log(chalk.dim('Workspace already exists at'), workspacePath);
59
+ } else {
60
+ const { proceed } = await inquirer.prompt([{
61
+ type: 'confirm',
62
+ name: 'proceed',
63
+ message: `Workspace already exists at ${workspacePath}. Re-run onboarding?`,
64
+ default: false
65
+ }]);
66
+
67
+ if (!proceed) {
68
+ console.log(chalk.yellow('\nOnboarding cancelled.'));
69
+ return;
70
+ }
58
71
  }
59
72
  }
60
73
 
@@ -24,7 +24,7 @@ export default async function update() {
24
24
 
25
25
  try {
26
26
  // Check npm registry for latest version
27
- const { stdout } = await execAsync('npm view cairn version');
27
+ const { stdout } = await execAsync('npm view cairn-work version');
28
28
  const latestVersion = stdout.trim();
29
29
 
30
30
  console.log(chalk.dim('Current version:'), chalk.cyan(currentVersion));
@@ -52,10 +52,10 @@ export default async function update() {
52
52
 
53
53
  // Perform update
54
54
  console.log();
55
- console.log(chalk.dim('Running:'), chalk.cyan('npm install -g cairn@latest'));
55
+ console.log(chalk.dim('Running:'), chalk.cyan('npm install -g cairn-work@latest'));
56
56
  console.log();
57
57
 
58
- const updateProcess = exec('npm install -g cairn@latest');
58
+ const updateProcess = exec('npm install -g cairn-work@latest');
59
59
  updateProcess.stdout.pipe(process.stdout);
60
60
  updateProcess.stderr.pipe(process.stderr);
61
61
 
@@ -68,7 +68,7 @@ export default async function update() {
68
68
  } else {
69
69
  console.log();
70
70
  console.error(chalk.red('✗'), 'Update failed');
71
- console.log(chalk.dim('Try running manually:'), chalk.cyan('npm install -g cairn@latest'));
71
+ console.log(chalk.dim('Try running manually:'), chalk.cyan('npm install -g cairn-work@latest'));
72
72
  console.log();
73
73
  process.exit(1);
74
74
  }
@@ -77,7 +77,7 @@ export default async function update() {
77
77
  } catch (error) {
78
78
  console.error(chalk.red('Error checking for updates:'), error.message);
79
79
  console.log(chalk.dim('\nYou can update manually:'));
80
- console.log(chalk.cyan(' npm install -g cairn@latest'));
80
+ console.log(chalk.cyan(' npm install -g cairn-work@latest'));
81
81
  console.log();
82
82
  process.exit(1);
83
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cairn-work",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "AI-native project management - work with AI agents using markdown files",
5
5
  "type": "module",
6
6
  "bin": {
@@ -175,17 +175,20 @@ The CLI ensures proper structure, slugification, and frontmatter.
175
175
  Options:
176
176
  - `--project <slug>` - Project slug (REQUIRED)
177
177
  - `--assignee <name>` - Who's responsible (default: human)
178
- - `--description "text"` - Task description
178
+ - `--description "text"` - Task description
179
179
  - `--objective "text"` - What needs to be accomplished
180
180
  - `--status <status>` - Initial status (default: pending)
181
181
  - `--due YYYY-MM-DD` - Due date
182
182
 
183
+ **IMPORTANT:** Always pass `--description` and `--objective` with real content. If you omit them, the file will have empty sections. Write specific, actionable text — never placeholder text like "[Describe what needs to be accomplished]".
184
+
183
185
  Example:
184
186
  ```bash
185
187
  cairn create task "Set up CI pipeline" \\
186
188
  --project launch-app \\
187
189
  --assignee pagoda \\
188
190
  --description "Configure GitHub Actions for automated testing" \\
191
+ --objective "Set up a CI pipeline that runs lint, typecheck, and tests on every PR. Use GitHub Actions with a Node.js matrix for v18 and v20. Cache node_modules for faster runs." \\
189
192
  --due 2026-02-01
190
193
  ```
191
194
 
@@ -198,13 +201,20 @@ cairn create task "Set up CI pipeline" \\
198
201
  Options:
199
202
  - `--description "text"` - Project description
200
203
  - `--objective "text"` - Why this matters
204
+ - `--criteria "text"` - Success criteria
205
+ - `--context "text"` - Background, constraints, or dependencies
201
206
  - `--due YYYY-MM-DD` - Project deadline
202
207
  - `--assignee <name>` - Project owner
203
208
 
209
+ **IMPORTANT:** Always pass `--description`, `--objective`, `--criteria`, and `--context` with real content. Write thoughtful, specific text for each — these sections drive the entire project. After creating the project, read the charter and fill in anything that still needs detail.
210
+
204
211
  Example:
205
212
  ```bash
206
213
  cairn create project "Launch Mobile App" \\
207
214
  --description "Ship iOS and Android app by Q2" \\
215
+ --objective "We need a mobile app to reach users who primarily use phones. The web app has 60% mobile traffic but poor mobile UX." \\
216
+ --criteria "App published to App Store and Play Store. Supports login, dashboard, and notifications. 4+ star rating in first month." \\
217
+ --context "Using React Native for cross-platform. Backend API already exists. Design mockups in Figma." \\
208
218
  --due 2026-06-30
209
219
  ```
210
220