@codapult/guard 0.3.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 +55 -1
- package/dist/adapters/command.d.ts +10 -8
- package/dist/adapters/project-checks.d.ts +23 -20
- package/dist/cli/commands/guard.d.ts +53 -60
- package/dist/cli/commands/guard.js +32 -19
- package/dist/cli/index.js +22 -9
- 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 +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 +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 +64 -29
- package/dist/mcp/tools/guard.js +147 -28
- package/package.json +7 -7
package/README.md
CHANGED
|
@@ -231,12 +231,37 @@ Guard state is stored in `.codapult/guard/`:
|
|
|
231
231
|
├── proposals.json evidence and approval history
|
|
232
232
|
├── baseline.json accepted pre-existing findings
|
|
233
233
|
├── agent.json AI-host completion-gate configuration
|
|
234
|
-
└── history/ project snapshots
|
|
234
|
+
└── history/ project snapshots and local verification run manifests
|
|
235
235
|
```
|
|
236
236
|
|
|
237
237
|
Commit policy and baseline files when the team wants shared guardrails. Treat cache artifacts as
|
|
238
238
|
disposable according to the project’s policy, and never commit secrets.
|
|
239
239
|
|
|
240
|
+
### Optional scoped budgets
|
|
241
|
+
|
|
242
|
+
Budgets are policy, not a universal style rule. Add them only for a named risk boundary such as a
|
|
243
|
+
service, route handler, or dependency-heavy module:
|
|
244
|
+
|
|
245
|
+
```json
|
|
246
|
+
{
|
|
247
|
+
"id": "service-lines",
|
|
248
|
+
"description": "Services must remain reviewable.",
|
|
249
|
+
"metric": "lines",
|
|
250
|
+
"scope": ["src/services"],
|
|
251
|
+
"limit": 300,
|
|
252
|
+
"severity": "warning",
|
|
253
|
+
"reason": "Keep service changes reviewable by one owner.",
|
|
254
|
+
"status": "active"
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Place budgets in the `budgets` array in `.codapult/guard/rules.json`. Supported metrics are
|
|
259
|
+
`lines`, `bytes`, and `imports`. Generated files, schemas, migrations, and other paths are not
|
|
260
|
+
checked unless they are explicitly included in `scope`; a wildcard such as `*` can intentionally
|
|
261
|
+
cover the project root. Invalid, absolute, parent-directory, or nonexistent scopes fail the Guard
|
|
262
|
+
gate. Changing a budget is a policy decision:
|
|
263
|
+
review the diff and update its written `reason` rather than silently increasing the limit.
|
|
264
|
+
|
|
240
265
|
## AI agents and MCP
|
|
241
266
|
|
|
242
267
|
Guard is deliberately model-agnostic. It does not send source code to a remote LLM and does not
|
|
@@ -283,6 +308,25 @@ task finished
|
|
|
283
308
|
For Cursor, Claude Code, Codex, Gemini CLI, GitHub Copilot, and generic hosts, see
|
|
284
309
|
[`docs/integrations/`](docs/integrations/).
|
|
285
310
|
|
|
311
|
+
For workflows where the authoring agent must not approve its own policy proposals, set Guard to
|
|
312
|
+
protected mode in `rules.json`:
|
|
313
|
+
|
|
314
|
+
```json
|
|
315
|
+
{
|
|
316
|
+
"approval": {
|
|
317
|
+
"mode": "protected",
|
|
318
|
+
"allowMcpApproval": false,
|
|
319
|
+
"requireDistinctActor": true
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
MCP can then read and propose policy, while approval happens through the CLI or a protected CI/PR
|
|
325
|
+
process. With `requireDistinctActor`, CLI and permitted MCP approvals require `GUARD_APPROVER`,
|
|
326
|
+
and Guard rejects the same declared actor when a proposal was generated with `GUARD_PROPOSER`.
|
|
327
|
+
These variables provide declared provenance only; external branch protection or signed identity
|
|
328
|
+
remains responsible for proving who approved the change.
|
|
329
|
+
|
|
286
330
|
## CI
|
|
287
331
|
|
|
288
332
|
Copy the consumer workflow into a project that has installed and initialized Guard:
|
|
@@ -319,8 +363,18 @@ real project shapes.
|
|
|
319
363
|
Use `analyze --refresh` after a structural change. `doctor --fix-cache` removes only the disposable
|
|
320
364
|
discovery cache; it does not change rules, contracts, baseline, or source files.
|
|
321
365
|
|
|
366
|
+
Guard state is safe for concurrent CLI/MCP readers and writers: derived facts are published as an
|
|
367
|
+
atomic generation, policy updates use revisions, and short write contention is waited out. A
|
|
368
|
+
process that dies while holding the state lock is detected by its local PID and the lock is
|
|
369
|
+
recovered; use `--no-wait` when an integration needs immediate contention feedback.
|
|
370
|
+
|
|
322
371
|
Run `pnpm exec codapult-guard <command> --help` for command-specific options.
|
|
323
372
|
|
|
373
|
+
Every `verify` run also writes a local manifest under
|
|
374
|
+
`.codapult/guard/history/runs/<run-id>.json`. It records stage durations, the outcome, and the
|
|
375
|
+
policy gate that decided the result. It contains no source code or external telemetry. These
|
|
376
|
+
manifests make a failed run explainable without turning Guard into a production tracing system.
|
|
377
|
+
|
|
324
378
|
## Security and data handling
|
|
325
379
|
|
|
326
380
|
- Deterministic discovery and checks run locally.
|
|
@@ -5,17 +5,19 @@ export interface CommandResult {
|
|
|
5
5
|
exitCode: number;
|
|
6
6
|
stdout: string;
|
|
7
7
|
stderr: string;
|
|
8
|
-
timedOut?: boolean;
|
|
9
|
-
truncated?: boolean;
|
|
8
|
+
timedOut?: boolean | undefined;
|
|
9
|
+
truncated?: boolean | undefined;
|
|
10
10
|
}
|
|
11
|
-
export
|
|
12
|
-
timeout?: number;
|
|
13
|
-
env?: NodeJS.ProcessEnv;
|
|
14
|
-
}
|
|
15
|
-
export
|
|
11
|
+
export interface RunProjectCommandOptions {
|
|
12
|
+
timeout?: number | undefined;
|
|
13
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
14
|
+
}
|
|
15
|
+
export interface CommandResponse {
|
|
16
16
|
content: {
|
|
17
17
|
type: 'text';
|
|
18
18
|
text: string;
|
|
19
19
|
}[];
|
|
20
20
|
isError: boolean;
|
|
21
|
-
}
|
|
21
|
+
}
|
|
22
|
+
export declare function runProjectCommand(command: string, cwd: string, options?: RunProjectCommandOptions): CommandResult;
|
|
23
|
+
export declare function commandResponse(result: CommandResult): CommandResponse;
|
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
import { type CommandResult } from './command.js';
|
|
2
|
-
|
|
3
|
-
export type
|
|
4
|
-
export type
|
|
2
|
+
import type { GuardAdapterName, GuardToolMode, ProjectCheck } from '../core/model/types.js';
|
|
3
|
+
export type { ProjectCheck } from '../core/model/types.js';
|
|
4
|
+
export type GuardAdapter = GuardAdapterName;
|
|
5
|
+
export type ExternalToolMode = GuardToolMode;
|
|
5
6
|
export type ProjectCheckResults = Partial<Record<ProjectCheck, CommandResult>>;
|
|
7
|
+
export interface ProjectChecksOptions {
|
|
8
|
+
timeout?: number | undefined;
|
|
9
|
+
}
|
|
10
|
+
export interface WorkspaceProjectChecksOptions extends ProjectChecksOptions {
|
|
11
|
+
rootResults?: ProjectCheckResults | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface ProjectAdaptersOptions {
|
|
14
|
+
mode?: Exclude<ExternalToolMode, 'off'> | undefined;
|
|
15
|
+
timeout?: number | undefined;
|
|
16
|
+
tooling?: Partial<Record<GuardAdapter, {
|
|
17
|
+
script: string;
|
|
18
|
+
enabled: boolean;
|
|
19
|
+
}>> | undefined;
|
|
20
|
+
}
|
|
6
21
|
export interface ProjectRuntimeDiagnostics {
|
|
7
22
|
node: string;
|
|
8
23
|
packageManager: string;
|
|
9
|
-
declaredPackageManager?: string;
|
|
10
|
-
declaredNode?: string;
|
|
24
|
+
declaredPackageManager?: string | undefined;
|
|
25
|
+
declaredNode?: string | undefined;
|
|
11
26
|
compatible: boolean;
|
|
12
27
|
issues: string[];
|
|
13
28
|
}
|
|
14
29
|
export declare function inspectProjectRuntime(root: string): ProjectRuntimeDiagnostics;
|
|
15
|
-
export declare function runProjectChecks(root: string, checks: ProjectCheck[], options?:
|
|
16
|
-
timeout?: number;
|
|
17
|
-
}): ProjectCheckResults;
|
|
30
|
+
export declare function runProjectChecks(root: string, checks: ProjectCheck[], options?: ProjectChecksOptions): ProjectCheckResults;
|
|
18
31
|
/**
|
|
19
32
|
* Runs checks for workspace packages only when the root package does not own
|
|
20
33
|
* the corresponding check. Root orchestration remains the source of truth.
|
|
@@ -22,16 +35,6 @@ export declare function runProjectChecks(root: string, checks: ProjectCheck[], o
|
|
|
22
35
|
export declare function runWorkspaceProjectChecks(root: string, packages: {
|
|
23
36
|
path: string;
|
|
24
37
|
scripts: Record<string, string>;
|
|
25
|
-
}[], checks: ProjectCheck[], options?:
|
|
26
|
-
timeout?: number;
|
|
27
|
-
rootResults?: ProjectCheckResults;
|
|
28
|
-
}): Record<string, ProjectCheckResults>;
|
|
38
|
+
}[], checks: ProjectCheck[], options?: WorkspaceProjectChecksOptions): Record<string, ProjectCheckResults>;
|
|
29
39
|
/** Runs only explicitly configured project scripts; Guard does not recreate these tools. */
|
|
30
|
-
export declare function runProjectAdapters(root: string, options?:
|
|
31
|
-
mode?: Exclude<ExternalToolMode, 'off'>;
|
|
32
|
-
timeout?: number;
|
|
33
|
-
tooling?: Partial<Record<GuardAdapter, {
|
|
34
|
-
script: string;
|
|
35
|
-
enabled: boolean;
|
|
36
|
-
}>>;
|
|
37
|
-
}): Partial<Record<GuardAdapter, CommandResult>>;
|
|
40
|
+
export declare function runProjectAdapters(root: string, options?: ProjectAdaptersOptions): Partial<Record<GuardAdapter, CommandResult>>;
|
|
@@ -1,64 +1,57 @@
|
|
|
1
1
|
import { type GuardToolMode } from '../../core/guard.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
2
|
+
interface GuardOutputOptions {
|
|
3
|
+
json?: boolean | undefined;
|
|
4
|
+
}
|
|
5
|
+
interface GuardInitOptions {
|
|
6
|
+
force?: boolean | undefined;
|
|
7
|
+
wait?: boolean | undefined;
|
|
8
|
+
}
|
|
9
|
+
interface GuardAnalyzeOptions {
|
|
10
|
+
refresh?: boolean | undefined;
|
|
11
|
+
wait?: boolean | undefined;
|
|
12
|
+
}
|
|
13
|
+
interface GuardVerifyOptions extends GuardOutputOptions {
|
|
14
|
+
checks?: string | undefined;
|
|
15
|
+
changed?: boolean | undefined;
|
|
16
|
+
requirement?: string | undefined;
|
|
17
|
+
tools?: GuardToolMode | undefined;
|
|
18
|
+
strict?: boolean | undefined;
|
|
19
|
+
projectChecks?: boolean | undefined;
|
|
20
|
+
}
|
|
21
|
+
interface GuardCheckOptions extends GuardOutputOptions {
|
|
22
|
+
changed?: boolean | undefined;
|
|
23
|
+
sarif?: boolean | undefined;
|
|
24
|
+
}
|
|
25
|
+
interface GuardBaselineOptions extends GuardOutputOptions {
|
|
26
|
+
all?: boolean | undefined;
|
|
27
|
+
reason?: string | undefined;
|
|
28
|
+
}
|
|
29
|
+
interface GuardApprovalOptions {
|
|
30
|
+
all?: boolean | undefined;
|
|
31
|
+
}
|
|
32
|
+
interface GuardReviewOptions {
|
|
33
|
+
maxDiffChars?: string | undefined;
|
|
34
|
+
requirement?: string | undefined;
|
|
35
|
+
base?: string | undefined;
|
|
36
|
+
}
|
|
37
|
+
export declare function guardInitCommand(options?: GuardInitOptions): void;
|
|
38
|
+
export declare function guardAnalyzeCommand(options?: GuardAnalyzeOptions): void;
|
|
39
|
+
export declare function guardProposeCommand(options?: GuardOutputOptions): void;
|
|
40
|
+
export declare function guardInstallAgentCommand(target?: string, options?: GuardOutputOptions): void;
|
|
41
|
+
export declare function guardDoctorCommand(options?: GuardOutputOptions & {
|
|
42
|
+
fixCache?: boolean | undefined;
|
|
17
43
|
}): void;
|
|
18
44
|
export declare function guardHistoryCommand(): void;
|
|
19
|
-
export declare function guardImpactCommand(files: string[], options?:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export declare function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
export declare function
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
tools?: GuardToolMode;
|
|
31
|
-
strict?: boolean;
|
|
32
|
-
projectChecks?: boolean;
|
|
33
|
-
}): void;
|
|
34
|
-
export declare function guardCheckCommand(options?: {
|
|
35
|
-
changed?: boolean;
|
|
36
|
-
json?: boolean;
|
|
37
|
-
sarif?: boolean;
|
|
38
|
-
}): void;
|
|
39
|
-
export declare function guardAuditCommand(options?: {
|
|
40
|
-
json?: boolean;
|
|
41
|
-
}): void;
|
|
42
|
-
export declare function guardBaselineCommand(action: 'list' | 'accept' | 'remove', ids?: string, options?: {
|
|
43
|
-
all?: boolean;
|
|
44
|
-
reason?: string;
|
|
45
|
-
json?: boolean;
|
|
46
|
-
}): void;
|
|
47
|
-
export declare function guardRulesApproveCommand(ids?: string, options?: {
|
|
48
|
-
all?: boolean;
|
|
49
|
-
}): void;
|
|
50
|
-
export declare function guardPolicyExplainCommand(id: string, options?: {
|
|
51
|
-
json?: boolean;
|
|
52
|
-
}): void;
|
|
53
|
-
export declare function guardContractsApproveCommand(ids?: string, options?: {
|
|
54
|
-
all?: boolean;
|
|
55
|
-
}): void;
|
|
56
|
-
export declare function guardContractsRejectCommand(ids?: string, options?: {
|
|
57
|
-
all?: boolean;
|
|
58
|
-
}): void;
|
|
59
|
-
export declare function guardReviewCommand(options?: {
|
|
60
|
-
maxDiffChars?: string;
|
|
61
|
-
requirement?: string;
|
|
62
|
-
base?: string;
|
|
63
|
-
}): void;
|
|
45
|
+
export declare function guardImpactCommand(files: string[], options?: GuardOutputOptions): void;
|
|
46
|
+
export declare function guardHistoryDiffCommand(from: string, to: string, options?: GuardOutputOptions): void;
|
|
47
|
+
export declare function guardVerifyCommand(options?: GuardVerifyOptions): void;
|
|
48
|
+
export declare function guardCheckCommand(options?: GuardCheckOptions): void;
|
|
49
|
+
export declare function guardAuditCommand(options?: GuardOutputOptions): void;
|
|
50
|
+
export declare function guardBaselineCommand(action: 'list' | 'accept' | 'remove', ids?: string, options?: GuardBaselineOptions): void;
|
|
51
|
+
export declare function guardRulesApproveCommand(ids?: string, options?: GuardApprovalOptions): void;
|
|
52
|
+
export declare function guardPolicyExplainCommand(id: string, options?: GuardOutputOptions): void;
|
|
53
|
+
export declare function guardContractsApproveCommand(ids?: string, options?: GuardApprovalOptions): void;
|
|
54
|
+
export declare function guardContractsRejectCommand(ids?: string, options?: GuardApprovalOptions): void;
|
|
55
|
+
export declare function guardReviewCommand(options?: GuardReviewOptions): void;
|
|
64
56
|
export declare function guardIsInitialized(root: string): boolean;
|
|
57
|
+
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { relative, resolve } from 'node:path';
|
|
3
|
-
import { GUARD_BASELINE_FILE, GUARD_BASELINE_META_FILE, GUARD_ARCHITECTURE_FILE, GUARD_CONVENTIONS_FILE, GUARD_AGENT_FILE, GUARD_CONTRACTS_FILE, GUARD_PROPOSALS_FILE, GUARD_PROJECT_FILE, GUARD_HISTORY_DIR, GUARD_RULES_FILE, findGuardRoot, discoverProjectWithMetrics, clearDiscoveryCache, buildGuardReviewPacket, buildGuardProposals, buildGeneratedGuardConfig, discoverProject, GuardAlreadyInitializedError, initializeGuard, loadBaseline, updateBaseline, loadGuardConfig, loadGuardProposals,
|
|
3
|
+
import { GUARD_BASELINE_FILE, GUARD_BASELINE_META_FILE, GUARD_ARCHITECTURE_FILE, GUARD_CONVENTIONS_FILE, GUARD_AGENT_FILE, GUARD_CONTRACTS_FILE, GUARD_PROPOSALS_FILE, GUARD_PROJECT_FILE, GUARD_HISTORY_DIR, GUARD_RULES_FILE, findGuardRoot, discoverProjectWithMetrics, clearDiscoveryCache, buildGuardReviewPacket, buildGuardProposals, buildGeneratedGuardConfig, discoverProject, GuardAlreadyInitializedError, initializeGuard, loadBaseline, updateBaseline, loadGuardConfig, loadGuardProposals, applyGuardProposalDecision, writeGuardProposals, validateGuardProposalApproval, getGuardProposalFreshness, scanGuard, classifyGuardOutcome, validateGuardPolicy, writeProjectState, } from '../../core/guard.js';
|
|
4
4
|
import { guardErrorPayload } from '../../core/errors.js';
|
|
5
5
|
import { runGuardVerification, } from '../../core/verification/verify.js';
|
|
6
6
|
import { diagnoseGuard } from '../../core/analysis/doctor.js';
|
|
@@ -70,7 +70,7 @@ export function guardInitCommand(options = {}) {
|
|
|
70
70
|
const root = getRoot();
|
|
71
71
|
let initialized;
|
|
72
72
|
try {
|
|
73
|
-
initialized = initializeGuard(root, { force: options.force });
|
|
73
|
+
initialized = initializeGuard(root, options.wait === false ? { force: options.force, noWait: true } : { force: options.force });
|
|
74
74
|
}
|
|
75
75
|
catch (error) {
|
|
76
76
|
if (error instanceof GuardAlreadyInitializedError) {
|
|
@@ -97,14 +97,12 @@ export function guardInitCommand(options = {}) {
|
|
|
97
97
|
info(`${config.rules.length} rule(s) generated (${proposedRules} proposed); ${report.scannedFiles} source file(s) scanned.`);
|
|
98
98
|
dim('Existing findings are baselined. New violations will be reported by `codapult-guard check`.');
|
|
99
99
|
}
|
|
100
|
-
export function guardAnalyzeCommand(
|
|
100
|
+
export function guardAnalyzeCommand(options = {}) {
|
|
101
101
|
const root = getRoot();
|
|
102
102
|
const { model: projectModel, metrics } = discoverProjectWithMetrics(root, {
|
|
103
103
|
persistCache: true,
|
|
104
104
|
});
|
|
105
|
-
|
|
106
|
-
writeGuardMemory(root, projectModel);
|
|
107
|
-
const revision = writeProjectSnapshot(root, projectModel);
|
|
105
|
+
const revision = writeProjectState(root, projectModel, options.wait === false ? { noWait: true } : undefined);
|
|
108
106
|
heading('Codapult Guard Analyze');
|
|
109
107
|
success(`Updated ${GUARD_PROJECT_FILE}, ${GUARD_ARCHITECTURE_FILE}, and ${GUARD_CONVENTIONS_FILE}`);
|
|
110
108
|
info(`${projectModel.files.length} file(s), ${projectModel.modules.length} module(s), ${projectModel.insights.cycles.length} cycle(s) recorded as project context.`);
|
|
@@ -326,7 +324,7 @@ export function guardCheckCommand(options = {}) {
|
|
|
326
324
|
baseline: loadBaseline(root),
|
|
327
325
|
includeArchitectureInsights: true,
|
|
328
326
|
});
|
|
329
|
-
const contractIssues =
|
|
327
|
+
const contractIssues = validateGuardPolicy(root, config);
|
|
330
328
|
const errors = report.findings.filter((finding) => finding.severity === 'error').length;
|
|
331
329
|
const warnings = report.findings.filter((finding) => finding.severity === 'warning').length;
|
|
332
330
|
const contractFindings = contractIssues.map((issue) => ({
|
|
@@ -392,7 +390,7 @@ export function guardAuditCommand(options = {}) {
|
|
|
392
390
|
return;
|
|
393
391
|
}
|
|
394
392
|
const report = scanGuard(root, config, { includeArchitectureInsights: true });
|
|
395
|
-
const contractIssues =
|
|
393
|
+
const contractIssues = validateGuardPolicy(root, config);
|
|
396
394
|
const errors = report.findings.filter((finding) => finding.severity === 'error').length;
|
|
397
395
|
const warnings = report.findings.filter((finding) => finding.severity === 'warning').length;
|
|
398
396
|
if (options.json) {
|
|
@@ -491,11 +489,16 @@ export function guardRulesApproveCommand(ids, options = {}) {
|
|
|
491
489
|
return;
|
|
492
490
|
}
|
|
493
491
|
const selectedIds = new Set(selected.map((rule) => rule.id));
|
|
494
|
-
|
|
492
|
+
const approvalError = validateGuardProposalApproval(config.approval, loadGuardProposals(root));
|
|
493
|
+
if (approvalError) {
|
|
494
|
+
fail(approvalError);
|
|
495
|
+
process.exitCode = 1;
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
applyGuardProposalDecision(root, {
|
|
495
499
|
...config,
|
|
496
500
|
rules: config.rules.map((rule) => selectedIds.has(rule.id) ? { ...rule, status: 'active' } : rule),
|
|
497
|
-
});
|
|
498
|
-
recordGuardProposalDecision(root, selected.map((rule) => ({ id: rule.id, type: 'rule', decision: 'approved' })));
|
|
501
|
+
}, selected.map((rule) => ({ id: rule.id, type: 'rule', decision: 'approved' })), { source: 'cli' });
|
|
499
502
|
for (const rule of selected)
|
|
500
503
|
success(`Activated ${rule.id}`);
|
|
501
504
|
process.exitCode = 0;
|
|
@@ -566,15 +569,20 @@ export function guardContractsApproveCommand(ids, options = {}) {
|
|
|
566
569
|
return;
|
|
567
570
|
}
|
|
568
571
|
const selectedIds = new Set(selected.map((contract) => contract.id));
|
|
569
|
-
|
|
572
|
+
const approvalError = validateGuardProposalApproval(config.approval, loadGuardProposals(root));
|
|
573
|
+
if (approvalError) {
|
|
574
|
+
fail(approvalError);
|
|
575
|
+
process.exitCode = 1;
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
applyGuardProposalDecision(root, {
|
|
570
579
|
...config,
|
|
571
580
|
contracts: (config.contracts ?? []).map((contract) => selectedIds.has(contract.id) ? { ...contract, status: 'active' } : contract),
|
|
572
|
-
})
|
|
573
|
-
recordGuardProposalDecision(root, selected.map((contract) => ({
|
|
581
|
+
}, selected.map((contract) => ({
|
|
574
582
|
id: contract.id,
|
|
575
583
|
type: 'contract',
|
|
576
584
|
decision: 'approved',
|
|
577
|
-
})));
|
|
585
|
+
})), { source: 'cli' });
|
|
578
586
|
for (const contract of selected)
|
|
579
587
|
success(`Activated ${contract.id}`);
|
|
580
588
|
process.exitCode = 0;
|
|
@@ -601,15 +609,20 @@ export function guardContractsRejectCommand(ids, options = {}) {
|
|
|
601
609
|
process.exitCode = 1;
|
|
602
610
|
return;
|
|
603
611
|
}
|
|
604
|
-
|
|
612
|
+
const approvalError = validateGuardProposalApproval(config.approval, loadGuardProposals(root));
|
|
613
|
+
if (approvalError) {
|
|
614
|
+
fail(approvalError);
|
|
615
|
+
process.exitCode = 1;
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
applyGuardProposalDecision(root, {
|
|
605
619
|
...config,
|
|
606
620
|
contracts: config.contracts ?? [],
|
|
607
|
-
})
|
|
608
|
-
recordGuardProposalDecision(root, selected.map((contract) => ({
|
|
621
|
+
}, selected.map((contract) => ({
|
|
609
622
|
id: contract.id,
|
|
610
623
|
type: 'contract',
|
|
611
624
|
decision: 'rejected',
|
|
612
|
-
})));
|
|
625
|
+
})), { source: 'cli' });
|
|
613
626
|
for (const contract of selected)
|
|
614
627
|
success(`Rejected ${contract.id}`);
|
|
615
628
|
process.exitCode = 0;
|
package/dist/cli/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { Command } from 'commander';
|
|
|
3
3
|
import pc from 'picocolors';
|
|
4
4
|
import { guardAnalyzeCommand, guardAuditCommand, guardBaselineCommand, guardCheckCommand, guardContractsApproveCommand, guardContractsRejectCommand, guardDoctorCommand, guardHistoryCommand, guardHistoryDiffCommand, guardImpactCommand, guardInitCommand, guardInstallAgentCommand, guardPolicyExplainCommand, guardProposeCommand, guardReviewCommand, guardRulesApproveCommand, guardVerifyCommand, } from './commands/guard.js';
|
|
5
5
|
import { config } from '../core/config.js';
|
|
6
|
-
import { GuardConfigError, GuardStateBusyError } from '../core/guard.js';
|
|
6
|
+
import { GuardBaselineReasonError, GuardConfigError, GuardStateBusyError, GuardStateStaleError, } from '../core/guard.js';
|
|
7
7
|
import { guardErrorPayload } from '../core/errors.js';
|
|
8
8
|
const program = new Command()
|
|
9
9
|
.name(config.commandName)
|
|
@@ -16,8 +16,8 @@ const program = new Command()
|
|
|
16
16
|
// Guard is the standalone product, so its commands live at the package root:
|
|
17
17
|
// `codapult-guard init`, not `codapult-guard guard init`.
|
|
18
18
|
const guard = program;
|
|
19
|
-
guard.command('init').option('--force').action(guardInitCommand);
|
|
20
|
-
guard.command('analyze').option('--refresh').action(guardAnalyzeCommand);
|
|
19
|
+
guard.command('init').option('--force').option('--no-wait').action(guardInitCommand);
|
|
20
|
+
guard.command('analyze').option('--refresh').option('--no-wait').action(guardAnalyzeCommand);
|
|
21
21
|
guard.command('propose').option('--json').action(guardProposeCommand);
|
|
22
22
|
guard.command('install-agent [target]').option('--json').action(guardInstallAgentCommand);
|
|
23
23
|
guard.command('doctor').option('--json').option('--fix-cache').action(guardDoctorCommand);
|
|
@@ -81,15 +81,28 @@ try {
|
|
|
81
81
|
program.parse();
|
|
82
82
|
}
|
|
83
83
|
catch (error) {
|
|
84
|
-
if (error instanceof GuardConfigError ||
|
|
84
|
+
if (error instanceof GuardConfigError ||
|
|
85
|
+
error instanceof GuardBaselineReasonError ||
|
|
86
|
+
error instanceof GuardStateBusyError ||
|
|
87
|
+
error instanceof GuardStateStaleError) {
|
|
85
88
|
const isBusy = error instanceof GuardStateBusyError;
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
const isStale = error instanceof GuardStateStaleError;
|
|
90
|
+
const isInvalidInput = error instanceof GuardBaselineReasonError;
|
|
91
|
+
console.error(JSON.stringify(guardErrorPayload(isBusy
|
|
92
|
+
? 'GUARD_STATE_BUSY'
|
|
93
|
+
: isStale
|
|
94
|
+
? 'GUARD_STATE_STALE'
|
|
95
|
+
: isInvalidInput
|
|
96
|
+
? 'GUARD_INVALID_INPUT'
|
|
97
|
+
: 'GUARD_CONFIG_INVALID', error.message, {
|
|
98
|
+
configured: !isBusy && !isStale,
|
|
88
99
|
outcome: 'error',
|
|
89
100
|
recoverable: true,
|
|
90
|
-
hint: isBusy
|
|
91
|
-
? '
|
|
92
|
-
:
|
|
101
|
+
hint: isBusy || isStale
|
|
102
|
+
? 'Re-read Guard state and retry the operation.'
|
|
103
|
+
: isInvalidInput
|
|
104
|
+
? 'Provide a written reason for the baseline decision.'
|
|
105
|
+
: 'Repair the invalid Guard artifact, then run codapult-guard doctor.',
|
|
93
106
|
}), null, 2));
|
|
94
107
|
process.exitCode = 1;
|
|
95
108
|
}
|
|
@@ -7,6 +7,6 @@ export interface GuardDoctorReport {
|
|
|
7
7
|
status: 'ok' | 'warning' | 'fail';
|
|
8
8
|
initialized: boolean;
|
|
9
9
|
items: GuardDoctorItem[];
|
|
10
|
-
recommendation?: string;
|
|
10
|
+
recommendation?: string | undefined;
|
|
11
11
|
}
|
|
12
12
|
export declare function diagnoseGuard(root: string): GuardDoctorReport;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
|
-
import { GUARD_AGENT_FILE, GUARD_BASELINE_META_FILE, GUARD_ARCHITECTURE_FILE, GUARD_BASELINE_FILE, GUARD_CONVENTIONS_FILE, GUARD_CONTRACTS_FILE, GUARD_PROPOSALS_FILE, GUARD_PROJECT_FILE, GUARD_RULES_FILE, loadGuardConfig, loadProjectModel, isGuardAgentConfig, isGuardContractsFile, isGuardProposalFile,
|
|
3
|
+
import { GUARD_AGENT_FILE, GUARD_BASELINE_META_FILE, GUARD_ARCHITECTURE_FILE, GUARD_BASELINE_FILE, GUARD_CONVENTIONS_FILE, GUARD_CONTRACTS_FILE, GUARD_PROPOSALS_FILE, GUARD_PROJECT_FILE, GUARD_RULES_FILE, loadGuardConfig, loadProjectModel, isGuardAgentConfig, isGuardContractsFile, isGuardProposalFile, validateGuardPolicy, } from '../guard.js';
|
|
4
4
|
const requiredArtifacts = [
|
|
5
5
|
GUARD_BASELINE_FILE,
|
|
6
6
|
GUARD_RULES_FILE,
|
|
@@ -48,8 +48,7 @@ export function diagnoseGuard(root) {
|
|
|
48
48
|
(() => {
|
|
49
49
|
try {
|
|
50
50
|
const guardConfig = loadGuardConfig(root);
|
|
51
|
-
return
|
|
52
|
-
validateGuardContracts(root, guardConfig.contracts).length > 0);
|
|
51
|
+
return guardConfig !== undefined && validateGuardPolicy(root, guardConfig).length > 0;
|
|
53
52
|
}
|
|
54
53
|
catch {
|
|
55
54
|
return true;
|
|
@@ -11,7 +11,7 @@ export interface ModuleRecord {
|
|
|
11
11
|
exports: string[];
|
|
12
12
|
calls: string[];
|
|
13
13
|
resolvedImports: string[];
|
|
14
|
-
resolvedImportMap?: Record<string, string
|
|
14
|
+
resolvedImportMap?: Record<string, string> | undefined;
|
|
15
15
|
dynamicImports: string[];
|
|
16
16
|
declarations: {
|
|
17
17
|
classes: number;
|
|
@@ -33,20 +33,20 @@ export interface CapabilitySignal {
|
|
|
33
33
|
export interface ProjectModel {
|
|
34
34
|
version: 1;
|
|
35
35
|
project: {
|
|
36
|
-
name?: string;
|
|
37
|
-
packageManager?: string;
|
|
36
|
+
name?: string | undefined;
|
|
37
|
+
packageManager?: string | undefined;
|
|
38
38
|
frameworks: string[];
|
|
39
39
|
scripts: Record<string, string>;
|
|
40
40
|
dependencies: Record<string, string>;
|
|
41
41
|
devDependencies: Record<string, string>;
|
|
42
42
|
workspacePackages: {
|
|
43
43
|
path: string;
|
|
44
|
-
name?: string;
|
|
44
|
+
name?: string | undefined;
|
|
45
45
|
private: boolean;
|
|
46
46
|
scripts: Record<string, string>;
|
|
47
47
|
dependencies: string[];
|
|
48
|
-
exports?: string[];
|
|
49
|
-
projectReferences?: string[];
|
|
48
|
+
exports?: string[] | undefined;
|
|
49
|
+
projectReferences?: string[] | undefined;
|
|
50
50
|
}[];
|
|
51
51
|
};
|
|
52
52
|
files: ProjectFileRecord[];
|
|
@@ -59,7 +59,7 @@ export interface ProjectModel {
|
|
|
59
59
|
};
|
|
60
60
|
git: {
|
|
61
61
|
repository: boolean;
|
|
62
|
-
branch?: string;
|
|
62
|
+
branch?: string | undefined;
|
|
63
63
|
dirty: boolean;
|
|
64
64
|
changedFiles: string[];
|
|
65
65
|
status: string[];
|
|
@@ -125,16 +125,23 @@ export interface DiscoveryMetrics {
|
|
|
125
125
|
changedFiles: number;
|
|
126
126
|
reusedModules: number;
|
|
127
127
|
}
|
|
128
|
+
export interface DiscoveryResult {
|
|
129
|
+
model: ProjectModel;
|
|
130
|
+
metrics: DiscoveryMetrics;
|
|
131
|
+
}
|
|
128
132
|
export interface DiscoveryOptions {
|
|
129
|
-
persistCache?: boolean;
|
|
133
|
+
persistCache?: boolean | undefined;
|
|
130
134
|
/** Maximum number of files included in one model. */
|
|
131
|
-
maxFiles?: number;
|
|
135
|
+
maxFiles?: number | undefined;
|
|
132
136
|
/** Maximum size of one included file in bytes. */
|
|
133
|
-
maxFileBytes?: number;
|
|
137
|
+
maxFileBytes?: number | undefined;
|
|
134
138
|
}
|
|
135
139
|
export declare class DiscoveryLimitError extends Error {
|
|
136
140
|
constructor(message: string);
|
|
137
141
|
}
|
|
142
|
+
export declare class DiscoveryStaleError extends Error {
|
|
143
|
+
constructor();
|
|
144
|
+
}
|
|
138
145
|
export declare function clearDiscoveryCache(root: string): void;
|
|
139
146
|
export declare function findGuardRoot(from?: string): string;
|
|
140
147
|
/** Build the internal module graph from AST resolution plus conservative fallback resolution.
|
|
@@ -145,7 +152,4 @@ export declare function findGuardRoot(from?: string): string;
|
|
|
145
152
|
*/
|
|
146
153
|
export declare function buildModuleTargetGraph(modules: ModuleRecord[]): Map<string, string[]>;
|
|
147
154
|
export declare function discoverProject(root: string, options?: DiscoveryOptions): ProjectModel;
|
|
148
|
-
export declare function discoverProjectWithMetrics(root: string, options?: DiscoveryOptions):
|
|
149
|
-
model: ProjectModel;
|
|
150
|
-
metrics: DiscoveryMetrics;
|
|
151
|
-
};
|
|
155
|
+
export declare function discoverProjectWithMetrics(root: string, options?: DiscoveryOptions): DiscoveryResult;
|