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.
- package/LICENSE +15 -0
- package/README.md +199 -0
- package/agents/reviewer.agent.md +50 -0
- package/agents/worker.agent.md +41 -0
- package/config/default-agents.yaml +56 -0
- package/config/pm-agent.yaml +36 -0
- package/config/quality-gates.yaml +88 -0
- package/config/repair-agent.yaml +34 -0
- package/config/semgrep-rules/complexity-mismatch.yaml +6 -0
- package/config/semgrep-rules/exception-swallowing.yaml +13 -0
- package/config/semgrep-rules/hardcoded-answer.yaml +6 -0
- package/config/semgrep-rules/mock-mutation.yaml +6 -0
- package/config/semgrep-rules/test-modification.yaml +6 -0
- package/config/user-agents.yaml +23 -0
- package/dist/src/adapters/adapter-factory.js +29 -0
- package/dist/src/adapters/agent-adapter.js +32 -0
- package/dist/src/adapters/claude-code-adapter.js +126 -0
- package/dist/src/adapters/claude-code-teams.js +119 -0
- package/dist/src/adapters/codex-adapter.js +118 -0
- package/dist/src/adapters/copilot-adapter.js +224 -0
- package/dist/src/adapters/fatal-error-classifier.js +88 -0
- package/dist/src/adapters/index.js +44 -0
- package/dist/src/adapters/persistent-session.js +204 -0
- package/dist/src/adapters/process-supervisor.js +199 -0
- package/dist/src/agents-exporter.js +398 -0
- package/dist/src/analytics-log.js +161 -0
- package/dist/src/baseline-scanner.js +65 -0
- package/dist/src/bootstrap-evidence.js +88 -0
- package/dist/src/bootstrap-orchestrator.js +205 -0
- package/dist/src/bootstrap-types.js +5 -0
- package/dist/src/branch-merger.js +427 -0
- package/dist/src/cli/attest-handlers.js +44 -0
- package/dist/src/cli/cost-prompt.js +51 -0
- package/dist/src/cli/demo-handlers.js +274 -0
- package/dist/src/cli/flags.js +152 -0
- package/dist/src/cli/index.js +57 -0
- package/dist/src/cli/live-status.js +186 -0
- package/dist/src/cli/misc-handlers.js +242 -0
- package/dist/src/cli/plan-handlers.js +350 -0
- package/dist/src/cli/share-handlers.js +163 -0
- package/dist/src/cli/status-handlers.js +457 -0
- package/dist/src/cli/swarm-handlers.js +638 -0
- package/dist/src/cli/usage.js +72 -0
- package/dist/src/cli.js +217 -0
- package/dist/src/commit-pattern-detector.js +143 -0
- package/dist/src/commit-quality-analyzer.js +38 -0
- package/dist/src/config-loader.js +339 -0
- package/dist/src/context-broker.js +358 -0
- package/dist/src/copilot-cli-wrapper.js +258 -0
- package/dist/src/cost-estimator.js +241 -0
- package/dist/src/defaults.js +18 -0
- package/dist/src/demo-mode.js +180 -0
- package/dist/src/deployment-handler.js +78 -0
- package/dist/src/deployment-manager.js +228 -0
- package/dist/src/execution-queue.js +196 -0
- package/dist/src/external-tool-manager.js +184 -0
- package/dist/src/gate-prompt-builder.js +117 -0
- package/dist/src/gate-remediation.js +173 -0
- package/dist/src/github-issues-ingester.js +152 -0
- package/dist/src/github-mcp-integrator.js +164 -0
- package/dist/src/hook-generator.js +229 -0
- package/dist/src/index.js +48 -0
- package/dist/src/knowledge-base.js +300 -0
- package/dist/src/logger.js +111 -0
- package/dist/src/meta-analyzer.js +208 -0
- package/dist/src/metrics-collector.js +209 -0
- package/dist/src/metrics-types.js +5 -0
- package/dist/src/multi-repo-coordinator.js +179 -0
- package/dist/src/orchestrator/async-meta-analysis.js +84 -0
- package/dist/src/orchestrator/fatal-run-error.js +43 -0
- package/dist/src/orchestrator/final-gates-remediation.js +258 -0
- package/dist/src/orchestrator/git-state-utils.js +119 -0
- package/dist/src/orchestrator/pause-controller.js +55 -0
- package/dist/src/orchestrator/replan-runner.js +258 -0
- package/dist/src/orchestrator/step-executor.js +391 -0
- package/dist/src/orchestrator/wave-scheduler-loop.js +204 -0
- package/dist/src/owasp-mapper.js +153 -0
- package/dist/src/owasp-report-renderer.js +39 -0
- package/dist/src/plan-files.js +142 -0
- package/dist/src/plan-generator.js +1207 -0
- package/dist/src/pm-agent.js +296 -0
- package/dist/src/post-run-reporter.js +203 -0
- package/dist/src/pr-automation.js +103 -0
- package/dist/src/pr-manager.js +318 -0
- package/dist/src/presenter/index.js +209 -0
- package/dist/src/prompt-builder.js +173 -0
- package/dist/src/quality-gates/config-loader.js +196 -0
- package/dist/src/quality-gates/default-config.js +243 -0
- package/dist/src/quality-gates/file-utils.js +152 -0
- package/dist/src/quality-gates/gate-runner.js +171 -0
- package/dist/src/quality-gates/gates/accessibility.js +355 -0
- package/dist/src/quality-gates/gates/duplicate-blocks.js +144 -0
- package/dist/src/quality-gates/gates/hardcoded-config.js +52 -0
- package/dist/src/quality-gates/gates/readme-claims.js +62 -0
- package/dist/src/quality-gates/gates/runtime-checks.js +265 -0
- package/dist/src/quality-gates/gates/scaffold-defaults.js +108 -0
- package/dist/src/quality-gates/gates/test-coverage.js +240 -0
- package/dist/src/quality-gates/gates/test-file-protection.js +85 -0
- package/dist/src/quality-gates/gates/test-isolation.js +74 -0
- package/dist/src/quality-gates/index.js +17 -0
- package/dist/src/quality-gates/registry.js +205 -0
- package/dist/src/quality-gates/types.js +2 -0
- package/dist/src/quick-fix-mode.js +302 -0
- package/dist/src/recipe-loader.js +122 -0
- package/dist/src/repair-agent.js +396 -0
- package/dist/src/repo-analyzer.js +342 -0
- package/dist/src/report-generator.js +146 -0
- package/dist/src/report-renderer.js +90 -0
- package/dist/src/requirement-filter.js +52 -0
- package/dist/src/sarif-formatter.js +140 -0
- package/dist/src/scheduling/dependency-analyzer.js +178 -0
- package/dist/src/scheduling/work-stealing-queue.js +123 -0
- package/dist/src/secret-redactor.js +90 -0
- package/dist/src/session-executor.js +587 -0
- package/dist/src/session-manager.js +221 -0
- package/dist/src/share/transcript-verification.js +128 -0
- package/dist/src/share-parser.js +477 -0
- package/dist/src/spinner.js +146 -0
- package/dist/src/step-runner.js +365 -0
- package/dist/src/swarm-orchestrator.js +686 -0
- package/dist/src/task-classifier.js +134 -0
- package/dist/src/test-command-discovery.js +123 -0
- package/dist/src/text-similarity.js +24 -0
- package/dist/src/tier-maps.js +158 -0
- package/dist/src/types.js +5 -0
- package/dist/src/url-shortener.js +135 -0
- package/dist/src/verification/attestation.js +166 -0
- package/dist/src/verification/cheat-detector.js +240 -0
- package/dist/src/verification/command-runner.js +70 -0
- package/dist/src/verification/composite-score.js +157 -0
- package/dist/src/verification/cosign-attestation.js +137 -0
- package/dist/src/verification/diff-analysis.js +89 -0
- package/dist/src/verification/differential-gate.js +173 -0
- package/dist/src/verification/index.js +41 -0
- package/dist/src/verification/mutation-gate.js +242 -0
- package/dist/src/verification/property-gate.js +201 -0
- package/dist/src/verification/test-synthesizer.js +243 -0
- package/dist/src/verifier/outcome-checks.js +396 -0
- package/dist/src/verifier/transcript-checks.js +207 -0
- package/dist/src/verifier/verification-reporters.js +148 -0
- package/dist/src/verifier-engine.js +491 -0
- package/dist/src/wave-resizer.js +131 -0
- package/dist/src/wave-scheduler.js +44 -0
- package/dist/src/worktree-manager.js +368 -0
- package/dist/src/worktree-reserved-paths.js +97 -0
- package/package.json +80 -0
- package/plugin/agents/.gitkeep +0 -0
- package/plugin/agents/backend_master.agent.md +72 -0
- package/plugin/agents/devops_pro.agent.md +45 -0
- package/plugin/agents/frontend_expert.agent.md +59 -0
- package/plugin/agents/integrator_finalizer.agent.md +44 -0
- package/plugin/agents/meta_reviewer.agent.md +44 -0
- package/plugin/agents/security_auditor.agent.md +43 -0
- package/plugin/agents/tester_elite.agent.md +43 -0
- package/plugin/hooks/hooks.json +19 -0
- package/plugin/hooks/post-tool-use.json +9 -0
- package/plugin/hooks/pre-tool-use.json +9 -0
- package/plugin/plugin.json +47 -0
- package/plugin/skills/swarm-gates/SKILL.md +53 -0
- package/plugin/skills/swarm-orchestrate/SKILL.md +34 -0
- package/plugin/skills/swarm-verify/SKILL.md +44 -0
- package/templates/README.md +35 -0
- package/templates/cli-tool.json +31 -0
- package/templates/fullstack.json +52 -0
- package/templates/library.json +38 -0
- package/templates/react-app.json +45 -0
- package/templates/recipes/add-api-docs.json +35 -0
- package/templates/recipes/add-auth.json +47 -0
- package/templates/recipes/add-ci.json +39 -0
- package/templates/recipes/add-tests.json +39 -0
- package/templates/recipes/migrate-to-ts.json +42 -0
- package/templates/recipes/refactor-modularize.json +44 -0
- package/templates/recipes/security-audit.json +35 -0
- 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
|
+
};
|