brainclaw 0.29.2 → 1.5.4

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 (197) hide show
  1. package/LICENSE +21 -74
  2. package/README.md +199 -176
  3. package/dist/brainclaw-vscode.vsix +0 -0
  4. package/dist/cli.js +710 -25
  5. package/dist/commands/accept.js +3 -0
  6. package/dist/commands/add-step.js +11 -26
  7. package/dist/commands/agent-board.js +70 -3
  8. package/dist/commands/audit.js +19 -0
  9. package/dist/commands/check-policy.js +54 -0
  10. package/dist/commands/check-security-mcp.js +145 -0
  11. package/dist/commands/check-security.js +106 -0
  12. package/dist/commands/claim-resource.js +1 -0
  13. package/dist/commands/codev.js +672 -0
  14. package/dist/commands/compact.js +74 -0
  15. package/dist/commands/complete-step.js +16 -26
  16. package/dist/commands/constraint.js +8 -20
  17. package/dist/commands/decision.js +9 -20
  18. package/dist/commands/delete-plan.js +10 -12
  19. package/dist/commands/delete-step.js +16 -0
  20. package/dist/commands/dispatch.js +163 -0
  21. package/dist/commands/doctor.js +1122 -49
  22. package/dist/commands/enable-agent.js +1 -0
  23. package/dist/commands/export.js +280 -22
  24. package/dist/commands/handoff.js +33 -0
  25. package/dist/commands/harvest.js +189 -0
  26. package/dist/commands/hooks.js +82 -25
  27. package/dist/commands/inbox.js +169 -0
  28. package/dist/commands/init.js +38 -31
  29. package/dist/commands/install-hooks.js +71 -44
  30. package/dist/commands/link.js +89 -0
  31. package/dist/commands/list-claims.js +48 -3
  32. package/dist/commands/list-plans.js +129 -25
  33. package/dist/commands/loops-handlers.js +409 -0
  34. package/dist/commands/mcp-read-handlers.js +1628 -0
  35. package/dist/commands/mcp-schemas.generated.js +269 -0
  36. package/dist/commands/mcp.js +4224 -1501
  37. package/dist/commands/plan-resource.js +64 -0
  38. package/dist/commands/plan.js +12 -26
  39. package/dist/commands/prune.js +37 -2
  40. package/dist/commands/reflect.js +20 -7
  41. package/dist/commands/release-claim.js +11 -6
  42. package/dist/commands/release-notes.js +170 -0
  43. package/dist/commands/repair.js +210 -0
  44. package/dist/commands/run-profile.js +57 -0
  45. package/dist/commands/sequence.js +113 -0
  46. package/dist/commands/session-end.js +423 -14
  47. package/dist/commands/session-start.js +214 -41
  48. package/dist/commands/setup-security.js +103 -0
  49. package/dist/commands/setup.js +42 -4
  50. package/dist/commands/stale.js +109 -0
  51. package/dist/commands/switch.js +100 -2
  52. package/dist/commands/trap.js +14 -31
  53. package/dist/commands/update-handoff.js +63 -4
  54. package/dist/commands/update-plan.js +21 -28
  55. package/dist/commands/update-step.js +37 -0
  56. package/dist/commands/upgrade.js +313 -6
  57. package/dist/commands/usage.js +102 -0
  58. package/dist/commands/version.js +20 -0
  59. package/dist/commands/who.js +33 -5
  60. package/dist/commands/worktree.js +105 -0
  61. package/dist/core/actions.js +315 -0
  62. package/dist/core/agent-capability.js +610 -17
  63. package/dist/core/agent-context.js +7 -1
  64. package/dist/core/agent-files.js +1169 -85
  65. package/dist/core/agent-integrations.js +160 -5
  66. package/dist/core/agent-inventory.js +2 -0
  67. package/dist/core/agent-profiles.js +93 -0
  68. package/dist/core/agent-registry.js +162 -30
  69. package/dist/core/agentrun-reconciler.js +345 -0
  70. package/dist/core/agentruns.js +424 -0
  71. package/dist/core/ai-agent-detection.js +31 -10
  72. package/dist/core/archival.js +77 -0
  73. package/dist/core/assignment-sweeper.js +82 -0
  74. package/dist/core/assignments.js +367 -0
  75. package/dist/core/audit.js +30 -0
  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 +381 -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/events.js +106 -2
  98. package/dist/core/execution-adapters.js +154 -0
  99. package/dist/core/execution-context.js +63 -0
  100. package/dist/core/execution-profile.js +270 -0
  101. package/dist/core/execution.js +255 -0
  102. package/dist/core/facade-schema.js +81 -0
  103. package/dist/core/federation-cloud.js +99 -0
  104. package/dist/core/federation-message.js +52 -0
  105. package/dist/core/federation-transport.js +65 -0
  106. package/dist/core/gc-semantic.js +482 -0
  107. package/dist/core/governance.js +247 -0
  108. package/dist/core/guards.js +19 -0
  109. package/dist/core/ideation.js +72 -0
  110. package/dist/core/identity.js +110 -25
  111. package/dist/core/ids.js +6 -0
  112. package/dist/core/input-validation.js +2 -2
  113. package/dist/core/instruction-templates.js +344 -136
  114. package/dist/core/io.js +90 -11
  115. package/dist/core/lock.js +6 -2
  116. package/dist/core/loops/brief-assembly.js +213 -0
  117. package/dist/core/loops/facade-schema.js +148 -0
  118. package/dist/core/loops/index.js +7 -0
  119. package/dist/core/loops/iteration-engine.js +139 -0
  120. package/dist/core/loops/lock.js +385 -0
  121. package/dist/core/loops/store.js +201 -0
  122. package/dist/core/loops/types.js +403 -0
  123. package/dist/core/loops/verbs.js +534 -0
  124. package/dist/core/markdown.js +15 -3
  125. package/dist/core/memory-compactor.js +432 -0
  126. package/dist/core/memory-git.js +152 -8
  127. package/dist/core/messaging.js +278 -0
  128. package/dist/core/migration.js +32 -1
  129. package/dist/core/mutation-pipeline.js +4 -2
  130. package/dist/core/operations/memory-mutation.js +129 -0
  131. package/dist/core/operations/memory-write.js +78 -0
  132. package/dist/core/operations/plan.js +190 -0
  133. package/dist/core/policy.js +169 -0
  134. package/dist/core/reputation.js +9 -3
  135. package/dist/core/schema.js +491 -6
  136. package/dist/core/search.js +21 -2
  137. package/dist/core/security-cache.js +71 -0
  138. package/dist/core/security-guard.js +152 -0
  139. package/dist/core/security-scoring.js +86 -0
  140. package/dist/core/sequence.js +130 -0
  141. package/dist/core/socket-client.js +113 -0
  142. package/dist/core/staleness.js +246 -0
  143. package/dist/core/state.js +98 -22
  144. package/dist/core/store-resolution.js +43 -11
  145. package/dist/core/toml-writer.js +76 -0
  146. package/dist/core/upgrades/backup.js +232 -0
  147. package/dist/core/upgrades/health-check.js +169 -0
  148. package/dist/core/upgrades/patches/candidate-archive.js +145 -0
  149. package/dist/core/upgrades/patches/handoff-review-strip.js +128 -0
  150. package/dist/core/upgrades/patches/provenance-rollout.js +136 -0
  151. package/dist/core/upgrades/schema-version.js +97 -0
  152. package/dist/core/worktree.js +606 -0
  153. package/dist/facts.js +114 -0
  154. package/dist/facts.json +111 -0
  155. package/docs/architecture/project-refs.md +5 -1
  156. package/docs/cli.md +690 -43
  157. package/docs/concepts/ideation-loop.md +317 -0
  158. package/docs/concepts/loop-engine.md +456 -0
  159. package/docs/concepts/mcp-governance.md +268 -0
  160. package/docs/concepts/memory-staleness.md +122 -0
  161. package/docs/concepts/multi-agent-workflows.md +166 -0
  162. package/docs/concepts/plans-and-claims.md +31 -6
  163. package/docs/concepts/project-md-convention.md +35 -0
  164. package/docs/concepts/troubleshooting.md +220 -0
  165. package/docs/concepts/upgrade-cli.md +202 -0
  166. package/docs/concepts/upgrade-dogfood-procedure.md +114 -0
  167. package/docs/context-format-changelog.md +2 -2
  168. package/docs/context-format.md +2 -2
  169. package/docs/index.md +68 -0
  170. package/docs/integrations/agents.md +15 -16
  171. package/docs/integrations/cline.md +88 -0
  172. package/docs/integrations/codex.md +75 -23
  173. package/docs/integrations/continue.md +60 -0
  174. package/docs/integrations/copilot.md +67 -9
  175. package/docs/integrations/kilocode.md +72 -0
  176. package/docs/integrations/mcp.md +304 -21
  177. package/docs/integrations/mistral-vibe.md +122 -0
  178. package/docs/integrations/opencode.md +84 -0
  179. package/docs/integrations/overview.md +23 -8
  180. package/docs/integrations/roo.md +74 -0
  181. package/docs/integrations/windsurf.md +83 -0
  182. package/docs/mcp-schema-changelog.md +191 -1
  183. package/docs/playbooks/integration/index.md +121 -0
  184. package/docs/playbooks/productivity/index.md +102 -0
  185. package/docs/playbooks/team/index.md +122 -0
  186. package/docs/product/agent-first-model.md +184 -0
  187. package/docs/product/entity-model-audit.md +462 -0
  188. package/docs/product/positioning.md +10 -10
  189. package/docs/quickstart-existing-project.md +135 -0
  190. package/docs/quickstart.md +124 -37
  191. package/docs/release-maintenance.md +79 -0
  192. package/docs/review.md +2 -0
  193. package/docs/server-operations.md +118 -0
  194. package/package.json +21 -13
  195. package/dist/commands/claude-desktop-extension.js +0 -18
  196. package/dist/commands/diff.js +0 -99
  197. package/dist/core/claude-desktop-extension.js +0 -224
@@ -2,6 +2,9 @@ import { runPlan } from './plan.js';
2
2
  import { runListPlans } from './list-plans.js';
3
3
  import { runUpdatePlan } from './update-plan.js';
4
4
  import { runDeletePlan } from './delete-plan.js';
5
+ import { loadState } from '../core/state.js';
6
+ import { memoryExists } from '../core/io.js';
7
+ const KNOWN_SUBCOMMANDS = new Set(['create', 'list', 'ls', 'update', 'delete', 'show', 'get']);
5
8
  export function runPlanResource(subcommand, args, options = {}) {
6
9
  const normalized = subcommand.trim().toLowerCase();
7
10
  if (normalized === 'create') {
@@ -21,9 +24,21 @@ export function runPlanResource(subcommand, args, options = {}) {
21
24
  assignee: options.assignee,
22
25
  project: options.project,
23
26
  all: options.all,
27
+ recursive: options.recursive,
28
+ localOnly: options.localOnly,
24
29
  });
25
30
  return;
26
31
  }
32
+ if (normalized === 'show' || normalized === 'get') {
33
+ const id = args[0];
34
+ if (!id) {
35
+ console.error(`Error: plan ${normalized} requires <id>.`);
36
+ console.error(` Usage: brainclaw plan ${normalized} <id>`);
37
+ process.exit(1);
38
+ }
39
+ runShowPlan(id, options);
40
+ return;
41
+ }
27
42
  if (normalized === 'update') {
28
43
  const id = args[0];
29
44
  if (!id) {
@@ -51,6 +66,12 @@ export function runPlanResource(subcommand, args, options = {}) {
51
66
  runDeletePlan(id, { cwd: options.cwd });
52
67
  return;
53
68
  }
69
+ // Reject known-looking subcommands to prevent accidental plan creation
70
+ if (normalized.startsWith('pln_') || KNOWN_SUBCOMMANDS.has(normalized)) {
71
+ console.error(`Error: unknown plan subcommand "${subcommand}".`);
72
+ console.error(' Available: create, list, show, get, update, delete');
73
+ process.exit(1);
74
+ }
54
75
  // Compatibility path: `brainclaw plan "text"` still creates a plan.
55
76
  const legacyText = [subcommand, ...args].join(' ').trim();
56
77
  if (!legacyText) {
@@ -59,4 +80,47 @@ export function runPlanResource(subcommand, args, options = {}) {
59
80
  }
60
81
  runPlan(legacyText, options);
61
82
  }
83
+ function runShowPlan(id, options) {
84
+ if (!memoryExists(options.cwd)) {
85
+ console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
86
+ process.exit(1);
87
+ }
88
+ const state = loadState(options.cwd);
89
+ const plan = state.plan_items.find(p => p.id === id);
90
+ if (!plan) {
91
+ console.error(`Error: plan not found: ${id}`);
92
+ process.exit(1);
93
+ }
94
+ if (options.json) {
95
+ console.log(JSON.stringify(plan, null, 2));
96
+ return;
97
+ }
98
+ console.log(`Plan: ${plan.id}`);
99
+ console.log(` Text: ${plan.text}`);
100
+ console.log(` Status: ${plan.status}`);
101
+ if (plan.type)
102
+ console.log(` Type: ${plan.type}`);
103
+ if (plan.priority)
104
+ console.log(` Priority: ${plan.priority}`);
105
+ if (plan.assignee)
106
+ console.log(` Assignee: ${plan.assignee}`);
107
+ if (plan.project)
108
+ console.log(` Project: ${plan.project}`);
109
+ if (plan.estimated_effort)
110
+ console.log(` Estimate: ${plan.estimated_effort}min`);
111
+ if (plan.actual_effort)
112
+ console.log(` Actual: ${plan.actual_effort}`);
113
+ if (plan.tags && plan.tags.length > 0)
114
+ console.log(` Tags: ${plan.tags.join(', ')}`);
115
+ console.log(` Created: ${plan.created_at}`);
116
+ if (plan.updated_at)
117
+ console.log(` Updated: ${plan.updated_at}`);
118
+ if (plan.steps && plan.steps.length > 0) {
119
+ console.log(' Steps:');
120
+ for (const step of plan.steps) {
121
+ const check = step.status === 'done' ? '✔' : '○';
122
+ console.log(` ${check} ${step.text}`);
123
+ }
124
+ }
125
+ }
62
126
  //# sourceMappingURL=plan-resource.js.map
@@ -1,21 +1,17 @@
1
- import { loadState, persistState } from '../core/state.js';
2
1
  import { resolveCurrentAgentName } from '../core/agent-registry.js';
3
2
  import { loadConfig } from '../core/config.js';
4
- import { generateIdWithLabel, nowISO } from '../core/ids.js';
5
3
  import { scanText } from '../core/security.js';
6
- import { memoryExists } from '../core/io.js';
4
+ import { requireInitialized } from '../core/guards.js';
7
5
  import { validateCliInput } from '../core/input-validation.js';
8
6
  import { runListPlans } from './list-plans.js';
9
7
  import { resolveTargetStore } from '../core/store-resolution.js';
8
+ import { createPlan } from '../core/operations/plan.js';
10
9
  // Known plan subcommands that should not be accepted as plan text
11
10
  const PLAN_SUBCOMMAND_ALIASES = new Set(['list', 'ls']);
12
11
  const PLAN_SUBCOMMAND_ERRORS = new Set(['update']);
13
12
  export function runPlan(text, options = {}) {
14
13
  const cwd = resolveTargetStore(options.cwd ?? process.cwd(), options.store ?? 'local');
15
- if (!memoryExists(cwd)) {
16
- console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
17
- process.exit(1);
18
- }
14
+ requireInitialized(cwd);
19
15
  const normalized = text.trim().toLowerCase();
20
16
  if (PLAN_SUBCOMMAND_ALIASES.has(normalized)) {
21
17
  runListPlans({});
@@ -46,29 +42,19 @@ export function runPlan(text, options = {}) {
46
42
  }
47
43
  estimatedEffort = n;
48
44
  }
49
- const state = loadState(cwd);
50
- const { id, short_label } = generateIdWithLabel('plan_items');
51
- const timestamp = nowISO();
52
- const entry = {
53
- id,
54
- short_label,
45
+ const result = createPlan({
55
46
  text,
56
- type: options.type,
57
- created_at: timestamp,
58
- updated_at: timestamp,
59
47
  author: options.author ?? resolveCurrentAgentName(cwd),
60
- status: 'todo',
61
- priority: options.priority ?? 'medium',
48
+ type: options.type,
49
+ priority: options.priority,
62
50
  assignee: options.assignee,
63
51
  project: options.project,
64
- tags: options.tag ?? [],
65
- related_paths: options.path,
66
- depends_on: options.dependsOn ?? [],
67
- estimated_effort: estimatedEffort,
68
- };
69
- state.plan_items.push(entry);
70
- persistState(state, cwd);
52
+ tags: options.tag,
53
+ relatedPaths: options.path,
54
+ dependsOn: options.dependsOn,
55
+ estimatedEffort,
56
+ }, cwd);
71
57
  const storeLabel = options.store && options.store !== 'local' ? ` [store:${options.store}]` : '';
72
- console.log(`✔ Plan item added: [${id}] ${text}${storeLabel}`);
58
+ console.log(`✔ Plan item added: [${result.id}] ${text}${storeLabel}`);
73
59
  }
74
60
  //# sourceMappingURL=plan.js.map
@@ -4,12 +4,36 @@ import { mutate } from '../core/mutation-pipeline.js';
4
4
  import { rebuildProjectMd } from '../core/markdown.js';
5
5
  import { deleteRuntimeNote, listRuntimeNotes } from '../core/runtime.js';
6
6
  import { expireStaleActiveClaims } from '../core/claims.js';
7
+ import { archiveStalePlansAndHandoffs } from '../core/archival.js';
8
+ import { rotateAuditLogIfNeeded } from '../core/audit.js';
9
+ import { analyzeMemory, analyzeAndApply, formatReport } from '../core/memory-compactor.js';
7
10
  export function runPrune(options = {}) {
8
11
  const cwd = process.cwd();
9
12
  if (!memoryExists(cwd)) {
10
13
  console.error('Project memory not initialized. Run `brainclaw init` first.');
11
14
  process.exit(1);
12
15
  }
16
+ // Semantic compaction mode
17
+ if (options.semantic) {
18
+ if (options.dryRun) {
19
+ // Dry-run: read-only analysis, no lock needed
20
+ const state = loadState(cwd);
21
+ const report = analyzeMemory(state, { cwd });
22
+ console.log(formatReport(report));
23
+ return;
24
+ }
25
+ // Apply: analyze + apply atomically under a single mutation lock
26
+ const { report, result } = analyzeAndApply({ cwd });
27
+ if (report.archivableCount === 0) {
28
+ console.log('No compaction opportunities found.');
29
+ return;
30
+ }
31
+ console.log(formatReport(report));
32
+ console.log('');
33
+ console.log(`✔ Compaction applied: ${result.archivedCount} items archived (${result.mergedClusters} clusters merged, ${result.staleArchived} stale items).`);
34
+ return;
35
+ }
36
+ // Original prune logic
13
37
  const now = new Date().toISOString();
14
38
  let prunedCount = 0;
15
39
  let expiredClaimsCount = 0;
@@ -41,11 +65,22 @@ export function runPrune(options = {}) {
41
65
  }
42
66
  rebuildProjectMd(loadState(cwd), cwd);
43
67
  });
68
+ // Archive and rotate outside the mutation lock (they manage their own IO)
69
+ let archiveMsg = '';
70
+ if (options.archive) {
71
+ const archiveResults = archiveStalePlansAndHandoffs(cwd);
72
+ if (archiveResults.length > 0) {
73
+ const parts = archiveResults.map(r => `${r.archived} ${r.entity}`);
74
+ archiveMsg = `, archived ${parts.join(' + ')} to cold storage`;
75
+ }
76
+ }
77
+ const rotated = rotateAuditLogIfNeeded(cwd);
78
+ const rotateMsg = rotated ? ', rotated audit.log' : '';
44
79
  if (options.expired) {
45
- console.log(`✔ Pruned ${prunedCount} expired constraints, ${expiredNotesCount} expired runtime notes, ${expiredClaimsCount} expired claims.`);
80
+ console.log(`✔ Pruned ${prunedCount} expired constraints, ${expiredNotesCount} expired runtime notes, ${expiredClaimsCount} expired claims${archiveMsg}${rotateMsg}.`);
46
81
  }
47
82
  else {
48
- console.log(`✔ Pruned ${prunedCount} expired constraints, ${expiredClaimsCount} expired claims.`);
83
+ console.log(`✔ Pruned ${prunedCount} expired constraints, ${expiredClaimsCount} expired claims${archiveMsg}${rotateMsg}.`);
49
84
  }
50
85
  }
51
86
  //# sourceMappingURL=prune.js.map
@@ -8,8 +8,8 @@ import { scanText } from '../core/security.js';
8
8
  import { nowISO, generateIdWithLabel } from '../core/ids.js';
9
9
  import { saveCandidate, generateCandidateIdWithLabel, listCandidates } from '../core/candidates.js';
10
10
  import { detectDuplicates } from '../core/duplicates.js';
11
- import { RuntimeEventSchema } from '../core/schema.js';
12
- import { listRuntimeEventsBySession } from '../core/events.js';
11
+ import { CandidateSourceSchema, RuntimeEventSchema } from '../core/schema.js';
12
+ import { isReflectableRuntimeEvent, listRuntimeEventsBySession } from '../core/events.js';
13
13
  import { agentCanWriteDirect, requireMinimumTrustLevel, requireRegisteredAgentIdentity } from '../core/agent-registry.js';
14
14
  import { appendAuditEntry } from '../core/audit.js';
15
15
  import { generateTrapIdWithLabel } from '../core/traps.js';
@@ -56,6 +56,9 @@ function runReflectBatchFromFile(filepath, baseOptions) {
56
56
  for (const rawEvent of rawEvents) {
57
57
  try {
58
58
  const event = RuntimeEventSchema.parse(rawEvent);
59
+ if (!isReflectableRuntimeEvent(event)) {
60
+ continue;
61
+ }
59
62
  const candidateType = event.candidate_type ?? mapEventTypeToCandidateType(event.event_type);
60
63
  createCandidateFromInput(event.text, candidateType, {
61
64
  ...baseOptions,
@@ -87,6 +90,9 @@ function runReflectBatchFromSession(session, baseOptions) {
87
90
  }
88
91
  let created = 0;
89
92
  for (const event of events) {
93
+ if (!isReflectableRuntimeEvent(event)) {
94
+ continue;
95
+ }
90
96
  const candidateType = event.candidate_type ?? mapEventTypeToCandidateType(event.event_type);
91
97
  createCandidateFromInput(event.text, candidateType, {
92
98
  ...baseOptions,
@@ -158,7 +164,14 @@ export function createCandidateFromInput(text, type, options, printSuccess = tru
158
164
  project_id: options.projectId ?? actorIdentity.project_id,
159
165
  host_id: options.hostId ?? actorIdentity.host_id,
160
166
  session_id: options.sessionId ?? actorIdentity.session_id,
161
- source: options.source,
167
+ // Normalize source to the enum when possible; otherwise infer from the
168
+ // free-text origin value below. Preserves legacy provenance patterns
169
+ // (runtime-note:*, mcp:*, session-end:*, cross-project:*) in `origin`
170
+ // while keeping `source` strictly enum-constrained.
171
+ source: CandidateSourceSchema.safeParse(options.source).data,
172
+ origin: typeof options.source === 'string' && !CandidateSourceSchema.safeParse(options.source).success
173
+ ? options.source
174
+ : undefined,
162
175
  tags: options.tag ?? [],
163
176
  status: 'pending',
164
177
  severity: type === 'trap' ? options.severity ?? 'medium' : undefined,
@@ -238,28 +251,28 @@ function promoteCandidateToState(candidate, cwd) {
238
251
  switch (candidate.type) {
239
252
  case 'constraint': {
240
253
  const { id: cId, short_label } = generateIdWithLabel('active_constraints', cwd);
241
- const entry = { id: cId, short_label, text: candidate.text, created_at: candidate.created_at, author: candidate.author, author_id: candidate.author_id, project_id: candidate.project_id, host_id: candidate.host_id, session_id: candidate.session_id, status: 'active', tags: candidate.tags };
254
+ const entry = { id: cId, short_label, text: candidate.text, created_at: candidate.created_at, author: candidate.author, author_id: candidate.author_id, project_id: candidate.project_id, host_id: candidate.host_id, session_id: candidate.session_id, status: 'active', tags: candidate.tags, plan_id: candidate.plan_id };
242
255
  state.active_constraints.push(entry);
243
256
  promotedItemId = entry.id;
244
257
  break;
245
258
  }
246
259
  case 'decision': {
247
260
  const { id: dId, short_label } = generateIdWithLabel('recent_decisions', cwd);
248
- const entry = { id: dId, short_label, text: candidate.text, created_at: candidate.created_at, author: candidate.author, author_id: candidate.author_id, project_id: candidate.project_id, host_id: candidate.host_id, session_id: candidate.session_id, related_paths: candidate.related_paths, tags: candidate.tags };
261
+ const entry = { id: dId, short_label, text: candidate.text, created_at: candidate.created_at, author: candidate.author, author_id: candidate.author_id, project_id: candidate.project_id, host_id: candidate.host_id, session_id: candidate.session_id, related_paths: candidate.related_paths, plan_id: candidate.plan_id, tags: candidate.tags };
249
262
  state.recent_decisions.push(entry);
250
263
  promotedItemId = entry.id;
251
264
  break;
252
265
  }
253
266
  case 'trap': {
254
267
  const { id: tId, short_label } = generateTrapIdWithLabel(cwd);
255
- const entry = { id: tId, short_label, text: candidate.text, created_at: candidate.created_at, author: candidate.author, author_id: candidate.author_id, project_id: candidate.project_id, host_id: candidate.host_id, session_id: candidate.session_id, status: 'active', severity: candidate.severity ?? 'medium', tags: candidate.tags, visibility: 'shared' };
268
+ const entry = { id: tId, short_label, text: candidate.text, created_at: candidate.created_at, author: candidate.author, author_id: candidate.author_id, project_id: candidate.project_id, host_id: candidate.host_id, session_id: candidate.session_id, status: 'active', severity: candidate.severity ?? 'medium', tags: candidate.tags, plan_id: candidate.plan_id, visibility: 'shared' };
256
269
  state.known_traps.push(entry);
257
270
  promotedItemId = entry.id;
258
271
  break;
259
272
  }
260
273
  case 'handoff': {
261
274
  const { id: hId, short_label } = generateIdWithLabel('open_handoffs', cwd);
262
- const entry = { id: hId, short_label, text: candidate.text, created_at: candidate.created_at, author: candidate.author, author_id: candidate.author_id, project_id: candidate.project_id, host_id: candidate.host_id, session_id: candidate.session_id, from: candidate.from ?? '', to: candidate.to ?? '', status: 'open', tags: candidate.tags, related_paths: candidate.related_paths };
275
+ const entry = { id: hId, short_label, text: candidate.text, created_at: candidate.created_at, author: candidate.author, author_id: candidate.author_id, project_id: candidate.project_id, host_id: candidate.host_id, session_id: candidate.session_id, from: candidate.from ?? '', to: candidate.to ?? '', status: 'open', tags: candidate.tags, plan_id: candidate.plan_id, narrative: candidate.narrative, related_paths: candidate.related_paths };
263
276
  state.open_handoffs.push(entry);
264
277
  promotedItemId = entry.id;
265
278
  break;
@@ -2,7 +2,7 @@ import { memoryExists } from '../core/io.js';
2
2
  import { mutate } from '../core/mutation-pipeline.js';
3
3
  import { loadClaim, listClaims, releaseClaim } from '../core/claims.js';
4
4
  import { rebuildProjectMd } from '../core/markdown.js';
5
- import { loadState, saveState } from '../core/state.js';
5
+ import { loadState, mutateState } from '../core/state.js';
6
6
  export function runReleaseClaim(id, options = {}) {
7
7
  if (!memoryExists(options.cwd)) {
8
8
  console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
@@ -13,10 +13,12 @@ export function runReleaseClaim(id, options = {}) {
13
13
  mutate({ cwd: options.cwd }, () => {
14
14
  const existing = loadClaim(id, options.cwd);
15
15
  claim = releaseClaim(id, options.cwd);
16
- let state = loadState(options.cwd);
17
16
  if (existing.plan_id) {
18
- const plan = state.plan_items.find((item) => item.id === existing.plan_id);
19
- if (plan) {
17
+ const updated = mutateState((state) => {
18
+ const plan = state.plan_items.find((item) => item.id === existing.plan_id);
19
+ if (!plan) {
20
+ return false;
21
+ }
20
22
  const otherActiveClaims = listClaims(options.cwd).filter((item) => item.status === 'active' && item.plan_id === existing.plan_id);
21
23
  if (options.planStatus) {
22
24
  plan.status = options.planStatus;
@@ -28,10 +30,13 @@ export function runReleaseClaim(id, options = {}) {
28
30
  plan.assignee = undefined;
29
31
  }
30
32
  plan.updated_at = new Date().toISOString();
31
- saveState(state, options.cwd);
33
+ return true;
34
+ }, options.cwd, { writeProjectMarkdown: false });
35
+ if (updated) {
36
+ const state = loadState(options.cwd);
37
+ rebuildProjectMd(state, options.cwd);
32
38
  }
33
39
  }
34
- rebuildProjectMd(state, options.cwd);
35
40
  });
36
41
  console.log(`✔ Claim [${id}] released (was: ${claim.agent} → ${claim.scope})`);
37
42
  }
@@ -0,0 +1,170 @@
1
+ import { execSync } from 'node:child_process';
2
+ import { loadConfig } from '../core/config.js';
3
+ import { memoryExists } from '../core/io.js';
4
+ import { checkBrainclawInstallableUpdate, getInstalledBrainclawVersion, } from '../core/brainclaw-version.js';
5
+ /**
6
+ * Generate agent-first release notes from git log since a given ref.
7
+ * Returns structured notes suitable for --agent-release-notes.
8
+ */
9
+ export function generateAgentReleaseNotes(cwd, since) {
10
+ const version = getInstalledBrainclawVersion();
11
+ const baseRef = since ?? findLastVersionTag(cwd) ?? 'HEAD~20';
12
+ let commits;
13
+ try {
14
+ const raw = execSync(`git log ${baseRef}..HEAD --oneline --no-decorate`, {
15
+ cwd,
16
+ encoding: 'utf-8',
17
+ timeout: 10000,
18
+ });
19
+ commits = raw.split('\n').map((l) => l.trim()).filter(Boolean);
20
+ }
21
+ catch {
22
+ commits = [];
23
+ }
24
+ if (commits.length === 0) {
25
+ return {
26
+ summary: `Brainclaw ${version} — no changes since last release.`,
27
+ breaking_risk: 'none',
28
+ action_recommendation: 'No update needed.',
29
+ };
30
+ }
31
+ const highlights = categorizeCommits(commits);
32
+ const hasBreaking = commits.some((c) => /breaking|BREAKING/i.test(c));
33
+ const hasFix = commits.some((c) => /^[a-f0-9]+ fix/i.test(c));
34
+ const hasFeat = commits.some((c) => /^[a-f0-9]+ feat/i.test(c));
35
+ const summaryParts = [];
36
+ if (hasFeat)
37
+ summaryParts.push('new features');
38
+ if (hasFix)
39
+ summaryParts.push('bug fixes');
40
+ if (!hasFeat && !hasFix)
41
+ summaryParts.push('improvements');
42
+ const summary = `Brainclaw ${version} — ${summaryParts.join(' and ')} (${commits.length} commits).`;
43
+ const breakingRisk = hasBreaking ? 'high' : hasFeat ? 'low' : 'none';
44
+ return {
45
+ summary,
46
+ agent_relevance: hasFeat
47
+ ? 'New capabilities available — review highlights for agent workflow improvements.'
48
+ : hasFix
49
+ ? 'Bug fixes that may affect agent reliability.'
50
+ : 'Maintenance release.',
51
+ breaking_risk: breakingRisk,
52
+ recommended_for: hasBreaking ? ['operators'] : ['all'],
53
+ highlights: highlights.slice(0, 5),
54
+ action_recommendation: hasBreaking
55
+ ? 'Review breaking changes before updating. Operator confirmation recommended.'
56
+ : 'Safe to update. No operator confirmation needed.',
57
+ };
58
+ }
59
+ function findLastVersionTag(cwd) {
60
+ try {
61
+ const tag = execSync('git describe --tags --abbrev=0 HEAD', {
62
+ cwd,
63
+ encoding: 'utf-8',
64
+ timeout: 5000,
65
+ stdio: ['pipe', 'pipe', 'pipe'],
66
+ }).trim();
67
+ return tag.length > 0 ? tag : undefined;
68
+ }
69
+ catch {
70
+ return undefined;
71
+ }
72
+ }
73
+ function categorizeCommits(commits) {
74
+ const highlights = [];
75
+ const seen = new Set();
76
+ for (const commit of commits) {
77
+ // Strip hash prefix
78
+ const msg = commit.replace(/^[a-f0-9]+\s+/, '');
79
+ const category = extractCategory(msg);
80
+ if (category && !seen.has(category)) {
81
+ seen.add(category);
82
+ highlights.push(msg);
83
+ }
84
+ }
85
+ // If not enough categorized, add uncategorized ones
86
+ if (highlights.length < 5) {
87
+ for (const commit of commits) {
88
+ const msg = commit.replace(/^[a-f0-9]+\s+/, '');
89
+ if (!highlights.includes(msg)) {
90
+ highlights.push(msg);
91
+ if (highlights.length >= 5)
92
+ break;
93
+ }
94
+ }
95
+ }
96
+ return highlights;
97
+ }
98
+ function extractCategory(msg) {
99
+ const match = /^(feat|fix|chore|test|docs|refactor|perf|ci|build)\b/i.exec(msg);
100
+ return match?.[1]?.toLowerCase();
101
+ }
102
+ export function runReleaseNotes(options = {}) {
103
+ const cwd = options.cwd ?? process.cwd();
104
+ if (options.generate) {
105
+ const notes = generateAgentReleaseNotes(cwd, options.since);
106
+ if (options.json) {
107
+ console.log(JSON.stringify(notes, null, 2));
108
+ }
109
+ else {
110
+ console.log(`Summary: ${notes.summary}`);
111
+ if (notes.agent_relevance)
112
+ console.log(`Agent relevance: ${notes.agent_relevance}`);
113
+ console.log(`Breaking risk: ${notes.breaking_risk ?? 'none'}`);
114
+ if (notes.recommended_for?.length)
115
+ console.log(`Recommended for: ${notes.recommended_for.join(', ')}`);
116
+ if (notes.highlights?.length) {
117
+ console.log('Highlights:');
118
+ for (const h of notes.highlights)
119
+ console.log(` • ${h}`);
120
+ }
121
+ if (notes.action_recommendation)
122
+ console.log(`Action: ${notes.action_recommendation}`);
123
+ }
124
+ return;
125
+ }
126
+ // Show current release notes from configured update source
127
+ if (!memoryExists(cwd)) {
128
+ console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
129
+ process.exit(1);
130
+ }
131
+ const config = loadConfig(cwd);
132
+ const updateCheck = checkBrainclawInstallableUpdate(config, cwd, { useDefaultNpmSource: true });
133
+ const arn = updateCheck.agent_release_notes;
134
+ if (options.json) {
135
+ console.log(JSON.stringify({
136
+ status: updateCheck.status,
137
+ latest_installable_version: updateCheck.latest_installable_version,
138
+ agent_release_notes: arn ?? null,
139
+ release_notes: updateCheck.release_notes ?? null,
140
+ }, null, 2));
141
+ return;
142
+ }
143
+ if (arn) {
144
+ console.log(`Version: ${updateCheck.latest_installable_version ?? 'unknown'}`);
145
+ console.log(`Summary: ${arn.summary}`);
146
+ if (arn.agent_relevance)
147
+ console.log(`Agent relevance: ${arn.agent_relevance}`);
148
+ console.log(`Breaking risk: ${arn.breaking_risk ?? 'none'}`);
149
+ if (arn.recommended_for?.length)
150
+ console.log(`Recommended for: ${arn.recommended_for.join(', ')}`);
151
+ if (arn.highlights?.length) {
152
+ console.log('Highlights:');
153
+ for (const h of arn.highlights)
154
+ console.log(` • ${h}`);
155
+ }
156
+ if (arn.action_recommendation)
157
+ console.log(`Action: ${arn.action_recommendation}`);
158
+ }
159
+ else if (updateCheck.release_notes) {
160
+ console.log(`Version: ${updateCheck.latest_installable_version ?? 'unknown'}`);
161
+ console.log(updateCheck.release_notes);
162
+ }
163
+ else {
164
+ console.log('No agent release notes available for the configured update source.');
165
+ if (updateCheck.status === 'not_configured') {
166
+ console.log('Configure brainclaw_update_source in your project config to enable update checks.');
167
+ }
168
+ }
169
+ }
170
+ //# sourceMappingURL=release-notes.js.map