brainclaw 0.29.2 → 1.5.3
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 +193 -170
- package/dist/brainclaw-vscode.vsix +0 -0
- package/dist/cli.js +673 -24
- 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 +74 -0
- package/dist/commands/mcp.js +4221 -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/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 +20 -12
- 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
package/dist/core/context.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
1
3
|
import path from 'node:path';
|
|
4
|
+
import { readProjectVision } from './io.js';
|
|
2
5
|
import { loadActiveProject } from './active-project.js';
|
|
3
6
|
import { checkBrainclawInstallableUpdate, renderBrainclawInstallableUpdateNotice } from './brainclaw-version.js';
|
|
4
7
|
import { loadConfig } from './config.js';
|
|
@@ -15,11 +18,14 @@ import { resolveCurrentHostId } from './host.js';
|
|
|
15
18
|
import { inferProjectFromTarget, loadInstructions, resolveInstructions } from './instructions.js';
|
|
16
19
|
import { buildCurrentAgentResumeSummary, buildReputationRankingLookup } from './reputation.js';
|
|
17
20
|
import { loadState } from './state.js';
|
|
21
|
+
import { readAuditLog } from './audit.js';
|
|
18
22
|
import { listCandidates } from './candidates.js';
|
|
19
|
-
import { listClaims, isClaimExpired } from './claims.js';
|
|
23
|
+
import { listClaims, isClaimExpired, assessClaimLiveness } from './claims.js';
|
|
24
|
+
import { listAssignments } from './assignments.js';
|
|
20
25
|
import { listRuntimeNotes } from './runtime.js';
|
|
21
26
|
import { isTrapActive, listOperationalTraps } from './traps.js';
|
|
22
27
|
import { buildEstimationReport } from '../commands/estimation-report.js';
|
|
28
|
+
import { detectStaleness } from './staleness.js';
|
|
23
29
|
export const CONTEXT_SCHEMA_VERSION = '1.2';
|
|
24
30
|
export function buildContext(options = {}) {
|
|
25
31
|
const requestedCwd = options.cwd ?? process.cwd();
|
|
@@ -28,18 +34,21 @@ export function buildContext(options = {}) {
|
|
|
28
34
|
const config = loadConfig(contextCwd);
|
|
29
35
|
// Resolve parent stores for multi-store merge (walk-up from cwd)
|
|
30
36
|
const storeChain = resolveStoreChain(contextCwd);
|
|
31
|
-
const profile = options.profile ?? config.profile ?? 'dev';
|
|
32
37
|
const projectMode = config.project_mode ?? 'auto';
|
|
33
38
|
const projectStrategy = config.projects?.strategy ?? 'manual';
|
|
34
39
|
const currentHost = resolveCurrentHostId();
|
|
35
40
|
const memoryVersion = getVisibleMemoryVersion({ cwd: contextCwd, hostId: options.host, allHosts: options.allHosts });
|
|
36
41
|
const target = normalizeContextTarget(options.target, requestedCwd, contextCwd);
|
|
37
42
|
const project = options.project?.trim() || inferProjectFromTarget(target, config);
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
+
// Agent resolution: explicit param > resolveCurrentAgentIdentity (env + detection).
|
|
44
|
+
// config.current_agent is NOT used — it's a singleton global that cross-contaminates in multi-agent.
|
|
45
|
+
const currentAgentIdentity = options.agent?.trim()
|
|
46
|
+
? findAgentIdentityByName(options.agent.trim(), contextCwd)
|
|
47
|
+
: resolveCurrentAgentIdentity(contextCwd);
|
|
48
|
+
const agent = options.agent?.trim() || currentAgentIdentity?.agent_name;
|
|
49
|
+
// Profile resolution: explicit param > agent default > config default > 'dev'
|
|
50
|
+
const profile = options.profile ?? currentAgentIdentity?.context_profile ?? config.profile ?? 'dev';
|
|
51
|
+
const profileMaxItems = { dense: 20, compact: 6, copilot: 5, quick: 3, briefing: 5, 'claude-desktop': 8 };
|
|
43
52
|
const maxItems = options.maxItems ?? profileMaxItems[profile] ?? 8;
|
|
44
53
|
const maxChars = options.maxChars && options.maxChars > 0 ? options.maxChars : undefined;
|
|
45
54
|
// Instructions will be resolved after parent-store merge below (line ~460)
|
|
@@ -48,6 +57,8 @@ export function buildContext(options = {}) {
|
|
|
48
57
|
compact: ['plan', 'constraint'],
|
|
49
58
|
copilot: ['constraint', 'trap'],
|
|
50
59
|
quick: ['constraint', 'plan'],
|
|
60
|
+
briefing: ['trap', 'constraint', 'decision'],
|
|
61
|
+
'claude-desktop': ['plan', 'handoff', 'candidate'],
|
|
51
62
|
};
|
|
52
63
|
const allowedSections = profileSections[profile];
|
|
53
64
|
const items = [];
|
|
@@ -83,6 +94,7 @@ export function buildContext(options = {}) {
|
|
|
83
94
|
score: 0,
|
|
84
95
|
reasons: [],
|
|
85
96
|
extra: c.status,
|
|
97
|
+
plan_id: c.plan_id,
|
|
86
98
|
provenance: {
|
|
87
99
|
actor: c.author,
|
|
88
100
|
actor_id: c.author_id,
|
|
@@ -102,6 +114,7 @@ export function buildContext(options = {}) {
|
|
|
102
114
|
score: 0,
|
|
103
115
|
reasons: [],
|
|
104
116
|
extra: d.related_paths?.join(', '),
|
|
117
|
+
plan_id: d.plan_id,
|
|
105
118
|
provenance: {
|
|
106
119
|
actor: d.author,
|
|
107
120
|
actor_id: d.author_id,
|
|
@@ -121,6 +134,7 @@ export function buildContext(options = {}) {
|
|
|
121
134
|
score: 0,
|
|
122
135
|
reasons: [],
|
|
123
136
|
extra: `${t.severity}, visibility:${t.visibility ?? 'shared'}`,
|
|
137
|
+
plan_id: t.plan_id,
|
|
124
138
|
provenance: {
|
|
125
139
|
actor: t.author,
|
|
126
140
|
actor_id: t.author_id,
|
|
@@ -344,9 +358,9 @@ export function buildContext(options = {}) {
|
|
|
344
358
|
item.score += 5;
|
|
345
359
|
item.reasons = uniqueReasons([...item.reasons, 'agent-layer: my assigned plan']);
|
|
346
360
|
}
|
|
347
|
-
// Layer 2: boost items authored by me (+
|
|
361
|
+
// Layer 2: boost items authored by me (+0.5)
|
|
348
362
|
if (item.score >= 0 && item.provenance?.actor === agentName) {
|
|
349
|
-
item.score +=
|
|
363
|
+
item.score += 0.5;
|
|
350
364
|
item.reasons = uniqueReasons([...item.reasons, 'agent-layer: my authored item']);
|
|
351
365
|
}
|
|
352
366
|
// Reputation signal
|
|
@@ -381,28 +395,32 @@ export function buildContext(options = {}) {
|
|
|
381
395
|
});
|
|
382
396
|
const memoryDensity = classifyMemoryDensity(selected.length);
|
|
383
397
|
const bootstrapEnabled = options.bootstrap !== false;
|
|
384
|
-
|
|
398
|
+
const testMode = process.env.BRAINCLAW_TEST_MODE === '1';
|
|
399
|
+
let bootstrapAvailable = false;
|
|
385
400
|
let derivedSignals;
|
|
386
|
-
if (
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
401
|
+
if (!testMode) {
|
|
402
|
+
bootstrapAvailable = hasReusableBootstrapProfile(target, contextCwd);
|
|
403
|
+
if (bootstrapEnabled && (options.refreshBootstrap || memoryDensity === 'low')) {
|
|
404
|
+
const bootstrap = runBootstrapProfile({
|
|
405
|
+
target,
|
|
406
|
+
refresh: options.refreshBootstrap,
|
|
407
|
+
cwd: contextCwd,
|
|
408
|
+
});
|
|
409
|
+
bootstrapAvailable = bootstrap.profile.seed_count > 0;
|
|
410
|
+
if (memoryDensity === 'low') {
|
|
411
|
+
const signals = selectDerivedSignals(target, 5, contextCwd);
|
|
412
|
+
if (signals.length > 0) {
|
|
413
|
+
derivedSignals = signals;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
else if (bootstrapEnabled && bootstrapAvailable && memoryDensity === 'low') {
|
|
394
418
|
const signals = selectDerivedSignals(target, 5, contextCwd);
|
|
395
419
|
if (signals.length > 0) {
|
|
396
420
|
derivedSignals = signals;
|
|
397
421
|
}
|
|
398
422
|
}
|
|
399
423
|
}
|
|
400
|
-
else if (bootstrapEnabled && bootstrapAvailable && memoryDensity === 'low') {
|
|
401
|
-
const signals = selectDerivedSignals(target, 5, contextCwd);
|
|
402
|
-
if (signals.length > 0) {
|
|
403
|
-
derivedSignals = signals;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
424
|
const executionSensitive = isExecutionSensitiveTarget(target);
|
|
407
425
|
const derivedUsesExecution = derivedSignals?.some((signal) => signal.source_kind === 'machine') ?? false;
|
|
408
426
|
const derivedUsesTooling = derivedSignals?.some((signal) => signal.source_kind === 'skill' || signal.source_kind === 'mcp') ?? false;
|
|
@@ -424,17 +442,44 @@ export function buildContext(options = {}) {
|
|
|
424
442
|
// Build open_work: active claims and in_progress plans owned by the current agent
|
|
425
443
|
// Reuses myClaims computed in agent-layer scoring above
|
|
426
444
|
let openWork;
|
|
445
|
+
const currentSession = loadCurrentSession(contextCwd);
|
|
427
446
|
if (currentAgentIdentity || agent) {
|
|
428
447
|
const claimPlanIds = new Set(myClaims.map((c) => c.plan_id).filter(Boolean));
|
|
448
|
+
const activeAssignments = listAssignments(contextCwd, { agent: agentName }).filter((assignment) => !['completed', 'failed', 'expired', 'rerouted'].includes(assignment.status));
|
|
429
449
|
const inProgressPlans = state.plan_items.filter((p) => p.status === 'in_progress' &&
|
|
430
450
|
(p.assignee === agentName || claimPlanIds.has(p.id)));
|
|
431
|
-
if (myClaims.length > 0 || inProgressPlans.length > 0) {
|
|
451
|
+
if (myClaims.length > 0 || activeAssignments.length > 0 || inProgressPlans.length > 0) {
|
|
432
452
|
openWork = {
|
|
433
|
-
active_claims: myClaims.map((
|
|
453
|
+
active_claims: myClaims.map((c) => ({
|
|
454
|
+
id: c.id,
|
|
455
|
+
scope: c.scope,
|
|
456
|
+
description: c.description,
|
|
457
|
+
created_at: c.created_at,
|
|
458
|
+
plan_id: c.plan_id,
|
|
459
|
+
expires_at: c.expires_at,
|
|
460
|
+
liveness: assessClaimLiveness(c, { cwd: contextCwd }).status,
|
|
461
|
+
})),
|
|
462
|
+
active_assignments: activeAssignments.map(({ id, status, scope, description, plan_id, last_heartbeat_at }) => ({
|
|
463
|
+
id,
|
|
464
|
+
status,
|
|
465
|
+
scope,
|
|
466
|
+
description,
|
|
467
|
+
plan_id,
|
|
468
|
+
last_heartbeat_at,
|
|
469
|
+
})),
|
|
434
470
|
in_progress_plans: inProgressPlans.map(({ id, text, assignee }) => ({ id, text, assignee })),
|
|
435
471
|
};
|
|
436
472
|
}
|
|
437
473
|
}
|
|
474
|
+
const sessionMetrics = buildSessionMetrics({
|
|
475
|
+
cwd: contextCwd,
|
|
476
|
+
sessionId: currentSession?.session_id,
|
|
477
|
+
sessionStartedAt: currentSession?.started_at,
|
|
478
|
+
agentName: agentName,
|
|
479
|
+
agentId: currentAgentIdentity?.agent_id,
|
|
480
|
+
activeClaimsCount: openWork?.active_claims.length ?? 0,
|
|
481
|
+
runtimeNotes,
|
|
482
|
+
});
|
|
438
483
|
// Cross-project items (subscriber links — read-only, always injected, bypass scoring)
|
|
439
484
|
const crossProjectItems = [];
|
|
440
485
|
for (const link of resolveCrossProjectLinks(contextCwd)) {
|
|
@@ -442,27 +487,50 @@ export function buildContext(options = {}) {
|
|
|
442
487
|
continue;
|
|
443
488
|
try {
|
|
444
489
|
const linkedState = loadCrossProjectState(link.absolutePath);
|
|
490
|
+
for (const p of linkedState.plan_items.filter((plan) => plan.status !== 'done' && plan.status !== 'dropped')) {
|
|
491
|
+
crossProjectItems.push({
|
|
492
|
+
id: p.id, section: 'cross_project', text: `[plan] ${p.text}`,
|
|
493
|
+
tags: p.tags, score: 0, reasons: [], from_project: link.projectName,
|
|
494
|
+
extra: `${p.status}, ${p.priority}`,
|
|
495
|
+
});
|
|
496
|
+
}
|
|
445
497
|
for (const d of linkedState.recent_decisions) {
|
|
446
498
|
crossProjectItems.push({
|
|
447
499
|
id: d.id, section: 'cross_project', text: d.text,
|
|
448
500
|
tags: d.tags, score: 0, reasons: [], from_project: link.projectName,
|
|
501
|
+
plan_id: d.plan_id,
|
|
449
502
|
});
|
|
450
503
|
}
|
|
451
504
|
for (const c of linkedState.active_constraints) {
|
|
452
505
|
crossProjectItems.push({
|
|
453
506
|
id: c.id, section: 'cross_project', text: c.text,
|
|
454
507
|
tags: c.tags, score: 0, reasons: [], from_project: link.projectName,
|
|
508
|
+
plan_id: c.plan_id,
|
|
455
509
|
});
|
|
456
510
|
}
|
|
457
511
|
for (const t of linkedState.known_traps.filter((trap) => isTrapActive(trap))) {
|
|
458
512
|
crossProjectItems.push({
|
|
459
513
|
id: t.id, section: 'cross_project', text: t.text,
|
|
460
514
|
tags: t.tags, score: 0, reasons: [], from_project: link.projectName,
|
|
515
|
+
plan_id: t.plan_id,
|
|
461
516
|
});
|
|
462
517
|
}
|
|
463
518
|
}
|
|
464
519
|
catch { /* skip unavailable linked project */ }
|
|
465
520
|
}
|
|
521
|
+
// Staleness detection: non-blocking, capped at 5 warnings to keep context lean.
|
|
522
|
+
// Phase 4 Sprint 1 Lane A step 3 (pln#390): runtime_note staleness now
|
|
523
|
+
// flows through the same surface.
|
|
524
|
+
let staleWarnings;
|
|
525
|
+
try {
|
|
526
|
+
const pendingCandidatesForStaleness = listCandidates('pending', contextCwd);
|
|
527
|
+
const runtimeNotesForStaleness = listRuntimeNotes(undefined, contextCwd);
|
|
528
|
+
const staleReport = detectStaleness(state.plan_items, state.known_traps, state.open_handoffs, pendingCandidatesForStaleness, Date.now(), runtimeNotesForStaleness);
|
|
529
|
+
if (staleReport.warnings.length > 0) {
|
|
530
|
+
staleWarnings = staleReport.warnings.slice(0, 5);
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
catch { /* non-fatal */ }
|
|
466
534
|
const result = {
|
|
467
535
|
context_schema: CONTEXT_SCHEMA_VERSION,
|
|
468
536
|
profile,
|
|
@@ -471,6 +539,7 @@ export function buildContext(options = {}) {
|
|
|
471
539
|
project_mode: projectMode,
|
|
472
540
|
project_strategy: projectStrategy,
|
|
473
541
|
current_host: currentHost,
|
|
542
|
+
project_vision: readProjectVision(contextCwd),
|
|
474
543
|
host_filter: options.host,
|
|
475
544
|
all_hosts: options.allHosts ?? false,
|
|
476
545
|
memory_version: memoryVersion,
|
|
@@ -493,6 +562,7 @@ export function buildContext(options = {}) {
|
|
|
493
562
|
resolved_instructions: resolvedInstructions,
|
|
494
563
|
resume_summary: resumeSummary,
|
|
495
564
|
open_work: openWork,
|
|
565
|
+
session_metrics: sessionMetrics,
|
|
496
566
|
stores: storeChain.length > 1
|
|
497
567
|
? storeChain.map(({ cwd, depth, role }) => ({ cwd, depth, role }))
|
|
498
568
|
: undefined,
|
|
@@ -509,6 +579,7 @@ export function buildContext(options = {}) {
|
|
|
509
579
|
cross_project_items: crossProjectItems.length > 0 ? crossProjectItems : undefined,
|
|
510
580
|
claim_conflicts: detectClaimConflicts(myClaims, otherActiveClaims),
|
|
511
581
|
workflow_hints: buildWorkflowHints(myClaims, openWork, state.plan_items),
|
|
582
|
+
stale_warnings: staleWarnings,
|
|
512
583
|
selected,
|
|
513
584
|
};
|
|
514
585
|
if (options.digest) {
|
|
@@ -520,7 +591,7 @@ export function renderContextMarkdown(result, explain = false) {
|
|
|
520
591
|
const lines = [];
|
|
521
592
|
lines.push(`# Agent Context (${result.profile})`);
|
|
522
593
|
lines.push('');
|
|
523
|
-
if (result.open_work && (result.open_work.active_claims.length > 0 || result.open_work.in_progress_plans.length > 0)) {
|
|
594
|
+
if (result.open_work && (result.open_work.active_claims.length > 0 || result.open_work.active_assignments.length > 0 || result.open_work.in_progress_plans.length > 0)) {
|
|
524
595
|
lines.push('## ⚠ Your open work');
|
|
525
596
|
lines.push('');
|
|
526
597
|
if (result.open_work.active_claims.length > 0) {
|
|
@@ -530,10 +601,22 @@ export function renderContextMarkdown(result, explain = false) {
|
|
|
530
601
|
const planRef = claim.plan_id ? ` [plan: ${claim.plan_id}]` : '';
|
|
531
602
|
const expired = claim.expires_at && claim.expires_at < now ? ' ⚠ EXPIRED — run brainclaw prune' : '';
|
|
532
603
|
const ttlInfo = claim.expires_at && !expired ? ` (expires ${claim.expires_at.slice(0, 16).replace('T', ' ')})` : '';
|
|
533
|
-
|
|
604
|
+
const livenessTag = claim.liveness && claim.liveness !== 'live' && claim.liveness !== 'young'
|
|
605
|
+
? ` [${claim.liveness.toUpperCase()}]`
|
|
606
|
+
: '';
|
|
607
|
+
lines.push(`- [${claim.id}] ${claim.description}${planRef}${ttlInfo}${livenessTag}${expired}`);
|
|
534
608
|
lines.push(` scope: ${claim.scope}`);
|
|
535
609
|
}
|
|
536
610
|
}
|
|
611
|
+
if (result.open_work.active_assignments.length > 0) {
|
|
612
|
+
lines.push('Active assignments (runtime state):');
|
|
613
|
+
for (const assignment of result.open_work.active_assignments) {
|
|
614
|
+
const planRef = assignment.plan_id ? ` [plan: ${assignment.plan_id}]` : '';
|
|
615
|
+
const heartbeat = assignment.last_heartbeat_at ? ` [heartbeat: ${assignment.last_heartbeat_at.slice(0, 16).replace('T', ' ')}]` : '';
|
|
616
|
+
lines.push(`- [${assignment.id}] ${assignment.description}${planRef} (${assignment.status})${heartbeat}`);
|
|
617
|
+
lines.push(` scope: ${assignment.scope}`);
|
|
618
|
+
}
|
|
619
|
+
}
|
|
537
620
|
if (result.open_work.in_progress_plans.length > 0) {
|
|
538
621
|
lines.push('In-progress plan items (update status when done):');
|
|
539
622
|
for (const plan of result.open_work.in_progress_plans) {
|
|
@@ -542,6 +625,17 @@ export function renderContextMarkdown(result, explain = false) {
|
|
|
542
625
|
}
|
|
543
626
|
lines.push('');
|
|
544
627
|
}
|
|
628
|
+
if (result.session_metrics) {
|
|
629
|
+
lines.push('## Session metrics');
|
|
630
|
+
lines.push('');
|
|
631
|
+
lines.push(`- Active claims: ${result.session_metrics.active_claims}`);
|
|
632
|
+
lines.push(`- Edits since last Brainclaw write: ${result.session_metrics.edits_since_last_memory}`);
|
|
633
|
+
lines.push(`- Session duration: ${result.session_metrics.session_duration_minutes} min`);
|
|
634
|
+
if (result.session_metrics.last_brainclaw_write) {
|
|
635
|
+
lines.push(`- Last Brainclaw write: ${result.session_metrics.last_brainclaw_write}`);
|
|
636
|
+
}
|
|
637
|
+
lines.push('');
|
|
638
|
+
}
|
|
545
639
|
if (result.estimation_calibration) {
|
|
546
640
|
lines.push(`Estimation calibration: ${result.estimation_calibration}`);
|
|
547
641
|
lines.push('');
|
|
@@ -553,6 +647,9 @@ export function renderContextMarkdown(result, explain = false) {
|
|
|
553
647
|
if (result.agent_id && result.agent) {
|
|
554
648
|
lines.push(`Agent ID: ${result.agent_id}`);
|
|
555
649
|
}
|
|
650
|
+
if (result.project_vision) {
|
|
651
|
+
lines.push(`Project vision: ${result.project_vision.split('\n')[0]}`);
|
|
652
|
+
}
|
|
556
653
|
lines.push(`Project mode: ${result.project_mode} (${result.project_strategy})`);
|
|
557
654
|
if (result.active_project) {
|
|
558
655
|
const ap = result.active_project;
|
|
@@ -701,8 +798,9 @@ export function renderContextMarkdown(result, explain = false) {
|
|
|
701
798
|
for (const item of result.selected) {
|
|
702
799
|
const tags = item.tags.length ? ` [${item.tags.join(', ')}]` : '';
|
|
703
800
|
const extra = item.extra ? ` (${item.extra})` : '';
|
|
801
|
+
const planRef = item.plan_id ? ` [plan: ${item.plan_id}]` : '';
|
|
704
802
|
const why = explain && item.reasons.length ? ` {why: ${item.reasons.join(', ')}}` : '';
|
|
705
|
-
lines.push(`- [${item.id}] <${item.section}> ${item.text}${extra}${tags}${why}`);
|
|
803
|
+
lines.push(`- [${item.id}] <${item.section}> ${item.text}${extra}${planRef}${tags}${why}`);
|
|
706
804
|
}
|
|
707
805
|
if (result.derived_signals && result.derived_signals.length > 0) {
|
|
708
806
|
lines.push('');
|
|
@@ -770,6 +868,12 @@ export function renderContextPromptTemplate(result, compact = false) {
|
|
|
770
868
|
lines.push(`sd=${result.context_diff.since_session ?? ''}`);
|
|
771
869
|
lines.push(`dc=${result.context_diff.counts.total}`);
|
|
772
870
|
}
|
|
871
|
+
if (result.stale_warnings && result.stale_warnings.length > 0) {
|
|
872
|
+
lines.push(`sw=${result.stale_warnings.length}`);
|
|
873
|
+
for (const w of result.stale_warnings) {
|
|
874
|
+
lines.push(` - en=${w.entity} id=${w.id} ag=${w.age_days} tx="${w.reason}"`);
|
|
875
|
+
}
|
|
876
|
+
}
|
|
773
877
|
if (result.resume_summary) {
|
|
774
878
|
lines.push(`rt=${result.resume_summary.internal_trust}`);
|
|
775
879
|
lines.push('rs:');
|
|
@@ -798,6 +902,9 @@ export function renderContextPromptTemplate(result, compact = false) {
|
|
|
798
902
|
}
|
|
799
903
|
lines.push(`project_mode: ${result.project_mode}`);
|
|
800
904
|
lines.push(`project_strategy: ${result.project_strategy}`);
|
|
905
|
+
if (result.project_vision) {
|
|
906
|
+
lines.push(`project_vision: "${result.project_vision.split('\n')[0]}"`);
|
|
907
|
+
}
|
|
801
908
|
if (result.active_project) {
|
|
802
909
|
lines.push(`active_project: ${result.active_project.name ?? result.active_project.path}`);
|
|
803
910
|
lines.push(`active_project_switched: ${result.active_project.switched_at}`);
|
|
@@ -883,22 +990,42 @@ export function renderContextPromptTemplate(result, compact = false) {
|
|
|
883
990
|
lines.push(` - ${item}`);
|
|
884
991
|
}
|
|
885
992
|
}
|
|
993
|
+
if (result.stale_warnings && result.stale_warnings.length > 0) {
|
|
994
|
+
lines.push('stale_warnings:');
|
|
995
|
+
for (const w of result.stale_warnings) {
|
|
996
|
+
lines.push(` - entity=${w.entity} id=${w.id} age_days=${w.age_days} reason="${w.reason}" action="${w.suggested_action}"`);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
886
999
|
if (result.target) {
|
|
887
1000
|
lines.push(`target: ${result.target}`);
|
|
888
1001
|
}
|
|
889
1002
|
}
|
|
890
|
-
if (result.open_work && (result.open_work.active_claims.length > 0 || result.open_work.in_progress_plans.length > 0)) {
|
|
1003
|
+
if (result.open_work && (result.open_work.active_claims.length > 0 || result.open_work.active_assignments.length > 0 || result.open_work.in_progress_plans.length > 0)) {
|
|
891
1004
|
lines.push(compact ? 'ow:' : 'open_work:');
|
|
892
1005
|
if (result.open_work.active_claims.length > 0) {
|
|
893
1006
|
lines.push(compact ? ' claims:' : ' active_claims:');
|
|
894
1007
|
for (const claim of result.open_work.active_claims) {
|
|
1008
|
+
const lv = claim.liveness ? (compact ? ` lv=${claim.liveness}` : ` liveness=${claim.liveness}`) : '';
|
|
895
1009
|
if (compact) {
|
|
896
1010
|
const planRef = claim.plan_id ? ` pl=${claim.plan_id}` : '';
|
|
897
|
-
lines.push(` - id=${claim.id}${planRef} sc="${claim.scope}" tx="${claim.description}"`);
|
|
1011
|
+
lines.push(` - id=${claim.id}${planRef} sc="${claim.scope}" tx="${claim.description}"${lv}`);
|
|
898
1012
|
}
|
|
899
1013
|
else {
|
|
900
1014
|
const planRef = claim.plan_id ? ` plan_id=${claim.plan_id}` : '';
|
|
901
|
-
lines.push(` - id=${claim.id}${planRef} scope="${claim.scope}" description="${claim.description}"`);
|
|
1015
|
+
lines.push(` - id=${claim.id}${planRef} scope="${claim.scope}" description="${claim.description}"${lv}`);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
if (result.open_work.active_assignments.length > 0) {
|
|
1020
|
+
lines.push(compact ? ' assignments:' : ' active_assignments:');
|
|
1021
|
+
for (const assignment of result.open_work.active_assignments) {
|
|
1022
|
+
if (compact) {
|
|
1023
|
+
const planRef = assignment.plan_id ? ` pl=${assignment.plan_id}` : '';
|
|
1024
|
+
lines.push(` - id=${assignment.id}${planRef} st=${assignment.status} sc="${assignment.scope}" tx="${assignment.description}"`);
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
const planRef = assignment.plan_id ? ` plan_id=${assignment.plan_id}` : '';
|
|
1028
|
+
lines.push(` - id=${assignment.id}${planRef} status=${assignment.status} scope="${assignment.scope}" description="${assignment.description}"`);
|
|
902
1029
|
}
|
|
903
1030
|
}
|
|
904
1031
|
}
|
|
@@ -914,6 +1041,25 @@ export function renderContextPromptTemplate(result, compact = false) {
|
|
|
914
1041
|
}
|
|
915
1042
|
}
|
|
916
1043
|
}
|
|
1044
|
+
if (result.session_metrics) {
|
|
1045
|
+
lines.push(compact ? 'sm:' : 'session_metrics:');
|
|
1046
|
+
if (compact) {
|
|
1047
|
+
lines.push(` ac=${result.session_metrics.active_claims}`);
|
|
1048
|
+
lines.push(` ed=${result.session_metrics.edits_since_last_memory}`);
|
|
1049
|
+
lines.push(` dur=${result.session_metrics.session_duration_minutes}`);
|
|
1050
|
+
if (result.session_metrics.last_brainclaw_write) {
|
|
1051
|
+
lines.push(` lbw=${result.session_metrics.last_brainclaw_write}`);
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
else {
|
|
1055
|
+
lines.push(` active_claims: ${result.session_metrics.active_claims}`);
|
|
1056
|
+
lines.push(` edits_since_last_memory: ${result.session_metrics.edits_since_last_memory}`);
|
|
1057
|
+
lines.push(` session_duration_minutes: ${result.session_metrics.session_duration_minutes}`);
|
|
1058
|
+
if (result.session_metrics.last_brainclaw_write) {
|
|
1059
|
+
lines.push(` last_brainclaw_write: ${result.session_metrics.last_brainclaw_write}`);
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
917
1063
|
lines.push(compact ? 'ins:' : 'instructions:');
|
|
918
1064
|
if (result.resolved_instructions.length === 0) {
|
|
919
1065
|
lines.push(compact ? ' - n' : ' - none');
|
|
@@ -941,14 +1087,16 @@ export function renderContextPromptTemplate(result, compact = false) {
|
|
|
941
1087
|
if (compact) {
|
|
942
1088
|
const tags = item.tags.length ? ` tg=[${item.tags.join(',')}]` : '';
|
|
943
1089
|
const extra = item.extra ? ` ex="${item.extra}"` : '';
|
|
1090
|
+
const planRef = item.plan_id ? ` pl=${item.plan_id}` : '';
|
|
944
1091
|
const why = item.reasons.length ? ` why=[${item.reasons.join('|')}]` : '';
|
|
945
|
-
lines.push(` - id=${item.id} tp=${item.section}${tags}${extra}${why} tx="${item.text}"`);
|
|
1092
|
+
lines.push(` - id=${item.id} tp=${item.section}${tags}${extra}${planRef}${why} tx="${item.text}"`);
|
|
946
1093
|
}
|
|
947
1094
|
else {
|
|
948
1095
|
const tags = item.tags.length ? ` tags=[${item.tags.join(',')}]` : '';
|
|
949
1096
|
const extra = item.extra ? ` extra="${item.extra}"` : '';
|
|
1097
|
+
const planRef = item.plan_id ? ` plan=${item.plan_id}` : '';
|
|
950
1098
|
const why = item.reasons.length ? ` why=[${item.reasons.join(', ')}]` : '';
|
|
951
|
-
lines.push(` - id=${item.id} type=${item.section}${tags}${extra}${why} text="${item.text}"`);
|
|
1099
|
+
lines.push(` - id=${item.id} type=${item.section}${tags}${extra}${planRef}${why} text="${item.text}"`);
|
|
952
1100
|
}
|
|
953
1101
|
}
|
|
954
1102
|
}
|
|
@@ -979,6 +1127,119 @@ export function renderContextPromptTemplate(result, compact = false) {
|
|
|
979
1127
|
lines.push('```');
|
|
980
1128
|
return lines.join('\n');
|
|
981
1129
|
}
|
|
1130
|
+
/**
|
|
1131
|
+
* Render context as an ultra-compact scope briefing (< 500 chars).
|
|
1132
|
+
* Designed for pre-action injection: only what matters for THIS scope, NOW.
|
|
1133
|
+
*
|
|
1134
|
+
* Output example:
|
|
1135
|
+
* scope: src/core/agent-files.ts
|
|
1136
|
+
* traps: [high] MCP server hérite env vars VS Code
|
|
1137
|
+
* claims: no conflict
|
|
1138
|
+
* decisions: brainclawMcpEntry preserves existing command
|
|
1139
|
+
* confidence: high (3 items, freshest 2d ago)
|
|
1140
|
+
*/
|
|
1141
|
+
export function renderContextBriefing(result) {
|
|
1142
|
+
const lines = [];
|
|
1143
|
+
if (result.target) {
|
|
1144
|
+
lines.push(`scope: ${result.target}`);
|
|
1145
|
+
}
|
|
1146
|
+
// Traps (high severity first, max 2)
|
|
1147
|
+
const traps = result.selected
|
|
1148
|
+
.filter(i => i.section === 'trap')
|
|
1149
|
+
.slice(0, 2);
|
|
1150
|
+
if (traps.length > 0) {
|
|
1151
|
+
for (const t of traps) {
|
|
1152
|
+
const severity = t.extra?.match(/\b(high|medium|low)\b/)?.[0] ?? '';
|
|
1153
|
+
const text = t.text.length > 80 ? t.text.slice(0, 77) + '...' : t.text;
|
|
1154
|
+
lines.push(`trap: [${severity}] ${text}`);
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
// Claim conflicts
|
|
1158
|
+
if (result.claim_conflicts && result.claim_conflicts.length > 0) {
|
|
1159
|
+
for (const c of result.claim_conflicts.slice(0, 2)) {
|
|
1160
|
+
lines.push(`conflict: ${c.other_agent} claims ${c.other_scope}`);
|
|
1161
|
+
}
|
|
1162
|
+
}
|
|
1163
|
+
else {
|
|
1164
|
+
lines.push('claims: no conflict');
|
|
1165
|
+
}
|
|
1166
|
+
// Recent decisions (max 1, truncated)
|
|
1167
|
+
const decisions = result.selected
|
|
1168
|
+
.filter(i => i.section === 'decision')
|
|
1169
|
+
.slice(0, 1);
|
|
1170
|
+
if (decisions.length > 0) {
|
|
1171
|
+
const text = decisions[0].text.length > 80 ? decisions[0].text.slice(0, 77) + '...' : decisions[0].text;
|
|
1172
|
+
lines.push(`decision: ${text}`);
|
|
1173
|
+
}
|
|
1174
|
+
// Confidence score based on item count and scope activity
|
|
1175
|
+
const totalItems = result.selected.length;
|
|
1176
|
+
const hasActivity = result.scoped_activity != null;
|
|
1177
|
+
let confidence = 'low';
|
|
1178
|
+
if (totalItems >= 3 && hasActivity) {
|
|
1179
|
+
confidence = 'high';
|
|
1180
|
+
}
|
|
1181
|
+
else if (totalItems >= 1) {
|
|
1182
|
+
confidence = 'medium';
|
|
1183
|
+
}
|
|
1184
|
+
lines.push(`confidence: ${confidence} (${totalItems} items${hasActivity ? ', scope has recent activity' : ''})`);
|
|
1185
|
+
// Sequence position placeholder (populated when Sequences land)
|
|
1186
|
+
// lines.push(`sequence: ...`);
|
|
1187
|
+
return lines.join('\n');
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Render context for Claude Desktop: surface tasks, inbox items, and active plans.
|
|
1191
|
+
* Excludes traps, constraints, and decisions — Claude Desktop sessions are task-oriented.
|
|
1192
|
+
* Target output: < 1500 chars, actionable items only.
|
|
1193
|
+
*/
|
|
1194
|
+
export function renderContextClaudeDesktop(result) {
|
|
1195
|
+
const lines = [];
|
|
1196
|
+
lines.push('# Brainclaw — Claude Desktop Session');
|
|
1197
|
+
lines.push('');
|
|
1198
|
+
// Active plans (task queue)
|
|
1199
|
+
const plans = result.selected.filter(i => i.section === 'plan');
|
|
1200
|
+
if (plans.length > 0) {
|
|
1201
|
+
lines.push('## Tasks');
|
|
1202
|
+
for (const p of plans.slice(0, 5)) {
|
|
1203
|
+
const status = p.extra?.match(/\b(todo|in_progress|blocked)\b/)?.[0] ?? 'todo';
|
|
1204
|
+
const text = p.text.length > 100 ? p.text.slice(0, 97) + '...' : p.text;
|
|
1205
|
+
lines.push(`- [${status}] ${text} (${p.id})`);
|
|
1206
|
+
}
|
|
1207
|
+
lines.push('');
|
|
1208
|
+
}
|
|
1209
|
+
// Open handoffs (pending work from other agents)
|
|
1210
|
+
const handoffs = result.selected.filter(i => i.section === 'handoff');
|
|
1211
|
+
if (handoffs.length > 0) {
|
|
1212
|
+
lines.push('## Inbox');
|
|
1213
|
+
for (const h of handoffs.slice(0, 3)) {
|
|
1214
|
+
const from = h.provenance?.actor ?? 'unknown';
|
|
1215
|
+
const text = h.text.length > 100 ? h.text.slice(0, 97) + '...' : h.text;
|
|
1216
|
+
lines.push(`- from:${from} — ${text}`);
|
|
1217
|
+
}
|
|
1218
|
+
lines.push('');
|
|
1219
|
+
}
|
|
1220
|
+
// Candidates (review items)
|
|
1221
|
+
const candidates = result.selected.filter(i => i.section === 'candidate');
|
|
1222
|
+
if (candidates.length > 0) {
|
|
1223
|
+
lines.push('## Review');
|
|
1224
|
+
for (const c of candidates.slice(0, 3)) {
|
|
1225
|
+
const text = c.text.length > 100 ? c.text.slice(0, 97) + '...' : c.text;
|
|
1226
|
+
lines.push(`- ${text} (${c.id})`);
|
|
1227
|
+
}
|
|
1228
|
+
lines.push('');
|
|
1229
|
+
}
|
|
1230
|
+
if (plans.length === 0 && handoffs.length === 0 && candidates.length === 0) {
|
|
1231
|
+
lines.push('No pending tasks. Use `bclaw_quick_capture` to queue work for Claude Desktop.');
|
|
1232
|
+
}
|
|
1233
|
+
// Claim conflicts (always show if present)
|
|
1234
|
+
if (result.claim_conflicts && result.claim_conflicts.length > 0) {
|
|
1235
|
+
lines.push('## Conflicts');
|
|
1236
|
+
for (const c of result.claim_conflicts.slice(0, 2)) {
|
|
1237
|
+
lines.push(`- ${c.other_agent} claims ${c.other_scope}`);
|
|
1238
|
+
}
|
|
1239
|
+
lines.push('');
|
|
1240
|
+
}
|
|
1241
|
+
return lines.join('\n');
|
|
1242
|
+
}
|
|
982
1243
|
export function buildScopedActivity(input) {
|
|
983
1244
|
const target = input.target?.trim();
|
|
984
1245
|
if (!target) {
|
|
@@ -1077,6 +1338,85 @@ function classifyMemoryDensity(selectedCount) {
|
|
|
1077
1338
|
return 'medium';
|
|
1078
1339
|
return 'high';
|
|
1079
1340
|
}
|
|
1341
|
+
const BRAINCLAW_WRITE_ACTIONS = ['create', 'update', 'delete', 'accept', 'reject', 'trust_change', 'promote_direct', 'rollback'];
|
|
1342
|
+
function buildSessionMetrics(input) {
|
|
1343
|
+
if (!input.sessionId || !input.sessionStartedAt || !input.agentName) {
|
|
1344
|
+
return undefined;
|
|
1345
|
+
}
|
|
1346
|
+
const sessionId = input.sessionId;
|
|
1347
|
+
const startedAtMs = Date.parse(input.sessionStartedAt);
|
|
1348
|
+
if (!Number.isFinite(startedAtMs)) {
|
|
1349
|
+
return undefined;
|
|
1350
|
+
}
|
|
1351
|
+
const runtimeWrites = input.runtimeNotes
|
|
1352
|
+
.filter((note) => note.agent === input.agentName
|
|
1353
|
+
&& note.session_id === sessionId
|
|
1354
|
+
&& ((note.note_type ?? 'observation') === 'observation'))
|
|
1355
|
+
.map((note) => note.created_at);
|
|
1356
|
+
const auditWrites = readAuditLog({ since: input.sessionStartedAt, actor: input.agentId ?? input.agentName }, input.cwd)
|
|
1357
|
+
.filter((entry) => belongsToSession(entry, sessionId))
|
|
1358
|
+
.filter((entry) => BRAINCLAW_WRITE_ACTIONS.includes(entry.action))
|
|
1359
|
+
.map((entry) => entry.timestamp);
|
|
1360
|
+
const lastBrainclawWrite = [...runtimeWrites, ...auditWrites].sort().at(-1);
|
|
1361
|
+
const editsSinceLastMemory = countChangedFilesSince(input.cwd, lastBrainclawWrite ?? input.sessionStartedAt);
|
|
1362
|
+
return {
|
|
1363
|
+
active_claims: input.activeClaimsCount,
|
|
1364
|
+
edits_since_last_memory: editsSinceLastMemory,
|
|
1365
|
+
session_duration_minutes: Math.max(0, Math.floor((Date.now() - startedAtMs) / 60_000)),
|
|
1366
|
+
last_brainclaw_write: lastBrainclawWrite,
|
|
1367
|
+
};
|
|
1368
|
+
}
|
|
1369
|
+
function belongsToSession(entry, sessionId) {
|
|
1370
|
+
return !entry.session_id || entry.session_id === sessionId;
|
|
1371
|
+
}
|
|
1372
|
+
function countChangedFilesSince(cwd, since) {
|
|
1373
|
+
const sinceMs = Date.parse(since);
|
|
1374
|
+
if (!Number.isFinite(sinceMs)) {
|
|
1375
|
+
return 0;
|
|
1376
|
+
}
|
|
1377
|
+
try {
|
|
1378
|
+
const output = execSync('git status --porcelain --untracked-files=normal', {
|
|
1379
|
+
cwd,
|
|
1380
|
+
encoding: 'utf-8',
|
|
1381
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
1382
|
+
}).trim();
|
|
1383
|
+
if (!output) {
|
|
1384
|
+
return 0;
|
|
1385
|
+
}
|
|
1386
|
+
const changedPaths = new Set();
|
|
1387
|
+
for (const line of output.split(/\r?\n/)) {
|
|
1388
|
+
const rawPath = line.slice(3).trim();
|
|
1389
|
+
const resolvedPath = rawPath.includes(' -> ') ? rawPath.split(' -> ').pop() ?? rawPath : rawPath;
|
|
1390
|
+
if (resolvedPath && shouldCountEditedPath(resolvedPath)) {
|
|
1391
|
+
changedPaths.add(resolvedPath);
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
let count = 0;
|
|
1395
|
+
for (const relativePath of changedPaths) {
|
|
1396
|
+
const absolutePath = path.join(cwd, relativePath);
|
|
1397
|
+
try {
|
|
1398
|
+
if (!fs.existsSync(absolutePath)) {
|
|
1399
|
+
count++;
|
|
1400
|
+
continue;
|
|
1401
|
+
}
|
|
1402
|
+
if (fs.statSync(absolutePath).mtimeMs >= sinceMs) {
|
|
1403
|
+
count++;
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
catch {
|
|
1407
|
+
count++;
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
return count;
|
|
1411
|
+
}
|
|
1412
|
+
catch {
|
|
1413
|
+
return 0;
|
|
1414
|
+
}
|
|
1415
|
+
}
|
|
1416
|
+
function shouldCountEditedPath(relativePath) {
|
|
1417
|
+
const normalized = relativePath.replace(/\\/g, '/');
|
|
1418
|
+
return !normalized.startsWith('.brainclaw/') && !normalized.startsWith('.git/');
|
|
1419
|
+
}
|
|
1080
1420
|
function summariseAgentTooling(snapshot) {
|
|
1081
1421
|
return {
|
|
1082
1422
|
agents_md_present: snapshot.agents_md_present,
|
|
@@ -1226,6 +1566,13 @@ function computeRelevance(item, terms, profile, target) {
|
|
|
1226
1566
|
score += 1;
|
|
1227
1567
|
reasons.push('runtime execution signal');
|
|
1228
1568
|
}
|
|
1569
|
+
if (profile === 'dense') {
|
|
1570
|
+
// Dense profile: boost all actionable sections equally for maximum coverage
|
|
1571
|
+
if (item.section === 'decision' || item.section === 'trap' || item.section === 'candidate' || item.section === 'runtime') {
|
|
1572
|
+
score += 2;
|
|
1573
|
+
reasons.push('profile boost: dense');
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1229
1576
|
if (profile === 'dev' && (item.section === 'decision' || item.section === 'trap')) {
|
|
1230
1577
|
score += 2;
|
|
1231
1578
|
reasons.push('profile boost: dev');
|