brainclaw 1.8.0 → 1.9.1
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 +592 -505
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +138 -13
- package/dist/commands/add-step.js +1 -1
- package/dist/commands/bootstrap.js +2 -26
- package/dist/commands/check-security-mcp.js +50 -33
- package/dist/commands/check-security.js +86 -43
- package/dist/commands/claim.js +22 -21
- package/dist/commands/confirm.js +26 -0
- package/dist/commands/context-diff.js +1 -1
- package/dist/commands/dispatch-watch.js +142 -0
- package/dist/commands/doctor.js +113 -2
- package/dist/commands/estimation-report.js +115 -16
- package/dist/commands/harvest.js +286 -23
- package/dist/commands/hooks.js +73 -73
- package/dist/commands/init.js +124 -22
- package/dist/commands/install-hooks.js +78 -78
- package/dist/commands/loops-handlers.js +4 -0
- package/dist/commands/mcp-read-handlers.js +253 -41
- package/dist/commands/mcp.js +664 -102
- package/dist/commands/memory.js +21 -17
- package/dist/commands/migrate.js +81 -17
- package/dist/commands/prune.js +78 -4
- package/dist/commands/reflect.js +26 -20
- package/dist/commands/register-agent.js +57 -1
- package/dist/commands/repair.js +20 -0
- package/dist/commands/session-end.js +15 -6
- package/dist/commands/session-start.js +18 -1
- package/dist/commands/setup-security.js +39 -18
- package/dist/commands/setup.js +26 -27
- package/dist/commands/stale.js +16 -2
- package/dist/commands/switch.js +26 -5
- package/dist/commands/uninstall.js +126 -34
- package/dist/commands/update-step.js +6 -0
- package/dist/commands/version.js +1 -1
- package/dist/commands/worktree.js +60 -0
- package/dist/core/actions.js +12 -3
- package/dist/core/agent-capability.js +30 -17
- package/dist/core/agent-files.js +963 -666
- package/dist/core/agent-integrations.js +0 -3
- package/dist/core/agent-inventory.js +67 -0
- package/dist/core/agent-registry.js +163 -29
- package/dist/core/agentrun-reconciler.js +33 -2
- package/dist/core/agentruns.js +7 -1
- package/dist/core/ai-agent-detection.js +31 -44
- package/dist/core/archival.js +15 -9
- package/dist/core/assignment-reconciler.js +56 -0
- package/dist/core/assignment-sweeper.js +127 -4
- package/dist/core/assignments.js +69 -11
- package/dist/core/bootstrap.js +233 -67
- package/dist/core/brainclaw-version.js +22 -0
- package/dist/core/candidates.js +21 -1
- package/dist/core/claims.js +313 -150
- package/dist/core/codev-prompts.js +38 -38
- package/dist/core/config.js +6 -1
- package/dist/core/context-diff.js +148 -20
- package/dist/core/context.js +129 -8
- package/dist/core/coordination.js +22 -3
- package/dist/core/default-profiles/doctor.yaml +11 -11
- package/dist/core/default-profiles/janitor.yaml +11 -11
- package/dist/core/default-profiles/onboarder.yaml +11 -11
- package/dist/core/default-profiles/reviewer.yaml +13 -13
- package/dist/core/dispatch-status.js +79 -5
- package/dist/core/dispatcher.js +65 -12
- package/dist/core/entity-operations.js +74 -27
- package/dist/core/entity-registry.js +31 -5
- package/dist/core/event-log.js +138 -21
- package/dist/core/events/checkpoint.js +258 -0
- package/dist/core/events/genesis.js +220 -0
- package/dist/core/events/journal.js +507 -0
- package/dist/core/events/materialize.js +126 -0
- package/dist/core/events/registry-post-image.js +110 -0
- package/dist/core/events/verify.js +109 -0
- package/dist/core/execution-adapters.js +23 -0
- package/dist/core/execution.js +1 -1
- package/dist/core/facade-schema.js +38 -0
- package/dist/core/gc-semantic.js +130 -5
- package/dist/core/handoff-snapshot.js +68 -0
- package/dist/core/ids.js +19 -8
- package/dist/core/instruction-templates.js +34 -115
- package/dist/core/io.js +39 -3
- package/dist/core/json-store.js +10 -1
- package/dist/core/lock.js +153 -28
- package/dist/core/loops/bootstrap-acquire.js +25 -1
- package/dist/core/loops/facade-schema.js +2 -0
- package/dist/core/loops/hooks/survey-signals-baseline.js +36 -0
- package/dist/core/loops/index.js +1 -0
- package/dist/core/loops/presets/bootstrap.js +7 -0
- package/dist/core/loops/store.js +17 -0
- package/dist/core/loops/verbs.js +24 -2
- package/dist/core/markdown.js +8 -76
- package/dist/core/mcp-command-resolution.js +245 -0
- package/dist/core/memory-compactor.js +5 -3
- package/dist/core/memory-lifecycle.js +282 -0
- package/dist/core/merge-risk.js +150 -0
- package/dist/core/messaging.js +10 -3
- package/dist/core/migration.js +11 -1
- package/dist/core/observer-mode.js +26 -0
- package/dist/core/operations/memory-mutation.js +90 -65
- package/dist/core/operations/plan.js +27 -1
- package/dist/core/protocol-skills.js +210 -0
- package/dist/core/reflection-safety.js +6 -7
- package/dist/core/reputation.js +84 -2
- package/dist/core/runtime-signals.js +72 -10
- package/dist/core/runtime.js +84 -1
- package/dist/core/schema.js +114 -0
- package/dist/core/search.js +19 -2
- package/dist/core/security-detectors.js +125 -0
- package/dist/core/security-extract.js +189 -0
- package/dist/core/security-guard.js +217 -139
- package/dist/core/security-packages.js +121 -0
- package/dist/core/security-scoring.js +76 -9
- package/dist/core/security.js +34 -2
- package/dist/core/sequence.js +11 -2
- package/dist/core/setup-flow.js +141 -13
- package/dist/core/spawn-check.js +16 -2
- package/dist/core/staleness.js +73 -2
- package/dist/core/state.js +250 -54
- package/dist/core/store-resolution.js +45 -12
- package/dist/core/worktree.js +90 -26
- package/dist/facts.js +8 -8
- package/dist/facts.json +7 -7
- package/docs/PROTOCOL.md +223 -0
- package/docs/adapters/openclaw.md +43 -43
- package/docs/architecture/project-refs.md +328 -328
- package/docs/cli.md +2097 -2096
- package/docs/concepts/coordination.md +52 -52
- package/docs/concepts/coordinator-runbook.md +129 -0
- package/docs/concepts/dispatch-lifecycle.md +245 -245
- package/docs/concepts/event-log-store.md +928 -0
- package/docs/concepts/ideation-loop.md +317 -317
- package/docs/concepts/loop-engine.md +520 -511
- package/docs/concepts/mcp-governance.md +268 -268
- package/docs/concepts/memory.md +89 -88
- package/docs/concepts/multi-agent-workflows.md +167 -167
- package/docs/concepts/observer-protocol.md +361 -0
- package/docs/concepts/parallel-merge-protocol.md +71 -0
- package/docs/concepts/plans-and-claims.md +217 -174
- package/docs/concepts/project-md-convention.md +35 -35
- package/docs/concepts/runtime-notes.md +38 -38
- package/docs/concepts/skills.md +78 -0
- package/docs/concepts/troubleshooting.md +254 -254
- package/docs/concepts/workspace-bootstrapping.md +142 -81
- package/docs/context-format-changelog.md +35 -35
- package/docs/context-format.md +48 -48
- package/docs/index.md +65 -65
- package/docs/integrations/agents.md +162 -162
- package/docs/integrations/claude-code.md +23 -23
- package/docs/integrations/cline.md +87 -88
- package/docs/integrations/codex.md +2 -2
- package/docs/integrations/continue.md +60 -60
- package/docs/integrations/copilot.md +82 -80
- package/docs/integrations/cursor.md +23 -23
- package/docs/integrations/kilocode.md +72 -72
- package/docs/integrations/mcp.md +377 -377
- package/docs/integrations/mistral-vibe.md +122 -122
- package/docs/integrations/openclaw.md +99 -98
- package/docs/integrations/opencode.md +84 -84
- package/docs/integrations/overview.md +122 -122
- package/docs/integrations/roo.md +74 -74
- package/docs/integrations/windsurf.md +83 -83
- package/docs/mcp-schema-changelog.md +360 -329
- package/docs/playbooks/integration/index.md +121 -121
- package/docs/playbooks/orchestration.md +37 -0
- package/docs/playbooks/productivity/index.md +99 -99
- package/docs/playbooks/team/index.md +117 -117
- package/docs/product/agent-first-model.md +184 -184
- package/docs/product/entity-model-audit.md +462 -462
- package/docs/product/positioning.md +86 -86
- package/docs/quickstart-existing-project.md +107 -107
- package/docs/quickstart.md +148 -147
- package/docs/release-maintenance.md +79 -79
- package/docs/reputation.md +52 -52
- package/docs/review.md +45 -45
- package/docs/security.md +212 -53
- package/docs/server-operations.md +118 -118
- package/docs/storage.md +110 -108
- package/package.json +86 -69
package/dist/commands/memory.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { loadConfig } from '../core/config.js';
|
|
2
2
|
import { memoryExists } from '../core/io.js';
|
|
3
|
-
import { loadState, persistState } from '../core/state.js';
|
|
3
|
+
import { loadState, persistState, mutateState } from '../core/state.js';
|
|
4
4
|
import { scanText } from '../core/security.js';
|
|
5
5
|
import { validateCliInput } from '../core/input-validation.js';
|
|
6
6
|
import { resolveTargetStore } from '../core/store-resolution.js';
|
|
@@ -229,26 +229,30 @@ function runMemoryDelete(id, cwd) {
|
|
|
229
229
|
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
230
230
|
process.exit(1);
|
|
231
231
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
state.recent_decisions,
|
|
235
|
-
state.active_constraints,
|
|
236
|
-
state.known_traps,
|
|
237
|
-
state.open_handoffs,
|
|
238
|
-
];
|
|
232
|
+
// mutateState (RMW under the store lock + deleteMissing) so the entity file
|
|
233
|
+
// is actually unlinked — persistState alone no longer deletes absent records.
|
|
239
234
|
let deleted;
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
235
|
+
try {
|
|
236
|
+
deleted = mutateState((state) => {
|
|
237
|
+
const arrays = [
|
|
238
|
+
state.recent_decisions,
|
|
239
|
+
state.active_constraints,
|
|
240
|
+
state.known_traps,
|
|
241
|
+
state.open_handoffs,
|
|
242
|
+
];
|
|
243
|
+
for (const items of arrays) {
|
|
244
|
+
const index = items.findIndex((item) => item.id === id || item.short_label === id);
|
|
245
|
+
if (index >= 0) {
|
|
246
|
+
return items.splice(index, 1)[0];
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
throw new Error(`Memory item '${id}' not found.`);
|
|
250
|
+
}, cwd);
|
|
246
251
|
}
|
|
247
|
-
|
|
248
|
-
console.error(`Error:
|
|
252
|
+
catch (err) {
|
|
253
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
249
254
|
process.exit(1);
|
|
250
255
|
}
|
|
251
|
-
persistState(state, cwd);
|
|
252
256
|
console.log(`✔ Memory item deleted: [${deleted.id}] ${deleted.text}`);
|
|
253
257
|
}
|
|
254
258
|
function collectMemoryItems(state) {
|
package/dist/commands/migrate.js
CHANGED
|
@@ -1,21 +1,69 @@
|
|
|
1
|
-
import { loadState,
|
|
1
|
+
import { loadState, mutateState } from '../core/state.js';
|
|
2
2
|
import { memoryExists } from '../core/io.js';
|
|
3
3
|
import { resolveTargetStore } from '../core/store-resolution.js';
|
|
4
4
|
import { appendAuditEntry } from '../core/audit.js';
|
|
5
5
|
import { resolveCurrentAgentName } from '../core/agent-registry.js';
|
|
6
|
+
import { loadConfig, saveConfig } from '../core/config.js';
|
|
7
|
+
import { runGenesisMigration, runRegistryGenesisSupplement } from '../core/events/genesis.js';
|
|
6
8
|
export function runMigrate(options = {}) {
|
|
7
9
|
const cwd = options.cwd ?? process.cwd();
|
|
8
10
|
if (!memoryExists(cwd)) {
|
|
9
11
|
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
10
12
|
process.exit(1);
|
|
11
13
|
}
|
|
12
|
-
if (options.
|
|
14
|
+
if (options.enableJournal) {
|
|
15
|
+
enableJournalMode(cwd, options.dryRun ?? false);
|
|
16
|
+
}
|
|
17
|
+
else if (options.promoteMachineItems) {
|
|
13
18
|
promoteMachineItems(cwd, options.dryRun ?? false);
|
|
14
19
|
}
|
|
15
20
|
else {
|
|
16
|
-
console.log('Usage:
|
|
17
|
-
console.log('');
|
|
18
|
-
console.log('
|
|
21
|
+
console.log('Usage:');
|
|
22
|
+
console.log(' brainclaw migrate --promote-machine-items [--dry-run]');
|
|
23
|
+
console.log(' Move items tagged scope:machine from project store to user store (~/.brainclaw/).');
|
|
24
|
+
console.log(' brainclaw migrate --enable-journal [--dry-run]');
|
|
25
|
+
console.log(' Turn on the event journal (mode=dual) for this existing store and backfill it (pln#567).');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* pln#567 (decision A+D) — enable the event journal on an EXISTING store. New
|
|
30
|
+
* stores get this from `init`; this is the explicit opt-in for stores created
|
|
31
|
+
* before the cutover. Sets `store.journal.mode=dual` (an explicit user action,
|
|
32
|
+
* so it overrides a prior off) THEN runs genesis so the journal carries the
|
|
33
|
+
* full history rather than only mutations from now on (idempotent — a second
|
|
34
|
+
* run no-ops once a genesis note exists).
|
|
35
|
+
*/
|
|
36
|
+
function enableJournalMode(cwd, dryRun) {
|
|
37
|
+
const config = loadConfig(cwd);
|
|
38
|
+
const currentMode = config.store?.journal?.mode ?? 'unset';
|
|
39
|
+
if (dryRun) {
|
|
40
|
+
const planned = runGenesisMigration({ cwd, dryRun: true });
|
|
41
|
+
const plannedRegistry = runRegistryGenesisSupplement({ cwd, dryRun: true });
|
|
42
|
+
console.log(`(dry-run) Would set store.journal.mode=dual (currently ${currentMode}), backfill ${planned.backfilled} memory entit(y/ies) and ${plannedRegistry.backfilled} registry entit(y/ies), and emit the registry cutover marker (pln#568).`);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
config.store = { ...config.store, journal: { ...config.store?.journal, mode: 'dual' } };
|
|
46
|
+
saveConfig(config, cwd);
|
|
47
|
+
// Config is written first so genesis (which checks resolveJournalMode) seeds
|
|
48
|
+
// under dual and the journal stays consistent with subsequent dual-writes.
|
|
49
|
+
const result = runGenesisMigration({ cwd });
|
|
50
|
+
if (result.status === 'already_present') {
|
|
51
|
+
console.log(`✔ store.journal.mode=dual. Memory journal already seeded (genesis present).`);
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
console.log(`✔ store.journal.mode=dual and seeded: ${result.backfilled} memory entit(y/ies) backfilled across ${Object.keys(result.per_family ?? {}).length} families.`);
|
|
55
|
+
}
|
|
56
|
+
// pln#568 slice 3 — registry cutover: backfill the registry/coordination
|
|
57
|
+
// families and emit the `registry_genesis` marker so the observer can trust
|
|
58
|
+
// the journal as authoritative for claims/assignments/runs/actions (drop the
|
|
59
|
+
// board_summary MCP seed). Incremental + idempotent; safe to re-run to upgrade
|
|
60
|
+
// a store that was journal-enabled before this slice landed.
|
|
61
|
+
const registry = runRegistryGenesisSupplement({ cwd });
|
|
62
|
+
if (registry.status === 'already_present') {
|
|
63
|
+
console.log(`✔ Registry cutover marker already present — observer authority unchanged.`);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
console.log(`✔ Registry cutover: ${registry.backfilled} registry entit(y/ies) backfilled, observer can now trust the journal for coordination counts (pln#568).`);
|
|
19
67
|
}
|
|
20
68
|
}
|
|
21
69
|
function promoteMachineItems(cwd, dryRun) {
|
|
@@ -49,27 +97,43 @@ function promoteMachineItems(cwd, dryRun) {
|
|
|
49
97
|
console.error('Error: cannot resolve user store. Run `brainclaw setup` first.');
|
|
50
98
|
process.exit(1);
|
|
51
99
|
}
|
|
52
|
-
const
|
|
53
|
-
|
|
100
|
+
const movedIds = {
|
|
101
|
+
constraints: new Set(machineConstraints.map((c) => c.id)),
|
|
102
|
+
decisions: new Set(machineDecisions.map((d) => d.id)),
|
|
103
|
+
traps: new Set(machineTraps.map((t) => t.id)),
|
|
104
|
+
};
|
|
105
|
+
// Write target FIRST (a crash here leaves a duplicate, never silent loss),
|
|
106
|
+
// each side as an atomic locked RMW so concurrent writes are not clobbered.
|
|
107
|
+
mutateState((userState) => {
|
|
108
|
+
for (const c of machineConstraints) {
|
|
109
|
+
if (!userState.active_constraints.some((x) => x.id === c.id))
|
|
110
|
+
userState.active_constraints.push(c);
|
|
111
|
+
}
|
|
112
|
+
for (const d of machineDecisions) {
|
|
113
|
+
if (!userState.recent_decisions.some((x) => x.id === d.id))
|
|
114
|
+
userState.recent_decisions.push(d);
|
|
115
|
+
}
|
|
116
|
+
for (const t of machineTraps) {
|
|
117
|
+
if (!userState.known_traps.some((x) => x.id === t.id))
|
|
118
|
+
userState.known_traps.push(t);
|
|
119
|
+
}
|
|
120
|
+
}, userCwd);
|
|
121
|
+
// Then delete from source — mutateState persists with deleteMissing so the
|
|
122
|
+
// promoted entity files are actually unlinked from the project store.
|
|
123
|
+
mutateState((sourceState) => {
|
|
124
|
+
sourceState.active_constraints = sourceState.active_constraints.filter((x) => !movedIds.constraints.has(x.id));
|
|
125
|
+
sourceState.recent_decisions = sourceState.recent_decisions.filter((x) => !movedIds.decisions.has(x.id));
|
|
126
|
+
sourceState.known_traps = sourceState.known_traps.filter((x) => !movedIds.traps.has(x.id));
|
|
127
|
+
}, cwd);
|
|
54
128
|
for (const c of machineConstraints) {
|
|
55
|
-
userState.active_constraints.push(c);
|
|
56
|
-
state.active_constraints = state.active_constraints.filter((x) => x.id !== c.id);
|
|
57
129
|
appendAuditEntry({ actor: agent, action: 'update', item_id: c.id, item_type: 'constraint', reason: 'promote to user store (machine scope)' }, cwd);
|
|
58
130
|
}
|
|
59
|
-
// Move decisions
|
|
60
131
|
for (const d of machineDecisions) {
|
|
61
|
-
userState.recent_decisions.push(d);
|
|
62
|
-
state.recent_decisions = state.recent_decisions.filter((x) => x.id !== d.id);
|
|
63
132
|
appendAuditEntry({ actor: agent, action: 'update', item_id: d.id, item_type: 'decision', reason: 'promote to user store (machine scope)' }, cwd);
|
|
64
133
|
}
|
|
65
|
-
// Move traps
|
|
66
134
|
for (const t of machineTraps) {
|
|
67
|
-
userState.known_traps.push(t);
|
|
68
|
-
state.known_traps = state.known_traps.filter((x) => x.id !== t.id);
|
|
69
135
|
appendAuditEntry({ actor: agent, action: 'update', item_id: t.id, item_type: 'trap', reason: 'promote to user store (machine scope)' }, cwd);
|
|
70
136
|
}
|
|
71
|
-
persistState(userState, userCwd);
|
|
72
|
-
persistState(state, cwd);
|
|
73
137
|
console.log(`\n✔ Promoted ${total} item(s) to user store (${userCwd})`);
|
|
74
138
|
}
|
|
75
139
|
//# sourceMappingURL=migrate.js.map
|
package/dist/commands/prune.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { loadState, persistState } from '../core/state.js';
|
|
4
|
+
import { memoryExists, resolveEntityDir } from '../core/io.js';
|
|
3
5
|
import { mutate } from '../core/mutation-pipeline.js';
|
|
4
6
|
import { rebuildProjectMd } from '../core/markdown.js';
|
|
5
7
|
import { deleteRuntimeNote, listRuntimeNotes } from '../core/runtime.js';
|
|
6
|
-
import { expireStaleActiveClaims } from '../core/claims.js';
|
|
8
|
+
import { expireStaleActiveClaims, isClaimExpired, listClaims } from '../core/claims.js';
|
|
7
9
|
import { archiveStalePlansAndHandoffs } from '../core/archival.js';
|
|
8
10
|
import { rotateAuditLogIfNeeded } from '../core/audit.js';
|
|
9
11
|
import { analyzeMemory, analyzeAndApply, formatReport } from '../core/memory-compactor.js';
|
|
@@ -35,6 +37,10 @@ export function runPrune(options = {}) {
|
|
|
35
37
|
}
|
|
36
38
|
// Original prune logic
|
|
37
39
|
const now = new Date().toISOString();
|
|
40
|
+
if (options.dryRun) {
|
|
41
|
+
previewPrune(cwd, now, options);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
38
44
|
let prunedCount = 0;
|
|
39
45
|
let expiredClaimsCount = 0;
|
|
40
46
|
let expiredNotesCount = 0;
|
|
@@ -48,7 +54,9 @@ export function runPrune(options = {}) {
|
|
|
48
54
|
}
|
|
49
55
|
state.active_constraints = state.active_constraints.filter(c => c.status !== 'expired');
|
|
50
56
|
prunedCount = originalLength - state.active_constraints.length;
|
|
51
|
-
|
|
57
|
+
// deleteMissing: this RMW is atomic (loadState above runs under the same
|
|
58
|
+
// mutate() lock), so removing pruned files here cannot clobber concurrent writes.
|
|
59
|
+
persistState(state, cwd, { writeProjectMarkdown: false, deleteMissing: true });
|
|
52
60
|
expiredClaimsCount = expireStaleActiveClaims(cwd);
|
|
53
61
|
if (options.expired) {
|
|
54
62
|
const notes = listRuntimeNotes(undefined, cwd);
|
|
@@ -83,4 +91,70 @@ export function runPrune(options = {}) {
|
|
|
83
91
|
console.log(`✔ Pruned ${prunedCount} expired constraints, ${expiredClaimsCount} expired claims${archiveMsg}${rotateMsg}.`);
|
|
84
92
|
}
|
|
85
93
|
}
|
|
94
|
+
function previewPrune(cwd, now, options) {
|
|
95
|
+
const state = loadState(cwd);
|
|
96
|
+
const expiredConstraints = state.active_constraints.filter((c) => c.status === 'expired' || (c.status === 'active' && c.expires_at && c.expires_at < now));
|
|
97
|
+
const expiredClaims = listClaims(cwd).filter((claim) => claim.status === 'active' && isClaimExpired(claim));
|
|
98
|
+
const expiredNotes = options.expired
|
|
99
|
+
? listRuntimeNotes(undefined, cwd).filter((note) => note.expires_at && note.expires_at < now)
|
|
100
|
+
: [];
|
|
101
|
+
const archivePreview = options.archive ? previewArchive(cwd) : [];
|
|
102
|
+
console.log('Dry run: no files will be changed.');
|
|
103
|
+
console.log(`Would prune ${expiredConstraints.length} expired constraints.`);
|
|
104
|
+
for (const constraint of expiredConstraints) {
|
|
105
|
+
console.log(` - constraint ${constraint.id}: ${constraint.text.slice(0, 80)}`);
|
|
106
|
+
}
|
|
107
|
+
console.log(`Would release ${expiredClaims.length} expired claims.`);
|
|
108
|
+
for (const claim of expiredClaims) {
|
|
109
|
+
console.log(` - claim ${claim.id}: ${claim.scope}`);
|
|
110
|
+
}
|
|
111
|
+
if (options.expired) {
|
|
112
|
+
console.log(`Would delete ${expiredNotes.length} expired runtime notes.`);
|
|
113
|
+
for (const note of expiredNotes) {
|
|
114
|
+
console.log(` - runtime note ${note.id}: ${note.agent}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (options.archive) {
|
|
118
|
+
const total = archivePreview.reduce((sum, item) => sum + item.ids.length, 0);
|
|
119
|
+
console.log(`Would archive ${total} stale plans/handoffs.`);
|
|
120
|
+
for (const item of archivePreview) {
|
|
121
|
+
for (const id of item.ids) {
|
|
122
|
+
console.log(` - ${item.entity} ${id}`);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function previewArchive(cwd) {
|
|
128
|
+
const maxAgeMs = 30 * 24 * 60 * 60 * 1000;
|
|
129
|
+
const cutoff = new Date(Date.now() - maxAgeMs).toISOString();
|
|
130
|
+
return [
|
|
131
|
+
{
|
|
132
|
+
entity: 'plans',
|
|
133
|
+
ids: listArchiveEligibleIds(cwd, 'plans', cutoff, (item) => item.status === 'done' || item.status === 'dropped'),
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
entity: 'handoffs',
|
|
137
|
+
ids: listArchiveEligibleIds(cwd, 'handoffs', cutoff, (item) => item.status === 'closed'),
|
|
138
|
+
},
|
|
139
|
+
].filter((entry) => entry.ids.length > 0);
|
|
140
|
+
}
|
|
141
|
+
function listArchiveEligibleIds(cwd, entity, cutoffDate, isEligible) {
|
|
142
|
+
const dir = resolveEntityDir(entity, cwd, 'read');
|
|
143
|
+
if (!fs.existsSync(dir))
|
|
144
|
+
return [];
|
|
145
|
+
const ids = [];
|
|
146
|
+
for (const file of fs.readdirSync(dir).filter((entry) => entry.endsWith('.json') && entry !== 'archive.json')) {
|
|
147
|
+
try {
|
|
148
|
+
const item = JSON.parse(fs.readFileSync(path.join(dir, file), 'utf-8'));
|
|
149
|
+
const date = (item.completed_at ?? item.updated_at ?? item.created_at);
|
|
150
|
+
if (!isEligible(item))
|
|
151
|
+
continue;
|
|
152
|
+
if (date && date > cutoffDate)
|
|
153
|
+
continue;
|
|
154
|
+
ids.push(typeof item.id === 'string' ? item.id : path.basename(file, '.json'));
|
|
155
|
+
}
|
|
156
|
+
catch { /* ignore malformed files in preview */ }
|
|
157
|
+
}
|
|
158
|
+
return ids;
|
|
159
|
+
}
|
|
86
160
|
//# sourceMappingURL=prune.js.map
|
package/dist/commands/reflect.js
CHANGED
|
@@ -54,31 +54,37 @@ function runReflectBatchFromFile(filepath, baseOptions) {
|
|
|
54
54
|
: [parsed];
|
|
55
55
|
let created = 0;
|
|
56
56
|
for (const rawEvent of rawEvents) {
|
|
57
|
+
// Only event-shape parse errors are skippable here. Errors raised by
|
|
58
|
+
// createCandidateFromInput (identity resolution, strict security blocks)
|
|
59
|
+
// MUST propagate: swallowing them silently dropped sensitive content and
|
|
60
|
+
// exited 0, defeating strict-import enforcement (pln#572).
|
|
61
|
+
let event;
|
|
57
62
|
try {
|
|
58
|
-
|
|
59
|
-
if (!isReflectableRuntimeEvent(event)) {
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
const candidateType = event.candidate_type ?? mapEventTypeToCandidateType(event.event_type);
|
|
63
|
-
createCandidateFromInput(event.text, candidateType, {
|
|
64
|
-
...baseOptions,
|
|
65
|
-
type: candidateType,
|
|
66
|
-
tag: event.tags.length ? event.tags : baseOptions.tag,
|
|
67
|
-
authorId: baseOptions.authorId ?? event.agent_id,
|
|
68
|
-
projectId: baseOptions.projectId ?? event.project_id,
|
|
69
|
-
hostId: baseOptions.hostId ?? event.host_id,
|
|
70
|
-
sessionId: baseOptions.sessionId ?? event.session_id,
|
|
71
|
-
source: baseOptions.source ?? event.agent,
|
|
72
|
-
severity: baseOptions.severity ?? event.severity,
|
|
73
|
-
from: baseOptions.from ?? event.from,
|
|
74
|
-
to: baseOptions.to ?? event.to,
|
|
75
|
-
path: baseOptions.path ?? event.related_paths?.[0],
|
|
76
|
-
}, false, true, true);
|
|
77
|
-
created++;
|
|
63
|
+
event = RuntimeEventSchema.parse(rawEvent);
|
|
78
64
|
}
|
|
79
65
|
catch {
|
|
80
66
|
// skip malformed event records
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (!isReflectableRuntimeEvent(event)) {
|
|
70
|
+
continue;
|
|
81
71
|
}
|
|
72
|
+
const candidateType = event.candidate_type ?? mapEventTypeToCandidateType(event.event_type);
|
|
73
|
+
createCandidateFromInput(event.text, candidateType, {
|
|
74
|
+
...baseOptions,
|
|
75
|
+
type: candidateType,
|
|
76
|
+
tag: event.tags.length ? event.tags : baseOptions.tag,
|
|
77
|
+
authorId: baseOptions.authorId ?? event.agent_id,
|
|
78
|
+
projectId: baseOptions.projectId ?? event.project_id,
|
|
79
|
+
hostId: baseOptions.hostId ?? event.host_id,
|
|
80
|
+
sessionId: baseOptions.sessionId ?? event.session_id,
|
|
81
|
+
source: baseOptions.source ?? event.agent,
|
|
82
|
+
severity: baseOptions.severity ?? event.severity,
|
|
83
|
+
from: baseOptions.from ?? event.from,
|
|
84
|
+
to: baseOptions.to ?? event.to,
|
|
85
|
+
path: baseOptions.path ?? event.related_paths?.[0],
|
|
86
|
+
}, false, true, true);
|
|
87
|
+
created++;
|
|
82
88
|
}
|
|
83
89
|
console.log(`✔ Created ${created} candidate(s) from batch file`);
|
|
84
90
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { registerAgentIdentity, setCurrentAgentIdentity } from '../core/agent-registry.js';
|
|
1
|
+
import { listDebrisAgentIdentities, registerAgentIdentity, removeAgentIdentity, setCurrentAgentIdentity, } from '../core/agent-registry.js';
|
|
2
2
|
import { memoryExists } from '../core/io.js';
|
|
3
3
|
export function runRegisterAgent(agentName, options = {}) {
|
|
4
4
|
if (!memoryExists()) {
|
|
@@ -26,4 +26,60 @@ export function runRegisterAgent(agentName, options = {}) {
|
|
|
26
26
|
const fingerprintLabel = agent.identity_key ? `, fp=${agent.identity_key.fingerprint.slice(0, 12)}` : '';
|
|
27
27
|
console.log(`✔ Agent registered: ${agent.agent_name} (${agent.agent_id}, kind=${agent.kind}${capabilitiesLabel}${fingerprintLabel})${currentLabel}`);
|
|
28
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Guarded identity removal (pln#562 step 2). Without --force, only known
|
|
31
|
+
* debris identities (test fixtures, alias leftovers) can be removed.
|
|
32
|
+
*/
|
|
33
|
+
export function runRemoveAgent(agentNameOrId, options = {}) {
|
|
34
|
+
if (!memoryExists()) {
|
|
35
|
+
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
const removed = removeAgentIdentity(agentNameOrId, { force: options.force });
|
|
40
|
+
if (options.json) {
|
|
41
|
+
console.log(JSON.stringify({ removed: true, agent: removed }, null, 2));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
console.log(`✔ Agent identity removed: ${removed.agent_name} (${removed.agent_id})`);
|
|
45
|
+
}
|
|
46
|
+
catch (err) {
|
|
47
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
48
|
+
if (options.json) {
|
|
49
|
+
console.log(JSON.stringify({ removed: false, error: message }, null, 2));
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.error(`✖ ${message}`);
|
|
53
|
+
}
|
|
54
|
+
process.exitCode = 1;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/** List identities flagged as registration debris, without removing anything. */
|
|
58
|
+
export function runListDebrisAgents(options = {}) {
|
|
59
|
+
if (!memoryExists()) {
|
|
60
|
+
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
const debris = listDebrisAgentIdentities();
|
|
64
|
+
if (options.json) {
|
|
65
|
+
console.log(JSON.stringify({
|
|
66
|
+
debris: debris.map((d) => ({
|
|
67
|
+
agent_id: d.identity.agent_id,
|
|
68
|
+
agent_name: d.identity.agent_name,
|
|
69
|
+
trust_level: d.identity.trust_level,
|
|
70
|
+
reason: d.reason,
|
|
71
|
+
})),
|
|
72
|
+
}, null, 2));
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (debris.length === 0) {
|
|
76
|
+
console.log('✔ No debris agent identities found.');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
console.log(`⚠ ${debris.length} debris agent identit${debris.length === 1 ? 'y' : 'ies'} found:`);
|
|
80
|
+
for (const d of debris) {
|
|
81
|
+
console.log(` - ${d.identity.agent_name} (${d.identity.agent_id}): ${d.reason}`);
|
|
82
|
+
}
|
|
83
|
+
console.log('Remove with `brainclaw register-agent <name> --remove`.');
|
|
84
|
+
}
|
|
29
85
|
//# sourceMappingURL=register-agent.js.map
|
package/dist/commands/repair.js
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
import fs from 'node:fs';
|
|
27
27
|
import path from 'node:path';
|
|
28
28
|
import { memoryExists, memoryDir } from '../core/io.js';
|
|
29
|
+
import { migrateRuntimeNoteIdPrefixes } from '../core/runtime.js';
|
|
29
30
|
import { runDoctor } from './doctor.js';
|
|
30
31
|
/**
|
|
31
32
|
* Execute a single repair candidate. Returns the outcome; never throws.
|
|
@@ -66,6 +67,25 @@ function executeCandidate(candidate, cwd) {
|
|
|
66
67
|
fs.renameSync(sourceAbs, destAbs);
|
|
67
68
|
return { action: candidate.action, target: candidate.target, status: 'applied' };
|
|
68
69
|
}
|
|
70
|
+
case 'migrate_runtime_note_ids': {
|
|
71
|
+
// can_b8d53d18 — lossless rename of legacy run_-prefixed runtime note
|
|
72
|
+
// ids to rtn_ (file rename + id rewrite, no deletion of note content).
|
|
73
|
+
const migration = migrateRuntimeNoteIdPrefixes(cwd);
|
|
74
|
+
if (migration.errors.length > 0) {
|
|
75
|
+
return {
|
|
76
|
+
action: candidate.action,
|
|
77
|
+
target: candidate.target,
|
|
78
|
+
status: migration.migrated.length > 0 ? 'applied' : 'failed',
|
|
79
|
+
reason: `migrated ${migration.migrated.length}, errors: ${migration.errors.join('; ')}`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
action: candidate.action,
|
|
84
|
+
target: candidate.target,
|
|
85
|
+
status: 'applied',
|
|
86
|
+
reason: `migrated ${migration.migrated.length} runtime note id(s) to rtn_`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
69
89
|
case 'quarantine_inbox_message': {
|
|
70
90
|
// Unsafe: move malformed message to a quarantine directory so a human
|
|
71
91
|
// can inspect it. Never deletes. Requires includeUnsafe.
|
|
@@ -36,13 +36,15 @@ import { appendAuditEntry, readAuditLog } from '../core/audit.js';
|
|
|
36
36
|
import { requireMinimumTrustLevel, requireRegisteredAgentIdentity } from '../core/agent-registry.js';
|
|
37
37
|
import { loadSessionSnapshot } from '../commands/session-start.js';
|
|
38
38
|
import { extractFilesFromDiff } from '../commands/handoff.js';
|
|
39
|
+
import { capHandoffDiff } from '../core/handoff-snapshot.js';
|
|
39
40
|
import { suggestCompaction } from '../core/memory-compactor.js';
|
|
40
41
|
import { dispatchReview } from '../core/dispatcher.js';
|
|
41
42
|
export const REFLECTION_QUESTIONS = [
|
|
42
|
-
'
|
|
43
|
-
'
|
|
44
|
-
'What
|
|
43
|
+
'Dogfooding the project — using brainclaw to do real work this session, what friction did you hit (slow reads, confusing surfaces, missing affordances, awkward workflows)? What concrete change to the project would have removed it?',
|
|
44
|
+
'Your surfaces, skills & tools — did your generated surface files (CLAUDE.md / agent surface), skills (SKILL.md), or tools (MCP / CLI) help or get in the way? Name at least one concrete edit that would make them serve you better next time.',
|
|
45
|
+
'What was the biggest time waste this session, and how could it have been avoided?',
|
|
45
46
|
];
|
|
47
|
+
export const REFLECTION_INSTRUCTION = 'Take a short reflection pass before you stop. For each question, capture anything ACTIONABLE as durable memory with bclaw_quick_capture (type "trap" for a sharp edge to avoid, "decision"/"note" for an improvement idea) so it enters the improvement backlog — improvements to the project AND to your own brainclaw surfaces/skills/tools both count. Use bclaw_write_note with tags ["reflection", "session:<id>"] for free-form narrative. Skipping is fine if the session was trivial.';
|
|
46
48
|
export async function runSessionEnd(options = {}) {
|
|
47
49
|
try {
|
|
48
50
|
const result = await endSession(options);
|
|
@@ -362,10 +364,14 @@ export async function endSession(options = {}) {
|
|
|
362
364
|
compaction_hint: compactionHint,
|
|
363
365
|
...(reflectedHandoff ? { handoff: reflectedHandoff } : {}),
|
|
364
366
|
};
|
|
365
|
-
|
|
367
|
+
// pln#564 — session_end pushes the agent into a short dogfooding reflection
|
|
368
|
+
// by DEFAULT (opt-out via reflect:false). The session_end runtime note is the
|
|
369
|
+
// natural trigger to ask "did the tooling serve me, and what should improve?"
|
|
370
|
+
// — both for the project worked on and for the agent's own brainclaw surfaces.
|
|
371
|
+
if (options.reflect !== false) {
|
|
366
372
|
result.reflection_prompt = {
|
|
367
373
|
questions: [...REFLECTION_QUESTIONS],
|
|
368
|
-
instruction:
|
|
374
|
+
instruction: REFLECTION_INSTRUCTION.replace('session:<id>', `session:${sessionId}`),
|
|
369
375
|
};
|
|
370
376
|
}
|
|
371
377
|
return result;
|
|
@@ -395,7 +401,10 @@ function materializeSessionHandoff(input) {
|
|
|
395
401
|
linked_plans: input.linkedPlans.length > 0 ? input.linkedPlans : undefined,
|
|
396
402
|
}
|
|
397
403
|
: undefined,
|
|
398
|
-
|
|
404
|
+
// pln#569 — cap the inline diff to a preview + digest (the full ~450 KB
|
|
405
|
+
// uncommitted diff bloated auto-handoffs to 53 MB of the journal; no consumer
|
|
406
|
+
// reads past a bounded prefix and the worktree branch carries the full diff).
|
|
407
|
+
snapshot: capHandoffDiff(input.fullDiff),
|
|
399
408
|
});
|
|
400
409
|
persistState(state, cwd);
|
|
401
410
|
return { handoff_id: id, plan_id: input.planId };
|
|
@@ -15,7 +15,8 @@ import { releaseStaleClaimsFromOtherAgents } from '../core/claims.js';
|
|
|
15
15
|
import { SessionSnapshotSchema } from '../core/schema.js';
|
|
16
16
|
import { auditLocalAgentWorkspaceFiles } from '../core/agent-files.js';
|
|
17
17
|
import { buildAgentInventory, loadAgentInventory, saveAgentInventory, diffInventory } from '../core/agent-inventory.js';
|
|
18
|
-
import { checkMemoryPressure } from '../core/gc-semantic.js';
|
|
18
|
+
import { checkMemoryPressure, enforceRuntimeNoteRetention } from '../core/gc-semantic.js';
|
|
19
|
+
import { maybeCreateCheckpoint } from '../core/events/checkpoint.js';
|
|
19
20
|
import { pullSignalsFromLinkedProjects, markSignalProcessed } from '../core/federation-transport.js';
|
|
20
21
|
import { pullSignalsFromCloud, isCloudSyncEnabled } from '../core/federation-cloud.js';
|
|
21
22
|
import { materializeFederationSignal } from '../core/federation-materialize.js';
|
|
@@ -191,6 +192,22 @@ export async function startSession(options = {}) {
|
|
|
191
192
|
inventoryAdvisory = lines;
|
|
192
193
|
}
|
|
193
194
|
catch { /* non-fatal — inventory scan failure should not block session start */ }
|
|
195
|
+
// pln#564 step B — cap the runtime-note tree on session start (no LLM gate,
|
|
196
|
+
// unlike the compaction-phase archiveSessionNotes). Keeps the newest N
|
|
197
|
+
// session/lifecycle notes per agent + all genuine observations, parks the
|
|
198
|
+
// rest. Bounds the buildContext read-path scan (trp_439fec51). Best-effort.
|
|
199
|
+
try {
|
|
200
|
+
enforceRuntimeNoteRetention({ cwd: options.cwd });
|
|
201
|
+
}
|
|
202
|
+
catch { /* non-fatal — retention sweep must never block session start */ }
|
|
203
|
+
// pln#566 Inc0 — keep a recent journal-derived checkpoint available off the
|
|
204
|
+
// hot path so the (capability-gated, OFF by default) checkpointRead read
|
|
205
|
+
// path has something to serve once enabled. Gated by a growth threshold so
|
|
206
|
+
// it only builds occasionally; journal-derived (F6). Best-effort.
|
|
207
|
+
try {
|
|
208
|
+
maybeCreateCheckpoint(options.cwd);
|
|
209
|
+
}
|
|
210
|
+
catch { /* non-fatal — checkpoint build must never block session start */ }
|
|
194
211
|
}
|
|
195
212
|
// Shared checkout detection: warn if other active sessions share the same worktree
|
|
196
213
|
let sharedCheckoutWarning;
|
|
@@ -2,7 +2,14 @@ import fs from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { memoryExists, memoryPath, ensureMemoryDir } from '../core/io.js';
|
|
4
4
|
import { loadConfig, saveConfig } from '../core/config.js';
|
|
5
|
-
import { generateBashGuard, generatePowerShellGuard, generatePipBashGuard } from '../core/security-guard.js';
|
|
5
|
+
import { generateBashGuard, generatePowerShellGuard, generatePipBashGuard, } from '../core/security-guard.js';
|
|
6
|
+
/**
|
|
7
|
+
* Each guard wraps a single install command. The CLI invocation that gets
|
|
8
|
+
* called is identical — only ORIGINAL_CMD differs — so we generate one
|
|
9
|
+
* script per supported tool with the right ORIGINAL_CMD baked in.
|
|
10
|
+
*/
|
|
11
|
+
const NPM_LIKE = ['npm', 'pnpm', 'yarn'];
|
|
12
|
+
const PIP_LIKE = ['pip', 'pip3'];
|
|
6
13
|
export function runSetupSecurity(options = {}) {
|
|
7
14
|
const cwd = options.cwd;
|
|
8
15
|
if (!memoryExists(cwd)) {
|
|
@@ -13,7 +20,16 @@ export function runSetupSecurity(options = {}) {
|
|
|
13
20
|
const mode = options.mode ?? 'advisory';
|
|
14
21
|
// Enable preinstall in config
|
|
15
22
|
if (!config.security) {
|
|
16
|
-
config.security = {
|
|
23
|
+
config.security = {
|
|
24
|
+
mode: 'warn',
|
|
25
|
+
strict_redaction: false,
|
|
26
|
+
block_sensitive_paths: true,
|
|
27
|
+
token_detection: {
|
|
28
|
+
enabled: true,
|
|
29
|
+
entropy: { enabled: true, min_length: 32, min_entropy: 4.0 },
|
|
30
|
+
detectors: {},
|
|
31
|
+
},
|
|
32
|
+
};
|
|
17
33
|
}
|
|
18
34
|
config.security.preinstall = {
|
|
19
35
|
enabled: true,
|
|
@@ -46,25 +62,30 @@ export function runSetupSecurity(options = {}) {
|
|
|
46
62
|
if (!fs.existsSync(guardDir)) {
|
|
47
63
|
fs.mkdirSync(guardDir, { recursive: true });
|
|
48
64
|
}
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
const writtenScripts = [];
|
|
66
|
+
for (const cmd of NPM_LIKE) {
|
|
67
|
+
const bashPath = path.join(guardDir, cmd);
|
|
68
|
+
const bashScript = generateBashGuard(brainclawBin).replace('ORIGINAL_CMD="${BRAINCLAW_GUARD_ORIGINAL_CMD:-npm}"', `ORIGINAL_CMD="\${BRAINCLAW_GUARD_ORIGINAL_CMD:-${cmd}}"`);
|
|
69
|
+
fs.writeFileSync(bashPath, bashScript, { mode: 0o755 });
|
|
70
|
+
writtenScripts.push(bashPath);
|
|
71
|
+
const ps1Path = path.join(guardDir, `${cmd}.ps1`);
|
|
72
|
+
const ps1Script = generatePowerShellGuard(brainclawBin).replace('} else { "npm" }', `} else { "${cmd}" }`);
|
|
73
|
+
fs.writeFileSync(ps1Path, ps1Script);
|
|
74
|
+
writtenScripts.push(ps1Path);
|
|
75
|
+
}
|
|
76
|
+
for (const cmd of PIP_LIKE) {
|
|
77
|
+
const bashPath = path.join(guardDir, cmd);
|
|
78
|
+
fs.writeFileSync(bashPath, generatePipBashGuard(brainclawBin).replace('ORIGINAL_CMD="${BRAINCLAW_GUARD_ORIGINAL_CMD:-pip}"', `ORIGINAL_CMD="\${BRAINCLAW_GUARD_ORIGINAL_CMD:-${cmd}}"`), { mode: 0o755 });
|
|
79
|
+
writtenScripts.push(bashPath);
|
|
80
|
+
const ps1Path = path.join(guardDir, `${cmd}.ps1`);
|
|
81
|
+
fs.writeFileSync(ps1Path, generatePowerShellGuard(brainclawBin).replace('} else { "npm" }', `} else { "${cmd}" }`));
|
|
82
|
+
writtenScripts.push(ps1Path);
|
|
83
|
+
}
|
|
61
84
|
console.log(`\u2705 Security gate enabled (mode: ${mode})`);
|
|
62
85
|
console.log('');
|
|
63
86
|
console.log('Generated wrapper scripts:');
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
console.log(` ${pipBashPath}`);
|
|
67
|
-
console.log(` ${pipPs1Path}`);
|
|
87
|
+
for (const p of writtenScripts)
|
|
88
|
+
console.log(` ${p}`);
|
|
68
89
|
console.log('');
|
|
69
90
|
console.log('To activate, prepend the guard directory to your PATH:');
|
|
70
91
|
console.log(` export PATH="${guardDir}:$PATH" # bash/zsh`);
|