brainclaw 0.29.2 → 1.5.4
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/LICENSE +21 -74
- package/README.md +199 -176
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +710 -25
- 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 +269 -0
- package/dist/commands/mcp.js +4224 -1501
- 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 +100 -2
- 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 +33 -5
- 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/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 +381 -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/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 +110 -25
- 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/reputation.js +9 -3
- package/dist/core/schema.js +491 -6
- 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 +43 -11
- 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/product/positioning.md +10 -10
- 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 +21 -13
- 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
|
@@ -1,35 +1,66 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
import { execSync } from 'node:child_process';
|
|
4
5
|
import { memoryExists, resolveEntityDir } from '../core/io.js';
|
|
5
6
|
import { loadVersionedJsonFile, saveVersionedJsonFile } from '../core/migration.js';
|
|
6
|
-
import { buildOperationalIdentity, saveCurrentSession } from '../core/identity.js';
|
|
7
|
-
import { requireMinimumTrustLevel,
|
|
8
|
-
import { buildContext } from '../core/context.js';
|
|
7
|
+
import { buildOperationalIdentity, loadAllSessions, saveCurrentSession } from '../core/identity.js';
|
|
8
|
+
import { requireMinimumTrustLevel, resolveCurrentModel, resolveOrAutoRegisterAgentIdentity } from '../core/agent-registry.js';
|
|
9
|
+
import { buildContext, renderContextPromptTemplate } from '../core/context.js';
|
|
10
|
+
import { writeContextMarker } from '../core/freshness.js';
|
|
9
11
|
import { saveRuntimeNote, generateRuntimeNoteId } from '../core/runtime.js';
|
|
10
|
-
import { nowISO, generateId } from '../core/ids.js';
|
|
12
|
+
import { nowISO, generateId, generateIdWithLabel } from '../core/ids.js';
|
|
11
13
|
import { appendAuditEntry } from '../core/audit.js';
|
|
12
|
-
import {
|
|
14
|
+
import { releaseStaleClaimsFromOtherAgents } from '../core/claims.js';
|
|
15
|
+
import { SessionSnapshotSchema, CandidateSchema, HandoffSchema, RuntimeNoteSchema } from '../core/schema.js';
|
|
13
16
|
import { auditLocalAgentWorkspaceFiles } from '../core/agent-files.js';
|
|
14
17
|
import { buildAgentInventory, loadAgentInventory, saveAgentInventory, diffInventory } from '../core/agent-inventory.js';
|
|
18
|
+
import { checkMemoryPressure } from '../core/gc-semantic.js';
|
|
19
|
+
import { pullSignalsFromLinkedProjects, markSignalProcessed } from '../core/federation-transport.js';
|
|
20
|
+
import { saveCandidate, generateCandidateIdWithLabel } from '../core/candidates.js';
|
|
21
|
+
import { mutateState } from '../core/state.js';
|
|
15
22
|
function sessionsDir(cwd) {
|
|
16
23
|
return resolveEntityDir('sessions', cwd ?? process.cwd(), 'read');
|
|
17
24
|
}
|
|
18
25
|
function sessionSnapshotPath(sessionId, cwd) {
|
|
19
26
|
return path.join(sessionsDir(cwd), `${sessionId}.json`);
|
|
20
27
|
}
|
|
21
|
-
function createHash(data) {
|
|
22
|
-
let hash = 0;
|
|
23
|
-
for (let i = 0; i < data.length; i++) {
|
|
24
|
-
const chr = data.charCodeAt(i);
|
|
25
|
-
hash = ((hash << 5) - hash) + chr;
|
|
26
|
-
hash |= 0;
|
|
27
|
-
}
|
|
28
|
-
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
29
|
-
}
|
|
30
28
|
export function runSessionStart(options = {}) {
|
|
31
29
|
try {
|
|
32
|
-
const snapshot = startSession(
|
|
30
|
+
const snapshot = startSession({
|
|
31
|
+
...options,
|
|
32
|
+
maintenanceMode: options.maintenanceMode ?? 'full',
|
|
33
|
+
});
|
|
34
|
+
// --include-context: output full project context (replaces separate `brainclaw context` call)
|
|
35
|
+
if (options.includeContext) {
|
|
36
|
+
try {
|
|
37
|
+
const cwd = options.cwd ?? process.cwd();
|
|
38
|
+
// Find the previous session for the same agent to auto-surface a context diff on resume.
|
|
39
|
+
// We exclude the session just created so we always point at the prior one.
|
|
40
|
+
const previousSession = loadAllSessions(cwd)
|
|
41
|
+
.find((s) => s.agent === snapshot.agent && s.session_id !== snapshot.session_id);
|
|
42
|
+
const contextResult = buildContext({
|
|
43
|
+
target: options.context,
|
|
44
|
+
agent: snapshot.agent,
|
|
45
|
+
cwd,
|
|
46
|
+
sinceSession: previousSession?.session_id,
|
|
47
|
+
});
|
|
48
|
+
console.log(renderContextPromptTemplate(contextResult, false));
|
|
49
|
+
writeContextMarker({
|
|
50
|
+
read_at: nowISO(),
|
|
51
|
+
memory_version: contextResult.memory_version,
|
|
52
|
+
host_id: contextResult.current_host,
|
|
53
|
+
target: options.context,
|
|
54
|
+
project: contextResult.project,
|
|
55
|
+
all_hosts: false,
|
|
56
|
+
}, cwd);
|
|
57
|
+
}
|
|
58
|
+
catch (ctxErr) {
|
|
59
|
+
// Context build failure should not block session start output
|
|
60
|
+
console.error(`⚠ Context build failed: ${ctxErr instanceof Error ? ctxErr.message : String(ctxErr)}`);
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
33
64
|
if (options.json) {
|
|
34
65
|
console.log(JSON.stringify(snapshot, null, 2));
|
|
35
66
|
return;
|
|
@@ -53,6 +84,12 @@ export function runSessionStart(options = {}) {
|
|
|
53
84
|
console.warn(`⚠ ${line}`);
|
|
54
85
|
}
|
|
55
86
|
}
|
|
87
|
+
if (snapshot.stale_claims_released) {
|
|
88
|
+
console.warn(`⚠ Auto-released ${snapshot.stale_claims_released.length} stale claim(s):`);
|
|
89
|
+
for (const c of snapshot.stale_claims_released) {
|
|
90
|
+
console.warn(` ${c.agent} → ${c.scope}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
56
93
|
}
|
|
57
94
|
catch (e) {
|
|
58
95
|
console.error(`Error: ${e instanceof Error ? e.message : String(e)}`);
|
|
@@ -63,7 +100,7 @@ export function startSession(options = {}) {
|
|
|
63
100
|
if (!memoryExists(options.cwd)) {
|
|
64
101
|
throw new Error('.brainclaw/ not found. Run `brainclaw init` first.');
|
|
65
102
|
}
|
|
66
|
-
const registered =
|
|
103
|
+
const { identity: registered, auto_registered: autoRegistered } = resolveOrAutoRegisterAgentIdentity({
|
|
67
104
|
agentName: options.agent,
|
|
68
105
|
agentId: options.agentId,
|
|
69
106
|
cwd: options.cwd,
|
|
@@ -72,13 +109,7 @@ export function startSession(options = {}) {
|
|
|
72
109
|
});
|
|
73
110
|
requireMinimumTrustLevel(registered, 'contributor');
|
|
74
111
|
const actor = buildOperationalIdentity(registered.agent_name, options.cwd, { agentId: registered.agent_id });
|
|
75
|
-
|
|
76
|
-
let initialContextHash;
|
|
77
|
-
try {
|
|
78
|
-
const ctx = buildContext({ target: options.context, agent: actor.agent, cwd: options.cwd });
|
|
79
|
-
initialContextHash = createHash(JSON.stringify(ctx.selected));
|
|
80
|
-
}
|
|
81
|
-
catch { /* non-fatal */ }
|
|
112
|
+
const maintenanceMode = options.maintenanceMode ?? 'fast';
|
|
82
113
|
// Capture git HEAD SHA for later handoff generation
|
|
83
114
|
let gitSha;
|
|
84
115
|
try {
|
|
@@ -93,7 +124,6 @@ export function startSession(options = {}) {
|
|
|
93
124
|
agent_id: actor.agent_id,
|
|
94
125
|
started_at: nowISO(),
|
|
95
126
|
context_target: options.context,
|
|
96
|
-
initial_context_hash: initialContextHash,
|
|
97
127
|
git_sha: gitSha,
|
|
98
128
|
...(model ? { model } : {}),
|
|
99
129
|
};
|
|
@@ -102,6 +132,14 @@ export function startSession(options = {}) {
|
|
|
102
132
|
if (!fs.existsSync(dir))
|
|
103
133
|
fs.mkdirSync(dir, { recursive: true });
|
|
104
134
|
saveVersionedJsonFile('session_snapshot', sessionSnapshotPath(snapshot.session_id, options.cwd), SessionSnapshotSchema.parse(snapshot));
|
|
135
|
+
// Resolve git branch and worktree for session tracking
|
|
136
|
+
let currentBranch;
|
|
137
|
+
let currentWorktreePath;
|
|
138
|
+
try {
|
|
139
|
+
currentBranch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf-8', cwd: options.cwd ?? process.cwd() }).trim();
|
|
140
|
+
currentWorktreePath = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8', cwd: options.cwd ?? process.cwd() }).trim();
|
|
141
|
+
}
|
|
142
|
+
catch { /* non-fatal — not a git repo */ }
|
|
105
143
|
saveCurrentSession({
|
|
106
144
|
schema_version: 2,
|
|
107
145
|
session_id: snapshot.session_id,
|
|
@@ -110,6 +148,12 @@ export function startSession(options = {}) {
|
|
|
110
148
|
agent: actor.agent,
|
|
111
149
|
agent_id: actor.agent_id,
|
|
112
150
|
host_id: actor.host_id,
|
|
151
|
+
user: process.env.USER || process.env.USERNAME || os.userInfo().username || undefined,
|
|
152
|
+
pid: process.pid,
|
|
153
|
+
model: model ?? undefined,
|
|
154
|
+
branch: currentBranch,
|
|
155
|
+
worktree_path: currentWorktreePath,
|
|
156
|
+
isolation_mode: 'shared-checkout',
|
|
113
157
|
}, options.cwd);
|
|
114
158
|
// Write session_start runtime note
|
|
115
159
|
const noteId = generateRuntimeNoteId();
|
|
@@ -125,27 +169,143 @@ export function startSession(options = {}) {
|
|
|
125
169
|
visibility: 'shared',
|
|
126
170
|
note_type: 'session_start',
|
|
127
171
|
}, options.cwd);
|
|
128
|
-
appendAuditEntry({ action: 'session_start', actor: actor.agent, actor_id: actor.agent_id, item_id: snapshot.session_id, item_type: 'session' }, options.cwd);
|
|
172
|
+
appendAuditEntry({ action: 'session_start', actor: actor.agent, actor_id: actor.agent_id, item_id: snapshot.session_id, item_type: 'session', session_id: snapshot.session_id, host_id: actor.host_id }, options.cwd);
|
|
129
173
|
const agentGitHygiene = auditLocalAgentWorkspaceFiles(options.cwd ?? process.cwd());
|
|
130
|
-
//
|
|
174
|
+
// Non-critical maintenance work lives behind the full mode only.
|
|
131
175
|
let inventoryAdvisory;
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
176
|
+
if (maintenanceMode === 'full') {
|
|
177
|
+
try {
|
|
178
|
+
const previousInventory = loadAgentInventory();
|
|
179
|
+
const currentInventory = buildAgentInventory();
|
|
180
|
+
const diff = diffInventory(previousInventory, currentInventory);
|
|
181
|
+
saveAgentInventory(currentInventory);
|
|
182
|
+
const lines = [];
|
|
183
|
+
if (diff.appeared.length > 0)
|
|
184
|
+
lines.push(`New agents detected: ${diff.appeared.join(', ')}`);
|
|
185
|
+
if (diff.disappeared.length > 0)
|
|
186
|
+
lines.push(`Agents no longer detected: ${diff.disappeared.join(', ')}`);
|
|
187
|
+
for (const vc of diff.version_changed) {
|
|
188
|
+
lines.push(`${vc.name} version changed: ${vc.from ?? '?'} → ${vc.to ?? '?'}`);
|
|
189
|
+
}
|
|
190
|
+
if (lines.length > 0)
|
|
191
|
+
inventoryAdvisory = lines;
|
|
144
192
|
}
|
|
145
|
-
|
|
146
|
-
|
|
193
|
+
catch { /* non-fatal — inventory scan failure should not block session start */ }
|
|
194
|
+
}
|
|
195
|
+
// Shared checkout detection: warn if other active sessions share the same worktree
|
|
196
|
+
let sharedCheckoutWarning;
|
|
197
|
+
if (currentWorktreePath) {
|
|
198
|
+
try {
|
|
199
|
+
const allSessions = loadAllSessions(options.cwd);
|
|
200
|
+
const ttlMs = 4 * 60 * 60 * 1000; // 4h
|
|
201
|
+
const now = Date.now();
|
|
202
|
+
const otherSessions = allSessions.filter(s => s.session_id !== snapshot.session_id
|
|
203
|
+
&& s.worktree_path === currentWorktreePath
|
|
204
|
+
&& (now - Date.parse(s.last_seen_at)) <= ttlMs
|
|
205
|
+
&& (!s.pid || isPidAlive(s.pid)));
|
|
206
|
+
if (otherSessions.length > 0) {
|
|
207
|
+
sharedCheckoutWarning = {
|
|
208
|
+
worktree_path: currentWorktreePath,
|
|
209
|
+
other_sessions: otherSessions.map(s => ({
|
|
210
|
+
session_id: s.session_id,
|
|
211
|
+
agent: s.agent,
|
|
212
|
+
user: s.user,
|
|
213
|
+
branch: s.branch,
|
|
214
|
+
pid: s.pid,
|
|
215
|
+
})),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
catch { /* non-fatal */ }
|
|
220
|
+
}
|
|
221
|
+
// Stale claim auto-release. Phase 4 slice pln#388 stp_e2b10ab4: pass
|
|
222
|
+
// the new session_id so same-agent prior-session claims can be swept
|
|
223
|
+
// too — without it, a crash-recovered agent would keep its own orphaned
|
|
224
|
+
// claims hanging until 24h.
|
|
225
|
+
let staleClaimsReleased;
|
|
226
|
+
if (maintenanceMode === 'full') {
|
|
227
|
+
try {
|
|
228
|
+
const staleResult = releaseStaleClaimsFromOtherAgents(actor.agent, options.cwd, snapshot.session_id);
|
|
229
|
+
if (staleResult.released.length > 0) {
|
|
230
|
+
staleClaimsReleased = staleResult.released.map(c => ({ id: c.id, agent: c.agent, scope: c.scope }));
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
catch { /* non-fatal */ }
|
|
234
|
+
}
|
|
235
|
+
// Memory pressure check: hint agent to run bclaw_compact if store is large
|
|
236
|
+
let memoryPressure;
|
|
237
|
+
if (maintenanceMode === 'full') {
|
|
238
|
+
try {
|
|
239
|
+
const pressure = checkMemoryPressure(options.cwd);
|
|
240
|
+
if (pressure.memory_pressure) {
|
|
241
|
+
memoryPressure = pressure;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch { /* non-fatal */ }
|
|
245
|
+
}
|
|
246
|
+
// Materialize incoming federation signals from linked projects
|
|
247
|
+
if (maintenanceMode === 'full') {
|
|
248
|
+
try {
|
|
249
|
+
const federationSignals = pullSignalsFromLinkedProjects(options.cwd);
|
|
250
|
+
let materialized = 0;
|
|
251
|
+
for (const signal of federationSignals) {
|
|
252
|
+
try {
|
|
253
|
+
const origin = `remote:${signal.from.project_name}:${signal.from.agent_name}`;
|
|
254
|
+
if (signal.type === 'candidate') {
|
|
255
|
+
const parsed = CandidateSchema.safeParse(signal.payload);
|
|
256
|
+
if (parsed.success) {
|
|
257
|
+
const { id, short_label } = generateCandidateIdWithLabel(options.cwd);
|
|
258
|
+
saveCandidate({
|
|
259
|
+
...parsed.data,
|
|
260
|
+
id,
|
|
261
|
+
short_label,
|
|
262
|
+
created_at: nowISO(),
|
|
263
|
+
source: undefined, // remote federation signal — treated as 'human' (legacy default)
|
|
264
|
+
star_count: 0,
|
|
265
|
+
starred_by: [],
|
|
266
|
+
usage_count: 0,
|
|
267
|
+
usage_events: [],
|
|
268
|
+
status: 'pending',
|
|
269
|
+
}, options.cwd);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
else if (signal.type === 'handoff') {
|
|
273
|
+
const parsed = HandoffSchema.safeParse(signal.payload);
|
|
274
|
+
if (parsed.success) {
|
|
275
|
+
const { id, short_label } = generateIdWithLabel('open_handoffs', options.cwd);
|
|
276
|
+
mutateState((state) => {
|
|
277
|
+
state.open_handoffs.push({
|
|
278
|
+
...parsed.data,
|
|
279
|
+
id,
|
|
280
|
+
short_label,
|
|
281
|
+
created_at: nowISO(),
|
|
282
|
+
tags: [...(parsed.data.tags ?? []), origin],
|
|
283
|
+
});
|
|
284
|
+
}, options.cwd);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
else if (signal.type === 'runtime_note') {
|
|
288
|
+
const parsed = RuntimeNoteSchema.safeParse(signal.payload);
|
|
289
|
+
if (parsed.success) {
|
|
290
|
+
saveRuntimeNote({
|
|
291
|
+
...parsed.data,
|
|
292
|
+
id: generateRuntimeNoteId(),
|
|
293
|
+
created_at: nowISO(),
|
|
294
|
+
tags: [...(parsed.data.tags ?? []), origin],
|
|
295
|
+
}, options.cwd);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
markSignalProcessed(signal.from.project_path, signal.id);
|
|
299
|
+
materialized++;
|
|
300
|
+
}
|
|
301
|
+
catch { /* skip this signal — do not block session start */ }
|
|
302
|
+
}
|
|
303
|
+
if (materialized > 0) {
|
|
304
|
+
console.log(`✔ Materialized ${materialized} federation signal(s) from linked projects`);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
catch { /* Non-fatal — federation pull failure should not block session start */ }
|
|
147
308
|
}
|
|
148
|
-
catch { /* non-fatal — inventory scan failure should not block session start */ }
|
|
149
309
|
return {
|
|
150
310
|
...snapshot,
|
|
151
311
|
...(agentGitHygiene.isGitRepo && (agentGitHygiene.missingGitignorePaths.length > 0 || agentGitHygiene.trackedPaths.length > 0)
|
|
@@ -157,8 +317,21 @@ export function startSession(options = {}) {
|
|
|
157
317
|
}
|
|
158
318
|
: {}),
|
|
159
319
|
...(inventoryAdvisory ? { inventory_advisory: inventoryAdvisory } : {}),
|
|
320
|
+
...(sharedCheckoutWarning ? { shared_checkout_warning: sharedCheckoutWarning } : {}),
|
|
321
|
+
...(staleClaimsReleased ? { stale_claims_released: staleClaimsReleased } : {}),
|
|
322
|
+
...(memoryPressure ? { memory_pressure: memoryPressure } : {}),
|
|
323
|
+
...(autoRegistered ? { auto_registered: true } : {}),
|
|
160
324
|
};
|
|
161
325
|
}
|
|
326
|
+
function isPidAlive(pid) {
|
|
327
|
+
try {
|
|
328
|
+
process.kill(pid, 0);
|
|
329
|
+
return true;
|
|
330
|
+
}
|
|
331
|
+
catch {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
162
335
|
export function loadSessionSnapshot(sessionId, cwd) {
|
|
163
336
|
const p = sessionSnapshotPath(sessionId, cwd);
|
|
164
337
|
if (!fs.existsSync(p))
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { memoryExists, memoryPath, ensureMemoryDir } from '../core/io.js';
|
|
4
|
+
import { loadConfig, saveConfig } from '../core/config.js';
|
|
5
|
+
import { generateBashGuard, generatePowerShellGuard, generatePipBashGuard } from '../core/security-guard.js';
|
|
6
|
+
export function runSetupSecurity(options = {}) {
|
|
7
|
+
const cwd = options.cwd;
|
|
8
|
+
if (!memoryExists(cwd)) {
|
|
9
|
+
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const config = loadConfig(cwd);
|
|
13
|
+
const mode = options.mode ?? 'advisory';
|
|
14
|
+
// Enable preinstall in config
|
|
15
|
+
if (!config.security) {
|
|
16
|
+
config.security = { mode: 'warn', strict_redaction: false, block_sensitive_paths: true };
|
|
17
|
+
}
|
|
18
|
+
config.security.preinstall = {
|
|
19
|
+
enabled: true,
|
|
20
|
+
mode,
|
|
21
|
+
thresholds: {
|
|
22
|
+
composite_pass: 70,
|
|
23
|
+
composite_warn: 50,
|
|
24
|
+
supply_chain_block: 30,
|
|
25
|
+
vulnerability_block: 20,
|
|
26
|
+
},
|
|
27
|
+
weights: {
|
|
28
|
+
supply_chain: 0.35,
|
|
29
|
+
vulnerability: 0.30,
|
|
30
|
+
quality: 0.15,
|
|
31
|
+
maintenance: 0.15,
|
|
32
|
+
license: 0.05,
|
|
33
|
+
},
|
|
34
|
+
cache_ttl_hours: 24,
|
|
35
|
+
fallback_on_error: 'warn',
|
|
36
|
+
allowlist: [],
|
|
37
|
+
denylist: [],
|
|
38
|
+
socket_endpoint: 'https://mcp.socket.dev/',
|
|
39
|
+
};
|
|
40
|
+
saveConfig(config, cwd);
|
|
41
|
+
// Resolve brainclaw binary path
|
|
42
|
+
const brainclawBin = resolveBrainclawBin(cwd);
|
|
43
|
+
// Generate wrapper scripts
|
|
44
|
+
const guardDir = memoryPath('security/bin', cwd);
|
|
45
|
+
ensureMemoryDir(cwd);
|
|
46
|
+
if (!fs.existsSync(guardDir)) {
|
|
47
|
+
fs.mkdirSync(guardDir, { recursive: true });
|
|
48
|
+
}
|
|
49
|
+
// npm guard (bash)
|
|
50
|
+
const npmBashPath = path.join(guardDir, 'npm');
|
|
51
|
+
fs.writeFileSync(npmBashPath, generateBashGuard(brainclawBin), { mode: 0o755 });
|
|
52
|
+
// npm guard (PowerShell)
|
|
53
|
+
const npmPs1Path = path.join(guardDir, 'npm.ps1');
|
|
54
|
+
fs.writeFileSync(npmPs1Path, generatePowerShellGuard(brainclawBin));
|
|
55
|
+
// pip guard (bash)
|
|
56
|
+
const pipBashPath = path.join(guardDir, 'pip');
|
|
57
|
+
fs.writeFileSync(pipBashPath, generatePipBashGuard(brainclawBin), { mode: 0o755 });
|
|
58
|
+
// pip guard (PowerShell)
|
|
59
|
+
const pipPs1Path = path.join(guardDir, 'pip.ps1');
|
|
60
|
+
fs.writeFileSync(pipPs1Path, generatePowerShellGuard(brainclawBin).replace('} else { "npm" }', '} else { "pip" }'));
|
|
61
|
+
console.log(`\u2705 Security gate enabled (mode: ${mode})`);
|
|
62
|
+
console.log('');
|
|
63
|
+
console.log('Generated wrapper scripts:');
|
|
64
|
+
console.log(` ${npmBashPath}`);
|
|
65
|
+
console.log(` ${npmPs1Path}`);
|
|
66
|
+
console.log(` ${pipBashPath}`);
|
|
67
|
+
console.log(` ${pipPs1Path}`);
|
|
68
|
+
console.log('');
|
|
69
|
+
console.log('To activate, prepend the guard directory to your PATH:');
|
|
70
|
+
console.log(` export PATH="${guardDir}:$PATH" # bash/zsh`);
|
|
71
|
+
console.log(` $env:PATH = "${guardDir};$env:PATH" # PowerShell`);
|
|
72
|
+
console.log('');
|
|
73
|
+
console.log('Or add it to your shell profile for persistent activation.');
|
|
74
|
+
console.log('');
|
|
75
|
+
console.log(`Mode: ${mode}`);
|
|
76
|
+
if (mode === 'advisory') {
|
|
77
|
+
console.log(' Advisory mode: warnings and traps are created but installs are not blocked.');
|
|
78
|
+
console.log(' Switch to enforced mode: brainclaw setup --security --mode enforced');
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
console.log(' Enforced mode: risky installs will be blocked (exit code 1).');
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function resolveBrainclawBin(cwd) {
|
|
85
|
+
// Try to find brainclaw in common locations
|
|
86
|
+
const candidates = [
|
|
87
|
+
'brainclaw',
|
|
88
|
+
path.resolve(cwd ?? process.cwd(), 'node_modules/.bin/brainclaw'),
|
|
89
|
+
path.resolve(cwd ?? process.cwd(), 'dist/cli.js'),
|
|
90
|
+
];
|
|
91
|
+
for (const c of candidates) {
|
|
92
|
+
try {
|
|
93
|
+
if (fs.existsSync(c))
|
|
94
|
+
return c;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// continue
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Default to just 'brainclaw' and hope it's in PATH
|
|
101
|
+
return 'brainclaw';
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=setup-security.js.map
|
package/dist/commands/setup.js
CHANGED
|
@@ -7,7 +7,7 @@ import { detectAiAgent } from '../core/ai-agent-detection.js';
|
|
|
7
7
|
import { buildAiSurfaceInventory, renderAiSurfaceUsageHints } from '../core/ai-surface-inventory.js';
|
|
8
8
|
import { buildMachineProfile, saveMachineProfile, loadMachineProfile } from '../core/machine-profile.js';
|
|
9
9
|
import { buildAgentInventory, saveAgentInventory, loadAgentInventory } from '../core/agent-inventory.js';
|
|
10
|
-
import { ensureClaudeCodeUserSettings, ensureClaudeCodeUserCommand, ensureCursorMcpConfig, ensureWindsurfMcpConfig, ensureAntigravityMcpConfig, ensureContinueUserMcpConfig, ensureCodexMcpConfig, writeDetectedAgentAutoConfig, describeAutoConfigWrite, ensureGitignoreEntries, collectWorkspaceGitignoreEntries, } from '../core/agent-files.js';
|
|
10
|
+
import { ensureClaudeCodeUserSettings, ensureClaudeCodeUserCommand, ensureCursorMcpConfig, ensureWindsurfMcpConfig, ensureAntigravityMcpConfig, ensureContinueUserMcpConfig, ensureContinueUserPermissions, ensureCodexMcpConfig, writeDetectedAgentAutoConfig, describeAutoConfigWrite, ensureGitignoreEntries, collectWorkspaceGitignoreEntries, BRAINCLAW_EXCLUSIVE_DIRECTORIES, } from '../core/agent-files.js';
|
|
11
11
|
import { MEMORY_DIR, memoryExists, ensureMemoryDir } from '../core/io.js';
|
|
12
12
|
import { saveConfig, defaultConfig } from '../core/config.js';
|
|
13
13
|
import { readSetupState, resolveHomeDir, writeSetupState } from '../core/setup-state.js';
|
|
@@ -26,6 +26,10 @@ export const ALL_KNOWN_AGENTS = [
|
|
|
26
26
|
'continue',
|
|
27
27
|
'roo',
|
|
28
28
|
'openclaw',
|
|
29
|
+
'nanoclaw',
|
|
30
|
+
'nemoclaw',
|
|
31
|
+
'picoclaw',
|
|
32
|
+
'zeroclaw',
|
|
29
33
|
];
|
|
30
34
|
const BRAINCLAW_ASCII = [
|
|
31
35
|
'',
|
|
@@ -224,6 +228,9 @@ export function runGlobalInstall(selectedAgents, env = process.env) {
|
|
|
224
228
|
const r = ensureContinueUserMcpConfig(home);
|
|
225
229
|
if (r && (r.created || r.updated))
|
|
226
230
|
written.push(r.filePath);
|
|
231
|
+
const perms = ensureContinueUserPermissions(home);
|
|
232
|
+
if (perms && (perms.created || perms.updated))
|
|
233
|
+
written.push(perms.filePath);
|
|
227
234
|
}
|
|
228
235
|
if (selectedAgents.includes('codex')) {
|
|
229
236
|
const r = ensureCodexMcpConfig(home, env);
|
|
@@ -272,7 +279,7 @@ export async function initReposAndConfigureAgents(selectedRepos, selectedAgents,
|
|
|
272
279
|
}
|
|
273
280
|
}
|
|
274
281
|
if (gitignoreEntries.size > 0) {
|
|
275
|
-
ensureGitignoreEntries(repo.path, [...gitignoreEntries]);
|
|
282
|
+
ensureGitignoreEntries(repo.path, [...gitignoreEntries, ...BRAINCLAW_EXCLUSIVE_DIRECTORIES]);
|
|
276
283
|
console.log(` ✔ Updated .gitignore with generated agent files (${[...gitignoreEntries].join(', ')})`);
|
|
277
284
|
}
|
|
278
285
|
}
|
|
@@ -390,7 +397,8 @@ export async function runSetup(options = {}) {
|
|
|
390
397
|
// Step 4: Agent detection & selection
|
|
391
398
|
const detectedAi = detectAiAgent(env);
|
|
392
399
|
const detectedName = detectedAi?.name;
|
|
393
|
-
const
|
|
400
|
+
const testMode = process.env.BRAINCLAW_TEST_MODE === '1';
|
|
401
|
+
const detectedSurfaces = testMode ? [] : buildAiSurfaceInventory();
|
|
394
402
|
console.log('');
|
|
395
403
|
if (detectedName) {
|
|
396
404
|
console.log(`Detected AI agent: ${detectedName}`);
|
|
@@ -452,6 +460,8 @@ export async function runSetup(options = {}) {
|
|
|
452
460
|
// Step 6: Init repos + configure agents
|
|
453
461
|
console.log('\n→ Initialising repositories and configuring agents...');
|
|
454
462
|
const { initialisedRepos, configActions } = await initReposAndConfigureAgents(selectedRepos, selectedAgents, env);
|
|
463
|
+
// Step 7: VS Code extension
|
|
464
|
+
installVscodeExtension();
|
|
455
465
|
// Save state
|
|
456
466
|
writeSetupState({
|
|
457
467
|
completed_at: new Date().toISOString(),
|
|
@@ -459,8 +469,36 @@ export async function runSetup(options = {}) {
|
|
|
459
469
|
initialised_repos: initialisedRepos,
|
|
460
470
|
global_configs_written: selectedAgents,
|
|
461
471
|
}, env);
|
|
462
|
-
// Step
|
|
472
|
+
// Step 8: Reload reminder
|
|
463
473
|
printReloadReminder(detectedName);
|
|
464
474
|
void configActions;
|
|
465
475
|
}
|
|
476
|
+
// ─── VS Code extension auto-install ──────────────────────────────────────────
|
|
477
|
+
function installVscodeExtension() {
|
|
478
|
+
// Find the bundled vsix
|
|
479
|
+
const vsixPath = resolveVsixPath();
|
|
480
|
+
if (!vsixPath)
|
|
481
|
+
return;
|
|
482
|
+
// Check if `code` CLI is available
|
|
483
|
+
const codeAvailable = spawnSync('code', ['--version'], { stdio: 'ignore', timeout: 5000 });
|
|
484
|
+
if (codeAvailable.status !== 0)
|
|
485
|
+
return; // VS Code not installed or not in PATH — skip silently
|
|
486
|
+
// Install the extension
|
|
487
|
+
const result = spawnSync('code', ['--install-extension', vsixPath, '--force'], { stdio: 'pipe', timeout: 30000 });
|
|
488
|
+
if (result.status === 0) {
|
|
489
|
+
console.log(' ✔ Installed Brainclaw VS Code extension');
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
function resolveVsixPath() {
|
|
493
|
+
const thisDir = path.dirname(new URL(import.meta.url).pathname.replace(/^\/([A-Z]:)/i, '$1'));
|
|
494
|
+
// 1. Bundled in dist/ (npm install) — dist/commands/setup.js → dist/brainclaw-vscode.vsix
|
|
495
|
+
const distVsix = path.join(thisDir, '..', 'brainclaw-vscode.vsix');
|
|
496
|
+
if (fs.existsSync(distVsix))
|
|
497
|
+
return distVsix;
|
|
498
|
+
// 2. Dev mode: vscode-extension/ directory
|
|
499
|
+
const devVsix = path.join(thisDir, '..', '..', 'vscode-extension', 'brainclaw-vscode-0.1.0.vsix');
|
|
500
|
+
if (fs.existsSync(devVsix))
|
|
501
|
+
return devVsix;
|
|
502
|
+
return undefined;
|
|
503
|
+
}
|
|
466
504
|
//# sourceMappingURL=setup.js.map
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `brainclaw stale` — minimal operator flow over the staleness detector.
|
|
3
|
+
*
|
|
4
|
+
* Phase 4 Sprint 1 Lane A step 4 (pln#390 / stp_18d6bc03). Lets operators
|
|
5
|
+
* list stale items and apply the canonical v1.0 action without having to
|
|
6
|
+
* remember per-entity syntax.
|
|
7
|
+
*
|
|
8
|
+
* Subcommands:
|
|
9
|
+
* list — print the stale report (same as doctor + board)
|
|
10
|
+
* resolve <id> — apply the canonical action for the entity type:
|
|
11
|
+
* plan → transition status 'dropped'
|
|
12
|
+
* handoff → transition status 'closed'
|
|
13
|
+
* candidate → transition status 'rejected'
|
|
14
|
+
* trap → transition status 'resolved'
|
|
15
|
+
* runtime_note → remove (archive via entity-operations)
|
|
16
|
+
*/
|
|
17
|
+
import { listCandidates } from '../core/candidates.js';
|
|
18
|
+
import { removeEntity, transitionEntity, } from '../core/entity-operations.js';
|
|
19
|
+
import { listRuntimeNotes } from '../core/runtime.js';
|
|
20
|
+
import { loadState } from '../core/state.js';
|
|
21
|
+
import { detectStaleness, staleSummary, } from '../core/staleness.js';
|
|
22
|
+
/** Resolve the canonical transition target (or removal) per entity kind. */
|
|
23
|
+
const RESOLVE_ACTIONS = {
|
|
24
|
+
plan: { kind: 'transition', entity: 'plan', to: 'dropped' },
|
|
25
|
+
handoff: { kind: 'transition', entity: 'handoff', to: 'closed' },
|
|
26
|
+
candidate: { kind: 'transition', entity: 'candidate', to: 'rejected' },
|
|
27
|
+
trap: { kind: 'transition', entity: 'trap', to: 'resolved' },
|
|
28
|
+
runtime_note: { kind: 'remove', entity: 'runtime_note' },
|
|
29
|
+
};
|
|
30
|
+
function buildReport(cwd) {
|
|
31
|
+
const state = loadState(cwd);
|
|
32
|
+
const pending = listCandidates('pending', cwd);
|
|
33
|
+
const notes = listRuntimeNotes(undefined, cwd);
|
|
34
|
+
return detectStaleness(state.plan_items, state.known_traps, state.open_handoffs, pending, Date.now(), notes);
|
|
35
|
+
}
|
|
36
|
+
export function runStaleList(options = {}) {
|
|
37
|
+
const report = buildReport(options.cwd);
|
|
38
|
+
if (options.json) {
|
|
39
|
+
console.log(JSON.stringify(report, null, 2));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (report.warnings.length === 0) {
|
|
43
|
+
console.log('✔ No stale items detected.');
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
console.log(`⚠ ${staleSummary(report)}`);
|
|
47
|
+
console.log('');
|
|
48
|
+
for (const w of report.warnings) {
|
|
49
|
+
console.log(` [${w.entity} ${w.id}] ${w.reason}`);
|
|
50
|
+
console.log(` → brainclaw stale resolve ${w.id} # or: ${w.suggested_action}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
export function runStaleResolve(id, options = {}) {
|
|
54
|
+
const cwd = options.cwd ?? process.cwd();
|
|
55
|
+
const report = buildReport(cwd);
|
|
56
|
+
const warning = report.warnings.find((w) => w.id === id);
|
|
57
|
+
if (!warning) {
|
|
58
|
+
console.error(`Error: ${id} is not currently flagged stale.`);
|
|
59
|
+
console.error('Run `brainclaw stale list` to see the current warnings.');
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
const action = RESOLVE_ACTIONS[warning.entity];
|
|
63
|
+
try {
|
|
64
|
+
if (action.kind === 'transition') {
|
|
65
|
+
const result = transitionEntity(action.entity, id, action.to, cwd, 'resolved via brainclaw stale resolve');
|
|
66
|
+
if (options.json) {
|
|
67
|
+
console.log(JSON.stringify({ resolved: id, ...result }, null, 2));
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
console.log(`✔ ${warning.entity} ${id}: ${result.from} → ${action.to}`);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
const result = removeEntity(action.entity, id, cwd);
|
|
75
|
+
if (options.json) {
|
|
76
|
+
console.log(JSON.stringify({ resolved: id, ...result }, null, 2));
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
const verb = result.purged ? 'purged' : 'archived';
|
|
80
|
+
console.log(`✔ ${warning.entity} ${id}: ${verb}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
86
|
+
console.error(`Error: could not resolve ${id} — ${msg}`);
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
export function runStale(subcommand, arg, options = {}) {
|
|
91
|
+
const effective = subcommand ?? 'list';
|
|
92
|
+
switch (effective) {
|
|
93
|
+
case 'list':
|
|
94
|
+
runStaleList(options);
|
|
95
|
+
return;
|
|
96
|
+
case 'resolve': {
|
|
97
|
+
if (!arg) {
|
|
98
|
+
console.error('Error: `brainclaw stale resolve <id>` requires an entity id.');
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
runStaleResolve(arg, options);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
default:
|
|
105
|
+
console.error(`Error: unknown subcommand '${effective}'. Use: list | resolve <id>.`);
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=stale.js.map
|