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,212 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* bootstrap/ci-workflow-template — Story #1401 (Epic #1386)
|
|
3
|
-
*
|
|
4
|
-
* Renders the stabilized-quality-gates CI workflow YAML that
|
|
5
|
-
* `/agents-bootstrap-github` writes (or refreshes) on a downstream project's
|
|
6
|
-
* `.github/workflows/ci.yml`. Two design rules drive the shape:
|
|
7
|
-
*
|
|
8
|
-
* 1. **Lean on the diff-scoped default.** Story #1394 flipped both gate
|
|
9
|
-
* CLIs (`check-maintainability.js`, `check-crap.js`) to default
|
|
10
|
-
* `--changed-since=main`. The template no longer passes `--changed-since`
|
|
11
|
-
* explicitly on the per-PR job — the default already produces the right
|
|
12
|
-
* diff scope and an explicit re-statement risks drifting from the
|
|
13
|
-
* framework default at the next bump. The push-to-main job still passes
|
|
14
|
-
* `--full-scope` so a self-diff doesn't degrade to "no files in diff".
|
|
15
|
-
*
|
|
16
|
-
* 2. **Pin the baseline read to the PR's base branch via `BASELINE_REF`.**
|
|
17
|
-
* Inside `/deliver` the close-validation chain sets the same env var
|
|
18
|
-
* (`close-validation/gates.js` → `BASELINE_REF: origin/<baseBranch>`);
|
|
19
|
-
* on CI the equivalent surface is the PR's base branch. This is an
|
|
20
|
-
* ENV VAR, not a CLI flag: `check-baselines.js` resolves it through
|
|
21
|
-
* `resolveDispatchScope`, and the `--epic-ref` flag this template used
|
|
22
|
-
* to pass never existed on it — it was left over from the separate
|
|
23
|
-
* `check-crap.js` / `check-maintainability.js` CLIs that were unified
|
|
24
|
-
* away, and it named the ref `epic/<base>`, a branch the Story-only
|
|
25
|
-
* model never creates. When `BASELINE_REF` is empty (push-to-base,
|
|
26
|
-
* where there is no base to compare against) both gates fall through to
|
|
27
|
-
* the working-tree baseline, so the expansion is always safe.
|
|
28
|
-
*
|
|
29
|
-
* The renderer is a pure string template so the integration test can assert
|
|
30
|
-
* on its exact shape without instantiating a YAML parser.
|
|
31
|
-
*
|
|
32
|
-
* @module bootstrap/ci-workflow-template
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
import fs from 'node:fs';
|
|
36
|
-
import path from 'node:path';
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* @typedef {object} CiTemplateOptions
|
|
40
|
-
* @property {string} [nodeVersion='22'] - Node major to install via setup-node.
|
|
41
|
-
* @property {string} [baseRef='main'] - Default base ref the gate CLIs diff
|
|
42
|
-
* against. Surfaces as the workflow's `BASE_REF` env so the template stays
|
|
43
|
-
* tunable per consumer.
|
|
44
|
-
* @property {boolean} [includeCrap=true] - Whether to render the CRAP step.
|
|
45
|
-
* Some downstream projects keep CRAP off until they have coverage wired.
|
|
46
|
-
*/
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* Render the CI workflow YAML body. The output is deterministic and safe to
|
|
50
|
-
* write byte-for-byte over an existing template — the bootstrap caller is
|
|
51
|
-
* responsible for diff-aware merging when an operator has hand-edited
|
|
52
|
-
* `ci.yml`.
|
|
53
|
-
*
|
|
54
|
-
* @param {CiTemplateOptions} [opts]
|
|
55
|
-
* @returns {string}
|
|
56
|
-
*/
|
|
57
|
-
export function renderCiWorkflow(opts = {}) {
|
|
58
|
-
const nodeVersion = opts.nodeVersion ?? '22';
|
|
59
|
-
const baseRef = opts.baseRef ?? 'main';
|
|
60
|
-
const includeCrap = opts.includeCrap !== false;
|
|
61
|
-
|
|
62
|
-
const crapBlock = includeCrap
|
|
63
|
-
? `
|
|
64
|
-
- name: CRAP Check
|
|
65
|
-
# Diff-scoped via the framework default (Story #1394 flipped
|
|
66
|
-
# --changed-since to '${baseRef}'). On PRs we let the default win;
|
|
67
|
-
# on push-to-main we pass --full-scope so a self-diff doesn't
|
|
68
|
-
# collapse to an empty file set. BASELINE_REF makes the gate read
|
|
69
|
-
# the baseline at the base branch tip via
|
|
70
|
-
# baseline-loader.readBaselineAtRef.
|
|
71
|
-
env:
|
|
72
|
-
BASELINE_REF: \${{ env.BASELINE_REF }}
|
|
73
|
-
run: |
|
|
74
|
-
mkdir -p temp
|
|
75
|
-
if [[ "\${{ github.event_name }}" == "pull_request" ]]; then
|
|
76
|
-
npm run crap:check -- --json temp/crap-report.json
|
|
77
|
-
else
|
|
78
|
-
npm run crap:check -- --full-scope --json temp/crap-report.json
|
|
79
|
-
fi
|
|
80
|
-
|
|
81
|
-
- name: Upload CRAP Report
|
|
82
|
-
uses: actions/upload-artifact@v4
|
|
83
|
-
if: always()
|
|
84
|
-
with:
|
|
85
|
-
name: crap-report
|
|
86
|
-
path: temp/crap-report.json
|
|
87
|
-
`
|
|
88
|
-
: '';
|
|
89
|
-
|
|
90
|
-
return `# Generated by agents-bootstrap-github (Epic #1386 / Story #1401).
|
|
91
|
-
# Re-run /agents-bootstrap-github to refresh this file. Hand edits are
|
|
92
|
-
# preserved by the bootstrap helper when it detects operator changes —
|
|
93
|
-
# inspect the diff before accepting an overwrite.
|
|
94
|
-
name: CI
|
|
95
|
-
|
|
96
|
-
on:
|
|
97
|
-
push:
|
|
98
|
-
branches: [${baseRef}]
|
|
99
|
-
pull_request:
|
|
100
|
-
branches: [${baseRef}]
|
|
101
|
-
types: [opened, synchronize, reopened]
|
|
102
|
-
workflow_dispatch:
|
|
103
|
-
|
|
104
|
-
concurrency:
|
|
105
|
-
group: \${{ github.workflow }}-\${{ github.ref }}
|
|
106
|
-
cancel-in-progress: true
|
|
107
|
-
|
|
108
|
-
jobs:
|
|
109
|
-
validate:
|
|
110
|
-
name: Validate and Test
|
|
111
|
-
runs-on: ubuntu-latest
|
|
112
|
-
permissions:
|
|
113
|
-
contents: read
|
|
114
|
-
env:
|
|
115
|
-
# Story #1120: on a PR the gate CLIs read the canonical baseline at
|
|
116
|
-
# the BASE branch tip via --baseline-ref (git show), not from the
|
|
117
|
-
# PR head's working tree — a branch that edited its own baseline
|
|
118
|
-
# must not be allowed to grade itself against the edit.
|
|
119
|
-
# Empty on push-to-${baseRef} (there is no base to compare against),
|
|
120
|
-
# where both gates fall through to the working-tree baseline, so the
|
|
121
|
-
# env-var expansion is always safe.
|
|
122
|
-
BASELINE_REF: \${{ github.event_name == 'pull_request' && format('origin/{0}', github.event.pull_request.base.ref) || '' }}
|
|
123
|
-
BASE_REF: ${baseRef}
|
|
124
|
-
steps:
|
|
125
|
-
- name: Checkout Code
|
|
126
|
-
uses: actions/checkout@v4
|
|
127
|
-
with:
|
|
128
|
-
fetch-depth: 0
|
|
129
|
-
|
|
130
|
-
- name: Setup Node.js
|
|
131
|
-
uses: actions/setup-node@v4
|
|
132
|
-
with:
|
|
133
|
-
node-version: '${nodeVersion}'
|
|
134
|
-
cache: 'npm'
|
|
135
|
-
|
|
136
|
-
- name: Install Dependencies
|
|
137
|
-
run: npm ci --ignore-scripts
|
|
138
|
-
|
|
139
|
-
- name: Lint and Format
|
|
140
|
-
run: |
|
|
141
|
-
npm run lint
|
|
142
|
-
npm run format:check
|
|
143
|
-
|
|
144
|
-
- name: Maintainability Check
|
|
145
|
-
# Story #1394 (Epic #1386): the gate CLI defaults --changed-since to
|
|
146
|
-
# the project's BASE_REF, so PRs no longer pass --changed-since
|
|
147
|
-
# explicitly. push-to-${baseRef} runs with --full-scope so the
|
|
148
|
-
# diff-scoped default doesn't degrade to a self-diff (= empty file
|
|
149
|
-
# set). BASELINE_REF pins the baseline read to the base branch tip.
|
|
150
|
-
env:
|
|
151
|
-
BASELINE_REF: \${{ env.BASELINE_REF }}
|
|
152
|
-
run: |
|
|
153
|
-
if [[ "\${{ github.event_name }}" == "pull_request" ]]; then
|
|
154
|
-
npm run maintainability:check
|
|
155
|
-
else
|
|
156
|
-
npm run maintainability:check -- --full-scope
|
|
157
|
-
fi
|
|
158
|
-
|
|
159
|
-
- name: Run Tests
|
|
160
|
-
run: npm test
|
|
161
|
-
${crapBlock}`;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* Default workflow filename emitted by the bootstrap. Surfaced as a constant
|
|
166
|
-
* so the integration test can assert the bootstrap writes to the canonical
|
|
167
|
-
* path without re-deriving it.
|
|
168
|
-
*/
|
|
169
|
-
export const CI_WORKFLOW_RELATIVE_PATH = '.github/workflows/ci.yml';
|
|
170
|
-
|
|
171
|
-
/**
|
|
172
|
-
* Render the stabilized-quality-gates CI workflow template into a project
|
|
173
|
-
* checkout. Idempotent on the byte level: when `.github/workflows/ci.yml`
|
|
174
|
-
* already matches the rendered template, no write occurs and the action is
|
|
175
|
-
* `unchanged`. When the file is absent the action is `created`. When the
|
|
176
|
-
* file exists with operator-authored differences the helper preserves it
|
|
177
|
-
* and returns `custom-workflow-skip` along with the rendered body so the
|
|
178
|
-
* bootstrap caller (or `/mandrel-update`) can offer a side-by-side diff.
|
|
179
|
-
*
|
|
180
|
-
* Network-free; safe to invoke under tests with a tmp `projectRoot`.
|
|
181
|
-
*
|
|
182
|
-
* Moved from `agents-bootstrap-github.js` to this module so it lives
|
|
183
|
-
* alongside `renderCiWorkflow` and `CI_WORKFLOW_RELATIVE_PATH`
|
|
184
|
-
* (Story #4048 B2 — dead-code deletion from the bootstrap entry point).
|
|
185
|
-
*
|
|
186
|
-
* @param {object} args
|
|
187
|
-
* @param {string} args.projectRoot - Repo root (must contain or accept
|
|
188
|
-
* `.github/workflows/`).
|
|
189
|
-
* @param {object} [args.template] - Forwarded to `renderCiWorkflow`.
|
|
190
|
-
* @param {boolean} [args.write=true] - When `false`, the helper computes
|
|
191
|
-
* the would-be action without touching disk. Used by the
|
|
192
|
-
* bootstrap CLI's dry-run mode.
|
|
193
|
-
* @returns {{ action: 'created'|'unchanged'|'custom-workflow-skip',
|
|
194
|
-
* path: string, rendered: string }}
|
|
195
|
-
*/
|
|
196
|
-
export function ensureCiWorkflow(args) {
|
|
197
|
-
const projectRoot = args.projectRoot;
|
|
198
|
-
const rendered = renderCiWorkflow(args.template);
|
|
199
|
-
const target = path.join(projectRoot, CI_WORKFLOW_RELATIVE_PATH);
|
|
200
|
-
if (!fs.existsSync(target)) {
|
|
201
|
-
if (args.write !== false) {
|
|
202
|
-
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
203
|
-
fs.writeFileSync(target, rendered, 'utf8');
|
|
204
|
-
}
|
|
205
|
-
return { action: 'created', path: target, rendered };
|
|
206
|
-
}
|
|
207
|
-
const existing = fs.readFileSync(target, 'utf8');
|
|
208
|
-
if (existing === rendered) {
|
|
209
|
-
return { action: 'unchanged', path: target, rendered };
|
|
210
|
-
}
|
|
211
|
-
return { action: 'custom-workflow-skip', path: target, rendered };
|
|
212
|
-
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* baseline-drift-main-checkout — refuse-and-print check.
|
|
3
|
-
*
|
|
4
|
-
* Detects the close-validation-main-drift failure mode where `story-close`
|
|
5
|
-
* (or any close-validation step) runs against the **main checkout** while a
|
|
6
|
-
* corresponding worktree exists. The active branch in the main checkout is
|
|
7
|
-
* usually drifted from the Story branch by stale formatting / MI-baseline
|
|
8
|
-
* commits, and the close validation then refuses to merge.
|
|
9
|
-
*
|
|
10
|
-
* Pre-existing epic-branch format drift blocks every story close when the
|
|
11
|
-
* validator hits the main checkout.
|
|
12
|
-
*
|
|
13
|
-
* Surface: `story-close` preflight and the `/diagnose` CLI. Surfaced as a
|
|
14
|
-
* blocker — if the validator is running against the wrong tree, none of the
|
|
15
|
-
* downstream gates are reliable.
|
|
16
|
-
*
|
|
17
|
-
* AutoCorrect is `refuse-and-print`. The fix is for the *operator* to `cd`
|
|
18
|
-
* into the worktree; an auto-fix would mutate the process's cwd, which is
|
|
19
|
-
* a footgun no other check assumes.
|
|
20
|
-
*
|
|
21
|
-
* Detection logic:
|
|
22
|
-
* 1. If headRef is not a story branch (`story-<n>`), the check does not
|
|
23
|
-
* apply — only story-close runs in this scope, and only against a
|
|
24
|
-
* story branch. Returns null.
|
|
25
|
-
* 2. If a worktree at `.worktrees/<headRef>/` does NOT exist, the check
|
|
26
|
-
* cannot say anything — the operator may legitimately be running in
|
|
27
|
-
* single-tree mode. Returns null.
|
|
28
|
-
* 3. If the active cwd is the worktree path, all good. Returns null.
|
|
29
|
-
* 4. Otherwise the validator is running against a different tree (the
|
|
30
|
-
* main checkout) while the worktree exists. Returns a blocker with
|
|
31
|
-
* the `cd <worktree>` recipe.
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
import path from 'node:path';
|
|
35
|
-
import { isStoryBranch as isFlatStoryBranch } from '../git-utils.js';
|
|
36
|
-
|
|
37
|
-
const ID = 'baseline-drift-main-checkout';
|
|
38
|
-
|
|
39
|
-
export default {
|
|
40
|
-
id: ID,
|
|
41
|
-
severity: 'blocker',
|
|
42
|
-
scope: ['story-close', 'diagnose'],
|
|
43
|
-
autoCorrect: 'refuse-and-print',
|
|
44
|
-
detect(state) {
|
|
45
|
-
const headRef = state?.git?.headRef ?? null;
|
|
46
|
-
if (!headRef || !isStoryBranch(headRef)) return null;
|
|
47
|
-
const worktreePaths = state?.fs?.worktreePaths ?? [];
|
|
48
|
-
if (!Array.isArray(worktreePaths) || worktreePaths.length === 0) {
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
const expected = worktreePaths.find((p) => path.basename(p) === headRef);
|
|
52
|
-
if (!expected) return null; // worktree for this branch doesn't exist
|
|
53
|
-
const cwd = state?.cwd ?? '';
|
|
54
|
-
if (samePath(cwd, expected)) return null; // already in the worktree
|
|
55
|
-
return {
|
|
56
|
-
id: ID,
|
|
57
|
-
severity: 'blocker',
|
|
58
|
-
scope: state?.scope ?? '',
|
|
59
|
-
summary: `Close-validation running outside worktree (cwd: ${cwd}, expected: ${expected})`,
|
|
60
|
-
detail: [
|
|
61
|
-
`The active branch is ${headRef} and a worktree exists at ${expected},`,
|
|
62
|
-
`but the current working directory is ${cwd}.`,
|
|
63
|
-
'',
|
|
64
|
-
'Close-validation must run inside the Story worktree — the main checkout',
|
|
65
|
-
'may carry stale formatting or MI-baseline drift that will fail validation',
|
|
66
|
-
'even when the Story branch is clean.',
|
|
67
|
-
].join('\n'),
|
|
68
|
-
fixCommand: `cd "${expected}"`,
|
|
69
|
-
autoCorrectable: false,
|
|
70
|
-
};
|
|
71
|
-
},
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Story branches use a flat `story-<n>` shape; the older
|
|
76
|
-
* `story/epic-<id>/<n>` layout no longer applies. Accept both for forward
|
|
77
|
-
* compatibility.
|
|
78
|
-
*
|
|
79
|
-
* @param {string} ref
|
|
80
|
-
* @returns {boolean}
|
|
81
|
-
*/
|
|
82
|
-
function isStoryBranch(ref) {
|
|
83
|
-
return isFlatStoryBranch(ref) || /^story\/epic-\d+\/\d+$/.test(ref);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Compare two paths normalized for the platform. Windows is case-insensitive
|
|
88
|
-
* and uses backslashes; POSIX is case-sensitive. We normalize both sides
|
|
89
|
-
* with `path.resolve` so the comparison is stable regardless of trailing
|
|
90
|
-
* separators or `./` prefixes.
|
|
91
|
-
*
|
|
92
|
-
* @param {string} a
|
|
93
|
-
* @param {string} b
|
|
94
|
-
* @returns {boolean}
|
|
95
|
-
*/
|
|
96
|
-
function samePath(a, b) {
|
|
97
|
-
if (!a || !b) return false;
|
|
98
|
-
const na = path.resolve(a);
|
|
99
|
-
const nb = path.resolve(b);
|
|
100
|
-
if (process.platform === 'win32') {
|
|
101
|
-
return na.toLowerCase() === nb.toLowerCase();
|
|
102
|
-
}
|
|
103
|
-
return na === nb;
|
|
104
|
-
}
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* push-hook-parity — refuse-and-print check.
|
|
3
|
-
*
|
|
4
|
-
* Detects when the pre-push gates (biome check, MI gate) would fail at
|
|
5
|
-
* `git push` time, BEFORE merge commits land. Biome check and the MI
|
|
6
|
-
* gate fire at push time (not lint time), so an apparently-clean commit
|
|
7
|
-
* cascade can still get stuck at the actual push step.
|
|
8
|
-
*
|
|
9
|
-
* Surface: `/diagnose` CLI. The gates execute against HEAD in dry-run
|
|
10
|
-
* mode (no working-tree mutation) and report pass/fail. This check is
|
|
11
|
-
* intentionally scoped to `diagnose` rather than `story-close` because:
|
|
12
|
-
*
|
|
13
|
-
* - story-close already runs the full validation chain (typecheck,
|
|
14
|
-
* lint, test, format, MI, coverage, crap) against the worktree. The
|
|
15
|
-
* gates are part of that chain. Running them twice in preflight is
|
|
16
|
-
* wasteful.
|
|
17
|
-
* - /diagnose is the surface where the operator wants a fast pre-push
|
|
18
|
-
* verdict ("would this commit pass the push hook?"). That's exactly
|
|
19
|
-
* what this check answers.
|
|
20
|
-
*
|
|
21
|
-
* The gate runs live in `state.js`'s `gates.*` projection so they are
|
|
22
|
-
* memoized by `(scope, cwd)` and the heavy spawn cost is paid at most
|
|
23
|
-
* once per assembly. Tests inject a fake `gates` probe to drive the
|
|
24
|
-
* specific pass/fail scenarios without spawning real binaries.
|
|
25
|
-
*
|
|
26
|
-
* AutoCorrect is `refuse-and-print`. The fix is "run the gate locally
|
|
27
|
-
* and address its findings" — there is no safe auto-fix because we
|
|
28
|
-
* cannot mutate the working tree to satisfy MI baseline regressions
|
|
29
|
-
* without making domain decisions about which file to touch.
|
|
30
|
-
*/
|
|
31
|
-
|
|
32
|
-
const ID = 'push-hook-parity';
|
|
33
|
-
|
|
34
|
-
export default {
|
|
35
|
-
id: ID,
|
|
36
|
-
severity: 'blocker',
|
|
37
|
-
scope: ['diagnose'],
|
|
38
|
-
autoCorrect: 'refuse-and-print',
|
|
39
|
-
detect(state) {
|
|
40
|
-
const gates = state?.gates ?? {};
|
|
41
|
-
const biome = gates.biome;
|
|
42
|
-
const miGate = gates.miGate;
|
|
43
|
-
// If neither gate ran (state.js was assembled for a scope without
|
|
44
|
-
// gates.* keys), the check has nothing to say. Returning null avoids
|
|
45
|
-
// a false positive when the consumer happens to share state across
|
|
46
|
-
// scopes.
|
|
47
|
-
if (!biome && !miGate) return null;
|
|
48
|
-
const failed = [];
|
|
49
|
-
if (biome && !biome.ok)
|
|
50
|
-
failed.push({ name: 'biome', output: biome.output });
|
|
51
|
-
if (miGate && !miGate.ok)
|
|
52
|
-
failed.push({ name: 'miGate', output: miGate.output });
|
|
53
|
-
if (failed.length === 0) return null;
|
|
54
|
-
const failedNames = failed.map((f) => f.name).join(' + ');
|
|
55
|
-
const detailLines = failed.map(
|
|
56
|
-
(f) => ` - ${f.name}:\n${indent(f.output ?? '(no output)')}`,
|
|
57
|
-
);
|
|
58
|
-
return {
|
|
59
|
-
id: ID,
|
|
60
|
-
severity: 'blocker',
|
|
61
|
-
scope: state?.scope ?? '',
|
|
62
|
-
summary: `pre-push gate(s) would fail at push time: ${failedNames}`,
|
|
63
|
-
detail: [
|
|
64
|
-
'These gates run on `git push`, not on `git commit`, so the working',
|
|
65
|
-
'tree looks clean but the push will be rejected. Resolve before merging:',
|
|
66
|
-
'',
|
|
67
|
-
...detailLines,
|
|
68
|
-
].join('\n'),
|
|
69
|
-
fixCommand: buildFixCommand(failed.map((f) => f.name)),
|
|
70
|
-
autoCorrectable: false,
|
|
71
|
-
};
|
|
72
|
-
},
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Build the literal command(s) the operator should run locally to see the
|
|
77
|
-
* same failure the push hook would surface. Each gate has its own canonical
|
|
78
|
-
* invocation; chaining with `&&` lets the operator copy-paste the whole
|
|
79
|
-
* recipe as one line.
|
|
80
|
-
*
|
|
81
|
-
* @param {string[]} gateNames
|
|
82
|
-
* @returns {string}
|
|
83
|
-
*/
|
|
84
|
-
function buildFixCommand(gateNames) {
|
|
85
|
-
const recipes = {
|
|
86
|
-
biome: 'npx biome check .',
|
|
87
|
-
miGate: 'npm run check:maintainability',
|
|
88
|
-
};
|
|
89
|
-
return gateNames
|
|
90
|
-
.map((g) => recipes[g] ?? `# unknown gate: ${g}`)
|
|
91
|
-
.join(' && ');
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Indent every line of `text` by two spaces so multi-line gate output
|
|
96
|
-
* renders cleanly inside the bullet list in `detail`.
|
|
97
|
-
*
|
|
98
|
-
* @param {string} text
|
|
99
|
-
* @returns {string}
|
|
100
|
-
*/
|
|
101
|
-
function indent(text) {
|
|
102
|
-
return String(text)
|
|
103
|
-
.split('\n')
|
|
104
|
-
.map((line) => ` ${line}`)
|
|
105
|
-
.join('\n');
|
|
106
|
-
}
|
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* windows-coverage-noise-floor — refuse-and-print warning check.
|
|
3
|
-
*
|
|
4
|
-
* Detects coverage-gate deltas that are within the Windows / Node-22
|
|
5
|
-
* noise floor — i.e., tiny fractional branch-coverage swings that vary
|
|
6
|
-
* run-to-run and would chase the gate to a different file if the
|
|
7
|
-
* operator ratcheted from a flapping CI artifact. The canonical mistake
|
|
8
|
-
* is to fetch a failing run's coverage delta and bump the baseline by
|
|
9
|
-
* that delta, which simply moves the noise floor to another file rather
|
|
10
|
-
* than stabilising the gate.
|
|
11
|
-
*
|
|
12
|
-
* Scope: 'story-close', 'retro'. The check is `warning` severity (not
|
|
13
|
-
* `blocker`) because flapping is informational — the operator decides
|
|
14
|
-
* whether to ratchet, skip, or investigate. Coverage-blocking belongs
|
|
15
|
-
* to a separate baseline-refresh gate.
|
|
16
|
-
*
|
|
17
|
-
* The check is `refuse-and-print`. A `fix()` here would mean either
|
|
18
|
-
* suppressing the flap (which silently lowers signal) or ratcheting
|
|
19
|
-
* (which is the very mistake the check exists to prevent). The
|
|
20
|
-
* fixCommand advises against ratcheting from a flapping artifact.
|
|
21
|
-
*
|
|
22
|
-
* Input shape — the check reads `state.coverage`:
|
|
23
|
-
* {
|
|
24
|
-
* branchDelta: number, // signed fractional delta (e.g. 0.0015 = +0.15%)
|
|
25
|
-
* noiseFloor?: number, // optional override; default 0.0025
|
|
26
|
-
* file?: string, // optional file the delta is attributed to
|
|
27
|
-
* }
|
|
28
|
-
*
|
|
29
|
-
* Returns null when `state.coverage` is absent (the assembler does not
|
|
30
|
-
* always populate coverage; absent input is a no-op rather than a
|
|
31
|
-
* failure).
|
|
32
|
-
*/
|
|
33
|
-
|
|
34
|
-
const DEFAULT_NOISE_FLOOR = 0.0025; // 0.25%
|
|
35
|
-
|
|
36
|
-
const FIX_COMMAND = [
|
|
37
|
-
'# Do NOT ratchet the coverage baseline from a flapping CI artifact.',
|
|
38
|
-
'# A delta within the Windows / Node-22 noise floor (< 0.25% by default)',
|
|
39
|
-
'# is run-to-run jitter, not real coverage loss. Ratcheting in response',
|
|
40
|
-
'# moves the noise floor to a different file rather than fixing it.',
|
|
41
|
-
'#',
|
|
42
|
-
'# Instead: re-run the gate. If the delta persists across multiple runs',
|
|
43
|
-
'# in the same file, investigate the specific test that flipped — most',
|
|
44
|
-
'# often a Date.now() / Math.random() leak into the branch decision.',
|
|
45
|
-
'#',
|
|
46
|
-
'# If the delta is real but small, capture a fresh baseline-refresh',
|
|
47
|
-
'# commit deliberately, separate from the failing PR.',
|
|
48
|
-
].join('\n');
|
|
49
|
-
|
|
50
|
-
export default {
|
|
51
|
-
id: 'windows-coverage-noise-floor',
|
|
52
|
-
severity: 'warning',
|
|
53
|
-
scope: ['story-close', 'retro'],
|
|
54
|
-
autoCorrect: 'refuse-and-print',
|
|
55
|
-
|
|
56
|
-
detect(state) {
|
|
57
|
-
const cov = state?.coverage;
|
|
58
|
-
if (!cov || typeof cov !== 'object') return null;
|
|
59
|
-
if (typeof cov.branchDelta !== 'number' || Number.isNaN(cov.branchDelta)) {
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
const noiseFloor =
|
|
63
|
-
typeof cov.noiseFloor === 'number' && cov.noiseFloor > 0
|
|
64
|
-
? cov.noiseFloor
|
|
65
|
-
: DEFAULT_NOISE_FLOOR;
|
|
66
|
-
const absDelta = Math.abs(cov.branchDelta);
|
|
67
|
-
// Within (or equal to) the floor → flapping → warning.
|
|
68
|
-
// Above the floor → real signal → return null and let the actual
|
|
69
|
-
// coverage gate decide.
|
|
70
|
-
if (absDelta > noiseFloor) {
|
|
71
|
-
return null;
|
|
72
|
-
}
|
|
73
|
-
const deltaPct = (cov.branchDelta * 100).toFixed(3);
|
|
74
|
-
const floorPct = (noiseFloor * 100).toFixed(3);
|
|
75
|
-
const fileNote = cov.file ? ` (file: ${cov.file})` : '';
|
|
76
|
-
return {
|
|
77
|
-
id: 'windows-coverage-noise-floor',
|
|
78
|
-
severity: 'warning',
|
|
79
|
-
scope: state?.scope ?? 'story-close',
|
|
80
|
-
summary: `Branch-coverage delta ${deltaPct}% is within the Windows noise floor (${floorPct}%) — likely flap, do not ratchet${fileNote}`,
|
|
81
|
-
detail: [
|
|
82
|
-
`branchDelta: ${cov.branchDelta}`,
|
|
83
|
-
`noiseFloor: ${noiseFloor}`,
|
|
84
|
-
cov.file ? `attributedFile: ${cov.file}` : null,
|
|
85
|
-
]
|
|
86
|
-
.filter(Boolean)
|
|
87
|
-
.join('\n'),
|
|
88
|
-
fixCommand: FIX_COMMAND,
|
|
89
|
-
autoCorrectable: false,
|
|
90
|
-
};
|
|
91
|
-
},
|
|
92
|
-
};
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* worktree-bootstrap-env — refuse-and-print check.
|
|
3
|
-
*
|
|
4
|
-
* Detects active worktrees that are missing the `.env` or `.mcp.json`
|
|
5
|
-
* bootstrap files. These files are intentionally untracked in git, so a
|
|
6
|
-
* freshly-created worktree under `.worktrees/<story>/` does not get them
|
|
7
|
-
* unless the orchestration explicitly copies them. When they are missing,
|
|
8
|
-
* tests that read environment variables silently take a different code
|
|
9
|
-
* path.
|
|
10
|
-
*
|
|
11
|
-
* Surface: the `/diagnose` CLI. Surfaced as a
|
|
12
|
-
* warning rather than a blocker — the missing file may be a deliberate
|
|
13
|
-
* choice (e.g. a worktree that doesn't need MCP), but the operator should
|
|
14
|
-
* see the divergence from the main checkout.
|
|
15
|
-
*
|
|
16
|
-
* AutoCorrect is `refuse-and-print`. Copying `.env` between trees is
|
|
17
|
-
* defensible at the orchestration layer (`worktree.bootstrap` step) but
|
|
18
|
-
* not as a self-healing fix — the contents are project-specific and may
|
|
19
|
-
* include secrets the check explicitly never reads.
|
|
20
|
-
*
|
|
21
|
-
* Privacy: this check reads only the bootstrap *status* projection
|
|
22
|
-
* (`fs.worktreeBootstrapStatus`) which records presence per worktree, not
|
|
23
|
-
* file contents. The Finding's `detail` never exposes any env-var value.
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
const ID = 'worktree-bootstrap-env';
|
|
27
|
-
|
|
28
|
-
export default {
|
|
29
|
-
id: ID,
|
|
30
|
-
severity: 'warning',
|
|
31
|
-
scope: ['diagnose'],
|
|
32
|
-
autoCorrect: 'refuse-and-print',
|
|
33
|
-
detect(state) {
|
|
34
|
-
const status = state?.fs?.worktreeBootstrapStatus ?? {};
|
|
35
|
-
const entries = Object.entries(status);
|
|
36
|
-
if (entries.length === 0) return null;
|
|
37
|
-
const missing = entries
|
|
38
|
-
.map(([wtPath, files]) => {
|
|
39
|
-
const missingFiles = [];
|
|
40
|
-
if (!files.dotEnv) missingFiles.push('.env');
|
|
41
|
-
if (!files.dotMcp) missingFiles.push('.mcp.json');
|
|
42
|
-
return { wtPath, missingFiles };
|
|
43
|
-
})
|
|
44
|
-
.filter((entry) => entry.missingFiles.length > 0);
|
|
45
|
-
if (missing.length === 0) return null;
|
|
46
|
-
const detailLines = missing.map(
|
|
47
|
-
(m) => ` - ${m.wtPath}: missing ${m.missingFiles.join(', ')}`,
|
|
48
|
-
);
|
|
49
|
-
return {
|
|
50
|
-
id: ID,
|
|
51
|
-
severity: 'warning',
|
|
52
|
-
scope: state?.scope ?? '',
|
|
53
|
-
summary: `${missing.length} worktree(s) missing bootstrap files (.env / .mcp.json)`,
|
|
54
|
-
detail: [
|
|
55
|
-
'Worktrees with missing bootstrap files:',
|
|
56
|
-
...detailLines,
|
|
57
|
-
'',
|
|
58
|
-
'Tests that read these vars will silently degrade in the affected worktrees.',
|
|
59
|
-
].join('\n'),
|
|
60
|
-
fixCommand: buildFixCommand(missing),
|
|
61
|
-
autoCorrectable: false,
|
|
62
|
-
};
|
|
63
|
-
},
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Build a copy recipe the operator can run from the main checkout. Each
|
|
68
|
-
* missing file gets its own `cp` line targeting the affected worktree.
|
|
69
|
-
*
|
|
70
|
-
* @param {Array<{wtPath: string, missingFiles: string[]}>} missing
|
|
71
|
-
* @returns {string}
|
|
72
|
-
*/
|
|
73
|
-
function buildFixCommand(missing) {
|
|
74
|
-
const lines = [];
|
|
75
|
-
for (const { wtPath, missingFiles } of missing) {
|
|
76
|
-
for (const file of missingFiles) {
|
|
77
|
-
lines.push(`cp "${file}" "${wtPath}/${file}"`);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
return lines.join(' && ');
|
|
81
|
-
}
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* worktree-residue-biome — refuse-and-print check.
|
|
3
|
-
*
|
|
4
|
-
* Detects partial-reap residue under `.worktrees/` where a previous Story's
|
|
5
|
-
* tree was deleted incompletely and left a nested `biome.json` behind. The
|
|
6
|
-
* root `npm run lint` walks into the orphan tree, hits the nested config,
|
|
7
|
-
* and fails. The close-time biome block is the canonical symptom.
|
|
8
|
-
*
|
|
9
|
-
* Surface: `story-close` preflight, the `/diagnose` CLI, and retro.
|
|
10
|
-
* Surfaced as a blocker so the operator clears the residue
|
|
11
|
-
* with `rm -rf` before close re-runs the validation chain.
|
|
12
|
-
*
|
|
13
|
-
* AutoCorrect is `refuse-and-print`. Recursive deletion under `.worktrees/`
|
|
14
|
-
* is technically blessed by the README, but the residue may include
|
|
15
|
-
* partially-merged work the operator wants to inspect first. We print the
|
|
16
|
-
* exact `rm -rf` recipe rather than running it.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
const ID = 'worktree-residue-biome';
|
|
20
|
-
|
|
21
|
-
export default {
|
|
22
|
-
id: ID,
|
|
23
|
-
severity: 'blocker',
|
|
24
|
-
scope: ['story-close', 'diagnose', 'retro'],
|
|
25
|
-
autoCorrect: 'refuse-and-print',
|
|
26
|
-
detect(state) {
|
|
27
|
-
const orphans = state?.fs?.worktreeBiomeOrphans ?? [];
|
|
28
|
-
if (!Array.isArray(orphans) || orphans.length === 0) return null;
|
|
29
|
-
const lines = orphans.map((p) => ` - ${p}`).join('\n');
|
|
30
|
-
return {
|
|
31
|
-
id: ID,
|
|
32
|
-
severity: 'blocker',
|
|
33
|
-
scope: state?.scope ?? '',
|
|
34
|
-
summary: `${orphans.length} orphan worktree(s) with nested biome.json — root biome lint will fail`,
|
|
35
|
-
detail: `Nested biome.json files found under .worktrees/ subdirectories:\n${lines}\n\nThese block root \`npm run lint\` because biome walks into them and resolves the nested config.`,
|
|
36
|
-
fixCommand: buildFixCommand(orphans),
|
|
37
|
-
autoCorrectable: false,
|
|
38
|
-
};
|
|
39
|
-
},
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Build the `rm -rf` command(s) the operator should run to clear the
|
|
44
|
-
* residue. Multiple orphans get a single chained command so it can be
|
|
45
|
-
* copy-pasted as one line.
|
|
46
|
-
*
|
|
47
|
-
* @param {string[]} orphans
|
|
48
|
-
* @returns {string}
|
|
49
|
-
*/
|
|
50
|
-
function buildFixCommand(orphans) {
|
|
51
|
-
if (orphans.length === 0) return '';
|
|
52
|
-
// Each path is already an absolute orphan worktree directory; deleting
|
|
53
|
-
// the whole directory is the canonical reap recipe.
|
|
54
|
-
return orphans.map((p) => `rm -rf "${p}"`).join(' && ');
|
|
55
|
-
}
|