brainclaw 1.7.5 → 1.9.0

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 (143) hide show
  1. package/README.md +28 -11
  2. package/dist/brainclaw-vscode.vsix +0 -0
  3. package/dist/cli.js +139 -13
  4. package/dist/commands/add-step.js +1 -1
  5. package/dist/commands/bootstrap.js +2 -26
  6. package/dist/commands/check-security-mcp.js +50 -33
  7. package/dist/commands/check-security.js +86 -43
  8. package/dist/commands/claim.js +22 -21
  9. package/dist/commands/confirm.js +26 -0
  10. package/dist/commands/context-diff.js +1 -1
  11. package/dist/commands/dispatch-watch.js +142 -0
  12. package/dist/commands/doctor.js +113 -2
  13. package/dist/commands/estimation-report.js +115 -16
  14. package/dist/commands/harvest.js +502 -16
  15. package/dist/commands/init.js +123 -21
  16. package/dist/commands/loops-handlers.js +4 -0
  17. package/dist/commands/mcp-read-handlers.js +198 -29
  18. package/dist/commands/mcp.js +615 -92
  19. package/dist/commands/memory.js +21 -17
  20. package/dist/commands/migrate.js +81 -17
  21. package/dist/commands/prune.js +78 -4
  22. package/dist/commands/reflect.js +26 -20
  23. package/dist/commands/register-agent.js +57 -1
  24. package/dist/commands/repair.js +20 -0
  25. package/dist/commands/session-end.js +15 -6
  26. package/dist/commands/session-start.js +18 -1
  27. package/dist/commands/setup-security.js +39 -18
  28. package/dist/commands/setup.js +26 -27
  29. package/dist/commands/stale.js +16 -2
  30. package/dist/commands/uninstall.js +126 -34
  31. package/dist/commands/update-step.js +6 -0
  32. package/dist/commands/worktree.js +60 -0
  33. package/dist/core/actions.js +12 -3
  34. package/dist/core/agent-capability.js +11 -13
  35. package/dist/core/agent-files.js +844 -547
  36. package/dist/core/agent-integrations.js +0 -3
  37. package/dist/core/agent-inventory.js +67 -0
  38. package/dist/core/agent-registry.js +163 -29
  39. package/dist/core/agentrun-reconciler.js +33 -2
  40. package/dist/core/agentruns.js +7 -1
  41. package/dist/core/ai-agent-detection.js +31 -44
  42. package/dist/core/archival.js +15 -9
  43. package/dist/core/assignment-reconciler.js +56 -0
  44. package/dist/core/assignment-sweeper.js +127 -4
  45. package/dist/core/assignments.js +69 -11
  46. package/dist/core/bootstrap.js +233 -67
  47. package/dist/core/brainclaw-version.js +22 -0
  48. package/dist/core/candidates.js +21 -1
  49. package/dist/core/claims.js +313 -150
  50. package/dist/core/config.js +6 -1
  51. package/dist/core/context-diff.js +148 -20
  52. package/dist/core/context.js +129 -8
  53. package/dist/core/coordination.js +22 -3
  54. package/dist/core/dispatch-status.js +109 -5
  55. package/dist/core/dispatcher.js +65 -11
  56. package/dist/core/entity-operations.js +45 -24
  57. package/dist/core/entity-registry.js +31 -5
  58. package/dist/core/event-log.js +138 -21
  59. package/dist/core/events/checkpoint.js +258 -0
  60. package/dist/core/events/genesis.js +220 -0
  61. package/dist/core/events/journal.js +507 -0
  62. package/dist/core/events/materialize.js +126 -0
  63. package/dist/core/events/registry-post-image.js +110 -0
  64. package/dist/core/events/verify.js +109 -0
  65. package/dist/core/execution-adapters.js +23 -0
  66. package/dist/core/execution.js +25 -0
  67. package/dist/core/facade-schema.js +48 -0
  68. package/dist/core/gc-semantic.js +130 -5
  69. package/dist/core/handoff-snapshot.js +68 -0
  70. package/dist/core/ids.js +19 -8
  71. package/dist/core/instruction-templates.js +34 -115
  72. package/dist/core/io.js +39 -3
  73. package/dist/core/json-store.js +10 -1
  74. package/dist/core/lock.js +153 -28
  75. package/dist/core/loops/bootstrap-acquire.js +25 -1
  76. package/dist/core/loops/facade-schema.js +2 -0
  77. package/dist/core/loops/hooks/survey-signals-baseline.js +36 -0
  78. package/dist/core/loops/index.js +1 -0
  79. package/dist/core/loops/presets/bootstrap.js +7 -0
  80. package/dist/core/loops/store.js +17 -0
  81. package/dist/core/loops/verbs.js +24 -1
  82. package/dist/core/markdown.js +8 -76
  83. package/dist/core/mcp-command-resolution.js +245 -0
  84. package/dist/core/memory-compactor.js +5 -3
  85. package/dist/core/memory-lifecycle.js +282 -0
  86. package/dist/core/merge-risk.js +150 -0
  87. package/dist/core/messaging.js +8 -1
  88. package/dist/core/migration.js +11 -1
  89. package/dist/core/observer-mode.js +26 -0
  90. package/dist/core/operations/memory-mutation.js +90 -65
  91. package/dist/core/operations/plan.js +27 -1
  92. package/dist/core/protocol-skills.js +210 -0
  93. package/dist/core/reflection-safety.js +6 -7
  94. package/dist/core/reputation.js +84 -2
  95. package/dist/core/runtime-signals.js +71 -9
  96. package/dist/core/runtime.js +84 -1
  97. package/dist/core/schema.js +125 -0
  98. package/dist/core/security-detectors.js +125 -0
  99. package/dist/core/security-extract.js +189 -0
  100. package/dist/core/security-guard.js +107 -29
  101. package/dist/core/security-packages.js +121 -0
  102. package/dist/core/security-scoring.js +76 -9
  103. package/dist/core/security.js +34 -2
  104. package/dist/core/sequence.js +11 -2
  105. package/dist/core/setup-flow.js +141 -13
  106. package/dist/core/spawn-check.js +110 -4
  107. package/dist/core/staleness.js +109 -1
  108. package/dist/core/state.js +250 -54
  109. package/dist/core/store-resolution.js +19 -5
  110. package/dist/core/worktree.js +169 -7
  111. package/dist/facts.js +8 -8
  112. package/dist/facts.json +7 -7
  113. package/docs/PROTOCOL.md +223 -0
  114. package/docs/cli.md +11 -10
  115. package/docs/concepts/coordinator-runbook.md +129 -0
  116. package/docs/concepts/dispatch-lifecycle.md +17 -0
  117. package/docs/concepts/event-log-store-critique-A.md +333 -0
  118. package/docs/concepts/event-log-store-critique-B.md +353 -0
  119. package/docs/concepts/event-log-store-phase0-measurements.md +58 -0
  120. package/docs/concepts/event-log-store-proposal-A.md +365 -0
  121. package/docs/concepts/event-log-store-proposal-B.md +404 -0
  122. package/docs/concepts/event-log-store.md +928 -0
  123. package/docs/concepts/identity-model-proposal.md +371 -0
  124. package/docs/concepts/memory.md +5 -4
  125. package/docs/concepts/observer-protocol.md +361 -0
  126. package/docs/concepts/parallel-merge-protocol.md +71 -0
  127. package/docs/concepts/plans-and-claims.md +43 -0
  128. package/docs/concepts/skills.md +78 -0
  129. package/docs/concepts/workspace-bootstrapping.md +61 -0
  130. package/docs/integrations/agents.md +4 -4
  131. package/docs/integrations/cline.md +10 -11
  132. package/docs/integrations/codex.md +2 -2
  133. package/docs/integrations/continue.md +5 -5
  134. package/docs/integrations/copilot.md +14 -12
  135. package/docs/integrations/openclaw.md +7 -6
  136. package/docs/integrations/overview.md +7 -7
  137. package/docs/integrations/roo.md +3 -3
  138. package/docs/integrations/windsurf.md +6 -6
  139. package/docs/mcp-schema-changelog.md +51 -20
  140. package/docs/quickstart.md +48 -47
  141. package/docs/security.md +174 -15
  142. package/docs/storage.md +4 -2
  143. package/package.json +8 -6
@@ -1,5 +1,47 @@
1
1
  import { loadState } from '../core/state.js';
2
2
  import { memoryExists } from '../core/io.js';
3
+ /** Default wall-clock outlier cutoff: a single plan's "actual" over 24h is almost certainly idle, not work. */
4
+ export const DEFAULT_OUTLIER_THRESHOLD_MINUTES = 1440;
5
+ /**
6
+ * Sum of per-step estimates — only when EVERY step carries one. A mixed plan
7
+ * (some steps estimated, some not) returns undefined so the caller falls back
8
+ * to the plan-level estimate rather than mixing half-measured data (pln#495).
9
+ */
10
+ function sumStepEstimates(plan) {
11
+ const steps = plan.steps ?? [];
12
+ if (steps.length === 0)
13
+ return undefined;
14
+ if (!steps.every((s) => typeof s.estimated_effort === 'number'))
15
+ return undefined;
16
+ return steps.reduce((acc, s) => acc + s.estimated_effort, 0);
17
+ }
18
+ /**
19
+ * Sum of per-step actual durations — only when EVERY step is measurable, via an
20
+ * explicit `actual_effort` string OR both `started_at`+`completed_at`. Returns
21
+ * undefined otherwise (→ fall back to plan-level). This is the key win: summing
22
+ * per-step durations excludes the idle time BETWEEN steps that plan-level
23
+ * wall-clock wrongly counts as work (pln#495, the pln#494 step-6 bias).
24
+ */
25
+ function sumStepActuals(plan) {
26
+ const steps = plan.steps ?? [];
27
+ if (steps.length === 0)
28
+ return undefined;
29
+ let total = 0;
30
+ for (const s of steps) {
31
+ let dur;
32
+ if (s.actual_effort) {
33
+ dur = parseEffortMinutes(s.actual_effort);
34
+ }
35
+ else if (s.started_at && s.completed_at) {
36
+ const d = (new Date(s.completed_at).getTime() - new Date(s.started_at).getTime()) / 60000;
37
+ dur = d > 0 ? d : undefined;
38
+ }
39
+ if (dur === undefined)
40
+ return undefined; // any unmeasurable step → no step-level actual
41
+ total += dur;
42
+ }
43
+ return total > 0 ? total : undefined;
44
+ }
3
45
  /** Parse legacy actual_effort strings ("30min", "2h", "1h30m", "1d", "45m") → minutes.
4
46
  * Still needed for actual_effort which remains a free string. */
5
47
  export function parseEffortMinutes(effort) {
@@ -23,7 +65,7 @@ export function parseEffortMinutes(effort) {
23
65
  }
24
66
  if (!matched) {
25
67
  const bare = parseFloat(s);
26
- if (!isNaN(bare))
68
+ if (!isNaN(bare) && /^\d+(?:\.\d+)?$/.test(s))
27
69
  return bare;
28
70
  return undefined;
29
71
  }
@@ -65,37 +107,78 @@ export function buildEstimationReport(options = {}) {
65
107
  const state = loadState(options.cwd);
66
108
  const done = state.plan_items.filter((p) => p.status === 'done' && (!options.agent || p.author === options.agent));
67
109
  const entries = done.map((p) => {
110
+ // Estimate: prefer the sum of per-step estimates when ALL steps carry one,
111
+ // else the plan-level estimate (pln#495).
112
+ const stepEstimate = sumStepEstimates(p);
68
113
  const entry = {
69
114
  id: p.id,
70
115
  text: p.text,
71
116
  author: p.author,
72
- estimated_minutes: p.estimated_effort, // already a number after schema coercion
117
+ estimated_minutes: stepEstimate ?? p.estimated_effort,
73
118
  actual_effort: p.actual_effort,
74
119
  completed_at: p.completed_at,
75
120
  };
76
- // Compute elapsed from wall-clock timestamps
77
- const endTime = p.completed_at ?? p.updated_at;
78
- const startTime = p.started_at ?? p.created_at;
79
- if (endTime && startTime) {
80
- const elapsed = (new Date(endTime).getTime() - new Date(startTime).getTime()) / 60000;
81
- if (elapsed > 0)
82
- entry.elapsed_minutes = Math.round(elapsed);
121
+ // Actual + source, in fallback order (pln#495):
122
+ // step durations (idle-gap-free) > plan.actual_effort string > plan wall-clock.
123
+ let actualMinutes;
124
+ let source;
125
+ const stepActual = sumStepActuals(p);
126
+ if (stepActual !== undefined) {
127
+ actualMinutes = stepActual;
128
+ source = 'step';
83
129
  }
84
- // Resolve actual minutes: explicit actual_effort string > elapsed wall-clock
85
- const actualMinutes = p.actual_effort
86
- ? parseEffortMinutes(p.actual_effort)
87
- : entry.elapsed_minutes;
88
- if (actualMinutes !== undefined)
130
+ else if (p.actual_effort) {
131
+ actualMinutes = parseEffortMinutes(p.actual_effort);
132
+ source = 'plan_string';
133
+ }
134
+ else {
135
+ const endTime = p.completed_at ?? p.updated_at;
136
+ const startTime = p.started_at ?? p.created_at;
137
+ if (endTime && startTime) {
138
+ const elapsed = (new Date(endTime).getTime() - new Date(startTime).getTime()) / 60000;
139
+ if (elapsed > 0)
140
+ actualMinutes = elapsed;
141
+ }
142
+ source = 'plan_wallclock';
143
+ }
144
+ if (actualMinutes !== undefined) {
89
145
  entry.elapsed_minutes = Math.round(actualMinutes);
146
+ entry.source = source;
147
+ }
90
148
  if (entry.estimated_minutes !== undefined && actualMinutes !== undefined && actualMinutes > 0) {
91
149
  entry.ratio = parseFloat((entry.estimated_minutes / actualMinutes).toFixed(2));
92
150
  }
93
151
  return entry;
94
152
  });
153
+ // Wall-clock outlier filter (pln#495 step 7): a plan whose actual is plan-level
154
+ // wall-clock AND exceeds the threshold is flagged and dropped from the summary
155
+ // stats — it would otherwise drag the median/mean (the post-restoration +9900%
156
+ // accident). Step- and string-derived actuals are trusted at any size. A
157
+ // threshold of 0 disables the filter. Outliers keep their ratio and stay in
158
+ // `entries` so the chart still shows them, just marked.
159
+ const threshold = options.outlierThresholdMinutes ?? DEFAULT_OUTLIER_THRESHOLD_MINUTES;
160
+ if (threshold > 0) {
161
+ for (const e of entries) {
162
+ if (e.source === 'plan_wallclock' && e.elapsed_minutes !== undefined && e.elapsed_minutes > threshold) {
163
+ e.excluded_from_stats = true;
164
+ }
165
+ }
166
+ }
95
167
  const withBoth = entries.filter((e) => e.ratio !== undefined);
96
- const ratios = withBoth.map((e) => e.ratio);
168
+ const included = withBoth.filter((e) => !e.excluded_from_stats);
169
+ const ratios = included.map((e) => e.ratio);
97
170
  const medianRatio = ratios.length > 0 ? parseFloat(median(ratios).toFixed(2)) : undefined;
98
171
  const meanRatio = ratios.length > 0 ? parseFloat(mean(ratios).toFixed(2)) : undefined;
172
+ // Per-source median (pln#495): exposes how much calibration noise is wall-clock
173
+ // contamination vs idle-gap-free step measurement. Excludes flagged outliers.
174
+ const bySource = {};
175
+ for (const src of ['step', 'plan_string', 'plan_wallclock']) {
176
+ const srcRatios = included.filter((e) => e.source === src).map((e) => e.ratio);
177
+ if (srcRatios.length > 0) {
178
+ bySource[src] = { count: srcRatios.length, median_ratio: parseFloat(median(srcRatios).toFixed(2)) };
179
+ }
180
+ }
181
+ const outliersExcluded = withBoth.filter((e) => e.excluded_from_stats).length;
99
182
  return {
100
183
  entries,
101
184
  summary: {
@@ -105,6 +188,8 @@ export function buildEstimationReport(options = {}) {
105
188
  median_ratio: medianRatio,
106
189
  mean_ratio: meanRatio,
107
190
  calibration_hint: medianRatio !== undefined ? buildCalibrationHint(medianRatio) : undefined,
191
+ by_source: Object.keys(bySource).length > 0 ? bySource : undefined,
192
+ outliers_excluded: outliersExcluded > 0 ? outliersExcluded : undefined,
108
193
  },
109
194
  };
110
195
  }
@@ -129,6 +214,19 @@ export function runEstimationReport(options = {}) {
129
214
  console.log(`\nMedian ratio (estimated÷actual): ${summary.median_ratio}x · Mean: ${summary.mean_ratio}x`);
130
215
  console.log(`→ ${summary.calibration_hint}`);
131
216
  }
217
+ if (summary.outliers_excluded) {
218
+ console.log(`(${summary.outliers_excluded} wall-clock outlier(s) >24h excluded from the stats above — shown ⚠ in the chart)`);
219
+ }
220
+ if (summary.by_source) {
221
+ const labels = {
222
+ step: 'step-derived', plan_string: 'plan-string', plan_wallclock: 'plan-wallclock',
223
+ };
224
+ const parts = ['step', 'plan_string', 'plan_wallclock']
225
+ .filter((s) => summary.by_source[s])
226
+ .map((s) => `${labels[s]}: ${summary.by_source[s].median_ratio}x (n=${summary.by_source[s].count})`);
227
+ console.log(`By measurement quality — ${parts.join(' · ')}`);
228
+ console.log(' (step-derived excludes inter-step idle; plan-wallclock is the noisiest)');
229
+ }
132
230
  // Chart — only plans with ratio data
133
231
  const chartable = entries.filter((e) => e.ratio !== undefined);
134
232
  if (chartable.length > 0) {
@@ -149,7 +247,8 @@ export function runEstimationReport(options = {}) {
149
247
  : ` OVER +${Math.round((1 / e.ratio - 1) * 100)}%`;
150
248
  const est = e.estimated_minutes !== undefined ? `${e.estimated_minutes}min` : '?';
151
249
  const act = e.elapsed_minutes !== undefined ? `${e.elapsed_minutes}min` : '?';
152
- console.log(` ${label} ${bar} ${e.ratio}x ${est}→${act}${pct}`);
250
+ const srcTag = e.excluded_from_stats ? ' ⚠outlier' : e.source === 'step' ? ' ✓step' : e.source === 'plan_wallclock' ? ' ~wall' : '';
251
+ console.log(` ${label} ${bar} ${e.ratio}x ${est}→${act}${pct}${srcTag}`);
153
252
  }
154
253
  console.log('');
155
254
  }