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
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bdd-step-index.js — scoped discovery and matching for BDD step definitions.
|
|
3
|
+
*
|
|
4
|
+
* `check-gherkin-corpus.js` answers one question per Gherkin step: does a step
|
|
5
|
+
* definition **under this step's own scope** claim it? That question has two
|
|
6
|
+
* halves, and this module owns both so the corpus gate never re-implements
|
|
7
|
+
* either inline:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Discovery** — which files under a scope's `stepRoots` hold step
|
|
10
|
+
* definitions, and which `.feature` files sit under its `featureRoots`.
|
|
11
|
+
* Feature discovery is delegated to `listFeatureFiles` in
|
|
12
|
+
* `bdd-scenario-scanner.js` rather than copied: /plan's scenario index and
|
|
13
|
+
* this gate must agree on what counts as a feature file, and two walkers
|
|
14
|
+
* would eventually disagree.
|
|
15
|
+
* 2. **Matching** — turn each definition's Cucumber expression or regular
|
|
16
|
+
* expression into one `RegExp`, and test a step's text against the index.
|
|
17
|
+
*
|
|
18
|
+
* ## Heuristic index, exact parser
|
|
19
|
+
*
|
|
20
|
+
* The parser half of the gate is exact: `@cucumber/gherkin` decides what
|
|
21
|
+
* compiles. This half is deliberately **not**. Reading step definitions
|
|
22
|
+
* without executing them means a regex scan over source text, and a scan
|
|
23
|
+
* cannot see a definition assembled at runtime, registered through a wrapper,
|
|
24
|
+
* or parameterised by a custom `defineParameterType`. That asymmetry is why
|
|
25
|
+
* the gate ships a step-waiver list: a false "unbound" must always have an
|
|
26
|
+
* escape that does not require switching the whole gate off.
|
|
27
|
+
*
|
|
28
|
+
* Supported Cucumber-expression constructs are the ones the built-in parameter
|
|
29
|
+
* types and the optional/alternation syntax cover — `{int}`, `{float}`,
|
|
30
|
+
* `{word}`, `{string}`, an anonymous `{}`, `text(s)` optionals, and `a/an`
|
|
31
|
+
* word alternation. An unrecognised `{custom}` degrades to `(.*)` rather than
|
|
32
|
+
* failing to match, because over-matching produces a missed finding while
|
|
33
|
+
* under-matching produces a false one, and a false one blocks a delivery.
|
|
34
|
+
*
|
|
35
|
+
* Nothing here reads configuration or exits a process; the CLI owns both.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs';
|
|
39
|
+
import path from 'node:path';
|
|
40
|
+
|
|
41
|
+
import { listFeatureFiles } from './bdd-scenario-scanner.js';
|
|
42
|
+
|
|
43
|
+
export { listFeatureFiles };
|
|
44
|
+
|
|
45
|
+
/** Extensions a step-definition module may carry. */
|
|
46
|
+
const STEP_FILE_EXTENSIONS = Object.freeze([
|
|
47
|
+
'.js',
|
|
48
|
+
'.mjs',
|
|
49
|
+
'.cjs',
|
|
50
|
+
'.ts',
|
|
51
|
+
'.mts',
|
|
52
|
+
'.cts',
|
|
53
|
+
'.tsx',
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
/** Directory names never walked when looking for step definitions. */
|
|
57
|
+
const SKIPPED_DIRECTORIES = Object.freeze(
|
|
58
|
+
new Set(['node_modules', '.git', 'dist', 'build', '.next', 'coverage']),
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Regular expressions for the built-in Cucumber parameter types, keyed by the
|
|
63
|
+
* name inside the braces. The empty key is the anonymous `{}` parameter and
|
|
64
|
+
* doubles as the fallback for a custom type this scan cannot resolve.
|
|
65
|
+
*
|
|
66
|
+
* @type {Readonly<Record<string, string>>}
|
|
67
|
+
*/
|
|
68
|
+
const PARAMETER_PATTERNS = Object.freeze({
|
|
69
|
+
'': '(.*)',
|
|
70
|
+
int: '(-?\\d+)',
|
|
71
|
+
float: '(-?\\d*\\.?\\d+)',
|
|
72
|
+
word: '([^\\s]+)',
|
|
73
|
+
string: '("[^"]*"|\'[^\']*\')',
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Step-registration call sites this scan recognises. `Step` and `defineStep`
|
|
78
|
+
* cover the generic registrars playwright-bdd and cucumber-js both expose.
|
|
79
|
+
*
|
|
80
|
+
* Group 2/3 capture a quoted expression, group 4/5 a regular-expression
|
|
81
|
+
* literal with its flags.
|
|
82
|
+
*/
|
|
83
|
+
const STEP_CALL_PATTERN =
|
|
84
|
+
/\b(Given|When|Then|And|But|Step|defineStep)\s*\(\s*(?:(['"`])((?:\\.|(?!\2)[^\\])*)\2|\/((?:\\.|\[(?:\\.|[^\]\\])*\]|[^/\\[])+)\/([dgimsuvy]*))/g;
|
|
85
|
+
|
|
86
|
+
/** Escape one character for literal use inside a regular expression. */
|
|
87
|
+
function escapeRegExp(text) {
|
|
88
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Render one alternation-free segment of a Cucumber expression: parameter
|
|
93
|
+
* placeholders become capture groups, `(optional)` runs become optional
|
|
94
|
+
* groups, and everything else is escaped literal text.
|
|
95
|
+
*
|
|
96
|
+
* @param {string} segment
|
|
97
|
+
* @returns {string}
|
|
98
|
+
*/
|
|
99
|
+
function renderSegment(segment) {
|
|
100
|
+
let out = '';
|
|
101
|
+
let i = 0;
|
|
102
|
+
while (i < segment.length) {
|
|
103
|
+
const ch = segment[i];
|
|
104
|
+
if (ch === '\\' && i + 1 < segment.length) {
|
|
105
|
+
out += escapeRegExp(segment[i + 1]);
|
|
106
|
+
i += 2;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (ch === '{') {
|
|
110
|
+
const end = segment.indexOf('}', i);
|
|
111
|
+
if (end !== -1) {
|
|
112
|
+
const name = segment.slice(i + 1, end);
|
|
113
|
+
out += PARAMETER_PATTERNS[name] ?? PARAMETER_PATTERNS[''];
|
|
114
|
+
i = end + 1;
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
if (ch === '(') {
|
|
119
|
+
const end = segment.indexOf(')', i);
|
|
120
|
+
if (end !== -1) {
|
|
121
|
+
out += `(?:${renderSegment(segment.slice(i + 1, end))})?`;
|
|
122
|
+
i = end + 1;
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
out += escapeRegExp(ch);
|
|
127
|
+
i += 1;
|
|
128
|
+
}
|
|
129
|
+
return out;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Split a word on unescaped `/` so `a/an` becomes two alternatives. Escaped
|
|
134
|
+
* `\/` stays literal.
|
|
135
|
+
*
|
|
136
|
+
* @param {string} word
|
|
137
|
+
* @returns {string[]}
|
|
138
|
+
*/
|
|
139
|
+
function splitAlternation(word) {
|
|
140
|
+
const parts = [];
|
|
141
|
+
let current = '';
|
|
142
|
+
for (let i = 0; i < word.length; i += 1) {
|
|
143
|
+
const ch = word[i];
|
|
144
|
+
if (ch === '\\' && i + 1 < word.length) {
|
|
145
|
+
current += ch + word[i + 1];
|
|
146
|
+
i += 1;
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (ch === '/') {
|
|
150
|
+
parts.push(current);
|
|
151
|
+
current = '';
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
current += ch;
|
|
155
|
+
}
|
|
156
|
+
parts.push(current);
|
|
157
|
+
return parts;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Compile a Cucumber expression into an anchored `RegExp`.
|
|
162
|
+
*
|
|
163
|
+
* Alternation is resolved per whitespace-separated word, which is what the
|
|
164
|
+
* real expression grammar does. Resolving it across the whole string instead
|
|
165
|
+
* would turn `I have a/an apple` into `^I have a|an apple$` — two anchored
|
|
166
|
+
* alternatives, neither of them the intended step.
|
|
167
|
+
*
|
|
168
|
+
* @param {string} expression
|
|
169
|
+
* @returns {RegExp}
|
|
170
|
+
*/
|
|
171
|
+
function expressionToRegExp(expression) {
|
|
172
|
+
const tokens = String(expression).split(/(\s+)/);
|
|
173
|
+
const body = tokens
|
|
174
|
+
.map((token) => {
|
|
175
|
+
if (token.length === 0) return '';
|
|
176
|
+
if (/^\s+$/.test(token)) return escapeRegExp(token);
|
|
177
|
+
const alternatives = splitAlternation(token);
|
|
178
|
+
const rendered = alternatives.map(renderSegment);
|
|
179
|
+
return rendered.length > 1 ? `(?:${rendered.join('|')})` : rendered[0];
|
|
180
|
+
})
|
|
181
|
+
.join('');
|
|
182
|
+
return new RegExp(`^${body}$`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Recursively list step-definition source files under the given roots.
|
|
187
|
+
*
|
|
188
|
+
* Deliberately not `walkFilesByExtension` from `fs-walk.js`, which is the
|
|
189
|
+
* shared walker for the lint surfaces. That one matches a single extension and
|
|
190
|
+
* walks everything below the root, so reusing it here would mean seven passes
|
|
191
|
+
* — one per accepted extension — each of them descending into a `node_modules`
|
|
192
|
+
* a consumer's step root may well contain. It also rethrows every non-ENOENT
|
|
193
|
+
* `readdir` failure, where this walker must skip an unreadable directory: an
|
|
194
|
+
* unreadable *scope* has to surface as "zero step definitions", the gate's
|
|
195
|
+
* fail-closed path, which names the scope and its step roots.
|
|
196
|
+
*
|
|
197
|
+
* @param {string[]} roots absolute or cwd-relative directories
|
|
198
|
+
* @returns {string[]} absolute paths, sorted
|
|
199
|
+
*/
|
|
200
|
+
export function listStepFiles(roots) {
|
|
201
|
+
const found = [];
|
|
202
|
+
for (const root of roots ?? []) {
|
|
203
|
+
walkStepDir(path.resolve(root), found);
|
|
204
|
+
}
|
|
205
|
+
return found.sort();
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function walkStepDir(dir, acc) {
|
|
209
|
+
let entries;
|
|
210
|
+
try {
|
|
211
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
212
|
+
} catch {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
for (const entry of entries) {
|
|
216
|
+
const full = path.join(dir, entry.name);
|
|
217
|
+
// `withFileTypes` reports a symlink as neither file nor directory, so
|
|
218
|
+
// fall back to a stat for those rather than dropping them silently.
|
|
219
|
+
const isDir =
|
|
220
|
+
entry.isDirectory() || (entry.isSymbolicLink() && isDirAt(full));
|
|
221
|
+
if (isDir) {
|
|
222
|
+
if (!SKIPPED_DIRECTORIES.has(entry.name)) walkStepDir(full, acc);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
if (STEP_FILE_EXTENSIONS.includes(path.extname(entry.name))) acc.push(full);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function isDirAt(target) {
|
|
230
|
+
try {
|
|
231
|
+
return statSync(target).isDirectory();
|
|
232
|
+
} catch {
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Extract every step registration in one source file.
|
|
239
|
+
*
|
|
240
|
+
* @param {string} source file contents
|
|
241
|
+
* @param {string} file absolute path, recorded on each entry
|
|
242
|
+
* @returns {Array<{ file: string, line: number, source: string, regex: RegExp }>}
|
|
243
|
+
*/
|
|
244
|
+
function parseStepDefinitions(source, file) {
|
|
245
|
+
const entries = [];
|
|
246
|
+
STEP_CALL_PATTERN.lastIndex = 0;
|
|
247
|
+
let match = STEP_CALL_PATTERN.exec(source);
|
|
248
|
+
while (match !== null) {
|
|
249
|
+
const [, , , quoted, pattern, flags] = match;
|
|
250
|
+
const line = source.slice(0, match.index).split('\n').length;
|
|
251
|
+
const regex = compileMatcher({ quoted, pattern, flags });
|
|
252
|
+
if (regex) entries.push({ file, line, source: quoted ?? pattern, regex });
|
|
253
|
+
match = STEP_CALL_PATTERN.exec(source);
|
|
254
|
+
}
|
|
255
|
+
return entries;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Build one matcher from a captured registration. A malformed regular
|
|
260
|
+
* expression yields `null` — the definition is skipped rather than crashing
|
|
261
|
+
* the scan, and the steps it would have claimed surface as unbound, which is
|
|
262
|
+
* the safe direction.
|
|
263
|
+
*/
|
|
264
|
+
function compileMatcher({ quoted, pattern, flags }) {
|
|
265
|
+
if (typeof quoted === 'string') {
|
|
266
|
+
try {
|
|
267
|
+
return expressionToRegExp(quoted);
|
|
268
|
+
} catch {
|
|
269
|
+
return null;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
// `g` and `y` are stateful across `.test()` calls; strip them so the index
|
|
274
|
+
// cannot depend on how many times it has been consulted.
|
|
275
|
+
return new RegExp(pattern, (flags ?? '').replace(/[gy]/g, ''));
|
|
276
|
+
} catch {
|
|
277
|
+
return null;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Build the step index for one scope.
|
|
283
|
+
*
|
|
284
|
+
* @param {{ files: string[], readFile?: (p: string) => string }} params
|
|
285
|
+
* @returns {{ entries: Array<{ file: string, line: number, source: string, regex: RegExp }>, files: string[] }}
|
|
286
|
+
*/
|
|
287
|
+
export function buildStepIndex({ files, readFile }) {
|
|
288
|
+
const read = readFile ?? ((p) => readFileSync(p, 'utf8'));
|
|
289
|
+
const entries = [];
|
|
290
|
+
for (const file of files ?? []) {
|
|
291
|
+
let source;
|
|
292
|
+
try {
|
|
293
|
+
source = read(file);
|
|
294
|
+
} catch {
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
entries.push(...parseStepDefinitions(source, file));
|
|
298
|
+
}
|
|
299
|
+
return { entries, files: [...(files ?? [])] };
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Find the first definition in the index claiming `text`.
|
|
304
|
+
*
|
|
305
|
+
* @param {{ entries: Array<{ regex: RegExp }> }} index
|
|
306
|
+
* @param {string} text the step text, keyword already stripped by the parser
|
|
307
|
+
* @returns {object | null} the matching entry, or `null` when nothing claims it
|
|
308
|
+
*/
|
|
309
|
+
export function matchStep(index, text) {
|
|
310
|
+
for (const entry of index?.entries ?? []) {
|
|
311
|
+
if (entry.regex.test(text)) return entry;
|
|
312
|
+
}
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Module-private helpers the suite drives directly. Bundled rather than
|
|
318
|
+
* exported individually — the same seam `knip-entry-sync.js` and
|
|
319
|
+
* `source-classifier.js` use — so test-only symbols cost one production
|
|
320
|
+
* dead-export row instead of one each, and so private helpers do not read as
|
|
321
|
+
* API.
|
|
322
|
+
*/
|
|
323
|
+
export const __testing = Object.freeze({
|
|
324
|
+
expressionToRegExp,
|
|
325
|
+
parseStepDefinitions,
|
|
326
|
+
});
|
|
@@ -105,9 +105,11 @@ export function ledgerPath(projectRoot) {
|
|
|
105
105
|
*
|
|
106
106
|
* @param {object} args
|
|
107
107
|
* @param {import('./manifest.js').MutationManifestEntry[]} args.entries
|
|
108
|
-
* — the
|
|
109
|
-
* @param {string[]} args.approvedGroups — the phase groups
|
|
110
|
-
*
|
|
108
|
+
* — the subset of the mutation manifest that was applied.
|
|
109
|
+
* @param {string[]} args.approvedGroups — the phase groups whose mutations
|
|
110
|
+
* landed (sorted for stable output). Field name predates Story #5007's
|
|
111
|
+
* removal of the phased-approval gate; the ledger record shape is a
|
|
112
|
+
* consumer contract read back by `mandrel uninstall`, so it is unchanged.
|
|
111
113
|
* @param {{ owner?: string, repo?: string }} [args.answers]
|
|
112
114
|
* @param {string} [args.appliedAt] — ISO-8601 timestamp (default: now).
|
|
113
115
|
* @param {Record<string, { action?: string }>} [args.report] — the live
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
* `HUMAN_INTENT_FIELDS` table here, and each field's heading is the exact
|
|
16
16
|
* section name the parser maps (`goal` → `## Goal`, etc.). The CI
|
|
17
17
|
* conformance lint (`lint-issue-body.js`) runs the real `parse()` against
|
|
18
|
-
* human-opened issues so the form and the parser cannot silently drift
|
|
19
|
-
* the same model `ci-workflow-template.js` follows for `ci.yml`.
|
|
18
|
+
* human-opened issues so the form and the parser cannot silently drift.
|
|
20
19
|
*
|
|
21
20
|
* ## Form fields ⊆ body schema
|
|
22
21
|
*
|
|
@@ -70,9 +69,8 @@ export const STORY_FORM_RELATIVE_PATH = `${ISSUE_TEMPLATE_RELATIVE_DIR}/story.ym
|
|
|
70
69
|
* generated YAML uses it verbatim as the field `label` so GitHub's
|
|
71
70
|
* `### {label}` render produces a heading the parser recognises.
|
|
72
71
|
*
|
|
73
|
-
* Machine-managed body fields (`wide`, `reason_to_exist`,
|
|
74
|
-
*
|
|
75
|
-
* the runtime fills those. `depends_on` is exposed as a free-text input
|
|
72
|
+
* Machine-managed body fields (`wide`, `reason_to_exist`, `depends_on`
|
|
73
|
+
* meta) are intentionally absent — the runtime fills those. `depends_on` is exposed as a free-text input
|
|
76
74
|
* that serializes to the `blocked by #N` footer `parse()` already reads.
|
|
77
75
|
*
|
|
78
76
|
* @type {Array<{
|
|
@@ -367,7 +365,7 @@ jobs:
|
|
|
367
365
|
|
|
368
366
|
/**
|
|
369
367
|
* Write (or refresh) the Story issue form into a project checkout. Idempotent at
|
|
370
|
-
* the byte level
|
|
368
|
+
* the byte level:
|
|
371
369
|
*
|
|
372
370
|
* - file absent → `created`
|
|
373
371
|
* - byte-identical → `unchanged`
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* bootstrap/manifest — single mutation-manifest source for the
|
|
3
|
-
*
|
|
2
|
+
* bootstrap/manifest — single mutation-manifest source for the install
|
|
3
|
+
* ledger (Story #3521, Feature #3515, Epic #3438).
|
|
4
4
|
*
|
|
5
5
|
* `buildMutationManifest(ctx)` enumerates every mutation the bootstrap
|
|
6
|
-
* pipeline can perform as a flat, declarative array. Each entry carries
|
|
7
|
-
*
|
|
8
|
-
* before any write lands:
|
|
6
|
+
* pipeline can perform as a flat, declarative array. Each entry carries five
|
|
7
|
+
* fields:
|
|
9
8
|
*
|
|
10
|
-
* - `phaseGroup` — one of the four
|
|
11
|
-
*
|
|
9
|
+
* - `phaseGroup` — one of the four groups (`ide-wiring`, `repo-config`,
|
|
10
|
+
* `github-admin`, `quality-gates`).
|
|
12
11
|
* - `target` — the file path or remote resource the mutation touches.
|
|
13
12
|
* - `action` — the verb describing the mutation (`create`, `merge`,
|
|
14
13
|
* `update`, `run`, `configure`).
|
|
@@ -17,16 +16,18 @@
|
|
|
17
16
|
* (e.g. delete a created file) vs. a remote-admin mutation that is not
|
|
18
17
|
* a simple local revert.
|
|
19
18
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
19
|
+
* Story #3690 replaced the consent-first phased-approval install screen with
|
|
20
|
+
* a plain summary+confirm loop, and Story #5007 removed the vestigial
|
|
21
|
+
* machinery it left behind (the no-write `previewMutationManifest` renderer
|
|
22
|
+
* and the `approvedGroups` set-threading). What survives is load-bearing:
|
|
23
|
+
* `recordLedger` filters these entries into the install ledger, and
|
|
24
|
+
* `mandrel uninstall` walks them — the `phaseGroup` string is the ledger's
|
|
25
|
+
* grouping key, not an approval gate.
|
|
24
26
|
*
|
|
25
27
|
* The manifest describes *intended* mutations deterministically from the
|
|
26
28
|
* project root; it does not read or mutate any file. Whether a given
|
|
27
29
|
* mutation is a no-op on a particular clone (the file already carries the
|
|
28
|
-
* wiring) is decided at execution time by the idempotent `ensure*` steps
|
|
29
|
-
* the manifest always lists the full surface so the preview is complete.
|
|
30
|
+
* wiring) is decided at execution time by the idempotent `ensure*` steps.
|
|
30
31
|
*
|
|
31
32
|
* @module bootstrap/manifest
|
|
32
33
|
*/
|
|
@@ -34,9 +35,9 @@
|
|
|
34
35
|
import path from 'node:path';
|
|
35
36
|
|
|
36
37
|
/**
|
|
37
|
-
* The four
|
|
38
|
-
*
|
|
39
|
-
*
|
|
38
|
+
* The four phase groups. Ledger/uninstall metadata only (Story #5007): every
|
|
39
|
+
* manifest entry MUST carry exactly one of these values so the install ledger
|
|
40
|
+
* can group and the uninstaller can select what a run actually landed.
|
|
40
41
|
*
|
|
41
42
|
* @type {Readonly<{ IDE_WIRING: 'ide-wiring', REPO_CONFIG: 'repo-config',
|
|
42
43
|
* GITHUB_ADMIN: 'github-admin', QUALITY_GATES: 'quality-gates' }>}
|
|
@@ -271,27 +272,3 @@ export function buildMutationManifest(ctx = {}) {
|
|
|
271
272
|
|
|
272
273
|
return entries;
|
|
273
274
|
}
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Render the manifest as a no-write preview report grouped by phase group.
|
|
277
|
-
* Pure helper — derives entirely from {@link buildMutationManifest}, so the
|
|
278
|
-
* preview and the executing pipeline enumerate one identical source.
|
|
279
|
-
*
|
|
280
|
-
* The returned shape is `{ preview: true, groups: { <phaseGroup>: entry[] },
|
|
281
|
-
* entries: entry[] }`: callers that want the flat list read `entries`, and
|
|
282
|
-
* the consent-first screen reads `groups` to render one approvable section
|
|
283
|
-
* per phase group (only groups with at least one entry appear).
|
|
284
|
-
*
|
|
285
|
-
* @param {object} [ctx] — same context as {@link buildMutationManifest}.
|
|
286
|
-
* @returns {{ preview: true, groups: Record<string, MutationManifestEntry[]>,
|
|
287
|
-
* entries: MutationManifestEntry[] }}
|
|
288
|
-
*/
|
|
289
|
-
export function previewMutationManifest(ctx = {}) {
|
|
290
|
-
const entries = buildMutationManifest(ctx);
|
|
291
|
-
const groups = {};
|
|
292
|
-
for (const entry of entries) {
|
|
293
|
-
if (!groups[entry.phaseGroup]) groups[entry.phaseGroup] = [];
|
|
294
|
-
groups[entry.phaseGroup].push(entry);
|
|
295
|
-
}
|
|
296
|
-
return { preview: true, groups, entries };
|
|
297
|
-
}
|
|
@@ -20,7 +20,7 @@ import { isCommandExcluded } from '../command-header.js';
|
|
|
20
20
|
import { detectPackageManager as detectPm } from '../detect-package-manager.js';
|
|
21
21
|
import { LEDGER_RELATIVE_PATH } from './install-ledger.js';
|
|
22
22
|
import { ensureIssueForms } from './issue-forms-template.js';
|
|
23
|
-
import { PHASE_GROUPS
|
|
23
|
+
import { PHASE_GROUPS } from './manifest.js';
|
|
24
24
|
import { applyQualityBootstrap } from './quality-bootstrap.js';
|
|
25
25
|
|
|
26
26
|
export const SYNC_COMMAND = 'node .agents/scripts/sync-claude-commands.js';
|
|
@@ -436,8 +436,7 @@ export function ensureGitignore(ctx) {
|
|
|
436
436
|
* project (Story #4227). Derived from the Story-body SSOT so a human-filed
|
|
437
437
|
* ticket round-trips through `story-body.parse()`. Idempotent and additive,
|
|
438
438
|
* mirroring `ensureGitignore`: byte-identical forms are `unchanged`,
|
|
439
|
-
* operator-edited forms are preserved (`custom-skip`).
|
|
440
|
-
* (no writes) like the other phases.
|
|
439
|
+
* operator-edited forms are preserved (`custom-skip`).
|
|
441
440
|
*
|
|
442
441
|
* Returns the per-form action envelope keyed by ticket type.
|
|
443
442
|
*
|
|
@@ -446,13 +445,9 @@ export function ensureGitignore(ctx) {
|
|
|
446
445
|
*
|
|
447
446
|
* @param {object} ctx
|
|
448
447
|
* @param {string} ctx.projectRoot
|
|
449
|
-
* @param {boolean} [ctx.preview]
|
|
450
448
|
*/
|
|
451
449
|
function ensureIssueFormsPhase(ctx) {
|
|
452
|
-
const { forms } = ensureIssueForms({
|
|
453
|
-
projectRoot: ctx.projectRoot,
|
|
454
|
-
write: !ctx.preview,
|
|
455
|
-
});
|
|
450
|
+
const { forms } = ensureIssueForms({ projectRoot: ctx.projectRoot });
|
|
456
451
|
const outcomes = {};
|
|
457
452
|
for (const form of forms) {
|
|
458
453
|
outcomes[form.type] = { action: form.action, path: form.path };
|
|
@@ -691,12 +686,11 @@ const fatalParity = (result) =>
|
|
|
691
686
|
* bootstrap.
|
|
692
687
|
*
|
|
693
688
|
* Each project-side mutation phase carries a `phaseGroup` matching one of
|
|
694
|
-
* the
|
|
695
|
-
*
|
|
696
|
-
* the
|
|
697
|
-
*
|
|
698
|
-
*
|
|
699
|
-
* dependency install) are always-run infrastructure, never gated.
|
|
689
|
+
* the {@link PHASE_GROUPS}. Since Story #5007 that string is pure
|
|
690
|
+
* ledger/uninstall metadata — every phase runs on every install, and the
|
|
691
|
+
* ledger decides after the fact which groups actually landed. Phases with no
|
|
692
|
+
* `phaseGroup` (the Node-version precondition and the dependency install)
|
|
693
|
+
* are always-run infrastructure.
|
|
700
694
|
*/
|
|
701
695
|
export const BOOTSTRAP_PHASES = Object.freeze([
|
|
702
696
|
{
|
|
@@ -770,24 +764,6 @@ export const BOOTSTRAP_PHASES = Object.freeze([
|
|
|
770
764
|
},
|
|
771
765
|
]);
|
|
772
766
|
|
|
773
|
-
/**
|
|
774
|
-
* Decide whether a phase should run given the approved-phase-group gate.
|
|
775
|
-
* An always-run infrastructure phase (no `phaseGroup`) runs unconditionally.
|
|
776
|
-
* A grouped phase runs only when no gate is supplied (`approvedGroups`
|
|
777
|
-
* absent — the un-gated legacy path) or when its group is in the gate.
|
|
778
|
-
*
|
|
779
|
-
* Exported for unit testing.
|
|
780
|
-
*
|
|
781
|
-
* @param {BootstrapPhase} phase
|
|
782
|
-
* @param {Set<string>|undefined} approvedGroups
|
|
783
|
-
* @returns {boolean}
|
|
784
|
-
*/
|
|
785
|
-
export function isPhaseApproved(phase, approvedGroups) {
|
|
786
|
-
if (!phase.phaseGroup) return true;
|
|
787
|
-
if (!approvedGroups) return true;
|
|
788
|
-
return approvedGroups.has(phase.phaseGroup);
|
|
789
|
-
}
|
|
790
|
-
|
|
791
767
|
/**
|
|
792
768
|
* Throw with the formatted message when the phase is marked fatal and
|
|
793
769
|
* the result indicates an abort. Pure helper so the driver stays a
|
|
@@ -809,12 +785,6 @@ export function throwIfFatal(phase, result) {
|
|
|
809
785
|
* the result on `report[phase.name]`, then route fatal phases through
|
|
810
786
|
* `throwIfFatal`. Returns the accumulated report.
|
|
811
787
|
*
|
|
812
|
-
* When `ctx.approvedGroups` is a `Set`, a grouped phase whose `phaseGroup`
|
|
813
|
-
* is not approved is skipped and recorded as
|
|
814
|
-
* `{ skipped: true, reason: 'phase-group-declined', phaseGroup }` — it never
|
|
815
|
-
* runs, never throws (so a declined `ide-wiring` group also skips its
|
|
816
|
-
* fatal `parity` check), and never short-circuits the remaining phases.
|
|
817
|
-
*
|
|
818
788
|
* Exported for tests so phase ordering and fatal behaviour can be
|
|
819
789
|
* asserted without spawning a full bootstrap.
|
|
820
790
|
*
|
|
@@ -825,14 +795,6 @@ export function throwIfFatal(phase, result) {
|
|
|
825
795
|
export async function runPhases(phases, ctx) {
|
|
826
796
|
const report = {};
|
|
827
797
|
for (const phase of phases) {
|
|
828
|
-
if (!isPhaseApproved(phase, ctx.approvedGroups)) {
|
|
829
|
-
report[phase.name] = {
|
|
830
|
-
skipped: true,
|
|
831
|
-
reason: 'phase-group-declined',
|
|
832
|
-
phaseGroup: phase.phaseGroup,
|
|
833
|
-
};
|
|
834
|
-
continue;
|
|
835
|
-
}
|
|
836
798
|
const result = await phase.run(ctx, report);
|
|
837
799
|
report[phase.name] = result;
|
|
838
800
|
throwIfFatal(phase, result);
|
|
@@ -844,24 +806,16 @@ export async function runPhases(phases, ctx) {
|
|
|
844
806
|
* Compose every step in order. Each returned key is the outcome of one
|
|
845
807
|
* step so the CLI can render a structured summary.
|
|
846
808
|
*
|
|
847
|
-
*
|
|
848
|
-
*
|
|
849
|
-
*
|
|
850
|
-
*
|
|
851
|
-
* consent-first install screen renders. Deriving the preview from
|
|
852
|
-
* `buildMutationManifest` (rather than from a parallel hand-maintained list)
|
|
853
|
-
* guarantees the preview the operator approves and the execution that
|
|
854
|
-
* follows enumerate one identical set of mutations (Story #3521).
|
|
809
|
+
* Story #5007 removed the no-write `ctx.preview` branch: it had zero
|
|
810
|
+
* production callers (bootstrap's own `--dry-run` renders a hand-rolled text
|
|
811
|
+
* plan instead) and it survived only to serve the consent-first install
|
|
812
|
+
* screen Story #3690 replaced with a plain summary+confirm loop.
|
|
855
813
|
*
|
|
856
814
|
* @param {object} ctx
|
|
857
815
|
* @param {string} ctx.projectRoot
|
|
858
816
|
* @param {string} [ctx.agentRoot]
|
|
859
817
|
* @param {{ owner: string, repo: string, baseBranch: string,
|
|
860
818
|
* operatorHandle: string|null }} ctx.answers
|
|
861
|
-
* @param {boolean} [ctx.preview] — no-write preview from the manifest.
|
|
862
|
-
* @param {Set<string>} [ctx.approvedGroups] — when present, only phases
|
|
863
|
-
* whose `phaseGroup` is in this set execute (the consent-first gate from
|
|
864
|
-
* Story #3524); always-run infrastructure phases ignore it.
|
|
865
819
|
* @param {boolean} [ctx.withQuality]
|
|
866
820
|
* @param {boolean} [ctx.skipGithub]
|
|
867
821
|
* @param {boolean} [ctx.skipInstall]
|
|
@@ -871,6 +825,5 @@ export async function runPhases(phases, ctx) {
|
|
|
871
825
|
* @returns {Promise<object>}
|
|
872
826
|
*/
|
|
873
827
|
export async function applyProjectBootstrap(ctx) {
|
|
874
|
-
if (ctx.preview) return previewMutationManifest(ctx);
|
|
875
828
|
return runPhases(BOOTSTRAP_PHASES, ctx);
|
|
876
829
|
}
|