brainclaw 0.28.0 → 1.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +193 -170
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +683 -23
- package/dist/commands/accept.js +3 -0
- package/dist/commands/add-step.js +11 -26
- package/dist/commands/agent-board.js +70 -3
- package/dist/commands/audit.js +19 -0
- package/dist/commands/check-policy.js +54 -0
- package/dist/commands/check-security-mcp.js +145 -0
- package/dist/commands/check-security.js +106 -0
- package/dist/commands/claim-resource.js +1 -0
- package/dist/commands/codev.js +672 -0
- package/dist/commands/compact.js +74 -0
- package/dist/commands/complete-step.js +16 -26
- package/dist/commands/constraint.js +8 -20
- package/dist/commands/decision.js +9 -20
- package/dist/commands/delete-plan.js +10 -12
- package/dist/commands/delete-step.js +16 -0
- package/dist/commands/dispatch.js +163 -0
- package/dist/commands/doctor.js +1122 -49
- package/dist/commands/enable-agent.js +1 -0
- package/dist/commands/export.js +280 -22
- package/dist/commands/handoff.js +33 -0
- package/dist/commands/harvest.js +189 -0
- package/dist/commands/hooks.js +82 -25
- package/dist/commands/inbox.js +169 -0
- package/dist/commands/init.js +38 -31
- package/dist/commands/install-hooks.js +71 -44
- package/dist/commands/link.js +89 -0
- package/dist/commands/list-claims.js +48 -3
- package/dist/commands/list-plans.js +129 -25
- package/dist/commands/loops-handlers.js +409 -0
- package/dist/commands/mcp-read-handlers.js +1628 -0
- package/dist/commands/mcp-schemas.generated.js +74 -0
- package/dist/commands/mcp.js +4244 -1475
- package/dist/commands/plan-resource.js +64 -0
- package/dist/commands/plan.js +12 -26
- package/dist/commands/prune.js +37 -2
- package/dist/commands/reflect.js +20 -7
- package/dist/commands/release-claim.js +11 -6
- package/dist/commands/release-notes.js +170 -0
- package/dist/commands/repair.js +210 -0
- package/dist/commands/run-profile.js +57 -0
- package/dist/commands/sequence.js +113 -0
- package/dist/commands/session-end.js +423 -14
- package/dist/commands/session-start.js +214 -41
- package/dist/commands/setup-security.js +103 -0
- package/dist/commands/setup.js +42 -4
- package/dist/commands/stale.js +109 -0
- package/dist/commands/switch.js +131 -10
- package/dist/commands/trap.js +14 -31
- package/dist/commands/update-handoff.js +63 -4
- package/dist/commands/update-plan.js +21 -28
- package/dist/commands/update-step.js +37 -0
- package/dist/commands/upgrade.js +313 -6
- package/dist/commands/usage.js +102 -0
- package/dist/commands/version.js +20 -0
- package/dist/commands/who.js +124 -0
- package/dist/commands/worktree.js +105 -0
- package/dist/core/actions.js +315 -0
- package/dist/core/agent-capability.js +610 -17
- package/dist/core/agent-context.js +7 -1
- package/dist/core/agent-files.js +1169 -85
- package/dist/core/agent-integrations.js +160 -5
- package/dist/core/agent-inventory.js +2 -0
- package/dist/core/agent-profiles.js +93 -0
- package/dist/core/agent-registry.js +162 -30
- package/dist/core/agentrun-reconciler.js +345 -0
- package/dist/core/agentruns.js +424 -0
- package/dist/core/ai-agent-detection.js +31 -10
- package/dist/core/archival.js +77 -0
- package/dist/core/assignment-sweeper.js +82 -0
- package/dist/core/assignments.js +367 -0
- package/dist/core/audit.js +30 -0
- package/dist/core/bootstrap.js +61 -10
- package/dist/core/brainclaw-version.js +94 -2
- package/dist/core/candidates.js +93 -2
- package/dist/core/claims.js +419 -0
- package/dist/core/codev-metrics.js +77 -0
- package/dist/core/codev-personas.js +31 -0
- package/dist/core/codev-plan-gen.js +35 -0
- package/dist/core/codev-prompts.js +74 -0
- package/dist/core/codev-responses.js +62 -0
- package/dist/core/codev-rounds.js +218 -0
- package/dist/core/config.js +4 -0
- package/dist/core/context.js +454 -34
- package/dist/core/coordination.js +201 -6
- package/dist/core/cross-project.js +230 -16
- package/dist/core/default-profiles/doctor.yaml +11 -0
- package/dist/core/default-profiles/janitor.yaml +11 -0
- package/dist/core/default-profiles/onboarder.yaml +11 -0
- package/dist/core/default-profiles/reviewer.yaml +13 -0
- package/dist/core/dispatcher.js +1189 -0
- package/dist/core/duplicates.js +2 -2
- package/dist/core/entity-operations.js +450 -0
- package/dist/core/entity-registry.js +344 -0
- package/dist/core/event-log.js +1 -0
- package/dist/core/events.js +106 -2
- package/dist/core/execution-adapters.js +154 -0
- package/dist/core/execution-context.js +63 -0
- package/dist/core/execution-profile.js +270 -0
- package/dist/core/execution.js +255 -0
- package/dist/core/facade-schema.js +81 -0
- package/dist/core/federation-cloud.js +99 -0
- package/dist/core/federation-message.js +52 -0
- package/dist/core/federation-transport.js +65 -0
- package/dist/core/gc-semantic.js +482 -0
- package/dist/core/governance.js +247 -0
- package/dist/core/guards.js +19 -0
- package/dist/core/ideation.js +72 -0
- package/dist/core/identity.js +252 -28
- package/dist/core/ids.js +6 -0
- package/dist/core/input-validation.js +2 -2
- package/dist/core/instruction-templates.js +344 -136
- package/dist/core/io.js +90 -11
- package/dist/core/lock.js +6 -2
- package/dist/core/loops/brief-assembly.js +213 -0
- package/dist/core/loops/facade-schema.js +148 -0
- package/dist/core/loops/index.js +7 -0
- package/dist/core/loops/iteration-engine.js +139 -0
- package/dist/core/loops/lock.js +385 -0
- package/dist/core/loops/store.js +201 -0
- package/dist/core/loops/types.js +403 -0
- package/dist/core/loops/verbs.js +534 -0
- package/dist/core/markdown.js +15 -3
- package/dist/core/memory-compactor.js +432 -0
- package/dist/core/memory-git.js +152 -8
- package/dist/core/messaging.js +278 -0
- package/dist/core/migration.js +32 -1
- package/dist/core/mutation-pipeline.js +4 -2
- package/dist/core/operations/memory-mutation.js +129 -0
- package/dist/core/operations/memory-write.js +78 -0
- package/dist/core/operations/plan.js +190 -0
- package/dist/core/policy.js +169 -0
- package/dist/core/repo-analysis.js +67 -0
- package/dist/core/reputation.js +9 -3
- package/dist/core/schema.js +546 -21
- package/dist/core/search.js +21 -2
- package/dist/core/security-cache.js +71 -0
- package/dist/core/security-guard.js +152 -0
- package/dist/core/security-scoring.js +86 -0
- package/dist/core/sequence.js +130 -0
- package/dist/core/socket-client.js +113 -0
- package/dist/core/staleness.js +246 -0
- package/dist/core/state.js +98 -22
- package/dist/core/store-resolution.js +54 -12
- package/dist/core/toml-writer.js +76 -0
- package/dist/core/upgrades/backup.js +232 -0
- package/dist/core/upgrades/health-check.js +169 -0
- package/dist/core/upgrades/patches/candidate-archive.js +145 -0
- package/dist/core/upgrades/patches/handoff-review-strip.js +128 -0
- package/dist/core/upgrades/patches/provenance-rollout.js +136 -0
- package/dist/core/upgrades/schema-version.js +97 -0
- package/dist/core/worktree.js +606 -0
- package/dist/facts.js +114 -0
- package/dist/facts.json +111 -0
- package/docs/architecture/project-refs.md +5 -1
- package/docs/cli.md +690 -43
- package/docs/concepts/ideation-loop.md +317 -0
- package/docs/concepts/loop-engine.md +456 -0
- package/docs/concepts/mcp-governance.md +268 -0
- package/docs/concepts/memory-staleness.md +122 -0
- package/docs/concepts/multi-agent-workflows.md +166 -0
- package/docs/concepts/plans-and-claims.md +31 -6
- package/docs/concepts/project-md-convention.md +35 -0
- package/docs/concepts/troubleshooting.md +220 -0
- package/docs/concepts/upgrade-cli.md +202 -0
- package/docs/concepts/upgrade-dogfood-procedure.md +114 -0
- package/docs/context-format-changelog.md +2 -2
- package/docs/context-format.md +2 -2
- package/docs/index.md +68 -0
- package/docs/integrations/agents.md +15 -16
- package/docs/integrations/cline.md +88 -0
- package/docs/integrations/codex.md +75 -23
- package/docs/integrations/continue.md +60 -0
- package/docs/integrations/copilot.md +67 -9
- package/docs/integrations/kilocode.md +72 -0
- package/docs/integrations/mcp.md +304 -21
- package/docs/integrations/mistral-vibe.md +122 -0
- package/docs/integrations/opencode.md +84 -0
- package/docs/integrations/overview.md +23 -8
- package/docs/integrations/roo.md +74 -0
- package/docs/integrations/windsurf.md +83 -0
- package/docs/mcp-schema-changelog.md +191 -1
- package/docs/playbooks/integration/index.md +121 -0
- package/docs/playbooks/productivity/index.md +102 -0
- package/docs/playbooks/team/index.md +122 -0
- package/docs/product/agent-first-model.md +184 -0
- package/docs/product/entity-model-audit.md +462 -0
- package/docs/quickstart-existing-project.md +135 -0
- package/docs/quickstart.md +124 -37
- package/docs/release-maintenance.md +79 -0
- package/docs/review.md +2 -0
- package/docs/server-operations.md +118 -0
- package/package.json +20 -12
- package/dist/commands/claude-desktop-extension.js +0 -18
- package/dist/commands/diff.js +0 -99
- package/dist/core/claude-desktop-extension.js +0 -224
package/dist/commands/accept.js
CHANGED
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
console.
|
|
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(
|
|
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
|
-
|
|
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
|
package/dist/commands/audit.js
CHANGED
|
@@ -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
|