brainclaw 1.7.5 → 1.9.0
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 +28 -11
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +139 -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 +502 -16
- package/dist/commands/init.js +123 -21
- package/dist/commands/loops-handlers.js +4 -0
- package/dist/commands/mcp-read-handlers.js +198 -29
- package/dist/commands/mcp.js +615 -92
- 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/uninstall.js +126 -34
- package/dist/commands/update-step.js +6 -0
- package/dist/commands/worktree.js +60 -0
- package/dist/core/actions.js +12 -3
- package/dist/core/agent-capability.js +11 -13
- package/dist/core/agent-files.js +844 -547
- 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/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/dispatch-status.js +109 -5
- package/dist/core/dispatcher.js +65 -11
- package/dist/core/entity-operations.js +45 -24
- 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 +25 -0
- package/dist/core/facade-schema.js +48 -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 -1
- 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 +8 -1
- 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 +71 -9
- package/dist/core/runtime.js +84 -1
- package/dist/core/schema.js +125 -0
- package/dist/core/security-detectors.js +125 -0
- package/dist/core/security-extract.js +189 -0
- package/dist/core/security-guard.js +107 -29
- 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 +110 -4
- package/dist/core/staleness.js +109 -1
- package/dist/core/state.js +250 -54
- package/dist/core/store-resolution.js +19 -5
- package/dist/core/worktree.js +169 -7
- package/dist/facts.js +8 -8
- package/dist/facts.json +7 -7
- package/docs/PROTOCOL.md +223 -0
- package/docs/cli.md +11 -10
- package/docs/concepts/coordinator-runbook.md +129 -0
- package/docs/concepts/dispatch-lifecycle.md +17 -0
- package/docs/concepts/event-log-store-critique-A.md +333 -0
- package/docs/concepts/event-log-store-critique-B.md +353 -0
- package/docs/concepts/event-log-store-phase0-measurements.md +58 -0
- package/docs/concepts/event-log-store-proposal-A.md +365 -0
- package/docs/concepts/event-log-store-proposal-B.md +404 -0
- package/docs/concepts/event-log-store.md +928 -0
- package/docs/concepts/identity-model-proposal.md +371 -0
- package/docs/concepts/memory.md +5 -4
- package/docs/concepts/observer-protocol.md +361 -0
- package/docs/concepts/parallel-merge-protocol.md +71 -0
- package/docs/concepts/plans-and-claims.md +43 -0
- package/docs/concepts/skills.md +78 -0
- package/docs/concepts/workspace-bootstrapping.md +61 -0
- package/docs/integrations/agents.md +4 -4
- package/docs/integrations/cline.md +10 -11
- package/docs/integrations/codex.md +2 -2
- package/docs/integrations/continue.md +5 -5
- package/docs/integrations/copilot.md +14 -12
- package/docs/integrations/openclaw.md +7 -6
- package/docs/integrations/overview.md +7 -7
- package/docs/integrations/roo.md +3 -3
- package/docs/integrations/windsurf.md +6 -6
- package/docs/mcp-schema-changelog.md +51 -20
- package/docs/quickstart.md +48 -47
- package/docs/security.md +174 -15
- package/docs/storage.md +4 -2
- package/package.json +8 -6
|
@@ -6,8 +6,53 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module
|
|
8
8
|
*/
|
|
9
|
-
import
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { loadState, mutateState } from '../state.js';
|
|
10
11
|
import { resolveStoreChain, resolveTargetStore } from '../store-resolution.js';
|
|
12
|
+
function bucketFor(state, itemType) {
|
|
13
|
+
if (itemType === 'constraint')
|
|
14
|
+
return state.active_constraints;
|
|
15
|
+
if (itemType === 'decision')
|
|
16
|
+
return state.recent_decisions;
|
|
17
|
+
return state.known_traps;
|
|
18
|
+
}
|
|
19
|
+
function findInState(state, itemId, itemType) {
|
|
20
|
+
return bucketFor(state, itemType).find((item) => item.id === itemId || item.short_label === itemId);
|
|
21
|
+
}
|
|
22
|
+
function replaceInState(state, itemId, itemType, item) {
|
|
23
|
+
const bucket = bucketFor(state, itemType);
|
|
24
|
+
const idx = bucket.findIndex((entry) => entry.id === itemId || entry.short_label === itemId);
|
|
25
|
+
if (idx < 0)
|
|
26
|
+
return false;
|
|
27
|
+
bucket[idx] = item;
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
function removeFromState(state, itemId, itemType) {
|
|
31
|
+
const bucket = bucketFor(state, itemType);
|
|
32
|
+
const before = bucket.length;
|
|
33
|
+
const filtered = bucket.filter((item) => item.id !== itemId && item.short_label !== itemId);
|
|
34
|
+
if (itemType === 'constraint')
|
|
35
|
+
state.active_constraints = filtered;
|
|
36
|
+
if (itemType === 'decision')
|
|
37
|
+
state.recent_decisions = filtered;
|
|
38
|
+
if (itemType === 'trap')
|
|
39
|
+
state.known_traps = filtered;
|
|
40
|
+
return filtered.length !== before;
|
|
41
|
+
}
|
|
42
|
+
function applyMemoryPatch(item, input) {
|
|
43
|
+
const next = { ...item };
|
|
44
|
+
if (input.text)
|
|
45
|
+
next.text = input.text;
|
|
46
|
+
if (input.tags)
|
|
47
|
+
next.tags = input.tags;
|
|
48
|
+
if (input.status && input.type === 'trap') {
|
|
49
|
+
next.status = input.status;
|
|
50
|
+
}
|
|
51
|
+
if (input.patch) {
|
|
52
|
+
Object.assign(next, input.patch);
|
|
53
|
+
}
|
|
54
|
+
return next;
|
|
55
|
+
}
|
|
11
56
|
/**
|
|
12
57
|
* Walk the store chain to find a memory item by type and id.
|
|
13
58
|
* Searches by both id and short_label.
|
|
@@ -37,17 +82,11 @@ export function deleteMemoryItem(itemId, itemType, cwd) {
|
|
|
37
82
|
if (!found) {
|
|
38
83
|
throw new Error(`${itemType} with id '${itemId}' not found in any store`);
|
|
39
84
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
state.recent_decisions = state.recent_decisions.filter((d) => d.id !== itemId && d.short_label !== itemId);
|
|
46
|
-
}
|
|
47
|
-
else if (itemType === 'trap') {
|
|
48
|
-
state.known_traps = state.known_traps.filter((t) => t.id !== itemId && t.short_label !== itemId);
|
|
49
|
-
}
|
|
50
|
-
persistState(state, found.store.cwd);
|
|
85
|
+
mutateState((state) => {
|
|
86
|
+
if (!removeFromState(state, itemId, itemType)) {
|
|
87
|
+
throw new Error(`${itemType} with id '${itemId}' not found in ${found.store.role} store`);
|
|
88
|
+
}
|
|
89
|
+
}, found.store.cwd);
|
|
51
90
|
return {
|
|
52
91
|
deletedId: itemId,
|
|
53
92
|
itemType,
|
|
@@ -59,65 +98,51 @@ export function updateMemoryItem(input, cwd) {
|
|
|
59
98
|
if (!found) {
|
|
60
99
|
throw new Error(`${input.type} with id '${input.id}' not found in any store`);
|
|
61
100
|
}
|
|
62
|
-
const {
|
|
101
|
+
const { store: sourceStore } = found;
|
|
63
102
|
const previousStore = sourceStore.role;
|
|
64
|
-
// Apply field updates
|
|
65
|
-
if (input.text)
|
|
66
|
-
item.text = input.text;
|
|
67
|
-
if (input.tags)
|
|
68
|
-
item.tags = input.tags;
|
|
69
|
-
if (input.status && input.type === 'trap') {
|
|
70
|
-
item.status = input.status;
|
|
71
|
-
}
|
|
72
|
-
if (input.patch) {
|
|
73
|
-
Object.assign(item, input.patch);
|
|
74
|
-
}
|
|
75
103
|
if (input.moveToStore) {
|
|
76
104
|
const targetCwd = resolveTargetStore(cwd, input.moveToStore);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
105
|
+
if (path.resolve(targetCwd) === path.resolve(sourceStore.cwd)) {
|
|
106
|
+
mutateState((state) => {
|
|
107
|
+
const current = findInState(state, input.id, input.type);
|
|
108
|
+
if (!current) {
|
|
109
|
+
throw new Error(`${input.type} with id '${input.id}' not found in ${sourceStore.role} store`);
|
|
110
|
+
}
|
|
111
|
+
replaceInState(state, input.id, input.type, applyMemoryPatch(current, input));
|
|
112
|
+
}, sourceStore.cwd);
|
|
113
|
+
return {
|
|
114
|
+
updatedId: input.id,
|
|
115
|
+
itemType: input.type,
|
|
116
|
+
previousStore,
|
|
117
|
+
newStore: input.moveToStore,
|
|
118
|
+
};
|
|
84
119
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
120
|
+
const movedItem = mutateState((state) => {
|
|
121
|
+
const current = findInState(state, input.id, input.type);
|
|
122
|
+
if (!current) {
|
|
123
|
+
throw new Error(`${input.type} with id '${input.id}' not found in ${sourceStore.role} store`);
|
|
124
|
+
}
|
|
125
|
+
return applyMemoryPatch(current, input);
|
|
126
|
+
}, sourceStore.cwd);
|
|
127
|
+
// Write target before deleting source. A failure can leave a duplicate, but
|
|
128
|
+
// not silent data loss.
|
|
129
|
+
mutateState((state) => {
|
|
130
|
+
if (!replaceInState(state, input.id, input.type, movedItem)) {
|
|
131
|
+
bucketFor(state, input.type).push(movedItem);
|
|
132
|
+
}
|
|
133
|
+
}, targetCwd);
|
|
134
|
+
mutateState((state) => {
|
|
135
|
+
removeFromState(state, input.id, input.type);
|
|
136
|
+
}, sourceStore.cwd);
|
|
101
137
|
}
|
|
102
138
|
else {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
}
|
|
110
|
-
else if (input.type === 'decision') {
|
|
111
|
-
const idx = state.recent_decisions.findIndex((d) => d.id === input.id);
|
|
112
|
-
if (idx >= 0)
|
|
113
|
-
state.recent_decisions[idx] = item;
|
|
114
|
-
}
|
|
115
|
-
else if (input.type === 'trap') {
|
|
116
|
-
const idx = state.known_traps.findIndex((t) => t.id === input.id);
|
|
117
|
-
if (idx >= 0)
|
|
118
|
-
state.known_traps[idx] = item;
|
|
119
|
-
}
|
|
120
|
-
persistState(state, sourceStore.cwd);
|
|
139
|
+
mutateState((state) => {
|
|
140
|
+
const current = findInState(state, input.id, input.type);
|
|
141
|
+
if (!current) {
|
|
142
|
+
throw new Error(`${input.type} with id '${input.id}' not found in ${sourceStore.role} store`);
|
|
143
|
+
}
|
|
144
|
+
replaceInState(state, input.id, input.type, applyMemoryPatch(current, input));
|
|
145
|
+
}, sourceStore.cwd);
|
|
121
146
|
}
|
|
122
147
|
return {
|
|
123
148
|
updatedId: input.id,
|
|
@@ -52,6 +52,10 @@ export function addStep(input, cwd) {
|
|
|
52
52
|
assignee: input.assignee,
|
|
53
53
|
created_at: nowISO(),
|
|
54
54
|
updated_at: nowISO(),
|
|
55
|
+
// PlanStepSchema preprocesses estimated_effort (string→minutes); the cast
|
|
56
|
+
// defers coercion to the schema parse on persist, matching plan-level.
|
|
57
|
+
estimated_effort: input.estimatedEffort,
|
|
58
|
+
actual_effort: input.actualEffort,
|
|
55
59
|
};
|
|
56
60
|
plan.steps = [...(plan.steps ?? []), step];
|
|
57
61
|
plan.updated_at = nowISO();
|
|
@@ -106,7 +110,12 @@ export function completeStep(input, cwd) {
|
|
|
106
110
|
throw new Error(`Step '${input.stepId}' not found in plan '${input.planId}'.`);
|
|
107
111
|
}
|
|
108
112
|
const timestamp = nowISO();
|
|
113
|
+
const previousStatus = step.status;
|
|
109
114
|
step.status = 'done';
|
|
115
|
+
if (!step.started_at)
|
|
116
|
+
step.started_at = timestamp;
|
|
117
|
+
if (previousStatus !== 'done' || !step.completed_at)
|
|
118
|
+
step.completed_at = timestamp; // pln#495: stamp completion for per-step duration
|
|
110
119
|
step.updated_at = timestamp;
|
|
111
120
|
plan.updated_at = timestamp;
|
|
112
121
|
const totalSteps = plan.steps.length;
|
|
@@ -135,12 +144,29 @@ export function updateStep(input, cwd) {
|
|
|
135
144
|
throw new Error(`Step '${input.stepId}' not found in plan '${input.planId}'.`);
|
|
136
145
|
}
|
|
137
146
|
const timestamp = nowISO();
|
|
138
|
-
if (input.status)
|
|
147
|
+
if (input.status) {
|
|
148
|
+
const previousStatus = step.status;
|
|
139
149
|
step.status = input.status;
|
|
150
|
+
// pln#495: stamp the step lifecycle so the report can sum per-step
|
|
151
|
+
// durations (started→completed) and exclude inter-step idle gaps.
|
|
152
|
+
if (input.status !== 'todo' && (!step.started_at || previousStatus === 'done'))
|
|
153
|
+
step.started_at = timestamp;
|
|
154
|
+
if (input.status === 'done') {
|
|
155
|
+
if (previousStatus !== 'done' || !step.completed_at)
|
|
156
|
+
step.completed_at = timestamp;
|
|
157
|
+
}
|
|
158
|
+
else if (previousStatus === 'done') {
|
|
159
|
+
step.completed_at = undefined;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
140
162
|
if (input.text !== undefined)
|
|
141
163
|
step.text = input.text;
|
|
142
164
|
if (input.assignee !== undefined)
|
|
143
165
|
step.assignee = input.assignee || undefined;
|
|
166
|
+
if (input.estimatedEffort !== undefined)
|
|
167
|
+
step.estimated_effort = input.estimatedEffort;
|
|
168
|
+
if (input.actualEffort !== undefined)
|
|
169
|
+
step.actual_effort = input.actualEffort;
|
|
144
170
|
step.updated_at = timestamp;
|
|
145
171
|
plan.updated_at = timestamp;
|
|
146
172
|
const totalSteps = plan.steps.length;
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Protocol-skills pack (pln#519) — workflow-decomposed agent skills.
|
|
3
|
+
*
|
|
4
|
+
* Three SKILL.md files that package brainclaw's critical workflows so an agent
|
|
5
|
+
* loads THE right protocol at the right moment, instead of skimming a
|
|
6
|
+
* monolithic AGENTS.md. Orthogonal to the agent-PROFILE skills
|
|
7
|
+
* (openclaw/nano-/nemo-/pico-/zeroclaw): the same agent can load both. Marked
|
|
8
|
+
* `metadata.protocol: true` so skill-loader UIs can list protocols separately.
|
|
9
|
+
*
|
|
10
|
+
* Design: .brainclaw/coordination/briefs/pln519-protocol-skills-design.md.
|
|
11
|
+
* Content lives here as the single source of truth and is EMBEDDED (not read
|
|
12
|
+
* from a repo file) so it ships and installs identically whether brainclaw runs
|
|
13
|
+
* from source or an npm install — matching ensureUniversalBrainclawSkill.
|
|
14
|
+
*
|
|
15
|
+
* Guard-rails (design §E): skills carry NO dynamic state (claim/loop/plan ids
|
|
16
|
+
* are always live lookups, never literals), reference facade verbs by name only
|
|
17
|
+
* (never re-implement mcp.ts logic), show BOTH the MCP call and the CLI
|
|
18
|
+
* fallback (cold-start / worktree-without-.brainclaw cases), and stay capped at
|
|
19
|
+
* 3 for this version.
|
|
20
|
+
*/
|
|
21
|
+
const SESSION_BODY = `# brainclaw-session
|
|
22
|
+
|
|
23
|
+
Open / resume / close a working session on a brainclaw project. Prefer the MCP
|
|
24
|
+
facade; the \`brainclaw …\` CLI is the fallback when the MCP server is not
|
|
25
|
+
reachable (cold start, or a worktree without \`.brainclaw/\`).
|
|
26
|
+
|
|
27
|
+
## When to use
|
|
28
|
+
|
|
29
|
+
- Starting a NEW session on a brainclaw project — any first call in the session, or after a context compaction.
|
|
30
|
+
- RESUMING outstanding work — \`BRAINCLAW_CLAIM_ID\` is set, or the operator says "continue X".
|
|
31
|
+
- About to EDIT a specific scope (file / dir / feature) that should be reserved against other agents.
|
|
32
|
+
|
|
33
|
+
## Workflow
|
|
34
|
+
|
|
35
|
+
1. \`bclaw_work(intent='consult')\` — loads project memory and reports active claims. Read \`bootstrap_recommended\`: if true the project has no usable PROJECT.md (see \`brainclaw-multi-agent\` → bootstrap loop).
|
|
36
|
+
2. If you will edit a scope, claim it: \`bclaw_work(intent='execute', scope='<path-or-feature>')\`. The response's \`claim_id\` is yours; \`claim_status='created'\` = new, \`'existing'\` = resumed.
|
|
37
|
+
3. Do the work. Honor the \`warnings\` array (claim conflicts, sensitive paths, high-severity traps on your scope).
|
|
38
|
+
4. When done (committed, tested), \`bclaw_session_end(autoRelease: true)\` — closes the session record and releases your remaining claims. \`autoRelease\` defaults to false; pass it explicitly or claims survive the session.
|
|
39
|
+
|
|
40
|
+
CLI fallback: \`brainclaw context --json\` · \`brainclaw claim create "<desc>" --scope <path>\` · \`brainclaw session-end --auto-release\`.
|
|
41
|
+
|
|
42
|
+
## Anti-rationalizations
|
|
43
|
+
|
|
44
|
+
- **"I'm just exploring, I'll skip session-end."** → A live claim outlives a crash. The next agent sees your stale claim and is blocked. Calling \`bclaw_session_end(autoRelease: true)\` is the zero-cost guarantee — without the flag the claim survives.
|
|
45
|
+
- **"I know the project, I don't need to consult."** → State changes between sessions (commits, new traps, new constraints). Consult is cheap and surfaces what you'd miss.
|
|
46
|
+
- **"I'll claim later once I know the exact scope."** → Claim-before-edit IS the contract; it is exactly what prevents races with parallel agents.
|
|
47
|
+
|
|
48
|
+
## Red flags
|
|
49
|
+
|
|
50
|
+
- \`claim_conflicts\` reports another agent's claim on your target scope → STOP. Route through coordination (\`bclaw_coordinate intent='reroute'\` or ask the operator); never override silently.
|
|
51
|
+
- High-severity \`[trap]\`/\`[constraint]\` warning on your scope → read it before editing.
|
|
52
|
+
- \`bootstrap_recommended=true\` while you're about to make architectural calls → the project has no PROJECT.md; consider a bootstrap loop first.
|
|
53
|
+
|
|
54
|
+
## Verification
|
|
55
|
+
|
|
56
|
+
- The \`claim_id\` returned stays stable across subsequent calls (session continuity).
|
|
57
|
+
- After session-end, \`bclaw_find(entity='claim', filter={status:'active'})\` shows no active claim of yours on the scope.
|
|
58
|
+
|
|
59
|
+
## See also
|
|
60
|
+
|
|
61
|
+
- \`brainclaw-memory-capture\` — for decisions/traps captured DURING the session.
|
|
62
|
+
- \`brainclaw-multi-agent\` — for delegating part of the work.
|
|
63
|
+
`;
|
|
64
|
+
const MEMORY_CAPTURE_BODY = `# brainclaw-memory-capture
|
|
65
|
+
|
|
66
|
+
Capture project memory at the RIGHT granularity and type, so it is retrievable
|
|
67
|
+
later. The entity type is not cosmetic — it drives retrieval and surfacing.
|
|
68
|
+
|
|
69
|
+
## When to use
|
|
70
|
+
|
|
71
|
+
- You made a **design call** future agents must respect → **decision**.
|
|
72
|
+
- You hit an **externally-imposed rule** you cannot relax alone → **constraint**.
|
|
73
|
+
- You found an **environment / process pitfall** another agent would also hit → **trap**.
|
|
74
|
+
- You finished a chunk of work to be consumed by another agent → **handoff**.
|
|
75
|
+
- A keep-worthy observation that is not a hard decision/constraint/trap → **runtime_note** (lowest signal).
|
|
76
|
+
- You are genuinely unsure of the type → **candidate** (carries a proposed \`type\`; a reviewer reclassifies).
|
|
77
|
+
|
|
78
|
+
## Workflow
|
|
79
|
+
|
|
80
|
+
1. Pick the type:
|
|
81
|
+
|
|
82
|
+
\`\`\`
|
|
83
|
+
Negotiated + recorded as the way forward? → decision
|
|
84
|
+
Imposed externally, cannot be relaxed by you? → constraint
|
|
85
|
+
About HOW the system/env/tools behave (not WHAT)? → trap
|
|
86
|
+
A "danger ahead" pointer for a future agent? → trap
|
|
87
|
+
Output of work to be consumed by another agent? → handoff
|
|
88
|
+
Unsure between decision/constraint/trap? → candidate (with type)
|
|
89
|
+
Else → runtime_note
|
|
90
|
+
\`\`\`
|
|
91
|
+
|
|
92
|
+
2. Write via the canonical grammar: \`bclaw_create(entity='<type>', data={ text, author, ...optional })\`. Declare the classifying field yourself (caller assertion wins over keyword heuristics): for a \`decision\` set \`outcome\` (one of \`approved | rejected | deferred | pending\` — \`pending\` until ratified); for a \`trap\` set \`severity\` (\`low | medium | high\`, defaults to medium); for a \`constraint\` set \`category\`; for a \`candidate\` its proposed \`type\` (\`constraint | decision | trap | handoff\` — required). \`handoff\` is NOT created via bclaw_create — use \`brainclaw handoff "<text>"\` (CLI) or let \`bclaw_session_end(reflectHandoff: true)\` materialize one from commits. For free-form capture restricted to \`decision | trap | constraint | note\`, \`bclaw_quick_capture(text, type)\` is the shortcut.
|
|
93
|
+
3. Verify it is re-readable: \`bclaw_get(entity='<type>', id='<id>')\` returns your content. If \`bclaw_create\` returned \`validation_error\` (e.g. \`outcome\` not in enum), fix the field and retry — the error message lists the accepted values.
|
|
94
|
+
|
|
95
|
+
CLI fallback: \`brainclaw memory create <type> "<text>"\` (decision | constraint | trap | handoff via top-level \`brainclaw handoff "<text>"\`).
|
|
96
|
+
|
|
97
|
+
## Anti-rationalizations
|
|
98
|
+
|
|
99
|
+
- **"I'll write a runtime_note, I'm unsure of the type."** → Notes are the lowest-signal type (aggregated, not surfaced). If it's actionable, pick decision/constraint/trap — being wrong is fine, that's what candidates are for.
|
|
100
|
+
- **"I'll add it as a decision AND a runtime_note to be safe."** → Duplicates pollute search and retrieval. Pick ONE.
|
|
101
|
+
- **"I'll write the decision without an outcome."** → \`outcome\` is optional but enum-validated when set; an invalid value (e.g. \`proposed\`, \`accepted\`) is rejected with the accepted list (\`approved | rejected | deferred | pending\`). Set \`pending\` early if not yet ratified — it makes the lifecycle explicit and lets reviewers transition it cleanly.
|
|
102
|
+
- **"A 5-line trap for a 1-line problem."** → Traps are read under pressure. Severity + symptom + mitigation, scannable.
|
|
103
|
+
|
|
104
|
+
## Red flags
|
|
105
|
+
|
|
106
|
+
- The content already exists under a different id → \`bclaw_find\` first; \`bclaw_update\` the existing one instead of duplicating.
|
|
107
|
+
- You're about to write 10+ items at once → pause; you're dumping context, not capturing signal. Consolidate.
|
|
108
|
+
- The "decision" was proposed by someone else and not yet accepted → it's a \`candidate\`, not a \`decision\`.
|
|
109
|
+
|
|
110
|
+
## Verification
|
|
111
|
+
|
|
112
|
+
- \`bclaw_create\` returns an id (e.g. \`dec_…\`).
|
|
113
|
+
- \`bclaw_get(entity='<type>', id)\` returns identical content; \`bclaw_search\`/\`bclaw_find\` index it.
|
|
114
|
+
- Traps surface in \`bclaw_work\` warnings on the relevant scope.
|
|
115
|
+
|
|
116
|
+
## See also
|
|
117
|
+
|
|
118
|
+
- \`brainclaw-session\` — capture happens DURING a session.
|
|
119
|
+
- \`brainclaw-multi-agent\` — capture review findings as decisions/traps.
|
|
120
|
+
`;
|
|
121
|
+
const MULTI_AGENT_BODY = `# brainclaw-multi-agent
|
|
122
|
+
|
|
123
|
+
Coordinate work across agents — delegate, request review, drive parallel
|
|
124
|
+
dispatch, run multi-turn loops. Use when the work exceeds one agent's
|
|
125
|
+
competence or when parallelism gains outweigh orchestration cost.
|
|
126
|
+
|
|
127
|
+
## When to use
|
|
128
|
+
|
|
129
|
+
- A second pair of eyes before merging → **review loop**.
|
|
130
|
+
- An open-ended design needing multiple perspectives → **ideation loop** (or **bootstrap loop** for PROJECT.md genesis).
|
|
131
|
+
- N independent sub-tasks runnable in parallel → **dispatch**.
|
|
132
|
+
- You're inside a loop someone else opened and must drive your turn → **loop verbs**.
|
|
133
|
+
|
|
134
|
+
## Workflow — pick the verb first
|
|
135
|
+
|
|
136
|
+
\`\`\`
|
|
137
|
+
Delegate a scope (with a claim)? → bclaw_coordinate(intent='assign')
|
|
138
|
+
Ask an agent for input, no claim? → bclaw_coordinate(intent='consult')
|
|
139
|
+
Review a commit / branch / candidate? → bclaw_coordinate(intent='review', open_loop=true)
|
|
140
|
+
Brainstorm with multiple agents? → bclaw_coordinate(intent='ideate')
|
|
141
|
+
Parallelize a sequence's lanes? → bclaw_dispatch(intent='execute')
|
|
142
|
+
Drive YOUR turn in an open loop? → bclaw_loop(intent='turn|complete_turn|advance|close')
|
|
143
|
+
\`\`\`
|
|
144
|
+
|
|
145
|
+
### Review loop
|
|
146
|
+
1. Commit your changes first (or pass \`allow_dirty=true\` only if the worker doesn't need the dirty files — it spawns from HEAD).
|
|
147
|
+
2. \`bclaw_coordinate(intent='review', open_loop=true, review_mode='symmetric', targetAgents=['<agent>'], scope='<commit/branch/feature>', task='<what to review + acceptance bar>')\`.
|
|
148
|
+
3. Verify the worker is ALIVE — \`bclaw_dispatch_status(target_id='<asgn_…>')\` (health verdict + recommended action), not the bare \`delivered_and_started\` return.
|
|
149
|
+
4. When findings land, apply fixes / push back via another turn.
|
|
150
|
+
5. \`bclaw_loop(intent='close', loop_id='<id>', status='completed', reason='<verdict>')\`; release the worker's claim.
|
|
151
|
+
|
|
152
|
+
### Parallel dispatch
|
|
153
|
+
1. A sequence with lane declarations (or a plan with lanes).
|
|
154
|
+
2. \`bclaw_dispatch(intent='execute')\` fans the items across agents per their lanes. Before merging the lanes back, run \`brainclaw worktree check\` (pre-merge conflict detection).
|
|
155
|
+
|
|
156
|
+
## Anti-rationalizations
|
|
157
|
+
|
|
158
|
+
- **"I'll call \`bclaw_loop(intent='open')\` to start the review."** → STOP. \`open\` creates the loop WITHOUT dispatching a turn; the reviewer never gets the work. Use \`bclaw_coordinate(intent='review', open_loop=true)\` — it opens AND dispatches.
|
|
159
|
+
- **"It returned \`delivered_and_started\`, so the worker is running."** → That only means the brief-ack sentinel was touched. Verify with \`bclaw_dispatch_status\`; spawns die silently.
|
|
160
|
+
- **"I'll dispatch with uncommitted changes, the worker will see them."** → It spawns from HEAD in a worktree; your dirty files are invisible. Commit, or pass \`allow_dirty=true\` consciously.
|
|
161
|
+
|
|
162
|
+
## Red flags
|
|
163
|
+
|
|
164
|
+
- \`agent_run.status='running'\` but the worker pid is dead → silent spawn death; check the captured stderr, retry or reassign. (Do NOT trust a stale \`LANE-RESULT.json\` inherited by the worktree — verify its \`assignment_id\` matches.)
|
|
165
|
+
- Cross-project dispatch (\`project='<other>'\`) → auto-spawn is disabled by design; the target picks the brief up async via its own \`bclaw_work\`. Don't block waiting.
|
|
166
|
+
- A dispatched worker operates in a worktree that has no \`.brainclaw/\` → its MCP/CLI may be limited (trp#336); it falls back to file-based output. Expect a file deliverable, harvest it.
|
|
167
|
+
|
|
168
|
+
## Verification
|
|
169
|
+
|
|
170
|
+
- Dispatch: \`bclaw_dispatch_status(target_id)\` returns \`healthy\` (pid alive, recent fs activity), or a terminal verdict with a recommended next action.
|
|
171
|
+
- Review loop: \`bclaw_loop(intent='get', loop_id)\` shows the reviewer slot \`done\` and the findings artifact attached.
|
|
172
|
+
- Bootstrap/ideate: at converge the loop is \`completed\` (and for bootstrap, PROJECT.md is materialized at the project root).
|
|
173
|
+
|
|
174
|
+
## See also
|
|
175
|
+
|
|
176
|
+
- \`brainclaw-session\` — start a session BEFORE dispatching.
|
|
177
|
+
- \`brainclaw-memory-capture\` — capture review findings as typed memory.
|
|
178
|
+
- Docs: \`docs/concepts/loop-engine.md\`, \`docs/concepts/dispatch-lifecycle.md\`, \`docs/concepts/parallel-merge-protocol.md\`.
|
|
179
|
+
`;
|
|
180
|
+
/** The single source of truth for what ships in the protocol-skills pack. */
|
|
181
|
+
export const PROTOCOL_SKILLS = [
|
|
182
|
+
{
|
|
183
|
+
id: 'brainclaw-session',
|
|
184
|
+
description: 'Open / resume / close a working session on a brainclaw project. Use at session start, when resuming after a compaction, or before editing a scope that needs a claim.',
|
|
185
|
+
body: SESSION_BODY,
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
id: 'brainclaw-memory-capture',
|
|
189
|
+
description: 'Capture project memory (decision, constraint, trap, runtime_note, handoff, candidate) at the right type and granularity. Use after a design call, hitting a constraint, discovering a trap, or producing a handoff.',
|
|
190
|
+
body: MEMORY_CAPTURE_BODY,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
id: 'brainclaw-multi-agent',
|
|
194
|
+
description: 'Coordinate across agents — delegate, request review, drive parallel dispatch, run multi-turn loops. Use when work exceeds one agent or parallelism beats orchestration cost.',
|
|
195
|
+
body: MULTI_AGENT_BODY,
|
|
196
|
+
},
|
|
197
|
+
];
|
|
198
|
+
/** Render the full SKILL.md (frontmatter + body) for a protocol skill. */
|
|
199
|
+
export function renderProtocolSkill(skill, brainclawVersion) {
|
|
200
|
+
return `---
|
|
201
|
+
name: ${skill.id}
|
|
202
|
+
description: '${skill.description.replace(/'/g, "''")}'
|
|
203
|
+
metadata:
|
|
204
|
+
protocol: true
|
|
205
|
+
brainclaw_version: ${brainclawVersion}
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
${skill.body}`;
|
|
209
|
+
}
|
|
210
|
+
//# sourceMappingURL=protocol-skills.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { loadState } from './state.js';
|
|
2
|
-
import { detectNewItemContradictions,
|
|
2
|
+
import { detectNewItemContradictions, summarizeContradictions } from './contradictions.js';
|
|
3
3
|
export function evaluateReflectionSafety(input) {
|
|
4
4
|
if (input.type === 'handoff') {
|
|
5
5
|
return {};
|
|
@@ -8,14 +8,13 @@ export function evaluateReflectionSafety(input) {
|
|
|
8
8
|
if (contradictions.length === 0) {
|
|
9
9
|
return {};
|
|
10
10
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
// Advisory only (pln#542, cnd_abe61d68 incident: 18 keyword false positives
|
|
12
|
+
// on a review summary blocked promotion). Contradictions ride along as
|
|
13
|
+
// metadata on the candidate for the human/curator to weigh — they never
|
|
14
|
+
// set promotion_blocked_reason anymore.
|
|
15
15
|
return {
|
|
16
16
|
contradictions_detected: contradictions,
|
|
17
|
-
contradiction_summary:
|
|
18
|
-
promotion_blocked_reason: promotionBlockedReason,
|
|
17
|
+
contradiction_summary: summarizeContradictions(contradictions),
|
|
19
18
|
};
|
|
20
19
|
}
|
|
21
20
|
//# sourceMappingURL=reflection-safety.js.map
|
package/dist/core/reputation.js
CHANGED
|
@@ -4,6 +4,7 @@ import { listClaims } from './claims.js';
|
|
|
4
4
|
import { loadConfig } from './config.js';
|
|
5
5
|
import { nowISO } from './ids.js';
|
|
6
6
|
import { listRuntimeNotes } from './runtime.js';
|
|
7
|
+
import { loadState } from './state.js';
|
|
7
8
|
function clampScore(value) {
|
|
8
9
|
if (!Number.isFinite(value)) {
|
|
9
10
|
return 0;
|
|
@@ -30,6 +31,12 @@ function createAccumulator(identity) {
|
|
|
30
31
|
claims_created: 0,
|
|
31
32
|
released_claims: 0,
|
|
32
33
|
orphan_runtime_noise: 0,
|
|
34
|
+
memory_confirmations_authored: 0,
|
|
35
|
+
memory_infirmations_authored: 0,
|
|
36
|
+
memory_saved_me_reports: 0,
|
|
37
|
+
memory_misled_me_reports: 0,
|
|
38
|
+
memory_items_reinforced: 0,
|
|
39
|
+
memory_items_misled_others: 0,
|
|
33
40
|
},
|
|
34
41
|
};
|
|
35
42
|
}
|
|
@@ -151,6 +158,55 @@ function trackRuntimeSignals(note, store, sinceMs, resolveIdentity) {
|
|
|
151
158
|
stats.signals.plan_linked_activity += 1;
|
|
152
159
|
}
|
|
153
160
|
}
|
|
161
|
+
/**
|
|
162
|
+
* pln#544 — memory lifecycle reinforcement signals.
|
|
163
|
+
*
|
|
164
|
+
* For each decision / constraint / trap, walk the bounded confirmations[]
|
|
165
|
+
* log and attribute:
|
|
166
|
+
* - the event itself to the attesting agent (`by`/`by_id`), increasing
|
|
167
|
+
* their confirmations/infirmations/saved-me/misled-me counters.
|
|
168
|
+
* - reinforcement back to the item author (saved_me / misled_me) so that
|
|
169
|
+
* "memory that actually helped" rewards whoever wrote it.
|
|
170
|
+
*
|
|
171
|
+
* The 30-day reputation window applies — older confirmation events do not
|
|
172
|
+
* count, mirroring how stale candidate signals are excluded above.
|
|
173
|
+
*/
|
|
174
|
+
function trackMemoryLifecycleSignals(item, store, sinceMs, resolveIdentity) {
|
|
175
|
+
const events = item.confirmations ?? [];
|
|
176
|
+
if (events.length === 0)
|
|
177
|
+
return;
|
|
178
|
+
const author = resolveIdentity(item.author_id ?? item.author);
|
|
179
|
+
for (const event of events) {
|
|
180
|
+
if (!withinWindow(event.at, sinceMs))
|
|
181
|
+
continue;
|
|
182
|
+
const attester = resolveIdentity(event.by_id ?? event.by);
|
|
183
|
+
if (attester) {
|
|
184
|
+
const stats = getAccumulator(store, attester);
|
|
185
|
+
if (event.kind === 'confirm')
|
|
186
|
+
stats.signals.memory_confirmations_authored += 1;
|
|
187
|
+
else if (event.kind === 'infirm')
|
|
188
|
+
stats.signals.memory_infirmations_authored += 1;
|
|
189
|
+
else if (event.kind === 'saved_me') {
|
|
190
|
+
stats.signals.memory_confirmations_authored += 1;
|
|
191
|
+
stats.signals.memory_saved_me_reports += 1;
|
|
192
|
+
}
|
|
193
|
+
else if (event.kind === 'misled_me') {
|
|
194
|
+
stats.signals.memory_infirmations_authored += 1;
|
|
195
|
+
stats.signals.memory_misled_me_reports += 1;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// Reinforcement back to the item author — only "explicitly reinforced" /
|
|
199
|
+
// "explicitly debunked" events flow there. Passive confirm/infirm are
|
|
200
|
+
// peer-review signals, not author signals.
|
|
201
|
+
if (author && attester && attester.key !== author.key) {
|
|
202
|
+
const ownerStats = getAccumulator(store, author);
|
|
203
|
+
if (event.kind === 'saved_me')
|
|
204
|
+
ownerStats.signals.memory_items_reinforced += 1;
|
|
205
|
+
else if (event.kind === 'misled_me')
|
|
206
|
+
ownerStats.signals.memory_items_misled_others += 1;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
154
210
|
function trackClaimSignals(claim, store, sinceMs, resolveIdentity) {
|
|
155
211
|
if (!withinWindow(claim.created_at, sinceMs) && !withinWindow(claim.released_at, sinceMs)) {
|
|
156
212
|
return;
|
|
@@ -177,14 +233,24 @@ function finalizeSnapshot(accumulator) {
|
|
|
177
233
|
const boundedPromotions = Math.min(accumulator.signals.promoted_runtime_candidates, 4);
|
|
178
234
|
const boundedPlanActivity = Math.min(accumulator.signals.plan_linked_activity, 4);
|
|
179
235
|
const boundedNoise = Math.min(accumulator.signals.orphan_runtime_noise, 4);
|
|
236
|
+
// pln#544 — memory-lifecycle reinforcement caps so a single noisy attester
|
|
237
|
+
// can't dominate the score. Saved-me on items I authored is the strongest
|
|
238
|
+
// positive signal ("my memory actually saved another agent"); misled-me on
|
|
239
|
+
// my items is the symmetric penalty.
|
|
240
|
+
const boundedSavedMeAuthored = Math.min(accumulator.signals.memory_items_reinforced, 5);
|
|
241
|
+
const boundedMisledOthers = Math.min(accumulator.signals.memory_items_misled_others, 5);
|
|
242
|
+
const boundedMemoryReviewVolume = Math.min(accumulator.signals.memory_confirmations_authored + accumulator.signals.memory_infirmations_authored, 10);
|
|
180
243
|
const contributionQuality = clampScore(35 * accumulator.signals.accepted_candidates
|
|
181
244
|
+ 20 * accumulator.signals.promoted_runtime_accepted
|
|
182
245
|
+ 10 * boundedUses
|
|
183
246
|
+ 4 * boundedStars
|
|
184
|
-
|
|
247
|
+
+ 15 * boundedSavedMeAuthored
|
|
248
|
+
- 12 * accumulator.signals.rejected_candidates_authored
|
|
249
|
+
- 18 * boundedMisledOthers);
|
|
185
250
|
const reviewReliability = clampScore(30 * accumulator.signals.accepted_reviews
|
|
186
251
|
+ 18 * accumulator.signals.rejected_reviews
|
|
187
|
-
+ 4 * accumulator.signals.reasoned_rejections
|
|
252
|
+
+ 4 * accumulator.signals.reasoned_rejections
|
|
253
|
+
+ 6 * boundedMemoryReviewVolume);
|
|
188
254
|
const continuityHygiene = clampScore(12 * boundedRuntimeNotes
|
|
189
255
|
+ 12 * boundedPromotions
|
|
190
256
|
+ 8 * boundedPlanActivity
|
|
@@ -243,6 +309,22 @@ export function buildReputationSnapshot(cwd) {
|
|
|
243
309
|
for (const claim of listClaims(cwd)) {
|
|
244
310
|
trackClaimSignals(claim, store, sinceMs, resolvers.resolve);
|
|
245
311
|
}
|
|
312
|
+
// pln#544 — feed memory-lifecycle confirmations back into reputation:
|
|
313
|
+
// attesting agents earn confirmation_authored; items reinforced by
|
|
314
|
+
// 'saved_me' reward their author. Best-effort: never let a lifecycle
|
|
315
|
+
// signal failure block reputation rebuild.
|
|
316
|
+
try {
|
|
317
|
+
const state = loadState(cwd);
|
|
318
|
+
const memoryItems = [
|
|
319
|
+
...state.recent_decisions,
|
|
320
|
+
...state.active_constraints,
|
|
321
|
+
...state.known_traps,
|
|
322
|
+
];
|
|
323
|
+
for (const item of memoryItems) {
|
|
324
|
+
trackMemoryLifecycleSignals(item, store, sinceMs, resolvers.resolve);
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
catch { /* state may be unreadable in cold-start scenarios */ }
|
|
246
328
|
const agents = [...store.values()]
|
|
247
329
|
.map((entry) => finalizeSnapshot(entry))
|
|
248
330
|
.sort((a, b) => {
|