@codapult/guard 0.1.0 → 0.3.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 +95 -15
- package/dist/adapters/command.js +23 -2
- package/dist/cli/commands/guard.d.ts +10 -1
- package/dist/cli/commands/guard.js +148 -14
- package/dist/cli/index.js +28 -4
- package/dist/core/analysis/doctor.js +19 -5
- package/dist/core/analysis/impact.d.ts +22 -0
- package/dist/core/analysis/impact.js +73 -0
- package/dist/core/discovery/discovery.d.ts +23 -6
- package/dist/core/discovery/discovery.js +166 -26
- package/dist/core/errors.d.ts +11 -0
- package/dist/core/errors.js +11 -0
- package/dist/core/guard.d.ts +26 -5
- package/dist/core/guard.js +262 -43
- package/dist/core/history/history.d.ts +6 -0
- package/dist/core/history/history.js +26 -2
- package/dist/core/policy/schemas.d.ts +12 -0
- package/dist/core/policy/schemas.js +21 -17
- package/dist/core/verification/verify.d.ts +5 -0
- package/dist/core/verification/verify.js +26 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/mcp/tools/guard.js +167 -47
- package/package.json +17 -12
|
@@ -2,29 +2,31 @@ import { z } from 'zod';
|
|
|
2
2
|
const severity = z.enum(['error', 'warning', 'info']);
|
|
3
3
|
const status = z.enum(['active', 'proposed']);
|
|
4
4
|
export const guardRuleSchema = z.object({
|
|
5
|
-
id: z.string(),
|
|
6
|
-
description: z.string(),
|
|
5
|
+
id: z.string().trim().min(1),
|
|
6
|
+
description: z.string().trim().min(1),
|
|
7
7
|
severity,
|
|
8
8
|
kind: z.enum(['forbidden-import', 'client-forbidden-import']),
|
|
9
|
-
patterns: z.array(z.string()),
|
|
10
|
-
files: z.array(z.string()).optional(),
|
|
9
|
+
patterns: z.array(z.string().trim().min(1)).min(1),
|
|
10
|
+
files: z.array(z.string().trim().min(1)).optional(),
|
|
11
11
|
status: status.optional(),
|
|
12
12
|
confidence: z.enum(['high', 'medium', 'low']).optional(),
|
|
13
13
|
evidence: z.array(z.string()).optional(),
|
|
14
14
|
});
|
|
15
15
|
export const guardContractSchema = z.object({
|
|
16
|
-
id: z.string(),
|
|
17
|
-
statement: z.string(),
|
|
18
|
-
kind: z.enum(['guidance', 'import-boundary', 'required-call']).optional(),
|
|
16
|
+
id: z.string().trim().min(1),
|
|
17
|
+
statement: z.string().trim().min(1),
|
|
18
|
+
kind: z.enum(['guidance', 'import-boundary', 'required-call', 'package-boundary']).optional(),
|
|
19
19
|
severity: severity.optional(),
|
|
20
|
-
scope: z.array(z.string()).optional(),
|
|
21
|
-
entrypoints: z.array(z.string()).optional(),
|
|
22
|
-
exclude: z.array(z.string()).optional(),
|
|
23
|
-
guidance: z.array(z.string()).optional(),
|
|
24
|
-
references: z.array(z.string()).optional(),
|
|
25
|
-
mustImport: z.array(z.string()).optional(),
|
|
26
|
-
mustNotImport: z.array(z.string()).optional(),
|
|
27
|
-
mustCall: z.array(z.string()).optional(),
|
|
20
|
+
scope: z.array(z.string().trim().min(1)).optional(),
|
|
21
|
+
entrypoints: z.array(z.string().trim().min(1)).optional(),
|
|
22
|
+
exclude: z.array(z.string().trim().min(1)).optional(),
|
|
23
|
+
guidance: z.array(z.string().trim().min(1)).optional(),
|
|
24
|
+
references: z.array(z.string().trim().min(1)).optional(),
|
|
25
|
+
mustImport: z.array(z.string().trim().min(1)).optional(),
|
|
26
|
+
mustNotImport: z.array(z.string().trim().min(1)).optional(),
|
|
27
|
+
mustCall: z.array(z.string().trim().min(1)).optional(),
|
|
28
|
+
fromPackages: z.array(z.string().trim().min(1)).optional(),
|
|
29
|
+
mustNotImportPackages: z.array(z.string().trim().min(1)).optional(),
|
|
28
30
|
status: status.optional(),
|
|
29
31
|
confidence: z.enum(['high', 'medium', 'low']).optional(),
|
|
30
32
|
evidence: z.array(z.string()).optional(),
|
|
@@ -39,7 +41,7 @@ export const guardContractsFileSchema = z.object({
|
|
|
39
41
|
contracts: z.array(guardContractSchema),
|
|
40
42
|
});
|
|
41
43
|
export const guardProposalDecisionSchema = z.object({
|
|
42
|
-
id: z.string(),
|
|
44
|
+
id: z.string().trim().min(1),
|
|
43
45
|
type: z.enum(['rule', 'contract']),
|
|
44
46
|
decision: z.enum(['approved', 'rejected']),
|
|
45
47
|
decidedAt: z.string(),
|
|
@@ -62,7 +64,9 @@ export const guardProposalSchema = z.object({
|
|
|
62
64
|
export const guardAgentConfigSchema = z.object({
|
|
63
65
|
version: z.literal(1),
|
|
64
66
|
tools: z.enum(['auto', 'on', 'off']),
|
|
65
|
-
tooling: z
|
|
67
|
+
tooling: z
|
|
68
|
+
.record(z.string().trim().min(1), z.object({ script: z.string().trim().min(1), enabled: z.boolean() }))
|
|
69
|
+
.default({}),
|
|
66
70
|
completionGate: z.object({
|
|
67
71
|
enabled: z.boolean(),
|
|
68
72
|
maxIterations: z.number().int().min(1).max(10),
|
|
@@ -1,16 +1,21 @@
|
|
|
1
1
|
import { type CommandResult } from '../../adapters/command.js';
|
|
2
2
|
import { type GuardAdapter, type ProjectCheck, type ProjectCheckResults, type ProjectRuntimeDiagnostics } from '../../adapters/project-checks.js';
|
|
3
3
|
import { type GuardToolMode, type GuardContractIssue, type GuardReport } from '../guard.js';
|
|
4
|
+
import { type GuardImpactAnalysis } from '../analysis/impact.js';
|
|
4
5
|
export type GuardVerificationCheck = ProjectCheck;
|
|
5
6
|
export interface GuardVerificationResult {
|
|
6
7
|
status: 'ok' | 'fail' | 'not-configured';
|
|
7
8
|
outcome: 'pass' | 'fail' | 'warning' | 'needs-review' | 'not-configured';
|
|
9
|
+
errorCode?: 'GUARD_NOT_CONFIGURED' | 'GUARD_CONFIG_INVALID';
|
|
10
|
+
recoverable?: boolean;
|
|
8
11
|
tools?: GuardToolMode;
|
|
9
12
|
checks: ProjectCheckResults;
|
|
10
13
|
workspaceChecks: Record<string, ProjectCheckResults>;
|
|
11
14
|
adapters: Partial<Record<GuardAdapter, CommandResult>>;
|
|
12
15
|
runtime: ProjectRuntimeDiagnostics;
|
|
16
|
+
configError?: string;
|
|
13
17
|
architecture?: GuardReport;
|
|
18
|
+
impact?: GuardImpactAnalysis;
|
|
14
19
|
contractIssues: GuardContractIssue[];
|
|
15
20
|
requirement: {
|
|
16
21
|
status: 'delegated-to-review';
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { runProjectAdapters, runProjectChecks, runWorkspaceProjectChecks, inspectProjectRuntime, } from '../../adapters/project-checks.js';
|
|
2
2
|
import { loadBaseline, loadGuardAgentConfig, loadGuardConfig, scanGuard, classifyGuardOutcome, validateGuardContracts, } from '../guard.js';
|
|
3
|
+
import { analyzeProjectImpact } from '../analysis/impact.js';
|
|
3
4
|
import { discoverProject } from '../discovery/discovery.js';
|
|
4
5
|
export function runGuardVerification(root, options = {}) {
|
|
5
|
-
const config = loadGuardConfig(root);
|
|
6
6
|
const requirement = {
|
|
7
7
|
status: 'delegated-to-review',
|
|
8
8
|
provided: Boolean(options.requirement?.trim()),
|
|
@@ -11,10 +11,31 @@ export function runGuardVerification(root, options = {}) {
|
|
|
11
11
|
: 'Requirement satisfaction is evaluated by guard review using the task and diff.',
|
|
12
12
|
};
|
|
13
13
|
const runtime = inspectProjectRuntime(root);
|
|
14
|
+
let config;
|
|
15
|
+
try {
|
|
16
|
+
config = loadGuardConfig(root);
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
return {
|
|
20
|
+
status: 'fail',
|
|
21
|
+
outcome: 'fail',
|
|
22
|
+
errorCode: 'GUARD_CONFIG_INVALID',
|
|
23
|
+
recoverable: true,
|
|
24
|
+
checks: {},
|
|
25
|
+
workspaceChecks: {},
|
|
26
|
+
adapters: {},
|
|
27
|
+
runtime,
|
|
28
|
+
configError: error instanceof Error ? error.message : String(error),
|
|
29
|
+
contractIssues: [],
|
|
30
|
+
requirement,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
14
33
|
if (!config)
|
|
15
34
|
return {
|
|
16
35
|
status: 'not-configured',
|
|
17
36
|
outcome: 'not-configured',
|
|
37
|
+
errorCode: 'GUARD_NOT_CONFIGURED',
|
|
38
|
+
recoverable: true,
|
|
18
39
|
checks: {},
|
|
19
40
|
workspaceChecks: {},
|
|
20
41
|
adapters: {},
|
|
@@ -27,6 +48,9 @@ export function runGuardVerification(root, options = {}) {
|
|
|
27
48
|
const checks = projectChecks ? (options.checks ?? agentConfig.completionGate.checks) : [];
|
|
28
49
|
const results = runProjectChecks(root, checks, { timeout: options.timeout });
|
|
29
50
|
const workspaceModel = discoverProject(root);
|
|
51
|
+
const impact = analyzeProjectImpact(workspaceModel, options.changedOnly === false
|
|
52
|
+
? workspaceModel.modules.map((module) => module.path)
|
|
53
|
+
: workspaceModel.git.changedFiles, config.contracts ?? []);
|
|
30
54
|
const workspaceChecks = runWorkspaceProjectChecks(root, workspaceModel.project.workspacePackages ?? [], checks, {
|
|
31
55
|
timeout: options.timeout,
|
|
32
56
|
rootResults: results,
|
|
@@ -71,6 +95,7 @@ export function runGuardVerification(root, options = {}) {
|
|
|
71
95
|
adapters,
|
|
72
96
|
runtime,
|
|
73
97
|
architecture,
|
|
98
|
+
impact,
|
|
74
99
|
contractIssues,
|
|
75
100
|
requirement,
|
|
76
101
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/** Public, framework-agnostic Guard API. */
|
|
2
2
|
export * from './core/guard.js';
|
|
3
|
+
export * from './core/errors.js';
|
|
3
4
|
export * from './core/discovery/discovery.js';
|
|
4
5
|
export { runGuardVerification } from './core/verification/verify.js';
|
|
5
6
|
export type { GuardVerificationCheck, GuardVerificationResult, } from './core/verification/verify.js';
|
|
@@ -7,4 +8,5 @@ export * from './core/history/history.js';
|
|
|
7
8
|
export * from './core/output/sarif.js';
|
|
8
9
|
export * from './core/analysis/doctor.js';
|
|
9
10
|
export * from './core/analysis/packs.js';
|
|
11
|
+
export * from './core/analysis/impact.js';
|
|
10
12
|
export * from './adapters/project-checks.js';
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/** Public, framework-agnostic Guard API. */
|
|
2
2
|
export * from './core/guard.js';
|
|
3
|
+
export * from './core/errors.js';
|
|
3
4
|
export * from './core/discovery/discovery.js';
|
|
4
5
|
export { runGuardVerification } from './core/verification/verify.js';
|
|
5
6
|
export * from './core/history/history.js';
|
|
6
7
|
export * from './core/output/sarif.js';
|
|
7
8
|
export * from './core/analysis/doctor.js';
|
|
8
9
|
export * from './core/analysis/packs.js';
|
|
10
|
+
export * from './core/analysis/impact.js';
|
|
9
11
|
export * from './adapters/project-checks.js';
|
package/dist/mcp/tools/guard.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import { buildArchitectureMemory, buildConventionsMemory, buildGeneratedGuardConfig, buildGuardProposals, buildGuardReviewPacket, discoverProject, discoverProjectWithMetrics, findGuardRoot, GUARD_ARCHITECTURE_FILE, GUARD_CONVENTIONS_FILE, loadBaseline, loadGuardArtifact, loadGuardConfig, loadGuardAgentConfig, loadGuardProposals, getGuardProposalFreshness, loadProjectModel, writeGuardProposals, initializeGuard, GuardAlreadyInitializedError, recordGuardProposalDecision, writeGuardConfig, scanGuard, validateGuardContracts, classifyGuardOutcome, } from '../../core/guard.js';
|
|
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, recordGuardProposalDecision, writeGuardConfig, writeGuardMemory, writeProjectModel, writeProjectSnapshot, scanGuard, validateGuardContracts, classifyGuardOutcome, } from '../../core/guard.js';
|
|
2
|
+
import { guardErrorPayload } from '../../core/errors.js';
|
|
3
|
+
import { analyzeProjectImpact } from '../../core/analysis/impact.js';
|
|
2
4
|
import { z } from 'zod';
|
|
3
5
|
import { runGuardVerification } from '../../core/verification/verify.js';
|
|
4
6
|
const rootSchema = z.string().trim().min(1).optional().describe('Project path or workspace root.');
|
|
@@ -11,7 +13,135 @@ function jsonToolResult(value, isError = false) {
|
|
|
11
13
|
...(isError ? { isError: true } : {}),
|
|
12
14
|
};
|
|
13
15
|
}
|
|
16
|
+
function notConfiguredToolResult() {
|
|
17
|
+
return jsonToolResult({
|
|
18
|
+
status: 'error',
|
|
19
|
+
outcome: 'not-configured',
|
|
20
|
+
configured: false,
|
|
21
|
+
errorCode: 'GUARD_NOT_CONFIGURED',
|
|
22
|
+
message: 'Guard is not initialized.',
|
|
23
|
+
recoverable: true,
|
|
24
|
+
}, true);
|
|
25
|
+
}
|
|
26
|
+
function loadConfigSafely(root) {
|
|
27
|
+
try {
|
|
28
|
+
return { config: loadGuardConfig(root) };
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
return {
|
|
32
|
+
error: jsonToolResult(guardErrorPayload('GUARD_CONFIG_INVALID', error instanceof Error ? error.message : String(error), {
|
|
33
|
+
configured: false,
|
|
34
|
+
outcome: 'error',
|
|
35
|
+
recoverable: true,
|
|
36
|
+
hint: 'Repair the invalid Guard artifact, then run codapult-guard doctor.',
|
|
37
|
+
}), true),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
}
|
|
14
41
|
export function registerGuardTools(server) {
|
|
42
|
+
server.registerTool('codapult_guard_analyze', {
|
|
43
|
+
title: 'Refresh Guard Model',
|
|
44
|
+
description: 'Refresh persisted project facts and snapshots without changing rules, contracts, or baseline.',
|
|
45
|
+
inputSchema: {
|
|
46
|
+
root: rootSchema,
|
|
47
|
+
confirm: z
|
|
48
|
+
.boolean()
|
|
49
|
+
.default(false)
|
|
50
|
+
.describe('Required before writing refreshed Guard state.'),
|
|
51
|
+
},
|
|
52
|
+
}, ({ root: requestedRoot, confirm }) => {
|
|
53
|
+
const root = getGuardRoot(requestedRoot);
|
|
54
|
+
if (!confirm)
|
|
55
|
+
return jsonToolResult({ status: 'needs-confirmation', action: 'analyze' });
|
|
56
|
+
const discovery = discoverProjectWithMetrics(root, { persistCache: true });
|
|
57
|
+
writeProjectModel(root, discovery.model);
|
|
58
|
+
writeGuardMemory(root, discovery.model);
|
|
59
|
+
const revision = writeProjectSnapshot(root, discovery.model);
|
|
60
|
+
return jsonToolResult({
|
|
61
|
+
status: 'ok',
|
|
62
|
+
action: 'analyze',
|
|
63
|
+
revision,
|
|
64
|
+
discovery: discovery.metrics,
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
server.registerTool('codapult_guard_next_action', {
|
|
68
|
+
title: 'Guard Next Action',
|
|
69
|
+
description: 'Return the next bounded action for an AI coding-agent loop without modifying project files.',
|
|
70
|
+
inputSchema: { root: rootSchema },
|
|
71
|
+
}, ({ root: requestedRoot }) => {
|
|
72
|
+
const root = getGuardRoot(requestedRoot);
|
|
73
|
+
const loaded = loadConfigSafely(root);
|
|
74
|
+
if (loaded.error)
|
|
75
|
+
return loaded.error;
|
|
76
|
+
const config = loaded.config;
|
|
77
|
+
if (!config) {
|
|
78
|
+
return jsonToolResult({
|
|
79
|
+
status: 'needs-setup',
|
|
80
|
+
action: 'init',
|
|
81
|
+
reason: 'Guard policy is not initialized.',
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
const project = discoverProject(root);
|
|
85
|
+
const persisted = loadProjectModel(root);
|
|
86
|
+
if (persisted &&
|
|
87
|
+
'project' in persisted &&
|
|
88
|
+
fingerprintProjectModel(persisted) !== fingerprintProjectModel(project)) {
|
|
89
|
+
return jsonToolResult({
|
|
90
|
+
status: 'needs-refresh',
|
|
91
|
+
action: 'analyze',
|
|
92
|
+
reason: 'Persisted Guard project facts are stale after repository changes.',
|
|
93
|
+
canRetry: false,
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
const proposals = loadGuardProposals(root);
|
|
97
|
+
const proposedCount = getPendingGuardProposals(proposals).length;
|
|
98
|
+
if (proposals && getGuardProposalFreshness(project, proposals) === 'stale') {
|
|
99
|
+
return jsonToolResult({
|
|
100
|
+
status: 'needs-decision',
|
|
101
|
+
action: 'propose',
|
|
102
|
+
reason: 'Guard proposals are stale after project changes.',
|
|
103
|
+
canRetry: false,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
if (proposedCount > 0) {
|
|
107
|
+
return jsonToolResult({
|
|
108
|
+
status: 'needs-decision',
|
|
109
|
+
action: 'policy-review',
|
|
110
|
+
reason: `${proposedCount} proposed policy item(s) await explicit approval or rejection.`,
|
|
111
|
+
canRetry: false,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
const report = scanGuard(root, config, {
|
|
115
|
+
changedOnly: true,
|
|
116
|
+
baseline: loadBaseline(root),
|
|
117
|
+
includeArchitectureInsights: true,
|
|
118
|
+
});
|
|
119
|
+
const contractIssues = validateGuardContracts(root, config.contracts ?? []);
|
|
120
|
+
if (report.findings.some((finding) => finding.severity === 'error')) {
|
|
121
|
+
return jsonToolResult({
|
|
122
|
+
status: 'needs-repair',
|
|
123
|
+
action: 'repair',
|
|
124
|
+
reason: 'Active architecture findings remain in changed files.',
|
|
125
|
+
findings: report.findings,
|
|
126
|
+
canRetry: true,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
if (contractIssues.length > 0) {
|
|
130
|
+
return jsonToolResult({
|
|
131
|
+
status: 'needs-decision',
|
|
132
|
+
action: 'policy-repair',
|
|
133
|
+
reason: 'Guard contracts are stale or invalid.',
|
|
134
|
+
contractIssues,
|
|
135
|
+
canRetry: false,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return jsonToolResult({
|
|
139
|
+
status: 'ready-for-verification',
|
|
140
|
+
action: 'verify',
|
|
141
|
+
reason: 'No changed-file architecture regressions remain.',
|
|
142
|
+
canRetry: true,
|
|
143
|
+
});
|
|
144
|
+
});
|
|
15
145
|
server.registerTool('codapult_guard_context', {
|
|
16
146
|
title: 'Guard Context',
|
|
17
147
|
description: 'Read the discovered project model, architecture insights, and local rules used by Codapult Guard. Works before init by discovering the project on demand.',
|
|
@@ -24,7 +154,10 @@ export function registerGuardTools(server) {
|
|
|
24
154
|
},
|
|
25
155
|
}, ({ root: requestedRoot, refresh }) => {
|
|
26
156
|
const root = getGuardRoot(requestedRoot);
|
|
27
|
-
const
|
|
157
|
+
const loaded = loadConfigSafely(root);
|
|
158
|
+
if (loaded.error)
|
|
159
|
+
return loaded.error;
|
|
160
|
+
const config = loaded.config;
|
|
28
161
|
const agentConfig = loadGuardAgentConfig(root);
|
|
29
162
|
const discovery = refresh
|
|
30
163
|
? discoverProjectWithMetrics(root, { persistCache: true })
|
|
@@ -72,7 +205,10 @@ export function registerGuardTools(server) {
|
|
|
72
205
|
if (persist && !confirm)
|
|
73
206
|
return jsonToolResult({ status: 'needs-confirmation', persist: true });
|
|
74
207
|
const project = discoverProject(root);
|
|
75
|
-
const
|
|
208
|
+
const loaded = loadConfigSafely(root);
|
|
209
|
+
if (loaded.error)
|
|
210
|
+
return loaded.error;
|
|
211
|
+
const config = loaded.config ?? buildGeneratedGuardConfig(project);
|
|
76
212
|
const proposals = buildGuardProposals(project, config);
|
|
77
213
|
const previous = loadGuardProposals(root);
|
|
78
214
|
if (persist) {
|
|
@@ -146,7 +282,10 @@ export function registerGuardTools(server) {
|
|
|
146
282
|
if (!confirm)
|
|
147
283
|
return jsonToolResult({ status: 'needs-confirmation', decision, ids });
|
|
148
284
|
if (decision === 'approved') {
|
|
149
|
-
const
|
|
285
|
+
const loaded = loadConfigSafely(root);
|
|
286
|
+
if (loaded.error)
|
|
287
|
+
return loaded.error;
|
|
288
|
+
const config = loaded.config ?? { version: 1, rules: [], contracts: [] };
|
|
150
289
|
const nextRules = [...config.rules];
|
|
151
290
|
const nextContracts = [...(config.contracts ?? [])];
|
|
152
291
|
for (const item of selected) {
|
|
@@ -224,17 +363,12 @@ export function registerGuardTools(server) {
|
|
|
224
363
|
inputSchema: { root: rootSchema },
|
|
225
364
|
}, ({ root: requestedRoot }) => {
|
|
226
365
|
const root = getGuardRoot(requestedRoot);
|
|
227
|
-
const
|
|
366
|
+
const loaded = loadConfigSafely(root);
|
|
367
|
+
if (loaded.error)
|
|
368
|
+
return loaded.error;
|
|
369
|
+
const config = loaded.config;
|
|
228
370
|
if (!config) {
|
|
229
|
-
return
|
|
230
|
-
content: [
|
|
231
|
-
{
|
|
232
|
-
type: 'text',
|
|
233
|
-
text: JSON.stringify({ configured: false, outcome: 'not-configured' }, null, 2),
|
|
234
|
-
},
|
|
235
|
-
],
|
|
236
|
-
isError: true,
|
|
237
|
-
};
|
|
371
|
+
return notConfiguredToolResult();
|
|
238
372
|
}
|
|
239
373
|
const report = scanGuard(root, config, { includeArchitectureInsights: true });
|
|
240
374
|
const contractIssues = validateGuardContracts(root, config.contracts ?? []);
|
|
@@ -270,17 +404,12 @@ export function registerGuardTools(server) {
|
|
|
270
404
|
},
|
|
271
405
|
}, ({ root: requestedRoot, changed_only, max_diff_chars, requirement, base }) => {
|
|
272
406
|
const root = getGuardRoot(requestedRoot);
|
|
273
|
-
const
|
|
407
|
+
const loaded = loadConfigSafely(root);
|
|
408
|
+
if (loaded.error)
|
|
409
|
+
return loaded.error;
|
|
410
|
+
const config = loaded.config;
|
|
274
411
|
if (!config) {
|
|
275
|
-
return
|
|
276
|
-
content: [
|
|
277
|
-
{
|
|
278
|
-
type: 'text',
|
|
279
|
-
text: JSON.stringify({ configured: false, outcome: 'not-configured' }, null, 2),
|
|
280
|
-
},
|
|
281
|
-
],
|
|
282
|
-
isError: true,
|
|
283
|
-
};
|
|
412
|
+
return notConfiguredToolResult();
|
|
284
413
|
}
|
|
285
414
|
const packet = buildGuardReviewPacket(root, config, changed_only ? loadBaseline(root) : new Set(), max_diff_chars, changed_only, requirement, base);
|
|
286
415
|
return jsonToolResult({ ...packet, status: packet.diffError ? 'fail' : 'needs-review' }, Boolean(packet.diffError));
|
|
@@ -291,17 +420,12 @@ export function registerGuardTools(server) {
|
|
|
291
420
|
inputSchema: { root: rootSchema, changed_only: z.boolean().optional() },
|
|
292
421
|
}, ({ root: requestedRoot, changed_only }) => {
|
|
293
422
|
const root = getGuardRoot(requestedRoot);
|
|
294
|
-
const
|
|
423
|
+
const loaded = loadConfigSafely(root);
|
|
424
|
+
if (loaded.error)
|
|
425
|
+
return loaded.error;
|
|
426
|
+
const config = loaded.config;
|
|
295
427
|
if (!config) {
|
|
296
|
-
return
|
|
297
|
-
content: [
|
|
298
|
-
{
|
|
299
|
-
type: 'text',
|
|
300
|
-
text: JSON.stringify({ configured: false, outcome: 'not-configured' }, null, 2),
|
|
301
|
-
},
|
|
302
|
-
],
|
|
303
|
-
isError: true,
|
|
304
|
-
};
|
|
428
|
+
return notConfiguredToolResult();
|
|
305
429
|
}
|
|
306
430
|
const report = scanGuard(root, config, {
|
|
307
431
|
changedOnly: changed_only,
|
|
@@ -330,20 +454,13 @@ export function registerGuardTools(server) {
|
|
|
330
454
|
}, ({ root: requestedRoot, files }) => {
|
|
331
455
|
const root = getGuardRoot(requestedRoot);
|
|
332
456
|
const model = discoverProject(root);
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
const
|
|
337
|
-
.filter(([, signal]) => signal.files.some((file) => changed.has(file)))
|
|
338
|
-
.map(([id]) => id);
|
|
339
|
-
const contracts = (loadGuardConfig(root)?.contracts ?? []).filter((contract) => [...(contract.scope ?? []), ...(contract.entrypoints ?? [])].some((scope) => [...changed].some((file) => file === scope || file.startsWith(`${scope}/`))));
|
|
457
|
+
const loaded = loadConfigSafely(root);
|
|
458
|
+
if (loaded.error)
|
|
459
|
+
return loaded.error;
|
|
460
|
+
const impact = analyzeProjectImpact(model, files, loaded.config?.contracts ?? []);
|
|
340
461
|
return jsonToolResult({
|
|
341
462
|
status: 'ok',
|
|
342
|
-
|
|
343
|
-
affectedModules: modules.map((module) => module.path),
|
|
344
|
-
impactPaths: paths,
|
|
345
|
-
capabilities,
|
|
346
|
-
relevantContracts: contracts,
|
|
463
|
+
...impact,
|
|
347
464
|
});
|
|
348
465
|
});
|
|
349
466
|
server.registerTool('codapult_guard_explain', {
|
|
@@ -355,7 +472,10 @@ export function registerGuardTools(server) {
|
|
|
355
472
|
},
|
|
356
473
|
}, ({ root: requestedRoot, id }) => {
|
|
357
474
|
const root = getGuardRoot(requestedRoot);
|
|
358
|
-
const
|
|
475
|
+
const loaded = loadConfigSafely(root);
|
|
476
|
+
if (loaded.error)
|
|
477
|
+
return loaded.error;
|
|
478
|
+
const config = loaded.config;
|
|
359
479
|
const item = [...(config?.rules ?? []), ...(config?.contracts ?? [])].find((candidate) => candidate.id === id);
|
|
360
480
|
if (!item)
|
|
361
481
|
return jsonToolResult({ status: 'not-found', id }, true);
|
package/package.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@codapult/guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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
|
-
"packageManager": "pnpm@
|
|
6
|
+
"packageManager": "pnpm@12.4.2+sha512.08adc6613180275c7c9edada39dcf08c9c61ad4e7eaf330a4f3461f102b0f907423454d117f98e72d47fef0616070644d7bffc973a6a57f5090a6d7c368b07c9",
|
|
7
7
|
"engines": {
|
|
8
8
|
"node": ">=20.19.0"
|
|
9
9
|
},
|
|
@@ -60,7 +60,12 @@
|
|
|
60
60
|
"test:guard:fixtures": "pnpm build && node scripts/guard-fixture-smoke.mjs",
|
|
61
61
|
"test:guard:fixtures:regression": "pnpm build && node scripts/guard-fixture-regression.mjs",
|
|
62
62
|
"test:guard:fixtures:native": "pnpm build && node scripts/guard-fixture-native.mjs",
|
|
63
|
+
"test:guard:fixtures:adversarial": "pnpm build && node scripts/guard-fixture-adversarial.mjs",
|
|
64
|
+
"test:guard:fixtures:all": "pnpm build && node scripts/guard-fixture-suite.mjs",
|
|
65
|
+
"test:guard:fixtures:full": "pnpm build && node scripts/guard-fixture-suite.mjs --all",
|
|
66
|
+
"test:guard:concurrent": "pnpm build && node scripts/guard-concurrent-state.mjs",
|
|
63
67
|
"test:guard:pr": "pnpm build && node scripts/guard-pr-review.mjs",
|
|
68
|
+
"test:guard:benchmark": "pnpm build && node scripts/guard-benchmark.mjs",
|
|
64
69
|
"test:guard:golden": "pnpm build && node scripts/guard-golden-demo.mjs",
|
|
65
70
|
"test:watch": "vitest",
|
|
66
71
|
"typecheck": "tsc --noEmit",
|
|
@@ -95,23 +100,23 @@
|
|
|
95
100
|
"semver": "^7.8.5",
|
|
96
101
|
"ts-morph": "^28.0.0",
|
|
97
102
|
"typescript": "npm:@typescript/typescript6@^6.0.2",
|
|
98
|
-
"zod": "^4.
|
|
103
|
+
"zod": "^4.6.5"
|
|
99
104
|
},
|
|
100
105
|
"devDependencies": {
|
|
101
106
|
"@js-toolkit/config-utils": "^1.0.1",
|
|
102
|
-
"@js-toolkit/eslint-config": "^1.
|
|
107
|
+
"@js-toolkit/eslint-config": "^1.3.0",
|
|
103
108
|
"@js-toolkit/prettier-config": "^1.0.1",
|
|
104
109
|
"@js-toolkit/tsconfig": "^1.0.1",
|
|
105
|
-
"@release-it/conventional-changelog": "^12.0.
|
|
106
|
-
"@types/node": "^26.
|
|
110
|
+
"@release-it/conventional-changelog": "^12.0.2",
|
|
111
|
+
"@types/node": "^26.6.1",
|
|
107
112
|
"@typescript/native": "npm:typescript@^7.0.2",
|
|
108
|
-
"@vitest/coverage-v8": "^
|
|
109
|
-
"eslint": "^
|
|
113
|
+
"@vitest/coverage-v8": "^5.0.1",
|
|
114
|
+
"eslint": "^10.10.0",
|
|
110
115
|
"eslint-config-prettier": "^10.1.8",
|
|
111
116
|
"eslint-plugin-prettier": "^5.5.6",
|
|
112
|
-
"prettier": "^3.9.
|
|
113
|
-
"release-it": "^21.0.
|
|
114
|
-
"typescript-eslint": "^8.
|
|
115
|
-
"vitest": "^
|
|
117
|
+
"prettier": "^3.9.7",
|
|
118
|
+
"release-it": "^21.0.3",
|
|
119
|
+
"typescript-eslint": "^8.70.0",
|
|
120
|
+
"vitest": "^5.0.1"
|
|
116
121
|
}
|
|
117
122
|
}
|