brainclaw 0.28.0 → 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 +683 -23
- 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 +4244 -1475
- 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 +131 -10
- 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 +124 -0
- 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/bootstrap.js +61 -10
- 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 +454 -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/event-log.js +1 -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 +252 -28
- 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/repo-analysis.js +67 -0
- package/dist/core/reputation.js +9 -3
- package/dist/core/schema.js +546 -21
- 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 +54 -12
- 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,5 +1,11 @@
|
|
|
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';
|
|
5
|
+
import { loadActiveProject } from './active-project.js';
|
|
6
|
+
import { checkBrainclawInstallableUpdate, renderBrainclawInstallableUpdateNotice } from './brainclaw-version.js';
|
|
2
7
|
import { loadConfig } from './config.js';
|
|
8
|
+
import { loadCurrentSession, loadAllSessions } from './identity.js';
|
|
3
9
|
import { resolveCrossProjectLinks, loadCrossProjectState } from './cross-project.js';
|
|
4
10
|
import { buildContextDiff } from './context-diff.js';
|
|
5
11
|
import { resolveContextStoreCwd, resolveStoreChain } from './store-resolution.js';
|
|
@@ -12,11 +18,14 @@ import { resolveCurrentHostId } from './host.js';
|
|
|
12
18
|
import { inferProjectFromTarget, loadInstructions, resolveInstructions } from './instructions.js';
|
|
13
19
|
import { buildCurrentAgentResumeSummary, buildReputationRankingLookup } from './reputation.js';
|
|
14
20
|
import { loadState } from './state.js';
|
|
21
|
+
import { readAuditLog } from './audit.js';
|
|
15
22
|
import { listCandidates } from './candidates.js';
|
|
16
|
-
import { listClaims, isClaimExpired } from './claims.js';
|
|
23
|
+
import { listClaims, isClaimExpired, assessClaimLiveness } from './claims.js';
|
|
24
|
+
import { listAssignments } from './assignments.js';
|
|
17
25
|
import { listRuntimeNotes } from './runtime.js';
|
|
18
26
|
import { isTrapActive, listOperationalTraps } from './traps.js';
|
|
19
27
|
import { buildEstimationReport } from '../commands/estimation-report.js';
|
|
28
|
+
import { detectStaleness } from './staleness.js';
|
|
20
29
|
export const CONTEXT_SCHEMA_VERSION = '1.2';
|
|
21
30
|
export function buildContext(options = {}) {
|
|
22
31
|
const requestedCwd = options.cwd ?? process.cwd();
|
|
@@ -25,18 +34,21 @@ export function buildContext(options = {}) {
|
|
|
25
34
|
const config = loadConfig(contextCwd);
|
|
26
35
|
// Resolve parent stores for multi-store merge (walk-up from cwd)
|
|
27
36
|
const storeChain = resolveStoreChain(contextCwd);
|
|
28
|
-
const profile = options.profile ?? config.profile ?? 'dev';
|
|
29
37
|
const projectMode = config.project_mode ?? 'auto';
|
|
30
38
|
const projectStrategy = config.projects?.strategy ?? 'manual';
|
|
31
39
|
const currentHost = resolveCurrentHostId();
|
|
32
40
|
const memoryVersion = getVisibleMemoryVersion({ cwd: contextCwd, hostId: options.host, allHosts: options.allHosts });
|
|
33
41
|
const target = normalizeContextTarget(options.target, requestedCwd, contextCwd);
|
|
34
42
|
const project = options.project?.trim() || inferProjectFromTarget(target, config);
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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 };
|
|
40
52
|
const maxItems = options.maxItems ?? profileMaxItems[profile] ?? 8;
|
|
41
53
|
const maxChars = options.maxChars && options.maxChars > 0 ? options.maxChars : undefined;
|
|
42
54
|
// Instructions will be resolved after parent-store merge below (line ~460)
|
|
@@ -45,6 +57,8 @@ export function buildContext(options = {}) {
|
|
|
45
57
|
compact: ['plan', 'constraint'],
|
|
46
58
|
copilot: ['constraint', 'trap'],
|
|
47
59
|
quick: ['constraint', 'plan'],
|
|
60
|
+
briefing: ['trap', 'constraint', 'decision'],
|
|
61
|
+
'claude-desktop': ['plan', 'handoff', 'candidate'],
|
|
48
62
|
};
|
|
49
63
|
const allowedSections = profileSections[profile];
|
|
50
64
|
const items = [];
|
|
@@ -80,6 +94,7 @@ export function buildContext(options = {}) {
|
|
|
80
94
|
score: 0,
|
|
81
95
|
reasons: [],
|
|
82
96
|
extra: c.status,
|
|
97
|
+
plan_id: c.plan_id,
|
|
83
98
|
provenance: {
|
|
84
99
|
actor: c.author,
|
|
85
100
|
actor_id: c.author_id,
|
|
@@ -99,6 +114,7 @@ export function buildContext(options = {}) {
|
|
|
99
114
|
score: 0,
|
|
100
115
|
reasons: [],
|
|
101
116
|
extra: d.related_paths?.join(', '),
|
|
117
|
+
plan_id: d.plan_id,
|
|
102
118
|
provenance: {
|
|
103
119
|
actor: d.author,
|
|
104
120
|
actor_id: d.author_id,
|
|
@@ -118,6 +134,7 @@ export function buildContext(options = {}) {
|
|
|
118
134
|
score: 0,
|
|
119
135
|
reasons: [],
|
|
120
136
|
extra: `${t.severity}, visibility:${t.visibility ?? 'shared'}`,
|
|
137
|
+
plan_id: t.plan_id,
|
|
121
138
|
provenance: {
|
|
122
139
|
actor: t.author,
|
|
123
140
|
actor_id: t.author_id,
|
|
@@ -341,9 +358,9 @@ export function buildContext(options = {}) {
|
|
|
341
358
|
item.score += 5;
|
|
342
359
|
item.reasons = uniqueReasons([...item.reasons, 'agent-layer: my assigned plan']);
|
|
343
360
|
}
|
|
344
|
-
// Layer 2: boost items authored by me (+
|
|
361
|
+
// Layer 2: boost items authored by me (+0.5)
|
|
345
362
|
if (item.score >= 0 && item.provenance?.actor === agentName) {
|
|
346
|
-
item.score +=
|
|
363
|
+
item.score += 0.5;
|
|
347
364
|
item.reasons = uniqueReasons([...item.reasons, 'agent-layer: my authored item']);
|
|
348
365
|
}
|
|
349
366
|
// Reputation signal
|
|
@@ -378,28 +395,32 @@ export function buildContext(options = {}) {
|
|
|
378
395
|
});
|
|
379
396
|
const memoryDensity = classifyMemoryDensity(selected.length);
|
|
380
397
|
const bootstrapEnabled = options.bootstrap !== false;
|
|
381
|
-
|
|
398
|
+
const testMode = process.env.BRAINCLAW_TEST_MODE === '1';
|
|
399
|
+
let bootstrapAvailable = false;
|
|
382
400
|
let derivedSignals;
|
|
383
|
-
if (
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
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') {
|
|
391
418
|
const signals = selectDerivedSignals(target, 5, contextCwd);
|
|
392
419
|
if (signals.length > 0) {
|
|
393
420
|
derivedSignals = signals;
|
|
394
421
|
}
|
|
395
422
|
}
|
|
396
423
|
}
|
|
397
|
-
else if (bootstrapEnabled && bootstrapAvailable && memoryDensity === 'low') {
|
|
398
|
-
const signals = selectDerivedSignals(target, 5, contextCwd);
|
|
399
|
-
if (signals.length > 0) {
|
|
400
|
-
derivedSignals = signals;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
424
|
const executionSensitive = isExecutionSensitiveTarget(target);
|
|
404
425
|
const derivedUsesExecution = derivedSignals?.some((signal) => signal.source_kind === 'machine') ?? false;
|
|
405
426
|
const derivedUsesTooling = derivedSignals?.some((signal) => signal.source_kind === 'skill' || signal.source_kind === 'mcp') ?? false;
|
|
@@ -421,17 +442,44 @@ export function buildContext(options = {}) {
|
|
|
421
442
|
// Build open_work: active claims and in_progress plans owned by the current agent
|
|
422
443
|
// Reuses myClaims computed in agent-layer scoring above
|
|
423
444
|
let openWork;
|
|
445
|
+
const currentSession = loadCurrentSession(contextCwd);
|
|
424
446
|
if (currentAgentIdentity || agent) {
|
|
425
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));
|
|
426
449
|
const inProgressPlans = state.plan_items.filter((p) => p.status === 'in_progress' &&
|
|
427
450
|
(p.assignee === agentName || claimPlanIds.has(p.id)));
|
|
428
|
-
if (myClaims.length > 0 || inProgressPlans.length > 0) {
|
|
451
|
+
if (myClaims.length > 0 || activeAssignments.length > 0 || inProgressPlans.length > 0) {
|
|
429
452
|
openWork = {
|
|
430
|
-
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
|
+
})),
|
|
431
470
|
in_progress_plans: inProgressPlans.map(({ id, text, assignee }) => ({ id, text, assignee })),
|
|
432
471
|
};
|
|
433
472
|
}
|
|
434
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
|
+
});
|
|
435
483
|
// Cross-project items (subscriber links — read-only, always injected, bypass scoring)
|
|
436
484
|
const crossProjectItems = [];
|
|
437
485
|
for (const link of resolveCrossProjectLinks(contextCwd)) {
|
|
@@ -439,27 +487,50 @@ export function buildContext(options = {}) {
|
|
|
439
487
|
continue;
|
|
440
488
|
try {
|
|
441
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
|
+
}
|
|
442
497
|
for (const d of linkedState.recent_decisions) {
|
|
443
498
|
crossProjectItems.push({
|
|
444
499
|
id: d.id, section: 'cross_project', text: d.text,
|
|
445
500
|
tags: d.tags, score: 0, reasons: [], from_project: link.projectName,
|
|
501
|
+
plan_id: d.plan_id,
|
|
446
502
|
});
|
|
447
503
|
}
|
|
448
504
|
for (const c of linkedState.active_constraints) {
|
|
449
505
|
crossProjectItems.push({
|
|
450
506
|
id: c.id, section: 'cross_project', text: c.text,
|
|
451
507
|
tags: c.tags, score: 0, reasons: [], from_project: link.projectName,
|
|
508
|
+
plan_id: c.plan_id,
|
|
452
509
|
});
|
|
453
510
|
}
|
|
454
511
|
for (const t of linkedState.known_traps.filter((trap) => isTrapActive(trap))) {
|
|
455
512
|
crossProjectItems.push({
|
|
456
513
|
id: t.id, section: 'cross_project', text: t.text,
|
|
457
514
|
tags: t.tags, score: 0, reasons: [], from_project: link.projectName,
|
|
515
|
+
plan_id: t.plan_id,
|
|
458
516
|
});
|
|
459
517
|
}
|
|
460
518
|
}
|
|
461
519
|
catch { /* skip unavailable linked project */ }
|
|
462
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 */ }
|
|
463
534
|
const result = {
|
|
464
535
|
context_schema: CONTEXT_SCHEMA_VERSION,
|
|
465
536
|
profile,
|
|
@@ -468,6 +539,7 @@ export function buildContext(options = {}) {
|
|
|
468
539
|
project_mode: projectMode,
|
|
469
540
|
project_strategy: projectStrategy,
|
|
470
541
|
current_host: currentHost,
|
|
542
|
+
project_vision: readProjectVision(contextCwd),
|
|
471
543
|
host_filter: options.host,
|
|
472
544
|
all_hosts: options.allHosts ?? false,
|
|
473
545
|
memory_version: memoryVersion,
|
|
@@ -490,6 +562,7 @@ export function buildContext(options = {}) {
|
|
|
490
562
|
resolved_instructions: resolvedInstructions,
|
|
491
563
|
resume_summary: resumeSummary,
|
|
492
564
|
open_work: openWork,
|
|
565
|
+
session_metrics: sessionMetrics,
|
|
493
566
|
stores: storeChain.length > 1
|
|
494
567
|
? storeChain.map(({ cwd, depth, role }) => ({ cwd, depth, role }))
|
|
495
568
|
: undefined,
|
|
@@ -502,9 +575,11 @@ export function buildContext(options = {}) {
|
|
|
502
575
|
return undefined;
|
|
503
576
|
}
|
|
504
577
|
})(),
|
|
578
|
+
active_project: findActiveProjectInChain(contextCwd, storeChain),
|
|
505
579
|
cross_project_items: crossProjectItems.length > 0 ? crossProjectItems : undefined,
|
|
506
580
|
claim_conflicts: detectClaimConflicts(myClaims, otherActiveClaims),
|
|
507
581
|
workflow_hints: buildWorkflowHints(myClaims, openWork, state.plan_items),
|
|
582
|
+
stale_warnings: staleWarnings,
|
|
508
583
|
selected,
|
|
509
584
|
};
|
|
510
585
|
if (options.digest) {
|
|
@@ -516,7 +591,7 @@ export function renderContextMarkdown(result, explain = false) {
|
|
|
516
591
|
const lines = [];
|
|
517
592
|
lines.push(`# Agent Context (${result.profile})`);
|
|
518
593
|
lines.push('');
|
|
519
|
-
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)) {
|
|
520
595
|
lines.push('## ⚠ Your open work');
|
|
521
596
|
lines.push('');
|
|
522
597
|
if (result.open_work.active_claims.length > 0) {
|
|
@@ -526,10 +601,22 @@ export function renderContextMarkdown(result, explain = false) {
|
|
|
526
601
|
const planRef = claim.plan_id ? ` [plan: ${claim.plan_id}]` : '';
|
|
527
602
|
const expired = claim.expires_at && claim.expires_at < now ? ' ⚠ EXPIRED — run brainclaw prune' : '';
|
|
528
603
|
const ttlInfo = claim.expires_at && !expired ? ` (expires ${claim.expires_at.slice(0, 16).replace('T', ' ')})` : '';
|
|
529
|
-
|
|
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}`);
|
|
530
608
|
lines.push(` scope: ${claim.scope}`);
|
|
531
609
|
}
|
|
532
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
|
+
}
|
|
533
620
|
if (result.open_work.in_progress_plans.length > 0) {
|
|
534
621
|
lines.push('In-progress plan items (update status when done):');
|
|
535
622
|
for (const plan of result.open_work.in_progress_plans) {
|
|
@@ -538,6 +625,17 @@ export function renderContextMarkdown(result, explain = false) {
|
|
|
538
625
|
}
|
|
539
626
|
lines.push('');
|
|
540
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
|
+
}
|
|
541
639
|
if (result.estimation_calibration) {
|
|
542
640
|
lines.push(`Estimation calibration: ${result.estimation_calibration}`);
|
|
543
641
|
lines.push('');
|
|
@@ -549,8 +647,47 @@ export function renderContextMarkdown(result, explain = false) {
|
|
|
549
647
|
if (result.agent_id && result.agent) {
|
|
550
648
|
lines.push(`Agent ID: ${result.agent_id}`);
|
|
551
649
|
}
|
|
650
|
+
if (result.project_vision) {
|
|
651
|
+
lines.push(`Project vision: ${result.project_vision.split('\n')[0]}`);
|
|
652
|
+
}
|
|
552
653
|
lines.push(`Project mode: ${result.project_mode} (${result.project_strategy})`);
|
|
654
|
+
if (result.active_project) {
|
|
655
|
+
const ap = result.active_project;
|
|
656
|
+
const age = Math.floor((Date.now() - Date.parse(ap.switched_at)) / 3_600_000);
|
|
657
|
+
const sourceHint = ap.source === 'session' ? ', session-scoped' : ', global';
|
|
658
|
+
lines.push(`Active project: ${ap.name ?? ap.path} (switched ${age}h ago by ${ap.switched_by ?? 'unknown'}${sourceHint})`);
|
|
659
|
+
if (ap.source === 'global') {
|
|
660
|
+
lines.push(` ⚠ This is a global switch — all agents on this host see the same project. Use \`brainclaw switch <project>\` during a session for agent-scoped switching.`);
|
|
661
|
+
}
|
|
662
|
+
lines.push(` All commands target this project. Use \`brainclaw switch --clear\` to return to workspace root or \`brainclaw switch <project>\` to change.`);
|
|
663
|
+
}
|
|
553
664
|
lines.push(`Current host: ${result.current_host}`);
|
|
665
|
+
// Show other active sessions
|
|
666
|
+
try {
|
|
667
|
+
const allSessions = loadAllSessions();
|
|
668
|
+
const ttlMs = 4 * 60 * 60 * 1000;
|
|
669
|
+
const now = Date.now();
|
|
670
|
+
const otherSessions = allSessions.filter(s => s.agent_id !== result.agent_id
|
|
671
|
+
&& (now - Date.parse(s.last_seen_at)) <= ttlMs);
|
|
672
|
+
if (otherSessions.length > 0) {
|
|
673
|
+
const summaries = otherSessions.map(s => {
|
|
674
|
+
const proj = s.active_project?.name ?? s.active_project?.path;
|
|
675
|
+
return `${s.user ?? 'unknown'}/${s.agent}${proj ? ` on ${proj}` : ''}`;
|
|
676
|
+
});
|
|
677
|
+
lines.push(`Other active agents: ${summaries.join(', ')}`);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
catch { /* ignore — sessions dir may not exist yet */ }
|
|
681
|
+
// Check for brainclaw update (lightweight local manifest read only)
|
|
682
|
+
try {
|
|
683
|
+
const config = loadConfig();
|
|
684
|
+
const updateCheck = checkBrainclawInstallableUpdate(config, process.cwd());
|
|
685
|
+
const notice = renderBrainclawInstallableUpdateNotice(updateCheck);
|
|
686
|
+
if (notice) {
|
|
687
|
+
lines.push(`⚠ ${notice}`);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
catch { /* ignore — update check is best-effort */ }
|
|
554
691
|
lines.push(`Memory version: ${result.memory_version}`);
|
|
555
692
|
lines.push(`Memory density: ${result.memory_density}`);
|
|
556
693
|
lines.push(`Bootstrap available: ${result.bootstrap_available ? 'yes' : 'no'}`);
|
|
@@ -661,8 +798,9 @@ export function renderContextMarkdown(result, explain = false) {
|
|
|
661
798
|
for (const item of result.selected) {
|
|
662
799
|
const tags = item.tags.length ? ` [${item.tags.join(', ')}]` : '';
|
|
663
800
|
const extra = item.extra ? ` (${item.extra})` : '';
|
|
801
|
+
const planRef = item.plan_id ? ` [plan: ${item.plan_id}]` : '';
|
|
664
802
|
const why = explain && item.reasons.length ? ` {why: ${item.reasons.join(', ')}}` : '';
|
|
665
|
-
lines.push(`- [${item.id}] <${item.section}> ${item.text}${extra}${tags}${why}`);
|
|
803
|
+
lines.push(`- [${item.id}] <${item.section}> ${item.text}${extra}${planRef}${tags}${why}`);
|
|
666
804
|
}
|
|
667
805
|
if (result.derived_signals && result.derived_signals.length > 0) {
|
|
668
806
|
lines.push('');
|
|
@@ -730,6 +868,12 @@ export function renderContextPromptTemplate(result, compact = false) {
|
|
|
730
868
|
lines.push(`sd=${result.context_diff.since_session ?? ''}`);
|
|
731
869
|
lines.push(`dc=${result.context_diff.counts.total}`);
|
|
732
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
|
+
}
|
|
733
877
|
if (result.resume_summary) {
|
|
734
878
|
lines.push(`rt=${result.resume_summary.internal_trust}`);
|
|
735
879
|
lines.push('rs:');
|
|
@@ -758,6 +902,14 @@ export function renderContextPromptTemplate(result, compact = false) {
|
|
|
758
902
|
}
|
|
759
903
|
lines.push(`project_mode: ${result.project_mode}`);
|
|
760
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
|
+
}
|
|
908
|
+
if (result.active_project) {
|
|
909
|
+
lines.push(`active_project: ${result.active_project.name ?? result.active_project.path}`);
|
|
910
|
+
lines.push(`active_project_switched: ${result.active_project.switched_at}`);
|
|
911
|
+
lines.push(`active_project_source: ${result.active_project.source ?? 'global'}`);
|
|
912
|
+
}
|
|
761
913
|
lines.push(`current_host: ${result.current_host}`);
|
|
762
914
|
lines.push(`memory_version: ${result.memory_version}`);
|
|
763
915
|
lines.push(`memory_density: ${result.memory_density}`);
|
|
@@ -838,22 +990,42 @@ export function renderContextPromptTemplate(result, compact = false) {
|
|
|
838
990
|
lines.push(` - ${item}`);
|
|
839
991
|
}
|
|
840
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
|
+
}
|
|
841
999
|
if (result.target) {
|
|
842
1000
|
lines.push(`target: ${result.target}`);
|
|
843
1001
|
}
|
|
844
1002
|
}
|
|
845
|
-
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)) {
|
|
846
1004
|
lines.push(compact ? 'ow:' : 'open_work:');
|
|
847
1005
|
if (result.open_work.active_claims.length > 0) {
|
|
848
1006
|
lines.push(compact ? ' claims:' : ' active_claims:');
|
|
849
1007
|
for (const claim of result.open_work.active_claims) {
|
|
1008
|
+
const lv = claim.liveness ? (compact ? ` lv=${claim.liveness}` : ` liveness=${claim.liveness}`) : '';
|
|
850
1009
|
if (compact) {
|
|
851
1010
|
const planRef = claim.plan_id ? ` pl=${claim.plan_id}` : '';
|
|
852
|
-
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}`);
|
|
853
1012
|
}
|
|
854
1013
|
else {
|
|
855
1014
|
const planRef = claim.plan_id ? ` plan_id=${claim.plan_id}` : '';
|
|
856
|
-
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}"`);
|
|
857
1029
|
}
|
|
858
1030
|
}
|
|
859
1031
|
}
|
|
@@ -869,6 +1041,25 @@ export function renderContextPromptTemplate(result, compact = false) {
|
|
|
869
1041
|
}
|
|
870
1042
|
}
|
|
871
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
|
+
}
|
|
872
1063
|
lines.push(compact ? 'ins:' : 'instructions:');
|
|
873
1064
|
if (result.resolved_instructions.length === 0) {
|
|
874
1065
|
lines.push(compact ? ' - n' : ' - none');
|
|
@@ -896,14 +1087,16 @@ export function renderContextPromptTemplate(result, compact = false) {
|
|
|
896
1087
|
if (compact) {
|
|
897
1088
|
const tags = item.tags.length ? ` tg=[${item.tags.join(',')}]` : '';
|
|
898
1089
|
const extra = item.extra ? ` ex="${item.extra}"` : '';
|
|
1090
|
+
const planRef = item.plan_id ? ` pl=${item.plan_id}` : '';
|
|
899
1091
|
const why = item.reasons.length ? ` why=[${item.reasons.join('|')}]` : '';
|
|
900
|
-
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}"`);
|
|
901
1093
|
}
|
|
902
1094
|
else {
|
|
903
1095
|
const tags = item.tags.length ? ` tags=[${item.tags.join(',')}]` : '';
|
|
904
1096
|
const extra = item.extra ? ` extra="${item.extra}"` : '';
|
|
1097
|
+
const planRef = item.plan_id ? ` plan=${item.plan_id}` : '';
|
|
905
1098
|
const why = item.reasons.length ? ` why=[${item.reasons.join(', ')}]` : '';
|
|
906
|
-
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}"`);
|
|
907
1100
|
}
|
|
908
1101
|
}
|
|
909
1102
|
}
|
|
@@ -934,6 +1127,119 @@ export function renderContextPromptTemplate(result, compact = false) {
|
|
|
934
1127
|
lines.push('```');
|
|
935
1128
|
return lines.join('\n');
|
|
936
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
|
+
}
|
|
937
1243
|
export function buildScopedActivity(input) {
|
|
938
1244
|
const target = input.target?.trim();
|
|
939
1245
|
if (!target) {
|
|
@@ -1032,6 +1338,85 @@ function classifyMemoryDensity(selectedCount) {
|
|
|
1032
1338
|
return 'medium';
|
|
1033
1339
|
return 'high';
|
|
1034
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
|
+
}
|
|
1035
1420
|
function summariseAgentTooling(snapshot) {
|
|
1036
1421
|
return {
|
|
1037
1422
|
agents_md_present: snapshot.agents_md_present,
|
|
@@ -1181,6 +1566,13 @@ function computeRelevance(item, terms, profile, target) {
|
|
|
1181
1566
|
score += 1;
|
|
1182
1567
|
reasons.push('runtime execution signal');
|
|
1183
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
|
+
}
|
|
1184
1576
|
if (profile === 'dev' && (item.section === 'decision' || item.section === 'trap')) {
|
|
1185
1577
|
score += 2;
|
|
1186
1578
|
reasons.push('profile boost: dev');
|
|
@@ -1303,6 +1695,34 @@ function scopesOverlap(a, b) {
|
|
|
1303
1695
|
}
|
|
1304
1696
|
return null;
|
|
1305
1697
|
}
|
|
1698
|
+
// --- Active project resolution ---
|
|
1699
|
+
function findActiveProjectInChain(contextCwd, _storeChain) {
|
|
1700
|
+
// 1. Session-scoped active project (per-agent, highest priority)
|
|
1701
|
+
const session = loadCurrentSession(contextCwd);
|
|
1702
|
+
if (session?.active_project) {
|
|
1703
|
+
return {
|
|
1704
|
+
path: session.active_project.path,
|
|
1705
|
+
name: session.active_project.name,
|
|
1706
|
+
switched_at: session.active_project.switched_at,
|
|
1707
|
+
switched_by: session.agent,
|
|
1708
|
+
source: 'session',
|
|
1709
|
+
};
|
|
1710
|
+
}
|
|
1711
|
+
// 2. Global active-project.json (walk up from contextCwd)
|
|
1712
|
+
let dir = path.resolve(contextCwd);
|
|
1713
|
+
const root = path.parse(dir).root;
|
|
1714
|
+
const home = process.env.HOME || process.env.USERPROFILE || root;
|
|
1715
|
+
while (dir !== root && dir !== home) {
|
|
1716
|
+
const ap = loadActiveProject(dir);
|
|
1717
|
+
if (ap)
|
|
1718
|
+
return { ...ap, source: 'global' };
|
|
1719
|
+
const parent = path.dirname(dir);
|
|
1720
|
+
if (parent === dir)
|
|
1721
|
+
break;
|
|
1722
|
+
dir = parent;
|
|
1723
|
+
}
|
|
1724
|
+
return undefined;
|
|
1725
|
+
}
|
|
1306
1726
|
// --- Workflow hints ---
|
|
1307
1727
|
function buildWorkflowHints(myClaims, openWork, plans) {
|
|
1308
1728
|
const hints = [];
|