@codapult/guard 0.1.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.
Files changed (45) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +290 -0
  3. package/dist/adapters/agents/agent-integration.d.ts +9 -0
  4. package/dist/adapters/agents/agent-integration.js +62 -0
  5. package/dist/adapters/command.d.ts +21 -0
  6. package/dist/adapters/command.js +60 -0
  7. package/dist/adapters/project-checks.d.ts +37 -0
  8. package/dist/adapters/project-checks.js +149 -0
  9. package/dist/cli/commands/guard.d.ts +55 -0
  10. package/dist/cli/commands/guard.js +510 -0
  11. package/dist/cli/index.d.ts +2 -0
  12. package/dist/cli/index.js +75 -0
  13. package/dist/cli/ui.d.ts +6 -0
  14. package/dist/cli/ui.js +7 -0
  15. package/dist/commands/guard.d.ts +2 -0
  16. package/dist/commands/guard.js +2 -0
  17. package/dist/core/analysis/doctor.d.ts +12 -0
  18. package/dist/core/analysis/doctor.js +85 -0
  19. package/dist/core/analysis/packs.d.ts +21 -0
  20. package/dist/core/analysis/packs.js +251 -0
  21. package/dist/core/config.d.ts +6 -0
  22. package/dist/core/config.js +6 -0
  23. package/dist/core/discovery/discovery.d.ts +134 -0
  24. package/dist/core/discovery/discovery.js +816 -0
  25. package/dist/core/guard.d.ts +189 -0
  26. package/dist/core/guard.js +936 -0
  27. package/dist/core/history/history.d.ts +24 -0
  28. package/dist/core/history/history.js +65 -0
  29. package/dist/core/output/sarif.d.ts +35 -0
  30. package/dist/core/output/sarif.js +29 -0
  31. package/dist/core/policy/schemas.d.ts +272 -0
  32. package/dist/core/policy/schemas.js +72 -0
  33. package/dist/core/verification/verify.d.ts +29 -0
  34. package/dist/core/verification/verify.js +77 -0
  35. package/dist/index.d.ts +10 -0
  36. package/dist/index.js +9 -0
  37. package/dist/mcp/prompts.d.ts +2 -0
  38. package/dist/mcp/prompts.js +54 -0
  39. package/dist/mcp/resources.d.ts +2 -0
  40. package/dist/mcp/resources.js +64 -0
  41. package/dist/mcp/server.d.ts +1 -0
  42. package/dist/mcp/server.js +17 -0
  43. package/dist/mcp/tools/guard.d.ts +2 -0
  44. package/dist/mcp/tools/guard.js +375 -0
  45. package/package.json +117 -0
@@ -0,0 +1,55 @@
1
+ import { type GuardToolMode } from '../../core/guard.js';
2
+ export declare function guardInitCommand(options?: {
3
+ force?: boolean;
4
+ }): void;
5
+ export declare function guardAnalyzeCommand(): void;
6
+ export declare function guardProposeCommand(options?: {
7
+ json?: boolean;
8
+ }): void;
9
+ export declare function guardInstallAgentCommand(target?: string, options?: {
10
+ json?: boolean;
11
+ }): void;
12
+ export declare function guardDoctorCommand(options?: {
13
+ json?: boolean;
14
+ }): void;
15
+ export declare function guardHistoryCommand(): void;
16
+ export declare function guardHistoryDiffCommand(from: string, to: string, options?: {
17
+ json?: boolean;
18
+ }): void;
19
+ export declare function guardVerifyCommand(options?: {
20
+ checks?: string;
21
+ changed?: boolean;
22
+ json?: boolean;
23
+ requirement?: string;
24
+ tools?: GuardToolMode;
25
+ strict?: boolean;
26
+ projectChecks?: boolean;
27
+ }): void;
28
+ export declare function guardCheckCommand(options?: {
29
+ changed?: boolean;
30
+ json?: boolean;
31
+ sarif?: boolean;
32
+ }): void;
33
+ export declare function guardAuditCommand(options?: {
34
+ json?: boolean;
35
+ }): void;
36
+ export declare function guardBaselineCommand(action: 'list' | 'accept' | 'remove', ids?: string, options?: {
37
+ all?: boolean;
38
+ reason?: string;
39
+ json?: boolean;
40
+ }): void;
41
+ export declare function guardRulesApproveCommand(ids?: string, options?: {
42
+ all?: boolean;
43
+ }): void;
44
+ export declare function guardContractsApproveCommand(ids?: string, options?: {
45
+ all?: boolean;
46
+ }): void;
47
+ export declare function guardContractsRejectCommand(ids?: string, options?: {
48
+ all?: boolean;
49
+ }): void;
50
+ export declare function guardReviewCommand(options?: {
51
+ maxDiffChars?: string;
52
+ requirement?: string;
53
+ base?: string;
54
+ }): void;
55
+ export declare function guardIsInitialized(root: string): boolean;
@@ -0,0 +1,510 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
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, buildGuardReviewPacket, buildGuardProposals, buildGeneratedGuardConfig, discoverProject, GuardAlreadyInitializedError, initializeGuard, loadBaseline, updateBaseline, loadGuardConfig, loadGuardProposals, writeGuardConfig, writeGuardProposals, recordGuardProposalDecision, getGuardProposalFreshness, scanGuard, classifyGuardOutcome, validateGuardContracts, writeProjectModel, writeGuardMemory, writeProjectSnapshot, } from '../../core/guard.js';
4
+ import { runGuardVerification, } from '../../core/verification/verify.js';
5
+ import { diagnoseGuard } from '../../core/analysis/doctor.js';
6
+ import { diffGuardSnapshots, listGuardSnapshots } from '../../core/history/history.js';
7
+ import { guardAgentTargets, installGuardAgentInstructions, installGuardAgentTargets, } from '../../adapters/agents/agent-integration.js';
8
+ import { dim, fail, heading, info, success, warn } from '../ui.js';
9
+ import { guardFindingsToSarif } from '../../core/output/sarif.js';
10
+ function renderFindings(findings) {
11
+ for (const finding of findings) {
12
+ const printer = finding.severity === 'error' ? fail : finding.severity === 'warning' ? warn : info;
13
+ printer(`${finding.file}:${finding.line} [${finding.ruleId}] ${finding.message}`);
14
+ dim(` import: ${finding.importPath}`);
15
+ }
16
+ }
17
+ function getRoot() {
18
+ return findGuardRoot();
19
+ }
20
+ function readRequirement(root, file) {
21
+ if (!file)
22
+ return undefined;
23
+ const path = resolve(root, file);
24
+ const relativePath = relative(root, path);
25
+ if (relativePath.split(/[\\/]/).includes('..')) {
26
+ fail(`--requirement ${file}: file must be inside the project root`);
27
+ process.exitCode = 1;
28
+ return undefined;
29
+ }
30
+ try {
31
+ const value = readFileSync(path, 'utf8').trim();
32
+ if (!value)
33
+ throw new Error('file is empty');
34
+ if (value.length > 20_000)
35
+ throw new Error('file exceeds the 20,000 character limit');
36
+ return value;
37
+ }
38
+ catch (error) {
39
+ const message = error instanceof Error ? error.message : 'cannot read file';
40
+ fail(`--requirement ${file}: ${message}`);
41
+ process.exitCode = 1;
42
+ return undefined;
43
+ }
44
+ }
45
+ export function guardInitCommand(options = {}) {
46
+ const root = getRoot();
47
+ let initialized;
48
+ try {
49
+ initialized = initializeGuard(root, { force: options.force });
50
+ }
51
+ catch (error) {
52
+ if (error instanceof GuardAlreadyInitializedError) {
53
+ fail(`${error.message} Use \`codapult-guard init --force\` to replace it.`);
54
+ }
55
+ else {
56
+ fail(error instanceof Error ? error.message : 'Guard initialization failed.');
57
+ }
58
+ process.exitCode = 1;
59
+ return;
60
+ }
61
+ const { config, report } = initialized;
62
+ heading('Codapult Guard Init');
63
+ success(`Created ${GUARD_RULES_FILE}`);
64
+ success(`Created ${GUARD_ARCHITECTURE_FILE}`);
65
+ success(`Created ${GUARD_CONVENTIONS_FILE}`);
66
+ success(`Created ${GUARD_BASELINE_FILE}`);
67
+ success(`Created ${GUARD_BASELINE_META_FILE}`);
68
+ success(`Created ${GUARD_PROJECT_FILE}`);
69
+ success(`Created ${GUARD_AGENT_FILE}`);
70
+ success(`Created ${GUARD_CONTRACTS_FILE}`);
71
+ success(`Created ${GUARD_PROPOSALS_FILE}`);
72
+ const proposedRules = config.rules.filter((rule) => rule.status === 'proposed').length;
73
+ info(`${config.rules.length} rule(s) generated (${proposedRules} proposed); ${report.scannedFiles} source file(s) scanned.`);
74
+ dim('Existing findings are baselined. New violations will be reported by `codapult-guard check`.');
75
+ }
76
+ export function guardAnalyzeCommand() {
77
+ const root = getRoot();
78
+ const { model: projectModel, metrics } = discoverProjectWithMetrics(root, {
79
+ persistCache: true,
80
+ });
81
+ writeProjectModel(root, projectModel);
82
+ writeGuardMemory(root, projectModel);
83
+ const revision = writeProjectSnapshot(root, projectModel);
84
+ heading('Codapult Guard Analyze');
85
+ success(`Updated ${GUARD_PROJECT_FILE}, ${GUARD_ARCHITECTURE_FILE}, and ${GUARD_CONVENTIONS_FILE}`);
86
+ info(`${projectModel.files.length} file(s), ${projectModel.modules.length} module(s), ${projectModel.insights.cycles.length} cycle(s) recorded as project context.`);
87
+ dim(`Discovery: ${metrics.durationMs} ms; cache ${metrics.cacheHit ? 'hit' : metrics.cacheAvailable ? 'available' : 'cold'}; ${metrics.modules} module(s), ${metrics.changedFiles} changed file(s).`);
88
+ dim(`Snapshot: ${GUARD_HISTORY_DIR}/${revision}.json`);
89
+ }
90
+ export function guardProposeCommand(options = {}) {
91
+ const root = getRoot();
92
+ const model = discoverProject(root);
93
+ const config = loadGuardConfig(root) ?? buildGeneratedGuardConfig(model);
94
+ const proposals = buildGuardProposals(model, config);
95
+ const previous = loadGuardProposals(root);
96
+ writeGuardProposals(root, {
97
+ ...proposals,
98
+ ...(previous?.decisions ? { decisions: previous.decisions } : {}),
99
+ });
100
+ const result = {
101
+ ...proposals,
102
+ freshness: getGuardProposalFreshness(model, proposals),
103
+ instructions: 'Give this proposal packet to the host AI for evidence-based contract refinement. Do not activate proposals without review.',
104
+ };
105
+ if (options.json)
106
+ console.log(JSON.stringify(result, null, 2));
107
+ else {
108
+ heading('Codapult Guard Proposals');
109
+ info(`${proposals.rules.length} rule proposal(s), ${proposals.contracts.length} contract proposal(s).`);
110
+ for (const question of proposals.questions)
111
+ warn(question);
112
+ dim(`Saved ${GUARD_PROPOSALS_FILE}`);
113
+ }
114
+ process.exitCode = 0;
115
+ }
116
+ export function guardInstallAgentCommand(target = 'generic', options = {}) {
117
+ const normalized = target.toLowerCase();
118
+ const targets = normalized === 'all' ? guardAgentTargets : [normalized];
119
+ if (!targets.every((item) => guardAgentTargets.includes(item))) {
120
+ fail(`Unknown agent target. Choose one of: ${guardAgentTargets.join(', ')}, all.`);
121
+ process.exitCode = 1;
122
+ return;
123
+ }
124
+ const results = targets.length === 1
125
+ ? [installGuardAgentInstructions(getRoot(), targets.at(0) ?? 'generic')]
126
+ : installGuardAgentTargets(getRoot(), targets);
127
+ if (options.json)
128
+ console.log(JSON.stringify(results, null, 2));
129
+ else {
130
+ heading('Codapult Guard Agent Integration');
131
+ for (const result of results)
132
+ success(`${result.action} ${result.path}`);
133
+ }
134
+ process.exitCode = 0;
135
+ }
136
+ export function guardDoctorCommand(options = {}) {
137
+ const report = diagnoseGuard(getRoot());
138
+ if (options.json) {
139
+ console.log(JSON.stringify(report, null, 2));
140
+ }
141
+ else {
142
+ heading('Codapult Guard Doctor');
143
+ for (const item of report.items) {
144
+ if (item.status === 'ok')
145
+ success(`${item.path}: ok`);
146
+ else if (item.status === 'missing')
147
+ warn(`${item.path}: missing`);
148
+ else
149
+ fail(`${item.path}: invalid`);
150
+ }
151
+ if (report.recommendation)
152
+ dim(report.recommendation);
153
+ else
154
+ success('Guard state is healthy.');
155
+ }
156
+ process.exitCode = report.status === 'fail' ? 1 : 0;
157
+ }
158
+ export function guardHistoryCommand() {
159
+ const snapshots = listGuardSnapshots(getRoot());
160
+ heading('Codapult Guard History');
161
+ if (snapshots.length === 0) {
162
+ info('No Guard snapshots found. Run `codapult-guard analyze`.');
163
+ return;
164
+ }
165
+ for (const snapshot of snapshots) {
166
+ info(`${snapshot.revision}: ${snapshot.files} files, ${snapshot.modules} modules, ${snapshot.cycles} cycles`);
167
+ }
168
+ }
169
+ export function guardHistoryDiffCommand(from, to, options = {}) {
170
+ const diff = diffGuardSnapshots(getRoot(), from, to);
171
+ if (!diff) {
172
+ fail(`Could not load snapshots '${from}' and '${to}'.`);
173
+ process.exitCode = 1;
174
+ return;
175
+ }
176
+ if (options.json)
177
+ console.log(JSON.stringify(diff, null, 2));
178
+ else {
179
+ heading(`Guard Snapshot Diff: ${from} → ${to}`);
180
+ info(`Added files: ${diff.addedFiles.length}`);
181
+ info(`Removed files: ${diff.removedFiles.length}`);
182
+ info(`Added dependencies: ${diff.addedDependencies.join(', ') || 'none'}`);
183
+ info(`Removed dependencies: ${diff.removedDependencies.join(', ') || 'none'}`);
184
+ info(`Capabilities: +${diff.addedCapabilities.join(', ') || 'none'} / -${diff.removedCapabilities.join(', ') || 'none'}`);
185
+ info(`Cycles: ${diff.cycles.from} → ${diff.cycles.to}`);
186
+ }
187
+ process.exitCode = 0;
188
+ }
189
+ export function guardVerifyCommand(options = {}) {
190
+ const root = getRoot();
191
+ const requirement = readRequirement(root, options.requirement);
192
+ if (options.requirement && !requirement)
193
+ return;
194
+ const checks = options.checks
195
+ ?.split(',')
196
+ .map((check) => check.trim())
197
+ .filter((check) => ['lint', 'typecheck', 'test', 'build'].includes(check));
198
+ const result = runGuardVerification(root, {
199
+ checks,
200
+ changedOnly: options.changed,
201
+ requirement,
202
+ tools: options.tools,
203
+ strict: options.strict,
204
+ projectChecks: options.projectChecks,
205
+ });
206
+ if (options.json) {
207
+ console.log(JSON.stringify(result, null, 2));
208
+ process.exitCode = result.status === 'ok' ? 0 : 1;
209
+ return;
210
+ }
211
+ heading('Codapult Guard Verify');
212
+ if (result.status === 'not-configured') {
213
+ fail('Guard is not initialized. Run `codapult-guard init` first.');
214
+ process.exitCode = 1;
215
+ return;
216
+ }
217
+ for (const [name, check] of Object.entries(result.checks)) {
218
+ if (check.status === 'passed')
219
+ success(`${name}: passed`);
220
+ else if (check.status === 'not-configured')
221
+ dim(`${name}: not configured`);
222
+ else
223
+ fail(`${name}: failed (exit ${check.exitCode})`);
224
+ }
225
+ for (const [name, check] of Object.entries(result.adapters ?? {})) {
226
+ if (check.status === 'passed')
227
+ success(`${name}: passed`);
228
+ else if (check.status === 'not-configured')
229
+ dim(`${name}: not configured`);
230
+ else
231
+ fail(`${name}: failed (exit ${check.exitCode})`);
232
+ }
233
+ const findings = result.architecture?.findings ?? [];
234
+ if (findings.length === 0)
235
+ success('architecture: no new regressions');
236
+ else
237
+ renderFindings(findings);
238
+ for (const issue of result.contractIssues)
239
+ warn(`[${issue.contractId}] ${issue.message}`);
240
+ if (result.contractIssues.length > 0)
241
+ fail(`contracts: ${result.contractIssues.length} invalid`);
242
+ dim(`requirements: ${result.requirement.message}`);
243
+ process.exitCode = result.status === 'ok' ? 0 : 1;
244
+ }
245
+ export function guardCheckCommand(options = {}) {
246
+ const root = getRoot();
247
+ const config = loadGuardConfig(root);
248
+ if (!config) {
249
+ if (options.json) {
250
+ console.log(JSON.stringify({ configured: false, error: 'Guard is not initialized' }));
251
+ process.exitCode = 1;
252
+ return;
253
+ }
254
+ fail(`Guard is not initialized. Run \`codapult-guard init\` first.`);
255
+ process.exitCode = 1;
256
+ return;
257
+ }
258
+ const report = scanGuard(root, config, {
259
+ changedOnly: options.changed,
260
+ baseline: loadBaseline(root),
261
+ includeArchitectureInsights: true,
262
+ });
263
+ const contractIssues = validateGuardContracts(root, config.contracts ?? []);
264
+ const errors = report.findings.filter((finding) => finding.severity === 'error').length;
265
+ const warnings = report.findings.filter((finding) => finding.severity === 'warning').length;
266
+ const contractFindings = contractIssues.map((issue) => ({
267
+ ruleId: `contract:${issue.contractId}`,
268
+ severity: 'error',
269
+ file: issue.value,
270
+ line: 1,
271
+ importPath: 'contract',
272
+ message: issue.message,
273
+ fingerprint: `contract-invalid|${issue.contractId}|${issue.field}|${issue.value}`,
274
+ }));
275
+ if (options.sarif) {
276
+ console.log(JSON.stringify(guardFindingsToSarif([...report.findings, ...contractFindings]), null, 2));
277
+ process.exitCode = errors > 0 || contractIssues.length > 0 ? 1 : 0;
278
+ return;
279
+ }
280
+ if (options.json) {
281
+ console.log(JSON.stringify({
282
+ status: errors > 0 || contractIssues.length > 0 ? 'fail' : warnings > 0 ? 'warning' : 'ok',
283
+ outcome: classifyGuardOutcome({ errors: errors + contractIssues.length, warnings }),
284
+ errors,
285
+ warnings,
286
+ contractIssues,
287
+ report,
288
+ }, null, 2));
289
+ process.exitCode = errors > 0 || contractIssues.length > 0 ? 1 : 0;
290
+ return;
291
+ }
292
+ heading(`Codapult Guard${options.changed ? ' (changed files)' : ''}`);
293
+ dim(`Scanned ${report.scannedFiles} source file(s); suppressed ${report.suppressed} baseline finding(s).`);
294
+ renderFindings(report.findings);
295
+ for (const issue of contractIssues)
296
+ warn(`[${issue.contractId}] ${issue.message}`);
297
+ if (errors > 0 || contractIssues.length > 0)
298
+ fail(`${errors + contractIssues.length} issue(s) found`);
299
+ else if (warnings > 0)
300
+ warn(`${warnings} warning(s) found`);
301
+ else
302
+ success('No new guard violations found.');
303
+ process.exitCode = errors > 0 || contractIssues.length > 0 ? 1 : 0;
304
+ }
305
+ export function guardAuditCommand(options = {}) {
306
+ const root = getRoot();
307
+ const config = loadGuardConfig(root);
308
+ if (!config) {
309
+ if (options.json) {
310
+ console.log(JSON.stringify({ configured: false, error: 'Guard is not initialized' }));
311
+ process.exitCode = 1;
312
+ return;
313
+ }
314
+ fail(`Guard is not initialized. Run \`codapult-guard init\` first.`);
315
+ process.exitCode = 1;
316
+ return;
317
+ }
318
+ const report = scanGuard(root, config, { includeArchitectureInsights: true });
319
+ const contractIssues = validateGuardContracts(root, config.contracts ?? []);
320
+ const errors = report.findings.filter((finding) => finding.severity === 'error').length;
321
+ const warnings = report.findings.filter((finding) => finding.severity === 'warning').length;
322
+ if (options.json) {
323
+ console.log(JSON.stringify({
324
+ status: errors > 0 || contractIssues.length > 0 ? 'fail' : warnings > 0 ? 'warning' : 'ok',
325
+ errors,
326
+ warnings,
327
+ outcome: classifyGuardOutcome({
328
+ errors: errors + contractIssues.length,
329
+ warnings,
330
+ }),
331
+ contractIssues,
332
+ report,
333
+ }, null, 2));
334
+ process.exitCode = errors > 0 || contractIssues.length > 0 ? 1 : 0;
335
+ return;
336
+ }
337
+ heading('Codapult Guard Audit');
338
+ dim(`Scanned ${report.scannedFiles} source file(s); baseline is ignored for this full audit.`);
339
+ renderFindings(report.findings);
340
+ for (const issue of contractIssues)
341
+ warn(`[${issue.contractId}] ${issue.message}`);
342
+ if (errors > 0)
343
+ fail(`${errors} error(s) found`);
344
+ else if (warnings > 0 || contractIssues.length > 0) {
345
+ warn(`${warnings + contractIssues.length} warning(s) found`);
346
+ }
347
+ else
348
+ success('No Guard issues found.');
349
+ process.exitCode = errors > 0 || contractIssues.length > 0 ? 1 : 0;
350
+ }
351
+ export function guardBaselineCommand(action, ids, options = {}) {
352
+ const root = getRoot();
353
+ const baseline = loadBaseline(root);
354
+ if (action === 'list') {
355
+ const result = { count: baseline.size, fingerprints: [...baseline].sort() };
356
+ if (options.json)
357
+ console.log(JSON.stringify(result, null, 2));
358
+ else {
359
+ heading('Codapult Guard Baseline');
360
+ info(`${result.count} fingerprint(s)`);
361
+ for (const fingerprint of result.fingerprints)
362
+ dim(fingerprint);
363
+ }
364
+ process.exitCode = 0;
365
+ return;
366
+ }
367
+ const selected = options.all
368
+ ? [
369
+ ...scanGuard(root, loadGuardConfig(root) ?? buildGeneratedGuardConfig(discoverProject(root)), {
370
+ includeArchitectureInsights: true,
371
+ }).findings.map((finding) => finding.fingerprint),
372
+ ]
373
+ : (ids ?? '')
374
+ .split(',')
375
+ .map((id) => id.trim())
376
+ .filter(Boolean);
377
+ if (selected.length === 0) {
378
+ fail(`Specify fingerprints or use --all to ${action} findings.`);
379
+ process.exitCode = 1;
380
+ return;
381
+ }
382
+ const next = updateBaseline(root, action === 'accept'
383
+ ? { add: selected, reason: options.reason }
384
+ : { remove: selected, reason: options.reason });
385
+ const result = { action, changed: selected.length, count: next.size, fingerprints: selected };
386
+ if (options.json)
387
+ console.log(JSON.stringify(result, null, 2));
388
+ else
389
+ success(`${action === 'accept' ? 'Accepted' : 'Removed'} ${selected.length} baseline fingerprint(s).`);
390
+ process.exitCode = 0;
391
+ }
392
+ export function guardRulesApproveCommand(ids, options = {}) {
393
+ const root = getRoot();
394
+ const config = loadGuardConfig(root);
395
+ if (!config) {
396
+ fail('Guard is not initialized. Run `codapult-guard init` first.');
397
+ process.exitCode = 1;
398
+ return;
399
+ }
400
+ const requested = new Set(ids
401
+ ?.split(',')
402
+ .map((id) => id.trim())
403
+ .filter(Boolean));
404
+ const proposed = config.rules.filter((rule) => rule.status === 'proposed');
405
+ const selected = proposed.filter((rule) => options.all || requested.has(rule.id));
406
+ if (!options.all && selected.length === 0) {
407
+ fail('Specify proposed rule IDs or use `codapult-guard rules approve --all`.');
408
+ process.exitCode = 1;
409
+ return;
410
+ }
411
+ const selectedIds = new Set(selected.map((rule) => rule.id));
412
+ writeGuardConfig(root, {
413
+ ...config,
414
+ rules: config.rules.map((rule) => selectedIds.has(rule.id) ? { ...rule, status: 'active' } : rule),
415
+ });
416
+ recordGuardProposalDecision(root, selected.map((rule) => ({ id: rule.id, type: 'rule', decision: 'approved' })));
417
+ for (const rule of selected)
418
+ success(`Activated ${rule.id}`);
419
+ process.exitCode = 0;
420
+ }
421
+ export function guardContractsApproveCommand(ids, options = {}) {
422
+ const root = getRoot();
423
+ const config = loadGuardConfig(root);
424
+ if (!config) {
425
+ fail('Guard is not initialized. Run `codapult-guard init` first.');
426
+ process.exitCode = 1;
427
+ return;
428
+ }
429
+ const requested = new Set(ids
430
+ ?.split(',')
431
+ .map((id) => id.trim())
432
+ .filter(Boolean));
433
+ const proposed = (config.contracts ?? []).filter((contract) => contract.status === 'proposed');
434
+ const selected = proposed.filter((contract) => options.all || requested.has(contract.id));
435
+ if (!options.all && selected.length === 0) {
436
+ fail('Specify proposed contract IDs or use `codapult-guard contracts approve --all`.');
437
+ process.exitCode = 1;
438
+ return;
439
+ }
440
+ const selectedIds = new Set(selected.map((contract) => contract.id));
441
+ writeGuardConfig(root, {
442
+ ...config,
443
+ contracts: (config.contracts ?? []).map((contract) => selectedIds.has(contract.id) ? { ...contract, status: 'active' } : contract),
444
+ });
445
+ recordGuardProposalDecision(root, selected.map((contract) => ({
446
+ id: contract.id,
447
+ type: 'contract',
448
+ decision: 'approved',
449
+ })));
450
+ for (const contract of selected)
451
+ success(`Activated ${contract.id}`);
452
+ process.exitCode = 0;
453
+ }
454
+ export function guardContractsRejectCommand(ids, options = {}) {
455
+ const root = getRoot();
456
+ const config = loadGuardConfig(root);
457
+ if (!config) {
458
+ fail('Guard is not initialized. Run `codapult-guard init` first.');
459
+ process.exitCode = 1;
460
+ return;
461
+ }
462
+ const requested = new Set(ids
463
+ ?.split(',')
464
+ .map((id) => id.trim())
465
+ .filter(Boolean));
466
+ const proposed = (config.contracts ?? []).filter((contract) => contract.status === 'proposed');
467
+ const selected = proposed.filter((contract) => options.all || requested.has(contract.id));
468
+ if (!options.all && selected.length === 0) {
469
+ fail('Specify proposed contract IDs or use `codapult-guard contracts reject --all`.');
470
+ process.exitCode = 1;
471
+ return;
472
+ }
473
+ writeGuardConfig(root, {
474
+ ...config,
475
+ contracts: config.contracts ?? [],
476
+ });
477
+ recordGuardProposalDecision(root, selected.map((contract) => ({
478
+ id: contract.id,
479
+ type: 'contract',
480
+ decision: 'rejected',
481
+ })));
482
+ for (const contract of selected)
483
+ success(`Rejected ${contract.id}`);
484
+ process.exitCode = 0;
485
+ }
486
+ export function guardReviewCommand(options = {}) {
487
+ const root = getRoot();
488
+ const requirement = readRequirement(root, options.requirement);
489
+ if (options.requirement && !requirement)
490
+ return;
491
+ const config = loadGuardConfig(root);
492
+ if (!config) {
493
+ fail(`Guard is not initialized. Run \`codapult-guard init\` first.`);
494
+ process.exitCode = 1;
495
+ return;
496
+ }
497
+ const maxDiffChars = options.maxDiffChars ? Number(options.maxDiffChars) : 120_000;
498
+ if (!Number.isInteger(maxDiffChars) || maxDiffChars < 1) {
499
+ fail('--max-diff-chars must be a positive integer.');
500
+ process.exitCode = 1;
501
+ return;
502
+ }
503
+ const packet = buildGuardReviewPacket(root, config, loadBaseline(root), maxDiffChars, true, requirement, options.base);
504
+ console.log(JSON.stringify(packet, null, 2));
505
+ if (packet.diffError)
506
+ process.exitCode = 1;
507
+ }
508
+ export function guardIsInitialized(root) {
509
+ return existsSync(resolve(root, GUARD_RULES_FILE));
510
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from 'commander';
3
+ import pc from 'picocolors';
4
+ import { guardAnalyzeCommand, guardAuditCommand, guardBaselineCommand, guardCheckCommand, guardContractsApproveCommand, guardContractsRejectCommand, guardDoctorCommand, guardHistoryCommand, guardHistoryDiffCommand, guardInitCommand, guardInstallAgentCommand, guardProposeCommand, guardReviewCommand, guardRulesApproveCommand, guardVerifyCommand, } from './commands/guard.js';
5
+ import { config } from '../core/config.js';
6
+ const program = new Command()
7
+ .name(config.commandName)
8
+ .description('Local-first architecture guardrails for JavaScript and TypeScript projects')
9
+ .configureHelp({
10
+ styleTitle: (str) => pc.bold(pc.cyan(str)),
11
+ styleCommandText: (str) => pc.yellow(str),
12
+ styleOptionText: (str) => pc.green(str),
13
+ });
14
+ // Guard is the standalone product, so its commands live at the package root:
15
+ // `codapult-guard init`, not `codapult-guard guard init`.
16
+ const guard = program;
17
+ guard.command('init').option('--force').action(guardInitCommand);
18
+ guard.command('analyze').action(guardAnalyzeCommand);
19
+ guard.command('propose').option('--json').action(guardProposeCommand);
20
+ guard.command('install-agent [target]').option('--json').action(guardInstallAgentCommand);
21
+ guard.command('doctor').option('--json').action(guardDoctorCommand);
22
+ guard.command('history').action(guardHistoryCommand);
23
+ guard.command('history-diff <from> <to>').option('--json').action(guardHistoryDiffCommand);
24
+ guard
25
+ .command('check')
26
+ .option('--changed')
27
+ .option('--json')
28
+ .option('--sarif')
29
+ .action(guardCheckCommand);
30
+ guard.command('audit').option('--json').action(guardAuditCommand);
31
+ guard
32
+ .command('verify')
33
+ .option('--checks <list>')
34
+ .option('--tools <mode>', 'external tools: auto, on, or off', 'auto')
35
+ .option('--strict')
36
+ .option('--no-project-checks')
37
+ .option('--requirement <file>')
38
+ .option('--changed')
39
+ .option('--json')
40
+ .action(guardVerifyCommand);
41
+ guard
42
+ .command('review')
43
+ .option('--max-diff-chars <number>', undefined, '120000')
44
+ .option('--base <ref>')
45
+ .option('--requirement <file>')
46
+ .action(guardReviewCommand);
47
+ const rules = guard.command('rules');
48
+ rules.command('approve [ids]').option('--all').action(guardRulesApproveCommand);
49
+ const contracts = guard.command('contracts');
50
+ contracts.command('approve [ids]').option('--all').action(guardContractsApproveCommand);
51
+ contracts.command('reject [ids]').option('--all').action(guardContractsRejectCommand);
52
+ const baseline = guard.command('baseline');
53
+ baseline
54
+ .command('list')
55
+ .option('--json')
56
+ .action((options) => guardBaselineCommand('list', undefined, options));
57
+ baseline
58
+ .command('accept [ids]')
59
+ .option('--all')
60
+ .option('--reason <text>')
61
+ .option('--json')
62
+ .action((ids, options) => guardBaselineCommand('accept', ids, options));
63
+ baseline
64
+ .command('remove [ids]')
65
+ .option('--all')
66
+ .option('--reason <text>')
67
+ .option('--json')
68
+ .action((ids, options) => guardBaselineCommand('remove', ids, options));
69
+ program
70
+ .command('mcp-server')
71
+ .description('start the Guard MCP server over stdio')
72
+ .action(async () => {
73
+ await import('../mcp/server.js');
74
+ });
75
+ program.parse();
@@ -0,0 +1,6 @@
1
+ export declare const heading: (value: string) => void;
2
+ export declare const success: (value: string) => void;
3
+ export declare const info: (value: string) => void;
4
+ export declare const warn: (value: string) => void;
5
+ export declare const fail: (value: string) => void;
6
+ export declare const dim: (value: string) => void;
package/dist/cli/ui.js ADDED
@@ -0,0 +1,7 @@
1
+ import pc from 'picocolors';
2
+ export const heading = (value) => console.log(`\n${pc.bold(pc.cyan(value))}\n`);
3
+ export const success = (value) => console.log(pc.green(`✓ ${value}`));
4
+ export const info = (value) => console.log(pc.cyan(`ℹ ${value}`));
5
+ export const warn = (value) => console.warn(pc.yellow(`⚠ ${value}`));
6
+ export const fail = (value) => console.error(pc.red(`✗ ${value}`));
7
+ export const dim = (value) => console.log(pc.dim(value));
@@ -0,0 +1,2 @@
1
+ /** Compatibility entrypoint for consumers migrating from the pre-standalone layout. */
2
+ export * from '../cli/commands/guard.js';
@@ -0,0 +1,2 @@
1
+ /** Compatibility entrypoint for consumers migrating from the pre-standalone layout. */
2
+ export * from '../cli/commands/guard.js';
@@ -0,0 +1,12 @@
1
+ export interface GuardDoctorItem {
2
+ path: string;
3
+ status: 'ok' | 'missing' | 'invalid';
4
+ message: string;
5
+ }
6
+ export interface GuardDoctorReport {
7
+ status: 'ok' | 'warning' | 'fail';
8
+ initialized: boolean;
9
+ items: GuardDoctorItem[];
10
+ recommendation?: string;
11
+ }
12
+ export declare function diagnoseGuard(root: string): GuardDoctorReport;