@codapult/guard 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -1
- package/dist/adapters/command.d.ts +11 -8
- package/dist/adapters/command.js +4 -0
- package/dist/adapters/project-checks.d.ts +23 -20
- package/dist/adapters/project-checks.js +12 -3
- package/dist/cli/commands/guard.d.ts +54 -60
- package/dist/cli/commands/guard.js +46 -19
- package/dist/cli/index.js +24 -10
- package/dist/core/analysis/doctor.d.ts +1 -1
- package/dist/core/analysis/doctor.js +2 -3
- package/dist/core/discovery/discovery.d.ts +18 -14
- package/dist/core/discovery/discovery.js +35 -15
- package/dist/core/errors.d.ts +2 -2
- package/dist/core/guard.d.ts +124 -72
- package/dist/core/guard.js +542 -89
- package/dist/core/history/runs.d.ts +43 -0
- package/dist/core/history/runs.js +154 -0
- package/dist/core/model/types.d.ts +10 -0
- package/dist/core/model/types.js +1 -0
- package/dist/core/policy/schemas.d.ts +69 -0
- package/dist/core/policy/schemas.js +25 -0
- package/dist/core/verification/verify.d.ts +20 -16
- package/dist/core/verification/verify.js +55 -7
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mcp/resources.js +78 -29
- package/dist/mcp/tools/guard.js +147 -28
- package/package.json +7 -7
package/dist/mcp/resources.js
CHANGED
|
@@ -1,23 +1,57 @@
|
|
|
1
|
-
import { buildArchitectureMemory, buildConventionsMemory, discoverProject, findGuardRoot, GUARD_ARCHITECTURE_FILE, GUARD_CONVENTIONS_FILE, loadGuardAgentConfig, loadGuardArtifact, loadGuardConfig, loadGuardProposals, } from '../core/guard.js';
|
|
1
|
+
import { buildArchitectureMemory, buildConventionsMemory, discoverProject, findGuardRoot, GUARD_ARCHITECTURE_FILE, GUARD_CONVENTIONS_FILE, loadGuardAgentConfig, loadGuardArtifact, loadGuardConfig, loadGuardProposals, GuardStateBusyError, GuardStateStaleError, } from '../core/guard.js';
|
|
2
|
+
import { guardErrorPayload } from '../core/errors.js';
|
|
3
|
+
import { DiscoveryStaleError } from '../core/discovery/discovery.js';
|
|
4
|
+
import { listGuardRuns, summarizeGuardRuns } from '../core/history/runs.js';
|
|
2
5
|
function jsonResource(uri, value) {
|
|
3
6
|
return {
|
|
4
7
|
contents: [{ uri, text: JSON.stringify(value, null, 2), mimeType: 'application/json' }],
|
|
5
8
|
};
|
|
6
9
|
}
|
|
10
|
+
function resourceError(uri, error) {
|
|
11
|
+
const errorCode = error instanceof GuardStateBusyError
|
|
12
|
+
? 'GUARD_STATE_BUSY'
|
|
13
|
+
: error instanceof GuardStateStaleError || error instanceof DiscoveryStaleError
|
|
14
|
+
? 'GUARD_STATE_STALE'
|
|
15
|
+
: 'GUARD_CONFIG_INVALID';
|
|
16
|
+
return jsonResource(uri, guardErrorPayload(errorCode, error instanceof Error ? error.message : String(error), {
|
|
17
|
+
configured: errorCode !== 'GUARD_CONFIG_INVALID',
|
|
18
|
+
outcome: 'error',
|
|
19
|
+
recoverable: true,
|
|
20
|
+
hint: 'Repair the invalid Guard artifact, then run codapult-guard doctor.',
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
7
23
|
export function registerGuardResources(server) {
|
|
24
|
+
server.registerResource('codapult_guard_runs', 'codapult://guard/runs', {
|
|
25
|
+
title: 'Guard Run History',
|
|
26
|
+
description: 'Local verification outcomes, gates, and stage timings.',
|
|
27
|
+
mimeType: 'application/json',
|
|
28
|
+
}, () => {
|
|
29
|
+
const root = findGuardRoot();
|
|
30
|
+
return jsonResource('codapult://guard/runs', {
|
|
31
|
+
version: 1,
|
|
32
|
+
root,
|
|
33
|
+
summary: summarizeGuardRuns(root),
|
|
34
|
+
runs: listGuardRuns(root),
|
|
35
|
+
});
|
|
36
|
+
});
|
|
8
37
|
server.registerResource('codapult_guard_rules', 'codapult://guard/rules', {
|
|
9
38
|
title: 'Architecture Guard Rules',
|
|
10
39
|
description: 'Local architecture invariants available to CLI checks and AI agents',
|
|
11
40
|
mimeType: 'application/json',
|
|
12
41
|
}, () => {
|
|
13
42
|
const root = findGuardRoot();
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
43
|
+
try {
|
|
44
|
+
const config = loadGuardConfig(root);
|
|
45
|
+
return jsonResource('codapult://guard/rules', {
|
|
46
|
+
configured: config !== undefined,
|
|
47
|
+
rules: config?.rules ?? [],
|
|
48
|
+
contracts: config?.contracts ?? [],
|
|
49
|
+
project: discoverProject(root),
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
return resourceError('codapult://guard/rules', error);
|
|
54
|
+
}
|
|
21
55
|
});
|
|
22
56
|
server.registerResource('codapult_guard_context', 'codapult://guard/context', {
|
|
23
57
|
title: 'Guard Project Context',
|
|
@@ -25,19 +59,24 @@ export function registerGuardResources(server) {
|
|
|
25
59
|
mimeType: 'application/json',
|
|
26
60
|
}, () => {
|
|
27
61
|
const root = findGuardRoot();
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
62
|
+
try {
|
|
63
|
+
const project = discoverProject(root);
|
|
64
|
+
const config = loadGuardConfig(root);
|
|
65
|
+
return jsonResource('codapult://guard/context', {
|
|
66
|
+
version: 1,
|
|
67
|
+
root,
|
|
68
|
+
configured: config !== undefined,
|
|
69
|
+
project,
|
|
70
|
+
architecture: loadGuardArtifact(root, GUARD_ARCHITECTURE_FILE) ?? buildArchitectureMemory(project),
|
|
71
|
+
conventions: loadGuardArtifact(root, GUARD_CONVENTIONS_FILE) ?? buildConventionsMemory(project),
|
|
72
|
+
rules: config?.rules ?? [],
|
|
73
|
+
contracts: config?.contracts ?? [],
|
|
74
|
+
agent: loadGuardAgentConfig(root),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
return resourceError('codapult://guard/context', error);
|
|
79
|
+
}
|
|
41
80
|
});
|
|
42
81
|
server.registerResource('codapult_guard_architecture', 'codapult://guard/architecture', {
|
|
43
82
|
title: 'Guard Architecture Map',
|
|
@@ -45,13 +84,18 @@ export function registerGuardResources(server) {
|
|
|
45
84
|
mimeType: 'application/json',
|
|
46
85
|
}, () => {
|
|
47
86
|
const root = findGuardRoot();
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
87
|
+
try {
|
|
88
|
+
const project = discoverProject(root);
|
|
89
|
+
return jsonResource('codapult://guard/architecture', {
|
|
90
|
+
version: 1,
|
|
91
|
+
root,
|
|
92
|
+
architecture: loadGuardArtifact(root, GUARD_ARCHITECTURE_FILE) ?? buildArchitectureMemory(project),
|
|
93
|
+
conventions: loadGuardArtifact(root, GUARD_CONVENTIONS_FILE) ?? buildConventionsMemory(project),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
return resourceError('codapult://guard/architecture', error);
|
|
98
|
+
}
|
|
55
99
|
});
|
|
56
100
|
server.registerResource('codapult_guard_proposals', 'codapult://guard/proposals', {
|
|
57
101
|
title: 'Architecture Guard Proposals',
|
|
@@ -59,6 +103,11 @@ export function registerGuardResources(server) {
|
|
|
59
103
|
mimeType: 'application/json',
|
|
60
104
|
}, () => {
|
|
61
105
|
const root = findGuardRoot();
|
|
62
|
-
|
|
106
|
+
try {
|
|
107
|
+
return jsonResource('codapult://guard/proposals', loadGuardProposals(root) ?? { version: 1, rules: [], contracts: [], questions: [] });
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
return resourceError('codapult://guard/proposals', error);
|
|
111
|
+
}
|
|
63
112
|
});
|
|
64
113
|
}
|
package/dist/mcp/tools/guard.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { buildArchitectureMemory, buildConventionsMemory, buildGeneratedGuardConfig, buildGuardProposals, buildGuardReviewPacket, discoverProject, discoverProjectWithMetrics, fingerprintProjectModel, findGuardRoot, GUARD_ARCHITECTURE_FILE, GUARD_CONVENTIONS_FILE, loadBaseline, loadGuardArtifact, loadGuardConfig, loadGuardAgentConfig, loadGuardProposals, getPendingGuardProposals, getGuardProposalFreshness, loadProjectModel, writeGuardProposals, initializeGuard, GuardAlreadyInitializedError,
|
|
1
|
+
import { buildArchitectureMemory, buildConventionsMemory, buildGeneratedGuardConfig, buildGuardProposals, buildGuardReviewPacket, discoverProject, discoverProjectWithMetrics, fingerprintProjectModel, findGuardRoot, GUARD_ARCHITECTURE_FILE, GUARD_CONVENTIONS_FILE, loadBaseline, loadGuardArtifact, loadGuardConfig, loadGuardAgentConfig, loadGuardProposals, getPendingGuardProposals, getGuardProposalFreshness, loadProjectModel, writeGuardProposals, initializeGuard, GuardAlreadyInitializedError, GuardStateBusyError, GuardStateStaleError, applyGuardProposalDecision, validateGuardProposalApproval, writeProjectState, scanGuard, validateGuardPolicy, classifyGuardOutcome, } from '../../core/guard.js';
|
|
2
2
|
import { guardErrorPayload } from '../../core/errors.js';
|
|
3
3
|
import { analyzeProjectImpact } from '../../core/analysis/impact.js';
|
|
4
4
|
import { z } from 'zod';
|
|
@@ -38,6 +38,63 @@ function loadConfigSafely(root) {
|
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
|
+
function loadBaselineSafely(root) {
|
|
42
|
+
try {
|
|
43
|
+
return { baseline: loadBaseline(root) };
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
return {
|
|
47
|
+
error: jsonToolResult(guardErrorPayload('GUARD_CONFIG_INVALID', error instanceof Error ? error.message : String(error), {
|
|
48
|
+
configured: true,
|
|
49
|
+
outcome: 'error',
|
|
50
|
+
recoverable: true,
|
|
51
|
+
hint: 'Repair baseline.json, then run codapult-guard doctor.',
|
|
52
|
+
}), true),
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function stateErrorResult(error) {
|
|
57
|
+
const isBusy = error instanceof GuardStateBusyError;
|
|
58
|
+
const isStale = error instanceof GuardStateStaleError;
|
|
59
|
+
if (!isBusy && !isStale)
|
|
60
|
+
return undefined;
|
|
61
|
+
return jsonToolResult(guardErrorPayload(isBusy ? 'GUARD_STATE_BUSY' : 'GUARD_STATE_STALE', String(error), {
|
|
62
|
+
configured: true,
|
|
63
|
+
outcome: 'error',
|
|
64
|
+
recoverable: true,
|
|
65
|
+
hint: 'Re-read Guard state and retry the operation.',
|
|
66
|
+
}), true);
|
|
67
|
+
}
|
|
68
|
+
function loadAgentSafely(root) {
|
|
69
|
+
try {
|
|
70
|
+
return { config: loadGuardAgentConfig(root) };
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
return {
|
|
74
|
+
error: jsonToolResult(guardErrorPayload('GUARD_CONFIG_INVALID', error instanceof Error ? error.message : String(error), {
|
|
75
|
+
configured: false,
|
|
76
|
+
outcome: 'error',
|
|
77
|
+
recoverable: true,
|
|
78
|
+
hint: 'Repair agent.json, then run codapult-guard doctor.',
|
|
79
|
+
}), true),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function loadProposalsSafely(root) {
|
|
84
|
+
try {
|
|
85
|
+
return { proposals: loadGuardProposals(root) };
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
return {
|
|
89
|
+
error: jsonToolResult(guardErrorPayload('GUARD_CONFIG_INVALID', error instanceof Error ? error.message : String(error), {
|
|
90
|
+
configured: false,
|
|
91
|
+
outcome: 'error',
|
|
92
|
+
recoverable: true,
|
|
93
|
+
hint: 'Repair proposals.json, then run codapult-guard doctor.',
|
|
94
|
+
}), true),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
41
98
|
export function registerGuardTools(server) {
|
|
42
99
|
server.registerTool('codapult_guard_analyze', {
|
|
43
100
|
title: 'Refresh Guard Model',
|
|
@@ -48,15 +105,25 @@ export function registerGuardTools(server) {
|
|
|
48
105
|
.boolean()
|
|
49
106
|
.default(false)
|
|
50
107
|
.describe('Required before writing refreshed Guard state.'),
|
|
108
|
+
wait_ms: z.number().int().min(0).max(120_000).default(30_000),
|
|
109
|
+
no_wait: z.boolean().default(false),
|
|
51
110
|
},
|
|
52
|
-
}, ({ root: requestedRoot, confirm }) => {
|
|
111
|
+
}, ({ root: requestedRoot, confirm, wait_ms, no_wait }) => {
|
|
53
112
|
const root = getGuardRoot(requestedRoot);
|
|
54
113
|
if (!confirm)
|
|
55
114
|
return jsonToolResult({ status: 'needs-confirmation', action: 'analyze' });
|
|
56
115
|
const discovery = discoverProjectWithMetrics(root, { persistCache: true });
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
116
|
+
let revision;
|
|
117
|
+
try {
|
|
118
|
+
revision = writeProjectState(root, discovery.model, {
|
|
119
|
+
waitMs: wait_ms,
|
|
120
|
+
noWait: no_wait,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
return (stateErrorResult(error) ??
|
|
125
|
+
jsonToolResult({ status: 'fail', message: String(error) }, true));
|
|
126
|
+
}
|
|
60
127
|
return jsonToolResult({
|
|
61
128
|
status: 'ok',
|
|
62
129
|
action: 'analyze',
|
|
@@ -93,7 +160,10 @@ export function registerGuardTools(server) {
|
|
|
93
160
|
canRetry: false,
|
|
94
161
|
});
|
|
95
162
|
}
|
|
96
|
-
const
|
|
163
|
+
const loadedProposals = loadProposalsSafely(root);
|
|
164
|
+
if ('error' in loadedProposals)
|
|
165
|
+
return loadedProposals.error;
|
|
166
|
+
const proposals = loadedProposals.proposals;
|
|
97
167
|
const proposedCount = getPendingGuardProposals(proposals).length;
|
|
98
168
|
if (proposals && getGuardProposalFreshness(project, proposals) === 'stale') {
|
|
99
169
|
return jsonToolResult({
|
|
@@ -111,12 +181,15 @@ export function registerGuardTools(server) {
|
|
|
111
181
|
canRetry: false,
|
|
112
182
|
});
|
|
113
183
|
}
|
|
184
|
+
const loadedBaseline = loadBaselineSafely(root);
|
|
185
|
+
if (loadedBaseline.error)
|
|
186
|
+
return loadedBaseline.error;
|
|
114
187
|
const report = scanGuard(root, config, {
|
|
115
188
|
changedOnly: true,
|
|
116
|
-
baseline:
|
|
189
|
+
baseline: loadedBaseline.baseline,
|
|
117
190
|
includeArchitectureInsights: true,
|
|
118
191
|
});
|
|
119
|
-
const contractIssues =
|
|
192
|
+
const contractIssues = validateGuardPolicy(root, config);
|
|
120
193
|
if (report.findings.some((finding) => finding.severity === 'error')) {
|
|
121
194
|
return jsonToolResult({
|
|
122
195
|
status: 'needs-repair',
|
|
@@ -158,7 +231,10 @@ export function registerGuardTools(server) {
|
|
|
158
231
|
if (loaded.error)
|
|
159
232
|
return loaded.error;
|
|
160
233
|
const config = loaded.config;
|
|
161
|
-
const
|
|
234
|
+
const agent = loadAgentSafely(root);
|
|
235
|
+
if ('error' in agent)
|
|
236
|
+
return agent.error;
|
|
237
|
+
const agentConfig = agent.config;
|
|
162
238
|
const discovery = refresh
|
|
163
239
|
? discoverProjectWithMetrics(root, { persistCache: true })
|
|
164
240
|
: { model: loadProjectModel(root) ?? discoverProject(root), metrics: undefined };
|
|
@@ -210,7 +286,10 @@ export function registerGuardTools(server) {
|
|
|
210
286
|
return loaded.error;
|
|
211
287
|
const config = loaded.config ?? buildGeneratedGuardConfig(project);
|
|
212
288
|
const proposals = buildGuardProposals(project, config);
|
|
213
|
-
const
|
|
289
|
+
const loadedPrevious = loadProposalsSafely(root);
|
|
290
|
+
if ('error' in loadedPrevious)
|
|
291
|
+
return loadedPrevious.error;
|
|
292
|
+
const previous = loadedPrevious.proposals;
|
|
214
293
|
if (persist) {
|
|
215
294
|
writeGuardProposals(root, {
|
|
216
295
|
...proposals,
|
|
@@ -243,16 +322,21 @@ export function registerGuardTools(server) {
|
|
|
243
322
|
.boolean()
|
|
244
323
|
.default(false)
|
|
245
324
|
.describe('Replace existing Guard state; requires confirm.'),
|
|
325
|
+
wait_ms: z.number().int().min(0).max(120_000).default(30_000),
|
|
326
|
+
no_wait: z.boolean().default(false),
|
|
246
327
|
},
|
|
247
|
-
}, ({ root: requestedRoot, confirm, force }) => {
|
|
328
|
+
}, ({ root: requestedRoot, confirm, force, wait_ms, no_wait }) => {
|
|
248
329
|
const root = getGuardRoot(requestedRoot);
|
|
249
330
|
if (!confirm)
|
|
250
331
|
return jsonToolResult({ status: 'needs-confirmation', root, force });
|
|
251
332
|
try {
|
|
252
|
-
const result = initializeGuard(root, { force });
|
|
333
|
+
const result = initializeGuard(root, { force, waitMs: wait_ms, noWait: no_wait });
|
|
253
334
|
return jsonToolResult({ status: 'ok', root, report: result.report });
|
|
254
335
|
}
|
|
255
336
|
catch (error) {
|
|
337
|
+
const stateError = stateErrorResult(error);
|
|
338
|
+
if (stateError)
|
|
339
|
+
return stateError;
|
|
256
340
|
const status = error instanceof GuardAlreadyInitializedError ? 'already-initialized' : 'fail';
|
|
257
341
|
return jsonToolResult({ status, root, message: error instanceof Error ? error.message : String(error) }, true);
|
|
258
342
|
}
|
|
@@ -268,7 +352,10 @@ export function registerGuardTools(server) {
|
|
|
268
352
|
},
|
|
269
353
|
}, ({ root: requestedRoot, ids, decision, confirm }) => {
|
|
270
354
|
const root = getGuardRoot(requestedRoot);
|
|
271
|
-
const
|
|
355
|
+
const loadedProposals = loadProposalsSafely(root);
|
|
356
|
+
if ('error' in loadedProposals)
|
|
357
|
+
return loadedProposals.error;
|
|
358
|
+
const proposals = loadedProposals.proposals;
|
|
272
359
|
if (!proposals)
|
|
273
360
|
return jsonToolResult({ status: 'not-configured', message: 'No proposals found.' }, true);
|
|
274
361
|
const project = discoverProject(root);
|
|
@@ -279,13 +366,28 @@ export function registerGuardTools(server) {
|
|
|
279
366
|
const missing = ids.filter((id) => !selected.some((item) => item.id === id));
|
|
280
367
|
if (missing.length > 0)
|
|
281
368
|
return jsonToolResult({ status: 'invalid', missing }, true);
|
|
369
|
+
const loadedConfig = loadConfigSafely(root);
|
|
370
|
+
if (loadedConfig.error)
|
|
371
|
+
return loadedConfig.error;
|
|
372
|
+
const approval = loadedConfig.config?.approval;
|
|
373
|
+
if (decision === 'approved' && approval?.mode === 'protected' && !approval.allowMcpApproval) {
|
|
374
|
+
return jsonToolResult({
|
|
375
|
+
status: 'approval-required',
|
|
376
|
+
message: 'Protected Guard policy requires approval outside the MCP agent surface.',
|
|
377
|
+
source: 'mcp',
|
|
378
|
+
ids,
|
|
379
|
+
}, true);
|
|
380
|
+
}
|
|
381
|
+
if (decision === 'approved') {
|
|
382
|
+
const approvalError = validateGuardProposalApproval(approval, proposals);
|
|
383
|
+
if (approvalError) {
|
|
384
|
+
return jsonToolResult({ status: 'approval-required', message: approvalError, source: 'mcp', ids }, true);
|
|
385
|
+
}
|
|
386
|
+
}
|
|
282
387
|
if (!confirm)
|
|
283
388
|
return jsonToolResult({ status: 'needs-confirmation', decision, ids });
|
|
284
389
|
if (decision === 'approved') {
|
|
285
|
-
const
|
|
286
|
-
if (loaded.error)
|
|
287
|
-
return loaded.error;
|
|
288
|
-
const config = loaded.config ?? { version: 1, rules: [], contracts: [] };
|
|
390
|
+
const config = loadedConfig.config ?? { version: 1, rules: [], contracts: [] };
|
|
289
391
|
const nextRules = [...config.rules];
|
|
290
392
|
const nextContracts = [...(config.contracts ?? [])];
|
|
291
393
|
for (const item of selected) {
|
|
@@ -306,13 +408,19 @@ export function registerGuardTools(server) {
|
|
|
306
408
|
nextRules.push(active);
|
|
307
409
|
}
|
|
308
410
|
}
|
|
309
|
-
|
|
411
|
+
applyGuardProposalDecision(root, { version: 1, rules: nextRules, contracts: nextContracts }, selected.map((item) => ({
|
|
412
|
+
id: item.id,
|
|
413
|
+
type: 'statement' in item ? 'contract' : 'rule',
|
|
414
|
+
decision,
|
|
415
|
+
})), { source: 'mcp' });
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
applyGuardProposalDecision(root, loadedConfig.config ?? { version: 1, rules: [], contracts: [] }, selected.map((item) => ({
|
|
419
|
+
id: item.id,
|
|
420
|
+
type: 'statement' in item ? 'contract' : 'rule',
|
|
421
|
+
decision,
|
|
422
|
+
})), { source: 'mcp' });
|
|
310
423
|
}
|
|
311
|
-
recordGuardProposalDecision(root, selected.map((item) => ({
|
|
312
|
-
id: item.id,
|
|
313
|
-
type: 'statement' in item ? 'contract' : 'rule',
|
|
314
|
-
decision,
|
|
315
|
-
})));
|
|
316
424
|
return jsonToolResult({ status: 'ok', root, decision, ids });
|
|
317
425
|
});
|
|
318
426
|
server.registerTool('codapult_guard_verify', {
|
|
@@ -339,6 +447,9 @@ export function registerGuardTools(server) {
|
|
|
339
447
|
},
|
|
340
448
|
}, ({ root: requestedRoot, checks, changed_only, requirement, tools, strict, project_checks, iteration, }) => {
|
|
341
449
|
const root = getGuardRoot(requestedRoot);
|
|
450
|
+
const agent = loadAgentSafely(root);
|
|
451
|
+
if ('error' in agent)
|
|
452
|
+
return agent.error;
|
|
342
453
|
const result = runGuardVerification(root, {
|
|
343
454
|
checks,
|
|
344
455
|
changedOnly: changed_only,
|
|
@@ -347,7 +458,7 @@ export function registerGuardTools(server) {
|
|
|
347
458
|
strict,
|
|
348
459
|
projectChecks: project_checks,
|
|
349
460
|
});
|
|
350
|
-
const completionGate =
|
|
461
|
+
const completionGate = agent.config.completionGate;
|
|
351
462
|
return jsonToolResult({
|
|
352
463
|
...result,
|
|
353
464
|
completionGate: {
|
|
@@ -371,7 +482,7 @@ export function registerGuardTools(server) {
|
|
|
371
482
|
return notConfiguredToolResult();
|
|
372
483
|
}
|
|
373
484
|
const report = scanGuard(root, config, { includeArchitectureInsights: true });
|
|
374
|
-
const contractIssues =
|
|
485
|
+
const contractIssues = validateGuardPolicy(root, config);
|
|
375
486
|
const errors = report.findings.filter((finding) => finding.severity === 'error').length;
|
|
376
487
|
const warnings = report.findings.filter((finding) => finding.severity === 'warning').length;
|
|
377
488
|
const status = errors > 0 || contractIssues.length > 0 ? 'fail' : warnings > 0 ? 'warning' : 'ok';
|
|
@@ -411,7 +522,12 @@ export function registerGuardTools(server) {
|
|
|
411
522
|
if (!config) {
|
|
412
523
|
return notConfiguredToolResult();
|
|
413
524
|
}
|
|
414
|
-
const
|
|
525
|
+
const loadedBaseline = changed_only
|
|
526
|
+
? loadBaselineSafely(root)
|
|
527
|
+
: { baseline: new Set() };
|
|
528
|
+
if (loadedBaseline.error)
|
|
529
|
+
return loadedBaseline.error;
|
|
530
|
+
const packet = buildGuardReviewPacket(root, config, loadedBaseline.baseline ?? new Set(), max_diff_chars, changed_only, requirement, base);
|
|
415
531
|
return jsonToolResult({ ...packet, status: packet.diffError ? 'fail' : 'needs-review' }, Boolean(packet.diffError));
|
|
416
532
|
});
|
|
417
533
|
server.registerTool('codapult_guard_check', {
|
|
@@ -427,12 +543,15 @@ export function registerGuardTools(server) {
|
|
|
427
543
|
if (!config) {
|
|
428
544
|
return notConfiguredToolResult();
|
|
429
545
|
}
|
|
546
|
+
const loadedBaseline = loadBaselineSafely(root);
|
|
547
|
+
if (loadedBaseline.error)
|
|
548
|
+
return loadedBaseline.error;
|
|
430
549
|
const report = scanGuard(root, config, {
|
|
431
550
|
changedOnly: changed_only,
|
|
432
|
-
baseline:
|
|
551
|
+
baseline: loadedBaseline.baseline,
|
|
433
552
|
includeArchitectureInsights: true,
|
|
434
553
|
});
|
|
435
|
-
const contractIssues =
|
|
554
|
+
const contractIssues = validateGuardPolicy(root, config);
|
|
436
555
|
const errors = report.findings.filter((finding) => finding.severity === 'error').length;
|
|
437
556
|
return jsonToolResult({
|
|
438
557
|
status: errors > 0 || contractIssues.length > 0 ? 'fail' : 'ok',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codapult/guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Local-first architecture guardrails for JavaScript and TypeScript projects, with first-class support for Next.js SaaS and AI-assisted development",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"packageManager": "pnpm@12.4.2+sha512.08adc6613180275c7c9edada39dcf08c9c61ad4e7eaf330a4f3461f102b0f907423454d117f98e72d47fef0616070644d7bffc973a6a57f5090a6d7c368b07c9",
|
|
@@ -104,19 +104,19 @@
|
|
|
104
104
|
},
|
|
105
105
|
"devDependencies": {
|
|
106
106
|
"@js-toolkit/config-utils": "^1.0.1",
|
|
107
|
-
"@js-toolkit/eslint-config": "^1.3.
|
|
107
|
+
"@js-toolkit/eslint-config": "^1.3.2",
|
|
108
108
|
"@js-toolkit/prettier-config": "^1.0.1",
|
|
109
109
|
"@js-toolkit/tsconfig": "^1.0.1",
|
|
110
110
|
"@release-it/conventional-changelog": "^12.0.2",
|
|
111
|
-
"@types/node": "^26.6.
|
|
111
|
+
"@types/node": "^26.6.2",
|
|
112
112
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
113
113
|
"@vitest/coverage-v8": "^5.0.1",
|
|
114
|
-
"eslint": "^10.
|
|
114
|
+
"eslint": "^10.11.0",
|
|
115
115
|
"eslint-config-prettier": "^10.1.8",
|
|
116
116
|
"eslint-plugin-prettier": "^5.5.6",
|
|
117
|
-
"prettier": "^3.9.
|
|
118
|
-
"release-it": "^21.0
|
|
119
|
-
"typescript-eslint": "^8.70.
|
|
117
|
+
"prettier": "^3.9.8",
|
|
118
|
+
"release-it": "^21.1.0",
|
|
119
|
+
"typescript-eslint": "^8.70.1",
|
|
120
120
|
"vitest": "^5.0.1"
|
|
121
121
|
}
|
|
122
122
|
}
|