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
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Governance posture report.
|
|
3
|
+
*
|
|
4
|
+
* Aggregates claims, constraints, traps, instructions, and audit entries
|
|
5
|
+
* into a structured governance snapshot. No scores, no synthetic metrics —
|
|
6
|
+
* only verifiable facts.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { listClaims, isClaimExpired } from './claims.js';
|
|
11
|
+
import { loadState } from './state.js';
|
|
12
|
+
import { loadInstructions, resolveInstructions } from './instructions.js';
|
|
13
|
+
import { readAuditLog } from './audit.js';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Report builder
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
export function buildGovernanceReport(options = {}) {
|
|
18
|
+
const cwd = options.cwd;
|
|
19
|
+
const now = new Date();
|
|
20
|
+
const last24h = new Date(now.getTime() - 24 * 60 * 60 * 1000).toISOString();
|
|
21
|
+
// --- Constitution (global instructions) ---
|
|
22
|
+
const allInstructions = loadInstructions(cwd);
|
|
23
|
+
const activeInstructions = resolveInstructions(allInstructions, {});
|
|
24
|
+
const globalInstructions = activeInstructions.filter(i => i.layer === 'global');
|
|
25
|
+
// --- Red Lines (constraints) ---
|
|
26
|
+
const state = loadState(cwd);
|
|
27
|
+
const activeConstraints = state.active_constraints.filter(c => c.status === 'active');
|
|
28
|
+
const constraintsByCategory = {};
|
|
29
|
+
for (const c of activeConstraints) {
|
|
30
|
+
const cat = c.category ?? 'other';
|
|
31
|
+
(constraintsByCategory[cat] ??= []).push(c);
|
|
32
|
+
}
|
|
33
|
+
const highSeverityCount = activeConstraints.filter(c => c.tags?.includes('high') || c.category === 'security').length;
|
|
34
|
+
// --- Claims ---
|
|
35
|
+
const allClaims = listClaims(cwd);
|
|
36
|
+
const activeClaims = allClaims.filter(c => c.status === 'active' && !isClaimExpired(c));
|
|
37
|
+
const expiredUnreleased = allClaims.filter(c => c.status === 'active' && isClaimExpired(c));
|
|
38
|
+
const claimsByAgent = {};
|
|
39
|
+
for (const c of activeClaims) {
|
|
40
|
+
claimsByAgent[c.agent] = (claimsByAgent[c.agent] ?? 0) + 1;
|
|
41
|
+
}
|
|
42
|
+
// Apply filters
|
|
43
|
+
let filteredActiveClaims = activeClaims;
|
|
44
|
+
let filteredExpired = expiredUnreleased;
|
|
45
|
+
if (options.agent) {
|
|
46
|
+
const agentLower = options.agent.toLowerCase();
|
|
47
|
+
filteredActiveClaims = activeClaims.filter(c => c.agent.toLowerCase() === agentLower || c.agent_id?.toLowerCase() === agentLower);
|
|
48
|
+
filteredExpired = expiredUnreleased.filter(c => c.agent.toLowerCase() === agentLower || c.agent_id?.toLowerCase() === agentLower);
|
|
49
|
+
}
|
|
50
|
+
// --- Traps (shared visibility only — machine/private traps are environment-specific) ---
|
|
51
|
+
const openTraps = state.known_traps.filter(t => t.status === 'active' && (t.visibility === 'shared' || !t.visibility));
|
|
52
|
+
const trapsBySeverity = {};
|
|
53
|
+
for (const t of openTraps) {
|
|
54
|
+
trapsBySeverity[t.severity] = (trapsBySeverity[t.severity] ?? 0) + 1;
|
|
55
|
+
}
|
|
56
|
+
// --- Recent activity ---
|
|
57
|
+
const recentEntries = readAuditLog({ since: last24h }, cwd);
|
|
58
|
+
const claimsLast24h = recentEntries.filter(e => e.action === 'claim').length;
|
|
59
|
+
const releasesLast24h = recentEntries.filter(e => e.action === 'release_claim').length;
|
|
60
|
+
// Detect mutations without claim — check creates/updates that aren't claim/release/session actions
|
|
61
|
+
const mutationActions = new Set(['create', 'update', 'delete', 'promote_direct']);
|
|
62
|
+
const sessionTypes = new Set(['session', 'claim']);
|
|
63
|
+
const actionsWithoutClaim = recentEntries.filter(e => {
|
|
64
|
+
if (!mutationActions.has(e.action))
|
|
65
|
+
return false;
|
|
66
|
+
if (sessionTypes.has(e.item_type ?? ''))
|
|
67
|
+
return false;
|
|
68
|
+
// Check if the actor had any active claim at that time
|
|
69
|
+
const actorClaims = activeClaims.filter(c => c.agent === e.actor);
|
|
70
|
+
return actorClaims.length === 0;
|
|
71
|
+
});
|
|
72
|
+
// --- Recommendations ---
|
|
73
|
+
const recommendations = [];
|
|
74
|
+
if (expiredUnreleased.length > 0) {
|
|
75
|
+
recommendations.push(`${expiredUnreleased.length} expired claim(s) need release. Run: bclaw release-claims --expired`);
|
|
76
|
+
}
|
|
77
|
+
if (openTraps.length > 0) {
|
|
78
|
+
const highTraps = openTraps.filter(t => t.severity === 'high');
|
|
79
|
+
if (highTraps.length > 0) {
|
|
80
|
+
recommendations.push(`${highTraps.length} high-severity trap(s) open. Review before editing related files.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (actionsWithoutClaim.length > 0) {
|
|
84
|
+
recommendations.push(`${actionsWithoutClaim.length} mutation(s) detected without active claim in last 24h.`);
|
|
85
|
+
}
|
|
86
|
+
if (globalInstructions.length === 0) {
|
|
87
|
+
recommendations.push('No global instructions set. Consider adding governance rules via: bclaw instruction add --layer global');
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
generated_at: now.toISOString(),
|
|
91
|
+
scope_filter: options.scope,
|
|
92
|
+
agent_filter: options.agent,
|
|
93
|
+
constitution: {
|
|
94
|
+
global_instructions: globalInstructions,
|
|
95
|
+
total: globalInstructions.length,
|
|
96
|
+
},
|
|
97
|
+
red_lines: {
|
|
98
|
+
constraints_by_category: constraintsByCategory,
|
|
99
|
+
high_severity_count: highSeverityCount,
|
|
100
|
+
total: activeConstraints.length,
|
|
101
|
+
},
|
|
102
|
+
claims: {
|
|
103
|
+
active: filteredActiveClaims.map(toClaimSummary),
|
|
104
|
+
expired_unreleased: filteredExpired.map(toClaimSummary),
|
|
105
|
+
by_agent: claimsByAgent,
|
|
106
|
+
total_active: filteredActiveClaims.length,
|
|
107
|
+
total_expired_unreleased: filteredExpired.length,
|
|
108
|
+
},
|
|
109
|
+
traps: {
|
|
110
|
+
open: openTraps.map(toTrapSummary),
|
|
111
|
+
by_severity: trapsBySeverity,
|
|
112
|
+
total_open: openTraps.length,
|
|
113
|
+
},
|
|
114
|
+
recent_activity: {
|
|
115
|
+
claims_last_24h: claimsLast24h,
|
|
116
|
+
releases_last_24h: releasesLast24h,
|
|
117
|
+
actions_without_claim: actionsWithoutClaim,
|
|
118
|
+
},
|
|
119
|
+
recommendations,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
// ---------------------------------------------------------------------------
|
|
123
|
+
// Markdown renderer
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
export function renderGovernanceMarkdown(report) {
|
|
126
|
+
const lines = [];
|
|
127
|
+
const ts = report.generated_at.slice(0, 16).replace('T', ' ');
|
|
128
|
+
lines.push(`# Governance Posture Report`);
|
|
129
|
+
lines.push(`Generated: ${ts} UTC`);
|
|
130
|
+
if (report.scope_filter)
|
|
131
|
+
lines.push(`Scope: ${report.scope_filter}`);
|
|
132
|
+
if (report.agent_filter)
|
|
133
|
+
lines.push(`Agent: ${report.agent_filter}`);
|
|
134
|
+
lines.push('');
|
|
135
|
+
// --- Summary ---
|
|
136
|
+
lines.push('## Summary');
|
|
137
|
+
lines.push(`- Active claims: ${report.claims.total_active}`);
|
|
138
|
+
lines.push(`- Expired (unreleased): ${report.claims.total_expired_unreleased}`);
|
|
139
|
+
lines.push(`- Active constraints: ${report.red_lines.total}`);
|
|
140
|
+
lines.push(`- Open traps: ${report.traps.total_open}`);
|
|
141
|
+
lines.push(`- Global instructions: ${report.constitution.total}`);
|
|
142
|
+
lines.push(`- Claims (24h): ${report.recent_activity.claims_last_24h} created, ${report.recent_activity.releases_last_24h} released`);
|
|
143
|
+
if (report.recent_activity.actions_without_claim.length > 0) {
|
|
144
|
+
lines.push(`- **Mutations without claim (24h): ${report.recent_activity.actions_without_claim.length}**`);
|
|
145
|
+
}
|
|
146
|
+
lines.push('');
|
|
147
|
+
// --- Constitution ---
|
|
148
|
+
if (report.constitution.total > 0) {
|
|
149
|
+
lines.push('## Constitution (Global Instructions)');
|
|
150
|
+
for (const ins of report.constitution.global_instructions) {
|
|
151
|
+
lines.push(`- ${ins.text}`);
|
|
152
|
+
}
|
|
153
|
+
lines.push('');
|
|
154
|
+
}
|
|
155
|
+
// --- Red Lines ---
|
|
156
|
+
if (report.red_lines.total > 0) {
|
|
157
|
+
lines.push('## Red Lines (Active Constraints)');
|
|
158
|
+
for (const [category, constraints] of Object.entries(report.red_lines.constraints_by_category)) {
|
|
159
|
+
lines.push(`### ${category} (${constraints.length})`);
|
|
160
|
+
for (const c of constraints) {
|
|
161
|
+
const paths = c.related_paths?.length ? ` — ${c.related_paths.join(', ')}` : '';
|
|
162
|
+
lines.push(`- [${c.id}] ${c.text}${paths}`);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
lines.push('');
|
|
166
|
+
}
|
|
167
|
+
// --- Claims ---
|
|
168
|
+
if (report.claims.total_active > 0) {
|
|
169
|
+
lines.push('## Active Claims');
|
|
170
|
+
const agentEntries = Object.entries(report.claims.by_agent).sort((a, b) => b[1] - a[1]);
|
|
171
|
+
if (agentEntries.length > 1) {
|
|
172
|
+
lines.push(`By agent: ${agentEntries.map(([a, n]) => `${a} (${n})`).join(', ')}`);
|
|
173
|
+
lines.push('');
|
|
174
|
+
}
|
|
175
|
+
for (const c of report.claims.active) {
|
|
176
|
+
const planNote = c.plan_id ? ` [${c.plan_id}]` : '';
|
|
177
|
+
const expiryNote = c.expires_at ? ` (expires ${c.expires_at.slice(0, 16).replace('T', ' ')})` : '';
|
|
178
|
+
lines.push(`- [${c.id}] ${c.agent} → ${c.scope}: ${c.description}${planNote}${expiryNote}`);
|
|
179
|
+
}
|
|
180
|
+
lines.push('');
|
|
181
|
+
}
|
|
182
|
+
if (report.claims.total_expired_unreleased > 0) {
|
|
183
|
+
lines.push('## Expired Claims (Need Release)');
|
|
184
|
+
for (const c of report.claims.expired_unreleased) {
|
|
185
|
+
lines.push(`- [${c.id}] ${c.agent} → ${c.scope}: ${c.description} (expired ${c.expires_at?.slice(0, 16).replace('T', ' ') ?? '?'})`);
|
|
186
|
+
}
|
|
187
|
+
lines.push('');
|
|
188
|
+
}
|
|
189
|
+
// --- Traps ---
|
|
190
|
+
if (report.traps.total_open > 0) {
|
|
191
|
+
lines.push('## Open Traps');
|
|
192
|
+
const sevEntries = Object.entries(report.traps.by_severity);
|
|
193
|
+
if (sevEntries.length > 0) {
|
|
194
|
+
lines.push(`By severity: ${sevEntries.map(([s, n]) => `${s} (${n})`).join(', ')}`);
|
|
195
|
+
lines.push('');
|
|
196
|
+
}
|
|
197
|
+
for (const t of report.traps.open) {
|
|
198
|
+
const paths = t.related_paths?.length ? ` — ${t.related_paths.join(', ')}` : '';
|
|
199
|
+
lines.push(`- [${t.id}] [${t.severity}] ${t.text}${paths}`);
|
|
200
|
+
}
|
|
201
|
+
lines.push('');
|
|
202
|
+
}
|
|
203
|
+
// --- Mutations without claim ---
|
|
204
|
+
if (report.recent_activity.actions_without_claim.length > 0) {
|
|
205
|
+
lines.push('## Mutations Without Claim (Last 24h)');
|
|
206
|
+
for (const e of report.recent_activity.actions_without_claim.slice(0, 20)) {
|
|
207
|
+
const scope = e.scope ? ` → ${e.scope}` : '';
|
|
208
|
+
lines.push(`- ${e.timestamp.slice(0, 16).replace('T', ' ')} [${e.actor}] ${e.action} ${e.item_type ?? ''}${scope}`);
|
|
209
|
+
}
|
|
210
|
+
if (report.recent_activity.actions_without_claim.length > 20) {
|
|
211
|
+
lines.push(` ... and ${report.recent_activity.actions_without_claim.length - 20} more`);
|
|
212
|
+
}
|
|
213
|
+
lines.push('');
|
|
214
|
+
}
|
|
215
|
+
// --- Recommendations ---
|
|
216
|
+
if (report.recommendations.length > 0) {
|
|
217
|
+
lines.push('## Recommendations');
|
|
218
|
+
for (const r of report.recommendations) {
|
|
219
|
+
lines.push(`- ${r}`);
|
|
220
|
+
}
|
|
221
|
+
lines.push('');
|
|
222
|
+
}
|
|
223
|
+
return lines.join('\n');
|
|
224
|
+
}
|
|
225
|
+
// ---------------------------------------------------------------------------
|
|
226
|
+
// Helpers
|
|
227
|
+
// ---------------------------------------------------------------------------
|
|
228
|
+
function toClaimSummary(c) {
|
|
229
|
+
return {
|
|
230
|
+
id: c.id,
|
|
231
|
+
agent: c.agent,
|
|
232
|
+
scope: c.scope,
|
|
233
|
+
description: c.description,
|
|
234
|
+
created_at: c.created_at,
|
|
235
|
+
expires_at: c.expires_at,
|
|
236
|
+
plan_id: c.plan_id,
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
function toTrapSummary(t) {
|
|
240
|
+
return {
|
|
241
|
+
id: t.id,
|
|
242
|
+
text: t.text,
|
|
243
|
+
severity: t.severity,
|
|
244
|
+
related_paths: t.related_paths,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
//# sourceMappingURL=governance.js.map
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared guard utilities for CLI commands.
|
|
3
|
+
*
|
|
4
|
+
* These replace the duplicated memoryExists + process.exit(1) pattern
|
|
5
|
+
* found across 70+ command files.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { memoryExists } from './io.js';
|
|
10
|
+
/**
|
|
11
|
+
* Abort the CLI process if .brainclaw/ is not found at the given path.
|
|
12
|
+
*/
|
|
13
|
+
export function requireInitialized(cwd) {
|
|
14
|
+
if (!memoryExists(cwd)) {
|
|
15
|
+
console.error('Error: .brainclaw/ not found. Run `brainclaw init` first.');
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=guards.js.map
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as fs from 'node:fs';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
import { memoryDir } from './io.js';
|
|
5
|
+
export const IdeationRoundSchema = z.object({
|
|
6
|
+
schema_version: z.literal(1),
|
|
7
|
+
thread_id: z.string(),
|
|
8
|
+
round_number: z.number().int().min(0),
|
|
9
|
+
round_type: z.enum(['position', 'reaction', 'convergence']),
|
|
10
|
+
positions: z.array(z.object({
|
|
11
|
+
persona: z.string(),
|
|
12
|
+
agent: z.string(),
|
|
13
|
+
text: z.string(),
|
|
14
|
+
duration_ms: z.number().optional(),
|
|
15
|
+
})),
|
|
16
|
+
tensions: z.array(z.string()).default([]),
|
|
17
|
+
convergences: z.array(z.string()).default([]),
|
|
18
|
+
created_at: z.string(),
|
|
19
|
+
});
|
|
20
|
+
function sanitizeForPath(slug) {
|
|
21
|
+
return slug.replace(/[<>:"/\\|?*]/g, '_');
|
|
22
|
+
}
|
|
23
|
+
export function ideationDir(threadSlug, cwd) {
|
|
24
|
+
return path.join(memoryDir(cwd), 'coordination', 'ideation', sanitizeForPath(threadSlug));
|
|
25
|
+
}
|
|
26
|
+
export function saveIdeationRound(threadSlug, round, cwd) {
|
|
27
|
+
const dir = ideationDir(threadSlug, cwd);
|
|
28
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
29
|
+
const filePath = path.join(dir, `round_${round.round_number}.json`);
|
|
30
|
+
fs.writeFileSync(filePath, JSON.stringify(round, null, 2), 'utf8');
|
|
31
|
+
}
|
|
32
|
+
export function loadIdeationRound(threadSlug, roundNumber, cwd) {
|
|
33
|
+
const filePath = path.join(ideationDir(threadSlug, cwd), `round_${roundNumber}.json`);
|
|
34
|
+
if (!fs.existsSync(filePath)) {
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
try {
|
|
38
|
+
const raw = fs.readFileSync(filePath, 'utf8');
|
|
39
|
+
return IdeationRoundSchema.parse(JSON.parse(raw));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
export function listIdeationThreads(cwd) {
|
|
46
|
+
const base = path.join(memoryDir(cwd), 'coordination', 'ideation');
|
|
47
|
+
if (!fs.existsSync(base))
|
|
48
|
+
return [];
|
|
49
|
+
return fs.readdirSync(base, { withFileTypes: true })
|
|
50
|
+
.filter(e => e.isDirectory())
|
|
51
|
+
.map(e => e.name)
|
|
52
|
+
.sort();
|
|
53
|
+
}
|
|
54
|
+
export function listIdeationRounds(threadSlug, cwd) {
|
|
55
|
+
const dir = ideationDir(threadSlug, cwd);
|
|
56
|
+
if (!fs.existsSync(dir)) {
|
|
57
|
+
return [];
|
|
58
|
+
}
|
|
59
|
+
const rounds = [];
|
|
60
|
+
for (const entry of fs.readdirSync(dir)) {
|
|
61
|
+
const match = /^round_(\d+)\.json$/.exec(entry);
|
|
62
|
+
if (!match) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
const round = loadIdeationRound(threadSlug, Number(match[1]), cwd);
|
|
66
|
+
if (round) {
|
|
67
|
+
rounds.push(round);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return rounds.sort((a, b) => a.round_number - b.round_number);
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=ideation.js.map
|
package/dist/core/identity.js
CHANGED
|
@@ -76,18 +76,23 @@ export function loadCurrentSession(cwd) {
|
|
|
76
76
|
const currentUser = resolveCurrentUser();
|
|
77
77
|
const currentAgent = resolveCurrentAgentName();
|
|
78
78
|
// 1. Look in sessions/ directory for a matching session
|
|
79
|
-
if (fs.existsSync(dir)) {
|
|
79
|
+
if (fs.existsSync(dir) && currentAgent) {
|
|
80
80
|
const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
81
81
|
const ttlMs = parseDurationToMs(loadConfigSafe(cwd)?.implicit_session_ttl ?? '4h');
|
|
82
82
|
const now = Date.now();
|
|
83
83
|
for (const file of files) {
|
|
84
84
|
try {
|
|
85
|
-
const
|
|
86
|
-
|
|
85
|
+
const migration = loadVersionedJsonFile('current_session', path.join(dir, file));
|
|
86
|
+
const session = {
|
|
87
|
+
...CurrentSessionStateSchema.parse(migration.document),
|
|
88
|
+
schema_version: migration.metadata.currentVersion,
|
|
89
|
+
};
|
|
90
|
+
// Strict match: agent name must match, user must match (when both are known)
|
|
91
|
+
if (session.agent !== currentAgent)
|
|
92
|
+
continue;
|
|
87
93
|
const userMatch = !session.user || !currentUser || session.user === currentUser;
|
|
88
|
-
const agentMatch = !currentAgent || session.agent === currentAgent;
|
|
89
94
|
const alive = (now - Date.parse(session.last_seen_at)) <= ttlMs;
|
|
90
|
-
if (userMatch &&
|
|
95
|
+
if (userMatch && alive) {
|
|
91
96
|
return session;
|
|
92
97
|
}
|
|
93
98
|
}
|
|
@@ -100,7 +105,11 @@ export function loadCurrentSession(cwd) {
|
|
|
100
105
|
const legacyPath = path.join(memoryDir(cwd), LEGACY_SESSION_FILE);
|
|
101
106
|
if (fs.existsSync(legacyPath)) {
|
|
102
107
|
try {
|
|
103
|
-
|
|
108
|
+
const migration = loadVersionedJsonFile('current_session', legacyPath);
|
|
109
|
+
return {
|
|
110
|
+
...CurrentSessionStateSchema.parse(migration.document),
|
|
111
|
+
schema_version: migration.metadata.currentVersion,
|
|
112
|
+
};
|
|
104
113
|
}
|
|
105
114
|
catch {
|
|
106
115
|
return undefined;
|
|
@@ -116,7 +125,11 @@ export function loadSessionById(sessionId, cwd) {
|
|
|
116
125
|
if (!fs.existsSync(filepath))
|
|
117
126
|
return undefined;
|
|
118
127
|
try {
|
|
119
|
-
|
|
128
|
+
const migration = loadVersionedJsonFile('current_session', filepath);
|
|
129
|
+
return {
|
|
130
|
+
...CurrentSessionStateSchema.parse(migration.document),
|
|
131
|
+
schema_version: migration.metadata.currentVersion,
|
|
132
|
+
};
|
|
120
133
|
}
|
|
121
134
|
catch {
|
|
122
135
|
return undefined;
|
|
@@ -133,7 +146,11 @@ export function loadAllSessions(cwd) {
|
|
|
133
146
|
const sessions = [];
|
|
134
147
|
for (const file of files) {
|
|
135
148
|
try {
|
|
136
|
-
|
|
149
|
+
const migration = loadVersionedJsonFile('current_session', path.join(dir, file));
|
|
150
|
+
sessions.push({
|
|
151
|
+
...CurrentSessionStateSchema.parse(migration.document),
|
|
152
|
+
schema_version: migration.metadata.currentVersion,
|
|
153
|
+
});
|
|
137
154
|
}
|
|
138
155
|
catch {
|
|
139
156
|
// skip invalid
|
|
@@ -195,7 +212,11 @@ export function gcStaleSessions(cwd, ttlOverride) {
|
|
|
195
212
|
const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
196
213
|
for (const file of files) {
|
|
197
214
|
try {
|
|
198
|
-
const
|
|
215
|
+
const migration = loadVersionedJsonFile('current_session', path.join(dir, file));
|
|
216
|
+
const session = {
|
|
217
|
+
...CurrentSessionStateSchema.parse(migration.document),
|
|
218
|
+
schema_version: migration.metadata.currentVersion,
|
|
219
|
+
};
|
|
199
220
|
if (now - Date.parse(session.last_seen_at) > ttlMs) {
|
|
200
221
|
fs.unlinkSync(path.join(dir, file));
|
|
201
222
|
removed++;
|
|
@@ -223,7 +244,11 @@ function resolveCurrentUser() {
|
|
|
223
244
|
return process.env.USER || process.env.USERNAME || os.userInfo().username || undefined;
|
|
224
245
|
}
|
|
225
246
|
function resolveCurrentAgentName() {
|
|
226
|
-
|
|
247
|
+
if (process.env.BRAINCLAW_AGENT_NAME)
|
|
248
|
+
return process.env.BRAINCLAW_AGENT_NAME;
|
|
249
|
+
if (process.env.CLAUDE_CODE_VERSION)
|
|
250
|
+
return 'claude-code';
|
|
251
|
+
return undefined;
|
|
227
252
|
}
|
|
228
253
|
function loadConfigSafe(cwd) {
|
|
229
254
|
try {
|
|
@@ -233,29 +258,90 @@ function loadConfigSafe(cwd) {
|
|
|
233
258
|
return undefined;
|
|
234
259
|
}
|
|
235
260
|
}
|
|
261
|
+
function isPidAlive(pid) {
|
|
262
|
+
try {
|
|
263
|
+
process.kill(pid, 0);
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
catch {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Find the session matching the current process among all active sessions.
|
|
272
|
+
*
|
|
273
|
+
* Resolution order:
|
|
274
|
+
* 1. Preferred session ID (explicit env var / parameter) → exact match
|
|
275
|
+
* 2. Same agent + user + host + same PID → refresh (same process reconnecting)
|
|
276
|
+
* 3. Same agent + user + host + dead PID → reclaim stale session
|
|
277
|
+
* 4. No match → create new session
|
|
278
|
+
*
|
|
279
|
+
* Crucially, if another session exists for the same agent+user+host but with
|
|
280
|
+
* a LIVE different PID, it is left untouched — that's a parallel instance.
|
|
281
|
+
*/
|
|
236
282
|
function resolveImplicitSession(cwd, options) {
|
|
237
|
-
const current = loadCurrentSession(cwd);
|
|
238
283
|
const persistImplicit = options.persistImplicit ?? true;
|
|
239
284
|
const ttlMs = parseDurationToMs(loadConfigSafe(cwd)?.implicit_session_ttl ?? '4h');
|
|
240
285
|
const now = new Date();
|
|
241
286
|
const currentUser = resolveCurrentUser();
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
287
|
+
const currentPid = process.pid;
|
|
288
|
+
// 1. If a preferred session ID is given, try exact match first
|
|
289
|
+
if (options.preferredSessionId) {
|
|
290
|
+
const exact = loadSessionById(options.preferredSessionId, cwd);
|
|
291
|
+
if (exact && now.getTime() - Date.parse(exact.last_seen_at) <= ttlMs) {
|
|
292
|
+
const refreshed = {
|
|
293
|
+
...exact,
|
|
294
|
+
last_seen_at: now.toISOString(),
|
|
295
|
+
user: exact.user || currentUser,
|
|
296
|
+
pid: currentPid,
|
|
297
|
+
};
|
|
298
|
+
if (persistImplicit)
|
|
299
|
+
saveCurrentSession(refreshed, cwd);
|
|
300
|
+
return refreshed;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
// 2. Scan all sessions for PID-aware matching
|
|
304
|
+
const allSessions = loadAllSessions(cwd);
|
|
305
|
+
let samePidSession;
|
|
306
|
+
let deadPidSession;
|
|
307
|
+
for (const session of allSessions) {
|
|
308
|
+
if (session.agent !== options.agentName)
|
|
309
|
+
continue;
|
|
310
|
+
if (session.agent_id !== options.agentId)
|
|
311
|
+
continue;
|
|
312
|
+
if (session.host_id !== options.hostId)
|
|
313
|
+
continue;
|
|
314
|
+
if (currentUser && session.user && session.user !== currentUser)
|
|
315
|
+
continue;
|
|
316
|
+
if (now.getTime() - Date.parse(session.last_seen_at) > ttlMs)
|
|
317
|
+
continue;
|
|
318
|
+
// Same PID = same process reconnecting (e.g. MCP server refreshing)
|
|
319
|
+
if (session.pid === currentPid) {
|
|
320
|
+
samePidSession = session;
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
// Different PID but alive = parallel instance, do NOT reclaim
|
|
324
|
+
if (session.pid && isPidAlive(session.pid)) {
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
// Dead PID = stale session, candidate for reclaim
|
|
328
|
+
if (!deadPidSession) {
|
|
329
|
+
deadPidSession = session;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
const toRefresh = samePidSession ?? deadPidSession;
|
|
333
|
+
if (toRefresh) {
|
|
248
334
|
const refreshed = {
|
|
249
|
-
...
|
|
335
|
+
...toRefresh,
|
|
250
336
|
last_seen_at: now.toISOString(),
|
|
251
|
-
user:
|
|
252
|
-
pid:
|
|
337
|
+
user: toRefresh.user || currentUser,
|
|
338
|
+
pid: currentPid,
|
|
253
339
|
};
|
|
254
|
-
if (persistImplicit)
|
|
340
|
+
if (persistImplicit)
|
|
255
341
|
saveCurrentSession(refreshed, cwd);
|
|
256
|
-
}
|
|
257
342
|
return refreshed;
|
|
258
343
|
}
|
|
344
|
+
// 3. No match — create new session
|
|
259
345
|
const created = {
|
|
260
346
|
session_id: options.preferredSessionId ?? generateImplicitSessionId(),
|
|
261
347
|
started_at: now.toISOString(),
|
|
@@ -264,11 +350,10 @@ function resolveImplicitSession(cwd, options) {
|
|
|
264
350
|
agent_id: options.agentId,
|
|
265
351
|
host_id: options.hostId,
|
|
266
352
|
user: currentUser,
|
|
267
|
-
pid:
|
|
353
|
+
pid: currentPid,
|
|
268
354
|
};
|
|
269
|
-
if (persistImplicit)
|
|
355
|
+
if (persistImplicit)
|
|
270
356
|
saveCurrentSession(created, cwd);
|
|
271
|
-
}
|
|
272
357
|
return created;
|
|
273
358
|
}
|
|
274
359
|
function parseDurationToMs(value) {
|
package/dist/core/ids.js
CHANGED
|
@@ -10,8 +10,14 @@ const PREFIXES = {
|
|
|
10
10
|
open_handoffs: 'hnd',
|
|
11
11
|
plan_items: 'pln',
|
|
12
12
|
plan_steps: 'stp',
|
|
13
|
+
sequences: 'seq',
|
|
13
14
|
instruction_entries: 'ins',
|
|
14
15
|
ai_surface_tasks: 'ast',
|
|
16
|
+
inbox_messages: 'msg',
|
|
17
|
+
assignments: 'asgn',
|
|
18
|
+
runs: 'run',
|
|
19
|
+
actions: 'act',
|
|
20
|
+
runtime_events: 'evt',
|
|
15
21
|
};
|
|
16
22
|
const ID_COUNTER_FILE = '.id-counter.json';
|
|
17
23
|
function counterPath(cwd) {
|
|
@@ -11,7 +11,7 @@ export const TtlSchema = z
|
|
|
11
11
|
.string()
|
|
12
12
|
.regex(TTL_PATTERN, 'Invalid TTL format. Use <number><unit> where unit is m (minutes), h (hours), or d (days). Example: 30m, 2h, 7d');
|
|
13
13
|
function formatZodError(error, field) {
|
|
14
|
-
return error.
|
|
14
|
+
return error.issues.map((e) => ({ field, message: e.message }));
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
17
|
* Validate CLI mutation inputs. On failure: prints error and calls process.exit(1).
|
|
@@ -41,7 +41,7 @@ export function validateCliInput(text, tags) {
|
|
|
41
41
|
export function validateCliTtl(ttl) {
|
|
42
42
|
const result = TtlSchema.safeParse(ttl);
|
|
43
43
|
if (!result.success) {
|
|
44
|
-
console.error(`Error: ${result.error.
|
|
44
|
+
console.error(`Error: ${result.error.issues[0]?.message ?? 'Invalid TTL'}`);
|
|
45
45
|
process.exit(1);
|
|
46
46
|
}
|
|
47
47
|
}
|