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,240 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.run_test_coverage_gate = run_test_coverage_gate;
4
+ const file_utils_1 = require("../file-utils");
5
+ // Patterns that indicate a file is an entry point (not a standalone module needing its own test).
6
+ // The (^|\/) prefix handles monorepo layouts like frontend/src/main.jsx alongside flat src/main.tsx.
7
+ const DEFAULT_ENTRY_PATTERNS = [
8
+ /(^|\/)src\/(main|index)\.(tsx?|jsx?|mjs)$/,
9
+ /(^|\/)src\/app\.(tsx?|jsx?)$/i
10
+ ];
11
+ async function run_test_coverage_gate(ctx, config, maxFileSizeBytes) {
12
+ const start = Date.now();
13
+ const issues = [];
14
+ // Custom patterns extend the structural defaults (main/index/app are always
15
+ // entry points regardless of project config), not replace them.
16
+ const customPatterns = config.entryPointPatterns.map(p => new RegExp(p, 'i'));
17
+ const entryPatterns = [...DEFAULT_ENTRY_PATTERNS, ...customPatterns];
18
+ // Identify source files that should have tests
19
+ const sourceFiles = ctx.files.filter(f => {
20
+ if (!/\.(tsx?|jsx?|mjs)$/i.test(f.relativePath))
21
+ return false;
22
+ if (/^(test|tests|__tests__)\//.test(f.relativePath))
23
+ return false;
24
+ if (/\.test\.|\.spec\./.test(f.relativePath))
25
+ return false;
26
+ // Root-level test scripts (test.js, tests.js) are test files, not source
27
+ if (/^tests?\.(tsx?|jsx?|mjs)$/i.test(f.relativePath))
28
+ return false;
29
+ // Integration/e2e test files at root level
30
+ if (/^(integration|e2e)[.-]/i.test(f.relativePath))
31
+ return false;
32
+ if (/^(server|config|scripts|examples?|deploy|benchmarks)\//.test(f.relativePath))
33
+ return false;
34
+ // Test setup/config files nested inside src/ (e.g. src/test/setup.js,
35
+ // src/setupTests.ts, frontend/src/test/setup.js) are test infrastructure
36
+ if (/\btest\/setup\b|\bsetup[Tt]ests?\b|\btestSetup\b/i.test(f.relativePath))
37
+ return false;
38
+ // Exclude standalone server entry points (e.g. src/server.js that just calls app.listen)
39
+ if (/\bserver\.(tsx?|jsx?|mjs)$/i.test(f.relativePath))
40
+ return false;
41
+ // Exclude vite/webpack configs and similar build tooling
42
+ if (/\.(config|d)\.(ts|js|mjs)$/i.test(f.relativePath))
43
+ return false;
44
+ // Exclude root-level entry scripts (start-*, index.js, app.js) outside src/
45
+ if (!/\//.test(f.relativePath) || /^[^/]+\.(js|mjs)$/.test(f.relativePath)) {
46
+ if (/^(start|launch|boot|serve)[-.]/i.test(f.relativePath))
47
+ return false;
48
+ }
49
+ // Exclude entry points
50
+ if (entryPatterns.some(p => p.test(f.relativePath)))
51
+ return false;
52
+ // Exclude Ink TUI components: .tsx files importing from 'ink' are terminal UI
53
+ // renderers that require ink-testing-library, not standard DOM test utilities.
54
+ // Coverage comes from integration tests that exercise the underlying data model.
55
+ if (/\.tsx$/i.test(f.relativePath)) {
56
+ const text = f.text ?? (0, file_utils_1.maybe_read_text)(f, maxFileSizeBytes);
57
+ if (text && /from\s+['"]ink['"]|require\s*\(\s*['"]ink['"]\)/.test(text)) {
58
+ return false;
59
+ }
60
+ }
61
+ return true;
62
+ });
63
+ // Identify test files: convention-named (.test./.spec.) and root-level test scripts
64
+ const testFiles = ctx.files.filter(f => /\.(test|spec)\.(tsx?|jsx?|mjs)$/i.test(f.relativePath) ||
65
+ /^tests?\.(tsx?|jsx?|mjs)$/i.test(f.relativePath));
66
+ // Collect all require/import edges from every source and test file so we
67
+ // can resolve transitive coverage. An integration test importing app.js
68
+ // should count as covering the controllers, middleware, and routes that
69
+ // app.js itself requires.
70
+ const allFiles = [...ctx.files];
71
+ const fileImports = new Map();
72
+ const importRegex = /(?:from\s+['"]|require\s*\(\s*['"])([^'"]+)['"]/g;
73
+ // Resolve a relative import specifier against the importing file's directory
74
+ function resolveImport(specifier, importerRelPath) {
75
+ const stripped = specifier.replace(/\.(js|ts|jsx|tsx|mjs)$/, '');
76
+ if (!specifier.startsWith('.'))
77
+ return stripped;
78
+ const importerKey = importerRelPath.replace(/\.(js|ts|jsx|tsx|mjs)$/, '');
79
+ const slashIdx = importerKey.lastIndexOf('/');
80
+ let dir = slashIdx >= 0 ? importerKey.substring(0, slashIdx) : '';
81
+ let remaining = stripped;
82
+ if (remaining.startsWith('./')) {
83
+ remaining = remaining.substring(2);
84
+ }
85
+ else {
86
+ while (remaining.startsWith('../')) {
87
+ remaining = remaining.substring(3);
88
+ const parentSlash = dir.lastIndexOf('/');
89
+ dir = parentSlash >= 0 ? dir.substring(0, parentSlash) : '';
90
+ }
91
+ }
92
+ return dir ? `${dir}/${remaining}` : remaining;
93
+ }
94
+ for (const f of allFiles) {
95
+ if (!/\.(tsx?|jsx?|mjs)$/i.test(f.relativePath))
96
+ continue;
97
+ const text = f.text ?? (0, file_utils_1.maybe_read_text)(f, maxFileSizeBytes);
98
+ if (!text)
99
+ continue;
100
+ const imports = [];
101
+ let m;
102
+ importRegex.lastIndex = 0;
103
+ while ((m = importRegex.exec(text)) !== null) {
104
+ if (!m[1].startsWith('.'))
105
+ continue;
106
+ imports.push(resolveImport(m[1], f.relativePath));
107
+ }
108
+ const key = f.relativePath.replace(/\.(js|ts|jsx|tsx|mjs)$/, '');
109
+ fileImports.set(key, imports);
110
+ // Register barrel alias: directory imports resolve to dir/index
111
+ const basename = key.split('/').pop();
112
+ if (basename === 'index') {
113
+ const dirKey = key.replace(/\/index$/, '');
114
+ fileImports.set(dirKey, imports);
115
+ }
116
+ }
117
+ // Walk transitive imports from test files to build the full coverage set
118
+ const importedByTests = new Set();
119
+ function walkImports(moduleKey, visited) {
120
+ if (visited.has(moduleKey))
121
+ return;
122
+ visited.add(moduleKey);
123
+ importedByTests.add(moduleKey);
124
+ const deps = fileImports.get(moduleKey) || fileImports.get(`src/${moduleKey}`) || [];
125
+ for (const dep of deps) {
126
+ walkImports(dep, visited);
127
+ // Also try with src/ prefix
128
+ if (!dep.startsWith('src/')) {
129
+ walkImports(`src/${dep}`, visited);
130
+ }
131
+ }
132
+ }
133
+ for (const tf of testFiles) {
134
+ const text = tf.text ?? (0, file_utils_1.maybe_read_text)(tf, maxFileSizeBytes);
135
+ if (!text)
136
+ continue;
137
+ importRegex.lastIndex = 0;
138
+ let match;
139
+ while ((match = importRegex.exec(text)) !== null) {
140
+ if (!match[1].startsWith('.'))
141
+ continue;
142
+ const resolved = resolveImport(match[1], tf.relativePath);
143
+ walkImports(resolved, new Set());
144
+ }
145
+ }
146
+ // 1. Check that each source file has a corresponding test or is imported by a test.
147
+ // Only flag files created by agents; pre-existing files are the project owner's concern.
148
+ for (const src of sourceFiles) {
149
+ if (ctx.baselineFiles?.has(src.relativePath))
150
+ continue;
151
+ const baseName = src.relativePath
152
+ .replace(/^src\//, '')
153
+ .replace(/\.(tsx?|jsx?|mjs)$/, '');
154
+ const hasDirectTest = testFiles.some(tf => {
155
+ const testBase = tf.relativePath
156
+ .replace(/^(test|tests|__tests__)\//, '')
157
+ .replace(/^(unit|integration|e2e)\//, '')
158
+ .replace(/\.(test|spec)\.(tsx?|jsx?|mjs)$/, '');
159
+ // Match by full relative path, by stripping common prefixes, or
160
+ // by filename only (test/unit/foo.test.js covers src/services/foo.js)
161
+ const testBaseName = testBase.split('/').pop() || testBase;
162
+ const srcBaseName = baseName.split('/').pop() || baseName;
163
+ return testBase === baseName
164
+ || testBase === baseName.replace(/^utils\//, '')
165
+ || testBaseName === srcBaseName;
166
+ });
167
+ const isImportedByTest = importedByTests.has(`src/${baseName}`) ||
168
+ importedByTests.has(baseName) ||
169
+ importedByTests.has(src.relativePath.replace(/\.(tsx?|jsx?|mjs)$/, ''));
170
+ if (!hasDirectTest && !isImportedByTest) {
171
+ issues.push({
172
+ message: `no test coverage for ${src.relativePath}`,
173
+ filePath: src.relativePath,
174
+ hint: `create a test file or import ${src.relativePath} in an existing test`
175
+ });
176
+ }
177
+ }
178
+ // 2. Check that test files contain real assertions (not empty stubs).
179
+ // Only validate agent-created test files, not pre-existing ones.
180
+ for (const tf of testFiles) {
181
+ if (ctx.baselineFiles?.has(tf.relativePath))
182
+ continue;
183
+ const text = tf.text ?? (0, file_utils_1.maybe_read_text)(tf, maxFileSizeBytes);
184
+ if (!text)
185
+ continue;
186
+ const assertionPatterns = /\b(assert\.|expect\(|\.toBe|\.toEqual|\.toThrow|\.ok\(|\.strictEqual|\.deepEqual|\.deepStrictEqual|\.match\(|\.rejects|\.resolves)/;
187
+ const assertionCount = (text.match(new RegExp(assertionPatterns.source, 'g')) || []).length;
188
+ if (assertionCount < config.minTestAssertions) {
189
+ issues.push({
190
+ message: `${tf.relativePath} has ${assertionCount} assertion(s), minimum is ${config.minTestAssertions}`,
191
+ filePath: tf.relativePath,
192
+ hint: 'add meaningful assertions that verify module behavior'
193
+ });
194
+ }
195
+ }
196
+ // 3. Check for component-level tests in React projects
197
+ // Skip for Ink (terminal UI) projects where DOM testing libraries don't apply.
198
+ if (config.requireComponentTests) {
199
+ const isReactProject = ctx.files.some(f => {
200
+ const text = f.text ?? (0, file_utils_1.maybe_read_text)(f, maxFileSizeBytes);
201
+ if (!text)
202
+ return false;
203
+ return /from\s+['"]react['"]|require\s*\(\s*['"]react['"]\)/.test(text);
204
+ });
205
+ const isInkProject = ctx.files.some(f => {
206
+ const text = f.text ?? (0, file_utils_1.maybe_read_text)(f, maxFileSizeBytes);
207
+ if (!text)
208
+ return false;
209
+ return /from\s+['"]ink['"]|require\s*\(\s*['"]ink['"]\)/.test(text);
210
+ });
211
+ if (isReactProject && !isInkProject) {
212
+ const hasComponentTest = testFiles.some(f => {
213
+ const text = f.text ?? (0, file_utils_1.maybe_read_text)(f, maxFileSizeBytes);
214
+ if (!text)
215
+ return false;
216
+ return /(render\s*\(|screen\.|document\.|getBy|queryBy|findBy|innerHTML|textContent|@testing-library)/i.test(text);
217
+ });
218
+ if (!hasComponentTest) {
219
+ issues.push({
220
+ message: 'React project has no component-level tests',
221
+ hint: 'add at least one test that renders a component and asserts DOM output'
222
+ });
223
+ }
224
+ }
225
+ }
226
+ const durationMs = Date.now() - start;
227
+ const status = issues.length > 0 ? 'fail' : 'pass';
228
+ return {
229
+ id: 'test-coverage',
230
+ title: 'Test coverage checks',
231
+ status,
232
+ durationMs,
233
+ issues,
234
+ stats: {
235
+ sourceFiles: sourceFiles.length,
236
+ testFiles: testFiles.length,
237
+ issues: issues.length
238
+ }
239
+ };
240
+ }
@@ -0,0 +1,85 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.run_test_file_protection_gate = run_test_file_protection_gate;
4
+ const child_process_1 = require("child_process");
5
+ /**
6
+ * D4: Test-File Protection Gate
7
+ *
8
+ * Detects when an agent has **modified** (not added) pre-existing test files.
9
+ * Agents should write new tests, not weaken existing ones.
10
+ *
11
+ * Uses `git diff --name-only --diff-filter=M` against a base commit
12
+ * and matches the results against configurable test-file patterns.
13
+ */
14
+ async function run_test_file_protection_gate(projectRoot, config, baseCommit) {
15
+ const start = Date.now();
16
+ const issues = [];
17
+ const base = baseCommit || 'HEAD~1';
18
+ const patterns = config.testFileGlobs;
19
+ try {
20
+ // Get all MODIFIED (not Added) files relative to the base commit
21
+ const raw = (0, child_process_1.execSync)(`git diff --name-only --diff-filter=M ${base}`, { cwd: projectRoot, encoding: 'utf8', timeout: 30_000, stdio: ['ignore', 'pipe', 'pipe'] }).trim();
22
+ if (raw.length === 0) {
23
+ return result('pass', issues, Date.now() - start, 0);
24
+ }
25
+ const modifiedFiles = raw.split('\n').filter(Boolean);
26
+ // Compile glob-like patterns to regexes
27
+ const matchers = patterns.map(glob_to_regex);
28
+ let flagged = 0;
29
+ for (const file of modifiedFiles) {
30
+ if (matchers.some(re => re.test(file))) {
31
+ flagged++;
32
+ issues.push({
33
+ message: `Pre-existing test file was modified by an agent`,
34
+ filePath: file,
35
+ hint: 'Agents should add new test files, not modify existing ones. ' +
36
+ 'If the modification is fixing an import path or test config, ' +
37
+ 'consider an allowlist entry in quality-gates.yaml.'
38
+ });
39
+ if (issues.length >= (config.maxFindings ?? 25))
40
+ break;
41
+ }
42
+ }
43
+ const status = issues.length > 0 ? 'fail' : 'pass';
44
+ return result(status, issues, Date.now() - start, flagged);
45
+ }
46
+ catch (err) {
47
+ // git not available or not a repo — skip gracefully
48
+ const msg = err instanceof Error ? err.message : String(err);
49
+ return {
50
+ id: 'test-file-protection',
51
+ title: 'Test files not modified by agents',
52
+ status: 'skip',
53
+ durationMs: Date.now() - start,
54
+ issues: [{
55
+ message: `Could not run git diff: ${msg.slice(0, 200)}`,
56
+ hint: 'Ensure the project is a git repo with at least one commit.'
57
+ }],
58
+ stats: { flagged: 0 }
59
+ };
60
+ }
61
+ }
62
+ function result(status, issues, durationMs, flagged) {
63
+ return {
64
+ id: 'test-file-protection',
65
+ title: 'Test files not modified by agents',
66
+ status,
67
+ durationMs,
68
+ issues,
69
+ stats: { flagged }
70
+ };
71
+ }
72
+ /**
73
+ * Convert a simple glob pattern to a RegExp.
74
+ * Supports: ** (any path), * (any segment chars), ? (single char).
75
+ * Patterns are matched against the full relative path.
76
+ */
77
+ function glob_to_regex(glob) {
78
+ const re = glob
79
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape regex specials (except * and ?)
80
+ .replace(/\*\*/g, '$$GLOBSTAR$$') // placeholder for **
81
+ .replace(/\*/g, '[^/]*') // * = anything except /
82
+ .replace(/\?/g, '.') // ? = single char
83
+ .replace(/\$\$GLOBSTAR\$\$/g, '.*'); // ** = anything including /
84
+ return new RegExp(`^${re}$`, 'i');
85
+ }
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.run_test_isolation_gate = run_test_isolation_gate;
4
+ const file_utils_1 = require("../file-utils");
5
+ function is_probably_source_file(relPath) {
6
+ return /\.(ts|js|tsx|jsx)$/.test(relPath) && !/\.d\.ts$/.test(relPath);
7
+ }
8
+ async function run_test_isolation_gate(ctx, config, maxFileSizeBytes) {
9
+ const start = Date.now();
10
+ const issues = [];
11
+ const storeRegexes = (0, file_utils_1.compile_regexes)(config.mutableStoreRegexes);
12
+ const allowIfFileContains = (0, file_utils_1.compile_regexes)(config.allowIfFileContainsRegexes);
13
+ const testFileRegexes = config.testFileRegexes.map(r => new RegExp(r, 'i'));
14
+ const allowIfAnyTestContains = (0, file_utils_1.compile_regexes)(config.allowIfAnyTestContainsRegexes);
15
+ const testTexts = [];
16
+ for (const file of ctx.files) {
17
+ if (!testFileRegexes.some(r => r.test(file.relativePath))) {
18
+ continue;
19
+ }
20
+ const text = (0, file_utils_1.maybe_read_text)(file, maxFileSizeBytes);
21
+ if (text) {
22
+ testTexts.push(text);
23
+ }
24
+ }
25
+ const anyTestHasResetHook = testTexts.some(t => allowIfAnyTestContains.some(r => r.test(t)));
26
+ for (const file of ctx.files) {
27
+ if (!is_probably_source_file(file.relativePath)) {
28
+ continue;
29
+ }
30
+ const text = (0, file_utils_1.maybe_read_text)(file, maxFileSizeBytes);
31
+ if (!text) {
32
+ continue;
33
+ }
34
+ const lines = text.split('\n');
35
+ // scan first ~80 lines for module-scope mutable stores
36
+ const scanLines = lines.slice(0, Math.min(80, lines.length));
37
+ for (let i = 0; i < scanLines.length; i++) {
38
+ const line = scanLines[i] || '';
39
+ if (!storeRegexes.some(r => r.test(line))) {
40
+ continue;
41
+ }
42
+ const allowed = allowIfFileContains.some(r => r.test(text)) || anyTestHasResetHook;
43
+ if (allowed) {
44
+ continue;
45
+ }
46
+ issues.push({
47
+ message: 'mutable module-scope store detected without reset strategy',
48
+ filePath: file.relativePath,
49
+ line: i + 1,
50
+ excerpt: line.trim(),
51
+ hint: 'export a reset hook for tests or reset state in beforeEach()'
52
+ });
53
+ if (issues.length >= 25) {
54
+ break;
55
+ }
56
+ }
57
+ if (issues.length >= 25) {
58
+ break;
59
+ }
60
+ }
61
+ const durationMs = Date.now() - start;
62
+ const status = issues.length > 0 ? 'fail' : 'pass';
63
+ return {
64
+ id: 'test-isolation',
65
+ title: 'Tests are isolated (reset mutable state)',
66
+ status,
67
+ durationMs,
68
+ issues,
69
+ stats: {
70
+ issues: issues.length,
71
+ testFiles: testTexts.length
72
+ }
73
+ };
74
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parse_gitignore_dirs = exports.list_project_files = exports.registerGate = exports.loadProjectGateRegistrations = exports.getRegisteredGates = exports.getRegisteredGateKeys = exports.run_quality_gates = exports.DEFAULT_QUALITY_GATES_CONFIG = exports.load_quality_gates_config = void 0;
4
+ var config_loader_1 = require("./config-loader");
5
+ Object.defineProperty(exports, "load_quality_gates_config", { enumerable: true, get: function () { return config_loader_1.load_quality_gates_config; } });
6
+ var default_config_1 = require("./default-config");
7
+ Object.defineProperty(exports, "DEFAULT_QUALITY_GATES_CONFIG", { enumerable: true, get: function () { return default_config_1.DEFAULT_QUALITY_GATES_CONFIG; } });
8
+ var gate_runner_1 = require("./gate-runner");
9
+ Object.defineProperty(exports, "run_quality_gates", { enumerable: true, get: function () { return gate_runner_1.run_quality_gates; } });
10
+ var registry_1 = require("./registry");
11
+ Object.defineProperty(exports, "getRegisteredGateKeys", { enumerable: true, get: function () { return registry_1.getRegisteredGateKeys; } });
12
+ Object.defineProperty(exports, "getRegisteredGates", { enumerable: true, get: function () { return registry_1.getRegisteredGates; } });
13
+ Object.defineProperty(exports, "loadProjectGateRegistrations", { enumerable: true, get: function () { return registry_1.loadProjectGateRegistrations; } });
14
+ Object.defineProperty(exports, "registerGate", { enumerable: true, get: function () { return registry_1.registerGate; } });
15
+ var file_utils_1 = require("./file-utils");
16
+ Object.defineProperty(exports, "list_project_files", { enumerable: true, get: function () { return file_utils_1.list_project_files; } });
17
+ Object.defineProperty(exports, "parse_gitignore_dirs", { enumerable: true, get: function () { return file_utils_1.parse_gitignore_dirs; } });
@@ -0,0 +1,205 @@
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.SELF_IMPROVEMENT_GATE_KEYS = void 0;
37
+ exports.registerGate = registerGate;
38
+ exports.resetProjectGates = resetProjectGates;
39
+ exports.getRegisteredGates = getRegisteredGates;
40
+ exports.getRegisteredGateKeys = getRegisteredGateKeys;
41
+ exports.loadProjectGateRegistrations = loadProjectGateRegistrations;
42
+ const fs = __importStar(require("fs"));
43
+ const path = __importStar(require("path"));
44
+ const default_config_1 = require("./default-config");
45
+ const accessibility_1 = require("./gates/accessibility");
46
+ const duplicate_blocks_1 = require("./gates/duplicate-blocks");
47
+ const hardcoded_config_1 = require("./gates/hardcoded-config");
48
+ const readme_claims_1 = require("./gates/readme-claims");
49
+ const runtime_checks_1 = require("./gates/runtime-checks");
50
+ const scaffold_defaults_1 = require("./gates/scaffold-defaults");
51
+ const test_coverage_1 = require("./gates/test-coverage");
52
+ const test_file_protection_1 = require("./gates/test-file-protection");
53
+ const test_isolation_1 = require("./gates/test-isolation");
54
+ const registry = new Map();
55
+ const builtInKeys = new Set();
56
+ const projectKeys = new Set();
57
+ function registerBuiltInGate(gate) {
58
+ registry.set(gate.key, gate);
59
+ builtInKeys.add(gate.key);
60
+ }
61
+ function registerGate(gate) {
62
+ registry.set(gate.key, gate);
63
+ projectKeys.add(gate.key);
64
+ }
65
+ function resetProjectGates() {
66
+ for (const key of projectKeys) {
67
+ if (!builtInKeys.has(key)) {
68
+ registry.delete(key);
69
+ }
70
+ }
71
+ projectKeys.clear();
72
+ }
73
+ function getRegisteredGates() {
74
+ return Array.from(registry.values());
75
+ }
76
+ function getRegisteredGateKeys() {
77
+ return getRegisteredGates().map(gate => gate.key);
78
+ }
79
+ function resolveProjectGateModule(projectRoot) {
80
+ const candidates = [
81
+ path.join(projectRoot, '.swarm', 'gates', 'index.js'),
82
+ path.join(projectRoot, '.swarm', 'gates', 'index.cjs'),
83
+ ];
84
+ for (const candidate of candidates) {
85
+ if (fs.existsSync(candidate)) {
86
+ return candidate;
87
+ }
88
+ }
89
+ return null;
90
+ }
91
+ function registerProjectExports(entry) {
92
+ if (typeof entry.registerGates === 'function') {
93
+ entry.registerGates({ registerGate });
94
+ return;
95
+ }
96
+ if (Array.isArray(entry.gates)) {
97
+ for (const gate of entry.gates)
98
+ registerGate(gate);
99
+ return;
100
+ }
101
+ if (typeof entry.default === 'function') {
102
+ entry.default({ registerGate });
103
+ return;
104
+ }
105
+ if (Array.isArray(entry.default)) {
106
+ for (const gate of entry.default)
107
+ registerGate(gate);
108
+ }
109
+ }
110
+ function loadProjectGateRegistrations(projectRoot) {
111
+ resetProjectGates();
112
+ const entryPath = resolveProjectGateModule(projectRoot);
113
+ if (!entryPath)
114
+ return [];
115
+ delete require.cache[require.resolve(entryPath)];
116
+ const loaded = require(entryPath);
117
+ registerProjectExports(loaded);
118
+ return [entryPath];
119
+ }
120
+ /**
121
+ * Gate keys that are "self-improvement" gates — the orchestrator applies
122
+ * them to its OWN generated code to enforce quality on code it produced.
123
+ * When the orchestrator is running against a target repo (swarm bootstrap
124
+ * against an external codebase), these gates should NOT fire: the target
125
+ * has its own conventions and the gates misattribute target-repo features
126
+ * as agent defects. Phase 4a smoke4 evidence: the accessibility gate
127
+ * flagged sympy (computer-algebra library, no UI) and triggered replan
128
+ * churn that wasted cycles and polluted the diff-capture.
129
+ *
130
+ * Gates NOT in this set are "universal" — they enforce agent-behavior
131
+ * contracts that apply regardless of whose codebase is being modified:
132
+ * - hardcodedConfig agent must not add secrets / hardcoded config
133
+ * (the gate scopes to baselineFiles, so it only
134
+ * flags what the agent added, not pre-existing
135
+ * target state — safe in target mode)
136
+ * - testFileProtection agent must not modify pre-existing test files
137
+ * (SWE-bench evaluation integrity + general
138
+ * safety property; always fires)
139
+ *
140
+ * `run_quality_gates` consults this set via the `skippedGateKeys` param
141
+ * the orchestrator passes when `targetMode === true`.
142
+ */
143
+ exports.SELF_IMPROVEMENT_GATE_KEYS = new Set([
144
+ 'scaffoldDefaults',
145
+ 'duplicateBlocks',
146
+ 'readmeClaims',
147
+ 'testIsolation',
148
+ 'runtimeChecks',
149
+ 'accessibility',
150
+ 'testCoverage',
151
+ ]);
152
+ registerBuiltInGate({
153
+ key: 'scaffoldDefaults',
154
+ title: 'Scaffold Defaults',
155
+ defaultConfig: default_config_1.DEFAULT_QUALITY_GATES_CONFIG.gates.scaffoldDefaults,
156
+ run: (ctx, config, maxFileSizeBytes) => (0, scaffold_defaults_1.run_scaffold_defaults_gate)(ctx, config, maxFileSizeBytes),
157
+ });
158
+ registerBuiltInGate({
159
+ key: 'duplicateBlocks',
160
+ title: 'Duplicate Blocks',
161
+ defaultConfig: default_config_1.DEFAULT_QUALITY_GATES_CONFIG.gates.duplicateBlocks,
162
+ run: (ctx, config, maxFileSizeBytes) => (0, duplicate_blocks_1.run_duplicate_blocks_gate)(ctx, config, maxFileSizeBytes),
163
+ });
164
+ registerBuiltInGate({
165
+ key: 'hardcodedConfig',
166
+ title: 'Hardcoded Config',
167
+ defaultConfig: default_config_1.DEFAULT_QUALITY_GATES_CONFIG.gates.hardcodedConfig,
168
+ run: (ctx, config, maxFileSizeBytes) => (0, hardcoded_config_1.run_hardcoded_config_gate)(ctx, config, maxFileSizeBytes),
169
+ });
170
+ registerBuiltInGate({
171
+ key: 'readmeClaims',
172
+ title: 'README Claims',
173
+ defaultConfig: default_config_1.DEFAULT_QUALITY_GATES_CONFIG.gates.readmeClaims,
174
+ run: (ctx, config, maxFileSizeBytes) => (0, readme_claims_1.run_readme_claims_gate)(ctx, config, maxFileSizeBytes),
175
+ });
176
+ registerBuiltInGate({
177
+ key: 'testIsolation',
178
+ title: 'Test Isolation',
179
+ defaultConfig: default_config_1.DEFAULT_QUALITY_GATES_CONFIG.gates.testIsolation,
180
+ run: (ctx, config, maxFileSizeBytes) => (0, test_isolation_1.run_test_isolation_gate)(ctx, config, maxFileSizeBytes),
181
+ });
182
+ registerBuiltInGate({
183
+ key: 'runtimeChecks',
184
+ title: 'Runtime Checks',
185
+ defaultConfig: default_config_1.DEFAULT_QUALITY_GATES_CONFIG.gates.runtimeChecks,
186
+ run: (ctx, config) => (0, runtime_checks_1.run_runtime_checks_gate)(ctx.projectRoot, config, ctx.baseCommit),
187
+ });
188
+ registerBuiltInGate({
189
+ key: 'accessibility',
190
+ title: 'Accessibility',
191
+ defaultConfig: default_config_1.DEFAULT_QUALITY_GATES_CONFIG.gates.accessibility,
192
+ run: (ctx, config, maxFileSizeBytes) => (0, accessibility_1.run_accessibility_gate)(ctx, config, maxFileSizeBytes),
193
+ });
194
+ registerBuiltInGate({
195
+ key: 'testCoverage',
196
+ title: 'Test Coverage',
197
+ defaultConfig: default_config_1.DEFAULT_QUALITY_GATES_CONFIG.gates.testCoverage,
198
+ run: (ctx, config, maxFileSizeBytes) => (0, test_coverage_1.run_test_coverage_gate)(ctx, config, maxFileSizeBytes),
199
+ });
200
+ registerBuiltInGate({
201
+ key: 'testFileProtection',
202
+ title: 'Test File Protection',
203
+ defaultConfig: default_config_1.DEFAULT_QUALITY_GATES_CONFIG.gates.testFileProtection,
204
+ run: (ctx, config) => (0, test_file_protection_1.run_test_file_protection_gate)(ctx.projectRoot, config, ctx.baseCommit),
205
+ });
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });