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,173 @@
1
+ "use strict";
2
+ // Extracted prompt-building and shared-instruction helpers (formerly class methods on SwarmOrchestrator).
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ Object.defineProperty(exports, "__esModule", { value: true });
37
+ exports.buildSwarmPrompt = buildSwarmPrompt;
38
+ exports.writeSharedInstructions = writeSharedInstructions;
39
+ const child_process_1 = require("child_process");
40
+ const fs = __importStar(require("fs"));
41
+ const path = __importStar(require("path"));
42
+ const baseline_scanner_1 = require("./baseline-scanner");
43
+ const logger_1 = require("./logger");
44
+ const test_command_discovery_1 = require("./test-command-discovery");
45
+ const logger = (0, logger_1.getLogger)('prompt-builder');
46
+ /**
47
+ * Build the per-step prompt that gets sent to the Copilot CLI agent.
48
+ */
49
+ function buildSwarmPrompt(step, agent, context, dependencyContext) {
50
+ const sections = [];
51
+ sections.push(`Step ${step.stepNumber}/${context.plan.steps.length} | Agent: ${agent.name}`);
52
+ sections.push(`Role: ${agent.purpose}\n`);
53
+ sections.push('TASK:');
54
+ sections.push(step.task + '\n');
55
+ if (dependencyContext && dependencyContext.trim().length > 0) {
56
+ sections.push('DEPENDENCY CONTEXT:');
57
+ sections.push(dependencyContext + '\n');
58
+ }
59
+ sections.push('BRANCH: you are already on your correct branch in a dedicated git worktree.');
60
+ sections.push('Do NOT run git checkout or switch branches.\n');
61
+ sections.push('Scope (what you ARE responsible for):');
62
+ agent.scope.forEach(item => sections.push('- ' + item));
63
+ sections.push('');
64
+ sections.push('Boundaries (what you must NOT do):');
65
+ agent.boundaries.forEach(item => sections.push('- ' + item));
66
+ sections.push('');
67
+ // Surface existing test files so agents know what they must not break.
68
+ // Full preservation rules live in .copilot-instructions.md; this is a
69
+ // per-step reminder with the actual filenames.
70
+ const baseline = context.baselineSnapshot;
71
+ if (baseline && baseline.testFiles.length > 0) {
72
+ sections.push('EXISTING TESTS (must still pass after your changes):');
73
+ for (const f of baseline.testFiles) {
74
+ sections.push('- ' + f);
75
+ }
76
+ sections.push('Do NOT modify, delete, or rewrite any test files. Only edit source code.');
77
+ sections.push('Test files are verified by an external harness and your edits will cause patch conflicts.');
78
+ sections.push('');
79
+ }
80
+ sections.push('Done when:');
81
+ agent.done_definition.forEach(item => sections.push('- ' + item));
82
+ sections.push('');
83
+ if (agent.refusal_rules.length > 0) {
84
+ sections.push('Refusal rules (stop and explain instead of proceeding):');
85
+ agent.refusal_rules.forEach(rule => sections.push('- ' + rule));
86
+ sections.push('');
87
+ }
88
+ // Hard rules, quality standards, git/comment conventions live in
89
+ // .copilot-instructions.md (written once per run). Not duplicated here
90
+ // to keep prompt tokens low and avoid double-loading by Claude Code.
91
+ sections.push('Expected outputs:');
92
+ step.expectedOutputs.forEach(output => sections.push('- ' + output));
93
+ sections.push('');
94
+ // Discover the target project's real test gate (pnpm/npm/yarn test) so the
95
+ // agent runs the full script rather than picking a subset like
96
+ // `npx vitest --run`, which masks lint/type-check failures.
97
+ const testDiscovery = (0, test_command_discovery_1.discoverTestCommand)(context.targetProjectRoot || process.cwd());
98
+ if (testDiscovery.warning) {
99
+ // Diagnostic only: the fallback to `npm test` is correct in 99% of repos
100
+ // and not actionable for the user. Surfaces on stderr with --verbose.
101
+ logger.debug(`test command discovery: ${testDiscovery.warning}`);
102
+ }
103
+ sections.push((0, test_command_discovery_1.renderVerifyCommandSection)(testDiscovery));
104
+ return sections.join('\n');
105
+ }
106
+ /**
107
+ * Write `.copilot-instructions.md` into the working directory so every
108
+ * worktree / agent inherits shared rules via a single committed file.
109
+ */
110
+ function writeSharedInstructions(workingDir, baseline, tierInjection) {
111
+ const instructionsPath = path.join(workingDir, '.copilot-instructions.md');
112
+ // Only write if the file does not already exist (avoid overwriting user content)
113
+ if (fs.existsSync(instructionsPath))
114
+ return;
115
+ const contentParts = [
116
+ '# Swarm Agent Instructions',
117
+ '',
118
+ '## Parallel Execution Context',
119
+ 'You are running inside a git worktree on a dedicated branch. Do NOT run `git checkout`.',
120
+ 'Make incremental commits with natural, varied messages.',
121
+ '',
122
+ '## Hard Rules',
123
+ '- Do not invent features, flags, APIs, or tool behavior not in your task.',
124
+ '- Do not claim tests passed unless you ran them and can show the output.',
125
+ '- Do not say "done" unless all required artifacts exist and you verified them.',
126
+ '- If uncertain about anything, say so explicitly.',
127
+ '- Never reference files, images, fonts, or assets that do not exist in the repo.',
128
+ '- Encapsulate JS: wrap in IIFE, module, or DOMContentLoaded. No bare globals in script scope.',
129
+ '',
130
+ ];
131
+ // Tier-filtered requirements replace the old flat "Quality Bar" section.
132
+ // Only requirements relevant to the detected task type get injected.
133
+ if (tierInjection && tierInjection.trim().length > 0) {
134
+ contentParts.push(tierInjection);
135
+ contentParts.push('');
136
+ }
137
+ // Pin the target project's real test gate into the shared instructions so
138
+ // every agent that inherits this file knows which single command to run
139
+ // instead of reaching for a subset runner like `npx vitest --run`.
140
+ const testDiscovery = (0, test_command_discovery_1.discoverTestCommand)(workingDir);
141
+ if (testDiscovery.warning) {
142
+ logger.debug(`test command discovery (shared instructions): ${testDiscovery.warning}`);
143
+ }
144
+ contentParts.push('## Verify Before Committing', `Before committing, run \`${testDiscovery.command}\` and verify it passes.`, 'Do not run individual test tools directly. Run the project\'s full test script.');
145
+ if (testDiscovery.hasScript && testDiscovery.rawScript) {
146
+ contentParts.push(`This project's "${testDiscovery.command}" runs: ${testDiscovery.rawScript}`, 'Running a subset (e.g. `npx vitest --run`) will miss lint and type checks and your commit may fail.');
147
+ }
148
+ else if (testDiscovery.warning) {
149
+ contentParts.push(`WARNING: ${testDiscovery.warning}`);
150
+ }
151
+ contentParts.push('');
152
+ contentParts.push('## Quality Bar', '- Code reads as human-written. No AI-typical patterns: no over-commented obvious logic, no generic variable names, no boilerplate filler.', '- Extract before repeat: if you copy the same logic more than twice, refactor into a shared util/hook/middleware.', '- Config first: do not hardcode API base URLs, timeouts, retry counts, or environment-specific values.', '- README truth: do not claim features that are not implemented. Do not add sections that do not apply (no troubleshooting tables for trivial projects).', '- Error messages must be specific and actionable with relevant values.', '', '## Code Comments', '- Add a 1-2 line purpose comment at the top of each new file.', '- Add brief inline comments for non-obvious logic (not every line).', '- Use natural, casual language. Good: "// bail early if empty". Bad: "// Check if the array has zero length".', '', '## Commit Messages', 'Use varied, natural messages like:', ' "add user authentication module"', ' "fix: handle null case in parser"', ' "update config and deps"', ' "implement todo API with tests"', '');
153
+ // Append baseline preservation rules when the repo has existing files
154
+ if (baseline && baseline.allFiles.length > 0) {
155
+ const rules = (0, baseline_scanner_1.formatPreservationRules)(baseline);
156
+ if (rules)
157
+ contentParts.push(rules);
158
+ }
159
+ const content = contentParts.join('\n');
160
+ fs.writeFileSync(instructionsPath, content, 'utf8');
161
+ // Commit so worktrees (branched from main) inherit the instructions file
162
+ try {
163
+ (0, child_process_1.execSync)('git add .copilot-instructions.md', { cwd: workingDir, stdio: 'pipe' });
164
+ (0, child_process_1.execSync)('git commit -m "add shared copilot instructions for swarm agents"', {
165
+ cwd: workingDir, stdio: 'pipe',
166
+ env: { ...process.env, GIT_TERMINAL_PROMPT: '0' },
167
+ });
168
+ }
169
+ catch {
170
+ // Commit may fail if repo has no initial commit yet; non-fatal
171
+ logger.warn('Could not commit .copilot-instructions.md (non-fatal)');
172
+ }
173
+ }
@@ -0,0 +1,196 @@
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.load_quality_gates_config = load_quality_gates_config;
37
+ const fs = __importStar(require("fs"));
38
+ const yaml = __importStar(require("js-yaml"));
39
+ const path = __importStar(require("path"));
40
+ const default_config_1 = require("./default-config");
41
+ const registry_1 = require("./registry");
42
+ const logger_1 = require("../logger");
43
+ const logger = (0, logger_1.getLogger)('quality-gates:config');
44
+ function is_object(value) {
45
+ return !!value && typeof value === 'object' && !Array.isArray(value);
46
+ }
47
+ /**
48
+ * Convert kebab-case identifiers (e.g. `duplicate-blocks`) to the camelCase
49
+ * canonical form the registry stores (`duplicateBlocks`). Identifiers that
50
+ * are already camelCase pass through unchanged. This bridges the README's
51
+ * Quality gates table (which displays kebab) and `.swarm/gates.yaml`
52
+ * (which historically required camel).
53
+ */
54
+ function kebab_to_camel(key) {
55
+ return key.replace(/-([a-z])/g, (_match, ch) => ch.toUpperCase());
56
+ }
57
+ /**
58
+ * Normalize the `gates` map so callers can write either kebab-case
59
+ * (`runtime-checks`) or camelCase (`runtimeChecks`) keys in YAML. Mutates a
60
+ * fresh copy of the parsed config; the original object is not altered.
61
+ * Throws if the same gate is configured under both spellings, since the
62
+ * intent is ambiguous.
63
+ */
64
+ function normalize_gate_keys(parsed, sourcePath) {
65
+ const gates = parsed.gates;
66
+ if (!is_object(gates))
67
+ return;
68
+ const normalized = {};
69
+ for (const rawKey of Object.keys(gates)) {
70
+ const camelKey = kebab_to_camel(rawKey);
71
+ if (Object.prototype.hasOwnProperty.call(normalized, camelKey)) {
72
+ throw new Error(`Duplicate gate config in ${sourcePath}: "${rawKey}" collides with another spelling that resolves to "${camelKey}". Use one form, not both.`);
73
+ }
74
+ normalized[camelKey] = gates[rawKey];
75
+ }
76
+ parsed.gates = normalized;
77
+ }
78
+ /**
79
+ * Validate that all gate keys in a user-provided config are recognized.
80
+ * Throws a descriptive error listing valid names when unknown keys are found.
81
+ * Runs after `normalize_gate_keys`, so the validation set is the camelCase
82
+ * registry keys.
83
+ * @param parsed - The parsed YAML object (already normalized)
84
+ * @param sourcePath - File path for error messages
85
+ */
86
+ function validate_gate_keys(parsed, sourcePath) {
87
+ const gates = parsed.gates;
88
+ if (!is_object(gates))
89
+ return;
90
+ const validKeys = new Set((0, registry_1.getRegisteredGateKeys)());
91
+ const unknownKeys = Object.keys(gates).filter(k => !validKeys.has(k));
92
+ if (unknownKeys.length > 0) {
93
+ const validList = (0, registry_1.getRegisteredGateKeys)().sort().join(', ');
94
+ throw new Error(`Unknown gate key(s) in ${sourcePath}: ${unknownKeys.join(', ')}. ` +
95
+ `Valid gate keys: ${validList}`);
96
+ }
97
+ }
98
+ function merge_config(base, override) {
99
+ const mergedGates = {
100
+ ...base.gates,
101
+ ...(override.gates || {})
102
+ };
103
+ for (const gate of (0, registry_1.getRegisteredGates)()) {
104
+ mergedGates[gate.key] = {
105
+ ...(base.gates[gate.key] || gate.defaultConfig),
106
+ ...(override.gates?.[gate.key] || {})
107
+ };
108
+ }
109
+ const merged = {
110
+ ...base,
111
+ ...override,
112
+ gates: mergedGates
113
+ };
114
+ return merged;
115
+ }
116
+ /**
117
+ * Load a YAML config file, validate it, and return the parsed object.
118
+ * Throws on YAML syntax errors with the file path in the message.
119
+ * @param filePath - Absolute path to the YAML file
120
+ * @returns Parsed config as a partial QualityGatesConfig
121
+ */
122
+ function load_yaml_config(filePath) {
123
+ const raw = fs.readFileSync(filePath, 'utf8');
124
+ let parsed;
125
+ try {
126
+ parsed = yaml.load(raw);
127
+ }
128
+ catch (err) {
129
+ const msg = err instanceof Error ? err.message : String(err);
130
+ throw new Error(`YAML syntax error in ${filePath}: ${msg}`, { cause: err });
131
+ }
132
+ if (!parsed || (is_object(parsed) && Object.keys(parsed).length === 0)) {
133
+ return {};
134
+ }
135
+ if (!is_object(parsed)) {
136
+ throw new Error(`Quality gates config must be a YAML object: ${filePath}`);
137
+ }
138
+ normalize_gate_keys(parsed, filePath);
139
+ validate_gate_keys(parsed, filePath);
140
+ return parsed;
141
+ }
142
+ /**
143
+ * Resolve quality gate configuration using a three-layer merge strategy:
144
+ * 1. Built-in defaults
145
+ * 2. Per-project `.swarm/gates.yaml` (if present in repoRoot)
146
+ * 3. Explicit path from `--quality-gates-config` flag (if provided)
147
+ *
148
+ * Unknown gate keys in user-provided configs cause a descriptive error.
149
+ * YAML syntax errors surface with the file path included.
150
+ *
151
+ * @param projectRoot - Root directory of the target repository
152
+ * @param explicitPath - Optional explicit config path from CLI flag
153
+ * @returns Fully resolved quality gates configuration
154
+ */
155
+ function load_quality_gates_config(projectRoot, explicitPath) {
156
+ const projectGateSources = (0, registry_1.loadProjectGateRegistrations)(projectRoot);
157
+ for (const source of projectGateSources) {
158
+ logger.debug(`loaded project gate registration from ${source}`);
159
+ }
160
+ // Start with a defensive copy so callers never mutate the shared default
161
+ let config = merge_config(default_config_1.DEFAULT_QUALITY_GATES_CONFIG, {});
162
+ // Layer 2: per-project .swarm/gates.yaml
163
+ const perProjectPath = path.join(projectRoot, '.swarm', 'gates.yaml');
164
+ if (fs.existsSync(perProjectPath)) {
165
+ logger.debug(`loading gate config ${perProjectPath}`);
166
+ const perProject = load_yaml_config(perProjectPath);
167
+ if (Object.keys(perProject).length > 0) {
168
+ config = merge_config(config, perProject);
169
+ }
170
+ }
171
+ // Layer 3: explicit path from --quality-gates-config flag
172
+ if (explicitPath) {
173
+ const resolved = path.isAbsolute(explicitPath) ? explicitPath : path.join(projectRoot, explicitPath);
174
+ if (!fs.existsSync(resolved)) {
175
+ throw new Error(`Quality gates config file not found: ${resolved}`);
176
+ }
177
+ logger.debug(`loading explicit gate config ${resolved}`);
178
+ const explicit = load_yaml_config(resolved);
179
+ if (Object.keys(explicit).length > 0) {
180
+ config = merge_config(config, explicit);
181
+ }
182
+ }
183
+ // Legacy fallback: config/quality-gates.yaml in the project root
184
+ // Only applies when neither .swarm/gates.yaml nor explicit path was used
185
+ if (!explicitPath && !fs.existsSync(perProjectPath)) {
186
+ const legacyPath = path.join(projectRoot, 'config', 'quality-gates.yaml');
187
+ if (fs.existsSync(legacyPath)) {
188
+ logger.debug(`loading legacy gate config ${legacyPath}`);
189
+ const legacy = load_yaml_config(legacyPath);
190
+ if (Object.keys(legacy).length > 0) {
191
+ config = merge_config(config, legacy);
192
+ }
193
+ }
194
+ }
195
+ return config;
196
+ }
@@ -0,0 +1,243 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_QUALITY_GATES_CONFIG = void 0;
4
+ exports.DEFAULT_QUALITY_GATES_CONFIG = {
5
+ enabled: true,
6
+ failOnIssues: true,
7
+ autoAddRefactorStepOnDuplicateBlocks: true,
8
+ autoAddReadmeTruthStepOnReadmeClaims: true,
9
+ autoAddScaffoldFixStepOnScaffoldDefaults: true,
10
+ autoAddConfigFixStepOnHardcodedConfig: true,
11
+ autoAddAccessibilityFixStepOnAccessibility: true,
12
+ autoAddTestCoverageStepOnTestCoverage: true,
13
+ excludeDirNames: [
14
+ '.git',
15
+ 'node_modules',
16
+ 'dist',
17
+ 'build',
18
+ 'coverage',
19
+ '.next',
20
+ '.turbo',
21
+ '.cache',
22
+ '.context',
23
+ '.locks',
24
+ 'runs',
25
+ 'plans',
26
+ 'proof',
27
+ '.quickfix',
28
+ 'fixtures',
29
+ // Python virtual environments and build artifacts
30
+ 'venv',
31
+ '.venv',
32
+ 'env',
33
+ '.env',
34
+ '__pycache__',
35
+ '.tox',
36
+ '.mypy_cache',
37
+ '.pytest_cache',
38
+ '.ruff_cache',
39
+ '.eggs',
40
+ 'site-packages',
41
+ // Ruby / Rust / Go / Java build dirs
42
+ 'vendor',
43
+ 'target',
44
+ '.gradle',
45
+ // IDE and OS artifacts
46
+ '.idea',
47
+ '.vscode',
48
+ '.vs'
49
+ ],
50
+ maxFileSizeBytes: 512 * 1024,
51
+ gates: {
52
+ scaffoldDefaults: {
53
+ enabled: true,
54
+ bannedTitleRegexes: [
55
+ 'Vite \\+ React',
56
+ 'React App',
57
+ 'Next\\.js App',
58
+ 'SvelteKit'
59
+ ],
60
+ bannedReadmeRegexes: [
61
+ 'This project was bootstrapped with',
62
+ 'Vite\\s+\\+\\s+React',
63
+ 'Create React App'
64
+ ],
65
+ bannedFiles: [
66
+ 'public/vite.svg',
67
+ 'src/logo.svg',
68
+ 'public/favicon.ico'
69
+ ]
70
+ },
71
+ duplicateBlocks: {
72
+ enabled: true,
73
+ minLines: 12,
74
+ maxOccurrences: 2,
75
+ maxFindings: 20,
76
+ excludeFileRegexes: [
77
+ '^package-lock\\.json$',
78
+ '^pnpm-lock\\.yaml$',
79
+ '^yarn\\.lock$',
80
+ '^test-output\\.txt$',
81
+ '^dist/',
82
+ '^build/',
83
+ '^coverage/',
84
+ '^runs/',
85
+ '^plans/',
86
+ '^proof/',
87
+ '^venv/',
88
+ '^\\.venv/',
89
+ '^env/',
90
+ '^vendor/',
91
+ '^target/',
92
+ '^__pycache__/'
93
+ ]
94
+ },
95
+ hardcodedConfig: {
96
+ enabled: true,
97
+ bannedLiteralRegexes: [
98
+ 'https?://localhost(?::\\d+)?',
99
+ 'https?://127\\.0\\.0\\.1(?::\\d+)?',
100
+ 'RETRY_COUNT\\s*=\\s*[0-9]+'
101
+ ],
102
+ allowIfFileContainsRegexes: [
103
+ 'process\\.env\\.',
104
+ 'import\\.meta\\.env',
105
+ 'window\\.__CONFIG__'
106
+ ],
107
+ excludeFileRegexes: [
108
+ '^\.github/',
109
+ '\\.(md|mdx)$',
110
+ '^dist/',
111
+ '^build/',
112
+ '^coverage/',
113
+ '^runs/',
114
+ '^plans/',
115
+ '^proof/',
116
+ '^venv/',
117
+ '^\\.venv/',
118
+ '^env/',
119
+ '^vendor/',
120
+ '^target/',
121
+ '^__pycache__/',
122
+ '^benchmarks/',
123
+ '^test/fixtures/',
124
+ '(^|/)tests?/',
125
+ '(^|/)__tests__/',
126
+ '\\.(test|spec)\\.(ts|js|tsx|jsx|mjs)$',
127
+ '(^|/)test_[^/]*\\.py$',
128
+ '^(integration|e2e)[.-]',
129
+ '^test\\.(js|ts|mjs)$',
130
+ '^tests\\.(js|ts|mjs)$',
131
+ '^(deploy|scripts)/',
132
+ '\\.(sh|bash|bat|cmd|ps1)$',
133
+ 'vite\\.config\\.(js|ts|mjs|mts)$',
134
+ 'webpack\\.config\\.(js|ts)$',
135
+ '\\.env(\\.example|\\.local)?$',
136
+ '^docker-compose[^/]*\\.ya?ml$',
137
+ '(^|/)Dockerfile',
138
+ '\\.dockerfile$'
139
+ ]
140
+ },
141
+ readmeClaims: {
142
+ enabled: true,
143
+ claimRules: [
144
+ {
145
+ id: 'claim-retry',
146
+ readmeRegex: '\\b(retry|retries|backoff|exponential backoff)\\b',
147
+ requiredEvidence: [
148
+ { fileRegex: 'src/.+', contentRegex: '\\bretry\\b', note: 'expected a retry helper or implementation' }
149
+ ]
150
+ },
151
+ {
152
+ id: 'claim-optimistic-ui',
153
+ readmeRegex: '\\boptimistic\\b',
154
+ requiredEvidence: [
155
+ { fileRegex: 'src/.+', contentRegex: '\\boptimistic\\b', note: 'expected optimistic UI implementation' }
156
+ ]
157
+ },
158
+ {
159
+ id: 'claim-correlation-id',
160
+ readmeRegex: '\\bcorrelation id\\b|\\bx-correlation-id\\b',
161
+ requiredEvidence: [
162
+ { fileRegex: 'src/.+', contentRegex: 'x-correlation-id|correlationId', note: 'expected correlation ID support in code' }
163
+ ]
164
+ },
165
+ {
166
+ id: 'claim-request-logging',
167
+ readmeRegex: '\\brequest logging\\b|\\blog requests\\b',
168
+ requiredEvidence: [
169
+ { fileRegex: 'src/.+', contentRegex: 'req\\.method|morgan', note: 'expected request logging middleware' }
170
+ ]
171
+ }
172
+ ]
173
+ },
174
+ testIsolation: {
175
+ enabled: true,
176
+ mutableStoreRegexes: [
177
+ '^\\s*let\\s+\\w+\\s*=\\s*\\[\\s*\\]\\s*;\\s*$',
178
+ '^\\s*let\\s+\\w+\\s*=\\s*\\{\\s*\\}\\s*;\\s*$'
179
+ ],
180
+ allowIfFileContainsRegexes: [
181
+ 'resetForTests',
182
+ '__reset',
183
+ 'clearAll',
184
+ 'beforeEach\\('
185
+ ],
186
+ testFileRegexes: [
187
+ '^(test|tests)/.+\\.test\\.(ts|js|tsx|jsx)$',
188
+ '^__tests__/.+\\.(ts|js|tsx|jsx)$',
189
+ '\\.test\\.(ts|js|tsx|jsx)$',
190
+ '\\.spec\\.(ts|js|tsx|jsx)$'
191
+ ],
192
+ allowIfAnyTestContainsRegexes: [
193
+ 'beforeEach\\(',
194
+ 'afterEach\\('
195
+ ]
196
+ },
197
+ runtimeChecks: {
198
+ enabled: true,
199
+ retries: 1,
200
+ runTests: true,
201
+ runLint: true,
202
+ runAudit: true,
203
+ timeoutMs: 120000
204
+ },
205
+ accessibility: {
206
+ enabled: true,
207
+ requireSkipLink: true,
208
+ requireHeadingHierarchy: true,
209
+ requireAriaLabels: true,
210
+ requireFocusStyles: true,
211
+ requireReducedMotion: true,
212
+ requireNoPhantomAssets: true,
213
+ requireMetaTags: true,
214
+ requireResponsiveCSS: true,
215
+ requireColorScheme: true,
216
+ requireSemanticHTML: true,
217
+ requireImgAlt: true
218
+ },
219
+ testCoverage: {
220
+ enabled: true,
221
+ entryPointPatterns: [],
222
+ minTestAssertions: 1,
223
+ requireComponentTests: true
224
+ },
225
+ testFileProtection: {
226
+ enabled: true,
227
+ testFileGlobs: [
228
+ 'tests/**',
229
+ 'test/**',
230
+ '**/__tests__/**',
231
+ '**/*.test.ts',
232
+ '**/*.test.tsx',
233
+ '**/*.test.js',
234
+ '**/*.test.jsx',
235
+ '**/*.spec.ts',
236
+ '**/*.spec.js',
237
+ '**/test_*.py',
238
+ '**/*_test.py'
239
+ ],
240
+ maxFindings: 25
241
+ }
242
+ }
243
+ };