@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
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export declare const GUARD_RUNS_DIR: string;
|
|
2
|
+
export declare const GUARD_RUN_RETENTION = 200;
|
|
3
|
+
export interface GuardRunStage {
|
|
4
|
+
durationMs: number;
|
|
5
|
+
status: 'ok' | 'fail' | 'skipped';
|
|
6
|
+
detail?: string | undefined;
|
|
7
|
+
}
|
|
8
|
+
export interface GuardRunManifest {
|
|
9
|
+
version: 1;
|
|
10
|
+
runId: string;
|
|
11
|
+
command: string;
|
|
12
|
+
startedAt: string;
|
|
13
|
+
completedAt: string;
|
|
14
|
+
durationMs: number;
|
|
15
|
+
outcome: 'pass' | 'fail' | 'warning' | 'needs-review' | 'not-configured';
|
|
16
|
+
gate: string;
|
|
17
|
+
stages: Record<string, GuardRunStage>;
|
|
18
|
+
commit?: string | undefined;
|
|
19
|
+
}
|
|
20
|
+
export interface GuardRunContext {
|
|
21
|
+
runId: string;
|
|
22
|
+
startedAt: string;
|
|
23
|
+
startedAtMs: number;
|
|
24
|
+
stages: Record<string, GuardRunStage>;
|
|
25
|
+
}
|
|
26
|
+
export interface GuardRunSummary {
|
|
27
|
+
total: number;
|
|
28
|
+
passed: number;
|
|
29
|
+
failed: number;
|
|
30
|
+
warnings: number;
|
|
31
|
+
averageDurationMs: number;
|
|
32
|
+
last?: GuardRunManifest | undefined;
|
|
33
|
+
}
|
|
34
|
+
export declare class GuardRunManifestError extends Error {
|
|
35
|
+
constructor();
|
|
36
|
+
}
|
|
37
|
+
export declare function startGuardRun(): GuardRunContext;
|
|
38
|
+
export declare function recordGuardRunStage(context: GuardRunContext, name: string, status: GuardRunStage['status'], startedAtMs: number, detail?: string): void;
|
|
39
|
+
export declare function writeGuardRun(root: string, manifest: GuardRunManifest): void;
|
|
40
|
+
/** Reads locally persisted run manifests without reaching a remote service. */
|
|
41
|
+
export declare function listGuardRuns(root: string, limit?: number): GuardRunManifest[];
|
|
42
|
+
export declare function summarizeGuardRuns(root: string, limit?: number): GuardRunSummary;
|
|
43
|
+
export declare function finishGuardRun(root: string, context: GuardRunContext, outcome: GuardRunManifest['outcome'], gate: string, command?: string): GuardRunManifest;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { execFileSync } from 'node:child_process';
|
|
3
|
+
import { mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, 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 const GUARD_RUN_RETENTION = 200;
|
|
8
|
+
export class GuardRunManifestError extends Error {
|
|
9
|
+
constructor() {
|
|
10
|
+
super('Guard run manifest is invalid or contains an unsafe run ID.');
|
|
11
|
+
this.name = 'GuardRunManifestError';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function isGuardRunManifest(value) {
|
|
15
|
+
if (value === null || typeof value !== 'object')
|
|
16
|
+
return false;
|
|
17
|
+
const manifest = value;
|
|
18
|
+
return (manifest.version === 1 &&
|
|
19
|
+
typeof manifest.runId === 'string' &&
|
|
20
|
+
/^guard-[A-Za-z0-9-]+$/.test(manifest.runId) &&
|
|
21
|
+
typeof manifest.command === 'string' &&
|
|
22
|
+
typeof manifest.startedAt === 'string' &&
|
|
23
|
+
Number.isFinite(Date.parse(manifest.startedAt)) &&
|
|
24
|
+
typeof manifest.completedAt === 'string' &&
|
|
25
|
+
Number.isFinite(Date.parse(manifest.completedAt)) &&
|
|
26
|
+
typeof manifest.durationMs === 'number' &&
|
|
27
|
+
Number.isFinite(manifest.durationMs) &&
|
|
28
|
+
manifest.durationMs >= 0 &&
|
|
29
|
+
(manifest.outcome === 'pass' ||
|
|
30
|
+
manifest.outcome === 'fail' ||
|
|
31
|
+
manifest.outcome === 'warning' ||
|
|
32
|
+
manifest.outcome === 'needs-review' ||
|
|
33
|
+
manifest.outcome === 'not-configured') &&
|
|
34
|
+
typeof manifest.gate === 'string' &&
|
|
35
|
+
manifest.stages !== null &&
|
|
36
|
+
typeof manifest.stages === 'object');
|
|
37
|
+
}
|
|
38
|
+
export function startGuardRun() {
|
|
39
|
+
return {
|
|
40
|
+
runId: `guard-${randomUUID()}`,
|
|
41
|
+
startedAt: new Date().toISOString(),
|
|
42
|
+
startedAtMs: Date.now(),
|
|
43
|
+
stages: {},
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export function recordGuardRunStage(context, name, status, startedAtMs, detail) {
|
|
47
|
+
context.stages[name] = {
|
|
48
|
+
durationMs: Math.max(0, Date.now() - startedAtMs),
|
|
49
|
+
status,
|
|
50
|
+
...(detail ? { detail } : {}),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
function pruneGuardRuns(root) {
|
|
54
|
+
const directory = resolve(root, GUARD_RUNS_DIR);
|
|
55
|
+
try {
|
|
56
|
+
const files = readdirSync(directory)
|
|
57
|
+
.filter((file) => file.endsWith('.json'))
|
|
58
|
+
.map((file) => ({
|
|
59
|
+
file,
|
|
60
|
+
modifiedAt: statSync(resolve(directory, file)).mtimeMs,
|
|
61
|
+
}))
|
|
62
|
+
.sort((left, right) => right.modifiedAt - left.modifiedAt);
|
|
63
|
+
for (const entry of files.slice(GUARD_RUN_RETENTION)) {
|
|
64
|
+
try {
|
|
65
|
+
unlinkSync(resolve(directory, entry.file));
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Retention is best effort; a concurrent reader may own the file.
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
// Diagnostics must never fail because retention cannot be completed.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
export function writeGuardRun(root, manifest) {
|
|
77
|
+
if (!isGuardRunManifest(manifest))
|
|
78
|
+
throw new GuardRunManifestError();
|
|
79
|
+
const directory = resolve(root, GUARD_RUNS_DIR);
|
|
80
|
+
mkdirSync(directory, { recursive: true });
|
|
81
|
+
const path = resolve(directory, `${manifest.runId}.json`);
|
|
82
|
+
const temporaryPath = `${path}.tmp-${process.pid}-${manifest.runId}`;
|
|
83
|
+
writeFileSync(temporaryPath, `${JSON.stringify(manifest, null, 2)}\n`, 'utf8');
|
|
84
|
+
renameSync(temporaryPath, path);
|
|
85
|
+
pruneGuardRuns(root);
|
|
86
|
+
}
|
|
87
|
+
/** Reads locally persisted run manifests without reaching a remote service. */
|
|
88
|
+
export function listGuardRuns(root, limit = 20) {
|
|
89
|
+
if (!Number.isInteger(limit) || limit < 1)
|
|
90
|
+
return [];
|
|
91
|
+
const boundedLimit = Math.min(limit, 1_000);
|
|
92
|
+
const directory = resolve(root, GUARD_RUNS_DIR);
|
|
93
|
+
try {
|
|
94
|
+
return readdirSync(directory)
|
|
95
|
+
.filter((file) => file.endsWith('.json'))
|
|
96
|
+
.map((file) => {
|
|
97
|
+
try {
|
|
98
|
+
const value = JSON.parse(readFileSync(resolve(directory, file), 'utf8'));
|
|
99
|
+
return isGuardRunManifest(value) ? value : undefined;
|
|
100
|
+
}
|
|
101
|
+
catch {
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
})
|
|
105
|
+
.filter((manifest) => manifest !== undefined)
|
|
106
|
+
.sort((left, right) => right.completedAt.localeCompare(left.completedAt))
|
|
107
|
+
.slice(0, boundedLimit);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
export function summarizeGuardRuns(root, limit = 100) {
|
|
114
|
+
const runs = listGuardRuns(root, limit);
|
|
115
|
+
const total = runs.length;
|
|
116
|
+
return {
|
|
117
|
+
total,
|
|
118
|
+
passed: runs.filter((run) => run.outcome === 'pass').length,
|
|
119
|
+
failed: runs.filter((run) => run.outcome === 'fail').length,
|
|
120
|
+
warnings: runs.filter((run) => run.outcome === 'warning').length,
|
|
121
|
+
averageDurationMs: total === 0 ? 0 : Math.round(runs.reduce((sum, run) => sum + run.durationMs, 0) / total),
|
|
122
|
+
...(total > 0 ? { last: runs[0] } : {}),
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
export function finishGuardRun(root, context, outcome, gate, command = 'verify') {
|
|
126
|
+
let commit;
|
|
127
|
+
try {
|
|
128
|
+
commit = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, stdio: 'pipe' })
|
|
129
|
+
.toString()
|
|
130
|
+
.trim();
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
commit = undefined;
|
|
134
|
+
}
|
|
135
|
+
const manifest = {
|
|
136
|
+
version: 1,
|
|
137
|
+
runId: context.runId,
|
|
138
|
+
command,
|
|
139
|
+
startedAt: context.startedAt,
|
|
140
|
+
completedAt: new Date().toISOString(),
|
|
141
|
+
durationMs: Math.max(0, Date.now() - context.startedAtMs),
|
|
142
|
+
outcome,
|
|
143
|
+
gate,
|
|
144
|
+
stages: context.stages,
|
|
145
|
+
...(commit ? { commit } : {}),
|
|
146
|
+
};
|
|
147
|
+
try {
|
|
148
|
+
writeGuardRun(root, manifest);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Verification must not fail because its local diagnostic record cannot be written.
|
|
152
|
+
}
|
|
153
|
+
return manifest;
|
|
154
|
+
}
|
|
@@ -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' | 'sast' | 'secret-scanning' | 'dependency-audit';
|
|
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;
|
|
@@ -61,6 +83,8 @@ export declare const guardContractSchema: z.ZodObject<{
|
|
|
61
83
|
}, z.core.$strip>;
|
|
62
84
|
export declare const guardConfigSchema: z.ZodObject<{
|
|
63
85
|
version: z.ZodLiteral<1>;
|
|
86
|
+
revision: z.ZodOptional<z.ZodNumber>;
|
|
87
|
+
contentFingerprint: z.ZodOptional<z.ZodString>;
|
|
64
88
|
rules: z.ZodArray<z.ZodObject<{
|
|
65
89
|
id: z.ZodString;
|
|
66
90
|
description: z.ZodString;
|
|
@@ -121,6 +145,36 @@ export declare const guardConfigSchema: z.ZodObject<{
|
|
|
121
145
|
}>>;
|
|
122
146
|
evidence: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
123
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>>;
|
|
124
178
|
}, z.core.$strip>;
|
|
125
179
|
export declare const guardContractsFileSchema: z.ZodObject<{
|
|
126
180
|
version: z.ZodLiteral<1>;
|
|
@@ -174,10 +228,18 @@ export declare const guardProposalDecisionSchema: z.ZodObject<{
|
|
|
174
228
|
proposalId: z.ZodOptional<z.ZodString>;
|
|
175
229
|
proposalFingerprint: z.ZodOptional<z.ZodString>;
|
|
176
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>;
|
|
177
238
|
}, z.core.$strip>;
|
|
178
239
|
export declare const guardProposalSchema: z.ZodObject<{
|
|
179
240
|
version: z.ZodLiteral<1>;
|
|
180
241
|
generatedAt: z.ZodString;
|
|
242
|
+
generatedBy: z.ZodOptional<z.ZodString>;
|
|
181
243
|
proposalId: z.ZodOptional<z.ZodString>;
|
|
182
244
|
projectFingerprint: z.ZodOptional<z.ZodString>;
|
|
183
245
|
revision: z.ZodOptional<z.ZodNumber>;
|
|
@@ -257,6 +319,13 @@ export declare const guardProposalSchema: z.ZodObject<{
|
|
|
257
319
|
proposalId: z.ZodOptional<z.ZodString>;
|
|
258
320
|
proposalFingerprint: z.ZodOptional<z.ZodString>;
|
|
259
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>;
|
|
260
329
|
}, z.core.$strip>>>;
|
|
261
330
|
}, z.core.$strip>;
|
|
262
331
|
export declare const guardAgentConfigSchema: z.ZodObject<{
|
|
@@ -1,6 +1,17 @@
|
|
|
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
16
|
id: z.string().trim().min(1),
|
|
6
17
|
description: z.string().trim().min(1),
|
|
@@ -33,8 +44,18 @@ export const guardContractSchema = z.object({
|
|
|
33
44
|
});
|
|
34
45
|
export const guardConfigSchema = z.object({
|
|
35
46
|
version: z.literal(1),
|
|
47
|
+
revision: z.number().int().nonnegative().optional(),
|
|
48
|
+
contentFingerprint: z.string().optional(),
|
|
36
49
|
rules: z.array(guardRuleSchema),
|
|
37
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(),
|
|
38
59
|
});
|
|
39
60
|
export const guardContractsFileSchema = z.object({
|
|
40
61
|
version: z.literal(1),
|
|
@@ -48,10 +69,14 @@ export const guardProposalDecisionSchema = z.object({
|
|
|
48
69
|
proposalId: z.string().optional(),
|
|
49
70
|
proposalFingerprint: z.string().optional(),
|
|
50
71
|
revision: z.number().optional(),
|
|
72
|
+
source: z.enum(['cli', 'mcp', 'external']).optional(),
|
|
73
|
+
actor: z.string().optional(),
|
|
74
|
+
commit: z.string().optional(),
|
|
51
75
|
});
|
|
52
76
|
export const guardProposalSchema = z.object({
|
|
53
77
|
version: z.literal(1),
|
|
54
78
|
generatedAt: z.string(),
|
|
79
|
+
generatedBy: z.string().trim().min(1).optional(),
|
|
55
80
|
proposalId: z.string().optional(),
|
|
56
81
|
projectFingerprint: z.string().optional(),
|
|
57
82
|
revision: z.number().int().positive().optional(),
|
|
@@ -1,34 +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';
|
|
4
5
|
import { type GuardImpactAnalysis } from '../analysis/impact.js';
|
|
6
|
+
import { type GuardRunManifest } from '../history/runs.js';
|
|
5
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
|
+
}
|
|
6
17
|
export interface GuardVerificationResult {
|
|
7
18
|
status: 'ok' | 'fail' | 'not-configured';
|
|
8
19
|
outcome: 'pass' | 'fail' | 'warning' | 'needs-review' | 'not-configured';
|
|
9
|
-
errorCode?: 'GUARD_NOT_CONFIGURED' | 'GUARD_CONFIG_INVALID';
|
|
10
|
-
recoverable?: boolean;
|
|
11
|
-
tools?: GuardToolMode;
|
|
20
|
+
errorCode?: 'GUARD_NOT_CONFIGURED' | 'GUARD_CONFIG_INVALID' | undefined;
|
|
21
|
+
recoverable?: boolean | undefined;
|
|
22
|
+
tools?: GuardToolMode | undefined;
|
|
12
23
|
checks: ProjectCheckResults;
|
|
13
24
|
workspaceChecks: Record<string, ProjectCheckResults>;
|
|
14
25
|
adapters: Partial<Record<GuardAdapter, CommandResult>>;
|
|
15
26
|
runtime: ProjectRuntimeDiagnostics;
|
|
16
|
-
configError?: string;
|
|
17
|
-
architecture?: GuardReport;
|
|
18
|
-
impact?: GuardImpactAnalysis;
|
|
27
|
+
configError?: string | undefined;
|
|
28
|
+
architecture?: GuardReport | undefined;
|
|
29
|
+
impact?: GuardImpactAnalysis | undefined;
|
|
19
30
|
contractIssues: GuardContractIssue[];
|
|
20
31
|
requirement: {
|
|
21
32
|
status: 'delegated-to-review';
|
|
22
33
|
provided: boolean;
|
|
23
34
|
message: string;
|
|
24
35
|
};
|
|
36
|
+
run: GuardRunManifest;
|
|
25
37
|
}
|
|
26
|
-
export declare function runGuardVerification(root: string, options?:
|
|
27
|
-
checks?: GuardVerificationCheck[];
|
|
28
|
-
changedOnly?: boolean;
|
|
29
|
-
timeout?: number;
|
|
30
|
-
requirement?: string;
|
|
31
|
-
tools?: GuardToolMode;
|
|
32
|
-
strict?: boolean;
|
|
33
|
-
projectChecks?: boolean;
|
|
34
|
-
}): 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';
|
|
3
4
|
import { analyzeProjectImpact } from '../analysis/impact.js';
|
|
4
5
|
import { discoverProject } from '../discovery/discovery.js';
|
|
6
|
+
import { finishGuardRun, recordGuardRunStage, startGuardRun, } from '../history/runs.js';
|
|
5
7
|
export function runGuardVerification(root, options = {}) {
|
|
8
|
+
const runContext = startGuardRun();
|
|
6
9
|
const requirement = {
|
|
7
10
|
status: 'delegated-to-review',
|
|
8
11
|
provided: Boolean(options.requirement?.trim()),
|
|
@@ -10,12 +13,15 @@ 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);
|
|
18
|
+
recordGuardRunStage(runContext, 'runtime', 'ok', runtimeStartedAt);
|
|
14
19
|
let config;
|
|
15
20
|
try {
|
|
16
21
|
config = loadGuardConfig(root);
|
|
17
22
|
}
|
|
18
23
|
catch (error) {
|
|
24
|
+
const run = finishGuardRun(root, runContext, 'fail', 'configuration');
|
|
19
25
|
return {
|
|
20
26
|
status: 'fail',
|
|
21
27
|
outcome: 'fail',
|
|
@@ -28,9 +34,11 @@ export function runGuardVerification(root, options = {}) {
|
|
|
28
34
|
configError: error instanceof Error ? error.message : String(error),
|
|
29
35
|
contractIssues: [],
|
|
30
36
|
requirement,
|
|
37
|
+
run,
|
|
31
38
|
};
|
|
32
39
|
}
|
|
33
|
-
if (!config)
|
|
40
|
+
if (!config) {
|
|
41
|
+
const run = finishGuardRun(root, runContext, 'not-configured', 'configuration');
|
|
34
42
|
return {
|
|
35
43
|
status: 'not-configured',
|
|
36
44
|
outcome: 'not-configured',
|
|
@@ -42,11 +50,16 @@ export function runGuardVerification(root, options = {}) {
|
|
|
42
50
|
runtime,
|
|
43
51
|
contractIssues: [],
|
|
44
52
|
requirement,
|
|
53
|
+
run,
|
|
45
54
|
};
|
|
55
|
+
}
|
|
46
56
|
const agentConfig = loadGuardAgentConfig(root);
|
|
47
57
|
const projectChecks = options.projectChecks ?? agentConfig.completionGate.projectChecks;
|
|
48
58
|
const checks = projectChecks ? (options.checks ?? agentConfig.completionGate.checks) : [];
|
|
59
|
+
const checksStartedAt = Date.now();
|
|
49
60
|
const results = runProjectChecks(root, checks, { timeout: options.timeout });
|
|
61
|
+
recordGuardRunStage(runContext, 'project-checks', projectChecks ? 'ok' : 'skipped', checksStartedAt);
|
|
62
|
+
const discoveryStartedAt = Date.now();
|
|
50
63
|
const workspaceModel = discoverProject(root);
|
|
51
64
|
const impact = analyzeProjectImpact(workspaceModel, options.changedOnly === false
|
|
52
65
|
? workspaceModel.modules.map((module) => module.path)
|
|
@@ -55,7 +68,9 @@ export function runGuardVerification(root, options = {}) {
|
|
|
55
68
|
timeout: options.timeout,
|
|
56
69
|
rootResults: results,
|
|
57
70
|
});
|
|
71
|
+
recordGuardRunStage(runContext, 'discovery-impact', 'ok', discoveryStartedAt);
|
|
58
72
|
const toolMode = options.tools ?? agentConfig.tools;
|
|
73
|
+
const adaptersStartedAt = Date.now();
|
|
59
74
|
const adapters = toolMode === 'off'
|
|
60
75
|
? {}
|
|
61
76
|
: runProjectAdapters(root, {
|
|
@@ -63,12 +78,15 @@ export function runGuardVerification(root, options = {}) {
|
|
|
63
78
|
timeout: options.timeout,
|
|
64
79
|
tooling: agentConfig.tooling,
|
|
65
80
|
});
|
|
81
|
+
recordGuardRunStage(runContext, 'adapters', toolMode === 'off' ? 'skipped' : 'ok', adaptersStartedAt);
|
|
82
|
+
const architectureStartedAt = Date.now();
|
|
66
83
|
const architecture = scanGuard(root, config, {
|
|
67
84
|
changedOnly: options.changedOnly,
|
|
68
85
|
baseline: loadBaseline(root),
|
|
69
86
|
includeArchitectureInsights: true,
|
|
70
87
|
});
|
|
71
|
-
const contractIssues =
|
|
88
|
+
const contractIssues = validateGuardPolicy(root, config);
|
|
89
|
+
recordGuardRunStage(runContext, 'architecture-policy', 'ok', architectureStartedAt);
|
|
72
90
|
const commandFailed = Object.values(results).some((result) => result.status === 'failed');
|
|
73
91
|
const workspaceCommandFailed = Object.values(workspaceChecks).some((packageResults) => Object.values(packageResults).some((result) => result.status === 'failed'));
|
|
74
92
|
const adapterFailed = Object.values(adapters).some((result) => result.status === 'failed');
|
|
@@ -83,12 +101,41 @@ export function runGuardVerification(root, options = {}) {
|
|
|
83
101
|
architectureFailed ||
|
|
84
102
|
contractIssues.length > 0 ||
|
|
85
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);
|
|
86
136
|
return {
|
|
87
137
|
status: failed ? 'fail' : 'ok',
|
|
88
|
-
outcome
|
|
89
|
-
errors: failed ? 1 : 0,
|
|
90
|
-
warnings: architectureWarnings ? 1 : 0,
|
|
91
|
-
}),
|
|
138
|
+
outcome,
|
|
92
139
|
tools: toolMode,
|
|
93
140
|
checks: results,
|
|
94
141
|
workspaceChecks,
|
|
@@ -98,5 +145,6 @@ export function runGuardVerification(root, options = {}) {
|
|
|
98
145
|
impact,
|
|
99
146
|
contractIssues,
|
|
100
147
|
requirement,
|
|
148
|
+
run,
|
|
101
149
|
};
|
|
102
150
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/** Public, framework-agnostic Guard API. */
|
|
2
|
+
export * from './core/model/types.js';
|
|
2
3
|
export * from './core/guard.js';
|
|
3
4
|
export * from './core/errors.js';
|
|
4
5
|
export * from './core/discovery/discovery.js';
|
|
5
6
|
export { runGuardVerification } from './core/verification/verify.js';
|
|
6
7
|
export type { GuardVerificationCheck, GuardVerificationResult, } from './core/verification/verify.js';
|
|
7
8
|
export * from './core/history/history.js';
|
|
9
|
+
export * from './core/history/runs.js';
|
|
8
10
|
export * from './core/output/sarif.js';
|
|
9
11
|
export * from './core/analysis/doctor.js';
|
|
10
12
|
export * from './core/analysis/packs.js';
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
/** Public, framework-agnostic Guard API. */
|
|
2
|
+
export * from './core/model/types.js';
|
|
2
3
|
export * from './core/guard.js';
|
|
3
4
|
export * from './core/errors.js';
|
|
4
5
|
export * from './core/discovery/discovery.js';
|
|
5
6
|
export { runGuardVerification } from './core/verification/verify.js';
|
|
6
7
|
export * from './core/history/history.js';
|
|
8
|
+
export * from './core/history/runs.js';
|
|
7
9
|
export * from './core/output/sarif.js';
|
|
8
10
|
export * from './core/analysis/doctor.js';
|
|
9
11
|
export * from './core/analysis/packs.js';
|