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
@@ -55,6 +55,7 @@ export function acceptCandidate(id, by, cwd, byId) {
55
55
  session_id: candidate.session_id,
56
56
  status: 'active',
57
57
  tags: candidate.tags,
58
+ plan_id: candidate.plan_id,
58
59
  };
59
60
  state.active_constraints.push(entry);
60
61
  promotedItemId = entryId;
@@ -118,6 +119,8 @@ export function acceptCandidate(id, by, cwd, byId) {
118
119
  session_id: candidate.session_id,
119
120
  status: 'open',
120
121
  tags: candidate.tags,
122
+ plan_id: candidate.plan_id,
123
+ narrative: candidate.narrative,
121
124
  };
122
125
  state.open_handoffs.push(entry);
123
126
  promotedItemId = entryId;
@@ -1,33 +1,18 @@
1
- import { loadState, persistState } from '../core/state.js';
2
- import { memoryExists } from '../core/io.js';
3
- import { generateId, nowISO } from '../core/ids.js';
1
+ import { requireInitialized } from '../core/guards.js';
4
2
  import { validateCliInput } from '../core/input-validation.js';
3
+ import { addStep } from '../core/operations/plan.js';
5
4
  export function runAddStep(planId, text, options = {}) {
6
- if (!memoryExists()) {
7
- console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
8
- process.exit(1);
9
- }
5
+ requireInitialized(process.cwd());
10
6
  validateCliInput(text);
11
- const state = loadState();
12
- const plan = state.plan_items.find((p) => p.id === planId || p.short_label === planId);
13
- if (!plan) {
14
- console.error(`Error: Plan '${planId}' not found.`);
7
+ try {
8
+ const result = addStep({ planId, text, assignee: options.assignee });
9
+ console.log(`✔ Step added: [${result.stepId}] ${text}`);
10
+ console.log(` Plan [${result.planId}] progress: ${result.doneSteps}/${result.totalSteps} steps done`);
11
+ }
12
+ catch (error) {
13
+ const msg = error instanceof Error ? error.message : String(error);
14
+ console.error(`Error: ${msg}`);
15
15
  process.exit(1);
16
16
  }
17
- const step = {
18
- id: generateId('plan_steps'),
19
- text,
20
- status: 'todo',
21
- assignee: options.assignee,
22
- created_at: nowISO(),
23
- updated_at: nowISO(),
24
- };
25
- plan.steps = [...(plan.steps ?? []), step];
26
- plan.updated_at = nowISO();
27
- persistState(state);
28
- const total = plan.steps.length;
29
- const done = plan.steps.filter((s) => s.status === 'done').length;
30
- console.log(`✔ Step added: [${step.id}] ${text}`);
31
- console.log(` Plan [${plan.id}] progress: ${done}/${total} steps done`);
32
17
  }
33
18
  //# sourceMappingURL=add-step.js.map
@@ -1,13 +1,25 @@
1
1
  import { buildCoordinationSnapshot } from '../core/coordination.js';
2
2
  import { listAgentIdentities } from '../core/agent-registry.js';
3
3
  import { memoryExists } from '../core/io.js';
4
+ import { listCandidates } from '../core/candidates.js';
5
+ import { listRuntimeNotes } from '../core/runtime.js';
6
+ import { loadState } from '../core/state.js';
7
+ import { detectStaleness, staleSummary } from '../core/staleness.js';
8
+ import { assessClaimLiveness } from '../core/claims.js';
9
+ /** Tag non-healthy liveness states so operators spot orphaned/stale/never-adopted claims at a glance. */
10
+ function livenessTag(status) {
11
+ if (status === 'live' || status === 'young')
12
+ return '';
13
+ return ` [${status.toUpperCase()}]`;
14
+ }
4
15
  export function runAgentBoard(options = {}) {
5
16
  if (!memoryExists()) {
6
17
  console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
7
18
  process.exit(1);
8
19
  }
9
20
  const board = buildCoordinationSnapshot({
10
- agent: options.agent,
21
+ agent: options.allAgents ? undefined : options.agent,
22
+ skipAgentAutoDetect: options.allAgents,
11
23
  project: options.project,
12
24
  target: options.for,
13
25
  host: options.host,
@@ -15,8 +27,22 @@ export function runAgentBoard(options = {}) {
15
27
  includeReputation: options.withReputation,
16
28
  includeSessionMeta: options.includeSessionMeta,
17
29
  });
30
+ // Phase 4 Sprint 1 Lane A step 3 (pln#390): surface non-destructive
31
+ // stale warnings alongside the board so operators see them without
32
+ // running `doctor` separately. Capped at 5 to keep output lean.
33
+ let staleReport;
34
+ try {
35
+ const state = loadState();
36
+ const pending = listCandidates('pending');
37
+ const notes = listRuntimeNotes(undefined);
38
+ staleReport = detectStaleness(state.plan_items, state.known_traps, state.open_handoffs, pending, Date.now(), notes);
39
+ }
40
+ catch { /* non-fatal */ }
18
41
  if (options.json) {
19
- console.log(JSON.stringify(board, null, 2));
42
+ console.log(JSON.stringify({
43
+ ...board,
44
+ ...(staleReport && staleReport.warnings.length > 0 ? { stale_report: staleReport } : {}),
45
+ }, null, 2));
20
46
  return;
21
47
  }
22
48
  console.log(`Agent board${board.agent ? ` for ${board.agent}` : ''}${board.project ? ` (${board.project})` : ''}`);
@@ -43,7 +69,34 @@ export function runAgentBoard(options = {}) {
43
69
  console.log('');
44
70
  console.log(`Active claims: ${board.active_claims.length}`);
45
71
  for (const claim of board.active_claims.slice(0, 10)) {
46
- console.log(` [${claim.id}] ${claim.agent} -> ${claim.scope}${claim.plan_id ? ` (plan ${claim.plan_id})` : ''}`);
72
+ const tag = livenessTag(assessClaimLiveness(claim).status);
73
+ console.log(` [${claim.id}] ${claim.agent} -> ${claim.scope}${claim.plan_id ? ` (plan ${claim.plan_id})` : ''}${tag}`);
74
+ }
75
+ console.log('');
76
+ console.log(`Active assignments: ${board.active_assignments.length}`);
77
+ for (const assignment of board.active_assignments.slice(0, 10)) {
78
+ console.log(` [${assignment.id}] ${assignment.agent} (${assignment.status}) -> ${assignment.scope}${assignment.plan_id ? ` (plan ${assignment.plan_id})` : ''}`);
79
+ }
80
+ console.log('');
81
+ console.log(`Active runs: ${board.active_runs.length}`);
82
+ for (const run of board.active_runs.slice(0, 10)) {
83
+ console.log(` [${run.id}] ${run.agent} (${run.status}/${run.transport}) -> ${run.scope}${run.assignment_id ? ` (assignment ${run.assignment_id})` : ''} [attempt ${run.attempt_index}]`);
84
+ }
85
+ console.log('');
86
+ console.log(`Pending actions: ${board.active_actions.length}`);
87
+ for (const action of board.active_actions.slice(0, 10)) {
88
+ console.log(` [${action.id}] ${action.kind} for ${action.agent} (${action.status})${action.assignment_id ? ` [assignment ${action.assignment_id}]` : ''}: ${action.title}`);
89
+ }
90
+ console.log('');
91
+ console.log(`Active sequence: ${board.active_sequence ? `1 (${board.active_sequence.name})` : '0'}`);
92
+ if (board.active_sequence) {
93
+ console.log(` [${board.active_sequence.id}] ${board.active_sequence.name} (${board.active_sequence.status})`);
94
+ for (const item of board.active_sequence.items.slice(0, 10)) {
95
+ const lane = item.lane ? ` lane=${item.lane}` : '';
96
+ const hardAfter = item.hard_after.length ? ` hard_after=${item.hard_after.join(',')}` : '';
97
+ const softAfter = item.soft_after.length ? ` soft_after=${item.soft_after.join(',')}` : '';
98
+ console.log(` #${item.rank} ${item.planId}${lane}${hardAfter}${softAfter}`);
99
+ }
47
100
  }
48
101
  console.log('');
49
102
  const sessionMetaHint = board.session_meta_hidden > 0 ? ` (+${board.session_meta_hidden} session lifecycle notes hidden — use --include-session-meta to show)` : '';
@@ -102,5 +155,19 @@ export function runAgentBoard(options = {}) {
102
155
  }
103
156
  }
104
157
  }
158
+ // Stale-memory summary — non-destructive. Shown after the core board
159
+ // so the signal is visible but does not push the primary state off
160
+ // the screen.
161
+ if (staleReport && staleReport.warnings.length > 0) {
162
+ console.log('');
163
+ console.log(`⚠ Stale memory: ${staleSummary(staleReport)}`);
164
+ for (const w of staleReport.warnings.slice(0, 5)) {
165
+ console.log(` [${w.entity} ${w.id}] ${w.reason}`);
166
+ console.log(` → ${w.suggested_action}`);
167
+ }
168
+ if (staleReport.warnings.length > 5) {
169
+ console.log(` … and ${staleReport.warnings.length - 5} more (run \`brainclaw doctor\` for the full list).`);
170
+ }
171
+ }
105
172
  }
106
173
  //# sourceMappingURL=agent-board.js.map
@@ -1,10 +1,15 @@
1
1
  import { readAuditLog } from '../core/audit.js';
2
+ import { buildGovernanceReport, renderGovernanceMarkdown } from '../core/governance.js';
2
3
  import { memoryExists } from '../core/io.js';
3
4
  export function runAuditCommand(options = {}) {
4
5
  if (!memoryExists()) {
5
6
  console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
6
7
  process.exit(1);
7
8
  }
9
+ if (options.governance) {
10
+ runGovernanceReport(options);
11
+ return;
12
+ }
8
13
  const entries = readAuditLog({
9
14
  since: options.since,
10
15
  actor: options.actor,
@@ -27,9 +32,23 @@ export function runAuditCommand(options = {}) {
27
32
  parts.push(`→ ${entry.item_id}`);
28
33
  if (entry.item_type)
29
34
  parts.push(`(${entry.item_type})`);
35
+ if (entry.scope)
36
+ parts.push(`scope:${entry.scope}`);
30
37
  if (entry.reason)
31
38
  parts.push(`| ${entry.reason}`);
32
39
  console.log(parts.join(' '));
33
40
  }
34
41
  }
42
+ function runGovernanceReport(options) {
43
+ const report = buildGovernanceReport({
44
+ scope: options.scope,
45
+ agent: options.actor,
46
+ since: options.since,
47
+ });
48
+ if (options.json) {
49
+ console.log(JSON.stringify(report, null, 2));
50
+ return;
51
+ }
52
+ console.log(renderGovernanceMarkdown(report));
53
+ }
35
54
  //# sourceMappingURL=audit.js.map
@@ -0,0 +1,54 @@
1
+ import { memoryExists } from '../core/io.js';
2
+ import { checkPolicy } from '../core/policy.js';
3
+ export function runCheckPolicy(options) {
4
+ if (!memoryExists(options.cwd)) {
5
+ console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
6
+ process.exit(1);
7
+ }
8
+ const result = checkPolicy({
9
+ scope: options.scope,
10
+ agent: options.agent,
11
+ agentId: options.agentId,
12
+ action: options.action,
13
+ cwd: options.cwd,
14
+ });
15
+ if (options.json) {
16
+ console.log(JSON.stringify(result, null, 2));
17
+ process.exit(result.allowed ? 0 : 1);
18
+ }
19
+ printResult(result, options.scope);
20
+ process.exit(result.allowed ? 0 : 1);
21
+ }
22
+ function printResult(result, scope) {
23
+ const status = result.allowed ? '✔ ALLOWED' : '✘ BLOCKED';
24
+ console.log(`\nPolicy check for scope "${scope}": ${status}\n`);
25
+ if (result.blocks.length > 0) {
26
+ console.log('Blocks:');
27
+ for (const b of result.blocks) {
28
+ console.log(` ✘ [${b.kind}] ${b.message}`);
29
+ }
30
+ console.log('');
31
+ }
32
+ if (result.warnings.length > 0) {
33
+ console.log('Warnings:');
34
+ for (const w of result.warnings) {
35
+ const idLabel = w.id ? ` (${w.id})` : '';
36
+ console.log(` ⚠ [${w.kind}]${idLabel} ${w.message}`);
37
+ }
38
+ console.log('');
39
+ }
40
+ if (result.blocks.length === 0 && result.warnings.length === 0) {
41
+ console.log(' No issues found.');
42
+ console.log('');
43
+ }
44
+ const ctx = result.governance_context;
45
+ if (ctx.active_instructions.length > 0) {
46
+ console.log(`Governance context: ${ctx.active_instructions.length} active instruction(s)`);
47
+ for (const ins of ctx.active_instructions) {
48
+ const layerLabel = ins.layer === 'global' ? '[global]' : `[${ins.layer}:${ins.scope ?? '*'}]`;
49
+ console.log(` ${layerLabel} ${ins.text.slice(0, 120)}${ins.text.length > 120 ? '…' : ''}`);
50
+ }
51
+ console.log('');
52
+ }
53
+ }
54
+ //# sourceMappingURL=check-policy.js.map
@@ -0,0 +1,145 @@
1
+ import { memoryExists, memoryPath } from '../core/io.js';
2
+ import { loadConfig } from '../core/config.js';
3
+ import { SecurityCache } from '../core/security-cache.js';
4
+ import { querySocketScores } from '../core/socket-client.js';
5
+ import { evaluateBatch, worstDecision } from '../core/security-scoring.js';
6
+ import { createTrap } from '../core/operations/memory-write.js';
7
+ export async function handleCheckSecurity(args, cwd) {
8
+ if (!memoryExists(cwd)) {
9
+ return { content: [{ type: 'text', text: 'Error: .brainclaw/ not found. Run brainclaw init first.' }] };
10
+ }
11
+ const config = loadConfig(cwd);
12
+ const preinstall = config.security?.preinstall;
13
+ if (!preinstall?.enabled) {
14
+ return { content: [{ type: 'text', text: 'Security preinstall checks are not enabled. Set security.preinstall.enabled: true in config.yaml.' }] };
15
+ }
16
+ const packagesStr = String(args.packages ?? '').trim();
17
+ if (!packagesStr) {
18
+ return { content: [{ type: 'text', text: 'Error: missing required argument: packages' }] };
19
+ }
20
+ const ecosystem = (String(args.ecosystem ?? 'npm').trim());
21
+ const packageNames = packagesStr.split(',').map(p => p.trim()).filter(Boolean);
22
+ // Build cache
23
+ const cachePath = memoryPath('security/cache.json', cwd);
24
+ const cache = new SecurityCache(cachePath, preinstall.cache_ttl_hours);
25
+ // Separate cached vs uncached
26
+ const queries = [];
27
+ const cachedScores = [];
28
+ for (const name of packageNames) {
29
+ const [depname, version] = parsePackageName(name);
30
+ const cached = cache.get(ecosystem, depname, version);
31
+ if (cached) {
32
+ cachedScores.push(cached);
33
+ }
34
+ else {
35
+ queries.push({ depname, ecosystem, ...(version !== 'latest' ? { version } : {}) });
36
+ }
37
+ }
38
+ // Fetch uncached from Socket
39
+ let fetchedScores = [];
40
+ let fetchError = null;
41
+ if (queries.length > 0) {
42
+ try {
43
+ fetchedScores = await querySocketScores(queries, { endpoint: preinstall.socket_endpoint });
44
+ for (const s of fetchedScores) {
45
+ const eco = s.purl.startsWith('pkg:pypi') ? 'pypi' : 'npm';
46
+ const depname = s.purl.replace(/^pkg:\w+\//, '');
47
+ cache.set(eco, depname, s.version, s);
48
+ }
49
+ cache.flush();
50
+ }
51
+ catch (err) {
52
+ fetchError = err instanceof Error ? err.message : String(err);
53
+ }
54
+ }
55
+ const allScores = [...cachedScores, ...fetchedScores];
56
+ if (allScores.length === 0 && fetchError) {
57
+ const fallback = preinstall.fallback_on_error;
58
+ return {
59
+ content: [{ type: 'text', text: `Socket MCP error: ${fetchError}\nFallback policy: ${fallback}` }],
60
+ structuredContent: { error: fetchError, fallback, decision: fallback === 'block' ? 'block' : fallback === 'warn' ? 'warn' : 'pass' },
61
+ };
62
+ }
63
+ const verdicts = evaluateBatch(allScores, preinstall);
64
+ const worst = worstDecision(verdicts);
65
+ // Build text output
66
+ const lines = [];
67
+ if (fetchError) {
68
+ lines.push(`\u26A0 Socket MCP partial error: ${fetchError} (${cachedScores.length} results from cache)`);
69
+ lines.push('');
70
+ }
71
+ for (const v of verdicts) {
72
+ const icon = v.decision === 'pass' ? '\u2705' : v.decision === 'warn' ? '\u26A0\uFE0F' : '\uD83D\uDED1';
73
+ lines.push(`${icon} ${v.ecosystem}/${v.package}@${v.version} \u2014 composite=${v.composite} [${v.decision.toUpperCase()}]`);
74
+ lines.push(` SC=${v.scores.supplyChain} vuln=${v.scores.vulnerability} qual=${v.scores.quality} maint=${v.scores.maintenance} lic=${v.scores.license}`);
75
+ for (const r of v.reasons) {
76
+ lines.push(` \u2192 ${r}`);
77
+ }
78
+ }
79
+ // Auto-create traps for WARN and BLOCK verdicts
80
+ const trapsCreated = [];
81
+ for (const v of verdicts) {
82
+ if (v.decision === 'pass')
83
+ continue;
84
+ const severity = v.decision === 'block' ? 'high' : 'medium';
85
+ const trapText = `Security ${v.decision.toUpperCase()}: ${v.ecosystem}/${v.package}@${v.version} — composite=${v.composite}, SC=${v.scores.supplyChain}, vuln=${v.scores.vulnerability}. ${v.reasons.join('; ')}`;
86
+ try {
87
+ const result = createTrap({
88
+ text: trapText,
89
+ author: 'brainclaw-security',
90
+ severity: severity,
91
+ tags: ['security', 'supply-chain', `decision:${v.decision}`, v.ecosystem],
92
+ expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
93
+ }, cwd);
94
+ trapsCreated.push(result.id);
95
+ }
96
+ catch {
97
+ // Non-critical: trap creation failure should not block the security check
98
+ }
99
+ }
100
+ if (trapsCreated.length > 0) {
101
+ lines.push('');
102
+ lines.push(`Created ${trapsCreated.length} security trap(s): ${trapsCreated.join(', ')}`);
103
+ }
104
+ lines.push('');
105
+ lines.push(`Overall decision: ${worst.toUpperCase()}`);
106
+ return {
107
+ content: [{ type: 'text', text: lines.join('\n') }],
108
+ structuredContent: {
109
+ verdicts: verdicts.map(v => ({
110
+ package: v.package,
111
+ ecosystem: v.ecosystem,
112
+ version: v.version,
113
+ composite: v.composite,
114
+ decision: v.decision,
115
+ reasons: v.reasons,
116
+ scores: {
117
+ supplyChain: v.scores.supplyChain,
118
+ vulnerability: v.scores.vulnerability,
119
+ quality: v.scores.quality,
120
+ maintenance: v.scores.maintenance,
121
+ license: v.scores.license,
122
+ },
123
+ })),
124
+ decision: worst,
125
+ ...(fetchError ? { fetch_error: fetchError } : {}),
126
+ },
127
+ };
128
+ }
129
+ function parsePackageName(name) {
130
+ // Handle scoped packages: @scope/pkg@version
131
+ if (name.startsWith('@')) {
132
+ const lastAt = name.lastIndexOf('@', name.length - 1);
133
+ if (lastAt > 0) {
134
+ return [name.slice(0, lastAt), name.slice(lastAt + 1)];
135
+ }
136
+ return [name, 'latest'];
137
+ }
138
+ // Regular: pkg@version
139
+ if (name.includes('@')) {
140
+ const [depname, version] = name.split('@');
141
+ return [depname, version || 'latest'];
142
+ }
143
+ return [name, 'latest'];
144
+ }
145
+ //# sourceMappingURL=check-security-mcp.js.map
@@ -0,0 +1,106 @@
1
+ import { memoryExists, memoryPath } from '../core/io.js';
2
+ import { loadConfig } from '../core/config.js';
3
+ import { SecurityCache } from '../core/security-cache.js';
4
+ import { querySocketScores } from '../core/socket-client.js';
5
+ import { evaluateBatch, worstDecision } from '../core/security-scoring.js';
6
+ export async function runCheckSecurity(options) {
7
+ if (!memoryExists(options.cwd)) {
8
+ console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
9
+ process.exit(1);
10
+ }
11
+ const config = loadConfig(options.cwd);
12
+ const preinstall = config.security?.preinstall;
13
+ if (!preinstall?.enabled) {
14
+ console.error('Security preinstall checks are not enabled. Set security.preinstall.enabled: true in config.yaml');
15
+ process.exit(1);
16
+ }
17
+ const ecosystem = options.ecosystem ?? 'npm';
18
+ const packageNames = options.packages.split(',').map(p => p.trim()).filter(Boolean);
19
+ if (packageNames.length === 0) {
20
+ console.error('No packages specified.');
21
+ process.exit(1);
22
+ }
23
+ // Build cache
24
+ const cachePath = memoryPath('security/cache.json', options.cwd);
25
+ const cache = new SecurityCache(cachePath, preinstall.cache_ttl_hours);
26
+ // Separate cached vs uncached
27
+ const queries = [];
28
+ const cachedResults = [];
29
+ for (const name of packageNames) {
30
+ const [depname, version] = name.includes('@') && !name.startsWith('@')
31
+ ? name.split('@')
32
+ : name.startsWith('@')
33
+ ? [name.slice(0, name.lastIndexOf('@')) || name, name.slice(name.lastIndexOf('@') + 1) || 'latest']
34
+ : [name, 'latest'];
35
+ const cached = cache.get(ecosystem, depname, version);
36
+ const query = { depname, ecosystem, ...(version !== 'latest' ? { version } : {}) };
37
+ if (cached) {
38
+ cachedResults.push({ query, scores: cached });
39
+ }
40
+ else {
41
+ queries.push(query);
42
+ }
43
+ }
44
+ // Fetch uncached from Socket
45
+ let fetchedScores = [];
46
+ if (queries.length > 0) {
47
+ try {
48
+ fetchedScores = await querySocketScores(queries, { endpoint: preinstall.socket_endpoint });
49
+ // Update cache
50
+ for (const s of fetchedScores) {
51
+ const eco = s.purl.startsWith('pkg:pypi') ? 'pypi' : 'npm';
52
+ const depname = s.purl.replace(/^pkg:\w+\//, '');
53
+ cache.set(eco, depname, s.version, s);
54
+ }
55
+ cache.flush();
56
+ }
57
+ catch (err) {
58
+ const msg = err instanceof Error ? err.message : String(err);
59
+ if (preinstall.fallback_on_error === 'block') {
60
+ console.error(`Socket MCP error: ${msg} — blocking (fallback=block)`);
61
+ process.exit(2);
62
+ }
63
+ if (preinstall.fallback_on_error === 'warn') {
64
+ console.error(`Socket MCP error: ${msg} — allowing with warning (fallback=warn)`);
65
+ }
66
+ // fallback=pass: silent continue
67
+ if (cachedResults.length === 0) {
68
+ process.exit(preinstall.fallback_on_error === 'warn' ? 1 : 0);
69
+ }
70
+ }
71
+ }
72
+ // Combine cached + fetched scores
73
+ const allScores = [
74
+ ...cachedResults.filter(c => c.scores !== null).map(c => c.scores),
75
+ ...fetchedScores,
76
+ ];
77
+ const verdicts = evaluateBatch(allScores, preinstall);
78
+ const worst = worstDecision(verdicts);
79
+ if (options.json) {
80
+ console.log(JSON.stringify({ verdicts, decision: worst }, null, 2));
81
+ }
82
+ else {
83
+ printVerdicts(verdicts);
84
+ }
85
+ // Exit codes: 0=pass, 1=warn, 2=block
86
+ if (worst === 'block')
87
+ process.exit(2);
88
+ if (worst === 'warn')
89
+ process.exit(1);
90
+ process.exit(0);
91
+ }
92
+ function printVerdicts(verdicts) {
93
+ if (verdicts.length === 0) {
94
+ console.log('No packages to check.');
95
+ return;
96
+ }
97
+ for (const v of verdicts) {
98
+ const icon = v.decision === 'pass' ? '\u2705' : v.decision === 'warn' ? '\u26A0\uFE0F' : '\uD83D\uDED1';
99
+ console.log(`${icon} ${v.ecosystem}/${v.package}@${v.version} — composite=${v.composite} [${v.decision.toUpperCase()}]`);
100
+ console.log(` SC=${v.scores.supplyChain} vuln=${v.scores.vulnerability} qual=${v.scores.quality} maint=${v.scores.maintenance} lic=${v.scores.license}`);
101
+ for (const r of v.reasons) {
102
+ console.log(` \u2192 ${r}`);
103
+ }
104
+ }
105
+ }
106
+ //# sourceMappingURL=check-security.js.map
@@ -24,6 +24,7 @@ export function runClaimResource(subcommand, args, options) {
24
24
  plan: options.plan,
25
25
  agent: options.agent,
26
26
  cwd: options.cwd,
27
+ localOnly: options.localOnly,
27
28
  });
28
29
  return;
29
30
  }