cairn-work 0.11.0 → 0.11.2

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
@@ -40,6 +40,7 @@ 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
42
  import triage from '../lib/commands/triage.js';
43
+ import learn from '../lib/commands/learn.js';
43
44
 
44
45
  // Onboard command - workspace setup with context files
45
46
  program
@@ -213,6 +214,13 @@ program
213
214
  .option('--assignee <name>', 'Default assignee for created tasks', 'you')
214
215
  .action(triage);
215
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
+
216
224
  // Parse and handle errors
217
225
  program.parseAsync(process.argv).catch((error) => {
218
226
  console.error(chalk.red('Error:'), error.message);
@@ -8,6 +8,7 @@ import jsYaml from 'js-yaml';
8
8
 
9
9
  import { validateWorkspace, createWelcomeFile, resolveWorkspace } from '../setup/workspace.js';
10
10
  import { verifyWorkspaceContext } from '../setup/context.js';
11
+ import { validateTaskFrontmatter, validateProjectFrontmatter, VALID_STATUSES } from '../schema/task-schema.js';
11
12
 
12
13
  function parseFrontmatter(content) {
13
14
  const lines = content.split('\n');
@@ -43,6 +44,83 @@ function isUrl(str) {
43
44
  return str.startsWith('http://') || str.startsWith('https://') || str.startsWith('obsidian://');
44
45
  }
45
46
 
47
+ function validateFrontmatter(workspacePath) {
48
+ const projectsDir = join(workspacePath, 'projects');
49
+ const results = {
50
+ totalTasks: 0,
51
+ totalProjects: 0,
52
+ tasksWithErrors: [],
53
+ projectsWithErrors: []
54
+ };
55
+
56
+ if (!existsSync(projectsDir)) {
57
+ return results;
58
+ }
59
+
60
+ const projects = readdirSync(projectsDir).filter(item => {
61
+ const path = join(projectsDir, item);
62
+ return statSync(path).isDirectory();
63
+ });
64
+
65
+ for (const project of projects) {
66
+ // Check project charter
67
+ const charterPath = join(projectsDir, project, 'charter.md');
68
+ if (existsSync(charterPath)) {
69
+ results.totalProjects++;
70
+ const content = readFileSync(charterPath, 'utf8');
71
+ const frontmatter = parseFrontmatter(content);
72
+
73
+ if (frontmatter) {
74
+ const errors = validateProjectFrontmatter(frontmatter);
75
+ if (errors.length > 0) {
76
+ results.projectsWithErrors.push({
77
+ project,
78
+ file: 'charter.md',
79
+ errors
80
+ });
81
+ }
82
+ }
83
+ }
84
+
85
+ // Check tasks
86
+ const tasksDir = join(projectsDir, project, 'tasks');
87
+ if (!existsSync(tasksDir)) {
88
+ continue;
89
+ }
90
+
91
+ const taskFiles = readdirSync(tasksDir).filter(f => f.endsWith('.md'));
92
+
93
+ for (const taskFile of taskFiles) {
94
+ const taskPath = join(tasksDir, taskFile);
95
+ const content = readFileSync(taskPath, 'utf8');
96
+ const frontmatter = parseFrontmatter(content);
97
+
98
+ if (!frontmatter) {
99
+ results.tasksWithErrors.push({
100
+ project,
101
+ file: taskFile,
102
+ errors: ['Unable to parse frontmatter']
103
+ });
104
+ continue;
105
+ }
106
+
107
+ results.totalTasks++;
108
+
109
+ const errors = validateTaskFrontmatter(frontmatter);
110
+ if (errors.length > 0) {
111
+ results.tasksWithErrors.push({
112
+ project,
113
+ file: taskFile,
114
+ frontmatter,
115
+ errors
116
+ });
117
+ }
118
+ }
119
+ }
120
+
121
+ return results;
122
+ }
123
+
46
124
  function validateArtifacts(workspacePath) {
47
125
  const projectsDir = join(workspacePath, 'projects');
48
126
  const results = {
@@ -215,7 +293,29 @@ export default async function doctor() {
215
293
  }
216
294
  }
217
295
 
218
- // Check 4: Validate artifacts
296
+ // Check 4: Validate frontmatter
297
+ const frontmatterSpinner = ora('Validating task and project frontmatter').start();
298
+ let frontmatterResults = { totalTasks: 0, totalProjects: 0, tasksWithErrors: [], projectsWithErrors: [] };
299
+
300
+ if (existsSync(workspacePath)) {
301
+ frontmatterResults = validateFrontmatter(workspacePath);
302
+
303
+ const totalErrors = frontmatterResults.tasksWithErrors.length + frontmatterResults.projectsWithErrors.length;
304
+
305
+ if (totalErrors > 0) {
306
+ frontmatterSpinner.fail(`Found ${totalErrors} frontmatter error(s)`);
307
+ issues.push({
308
+ problem: `${totalErrors} task(s)/project(s) have invalid frontmatter`,
309
+ fix: 'See details below'
310
+ });
311
+ } else {
312
+ frontmatterSpinner.succeed(`Frontmatter valid (${frontmatterResults.totalTasks} tasks, ${frontmatterResults.totalProjects} projects)`);
313
+ }
314
+ } else {
315
+ frontmatterSpinner.skip('Skipped (no workspace)');
316
+ }
317
+
318
+ // Check 5: Validate artifacts
219
319
  const artifactSpinner = ora('Checking task artifacts').start();
220
320
  let artifactResults = { totalTasks: 0, completedTasks: 0, completedWithoutArtifacts: [], tasksWithArtifacts: [], brokenArtifacts: [] };
221
321
 
@@ -286,6 +386,37 @@ export default async function doctor() {
286
386
  }
287
387
  }
288
388
 
389
+ // Detailed frontmatter report
390
+ if (frontmatterResults.tasksWithErrors.length > 0) {
391
+ console.log(chalk.red.bold('Tasks with frontmatter errors:\n'));
392
+ for (const task of frontmatterResults.tasksWithErrors.slice(0, 10)) {
393
+ console.log(chalk.red(' ✗'), `${task.project}/${task.file.replace('.md', '')}`);
394
+ for (const error of task.errors) {
395
+ console.log(chalk.dim(' -'), error);
396
+
397
+ // Add helpful suggestions for common errors
398
+ if (error.includes('Invalid status') && task.frontmatter && task.frontmatter.status === 'active') {
399
+ console.log(chalk.dim(' Suggestion: Change "active" to "in_progress"'));
400
+ }
401
+ }
402
+ }
403
+ if (frontmatterResults.tasksWithErrors.length > 10) {
404
+ console.log(chalk.dim(` ... and ${frontmatterResults.tasksWithErrors.length - 10} more`));
405
+ }
406
+ console.log();
407
+ }
408
+
409
+ if (frontmatterResults.projectsWithErrors.length > 0) {
410
+ console.log(chalk.red.bold('Projects with frontmatter errors:\n'));
411
+ for (const project of frontmatterResults.projectsWithErrors) {
412
+ console.log(chalk.red(' ✗'), `${project.project}/charter.md`);
413
+ for (const error of project.errors) {
414
+ console.log(chalk.dim(' -'), error);
415
+ }
416
+ }
417
+ console.log();
418
+ }
419
+
289
420
  // Detailed artifact report
290
421
  if (artifactResults.completedWithoutArtifacts.length > 0) {
291
422
  console.log(chalk.yellow.bold('Completed tasks without artifacts:\n'));
@@ -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
+ }
@@ -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.pending.length +
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'));
@@ -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 = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cairn-work",
3
- "version": "0.11.0",
3
+ "version": "0.11.2",
4
4
  "description": "AI-native project management - optimized CLI for AI agents and humans working together",
5
5
  "type": "module",
6
6
  "bin": {