cairn-work 0.10.0 ā 0.11.1
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 +16 -0
- package/lib/commands/learn.js +123 -0
- package/lib/commands/my.js +18 -2
- package/lib/commands/triage.js +214 -0
- package/lib/schema/task-schema.js +1 -1
- package/package.json +1 -1
package/bin/cairn.js
CHANGED
|
@@ -39,6 +39,8 @@ import status from '../lib/commands/status.js';
|
|
|
39
39
|
import note from '../lib/commands/note.js';
|
|
40
40
|
import edit from '../lib/commands/edit.js';
|
|
41
41
|
import search from '../lib/commands/search.js';
|
|
42
|
+
import triage from '../lib/commands/triage.js';
|
|
43
|
+
import learn from '../lib/commands/learn.js';
|
|
42
44
|
|
|
43
45
|
// Onboard command - workspace setup with context files
|
|
44
46
|
program
|
|
@@ -205,6 +207,20 @@ program
|
|
|
205
207
|
.option('--project <slug>', 'Limit search to specific project')
|
|
206
208
|
.action(search);
|
|
207
209
|
|
|
210
|
+
// Triage command - process inbox items interactively
|
|
211
|
+
program
|
|
212
|
+
.command('triage')
|
|
213
|
+
.description('Process inbox items interactively - create tasks, delete, or skip')
|
|
214
|
+
.option('--assignee <name>', 'Default assignee for created tasks', 'you')
|
|
215
|
+
.action(triage);
|
|
216
|
+
|
|
217
|
+
// Learn command - show system overview and documentation
|
|
218
|
+
program
|
|
219
|
+
.command('learn')
|
|
220
|
+
.description('Show Cairn system overview and available documentation')
|
|
221
|
+
.option('--verbose', 'Show full documentation paths')
|
|
222
|
+
.action(learn);
|
|
223
|
+
|
|
208
224
|
// Parse and handle errors
|
|
209
225
|
program.parseAsync(process.argv).catch((error) => {
|
|
210
226
|
console.error(chalk.red('Error:'), error.message);
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { resolveWorkspace } from '../setup/workspace.js';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
export default async function learn(options) {
|
|
11
|
+
console.log(chalk.cyan.bold('\nš Cairn System Overview\n'));
|
|
12
|
+
|
|
13
|
+
// 1. Show workspace location
|
|
14
|
+
const workspacePath = resolveWorkspace();
|
|
15
|
+
if (workspacePath) {
|
|
16
|
+
console.log(chalk.green('ā'), 'Workspace:', chalk.cyan(workspacePath));
|
|
17
|
+
} else {
|
|
18
|
+
console.log(chalk.yellow('ā '), 'No workspace found. Run:', chalk.cyan('cairn init'));
|
|
19
|
+
}
|
|
20
|
+
console.log();
|
|
21
|
+
|
|
22
|
+
// 2. Project structure
|
|
23
|
+
console.log(chalk.bold('š Project Structure'));
|
|
24
|
+
console.log(chalk.dim(' projects/{slug}/charter.md # Project definition'));
|
|
25
|
+
console.log(chalk.dim(' projects/{slug}/tasks/{slug}.md # Individual tasks'));
|
|
26
|
+
console.log(chalk.dim(' inbox/ # Unprocessed items'));
|
|
27
|
+
console.log(chalk.dim(' artifacts/ # Shared documents'));
|
|
28
|
+
console.log(chalk.dim(' .cairn/ # System files'));
|
|
29
|
+
console.log();
|
|
30
|
+
|
|
31
|
+
// 3. Key concepts
|
|
32
|
+
console.log(chalk.bold('šÆ Key Concepts'));
|
|
33
|
+
console.log();
|
|
34
|
+
|
|
35
|
+
console.log(chalk.cyan(' Statuses:'));
|
|
36
|
+
console.log(chalk.dim(' pending ā not started yet'));
|
|
37
|
+
console.log(chalk.dim(' next-up ā ready to work on'));
|
|
38
|
+
console.log(chalk.dim(' in_progress ā actively working'));
|
|
39
|
+
console.log(chalk.dim(' blocked ā waiting for input'));
|
|
40
|
+
console.log(chalk.dim(' review ā awaiting approval'));
|
|
41
|
+
console.log(chalk.dim(' done ā completed'));
|
|
42
|
+
console.log();
|
|
43
|
+
|
|
44
|
+
console.log(chalk.cyan(' Autonomy Levels:'));
|
|
45
|
+
console.log(chalk.dim(' propose ā log approach only, don\'t do work'));
|
|
46
|
+
console.log(chalk.dim(' draft ā do work but need review (code changes)'));
|
|
47
|
+
console.log(chalk.dim(' execute ā do everything including irreversible actions'));
|
|
48
|
+
console.log();
|
|
49
|
+
|
|
50
|
+
console.log(chalk.cyan(' Priority & Due Dates:'));
|
|
51
|
+
console.log(chalk.dim(' P1 (urgent) ā due today'));
|
|
52
|
+
console.log(chalk.dim(' P2+ (less urgent) ā due in 7 days'));
|
|
53
|
+
console.log();
|
|
54
|
+
|
|
55
|
+
// 4. Common workflows
|
|
56
|
+
console.log(chalk.bold('š Common Workflows'));
|
|
57
|
+
console.log();
|
|
58
|
+
|
|
59
|
+
console.log(chalk.cyan(' Starting work:'));
|
|
60
|
+
console.log(chalk.dim(' cairn my # See your tasks'));
|
|
61
|
+
console.log(chalk.dim(' cairn start <task-slug> # Move to in_progress'));
|
|
62
|
+
console.log();
|
|
63
|
+
|
|
64
|
+
console.log(chalk.cyan(' While working:'));
|
|
65
|
+
console.log(chalk.dim(' cairn note <task-slug> "message" # Add quick notes'));
|
|
66
|
+
console.log(chalk.dim(' cairn view <task-slug> # View full details'));
|
|
67
|
+
console.log();
|
|
68
|
+
|
|
69
|
+
console.log(chalk.cyan(' Finishing:'));
|
|
70
|
+
console.log(chalk.dim(' cairn done <task-slug> # Auto moves to done/review'));
|
|
71
|
+
console.log(chalk.dim(' cairn block <task-slug> "reason" # When stuck'));
|
|
72
|
+
console.log();
|
|
73
|
+
|
|
74
|
+
console.log(chalk.cyan(' Creating new work:'));
|
|
75
|
+
console.log(chalk.dim(' cairn create task "Name" --project <slug> \\'));
|
|
76
|
+
console.log(chalk.dim(' --description "..." --objective "..."'));
|
|
77
|
+
console.log();
|
|
78
|
+
|
|
79
|
+
// 5. Available documentation
|
|
80
|
+
if (options.verbose) {
|
|
81
|
+
console.log(chalk.bold('š Available Documentation\n'));
|
|
82
|
+
|
|
83
|
+
// Check for CLI skills
|
|
84
|
+
const cliRoot = join(__dirname, '..', '..');
|
|
85
|
+
const skillsDir = join(cliRoot, 'skills');
|
|
86
|
+
if (existsSync(skillsDir)) {
|
|
87
|
+
console.log(chalk.cyan(' CLI Skills:'));
|
|
88
|
+
const skillFiles = readdirSync(skillsDir).filter(f => f.endsWith('.md'));
|
|
89
|
+
skillFiles.forEach(file => {
|
|
90
|
+
console.log(chalk.dim(` ${join(skillsDir, file)}`));
|
|
91
|
+
});
|
|
92
|
+
console.log();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Check for workspace planning docs
|
|
96
|
+
if (workspacePath) {
|
|
97
|
+
const cairnDir = join(workspacePath, '.cairn');
|
|
98
|
+
if (existsSync(cairnDir)) {
|
|
99
|
+
console.log(chalk.cyan(' Workspace Documentation:'));
|
|
100
|
+
const cairnFiles = readdirSync(cairnDir).filter(f => f.endsWith('.md'));
|
|
101
|
+
cairnFiles.forEach(file => {
|
|
102
|
+
console.log(chalk.dim(` ${join(cairnDir, file)}`));
|
|
103
|
+
});
|
|
104
|
+
console.log();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
console.log(chalk.dim('Run with --verbose to see full documentation paths'));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// 6. Quick tips
|
|
112
|
+
console.log(chalk.bold('š” Quick Tips'));
|
|
113
|
+
console.log();
|
|
114
|
+
console.log(chalk.dim(' ⢠Always use CLI to create entities (never edit YAML manually)'));
|
|
115
|
+
console.log(chalk.dim(' ⢠Set status immediately when your state changes'));
|
|
116
|
+
console.log(chalk.dim(' ⢠Use next-up for queued work, in_progress for active work'));
|
|
117
|
+
console.log(chalk.dim(' ⢠Respect autonomy: draftāreview, executeādone'));
|
|
118
|
+
console.log(chalk.dim(' ⢠Check cairn my regularly to see your workload'));
|
|
119
|
+
console.log();
|
|
120
|
+
|
|
121
|
+
console.log(chalk.dim('For detailed help on any command:'), chalk.cyan('cairn <command> --help'));
|
|
122
|
+
console.log();
|
|
123
|
+
}
|
package/lib/commands/my.js
CHANGED
|
@@ -78,6 +78,7 @@ export default async function my(options) {
|
|
|
78
78
|
|
|
79
79
|
const myTasks = {
|
|
80
80
|
in_progress: [],
|
|
81
|
+
next_up: [],
|
|
81
82
|
pending: [],
|
|
82
83
|
blocked: [],
|
|
83
84
|
review: []
|
|
@@ -105,6 +106,8 @@ export default async function my(options) {
|
|
|
105
106
|
|
|
106
107
|
if (task.status === 'in_progress') {
|
|
107
108
|
myTasks.in_progress.push(taskData);
|
|
109
|
+
} else if (task.status === 'next-up' || task.status === 'next_up') {
|
|
110
|
+
myTasks.next_up.push(taskData);
|
|
108
111
|
} else if (task.status === 'pending') {
|
|
109
112
|
myTasks.pending.push(taskData);
|
|
110
113
|
} else if (task.status === 'blocked') {
|
|
@@ -116,8 +119,8 @@ export default async function my(options) {
|
|
|
116
119
|
}
|
|
117
120
|
}
|
|
118
121
|
|
|
119
|
-
const totalTasks = myTasks.in_progress.length + myTasks.
|
|
120
|
-
myTasks.blocked.length + myTasks.review.length;
|
|
122
|
+
const totalTasks = myTasks.in_progress.length + myTasks.next_up.length +
|
|
123
|
+
myTasks.pending.length + myTasks.blocked.length + myTasks.review.length;
|
|
121
124
|
|
|
122
125
|
if (totalTasks === 0) {
|
|
123
126
|
console.log(chalk.dim(`No tasks assigned to ${myName}`));
|
|
@@ -139,6 +142,19 @@ export default async function my(options) {
|
|
|
139
142
|
console.log();
|
|
140
143
|
}
|
|
141
144
|
|
|
145
|
+
// Show next-up (ready to work on)
|
|
146
|
+
if (myTasks.next_up.length > 0) {
|
|
147
|
+
console.log(chalk.cyan.bold('āļø Next Up'));
|
|
148
|
+
for (const task of myTasks.next_up) {
|
|
149
|
+
console.log(chalk.bold(` ${task.slug}`));
|
|
150
|
+
console.log(chalk.dim(` ${task.project}`));
|
|
151
|
+
if (task.description) {
|
|
152
|
+
console.log(` ${task.description}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
console.log();
|
|
156
|
+
}
|
|
157
|
+
|
|
142
158
|
// Show blocked next (important)
|
|
143
159
|
if (myTasks.blocked.length > 0) {
|
|
144
160
|
console.log(chalk.red.bold('ā ļø Blocked'));
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, unlinkSync } from 'fs';
|
|
2
|
+
import { join, basename } from 'path';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
|
+
import inquirer from 'inquirer';
|
|
5
|
+
import { resolveWorkspace } from '../setup/workspace.js';
|
|
6
|
+
import create from './create.js';
|
|
7
|
+
|
|
8
|
+
function slugify(text) {
|
|
9
|
+
return text
|
|
10
|
+
.toLowerCase()
|
|
11
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
12
|
+
.replace(/^-+|-+$/g, '');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Extract project suggestions from existing projects
|
|
17
|
+
*/
|
|
18
|
+
function getAvailableProjects(workspacePath) {
|
|
19
|
+
const projectsPath = join(workspacePath, 'projects');
|
|
20
|
+
if (!existsSync(projectsPath)) return [];
|
|
21
|
+
|
|
22
|
+
return readdirSync(projectsPath, { withFileTypes: true })
|
|
23
|
+
.filter(dirent => dirent.isDirectory())
|
|
24
|
+
.map(dirent => dirent.name);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parse inbox item content to suggest task details
|
|
29
|
+
*/
|
|
30
|
+
function parseInboxItem(content) {
|
|
31
|
+
// Simple heuristic: first line = title, rest = potential description
|
|
32
|
+
const lines = content.trim().split('\n').filter(l => l.trim());
|
|
33
|
+
const title = lines[0] || 'Untitled Task';
|
|
34
|
+
const description = lines.length > 1 ? lines.slice(1).join(' ').substring(0, 100) : title;
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
title: title.replace(/^#+\s*/, '').trim(), // Remove markdown headers
|
|
38
|
+
description: description.trim()
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Main triage function - interactive inbox processing
|
|
44
|
+
*/
|
|
45
|
+
export default async function triage(options = {}) {
|
|
46
|
+
const workspacePath = resolveWorkspace();
|
|
47
|
+
|
|
48
|
+
if (!workspacePath) {
|
|
49
|
+
console.error(chalk.red('Error:'), 'No workspace found. Run:', chalk.cyan('cairn init'));
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const inboxPath = join(workspacePath, 'inbox');
|
|
54
|
+
|
|
55
|
+
if (!existsSync(inboxPath)) {
|
|
56
|
+
console.log(chalk.yellow('ā '), 'Inbox folder not found');
|
|
57
|
+
console.log(chalk.dim(' Create it at:'), inboxPath);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Get all inbox items
|
|
62
|
+
const inboxFiles = readdirSync(inboxPath)
|
|
63
|
+
.filter(f => f.endsWith('.md'))
|
|
64
|
+
.map(f => join(inboxPath, f));
|
|
65
|
+
|
|
66
|
+
if (inboxFiles.length === 0) {
|
|
67
|
+
console.log(chalk.green('ā'), 'Inbox is empty!');
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(chalk.cyan('\nš„ Inbox Triage\n'));
|
|
72
|
+
console.log(chalk.dim(`Found ${inboxFiles.length} item(s) to process\n`));
|
|
73
|
+
|
|
74
|
+
const availableProjects = getAvailableProjects(workspacePath);
|
|
75
|
+
|
|
76
|
+
if (availableProjects.length === 0) {
|
|
77
|
+
console.log(chalk.yellow('ā '), 'No projects found. Create one first:');
|
|
78
|
+
console.log(chalk.cyan(' cairn create project "My Project" --description "..." --objective "..." --criteria "..." --context "..."'));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
let processed = 0;
|
|
83
|
+
let skipped = 0;
|
|
84
|
+
|
|
85
|
+
for (const filePath of inboxFiles) {
|
|
86
|
+
const filename = basename(filePath);
|
|
87
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
88
|
+
const parsed = parseInboxItem(content);
|
|
89
|
+
|
|
90
|
+
console.log(chalk.bold(`\n${filename}`));
|
|
91
|
+
console.log(chalk.dim('ā'.repeat(60)));
|
|
92
|
+
console.log(content.substring(0, 200) + (content.length > 200 ? '...' : ''));
|
|
93
|
+
console.log(chalk.dim('ā'.repeat(60)));
|
|
94
|
+
|
|
95
|
+
// Ask what to do with this item
|
|
96
|
+
const { action } = await inquirer.prompt([{
|
|
97
|
+
type: 'list',
|
|
98
|
+
name: 'action',
|
|
99
|
+
message: 'What should we do with this?',
|
|
100
|
+
choices: [
|
|
101
|
+
{ name: 'Create task', value: 'create' },
|
|
102
|
+
{ name: 'Delete (already done/irrelevant)', value: 'delete' },
|
|
103
|
+
{ name: 'Skip (decide later)', value: 'skip' }
|
|
104
|
+
]
|
|
105
|
+
}]);
|
|
106
|
+
|
|
107
|
+
if (action === 'skip') {
|
|
108
|
+
skipped++;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (action === 'delete') {
|
|
113
|
+
unlinkSync(filePath);
|
|
114
|
+
console.log(chalk.green('ā'), 'Deleted');
|
|
115
|
+
processed++;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Create task flow
|
|
120
|
+
const answers = await inquirer.prompt([
|
|
121
|
+
{
|
|
122
|
+
type: 'list',
|
|
123
|
+
name: 'project',
|
|
124
|
+
message: 'Which project?',
|
|
125
|
+
choices: [
|
|
126
|
+
...availableProjects,
|
|
127
|
+
new inquirer.Separator(),
|
|
128
|
+
{ name: chalk.dim('(create new project)'), value: '_new_' }
|
|
129
|
+
]
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
type: 'input',
|
|
133
|
+
name: 'newProjectName',
|
|
134
|
+
message: 'New project name:',
|
|
135
|
+
when: (ans) => ans.project === '_new_'
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
type: 'input',
|
|
139
|
+
name: 'taskTitle',
|
|
140
|
+
message: 'Task title:',
|
|
141
|
+
default: parsed.title
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
type: 'input',
|
|
145
|
+
name: 'description',
|
|
146
|
+
message: 'Description:',
|
|
147
|
+
default: parsed.description
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
type: 'input',
|
|
151
|
+
name: 'objective',
|
|
152
|
+
message: 'Objective (what needs to happen):',
|
|
153
|
+
default: parsed.description
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
type: 'list',
|
|
157
|
+
name: 'status',
|
|
158
|
+
message: 'Initial status:',
|
|
159
|
+
choices: ['pending', 'in-progress', 'in-review', 'blocked'],
|
|
160
|
+
default: 'pending'
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
type: 'list',
|
|
164
|
+
name: 'priority',
|
|
165
|
+
message: 'Priority:',
|
|
166
|
+
choices: [
|
|
167
|
+
{ name: 'P1 - Critical', value: '1' },
|
|
168
|
+
{ name: 'P2 - High', value: '2' },
|
|
169
|
+
{ name: 'P3 - Normal', value: '3' },
|
|
170
|
+
{ name: 'P4 - Low', value: '4' }
|
|
171
|
+
],
|
|
172
|
+
default: '3'
|
|
173
|
+
}
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
// Handle new project creation
|
|
177
|
+
if (answers.project === '_new_') {
|
|
178
|
+
console.log(chalk.yellow('\nā '), 'Creating a new project requires full details.');
|
|
179
|
+
console.log(chalk.dim(' Run:'), chalk.cyan(`cairn create project "${answers.newProjectName}" --description "..." --objective "..." --criteria "..." --context "..."`));
|
|
180
|
+
console.log(chalk.dim(' Then re-run triage.\n'));
|
|
181
|
+
skipped++;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Create the task
|
|
186
|
+
try {
|
|
187
|
+
await create('task', answers.taskTitle, {
|
|
188
|
+
project: answers.project,
|
|
189
|
+
description: answers.description,
|
|
190
|
+
objective: answers.objective,
|
|
191
|
+
status: answers.status,
|
|
192
|
+
priority: answers.priority,
|
|
193
|
+
assignee: options.assignee || 'you'
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
// Delete inbox item after successful creation
|
|
197
|
+
unlinkSync(filePath);
|
|
198
|
+
console.log(chalk.dim(' Removed from inbox\n'));
|
|
199
|
+
processed++;
|
|
200
|
+
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error(chalk.red('ā'), 'Failed to create task:', error.message);
|
|
203
|
+
skipped++;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Summary
|
|
208
|
+
console.log(chalk.cyan('\nš Triage Complete\n'));
|
|
209
|
+
console.log(chalk.green(' Processed:'), processed);
|
|
210
|
+
if (skipped > 0) {
|
|
211
|
+
console.log(chalk.yellow(' Skipped:'), skipped);
|
|
212
|
+
}
|
|
213
|
+
console.log();
|
|
214
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Task and Project schema definitions
|
|
2
2
|
|
|
3
|
-
export const VALID_STATUSES = ['pending', 'in_progress', 'blocked', 'review', 'done', 'completed'];
|
|
3
|
+
export const VALID_STATUSES = ['pending', 'next-up', 'next_up', 'in_progress', 'blocked', 'review', 'done', 'completed'];
|
|
4
4
|
export const VALID_AUTONOMY_LEVELS = ['propose', 'draft', 'execute'];
|
|
5
5
|
|
|
6
6
|
export const TASK_SCHEMA = {
|