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
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure business logic for plan operations.
|
|
3
|
+
*
|
|
4
|
+
* No console.log, no process.exit, no MCP formatting.
|
|
5
|
+
* Both CLI commands and MCP handlers call these functions.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { mutateState } from '../state.js';
|
|
10
|
+
import { generateIdWithLabel, generateId, nowISO } from '../ids.js';
|
|
11
|
+
export function createPlan(input, cwd) {
|
|
12
|
+
if (input.estimatedEffort !== undefined) {
|
|
13
|
+
if (!Number.isInteger(input.estimatedEffort) || input.estimatedEffort <= 0) {
|
|
14
|
+
throw new Error('estimate must be a positive integer (minutes)');
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
const result = mutateState((state) => {
|
|
18
|
+
const { id, short_label } = generateIdWithLabel('plan_items', cwd);
|
|
19
|
+
const timestamp = nowISO();
|
|
20
|
+
const entry = {
|
|
21
|
+
id,
|
|
22
|
+
short_label,
|
|
23
|
+
text: input.text,
|
|
24
|
+
type: input.type,
|
|
25
|
+
created_at: timestamp,
|
|
26
|
+
updated_at: timestamp,
|
|
27
|
+
author: input.author,
|
|
28
|
+
status: 'todo',
|
|
29
|
+
priority: input.priority ?? 'medium',
|
|
30
|
+
assignee: input.assignee,
|
|
31
|
+
project: input.project,
|
|
32
|
+
tags: input.tags ?? [],
|
|
33
|
+
related_paths: input.relatedPaths,
|
|
34
|
+
depends_on: input.dependsOn ?? [],
|
|
35
|
+
estimated_effort: input.estimatedEffort,
|
|
36
|
+
};
|
|
37
|
+
state.plan_items.push(entry);
|
|
38
|
+
return { id, shortLabel: short_label, text: input.text };
|
|
39
|
+
}, cwd);
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
export function addStep(input, cwd) {
|
|
43
|
+
const result = mutateState((state) => {
|
|
44
|
+
const plan = state.plan_items.find((p) => p.id === input.planId || p.short_label === input.planId);
|
|
45
|
+
if (!plan) {
|
|
46
|
+
throw new Error(`Plan '${input.planId}' not found.`);
|
|
47
|
+
}
|
|
48
|
+
const step = {
|
|
49
|
+
id: generateId('plan_steps'),
|
|
50
|
+
text: input.text,
|
|
51
|
+
status: 'todo',
|
|
52
|
+
assignee: input.assignee,
|
|
53
|
+
created_at: nowISO(),
|
|
54
|
+
updated_at: nowISO(),
|
|
55
|
+
};
|
|
56
|
+
plan.steps = [...(plan.steps ?? []), step];
|
|
57
|
+
plan.updated_at = nowISO();
|
|
58
|
+
return {
|
|
59
|
+
stepId: step.id,
|
|
60
|
+
planId: plan.id,
|
|
61
|
+
totalSteps: plan.steps.length,
|
|
62
|
+
doneSteps: plan.steps.filter((s) => s.status === 'done').length,
|
|
63
|
+
};
|
|
64
|
+
}, cwd);
|
|
65
|
+
return result;
|
|
66
|
+
}
|
|
67
|
+
export function updatePlan(input, cwd) {
|
|
68
|
+
const result = mutateState((state) => {
|
|
69
|
+
const plan = state.plan_items.find((p) => p.id === input.id || p.short_label === input.id);
|
|
70
|
+
if (!plan) {
|
|
71
|
+
throw new Error(`Plan item '${input.id}' not found.`);
|
|
72
|
+
}
|
|
73
|
+
const timestamp = nowISO();
|
|
74
|
+
if (input.status) {
|
|
75
|
+
plan.status = input.status;
|
|
76
|
+
if (input.status === 'in_progress' && !plan.started_at)
|
|
77
|
+
plan.started_at = timestamp;
|
|
78
|
+
if (input.status === 'done' && !plan.completed_at)
|
|
79
|
+
plan.completed_at = timestamp;
|
|
80
|
+
}
|
|
81
|
+
if (input.assignee !== undefined)
|
|
82
|
+
plan.assignee = input.assignee;
|
|
83
|
+
if (input.priority)
|
|
84
|
+
plan.priority = input.priority;
|
|
85
|
+
if (input.actualEffort)
|
|
86
|
+
plan.actual_effort = input.actualEffort;
|
|
87
|
+
if (input.patch) {
|
|
88
|
+
Object.assign(plan, input.patch);
|
|
89
|
+
}
|
|
90
|
+
plan.updated_at = timestamp;
|
|
91
|
+
return { id: plan.id, text: plan.text, status: plan.status };
|
|
92
|
+
}, cwd);
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
export function completeStep(input, cwd) {
|
|
96
|
+
const result = mutateState((state) => {
|
|
97
|
+
const plan = state.plan_items.find((p) => p.id === input.planId || p.short_label === input.planId);
|
|
98
|
+
if (!plan) {
|
|
99
|
+
throw new Error(`Plan '${input.planId}' not found.`);
|
|
100
|
+
}
|
|
101
|
+
if (!plan.steps || plan.steps.length === 0) {
|
|
102
|
+
throw new Error(`Plan '${input.planId}' has no steps.`);
|
|
103
|
+
}
|
|
104
|
+
const step = plan.steps.find((s) => s.id === input.stepId || s.text.toLowerCase().includes(input.stepId.toLowerCase()));
|
|
105
|
+
if (!step) {
|
|
106
|
+
throw new Error(`Step '${input.stepId}' not found in plan '${input.planId}'.`);
|
|
107
|
+
}
|
|
108
|
+
const timestamp = nowISO();
|
|
109
|
+
step.status = 'done';
|
|
110
|
+
step.updated_at = timestamp;
|
|
111
|
+
plan.updated_at = timestamp;
|
|
112
|
+
const totalSteps = plan.steps.length;
|
|
113
|
+
const doneSteps = plan.steps.filter((s) => s.status === 'done').length;
|
|
114
|
+
let planAutoCompleted = false;
|
|
115
|
+
if (doneSteps === totalSteps && plan.status !== 'done') {
|
|
116
|
+
plan.status = 'done';
|
|
117
|
+
plan.completed_at = timestamp;
|
|
118
|
+
planAutoCompleted = true;
|
|
119
|
+
}
|
|
120
|
+
return { stepId: step.id, planId: plan.id, totalSteps, doneSteps, planAutoCompleted };
|
|
121
|
+
}, cwd);
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
export function updateStep(input, cwd) {
|
|
125
|
+
const result = mutateState((state) => {
|
|
126
|
+
const plan = state.plan_items.find((p) => p.id === input.planId || p.short_label === input.planId);
|
|
127
|
+
if (!plan) {
|
|
128
|
+
throw new Error(`Plan '${input.planId}' not found.`);
|
|
129
|
+
}
|
|
130
|
+
if (!plan.steps || plan.steps.length === 0) {
|
|
131
|
+
throw new Error(`Plan '${input.planId}' has no steps.`);
|
|
132
|
+
}
|
|
133
|
+
const step = plan.steps.find((s) => s.id === input.stepId);
|
|
134
|
+
if (!step) {
|
|
135
|
+
throw new Error(`Step '${input.stepId}' not found in plan '${input.planId}'.`);
|
|
136
|
+
}
|
|
137
|
+
const timestamp = nowISO();
|
|
138
|
+
if (input.status)
|
|
139
|
+
step.status = input.status;
|
|
140
|
+
if (input.text !== undefined)
|
|
141
|
+
step.text = input.text;
|
|
142
|
+
if (input.assignee !== undefined)
|
|
143
|
+
step.assignee = input.assignee || undefined;
|
|
144
|
+
step.updated_at = timestamp;
|
|
145
|
+
plan.updated_at = timestamp;
|
|
146
|
+
const totalSteps = plan.steps.length;
|
|
147
|
+
const doneSteps = plan.steps.filter((s) => s.status === 'done').length;
|
|
148
|
+
let planAutoCompleted = false;
|
|
149
|
+
if (doneSteps === totalSteps && plan.status !== 'done') {
|
|
150
|
+
plan.status = 'done';
|
|
151
|
+
plan.completed_at = timestamp;
|
|
152
|
+
planAutoCompleted = true;
|
|
153
|
+
}
|
|
154
|
+
return { stepId: step.id, planId: plan.id, totalSteps, doneSteps, planAutoCompleted };
|
|
155
|
+
}, cwd);
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
export function deleteStep(input, cwd) {
|
|
159
|
+
const result = mutateState((state) => {
|
|
160
|
+
const plan = state.plan_items.find((p) => p.id === input.planId || p.short_label === input.planId);
|
|
161
|
+
if (!plan) {
|
|
162
|
+
throw new Error(`Plan '${input.planId}' not found.`);
|
|
163
|
+
}
|
|
164
|
+
if (!plan.steps || plan.steps.length === 0) {
|
|
165
|
+
throw new Error(`Plan '${input.planId}' has no steps.`);
|
|
166
|
+
}
|
|
167
|
+
const idx = plan.steps.findIndex((s) => s.id === input.stepId);
|
|
168
|
+
if (idx < 0) {
|
|
169
|
+
throw new Error(`Step '${input.stepId}' not found in plan '${input.planId}'.`);
|
|
170
|
+
}
|
|
171
|
+
plan.steps.splice(idx, 1);
|
|
172
|
+
plan.updated_at = nowISO();
|
|
173
|
+
const totalSteps = plan.steps.length;
|
|
174
|
+
const doneSteps = plan.steps.filter((s) => s.status === 'done').length;
|
|
175
|
+
return { stepId: input.stepId, planId: plan.id, totalSteps, doneSteps };
|
|
176
|
+
}, cwd);
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
export function deletePlan(planId, cwd) {
|
|
180
|
+
const result = mutateState((state) => {
|
|
181
|
+
const idx = state.plan_items.findIndex((p) => p.id === planId || p.short_label === planId);
|
|
182
|
+
if (idx < 0) {
|
|
183
|
+
throw new Error(`Plan item '${planId}' not found.`);
|
|
184
|
+
}
|
|
185
|
+
const removed = state.plan_items.splice(idx, 1)[0];
|
|
186
|
+
return { id: removed.id, text: removed.text };
|
|
187
|
+
}, cwd);
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
//# sourceMappingURL=plan.js.map
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-execution policy checks.
|
|
3
|
+
*
|
|
4
|
+
* Deterministic guardrails that verify whether an agent is allowed
|
|
5
|
+
* to act on a given scope. No AI, no external dependencies —
|
|
6
|
+
* just claims, constraints, traps and instructions.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
import { listClaims, isClaimExpired } from './claims.js';
|
|
11
|
+
import { loadState } from './state.js';
|
|
12
|
+
import { loadInstructions, resolveInstructions } from './instructions.js';
|
|
13
|
+
const CROSS_PROJECT_SIGNALING_ENTITIES = new Set(['candidate', 'handoff', 'runtime_note']);
|
|
14
|
+
export function checkCrossProjectBoundary(entity, targetProject) {
|
|
15
|
+
if (!targetProject) {
|
|
16
|
+
return { allowed: true };
|
|
17
|
+
}
|
|
18
|
+
if (CROSS_PROJECT_SIGNALING_ENTITIES.has(entity)) {
|
|
19
|
+
return { allowed: true };
|
|
20
|
+
}
|
|
21
|
+
return {
|
|
22
|
+
allowed: false,
|
|
23
|
+
issue: {
|
|
24
|
+
kind: 'cross_project_boundary',
|
|
25
|
+
severity: 'block',
|
|
26
|
+
message: `Cross-project writes are limited to signaling entities (candidate, handoff, runtime_note). ${entity} stays local to the current project.`,
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export function assertCrossProjectBoundary(entity, targetProject) {
|
|
31
|
+
const result = checkCrossProjectBoundary(entity, targetProject);
|
|
32
|
+
if (!result.allowed) {
|
|
33
|
+
throw new Error(result.issue?.message ?? 'Cross-project boundary violation.');
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Path matching (reuses check-constraints pattern)
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
function normPath(p) {
|
|
40
|
+
return p.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Returns true if `targetScope` overlaps with `relatedPath`.
|
|
44
|
+
* Matches exactly or via prefix (directory containment) in both directions.
|
|
45
|
+
*/
|
|
46
|
+
function scopeOverlaps(targetScope, relatedPath) {
|
|
47
|
+
const s = normPath(targetScope).replace(/\/$/, '');
|
|
48
|
+
const r = normPath(relatedPath).replace(/\/$/, '');
|
|
49
|
+
// Exact match
|
|
50
|
+
if (s === r)
|
|
51
|
+
return true;
|
|
52
|
+
// Target is inside related path (e.g. scope=src/core/foo.ts, related=src/core)
|
|
53
|
+
if (s.startsWith(r + '/'))
|
|
54
|
+
return true;
|
|
55
|
+
// Related path is inside target scope (e.g. scope=src/core, related=src/core/foo.ts)
|
|
56
|
+
if (r.startsWith(s + '/'))
|
|
57
|
+
return true;
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Check if a claim's scope overlaps with the target scope.
|
|
62
|
+
* Claims use free-form scope strings — may be a single path, a directory,
|
|
63
|
+
* or multiple space-separated paths (e.g. "src/views/A.astro src/components/B.astro").
|
|
64
|
+
*/
|
|
65
|
+
function claimScopeOverlaps(targetScope, claimScope) {
|
|
66
|
+
// Split space-separated scopes and check each segment
|
|
67
|
+
const segments = claimScope.split(/\s+/).filter(Boolean);
|
|
68
|
+
return segments.some(segment => scopeOverlaps(targetScope, segment));
|
|
69
|
+
}
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
// Core check
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
export function checkPolicy(options) {
|
|
74
|
+
const cwd = options.cwd;
|
|
75
|
+
const scope = options.scope;
|
|
76
|
+
const agent = options.agent;
|
|
77
|
+
const blocks = [];
|
|
78
|
+
const warnings = [];
|
|
79
|
+
// --- 1. Claim check ---
|
|
80
|
+
const allClaims = listClaims(cwd);
|
|
81
|
+
const activeClaims = allClaims.filter(c => c.status === 'active' && !isClaimExpired(c));
|
|
82
|
+
const claimsOnScope = activeClaims.filter(c => claimScopeOverlaps(scope, c.scope));
|
|
83
|
+
if (agent) {
|
|
84
|
+
const agentLower = agent.toLowerCase();
|
|
85
|
+
const myClaimsOnScope = claimsOnScope.filter(c => c.agent.toLowerCase() === agentLower ||
|
|
86
|
+
c.agent_id?.toLowerCase() === agentLower);
|
|
87
|
+
const otherClaimsOnScope = claimsOnScope.filter(c => c.agent.toLowerCase() !== agentLower &&
|
|
88
|
+
c.agent_id?.toLowerCase() !== agentLower);
|
|
89
|
+
if (myClaimsOnScope.length === 0 && claimsOnScope.length === 0) {
|
|
90
|
+
blocks.push({
|
|
91
|
+
kind: 'no_claim',
|
|
92
|
+
severity: 'block',
|
|
93
|
+
message: `No active claim on scope "${scope}". Run bclaw_claim first.`,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
else if (myClaimsOnScope.length === 0 && otherClaimsOnScope.length > 0) {
|
|
97
|
+
for (const c of otherClaimsOnScope) {
|
|
98
|
+
blocks.push({
|
|
99
|
+
kind: 'claim_conflict',
|
|
100
|
+
severity: 'block',
|
|
101
|
+
id: c.id,
|
|
102
|
+
message: `Scope "${scope}" is claimed by ${c.agent} [${c.id}]: ${c.description}`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
// Check for expired claims by this agent (informational)
|
|
107
|
+
const expiredMyClaims = allClaims.filter(c => c.status === 'active' && isClaimExpired(c) &&
|
|
108
|
+
(c.agent.toLowerCase() === agentLower || c.agent_id?.toLowerCase() === agentLower) &&
|
|
109
|
+
claimScopeOverlaps(scope, c.scope));
|
|
110
|
+
for (const c of expiredMyClaims) {
|
|
111
|
+
warnings.push({
|
|
112
|
+
kind: 'claim_expired',
|
|
113
|
+
severity: 'warn',
|
|
114
|
+
id: c.id,
|
|
115
|
+
message: `Your claim [${c.id}] on "${c.scope}" has expired. Re-claim before editing.`,
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
// No agent specified — just report claim state
|
|
121
|
+
if (claimsOnScope.length === 0) {
|
|
122
|
+
warnings.push({
|
|
123
|
+
kind: 'no_claim',
|
|
124
|
+
severity: 'warn',
|
|
125
|
+
message: `No active claim on scope "${scope}".`,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
// --- 2. Constraint check ---
|
|
130
|
+
const state = loadState(cwd);
|
|
131
|
+
const matchingConstraints = state.active_constraints.filter(c => c.related_paths?.some(rp => scopeOverlaps(scope, rp)));
|
|
132
|
+
for (const c of matchingConstraints) {
|
|
133
|
+
const catLabel = c.category ? ` [${c.category}]` : '';
|
|
134
|
+
warnings.push({
|
|
135
|
+
kind: 'constraint',
|
|
136
|
+
severity: 'warn',
|
|
137
|
+
id: c.id,
|
|
138
|
+
message: `Constraint${catLabel}: ${c.text}`,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// --- 3. Trap check ---
|
|
142
|
+
const matchingTraps = state.known_traps.filter(t => t.status === 'active' &&
|
|
143
|
+
t.related_paths?.some(rp => scopeOverlaps(scope, rp)));
|
|
144
|
+
for (const t of matchingTraps) {
|
|
145
|
+
const sevLabel = t.severity ? ` [${t.severity}]` : '';
|
|
146
|
+
warnings.push({
|
|
147
|
+
kind: 'trap',
|
|
148
|
+
severity: 'warn',
|
|
149
|
+
id: t.id,
|
|
150
|
+
message: `Trap${sevLabel}: ${t.text}`,
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
// --- 4. Instructions (governance context — no matching in v1) ---
|
|
154
|
+
const allInstructions = loadInstructions(cwd);
|
|
155
|
+
const activeInstructions = resolveInstructions(allInstructions, {});
|
|
156
|
+
const allowed = blocks.length === 0;
|
|
157
|
+
return {
|
|
158
|
+
allowed,
|
|
159
|
+
blocks,
|
|
160
|
+
warnings,
|
|
161
|
+
governance_context: {
|
|
162
|
+
active_instructions: activeInstructions,
|
|
163
|
+
matching_constraints: matchingConstraints,
|
|
164
|
+
matching_traps: matchingTraps,
|
|
165
|
+
active_claims_on_scope: claimsOnScope,
|
|
166
|
+
},
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=policy.js.map
|
package/dist/core/reputation.js
CHANGED
|
@@ -110,7 +110,13 @@ function trackCandidateSignals(candidate, bucket, store, sinceMs, config, resolv
|
|
|
110
110
|
if (bucket === 'rejected') {
|
|
111
111
|
stats.signals.rejected_candidates_authored += 1;
|
|
112
112
|
}
|
|
113
|
-
|
|
113
|
+
// Count runtime-origin candidates: either explicit `source: 'auto'` (session-end
|
|
114
|
+
// auto handoffs) or any runtime-note / session-end origin string (back-compat
|
|
115
|
+
// with writers that still set only `origin`).
|
|
116
|
+
const isRuntimeOrigin = candidate.source === 'auto'
|
|
117
|
+
|| candidate.origin?.startsWith('runtime-note:') === true
|
|
118
|
+
|| candidate.origin?.startsWith('session-end:') === true;
|
|
119
|
+
if (isRuntimeOrigin) {
|
|
114
120
|
stats.signals.promoted_runtime_candidates += 1;
|
|
115
121
|
if (bucket === 'accepted') {
|
|
116
122
|
stats.signals.promoted_runtime_accepted += 1;
|
|
@@ -354,8 +360,8 @@ export function buildReputationRankingLookup(cwd) {
|
|
|
354
360
|
return 0;
|
|
355
361
|
}
|
|
356
362
|
const trust = getInternalTrust(actorId, actorName);
|
|
357
|
-
const bonus = (trust / 100) * (rankingWeight *
|
|
358
|
-
return Math.max(0, Math.min(
|
|
363
|
+
const bonus = (trust / 100) * (rankingWeight * 20);
|
|
364
|
+
return Math.max(0, Math.min(3, Number(bonus.toFixed(2))));
|
|
359
365
|
};
|
|
360
366
|
return {
|
|
361
367
|
enabled: snapshot.enabled,
|