@ai-sdlc/orchestrator 0.13.0 → 0.15.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/dist/analysis/file-walker.js +5 -0
- package/dist/cli/commands/git-remote.js +1 -1
- package/dist/cli/commands/init-features.d.ts +85 -0
- package/dist/cli/commands/init-features.js +443 -23
- package/dist/cli/commands/init-templates.d.ts +45 -5
- package/dist/cli/commands/init-templates.js +80 -6
- package/dist/cycle-utils.js +14 -1
- package/dist/execute.js +29 -8
- package/dist/fix-review.js +1 -1
- package/dist/journey/metric-snapshot.d.ts +403 -0
- package/dist/journey/metric-snapshot.js +370 -0
- package/dist/otel-exporter.js +0 -3
- package/dist/runners/claude-code.js +3 -3
- package/dist/runners/runner-registry.js +1 -1
- package/dist/runtime/attestations.js +15 -5
- package/dist/sa-scoring/exemplar-bank.js +3 -1
- package/dist/shared.d.ts +28 -0
- package/dist/shared.js +37 -0
- package/dist/webhook-manager.js +7 -1
- package/package.json +12 -7
|
@@ -42,16 +42,56 @@ export declare const AI_SDLC_GATE_WORKFLOW = "name: AI-SDLC PR Ready Gate\n\n# S
|
|
|
42
42
|
*/
|
|
43
43
|
export declare const VERIFY_ATTESTATION_WORKFLOW = "name: AI-SDLC Verify Review Attestation\n\n# Reads the DSSE attestation at .ai-sdlc/attestations/<head-sha>.dsse.json\n# and verifies the signature against any-of-N pubkeys in\n# .ai-sdlc/trusted-reviewers.yaml.\n#\n# AUDIT-ONLY: this workflow logs verification results (success/failure with\n# reason) for forensic purposes but does NOT post a required commit status.\n# The single merge gate is `ai-sdlc/pr-ready` from ai-sdlc-gate.yml.\n\non:\n pull_request:\n types: [opened, synchronize, reopened]\n branches: [main]\n paths-ignore:\n - 'docs/**'\n - '*.md'\n merge_group:\n types: [checks_requested]\n\nconcurrency:\n group: verify-attestation-${{ github.event.pull_request.number || github.event.merge_group.head_sha }}\n cancel-in-progress: true\n\njobs:\n verify:\n name: Verify attestation\n runs-on: ubuntu-latest\n permissions:\n contents: read\n steps:\n - name: Resolve subject SHA + base SHA from event payload\n id: resolve\n run: |\n if [ \"${{ github.event_name }}\" = \"merge_group\" ]; then\n echo \"head_sha=${{ github.event.merge_group.head_sha }}\" >> \"$GITHUB_OUTPUT\"\n echo \"base_sha=${{ github.event.merge_group.base_sha }}\" >> \"$GITHUB_OUTPUT\"\n else\n echo \"head_sha=${{ github.event.pull_request.head.sha }}\" >> \"$GITHUB_OUTPUT\"\n echo \"base_sha=${{ github.event.pull_request.base.sha }}\" >> \"$GITHUB_OUTPUT\"\n fi\n\n - uses: actions/checkout@v4\n with:\n fetch-depth: 0\n ref: ${{ steps.resolve.outputs.head_sha }}\n\n - name: Log audit result\n env:\n HEAD_SHA: ${{ steps.resolve.outputs.head_sha }}\n run: |\n if [ -f \".ai-sdlc/attestations/${HEAD_SHA}.dsse.json\" ]; then\n echo \"::notice::ai-sdlc attestation AUDIT \u2014 envelope present at ${HEAD_SHA}\"\n else\n echo \"::notice::ai-sdlc attestation AUDIT \u2014 no envelope on ${HEAD_SHA} (audit-only, not blocking)\"\n fi\n";
|
|
44
44
|
/**
|
|
45
|
-
* `.husky/pre-push`
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
45
|
+
* `.husky/pre-push` (or `.git/hooks/pre-push` for non-husky repos) snippet
|
|
46
|
+
* that signs an attestation when one is missing for the current HEAD.
|
|
47
|
+
* Installed when `--with-attestation` is opted in.
|
|
48
|
+
*
|
|
49
|
+
* AISDLC-555: pre-fix, this snippet checked ONLY `./scripts/check-attestation-
|
|
50
|
+
* sign.sh` — a path that exists in the ai-sdlc monorepo (where the hook is
|
|
51
|
+
* hand-authored, not wizard-generated) but NEVER in an adopter repo, because
|
|
52
|
+
* nothing ever copied that script there. The `[ -x ... ]` guard silently
|
|
53
|
+
* failed forever, so `--with-attestation` produced a hook that looked
|
|
54
|
+
* complete but never signed anything — the exact bug this task exists to
|
|
55
|
+
* fix. `check-attestation-sign.sh` now also ships under
|
|
56
|
+
* `ai-sdlc-plugin/scripts/` (AISDLC-555), so this snippet resolves it from
|
|
57
|
+
* the PLUGIN INSTALL ONLY: `$CLAUDE_PLUGIN_ROOT` / `$CLAUDE_PLUGIN_DIR` (the
|
|
58
|
+
* zero-config path when `git push` runs inside a Claude Code session), then a
|
|
59
|
+
* read-only plugin-cache probe (bare-terminal `git push`, which never inherits
|
|
60
|
+
* those env vars).
|
|
61
|
+
*
|
|
62
|
+
* There is deliberately NO repo-local tier — see item 2 below. An earlier
|
|
63
|
+
* revision of this docblock described one, which contradicted the code and,
|
|
64
|
+
* worse, advertised a resolution order that was removed for security.
|
|
65
|
+
*
|
|
66
|
+
* Review round 1 (AISDLC-555) — TWO deliberate changes here, both correcting
|
|
67
|
+
* the first version of this fix:
|
|
68
|
+
*
|
|
69
|
+
* 1. **It is no longer silent when nothing resolves.** The original ended in a
|
|
70
|
+
* bare `if [ -n "$HOOK" ]; then bash ...; fi` with no else, so an adopter
|
|
71
|
+
* who installed via `npm i -g @ai-sdlc/orchestrator` (the documented
|
|
72
|
+
* getting-started path) and never installed the Claude Code plugin got a
|
|
73
|
+
* hook that could never fire and never said so — reproducing the exact
|
|
74
|
+
* defect this task exists to close, for a whole adopter persona.
|
|
75
|
+
* `ai-sdlc-plugin/` is not published to npm and orchestrator's `files` is
|
|
76
|
+
* `["dist"]`, so none of the tiers can resolve in that setup.
|
|
77
|
+
*
|
|
78
|
+
* Silence is still correct when there is nothing to sign, so the diagnostic
|
|
79
|
+
* fires only when `.ai-sdlc/verdicts/` is non-empty: reviewers ran, an
|
|
80
|
+
* envelope is owed, and none will be produced. That is the state an
|
|
81
|
+
* operator must never discover months later.
|
|
82
|
+
*
|
|
83
|
+
* 2. **The repo-relative tier was removed.** It previously preferred
|
|
84
|
+
* `./scripts/check-attestation-sign.sh` from the working tree, which put
|
|
85
|
+
* repo-tracked content on the push-time execution path with the operator's
|
|
86
|
+
* Ed25519 signing key in scope — a contributor could land that file and
|
|
87
|
+
* have it run as the maintainer on their next push. Resolution is now only
|
|
88
|
+
* from the plugin install (env vars, then the read-only cache probe).
|
|
49
89
|
*
|
|
50
90
|
* Adopters typically already have a `.husky/pre-push` from their existing
|
|
51
91
|
* tooling; the wizard appends our snippet behind a sentinel so we can
|
|
52
92
|
* extend an existing hook without trampling user content.
|
|
53
93
|
*/
|
|
54
|
-
export declare const HUSKY_PREPUSH_SIGN_SNIPPET = "# ai-sdlc:attestation-sign-block\n# Signs the DSSE attestation envelope for the current HEAD when verdict\n# files exist. Skip with AI_SDLC_SKIP_ATTESTATION_SIGN=1.\nif [ -z \"${AI_SDLC_SKIP_ATTESTATION_SIGN:-}\" ] && [ -
|
|
94
|
+
export declare const HUSKY_PREPUSH_SIGN_SNIPPET = "# ai-sdlc:attestation-sign-block\n# Signs the DSSE attestation envelope for the current HEAD when verdict\n# files exist. Skip with AI_SDLC_SKIP_ATTESTATION_SIGN=1.\nif [ -z \"${AI_SDLC_SKIP_ATTESTATION_SIGN:-}\" ]; then\n AI_SDLC_ATTESTATION_HOOK=\"\"\n if [ -n \"${CLAUDE_PLUGIN_ROOT:-}\" ] && [ -f \"${CLAUDE_PLUGIN_ROOT}/scripts/check-attestation-sign.sh\" ]; then\n AI_SDLC_ATTESTATION_HOOK=\"${CLAUDE_PLUGIN_ROOT}/scripts/check-attestation-sign.sh\"\n elif [ -n \"${CLAUDE_PLUGIN_DIR:-}\" ] && [ -f \"${CLAUDE_PLUGIN_DIR}/scripts/check-attestation-sign.sh\" ]; then\n AI_SDLC_ATTESTATION_HOOK=\"${CLAUDE_PLUGIN_DIR}/scripts/check-attestation-sign.sh\"\n else\n for _ai_sdlc_dir in \"$HOME\"/.claude/plugins/cache/*/ai-sdlc/*/; do\n if [ -f \"${_ai_sdlc_dir}scripts/check-attestation-sign.sh\" ]; then\n AI_SDLC_ATTESTATION_HOOK=\"${_ai_sdlc_dir}scripts/check-attestation-sign.sh\"\n break\n fi\n done\n fi\n if [ -n \"$AI_SDLC_ATTESTATION_HOOK\" ]; then\n echo \"[ai-sdlc] attestation signer: $AI_SDLC_ATTESTATION_HOOK\" >&2\n bash \"$AI_SDLC_ATTESTATION_HOOK\"\n elif [ -n \"$(ls -A .ai-sdlc/verdicts 2>/dev/null)\" ]; then\n echo \"[ai-sdlc] ERROR: reviewer verdicts exist under .ai-sdlc/verdicts/ but NO attestation signer\" >&2\n echo \"[ai-sdlc] could be found \u2014 this push will carry no attestation.\" >&2\n echo \"[ai-sdlc] Searched CLAUDE_PLUGIN_ROOT, CLAUDE_PLUGIN_DIR, and\" >&2\n echo \"[ai-sdlc] ~/.claude/plugins/cache/*/ai-sdlc/*/scripts/check-attestation-sign.sh\" >&2\n echo \"[ai-sdlc] Install the ai-sdlc Claude Code plugin, or set CLAUDE_PLUGIN_ROOT.\" >&2\n fi\nfi\n# end ai-sdlc:attestation-sign-block\n";
|
|
55
95
|
/**
|
|
56
96
|
* `.ai-sdlc/trusted-reviewers.yaml` stub — empty allowlist with operator
|
|
57
97
|
* instructions. The wizard scaffolds this so adopters have a single file
|
|
@@ -207,10 +207,50 @@ jobs:
|
|
|
207
207
|
fi
|
|
208
208
|
`;
|
|
209
209
|
/**
|
|
210
|
-
* `.husky/pre-push`
|
|
211
|
-
*
|
|
212
|
-
*
|
|
213
|
-
*
|
|
210
|
+
* `.husky/pre-push` (or `.git/hooks/pre-push` for non-husky repos) snippet
|
|
211
|
+
* that signs an attestation when one is missing for the current HEAD.
|
|
212
|
+
* Installed when `--with-attestation` is opted in.
|
|
213
|
+
*
|
|
214
|
+
* AISDLC-555: pre-fix, this snippet checked ONLY `./scripts/check-attestation-
|
|
215
|
+
* sign.sh` — a path that exists in the ai-sdlc monorepo (where the hook is
|
|
216
|
+
* hand-authored, not wizard-generated) but NEVER in an adopter repo, because
|
|
217
|
+
* nothing ever copied that script there. The `[ -x ... ]` guard silently
|
|
218
|
+
* failed forever, so `--with-attestation` produced a hook that looked
|
|
219
|
+
* complete but never signed anything — the exact bug this task exists to
|
|
220
|
+
* fix. `check-attestation-sign.sh` now also ships under
|
|
221
|
+
* `ai-sdlc-plugin/scripts/` (AISDLC-555), so this snippet resolves it from
|
|
222
|
+
* the PLUGIN INSTALL ONLY: `$CLAUDE_PLUGIN_ROOT` / `$CLAUDE_PLUGIN_DIR` (the
|
|
223
|
+
* zero-config path when `git push` runs inside a Claude Code session), then a
|
|
224
|
+
* read-only plugin-cache probe (bare-terminal `git push`, which never inherits
|
|
225
|
+
* those env vars).
|
|
226
|
+
*
|
|
227
|
+
* There is deliberately NO repo-local tier — see item 2 below. An earlier
|
|
228
|
+
* revision of this docblock described one, which contradicted the code and,
|
|
229
|
+
* worse, advertised a resolution order that was removed for security.
|
|
230
|
+
*
|
|
231
|
+
* Review round 1 (AISDLC-555) — TWO deliberate changes here, both correcting
|
|
232
|
+
* the first version of this fix:
|
|
233
|
+
*
|
|
234
|
+
* 1. **It is no longer silent when nothing resolves.** The original ended in a
|
|
235
|
+
* bare `if [ -n "$HOOK" ]; then bash ...; fi` with no else, so an adopter
|
|
236
|
+
* who installed via `npm i -g @ai-sdlc/orchestrator` (the documented
|
|
237
|
+
* getting-started path) and never installed the Claude Code plugin got a
|
|
238
|
+
* hook that could never fire and never said so — reproducing the exact
|
|
239
|
+
* defect this task exists to close, for a whole adopter persona.
|
|
240
|
+
* `ai-sdlc-plugin/` is not published to npm and orchestrator's `files` is
|
|
241
|
+
* `["dist"]`, so none of the tiers can resolve in that setup.
|
|
242
|
+
*
|
|
243
|
+
* Silence is still correct when there is nothing to sign, so the diagnostic
|
|
244
|
+
* fires only when `.ai-sdlc/verdicts/` is non-empty: reviewers ran, an
|
|
245
|
+
* envelope is owed, and none will be produced. That is the state an
|
|
246
|
+
* operator must never discover months later.
|
|
247
|
+
*
|
|
248
|
+
* 2. **The repo-relative tier was removed.** It previously preferred
|
|
249
|
+
* `./scripts/check-attestation-sign.sh` from the working tree, which put
|
|
250
|
+
* repo-tracked content on the push-time execution path with the operator's
|
|
251
|
+
* Ed25519 signing key in scope — a contributor could land that file and
|
|
252
|
+
* have it run as the maintainer on their next push. Resolution is now only
|
|
253
|
+
* from the plugin install (env vars, then the read-only cache probe).
|
|
214
254
|
*
|
|
215
255
|
* Adopters typically already have a `.husky/pre-push` from their existing
|
|
216
256
|
* tooling; the wizard appends our snippet behind a sentinel so we can
|
|
@@ -219,8 +259,30 @@ jobs:
|
|
|
219
259
|
export const HUSKY_PREPUSH_SIGN_SNIPPET = `# ai-sdlc:attestation-sign-block
|
|
220
260
|
# Signs the DSSE attestation envelope for the current HEAD when verdict
|
|
221
261
|
# files exist. Skip with AI_SDLC_SKIP_ATTESTATION_SIGN=1.
|
|
222
|
-
if [ -z "\${AI_SDLC_SKIP_ATTESTATION_SIGN:-}" ]
|
|
223
|
-
|
|
262
|
+
if [ -z "\${AI_SDLC_SKIP_ATTESTATION_SIGN:-}" ]; then
|
|
263
|
+
AI_SDLC_ATTESTATION_HOOK=""
|
|
264
|
+
if [ -n "\${CLAUDE_PLUGIN_ROOT:-}" ] && [ -f "\${CLAUDE_PLUGIN_ROOT}/scripts/check-attestation-sign.sh" ]; then
|
|
265
|
+
AI_SDLC_ATTESTATION_HOOK="\${CLAUDE_PLUGIN_ROOT}/scripts/check-attestation-sign.sh"
|
|
266
|
+
elif [ -n "\${CLAUDE_PLUGIN_DIR:-}" ] && [ -f "\${CLAUDE_PLUGIN_DIR}/scripts/check-attestation-sign.sh" ]; then
|
|
267
|
+
AI_SDLC_ATTESTATION_HOOK="\${CLAUDE_PLUGIN_DIR}/scripts/check-attestation-sign.sh"
|
|
268
|
+
else
|
|
269
|
+
for _ai_sdlc_dir in "$HOME"/.claude/plugins/cache/*/ai-sdlc/*/; do
|
|
270
|
+
if [ -f "\${_ai_sdlc_dir}scripts/check-attestation-sign.sh" ]; then
|
|
271
|
+
AI_SDLC_ATTESTATION_HOOK="\${_ai_sdlc_dir}scripts/check-attestation-sign.sh"
|
|
272
|
+
break
|
|
273
|
+
fi
|
|
274
|
+
done
|
|
275
|
+
fi
|
|
276
|
+
if [ -n "$AI_SDLC_ATTESTATION_HOOK" ]; then
|
|
277
|
+
echo "[ai-sdlc] attestation signer: $AI_SDLC_ATTESTATION_HOOK" >&2
|
|
278
|
+
bash "$AI_SDLC_ATTESTATION_HOOK"
|
|
279
|
+
elif [ -n "$(ls -A .ai-sdlc/verdicts 2>/dev/null)" ]; then
|
|
280
|
+
echo "[ai-sdlc] ERROR: reviewer verdicts exist under .ai-sdlc/verdicts/ but NO attestation signer" >&2
|
|
281
|
+
echo "[ai-sdlc] could be found — this push will carry no attestation." >&2
|
|
282
|
+
echo "[ai-sdlc] Searched CLAUDE_PLUGIN_ROOT, CLAUDE_PLUGIN_DIR, and" >&2
|
|
283
|
+
echo "[ai-sdlc] ~/.claude/plugins/cache/*/ai-sdlc/*/scripts/check-attestation-sign.sh" >&2
|
|
284
|
+
echo "[ai-sdlc] Install the ai-sdlc Claude Code plugin, or set CLAUDE_PLUGIN_ROOT." >&2
|
|
285
|
+
fi
|
|
224
286
|
fi
|
|
225
287
|
# end ai-sdlc:attestation-sign-block
|
|
226
288
|
`;
|
|
@@ -857,6 +919,18 @@ export const ATTESTATION_TEMPLATES = {
|
|
|
857
919
|
// first PR's envelope lands cleanly without "directory does not exist"
|
|
858
920
|
// errors from the signing script.
|
|
859
921
|
'.ai-sdlc/attestations/.gitkeep': '',
|
|
922
|
+
// AISDLC-555 (partial AC #7): the pre-push hook's gate condition reads
|
|
923
|
+
// `.ai-sdlc/verdicts/<task-id>.json` — tightly coupled to attestation,
|
|
924
|
+
// so it's scaffolded here rather than deferred to a separate
|
|
925
|
+
// init-orchestration task. Dispatch Board directories + dispatch-
|
|
926
|
+
// config.yaml (the rest of the widened AC #7 scope) are NOT scaffolded
|
|
927
|
+
// here, and — confirmed by round-2 review — NO backlog task currently
|
|
928
|
+
// owns them: nothing under `backlog/tasks/` mentions dispatch-config.yaml.
|
|
929
|
+
// An earlier revision of this comment cited AISDLC-560; that task covers
|
|
930
|
+
// attestation enforcement/doctor and says nothing about the Dispatch
|
|
931
|
+
// Board, so the citation was wrong. Left explicitly unowned rather than
|
|
932
|
+
// pointed at a task that would not deliver it.
|
|
933
|
+
'.ai-sdlc/verdicts/.gitkeep': '',
|
|
860
934
|
},
|
|
861
935
|
};
|
|
862
936
|
export const CLASSIFIER_TEMPLATES = {
|
package/dist/cycle-utils.js
CHANGED
|
@@ -6,9 +6,22 @@ import { NOTIFICATION_TITLES } from './defaults.js';
|
|
|
6
6
|
/**
|
|
7
7
|
* Sanitize template content to prevent markdown/HTML injection.
|
|
8
8
|
* Strips HTML tags and limits length.
|
|
9
|
+
*
|
|
10
|
+
* A single-pass `<[^>]*>` strip is flagged by CodeQL as
|
|
11
|
+
* `js/incomplete-multi-character-sanitization` (alert #65) because a
|
|
12
|
+
* crafted string like `<scr<script>ipt>` leaves `<script>` after one pass.
|
|
13
|
+
* We iterate the replacement until stable so any re-introduced bad
|
|
14
|
+
* substring is eliminated on the next pass — making the sanitization
|
|
15
|
+
* demonstrably idempotent.
|
|
9
16
|
*/
|
|
10
17
|
function sanitizeTemplate(text) {
|
|
11
|
-
|
|
18
|
+
let prev;
|
|
19
|
+
let current = text;
|
|
20
|
+
do {
|
|
21
|
+
prev = current;
|
|
22
|
+
current = prev.replace(/<[^>]*>/g, '');
|
|
23
|
+
} while (current !== prev);
|
|
24
|
+
return current.slice(0, 2000);
|
|
12
25
|
}
|
|
13
26
|
/**
|
|
14
27
|
* Check for pipeline cycles and post notification if detected.
|
package/dist/execute.js
CHANGED
|
@@ -12,7 +12,7 @@ import { validateAgentOutput } from './validate-agent-output.js';
|
|
|
12
12
|
import { createLogger } from './logger.js';
|
|
13
13
|
import { createStructuredConsoleLogger, createStructuredBufferLogger, } from './structured-logger.js';
|
|
14
14
|
import { createRunnerRegistry, resolveRunner } from './runners/runner-registry.js';
|
|
15
|
-
import { execFileAsync, getGitHubConfig, resolveRepoRoot, createDefaultAuditLog, resolveAutonomyLevel, resolveConstraints, isAutonomousStrategy, recordMetric, evaluatePipelineCompliance, authorizeFilesChanged, interpolateBranchPattern, interpolatePRTitle, issueIdToNumber, formatIssueRef, buildIssueTemplateVars, } from './shared.js';
|
|
15
|
+
import { execFileAsync, getGitHubConfig, resolveRepoRoot, createDefaultAuditLog, resolveAutonomyLevel, resolveConstraints, isAutonomousStrategy, recordMetric, evaluatePipelineCompliance, authorizeFilesChanged, interpolateBranchPattern, interpolatePRTitle, issueIdToNumber, formatIssueRef, buildIssueTemplateVars, validateBranchName, } from './shared.js';
|
|
16
16
|
import { cleanGitEnv } from './runtime/git-env.js';
|
|
17
17
|
import { checkKillSwitch, issueAgentCredentials, revokeAgentCredentials, classifyAndSubmitApproval, createPipelineSecurity, } from './security.js';
|
|
18
18
|
import { createPipelineOrchestration, executePipelineOrchestration, validatePipelineHandoffs, } from './orchestration.js';
|
|
@@ -307,7 +307,7 @@ export async function pushBranchWithRebase(workDir, branchName, log) {
|
|
|
307
307
|
/* nothing to abort */
|
|
308
308
|
}
|
|
309
309
|
throw new Error(`Push rebase failed for branch ${branchName}: ${rebaseErr.message}. ` +
|
|
310
|
-
`Resolve conflicts manually and re-run the pipeline
|
|
310
|
+
`Resolve conflicts manually and re-run the pipeline.`, { cause: rebaseErr });
|
|
311
311
|
}
|
|
312
312
|
await execFileAsync('git', ['push', 'origin', branchName], { cwd: workDir, env });
|
|
313
313
|
return false;
|
|
@@ -453,6 +453,10 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
|
|
|
453
453
|
// 7. Create branch and checkout locally (read pattern from pipeline config)
|
|
454
454
|
const branchVars = buildIssueTemplateVars(issueId, issue.title);
|
|
455
455
|
const branchName = interpolateBranchPattern(config.pipeline?.spec.branching?.pattern, branchVars);
|
|
456
|
+
// Defense against second-order command injection: reject branch names that
|
|
457
|
+
// start with '-' (git flag injection) or contain chars outside the safe ref
|
|
458
|
+
// charset. CodeQL alert #167 (js/second-order-command-line-injection).
|
|
459
|
+
validateBranchName(branchName);
|
|
456
460
|
await sc.createBranch({ name: branchName });
|
|
457
461
|
// cleanGitEnv() prevents leaked GIT_DIR from corrupting these calls (AISDLC-72).
|
|
458
462
|
// Guard: skip fetch when no 'origin' remote is configured (local-only repos).
|
|
@@ -1013,8 +1017,13 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
|
|
|
1013
1017
|
details: { averageCoverage: avgCoverage, frameworks: complianceReports.length },
|
|
1014
1018
|
});
|
|
1015
1019
|
// 16b. Extended diagnostics (non-blocking)
|
|
1020
|
+
// Awaited so that every async operation started inside the diagnostics pass
|
|
1021
|
+
// completes before executePipeline returns. Without the await, fire-and-forget
|
|
1022
|
+
// promises (resolveAdapterFromGit, scanPipelineAdapters, loadAuditEntries) stay
|
|
1023
|
+
// pending past the function boundary and can fire into a closed vitest worker
|
|
1024
|
+
// MessagePort, producing ERR_IPC_CHANNEL_CLOSED (AISDLC-542).
|
|
1016
1025
|
try {
|
|
1017
|
-
runPipelineDiagnostics({
|
|
1026
|
+
await runPipelineDiagnostics({
|
|
1018
1027
|
config,
|
|
1019
1028
|
qualityGate,
|
|
1020
1029
|
agentRole,
|
|
@@ -1083,9 +1092,15 @@ async function executePipelineBody(issueId, issue, config, qualityGate, agentRol
|
|
|
1083
1092
|
/**
|
|
1084
1093
|
* Non-blocking diagnostics pass that exercises all previously unwired modules.
|
|
1085
1094
|
* Runs after the main pipeline succeeds — failures are silently ignored.
|
|
1095
|
+
*
|
|
1096
|
+
* Returns a Promise so the caller can await all async work, preventing dangling
|
|
1097
|
+
* promises from outliving the pipeline call and firing into a closed vitest
|
|
1098
|
+
* worker MessagePort (AISDLC-542: ERR_IPC_CHANNEL_CLOSED teardown race).
|
|
1086
1099
|
*/
|
|
1087
|
-
function runPipelineDiagnostics(input) {
|
|
1100
|
+
async function runPipelineDiagnostics(input) {
|
|
1088
1101
|
const { config, qualityGate, agentRole, autonomyPolicy, log } = input;
|
|
1102
|
+
// Collect async operations; await all at the end so no promise escapes the function.
|
|
1103
|
+
const asyncOps = [];
|
|
1089
1104
|
// Policy evaluators: Rego, CEL, ABAC, gate evaluation, complexity scoring
|
|
1090
1105
|
const _rego = createPipelineRegoEvaluator();
|
|
1091
1106
|
const _cel = createPipelineCELEvaluator();
|
|
@@ -1110,9 +1125,10 @@ function runPipelineDiagnostics(input) {
|
|
|
1110
1125
|
const adapterRegistry = createPipelineAdapterRegistry();
|
|
1111
1126
|
log.info(`Adapter registry: ${adapterRegistry.list().length} adapters registered`);
|
|
1112
1127
|
const _bridge = createPipelineWebhookBridge();
|
|
1113
|
-
//
|
|
1114
|
-
|
|
1115
|
-
|
|
1128
|
+
// Collect async ops instead of fire-and-forget — awaited below via Promise.allSettled
|
|
1129
|
+
// so no pending promise outlives this function (AISDLC-542).
|
|
1130
|
+
asyncOps.push(resolveAdapterFromGit('github:ai-sdlc-framework/ai-sdlc').catch(() => { }));
|
|
1131
|
+
asyncOps.push(scanPipelineAdapters({ basePath: `${input.workDir}/.ai-sdlc/adapters` }).catch(() => { }));
|
|
1116
1132
|
// Extended compliance: per-framework checks, control catalog, mappings
|
|
1117
1133
|
const controlIds = getControlCatalog();
|
|
1118
1134
|
const frameworks = listSupportedFrameworks();
|
|
@@ -1167,10 +1183,15 @@ function runPipelineDiagnostics(input) {
|
|
|
1167
1183
|
resource: 'pipeline',
|
|
1168
1184
|
decision: 'allowed',
|
|
1169
1185
|
});
|
|
1170
|
-
|
|
1186
|
+
// Collected into asyncOps — awaited below so the promise doesn't escape (AISDLC-542).
|
|
1187
|
+
asyncOps.push(loadAuditEntries(auditPath).catch(() => { }));
|
|
1171
1188
|
// Note: rotateAuditLog intentionally omitted — it truncates the file,
|
|
1172
1189
|
// which would empty it before artifact upload can capture the contents.
|
|
1173
1190
|
}
|
|
1191
|
+
// Await all async diagnostic operations so no promise outlives this function.
|
|
1192
|
+
// allSettled keeps the best-effort semantic: individual failures are silently swallowed,
|
|
1193
|
+
// but the caller can await the diagnostics without unhandled rejections leaking.
|
|
1194
|
+
await Promise.allSettled(asyncOps);
|
|
1174
1195
|
}
|
|
1175
1196
|
// ── Gitignore helper ─────────────────────────────────────────────────
|
|
1176
1197
|
const RUNTIME_GITIGNORE_PATHS = ['.ai-sdlc/state.db', '.ai-sdlc/state/', '.ai-sdlc/audit.jsonl'];
|
package/dist/fix-review.js
CHANGED
|
@@ -67,7 +67,7 @@ export async function fetchReviewFindings(prNumber, injectedFindings, _secretSto
|
|
|
67
67
|
reviews = JSON.parse(stdout);
|
|
68
68
|
}
|
|
69
69
|
catch (err) {
|
|
70
|
-
throw new Error(`Failed to parse review data from gh CLI: ${err instanceof Error ? err.message : String(err)}
|
|
70
|
+
throw new Error(`Failed to parse review data from gh CLI: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
71
71
|
}
|
|
72
72
|
const changesRequestedReviews = reviews.filter((r) => r.state === 'CHANGES_REQUESTED');
|
|
73
73
|
if (changesRequestedReviews.length === 0) {
|