@eddacraft/anvil-runtime 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 (170) hide show
  1. package/LICENSE +14 -0
  2. package/dist/cache/cache-key.d.ts +45 -0
  3. package/dist/cache/cache-key.d.ts.map +1 -0
  4. package/dist/cache/cache-key.js +135 -0
  5. package/dist/cache/index.d.ts +27 -0
  6. package/dist/cache/index.d.ts.map +1 -0
  7. package/dist/cache/index.js +38 -0
  8. package/dist/cache/providers/file-cache.d.ts +63 -0
  9. package/dist/cache/providers/file-cache.d.ts.map +1 -0
  10. package/dist/cache/providers/file-cache.js +369 -0
  11. package/dist/cache/providers/memory-cache.d.ts +52 -0
  12. package/dist/cache/providers/memory-cache.d.ts.map +1 -0
  13. package/dist/cache/providers/memory-cache.js +197 -0
  14. package/dist/cache/providers/null-cache.d.ts +26 -0
  15. package/dist/cache/providers/null-cache.d.ts.map +1 -0
  16. package/dist/cache/providers/null-cache.js +50 -0
  17. package/dist/cache/types.d.ts +114 -0
  18. package/dist/cache/types.d.ts.map +1 -0
  19. package/dist/cache/types.js +4 -0
  20. package/dist/concurrency/agent.d.ts +137 -0
  21. package/dist/concurrency/agent.d.ts.map +1 -0
  22. package/dist/concurrency/agent.js +440 -0
  23. package/dist/concurrency/atomic.d.ts +93 -0
  24. package/dist/concurrency/atomic.d.ts.map +1 -0
  25. package/dist/concurrency/atomic.js +281 -0
  26. package/dist/concurrency/git-agent.d.ts +114 -0
  27. package/dist/concurrency/git-agent.d.ts.map +1 -0
  28. package/dist/concurrency/git-agent.js +313 -0
  29. package/dist/concurrency/index.d.ts +95 -0
  30. package/dist/concurrency/index.d.ts.map +1 -0
  31. package/dist/concurrency/index.js +127 -0
  32. package/dist/concurrency/lock-manager.d.ts +170 -0
  33. package/dist/concurrency/lock-manager.d.ts.map +1 -0
  34. package/dist/concurrency/lock-manager.js +525 -0
  35. package/dist/concurrency/queue-manager.d.ts +166 -0
  36. package/dist/concurrency/queue-manager.d.ts.map +1 -0
  37. package/dist/concurrency/queue-manager.js +442 -0
  38. package/dist/concurrency/types.d.ts +382 -0
  39. package/dist/concurrency/types.d.ts.map +1 -0
  40. package/dist/concurrency/types.js +204 -0
  41. package/dist/export/constraint-collector.d.ts +175 -0
  42. package/dist/export/constraint-collector.d.ts.map +1 -0
  43. package/dist/export/constraint-collector.js +203 -0
  44. package/dist/export/formatters/llms-txt-formatter.d.ts +89 -0
  45. package/dist/export/formatters/llms-txt-formatter.d.ts.map +1 -0
  46. package/dist/export/formatters/llms-txt-formatter.js +249 -0
  47. package/dist/export/formatters/mcp-resource-formatter.d.ts +186 -0
  48. package/dist/export/formatters/mcp-resource-formatter.d.ts.map +1 -0
  49. package/dist/export/formatters/mcp-resource-formatter.js +139 -0
  50. package/dist/export/formatters/prompt-formatter.d.ts +83 -0
  51. package/dist/export/formatters/prompt-formatter.d.ts.map +1 -0
  52. package/dist/export/formatters/prompt-formatter.js +256 -0
  53. package/dist/export/index.d.ts +10 -0
  54. package/dist/export/index.d.ts.map +1 -0
  55. package/dist/export/index.js +9 -0
  56. package/dist/gate/check.interface.d.ts +15 -0
  57. package/dist/gate/check.interface.d.ts.map +1 -0
  58. package/dist/gate/check.interface.js +18 -0
  59. package/dist/gate/checks/antipattern.check.d.ts +27 -0
  60. package/dist/gate/checks/antipattern.check.d.ts.map +1 -0
  61. package/dist/gate/checks/antipattern.check.js +140 -0
  62. package/dist/gate/checks/architecture/circular-detector.d.ts +33 -0
  63. package/dist/gate/checks/architecture/circular-detector.d.ts.map +1 -0
  64. package/dist/gate/checks/architecture/circular-detector.js +71 -0
  65. package/dist/gate/checks/architecture/dependency-analyzer.d.ts +81 -0
  66. package/dist/gate/checks/architecture/dependency-analyzer.d.ts.map +1 -0
  67. package/dist/gate/checks/architecture/dependency-analyzer.js +136 -0
  68. package/dist/gate/checks/architecture/layer-validator.d.ts +75 -0
  69. package/dist/gate/checks/architecture/layer-validator.d.ts.map +1 -0
  70. package/dist/gate/checks/architecture/layer-validator.js +193 -0
  71. package/dist/gate/checks/architecture.check.d.ts +56 -0
  72. package/dist/gate/checks/architecture.check.d.ts.map +1 -0
  73. package/dist/gate/checks/architecture.check.js +394 -0
  74. package/dist/gate/checks/command-safety.check.d.ts +12 -0
  75. package/dist/gate/checks/command-safety.check.d.ts.map +1 -0
  76. package/dist/gate/checks/command-safety.check.js +230 -0
  77. package/dist/gate/checks/coverage.check.d.ts +9 -0
  78. package/dist/gate/checks/coverage.check.d.ts.map +1 -0
  79. package/dist/gate/checks/coverage.check.js +81 -0
  80. package/dist/gate/checks/dependency.check.d.ts +17 -0
  81. package/dist/gate/checks/dependency.check.d.ts.map +1 -0
  82. package/dist/gate/checks/dependency.check.js +342 -0
  83. package/dist/gate/checks/eslint.check.d.ts +14 -0
  84. package/dist/gate/checks/eslint.check.d.ts.map +1 -0
  85. package/dist/gate/checks/eslint.check.js +79 -0
  86. package/dist/gate/checks/policy.check.d.ts +78 -0
  87. package/dist/gate/checks/policy.check.d.ts.map +1 -0
  88. package/dist/gate/checks/policy.check.js +457 -0
  89. package/dist/gate/checks/secret/entropy-detector.d.ts +44 -0
  90. package/dist/gate/checks/secret/entropy-detector.d.ts.map +1 -0
  91. package/dist/gate/checks/secret/entropy-detector.js +76 -0
  92. package/dist/gate/checks/secret/git-scanner.d.ts +36 -0
  93. package/dist/gate/checks/secret/git-scanner.d.ts.map +1 -0
  94. package/dist/gate/checks/secret/git-scanner.js +90 -0
  95. package/dist/gate/checks/secret/secret-patterns.d.ts +42 -0
  96. package/dist/gate/checks/secret/secret-patterns.d.ts.map +1 -0
  97. package/dist/gate/checks/secret/secret-patterns.js +137 -0
  98. package/dist/gate/checks/secret.check.d.ts +56 -0
  99. package/dist/gate/checks/secret.check.d.ts.map +1 -0
  100. package/dist/gate/checks/secret.check.js +245 -0
  101. package/dist/gate/config/command-safety-config.d.ts +5 -0
  102. package/dist/gate/config/command-safety-config.d.ts.map +1 -0
  103. package/dist/gate/config/command-safety-config.js +69 -0
  104. package/dist/gate/config/index.d.ts +2 -0
  105. package/dist/gate/config/index.d.ts.map +1 -0
  106. package/dist/gate/config/index.js +1 -0
  107. package/dist/gate/formatters/command-safety-formatter.d.ts +10 -0
  108. package/dist/gate/formatters/command-safety-formatter.d.ts.map +1 -0
  109. package/dist/gate/formatters/command-safety-formatter.js +64 -0
  110. package/dist/gate/formatters/index.d.ts +2 -0
  111. package/dist/gate/formatters/index.d.ts.map +1 -0
  112. package/dist/gate/formatters/index.js +1 -0
  113. package/dist/gate/gate-config.d.ts +44 -0
  114. package/dist/gate/gate-config.d.ts.map +1 -0
  115. package/dist/gate/gate-config.js +334 -0
  116. package/dist/gate/gate-runner.d.ts +160 -0
  117. package/dist/gate/gate-runner.d.ts.map +1 -0
  118. package/dist/gate/gate-runner.js +531 -0
  119. package/dist/gate/index.d.ts +20 -0
  120. package/dist/gate/index.d.ts.map +1 -0
  121. package/dist/gate/index.js +14 -0
  122. package/dist/gate/parsers/command-parser.d.ts +18 -0
  123. package/dist/gate/parsers/command-parser.d.ts.map +1 -0
  124. package/dist/gate/parsers/command-parser.js +363 -0
  125. package/dist/gate/parsers/index.d.ts +2 -0
  126. package/dist/gate/parsers/index.d.ts.map +1 -0
  127. package/dist/gate/parsers/index.js +1 -0
  128. package/dist/gate/policy/index.d.ts +12 -0
  129. package/dist/gate/policy/index.d.ts.map +1 -0
  130. package/dist/gate/policy/index.js +10 -0
  131. package/dist/gate/rules/default-filesystem-rules.d.ts +3 -0
  132. package/dist/gate/rules/default-filesystem-rules.d.ts.map +1 -0
  133. package/dist/gate/rules/default-filesystem-rules.js +201 -0
  134. package/dist/gate/rules/default-git-rules.d.ts +3 -0
  135. package/dist/gate/rules/default-git-rules.d.ts.map +1 -0
  136. package/dist/gate/rules/default-git-rules.js +192 -0
  137. package/dist/gate/rules/index.d.ts +5 -0
  138. package/dist/gate/rules/index.d.ts.map +1 -0
  139. package/dist/gate/rules/index.js +3 -0
  140. package/dist/gate/rules/rule-matcher.d.ts +27 -0
  141. package/dist/gate/rules/rule-matcher.d.ts.map +1 -0
  142. package/dist/gate/rules/rule-matcher.js +228 -0
  143. package/dist/gate/rules/types.d.ts +250 -0
  144. package/dist/gate/rules/types.d.ts.map +1 -0
  145. package/dist/gate/rules/types.js +1 -0
  146. package/dist/index.d.ts +19 -0
  147. package/dist/index.d.ts.map +1 -0
  148. package/dist/index.js +35 -0
  149. package/dist/types/gate.types.d.ts +42 -0
  150. package/dist/types/gate.types.d.ts.map +1 -0
  151. package/dist/types/gate.types.js +94 -0
  152. package/dist/watch/debouncer.d.ts +90 -0
  153. package/dist/watch/debouncer.d.ts.map +1 -0
  154. package/dist/watch/debouncer.js +135 -0
  155. package/dist/watch/file-watcher.d.ts +73 -0
  156. package/dist/watch/file-watcher.d.ts.map +1 -0
  157. package/dist/watch/file-watcher.js +121 -0
  158. package/dist/watch/git-status.d.ts +98 -0
  159. package/dist/watch/git-status.d.ts.map +1 -0
  160. package/dist/watch/git-status.js +266 -0
  161. package/dist/watch/index.d.ts +16 -0
  162. package/dist/watch/index.d.ts.map +1 -0
  163. package/dist/watch/index.js +15 -0
  164. package/dist/watch/orchestrator.d.ts +113 -0
  165. package/dist/watch/orchestrator.d.ts.map +1 -0
  166. package/dist/watch/orchestrator.js +409 -0
  167. package/dist/watch/types.d.ts +190 -0
  168. package/dist/watch/types.d.ts.map +1 -0
  169. package/dist/watch/types.js +76 -0
  170. package/package.json +60 -0
@@ -0,0 +1,81 @@
1
+ import { BaseCheck } from '../check.interface.js';
2
+ import { createDebugger } from '@eddacraft/anvil-core';
3
+ import { readFileSync, existsSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ const log = createDebugger('check');
6
+ export class CoverageCheck extends BaseCheck {
7
+ name = 'coverage';
8
+ description = 'Check test coverage thresholds';
9
+ async run(context) {
10
+ log(`coverage check starting, workspace=${context.workspace_root}`);
11
+ try {
12
+ const coveragePath = join(context.workspace_root, 'coverage', 'coverage-summary.json');
13
+ if (!existsSync(coveragePath)) {
14
+ log(`coverage check: coverage report not found at ${coveragePath}`);
15
+ return this.createFailure('Coverage report not found', 'Run tests with coverage first');
16
+ }
17
+ const coverageData = JSON.parse(readFileSync(coveragePath, 'utf-8'));
18
+ const defaultThresholds = {
19
+ lines: 80,
20
+ functions: 80,
21
+ branches: 80,
22
+ statements: 80,
23
+ };
24
+ const thresholds = typeof context.check_config.thresholds === 'object' &&
25
+ context.check_config.thresholds !== null
26
+ ? context.check_config.thresholds
27
+ : defaultThresholds;
28
+ log('coverage check: thresholds', { thresholds });
29
+ const results = this.analyzeCoverage(coverageData, thresholds);
30
+ const overallScore = results.overall;
31
+ const minScore = typeof context.check_config.min_score === 'number' ? context.check_config.min_score : 80;
32
+ const passed = overallScore >= minScore;
33
+ log('coverage check result', {
34
+ passed,
35
+ overall: overallScore,
36
+ minScore,
37
+ failedChecks: results.failed_checks,
38
+ });
39
+ const message = passed
40
+ ? `Coverage passed: ${overallScore.toFixed(1)}% overall`
41
+ : `Coverage failed: ${overallScore.toFixed(1)}% overall (required: ${minScore}%)`;
42
+ return this.createResult(passed, message, overallScore, results);
43
+ }
44
+ catch (error) {
45
+ log(`coverage check error: ${error instanceof Error ? error.message : 'Unknown error'}`);
46
+ return this.createFailure('Coverage check failed', error instanceof Error ? error.message : 'Unknown error');
47
+ }
48
+ }
49
+ analyzeCoverage(coverageData, thresholds) {
50
+ const totals = coverageData.total;
51
+ const results = {
52
+ overall: 0,
53
+ details: {},
54
+ passed: true,
55
+ failed_checks: [],
56
+ };
57
+ // Calculate overall coverage as average of all metrics
58
+ const metrics = ['lines', 'functions', 'branches', 'statements'];
59
+ let totalCoverage = 0;
60
+ let validMetrics = 0;
61
+ for (const metric of metrics) {
62
+ if (totals[metric]) {
63
+ const coverage = totals[metric].pct;
64
+ const threshold = thresholds[metric] || 0;
65
+ results.details[metric] = {
66
+ coverage,
67
+ threshold,
68
+ passed: coverage >= threshold,
69
+ };
70
+ totalCoverage += coverage;
71
+ validMetrics++;
72
+ if (coverage < threshold) {
73
+ results.passed = false;
74
+ results.failed_checks.push(`${metric}: ${coverage}% < ${threshold}%`);
75
+ }
76
+ }
77
+ }
78
+ results.overall = validMetrics > 0 ? totalCoverage / validMetrics : 0;
79
+ return results;
80
+ }
81
+ }
@@ -0,0 +1,17 @@
1
+ import { BaseCheck } from '../check.interface.js';
2
+ import { CheckContext, GateResult } from '../../types/gate.types.js';
3
+ export declare class DependencyCheck extends BaseCheck {
4
+ name: string;
5
+ description: string;
6
+ run(context: CheckContext): Promise<GateResult>;
7
+ private detectPackageManager;
8
+ private runAudit;
9
+ private normaliseAuditOutput;
10
+ private isNpmV7Format;
11
+ private normalisePnpmAudit;
12
+ private normaliseNpmV7Audit;
13
+ private normaliseYarnAudit;
14
+ private calculateScore;
15
+ private generateFixSuggestions;
16
+ }
17
+ //# sourceMappingURL=dependency.check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dependency.check.d.ts","sourceRoot":"","sources":["../../../src/gate/checks/dependency.check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AA2JrE,qBAAa,eAAgB,SAAQ,SAAS;IAC5C,IAAI,SAAgB;IACpB,WAAW,SAAmE;IAExE,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC;IA8HrD,OAAO,CAAC,oBAAoB;YAad,QAAQ;IAkDtB,OAAO,CAAC,oBAAoB;IAiB5B,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,kBAAkB;IAc1B,OAAO,CAAC,mBAAmB;IAyC3B,OAAO,CAAC,kBAAkB;IAyC1B,OAAO,CAAC,cAAc;IAWtB,OAAO,CAAC,sBAAsB;CAyC/B"}
@@ -0,0 +1,342 @@
1
+ import { BaseCheck } from '../check.interface.js';
2
+ import { createDebugger } from '@eddacraft/anvil-core';
3
+ import { exec } from 'node:child_process';
4
+ import { promisify } from 'node:util';
5
+ import { existsSync } from 'node:fs';
6
+ import { join } from 'node:path';
7
+ import { z } from 'zod';
8
+ const log = createDebugger('check');
9
+ const execAsync = promisify(exec);
10
+ const YarnAdvisoryDataSchema = z.object({
11
+ id: z.number(),
12
+ title: z.string(),
13
+ module_name: z.string(),
14
+ severity: z.enum(['info', 'low', 'moderate', 'high', 'critical']),
15
+ url: z.string(),
16
+ cves: z.array(z.string()),
17
+ vulnerable_versions: z.string(),
18
+ patched_versions: z.string(),
19
+ recommendation: z.string(),
20
+ findings: z.array(z.object({ version: z.string(), paths: z.array(z.string()) })),
21
+ });
22
+ const YarnClassicAdvisorySchema = z.object({
23
+ type: z.literal('auditAdvisory'),
24
+ data: z.object({
25
+ advisory: YarnAdvisoryDataSchema,
26
+ }),
27
+ });
28
+ const YarnClassicSummarySchema = z.object({
29
+ type: z.literal('auditSummary'),
30
+ data: z.object({
31
+ vulnerabilities: z.object({
32
+ info: z.number(),
33
+ low: z.number(),
34
+ moderate: z.number(),
35
+ high: z.number(),
36
+ critical: z.number(),
37
+ total: z.number(),
38
+ }),
39
+ }),
40
+ });
41
+ const YarnAuditLineSchema = z.union([YarnClassicAdvisorySchema, YarnClassicSummarySchema]);
42
+ const SEVERITY_SCORES = {
43
+ critical: 100,
44
+ high: 75,
45
+ moderate: 50,
46
+ low: 25,
47
+ info: 10,
48
+ };
49
+ const SEVERITY_ORDER = ['critical', 'high', 'moderate', 'low', 'info'];
50
+ const VULNERABILITY_SCORE_PENALTIES = {
51
+ critical: 20,
52
+ high: 10,
53
+ moderate: 5,
54
+ low: 2,
55
+ };
56
+ export class DependencyCheck extends BaseCheck {
57
+ name = 'dependency';
58
+ description = 'Scan for dependency vulnerabilities using npm/yarn/pnpm audit';
59
+ async run(context) {
60
+ log(`dependency check starting, workspace=${context.workspace_root}`);
61
+ try {
62
+ // Check if package.json exists
63
+ const packageJsonPath = join(context.workspace_root, 'package.json');
64
+ if (!existsSync(packageJsonPath)) {
65
+ log('dependency check: no package.json found, skipping');
66
+ return this.createSuccess('No package.json found, skipping dependency check', 100);
67
+ }
68
+ // Detect package manager
69
+ const packageManager = this.detectPackageManager(context.workspace_root);
70
+ if (!packageManager) {
71
+ log('dependency check: no lock file found, skipping');
72
+ return this.createSuccess('No lock file found, skipping dependency check', 100);
73
+ }
74
+ log(`dependency check: detected package manager=${packageManager}`);
75
+ // Determine severity threshold from config (default: moderate)
76
+ const minSeverity = context.check_config.min_severity || 'moderate';
77
+ const severityIndex = SEVERITY_ORDER.indexOf(minSeverity);
78
+ // Run audit with detected package manager
79
+ const auditResult = await this.runAudit(context.workspace_root, packageManager);
80
+ if (!auditResult) {
81
+ log('dependency check: no vulnerabilities found');
82
+ return this.createSuccess('No vulnerabilities found', 100);
83
+ }
84
+ const advisories = Object.values(auditResult.advisories);
85
+ const metadata = auditResult.metadata.vulnerabilities;
86
+ log('dependency check: found advisories', {
87
+ count: advisories.length,
88
+ total: metadata.total,
89
+ critical: metadata.critical,
90
+ high: metadata.high,
91
+ moderate: metadata.moderate,
92
+ low: metadata.low,
93
+ });
94
+ // Filter vulnerabilities by severity threshold
95
+ const relevantVulns = advisories.filter((advisory) => {
96
+ const vulnSeverityIndex = SEVERITY_ORDER.indexOf(advisory.severity);
97
+ return vulnSeverityIndex <= severityIndex;
98
+ });
99
+ // Sort by severity (critical first)
100
+ relevantVulns.sort((a, b) => {
101
+ const aScore = SEVERITY_SCORES[a.severity];
102
+ const bScore = SEVERITY_SCORES[b.severity];
103
+ return bScore - aScore;
104
+ });
105
+ // Calculate score based on vulnerabilities
106
+ const criticalCount = metadata.critical;
107
+ const highCount = metadata.high;
108
+ const moderateCount = metadata.moderate;
109
+ const lowCount = metadata.low;
110
+ const score = this.calculateScore(criticalCount, highCount, moderateCount, lowCount);
111
+ // Determine if check passed
112
+ const failOnCritical = context.check_config.fail_on_critical !== false;
113
+ const failOnHigh = context.check_config.fail_on_high !== false;
114
+ const failOnModerate = context.check_config.fail_on_moderate === true;
115
+ const hasCritical = criticalCount > 0;
116
+ const hasHigh = highCount > 0;
117
+ const hasModerate = moderateCount > 0;
118
+ const passed = !(failOnCritical && hasCritical) &&
119
+ !(failOnHigh && hasHigh) &&
120
+ !(failOnModerate && hasModerate);
121
+ log(`dependency check result: passed=${passed}, score=${score}, relevant=${relevantVulns.length}, minSeverity=${minSeverity}`);
122
+ const totalRelevant = relevantVulns.length;
123
+ const message = passed
124
+ ? `Dependency check passed: ${metadata.total} vulnerabilities found (${criticalCount} critical, ${highCount} high, ${moderateCount} moderate, ${lowCount} low)`
125
+ : `Dependency check failed: ${totalRelevant} vulnerabilities exceed threshold (${criticalCount} critical, ${highCount} high, ${moderateCount} moderate)`;
126
+ // Generate fix suggestions
127
+ const fixSuggestions = this.generateFixSuggestions(relevantVulns, packageManager);
128
+ return this.createResult(passed, message, score, {
129
+ total: metadata.total,
130
+ critical: criticalCount,
131
+ high: highCount,
132
+ moderate: moderateCount,
133
+ low: lowCount,
134
+ info: metadata.info,
135
+ packageManager,
136
+ vulnerabilities: relevantVulns.map((adv) => ({
137
+ id: adv.id,
138
+ title: adv.title,
139
+ severity: adv.severity,
140
+ module: adv.module_name,
141
+ cves: adv.cves,
142
+ url: adv.url,
143
+ vulnerableVersions: adv.vulnerable_versions,
144
+ patchedVersions: adv.patched_versions,
145
+ recommendation: adv.recommendation,
146
+ })),
147
+ fixSuggestions,
148
+ });
149
+ }
150
+ catch (error) {
151
+ // If audit command fails, it might be due to no vulnerabilities or command error
152
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
153
+ log(`dependency check error: ${errorMessage}`);
154
+ // pnpm audit exits with code 1 if vulnerabilities are found
155
+ // Check if it's a legitimate error or just vulnerabilities found
156
+ if (errorMessage.includes('EAUDITNOLOCK')) {
157
+ return this.createSuccess('No lock file found, skipping dependency check', 100);
158
+ }
159
+ return this.createFailure('Dependency check failed', errorMessage);
160
+ }
161
+ }
162
+ detectPackageManager(workspaceRoot) {
163
+ if (existsSync(join(workspaceRoot, 'pnpm-lock.yaml'))) {
164
+ return 'pnpm';
165
+ }
166
+ if (existsSync(join(workspaceRoot, 'yarn.lock'))) {
167
+ return 'yarn';
168
+ }
169
+ if (existsSync(join(workspaceRoot, 'package-lock.json'))) {
170
+ return 'npm';
171
+ }
172
+ return null;
173
+ }
174
+ async runAudit(workspaceRoot, packageManager) {
175
+ const auditCommand = `${packageManager} audit --json`;
176
+ try {
177
+ const { stdout } = await execAsync(auditCommand, {
178
+ cwd: workspaceRoot,
179
+ maxBuffer: 10 * 1024 * 1024,
180
+ timeout: 120_000,
181
+ });
182
+ return this.normaliseAuditOutput(stdout, packageManager);
183
+ }
184
+ catch (error) {
185
+ if (error && typeof error === 'object' && 'stdout' in error) {
186
+ const stdout = error.stdout;
187
+ if (stdout) {
188
+ try {
189
+ return this.normaliseAuditOutput(stdout, packageManager);
190
+ }
191
+ catch (parseError) {
192
+ console.error(`[DependencyCheck] Failed to parse ${packageManager} audit output:`, parseError);
193
+ return null;
194
+ }
195
+ }
196
+ }
197
+ if (error &&
198
+ typeof error === 'object' &&
199
+ 'stderr' in error &&
200
+ typeof error.stderr === 'string') {
201
+ const stderr = error.stderr;
202
+ if (stderr.includes('No known vulnerabilities found') ||
203
+ stderr.includes('0 vulnerabilities') ||
204
+ stderr.includes('found 0 vulnerabilities')) {
205
+ return null;
206
+ }
207
+ }
208
+ throw error;
209
+ }
210
+ }
211
+ normaliseAuditOutput(stdout, packageManager) {
212
+ if (packageManager === 'yarn') {
213
+ return this.normaliseYarnAudit(stdout);
214
+ }
215
+ const parsed = JSON.parse(stdout);
216
+ if (packageManager === 'npm' && this.isNpmV7Format(parsed)) {
217
+ return this.normaliseNpmV7Audit(parsed);
218
+ }
219
+ return this.normalisePnpmAudit(parsed);
220
+ }
221
+ isNpmV7Format(parsed) {
222
+ return (typeof parsed === 'object' &&
223
+ parsed !== null &&
224
+ 'auditReportVersion' in parsed &&
225
+ parsed.auditReportVersion >= 2);
226
+ }
227
+ normalisePnpmAudit(parsed) {
228
+ if (!parsed.advisories || !parsed.metadata?.vulnerabilities) {
229
+ return null;
230
+ }
231
+ return {
232
+ advisories: parsed.advisories,
233
+ metadata: { vulnerabilities: parsed.metadata.vulnerabilities },
234
+ };
235
+ }
236
+ normaliseNpmV7Audit(parsed) {
237
+ if (!parsed.vulnerabilities || !parsed.metadata?.vulnerabilities) {
238
+ return null;
239
+ }
240
+ const advisories = {};
241
+ let idCounter = 1;
242
+ for (const [moduleName, vuln] of Object.entries(parsed.vulnerabilities)) {
243
+ const viaDetails = vuln.via.find((v) => typeof v === 'object' && v !== null);
244
+ advisories[String(idCounter)] = {
245
+ id: idCounter,
246
+ title: viaDetails?.title || `Vulnerability in ${moduleName}`,
247
+ severity: vuln.severity,
248
+ url: viaDetails?.url || '',
249
+ cves: viaDetails?.cwe || [],
250
+ module_name: moduleName,
251
+ vulnerable_versions: vuln.range,
252
+ patched_versions: typeof vuln.fixAvailable === 'object' ? `>=${vuln.fixAvailable.version}` : '',
253
+ recommendation: typeof vuln.fixAvailable === 'object'
254
+ ? `Update to ${vuln.fixAvailable.name}@${vuln.fixAvailable.version}`
255
+ : vuln.fixAvailable
256
+ ? 'Run npm audit fix'
257
+ : 'No fix available',
258
+ findings: vuln.nodes.map((node) => ({ version: vuln.range, paths: [node] })),
259
+ };
260
+ idCounter++;
261
+ }
262
+ return {
263
+ advisories,
264
+ metadata: { vulnerabilities: parsed.metadata.vulnerabilities },
265
+ };
266
+ }
267
+ normaliseYarnAudit(stdout) {
268
+ const lines = stdout.trim().split('\n');
269
+ const advisories = {};
270
+ let metadata = {
271
+ info: 0,
272
+ low: 0,
273
+ moderate: 0,
274
+ high: 0,
275
+ critical: 0,
276
+ total: 0,
277
+ };
278
+ for (const line of lines) {
279
+ if (!line.trim())
280
+ continue;
281
+ try {
282
+ const parseResult = YarnAuditLineSchema.safeParse(JSON.parse(line));
283
+ if (!parseResult.success) {
284
+ continue;
285
+ }
286
+ const parsed = parseResult.data;
287
+ if (parsed.type === 'auditAdvisory') {
288
+ const advisory = parsed.data.advisory;
289
+ advisories[String(advisory.id)] = advisory;
290
+ }
291
+ else if (parsed.type === 'auditSummary') {
292
+ metadata = parsed.data.vulnerabilities;
293
+ }
294
+ }
295
+ catch (parseError) {
296
+ console.error('[DependencyCheck] Failed to parse Yarn audit line:', parseError);
297
+ continue;
298
+ }
299
+ }
300
+ if (Object.keys(advisories).length === 0 && metadata.total === 0) {
301
+ return null;
302
+ }
303
+ return { advisories, metadata: { vulnerabilities: metadata } };
304
+ }
305
+ calculateScore(critical, high, moderate, low) {
306
+ let score = 100;
307
+ score -= critical * VULNERABILITY_SCORE_PENALTIES.critical;
308
+ score -= high * VULNERABILITY_SCORE_PENALTIES.high;
309
+ score -= moderate * VULNERABILITY_SCORE_PENALTIES.moderate;
310
+ score -= low * VULNERABILITY_SCORE_PENALTIES.low;
311
+ return Math.max(0, score);
312
+ }
313
+ generateFixSuggestions(vulnerabilities, packageManager) {
314
+ const suggestions = [];
315
+ // Group by module
316
+ const moduleMap = new Map();
317
+ for (const vuln of vulnerabilities) {
318
+ const existing = moduleMap.get(vuln.module_name) || [];
319
+ existing.push(vuln);
320
+ moduleMap.set(vuln.module_name, existing);
321
+ }
322
+ // Generate suggestions for each module
323
+ for (const [module, vulns] of Array.from(moduleMap.entries())) {
324
+ const highestSeverity = vulns[0].severity; // Already sorted by severity
325
+ const patchedVersions = vulns[0].patched_versions;
326
+ if (patchedVersions && patchedVersions !== '<0.0.0') {
327
+ suggestions.push(`Update ${module} to ${patchedVersions} (fixes ${vulns.length} ${highestSeverity} vulnerability(ies))`);
328
+ }
329
+ else {
330
+ suggestions.push(`Review ${module}: No patch available for ${vulns.length} ${highestSeverity} vulnerability(ies)`);
331
+ }
332
+ }
333
+ // Add package manager-specific fix command
334
+ if (suggestions.length > 0) {
335
+ const fixCommand = packageManager === 'yarn'
336
+ ? `${packageManager} audit fix` // yarn uses 'audit fix'
337
+ : `${packageManager} audit fix`; // npm and pnpm both use 'audit fix'
338
+ suggestions.push(`Run \`${fixCommand}\` to automatically apply available patches`);
339
+ }
340
+ return suggestions;
341
+ }
342
+ }
@@ -0,0 +1,14 @@
1
+ import { BaseCheck } from '../check.interface.js';
2
+ import { CheckContext, GateResult } from '../../types/gate.types.js';
3
+ export declare class ESLintCheck extends BaseCheck {
4
+ name: string;
5
+ description: string;
6
+ run(context: CheckContext): Promise<GateResult>;
7
+ private getFilesFromPlan;
8
+ private isLintableFile;
9
+ /**
10
+ * Get all lintable files for full codebase scan
11
+ */
12
+ private getFilesForFullScan;
13
+ }
14
+ //# sourceMappingURL=eslint.check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"eslint.check.d.ts","sourceRoot":"","sources":["../../../src/gate/checks/eslint.check.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAuB,MAAM,2BAA2B,CAAC;AAM1F,qBAAa,WAAY,SAAQ,SAAS;IACxC,IAAI,SAAY;IAChB,WAAW,SAAoC;IAEzC,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC;IAqErD,OAAO,CAAC,gBAAgB;IAQxB,OAAO,CAAC,cAAc;IAKtB;;OAEG;YACW,mBAAmB;CAKlC"}
@@ -0,0 +1,79 @@
1
+ import { BaseCheck } from '../check.interface.js';
2
+ import { getFilesFromContext } from '../../types/gate.types.js';
3
+ import { createDebugger } from '@eddacraft/anvil-core';
4
+ import { ESLint } from 'eslint';
5
+ const log = createDebugger('check');
6
+ export class ESLintCheck extends BaseCheck {
7
+ name = 'eslint';
8
+ description = 'Run ESLint code quality checks';
9
+ async run(context) {
10
+ log(`eslint check starting, workspace=${context.workspace_root}, fullScan=${context.fullScan}`);
11
+ try {
12
+ const eslint = new ESLint({
13
+ cwd: context.workspace_root,
14
+ });
15
+ // Get files to lint - either from plan or full codebase scan
16
+ const files = context.fullScan
17
+ ? await this.getFilesForFullScan(context.workspace_root)
18
+ : this.getFilesFromPlan(context);
19
+ if (files.length === 0) {
20
+ log('eslint check: no lintable files found');
21
+ return this.createSuccess('No files to lint', 100);
22
+ }
23
+ log(`eslint check: linting ${files.length} file(s)`);
24
+ const results = await eslint.lintFiles(files);
25
+ const errorCount = results.reduce((acc, result) => acc + result.errorCount, 0);
26
+ const warningCount = results.reduce((acc, result) => acc + result.warningCount, 0);
27
+ const fixableCount = results.reduce((acc, result) => acc + result.fixableErrorCount + result.fixableWarningCount, 0);
28
+ const totalIssues = errorCount + warningCount;
29
+ const score = totalIssues === 0 ? 100 : Math.max(0, 100 - (errorCount * 10 + warningCount * 2));
30
+ const minScore = typeof context.check_config.min_score === 'number' ? context.check_config.min_score : 80;
31
+ const passed = errorCount === 0 && score >= minScore;
32
+ log('eslint check result', {
33
+ passed,
34
+ score,
35
+ errors: errorCount,
36
+ warnings: warningCount,
37
+ fixable: fixableCount,
38
+ filesLinted: results.length,
39
+ });
40
+ const message = passed
41
+ ? `ESLint passed: ${errorCount} errors, ${warningCount} warnings`
42
+ : `ESLint failed: ${errorCount} errors, ${warningCount} warnings`;
43
+ return this.createResult(passed, message, score, {
44
+ errorCount,
45
+ warningCount,
46
+ fixableCount,
47
+ results: results.map((r) => ({
48
+ filePath: r.filePath,
49
+ errorCount: r.errorCount,
50
+ warningCount: r.warningCount,
51
+ messages: r.messages,
52
+ })),
53
+ });
54
+ }
55
+ catch (error) {
56
+ log(`eslint check error: ${error instanceof Error ? error.message : 'Unknown error'}`);
57
+ return this.createFailure('ESLint check failed', error instanceof Error ? error.message : 'Unknown error');
58
+ }
59
+ }
60
+ getFilesFromPlan(context) {
61
+ // Use unified helper for both planless and plan-based modes
62
+ // This ensures consistent path normalisation and existence checking
63
+ return getFilesFromContext(context, {
64
+ filter: (f) => this.isLintableFile(f),
65
+ });
66
+ }
67
+ isLintableFile(filePath) {
68
+ const lintableExtensions = ['.js', '.ts', '.jsx', '.tsx', '.mjs', '.cjs'];
69
+ return lintableExtensions.some((ext) => filePath.endsWith(ext));
70
+ }
71
+ /**
72
+ * Get all lintable files for full codebase scan
73
+ */
74
+ async getFilesForFullScan(workspaceRoot) {
75
+ // Use ESLint's file resolution - pass the workspace root
76
+ // ESLint will use its own config to determine which files to lint
77
+ return [workspaceRoot];
78
+ }
79
+ }
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Policy Check - Evaluate OPA/Rego policies against plans
3
+ */
4
+ import { BaseCheck } from '../check.interface.js';
5
+ import { CheckContext, GateResult } from '../../types/gate.types.js';
6
+ /**
7
+ * Configuration for policy check
8
+ */
9
+ export interface PolicyCheckConfig {
10
+ /** Policy directory relative to workspace root */
11
+ policy_dir?: string;
12
+ /** Minimum severity to fail the check */
13
+ severity_threshold?: 'error' | 'warning' | 'info';
14
+ /** Policies to enable (if empty, all are enabled) */
15
+ enabled_policies?: string[];
16
+ /** Policies to disable */
17
+ disabled_policies?: string[];
18
+ /** Custom query (default: data.anvil.policies) */
19
+ query?: string;
20
+ /** Timeout in milliseconds */
21
+ timeout?: number;
22
+ /** Require policy tests to pass before evaluating policies */
23
+ require_policy_tests?: boolean;
24
+ /** Include git context in OPA input for repository-aware policies */
25
+ include_git_context?: boolean;
26
+ }
27
+ /**
28
+ * Policy check that evaluates OPA/Rego policies against plans
29
+ */
30
+ export declare class PolicyCheck extends BaseCheck {
31
+ name: string;
32
+ description: string;
33
+ private policyLoader;
34
+ constructor();
35
+ run(context: CheckContext): Promise<GateResult>;
36
+ /**
37
+ * Parse and validate check configuration
38
+ */
39
+ private parseConfig;
40
+ /**
41
+ * Build OPA input from check context
42
+ */
43
+ private buildOPAInput;
44
+ /**
45
+ * Bridges ArchitectureCheck output to PolicyCheck OPA input.
46
+ * Enables Rego policies to query architecture context via input.architecture
47
+ */
48
+ private buildArchitectureInput;
49
+ /**
50
+ * Get git context for repository-aware policies
51
+ */
52
+ private getGitContext;
53
+ /**
54
+ * Get CI context from environment variables
55
+ */
56
+ private getCIContext;
57
+ /**
58
+ * Extract PR number from GitHub ref (e.g., refs/pull/123/merge)
59
+ */
60
+ private extractPRNumber;
61
+ /**
62
+ * Run policy tests and return results
63
+ */
64
+ private runPolicyTests;
65
+ /**
66
+ * Calculate score based on violations
67
+ */
68
+ private calculateScore;
69
+ /**
70
+ * Check if a severity level should block the check
71
+ */
72
+ private isBlockingSeverity;
73
+ /**
74
+ * Build human-readable message
75
+ */
76
+ private buildMessage;
77
+ }
78
+ //# sourceMappingURL=policy.check.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"policy.check.d.ts","sourceRoot":"","sources":["../../../src/gate/checks/policy.check.ts"],"names":[],"mappings":"AAAA;;GAEG;AAGH,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAarE;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,kDAAkD;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,yCAAyC;IACzC,kBAAkB,CAAC,EAAE,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;IAClD,qDAAqD;IACrD,gBAAgB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC5B,0BAA0B;IAC1B,iBAAiB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC7B,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8BAA8B;IAC9B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qEAAqE;IACrE,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAgBD;;GAEG;AACH,qBAAa,WAAY,SAAQ,SAAS;IACxC,IAAI,SAAY;IAChB,WAAW,SAA8C;IAEzD,OAAO,CAAC,YAAY,CAAe;;IAO7B,GAAG,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,UAAU,CAAC;IA4IrD;;OAEG;IACH,OAAO,CAAC,WAAW;IAuBnB;;OAEG;IACH,OAAO,CAAC,aAAa;IA8DrB;;;OAGG;IACH,OAAO,CAAC,sBAAsB;IA+C9B;;OAEG;IACH,OAAO,CAAC,aAAa;IAyDrB;;OAEG;IACH,OAAO,CAAC,YAAY;IA+CpB;;OAEG;IACH,OAAO,CAAC,eAAe;IAMvB;;OAEG;YACW,cAAc;IAmC5B;;OAEG;IACH,OAAO,CAAC,cAAc;IAsCtB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;OAEG;IACH,OAAO,CAAC,YAAY;CAqBrB"}