@codapult/guard 0.2.0 → 0.4.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 +73 -1
- package/dist/adapters/command.d.ts +10 -8
- package/dist/adapters/command.js +23 -2
- package/dist/adapters/project-checks.d.ts +23 -20
- package/dist/cli/commands/guard.d.ts +53 -54
- package/dist/cli/commands/guard.js +155 -32
- package/dist/cli/index.js +41 -5
- package/dist/core/analysis/doctor.d.ts +1 -1
- package/dist/core/analysis/doctor.js +19 -6
- package/dist/core/discovery/discovery.d.ts +27 -13
- package/dist/core/discovery/discovery.js +182 -29
- package/dist/core/errors.d.ts +11 -0
- package/dist/core/errors.js +11 -0
- package/dist/core/guard.d.ts +134 -69
- package/dist/core/guard.js +767 -145
- package/dist/core/history/runs.d.ts +28 -0
- package/dist/core/history/runs.js +59 -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 +81 -0
- package/dist/core/policy/schemas.js +46 -17
- package/dist/core/verification/verify.d.ts +21 -12
- package/dist/core/verification/verify.js +81 -8
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/mcp/resources.js +64 -29
- package/dist/mcp/tools/guard.js +300 -52
- package/package.json +12 -7
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const GUARD_RUNS_DIR: string;
|
|
2
|
+
export interface GuardRunStage {
|
|
3
|
+
durationMs: number;
|
|
4
|
+
status: 'ok' | 'fail' | 'skipped';
|
|
5
|
+
detail?: string | undefined;
|
|
6
|
+
}
|
|
7
|
+
export interface GuardRunManifest {
|
|
8
|
+
version: 1;
|
|
9
|
+
runId: string;
|
|
10
|
+
command: string;
|
|
11
|
+
startedAt: string;
|
|
12
|
+
completedAt: string;
|
|
13
|
+
durationMs: number;
|
|
14
|
+
outcome: 'pass' | 'fail' | 'warning' | 'needs-review' | 'not-configured';
|
|
15
|
+
gate: string;
|
|
16
|
+
stages: Record<string, GuardRunStage>;
|
|
17
|
+
commit?: string | undefined;
|
|
18
|
+
}
|
|
19
|
+
export interface GuardRunContext {
|
|
20
|
+
runId: string;
|
|
21
|
+
startedAt: string;
|
|
22
|
+
startedAtMs: number;
|
|
23
|
+
stages: Record<string, GuardRunStage>;
|
|
24
|
+
}
|
|
25
|
+
export declare function startGuardRun(): GuardRunContext;
|
|
26
|
+
export declare function recordGuardRunStage(context: GuardRunContext, name: string, status: GuardRunStage['status'], startedAtMs: number, detail?: string): void;
|
|
27
|
+
export declare function writeGuardRun(root: string, manifest: GuardRunManifest): void;
|
|
28
|
+
export declare function finishGuardRun(root: string, context: GuardRunContext, outcome: GuardRunManifest['outcome'], gate: string, command?: string): GuardRunManifest;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { mkdirSync, renameSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import { config } from '../config.js';
|
|
6
|
+
export const GUARD_RUNS_DIR = `.${config.appName}/guard/history/runs`;
|
|
7
|
+
export function startGuardRun() {
|
|
8
|
+
return {
|
|
9
|
+
runId: `guard-${randomUUID()}`,
|
|
10
|
+
startedAt: new Date().toISOString(),
|
|
11
|
+
startedAtMs: Date.now(),
|
|
12
|
+
stages: {},
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
export function recordGuardRunStage(context, name, status, startedAtMs, detail) {
|
|
16
|
+
context.stages[name] = {
|
|
17
|
+
durationMs: Math.max(0, Date.now() - startedAtMs),
|
|
18
|
+
status,
|
|
19
|
+
...(detail ? { detail } : {}),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
export function writeGuardRun(root, manifest) {
|
|
23
|
+
const directory = resolve(root, GUARD_RUNS_DIR);
|
|
24
|
+
mkdirSync(directory, { recursive: true });
|
|
25
|
+
const path = resolve(directory, `${manifest.runId}.json`);
|
|
26
|
+
const temporaryPath = `${path}.tmp-${process.pid}-${manifest.runId}`;
|
|
27
|
+
writeFileSync(temporaryPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
28
|
+
renameSync(temporaryPath, path);
|
|
29
|
+
}
|
|
30
|
+
export function finishGuardRun(root, context, outcome, gate, command = 'verify') {
|
|
31
|
+
let commit;
|
|
32
|
+
try {
|
|
33
|
+
commit = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, stdio: 'pipe' })
|
|
34
|
+
.toString()
|
|
35
|
+
.trim();
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
commit = undefined;
|
|
39
|
+
}
|
|
40
|
+
const manifest = {
|
|
41
|
+
version: 1,
|
|
42
|
+
runId: context.runId,
|
|
43
|
+
command,
|
|
44
|
+
startedAt: context.startedAt,
|
|
45
|
+
completedAt: new Date().toISOString(),
|
|
46
|
+
durationMs: Math.max(0, Date.now() - context.startedAtMs),
|
|
47
|
+
outcome,
|
|
48
|
+
gate,
|
|
49
|
+
stages: context.stages,
|
|
50
|
+
...(commit ? { commit } : {}),
|
|
51
|
+
};
|
|
52
|
+
try {
|
|
53
|
+
writeGuardRun(root, manifest);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Verification must not fail because its local diagnostic record cannot be written.
|
|
57
|
+
}
|
|
58
|
+
return manifest;
|
|
59
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/** Shared domain vocabulary used by Guard core and its adapters. */
|
|
2
|
+
export type GuardSeverity = 'error' | 'warning' | 'info';
|
|
3
|
+
export type GuardRuleKind = 'forbidden-import' | 'client-forbidden-import';
|
|
4
|
+
export type GuardRuleStatus = 'active' | 'proposed';
|
|
5
|
+
export type GuardContractKind = 'guidance' | 'import-boundary' | 'required-call' | 'package-boundary';
|
|
6
|
+
export type GuardBudgetMetric = 'lines' | 'bytes' | 'imports';
|
|
7
|
+
export type GuardToolMode = 'auto' | 'on' | 'off';
|
|
8
|
+
export type GuardApprovalMode = 'local' | 'protected';
|
|
9
|
+
export type GuardAdapterName = 'dependency-graph' | 'security' | 'dependency-hygiene';
|
|
10
|
+
export type ProjectCheck = 'lint' | 'typecheck' | 'test' | 'build';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,4 +1,26 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
export declare const guardBudgetSchema: z.ZodObject<{
|
|
3
|
+
id: z.ZodString;
|
|
4
|
+
description: z.ZodString;
|
|
5
|
+
metric: z.ZodEnum<{
|
|
6
|
+
bytes: "bytes";
|
|
7
|
+
imports: "imports";
|
|
8
|
+
lines: "lines";
|
|
9
|
+
}>;
|
|
10
|
+
scope: z.ZodArray<z.ZodString>;
|
|
11
|
+
limit: z.ZodNumber;
|
|
12
|
+
severity: z.ZodEnum<{
|
|
13
|
+
error: "error";
|
|
14
|
+
info: "info";
|
|
15
|
+
warning: "warning";
|
|
16
|
+
}>;
|
|
17
|
+
reason: z.ZodString;
|
|
18
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
19
|
+
active: "active";
|
|
20
|
+
proposed: "proposed";
|
|
21
|
+
}>>;
|
|
22
|
+
evidence: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
23
|
+
}, z.core.$strip>;
|
|
2
24
|
export declare const guardRuleSchema: z.ZodObject<{
|
|
3
25
|
id: z.ZodString;
|
|
4
26
|
description: z.ZodString;
|
|
@@ -30,6 +52,7 @@ export declare const guardContractSchema: z.ZodObject<{
|
|
|
30
52
|
kind: z.ZodOptional<z.ZodEnum<{
|
|
31
53
|
guidance: "guidance";
|
|
32
54
|
"import-boundary": "import-boundary";
|
|
55
|
+
"package-boundary": "package-boundary";
|
|
33
56
|
"required-call": "required-call";
|
|
34
57
|
}>>;
|
|
35
58
|
severity: z.ZodOptional<z.ZodEnum<{
|
|
@@ -45,6 +68,8 @@ export declare const guardContractSchema: z.ZodObject<{
|
|
|
45
68
|
mustImport: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
46
69
|
mustNotImport: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
47
70
|
mustCall: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
71
|
+
fromPackages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
72
|
+
mustNotImportPackages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
48
73
|
status: z.ZodOptional<z.ZodEnum<{
|
|
49
74
|
active: "active";
|
|
50
75
|
proposed: "proposed";
|
|
@@ -58,6 +83,8 @@ export declare const guardContractSchema: z.ZodObject<{
|
|
|
58
83
|
}, z.core.$strip>;
|
|
59
84
|
export declare const guardConfigSchema: z.ZodObject<{
|
|
60
85
|
version: z.ZodLiteral<1>;
|
|
86
|
+
revision: z.ZodOptional<z.ZodNumber>;
|
|
87
|
+
contentFingerprint: z.ZodOptional<z.ZodString>;
|
|
61
88
|
rules: z.ZodArray<z.ZodObject<{
|
|
62
89
|
id: z.ZodString;
|
|
63
90
|
description: z.ZodString;
|
|
@@ -89,6 +116,7 @@ export declare const guardConfigSchema: z.ZodObject<{
|
|
|
89
116
|
kind: z.ZodOptional<z.ZodEnum<{
|
|
90
117
|
guidance: "guidance";
|
|
91
118
|
"import-boundary": "import-boundary";
|
|
119
|
+
"package-boundary": "package-boundary";
|
|
92
120
|
"required-call": "required-call";
|
|
93
121
|
}>>;
|
|
94
122
|
severity: z.ZodOptional<z.ZodEnum<{
|
|
@@ -104,6 +132,8 @@ export declare const guardConfigSchema: z.ZodObject<{
|
|
|
104
132
|
mustImport: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
105
133
|
mustNotImport: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
106
134
|
mustCall: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
135
|
+
fromPackages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
136
|
+
mustNotImportPackages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
107
137
|
status: z.ZodOptional<z.ZodEnum<{
|
|
108
138
|
active: "active";
|
|
109
139
|
proposed: "proposed";
|
|
@@ -115,6 +145,36 @@ export declare const guardConfigSchema: z.ZodObject<{
|
|
|
115
145
|
}>>;
|
|
116
146
|
evidence: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
117
147
|
}, z.core.$strip>>>;
|
|
148
|
+
budgets: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
149
|
+
id: z.ZodString;
|
|
150
|
+
description: z.ZodString;
|
|
151
|
+
metric: z.ZodEnum<{
|
|
152
|
+
bytes: "bytes";
|
|
153
|
+
imports: "imports";
|
|
154
|
+
lines: "lines";
|
|
155
|
+
}>;
|
|
156
|
+
scope: z.ZodArray<z.ZodString>;
|
|
157
|
+
limit: z.ZodNumber;
|
|
158
|
+
severity: z.ZodEnum<{
|
|
159
|
+
error: "error";
|
|
160
|
+
info: "info";
|
|
161
|
+
warning: "warning";
|
|
162
|
+
}>;
|
|
163
|
+
reason: z.ZodString;
|
|
164
|
+
status: z.ZodOptional<z.ZodEnum<{
|
|
165
|
+
active: "active";
|
|
166
|
+
proposed: "proposed";
|
|
167
|
+
}>>;
|
|
168
|
+
evidence: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
169
|
+
}, z.core.$strip>>>;
|
|
170
|
+
approval: z.ZodOptional<z.ZodObject<{
|
|
171
|
+
mode: z.ZodEnum<{
|
|
172
|
+
local: "local";
|
|
173
|
+
protected: "protected";
|
|
174
|
+
}>;
|
|
175
|
+
allowMcpApproval: z.ZodBoolean;
|
|
176
|
+
requireDistinctActor: z.ZodBoolean;
|
|
177
|
+
}, z.core.$strip>>;
|
|
118
178
|
}, z.core.$strip>;
|
|
119
179
|
export declare const guardContractsFileSchema: z.ZodObject<{
|
|
120
180
|
version: z.ZodLiteral<1>;
|
|
@@ -124,6 +184,7 @@ export declare const guardContractsFileSchema: z.ZodObject<{
|
|
|
124
184
|
kind: z.ZodOptional<z.ZodEnum<{
|
|
125
185
|
guidance: "guidance";
|
|
126
186
|
"import-boundary": "import-boundary";
|
|
187
|
+
"package-boundary": "package-boundary";
|
|
127
188
|
"required-call": "required-call";
|
|
128
189
|
}>>;
|
|
129
190
|
severity: z.ZodOptional<z.ZodEnum<{
|
|
@@ -139,6 +200,8 @@ export declare const guardContractsFileSchema: z.ZodObject<{
|
|
|
139
200
|
mustImport: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
140
201
|
mustNotImport: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
141
202
|
mustCall: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
203
|
+
fromPackages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
204
|
+
mustNotImportPackages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
142
205
|
status: z.ZodOptional<z.ZodEnum<{
|
|
143
206
|
active: "active";
|
|
144
207
|
proposed: "proposed";
|
|
@@ -165,10 +228,18 @@ export declare const guardProposalDecisionSchema: z.ZodObject<{
|
|
|
165
228
|
proposalId: z.ZodOptional<z.ZodString>;
|
|
166
229
|
proposalFingerprint: z.ZodOptional<z.ZodString>;
|
|
167
230
|
revision: z.ZodOptional<z.ZodNumber>;
|
|
231
|
+
source: z.ZodOptional<z.ZodEnum<{
|
|
232
|
+
cli: "cli";
|
|
233
|
+
external: "external";
|
|
234
|
+
mcp: "mcp";
|
|
235
|
+
}>>;
|
|
236
|
+
actor: z.ZodOptional<z.ZodString>;
|
|
237
|
+
commit: z.ZodOptional<z.ZodString>;
|
|
168
238
|
}, z.core.$strip>;
|
|
169
239
|
export declare const guardProposalSchema: z.ZodObject<{
|
|
170
240
|
version: z.ZodLiteral<1>;
|
|
171
241
|
generatedAt: z.ZodString;
|
|
242
|
+
generatedBy: z.ZodOptional<z.ZodString>;
|
|
172
243
|
proposalId: z.ZodOptional<z.ZodString>;
|
|
173
244
|
projectFingerprint: z.ZodOptional<z.ZodString>;
|
|
174
245
|
revision: z.ZodOptional<z.ZodNumber>;
|
|
@@ -204,6 +275,7 @@ export declare const guardProposalSchema: z.ZodObject<{
|
|
|
204
275
|
kind: z.ZodOptional<z.ZodEnum<{
|
|
205
276
|
guidance: "guidance";
|
|
206
277
|
"import-boundary": "import-boundary";
|
|
278
|
+
"package-boundary": "package-boundary";
|
|
207
279
|
"required-call": "required-call";
|
|
208
280
|
}>>;
|
|
209
281
|
severity: z.ZodOptional<z.ZodEnum<{
|
|
@@ -219,6 +291,8 @@ export declare const guardProposalSchema: z.ZodObject<{
|
|
|
219
291
|
mustImport: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
220
292
|
mustNotImport: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
221
293
|
mustCall: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
294
|
+
fromPackages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
295
|
+
mustNotImportPackages: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
222
296
|
status: z.ZodOptional<z.ZodEnum<{
|
|
223
297
|
active: "active";
|
|
224
298
|
proposed: "proposed";
|
|
@@ -245,6 +319,13 @@ export declare const guardProposalSchema: z.ZodObject<{
|
|
|
245
319
|
proposalId: z.ZodOptional<z.ZodString>;
|
|
246
320
|
proposalFingerprint: z.ZodOptional<z.ZodString>;
|
|
247
321
|
revision: z.ZodOptional<z.ZodNumber>;
|
|
322
|
+
source: z.ZodOptional<z.ZodEnum<{
|
|
323
|
+
cli: "cli";
|
|
324
|
+
external: "external";
|
|
325
|
+
mcp: "mcp";
|
|
326
|
+
}>>;
|
|
327
|
+
actor: z.ZodOptional<z.ZodString>;
|
|
328
|
+
commit: z.ZodOptional<z.ZodString>;
|
|
248
329
|
}, z.core.$strip>>>;
|
|
249
330
|
}, z.core.$strip>;
|
|
250
331
|
export declare const guardAgentConfigSchema: z.ZodObject<{
|
|
@@ -1,55 +1,82 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
const severity = z.enum(['error', 'warning', 'info']);
|
|
3
3
|
const status = z.enum(['active', 'proposed']);
|
|
4
|
+
export const guardBudgetSchema = z.object({
|
|
5
|
+
id: z.string().trim().min(1),
|
|
6
|
+
description: z.string().trim().min(1),
|
|
7
|
+
metric: z.enum(['lines', 'bytes', 'imports']),
|
|
8
|
+
scope: z.array(z.string().trim().min(1)).min(1),
|
|
9
|
+
limit: z.number().int().positive(),
|
|
10
|
+
severity,
|
|
11
|
+
reason: z.string().trim().min(1),
|
|
12
|
+
status: status.optional(),
|
|
13
|
+
evidence: z.array(z.string()).optional(),
|
|
14
|
+
});
|
|
4
15
|
export const guardRuleSchema = z.object({
|
|
5
|
-
id: z.string(),
|
|
6
|
-
description: z.string(),
|
|
16
|
+
id: z.string().trim().min(1),
|
|
17
|
+
description: z.string().trim().min(1),
|
|
7
18
|
severity,
|
|
8
19
|
kind: z.enum(['forbidden-import', 'client-forbidden-import']),
|
|
9
|
-
patterns: z.array(z.string()),
|
|
10
|
-
files: z.array(z.string()).optional(),
|
|
20
|
+
patterns: z.array(z.string().trim().min(1)).min(1),
|
|
21
|
+
files: z.array(z.string().trim().min(1)).optional(),
|
|
11
22
|
status: status.optional(),
|
|
12
23
|
confidence: z.enum(['high', 'medium', 'low']).optional(),
|
|
13
24
|
evidence: z.array(z.string()).optional(),
|
|
14
25
|
});
|
|
15
26
|
export const guardContractSchema = z.object({
|
|
16
|
-
id: z.string(),
|
|
17
|
-
statement: z.string(),
|
|
18
|
-
kind: z.enum(['guidance', 'import-boundary', 'required-call']).optional(),
|
|
27
|
+
id: z.string().trim().min(1),
|
|
28
|
+
statement: z.string().trim().min(1),
|
|
29
|
+
kind: z.enum(['guidance', 'import-boundary', 'required-call', 'package-boundary']).optional(),
|
|
19
30
|
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(),
|
|
31
|
+
scope: z.array(z.string().trim().min(1)).optional(),
|
|
32
|
+
entrypoints: z.array(z.string().trim().min(1)).optional(),
|
|
33
|
+
exclude: z.array(z.string().trim().min(1)).optional(),
|
|
34
|
+
guidance: z.array(z.string().trim().min(1)).optional(),
|
|
35
|
+
references: z.array(z.string().trim().min(1)).optional(),
|
|
36
|
+
mustImport: z.array(z.string().trim().min(1)).optional(),
|
|
37
|
+
mustNotImport: z.array(z.string().trim().min(1)).optional(),
|
|
38
|
+
mustCall: z.array(z.string().trim().min(1)).optional(),
|
|
39
|
+
fromPackages: z.array(z.string().trim().min(1)).optional(),
|
|
40
|
+
mustNotImportPackages: z.array(z.string().trim().min(1)).optional(),
|
|
28
41
|
status: status.optional(),
|
|
29
42
|
confidence: z.enum(['high', 'medium', 'low']).optional(),
|
|
30
43
|
evidence: z.array(z.string()).optional(),
|
|
31
44
|
});
|
|
32
45
|
export const guardConfigSchema = z.object({
|
|
33
46
|
version: z.literal(1),
|
|
47
|
+
revision: z.number().int().nonnegative().optional(),
|
|
48
|
+
contentFingerprint: z.string().optional(),
|
|
34
49
|
rules: z.array(guardRuleSchema),
|
|
35
50
|
contracts: z.array(guardContractSchema).optional(),
|
|
51
|
+
budgets: z.array(guardBudgetSchema).optional(),
|
|
52
|
+
approval: z
|
|
53
|
+
.object({
|
|
54
|
+
mode: z.enum(['local', 'protected']),
|
|
55
|
+
allowMcpApproval: z.boolean(),
|
|
56
|
+
requireDistinctActor: z.boolean(),
|
|
57
|
+
})
|
|
58
|
+
.optional(),
|
|
36
59
|
});
|
|
37
60
|
export const guardContractsFileSchema = z.object({
|
|
38
61
|
version: z.literal(1),
|
|
39
62
|
contracts: z.array(guardContractSchema),
|
|
40
63
|
});
|
|
41
64
|
export const guardProposalDecisionSchema = z.object({
|
|
42
|
-
id: z.string(),
|
|
65
|
+
id: z.string().trim().min(1),
|
|
43
66
|
type: z.enum(['rule', 'contract']),
|
|
44
67
|
decision: z.enum(['approved', 'rejected']),
|
|
45
68
|
decidedAt: z.string(),
|
|
46
69
|
proposalId: z.string().optional(),
|
|
47
70
|
proposalFingerprint: z.string().optional(),
|
|
48
71
|
revision: z.number().optional(),
|
|
72
|
+
source: z.enum(['cli', 'mcp', 'external']).optional(),
|
|
73
|
+
actor: z.string().optional(),
|
|
74
|
+
commit: z.string().optional(),
|
|
49
75
|
});
|
|
50
76
|
export const guardProposalSchema = z.object({
|
|
51
77
|
version: z.literal(1),
|
|
52
78
|
generatedAt: z.string(),
|
|
79
|
+
generatedBy: z.string().trim().min(1).optional(),
|
|
53
80
|
proposalId: z.string().optional(),
|
|
54
81
|
projectFingerprint: z.string().optional(),
|
|
55
82
|
revision: z.number().int().positive().optional(),
|
|
@@ -62,7 +89,9 @@ export const guardProposalSchema = z.object({
|
|
|
62
89
|
export const guardAgentConfigSchema = z.object({
|
|
63
90
|
version: z.literal(1),
|
|
64
91
|
tools: z.enum(['auto', 'on', 'off']),
|
|
65
|
-
tooling: z
|
|
92
|
+
tooling: z
|
|
93
|
+
.record(z.string().trim().min(1), z.object({ script: z.string().trim().min(1), enabled: z.boolean() }))
|
|
94
|
+
.default({}),
|
|
66
95
|
completionGate: z.object({
|
|
67
96
|
enabled: z.boolean(),
|
|
68
97
|
maxIterations: z.number().int().min(1).max(10),
|
|
@@ -1,29 +1,38 @@
|
|
|
1
1
|
import { type CommandResult } from '../../adapters/command.js';
|
|
2
|
-
import
|
|
2
|
+
import type { ProjectCheck } from '../model/types.js';
|
|
3
|
+
import { type GuardAdapter, type ProjectCheckResults, type ProjectRuntimeDiagnostics } from '../../adapters/project-checks.js';
|
|
3
4
|
import { type GuardToolMode, type GuardContractIssue, type GuardReport } from '../guard.js';
|
|
5
|
+
import { type GuardImpactAnalysis } from '../analysis/impact.js';
|
|
6
|
+
import { type GuardRunManifest } from '../history/runs.js';
|
|
4
7
|
export type GuardVerificationCheck = ProjectCheck;
|
|
8
|
+
export interface GuardVerificationOptions {
|
|
9
|
+
checks?: GuardVerificationCheck[] | undefined;
|
|
10
|
+
changedOnly?: boolean | undefined;
|
|
11
|
+
timeout?: number | undefined;
|
|
12
|
+
requirement?: string | undefined;
|
|
13
|
+
tools?: GuardToolMode | undefined;
|
|
14
|
+
strict?: boolean | undefined;
|
|
15
|
+
projectChecks?: boolean | undefined;
|
|
16
|
+
}
|
|
5
17
|
export interface GuardVerificationResult {
|
|
6
18
|
status: 'ok' | 'fail' | 'not-configured';
|
|
7
19
|
outcome: 'pass' | 'fail' | 'warning' | 'needs-review' | 'not-configured';
|
|
8
|
-
|
|
20
|
+
errorCode?: 'GUARD_NOT_CONFIGURED' | 'GUARD_CONFIG_INVALID' | undefined;
|
|
21
|
+
recoverable?: boolean | undefined;
|
|
22
|
+
tools?: GuardToolMode | undefined;
|
|
9
23
|
checks: ProjectCheckResults;
|
|
10
24
|
workspaceChecks: Record<string, ProjectCheckResults>;
|
|
11
25
|
adapters: Partial<Record<GuardAdapter, CommandResult>>;
|
|
12
26
|
runtime: ProjectRuntimeDiagnostics;
|
|
13
|
-
|
|
27
|
+
configError?: string | undefined;
|
|
28
|
+
architecture?: GuardReport | undefined;
|
|
29
|
+
impact?: GuardImpactAnalysis | undefined;
|
|
14
30
|
contractIssues: GuardContractIssue[];
|
|
15
31
|
requirement: {
|
|
16
32
|
status: 'delegated-to-review';
|
|
17
33
|
provided: boolean;
|
|
18
34
|
message: string;
|
|
19
35
|
};
|
|
36
|
+
run: GuardRunManifest;
|
|
20
37
|
}
|
|
21
|
-
export declare function runGuardVerification(root: string, options?:
|
|
22
|
-
checks?: GuardVerificationCheck[];
|
|
23
|
-
changedOnly?: boolean;
|
|
24
|
-
timeout?: number;
|
|
25
|
-
requirement?: string;
|
|
26
|
-
tools?: GuardToolMode;
|
|
27
|
-
strict?: boolean;
|
|
28
|
-
projectChecks?: boolean;
|
|
29
|
-
}): GuardVerificationResult;
|
|
38
|
+
export declare function runGuardVerification(root: string, options?: GuardVerificationOptions): GuardVerificationResult;
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import {} from '../../adapters/command.js';
|
|
1
2
|
import { runProjectAdapters, runProjectChecks, runWorkspaceProjectChecks, inspectProjectRuntime, } from '../../adapters/project-checks.js';
|
|
2
|
-
import { loadBaseline, loadGuardAgentConfig, loadGuardConfig, scanGuard, classifyGuardOutcome,
|
|
3
|
+
import { loadBaseline, loadGuardAgentConfig, loadGuardConfig, scanGuard, classifyGuardOutcome, validateGuardPolicy, } from '../guard.js';
|
|
4
|
+
import { analyzeProjectImpact } from '../analysis/impact.js';
|
|
3
5
|
import { discoverProject } from '../discovery/discovery.js';
|
|
6
|
+
import { finishGuardRun, recordGuardRunStage, startGuardRun, } from '../history/runs.js';
|
|
4
7
|
export function runGuardVerification(root, options = {}) {
|
|
5
|
-
const
|
|
8
|
+
const runContext = startGuardRun();
|
|
6
9
|
const requirement = {
|
|
7
10
|
status: 'delegated-to-review',
|
|
8
11
|
provided: Boolean(options.requirement?.trim()),
|
|
@@ -10,28 +13,64 @@ export function runGuardVerification(root, options = {}) {
|
|
|
10
13
|
? 'Requirement text is included in semantic review input; deterministic verify does not judge natural-language acceptance criteria.'
|
|
11
14
|
: 'Requirement satisfaction is evaluated by guard review using the task and diff.',
|
|
12
15
|
};
|
|
16
|
+
const runtimeStartedAt = Date.now();
|
|
13
17
|
const runtime = inspectProjectRuntime(root);
|
|
14
|
-
|
|
18
|
+
recordGuardRunStage(runContext, 'runtime', 'ok', runtimeStartedAt);
|
|
19
|
+
let config;
|
|
20
|
+
try {
|
|
21
|
+
config = loadGuardConfig(root);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
const run = finishGuardRun(root, runContext, 'fail', 'configuration');
|
|
25
|
+
return {
|
|
26
|
+
status: 'fail',
|
|
27
|
+
outcome: 'fail',
|
|
28
|
+
errorCode: 'GUARD_CONFIG_INVALID',
|
|
29
|
+
recoverable: true,
|
|
30
|
+
checks: {},
|
|
31
|
+
workspaceChecks: {},
|
|
32
|
+
adapters: {},
|
|
33
|
+
runtime,
|
|
34
|
+
configError: error instanceof Error ? error.message : String(error),
|
|
35
|
+
contractIssues: [],
|
|
36
|
+
requirement,
|
|
37
|
+
run,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (!config) {
|
|
41
|
+
const run = finishGuardRun(root, runContext, 'not-configured', 'configuration');
|
|
15
42
|
return {
|
|
16
43
|
status: 'not-configured',
|
|
17
44
|
outcome: 'not-configured',
|
|
45
|
+
errorCode: 'GUARD_NOT_CONFIGURED',
|
|
46
|
+
recoverable: true,
|
|
18
47
|
checks: {},
|
|
19
48
|
workspaceChecks: {},
|
|
20
49
|
adapters: {},
|
|
21
50
|
runtime,
|
|
22
51
|
contractIssues: [],
|
|
23
52
|
requirement,
|
|
53
|
+
run,
|
|
24
54
|
};
|
|
55
|
+
}
|
|
25
56
|
const agentConfig = loadGuardAgentConfig(root);
|
|
26
57
|
const projectChecks = options.projectChecks ?? agentConfig.completionGate.projectChecks;
|
|
27
58
|
const checks = projectChecks ? (options.checks ?? agentConfig.completionGate.checks) : [];
|
|
59
|
+
const checksStartedAt = Date.now();
|
|
28
60
|
const results = runProjectChecks(root, checks, { timeout: options.timeout });
|
|
61
|
+
recordGuardRunStage(runContext, 'project-checks', projectChecks ? 'ok' : 'skipped', checksStartedAt);
|
|
62
|
+
const discoveryStartedAt = Date.now();
|
|
29
63
|
const workspaceModel = discoverProject(root);
|
|
64
|
+
const impact = analyzeProjectImpact(workspaceModel, options.changedOnly === false
|
|
65
|
+
? workspaceModel.modules.map((module) => module.path)
|
|
66
|
+
: workspaceModel.git.changedFiles, config.contracts ?? []);
|
|
30
67
|
const workspaceChecks = runWorkspaceProjectChecks(root, workspaceModel.project.workspacePackages ?? [], checks, {
|
|
31
68
|
timeout: options.timeout,
|
|
32
69
|
rootResults: results,
|
|
33
70
|
});
|
|
71
|
+
recordGuardRunStage(runContext, 'discovery-impact', 'ok', discoveryStartedAt);
|
|
34
72
|
const toolMode = options.tools ?? agentConfig.tools;
|
|
73
|
+
const adaptersStartedAt = Date.now();
|
|
35
74
|
const adapters = toolMode === 'off'
|
|
36
75
|
? {}
|
|
37
76
|
: runProjectAdapters(root, {
|
|
@@ -39,12 +78,15 @@ export function runGuardVerification(root, options = {}) {
|
|
|
39
78
|
timeout: options.timeout,
|
|
40
79
|
tooling: agentConfig.tooling,
|
|
41
80
|
});
|
|
81
|
+
recordGuardRunStage(runContext, 'adapters', toolMode === 'off' ? 'skipped' : 'ok', adaptersStartedAt);
|
|
82
|
+
const architectureStartedAt = Date.now();
|
|
42
83
|
const architecture = scanGuard(root, config, {
|
|
43
84
|
changedOnly: options.changedOnly,
|
|
44
85
|
baseline: loadBaseline(root),
|
|
45
86
|
includeArchitectureInsights: true,
|
|
46
87
|
});
|
|
47
|
-
const contractIssues =
|
|
88
|
+
const contractIssues = validateGuardPolicy(root, config);
|
|
89
|
+
recordGuardRunStage(runContext, 'architecture-policy', 'ok', architectureStartedAt);
|
|
48
90
|
const commandFailed = Object.values(results).some((result) => result.status === 'failed');
|
|
49
91
|
const workspaceCommandFailed = Object.values(workspaceChecks).some((packageResults) => Object.values(packageResults).some((result) => result.status === 'failed'));
|
|
50
92
|
const adapterFailed = Object.values(adapters).some((result) => result.status === 'failed');
|
|
@@ -59,19 +101,50 @@ export function runGuardVerification(root, options = {}) {
|
|
|
59
101
|
architectureFailed ||
|
|
60
102
|
contractIssues.length > 0 ||
|
|
61
103
|
missingRequiredTools;
|
|
104
|
+
const projectChecksFailed = commandFailed || workspaceCommandFailed || (projectChecks && !runtime.compatible);
|
|
105
|
+
const adaptersFailed = adapterFailed || missingRequiredTools;
|
|
106
|
+
runContext.stages['project-checks'] = {
|
|
107
|
+
...runContext.stages['project-checks'],
|
|
108
|
+
status: projectChecks ? (projectChecksFailed ? 'fail' : 'ok') : 'skipped',
|
|
109
|
+
};
|
|
110
|
+
runContext.stages.adapters = {
|
|
111
|
+
...runContext.stages.adapters,
|
|
112
|
+
status: toolMode === 'off' ? 'skipped' : adaptersFailed ? 'fail' : 'ok',
|
|
113
|
+
};
|
|
114
|
+
runContext.stages['architecture-policy'] = {
|
|
115
|
+
...runContext.stages['architecture-policy'],
|
|
116
|
+
status: architectureFailed || contractIssues.length > 0 ? 'fail' : 'ok',
|
|
117
|
+
};
|
|
118
|
+
const outcome = classifyGuardOutcome({
|
|
119
|
+
errors: failed ? 1 : 0,
|
|
120
|
+
warnings: architectureWarnings ? 1 : 0,
|
|
121
|
+
});
|
|
122
|
+
const gate = architectureFailed
|
|
123
|
+
? 'architecture'
|
|
124
|
+
: contractIssues.length > 0
|
|
125
|
+
? 'contracts'
|
|
126
|
+
: commandFailed || workspaceCommandFailed
|
|
127
|
+
? 'project-checks'
|
|
128
|
+
: adapterFailed
|
|
129
|
+
? 'adapters'
|
|
130
|
+
: missingRequiredTools
|
|
131
|
+
? 'required-tools'
|
|
132
|
+
: !runtime.compatible && projectChecks
|
|
133
|
+
? 'runtime'
|
|
134
|
+
: 'none';
|
|
135
|
+
const run = finishGuardRun(root, runContext, outcome, gate);
|
|
62
136
|
return {
|
|
63
137
|
status: failed ? 'fail' : 'ok',
|
|
64
|
-
outcome
|
|
65
|
-
errors: failed ? 1 : 0,
|
|
66
|
-
warnings: architectureWarnings ? 1 : 0,
|
|
67
|
-
}),
|
|
138
|
+
outcome,
|
|
68
139
|
tools: toolMode,
|
|
69
140
|
checks: results,
|
|
70
141
|
workspaceChecks,
|
|
71
142
|
adapters,
|
|
72
143
|
runtime,
|
|
73
144
|
architecture,
|
|
145
|
+
impact,
|
|
74
146
|
contractIssues,
|
|
75
147
|
requirement,
|
|
148
|
+
run,
|
|
76
149
|
};
|
|
77
150
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
/** Public, framework-agnostic Guard API. */
|
|
2
|
+
export * from './core/model/types.js';
|
|
2
3
|
export * from './core/guard.js';
|
|
4
|
+
export * from './core/errors.js';
|
|
3
5
|
export * from './core/discovery/discovery.js';
|
|
4
6
|
export { runGuardVerification } from './core/verification/verify.js';
|
|
5
7
|
export type { GuardVerificationCheck, GuardVerificationResult, } from './core/verification/verify.js';
|
|
6
8
|
export * from './core/history/history.js';
|
|
9
|
+
export * from './core/history/runs.js';
|
|
7
10
|
export * from './core/output/sarif.js';
|
|
8
11
|
export * from './core/analysis/doctor.js';
|
|
9
12
|
export * from './core/analysis/packs.js';
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
/** Public, framework-agnostic Guard API. */
|
|
2
|
+
export * from './core/model/types.js';
|
|
2
3
|
export * from './core/guard.js';
|
|
4
|
+
export * from './core/errors.js';
|
|
3
5
|
export * from './core/discovery/discovery.js';
|
|
4
6
|
export { runGuardVerification } from './core/verification/verify.js';
|
|
5
7
|
export * from './core/history/history.js';
|
|
8
|
+
export * from './core/history/runs.js';
|
|
6
9
|
export * from './core/output/sarif.js';
|
|
7
10
|
export * from './core/analysis/doctor.js';
|
|
8
11
|
export * from './core/analysis/packs.js';
|