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/setup.js
CHANGED
|
@@ -9,9 +9,8 @@ import { buildAiSurfaceInventory, renderAiSurfaceUsageHints } from '../core/ai-s
|
|
|
9
9
|
import { buildMachineProfile, saveMachineProfile, loadMachineProfile } from '../core/machine-profile.js';
|
|
10
10
|
import { buildAgentInventory, saveAgentInventory, loadAgentInventory } from '../core/agent-inventory.js';
|
|
11
11
|
import { ensureClaudeCodeUserSettings, ensureClaudeCodeUserCommand, ensureCursorMcpConfig, ensureWindsurfMcpConfig, ensureAntigravityMcpConfig, ensureContinueUserMcpConfig, ensureContinueUserPermissions, ensureCodexMcpConfig, ensureHermesMcpConfig, writeDetectedAgentAutoConfig, describeAutoConfigWrite, ensureGitignoreEntries, collectWorkspaceGitignoreEntries, BRAINCLAW_EXCLUSIVE_DIRECTORIES, } from '../core/agent-files.js';
|
|
12
|
-
import { MEMORY_DIR, memoryExists
|
|
13
|
-
import {
|
|
14
|
-
import { readSetupState, resolveHomeDir, writeSetupState } from '../core/setup-state.js';
|
|
12
|
+
import { MEMORY_DIR, memoryExists } from '../core/io.js';
|
|
13
|
+
import { ensureUserStore, readSetupState, resolveHomeDir, writeSetupState } from '../core/setup-state.js';
|
|
15
14
|
import { writeDetectedAgentHooks } from './hooks.js';
|
|
16
15
|
export { readSetupState } from '../core/setup-state.js';
|
|
17
16
|
// ─── Constants ────────────────────────────────────────────────────────────────
|
|
@@ -184,28 +183,11 @@ export function parseAgentSelection(choice, detected, installedAgents = []) {
|
|
|
184
183
|
export function initUserStore(home, env = process.env) {
|
|
185
184
|
if (!home)
|
|
186
185
|
return [];
|
|
187
|
-
const
|
|
188
|
-
// Ensure ~/.brainclaw/ directory and subdirs exist
|
|
189
|
-
const userStorePath = path.join(home, '.brainclaw');
|
|
190
|
-
ensureMemoryDir(home);
|
|
191
|
-
// Check if config.yaml already exists (idempotent)
|
|
192
|
-
const configPath = path.join(userStorePath, 'config.yaml');
|
|
186
|
+
const configPath = path.join(home, '.brainclaw', 'config.yaml');
|
|
193
187
|
if (fs.existsSync(configPath)) {
|
|
194
188
|
return [];
|
|
195
189
|
}
|
|
196
|
-
|
|
197
|
-
// Write a minimal config.yaml for the user store
|
|
198
|
-
const defaultCfg = defaultConfig('user-global');
|
|
199
|
-
saveConfig(defaultCfg, home);
|
|
200
|
-
// Append store_type: user to the config.yaml (pattern already used in tests)
|
|
201
|
-
fs.appendFileSync(configPath, 'store_type: user\n');
|
|
202
|
-
written.push(configPath);
|
|
203
|
-
}
|
|
204
|
-
catch (err) {
|
|
205
|
-
// Non-fatal: if user store init fails, continue with agent setup
|
|
206
|
-
console.warn(`Warning: failed to initialize user store at ${configPath}:`, err instanceof Error ? err.message : String(err));
|
|
207
|
-
}
|
|
208
|
-
return written;
|
|
190
|
+
return ensureUserStore(env) ? [configPath] : [];
|
|
209
191
|
}
|
|
210
192
|
export function runGlobalInstall(selectedAgents, env = process.env) {
|
|
211
193
|
const home = resolveHomeDir(env);
|
|
@@ -507,11 +489,16 @@ export async function runSetup(options = {}) {
|
|
|
507
489
|
});
|
|
508
490
|
// Step 3: Repo selection
|
|
509
491
|
let repoChoice;
|
|
492
|
+
let nonInteractiveDefault = false;
|
|
510
493
|
if (options.repos) {
|
|
511
494
|
repoChoice = options.repos;
|
|
512
495
|
}
|
|
513
496
|
else if (options.yes || !process.stdin.isTTY) {
|
|
514
|
-
|
|
497
|
+
// Blast-radius guard: never initialise every repo under the roots just
|
|
498
|
+
// because nobody could be asked. Non-interactive runs default to the
|
|
499
|
+
// current repo; widening to all requires an explicit --repos all.
|
|
500
|
+
repoChoice = 'current';
|
|
501
|
+
nonInteractiveDefault = true;
|
|
515
502
|
}
|
|
516
503
|
else {
|
|
517
504
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -525,6 +512,9 @@ export async function runSetup(options = {}) {
|
|
|
525
512
|
const selectedRepos = parseRepoSelection(repoChoice, repos);
|
|
526
513
|
if (selectedRepos.length === 0) {
|
|
527
514
|
console.log('No repositories selected. Aborting.');
|
|
515
|
+
if (nonInteractiveDefault) {
|
|
516
|
+
console.log('Non-interactive setup defaults to the current directory\'s repo. Pass --repos all to initialise every repo under the roots.');
|
|
517
|
+
}
|
|
528
518
|
return;
|
|
529
519
|
}
|
|
530
520
|
console.log(`\nSelected ${selectedRepos.length} repository(s).`);
|
|
@@ -585,10 +575,19 @@ function resolveVsixPath() {
|
|
|
585
575
|
const distVsix = path.join(thisDir, '..', 'brainclaw-vscode.vsix');
|
|
586
576
|
if (fs.existsSync(distVsix))
|
|
587
577
|
return distVsix;
|
|
588
|
-
// 2. Dev mode:
|
|
589
|
-
const
|
|
590
|
-
if (fs.existsSync(
|
|
591
|
-
|
|
578
|
+
// 2. Dev mode: pick the newest locally packaged extension.
|
|
579
|
+
const devDir = path.join(thisDir, '..', '..', 'vscode-extension');
|
|
580
|
+
if (fs.existsSync(devDir)) {
|
|
581
|
+
const candidates = fs.readdirSync(devDir)
|
|
582
|
+
.filter((name) => /^brainclaw-vscode-\d+\.\d+\.\d+\.vsix$/.test(name))
|
|
583
|
+
.map((name) => {
|
|
584
|
+
const filePath = path.join(devDir, name);
|
|
585
|
+
return { filePath, mtimeMs: fs.statSync(filePath).mtimeMs };
|
|
586
|
+
})
|
|
587
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
588
|
+
if (candidates[0])
|
|
589
|
+
return candidates[0].filePath;
|
|
590
|
+
}
|
|
592
591
|
return undefined;
|
|
593
592
|
}
|
|
594
593
|
//# sourceMappingURL=setup.js.map
|
package/dist/commands/stale.js
CHANGED
|
@@ -19,7 +19,12 @@ import { removeEntity, transitionEntity, } from '../core/entity-operations.js';
|
|
|
19
19
|
import { listRuntimeNotes } from '../core/runtime.js';
|
|
20
20
|
import { loadState } from '../core/state.js';
|
|
21
21
|
import { detectStaleness, staleSummary, } from '../core/staleness.js';
|
|
22
|
-
/**
|
|
22
|
+
/**
|
|
23
|
+
* Resolve the canonical transition target (or removal) per entity kind.
|
|
24
|
+
* decision/constraint are intentionally absent: they are only flagged for
|
|
25
|
+
* dead related_paths (pln#557 step 2), and the fix is updating the paths via
|
|
26
|
+
* bclaw_update — not a lifecycle transition.
|
|
27
|
+
*/
|
|
23
28
|
const RESOLVE_ACTIONS = {
|
|
24
29
|
plan: { kind: 'transition', entity: 'plan', to: 'dropped' },
|
|
25
30
|
handoff: { kind: 'transition', entity: 'handoff', to: 'closed' },
|
|
@@ -31,7 +36,11 @@ function buildReport(cwd) {
|
|
|
31
36
|
const state = loadState(cwd);
|
|
32
37
|
const pending = listCandidates('pending', cwd);
|
|
33
38
|
const notes = listRuntimeNotes(undefined, cwd);
|
|
34
|
-
return detectStaleness(state.plan_items, state.known_traps, state.open_handoffs, pending, Date.now(), notes
|
|
39
|
+
return detectStaleness(state.plan_items, state.known_traps, state.open_handoffs, pending, Date.now(), notes, {
|
|
40
|
+
decisions: state.recent_decisions,
|
|
41
|
+
constraints: state.active_constraints,
|
|
42
|
+
projectRoot: cwd ?? process.cwd(),
|
|
43
|
+
});
|
|
35
44
|
}
|
|
36
45
|
export function runStaleList(options = {}) {
|
|
37
46
|
const report = buildReport(options.cwd);
|
|
@@ -60,6 +69,11 @@ export function runStaleResolve(id, options = {}) {
|
|
|
60
69
|
process.exit(1);
|
|
61
70
|
}
|
|
62
71
|
const action = RESOLVE_ACTIONS[warning.entity];
|
|
72
|
+
if (!action) {
|
|
73
|
+
console.error(`Error: ${warning.entity} ${id} has no canonical stale-resolve action.`);
|
|
74
|
+
console.error(`Fix it directly instead: ${warning.suggested_action}`);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
63
77
|
try {
|
|
64
78
|
if (action.kind === 'transition') {
|
|
65
79
|
const result = transitionEntity(action.entity, id, action.to, cwd, 'resolved via brainclaw stale resolve');
|
package/dist/commands/switch.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import { loadActiveProject, saveActiveProject, clearActiveProject } from '../core/active-project.js';
|
|
3
|
-
import { buildOperationalIdentity, loadCurrentSession, saveCurrentSession } from '../core/identity.js';
|
|
3
|
+
import { buildOperationalIdentity, loadCurrentSession, loadSessionById, saveCurrentSession } from '../core/identity.js';
|
|
4
4
|
import { memoryExists } from '../core/io.js';
|
|
5
5
|
import { resolveProjectRef } from '../core/store-resolution.js';
|
|
6
6
|
import { resolveCrossProjectLinks, resolveProjectCwd } from '../core/cross-project.js';
|
|
@@ -44,10 +44,28 @@ export function switchProject(projectRef, options = {}) {
|
|
|
44
44
|
catch { /* name is optional */ }
|
|
45
45
|
const now = new Date().toISOString();
|
|
46
46
|
const sessionOnly = options.sessionOnly ?? true;
|
|
47
|
-
let session = loadCurrentSession(cwd);
|
|
47
|
+
let session = options.sessionId ? loadSessionById(options.sessionId, cwd) : loadCurrentSession(cwd);
|
|
48
48
|
if (!session && sessionOnly) {
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
if (options.sessionId) {
|
|
50
|
+
const identity = buildOperationalIdentity(undefined, cwd, {
|
|
51
|
+
sessionId: options.sessionId,
|
|
52
|
+
persistImplicitSession: false,
|
|
53
|
+
});
|
|
54
|
+
saveCurrentSession({
|
|
55
|
+
session_id: options.sessionId,
|
|
56
|
+
started_at: now,
|
|
57
|
+
last_seen_at: now,
|
|
58
|
+
agent: identity.agent,
|
|
59
|
+
agent_id: identity.agent_id,
|
|
60
|
+
host_id: identity.host_id,
|
|
61
|
+
user: process.env.USER || process.env.USERNAME || undefined,
|
|
62
|
+
pid: process.pid,
|
|
63
|
+
}, cwd);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
buildOperationalIdentity(undefined, cwd, { persistImplicitSession: true });
|
|
67
|
+
}
|
|
68
|
+
session = options.sessionId ? loadSessionById(options.sessionId, cwd) : loadCurrentSession(cwd);
|
|
51
69
|
}
|
|
52
70
|
if (session && sessionOnly) {
|
|
53
71
|
saveCurrentSession({
|
|
@@ -78,11 +96,14 @@ export function switchProject(projectRef, options = {}) {
|
|
|
78
96
|
* List available projects in the workspace.
|
|
79
97
|
*/
|
|
80
98
|
export function listAvailableProjects(cwd) {
|
|
99
|
+
return listAvailableProjectsForSession(cwd);
|
|
100
|
+
}
|
|
101
|
+
export function listAvailableProjectsForSession(cwd, sessionId) {
|
|
81
102
|
const wsRoot = findOutermostWorkspaceRoot(cwd ?? process.cwd());
|
|
82
103
|
if (!wsRoot) {
|
|
83
104
|
throw new Error('No brainclaw workspace found.');
|
|
84
105
|
}
|
|
85
|
-
const sessionActive = loadCurrentSession(cwd)?.active_project;
|
|
106
|
+
const sessionActive = (sessionId ? loadSessionById(sessionId, cwd) : loadCurrentSession(cwd))?.active_project;
|
|
86
107
|
const globalActive = loadActiveProject(wsRoot);
|
|
87
108
|
const active = sessionActive ?? globalActive;
|
|
88
109
|
const activeSource = sessionActive ? 'session' : globalActive ? 'global' : 'none';
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import readline from 'node:readline/promises';
|
|
4
3
|
import { MEMORY_DIR, memoryExists } from '../core/io.js';
|
|
5
4
|
import { BRAINCLAW_SECTION_START, BRAINCLAW_SECTION_END, AGENT_EXPORT_REGISTRY, } from '../core/agent-files.js';
|
|
6
5
|
import { resolveHomeDir } from '../core/setup-state.js';
|
|
6
|
+
import { confirmAction } from './confirm.js';
|
|
7
7
|
/**
|
|
8
8
|
* Remove brainclaw from a project and/or machine.
|
|
9
9
|
*
|
|
@@ -29,19 +29,7 @@ async function uninstallProject(cwd, skipConfirm) {
|
|
|
29
29
|
console.log('No .brainclaw/ found in this project. Nothing to uninstall.');
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
|
-
|
|
33
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
34
|
-
try {
|
|
35
|
-
const answer = (await rl.question('Remove brainclaw from this project? This deletes .brainclaw/ and all generated agent files. [y/N]: ')).trim().toLowerCase();
|
|
36
|
-
if (answer !== 'y' && answer !== 'yes') {
|
|
37
|
-
console.log('Aborted.');
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
finally {
|
|
42
|
-
rl.close();
|
|
43
|
-
}
|
|
44
|
-
}
|
|
32
|
+
await confirmAction('Remove brainclaw from this project? This deletes .brainclaw/ and all generated agent files.', skipConfirm);
|
|
45
33
|
// Remove .brainclaw/ directory
|
|
46
34
|
const brainclawDir = path.join(cwd, MEMORY_DIR);
|
|
47
35
|
if (fs.existsSync(brainclawDir)) {
|
|
@@ -88,17 +76,12 @@ async function uninstallProject(cwd, skipConfirm) {
|
|
|
88
76
|
}
|
|
89
77
|
}
|
|
90
78
|
}
|
|
91
|
-
// Remove companion
|
|
79
|
+
// Remove companion files dedicated to brainclaw. Shared JSON configs are
|
|
80
|
+
// stripped below so uninstall does not delete user-owned settings.
|
|
92
81
|
const companionFiles = [
|
|
93
|
-
'.mcp.json',
|
|
94
82
|
'.claude/commands/brainclaw.md',
|
|
95
|
-
'.claude/settings.local.json',
|
|
96
83
|
'.claude/.bclaw-session',
|
|
97
84
|
'.cursor/rules/brainclaw-mcp-shim.mdc',
|
|
98
|
-
'.vscode/cline_mcp_settings.json',
|
|
99
|
-
'.roo/mcp.json',
|
|
100
|
-
'.continue/config.json',
|
|
101
|
-
'opencode.json',
|
|
102
85
|
'.github/skills/brainclaw-context/SKILL.md',
|
|
103
86
|
];
|
|
104
87
|
for (const relativePath of companionFiles) {
|
|
@@ -108,6 +91,7 @@ async function uninstallProject(cwd, skipConfirm) {
|
|
|
108
91
|
console.log(`✔ Removed ${relativePath}`);
|
|
109
92
|
}
|
|
110
93
|
}
|
|
94
|
+
stripProjectCompanionConfigs(cwd);
|
|
111
95
|
console.log('✔ Project uninstall complete.');
|
|
112
96
|
}
|
|
113
97
|
async function uninstallMachine(skipConfirm) {
|
|
@@ -121,19 +105,7 @@ async function uninstallMachine(skipConfirm) {
|
|
|
121
105
|
console.log('No ~/.brainclaw/ found. Nothing to uninstall.');
|
|
122
106
|
return;
|
|
123
107
|
}
|
|
124
|
-
|
|
125
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
126
|
-
try {
|
|
127
|
-
const answer = (await rl.question('Remove brainclaw global config (~/.brainclaw/)? [y/N]: ')).trim().toLowerCase();
|
|
128
|
-
if (answer !== 'y' && answer !== 'yes') {
|
|
129
|
-
console.log('Aborted.');
|
|
130
|
-
return;
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
finally {
|
|
134
|
-
rl.close();
|
|
135
|
-
}
|
|
136
|
-
}
|
|
108
|
+
await confirmAction('Remove brainclaw global config (~/.brainclaw/)?', skipConfirm);
|
|
137
109
|
fs.rmSync(userStore, { recursive: true, force: true });
|
|
138
110
|
console.log('✔ Removed ~/.brainclaw/');
|
|
139
111
|
// Note: global MCP configs in ~/.claude/settings.json, ~/.cursor/mcp.json etc.
|
|
@@ -142,4 +114,124 @@ async function uninstallMachine(skipConfirm) {
|
|
|
142
114
|
console.log('Remove brainclaw entries manually if needed.');
|
|
143
115
|
console.log('✔ Machine uninstall complete.');
|
|
144
116
|
}
|
|
117
|
+
function isJsonObject(value) {
|
|
118
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
119
|
+
}
|
|
120
|
+
function stripBrainclawKeyedMcp(config) {
|
|
121
|
+
const servers = isJsonObject(config.mcpServers) ? { ...config.mcpServers } : undefined;
|
|
122
|
+
if (!servers || !Object.prototype.hasOwnProperty.call(servers, 'brainclaw'))
|
|
123
|
+
return false;
|
|
124
|
+
delete servers.brainclaw;
|
|
125
|
+
if (Object.keys(servers).length === 0) {
|
|
126
|
+
delete config.mcpServers;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
config.mcpServers = servers;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
function stripBrainclawContinueMcp(config) {
|
|
134
|
+
if (!Array.isArray(config.mcpServers))
|
|
135
|
+
return false;
|
|
136
|
+
const next = config.mcpServers.filter((entry) => !(isJsonObject(entry) && entry.name === 'brainclaw'));
|
|
137
|
+
if (next.length === config.mcpServers.length)
|
|
138
|
+
return false;
|
|
139
|
+
if (next.length === 0) {
|
|
140
|
+
delete config.mcpServers;
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
config.mcpServers = next;
|
|
144
|
+
}
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
function stripBrainclawOpenCodeMcp(config) {
|
|
148
|
+
const mcp = isJsonObject(config.mcp) ? { ...config.mcp } : undefined;
|
|
149
|
+
if (!mcp || !Object.prototype.hasOwnProperty.call(mcp, 'brainclaw'))
|
|
150
|
+
return false;
|
|
151
|
+
delete mcp.brainclaw;
|
|
152
|
+
if (Object.keys(mcp).length === 0) {
|
|
153
|
+
delete config.mcp;
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
config.mcp = mcp;
|
|
157
|
+
}
|
|
158
|
+
return true;
|
|
159
|
+
}
|
|
160
|
+
function stripBrainclawClaudePermissions(config, cwd) {
|
|
161
|
+
const permissions = isJsonObject(config.permissions) ? { ...config.permissions } : undefined;
|
|
162
|
+
if (!permissions)
|
|
163
|
+
return false;
|
|
164
|
+
let changed = false;
|
|
165
|
+
if (Array.isArray(permissions.allow)) {
|
|
166
|
+
const next = permissions.allow.filter((entry) => entry !== 'Bash(npx brainclaw:*)' && entry !== 'mcp__brainclaw__*');
|
|
167
|
+
changed = changed || next.length !== permissions.allow.length;
|
|
168
|
+
if (next.length === 0) {
|
|
169
|
+
delete permissions.allow;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
permissions.allow = next;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (Array.isArray(permissions.additionalDirectories)) {
|
|
176
|
+
const brainclawDirs = new Set([
|
|
177
|
+
path.join(cwd, '.claude', 'worktrees'),
|
|
178
|
+
path.join(resolveHomeDir() ?? '', '.brainclaw', 'worktrees'),
|
|
179
|
+
].filter(Boolean).map((entry) => path.resolve(entry).replace(/\\/g, '/').toLowerCase()));
|
|
180
|
+
const next = permissions.additionalDirectories.filter((entry) => {
|
|
181
|
+
if (typeof entry !== 'string')
|
|
182
|
+
return true;
|
|
183
|
+
return !brainclawDirs.has(path.resolve(entry).replace(/\\/g, '/').toLowerCase());
|
|
184
|
+
});
|
|
185
|
+
changed = changed || next.length !== permissions.additionalDirectories.length;
|
|
186
|
+
if (next.length === 0) {
|
|
187
|
+
delete permissions.additionalDirectories;
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
permissions.additionalDirectories = next;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (!changed)
|
|
194
|
+
return false;
|
|
195
|
+
if (Object.keys(permissions).length === 0) {
|
|
196
|
+
delete config.permissions;
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
config.permissions = permissions;
|
|
200
|
+
}
|
|
201
|
+
return true;
|
|
202
|
+
}
|
|
203
|
+
function rewriteJsonConfig(relativePath, cwd, strip) {
|
|
204
|
+
const fullPath = path.join(cwd, relativePath);
|
|
205
|
+
if (!fs.existsSync(fullPath))
|
|
206
|
+
return;
|
|
207
|
+
let config;
|
|
208
|
+
try {
|
|
209
|
+
const parsed = JSON.parse(fs.readFileSync(fullPath, 'utf-8'));
|
|
210
|
+
if (!isJsonObject(parsed))
|
|
211
|
+
return;
|
|
212
|
+
config = { ...parsed };
|
|
213
|
+
}
|
|
214
|
+
catch {
|
|
215
|
+
console.log(`! Skipped ${relativePath} (not valid JSON)`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (!strip(config))
|
|
219
|
+
return;
|
|
220
|
+
if (Object.keys(config).length === 0) {
|
|
221
|
+
fs.unlinkSync(fullPath);
|
|
222
|
+
console.log(`✔ Removed ${relativePath} (was brainclaw-only)`);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
fs.writeFileSync(fullPath, `${JSON.stringify(config, null, 2)}\n`, 'utf-8');
|
|
226
|
+
console.log(`✔ Removed brainclaw entries from ${relativePath}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
function stripProjectCompanionConfigs(cwd) {
|
|
230
|
+
rewriteJsonConfig('.mcp.json', cwd, stripBrainclawKeyedMcp);
|
|
231
|
+
rewriteJsonConfig('.vscode/cline_mcp_settings.json', cwd, stripBrainclawKeyedMcp);
|
|
232
|
+
rewriteJsonConfig('.roo/mcp.json', cwd, stripBrainclawKeyedMcp);
|
|
233
|
+
rewriteJsonConfig('.continue/config.json', cwd, stripBrainclawContinueMcp);
|
|
234
|
+
rewriteJsonConfig('opencode.json', cwd, stripBrainclawOpenCodeMcp);
|
|
235
|
+
rewriteJsonConfig('.claude/settings.local.json', cwd, (config) => stripBrainclawClaudePermissions(config, cwd));
|
|
236
|
+
}
|
|
145
237
|
//# sourceMappingURL=uninstall.js.map
|
|
@@ -14,6 +14,8 @@ export function runUpdateStep(planId, stepId, options) {
|
|
|
14
14
|
status: options.status,
|
|
15
15
|
text: options.text,
|
|
16
16
|
assignee: options.assign,
|
|
17
|
+
estimatedEffort: options.estimate,
|
|
18
|
+
actualEffort: options.actualEffort,
|
|
17
19
|
});
|
|
18
20
|
const changes = [];
|
|
19
21
|
if (options.status)
|
|
@@ -22,6 +24,10 @@ export function runUpdateStep(planId, stepId, options) {
|
|
|
22
24
|
changes.push('text updated');
|
|
23
25
|
if (options.assign !== undefined)
|
|
24
26
|
changes.push(`assignee=${options.assign || 'unassigned'}`);
|
|
27
|
+
if (options.estimate !== undefined)
|
|
28
|
+
changes.push(`estimate=${options.estimate}`);
|
|
29
|
+
if (options.actualEffort !== undefined)
|
|
30
|
+
changes.push(`actual=${options.actualEffort}`);
|
|
25
31
|
console.log(`✔ Step updated: [${result.stepId}] ${changes.join(', ')}`);
|
|
26
32
|
console.log(` Plan [${result.planId}] progress: ${result.doneSteps}/${result.totalSteps} steps done`);
|
|
27
33
|
if (result.planAutoCompleted) {
|
package/dist/commands/version.js
CHANGED
|
@@ -8,7 +8,7 @@ import { generateAgentReleaseNotes } from './release-notes.js';
|
|
|
8
8
|
export function runVersion(options = {}) {
|
|
9
9
|
const cwd = options.cwd ?? process.cwd();
|
|
10
10
|
const initialized = memoryExists(cwd);
|
|
11
|
-
|
|
11
|
+
const config = initialized ? loadConfig(cwd) : undefined;
|
|
12
12
|
let publishedLocalRelease;
|
|
13
13
|
if (options.publishLocal) {
|
|
14
14
|
if (!initialized || !config) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createWorktree, listWorktrees, removeWorktree, pruneWorktrees, cleanMergedWorktrees, mergeWorktreeBranch, worktreesBaseDir, } from '../core/worktree.js';
|
|
2
|
+
import { analyzeMergeRisk } from '../core/merge-risk.js';
|
|
2
3
|
import { memoryExists } from '../core/io.js';
|
|
3
4
|
import { resolveTargetStore } from '../core/store-resolution.js';
|
|
4
5
|
export function runWorktreeCreate(options) {
|
|
@@ -81,6 +82,20 @@ export function runWorktreeClean(options) {
|
|
|
81
82
|
}
|
|
82
83
|
export function runWorktreeMerge(options) {
|
|
83
84
|
const cwd = resolveTargetStore(options.cwd ?? process.cwd(), options.store ?? 'local');
|
|
85
|
+
// Pre-merge conflict reflex (pln#396): warn if another live lane overlaps
|
|
86
|
+
// the files this branch would land. Advisory — never blocks the merge.
|
|
87
|
+
try {
|
|
88
|
+
const risk = analyzeMergeRisk(cwd, { branches: undefined });
|
|
89
|
+
const conflicting = risk.overlaps.filter(o => o.branches.includes(options.branch));
|
|
90
|
+
if (conflicting.length > 0) {
|
|
91
|
+
console.warn(`⚠ Pre-merge risk: ${options.branch} overlaps ${conflicting.length} file(s) with other live lane(s):`);
|
|
92
|
+
for (const o of conflicting.slice(0, 10)) {
|
|
93
|
+
console.warn(` ${o.file} — also in ${o.branches.filter(b => b !== options.branch).join(', ')}`);
|
|
94
|
+
}
|
|
95
|
+
console.warn(' Run `brainclaw worktree check` for the full picture. Proceeding with merge.');
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch { /* advisory only — never block the merge on the risk probe */ }
|
|
84
99
|
const result = mergeWorktreeBranch(cwd, options.branch, {
|
|
85
100
|
message: options.message,
|
|
86
101
|
dryRun: options.dryRun,
|
|
@@ -98,6 +113,51 @@ export function runWorktreeMerge(options) {
|
|
|
98
113
|
console.log(`✔ Merged ${options.branch} → ${result.commitHash}`);
|
|
99
114
|
console.log(` ${result.filesChanged} files changed, ${result.filesRestored} parasitic deletion(s) auto-restored.`);
|
|
100
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* `brainclaw worktree check` (pln#396) — pre-merge conflict detection across
|
|
118
|
+
* the parallel lanes. Exit 0 when lanes are disjoint, 3 when overlaps exist
|
|
119
|
+
* (a distinct non-error code so a supervisor script can gate a batch merge
|
|
120
|
+
* without treating "risk found" as a crash).
|
|
121
|
+
*/
|
|
122
|
+
export function runWorktreeCheck(options) {
|
|
123
|
+
const cwd = resolveTargetStore(options.cwd ?? process.cwd(), options.store ?? 'local');
|
|
124
|
+
const report = analyzeMergeRisk(cwd, { baseRef: options.baseRef });
|
|
125
|
+
if (options.json) {
|
|
126
|
+
console.log(JSON.stringify(report, null, 2));
|
|
127
|
+
if (report.has_risk)
|
|
128
|
+
process.exitCode = 3;
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
printMergeRiskReport(report);
|
|
132
|
+
if (report.has_risk)
|
|
133
|
+
process.exitCode = 3;
|
|
134
|
+
}
|
|
135
|
+
function printMergeRiskReport(report) {
|
|
136
|
+
console.log(`Pre-merge conflict check (base: ${report.base_ref})`);
|
|
137
|
+
console.log(` ${report.summary}`);
|
|
138
|
+
if (report.lanes.length === 0)
|
|
139
|
+
return;
|
|
140
|
+
console.log('\nLanes:');
|
|
141
|
+
for (const lane of report.lanes) {
|
|
142
|
+
const who = lane.claim_id
|
|
143
|
+
? `${lane.agent ?? 'unknown'} · claim ${lane.claim_id}`
|
|
144
|
+
: (lane.agent ?? lane.session_id ?? 'unclaimed');
|
|
145
|
+
const dirtyNote = lane.dirty_files.length ? ` (+${lane.dirty_files.length} uncommitted)` : '';
|
|
146
|
+
console.log(` • ${lane.branch} [${who}] — ${lane.changed_files.length} file(s)${dirtyNote}`);
|
|
147
|
+
}
|
|
148
|
+
if (report.overlaps.length > 0) {
|
|
149
|
+
console.log('\n⚠ Overlapping files (potential conflicts on merge):');
|
|
150
|
+
for (const o of report.overlaps) {
|
|
151
|
+
console.log(` ${o.file}`);
|
|
152
|
+
console.log(` lanes: ${o.branches.join(', ')}`);
|
|
153
|
+
if (o.claims.length)
|
|
154
|
+
console.log(` claims: ${o.claims.join(', ')}`);
|
|
155
|
+
}
|
|
156
|
+
console.log('\nMerge protocol: merge one overlapping lane, then rebase/re-check the others');
|
|
157
|
+
console.log('before merging them. Disjoint lanes can merge in any order. See');
|
|
158
|
+
console.log('docs/concepts/parallel-merge-protocol.md.');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
101
161
|
/** Returns WorktreeInfo[] for use by MCP tools. */
|
|
102
162
|
export function getWorktrees(cwd) {
|
|
103
163
|
return listWorktrees(cwd);
|
package/dist/core/actions.js
CHANGED
|
@@ -7,6 +7,7 @@ import { nowISO, generateIdWithLabel } from './ids.js';
|
|
|
7
7
|
import { ActionRequiredSchema, } from './schema.js';
|
|
8
8
|
import { saveVersionedJsonFile } from './migration.js';
|
|
9
9
|
import { appendAuditEntry } from './audit.js';
|
|
10
|
+
import { emitRegistryPostImage, registryFaultPoint } from './events/registry-post-image.js';
|
|
10
11
|
import { createRuntimeEvent } from './events.js';
|
|
11
12
|
import { loadAssignment, transitionAssignment } from './assignments.js';
|
|
12
13
|
import { loadAgentRun, transitionAgentRun } from './agentruns.js';
|
|
@@ -119,10 +120,12 @@ function expireStaleActions(actions, cwd) {
|
|
|
119
120
|
}
|
|
120
121
|
return actions;
|
|
121
122
|
}
|
|
122
|
-
export function listActionRequired(cwd, filter = {}) {
|
|
123
|
+
export function listActionRequired(cwd, filter = {}, options = {}) {
|
|
123
124
|
let actions = actionStore(cwd).list();
|
|
124
125
|
// Sweep-on-read: expire stale pending actions
|
|
125
|
-
|
|
126
|
+
if (options.expireStale !== false) {
|
|
127
|
+
actions = expireStaleActions(actions, cwd);
|
|
128
|
+
}
|
|
126
129
|
if (filter.status)
|
|
127
130
|
actions = actions.filter((action) => action.status === filter.status);
|
|
128
131
|
if (filter.kind)
|
|
@@ -145,7 +148,13 @@ function saveActionRequired(action, cwd) {
|
|
|
145
148
|
mutate({ cwd }, () => {
|
|
146
149
|
ensureActionsDir(cwd);
|
|
147
150
|
const filepath = path.join(actionsDir(cwd, 'write'), `${action.id}.json`);
|
|
148
|
-
|
|
151
|
+
const parsed = ActionRequiredSchema.parse(action);
|
|
152
|
+
// pln#568 (I2): journal the post-image BEFORE the projection write. The
|
|
153
|
+
// action family journals under item_type 'state' (the observer's slot).
|
|
154
|
+
const created = !fs.existsSync(filepath);
|
|
155
|
+
emitRegistryPostImage('action', parsed, { created, agent: parsed.agent, agent_id: parsed.agent_id, session_id: parsed.session_id, cwd });
|
|
156
|
+
registryFaultPoint('after_registry_journal');
|
|
157
|
+
saveVersionedJsonFile('action_required', filepath, parsed);
|
|
149
158
|
});
|
|
150
159
|
}
|
|
151
160
|
export function createActionRequired(options, cwd) {
|
|
@@ -4,14 +4,12 @@
|
|
|
4
4
|
* integration depth, and pressure level accordingly.
|
|
5
5
|
*
|
|
6
6
|
* Three profile tiers drive instruction file templates:
|
|
7
|
-
* A (full)
|
|
8
|
-
* B (standard)
|
|
9
|
-
* C (limited)
|
|
7
|
+
* A (full) — managed MCP/native surfaces → lightweight instructions
|
|
8
|
+
* B (standard) — MCP with fewer automation surfaces → more directive rules
|
|
9
|
+
* C (limited) — no MCP → rich static content (plans, traps, decisions)
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* Note: Cline hooks don't work on Windows — but templateTier stays A
|
|
14
|
-
* because brainclaw generates hooks that gracefully degrade.
|
|
11
|
+
* Hook support is declared separately through `hasHooks` and `runtime.hooks`.
|
|
12
|
+
* Do not infer hooks from `templateTier`.
|
|
15
13
|
*/
|
|
16
14
|
import os from 'node:os';
|
|
17
15
|
import path from 'node:path';
|
|
@@ -25,9 +23,21 @@ const AGENT_ALIASES = {
|
|
|
25
23
|
'vibe': 'mistral-vibe',
|
|
26
24
|
'hermes-agent': 'hermes',
|
|
27
25
|
};
|
|
28
|
-
/**
|
|
26
|
+
/**
|
|
27
|
+
* Resolve an alias to its canonical agent name (case-insensitive).
|
|
28
|
+
*
|
|
29
|
+
* Agent names are case-insensitive: every canonical profile key and every alias
|
|
30
|
+
* is lowercase, so we trim + lowercase the input before resolving. This is the
|
|
31
|
+
* single normalization point — registry, messaging, spawn-check and the
|
|
32
|
+
* coordinate/dispatch pre-flight all route through here, so a target like
|
|
33
|
+
* "Codex" or "Gemini" resolves identically to "codex" / "gemini". Without it the
|
|
34
|
+
* dispatch pre-flight collapsed an unresolved name into "no CLI spawn template
|
|
35
|
+
* (IDE-only?)" and silently dropped the reviewer (github-copilot/Gemini hit this
|
|
36
|
+
* passing targetAgents=["Codex"]).
|
|
37
|
+
*/
|
|
29
38
|
export function resolveAgentAlias(name) {
|
|
30
|
-
|
|
39
|
+
const key = name.trim().toLowerCase();
|
|
40
|
+
return AGENT_ALIASES[key] ?? key;
|
|
31
41
|
}
|
|
32
42
|
const PROFILES = {
|
|
33
43
|
// --- Code agents (interactive, IDE-driven) ---
|
|
@@ -77,20 +87,20 @@ const PROFILES = {
|
|
|
77
87
|
},
|
|
78
88
|
windsurf: {
|
|
79
89
|
name: 'windsurf', category: 'code-agent', workflowModel: 'interactive',
|
|
80
|
-
hasMcp: true, hasHooks:
|
|
90
|
+
hasMcp: true, hasHooks: false, hasAutoApprove: false, hasSkills: false, hasRules: true,
|
|
81
91
|
instructionFile: '.windsurfrules', sharedInstructionFile: true, mcpConfigScope: 'machine', templateTier: 'A',
|
|
82
92
|
role_capabilities: ['execute', 'review'],
|
|
83
|
-
runtime: { mcp_direct: true, hooks:
|
|
93
|
+
runtime: { mcp_direct: true, hooks: false, canBeSpawnedCli: false, canSpawnOtherCli: false, inbox: false },
|
|
84
94
|
max_concurrent_tasks: 1,
|
|
85
95
|
prompt_delivery: { methods: ['inbox_structured'], preferred: 'inbox_structured' },
|
|
86
96
|
execution_env: { surface: 'ide' },
|
|
87
97
|
},
|
|
88
98
|
cline: {
|
|
89
99
|
name: 'cline', category: 'code-agent', workflowModel: 'interactive',
|
|
90
|
-
hasMcp: true, hasHooks:
|
|
100
|
+
hasMcp: true, hasHooks: false, hasAutoApprove: true, hasSkills: false, hasRules: true,
|
|
91
101
|
instructionFile: '.clinerules/brainclaw.md', sharedInstructionFile: false, mcpConfigScope: 'project', templateTier: 'A',
|
|
92
102
|
role_capabilities: ['execute', 'review'],
|
|
93
|
-
runtime: { mcp_direct: true, hooks:
|
|
103
|
+
runtime: { mcp_direct: true, hooks: false, canBeSpawnedCli: true, canSpawnOtherCli: false, inbox: true },
|
|
94
104
|
max_concurrent_tasks: 3,
|
|
95
105
|
prompt_delivery: { methods: ['inline_arg', 'inbox_structured'], preferred: 'inline_arg', max_inline_length: 8000 },
|
|
96
106
|
execution_env: { surface: 'extension' },
|
|
@@ -143,10 +153,10 @@ const PROFILES = {
|
|
|
143
153
|
// worktree. The coordinator then syncs them via bclaw_harvest_candidates.
|
|
144
154
|
codex: {
|
|
145
155
|
name: 'codex', category: 'code-agent', workflowModel: 'task-based',
|
|
146
|
-
hasMcp: true, hasHooks:
|
|
156
|
+
hasMcp: true, hasHooks: false, hasAutoApprove: false, hasSkills: true, hasRules: true,
|
|
147
157
|
instructionFile: 'AGENTS.md', sharedInstructionFile: true, mcpConfigScope: 'machine', templateTier: 'A',
|
|
148
158
|
role_capabilities: ['execute', 'review'],
|
|
149
|
-
runtime: { mcp_direct: true, hooks:
|
|
159
|
+
runtime: { mcp_direct: true, hooks: false, canBeSpawnedCli: true, canSpawnOtherCli: false, inbox: true },
|
|
150
160
|
max_concurrent_tasks: 5,
|
|
151
161
|
// pln#475: prefer stdin_pipe to avoid Windows cmd.exe arg-parsing breaking
|
|
152
162
|
// long prompts. codex.cmd resolves through cmd shell, where embedded
|
|
@@ -316,7 +326,10 @@ const _customProfiles = new Map();
|
|
|
316
326
|
* Custom profiles take precedence over DEFAULT_CAPABILITY_PROFILES on lookup.
|
|
317
327
|
*/
|
|
318
328
|
export function registerCapabilityProfile(profile) {
|
|
319
|
-
|
|
329
|
+
// Key by the normalized (lowercased) name so lookups through the
|
|
330
|
+
// case-insensitive resolveAgentAlias match regardless of the casing the
|
|
331
|
+
// registrant used.
|
|
332
|
+
_customProfiles.set(profile.name.trim().toLowerCase(), profile);
|
|
320
333
|
}
|
|
321
334
|
/**
|
|
322
335
|
* Get the capability profile for an agent by name.
|
|
@@ -669,7 +682,7 @@ export function resolveBriefMode(agentName) {
|
|
|
669
682
|
* pln#528 — capability matrix DERIVED from the spawn template, so it stays in
|
|
670
683
|
* sync with how each agent is actually invoked (no per-profile duplication).
|
|
671
684
|
*
|
|
672
|
-
* The motivating reality (debrief
|
|
685
|
+
* The motivating reality (a cross-project field debrief): codex is spawned with
|
|
673
686
|
* `--sandbox workspace-write`, which (a) does NOT wire the brainclaw MCP server
|
|
674
687
|
* and (b) puts `.git` outside the sandbox root — so a sandboxed worker can
|
|
675
688
|
* neither call `bclaw_*` nor `git commit`, regardless of the profile's nominal
|