clawvault 2.4.5 → 2.4.7

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.
Files changed (73) hide show
  1. package/bin/clawvault.js +10 -0
  2. package/bin/command-registration.test.js +1 -1
  3. package/bin/help-contract.test.js +1 -0
  4. package/bin/register-config-route-commands.test.js +8 -1
  5. package/bin/register-core-commands.js +3 -3
  6. package/bin/register-kanban-commands.js +56 -0
  7. package/bin/register-kanban-commands.test.js +83 -0
  8. package/bin/register-project-commands.js +209 -0
  9. package/bin/register-project-commands.test.js +201 -0
  10. package/bin/register-query-commands.js +40 -0
  11. package/bin/register-task-commands.js +60 -25
  12. package/bin/register-task-commands.test.js +49 -4
  13. package/bin/test-helpers/cli-command-fixtures.js +15 -0
  14. package/dist/{chunk-3PJIGGWV.js → chunk-2CDEETQN.js} +1 -0
  15. package/dist/{chunk-B3SMJZIZ.js → chunk-2RK2AG32.js} +5 -5
  16. package/dist/chunk-5GZFTAL7.js +340 -0
  17. package/dist/{chunk-YIRWDQKA.js → chunk-6RQPD7X6.js} +3 -4
  18. package/dist/{chunk-HNMFXFYP.js → chunk-7OHQFMJK.js} +2 -1
  19. package/dist/{chunk-4JJL47IJ.js → chunk-C3PF7WBA.js} +2 -2
  20. package/dist/{chunk-JXY6T5R7.js → chunk-FW465EEA.js} +1 -1
  21. package/dist/{chunk-BI6SGGZP.js → chunk-G3OQJ2NQ.js} +1 -1
  22. package/dist/chunk-GSD4ALSI.js +724 -0
  23. package/dist/chunk-IOALNTAN.js +757 -0
  24. package/dist/chunk-ITPEXLHA.js +528 -0
  25. package/dist/chunk-J5EMBUPK.js +399 -0
  26. package/dist/chunk-K3CDT7IH.js +122 -0
  27. package/dist/{chunk-AHGUJG76.js → chunk-KCCHROBR.js} +13 -69
  28. package/dist/{chunk-U2ONVV7N.js → chunk-LMCC5OC7.js} +2 -2
  29. package/dist/{chunk-QALB2V3E.js → chunk-MQUJNOHK.js} +1 -1
  30. package/dist/{chunk-RXEIQ3KQ.js → chunk-TMZMN7OS.js} +334 -457
  31. package/dist/{chunk-HVTTYDCJ.js → chunk-VR5NE7PZ.js} +1 -1
  32. package/dist/{chunk-22WE3J4F.js → chunk-WIICLBNF.js} +35 -4
  33. package/dist/chunk-YCVDVI5B.js +273 -0
  34. package/dist/{chunk-NAMFB7ZA.js → chunk-Z2XBWN7A.js} +0 -2
  35. package/dist/commands/archive.js +3 -3
  36. package/dist/commands/backlog.js +1 -1
  37. package/dist/commands/blocked.js +1 -1
  38. package/dist/commands/canvas.d.ts +1 -14
  39. package/dist/commands/canvas.js +123 -1543
  40. package/dist/commands/context.js +5 -6
  41. package/dist/commands/doctor.js +5 -5
  42. package/dist/commands/inject.d.ts +2 -0
  43. package/dist/commands/inject.js +14 -0
  44. package/dist/commands/kanban.d.ts +63 -0
  45. package/dist/commands/kanban.js +21 -0
  46. package/dist/commands/migrate-observations.js +2 -2
  47. package/dist/commands/observe.js +8 -6
  48. package/dist/commands/project.d.ts +85 -0
  49. package/dist/commands/project.js +411 -0
  50. package/dist/commands/rebuild.js +7 -5
  51. package/dist/commands/reflect.js +5 -4
  52. package/dist/commands/replay.js +10 -7
  53. package/dist/commands/setup.d.ts +1 -1
  54. package/dist/commands/setup.js +2 -2
  55. package/dist/commands/sleep.d.ts +1 -1
  56. package/dist/commands/sleep.js +11 -8
  57. package/dist/commands/status.js +5 -5
  58. package/dist/commands/task.d.ts +20 -8
  59. package/dist/commands/task.js +11 -244
  60. package/dist/commands/wake.d.ts +1 -1
  61. package/dist/commands/wake.js +4 -4
  62. package/dist/index.d.ts +76 -106
  63. package/dist/index.js +99 -34
  64. package/dist/inject-x65KXWPk.d.ts +137 -0
  65. package/dist/lib/project-utils.d.ts +97 -0
  66. package/dist/lib/project-utils.js +19 -0
  67. package/dist/lib/task-utils.d.ts +48 -12
  68. package/dist/lib/task-utils.js +5 -1
  69. package/dist/{types-DMU3SuAV.d.ts → types-jjuYN2Xn.d.ts} +1 -1
  70. package/package.json +2 -2
  71. package/dist/chunk-DEFBIVQ3.js +0 -373
  72. package/dist/chunk-L3DJ36BZ.js +0 -40
  73. package/dist/chunk-UMMCYTJV.js +0 -105
@@ -3,6 +3,20 @@
3
3
  * Registers task, backlog, blocked, and canvas commands
4
4
  */
5
5
 
6
+ function parseCsvList(value) {
7
+ if (!value) return undefined;
8
+ const items = String(value)
9
+ .split(',')
10
+ .map((item) => item.trim())
11
+ .filter(Boolean);
12
+ return items.length > 0 ? items : undefined;
13
+ }
14
+
15
+ function clearableValue(value, shouldClear) {
16
+ if (shouldClear) return null;
17
+ return value;
18
+ }
19
+
6
20
  export function registerTaskCommands(
7
21
  program,
8
22
  { chalk, resolveVaultPath }
@@ -21,6 +35,11 @@ export function registerTaskCommands(
21
35
  .option('--project <project>', 'Project name')
22
36
  .option('--priority <priority>', 'Priority (critical, high, medium, low)')
23
37
  .option('--due <date>', 'Due date (YYYY-MM-DD)')
38
+ .option('--tags <tags>', 'Comma-separated tags')
39
+ .option('--description <description>', 'One-line task summary')
40
+ .option('--estimate <estimate>', 'Estimate (for example: 2h, 1d, 1w)')
41
+ .option('--parent <slug>', 'Parent task slug')
42
+ .option('--depends-on <slugs>', 'Comma-separated dependency slugs')
24
43
  .action(async (title, options) => {
25
44
  try {
26
45
  const vaultPath = resolveVaultPath(options.vault);
@@ -31,7 +50,12 @@ export function registerTaskCommands(
31
50
  owner: options.owner,
32
51
  project: options.project,
33
52
  priority: options.priority,
34
- due: options.due
53
+ due: options.due,
54
+ tags: parseCsvList(options.tags),
55
+ description: options.description,
56
+ estimate: options.estimate,
57
+ parent: options.parent,
58
+ dependsOn: parseCsvList(options.dependsOn)
35
59
  }
36
60
  });
37
61
  } catch (err) {
@@ -49,6 +73,9 @@ export function registerTaskCommands(
49
73
  .option('--project <project>', 'Filter by project')
50
74
  .option('--status <status>', 'Filter by status (open, in-progress, blocked, done)')
51
75
  .option('--priority <priority>', 'Filter by priority')
76
+ .option('--due', 'Show only tasks with due dates (sorted by due date)')
77
+ .option('--tag <tag>', 'Filter by tag')
78
+ .option('--overdue', 'Show overdue tasks that are not done')
52
79
  .option('--json', 'Output as JSON')
53
80
  .action(async (options) => {
54
81
  try {
@@ -60,6 +87,9 @@ export function registerTaskCommands(
60
87
  project: options.project,
61
88
  status: options.status,
62
89
  priority: options.priority,
90
+ due: options.due,
91
+ tag: options.tag,
92
+ overdue: options.overdue,
63
93
  json: options.json
64
94
  }
65
95
  });
@@ -76,12 +106,28 @@ export function registerTaskCommands(
76
106
  .option('-v, --vault <path>', 'Vault path')
77
107
  .option('--status <status>', 'New status')
78
108
  .option('--owner <owner>', 'New owner')
109
+ .option('--clear-owner', 'Clear owner')
79
110
  .option('--project <project>', 'New project')
111
+ .option('--clear-project', 'Clear project')
80
112
  .option('--priority <priority>', 'New priority')
113
+ .option('--clear-priority', 'Clear priority')
81
114
  .option('--blocked-by <blocker>', 'What is blocking this task')
115
+ .option('--clear-blocked-by', 'Clear blocked-by field')
82
116
  .option('--due <date>', 'New due date')
117
+ .option('--clear-due', 'Clear due date')
118
+ .option('--tags <tags>', 'Comma-separated tags')
119
+ .option('--clear-tags', 'Clear tags')
120
+ .option('--description <description>', 'One-line task summary')
121
+ .option('--clear-description', 'Clear description')
122
+ .option('--estimate <estimate>', 'Estimate (for example: 2h, 1d, 1w)')
123
+ .option('--clear-estimate', 'Clear estimate')
124
+ .option('--parent <slug>', 'Parent task slug')
125
+ .option('--clear-parent', 'Clear parent task')
126
+ .option('--depends-on <slugs>', 'Comma-separated dependency slugs')
127
+ .option('--clear-depends-on', 'Clear dependencies')
83
128
  .option('--confidence <value>', 'Transition confidence (0-1)', parseFloat)
84
129
  .option('--reason <reason>', 'Reason for status change')
130
+ .option('--clear-reason', 'Clear reason')
85
131
  .action(async (slug, options) => {
86
132
  try {
87
133
  const vaultPath = resolveVaultPath(options.vault);
@@ -90,13 +136,18 @@ export function registerTaskCommands(
90
136
  slug,
91
137
  options: {
92
138
  status: options.status,
93
- owner: options.owner,
94
- project: options.project,
95
- priority: options.priority,
96
- blockedBy: options.blockedBy,
97
- due: options.due,
139
+ owner: clearableValue(options.owner, options.clearOwner),
140
+ project: clearableValue(options.project, options.clearProject),
141
+ priority: clearableValue(options.priority, options.clearPriority),
142
+ blockedBy: clearableValue(options.blockedBy, options.clearBlockedBy),
143
+ due: clearableValue(options.due, options.clearDue),
144
+ tags: options.clearTags ? null : parseCsvList(options.tags),
145
+ description: clearableValue(options.description, options.clearDescription),
146
+ estimate: clearableValue(options.estimate, options.clearEstimate),
147
+ parent: clearableValue(options.parent, options.clearParent),
148
+ dependsOn: options.clearDependsOn ? null : parseCsvList(options.dependsOn),
98
149
  confidence: options.confidence,
99
- reason: options.reason
150
+ reason: clearableValue(options.reason, options.clearReason)
100
151
  }
101
152
  });
102
153
  } catch (err) {
@@ -282,28 +333,12 @@ export function registerTaskCommands(
282
333
  .description('Generate Obsidian canvas dashboard')
283
334
  .option('-v, --vault <path>', 'Vault path')
284
335
  .option('--output <path>', 'Output file path (default: dashboard.canvas)')
285
- .option('--template <id>', 'Canvas template ID (default, project-board, brain, sprint)')
286
- .option('--project <project>', 'Project filter for template-aware canvases')
287
- .option('--owner <owner>', 'Filter tasks by owner (agent name or human)')
288
- .option('--width <pixels>', 'Canvas width in pixels', parseInt)
289
- .option('--height <pixels>', 'Canvas height in pixels', parseInt)
290
- .option('--include-done', 'Include completed tasks (default: limited)')
291
- .option('--list-templates', 'List available canvas templates and exit')
292
336
  .action(async (options) => {
293
337
  try {
294
- const vaultPath = options.listTemplates
295
- ? (options.vault || '.')
296
- : resolveVaultPath(options.vault);
338
+ const vaultPath = resolveVaultPath(options.vault);
297
339
  const { canvasCommand } = await import('../dist/commands/canvas.js');
298
340
  await canvasCommand(vaultPath, {
299
- output: options.output,
300
- template: options.template,
301
- project: options.project,
302
- owner: options.owner,
303
- width: options.width,
304
- height: options.height,
305
- includeDone: options.includeDone,
306
- listTemplates: options.listTemplates
341
+ output: options.output
307
342
  });
308
343
  } catch (err) {
309
344
  console.error(chalk.red(`Error: ${err.message}`));
@@ -4,7 +4,53 @@ import { registerTaskCommands } from './register-task-commands.js';
4
4
  import { chalkStub, stubResolveVaultPath } from './test-helpers/cli-command-fixtures.js';
5
5
 
6
6
  describe('register-task-commands', () => {
7
- it('adds canvas template and listing flags', () => {
7
+ it('adds enriched task add/list/update options', () => {
8
+ const program = new Command();
9
+ registerTaskCommands(program, {
10
+ chalk: chalkStub,
11
+ resolveVaultPath: stubResolveVaultPath
12
+ });
13
+
14
+ const taskCommand = program.commands.find((command) => command.name() === 'task');
15
+ expect(taskCommand).toBeDefined();
16
+
17
+ const addCommand = taskCommand?.commands.find((command) => command.name() === 'add');
18
+ const addFlags = addCommand?.options.map((option) => option.flags) ?? [];
19
+ expect(addFlags).toEqual(expect.arrayContaining([
20
+ '--due <date>',
21
+ '--tags <tags>',
22
+ '--description <description>',
23
+ '--estimate <estimate>',
24
+ '--parent <slug>',
25
+ '--depends-on <slugs>'
26
+ ]));
27
+
28
+ const listCommand = taskCommand?.commands.find((command) => command.name() === 'list');
29
+ const listFlags = listCommand?.options.map((option) => option.flags) ?? [];
30
+ expect(listFlags).toEqual(expect.arrayContaining([
31
+ '--due',
32
+ '--tag <tag>',
33
+ '--overdue'
34
+ ]));
35
+
36
+ const updateCommand = taskCommand?.commands.find((command) => command.name() === 'update');
37
+ const updateFlags = updateCommand?.options.map((option) => option.flags) ?? [];
38
+ expect(updateFlags).toEqual(expect.arrayContaining([
39
+ '--tags <tags>',
40
+ '--description <description>',
41
+ '--estimate <estimate>',
42
+ '--parent <slug>',
43
+ '--depends-on <slugs>',
44
+ '--clear-due',
45
+ '--clear-tags',
46
+ '--clear-description',
47
+ '--clear-estimate',
48
+ '--clear-parent',
49
+ '--clear-depends-on'
50
+ ]));
51
+ });
52
+
53
+ it('adds simplified canvas flags', () => {
8
54
  const program = new Command();
9
55
  registerTaskCommands(program, {
10
56
  chalk: chalkStub,
@@ -16,9 +62,8 @@ describe('register-task-commands', () => {
16
62
 
17
63
  const optionFlags = canvasCommand?.options.map((option) => option.flags) ?? [];
18
64
  expect(optionFlags).toEqual(expect.arrayContaining([
19
- '--template <id>',
20
- '--list-templates',
21
- '--project <project>'
65
+ '-v, --vault <path>',
66
+ '--output <path>'
22
67
  ]));
23
68
  });
24
69
  });
@@ -10,6 +10,9 @@ import { registerTemplateCommands } from '../register-template-commands.js';
10
10
  import { registerVaultOperationsCommands } from '../register-vault-operations-commands.js';
11
11
  import { registerConfigCommands } from '../register-config-commands.js';
12
12
  import { registerRouteCommands } from '../register-route-commands.js';
13
+ import { registerTaskCommands } from '../register-task-commands.js';
14
+ import { registerKanbanCommands } from '../register-kanban-commands.js';
15
+ import { registerProjectCommands } from '../register-project-commands.js';
13
16
 
14
17
  export const chalkStub = {
15
18
  cyan: (value) => value,
@@ -99,6 +102,18 @@ export function registerAllCommandModules(program = new Command()) {
99
102
  chalk: chalkStub,
100
103
  resolveVaultPath: stubResolveVaultPath
101
104
  });
105
+ registerTaskCommands(program, {
106
+ chalk: chalkStub,
107
+ resolveVaultPath: stubResolveVaultPath
108
+ });
109
+ registerKanbanCommands(program, {
110
+ chalk: chalkStub,
111
+ resolveVaultPath: stubResolveVaultPath
112
+ });
113
+ registerProjectCommands(program, {
114
+ chalk: chalkStub,
115
+ resolveVaultPath: stubResolveVaultPath
116
+ });
102
117
 
103
118
  return program;
104
119
  }
@@ -1,5 +1,6 @@
1
1
  // src/types.ts
2
2
  var DEFAULT_CATEGORIES = [
3
+ "rules",
3
4
  "preferences",
4
5
  "decisions",
5
6
  "patterns",
@@ -1,18 +1,18 @@
1
- import {
2
- getSessionsDir
3
- } from "./chunk-HRLWZGMA.js";
4
1
  import {
5
2
  parseSessionFile
6
3
  } from "./chunk-P5EPF6MB.js";
7
4
  import {
8
5
  Observer
9
- } from "./chunk-RXEIQ3KQ.js";
6
+ } from "./chunk-TMZMN7OS.js";
7
+ import {
8
+ getSessionsDir
9
+ } from "./chunk-HRLWZGMA.js";
10
10
  import {
11
11
  resolveVaultPath
12
12
  } from "./chunk-MXSSG3QU.js";
13
13
  import {
14
14
  getObservationPath
15
- } from "./chunk-NAMFB7ZA.js";
15
+ } from "./chunk-Z2XBWN7A.js";
16
16
 
17
17
  // src/commands/observe.ts
18
18
  import * as fs3 from "fs";
@@ -0,0 +1,340 @@
1
+ import {
2
+ listTasks,
3
+ slugify
4
+ } from "./chunk-IOALNTAN.js";
5
+
6
+ // src/lib/project-utils.ts
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ import matter from "gray-matter";
10
+ function extractTitle(content) {
11
+ const match = content.match(/^#\s+(.+)$/m);
12
+ return match ? match[1].trim() : "";
13
+ }
14
+ function isDateSlug(slug) {
15
+ return /^\d{4}-\d{2}-\d{2}$/.test(slug);
16
+ }
17
+ function normalizeStringArray(value) {
18
+ return value.map((item) => item.trim()).filter(Boolean);
19
+ }
20
+ function getProjectsDir(vaultPath) {
21
+ return path.join(path.resolve(vaultPath), "projects");
22
+ }
23
+ function ensureProjectsDir(vaultPath) {
24
+ const projectsDir = getProjectsDir(vaultPath);
25
+ if (!fs.existsSync(projectsDir)) {
26
+ fs.mkdirSync(projectsDir, { recursive: true });
27
+ }
28
+ }
29
+ function getProjectPath(vaultPath, slug) {
30
+ return path.join(getProjectsDir(vaultPath), `${slug}.md`);
31
+ }
32
+ function parseProjectDateValue(filePath) {
33
+ const filename = path.basename(filePath, ".md");
34
+ if (/^\d{4}-\d{2}-\d{2}$/.test(filename)) {
35
+ const dateTs = Date.parse(`${filename}T00:00:00.000Z`);
36
+ if (!Number.isNaN(dateTs)) {
37
+ return dateTs;
38
+ }
39
+ }
40
+ return fs.statSync(filePath).mtime.getTime();
41
+ }
42
+ function parseSortableTimestamp(value) {
43
+ if (!value) return 0;
44
+ const timestamp = Date.parse(value);
45
+ return Number.isNaN(timestamp) ? 0 : timestamp;
46
+ }
47
+ function normalizeProjectStatus(value) {
48
+ if (value === "active" || value === "paused" || value === "completed" || value === "archived") {
49
+ return value;
50
+ }
51
+ return "active";
52
+ }
53
+ function normalizeProjectFrontmatter(frontmatter) {
54
+ const normalizedCreated = typeof frontmatter.created === "string" && frontmatter.created ? frontmatter.created : (/* @__PURE__ */ new Date(0)).toISOString();
55
+ const normalizedUpdated = typeof frontmatter.updated === "string" && frontmatter.updated ? frontmatter.updated : normalizedCreated;
56
+ const normalized = {
57
+ ...frontmatter,
58
+ type: "project",
59
+ status: normalizeProjectStatus(frontmatter.status),
60
+ created: normalizedCreated,
61
+ updated: normalizedUpdated
62
+ };
63
+ if (normalized.team) {
64
+ const team = normalizeStringArray(normalized.team);
65
+ if (team.length === 0) {
66
+ delete normalized.team;
67
+ } else {
68
+ normalized.team = team;
69
+ }
70
+ }
71
+ if (normalized.tags) {
72
+ const tags = normalizeStringArray(normalized.tags);
73
+ if (tags.length === 0) {
74
+ delete normalized.tags;
75
+ } else {
76
+ normalized.tags = tags;
77
+ }
78
+ }
79
+ return normalized;
80
+ }
81
+ function listProjects(vaultPath, filters) {
82
+ const projectsDir = getProjectsDir(vaultPath);
83
+ if (!fs.existsSync(projectsDir)) {
84
+ return [];
85
+ }
86
+ const projects = [];
87
+ const entries = fs.readdirSync(projectsDir, { withFileTypes: true });
88
+ for (const entry of entries) {
89
+ if (!entry.isFile() || !entry.name.endsWith(".md")) {
90
+ continue;
91
+ }
92
+ const slug = entry.name.replace(/\.md$/, "");
93
+ if (isDateSlug(slug)) {
94
+ continue;
95
+ }
96
+ const project = readProject(vaultPath, slug);
97
+ if (!project) continue;
98
+ if (filters) {
99
+ if (filters.status && project.frontmatter.status !== filters.status) continue;
100
+ if (filters.owner && project.frontmatter.owner !== filters.owner) continue;
101
+ if (filters.client && project.frontmatter.client !== filters.client) continue;
102
+ if (filters.tag) {
103
+ const tags = project.frontmatter.tags || [];
104
+ const hasTag = tags.some((tag) => tag.toLowerCase() === filters.tag?.toLowerCase());
105
+ if (!hasTag) continue;
106
+ }
107
+ }
108
+ projects.push(project);
109
+ }
110
+ return projects.sort((left, right) => {
111
+ const rightTime = parseSortableTimestamp(right.frontmatter.updated || right.frontmatter.created);
112
+ const leftTime = parseSortableTimestamp(left.frontmatter.updated || left.frontmatter.created);
113
+ return rightTime - leftTime;
114
+ });
115
+ }
116
+ function readProject(vaultPath, slug) {
117
+ if (!slug || isDateSlug(slug) || slug.includes(path.sep)) {
118
+ return null;
119
+ }
120
+ const projectPath = getProjectPath(vaultPath, slug);
121
+ if (!fs.existsSync(projectPath)) {
122
+ return null;
123
+ }
124
+ try {
125
+ const raw = fs.readFileSync(projectPath, "utf-8");
126
+ const { data, content } = matter(raw);
127
+ if (data.type !== "project") {
128
+ return null;
129
+ }
130
+ const frontmatter = normalizeProjectFrontmatter(data);
131
+ const title = extractTitle(content) || slug;
132
+ return {
133
+ slug,
134
+ title,
135
+ content,
136
+ frontmatter
137
+ };
138
+ } catch {
139
+ return null;
140
+ }
141
+ }
142
+ function createProject(vaultPath, title, options = {}) {
143
+ ensureProjectsDir(vaultPath);
144
+ const slug = slugify(title);
145
+ const projectPath = getProjectPath(vaultPath, slug);
146
+ if (fs.existsSync(projectPath)) {
147
+ throw new Error(`Project already exists: ${slug}`);
148
+ }
149
+ const now = (/* @__PURE__ */ new Date()).toISOString();
150
+ const frontmatter = {
151
+ type: "project",
152
+ status: options.status ?? "active",
153
+ created: now,
154
+ updated: now
155
+ };
156
+ if (options.owner) frontmatter.owner = options.owner;
157
+ if (options.team && options.team.length > 0) {
158
+ const team = normalizeStringArray(options.team);
159
+ if (team.length > 0) frontmatter.team = team;
160
+ }
161
+ if (options.client) frontmatter.client = options.client;
162
+ if (options.tags && options.tags.length > 0) {
163
+ const tags = normalizeStringArray(options.tags);
164
+ if (tags.length > 0) frontmatter.tags = tags;
165
+ }
166
+ if (options.description) frontmatter.description = options.description;
167
+ if (options.started) frontmatter.started = options.started;
168
+ if (options.deadline) frontmatter.deadline = options.deadline;
169
+ if (options.repo) frontmatter.repo = options.repo;
170
+ if (options.url) frontmatter.url = options.url;
171
+ if (options.completed) frontmatter.completed = options.completed;
172
+ if (options.reason) frontmatter.reason = options.reason;
173
+ let content = `# ${title}
174
+ `;
175
+ const wikiLinks = /* @__PURE__ */ new Set();
176
+ if (options.owner) wikiLinks.add(options.owner);
177
+ if (options.client) wikiLinks.add(options.client);
178
+ for (const member of options.team || []) {
179
+ const trimmed = member.trim();
180
+ if (trimmed) wikiLinks.add(trimmed);
181
+ }
182
+ if (wikiLinks.size > 0) {
183
+ content += `
184
+ ${Array.from(wikiLinks).map((link) => `[[${link}]]`).join(" | ")}
185
+ `;
186
+ }
187
+ if (options.content) {
188
+ content += `
189
+ ${options.content}
190
+ `;
191
+ }
192
+ const fileContent = matter.stringify(content, frontmatter);
193
+ fs.writeFileSync(projectPath, fileContent);
194
+ return {
195
+ slug,
196
+ title,
197
+ content,
198
+ frontmatter
199
+ };
200
+ }
201
+ function updateProject(vaultPath, slug, updates) {
202
+ const project = readProject(vaultPath, slug);
203
+ if (!project) {
204
+ throw new Error(`Project not found: ${slug}`);
205
+ }
206
+ const now = (/* @__PURE__ */ new Date()).toISOString();
207
+ const nextFrontmatter = {
208
+ ...project.frontmatter,
209
+ type: "project",
210
+ updated: now
211
+ };
212
+ if (updates.status !== void 0) {
213
+ nextFrontmatter.status = updates.status;
214
+ if (updates.status === "completed" && !updates.completed && !nextFrontmatter.completed) {
215
+ nextFrontmatter.completed = now;
216
+ }
217
+ }
218
+ if (updates.owner !== void 0) {
219
+ if (updates.owner === null || updates.owner.trim() === "") {
220
+ delete nextFrontmatter.owner;
221
+ } else {
222
+ nextFrontmatter.owner = updates.owner;
223
+ }
224
+ }
225
+ if (updates.team !== void 0) {
226
+ if (updates.team === null) {
227
+ delete nextFrontmatter.team;
228
+ } else {
229
+ const team = normalizeStringArray(updates.team);
230
+ if (team.length === 0) {
231
+ delete nextFrontmatter.team;
232
+ } else {
233
+ nextFrontmatter.team = team;
234
+ }
235
+ }
236
+ }
237
+ if (updates.client !== void 0) {
238
+ if (updates.client === null || updates.client.trim() === "") {
239
+ delete nextFrontmatter.client;
240
+ } else {
241
+ nextFrontmatter.client = updates.client;
242
+ }
243
+ }
244
+ if (updates.tags !== void 0) {
245
+ if (updates.tags === null) {
246
+ delete nextFrontmatter.tags;
247
+ } else {
248
+ const tags = normalizeStringArray(updates.tags);
249
+ if (tags.length === 0) {
250
+ delete nextFrontmatter.tags;
251
+ } else {
252
+ nextFrontmatter.tags = tags;
253
+ }
254
+ }
255
+ }
256
+ if (updates.description !== void 0) {
257
+ if (updates.description === null || updates.description.trim() === "") {
258
+ delete nextFrontmatter.description;
259
+ } else {
260
+ nextFrontmatter.description = updates.description;
261
+ }
262
+ }
263
+ if (updates.started !== void 0) {
264
+ if (updates.started === null || updates.started.trim() === "") {
265
+ delete nextFrontmatter.started;
266
+ } else {
267
+ nextFrontmatter.started = updates.started;
268
+ }
269
+ }
270
+ if (updates.deadline !== void 0) {
271
+ if (updates.deadline === null || updates.deadline.trim() === "") {
272
+ delete nextFrontmatter.deadline;
273
+ } else {
274
+ nextFrontmatter.deadline = updates.deadline;
275
+ }
276
+ }
277
+ if (updates.repo !== void 0) {
278
+ if (updates.repo === null || updates.repo.trim() === "") {
279
+ delete nextFrontmatter.repo;
280
+ } else {
281
+ nextFrontmatter.repo = updates.repo;
282
+ }
283
+ }
284
+ if (updates.url !== void 0) {
285
+ if (updates.url === null || updates.url.trim() === "") {
286
+ delete nextFrontmatter.url;
287
+ } else {
288
+ nextFrontmatter.url = updates.url;
289
+ }
290
+ }
291
+ if (updates.completed !== void 0) {
292
+ if (updates.completed === null || updates.completed.trim() === "") {
293
+ delete nextFrontmatter.completed;
294
+ } else {
295
+ nextFrontmatter.completed = updates.completed;
296
+ }
297
+ }
298
+ if (updates.reason !== void 0) {
299
+ if (updates.reason === null || updates.reason.trim() === "") {
300
+ delete nextFrontmatter.reason;
301
+ } else {
302
+ nextFrontmatter.reason = updates.reason;
303
+ }
304
+ }
305
+ const projectPath = getProjectPath(vaultPath, slug);
306
+ fs.writeFileSync(projectPath, matter.stringify(project.content, nextFrontmatter));
307
+ return {
308
+ ...project,
309
+ frontmatter: nextFrontmatter
310
+ };
311
+ }
312
+ function archiveProject(vaultPath, slug, reason) {
313
+ return updateProject(vaultPath, slug, {
314
+ status: "archived",
315
+ reason: reason ?? null,
316
+ completed: (/* @__PURE__ */ new Date()).toISOString()
317
+ });
318
+ }
319
+ function getProjectTasks(vaultPath, slug) {
320
+ return listTasks(vaultPath, { project: slug });
321
+ }
322
+ function getProjectActivity(vaultPath, slug) {
323
+ const projectActivityDir = path.join(getProjectsDir(vaultPath), slug);
324
+ if (!fs.existsSync(projectActivityDir) || !fs.statSync(projectActivityDir).isDirectory()) {
325
+ return [];
326
+ }
327
+ const entries = fs.readdirSync(projectActivityDir, { withFileTypes: true });
328
+ const files = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => path.join(projectActivityDir, entry.name));
329
+ return files.sort((left, right) => parseProjectDateValue(right) - parseProjectDateValue(left));
330
+ }
331
+
332
+ export {
333
+ listProjects,
334
+ readProject,
335
+ createProject,
336
+ updateProject,
337
+ archiveProject,
338
+ getProjectTasks,
339
+ getProjectActivity
340
+ };
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-6B3JWM7J.js";
4
4
  import {
5
5
  DEFAULT_CATEGORIES
6
- } from "./chunk-3PJIGGWV.js";
6
+ } from "./chunk-2CDEETQN.js";
7
7
 
8
8
  // src/commands/setup.ts
9
9
  import * as fs from "fs";
@@ -327,10 +327,9 @@ async function setupCommand(options = {}) {
327
327
  }
328
328
  }
329
329
  if (doCanvas) {
330
- const templateId = typeof options.canvas === "string" ? options.canvas : "default";
331
330
  try {
332
331
  const { canvasCommand } = await import("./commands/canvas.js");
333
- await canvasCommand(target.vaultPath, { template: templateId });
332
+ await canvasCommand(target.vaultPath);
334
333
  } catch (err) {
335
334
  console.log(`\u26A0 Canvas generation failed: ${err instanceof Error ? err.message : String(err)}`);
336
335
  }
@@ -353,7 +352,7 @@ async function setupCommand(options = {}) {
353
352
  console.log("\nCustomize further:");
354
353
  console.log(" clawvault setup --theme neural # Neural neural graph colors");
355
354
  console.log(" clawvault setup --theme minimal # Subtle category colors");
356
- console.log(" clawvault setup --canvas brain # Generate brain architecture canvas");
355
+ console.log(" clawvault setup --canvas # Generate vault status canvas");
357
356
  console.log(" clawvault setup --no-bases --no-graph-colors # Structure only");
358
357
  console.log(" clawvault setup --force # Overwrite existing configs");
359
358
  }
@@ -10,7 +10,7 @@ import {
10
10
  import {
11
11
  DEFAULT_CATEGORIES,
12
12
  TYPE_TO_CATEGORY
13
- } from "./chunk-3PJIGGWV.js";
13
+ } from "./chunk-2CDEETQN.js";
14
14
  import {
15
15
  buildOrUpdateMemoryGraphIndex
16
16
  } from "./chunk-ZZA73MFY.js";
@@ -851,6 +851,7 @@ clawvault wake # restore context on startup
851
851
  facts: "Raw information, data points, things that are true",
852
852
  feelings: "Emotional states, reactions, energy levels",
853
853
  decisions: "Choices made with context and reasoning",
854
+ rules: "Injectable operational constraints, guardrails, and runbooks",
854
855
  lessons: "What I learned, insights, patterns observed",
855
856
  commitments: "Promises, goals, obligations to fulfill",
856
857
  preferences: "Likes, dislikes, how I want things",
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  runReflection
3
- } from "./chunk-AHGUJG76.js";
3
+ } from "./chunk-KCCHROBR.js";
4
4
  import {
5
5
  Observer
6
- } from "./chunk-RXEIQ3KQ.js";
6
+ } from "./chunk-TMZMN7OS.js";
7
7
  import {
8
8
  resolveVaultPath
9
9
  } from "./chunk-MXSSG3QU.js";
@@ -8,7 +8,7 @@ import {
8
8
  } from "./chunk-FHFUXL6G.js";
9
9
  import {
10
10
  listObservationFiles
11
- } from "./chunk-NAMFB7ZA.js";
11
+ } from "./chunk-Z2XBWN7A.js";
12
12
 
13
13
  // src/commands/migrate-observations.ts
14
14
  import * as fs from "fs";