@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
package/README.md
CHANGED
|
@@ -168,6 +168,10 @@ Guard does not assume a fixed `UI → actions → services → repositories →
|
|
|
168
168
|
It can discover that shape when the project exhibits it, but observed patterns become enforceable
|
|
169
169
|
only after explicit approval.
|
|
170
170
|
|
|
171
|
+
Policy paths are validated as project-relative paths. Guard rejects traversal, absolute paths, and
|
|
172
|
+
symlinks escaping the project root; import-boundary checks include imports, re-exports, and literal
|
|
173
|
+
dynamic imports.
|
|
174
|
+
|
|
171
175
|
## What Guard discovers
|
|
172
176
|
|
|
173
177
|
The model is framework-aware without being framework-dependent:
|
|
@@ -186,6 +190,15 @@ The model is framework-aware without being framework-dependent:
|
|
|
186
190
|
Capabilities are evidence, not requirements. A Vite app, Express service, Hono project, Node
|
|
187
191
|
package, monorepo, or Next.js SaaS can all use the same Guard core.
|
|
188
192
|
|
|
193
|
+
## What Guard is — and is not
|
|
194
|
+
|
|
195
|
+
Guard is an architecture control plane for AI-assisted development. It protects project-specific
|
|
196
|
+
boundaries and change impact using local facts, approved policy, and deterministic verification.
|
|
197
|
+
|
|
198
|
+
Guard is not a replacement for ESLint, TypeScript, tests, SAST, or a general-purpose PR bot. Those
|
|
199
|
+
tools answer different questions; Guard connects their results with the architectural memory that
|
|
200
|
+
an AI coding agent needs before and after changing a repository.
|
|
201
|
+
|
|
189
202
|
## The normal loop
|
|
190
203
|
|
|
191
204
|
```text
|
|
@@ -218,12 +231,37 @@ Guard state is stored in `.codapult/guard/`:
|
|
|
218
231
|
├── proposals.json evidence and approval history
|
|
219
232
|
├── baseline.json accepted pre-existing findings
|
|
220
233
|
├── agent.json AI-host completion-gate configuration
|
|
221
|
-
└── history/ project snapshots
|
|
234
|
+
└── history/ project snapshots and local verification run manifests
|
|
222
235
|
```
|
|
223
236
|
|
|
224
237
|
Commit policy and baseline files when the team wants shared guardrails. Treat cache artifacts as
|
|
225
238
|
disposable according to the project’s policy, and never commit secrets.
|
|
226
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
|
+
|
|
227
265
|
## AI agents and MCP
|
|
228
266
|
|
|
229
267
|
Guard is deliberately model-agnostic. It does not send source code to a remote LLM and does not
|
|
@@ -259,6 +297,7 @@ Recommended agent loop:
|
|
|
259
297
|
|
|
260
298
|
```text
|
|
261
299
|
task finished
|
|
300
|
+
→ codapult_guard_next_action
|
|
262
301
|
→ codapult_guard_context
|
|
263
302
|
→ codapult_guard_review(requirement, diff)
|
|
264
303
|
→ codapult_guard_verify
|
|
@@ -269,6 +308,25 @@ task finished
|
|
|
269
308
|
For Cursor, Claude Code, Codex, Gemini CLI, GitHub Copilot, and generic hosts, see
|
|
270
309
|
[`docs/integrations/`](docs/integrations/).
|
|
271
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
|
+
|
|
272
330
|
## CI
|
|
273
331
|
|
|
274
332
|
Copy the consumer workflow into a project that has installed and initialized Guard:
|
|
@@ -298,11 +356,25 @@ real project shapes.
|
|
|
298
356
|
| `doctor` | Diagnose invalid or missing Guard artifacts. |
|
|
299
357
|
| `history` / `history-diff` | Inspect project model, module graph, and architecture-edge evolution. |
|
|
300
358
|
| `impact <files...>` | Explain dependencies, transitive dependents, capabilities, and contracts affected by files. |
|
|
359
|
+
| `policy explain <id>` | Explain a policy item, its evidence, and approval history. |
|
|
301
360
|
| `rules` / `contracts` | Approve or reject proposed policy. |
|
|
302
361
|
| `baseline` | Review or intentionally accept existing findings. |
|
|
303
362
|
|
|
363
|
+
Use `analyze --refresh` after a structural change. `doctor --fix-cache` removes only the disposable
|
|
364
|
+
discovery cache; it does not change rules, contracts, baseline, or source files.
|
|
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
|
+
|
|
304
371
|
Run `pnpm exec codapult-guard <command> --help` for command-specific options.
|
|
305
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
|
+
|
|
306
378
|
## Security and data handling
|
|
307
379
|
|
|
308
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;
|
package/dist/adapters/command.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
2
|
const MAX_OUTPUT_CHARS = 20_000;
|
|
3
3
|
const MAX_BUFFER_BYTES = 2_000_000;
|
|
4
4
|
function redactOutput(value) {
|
|
@@ -13,9 +13,30 @@ function captureOutput(value) {
|
|
|
13
13
|
? { value: `${redacted.slice(0, MAX_OUTPUT_CHARS)}\n[output truncated]`, truncated: true }
|
|
14
14
|
: { value: redacted, truncated: false };
|
|
15
15
|
}
|
|
16
|
+
function parseCommand(command) {
|
|
17
|
+
const parts = command.trim().split(/\s+/).filter(Boolean);
|
|
18
|
+
if (parts.length === 0 || parts.some((part) => /[;&|<>`$()]/.test(part)))
|
|
19
|
+
return undefined;
|
|
20
|
+
const [executable, ...args] = parts;
|
|
21
|
+
const windowsExecutable = process.platform === 'win32' && /^(?:npm|npx|pnpm|yarn|bun)$/.test(executable)
|
|
22
|
+
? `${executable}.cmd`
|
|
23
|
+
: executable;
|
|
24
|
+
return { executable: windowsExecutable, args };
|
|
25
|
+
}
|
|
16
26
|
export function runProjectCommand(command, cwd, options = {}) {
|
|
27
|
+
const parsed = parseCommand(command);
|
|
28
|
+
if (!parsed) {
|
|
29
|
+
return {
|
|
30
|
+
command,
|
|
31
|
+
status: 'failed',
|
|
32
|
+
passed: false,
|
|
33
|
+
exitCode: 2,
|
|
34
|
+
stdout: '',
|
|
35
|
+
stderr: 'Unsafe or empty project command rejected.',
|
|
36
|
+
};
|
|
37
|
+
}
|
|
17
38
|
try {
|
|
18
|
-
const stdout =
|
|
39
|
+
const stdout = execFileSync(parsed.executable, parsed.args, {
|
|
19
40
|
cwd,
|
|
20
41
|
env: { ...process.env, ...options.env },
|
|
21
42
|
stdio: 'pipe',
|
|
@@ -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,58 +1,57 @@
|
|
|
1
1
|
import { type GuardToolMode } from '../../core/guard.js';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
}
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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;
|
|
14
43
|
}): void;
|
|
15
44
|
export declare function guardHistoryCommand(): void;
|
|
16
|
-
export declare function guardImpactCommand(files: string[], options?:
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
export declare function
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export declare function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
tools?: GuardToolMode;
|
|
28
|
-
strict?: boolean;
|
|
29
|
-
projectChecks?: boolean;
|
|
30
|
-
}): void;
|
|
31
|
-
export declare function guardCheckCommand(options?: {
|
|
32
|
-
changed?: boolean;
|
|
33
|
-
json?: boolean;
|
|
34
|
-
sarif?: boolean;
|
|
35
|
-
}): void;
|
|
36
|
-
export declare function guardAuditCommand(options?: {
|
|
37
|
-
json?: boolean;
|
|
38
|
-
}): void;
|
|
39
|
-
export declare function guardBaselineCommand(action: 'list' | 'accept' | 'remove', ids?: string, options?: {
|
|
40
|
-
all?: boolean;
|
|
41
|
-
reason?: string;
|
|
42
|
-
json?: boolean;
|
|
43
|
-
}): void;
|
|
44
|
-
export declare function guardRulesApproveCommand(ids?: string, options?: {
|
|
45
|
-
all?: boolean;
|
|
46
|
-
}): void;
|
|
47
|
-
export declare function guardContractsApproveCommand(ids?: string, options?: {
|
|
48
|
-
all?: boolean;
|
|
49
|
-
}): void;
|
|
50
|
-
export declare function guardContractsRejectCommand(ids?: string, options?: {
|
|
51
|
-
all?: boolean;
|
|
52
|
-
}): void;
|
|
53
|
-
export declare function guardReviewCommand(options?: {
|
|
54
|
-
maxDiffChars?: string;
|
|
55
|
-
requirement?: string;
|
|
56
|
-
base?: string;
|
|
57
|
-
}): 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;
|
|
58
56
|
export declare function guardIsInitialized(root: string): boolean;
|
|
57
|
+
export {};
|