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 +3 -3
- package/bin/cairn.js +2 -0
- package/lib/commands/create.js +46 -19
- package/lib/commands/onboard.js +45 -32
- package/lib/commands/update.js +5 -5
- package/package.json +1 -1
- package/skills/agent-skill.template.md +11 -1
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
|
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,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
|
package/lib/commands/onboard.js
CHANGED
|
@@ -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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
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
|
|
package/lib/commands/update.js
CHANGED
|
@@ -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
|
@@ -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
|
|