brainclaw 0.29.2 → 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 (195) hide show
  1. package/README.md +193 -170
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli.js +673 -24
  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 +4221 -1501
  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 +100 -2
  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 +33 -5
  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/brainclaw-version.js +94 -2
  76. package/dist/core/candidates.js +93 -2
  77. package/dist/core/claims.js +419 -0
  78. package/dist/core/codev-metrics.js +77 -0
  79. package/dist/core/codev-personas.js +31 -0
  80. package/dist/core/codev-plan-gen.js +35 -0
  81. package/dist/core/codev-prompts.js +74 -0
  82. package/dist/core/codev-responses.js +62 -0
  83. package/dist/core/codev-rounds.js +218 -0
  84. package/dist/core/config.js +4 -0
  85. package/dist/core/context.js +381 -34
  86. package/dist/core/coordination.js +201 -6
  87. package/dist/core/cross-project.js +230 -16
  88. package/dist/core/default-profiles/doctor.yaml +11 -0
  89. package/dist/core/default-profiles/janitor.yaml +11 -0
  90. package/dist/core/default-profiles/onboarder.yaml +11 -0
  91. package/dist/core/default-profiles/reviewer.yaml +13 -0
  92. package/dist/core/dispatcher.js +1189 -0
  93. package/dist/core/duplicates.js +2 -2
  94. package/dist/core/entity-operations.js +450 -0
  95. package/dist/core/entity-registry.js +344 -0
  96. package/dist/core/events.js +106 -2
  97. package/dist/core/execution-adapters.js +154 -0
  98. package/dist/core/execution-context.js +63 -0
  99. package/dist/core/execution-profile.js +270 -0
  100. package/dist/core/execution.js +255 -0
  101. package/dist/core/facade-schema.js +81 -0
  102. package/dist/core/federation-cloud.js +99 -0
  103. package/dist/core/federation-message.js +52 -0
  104. package/dist/core/federation-transport.js +65 -0
  105. package/dist/core/gc-semantic.js +482 -0
  106. package/dist/core/governance.js +247 -0
  107. package/dist/core/guards.js +19 -0
  108. package/dist/core/ideation.js +72 -0
  109. package/dist/core/identity.js +110 -25
  110. package/dist/core/ids.js +6 -0
  111. package/dist/core/input-validation.js +2 -2
  112. package/dist/core/instruction-templates.js +344 -136
  113. package/dist/core/io.js +90 -11
  114. package/dist/core/lock.js +6 -2
  115. package/dist/core/loops/brief-assembly.js +213 -0
  116. package/dist/core/loops/facade-schema.js +148 -0
  117. package/dist/core/loops/index.js +7 -0
  118. package/dist/core/loops/iteration-engine.js +139 -0
  119. package/dist/core/loops/lock.js +385 -0
  120. package/dist/core/loops/store.js +201 -0
  121. package/dist/core/loops/types.js +403 -0
  122. package/dist/core/loops/verbs.js +534 -0
  123. package/dist/core/markdown.js +15 -3
  124. package/dist/core/memory-compactor.js +432 -0
  125. package/dist/core/memory-git.js +152 -8
  126. package/dist/core/messaging.js +278 -0
  127. package/dist/core/migration.js +32 -1
  128. package/dist/core/mutation-pipeline.js +4 -2
  129. package/dist/core/operations/memory-mutation.js +129 -0
  130. package/dist/core/operations/memory-write.js +78 -0
  131. package/dist/core/operations/plan.js +190 -0
  132. package/dist/core/policy.js +169 -0
  133. package/dist/core/reputation.js +9 -3
  134. package/dist/core/schema.js +491 -6
  135. package/dist/core/search.js +21 -2
  136. package/dist/core/security-cache.js +71 -0
  137. package/dist/core/security-guard.js +152 -0
  138. package/dist/core/security-scoring.js +86 -0
  139. package/dist/core/sequence.js +130 -0
  140. package/dist/core/socket-client.js +113 -0
  141. package/dist/core/staleness.js +246 -0
  142. package/dist/core/state.js +98 -22
  143. package/dist/core/store-resolution.js +43 -11
  144. package/dist/core/toml-writer.js +76 -0
  145. package/dist/core/upgrades/backup.js +232 -0
  146. package/dist/core/upgrades/health-check.js +169 -0
  147. package/dist/core/upgrades/patches/candidate-archive.js +145 -0
  148. package/dist/core/upgrades/patches/handoff-review-strip.js +128 -0
  149. package/dist/core/upgrades/patches/provenance-rollout.js +136 -0
  150. package/dist/core/upgrades/schema-version.js +97 -0
  151. package/dist/core/worktree.js +606 -0
  152. package/dist/facts.js +114 -0
  153. package/dist/facts.json +111 -0
  154. package/docs/architecture/project-refs.md +5 -1
  155. package/docs/cli.md +690 -43
  156. package/docs/concepts/ideation-loop.md +317 -0
  157. package/docs/concepts/loop-engine.md +456 -0
  158. package/docs/concepts/mcp-governance.md +268 -0
  159. package/docs/concepts/memory-staleness.md +122 -0
  160. package/docs/concepts/multi-agent-workflows.md +166 -0
  161. package/docs/concepts/plans-and-claims.md +31 -6
  162. package/docs/concepts/project-md-convention.md +35 -0
  163. package/docs/concepts/troubleshooting.md +220 -0
  164. package/docs/concepts/upgrade-cli.md +202 -0
  165. package/docs/concepts/upgrade-dogfood-procedure.md +114 -0
  166. package/docs/context-format-changelog.md +2 -2
  167. package/docs/context-format.md +2 -2
  168. package/docs/index.md +68 -0
  169. package/docs/integrations/agents.md +15 -16
  170. package/docs/integrations/cline.md +88 -0
  171. package/docs/integrations/codex.md +75 -23
  172. package/docs/integrations/continue.md +60 -0
  173. package/docs/integrations/copilot.md +67 -9
  174. package/docs/integrations/kilocode.md +72 -0
  175. package/docs/integrations/mcp.md +304 -21
  176. package/docs/integrations/mistral-vibe.md +122 -0
  177. package/docs/integrations/opencode.md +84 -0
  178. package/docs/integrations/overview.md +23 -8
  179. package/docs/integrations/roo.md +74 -0
  180. package/docs/integrations/windsurf.md +83 -0
  181. package/docs/mcp-schema-changelog.md +191 -1
  182. package/docs/playbooks/integration/index.md +121 -0
  183. package/docs/playbooks/productivity/index.md +102 -0
  184. package/docs/playbooks/team/index.md +122 -0
  185. package/docs/product/agent-first-model.md +184 -0
  186. package/docs/product/entity-model-audit.md +462 -0
  187. package/docs/quickstart-existing-project.md +135 -0
  188. package/docs/quickstart.md +124 -37
  189. package/docs/release-maintenance.md +79 -0
  190. package/docs/review.md +2 -0
  191. package/docs/server-operations.md +118 -0
  192. package/package.json +20 -12
  193. package/dist/commands/claude-desktop-extension.js +0 -18
  194. package/dist/commands/diff.js +0 -99
  195. package/dist/core/claude-desktop-extension.js +0 -224
@@ -1,4 +1,7 @@
1
+ import { execSync } from 'node:child_process';
2
+ import fs from 'node:fs';
1
3
  import path from 'node:path';
4
+ import { readProjectVision } from './io.js';
2
5
  import { loadActiveProject } from './active-project.js';
3
6
  import { checkBrainclawInstallableUpdate, renderBrainclawInstallableUpdateNotice } from './brainclaw-version.js';
4
7
  import { loadConfig } from './config.js';
@@ -15,11 +18,14 @@ import { resolveCurrentHostId } from './host.js';
15
18
  import { inferProjectFromTarget, loadInstructions, resolveInstructions } from './instructions.js';
16
19
  import { buildCurrentAgentResumeSummary, buildReputationRankingLookup } from './reputation.js';
17
20
  import { loadState } from './state.js';
21
+ import { readAuditLog } from './audit.js';
18
22
  import { listCandidates } from './candidates.js';
19
- import { listClaims, isClaimExpired } from './claims.js';
23
+ import { listClaims, isClaimExpired, assessClaimLiveness } from './claims.js';
24
+ import { listAssignments } from './assignments.js';
20
25
  import { listRuntimeNotes } from './runtime.js';
21
26
  import { isTrapActive, listOperationalTraps } from './traps.js';
22
27
  import { buildEstimationReport } from '../commands/estimation-report.js';
28
+ import { detectStaleness } from './staleness.js';
23
29
  export const CONTEXT_SCHEMA_VERSION = '1.2';
24
30
  export function buildContext(options = {}) {
25
31
  const requestedCwd = options.cwd ?? process.cwd();
@@ -28,18 +34,21 @@ export function buildContext(options = {}) {
28
34
  const config = loadConfig(contextCwd);
29
35
  // Resolve parent stores for multi-store merge (walk-up from cwd)
30
36
  const storeChain = resolveStoreChain(contextCwd);
31
- const profile = options.profile ?? config.profile ?? 'dev';
32
37
  const projectMode = config.project_mode ?? 'auto';
33
38
  const projectStrategy = config.projects?.strategy ?? 'manual';
34
39
  const currentHost = resolveCurrentHostId();
35
40
  const memoryVersion = getVisibleMemoryVersion({ cwd: contextCwd, hostId: options.host, allHosts: options.allHosts });
36
41
  const target = normalizeContextTarget(options.target, requestedCwd, contextCwd);
37
42
  const project = options.project?.trim() || inferProjectFromTarget(target, config);
38
- const agent = options.agent?.trim() || config.current_agent?.trim();
39
- const currentAgentIdentity = agent
40
- ? (options.agent?.trim() ? findAgentIdentityByName(agent, contextCwd) : resolveCurrentAgentIdentity(contextCwd))
41
- : undefined;
42
- const profileMaxItems = { compact: 6, copilot: 5, quick: 3 };
43
+ // Agent resolution: explicit param > resolveCurrentAgentIdentity (env + detection).
44
+ // config.current_agent is NOT used — it's a singleton global that cross-contaminates in multi-agent.
45
+ const currentAgentIdentity = options.agent?.trim()
46
+ ? findAgentIdentityByName(options.agent.trim(), contextCwd)
47
+ : resolveCurrentAgentIdentity(contextCwd);
48
+ const agent = options.agent?.trim() || currentAgentIdentity?.agent_name;
49
+ // Profile resolution: explicit param > agent default > config default > 'dev'
50
+ const profile = options.profile ?? currentAgentIdentity?.context_profile ?? config.profile ?? 'dev';
51
+ const profileMaxItems = { dense: 20, compact: 6, copilot: 5, quick: 3, briefing: 5, 'claude-desktop': 8 };
43
52
  const maxItems = options.maxItems ?? profileMaxItems[profile] ?? 8;
44
53
  const maxChars = options.maxChars && options.maxChars > 0 ? options.maxChars : undefined;
45
54
  // Instructions will be resolved after parent-store merge below (line ~460)
@@ -48,6 +57,8 @@ export function buildContext(options = {}) {
48
57
  compact: ['plan', 'constraint'],
49
58
  copilot: ['constraint', 'trap'],
50
59
  quick: ['constraint', 'plan'],
60
+ briefing: ['trap', 'constraint', 'decision'],
61
+ 'claude-desktop': ['plan', 'handoff', 'candidate'],
51
62
  };
52
63
  const allowedSections = profileSections[profile];
53
64
  const items = [];
@@ -83,6 +94,7 @@ export function buildContext(options = {}) {
83
94
  score: 0,
84
95
  reasons: [],
85
96
  extra: c.status,
97
+ plan_id: c.plan_id,
86
98
  provenance: {
87
99
  actor: c.author,
88
100
  actor_id: c.author_id,
@@ -102,6 +114,7 @@ export function buildContext(options = {}) {
102
114
  score: 0,
103
115
  reasons: [],
104
116
  extra: d.related_paths?.join(', '),
117
+ plan_id: d.plan_id,
105
118
  provenance: {
106
119
  actor: d.author,
107
120
  actor_id: d.author_id,
@@ -121,6 +134,7 @@ export function buildContext(options = {}) {
121
134
  score: 0,
122
135
  reasons: [],
123
136
  extra: `${t.severity}, visibility:${t.visibility ?? 'shared'}`,
137
+ plan_id: t.plan_id,
124
138
  provenance: {
125
139
  actor: t.author,
126
140
  actor_id: t.author_id,
@@ -344,9 +358,9 @@ export function buildContext(options = {}) {
344
358
  item.score += 5;
345
359
  item.reasons = uniqueReasons([...item.reasons, 'agent-layer: my assigned plan']);
346
360
  }
347
- // Layer 2: boost items authored by me (+2)
361
+ // Layer 2: boost items authored by me (+0.5)
348
362
  if (item.score >= 0 && item.provenance?.actor === agentName) {
349
- item.score += 2;
363
+ item.score += 0.5;
350
364
  item.reasons = uniqueReasons([...item.reasons, 'agent-layer: my authored item']);
351
365
  }
352
366
  // Reputation signal
@@ -381,28 +395,32 @@ export function buildContext(options = {}) {
381
395
  });
382
396
  const memoryDensity = classifyMemoryDensity(selected.length);
383
397
  const bootstrapEnabled = options.bootstrap !== false;
384
- let bootstrapAvailable = hasReusableBootstrapProfile(target, contextCwd);
398
+ const testMode = process.env.BRAINCLAW_TEST_MODE === '1';
399
+ let bootstrapAvailable = false;
385
400
  let derivedSignals;
386
- if (bootstrapEnabled && (options.refreshBootstrap || memoryDensity === 'low')) {
387
- const bootstrap = runBootstrapProfile({
388
- target,
389
- refresh: options.refreshBootstrap,
390
- cwd: contextCwd,
391
- });
392
- bootstrapAvailable = bootstrap.profile.seed_count > 0;
393
- if (memoryDensity === 'low') {
401
+ if (!testMode) {
402
+ bootstrapAvailable = hasReusableBootstrapProfile(target, contextCwd);
403
+ if (bootstrapEnabled && (options.refreshBootstrap || memoryDensity === 'low')) {
404
+ const bootstrap = runBootstrapProfile({
405
+ target,
406
+ refresh: options.refreshBootstrap,
407
+ cwd: contextCwd,
408
+ });
409
+ bootstrapAvailable = bootstrap.profile.seed_count > 0;
410
+ if (memoryDensity === 'low') {
411
+ const signals = selectDerivedSignals(target, 5, contextCwd);
412
+ if (signals.length > 0) {
413
+ derivedSignals = signals;
414
+ }
415
+ }
416
+ }
417
+ else if (bootstrapEnabled && bootstrapAvailable && memoryDensity === 'low') {
394
418
  const signals = selectDerivedSignals(target, 5, contextCwd);
395
419
  if (signals.length > 0) {
396
420
  derivedSignals = signals;
397
421
  }
398
422
  }
399
423
  }
400
- else if (bootstrapEnabled && bootstrapAvailable && memoryDensity === 'low') {
401
- const signals = selectDerivedSignals(target, 5, contextCwd);
402
- if (signals.length > 0) {
403
- derivedSignals = signals;
404
- }
405
- }
406
424
  const executionSensitive = isExecutionSensitiveTarget(target);
407
425
  const derivedUsesExecution = derivedSignals?.some((signal) => signal.source_kind === 'machine') ?? false;
408
426
  const derivedUsesTooling = derivedSignals?.some((signal) => signal.source_kind === 'skill' || signal.source_kind === 'mcp') ?? false;
@@ -424,17 +442,44 @@ export function buildContext(options = {}) {
424
442
  // Build open_work: active claims and in_progress plans owned by the current agent
425
443
  // Reuses myClaims computed in agent-layer scoring above
426
444
  let openWork;
445
+ const currentSession = loadCurrentSession(contextCwd);
427
446
  if (currentAgentIdentity || agent) {
428
447
  const claimPlanIds = new Set(myClaims.map((c) => c.plan_id).filter(Boolean));
448
+ const activeAssignments = listAssignments(contextCwd, { agent: agentName }).filter((assignment) => !['completed', 'failed', 'expired', 'rerouted'].includes(assignment.status));
429
449
  const inProgressPlans = state.plan_items.filter((p) => p.status === 'in_progress' &&
430
450
  (p.assignee === agentName || claimPlanIds.has(p.id)));
431
- if (myClaims.length > 0 || inProgressPlans.length > 0) {
451
+ if (myClaims.length > 0 || activeAssignments.length > 0 || inProgressPlans.length > 0) {
432
452
  openWork = {
433
- active_claims: myClaims.map(({ id, scope, description, created_at, plan_id, expires_at }) => ({ id, scope, description, created_at, plan_id, expires_at })),
453
+ active_claims: myClaims.map((c) => ({
454
+ id: c.id,
455
+ scope: c.scope,
456
+ description: c.description,
457
+ created_at: c.created_at,
458
+ plan_id: c.plan_id,
459
+ expires_at: c.expires_at,
460
+ liveness: assessClaimLiveness(c, { cwd: contextCwd }).status,
461
+ })),
462
+ active_assignments: activeAssignments.map(({ id, status, scope, description, plan_id, last_heartbeat_at }) => ({
463
+ id,
464
+ status,
465
+ scope,
466
+ description,
467
+ plan_id,
468
+ last_heartbeat_at,
469
+ })),
434
470
  in_progress_plans: inProgressPlans.map(({ id, text, assignee }) => ({ id, text, assignee })),
435
471
  };
436
472
  }
437
473
  }
474
+ const sessionMetrics = buildSessionMetrics({
475
+ cwd: contextCwd,
476
+ sessionId: currentSession?.session_id,
477
+ sessionStartedAt: currentSession?.started_at,
478
+ agentName: agentName,
479
+ agentId: currentAgentIdentity?.agent_id,
480
+ activeClaimsCount: openWork?.active_claims.length ?? 0,
481
+ runtimeNotes,
482
+ });
438
483
  // Cross-project items (subscriber links — read-only, always injected, bypass scoring)
439
484
  const crossProjectItems = [];
440
485
  for (const link of resolveCrossProjectLinks(contextCwd)) {
@@ -442,27 +487,50 @@ export function buildContext(options = {}) {
442
487
  continue;
443
488
  try {
444
489
  const linkedState = loadCrossProjectState(link.absolutePath);
490
+ for (const p of linkedState.plan_items.filter((plan) => plan.status !== 'done' && plan.status !== 'dropped')) {
491
+ crossProjectItems.push({
492
+ id: p.id, section: 'cross_project', text: `[plan] ${p.text}`,
493
+ tags: p.tags, score: 0, reasons: [], from_project: link.projectName,
494
+ extra: `${p.status}, ${p.priority}`,
495
+ });
496
+ }
445
497
  for (const d of linkedState.recent_decisions) {
446
498
  crossProjectItems.push({
447
499
  id: d.id, section: 'cross_project', text: d.text,
448
500
  tags: d.tags, score: 0, reasons: [], from_project: link.projectName,
501
+ plan_id: d.plan_id,
449
502
  });
450
503
  }
451
504
  for (const c of linkedState.active_constraints) {
452
505
  crossProjectItems.push({
453
506
  id: c.id, section: 'cross_project', text: c.text,
454
507
  tags: c.tags, score: 0, reasons: [], from_project: link.projectName,
508
+ plan_id: c.plan_id,
455
509
  });
456
510
  }
457
511
  for (const t of linkedState.known_traps.filter((trap) => isTrapActive(trap))) {
458
512
  crossProjectItems.push({
459
513
  id: t.id, section: 'cross_project', text: t.text,
460
514
  tags: t.tags, score: 0, reasons: [], from_project: link.projectName,
515
+ plan_id: t.plan_id,
461
516
  });
462
517
  }
463
518
  }
464
519
  catch { /* skip unavailable linked project */ }
465
520
  }
521
+ // Staleness detection: non-blocking, capped at 5 warnings to keep context lean.
522
+ // Phase 4 Sprint 1 Lane A step 3 (pln#390): runtime_note staleness now
523
+ // flows through the same surface.
524
+ let staleWarnings;
525
+ try {
526
+ const pendingCandidatesForStaleness = listCandidates('pending', contextCwd);
527
+ const runtimeNotesForStaleness = listRuntimeNotes(undefined, contextCwd);
528
+ const staleReport = detectStaleness(state.plan_items, state.known_traps, state.open_handoffs, pendingCandidatesForStaleness, Date.now(), runtimeNotesForStaleness);
529
+ if (staleReport.warnings.length > 0) {
530
+ staleWarnings = staleReport.warnings.slice(0, 5);
531
+ }
532
+ }
533
+ catch { /* non-fatal */ }
466
534
  const result = {
467
535
  context_schema: CONTEXT_SCHEMA_VERSION,
468
536
  profile,
@@ -471,6 +539,7 @@ export function buildContext(options = {}) {
471
539
  project_mode: projectMode,
472
540
  project_strategy: projectStrategy,
473
541
  current_host: currentHost,
542
+ project_vision: readProjectVision(contextCwd),
474
543
  host_filter: options.host,
475
544
  all_hosts: options.allHosts ?? false,
476
545
  memory_version: memoryVersion,
@@ -493,6 +562,7 @@ export function buildContext(options = {}) {
493
562
  resolved_instructions: resolvedInstructions,
494
563
  resume_summary: resumeSummary,
495
564
  open_work: openWork,
565
+ session_metrics: sessionMetrics,
496
566
  stores: storeChain.length > 1
497
567
  ? storeChain.map(({ cwd, depth, role }) => ({ cwd, depth, role }))
498
568
  : undefined,
@@ -509,6 +579,7 @@ export function buildContext(options = {}) {
509
579
  cross_project_items: crossProjectItems.length > 0 ? crossProjectItems : undefined,
510
580
  claim_conflicts: detectClaimConflicts(myClaims, otherActiveClaims),
511
581
  workflow_hints: buildWorkflowHints(myClaims, openWork, state.plan_items),
582
+ stale_warnings: staleWarnings,
512
583
  selected,
513
584
  };
514
585
  if (options.digest) {
@@ -520,7 +591,7 @@ export function renderContextMarkdown(result, explain = false) {
520
591
  const lines = [];
521
592
  lines.push(`# Agent Context (${result.profile})`);
522
593
  lines.push('');
523
- if (result.open_work && (result.open_work.active_claims.length > 0 || result.open_work.in_progress_plans.length > 0)) {
594
+ if (result.open_work && (result.open_work.active_claims.length > 0 || result.open_work.active_assignments.length > 0 || result.open_work.in_progress_plans.length > 0)) {
524
595
  lines.push('## ⚠ Your open work');
525
596
  lines.push('');
526
597
  if (result.open_work.active_claims.length > 0) {
@@ -530,10 +601,22 @@ export function renderContextMarkdown(result, explain = false) {
530
601
  const planRef = claim.plan_id ? ` [plan: ${claim.plan_id}]` : '';
531
602
  const expired = claim.expires_at && claim.expires_at < now ? ' ⚠ EXPIRED — run brainclaw prune' : '';
532
603
  const ttlInfo = claim.expires_at && !expired ? ` (expires ${claim.expires_at.slice(0, 16).replace('T', ' ')})` : '';
533
- lines.push(`- [${claim.id}] ${claim.description}${planRef}${ttlInfo}${expired}`);
604
+ const livenessTag = claim.liveness && claim.liveness !== 'live' && claim.liveness !== 'young'
605
+ ? ` [${claim.liveness.toUpperCase()}]`
606
+ : '';
607
+ lines.push(`- [${claim.id}] ${claim.description}${planRef}${ttlInfo}${livenessTag}${expired}`);
534
608
  lines.push(` scope: ${claim.scope}`);
535
609
  }
536
610
  }
611
+ if (result.open_work.active_assignments.length > 0) {
612
+ lines.push('Active assignments (runtime state):');
613
+ for (const assignment of result.open_work.active_assignments) {
614
+ const planRef = assignment.plan_id ? ` [plan: ${assignment.plan_id}]` : '';
615
+ const heartbeat = assignment.last_heartbeat_at ? ` [heartbeat: ${assignment.last_heartbeat_at.slice(0, 16).replace('T', ' ')}]` : '';
616
+ lines.push(`- [${assignment.id}] ${assignment.description}${planRef} (${assignment.status})${heartbeat}`);
617
+ lines.push(` scope: ${assignment.scope}`);
618
+ }
619
+ }
537
620
  if (result.open_work.in_progress_plans.length > 0) {
538
621
  lines.push('In-progress plan items (update status when done):');
539
622
  for (const plan of result.open_work.in_progress_plans) {
@@ -542,6 +625,17 @@ export function renderContextMarkdown(result, explain = false) {
542
625
  }
543
626
  lines.push('');
544
627
  }
628
+ if (result.session_metrics) {
629
+ lines.push('## Session metrics');
630
+ lines.push('');
631
+ lines.push(`- Active claims: ${result.session_metrics.active_claims}`);
632
+ lines.push(`- Edits since last Brainclaw write: ${result.session_metrics.edits_since_last_memory}`);
633
+ lines.push(`- Session duration: ${result.session_metrics.session_duration_minutes} min`);
634
+ if (result.session_metrics.last_brainclaw_write) {
635
+ lines.push(`- Last Brainclaw write: ${result.session_metrics.last_brainclaw_write}`);
636
+ }
637
+ lines.push('');
638
+ }
545
639
  if (result.estimation_calibration) {
546
640
  lines.push(`Estimation calibration: ${result.estimation_calibration}`);
547
641
  lines.push('');
@@ -553,6 +647,9 @@ export function renderContextMarkdown(result, explain = false) {
553
647
  if (result.agent_id && result.agent) {
554
648
  lines.push(`Agent ID: ${result.agent_id}`);
555
649
  }
650
+ if (result.project_vision) {
651
+ lines.push(`Project vision: ${result.project_vision.split('\n')[0]}`);
652
+ }
556
653
  lines.push(`Project mode: ${result.project_mode} (${result.project_strategy})`);
557
654
  if (result.active_project) {
558
655
  const ap = result.active_project;
@@ -701,8 +798,9 @@ export function renderContextMarkdown(result, explain = false) {
701
798
  for (const item of result.selected) {
702
799
  const tags = item.tags.length ? ` [${item.tags.join(', ')}]` : '';
703
800
  const extra = item.extra ? ` (${item.extra})` : '';
801
+ const planRef = item.plan_id ? ` [plan: ${item.plan_id}]` : '';
704
802
  const why = explain && item.reasons.length ? ` {why: ${item.reasons.join(', ')}}` : '';
705
- lines.push(`- [${item.id}] <${item.section}> ${item.text}${extra}${tags}${why}`);
803
+ lines.push(`- [${item.id}] <${item.section}> ${item.text}${extra}${planRef}${tags}${why}`);
706
804
  }
707
805
  if (result.derived_signals && result.derived_signals.length > 0) {
708
806
  lines.push('');
@@ -770,6 +868,12 @@ export function renderContextPromptTemplate(result, compact = false) {
770
868
  lines.push(`sd=${result.context_diff.since_session ?? ''}`);
771
869
  lines.push(`dc=${result.context_diff.counts.total}`);
772
870
  }
871
+ if (result.stale_warnings && result.stale_warnings.length > 0) {
872
+ lines.push(`sw=${result.stale_warnings.length}`);
873
+ for (const w of result.stale_warnings) {
874
+ lines.push(` - en=${w.entity} id=${w.id} ag=${w.age_days} tx="${w.reason}"`);
875
+ }
876
+ }
773
877
  if (result.resume_summary) {
774
878
  lines.push(`rt=${result.resume_summary.internal_trust}`);
775
879
  lines.push('rs:');
@@ -798,6 +902,9 @@ export function renderContextPromptTemplate(result, compact = false) {
798
902
  }
799
903
  lines.push(`project_mode: ${result.project_mode}`);
800
904
  lines.push(`project_strategy: ${result.project_strategy}`);
905
+ if (result.project_vision) {
906
+ lines.push(`project_vision: "${result.project_vision.split('\n')[0]}"`);
907
+ }
801
908
  if (result.active_project) {
802
909
  lines.push(`active_project: ${result.active_project.name ?? result.active_project.path}`);
803
910
  lines.push(`active_project_switched: ${result.active_project.switched_at}`);
@@ -883,22 +990,42 @@ export function renderContextPromptTemplate(result, compact = false) {
883
990
  lines.push(` - ${item}`);
884
991
  }
885
992
  }
993
+ if (result.stale_warnings && result.stale_warnings.length > 0) {
994
+ lines.push('stale_warnings:');
995
+ for (const w of result.stale_warnings) {
996
+ lines.push(` - entity=${w.entity} id=${w.id} age_days=${w.age_days} reason="${w.reason}" action="${w.suggested_action}"`);
997
+ }
998
+ }
886
999
  if (result.target) {
887
1000
  lines.push(`target: ${result.target}`);
888
1001
  }
889
1002
  }
890
- if (result.open_work && (result.open_work.active_claims.length > 0 || result.open_work.in_progress_plans.length > 0)) {
1003
+ if (result.open_work && (result.open_work.active_claims.length > 0 || result.open_work.active_assignments.length > 0 || result.open_work.in_progress_plans.length > 0)) {
891
1004
  lines.push(compact ? 'ow:' : 'open_work:');
892
1005
  if (result.open_work.active_claims.length > 0) {
893
1006
  lines.push(compact ? ' claims:' : ' active_claims:');
894
1007
  for (const claim of result.open_work.active_claims) {
1008
+ const lv = claim.liveness ? (compact ? ` lv=${claim.liveness}` : ` liveness=${claim.liveness}`) : '';
895
1009
  if (compact) {
896
1010
  const planRef = claim.plan_id ? ` pl=${claim.plan_id}` : '';
897
- lines.push(` - id=${claim.id}${planRef} sc="${claim.scope}" tx="${claim.description}"`);
1011
+ lines.push(` - id=${claim.id}${planRef} sc="${claim.scope}" tx="${claim.description}"${lv}`);
898
1012
  }
899
1013
  else {
900
1014
  const planRef = claim.plan_id ? ` plan_id=${claim.plan_id}` : '';
901
- lines.push(` - id=${claim.id}${planRef} scope="${claim.scope}" description="${claim.description}"`);
1015
+ lines.push(` - id=${claim.id}${planRef} scope="${claim.scope}" description="${claim.description}"${lv}`);
1016
+ }
1017
+ }
1018
+ }
1019
+ if (result.open_work.active_assignments.length > 0) {
1020
+ lines.push(compact ? ' assignments:' : ' active_assignments:');
1021
+ for (const assignment of result.open_work.active_assignments) {
1022
+ if (compact) {
1023
+ const planRef = assignment.plan_id ? ` pl=${assignment.plan_id}` : '';
1024
+ lines.push(` - id=${assignment.id}${planRef} st=${assignment.status} sc="${assignment.scope}" tx="${assignment.description}"`);
1025
+ }
1026
+ else {
1027
+ const planRef = assignment.plan_id ? ` plan_id=${assignment.plan_id}` : '';
1028
+ lines.push(` - id=${assignment.id}${planRef} status=${assignment.status} scope="${assignment.scope}" description="${assignment.description}"`);
902
1029
  }
903
1030
  }
904
1031
  }
@@ -914,6 +1041,25 @@ export function renderContextPromptTemplate(result, compact = false) {
914
1041
  }
915
1042
  }
916
1043
  }
1044
+ if (result.session_metrics) {
1045
+ lines.push(compact ? 'sm:' : 'session_metrics:');
1046
+ if (compact) {
1047
+ lines.push(` ac=${result.session_metrics.active_claims}`);
1048
+ lines.push(` ed=${result.session_metrics.edits_since_last_memory}`);
1049
+ lines.push(` dur=${result.session_metrics.session_duration_minutes}`);
1050
+ if (result.session_metrics.last_brainclaw_write) {
1051
+ lines.push(` lbw=${result.session_metrics.last_brainclaw_write}`);
1052
+ }
1053
+ }
1054
+ else {
1055
+ lines.push(` active_claims: ${result.session_metrics.active_claims}`);
1056
+ lines.push(` edits_since_last_memory: ${result.session_metrics.edits_since_last_memory}`);
1057
+ lines.push(` session_duration_minutes: ${result.session_metrics.session_duration_minutes}`);
1058
+ if (result.session_metrics.last_brainclaw_write) {
1059
+ lines.push(` last_brainclaw_write: ${result.session_metrics.last_brainclaw_write}`);
1060
+ }
1061
+ }
1062
+ }
917
1063
  lines.push(compact ? 'ins:' : 'instructions:');
918
1064
  if (result.resolved_instructions.length === 0) {
919
1065
  lines.push(compact ? ' - n' : ' - none');
@@ -941,14 +1087,16 @@ export function renderContextPromptTemplate(result, compact = false) {
941
1087
  if (compact) {
942
1088
  const tags = item.tags.length ? ` tg=[${item.tags.join(',')}]` : '';
943
1089
  const extra = item.extra ? ` ex="${item.extra}"` : '';
1090
+ const planRef = item.plan_id ? ` pl=${item.plan_id}` : '';
944
1091
  const why = item.reasons.length ? ` why=[${item.reasons.join('|')}]` : '';
945
- lines.push(` - id=${item.id} tp=${item.section}${tags}${extra}${why} tx="${item.text}"`);
1092
+ lines.push(` - id=${item.id} tp=${item.section}${tags}${extra}${planRef}${why} tx="${item.text}"`);
946
1093
  }
947
1094
  else {
948
1095
  const tags = item.tags.length ? ` tags=[${item.tags.join(',')}]` : '';
949
1096
  const extra = item.extra ? ` extra="${item.extra}"` : '';
1097
+ const planRef = item.plan_id ? ` plan=${item.plan_id}` : '';
950
1098
  const why = item.reasons.length ? ` why=[${item.reasons.join(', ')}]` : '';
951
- lines.push(` - id=${item.id} type=${item.section}${tags}${extra}${why} text="${item.text}"`);
1099
+ lines.push(` - id=${item.id} type=${item.section}${tags}${extra}${planRef}${why} text="${item.text}"`);
952
1100
  }
953
1101
  }
954
1102
  }
@@ -979,6 +1127,119 @@ export function renderContextPromptTemplate(result, compact = false) {
979
1127
  lines.push('```');
980
1128
  return lines.join('\n');
981
1129
  }
1130
+ /**
1131
+ * Render context as an ultra-compact scope briefing (< 500 chars).
1132
+ * Designed for pre-action injection: only what matters for THIS scope, NOW.
1133
+ *
1134
+ * Output example:
1135
+ * scope: src/core/agent-files.ts
1136
+ * traps: [high] MCP server hérite env vars VS Code
1137
+ * claims: no conflict
1138
+ * decisions: brainclawMcpEntry preserves existing command
1139
+ * confidence: high (3 items, freshest 2d ago)
1140
+ */
1141
+ export function renderContextBriefing(result) {
1142
+ const lines = [];
1143
+ if (result.target) {
1144
+ lines.push(`scope: ${result.target}`);
1145
+ }
1146
+ // Traps (high severity first, max 2)
1147
+ const traps = result.selected
1148
+ .filter(i => i.section === 'trap')
1149
+ .slice(0, 2);
1150
+ if (traps.length > 0) {
1151
+ for (const t of traps) {
1152
+ const severity = t.extra?.match(/\b(high|medium|low)\b/)?.[0] ?? '';
1153
+ const text = t.text.length > 80 ? t.text.slice(0, 77) + '...' : t.text;
1154
+ lines.push(`trap: [${severity}] ${text}`);
1155
+ }
1156
+ }
1157
+ // Claim conflicts
1158
+ if (result.claim_conflicts && result.claim_conflicts.length > 0) {
1159
+ for (const c of result.claim_conflicts.slice(0, 2)) {
1160
+ lines.push(`conflict: ${c.other_agent} claims ${c.other_scope}`);
1161
+ }
1162
+ }
1163
+ else {
1164
+ lines.push('claims: no conflict');
1165
+ }
1166
+ // Recent decisions (max 1, truncated)
1167
+ const decisions = result.selected
1168
+ .filter(i => i.section === 'decision')
1169
+ .slice(0, 1);
1170
+ if (decisions.length > 0) {
1171
+ const text = decisions[0].text.length > 80 ? decisions[0].text.slice(0, 77) + '...' : decisions[0].text;
1172
+ lines.push(`decision: ${text}`);
1173
+ }
1174
+ // Confidence score based on item count and scope activity
1175
+ const totalItems = result.selected.length;
1176
+ const hasActivity = result.scoped_activity != null;
1177
+ let confidence = 'low';
1178
+ if (totalItems >= 3 && hasActivity) {
1179
+ confidence = 'high';
1180
+ }
1181
+ else if (totalItems >= 1) {
1182
+ confidence = 'medium';
1183
+ }
1184
+ lines.push(`confidence: ${confidence} (${totalItems} items${hasActivity ? ', scope has recent activity' : ''})`);
1185
+ // Sequence position placeholder (populated when Sequences land)
1186
+ // lines.push(`sequence: ...`);
1187
+ return lines.join('\n');
1188
+ }
1189
+ /**
1190
+ * Render context for Claude Desktop: surface tasks, inbox items, and active plans.
1191
+ * Excludes traps, constraints, and decisions — Claude Desktop sessions are task-oriented.
1192
+ * Target output: < 1500 chars, actionable items only.
1193
+ */
1194
+ export function renderContextClaudeDesktop(result) {
1195
+ const lines = [];
1196
+ lines.push('# Brainclaw — Claude Desktop Session');
1197
+ lines.push('');
1198
+ // Active plans (task queue)
1199
+ const plans = result.selected.filter(i => i.section === 'plan');
1200
+ if (plans.length > 0) {
1201
+ lines.push('## Tasks');
1202
+ for (const p of plans.slice(0, 5)) {
1203
+ const status = p.extra?.match(/\b(todo|in_progress|blocked)\b/)?.[0] ?? 'todo';
1204
+ const text = p.text.length > 100 ? p.text.slice(0, 97) + '...' : p.text;
1205
+ lines.push(`- [${status}] ${text} (${p.id})`);
1206
+ }
1207
+ lines.push('');
1208
+ }
1209
+ // Open handoffs (pending work from other agents)
1210
+ const handoffs = result.selected.filter(i => i.section === 'handoff');
1211
+ if (handoffs.length > 0) {
1212
+ lines.push('## Inbox');
1213
+ for (const h of handoffs.slice(0, 3)) {
1214
+ const from = h.provenance?.actor ?? 'unknown';
1215
+ const text = h.text.length > 100 ? h.text.slice(0, 97) + '...' : h.text;
1216
+ lines.push(`- from:${from} — ${text}`);
1217
+ }
1218
+ lines.push('');
1219
+ }
1220
+ // Candidates (review items)
1221
+ const candidates = result.selected.filter(i => i.section === 'candidate');
1222
+ if (candidates.length > 0) {
1223
+ lines.push('## Review');
1224
+ for (const c of candidates.slice(0, 3)) {
1225
+ const text = c.text.length > 100 ? c.text.slice(0, 97) + '...' : c.text;
1226
+ lines.push(`- ${text} (${c.id})`);
1227
+ }
1228
+ lines.push('');
1229
+ }
1230
+ if (plans.length === 0 && handoffs.length === 0 && candidates.length === 0) {
1231
+ lines.push('No pending tasks. Use `bclaw_quick_capture` to queue work for Claude Desktop.');
1232
+ }
1233
+ // Claim conflicts (always show if present)
1234
+ if (result.claim_conflicts && result.claim_conflicts.length > 0) {
1235
+ lines.push('## Conflicts');
1236
+ for (const c of result.claim_conflicts.slice(0, 2)) {
1237
+ lines.push(`- ${c.other_agent} claims ${c.other_scope}`);
1238
+ }
1239
+ lines.push('');
1240
+ }
1241
+ return lines.join('\n');
1242
+ }
982
1243
  export function buildScopedActivity(input) {
983
1244
  const target = input.target?.trim();
984
1245
  if (!target) {
@@ -1077,6 +1338,85 @@ function classifyMemoryDensity(selectedCount) {
1077
1338
  return 'medium';
1078
1339
  return 'high';
1079
1340
  }
1341
+ const BRAINCLAW_WRITE_ACTIONS = ['create', 'update', 'delete', 'accept', 'reject', 'trust_change', 'promote_direct', 'rollback'];
1342
+ function buildSessionMetrics(input) {
1343
+ if (!input.sessionId || !input.sessionStartedAt || !input.agentName) {
1344
+ return undefined;
1345
+ }
1346
+ const sessionId = input.sessionId;
1347
+ const startedAtMs = Date.parse(input.sessionStartedAt);
1348
+ if (!Number.isFinite(startedAtMs)) {
1349
+ return undefined;
1350
+ }
1351
+ const runtimeWrites = input.runtimeNotes
1352
+ .filter((note) => note.agent === input.agentName
1353
+ && note.session_id === sessionId
1354
+ && ((note.note_type ?? 'observation') === 'observation'))
1355
+ .map((note) => note.created_at);
1356
+ const auditWrites = readAuditLog({ since: input.sessionStartedAt, actor: input.agentId ?? input.agentName }, input.cwd)
1357
+ .filter((entry) => belongsToSession(entry, sessionId))
1358
+ .filter((entry) => BRAINCLAW_WRITE_ACTIONS.includes(entry.action))
1359
+ .map((entry) => entry.timestamp);
1360
+ const lastBrainclawWrite = [...runtimeWrites, ...auditWrites].sort().at(-1);
1361
+ const editsSinceLastMemory = countChangedFilesSince(input.cwd, lastBrainclawWrite ?? input.sessionStartedAt);
1362
+ return {
1363
+ active_claims: input.activeClaimsCount,
1364
+ edits_since_last_memory: editsSinceLastMemory,
1365
+ session_duration_minutes: Math.max(0, Math.floor((Date.now() - startedAtMs) / 60_000)),
1366
+ last_brainclaw_write: lastBrainclawWrite,
1367
+ };
1368
+ }
1369
+ function belongsToSession(entry, sessionId) {
1370
+ return !entry.session_id || entry.session_id === sessionId;
1371
+ }
1372
+ function countChangedFilesSince(cwd, since) {
1373
+ const sinceMs = Date.parse(since);
1374
+ if (!Number.isFinite(sinceMs)) {
1375
+ return 0;
1376
+ }
1377
+ try {
1378
+ const output = execSync('git status --porcelain --untracked-files=normal', {
1379
+ cwd,
1380
+ encoding: 'utf-8',
1381
+ stdio: ['ignore', 'pipe', 'ignore'],
1382
+ }).trim();
1383
+ if (!output) {
1384
+ return 0;
1385
+ }
1386
+ const changedPaths = new Set();
1387
+ for (const line of output.split(/\r?\n/)) {
1388
+ const rawPath = line.slice(3).trim();
1389
+ const resolvedPath = rawPath.includes(' -> ') ? rawPath.split(' -> ').pop() ?? rawPath : rawPath;
1390
+ if (resolvedPath && shouldCountEditedPath(resolvedPath)) {
1391
+ changedPaths.add(resolvedPath);
1392
+ }
1393
+ }
1394
+ let count = 0;
1395
+ for (const relativePath of changedPaths) {
1396
+ const absolutePath = path.join(cwd, relativePath);
1397
+ try {
1398
+ if (!fs.existsSync(absolutePath)) {
1399
+ count++;
1400
+ continue;
1401
+ }
1402
+ if (fs.statSync(absolutePath).mtimeMs >= sinceMs) {
1403
+ count++;
1404
+ }
1405
+ }
1406
+ catch {
1407
+ count++;
1408
+ }
1409
+ }
1410
+ return count;
1411
+ }
1412
+ catch {
1413
+ return 0;
1414
+ }
1415
+ }
1416
+ function shouldCountEditedPath(relativePath) {
1417
+ const normalized = relativePath.replace(/\\/g, '/');
1418
+ return !normalized.startsWith('.brainclaw/') && !normalized.startsWith('.git/');
1419
+ }
1080
1420
  function summariseAgentTooling(snapshot) {
1081
1421
  return {
1082
1422
  agents_md_present: snapshot.agents_md_present,
@@ -1226,6 +1566,13 @@ function computeRelevance(item, terms, profile, target) {
1226
1566
  score += 1;
1227
1567
  reasons.push('runtime execution signal');
1228
1568
  }
1569
+ if (profile === 'dense') {
1570
+ // Dense profile: boost all actionable sections equally for maximum coverage
1571
+ if (item.section === 'decision' || item.section === 'trap' || item.section === 'candidate' || item.section === 'runtime') {
1572
+ score += 2;
1573
+ reasons.push('profile boost: dense');
1574
+ }
1575
+ }
1229
1576
  if (profile === 'dev' && (item.section === 'decision' || item.section === 'trap')) {
1230
1577
  score += 2;
1231
1578
  reasons.push('profile boost: dev');