@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.
@@ -1,6 +1,7 @@
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, buildGuardReviewPacket, buildGuardProposals, buildGeneratedGuardConfig, discoverProject, GuardAlreadyInitializedError, initializeGuard, loadBaseline, updateBaseline, loadGuardConfig, loadGuardProposals, writeGuardConfig, writeGuardProposals, recordGuardProposalDecision, getGuardProposalFreshness, scanGuard, classifyGuardOutcome, validateGuardContracts, writeProjectModel, writeGuardMemory, writeProjectSnapshot, } from '../../core/guard.js';
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
+ import { guardErrorPayload } from '../../core/errors.js';
4
5
  import { runGuardVerification, } from '../../core/verification/verify.js';
5
6
  import { diagnoseGuard } from '../../core/analysis/doctor.js';
6
7
  import { diffGuardSnapshots, listGuardSnapshots } from '../../core/history/history.js';
@@ -13,11 +14,33 @@ function renderFindings(findings) {
13
14
  const printer = finding.severity === 'error' ? fail : finding.severity === 'warning' ? warn : info;
14
15
  printer(`${finding.file}:${finding.line} [${finding.ruleId}] ${finding.message}`);
15
16
  dim(` import: ${finding.importPath}`);
17
+ if (finding.resolvedPath)
18
+ dim(` resolved: ${finding.resolvedPath}`);
16
19
  }
17
20
  }
18
21
  function getRoot() {
19
22
  return findGuardRoot();
20
23
  }
24
+ function loadConfigSafely(root, options = {}) {
25
+ try {
26
+ return { config: loadGuardConfig(root), invalid: false };
27
+ }
28
+ catch (error) {
29
+ const message = error instanceof Error ? error.message : 'Guard configuration is invalid.';
30
+ const payload = guardErrorPayload('GUARD_CONFIG_INVALID', message, {
31
+ configured: false,
32
+ outcome: 'error',
33
+ recoverable: true,
34
+ hint: 'Repair or remove the invalid Guard artifact, then run `codapult-guard doctor`.',
35
+ });
36
+ if (options.json || options.machine)
37
+ console.log(JSON.stringify(payload, null, 2));
38
+ else
39
+ fail(`${payload.message} Run \`codapult-guard doctor\` for details.`);
40
+ process.exitCode = 1;
41
+ return { invalid: true };
42
+ }
43
+ }
21
44
  function readRequirement(root, file) {
22
45
  if (!file)
23
46
  return undefined;
@@ -47,7 +70,7 @@ export function guardInitCommand(options = {}) {
47
70
  const root = getRoot();
48
71
  let initialized;
49
72
  try {
50
- initialized = initializeGuard(root, { force: options.force });
73
+ initialized = initializeGuard(root, options.wait === false ? { force: options.force, noWait: true } : { force: options.force });
51
74
  }
52
75
  catch (error) {
53
76
  if (error instanceof GuardAlreadyInitializedError) {
@@ -74,14 +97,12 @@ export function guardInitCommand(options = {}) {
74
97
  info(`${config.rules.length} rule(s) generated (${proposedRules} proposed); ${report.scannedFiles} source file(s) scanned.`);
75
98
  dim('Existing findings are baselined. New violations will be reported by `codapult-guard check`.');
76
99
  }
77
- export function guardAnalyzeCommand() {
100
+ export function guardAnalyzeCommand(options = {}) {
78
101
  const root = getRoot();
79
102
  const { model: projectModel, metrics } = discoverProjectWithMetrics(root, {
80
103
  persistCache: true,
81
104
  });
82
- writeProjectModel(root, projectModel);
83
- writeGuardMemory(root, projectModel);
84
- const revision = writeProjectSnapshot(root, projectModel);
105
+ const revision = writeProjectState(root, projectModel, options.wait === false ? { noWait: true } : undefined);
85
106
  heading('Codapult Guard Analyze');
86
107
  success(`Updated ${GUARD_PROJECT_FILE}, ${GUARD_ARCHITECTURE_FILE}, and ${GUARD_CONVENTIONS_FILE}`);
87
108
  info(`${projectModel.files.length} file(s), ${projectModel.modules.length} module(s), ${projectModel.insights.cycles.length} cycle(s) recorded as project context.`);
@@ -91,7 +112,10 @@ export function guardAnalyzeCommand() {
91
112
  export function guardProposeCommand(options = {}) {
92
113
  const root = getRoot();
93
114
  const model = discoverProject(root);
94
- const config = loadGuardConfig(root) ?? buildGeneratedGuardConfig(model);
115
+ const loaded = loadConfigSafely(root, options);
116
+ if (loaded.invalid)
117
+ return;
118
+ const config = loaded.config ?? buildGeneratedGuardConfig(model);
95
119
  const proposals = buildGuardProposals(model, config);
96
120
  const previous = loadGuardProposals(root);
97
121
  writeGuardProposals(root, {
@@ -135,9 +159,12 @@ export function guardInstallAgentCommand(target = 'generic', options = {}) {
135
159
  process.exitCode = 0;
136
160
  }
137
161
  export function guardDoctorCommand(options = {}) {
162
+ if (options.fixCache)
163
+ clearDiscoveryCache(getRoot());
138
164
  const report = diagnoseGuard(getRoot());
165
+ const result = options.fixCache ? { ...report, cacheFixed: true } : report;
139
166
  if (options.json) {
140
- console.log(JSON.stringify(report, null, 2));
167
+ console.log(JSON.stringify(result, null, 2));
141
168
  }
142
169
  else {
143
170
  heading('Codapult Guard Doctor');
@@ -154,7 +181,7 @@ export function guardDoctorCommand(options = {}) {
154
181
  else
155
182
  success('Guard state is healthy.');
156
183
  }
157
- process.exitCode = report.status === 'fail' ? 1 : 0;
184
+ process.exitCode = result.status === 'fail' ? 1 : 0;
158
185
  }
159
186
  export function guardHistoryCommand() {
160
187
  const snapshots = listGuardSnapshots(getRoot());
@@ -174,7 +201,10 @@ export function guardImpactCommand(files, options = {}) {
174
201
  process.exitCode = 1;
175
202
  return;
176
203
  }
177
- const result = analyzeProjectImpact(discoverProject(root), files, loadGuardConfig(root)?.contracts ?? []);
204
+ const loaded = loadConfigSafely(root, options);
205
+ if (loaded.invalid)
206
+ return;
207
+ const result = analyzeProjectImpact(discoverProject(root), files, loaded.config?.contracts ?? []);
178
208
  if (options.json) {
179
209
  console.log(JSON.stringify({ status: 'ok', ...result }, null, 2));
180
210
  return;
@@ -268,10 +298,20 @@ export function guardVerifyCommand(options = {}) {
268
298
  }
269
299
  export function guardCheckCommand(options = {}) {
270
300
  const root = getRoot();
271
- const config = loadGuardConfig(root);
301
+ const loaded = loadConfigSafely(root, { ...options, machine: options.json || options.sarif });
302
+ if (loaded.invalid)
303
+ return;
304
+ const config = loaded.config;
272
305
  if (!config) {
273
306
  if (options.json) {
274
- console.log(JSON.stringify({ configured: false, error: 'Guard is not initialized' }));
307
+ console.log(JSON.stringify({
308
+ status: 'error',
309
+ outcome: 'not-configured',
310
+ configured: false,
311
+ errorCode: 'GUARD_NOT_CONFIGURED',
312
+ message: 'Guard is not initialized.',
313
+ recoverable: true,
314
+ }));
275
315
  process.exitCode = 1;
276
316
  return;
277
317
  }
@@ -284,7 +324,7 @@ export function guardCheckCommand(options = {}) {
284
324
  baseline: loadBaseline(root),
285
325
  includeArchitectureInsights: true,
286
326
  });
287
- const contractIssues = validateGuardContracts(root, config.contracts ?? []);
327
+ const contractIssues = validateGuardPolicy(root, config);
288
328
  const errors = report.findings.filter((finding) => finding.severity === 'error').length;
289
329
  const warnings = report.findings.filter((finding) => finding.severity === 'warning').length;
290
330
  const contractFindings = contractIssues.map((issue) => ({
@@ -328,10 +368,20 @@ export function guardCheckCommand(options = {}) {
328
368
  }
329
369
  export function guardAuditCommand(options = {}) {
330
370
  const root = getRoot();
331
- const config = loadGuardConfig(root);
371
+ const loaded = loadConfigSafely(root, options);
372
+ if (loaded.invalid)
373
+ return;
374
+ const config = loaded.config;
332
375
  if (!config) {
333
376
  if (options.json) {
334
- console.log(JSON.stringify({ configured: false, error: 'Guard is not initialized' }));
377
+ console.log(JSON.stringify({
378
+ status: 'error',
379
+ outcome: 'not-configured',
380
+ configured: false,
381
+ errorCode: 'GUARD_NOT_CONFIGURED',
382
+ message: 'Guard is not initialized.',
383
+ recoverable: true,
384
+ }));
335
385
  process.exitCode = 1;
336
386
  return;
337
387
  }
@@ -340,7 +390,7 @@ export function guardAuditCommand(options = {}) {
340
390
  return;
341
391
  }
342
392
  const report = scanGuard(root, config, { includeArchitectureInsights: true });
343
- const contractIssues = validateGuardContracts(root, config.contracts ?? []);
393
+ const contractIssues = validateGuardPolicy(root, config);
344
394
  const errors = report.findings.filter((finding) => finding.severity === 'error').length;
345
395
  const warnings = report.findings.filter((finding) => finding.severity === 'warning').length;
346
396
  if (options.json) {
@@ -388,9 +438,12 @@ export function guardBaselineCommand(action, ids, options = {}) {
388
438
  process.exitCode = 0;
389
439
  return;
390
440
  }
441
+ const loaded = options.all ? loadConfigSafely(root, options) : { invalid: false };
442
+ if (loaded.invalid)
443
+ return;
391
444
  const selected = options.all
392
445
  ? [
393
- ...scanGuard(root, loadGuardConfig(root) ?? buildGeneratedGuardConfig(discoverProject(root)), {
446
+ ...scanGuard(root, loaded.config ?? buildGeneratedGuardConfig(discoverProject(root)), {
394
447
  includeArchitectureInsights: true,
395
448
  }).findings.map((finding) => finding.fingerprint),
396
449
  ]
@@ -415,7 +468,10 @@ export function guardBaselineCommand(action, ids, options = {}) {
415
468
  }
416
469
  export function guardRulesApproveCommand(ids, options = {}) {
417
470
  const root = getRoot();
418
- const config = loadGuardConfig(root);
471
+ const loaded = loadConfigSafely(root);
472
+ if (loaded.invalid)
473
+ return;
474
+ const config = loaded.config;
419
475
  if (!config) {
420
476
  fail('Guard is not initialized. Run `codapult-guard init` first.');
421
477
  process.exitCode = 1;
@@ -433,18 +489,69 @@ export function guardRulesApproveCommand(ids, options = {}) {
433
489
  return;
434
490
  }
435
491
  const selectedIds = new Set(selected.map((rule) => rule.id));
436
- writeGuardConfig(root, {
492
+ const approvalError = validateGuardProposalApproval(config.approval, loadGuardProposals(root));
493
+ if (approvalError) {
494
+ fail(approvalError);
495
+ process.exitCode = 1;
496
+ return;
497
+ }
498
+ applyGuardProposalDecision(root, {
437
499
  ...config,
438
500
  rules: config.rules.map((rule) => selectedIds.has(rule.id) ? { ...rule, status: 'active' } : rule),
439
- });
440
- 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' });
441
502
  for (const rule of selected)
442
503
  success(`Activated ${rule.id}`);
443
504
  process.exitCode = 0;
444
505
  }
506
+ export function guardPolicyExplainCommand(id, options = {}) {
507
+ const root = getRoot();
508
+ const loaded = loadConfigSafely(root, options);
509
+ if (loaded.invalid)
510
+ return;
511
+ const config = loaded.config;
512
+ const proposals = loadGuardProposals(root);
513
+ if (!config) {
514
+ fail('Guard is not initialized. Run `codapult-guard init` first.');
515
+ process.exitCode = 1;
516
+ return;
517
+ }
518
+ const item = [...config.rules, ...(config.contracts ?? [])].find((entry) => entry.id === id);
519
+ const proposed = [...(proposals?.rules ?? []), ...(proposals?.contracts ?? [])].find((entry) => entry.id === id);
520
+ if (!item && !proposed) {
521
+ fail(`Policy item not found: ${id}`);
522
+ process.exitCode = 1;
523
+ return;
524
+ }
525
+ const result = {
526
+ id,
527
+ active: item?.status !== 'proposed' && item !== undefined,
528
+ definition: item ?? proposed,
529
+ proposal: proposed,
530
+ decisions: proposals?.decisions?.filter((decision) => decision.id === id) ?? [],
531
+ };
532
+ if (options.json)
533
+ console.log(JSON.stringify(result, null, 2));
534
+ else {
535
+ heading(`Guard Policy: ${id}`);
536
+ info(`Status: ${result.active ? 'active' : 'proposed'}`);
537
+ const definition = result.definition;
538
+ if (definition && 'description' in definition)
539
+ dim(definition.description);
540
+ if (definition && 'statement' in definition)
541
+ dim(definition.statement);
542
+ if ((definition?.evidence?.length ?? 0) > 0)
543
+ dim(`Evidence: ${definition?.evidence?.join(', ')}`);
544
+ if (result.decisions.length > 0)
545
+ dim(`Decisions: ${result.decisions.length}`);
546
+ }
547
+ process.exitCode = 0;
548
+ }
445
549
  export function guardContractsApproveCommand(ids, options = {}) {
446
550
  const root = getRoot();
447
- const config = loadGuardConfig(root);
551
+ const loaded = loadConfigSafely(root);
552
+ if (loaded.invalid)
553
+ return;
554
+ const config = loaded.config;
448
555
  if (!config) {
449
556
  fail('Guard is not initialized. Run `codapult-guard init` first.');
450
557
  process.exitCode = 1;
@@ -462,22 +569,30 @@ export function guardContractsApproveCommand(ids, options = {}) {
462
569
  return;
463
570
  }
464
571
  const selectedIds = new Set(selected.map((contract) => contract.id));
465
- writeGuardConfig(root, {
572
+ const approvalError = validateGuardProposalApproval(config.approval, loadGuardProposals(root));
573
+ if (approvalError) {
574
+ fail(approvalError);
575
+ process.exitCode = 1;
576
+ return;
577
+ }
578
+ applyGuardProposalDecision(root, {
466
579
  ...config,
467
580
  contracts: (config.contracts ?? []).map((contract) => selectedIds.has(contract.id) ? { ...contract, status: 'active' } : contract),
468
- });
469
- recordGuardProposalDecision(root, selected.map((contract) => ({
581
+ }, selected.map((contract) => ({
470
582
  id: contract.id,
471
583
  type: 'contract',
472
584
  decision: 'approved',
473
- })));
585
+ })), { source: 'cli' });
474
586
  for (const contract of selected)
475
587
  success(`Activated ${contract.id}`);
476
588
  process.exitCode = 0;
477
589
  }
478
590
  export function guardContractsRejectCommand(ids, options = {}) {
479
591
  const root = getRoot();
480
- const config = loadGuardConfig(root);
592
+ const loaded = loadConfigSafely(root);
593
+ if (loaded.invalid)
594
+ return;
595
+ const config = loaded.config;
481
596
  if (!config) {
482
597
  fail('Guard is not initialized. Run `codapult-guard init` first.');
483
598
  process.exitCode = 1;
@@ -494,15 +609,20 @@ export function guardContractsRejectCommand(ids, options = {}) {
494
609
  process.exitCode = 1;
495
610
  return;
496
611
  }
497
- writeGuardConfig(root, {
612
+ const approvalError = validateGuardProposalApproval(config.approval, loadGuardProposals(root));
613
+ if (approvalError) {
614
+ fail(approvalError);
615
+ process.exitCode = 1;
616
+ return;
617
+ }
618
+ applyGuardProposalDecision(root, {
498
619
  ...config,
499
620
  contracts: config.contracts ?? [],
500
- });
501
- recordGuardProposalDecision(root, selected.map((contract) => ({
621
+ }, selected.map((contract) => ({
502
622
  id: contract.id,
503
623
  type: 'contract',
504
624
  decision: 'rejected',
505
- })));
625
+ })), { source: 'cli' });
506
626
  for (const contract of selected)
507
627
  success(`Rejected ${contract.id}`);
508
628
  process.exitCode = 0;
@@ -512,7 +632,10 @@ export function guardReviewCommand(options = {}) {
512
632
  const requirement = readRequirement(root, options.requirement);
513
633
  if (options.requirement && !requirement)
514
634
  return;
515
- const config = loadGuardConfig(root);
635
+ const loaded = loadConfigSafely(root);
636
+ if (loaded.invalid)
637
+ return;
638
+ const config = loaded.config;
516
639
  if (!config) {
517
640
  fail(`Guard is not initialized. Run \`codapult-guard init\` first.`);
518
641
  process.exitCode = 1;
package/dist/cli/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
3
  import pc from 'picocolors';
4
- import { guardAnalyzeCommand, guardAuditCommand, guardBaselineCommand, guardCheckCommand, guardContractsApproveCommand, guardContractsRejectCommand, guardDoctorCommand, guardHistoryCommand, guardHistoryDiffCommand, guardImpactCommand, guardInitCommand, guardInstallAgentCommand, guardProposeCommand, guardReviewCommand, guardRulesApproveCommand, guardVerifyCommand, } from './commands/guard.js';
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 { GuardBaselineReasonError, GuardConfigError, GuardStateBusyError, GuardStateStaleError, } from '../core/guard.js';
7
+ import { guardErrorPayload } from '../core/errors.js';
6
8
  const program = new Command()
7
9
  .name(config.commandName)
8
10
  .description('Local-first architecture guardrails for JavaScript and TypeScript projects')
@@ -14,11 +16,11 @@ const program = new Command()
14
16
  // Guard is the standalone product, so its commands live at the package root:
15
17
  // `codapult-guard init`, not `codapult-guard guard init`.
16
18
  const guard = program;
17
- guard.command('init').option('--force').action(guardInitCommand);
18
- guard.command('analyze').action(guardAnalyzeCommand);
19
+ guard.command('init').option('--force').option('--no-wait').action(guardInitCommand);
20
+ guard.command('analyze').option('--refresh').option('--no-wait').action(guardAnalyzeCommand);
19
21
  guard.command('propose').option('--json').action(guardProposeCommand);
20
22
  guard.command('install-agent [target]').option('--json').action(guardInstallAgentCommand);
21
- guard.command('doctor').option('--json').action(guardDoctorCommand);
23
+ guard.command('doctor').option('--json').option('--fix-cache').action(guardDoctorCommand);
22
24
  guard.command('history').action(guardHistoryCommand);
23
25
  guard.command('history-diff <from> <to>').option('--json').action(guardHistoryDiffCommand);
24
26
  guard.command('impact <files...>').option('--json').action(guardImpactCommand);
@@ -47,6 +49,8 @@ guard
47
49
  .action(guardReviewCommand);
48
50
  const rules = guard.command('rules');
49
51
  rules.command('approve [ids]').option('--all').action(guardRulesApproveCommand);
52
+ const policy = guard.command('policy');
53
+ policy.command('explain <id>').option('--json').action(guardPolicyExplainCommand);
50
54
  const contracts = guard.command('contracts');
51
55
  contracts.command('approve [ids]').option('--all').action(guardContractsApproveCommand);
52
56
  contracts.command('reject [ids]').option('--all').action(guardContractsRejectCommand);
@@ -73,4 +77,36 @@ program
73
77
  .action(async () => {
74
78
  await import('../mcp/server.js');
75
79
  });
76
- program.parse();
80
+ try {
81
+ program.parse();
82
+ }
83
+ catch (error) {
84
+ if (error instanceof GuardConfigError ||
85
+ error instanceof GuardBaselineReasonError ||
86
+ error instanceof GuardStateBusyError ||
87
+ error instanceof GuardStateStaleError) {
88
+ const isBusy = error instanceof GuardStateBusyError;
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,
99
+ outcome: 'error',
100
+ recoverable: true,
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.',
106
+ }), null, 2));
107
+ process.exitCode = 1;
108
+ }
109
+ else {
110
+ throw error;
111
+ }
112
+ }
@@ -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, validateGuardContracts, } from '../guard.js';
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,
@@ -22,12 +22,18 @@ function parseJson(root, path) {
22
22
  export function diagnoseGuard(root) {
23
23
  const initialized = existsSync(resolve(root, GUARD_BASELINE_FILE));
24
24
  const items = requiredArtifacts.map((path) => {
25
- const guardConfig = path === GUARD_CONTRACTS_FILE ? loadGuardConfig(root) : undefined;
26
25
  if (!existsSync(resolve(root, path))) {
27
26
  return { path, status: 'missing', message: 'Artifact is missing.' };
28
27
  }
29
- if (path === GUARD_RULES_FILE && !loadGuardConfig(root)) {
30
- return { path, status: 'invalid', message: 'Guard rules are invalid or unsupported.' };
28
+ if (path === GUARD_RULES_FILE) {
29
+ try {
30
+ if (!loadGuardConfig(root)) {
31
+ return { path, status: 'invalid', message: 'Guard rules are invalid or unsupported.' };
32
+ }
33
+ }
34
+ catch {
35
+ return { path, status: 'invalid', message: 'Guard rules are invalid or unsupported.' };
36
+ }
31
37
  }
32
38
  if (path === GUARD_PROJECT_FILE && !loadProjectModel(root)) {
33
39
  return { path, status: 'invalid', message: 'Project model is invalid or unsupported.' };
@@ -39,8 +45,15 @@ export function diagnoseGuard(root) {
39
45
  return { path, status: 'invalid', message: 'Guard contracts are invalid or unsupported.' };
40
46
  }
41
47
  if (path === GUARD_CONTRACTS_FILE &&
42
- guardConfig &&
43
- validateGuardContracts(root, guardConfig.contracts).length > 0) {
48
+ (() => {
49
+ try {
50
+ const guardConfig = loadGuardConfig(root);
51
+ return guardConfig !== undefined && validateGuardPolicy(root, guardConfig).length > 0;
52
+ }
53
+ catch {
54
+ return true;
55
+ }
56
+ })()) {
44
57
  return { path, status: 'invalid', message: 'Guard contract scopes or references are stale.' };
45
58
  }
46
59
  if (path === GUARD_PROPOSALS_FILE && !isGuardProposalFile(parseJson(root, path))) {
@@ -11,6 +11,7 @@ export interface ModuleRecord {
11
11
  exports: string[];
12
12
  calls: string[];
13
13
  resolvedImports: string[];
14
+ resolvedImportMap?: Record<string, string> | undefined;
14
15
  dynamicImports: string[];
15
16
  declarations: {
16
17
  classes: number;
@@ -32,18 +33,20 @@ export interface CapabilitySignal {
32
33
  export interface ProjectModel {
33
34
  version: 1;
34
35
  project: {
35
- name?: string;
36
- packageManager?: string;
36
+ name?: string | undefined;
37
+ packageManager?: string | undefined;
37
38
  frameworks: string[];
38
39
  scripts: Record<string, string>;
39
40
  dependencies: Record<string, string>;
40
41
  devDependencies: Record<string, string>;
41
42
  workspacePackages: {
42
43
  path: string;
43
- name?: string;
44
+ name?: string | undefined;
44
45
  private: boolean;
45
46
  scripts: Record<string, string>;
46
47
  dependencies: string[];
48
+ exports?: string[] | undefined;
49
+ projectReferences?: string[] | undefined;
47
50
  }[];
48
51
  };
49
52
  files: ProjectFileRecord[];
@@ -56,7 +59,7 @@ export interface ProjectModel {
56
59
  };
57
60
  git: {
58
61
  repository: boolean;
59
- branch?: string;
62
+ branch?: string | undefined;
60
63
  dirty: boolean;
61
64
  changedFiles: string[];
62
65
  status: string[];
@@ -122,6 +125,24 @@ export interface DiscoveryMetrics {
122
125
  changedFiles: number;
123
126
  reusedModules: number;
124
127
  }
128
+ export interface DiscoveryResult {
129
+ model: ProjectModel;
130
+ metrics: DiscoveryMetrics;
131
+ }
132
+ export interface DiscoveryOptions {
133
+ persistCache?: boolean | undefined;
134
+ /** Maximum number of files included in one model. */
135
+ maxFiles?: number | undefined;
136
+ /** Maximum size of one included file in bytes. */
137
+ maxFileBytes?: number | undefined;
138
+ }
139
+ export declare class DiscoveryLimitError extends Error {
140
+ constructor(message: string);
141
+ }
142
+ export declare class DiscoveryStaleError extends Error {
143
+ constructor();
144
+ }
145
+ export declare function clearDiscoveryCache(root: string): void;
125
146
  export declare function findGuardRoot(from?: string): string;
126
147
  /** Build the internal module graph from AST resolution plus conservative fallback resolution.
127
148
  *
@@ -130,12 +151,5 @@ export declare function findGuardRoot(from?: string): string;
130
151
  * no resolved imports silently drops unresolved edges from mixed projects.
131
152
  */
132
153
  export declare function buildModuleTargetGraph(modules: ModuleRecord[]): Map<string, string[]>;
133
- export declare function discoverProject(root: string, options?: {
134
- persistCache?: boolean;
135
- }): ProjectModel;
136
- export declare function discoverProjectWithMetrics(root: string, options?: {
137
- persistCache?: boolean;
138
- }): {
139
- model: ProjectModel;
140
- metrics: DiscoveryMetrics;
141
- };
154
+ export declare function discoverProject(root: string, options?: DiscoveryOptions): ProjectModel;
155
+ export declare function discoverProjectWithMetrics(root: string, options?: DiscoveryOptions): DiscoveryResult;