brainclaw 0.28.0 → 1.5.3

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 (198) hide show
  1. package/README.md +193 -170
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli.js +683 -23
  4. package/dist/commands/accept.js +3 -0
  5. package/dist/commands/add-step.js +11 -26
  6. package/dist/commands/agent-board.js +70 -3
  7. package/dist/commands/audit.js +19 -0
  8. package/dist/commands/check-policy.js +54 -0
  9. package/dist/commands/check-security-mcp.js +145 -0
  10. package/dist/commands/check-security.js +106 -0
  11. package/dist/commands/claim-resource.js +1 -0
  12. package/dist/commands/codev.js +672 -0
  13. package/dist/commands/compact.js +74 -0
  14. package/dist/commands/complete-step.js +16 -26
  15. package/dist/commands/constraint.js +8 -20
  16. package/dist/commands/decision.js +9 -20
  17. package/dist/commands/delete-plan.js +10 -12
  18. package/dist/commands/delete-step.js +16 -0
  19. package/dist/commands/dispatch.js +163 -0
  20. package/dist/commands/doctor.js +1122 -49
  21. package/dist/commands/enable-agent.js +1 -0
  22. package/dist/commands/export.js +280 -22
  23. package/dist/commands/handoff.js +33 -0
  24. package/dist/commands/harvest.js +189 -0
  25. package/dist/commands/hooks.js +82 -25
  26. package/dist/commands/inbox.js +169 -0
  27. package/dist/commands/init.js +38 -31
  28. package/dist/commands/install-hooks.js +71 -44
  29. package/dist/commands/link.js +89 -0
  30. package/dist/commands/list-claims.js +48 -3
  31. package/dist/commands/list-plans.js +129 -25
  32. package/dist/commands/loops-handlers.js +409 -0
  33. package/dist/commands/mcp-read-handlers.js +1628 -0
  34. package/dist/commands/mcp-schemas.generated.js +74 -0
  35. package/dist/commands/mcp.js +4244 -1475
  36. package/dist/commands/plan-resource.js +64 -0
  37. package/dist/commands/plan.js +12 -26
  38. package/dist/commands/prune.js +37 -2
  39. package/dist/commands/reflect.js +20 -7
  40. package/dist/commands/release-claim.js +11 -6
  41. package/dist/commands/release-notes.js +170 -0
  42. package/dist/commands/repair.js +210 -0
  43. package/dist/commands/run-profile.js +57 -0
  44. package/dist/commands/sequence.js +113 -0
  45. package/dist/commands/session-end.js +423 -14
  46. package/dist/commands/session-start.js +214 -41
  47. package/dist/commands/setup-security.js +103 -0
  48. package/dist/commands/setup.js +42 -4
  49. package/dist/commands/stale.js +109 -0
  50. package/dist/commands/switch.js +131 -10
  51. package/dist/commands/trap.js +14 -31
  52. package/dist/commands/update-handoff.js +63 -4
  53. package/dist/commands/update-plan.js +21 -28
  54. package/dist/commands/update-step.js +37 -0
  55. package/dist/commands/upgrade.js +313 -6
  56. package/dist/commands/usage.js +102 -0
  57. package/dist/commands/version.js +20 -0
  58. package/dist/commands/who.js +124 -0
  59. package/dist/commands/worktree.js +105 -0
  60. package/dist/core/actions.js +315 -0
  61. package/dist/core/agent-capability.js +610 -17
  62. package/dist/core/agent-context.js +7 -1
  63. package/dist/core/agent-files.js +1169 -85
  64. package/dist/core/agent-integrations.js +160 -5
  65. package/dist/core/agent-inventory.js +2 -0
  66. package/dist/core/agent-profiles.js +93 -0
  67. package/dist/core/agent-registry.js +162 -30
  68. package/dist/core/agentrun-reconciler.js +345 -0
  69. package/dist/core/agentruns.js +424 -0
  70. package/dist/core/ai-agent-detection.js +31 -10
  71. package/dist/core/archival.js +77 -0
  72. package/dist/core/assignment-sweeper.js +82 -0
  73. package/dist/core/assignments.js +367 -0
  74. package/dist/core/audit.js +30 -0
  75. package/dist/core/bootstrap.js +61 -10
  76. package/dist/core/brainclaw-version.js +94 -2
  77. package/dist/core/candidates.js +93 -2
  78. package/dist/core/claims.js +419 -0
  79. package/dist/core/codev-metrics.js +77 -0
  80. package/dist/core/codev-personas.js +31 -0
  81. package/dist/core/codev-plan-gen.js +35 -0
  82. package/dist/core/codev-prompts.js +74 -0
  83. package/dist/core/codev-responses.js +62 -0
  84. package/dist/core/codev-rounds.js +218 -0
  85. package/dist/core/config.js +4 -0
  86. package/dist/core/context.js +454 -34
  87. package/dist/core/coordination.js +201 -6
  88. package/dist/core/cross-project.js +230 -16
  89. package/dist/core/default-profiles/doctor.yaml +11 -0
  90. package/dist/core/default-profiles/janitor.yaml +11 -0
  91. package/dist/core/default-profiles/onboarder.yaml +11 -0
  92. package/dist/core/default-profiles/reviewer.yaml +13 -0
  93. package/dist/core/dispatcher.js +1189 -0
  94. package/dist/core/duplicates.js +2 -2
  95. package/dist/core/entity-operations.js +450 -0
  96. package/dist/core/entity-registry.js +344 -0
  97. package/dist/core/event-log.js +1 -0
  98. package/dist/core/events.js +106 -2
  99. package/dist/core/execution-adapters.js +154 -0
  100. package/dist/core/execution-context.js +63 -0
  101. package/dist/core/execution-profile.js +270 -0
  102. package/dist/core/execution.js +255 -0
  103. package/dist/core/facade-schema.js +81 -0
  104. package/dist/core/federation-cloud.js +99 -0
  105. package/dist/core/federation-message.js +52 -0
  106. package/dist/core/federation-transport.js +65 -0
  107. package/dist/core/gc-semantic.js +482 -0
  108. package/dist/core/governance.js +247 -0
  109. package/dist/core/guards.js +19 -0
  110. package/dist/core/ideation.js +72 -0
  111. package/dist/core/identity.js +252 -28
  112. package/dist/core/ids.js +6 -0
  113. package/dist/core/input-validation.js +2 -2
  114. package/dist/core/instruction-templates.js +344 -136
  115. package/dist/core/io.js +90 -11
  116. package/dist/core/lock.js +6 -2
  117. package/dist/core/loops/brief-assembly.js +213 -0
  118. package/dist/core/loops/facade-schema.js +148 -0
  119. package/dist/core/loops/index.js +7 -0
  120. package/dist/core/loops/iteration-engine.js +139 -0
  121. package/dist/core/loops/lock.js +385 -0
  122. package/dist/core/loops/store.js +201 -0
  123. package/dist/core/loops/types.js +403 -0
  124. package/dist/core/loops/verbs.js +534 -0
  125. package/dist/core/markdown.js +15 -3
  126. package/dist/core/memory-compactor.js +432 -0
  127. package/dist/core/memory-git.js +152 -8
  128. package/dist/core/messaging.js +278 -0
  129. package/dist/core/migration.js +32 -1
  130. package/dist/core/mutation-pipeline.js +4 -2
  131. package/dist/core/operations/memory-mutation.js +129 -0
  132. package/dist/core/operations/memory-write.js +78 -0
  133. package/dist/core/operations/plan.js +190 -0
  134. package/dist/core/policy.js +169 -0
  135. package/dist/core/repo-analysis.js +67 -0
  136. package/dist/core/reputation.js +9 -3
  137. package/dist/core/schema.js +546 -21
  138. package/dist/core/search.js +21 -2
  139. package/dist/core/security-cache.js +71 -0
  140. package/dist/core/security-guard.js +152 -0
  141. package/dist/core/security-scoring.js +86 -0
  142. package/dist/core/sequence.js +130 -0
  143. package/dist/core/socket-client.js +113 -0
  144. package/dist/core/staleness.js +246 -0
  145. package/dist/core/state.js +98 -22
  146. package/dist/core/store-resolution.js +54 -12
  147. package/dist/core/toml-writer.js +76 -0
  148. package/dist/core/upgrades/backup.js +232 -0
  149. package/dist/core/upgrades/health-check.js +169 -0
  150. package/dist/core/upgrades/patches/candidate-archive.js +145 -0
  151. package/dist/core/upgrades/patches/handoff-review-strip.js +128 -0
  152. package/dist/core/upgrades/patches/provenance-rollout.js +136 -0
  153. package/dist/core/upgrades/schema-version.js +97 -0
  154. package/dist/core/worktree.js +606 -0
  155. package/dist/facts.js +114 -0
  156. package/dist/facts.json +111 -0
  157. package/docs/architecture/project-refs.md +5 -1
  158. package/docs/cli.md +690 -43
  159. package/docs/concepts/ideation-loop.md +317 -0
  160. package/docs/concepts/loop-engine.md +456 -0
  161. package/docs/concepts/mcp-governance.md +268 -0
  162. package/docs/concepts/memory-staleness.md +122 -0
  163. package/docs/concepts/multi-agent-workflows.md +166 -0
  164. package/docs/concepts/plans-and-claims.md +31 -6
  165. package/docs/concepts/project-md-convention.md +35 -0
  166. package/docs/concepts/troubleshooting.md +220 -0
  167. package/docs/concepts/upgrade-cli.md +202 -0
  168. package/docs/concepts/upgrade-dogfood-procedure.md +114 -0
  169. package/docs/context-format-changelog.md +2 -2
  170. package/docs/context-format.md +2 -2
  171. package/docs/index.md +68 -0
  172. package/docs/integrations/agents.md +15 -16
  173. package/docs/integrations/cline.md +88 -0
  174. package/docs/integrations/codex.md +75 -23
  175. package/docs/integrations/continue.md +60 -0
  176. package/docs/integrations/copilot.md +67 -9
  177. package/docs/integrations/kilocode.md +72 -0
  178. package/docs/integrations/mcp.md +304 -21
  179. package/docs/integrations/mistral-vibe.md +122 -0
  180. package/docs/integrations/opencode.md +84 -0
  181. package/docs/integrations/overview.md +23 -8
  182. package/docs/integrations/roo.md +74 -0
  183. package/docs/integrations/windsurf.md +83 -0
  184. package/docs/mcp-schema-changelog.md +191 -1
  185. package/docs/playbooks/integration/index.md +121 -0
  186. package/docs/playbooks/productivity/index.md +102 -0
  187. package/docs/playbooks/team/index.md +122 -0
  188. package/docs/product/agent-first-model.md +184 -0
  189. package/docs/product/entity-model-audit.md +462 -0
  190. package/docs/quickstart-existing-project.md +135 -0
  191. package/docs/quickstart.md +124 -37
  192. package/docs/release-maintenance.md +79 -0
  193. package/docs/review.md +2 -0
  194. package/docs/server-operations.md +118 -0
  195. package/package.json +20 -12
  196. package/dist/commands/claude-desktop-extension.js +0 -18
  197. package/dist/commands/diff.js +0 -99
  198. package/dist/core/claude-desktop-extension.js +0 -224
@@ -1,12 +1,90 @@
1
1
  import path from 'node:path';
2
2
  import { loadActiveProject, saveActiveProject, clearActiveProject } from '../core/active-project.js';
3
+ import { loadCurrentSession, saveCurrentSession } from '../core/identity.js';
3
4
  import { memoryExists } from '../core/io.js';
4
- import { resolveProjectRef, resolveWorkspaceRoot } from '../core/store-resolution.js';
5
+ import { resolveProjectRef } from '../core/store-resolution.js';
5
6
  import { scanNestedBrainclawProjects } from '../core/workspace-projects.js';
6
7
  import { loadConfig } from '../core/config.js';
8
+ /**
9
+ * Switch to a project programmatically.
10
+ * When sessionOnly=true (default for MCP), ONLY writes to the session state.
11
+ * This prevents agents from cross-contaminating each other's active project.
12
+ */
13
+ export function switchProject(projectRef, options = {}) {
14
+ const cwd = options.cwd ?? process.cwd();
15
+ const wsRoot = findOutermostWorkspaceRoot(cwd);
16
+ if (!wsRoot) {
17
+ throw new Error('No brainclaw workspace found. Run `brainclaw init` first.');
18
+ }
19
+ const resolved = resolveProjectRef(projectRef, cwd);
20
+ if (!resolved) {
21
+ throw new Error(`Cannot resolve project "${projectRef}". Use bclaw_switch with list=true to see available projects.`);
22
+ }
23
+ let projectName;
24
+ try {
25
+ const config = loadConfig(resolved);
26
+ projectName = config.project_name;
27
+ }
28
+ catch { /* name is optional */ }
29
+ const now = new Date().toISOString();
30
+ const session = loadCurrentSession(cwd);
31
+ const sessionOnly = options.sessionOnly ?? true;
32
+ if (session && sessionOnly) {
33
+ saveCurrentSession({
34
+ ...session,
35
+ active_project: { path: resolved, name: projectName, switched_at: now },
36
+ }, cwd);
37
+ return { switched: true, path: resolved, name: projectName, scope: 'session', workspace_root: wsRoot };
38
+ }
39
+ if (session) {
40
+ // Also write to session even when not sessionOnly
41
+ saveCurrentSession({
42
+ ...session,
43
+ active_project: { path: resolved, name: projectName, switched_at: now },
44
+ }, cwd);
45
+ }
46
+ saveActiveProject(wsRoot, {
47
+ path: resolved,
48
+ name: projectName,
49
+ switched_at: now,
50
+ switched_by: process.env.BRAINCLAW_AGENT_NAME ?? process.env.USER ?? 'unknown',
51
+ });
52
+ return { switched: true, path: resolved, name: projectName, scope: 'global', workspace_root: wsRoot };
53
+ }
54
+ /**
55
+ * List available projects in the workspace.
56
+ */
57
+ export function listAvailableProjects(cwd) {
58
+ const wsRoot = findOutermostWorkspaceRoot(cwd ?? process.cwd());
59
+ if (!wsRoot) {
60
+ throw new Error('No brainclaw workspace found.');
61
+ }
62
+ const active = loadActiveProject(wsRoot);
63
+ const projects = [];
64
+ if (memoryExists(wsRoot)) {
65
+ try {
66
+ const config = loadConfig(wsRoot);
67
+ projects.push({ name: config.project_name, path: wsRoot, relative_path: '.', active: active?.path === wsRoot });
68
+ }
69
+ catch {
70
+ projects.push({ path: wsRoot, relative_path: '.', active: active?.path === wsRoot });
71
+ }
72
+ }
73
+ const children = scanNestedBrainclawProjects(wsRoot, 7);
74
+ for (const child of children) {
75
+ const childPath = path.resolve(child.path);
76
+ if (childPath === wsRoot)
77
+ continue;
78
+ const rel = path.relative(wsRoot, childPath) || '.';
79
+ projects.push({ name: child.project_name, path: childPath, relative_path: rel, active: active?.path === childPath });
80
+ }
81
+ return { workspace_root: wsRoot, projects };
82
+ }
7
83
  export function runSwitch(projectRef, options = {}) {
84
+ // Use real cwd, not effective cwd — switch must see the full workspace
8
85
  const cwd = options.cwd ?? process.cwd();
9
- const wsRoot = resolveWorkspaceRoot(cwd);
86
+ // Walk up from real cwd to find the outermost .brainclaw/ (workspace root)
87
+ const wsRoot = findOutermostWorkspaceRoot(cwd);
10
88
  if (!wsRoot) {
11
89
  console.error('Error: no brainclaw workspace found. Run `brainclaw init` first.');
12
90
  process.exit(1);
@@ -18,6 +96,11 @@ export function runSwitch(projectRef, options = {}) {
18
96
  }
19
97
  // --clear: remove active project
20
98
  if (options.clear) {
99
+ const session = loadCurrentSession(cwd);
100
+ if (session?.active_project) {
101
+ const { active_project: _removed, ...rest } = session;
102
+ saveCurrentSession(rest, cwd);
103
+ }
21
104
  clearActiveProject(wsRoot);
22
105
  if (options.json) {
23
106
  console.log(JSON.stringify({ cleared: true }));
@@ -47,18 +130,35 @@ export function runSwitch(projectRef, options = {}) {
47
130
  catch {
48
131
  // name is optional
49
132
  }
50
- saveActiveProject(wsRoot, {
51
- path: resolved,
52
- name: projectName,
53
- switched_at: new Date().toISOString(),
54
- switched_by: process.env.BRAINCLAW_AGENT_NAME ?? process.env.USER ?? 'unknown',
55
- });
133
+ const now = new Date().toISOString();
134
+ const session = loadCurrentSession(cwd);
135
+ const scopedToSession = options.session ?? !!session;
136
+ let scope;
137
+ if (scopedToSession && session) {
138
+ // Write to session state — only this agent sees this switch
139
+ saveCurrentSession({
140
+ ...session,
141
+ active_project: { path: resolved, name: projectName, switched_at: now },
142
+ }, cwd);
143
+ scope = 'session';
144
+ }
145
+ else {
146
+ // Fall back to global active-project.json
147
+ saveActiveProject(wsRoot, {
148
+ path: resolved,
149
+ name: projectName,
150
+ switched_at: now,
151
+ switched_by: process.env.BRAINCLAW_AGENT_NAME ?? process.env.USER ?? 'unknown',
152
+ });
153
+ scope = 'global';
154
+ }
56
155
  if (options.json) {
57
- console.log(JSON.stringify({ switched: true, path: resolved, name: projectName }));
156
+ console.log(JSON.stringify({ switched: true, path: resolved, name: projectName, scope }));
58
157
  }
59
158
  else {
60
159
  const rel = path.relative(wsRoot, resolved) || '.';
61
- console.log(`✔ Switched to ${projectName ? `"${projectName}" (${rel})` : rel}`);
160
+ const scopeHint = scope === 'session' ? ' (session-scoped)' : '';
161
+ console.log(`✔ Switched to ${projectName ? `"${projectName}" (${rel})` : rel}${scopeHint}`);
62
162
  }
63
163
  }
64
164
  function showCurrent(wsRoot, json) {
@@ -138,4 +238,25 @@ function listProjects(wsRoot, json) {
138
238
  console.log('\nNo active project. Use `brainclaw switch <project>` to set one.');
139
239
  }
140
240
  }
241
+ /**
242
+ * Find the outermost .brainclaw/ workspace root by walking UP from cwd.
243
+ * Unlike resolveWorkspaceRoot which may return the closest store,
244
+ * this returns the farthest one — the true multi-project workspace root.
245
+ */
246
+ function findOutermostWorkspaceRoot(startDir) {
247
+ let dir = path.resolve(startDir);
248
+ const root = path.parse(dir).root;
249
+ const home = process.env.HOME || process.env.USERPROFILE || root;
250
+ let outermost;
251
+ while (dir !== root && dir !== home) {
252
+ if (memoryExists(dir)) {
253
+ outermost = dir; // keep going — we want the outermost
254
+ }
255
+ const parent = path.dirname(dir);
256
+ if (parent === dir)
257
+ break;
258
+ dir = parent;
259
+ }
260
+ return outermost;
261
+ }
141
262
  //# sourceMappingURL=switch.js.map
@@ -1,19 +1,14 @@
1
- import { loadState, persistState } from '../core/state.js';
2
1
  import { resolveCurrentAgentName } from '../core/agent-registry.js';
3
2
  import { resolveCurrentHostId } from '../core/host.js';
4
3
  import { loadConfig } from '../core/config.js';
5
- import { nowISO } from '../core/ids.js';
6
4
  import { scanText } from '../core/security.js';
7
- import { memoryExists } from '../core/io.js';
8
- import { generateTrapIdWithLabel, saveOperationalTrap } from '../core/traps.js';
5
+ import { requireInitialized } from '../core/guards.js';
9
6
  import { validateCliInput, validateCliTtl } from '../core/input-validation.js';
10
7
  import { resolveTargetStore } from '../core/store-resolution.js';
8
+ import { createTrap } from '../core/operations/memory-write.js';
11
9
  export function runTrap(text, options = {}) {
12
10
  const cwd = resolveTargetStore(options.cwd ?? process.cwd(), options.store ?? 'local');
13
- if (!memoryExists(cwd)) {
14
- console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
15
- process.exit(1);
16
- }
11
+ requireInitialized(cwd);
17
12
  validateCliInput(text, options.tag);
18
13
  if (options.ttl) {
19
14
  validateCliTtl(options.ttl);
@@ -27,35 +22,23 @@ export function runTrap(text, options = {}) {
27
22
  process.exit(1);
28
23
  }
29
24
  }
30
- const state = loadState(cwd);
31
- const { id, short_label } = generateTrapIdWithLabel();
32
25
  const visibility = options.visibility ?? 'shared';
33
26
  const hostId = visibility === 'shared' ? undefined : resolveCurrentHostId(options.host);
34
- const entry = {
35
- id,
36
- short_label,
27
+ const result = createTrap({
37
28
  text,
38
- created_at: nowISO(),
39
29
  author: options.author ?? resolveCurrentAgentName(cwd),
40
- status: options.status ?? 'active',
41
- severity: options.severity ?? 'medium',
42
- tags: options.tag ?? [],
43
- related_paths: options.path,
44
- plan_id: options.plan,
30
+ status: options.status,
31
+ severity: options.severity,
32
+ tags: options.tag,
33
+ relatedPaths: options.path,
34
+ planId: options.plan,
45
35
  visibility,
46
- host_id: hostId,
47
- expires_at: options.ttl ? parseTtl(options.ttl) : undefined,
48
- };
49
- if (visibility === 'shared') {
50
- state.known_traps.push(entry);
51
- persistState(state, cwd);
52
- }
53
- else {
54
- saveOperationalTrap(entry, cwd);
55
- }
56
- const scopeInfo = visibility === 'shared' ? 'shared' : `${visibility}:${hostId}`;
36
+ hostId,
37
+ expiresAt: options.ttl ? parseTtl(options.ttl) : undefined,
38
+ }, cwd);
39
+ const scopeInfo = result.visibility === 'shared' ? 'shared' : `${result.visibility}:${result.hostId}`;
57
40
  const storeLabel = options.store && options.store !== 'local' ? ` [store:${options.store}]` : '';
58
- console.log(`✔ Trap added: [${id}] (${scopeInfo}) ${text}${storeLabel}`);
41
+ console.log(`✔ Trap added: [${result.id}] (${scopeInfo}) ${text}${storeLabel}`);
59
42
  }
60
43
  /** Parse a TTL string like "30m", "2h", "7d" and return an ISO expiry timestamp. */
61
44
  function parseTtl(ttl) {
@@ -1,5 +1,67 @@
1
1
  import { loadState, persistState } from '../core/state.js';
2
2
  import { memoryExists } from '../core/io.js';
3
+ import { nowISO } from '../core/ids.js';
4
+ export function applyHandoffUpdates(handoff, options = {}) {
5
+ if (options.status)
6
+ handoff.status = options.status;
7
+ if (options.to !== undefined)
8
+ handoff.to = options.to;
9
+ if (options.narrative !== undefined)
10
+ handoff.narrative = options.narrative;
11
+ const contractUpdates = {};
12
+ for (const key of ['files_touched', 'pre_conditions', 'post_conditions', 'tests_to_verify', 'linked_plans']) {
13
+ const value = options[key];
14
+ if (Array.isArray(value)) {
15
+ contractUpdates[key] = value;
16
+ }
17
+ }
18
+ if (Object.keys(contractUpdates).length > 0) {
19
+ handoff.contract = { ...handoff.contract, ...contractUpdates };
20
+ }
21
+ const hasReviewUpdate = options.reviewer !== undefined ||
22
+ options.requester !== undefined ||
23
+ options.requested_at !== undefined ||
24
+ options.review_thread_id !== undefined ||
25
+ options.review_message_id !== undefined ||
26
+ options.review_verdict !== undefined ||
27
+ options.reviewed_by !== undefined ||
28
+ options.review_summary !== undefined ||
29
+ options.blocking_issues !== undefined ||
30
+ options.suggestions !== undefined;
31
+ if (hasReviewUpdate) {
32
+ const review = { ...(handoff.review ?? {}) };
33
+ if (options.reviewer !== undefined)
34
+ review.reviewer = options.reviewer;
35
+ if (options.requester !== undefined)
36
+ review.requester = options.requester;
37
+ if (options.requested_at !== undefined)
38
+ review.requested_at = options.requested_at;
39
+ if (options.review_thread_id !== undefined)
40
+ review.thread_id = options.review_thread_id;
41
+ if (options.review_message_id !== undefined)
42
+ review.message_id = options.review_message_id;
43
+ if (options.review_verdict !== undefined)
44
+ review.verdict = options.review_verdict;
45
+ if (options.reviewed_by !== undefined)
46
+ review.reviewed_by = options.reviewed_by;
47
+ if (options.review_summary !== undefined)
48
+ review.summary = options.review_summary;
49
+ if (options.blocking_issues !== undefined)
50
+ review.blocking_issues = options.blocking_issues;
51
+ if (options.suggestions !== undefined)
52
+ review.suggestions = options.suggestions;
53
+ const reviewCompleted = options.review_verdict !== undefined ||
54
+ options.reviewed_by !== undefined ||
55
+ options.review_summary !== undefined ||
56
+ options.blocking_issues !== undefined ||
57
+ options.suggestions !== undefined;
58
+ if (reviewCompleted) {
59
+ review.reviewed_at = nowISO();
60
+ }
61
+ handoff.review = review;
62
+ }
63
+ return handoff;
64
+ }
3
65
  export function runUpdateHandoff(id, options = {}) {
4
66
  if (!memoryExists()) {
5
67
  console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
@@ -11,10 +73,7 @@ export function runUpdateHandoff(id, options = {}) {
11
73
  console.error(`Error: Handoff '${id}' not found.`);
12
74
  process.exit(1);
13
75
  }
14
- if (options.status)
15
- handoff.status = options.status;
16
- if (options.to !== undefined)
17
- handoff.to = options.to;
76
+ applyHandoffUpdates(handoff, options);
18
77
  persistState(state);
19
78
  console.log(`✔ Handoff updated: [${handoff.id}] ${handoff.from} → ${handoff.to} (${handoff.status})`);
20
79
  }
@@ -1,35 +1,28 @@
1
- import { loadState, persistState } from '../core/state.js';
2
- import { memoryExists } from '../core/io.js';
3
- import { nowISO } from '../core/ids.js';
1
+ import { requireInitialized } from '../core/guards.js';
2
+ import { updatePlan } from '../core/operations/plan.js';
4
3
  export function runUpdatePlan(id, options = {}) {
5
- if (!memoryExists(options.cwd)) {
6
- console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
4
+ const cwd = options.cwd ?? process.cwd();
5
+ requireInitialized(cwd);
6
+ if (id.startsWith('stp_')) {
7
+ console.error(`Error: '${id}' looks like a step ID, not a plan ID.`);
8
+ console.error(' Use: brainclaw complete-step <planId> <stepId>');
9
+ console.error(' Inspect the parent plan with: brainclaw plan show <planId>');
7
10
  process.exit(1);
8
11
  }
9
- const state = loadState(options.cwd);
10
- const plan = state.plan_items.find((item) => item.id === id);
11
- if (!plan) {
12
- console.error(`Error: Plan item '${id}' not found.`);
13
- process.exit(1);
12
+ try {
13
+ const result = updatePlan({
14
+ id,
15
+ status: options.status,
16
+ assignee: options.assignee,
17
+ priority: options.priority,
18
+ actualEffort: options.actualEffort,
19
+ }, cwd);
20
+ console.log(`✔ Plan item updated: [${result.id}] ${result.text}`);
14
21
  }
15
- const timestamp = nowISO();
16
- if (options.status) {
17
- plan.status = options.status;
18
- if (options.status === 'in_progress' && !plan.started_at)
19
- plan.started_at = timestamp;
20
- if (options.status === 'done' && !plan.completed_at)
21
- plan.completed_at = timestamp;
22
+ catch (error) {
23
+ const msg = error instanceof Error ? error.message : String(error);
24
+ console.error(`Error: ${msg}`);
25
+ process.exit(1);
22
26
  }
23
- if (options.assignee !== undefined)
24
- plan.assignee = options.assignee;
25
- if (options.project !== undefined)
26
- plan.project = options.project;
27
- if (options.priority)
28
- plan.priority = options.priority;
29
- if (options.actualEffort)
30
- plan.actual_effort = options.actualEffort;
31
- plan.updated_at = timestamp;
32
- persistState(state, options.cwd);
33
- console.log(`✔ Plan item updated: [${plan.id}] ${plan.text}`);
34
27
  }
35
28
  //# sourceMappingURL=update-plan.js.map
@@ -0,0 +1,37 @@
1
+ import { requireInitialized } from '../core/guards.js';
2
+ import { updateStep } from '../core/operations/plan.js';
3
+ export function runUpdateStep(planId, stepId, options) {
4
+ requireInitialized(process.cwd());
5
+ const validStatuses = ['todo', 'in_progress', 'testing', 'done', 'blocked'];
6
+ if (options.status && !validStatuses.includes(options.status)) {
7
+ console.error(`Error: Invalid status '${options.status}'. Valid: ${validStatuses.join(', ')}`);
8
+ process.exit(1);
9
+ }
10
+ try {
11
+ const result = updateStep({
12
+ planId,
13
+ stepId,
14
+ status: options.status,
15
+ text: options.text,
16
+ assignee: options.assign,
17
+ });
18
+ const changes = [];
19
+ if (options.status)
20
+ changes.push(`status=${options.status}`);
21
+ if (options.text)
22
+ changes.push('text updated');
23
+ if (options.assign !== undefined)
24
+ changes.push(`assignee=${options.assign || 'unassigned'}`);
25
+ console.log(`✔ Step updated: [${result.stepId}] ${changes.join(', ')}`);
26
+ console.log(` Plan [${result.planId}] progress: ${result.doneSteps}/${result.totalSteps} steps done`);
27
+ if (result.planAutoCompleted) {
28
+ console.log(` All steps done — plan auto-completed.`);
29
+ }
30
+ }
31
+ catch (error) {
32
+ const msg = error instanceof Error ? error.message : String(error);
33
+ console.error(`Error: ${msg}`);
34
+ process.exit(1);
35
+ }
36
+ }
37
+ //# sourceMappingURL=update-step.js.map