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,140 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatSarif = formatSarif;
|
|
4
|
+
const SARIF_SCHEMA = 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/main/sarif-2.1/schema/sarif-schema-2.1.0.json';
|
|
5
|
+
const SARIF_VERSION = '2.1.0';
|
|
6
|
+
const TOOL_NAME = 'swarm-orchestrator';
|
|
7
|
+
/** Maps gate IDs to their SARIF rule descriptions. */
|
|
8
|
+
const RULE_DESCRIPTIONS = {
|
|
9
|
+
'scaffold-defaults': 'Generated code contains TODO comments, placeholder text, or default scaffold values',
|
|
10
|
+
'duplicate-blocks': 'Code block duplicated beyond configured threshold',
|
|
11
|
+
'hardcoded-config': 'Configuration value hardcoded instead of externalized',
|
|
12
|
+
'readme-claims': 'README claim does not match implemented functionality',
|
|
13
|
+
'test-isolation': 'Test depends on shared mutable state',
|
|
14
|
+
'test-coverage': 'Test coverage below configured threshold',
|
|
15
|
+
'accessibility': 'Missing accessibility attribute or semantic element',
|
|
16
|
+
'runtime-checks': 'Code fails build or test execution',
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Convert a GateStatus to the corresponding SARIF level.
|
|
20
|
+
* Gates that pass produce no results; only fail and skip are mapped.
|
|
21
|
+
* @param status - The gate status from the quality gates pipeline
|
|
22
|
+
* @returns The SARIF severity level
|
|
23
|
+
*/
|
|
24
|
+
function statusToLevel(status) {
|
|
25
|
+
return status === 'fail' ? 'error' : 'note';
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Normalize a file path to a forward-slash relative URI with no leading "./".
|
|
29
|
+
* @param filePath - Absolute or relative file path from a gate issue
|
|
30
|
+
* @returns SARIF-compliant relative URI
|
|
31
|
+
*/
|
|
32
|
+
function normalizeUri(filePath) {
|
|
33
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
34
|
+
return normalized.replace(/^\.\//, '');
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Build a SARIF physical location from a gate issue.
|
|
38
|
+
* Populates region.startLine when the issue references a specific line.
|
|
39
|
+
* @param issue - The gate issue with optional file and line info
|
|
40
|
+
* @returns Physical location for the SARIF result
|
|
41
|
+
*/
|
|
42
|
+
function buildLocation(issue) {
|
|
43
|
+
const uri = issue.filePath ? normalizeUri(issue.filePath) : '.';
|
|
44
|
+
const location = {
|
|
45
|
+
artifactLocation: { uri },
|
|
46
|
+
};
|
|
47
|
+
if (issue.line && issue.line > 0) {
|
|
48
|
+
location.region = { startLine: issue.line };
|
|
49
|
+
}
|
|
50
|
+
return location;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Build SARIF rules array from gate results.
|
|
54
|
+
* One rule per gate that produced results (fail or skip with issues).
|
|
55
|
+
* @param gateResults - Array of gate results from the pipeline
|
|
56
|
+
* @returns Deduplicated SARIF rules
|
|
57
|
+
*/
|
|
58
|
+
function buildRules(gateResults) {
|
|
59
|
+
const seen = new Set();
|
|
60
|
+
const rules = [];
|
|
61
|
+
for (const gate of gateResults) {
|
|
62
|
+
const ruleId = `swarm/${gate.id}`;
|
|
63
|
+
if (seen.has(ruleId))
|
|
64
|
+
continue;
|
|
65
|
+
seen.add(ruleId);
|
|
66
|
+
rules.push({
|
|
67
|
+
id: ruleId,
|
|
68
|
+
shortDescription: {
|
|
69
|
+
text: RULE_DESCRIPTIONS[gate.id] || gate.title,
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return rules;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Convert gate results into SARIF result entries.
|
|
77
|
+
* Only gates with status 'fail' produce error-level results.
|
|
78
|
+
* Gates with status 'skip' produce note-level entries if they have issues.
|
|
79
|
+
* Gates with status 'pass' are omitted entirely.
|
|
80
|
+
* @param gateResults - Array of gate results from the pipeline
|
|
81
|
+
* @returns SARIF result entries
|
|
82
|
+
*/
|
|
83
|
+
function buildResults(gateResults) {
|
|
84
|
+
const results = [];
|
|
85
|
+
for (const gate of gateResults) {
|
|
86
|
+
if (gate.status === 'pass')
|
|
87
|
+
continue;
|
|
88
|
+
if (gate.status === 'skip' && gate.issues.length === 0)
|
|
89
|
+
continue;
|
|
90
|
+
const ruleId = `swarm/${gate.id}`;
|
|
91
|
+
const level = statusToLevel(gate.status);
|
|
92
|
+
if (gate.issues.length === 0) {
|
|
93
|
+
// Gate failed/skipped but has no specific issues; emit a single project-level entry
|
|
94
|
+
results.push({
|
|
95
|
+
ruleId,
|
|
96
|
+
message: { text: gate.title },
|
|
97
|
+
level,
|
|
98
|
+
locations: [{ physicalLocation: { artifactLocation: { uri: '.' } } }],
|
|
99
|
+
});
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
for (const issue of gate.issues) {
|
|
103
|
+
results.push({
|
|
104
|
+
ruleId,
|
|
105
|
+
message: { text: issue.message },
|
|
106
|
+
level,
|
|
107
|
+
locations: [{ physicalLocation: buildLocation(issue) }],
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return results;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Format quality gate results as a SARIF 2.1.0 JSON log.
|
|
115
|
+
* Produces output compatible with GitHub code scanning upload.
|
|
116
|
+
* @param runResult - The complete gate run result from the pipeline
|
|
117
|
+
* @param toolVersion - The swarm-orchestrator version from package.json
|
|
118
|
+
* @returns SARIF JSON string
|
|
119
|
+
*/
|
|
120
|
+
function formatSarif(runResult, toolVersion) {
|
|
121
|
+
const allGates = runResult.results;
|
|
122
|
+
const reportableGates = allGates.filter(g => g.status === 'fail' || (g.status === 'skip' && g.issues.length > 0));
|
|
123
|
+
const sarifLog = {
|
|
124
|
+
$schema: SARIF_SCHEMA,
|
|
125
|
+
version: SARIF_VERSION,
|
|
126
|
+
runs: [
|
|
127
|
+
{
|
|
128
|
+
tool: {
|
|
129
|
+
driver: {
|
|
130
|
+
name: TOOL_NAME,
|
|
131
|
+
version: toolVersion,
|
|
132
|
+
rules: buildRules(reportableGates),
|
|
133
|
+
},
|
|
134
|
+
},
|
|
135
|
+
results: buildResults(allGates),
|
|
136
|
+
},
|
|
137
|
+
],
|
|
138
|
+
};
|
|
139
|
+
return JSON.stringify(sarifLog, null, 2);
|
|
140
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
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.analyzeStepTouchpoints = analyzeStepTouchpoints;
|
|
37
|
+
exports.analyzePlanDependencies = analyzePlanDependencies;
|
|
38
|
+
exports.canRunTogether = canRunTogether;
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const FILE_REF_RE = /\b(?:[\w.-]+\/)+(?:[\w.-]+|\*)\.(?:[cm]?[jt]sx?|py|java|go|rs|json|ya?ml|toml|md|css|html|sh|sql|dockerignore)\b/g;
|
|
41
|
+
const DIRECTORY_REF_RE = /\b(?:src|test|tests|lib|app|server|client|config|scripts|docs|benchmarks|agents|\.github|\.swarm)\/(?:[\w./-]+\/?)?/g;
|
|
42
|
+
const BARE_FILE_RE = /\b(?:Dockerfile|Makefile|package\.json|package-lock\.json|pnpm-lock\.yaml|yarn\.lock|tsconfig\.json|README\.md|\.env(?:\.\w+)?|\.dockerignore)\b/g;
|
|
43
|
+
/**
|
|
44
|
+
* Conservatively infer file and state touchpoints from task text.
|
|
45
|
+
*
|
|
46
|
+
* Empty touchpoints make a step conservative, which means it conflicts with
|
|
47
|
+
* other ready steps unless an explicit path is present in both tasks.
|
|
48
|
+
*/
|
|
49
|
+
function analyzeStepTouchpoints(step) {
|
|
50
|
+
const text = [step.task, ...step.expectedOutputs].join('\n');
|
|
51
|
+
const touchpoints = new Set();
|
|
52
|
+
collectMatches(text, FILE_REF_RE, touchpoints);
|
|
53
|
+
collectMatches(text, DIRECTORY_REF_RE, touchpoints);
|
|
54
|
+
collectMatches(text, BARE_FILE_RE, touchpoints);
|
|
55
|
+
const sharedState = new Set();
|
|
56
|
+
if (/\b(database|db|migration|schema|sqlite|postgres|mysql|redis)\b/i.test(text)) {
|
|
57
|
+
sharedState.add('state:database');
|
|
58
|
+
}
|
|
59
|
+
if (/\b(auth|session|cookie|token|login|logout)\b/i.test(text)) {
|
|
60
|
+
sharedState.add('state:auth');
|
|
61
|
+
}
|
|
62
|
+
if (/\b(env|environment|secret|config var|configuration)\b/i.test(text)) {
|
|
63
|
+
sharedState.add('state:environment');
|
|
64
|
+
}
|
|
65
|
+
if (/\b(test setup|test fixture|mock|seed data|global state)\b/i.test(text)) {
|
|
66
|
+
sharedState.add('state:test-fixtures');
|
|
67
|
+
}
|
|
68
|
+
const normalized = Array.from(touchpoints)
|
|
69
|
+
.map(normalizeTouchpoint)
|
|
70
|
+
.filter(Boolean)
|
|
71
|
+
.sort();
|
|
72
|
+
return {
|
|
73
|
+
stepNumber: step.stepNumber,
|
|
74
|
+
touchpoints: unique(normalized),
|
|
75
|
+
sharedState: Array.from(sharedState).sort(),
|
|
76
|
+
conservative: normalized.length === 0,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Build a conflict graph for all plan steps.
|
|
81
|
+
*
|
|
82
|
+
* Steps conflict when touchpoints overlap, shared state overlaps, or either
|
|
83
|
+
* step lacks enough static evidence to prove independence.
|
|
84
|
+
*/
|
|
85
|
+
function analyzePlanDependencies(plan) {
|
|
86
|
+
const steps = new Map();
|
|
87
|
+
const conflicts = new Map();
|
|
88
|
+
for (const step of plan.steps) {
|
|
89
|
+
const analysis = analyzeStepTouchpoints(step);
|
|
90
|
+
steps.set(step.stepNumber, analysis);
|
|
91
|
+
conflicts.set(step.stepNumber, new Set());
|
|
92
|
+
}
|
|
93
|
+
for (let i = 0; i < plan.steps.length; i += 1) {
|
|
94
|
+
for (let j = i + 1; j < plan.steps.length; j += 1) {
|
|
95
|
+
const a = steps.get(plan.steps[i].stepNumber);
|
|
96
|
+
const b = steps.get(plan.steps[j].stepNumber);
|
|
97
|
+
if (stepsConflict(a, b)) {
|
|
98
|
+
conflicts.get(a.stepNumber).add(b.stepNumber);
|
|
99
|
+
conflicts.get(b.stepNumber).add(a.stepNumber);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return {
|
|
104
|
+
steps,
|
|
105
|
+
conflicts,
|
|
106
|
+
parallelizable: hasIndependentPair(plan.steps, conflicts),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
function canRunTogether(analysis, stepA, stepB) {
|
|
110
|
+
return !(analysis.conflicts.get(stepA)?.has(stepB));
|
|
111
|
+
}
|
|
112
|
+
function collectMatches(text, regex, out) {
|
|
113
|
+
for (const match of text.matchAll(regex)) {
|
|
114
|
+
if (isExcludedReference(text, match.index ?? 0))
|
|
115
|
+
continue;
|
|
116
|
+
if (match[0])
|
|
117
|
+
out.add(match[0]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function isExcludedReference(text, index) {
|
|
121
|
+
const before = text.slice(Math.max(0, index - 80), index).toLowerCase();
|
|
122
|
+
const lastBoundary = Math.max(before.lastIndexOf('\n'), before.lastIndexOf(';'));
|
|
123
|
+
const phrase = before.slice(lastBoundary + 1);
|
|
124
|
+
return /\b(excluding|exclude|excluded|ignore|ignoring|omit|omitting|skip|skipping)\b/.test(phrase);
|
|
125
|
+
}
|
|
126
|
+
function normalizeTouchpoint(value) {
|
|
127
|
+
const stripped = value.replace(/^[`'"]|[`'",.;:)]+$/g, '');
|
|
128
|
+
const normalized = stripped.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
129
|
+
if (!normalized)
|
|
130
|
+
return '';
|
|
131
|
+
if (normalized.endsWith('/'))
|
|
132
|
+
return normalized;
|
|
133
|
+
if (!path.extname(normalized) && normalized.includes('/'))
|
|
134
|
+
return `${normalized}/`;
|
|
135
|
+
return normalized;
|
|
136
|
+
}
|
|
137
|
+
function stepsConflict(a, b) {
|
|
138
|
+
if (a.conservative || b.conservative)
|
|
139
|
+
return true;
|
|
140
|
+
if (a.sharedState.some(state => b.sharedState.includes(state)))
|
|
141
|
+
return true;
|
|
142
|
+
for (const left of a.touchpoints) {
|
|
143
|
+
for (const right of b.touchpoints) {
|
|
144
|
+
if (touchpointsOverlap(left, right))
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
function touchpointsOverlap(a, b) {
|
|
151
|
+
if (a === b)
|
|
152
|
+
return true;
|
|
153
|
+
const aIsDir = a.endsWith('/');
|
|
154
|
+
const bIsDir = b.endsWith('/');
|
|
155
|
+
if (aIsDir && bIsDir)
|
|
156
|
+
return a.startsWith(b) || b.startsWith(a);
|
|
157
|
+
if (aIsDir)
|
|
158
|
+
return b.startsWith(a);
|
|
159
|
+
if (bIsDir)
|
|
160
|
+
return a.startsWith(b);
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
function hasIndependentPair(steps, conflicts) {
|
|
164
|
+
for (let i = 0; i < steps.length; i += 1) {
|
|
165
|
+
for (let j = i + 1; j < steps.length; j += 1) {
|
|
166
|
+
const a = steps[i];
|
|
167
|
+
const b = steps[j];
|
|
168
|
+
if (!a.dependencies.includes(b.stepNumber) && !b.dependencies.includes(a.stepNumber)) {
|
|
169
|
+
if (!(conflicts.get(a.stepNumber)?.has(b.stepNumber)))
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
function unique(values) {
|
|
177
|
+
return Array.from(new Set(values));
|
|
178
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WorkStealingQueue = void 0;
|
|
4
|
+
const dependency_analyzer_1 = require("./dependency-analyzer");
|
|
5
|
+
/**
|
|
6
|
+
* Dependency-aware queue for orchestrator steps.
|
|
7
|
+
*
|
|
8
|
+
* It is deliberately conservative: the static analyzer must prove that two
|
|
9
|
+
* ready steps do not conflict before they can be dispatched together.
|
|
10
|
+
*/
|
|
11
|
+
class WorkStealingQueue {
|
|
12
|
+
plan;
|
|
13
|
+
analysis;
|
|
14
|
+
pending = new Set();
|
|
15
|
+
running = new Set();
|
|
16
|
+
completed = new Set();
|
|
17
|
+
failed = new Set();
|
|
18
|
+
maxWorkers;
|
|
19
|
+
constructor(plan, options = {}) {
|
|
20
|
+
this.plan = plan;
|
|
21
|
+
this.analysis = (0, dependency_analyzer_1.analyzePlanDependencies)(plan);
|
|
22
|
+
this.maxWorkers = Math.max(1, options.maxWorkers ?? 1);
|
|
23
|
+
for (const step of plan.steps)
|
|
24
|
+
this.pending.add(step.stepNumber);
|
|
25
|
+
}
|
|
26
|
+
syncPlan(plan) {
|
|
27
|
+
this.plan = plan;
|
|
28
|
+
this.analysis = (0, dependency_analyzer_1.analyzePlanDependencies)(plan);
|
|
29
|
+
const activeNumbers = new Set(plan.steps.map(step => step.stepNumber));
|
|
30
|
+
for (const step of plan.steps) {
|
|
31
|
+
if (!this.completed.has(step.stepNumber) && !this.failed.has(step.stepNumber) && !this.running.has(step.stepNumber)) {
|
|
32
|
+
this.pending.add(step.stepNumber);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
for (const stepNumber of Array.from(this.pending)) {
|
|
36
|
+
if (!activeNumbers.has(stepNumber))
|
|
37
|
+
this.pending.delete(stepNumber);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
nextDispatches() {
|
|
41
|
+
const capacity = this.maxWorkers - this.running.size;
|
|
42
|
+
if (capacity <= 0)
|
|
43
|
+
return [];
|
|
44
|
+
const ready = this.readySteps();
|
|
45
|
+
const selected = [];
|
|
46
|
+
for (const stepNumber of ready) {
|
|
47
|
+
if (selected.length >= capacity)
|
|
48
|
+
break;
|
|
49
|
+
if (!this.canRunWithCurrentBatch(stepNumber, selected))
|
|
50
|
+
continue;
|
|
51
|
+
selected.push(stepNumber);
|
|
52
|
+
}
|
|
53
|
+
return selected;
|
|
54
|
+
}
|
|
55
|
+
markRunning(stepNumber) {
|
|
56
|
+
this.pending.delete(stepNumber);
|
|
57
|
+
this.running.add(stepNumber);
|
|
58
|
+
}
|
|
59
|
+
markCompleted(stepNumber) {
|
|
60
|
+
this.running.delete(stepNumber);
|
|
61
|
+
this.completed.add(stepNumber);
|
|
62
|
+
}
|
|
63
|
+
markFailed(stepNumber) {
|
|
64
|
+
this.running.delete(stepNumber);
|
|
65
|
+
this.failed.add(stepNumber);
|
|
66
|
+
}
|
|
67
|
+
setMaxWorkers(maxWorkers) {
|
|
68
|
+
this.maxWorkers = Math.max(1, maxWorkers);
|
|
69
|
+
}
|
|
70
|
+
hasPendingWork() {
|
|
71
|
+
return this.pending.size > 0 || this.running.size > 0;
|
|
72
|
+
}
|
|
73
|
+
isBlocked() {
|
|
74
|
+
return this.pending.size > 0 && this.running.size === 0 && this.readySteps().length === 0;
|
|
75
|
+
}
|
|
76
|
+
blockedSteps() {
|
|
77
|
+
return Array.from(this.pending).sort((a, b) => a - b);
|
|
78
|
+
}
|
|
79
|
+
stats() {
|
|
80
|
+
return {
|
|
81
|
+
pending: this.pending.size,
|
|
82
|
+
running: this.running.size,
|
|
83
|
+
completed: this.completed.size,
|
|
84
|
+
failed: this.failed.size,
|
|
85
|
+
maxWorkers: this.maxWorkers,
|
|
86
|
+
parallelEnabled: this.parallelEnabled(),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
getAnalysis() {
|
|
90
|
+
return this.analysis;
|
|
91
|
+
}
|
|
92
|
+
readySteps() {
|
|
93
|
+
const ready = [];
|
|
94
|
+
for (const stepNumber of this.pending) {
|
|
95
|
+
const step = this.plan.steps.find(candidate => candidate.stepNumber === stepNumber);
|
|
96
|
+
if (!step)
|
|
97
|
+
continue;
|
|
98
|
+
if (step.dependencies.some(dep => this.failed.has(dep)))
|
|
99
|
+
continue;
|
|
100
|
+
if (step.dependencies.every(dep => this.completed.has(dep)))
|
|
101
|
+
ready.push(stepNumber);
|
|
102
|
+
}
|
|
103
|
+
return ready.sort((a, b) => a - b);
|
|
104
|
+
}
|
|
105
|
+
canRunWithCurrentBatch(stepNumber, selected) {
|
|
106
|
+
if (!this.parallelEnabled()) {
|
|
107
|
+
return this.running.size === 0 && selected.length === 0;
|
|
108
|
+
}
|
|
109
|
+
for (const running of this.running) {
|
|
110
|
+
if (!(0, dependency_analyzer_1.canRunTogether)(this.analysis, stepNumber, running))
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
for (const candidate of selected) {
|
|
114
|
+
if (!(0, dependency_analyzer_1.canRunTogether)(this.analysis, stepNumber, candidate))
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
parallelEnabled() {
|
|
120
|
+
return this.maxWorkers > 1 && this.analysis.parallelizable;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.WorkStealingQueue = WorkStealingQueue;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Strips known secret values from strings and files so credentials
|
|
3
|
+
// never persist in transcripts, session state, or log artifacts.
|
|
4
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
|
+
if (k2 === undefined) k2 = k;
|
|
6
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
7
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
8
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9
|
+
}
|
|
10
|
+
Object.defineProperty(o, k2, desc);
|
|
11
|
+
}) : (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
o[k2] = m[k];
|
|
14
|
+
}));
|
|
15
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
16
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
17
|
+
}) : function(o, v) {
|
|
18
|
+
o["default"] = v;
|
|
19
|
+
});
|
|
20
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
21
|
+
var ownKeys = function(o) {
|
|
22
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
23
|
+
var ar = [];
|
|
24
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
25
|
+
return ar;
|
|
26
|
+
};
|
|
27
|
+
return ownKeys(o);
|
|
28
|
+
};
|
|
29
|
+
return function (mod) {
|
|
30
|
+
if (mod && mod.__esModule) return mod;
|
|
31
|
+
var result = {};
|
|
32
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
33
|
+
__setModuleDefault(result, mod);
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
})();
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.REDACTABLE_KEYS = void 0;
|
|
39
|
+
exports.redactString = redactString;
|
|
40
|
+
exports.redactFile = redactFile;
|
|
41
|
+
const fs = __importStar(require("fs"));
|
|
42
|
+
// Keys checked against process.env for values to redact.
|
|
43
|
+
// Ordering does not matter; every key with a non-empty value is applied.
|
|
44
|
+
const REDACTABLE_KEYS = [
|
|
45
|
+
'ANTHROPIC_API_KEY',
|
|
46
|
+
'OPENAI_API_KEY',
|
|
47
|
+
'GITHUB_TOKEN',
|
|
48
|
+
'COPILOT_TOKEN',
|
|
49
|
+
'GOOGLE_APPLICATION_CREDENTIALS',
|
|
50
|
+
];
|
|
51
|
+
exports.REDACTABLE_KEYS = REDACTABLE_KEYS;
|
|
52
|
+
function buildRedactionList() {
|
|
53
|
+
const entries = [];
|
|
54
|
+
for (const key of REDACTABLE_KEYS) {
|
|
55
|
+
const value = process.env[key];
|
|
56
|
+
if (value && value.length > 0) {
|
|
57
|
+
entries.push({ keyName: key, value });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return entries;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Replace all occurrences of known secret values in the input string
|
|
64
|
+
* with `[REDACTED:<KEY_NAME>]` placeholders. Returns the sanitized string.
|
|
65
|
+
*/
|
|
66
|
+
function redactString(input) {
|
|
67
|
+
const entries = buildRedactionList();
|
|
68
|
+
let result = input;
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
// Split/join is safe for literal replacement without regex escaping
|
|
71
|
+
while (result.includes(entry.value)) {
|
|
72
|
+
result = result.split(entry.value).join(`[REDACTED:${entry.keyName}]`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Redact known secret values in-place from a file on disk.
|
|
79
|
+
* No-ops silently if the file does not exist (already cleaned up).
|
|
80
|
+
*/
|
|
81
|
+
function redactFile(filePath) {
|
|
82
|
+
if (!fs.existsSync(filePath))
|
|
83
|
+
return;
|
|
84
|
+
const original = fs.readFileSync(filePath, 'utf8');
|
|
85
|
+
const redacted = redactString(original);
|
|
86
|
+
// Only write back if something actually changed
|
|
87
|
+
if (redacted !== original) {
|
|
88
|
+
fs.writeFileSync(filePath, redacted, 'utf8');
|
|
89
|
+
}
|
|
90
|
+
}
|