mandrel 2.31.0 → 2.32.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/.agents/README.md +13 -17
- package/.agents/agents/acceptance-critic.md +1 -2
- package/.agents/docs/SDLC.md +4 -4
- package/.agents/docs/agentrc-reference.json +61 -57
- package/.agents/docs/configuration.md +274 -227
- package/.agents/docs/execution-reference.md +13 -14
- package/.agents/docs/quality-gates.md +195 -23
- package/.agents/instructions.md +2 -5
- package/.agents/rules/git-conventions-reference.md +27 -27
- package/.agents/rules/git-conventions.md +4 -2
- package/.agents/rules/known-tooling-behavior.md +66 -30
- package/.agents/rules/testing-standards.md +35 -71
- package/.agents/runtime-deps.json +0 -1
- package/.agents/schemas/agentrc.schema.json +1939 -1400
- package/.agents/schemas/lifecycle/README.md +21 -14
- package/.agents/schemas/lifecycle/ledger-record.schema.json +76 -22
- package/.agents/schemas/story-deliver-terminal.schema.json +2 -2
- package/.agents/scripts/README.md +7 -29
- package/.agents/scripts/apply-quality-bootstrap.js +27 -34
- package/.agents/scripts/bootstrap.js +28 -26
- package/.agents/scripts/check-baseline-drift.js +73 -13
- package/.agents/scripts/check-baseline-scope.js +362 -0
- package/.agents/scripts/check-dead-exports.js +9 -1
- package/.agents/scripts/check-gherkin-corpus.js +508 -0
- package/.agents/scripts/check-knip-entries.js +136 -0
- package/.agents/scripts/check-lifecycle-lint.js +36 -112
- package/.agents/scripts/check-schema-references.js +1 -1
- package/.agents/scripts/diagnose-friction.js +7 -4
- package/.agents/scripts/generate-config-docs.js +263 -171
- package/.agents/scripts/install-matrix-assert.js +0 -1
- package/.agents/scripts/lib/ITicketingProvider.js +0 -58
- package/.agents/scripts/lib/audit-baselines/staleness.js +6 -6
- package/.agents/scripts/lib/audit-baselines/trend.js +7 -8
- package/.agents/scripts/lib/audit-baselines/weights.js +4 -5
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/build-story-body.js +0 -1
- package/.agents/scripts/lib/baselines/envelope.js +41 -60
- package/.agents/scripts/lib/baselines/git-base.js +30 -37
- package/.agents/scripts/lib/baselines/kinds/_crap-new-method-gate.js +103 -0
- package/.agents/scripts/lib/baselines/kinds/_crap-read.js +150 -0
- package/.agents/scripts/lib/baselines/kinds/crap.js +25 -65
- package/.agents/scripts/lib/baselines/orphan-pruner.js +233 -0
- package/.agents/scripts/lib/baselines/refresh-service.js +6 -8
- package/.agents/scripts/lib/baselines/scope-assert.js +223 -0
- package/.agents/scripts/lib/baselines/scope-inventory.js +314 -0
- package/.agents/scripts/lib/bdd-step-index.js +326 -0
- package/.agents/scripts/lib/bootstrap/install-ledger.js +5 -3
- package/.agents/scripts/lib/bootstrap/issue-forms-template.js +4 -6
- package/.agents/scripts/lib/bootstrap/manifest.js +17 -40
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +12 -59
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +62 -2
- package/.agents/scripts/lib/checks/loop-health.js +9 -37
- package/.agents/scripts/lib/child-exec.js +193 -0
- package/.agents/scripts/lib/cli/standard-args.js +1 -1
- package/.agents/scripts/lib/cli-args.js +64 -0
- package/.agents/scripts/lib/close-validation/gates.js +2 -2
- package/.agents/scripts/lib/close-validation/runner.js +3 -3
- package/.agents/scripts/lib/config/acceptance-eval.js +5 -52
- package/.agents/scripts/lib/config/commands.js +3 -5
- package/.agents/scripts/lib/config/explain.js +5 -7
- package/.agents/scripts/lib/config/gates/bundle-size.schema.js +32 -6
- package/.agents/scripts/lib/config/gates/coverage.schema.js +25 -5
- package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +12 -2
- package/.agents/scripts/lib/config/gates/crap.schema.js +68 -23
- package/.agents/scripts/lib/config/gates/duplication.schema.js +29 -17
- package/.agents/scripts/lib/config/gates/index.js +5 -2
- package/.agents/scripts/lib/config/gates/lighthouse.schema.js +34 -6
- package/.agents/scripts/lib/config/gates/lint.schema.js +11 -2
- package/.agents/scripts/lib/config/gates/maintainability.schema.js +37 -15
- package/.agents/scripts/lib/config/gates/mutation.schema.js +15 -3
- package/.agents/scripts/lib/config/gates/shared.js +58 -9
- package/.agents/scripts/lib/config/github.js +0 -1
- package/.agents/scripts/lib/config/limits.js +3 -48
- package/.agents/scripts/lib/config/qa.js +105 -0
- package/.agents/scripts/lib/config/temp-paths.js +6 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +237 -56
- package/.agents/scripts/lib/config-settings-schema-quality.js +209 -29
- package/.agents/scripts/lib/config-settings-schema.js +386 -39
- package/.agents/scripts/lib/crap-baseline-join.js +126 -9
- package/.agents/scripts/lib/crap-utils.js +84 -520
- package/.agents/scripts/lib/dead-exports-knip.js +79 -10
- package/.agents/scripts/lib/degraded-mode.js +2 -2
- package/.agents/scripts/lib/doc-tiers.js +3 -3
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +46 -104
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +10 -8
- package/.agents/scripts/lib/fs-walk.js +52 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +2 -2
- package/.agents/scripts/lib/git-utils.js +16 -36
- package/.agents/scripts/lib/knip-entry-sync.js +469 -0
- package/.agents/scripts/lib/observability/metrics-ledger.js +1 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +10 -0
- package/.agents/scripts/lib/observability/signal-validator.js +5 -85
- package/.agents/scripts/lib/observability/signals-writer.js +19 -62
- package/.agents/scripts/lib/observability/source-classifier.js +5 -7
- package/.agents/scripts/lib/observability/terse-result.js +3 -3
- package/.agents/scripts/lib/orchestration/behind-recovery.js +114 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +7 -8
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +34 -33
- package/.agents/scripts/lib/orchestration/code-review.js +2 -2
- package/.agents/scripts/lib/orchestration/complexity-gate.js +43 -161
- package/.agents/scripts/lib/orchestration/diff-magnitude.js +4 -4
- package/.agents/scripts/lib/orchestration/label-transitions.js +3 -2
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +12 -38
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +5 -6
- package/.agents/scripts/lib/orchestration/plan-metrics.js +2 -3
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +6 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +0 -1
- package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
- package/.agents/scripts/lib/orchestration/resolve-stories.js +5 -15
- package/.agents/scripts/lib/orchestration/review-providers/codex.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/mi-exemptions.js +130 -0
- package/.agents/scripts/lib/orchestration/review-providers/native.js +30 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +37 -26
- package/.agents/scripts/lib/orchestration/single-story-close/phases/conventional-subject.js +376 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/normalize-pr-title.js +161 -151
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +15 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +10 -15
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-override.js +157 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +0 -14
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +59 -25
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +20 -31
- package/.agents/scripts/lib/orchestration/spec-spill.js +17 -3
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +7 -6
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +2 -1
- package/.agents/scripts/lib/orchestration/task-body-validator.js +4 -1
- package/.agents/scripts/lib/orchestration/ticket-lease.js +28 -127
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +5 -5
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +5 -4
- package/.agents/scripts/lib/orchestration/verify-tier-repair.js +107 -0
- package/.agents/scripts/lib/qa/coverage-verdict.js +5 -87
- package/.agents/scripts/lib/signals/detectors/common.js +1 -1
- package/.agents/scripts/lib/signals/index.js +8 -6
- package/.agents/scripts/lib/signals/schema.js +20 -25
- package/.agents/scripts/lib/signals/write.js +8 -8
- package/.agents/scripts/lib/story-body/story-body.js +12 -59
- package/.agents/scripts/lib/temp-retention.js +1 -1
- package/.agents/scripts/lib/templates/decomposer-prompts.js +16 -14
- package/.agents/scripts/lib/ticket-body-sections.js +4 -5
- package/.agents/scripts/lib/worktree/lifecycle/merge-reachability.js +13 -45
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +4 -5
- package/.agents/scripts/lib/worktree-manager.js +2 -3
- package/.agents/scripts/lint-label-vocabulary.js +2 -24
- package/.agents/scripts/pr-watch-with-update.js +7 -5
- package/.agents/scripts/providers/github/cache.js +2 -2
- package/.agents/scripts/providers/github/comments.js +6 -28
- package/.agents/scripts/providers/github/compose.js +0 -15
- package/.agents/scripts/providers/github/errors.js +10 -27
- package/.agents/scripts/providers/github/request-helpers.js +1 -2
- package/.agents/scripts/providers/github/sub-issues.js +10 -218
- package/.agents/scripts/providers/github.js +4 -7
- package/.agents/scripts/prune-baseline-orphans.js +181 -0
- package/.agents/scripts/resolve-stories.js +0 -2
- package/.agents/scripts/run-lint.js +61 -61
- package/.agents/scripts/run-test-profile.js +6 -6
- package/.agents/scripts/run-verify.js +48 -30
- package/.agents/scripts/single-story-close.js +20 -0
- package/.agents/scripts/single-story-init.js +12 -35
- package/.agents/scripts/update-dead-exports-baseline.js +321 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +2 -2
- package/.agents/skills/skills.index.json +1 -11
- package/.agents/workflows/audit-documentation.md +5 -6
- package/.agents/workflows/audit-to-stories.md +2 -2
- package/.agents/workflows/helpers/audit-lens-core.md +11 -12
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -14
- package/.agents/workflows/helpers/code-review.md +3 -8
- package/.agents/workflows/helpers/deliver-reference.md +2 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +27 -16
- package/.agents/workflows/helpers/worktree-lifecycle.md +1 -2
- package/.agents/workflows/mandrel-update.md +10 -10
- package/.agents/workflows/qa-assist.md +15 -20
- package/.agents/workflows/qa-explore.md +9 -8
- package/README.md +1 -1
- package/docs/CHANGELOG.md +42 -0
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.32.0-retire-lint-baseline-command.js +127 -0
- package/package.json +12 -3
- package/.agents/schemas/lifecycle/checkpoint.written.schema.json +0 -13
- package/.agents/schemas/lifecycle/close-validate.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/close-validate.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/code-review.end.schema.json +0 -30
- package/.agents/schemas/lifecycle/code-review.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/intervention.recorded.schema.json +0 -15
- package/.agents/schemas/lifecycle/loop.tick.schema.json +0 -20
- package/.agents/schemas/lifecycle/notification.emitted.schema.json +0 -18
- package/.agents/schemas/lifecycle/pr.created.schema.json +0 -14
- package/.agents/schemas/lifecycle/retro.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/retro.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/story.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/story.dispatch.end.schema.json +0 -17
- package/.agents/schemas/lifecycle/story.dispatch.start.schema.json +0 -15
- package/.agents/schemas/lifecycle/story.merged.schema.json +0 -13
- package/.agents/scripts/check-gherkin-placeholders.js +0 -663
- package/.agents/scripts/check-lifecycle-doc-drift.js +0 -411
- package/.agents/scripts/lib/audit-suite/cli.js +0 -64
- package/.agents/scripts/lib/bootstrap/baselines-layout-migration.js +0 -202
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +0 -212
- package/.agents/scripts/lib/checks/baseline-drift-main-checkout.js +0 -104
- package/.agents/scripts/lib/checks/push-hook-parity.js +0 -106
- package/.agents/scripts/lib/checks/windows-coverage-noise-floor.js +0 -92
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +0 -81
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +0 -55
- package/.agents/scripts/lib/crap-baseline-index.js +0 -46
- package/.agents/scripts/lib/crap-utils-incremental.js +0 -113
- package/.agents/scripts/lib/dynamic-workflow/capability.js +0 -396
- package/.agents/scripts/lib/feedback-loop/audit-results-graduator.js +0 -335
- package/.agents/scripts/lib/mutation/baseline-snapshot.js +0 -239
- package/.agents/scripts/lib/mutation/config-detector.js +0 -119
- package/.agents/scripts/lib/mutation/stryker-runner.js +0 -306
- package/.agents/scripts/lib/mutation/survivor-report.js +0 -160
- package/.agents/scripts/lib/observability/active-story-env.js +0 -170
- package/.agents/scripts/lib/observability/tool-trace-hook.js +0 -456
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +0 -111
- package/.agents/scripts/lib/orchestration/context-envelope.js +0 -277
- package/.agents/scripts/lib/orchestration/detectors-phase.js +0 -194
- package/.agents/scripts/lib/orchestration/lifecycle/bus.js +0 -309
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +0 -181
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-writer.js +0 -229
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +0 -54
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +0 -344
- package/.agents/scripts/lib/orchestration/lint-baseline-service.js +0 -114
- package/.agents/scripts/lib/orchestration/pr-base-guard.js +0 -37
- package/.agents/scripts/lib/orchestration/resolves-token.js +0 -127
- package/.agents/scripts/lib/orchestration/spec-section-validator.js +0 -130
- package/.agents/scripts/lib/orchestration/story-close/emit-blocked.js +0 -55
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +0 -211
- package/.agents/scripts/lib/planning-corpus.js +0 -37
- package/.agents/scripts/lib/qa/coverage-report.js +0 -181
- package/.agents/scripts/lib/qa/propose-missing-test.js +0 -95
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +0 -217
- package/.agents/scripts/lib/signals/detectors/index.js +0 -14
- package/.agents/scripts/lib/signals/detectors/retry.js +0 -253
- package/.agents/scripts/lib/signals/detectors/rework.js +0 -167
- package/.agents/scripts/lib/signals/read.js +0 -268
- package/.agents/scripts/lib/signals/span-tree.js +0 -291
- package/.agents/scripts/lib/story-lifecycle.js +0 -194
- package/.agents/scripts/lib/story-plan.js +0 -379
- package/.agents/scripts/lib/util/phase-timer-state.js +0 -72
- package/.agents/scripts/lib/util/phase-timer.js +0 -163
- package/.agents/scripts/lib/workers/combined-mi-crap-worker.js +0 -169
- package/.agents/scripts/lint-baseline.js +0 -507
- package/.agents/scripts/providers/github/prs.js +0 -103
- package/.agents/scripts/signals-view.js +0 -309
- package/.agents/scripts/story-plan.js +0 -370
- package/.agents/scripts/sync-branch-from-base.js +0 -149
- package/.agents/scripts/validate-docs-freshness.js +0 -314
- package/.agents/skills/core/diagnose-friction/SKILL.md +0 -78
- package/.agents/workflows/helpers/signals.md +0 -112
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* crap-baseline-index.js — per-file method-identity indexing over a CRAP
|
|
3
|
-
* baseline's rows (Story #4981).
|
|
4
|
-
*
|
|
5
|
-
* Split out of `crap-engine.js` (rather than added inline) so the join
|
|
6
|
-
* support lands as a new file, not a same-file expansion of the module the
|
|
7
|
-
* scoring kernel already lives in. Deliberately dependency-free: both
|
|
8
|
-
* `crap-engine.js` (which needs `methodIdentityKey` inside
|
|
9
|
-
* `finalizeMethodRowsWithBaseline`) and `baselines/kinds/crap.js` (which
|
|
10
|
-
* needs the file-scoped index) import FROM here, and this module imports
|
|
11
|
-
* from neither — the only shape that keeps the edge one-directional.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Per-file half of the method-identity key `baselines/kinds/crap.js`'s
|
|
16
|
-
* `crapRowKey` composes with the file path (`${path}::${method}@${startLine}`).
|
|
17
|
-
* The path component is redundant once a row set is already narrowed to one
|
|
18
|
-
* file, which is exactly what `indexBaselineRowsByFile` does below.
|
|
19
|
-
*
|
|
20
|
-
* @param {{method: string, startLine: number}} row
|
|
21
|
-
* @returns {string}
|
|
22
|
-
*/
|
|
23
|
-
export function methodIdentityKey(row) {
|
|
24
|
-
return `${row.method}@${row.startLine}`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Index baseline rows (accepts either the `{file, method, startLine, crap}`
|
|
29
|
-
* legacy shape `compareCrap`/`scanAndScore` use, or the on-disk `{path, ...}`
|
|
30
|
-
* shape) by file, then by `methodIdentityKey`, for O(1) per-method lookup —
|
|
31
|
-
* exactly the shape `crap-engine.js#finalizeMethodRowsWithBaseline`'s
|
|
32
|
-
* `baselineByKey` expects.
|
|
33
|
-
*
|
|
34
|
-
* @param {Array<{file?: string, path?: string, method: string, startLine: number, crap: number}>} baselineRows
|
|
35
|
-
* @returns {Map<string, Map<string, {crap: number}>>} file → (method@startLine → row)
|
|
36
|
-
*/
|
|
37
|
-
export function indexBaselineRowsByFile(baselineRows) {
|
|
38
|
-
const byFile = new Map();
|
|
39
|
-
for (const row of baselineRows ?? []) {
|
|
40
|
-
const file = row?.file ?? row?.path;
|
|
41
|
-
if (typeof file !== 'string' || file.length === 0) continue;
|
|
42
|
-
if (!byFile.has(file)) byFile.set(file, new Map());
|
|
43
|
-
byFile.get(file).set(methodIdentityKey(row), row);
|
|
44
|
-
}
|
|
45
|
-
return byFile;
|
|
46
|
-
}
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* crap-utils-incremental.js — small pure helpers that wire the incremental
|
|
3
|
-
* CRAP join (Story #4981) into `crap-utils.js#scanAndScore`'s per-file work
|
|
4
|
-
* queue. Split into their own file so the wiring lands as new code rather
|
|
5
|
-
* than a same-file expansion of `scanAndScore` / `scoreFileSerial`.
|
|
6
|
-
*/
|
|
7
|
-
import { indexBaselineRowsByFile } from './crap-baseline-index.js';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Resolve `scanAndScore`'s `incremental` option into the two lookup
|
|
11
|
-
* structures the per-file queue build needs. Both are `null` when
|
|
12
|
-
* `incremental` is absent (full-scope, the default) — every downstream
|
|
13
|
-
* consumer treats a `null` context as "not incremental".
|
|
14
|
-
*
|
|
15
|
-
* @param {{ touchedFiles?: Set<string>|string[], baselineRows?: Array<object> } | null} incremental
|
|
16
|
-
* @returns {{ touchedFiles: Set<string>|null, baselineByFile: Map<string, Map<string, object>>|null }}
|
|
17
|
-
*/
|
|
18
|
-
export function resolveIncrementalContext(incremental) {
|
|
19
|
-
const touchedFiles = incremental?.touchedFiles
|
|
20
|
-
? incremental.touchedFiles instanceof Set
|
|
21
|
-
? incremental.touchedFiles
|
|
22
|
-
: new Set(incremental.touchedFiles)
|
|
23
|
-
: null;
|
|
24
|
-
const baselineByFile = incremental
|
|
25
|
-
? indexBaselineRowsByFile(incremental.baselineRows)
|
|
26
|
-
: null;
|
|
27
|
-
return { touchedFiles, baselineByFile };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Merge one queued file's `touched` flag and per-file `baselineByKey` map
|
|
32
|
-
* (resolved from the `resolveIncrementalContext` output) onto its base queue
|
|
33
|
-
* item. `touched` defaults to `true` (every file is "touched" outside
|
|
34
|
-
* incremental mode, matching
|
|
35
|
-
* `crap-baseline-join.js#finalizeMethodRowsWithBaseline`'s own default).
|
|
36
|
-
*
|
|
37
|
-
* @param {object} item Base queue item (`{ abs, relPath, requireCoverage, coverageAvailable }`).
|
|
38
|
-
* @param {{ touchedFiles: Set<string>|null, baselineByFile: Map<string, Map<string, object>>|null }} ctx
|
|
39
|
-
* @returns {object} `item` plus `{ touched, baselineByKey }`.
|
|
40
|
-
*/
|
|
41
|
-
export function resolveQueueIncrementalFields(
|
|
42
|
-
item,
|
|
43
|
-
{ touchedFiles, baselineByFile },
|
|
44
|
-
) {
|
|
45
|
-
const touched = touchedFiles ? touchedFiles.has(item.relPath) : true;
|
|
46
|
-
const baselineByKey = baselineByFile
|
|
47
|
-
? (baselineByFile.get(item.relPath) ?? new Map())
|
|
48
|
-
: null;
|
|
49
|
-
return { ...item, touched, baselineByKey };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* True when `scoreFileSerial` should resolve a file's methods from the
|
|
54
|
-
* baseline rather than requiring a fresh coverage entry — an untouched file
|
|
55
|
-
* with at least one indexed baseline row.
|
|
56
|
-
*
|
|
57
|
-
* @param {boolean} touched
|
|
58
|
-
* @param {Map<string, object>|null} baselineByKey
|
|
59
|
-
* @returns {boolean}
|
|
60
|
-
*/
|
|
61
|
-
function isIncrementalJoinActive(touched, baselineByKey) {
|
|
62
|
-
return !touched && baselineByKey != null && baselineByKey.size > 0;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* `scoreFileSerial`'s file-level skip decision, factored out whole so the
|
|
67
|
-
* incremental exception lives with the rest of this Story's branching
|
|
68
|
-
* rather than inflating the cyclomatic complexity of the pre-#4981 caller.
|
|
69
|
-
*
|
|
70
|
-
* @param {boolean} requireCoverage
|
|
71
|
-
* @param {object|null} entry Istanbul coverage entry for this file.
|
|
72
|
-
* @param {boolean} touched
|
|
73
|
-
* @param {Map<string, object>|null} baselineByKey
|
|
74
|
-
* @returns {boolean}
|
|
75
|
-
*/
|
|
76
|
-
export function shouldSkipFileForNoCoverage(
|
|
77
|
-
requireCoverage,
|
|
78
|
-
entry,
|
|
79
|
-
touched,
|
|
80
|
-
baselineByKey,
|
|
81
|
-
) {
|
|
82
|
-
return (
|
|
83
|
-
requireCoverage &&
|
|
84
|
-
entry === null &&
|
|
85
|
-
!isIncrementalJoinActive(touched, baselineByKey)
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* `scanAndScore`'s serial-vs-pool routing decision. Incremental mode always
|
|
91
|
-
* routes serial (see `isIncrementalJoinActive`'s doc for why the baseline
|
|
92
|
-
* lookup Maps don't cross the worker boundary); otherwise unchanged from the
|
|
93
|
-
* pre-#4981 queue-length cutover.
|
|
94
|
-
*
|
|
95
|
-
* @param {number} queueLength
|
|
96
|
-
* @param {unknown} incremental
|
|
97
|
-
* @param {number} serialThreshold
|
|
98
|
-
* @returns {boolean}
|
|
99
|
-
*/
|
|
100
|
-
export function shouldRunSerial(queueLength, incremental, serialThreshold) {
|
|
101
|
-
return queueLength < serialThreshold || Boolean(incremental);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Project the `resolvedFromBaseline` marker onto a `scanAndScore` result row
|
|
106
|
-
* — present only when true, so a full-scope scan's rows are unaffected.
|
|
107
|
-
*
|
|
108
|
-
* @param {{ resolvedFromBaseline?: boolean }} mr
|
|
109
|
-
* @returns {{ resolvedFromBaseline: true } | {}}
|
|
110
|
-
*/
|
|
111
|
-
export function resolvedFromBaselineFlag(mr) {
|
|
112
|
-
return mr.resolvedFromBaseline === true ? { resolvedFromBaseline: true } : {};
|
|
113
|
-
}
|
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
// .agents/scripts/lib/dynamic-workflow/capability.js
|
|
2
|
-
/**
|
|
3
|
-
* Dynamic-workflow capability detection + audit-lens strategy selection.
|
|
4
|
-
*
|
|
5
|
-
* Originated as the `audit-clean-code` dynamic-workflow pilot (Story #3278)
|
|
6
|
-
* and generalized to five read-only, dimensionally-decomposable lenses
|
|
7
|
-
* (`audit-clean-code`, `audit-security`, `audit-performance`,
|
|
8
|
-
* `audit-architecture`, `audit-quality`) under Epic #3597. The one-shot pilot
|
|
9
|
-
* doc has been retired; this module docstring is the **canonical home for the
|
|
10
|
-
* capability-degradation rationale** (the "Why this is not a contract shim"
|
|
11
|
-
* section below), and `docs/roadmap.md` (Part 3 — Dynamic-Workflow
|
|
12
|
-
* Orchestration) holds the orchestrated-run evidence and per-lens
|
|
13
|
-
* cost/precision gate verdicts.
|
|
14
|
-
*
|
|
15
|
-
* Claude Code's [dynamic workflows](https://code.claude.com/docs/en/workflows)
|
|
16
|
-
* let a JS script orchestrate many subagents in the background. They are a
|
|
17
|
-
* Claude Code-only, paid-plan, research-preview feature (CC >= 2.1.154). Each
|
|
18
|
-
* generalized lens runs along **two execution paths**:
|
|
19
|
-
*
|
|
20
|
-
* 1. **orchestrated** — the per-lens dynamic-workflow script
|
|
21
|
-
* (`.claude/workflows/<lens>.workflow.js`) fans the lens dimensions out
|
|
22
|
-
* as parallel subagents with an adversarial cross-check stage, then emits
|
|
23
|
-
* the report contract.
|
|
24
|
-
* 2. **sequential** — the existing single-pass lens
|
|
25
|
-
* (`.agents/workflows/<lens>.md`) followed turn-by-turn.
|
|
26
|
-
*
|
|
27
|
-
* Both paths MUST emit the identical per-lens report contract
|
|
28
|
-
* (`{{auditOutputDir}}/<lens>-results.md`), so downstream consumers
|
|
29
|
-
* (`/deliver` Phase 4 epic-audit, `audit-to-stories`) are agnostic to
|
|
30
|
-
* which path produced it.
|
|
31
|
-
*
|
|
32
|
-
* ## Why this is capability-degradation, not a contract shim
|
|
33
|
-
*
|
|
34
|
-
* The No-Shim / hard-cutover rule in `.agents/rules/git-conventions.md`
|
|
35
|
-
* governs *contract version* changes (config / schema / lifecycle shape) and
|
|
36
|
-
* forbids running two shapes of the **same contract** side by side. This
|
|
37
|
-
* module does the opposite: it keeps **one** report contract and selects an
|
|
38
|
-
* **execution strategy** from a runtime capability snapshot — the same
|
|
39
|
-
* pattern the protocol already endorses in `.agents/instructions.md`
|
|
40
|
-
* §1.C/§1.D (live-docs → in-repo → web fallback order). There is no second
|
|
41
|
-
* report shape, no version-windowed sunset, and no legacy reader to delete
|
|
42
|
-
* later.
|
|
43
|
-
*
|
|
44
|
-
* This module is **pure** — `detectDynamicWorkflowCapability` reads an
|
|
45
|
-
* injected environment snapshot (never `process` / `fs` directly) and
|
|
46
|
-
* `selectAuditStrategy` is a pure function of that snapshot. The split keeps
|
|
47
|
-
* strategy selection unit-testable without a live Claude Code runtime.
|
|
48
|
-
*
|
|
49
|
-
* @module dynamic-workflow/capability
|
|
50
|
-
*/
|
|
51
|
-
|
|
52
|
-
// ---------------------------------------------------------------------------
|
|
53
|
-
// Constants
|
|
54
|
-
// ---------------------------------------------------------------------------
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Minimum Claude Code version that ships the dynamic-workflow runtime
|
|
58
|
-
* (research preview). Below this floor the orchestrated path is unavailable
|
|
59
|
-
* even on a paid Claude Code runtime.
|
|
60
|
-
*
|
|
61
|
-
* Source: https://code.claude.com/docs/en/workflows ("require Claude Code
|
|
62
|
-
* v2.1.154 or later").
|
|
63
|
-
*/
|
|
64
|
-
export const DYNAMIC_WORKFLOW_VERSION_FLOOR = '2.1.154';
|
|
65
|
-
|
|
66
|
-
/** The two execution strategies the lens can run under. */
|
|
67
|
-
export const AUDIT_STRATEGY = Object.freeze({
|
|
68
|
-
ORCHESTRATED: 'orchestrated',
|
|
69
|
-
SEQUENTIAL: 'sequential',
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Environment-variable names consulted when a caller does not inject an
|
|
74
|
-
* explicit snapshot. Kept as constants so the docs / tests reference the
|
|
75
|
-
* exact keys rather than hard-coding the strings.
|
|
76
|
-
*/
|
|
77
|
-
export const ENV_KEYS = Object.freeze({
|
|
78
|
-
/** Hard kill-switch documented at code.claude.com/docs/en/workflows. */
|
|
79
|
-
DISABLE: 'CLAUDE_CODE_DISABLE_WORKFLOWS',
|
|
80
|
-
/** Test/operator force-override: `orchestrated` | `sequential`. */
|
|
81
|
-
FORCE_STRATEGY: 'MANDREL_AUDIT_STRATEGY',
|
|
82
|
-
/** Runtime identity (`claude-code` when running under Claude Code). */
|
|
83
|
-
RUNTIME: 'CLAUDE_CODE_RUNTIME',
|
|
84
|
-
/** Claude Code version string (e.g. `2.1.160`). */
|
|
85
|
-
VERSION: 'CLAUDE_CODE_VERSION',
|
|
86
|
-
/** Entitlement hint (`pro` | `max` | `team` | `enterprise` | `api`). */
|
|
87
|
-
PLAN: 'CLAUDE_CODE_PLAN',
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
/** Reason codes attached to a strategy decision for observability. */
|
|
91
|
-
export const DECISION_REASON = Object.freeze({
|
|
92
|
-
FORCED_ORCHESTRATED: 'forced-orchestrated',
|
|
93
|
-
FORCED_SEQUENTIAL: 'forced-sequential',
|
|
94
|
-
CAPABILITY_PRESENT: 'capability-present',
|
|
95
|
-
NOT_CLAUDE_RUNTIME: 'not-claude-runtime',
|
|
96
|
-
DISABLED_SETTING: 'disabled-setting',
|
|
97
|
-
DISABLED_ENV: 'disabled-env',
|
|
98
|
-
VERSION_BELOW_FLOOR: 'version-below-floor',
|
|
99
|
-
UNPAID_PLAN: 'unpaid-plan',
|
|
100
|
-
RUNTIME_UNKNOWN: 'runtime-unknown',
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
// Truthy env-flag values (mirrors how Claude Code reads boolean env flags:
|
|
104
|
-
// the var is "set" when present and not an explicit falsey token).
|
|
105
|
-
const FALSEY_FLAG = new Set(['', '0', 'false', 'off', 'no']);
|
|
106
|
-
|
|
107
|
-
// Paid plan/entitlement tokens that unlock dynamic workflows.
|
|
108
|
-
const PAID_PLANS = new Set(['pro', 'max', 'team', 'enterprise', 'api']);
|
|
109
|
-
|
|
110
|
-
// ---------------------------------------------------------------------------
|
|
111
|
-
// Helpers
|
|
112
|
-
// ---------------------------------------------------------------------------
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Is an environment flag "set" in the truthy sense Claude Code uses for
|
|
116
|
-
* `CLAUDE_CODE_DISABLE_WORKFLOWS`? Present and not an explicit falsey token.
|
|
117
|
-
*
|
|
118
|
-
* @param {string|undefined|null} raw
|
|
119
|
-
* @returns {boolean}
|
|
120
|
-
*/
|
|
121
|
-
export function isFlagSet(raw) {
|
|
122
|
-
if (raw === undefined || raw === null) return false;
|
|
123
|
-
return !FALSEY_FLAG.has(String(raw).trim().toLowerCase());
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Compare two dotted numeric version strings. Returns a negative number when
|
|
128
|
-
* `a < b`, zero when equal, positive when `a > b`. Non-numeric / missing
|
|
129
|
-
* segments are treated as 0. Pre-release suffixes (e.g. `-rc1`) are ignored.
|
|
130
|
-
*
|
|
131
|
-
* @param {string} a
|
|
132
|
-
* @param {string} b
|
|
133
|
-
* @returns {number}
|
|
134
|
-
*/
|
|
135
|
-
export function compareVersions(a, b) {
|
|
136
|
-
const norm = (v) =>
|
|
137
|
-
String(v ?? '')
|
|
138
|
-
.trim()
|
|
139
|
-
.replace(/^v/i, '')
|
|
140
|
-
.split('-')[0]
|
|
141
|
-
.split('.')
|
|
142
|
-
.map((seg) => {
|
|
143
|
-
const n = Number.parseInt(seg, 10);
|
|
144
|
-
return Number.isNaN(n) ? 0 : n;
|
|
145
|
-
});
|
|
146
|
-
const av = norm(a);
|
|
147
|
-
const bv = norm(b);
|
|
148
|
-
const len = Math.max(av.length, bv.length);
|
|
149
|
-
for (let i = 0; i < len; i += 1) {
|
|
150
|
-
const diff = (av[i] ?? 0) - (bv[i] ?? 0);
|
|
151
|
-
if (diff !== 0) return diff;
|
|
152
|
-
}
|
|
153
|
-
return 0;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Does `version` meet or exceed the dynamic-workflow floor?
|
|
158
|
-
*
|
|
159
|
-
* @param {string} version
|
|
160
|
-
* @param {string} [floor=DYNAMIC_WORKFLOW_VERSION_FLOOR]
|
|
161
|
-
* @returns {boolean}
|
|
162
|
-
*/
|
|
163
|
-
export function meetsVersionFloor(
|
|
164
|
-
version,
|
|
165
|
-
floor = DYNAMIC_WORKFLOW_VERSION_FLOOR,
|
|
166
|
-
) {
|
|
167
|
-
if (!version) return false;
|
|
168
|
-
return compareVersions(version, floor) >= 0;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// ---------------------------------------------------------------------------
|
|
172
|
-
// Capability detection
|
|
173
|
-
// ---------------------------------------------------------------------------
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* @typedef {object} CapabilitySnapshot
|
|
177
|
-
* @property {string|undefined} [runtime] - Runtime identity (`claude-code` …).
|
|
178
|
-
* @property {string|undefined} [version] - Claude Code version string.
|
|
179
|
-
* @property {string|undefined} [plan] - Plan/entitlement token.
|
|
180
|
-
* @property {boolean} [disableWorkflowsSetting] - `disableWorkflows` from settings.json.
|
|
181
|
-
* @property {string|undefined} [disableWorkflowsEnv] - Raw env value of CLAUDE_CODE_DISABLE_WORKFLOWS.
|
|
182
|
-
*/
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* @typedef {object} CapabilityResult
|
|
186
|
-
* @property {boolean} available - Whether the orchestrated path can run.
|
|
187
|
-
* @property {string} reason - One of {@link DECISION_REASON}.
|
|
188
|
-
* @property {object} detail - Echoed snapshot facts for logging.
|
|
189
|
-
*/
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* Detect whether the dynamic-workflow orchestrated path is available, from an
|
|
193
|
-
* **injected** environment snapshot. The function is pure: callers resolve
|
|
194
|
-
* `process.env` / settings.json and pass the facts in, so the decision is
|
|
195
|
-
* reproducible in tests without a live runtime.
|
|
196
|
-
*
|
|
197
|
-
* Detection accounts for, in priority order:
|
|
198
|
-
* 1. Non-Claude runtime → unavailable (`not-claude-runtime`).
|
|
199
|
-
* 2. `disableWorkflows: true` in settings → unavailable (`disabled-setting`).
|
|
200
|
-
* 3. `CLAUDE_CODE_DISABLE_WORKFLOWS` set → unavailable (`disabled-env`).
|
|
201
|
-
* 4. CC version below {@link DYNAMIC_WORKFLOW_VERSION_FLOOR} → unavailable.
|
|
202
|
-
* 5. Non-paid plan (when the plan token is known) → unavailable.
|
|
203
|
-
* 6. Otherwise → available.
|
|
204
|
-
*
|
|
205
|
-
* When the runtime identifies as Claude Code but supplies no version, the
|
|
206
|
-
* function fails **closed** (`runtime-unknown`, unavailable): an undetectable
|
|
207
|
-
* version cannot be assumed to clear the research-preview floor, and the
|
|
208
|
-
* sequential lens is always a safe fallback.
|
|
209
|
-
*
|
|
210
|
-
* @param {CapabilitySnapshot} snapshot
|
|
211
|
-
* @returns {CapabilityResult}
|
|
212
|
-
*/
|
|
213
|
-
export function detectDynamicWorkflowCapability(snapshot = {}) {
|
|
214
|
-
const {
|
|
215
|
-
runtime,
|
|
216
|
-
version,
|
|
217
|
-
plan,
|
|
218
|
-
disableWorkflowsSetting,
|
|
219
|
-
disableWorkflowsEnv,
|
|
220
|
-
} = snapshot;
|
|
221
|
-
|
|
222
|
-
const detail = {
|
|
223
|
-
runtime: runtime ?? null,
|
|
224
|
-
version: version ?? null,
|
|
225
|
-
plan: plan ?? null,
|
|
226
|
-
disableWorkflowsSetting: Boolean(disableWorkflowsSetting),
|
|
227
|
-
disableWorkflowsEnv: isFlagSet(disableWorkflowsEnv),
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
const isClaudeRuntime =
|
|
231
|
-
typeof runtime === 'string' && /claude[-_ ]?code/i.test(runtime);
|
|
232
|
-
if (!isClaudeRuntime) {
|
|
233
|
-
return {
|
|
234
|
-
available: false,
|
|
235
|
-
reason: DECISION_REASON.NOT_CLAUDE_RUNTIME,
|
|
236
|
-
detail,
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
if (disableWorkflowsSetting === true) {
|
|
241
|
-
return {
|
|
242
|
-
available: false,
|
|
243
|
-
reason: DECISION_REASON.DISABLED_SETTING,
|
|
244
|
-
detail,
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
if (isFlagSet(disableWorkflowsEnv)) {
|
|
249
|
-
return { available: false, reason: DECISION_REASON.DISABLED_ENV, detail };
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
if (!version) {
|
|
253
|
-
// Claude Code runtime but no resolvable version → fail closed.
|
|
254
|
-
return {
|
|
255
|
-
available: false,
|
|
256
|
-
reason: DECISION_REASON.RUNTIME_UNKNOWN,
|
|
257
|
-
detail,
|
|
258
|
-
};
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
if (!meetsVersionFloor(version)) {
|
|
262
|
-
return {
|
|
263
|
-
available: false,
|
|
264
|
-
reason: DECISION_REASON.VERSION_BELOW_FLOOR,
|
|
265
|
-
detail,
|
|
266
|
-
};
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
// Plan is only a gate when we actually know it. An absent plan token does
|
|
270
|
-
// not block — the runtime will refuse the launch itself if unentitled, and
|
|
271
|
-
// the sequential fallback covers that case.
|
|
272
|
-
if (typeof plan === 'string' && plan.trim().length > 0) {
|
|
273
|
-
if (!PAID_PLANS.has(plan.trim().toLowerCase())) {
|
|
274
|
-
return { available: false, reason: DECISION_REASON.UNPAID_PLAN, detail };
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
return {
|
|
279
|
-
available: true,
|
|
280
|
-
reason: DECISION_REASON.CAPABILITY_PRESENT,
|
|
281
|
-
detail,
|
|
282
|
-
};
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// ---------------------------------------------------------------------------
|
|
286
|
-
// Strategy selection
|
|
287
|
-
// ---------------------------------------------------------------------------
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* @typedef {object} StrategyDecision
|
|
291
|
-
* @property {('orchestrated'|'sequential')} strategy - The chosen execution path.
|
|
292
|
-
* @property {string} reason - One of {@link DECISION_REASON}.
|
|
293
|
-
* @property {boolean} forced - True when an explicit override drove the choice.
|
|
294
|
-
* @property {object} capability - The {@link CapabilityResult} that informed it.
|
|
295
|
-
*/
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Select the audit execution strategy for `audit-clean-code` from a capability
|
|
299
|
-
* snapshot and an optional explicit force-override.
|
|
300
|
-
*
|
|
301
|
-
* Decision order:
|
|
302
|
-
* 1. `forceStrategy === 'orchestrated'` → orchestrated (forced). Used to
|
|
303
|
-
* pin the dynamic path for testing even if detection is conservative;
|
|
304
|
-
* the runtime still refuses the launch if genuinely unentitled.
|
|
305
|
-
* 2. `forceStrategy === 'sequential'` → sequential (forced). Used to verify
|
|
306
|
-
* the fallback path (Acceptance: "running the lens with the feature
|
|
307
|
-
* disabled") without mutating the real environment.
|
|
308
|
-
* 3. Capability available → orchestrated.
|
|
309
|
-
* 4. Otherwise → sequential (graceful degradation).
|
|
310
|
-
*
|
|
311
|
-
* Pure function — no I/O, no side effects.
|
|
312
|
-
*
|
|
313
|
-
* @param {object} input
|
|
314
|
-
* @param {CapabilitySnapshot} [input.snapshot] - Environment facts.
|
|
315
|
-
* @param {('orchestrated'|'sequential'|null|undefined)} [input.forceStrategy] - Explicit override.
|
|
316
|
-
* @returns {StrategyDecision}
|
|
317
|
-
*/
|
|
318
|
-
export function selectAuditStrategy({ snapshot = {}, forceStrategy } = {}) {
|
|
319
|
-
const capability = detectDynamicWorkflowCapability(snapshot);
|
|
320
|
-
|
|
321
|
-
const normalizedForce =
|
|
322
|
-
typeof forceStrategy === 'string'
|
|
323
|
-
? forceStrategy.trim().toLowerCase()
|
|
324
|
-
: null;
|
|
325
|
-
|
|
326
|
-
if (normalizedForce === AUDIT_STRATEGY.ORCHESTRATED) {
|
|
327
|
-
return {
|
|
328
|
-
strategy: AUDIT_STRATEGY.ORCHESTRATED,
|
|
329
|
-
reason: DECISION_REASON.FORCED_ORCHESTRATED,
|
|
330
|
-
forced: true,
|
|
331
|
-
capability,
|
|
332
|
-
};
|
|
333
|
-
}
|
|
334
|
-
if (normalizedForce === AUDIT_STRATEGY.SEQUENTIAL) {
|
|
335
|
-
return {
|
|
336
|
-
strategy: AUDIT_STRATEGY.SEQUENTIAL,
|
|
337
|
-
reason: DECISION_REASON.FORCED_SEQUENTIAL,
|
|
338
|
-
forced: true,
|
|
339
|
-
capability,
|
|
340
|
-
};
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
if (capability.available) {
|
|
344
|
-
return {
|
|
345
|
-
strategy: AUDIT_STRATEGY.ORCHESTRATED,
|
|
346
|
-
reason: capability.reason,
|
|
347
|
-
forced: false,
|
|
348
|
-
capability,
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
return {
|
|
353
|
-
strategy: AUDIT_STRATEGY.SEQUENTIAL,
|
|
354
|
-
reason: capability.reason,
|
|
355
|
-
forced: false,
|
|
356
|
-
capability,
|
|
357
|
-
};
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
/**
|
|
361
|
-
* Build a {@link CapabilitySnapshot} from a raw environment bag (typically
|
|
362
|
-
* `process.env`) plus a resolved `disableWorkflows` settings value. Kept
|
|
363
|
-
* separate from `process.env` access so it stays testable; the caller decides
|
|
364
|
-
* where the env comes from.
|
|
365
|
-
*
|
|
366
|
-
* @param {Record<string,string|undefined>} env
|
|
367
|
-
* @param {{ disableWorkflows?: boolean }} [settings]
|
|
368
|
-
* @returns {CapabilitySnapshot}
|
|
369
|
-
*/
|
|
370
|
-
export function snapshotFromEnv(env = {}, settings = {}) {
|
|
371
|
-
return {
|
|
372
|
-
runtime: env[ENV_KEYS.RUNTIME],
|
|
373
|
-
version: env[ENV_KEYS.VERSION],
|
|
374
|
-
plan: env[ENV_KEYS.PLAN],
|
|
375
|
-
disableWorkflowsSetting: settings.disableWorkflows === true,
|
|
376
|
-
disableWorkflowsEnv: env[ENV_KEYS.DISABLE],
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
/**
|
|
381
|
-
* Read the operator/test force-override from a raw environment bag. Returns
|
|
382
|
-
* `'orchestrated'` / `'sequential'` when the value is a recognised strategy,
|
|
383
|
-
* else `null`.
|
|
384
|
-
*
|
|
385
|
-
* @param {Record<string,string|undefined>} env
|
|
386
|
-
* @returns {('orchestrated'|'sequential'|null)}
|
|
387
|
-
*/
|
|
388
|
-
export function forceStrategyFromEnv(env = {}) {
|
|
389
|
-
const raw = env[ENV_KEYS.FORCE_STRATEGY];
|
|
390
|
-
if (typeof raw !== 'string') return null;
|
|
391
|
-
const v = raw.trim().toLowerCase();
|
|
392
|
-
if (v === AUDIT_STRATEGY.ORCHESTRATED || v === AUDIT_STRATEGY.SEQUENTIAL) {
|
|
393
|
-
return v;
|
|
394
|
-
}
|
|
395
|
-
return null;
|
|
396
|
-
}
|