swarm-orchestrator 7.0.0-alpha.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 (174) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +199 -0
  3. package/agents/reviewer.agent.md +50 -0
  4. package/agents/worker.agent.md +41 -0
  5. package/config/default-agents.yaml +56 -0
  6. package/config/pm-agent.yaml +36 -0
  7. package/config/quality-gates.yaml +88 -0
  8. package/config/repair-agent.yaml +34 -0
  9. package/config/semgrep-rules/complexity-mismatch.yaml +6 -0
  10. package/config/semgrep-rules/exception-swallowing.yaml +13 -0
  11. package/config/semgrep-rules/hardcoded-answer.yaml +6 -0
  12. package/config/semgrep-rules/mock-mutation.yaml +6 -0
  13. package/config/semgrep-rules/test-modification.yaml +6 -0
  14. package/config/user-agents.yaml +23 -0
  15. package/dist/src/adapters/adapter-factory.js +29 -0
  16. package/dist/src/adapters/agent-adapter.js +32 -0
  17. package/dist/src/adapters/claude-code-adapter.js +126 -0
  18. package/dist/src/adapters/claude-code-teams.js +119 -0
  19. package/dist/src/adapters/codex-adapter.js +118 -0
  20. package/dist/src/adapters/copilot-adapter.js +224 -0
  21. package/dist/src/adapters/fatal-error-classifier.js +88 -0
  22. package/dist/src/adapters/index.js +44 -0
  23. package/dist/src/adapters/persistent-session.js +204 -0
  24. package/dist/src/adapters/process-supervisor.js +199 -0
  25. package/dist/src/agents-exporter.js +398 -0
  26. package/dist/src/analytics-log.js +161 -0
  27. package/dist/src/baseline-scanner.js +65 -0
  28. package/dist/src/bootstrap-evidence.js +88 -0
  29. package/dist/src/bootstrap-orchestrator.js +205 -0
  30. package/dist/src/bootstrap-types.js +5 -0
  31. package/dist/src/branch-merger.js +427 -0
  32. package/dist/src/cli/attest-handlers.js +44 -0
  33. package/dist/src/cli/cost-prompt.js +51 -0
  34. package/dist/src/cli/demo-handlers.js +274 -0
  35. package/dist/src/cli/flags.js +152 -0
  36. package/dist/src/cli/index.js +57 -0
  37. package/dist/src/cli/live-status.js +186 -0
  38. package/dist/src/cli/misc-handlers.js +242 -0
  39. package/dist/src/cli/plan-handlers.js +350 -0
  40. package/dist/src/cli/share-handlers.js +163 -0
  41. package/dist/src/cli/status-handlers.js +457 -0
  42. package/dist/src/cli/swarm-handlers.js +638 -0
  43. package/dist/src/cli/usage.js +72 -0
  44. package/dist/src/cli.js +217 -0
  45. package/dist/src/commit-pattern-detector.js +143 -0
  46. package/dist/src/commit-quality-analyzer.js +38 -0
  47. package/dist/src/config-loader.js +339 -0
  48. package/dist/src/context-broker.js +358 -0
  49. package/dist/src/copilot-cli-wrapper.js +258 -0
  50. package/dist/src/cost-estimator.js +241 -0
  51. package/dist/src/defaults.js +18 -0
  52. package/dist/src/demo-mode.js +180 -0
  53. package/dist/src/deployment-handler.js +78 -0
  54. package/dist/src/deployment-manager.js +228 -0
  55. package/dist/src/execution-queue.js +196 -0
  56. package/dist/src/external-tool-manager.js +184 -0
  57. package/dist/src/gate-prompt-builder.js +117 -0
  58. package/dist/src/gate-remediation.js +173 -0
  59. package/dist/src/github-issues-ingester.js +152 -0
  60. package/dist/src/github-mcp-integrator.js +164 -0
  61. package/dist/src/hook-generator.js +229 -0
  62. package/dist/src/index.js +48 -0
  63. package/dist/src/knowledge-base.js +300 -0
  64. package/dist/src/logger.js +111 -0
  65. package/dist/src/meta-analyzer.js +208 -0
  66. package/dist/src/metrics-collector.js +209 -0
  67. package/dist/src/metrics-types.js +5 -0
  68. package/dist/src/multi-repo-coordinator.js +179 -0
  69. package/dist/src/orchestrator/async-meta-analysis.js +84 -0
  70. package/dist/src/orchestrator/fatal-run-error.js +43 -0
  71. package/dist/src/orchestrator/final-gates-remediation.js +258 -0
  72. package/dist/src/orchestrator/git-state-utils.js +119 -0
  73. package/dist/src/orchestrator/pause-controller.js +55 -0
  74. package/dist/src/orchestrator/replan-runner.js +258 -0
  75. package/dist/src/orchestrator/step-executor.js +391 -0
  76. package/dist/src/orchestrator/wave-scheduler-loop.js +204 -0
  77. package/dist/src/owasp-mapper.js +153 -0
  78. package/dist/src/owasp-report-renderer.js +39 -0
  79. package/dist/src/plan-files.js +142 -0
  80. package/dist/src/plan-generator.js +1207 -0
  81. package/dist/src/pm-agent.js +296 -0
  82. package/dist/src/post-run-reporter.js +203 -0
  83. package/dist/src/pr-automation.js +103 -0
  84. package/dist/src/pr-manager.js +318 -0
  85. package/dist/src/presenter/index.js +209 -0
  86. package/dist/src/prompt-builder.js +173 -0
  87. package/dist/src/quality-gates/config-loader.js +196 -0
  88. package/dist/src/quality-gates/default-config.js +243 -0
  89. package/dist/src/quality-gates/file-utils.js +152 -0
  90. package/dist/src/quality-gates/gate-runner.js +171 -0
  91. package/dist/src/quality-gates/gates/accessibility.js +355 -0
  92. package/dist/src/quality-gates/gates/duplicate-blocks.js +144 -0
  93. package/dist/src/quality-gates/gates/hardcoded-config.js +52 -0
  94. package/dist/src/quality-gates/gates/readme-claims.js +62 -0
  95. package/dist/src/quality-gates/gates/runtime-checks.js +265 -0
  96. package/dist/src/quality-gates/gates/scaffold-defaults.js +108 -0
  97. package/dist/src/quality-gates/gates/test-coverage.js +240 -0
  98. package/dist/src/quality-gates/gates/test-file-protection.js +85 -0
  99. package/dist/src/quality-gates/gates/test-isolation.js +74 -0
  100. package/dist/src/quality-gates/index.js +17 -0
  101. package/dist/src/quality-gates/registry.js +205 -0
  102. package/dist/src/quality-gates/types.js +2 -0
  103. package/dist/src/quick-fix-mode.js +302 -0
  104. package/dist/src/recipe-loader.js +122 -0
  105. package/dist/src/repair-agent.js +396 -0
  106. package/dist/src/repo-analyzer.js +342 -0
  107. package/dist/src/report-generator.js +146 -0
  108. package/dist/src/report-renderer.js +90 -0
  109. package/dist/src/requirement-filter.js +52 -0
  110. package/dist/src/sarif-formatter.js +140 -0
  111. package/dist/src/scheduling/dependency-analyzer.js +178 -0
  112. package/dist/src/scheduling/work-stealing-queue.js +123 -0
  113. package/dist/src/secret-redactor.js +90 -0
  114. package/dist/src/session-executor.js +587 -0
  115. package/dist/src/session-manager.js +221 -0
  116. package/dist/src/share/transcript-verification.js +128 -0
  117. package/dist/src/share-parser.js +477 -0
  118. package/dist/src/spinner.js +146 -0
  119. package/dist/src/step-runner.js +365 -0
  120. package/dist/src/swarm-orchestrator.js +686 -0
  121. package/dist/src/task-classifier.js +134 -0
  122. package/dist/src/test-command-discovery.js +123 -0
  123. package/dist/src/text-similarity.js +24 -0
  124. package/dist/src/tier-maps.js +158 -0
  125. package/dist/src/types.js +5 -0
  126. package/dist/src/url-shortener.js +135 -0
  127. package/dist/src/verification/attestation.js +166 -0
  128. package/dist/src/verification/cheat-detector.js +240 -0
  129. package/dist/src/verification/command-runner.js +70 -0
  130. package/dist/src/verification/composite-score.js +157 -0
  131. package/dist/src/verification/cosign-attestation.js +137 -0
  132. package/dist/src/verification/diff-analysis.js +89 -0
  133. package/dist/src/verification/differential-gate.js +173 -0
  134. package/dist/src/verification/index.js +41 -0
  135. package/dist/src/verification/mutation-gate.js +242 -0
  136. package/dist/src/verification/property-gate.js +201 -0
  137. package/dist/src/verification/test-synthesizer.js +243 -0
  138. package/dist/src/verifier/outcome-checks.js +396 -0
  139. package/dist/src/verifier/transcript-checks.js +207 -0
  140. package/dist/src/verifier/verification-reporters.js +148 -0
  141. package/dist/src/verifier-engine.js +491 -0
  142. package/dist/src/wave-resizer.js +131 -0
  143. package/dist/src/wave-scheduler.js +44 -0
  144. package/dist/src/worktree-manager.js +368 -0
  145. package/dist/src/worktree-reserved-paths.js +97 -0
  146. package/package.json +80 -0
  147. package/plugin/agents/.gitkeep +0 -0
  148. package/plugin/agents/backend_master.agent.md +72 -0
  149. package/plugin/agents/devops_pro.agent.md +45 -0
  150. package/plugin/agents/frontend_expert.agent.md +59 -0
  151. package/plugin/agents/integrator_finalizer.agent.md +44 -0
  152. package/plugin/agents/meta_reviewer.agent.md +44 -0
  153. package/plugin/agents/security_auditor.agent.md +43 -0
  154. package/plugin/agents/tester_elite.agent.md +43 -0
  155. package/plugin/hooks/hooks.json +19 -0
  156. package/plugin/hooks/post-tool-use.json +9 -0
  157. package/plugin/hooks/pre-tool-use.json +9 -0
  158. package/plugin/plugin.json +47 -0
  159. package/plugin/skills/swarm-gates/SKILL.md +53 -0
  160. package/plugin/skills/swarm-orchestrate/SKILL.md +34 -0
  161. package/plugin/skills/swarm-verify/SKILL.md +44 -0
  162. package/templates/README.md +35 -0
  163. package/templates/cli-tool.json +31 -0
  164. package/templates/fullstack.json +52 -0
  165. package/templates/library.json +38 -0
  166. package/templates/react-app.json +45 -0
  167. package/templates/recipes/add-api-docs.json +35 -0
  168. package/templates/recipes/add-auth.json +47 -0
  169. package/templates/recipes/add-ci.json +39 -0
  170. package/templates/recipes/add-tests.json +39 -0
  171. package/templates/recipes/migrate-to-ts.json +42 -0
  172. package/templates/recipes/refactor-modularize.json +44 -0
  173. package/templates/recipes/security-audit.json +35 -0
  174. package/templates/rest-api.json +38 -0
@@ -0,0 +1,144 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.run_duplicate_blocks_gate = run_duplicate_blocks_gate;
37
+ const crypto = __importStar(require("crypto"));
38
+ const file_utils_1 = require("../file-utils");
39
+ function normalize_block(lines) {
40
+ return lines
41
+ .map(l => l.replace(/\s+$/g, ''))
42
+ .join('\n')
43
+ .trim();
44
+ }
45
+ function hash_block(text) {
46
+ return crypto.createHash('sha1').update(text).digest('hex');
47
+ }
48
+ async function run_duplicate_blocks_gate(ctx, config, maxFileSizeBytes) {
49
+ const start = Date.now();
50
+ const issues = [];
51
+ const exclude = (0, file_utils_1.compile_regexes)(config.excludeFileRegexes);
52
+ const blocks = new Map();
53
+ for (const file of ctx.files) {
54
+ if ((0, file_utils_1.is_excluded_by_regex)(file.relativePath, exclude)) {
55
+ continue;
56
+ }
57
+ const text = (0, file_utils_1.maybe_read_text)(file, maxFileSizeBytes);
58
+ if (!text) {
59
+ continue;
60
+ }
61
+ // ignore tiny files
62
+ const lines = text.split('\n');
63
+ if (lines.length < config.minLines) {
64
+ continue;
65
+ }
66
+ // blocks separated by blank lines (simple and deterministic)
67
+ let current = null;
68
+ const flush = () => {
69
+ if (!current)
70
+ return;
71
+ const cleaned = current.lines.filter(l => l.trim() !== '');
72
+ if (cleaned.length >= config.minLines) {
73
+ const normalized = normalize_block(cleaned);
74
+ if (normalized.length > 0) {
75
+ const h = hash_block(normalized);
76
+ const occ = {
77
+ filePath: file.relativePath,
78
+ startLine: current.startLine,
79
+ lines: cleaned
80
+ };
81
+ const existing = blocks.get(h) || [];
82
+ existing.push(occ);
83
+ blocks.set(h, existing);
84
+ }
85
+ }
86
+ current = null;
87
+ };
88
+ for (let i = 0; i < lines.length; i++) {
89
+ const line = lines[i] ?? '';
90
+ const isBlank = line.trim() === '';
91
+ if (isBlank) {
92
+ flush();
93
+ continue;
94
+ }
95
+ if (!current) {
96
+ current = { startLine: i + 1, lines: [] };
97
+ }
98
+ current.lines.push(line);
99
+ }
100
+ flush();
101
+ }
102
+ // report duplicates
103
+ const duplicates = Array.from(blocks.values())
104
+ .filter(occ => occ.length > config.maxOccurrences);
105
+ duplicates.sort((a, b) => b.length - a.length);
106
+ for (const occs of duplicates.slice(0, config.maxFindings)) {
107
+ const first = occs[0];
108
+ if (!first)
109
+ continue;
110
+ issues.push({
111
+ message: `duplicate code block (${occs.length} occurrences, ${first.lines.length} lines)`,
112
+ filePath: first.filePath,
113
+ line: first.startLine,
114
+ excerpt: first.lines.slice(0, 6).join('\n') + (first.lines.length > 6 ? '\n…' : ''),
115
+ hint: 'extract to a shared util/hook/middleware and reuse it'
116
+ });
117
+ // add a few more locations
118
+ for (const extra of occs.slice(1, 3)) {
119
+ issues.push({
120
+ message: 'duplicate also appears here',
121
+ filePath: extra.filePath,
122
+ line: extra.startLine,
123
+ excerpt: extra.lines.slice(0, 3).join('\n') + (extra.lines.length > 3 ? '\n…' : ''),
124
+ });
125
+ }
126
+ if (issues.length >= config.maxFindings) {
127
+ break;
128
+ }
129
+ }
130
+ const durationMs = Date.now() - start;
131
+ const status = issues.length > 0 ? 'fail' : 'pass';
132
+ return {
133
+ id: 'duplicate-blocks',
134
+ title: 'Duplicate code blocks under control',
135
+ status,
136
+ durationMs,
137
+ issues,
138
+ stats: {
139
+ uniqueBlocks: blocks.size,
140
+ duplicateGroups: duplicates.length,
141
+ issues: issues.length
142
+ }
143
+ };
144
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.run_hardcoded_config_gate = run_hardcoded_config_gate;
4
+ const file_utils_1 = require("../file-utils");
5
+ async function run_hardcoded_config_gate(ctx, config, maxFileSizeBytes) {
6
+ const start = Date.now();
7
+ const issues = [];
8
+ const banned = (0, file_utils_1.compile_regexes)(config.bannedLiteralRegexes);
9
+ const allowIfContains = (0, file_utils_1.compile_regexes)(config.allowIfFileContainsRegexes);
10
+ const exclude = (0, file_utils_1.compile_regexes)(config.excludeFileRegexes);
11
+ for (const file of ctx.files) {
12
+ if ((0, file_utils_1.is_excluded_by_regex)(file.relativePath, exclude)) {
13
+ continue;
14
+ }
15
+ const text = (0, file_utils_1.maybe_read_text)(file, maxFileSizeBytes);
16
+ if (!text) {
17
+ continue;
18
+ }
19
+ for (const re of banned) {
20
+ if (!re.test(text)) {
21
+ continue;
22
+ }
23
+ const allowed = allowIfContains.some(a => a.test(text));
24
+ if (allowed) {
25
+ continue;
26
+ }
27
+ issues.push({
28
+ message: `hardcoded config candidate matched /${re.source}/`,
29
+ filePath: file.relativePath,
30
+ hint: 'move to config/env and reference from code'
31
+ });
32
+ if (issues.length >= 50) {
33
+ break;
34
+ }
35
+ }
36
+ if (issues.length >= 50) {
37
+ break;
38
+ }
39
+ }
40
+ const durationMs = Date.now() - start;
41
+ const status = issues.length > 0 ? 'fail' : 'pass';
42
+ return {
43
+ id: 'hardcoded-config',
44
+ title: 'No obvious hardcoded config',
45
+ status,
46
+ durationMs,
47
+ issues,
48
+ stats: {
49
+ issues: issues.length
50
+ }
51
+ };
52
+ }
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.run_readme_claims_gate = run_readme_claims_gate;
4
+ const file_utils_1 = require("../file-utils");
5
+ function file_matches(fileRelPath, fileRegex) {
6
+ return fileRegex.test(fileRelPath);
7
+ }
8
+ async function run_readme_claims_gate(ctx, config, maxFileSizeBytes) {
9
+ const start = Date.now();
10
+ const issues = [];
11
+ const readmeFile = ctx.files.find(f => /^readme\.md$/i.test(f.relativePath));
12
+ if (!readmeFile) {
13
+ const durationMs = Date.now() - start;
14
+ return {
15
+ id: 'readme-claims',
16
+ title: 'README claims match implementation',
17
+ status: 'skip',
18
+ durationMs,
19
+ issues: [{ message: 'README.md not found, skipping claim checks' }]
20
+ };
21
+ }
22
+ const readmeText = (0, file_utils_1.maybe_read_text)(readmeFile, maxFileSizeBytes) || '';
23
+ for (const rule of config.claimRules) {
24
+ const claimRe = new RegExp(rule.readmeRegex, 'i');
25
+ if (!claimRe.test(readmeText)) {
26
+ continue;
27
+ }
28
+ for (const evidence of rule.requiredEvidence) {
29
+ const fileRe = new RegExp(evidence.fileRegex, 'i');
30
+ const contentRe = new RegExp(evidence.contentRegex, 'i');
31
+ const found = ctx.files.some(f => {
32
+ if (!file_matches(f.relativePath, fileRe)) {
33
+ return false;
34
+ }
35
+ const text = f.text;
36
+ if (!text) {
37
+ return false;
38
+ }
39
+ return contentRe.test(text);
40
+ });
41
+ if (!found) {
42
+ issues.push({
43
+ message: `README claim matched (${rule.id}) but evidence not found: /${contentRe.source}/ in files /${fileRe.source}/`,
44
+ filePath: readmeFile.relativePath,
45
+ hint: evidence.note || 'either implement it or remove the claim from README'
46
+ });
47
+ }
48
+ }
49
+ }
50
+ const durationMs = Date.now() - start;
51
+ const status = issues.length > 0 ? 'fail' : 'pass';
52
+ return {
53
+ id: 'readme-claims',
54
+ title: 'README claims match implementation',
55
+ status,
56
+ durationMs,
57
+ issues,
58
+ stats: {
59
+ issues: issues.length
60
+ }
61
+ };
62
+ }
@@ -0,0 +1,265 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.buildTestCommand = buildTestCommand;
37
+ exports.buildEslintCommand = buildEslintCommand;
38
+ exports.run_runtime_checks_gate = run_runtime_checks_gate;
39
+ const child_process_1 = require("child_process");
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ /**
43
+ * Execute a shell command with retry support.
44
+ * Returns success/failure with captured output. Does not throw.
45
+ */
46
+ function runCommand(command, cwd, timeoutMs, retries) {
47
+ for (let attempt = 0; attempt <= retries; attempt++) {
48
+ try {
49
+ const stdout = (0, child_process_1.execSync)(command, {
50
+ cwd,
51
+ encoding: 'utf-8',
52
+ timeout: timeoutMs,
53
+ stdio: ['pipe', 'pipe', 'pipe'],
54
+ env: { ...process.env, CI: 'true', FORCE_COLOR: '0' }
55
+ });
56
+ return { success: true, stdout, stderr: '', command };
57
+ }
58
+ catch (err) {
59
+ const execErr = err;
60
+ const isLastAttempt = attempt === retries;
61
+ if (isLastAttempt) {
62
+ return {
63
+ success: false,
64
+ stdout: String(execErr.stdout || ''),
65
+ stderr: String(execErr.stderr || ''),
66
+ command
67
+ };
68
+ }
69
+ // Brief pause before retry (1s, 2s)
70
+ const delay = (attempt + 1) * 1000;
71
+ (0, child_process_1.execSync)(`sleep ${delay / 1000}`, { stdio: 'pipe' });
72
+ }
73
+ }
74
+ // Unreachable, but TypeScript needs it
75
+ return { success: false, stdout: '', stderr: '', command };
76
+ }
77
+ /**
78
+ * Detect whether the project has a test script in package.json.
79
+ */
80
+ function hasScript(projectRoot, scriptName) {
81
+ const pkgPath = path.join(projectRoot, 'package.json');
82
+ if (!fs.existsSync(pkgPath))
83
+ return false;
84
+ try {
85
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
86
+ const script = pkg.scripts?.[scriptName];
87
+ // Treat missing or stub scripts as absent
88
+ return !!script && !script.includes('no test specified');
89
+ }
90
+ catch {
91
+ return false;
92
+ }
93
+ }
94
+ /**
95
+ * Detect whether eslint config exists in the project.
96
+ */
97
+ function hasEslintConfig(projectRoot) {
98
+ const candidates = [
99
+ '.eslintrc',
100
+ '.eslintrc.js',
101
+ '.eslintrc.cjs',
102
+ '.eslintrc.json',
103
+ '.eslintrc.yml',
104
+ '.eslintrc.yaml',
105
+ 'eslint.config.js',
106
+ 'eslint.config.mjs',
107
+ 'eslint.config.cjs',
108
+ 'eslint.config.ts'
109
+ ];
110
+ for (const name of candidates) {
111
+ if (fs.existsSync(path.join(projectRoot, name)))
112
+ return true;
113
+ }
114
+ // Check package.json eslintConfig field
115
+ const pkgPath = path.join(projectRoot, 'package.json');
116
+ if (fs.existsSync(pkgPath)) {
117
+ try {
118
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
119
+ if (pkg.eslintConfig)
120
+ return true;
121
+ }
122
+ catch { /* ignore parse errors */ }
123
+ }
124
+ return false;
125
+ }
126
+ /**
127
+ * Build the test command, appending ignore patterns for orchestrator
128
+ * artifact directories. Without this, recursive test runners discover
129
+ * duplicate test files inside runs/worktrees and fail on missing deps
130
+ * or port collisions.
131
+ */
132
+ function buildTestCommand(projectRoot) {
133
+ const pkgPath = path.join(projectRoot, 'package.json');
134
+ try {
135
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
136
+ const testScript = pkg.scripts?.test || '';
137
+ const isJest = testScript.includes('jest') || !!pkg.jest;
138
+ if (isJest) {
139
+ return 'npm test -- --testPathIgnorePatterns=runs/ --testPathIgnorePatterns=coverage/';
140
+ }
141
+ // Node.js built-in test runner discovers files recursively from cwd.
142
+ // When the project has conventional test directories, scope discovery
143
+ // to those directories so orchestrator artifacts are excluded.
144
+ // Cannot use `npm test -- dir/` because npm rewrites the arg list
145
+ // into `node dir/` (dropping --test). Use the runner directly.
146
+ if (testScript.includes('node --test') && !testScript.includes('tests/') && !testScript.includes('test/')) {
147
+ const testDirs = [];
148
+ if (fs.existsSync(path.join(projectRoot, 'tests')))
149
+ testDirs.push("'tests/**/*.test.js'");
150
+ if (fs.existsSync(path.join(projectRoot, 'test')))
151
+ testDirs.push("'test/**/*.test.js'");
152
+ if (testDirs.length > 0) {
153
+ return `node --test ${testDirs.join(' ')}`;
154
+ }
155
+ }
156
+ }
157
+ catch { /* fall through to plain npm test */ }
158
+ return 'npm test';
159
+ }
160
+ /**
161
+ * Build the ESLint command, scoping to agent-changed files when a baseline
162
+ * commit is available. Without a baseline, falls back to scanning everything
163
+ * but excludes common build-output and orchestrator artifact directories.
164
+ */
165
+ function buildEslintCommand(projectRoot, baseCommit) {
166
+ const ignoreDirs = ['dist', 'build', 'coverage', 'runs', 'plans',
167
+ '.next', '.turbo', '.cache', 'proof', '.quickfix', 'node_modules'];
168
+ if (baseCommit) {
169
+ try {
170
+ const diffOutput = (0, child_process_1.execSync)(`git diff --name-only --diff-filter=ACMR ${baseCommit} HEAD`, { cwd: projectRoot, encoding: 'utf-8', timeout: 10000, stdio: ['pipe', 'pipe', 'pipe'] }).trim();
171
+ if (!diffOutput)
172
+ return null;
173
+ const lintableExts = /\.(js|ts|jsx|tsx|mjs|cjs)$/i;
174
+ const changedFiles = diffOutput.split('\n')
175
+ .filter(f => lintableExts.test(f))
176
+ .filter(f => !ignoreDirs.some(d => f.startsWith(d + '/')));
177
+ if (changedFiles.length === 0)
178
+ return null;
179
+ // Large diffs: fall back to full scan with directory exclusions
180
+ if (changedFiles.length > 100) {
181
+ const ignoreArgs = ignoreDirs.map(d => `--ignore-pattern '${d}/'`).join(' ');
182
+ return `npx eslint . ${ignoreArgs} --max-warnings=0`;
183
+ }
184
+ const fileArgs = changedFiles.map(f => `'${f}'`).join(' ');
185
+ return `npx eslint ${fileArgs} --max-warnings=0`;
186
+ }
187
+ catch {
188
+ // git diff unavailable; fall through to full scan with ignores
189
+ }
190
+ }
191
+ const ignoreArgs = ignoreDirs.map(d => `--ignore-pattern '${d}/'`).join(' ');
192
+ return `npx eslint . ${ignoreArgs} --max-warnings=0`;
193
+ }
194
+ /**
195
+ * Runtime quality gate: executes the project's own test suite, linter,
196
+ * and security audit to validate the generated code actually works.
197
+ *
198
+ * Skipped checks (missing test script, no eslint config) are not failures.
199
+ */
200
+ async function run_runtime_checks_gate(projectRoot, config, baseCommit) {
201
+ const start = Date.now();
202
+ const id = 'runtime-checks';
203
+ const title = 'Runtime Checks (tests, lint, audit)';
204
+ if (!config.enabled) {
205
+ return { id, title, status: 'skip', durationMs: 0, issues: [] };
206
+ }
207
+ const issues = [];
208
+ const stats = { testsRun: 0, lintRun: 0, auditRun: 0 };
209
+ // --- npm test ---
210
+ if (config.runTests) {
211
+ if (hasScript(projectRoot, 'test')) {
212
+ // Orchestrator artifacts (runs/, worktrees) can contain duplicate test
213
+ // files that confuse recursive test runners like Jest. Append ignore
214
+ // patterns so the gate only evaluates the project's own tests.
215
+ const testCmd = buildTestCommand(projectRoot);
216
+ const result = runCommand(testCmd, projectRoot, config.timeoutMs, config.retries);
217
+ stats.testsRun = 1;
218
+ if (!result.success) {
219
+ const excerpt = (result.stderr || result.stdout).split('\n').slice(-15).join('\n').trim();
220
+ issues.push({
221
+ message: '`npm test` failed',
222
+ hint: 'Fix failing tests before merge.',
223
+ excerpt: excerpt.substring(0, 500)
224
+ });
225
+ }
226
+ }
227
+ }
228
+ // --- eslint ---
229
+ if (config.runLint) {
230
+ if (hasEslintConfig(projectRoot)) {
231
+ const eslintCmd = buildEslintCommand(projectRoot, baseCommit);
232
+ if (eslintCmd) {
233
+ const result = runCommand(eslintCmd, projectRoot, config.timeoutMs, config.retries);
234
+ stats.lintRun = 1;
235
+ if (!result.success) {
236
+ const excerpt = (result.stdout || result.stderr).split('\n').slice(0, 20).join('\n').trim();
237
+ issues.push({
238
+ message: '`npx eslint` reported errors on agent-changed files',
239
+ hint: 'Fix lint errors before merge.',
240
+ excerpt: excerpt.substring(0, 500)
241
+ });
242
+ }
243
+ }
244
+ }
245
+ }
246
+ // --- npm audit ---
247
+ if (config.runAudit) {
248
+ // npm audit requires a lockfile; skip for projects with no dependencies
249
+ if (fs.existsSync(path.join(projectRoot, 'package-lock.json'))) {
250
+ const result = runCommand('npm audit --audit-level=moderate --omit=dev', projectRoot, config.timeoutMs, config.retries);
251
+ stats.auditRun = 1;
252
+ if (!result.success) {
253
+ const excerpt = (result.stdout || result.stderr).split('\n').slice(0, 15).join('\n').trim();
254
+ issues.push({
255
+ message: '`npm audit` found moderate+ vulnerabilities',
256
+ hint: 'Run `npm audit fix` or address manually.',
257
+ excerpt: excerpt.substring(0, 500)
258
+ });
259
+ }
260
+ }
261
+ }
262
+ const durationMs = Date.now() - start;
263
+ const status = issues.length > 0 ? 'fail' : 'pass';
264
+ return { id, title, status, durationMs, issues, stats };
265
+ }
@@ -0,0 +1,108 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.run_scaffold_defaults_gate = run_scaffold_defaults_gate;
4
+ const file_utils_1 = require("../file-utils");
5
+ // Artifacts that should never be tracked. Each entry maps a file pattern
6
+ // (tested against relativePath) to the gitignore entry that would exclude it.
7
+ // Only flagged when the artifact is actually present in the project file list,
8
+ // so the check produces zero false positives on clean repos.
9
+ const GITIGNORE_ARTIFACT_RULES = [
10
+ { filePattern: /^\.coverage$/, gitignoreEntry: '.coverage', hint: 'pytest coverage data binary' },
11
+ { filePattern: /\.pyc$/, gitignoreEntry: '*.pyc', hint: 'compiled Python bytecode' },
12
+ { filePattern: /(^|\/)__pycache__\//, gitignoreEntry: '__pycache__/', hint: 'Python bytecode cache directory' },
13
+ { filePattern: /^\.env$/, gitignoreEntry: '.env', hint: 'environment secrets file' },
14
+ { filePattern: /(^|\/)node_modules\//, gitignoreEntry: 'node_modules/', hint: 'npm dependency tree' },
15
+ { filePattern: /^dist\//, gitignoreEntry: 'dist/', hint: 'build output directory' },
16
+ { filePattern: /^\.DS_Store$/, gitignoreEntry: '.DS_Store', hint: 'macOS Finder metadata' },
17
+ { filePattern: /^Thumbs\.db$/, gitignoreEntry: 'Thumbs.db', hint: 'Windows thumbnail cache' },
18
+ { filePattern: /\.sqlite3?$/, gitignoreEntry: '*.sqlite3', hint: 'SQLite database file (should not be tracked)' },
19
+ ];
20
+ async function run_scaffold_defaults_gate(ctx, config, maxFileSizeBytes) {
21
+ const start = Date.now();
22
+ const issues = [];
23
+ const bannedTitle = config.bannedTitleRegexes.map(r => new RegExp(r, 'i'));
24
+ const bannedReadme = config.bannedReadmeRegexes.map(r => new RegExp(r, 'i'));
25
+ const bannedFiles = new Set(config.bannedFiles);
26
+ // banned files present
27
+ for (const file of ctx.files) {
28
+ if (bannedFiles.has(file.relativePath)) {
29
+ issues.push({
30
+ message: `scaffold default file present: ${file.relativePath}`,
31
+ filePath: file.relativePath,
32
+ hint: 'delete it or replace with project-specific asset'
33
+ });
34
+ }
35
+ }
36
+ // index.html title defaults
37
+ const indexHtml = ctx.files.find(f => f.relativePath === 'index.html');
38
+ if (indexHtml) {
39
+ const text = (0, file_utils_1.maybe_read_text)(indexHtml, maxFileSizeBytes) || '';
40
+ const titleMatch = text.match(/<title>([^<]+)<\/title>/i);
41
+ if (titleMatch && titleMatch[1]) {
42
+ const title = titleMatch[1].trim();
43
+ for (const re of bannedTitle) {
44
+ if (re.test(title)) {
45
+ issues.push({
46
+ message: `scaffold default HTML title: "${title}"`,
47
+ filePath: indexHtml.relativePath,
48
+ excerpt: title,
49
+ hint: 'set a real product/app name'
50
+ });
51
+ break;
52
+ }
53
+ }
54
+ }
55
+ }
56
+ // README defaults
57
+ const readme = ctx.files.find(f => /^readme\.md$/i.test(f.relativePath));
58
+ if (readme) {
59
+ const text = (0, file_utils_1.maybe_read_text)(readme, maxFileSizeBytes) || '';
60
+ for (const re of bannedReadme) {
61
+ if (re.test(text)) {
62
+ issues.push({
63
+ message: `scaffold default README content matched /${re.source}/`,
64
+ filePath: readme.relativePath,
65
+ hint: 'replace scaffold docs with project-specific README'
66
+ });
67
+ }
68
+ }
69
+ }
70
+ // Gitignore completeness: when .gitignore exists, check that tracked files
71
+ // don't include artifacts that should be excluded. Only flags artifacts
72
+ // actually present in the project, not hypothetical ones.
73
+ const gitignore = ctx.files.find(f => f.relativePath === '.gitignore');
74
+ if (gitignore) {
75
+ const gitignoreText = (0, file_utils_1.maybe_read_text)(gitignore, maxFileSizeBytes) || '';
76
+ const gitignoreLines = gitignoreText.split('\n').map(l => l.trim());
77
+ for (const rule of GITIGNORE_ARTIFACT_RULES) {
78
+ const artifactPresent = ctx.files.some(f => rule.filePattern.test(f.relativePath));
79
+ if (!artifactPresent)
80
+ continue;
81
+ // Check whether gitignore already covers this artifact.
82
+ // Simple substring match on the entry (e.g. ".coverage" appears in a line).
83
+ // Not full gitignore glob semantics, but catches standard entries.
84
+ const covered = gitignoreLines.some(line => line === rule.gitignoreEntry ||
85
+ line === '/' + rule.gitignoreEntry ||
86
+ (rule.gitignoreEntry.startsWith('*') && line === rule.gitignoreEntry));
87
+ if (!covered) {
88
+ issues.push({
89
+ message: `tracked artifact should be in .gitignore: ${rule.gitignoreEntry}`,
90
+ filePath: '.gitignore',
91
+ hint: rule.hint,
92
+ });
93
+ }
94
+ }
95
+ }
96
+ const durationMs = Date.now() - start;
97
+ const status = issues.length > 0 ? 'fail' : 'pass';
98
+ return {
99
+ id: 'scaffold-defaults',
100
+ title: 'Scaffold defaults removed',
101
+ status,
102
+ durationMs,
103
+ issues,
104
+ stats: {
105
+ issues: issues.length
106
+ }
107
+ };
108
+ }