mandrel 2.0.0 → 2.2.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 +59 -28
- package/.agents/agents/acceptance-critic.md +20 -9
- package/.agents/agents/story-worker.md +45 -48
- package/.agents/audit-checklists/performance.md +1 -1
- package/.agents/docs/SDLC.md +60 -46
- package/.agents/docs/agentrc-reference.json +8 -13
- package/.agents/docs/configuration.md +33 -57
- package/.agents/docs/execution-reference.md +39 -10
- package/.agents/docs/quality-gates.md +17 -19
- package/.agents/docs/workflows.md +6 -6
- package/.agents/instructions.md +64 -79
- package/.agents/rules/ci-remediation.md +3 -3
- package/.agents/rules/gherkin-standards.md +10 -0
- package/.agents/rules/git-conventions-reference.md +42 -51
- package/.agents/schemas/acceptance-eval-verdict.schema.json +2 -2
- package/.agents/schemas/agentrc.schema.json +35 -46
- package/.agents/schemas/audit-rules.json +59 -1
- package/.agents/schemas/audit-rules.schema.json +33 -1
- package/.agents/schemas/lifecycle/README.md +1 -2
- package/.agents/schemas/lifecycle/ledger-record.schema.json +1 -1
- package/.agents/schemas/lifecycle/merge.flip-failed.schema.json +33 -0
- package/.agents/schemas/lifecycle/merge.unlanded.schema.json +1 -0
- package/.agents/schemas/lifecycle/story.merged.schema.json +1 -1
- package/.agents/schemas/signal-event.schema.json +3 -3
- package/.agents/schemas/story-deliver-terminal.schema.json +152 -0
- package/.agents/schemas/validation-evidence.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +24 -68
- package/.agents/scripts/agents-bootstrap-github.js +1 -1
- package/.agents/scripts/bootstrap.js +3 -3
- package/.agents/scripts/check-dead-exports.js +43 -104
- package/.agents/scripts/check-doc-links.js +2 -2
- package/.agents/scripts/check-lifecycle-lint.js +1 -1
- package/.agents/scripts/check-workflow-cli-lint.js +91 -0
- package/.agents/scripts/deliver-recover.js +122 -0
- package/.agents/scripts/drain-pending-cleanup.js +1 -1
- package/.agents/scripts/evidence-gate.js +20 -50
- package/.agents/scripts/generate-skills-index.js +17 -1
- package/.agents/scripts/generate-workflows-doc.js +4 -4
- package/.agents/scripts/lib/ITicketingProvider.js +1 -19
- package/.agents/scripts/lib/audit-suite/selector.js +323 -23
- package/.agents/scripts/lib/baselines/kinds/maintainability.js +0 -11
- package/.agents/scripts/lib/bootstrap/ci-workflow-template.js +28 -33
- package/.agents/scripts/lib/bootstrap/manifest.js +8 -11
- package/.agents/scripts/lib/bootstrap/project-bootstrap.js +30 -53
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -2
- package/.agents/scripts/lib/checks/core-bare-clean.js +4 -1
- package/.agents/scripts/lib/checks/index.js +1 -1
- package/.agents/scripts/lib/checks/loop-health.js +12 -11
- package/.agents/scripts/lib/checks/state.js +17 -248
- package/.agents/scripts/lib/checks/story-init-not-backgrounded.js +3 -3
- package/.agents/scripts/lib/checks/subagent-agent-tool-required.js +3 -4
- package/.agents/scripts/lib/checks/worktree-bootstrap-env.js +2 -2
- package/.agents/scripts/lib/checks/worktree-residue-biome.js +3 -3
- package/.agents/scripts/lib/cli-args.js +23 -2
- package/.agents/scripts/lib/close-validation/gates.js +13 -13
- package/.agents/scripts/lib/close-validation/projections/inputs.js +7 -7
- package/.agents/scripts/lib/close-validation/projections/maintainability.js +12 -12
- package/.agents/scripts/lib/close-validation/runner.js +13 -21
- package/.agents/scripts/lib/close-validation/telemetry.js +17 -8
- package/.agents/scripts/lib/config/acceptance-eval.js +2 -2
- package/.agents/scripts/lib/config/delivery-routing.js +7 -6
- package/.agents/scripts/lib/config/explain.js +10 -16
- package/.agents/scripts/lib/config/github.js +7 -5
- package/.agents/scripts/lib/config/limits.js +15 -25
- package/.agents/scripts/lib/config/quality.js +11 -14
- package/.agents/scripts/lib/config/runners.js +8 -21
- package/.agents/scripts/lib/config/temp-paths.js +18 -56
- package/.agents/scripts/lib/config-settings-schema-delivery.js +34 -16
- package/.agents/scripts/lib/config-settings-schema-quality.js +9 -2
- package/.agents/scripts/lib/config-settings-schema.js +48 -22
- package/.agents/scripts/lib/dead-exports-knip.js +105 -0
- package/.agents/scripts/lib/dead-exports-mode.js +51 -0
- package/.agents/scripts/lib/duplicate-search.js +38 -7
- package/.agents/scripts/lib/findings/promote-finding.js +23 -14
- package/.agents/scripts/lib/format-generated-json.js +97 -0
- package/.agents/scripts/lib/framework-version.js +19 -189
- package/.agents/scripts/lib/gh-exec.js +8 -0
- package/.agents/scripts/lib/git-branch-lifecycle.js +0 -158
- package/.agents/scripts/lib/git-utils.js +0 -14
- package/.agents/scripts/lib/json-utils.js +1 -2
- package/.agents/scripts/lib/label-constants.js +0 -15
- package/.agents/scripts/lib/label-taxonomy.js +1 -12
- package/.agents/scripts/lib/observability/active-story-env.js +42 -163
- package/.agents/scripts/lib/observability/runtime-friction.js +243 -0
- package/.agents/scripts/lib/observability/signal-validator.js +4 -4
- package/.agents/scripts/lib/observability/signals-writer.js +6 -82
- package/.agents/scripts/lib/observability/source-classifier.js +2 -2
- package/.agents/scripts/lib/observability/tool-trace-hook.js +2 -12
- package/.agents/scripts/lib/orchestration/acceptance-clusters.js +1 -1
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +2 -2
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +43 -45
- package/.agents/scripts/lib/orchestration/change-set.js +103 -0
- package/.agents/scripts/lib/orchestration/code-review.js +70 -191
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +3 -3
- package/.agents/scripts/lib/orchestration/deliver-recover.js +328 -0
- package/.agents/scripts/lib/orchestration/detectors-phase.js +12 -6
- package/.agents/scripts/lib/orchestration/git-cleanup/phases/fast-forward.js +34 -0
- package/.agents/scripts/lib/orchestration/lease-guard-shared.js +3 -2
- package/.agents/scripts/lib/orchestration/lifecycle/emit-ledger-event.js +142 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-loop-tick.js +9 -11
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-flip-failed.js +86 -0
- package/.agents/scripts/lib/orchestration/lifecycle/emit-merge-unlanded.js +37 -103
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/README.md +7 -3
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/watcher.js +50 -85
- package/.agents/scripts/lib/orchestration/lifecycle/trace-logger.js +3 -14
- package/.agents/scripts/lib/orchestration/merge-block-class.js +76 -20
- package/.agents/scripts/lib/orchestration/merge-poll.js +104 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +116 -33
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +26 -36
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +31 -22
- package/.agents/scripts/lib/orchestration/plan-metrics.js +38 -6
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +16 -6
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +173 -25
- package/.agents/scripts/lib/orchestration/plan-persist/plan-context-source.js +116 -0
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +280 -100
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +472 -55
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +21 -16
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +509 -0
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +230 -0
- package/.agents/scripts/lib/orchestration/planning/authoring-context.js +41 -40
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +1 -2
- package/.agents/scripts/lib/orchestration/planning/spec-authoring-grounding.js +1 -1
- package/.agents/scripts/lib/orchestration/resolve-stories.js +344 -0
- package/.agents/scripts/lib/orchestration/retro-proposals.js +7 -7
- package/.agents/scripts/lib/orchestration/review-depth.js +105 -40
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +3 -13
- package/.agents/scripts/lib/orchestration/review-providers/native.js +1 -154
- package/.agents/scripts/lib/orchestration/review-providers/review-depth.js +3 -2
- package/.agents/scripts/lib/orchestration/review-providers/security-review.js +1 -1
- package/.agents/scripts/lib/orchestration/review-providers/types.js +5 -4
- package/.agents/scripts/lib/orchestration/review-providers/ultrareview.js +1 -1
- package/.agents/scripts/lib/orchestration/run-epilogue.js +374 -16
- package/.agents/scripts/lib/orchestration/single-story-close/phases/auto-merge.js +24 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +11 -9
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +4 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +4 -13
- package/.agents/scripts/lib/orchestration/single-story-close/phases/confirm-merge.js +608 -152
- package/.agents/scripts/lib/orchestration/single-story-close/phases/options.js +72 -30
- package/.agents/scripts/lib/orchestration/single-story-close/phases/post-land.js +305 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pull-request.js +1 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-block.js +12 -8
- package/.agents/scripts/lib/orchestration/single-story-close/phases/worktree-reap.js +37 -4
- package/.agents/scripts/lib/orchestration/single-story-close/phases/wrong-tree-guard.js +2 -2
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +264 -43
- package/.agents/scripts/lib/orchestration/single-story-lease-guard.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/format-autofix.js +10 -10
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +104 -279
- package/.agents/scripts/lib/orchestration/story-close/phases/local-lens-review.js +191 -0
- package/.agents/scripts/lib/orchestration/story-close/phases/review-core.js +120 -0
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +360 -0
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +75 -14
- package/.agents/scripts/lib/orchestration/story-init-remote.js +12 -8
- package/.agents/scripts/lib/orchestration/story-plan-state.js +14 -29
- package/.agents/scripts/lib/orchestration/task-body-validator.js +52 -7
- package/.agents/scripts/lib/orchestration/ticket-lease.js +27 -74
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +119 -14
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +3 -4
- package/.agents/scripts/lib/orchestration/ticket-validator.js +121 -18
- package/.agents/scripts/lib/orchestration/ticketing/bulk.js +14 -47
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +19 -32
- package/.agents/scripts/lib/orchestration/ticketing/transition.js +61 -1
- package/.agents/scripts/lib/orchestration/ticketing.js +0 -1
- package/.agents/scripts/lib/plan-phase-cleanup.js +12 -14
- package/.agents/scripts/lib/planning-corpus.js +12 -286
- package/.agents/scripts/lib/preflight-runner.js +2 -2
- package/.agents/scripts/lib/qa/qa-context-hydrator.js +5 -5
- package/.agents/scripts/lib/signals/index.js +4 -17
- package/.agents/scripts/lib/signals/read.js +35 -35
- package/.agents/scripts/lib/signals/schema.js +8 -11
- package/.agents/scripts/lib/signals/span-tree.js +7 -7
- package/.agents/scripts/lib/signals/write.js +0 -1
- package/.agents/scripts/lib/single-story/story-merged-notify.js +13 -2
- package/.agents/scripts/lib/skills/parse-skill.js +16 -3
- package/.agents/scripts/lib/story-adjacency.js +8 -7
- package/.agents/scripts/lib/story-body/story-body.js +81 -13
- package/.agents/scripts/lib/templates/decomposer-prompts.js +15 -16
- package/.agents/scripts/lib/test-env.js +14 -1
- package/.agents/scripts/lib/test-tiers.js +0 -3
- package/.agents/scripts/lib/ticket-body-sections.js +0 -14
- package/.agents/scripts/lib/validation-evidence.js +31 -59
- package/.agents/scripts/lib/wave-runner/live-probe.js +315 -0
- package/.agents/scripts/lib/wave-runner/ready-set.js +32 -6
- package/.agents/scripts/lib/worktree/lifecycle/pending-cleanup.js +1 -1
- package/.agents/scripts/lib/worktree/lifecycle/reap.js +68 -19
- package/.agents/scripts/lib/worktree/lifecycle-manager.js +1 -2
- package/.agents/scripts/plan-context.js +38 -7
- package/.agents/scripts/plan-critics.js +203 -0
- package/.agents/scripts/plan-persist.js +145 -35
- package/.agents/scripts/plan-run-epilogue.js +83 -38
- package/.agents/scripts/post-structured-comment.js +0 -38
- package/.agents/scripts/pr-watch-with-update.js +43 -22
- package/.agents/scripts/providers/github/compose.js +0 -1
- package/.agents/scripts/providers/github/errors.js +0 -19
- package/.agents/scripts/providers/github/issues.js +1 -11
- package/.agents/scripts/providers/github/mappers.js +5 -0
- package/.agents/scripts/providers/github/sub-issues.js +0 -47
- package/.agents/scripts/providers/github/tickets.js +33 -153
- package/.agents/scripts/providers/github.js +17 -6
- package/.agents/scripts/quality-preview.js +13 -6
- package/.agents/scripts/resolve-stories.js +236 -0
- package/.agents/scripts/run-coverage.js +4 -1
- package/.agents/scripts/run-lint.js +2 -2
- package/.agents/scripts/run-verify.js +31 -2
- package/.agents/scripts/signals-view.js +9 -10
- package/.agents/scripts/single-story-close.js +173 -18
- package/.agents/scripts/single-story-confirm-merge.js +288 -15
- package/.agents/scripts/single-story-init.js +6 -10
- package/.agents/scripts/stories-wave-tick.js +380 -53
- package/.agents/scripts/story-plan.js +3 -3
- package/.agents/scripts/update-ticket-state.js +8 -50
- package/.agents/skills/core/code-review-and-quality/SKILL.md +28 -450
- package/.agents/skills/core/code-review-and-quality/reference.md +458 -0
- package/.agents/skills/core/debugging-and-error-recovery/SKILL.md +22 -315
- package/.agents/skills/core/debugging-and-error-recovery/reference.md +323 -0
- package/.agents/skills/core/diagnose-friction/SKILL.md +14 -18
- package/.agents/skills/core/documentation-and-adrs/SKILL.md +25 -397
- package/.agents/skills/core/documentation-and-adrs/reference.md +403 -0
- package/.agents/skills/core/gates-and-baselines/SKILL.md +12 -12
- package/.agents/skills/core/idea-refinement/SKILL.md +3 -3
- package/.agents/skills/core/scope-triage/SKILL.md +3 -0
- package/.agents/skills/core/security-and-hardening/SKILL.md +22 -367
- package/.agents/skills/core/security-and-hardening/reference.md +375 -0
- package/.agents/skills/skills.index.json +2 -12
- package/.agents/skills/stack/qa/playwright-bdd/SKILL.md +2 -4
- package/.agents/skills/stack/qa/qa-explore-driving/SKILL.md +1 -1
- package/.agents/skills/stack/qa/qa-harness/SKILL.md +1 -3
- package/.agents/workflows/audit-architecture.md +3 -4
- package/.agents/workflows/audit-clean-code.md +4 -4
- package/.agents/workflows/audit-documentation.md +4 -5
- package/.agents/workflows/audit-lighthouse.md +8 -0
- package/.agents/workflows/audit-navigability.md +10 -0
- package/.agents/workflows/audit-performance.md +2 -3
- package/.agents/workflows/audit-quality.md +8 -9
- package/.agents/workflows/audit-security.md +1 -2
- package/.agents/workflows/audit-seo.md +10 -0
- package/.agents/workflows/audit-ux-ui.md +7 -0
- package/.agents/workflows/deliver.md +133 -45
- package/.agents/workflows/git-cleanup.md +2 -2
- package/.agents/workflows/git-deliver.md +1 -1
- package/.agents/workflows/helpers/acceptance-self-eval.md +34 -17
- package/.agents/workflows/helpers/code-quality-guardrails.md +15 -12
- package/.agents/workflows/helpers/code-review.md +14 -12
- package/.agents/workflows/helpers/deliver-story-reference.md +73 -32
- package/.agents/workflows/helpers/deliver-story.md +209 -118
- package/.agents/workflows/helpers/parallel-tooling.md +2 -2
- package/.agents/workflows/helpers/worktree-lifecycle.md +28 -32
- package/.agents/workflows/plan.md +239 -19
- package/.agents/workflows/qa-assist.md +6 -6
- package/.agents/workflows/qa-explore.md +3 -3
- package/.agents/workflows/qa-run.md +1 -5
- package/bin/mandrel.js +12 -1
- package/docs/CHANGELOG.md +62 -0
- package/lib/cli/registry.js +262 -19
- package/lib/cli/sync-agents.js +157 -0
- package/lib/cli/sync-commands.js +115 -6
- package/lib/cli/sync.js +168 -6
- package/lib/cli/update.js +105 -8
- package/lib/cli/version-helpers.js +131 -0
- package/lib/migrations/README.md +7 -5
- package/lib/migrations/index.js +17 -9
- package/lib/migrations/steps/2.1.0-retire-mi-drop-knobs.js +100 -0
- package/lib/migrations/steps/2.1.0-retire-verify-concurrency-cap.js +101 -0
- package/lib/migrations/steps/2.2.0-retire-epic-ac-tags.js +154 -0
- package/package.json +2 -2
- package/.agents/schemas/epic-perf-report.schema.json +0 -89
- package/.agents/schemas/lifecycle/acceptance.reconcile.failed.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.ok.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.skipped.schema.json +0 -13
- package/.agents/schemas/lifecycle/acceptance.reconcile.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/acceptance.reconcile.waived.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.automerge.end.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.automerge.start.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.blocked.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.cleanup.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.cleanup.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.close.end.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.complete.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.end.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.finalize.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.merge.armed.schema.json +0 -13
- package/.agents/schemas/lifecycle/epic.merge.blocked.schema.json +0 -14
- package/.agents/schemas/lifecycle/epic.merge.confirmed.schema.json +0 -17
- package/.agents/schemas/lifecycle/epic.merge.ready.schema.json +0 -15
- package/.agents/schemas/lifecycle/epic.plan.end.schema.json +0 -18
- package/.agents/schemas/lifecycle/epic.plan.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.snapshot.end.schema.json +0 -16
- package/.agents/schemas/lifecycle/epic.snapshot.start.schema.json +0 -12
- package/.agents/schemas/lifecycle/epic.watch.end.schema.json +0 -29
- package/.agents/schemas/lifecycle/epic.watch.start.schema.json +0 -16
- package/.agents/schemas/lifecycle/story.heartbeat.schema.json +0 -20
- package/.agents/schemas/risk-verdict.schema.json +0 -53
- package/.agents/schemas/story-perf-summary.schema.json +0 -73
- package/.agents/scripts/analyze-execution.js +0 -444
- package/.agents/scripts/check-prepush-recovery.js +0 -90
- package/.agents/scripts/lib/git-merge-orchestrator.js +0 -261
- package/.agents/scripts/lib/observability/baseline-refresh-rate.js +0 -221
- package/.agents/scripts/lib/observability/hook-heartbeat.js +0 -187
- package/.agents/scripts/lib/observability/perf-aggregator.js +0 -813
- package/.agents/scripts/lib/observability/perf-report-readers.js +0 -328
- package/.agents/scripts/lib/observability/perf-report-render.js +0 -182
- package/.agents/scripts/lib/orchestration/audit-lens-routing.js +0 -128
- package/.agents/scripts/lib/orchestration/bookkeeping-outbox.js +0 -273
- package/.agents/scripts/lib/orchestration/error-journal.js +0 -139
- package/.agents/scripts/lib/orchestration/lifecycle/emit-story-heartbeat.js +0 -155
- package/.agents/scripts/lib/orchestration/lifecycle/ledger-diff.js +0 -140
- package/.agents/scripts/lib/orchestration/lifecycle/listeners/merge-watcher.js +0 -665
- package/.agents/scripts/lib/orchestration/plan-review-routing.js +0 -63
- package/.agents/scripts/lib/orchestration/planning/risk-verdict.js +0 -104
- package/.agents/scripts/lib/orchestration/planning-context-budget.js +0 -213
- package/.agents/scripts/lib/orchestration/planning-risk.js +0 -194
- package/.agents/scripts/lib/orchestration/post-merge/phases/branch-cleanup.js +0 -56
- package/.agents/scripts/lib/orchestration/post-merge/phases/dashboard-refresh.js +0 -21
- package/.agents/scripts/lib/orchestration/post-merge/phases/notification.js +0 -78
- package/.agents/scripts/lib/orchestration/post-merge/phases/temp-cleanup.js +0 -68
- package/.agents/scripts/lib/orchestration/post-merge/phases/ticket-closure.js +0 -118
- package/.agents/scripts/lib/orchestration/post-merge/phases/worktree-reap.js +0 -397
- package/.agents/scripts/lib/orchestration/preflight-cache.js +0 -187
- package/.agents/scripts/lib/orchestration/resolve-plan-run.js +0 -155
- package/.agents/scripts/lib/orchestration/retro-perf-heuristics.js +0 -275
- package/.agents/scripts/lib/orchestration/story-progress/story-run-progress-writer.js +0 -400
- package/.agents/scripts/lib/single-story/confirm-merge-follow-ups.js +0 -36
- package/.agents/scripts/resolve-plan-run.js +0 -117
- package/.agents/skills/core/analyze-execution/SKILL.md +0 -101
|
@@ -6,16 +6,19 @@
|
|
|
6
6
|
* without importing upward from a top-level CLI file.
|
|
7
7
|
*
|
|
8
8
|
* Pure (modulo `gitSpawn`) — exposed helpers are:
|
|
9
|
-
* - matchesFilePattern
|
|
10
|
-
* - matchesAnyFilePattern
|
|
11
|
-
* -
|
|
12
|
-
*
|
|
9
|
+
* - matchesFilePattern — single file × single glob (picomatch, `dot`)
|
|
10
|
+
* - matchesAnyFilePattern — file list × pattern list, short-circuiting
|
|
11
|
+
* - selectSensitivePathClasses — change set × the manifest's `sensitivePaths`
|
|
12
|
+
* block; the review-depth derivation's matcher
|
|
13
|
+
* - selectAudits — main entry; reads audit-rules.json, runs `git
|
|
14
|
+
* diff --name-only`, applies keyword + glob
|
|
15
|
+
* rules.
|
|
13
16
|
*
|
|
14
17
|
* All rule-matching lives here; the former `select-audits.js` CLI wrapper
|
|
15
18
|
* was retired in #4482 (consumers call `selectAudits` via the barrel).
|
|
16
19
|
*/
|
|
17
20
|
|
|
18
|
-
import { readFileSync } from 'node:fs';
|
|
21
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
19
22
|
import fs from 'node:fs/promises';
|
|
20
23
|
import path from 'node:path';
|
|
21
24
|
import picomatch from 'picomatch';
|
|
@@ -197,6 +200,50 @@ export function selectLocalLenses({
|
|
|
197
200
|
return selected;
|
|
198
201
|
}
|
|
199
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Select the sensitive-path classes a change set touches, from the
|
|
205
|
+
* `sensitivePaths` block of [`audit-rules.json`](../../../schemas/audit-rules.json).
|
|
206
|
+
*
|
|
207
|
+
* This is the matcher behind the close-time review-depth derivation
|
|
208
|
+
* (`review-depth.js#deriveChangeLevel`, Story #4542): a change set intersecting
|
|
209
|
+
* any registered class's globs is *observably* sensitive — auth, a migration, a
|
|
210
|
+
* billing path — and earns a deep pass however narrow the diff is. The classes
|
|
211
|
+
* and their globs live in the manifest next to the lens `triggers.filePatterns`
|
|
212
|
+
* precisely so an operator can extend them without a code change, and matching
|
|
213
|
+
* runs through the same {@link matchesAnyFilePattern} picomatch machinery rather
|
|
214
|
+
* than a second matcher with its own glob semantics.
|
|
215
|
+
*
|
|
216
|
+
* An empty change set, an absent `sensitivePaths` block, or a class whose globs
|
|
217
|
+
* match nothing all contribute no class — the caller then resolves depth from
|
|
218
|
+
* diff width alone.
|
|
219
|
+
*
|
|
220
|
+
* Pure over its injected seam: `injectedRules` skips the manifest disk read.
|
|
221
|
+
* Selection order follows the manifest's declaration order, which is
|
|
222
|
+
* deterministic.
|
|
223
|
+
*
|
|
224
|
+
* @param {{
|
|
225
|
+
* changedFiles?: string[],
|
|
226
|
+
* injectedRules?: { sensitivePaths?: Record<string, { filePatterns?: string[] }> },
|
|
227
|
+
* }} [params]
|
|
228
|
+
* @returns {string[]} The matched class names, in manifest order.
|
|
229
|
+
*/
|
|
230
|
+
export function selectSensitivePathClasses({
|
|
231
|
+
changedFiles,
|
|
232
|
+
injectedRules,
|
|
233
|
+
} = {}) {
|
|
234
|
+
const files = Array.isArray(changedFiles) ? changedFiles : [];
|
|
235
|
+
if (files.length === 0) return [];
|
|
236
|
+
|
|
237
|
+
const rules = injectedRules ?? readAuditRulesSync();
|
|
238
|
+
const matched = [];
|
|
239
|
+
for (const [name, entry] of Object.entries(rules.sensitivePaths ?? {})) {
|
|
240
|
+
if (matchesAnyFilePattern(entry?.filePatterns ?? [], files)) {
|
|
241
|
+
matched.push(name);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
return matched;
|
|
245
|
+
}
|
|
246
|
+
|
|
200
247
|
/**
|
|
201
248
|
* Resolve the consumer's navigability route globs from the resolved config.
|
|
202
249
|
* Reads `delivery.quality.navigability.routeGlobs` — the route-tree SSOT the
|
|
@@ -221,9 +268,9 @@ export function resolveNavigabilityRouteGlobs(config) {
|
|
|
221
268
|
* `false` (the unconfigured no-op), so the existing change-set-scoped lens
|
|
222
269
|
* selection is unchanged.
|
|
223
270
|
*
|
|
224
|
-
*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
271
|
+
* A pure predicate over a change set — the same input {@link selectAudits} and
|
|
272
|
+
* {@link selectLocalLenses} already match against. The caller unions its result
|
|
273
|
+
* into the lens roster it is assembling; no new routing machinery is added.
|
|
227
274
|
*
|
|
228
275
|
* @param {{ changedFiles?: string[], config?: object|null }} params
|
|
229
276
|
* @returns {boolean}
|
|
@@ -234,6 +281,206 @@ export function routesNavigabilityLens({ changedFiles, config } = {}) {
|
|
|
234
281
|
return matchesAnyFilePattern(globs, changedFiles ?? []);
|
|
235
282
|
}
|
|
236
283
|
|
|
284
|
+
/**
|
|
285
|
+
* Package names (or scope/name segments of them) that declare a **web
|
|
286
|
+
* rendering surface**. Matched against the consumer's root `package.json`
|
|
287
|
+
* `dependencies` + `devDependencies` keys, segment-wise, so `@angular/core`
|
|
288
|
+
* matches via `angular` and `@remix-run/react` via `react`.
|
|
289
|
+
*/
|
|
290
|
+
const WEB_FRAMEWORK_PACKAGES = Object.freeze([
|
|
291
|
+
'react',
|
|
292
|
+
'next',
|
|
293
|
+
'vue',
|
|
294
|
+
'svelte',
|
|
295
|
+
'sveltekit',
|
|
296
|
+
'astro',
|
|
297
|
+
'nuxt',
|
|
298
|
+
'remix',
|
|
299
|
+
'remix-run',
|
|
300
|
+
'gatsby',
|
|
301
|
+
'angular',
|
|
302
|
+
]);
|
|
303
|
+
|
|
304
|
+
/** File extensions whose presence in the source tree implies a web surface. */
|
|
305
|
+
const WEB_ASSET_EXTENSIONS = Object.freeze(['.html', '.css', '.jsx', '.tsx']);
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* Directories the web-surface file scan never descends into. `node_modules` is
|
|
309
|
+
* the load-bearing one (a dependency's bundled `.css` says nothing about the
|
|
310
|
+
* consumer's own surface); the test directories are excluded because a fixture
|
|
311
|
+
* `.html` is a test artifact, not a shipped page.
|
|
312
|
+
*/
|
|
313
|
+
const WEB_SCAN_SKIP_DIRS = Object.freeze([
|
|
314
|
+
'node_modules',
|
|
315
|
+
'.git',
|
|
316
|
+
'.worktrees',
|
|
317
|
+
'dist',
|
|
318
|
+
'build',
|
|
319
|
+
'out',
|
|
320
|
+
'coverage',
|
|
321
|
+
'temp',
|
|
322
|
+
'tmp',
|
|
323
|
+
'tests',
|
|
324
|
+
'test',
|
|
325
|
+
'__tests__',
|
|
326
|
+
'__mocks__',
|
|
327
|
+
'spec',
|
|
328
|
+
'e2e',
|
|
329
|
+
'fixtures',
|
|
330
|
+
]);
|
|
331
|
+
|
|
332
|
+
/** Scan bounds — the probe is a cheap heuristic, not an exhaustive crawl. */
|
|
333
|
+
const WEB_SCAN_MAX_DEPTH = 6;
|
|
334
|
+
const WEB_SCAN_MAX_ENTRIES = 4000;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Process-lifetime memo for the filesystem half of the web-surface probe,
|
|
338
|
+
* keyed by project root. The config half ({@link resolveNavigabilityRouteGlobs})
|
|
339
|
+
* is a free object read and is deliberately NOT memoized, so a caller passing a
|
|
340
|
+
* different config never reads a stale answer.
|
|
341
|
+
*
|
|
342
|
+
* @type {Map<string, boolean>}
|
|
343
|
+
*/
|
|
344
|
+
const _webSurfaceFsCache = new Map();
|
|
345
|
+
|
|
346
|
+
/** Test-only: drop the memo so a fixture root can be re-probed after edits. */
|
|
347
|
+
export function _resetWebSurfaceCache() {
|
|
348
|
+
_webSurfaceFsCache.clear();
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* True when the root `package.json` declares a web-framework dependency.
|
|
353
|
+
* Returns `null` — *indeterminate* — when the manifest exists but cannot be
|
|
354
|
+
* read or parsed; the caller fails open on that (see {@link hasWebSurface}).
|
|
355
|
+
* A genuinely absent manifest (`ENOENT`) is determinate: there is no
|
|
356
|
+
* declaration, so there is no signal, and the file scan gets its turn.
|
|
357
|
+
*
|
|
358
|
+
* @param {string} root
|
|
359
|
+
* @returns {boolean|null}
|
|
360
|
+
*/
|
|
361
|
+
function declaresWebFramework(root) {
|
|
362
|
+
let raw;
|
|
363
|
+
try {
|
|
364
|
+
raw = readFileSync(path.join(root, 'package.json'), 'utf8');
|
|
365
|
+
} catch (err) {
|
|
366
|
+
if (err?.code === 'ENOENT') return false;
|
|
367
|
+
return null;
|
|
368
|
+
}
|
|
369
|
+
let pkg;
|
|
370
|
+
try {
|
|
371
|
+
pkg = JSON.parse(raw);
|
|
372
|
+
} catch {
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
const names = [
|
|
376
|
+
...Object.keys(pkg?.dependencies ?? {}),
|
|
377
|
+
...Object.keys(pkg?.devDependencies ?? {}),
|
|
378
|
+
];
|
|
379
|
+
return names.some((name) =>
|
|
380
|
+
name
|
|
381
|
+
.replace(/^@/, '')
|
|
382
|
+
.split('/')
|
|
383
|
+
.some((segment) => WEB_FRAMEWORK_PACKAGES.includes(segment)),
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* True when a bounded scan of the source tree finds a web asset file
|
|
389
|
+
* (`.html` / `.css` / `.jsx` / `.tsx`) outside the skipped directories.
|
|
390
|
+
* Returns `null` — *indeterminate* — when the root itself is unreadable, or
|
|
391
|
+
* when the scan exhausted its entry budget without finding one: a truncated
|
|
392
|
+
* scan genuinely did not finish looking, and reporting "no web surface" from
|
|
393
|
+
* a half-walked tree would silently drop lens coverage.
|
|
394
|
+
*
|
|
395
|
+
* Uses `readdirSync(withFileTypes)` rather than `git ls-files` — a filesystem
|
|
396
|
+
* check suffices, and shelling out to git would make the probe cost a process
|
|
397
|
+
* spawn on every selection.
|
|
398
|
+
*
|
|
399
|
+
* @param {string} root
|
|
400
|
+
* @returns {boolean|null}
|
|
401
|
+
*/
|
|
402
|
+
function scanForWebAssets(root) {
|
|
403
|
+
let budget = WEB_SCAN_MAX_ENTRIES;
|
|
404
|
+
const queue = [{ dir: root, depth: 0 }];
|
|
405
|
+
let rootRead = false;
|
|
406
|
+
|
|
407
|
+
while (queue.length > 0) {
|
|
408
|
+
const { dir, depth } = queue.shift();
|
|
409
|
+
let entries;
|
|
410
|
+
try {
|
|
411
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
412
|
+
} catch {
|
|
413
|
+
// A single unreadable subdirectory is skipped: a permissions oddity is
|
|
414
|
+
// far likelier than an entire web surface hiding behind it. An
|
|
415
|
+
// unreadable ROOT is a different story — see below.
|
|
416
|
+
if (dir === root) return null;
|
|
417
|
+
continue;
|
|
418
|
+
}
|
|
419
|
+
if (dir === root) rootRead = true;
|
|
420
|
+
|
|
421
|
+
for (const entry of entries) {
|
|
422
|
+
if (budget-- <= 0) return null; // truncated ⇒ indeterminate
|
|
423
|
+
if (entry.isDirectory()) {
|
|
424
|
+
if (WEB_SCAN_SKIP_DIRS.includes(entry.name)) continue;
|
|
425
|
+
if (depth + 1 <= WEB_SCAN_MAX_DEPTH) {
|
|
426
|
+
queue.push({ dir: path.join(dir, entry.name), depth: depth + 1 });
|
|
427
|
+
}
|
|
428
|
+
continue;
|
|
429
|
+
}
|
|
430
|
+
if (WEB_ASSET_EXTENSIONS.includes(path.extname(entry.name))) return true;
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
return rootRead ? false : null;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/**
|
|
437
|
+
* Decide whether the project has a **web surface** — the applicability
|
|
438
|
+
* predicate behind the `target: "web"` gate in `audit-rules.json` (#4579).
|
|
439
|
+
*
|
|
440
|
+
* Mandrel is materialized into other projects, so this cannot be a property of
|
|
441
|
+
* *this* repository: a consumer with a real web surface MUST still get the web
|
|
442
|
+
* lenses. It is therefore derived from observables in the consumer's own
|
|
443
|
+
* checkout, and deliberately NOT from a new `.agentrc` key — a new key would
|
|
444
|
+
* have to be threaded through the runtime AJV (`lib/config-settings-schema*.js`)
|
|
445
|
+
* *and* the published mirror, and would make consumers hand-configure something
|
|
446
|
+
* already derivable.
|
|
447
|
+
*
|
|
448
|
+
* A project is web-capable when ANY of these hold:
|
|
449
|
+
* 1. `delivery.quality.navigability.routeGlobs` is configured — the existing
|
|
450
|
+
* web signal the navigability lens already routes off.
|
|
451
|
+
* 2. The root `package.json` declares a web framework (react / next / vue /
|
|
452
|
+
* svelte / astro / nuxt / remix / gatsby / angular).
|
|
453
|
+
* 3. A bounded source scan finds a `.html` / `.css` / `.jsx` / `.tsx` file
|
|
454
|
+
* outside test directories.
|
|
455
|
+
*
|
|
456
|
+
* **Fail direction: OPEN.** When a signal is indeterminate — an unparseable
|
|
457
|
+
* `package.json`, an unreadable root, a scan that hit its entry budget — the
|
|
458
|
+
* project is treated as web-capable. A false positive costs one wasted lens
|
|
459
|
+
* run; a false negative silently drops audit coverage on a project that has a
|
|
460
|
+
* real web surface, and nothing downstream would ever report the omission.
|
|
461
|
+
* Wasted spend is recoverable; dropped coverage is not.
|
|
462
|
+
*
|
|
463
|
+
* @param {{ config?: object|null, projectRoot?: string }} [params]
|
|
464
|
+
* @returns {boolean}
|
|
465
|
+
*/
|
|
466
|
+
export function hasWebSurface({ config, projectRoot = PROJECT_ROOT } = {}) {
|
|
467
|
+
if (resolveNavigabilityRouteGlobs(config).length > 0) return true;
|
|
468
|
+
|
|
469
|
+
if (_webSurfaceFsCache.has(projectRoot)) {
|
|
470
|
+
return _webSurfaceFsCache.get(projectRoot);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const declared = declaresWebFramework(projectRoot);
|
|
474
|
+
// `null` is indeterminate, not false — fail open.
|
|
475
|
+
const result =
|
|
476
|
+
declared === null || declared === true
|
|
477
|
+
? true
|
|
478
|
+
: scanForWebAssets(projectRoot) !== false;
|
|
479
|
+
|
|
480
|
+
_webSurfaceFsCache.set(projectRoot, result);
|
|
481
|
+
return result;
|
|
482
|
+
}
|
|
483
|
+
|
|
237
484
|
/**
|
|
238
485
|
* Test a single filename against a single glob pattern using the project's
|
|
239
486
|
* configured matcher semantics (`picomatch` with `dot: true`). Exported so
|
|
@@ -261,6 +508,16 @@ export function matchesAnyFilePattern(patterns, files) {
|
|
|
261
508
|
* @param {number} params.ticketId
|
|
262
509
|
* @param {string} params.gate
|
|
263
510
|
* @param {import('../ITicketingProvider.js').ITicketingProvider} params.provider
|
|
511
|
+
* @param {string[]} [params.changedFiles]
|
|
512
|
+
* The change set to select over, when the caller already knows it. Supplying
|
|
513
|
+
* it skips the internal `git diff` entirely and `baseBranch` / `headRef` are
|
|
514
|
+
* then unused. Callers whose change set is NOT the working tree's
|
|
515
|
+
* `baseBranch...headRef` MUST pass this: the plan-run epilogue runs from the
|
|
516
|
+
* main checkout *after* its Stories have merged, where any `main...HEAD`
|
|
517
|
+
* range is empty by construction and selection would silently degrade to
|
|
518
|
+
* keyword-only matching (Story #4571). An empty array is honoured as
|
|
519
|
+
* "the set is known, and it is empty" — distinct from omitting the option,
|
|
520
|
+
* which means "derive it from git".
|
|
264
521
|
* @param {string} [params.baseBranch]
|
|
265
522
|
* @param {string} [params.headRef]
|
|
266
523
|
* Git ref whose diff-against-`baseBranch` defines the change set. Defaults
|
|
@@ -284,6 +541,9 @@ export function matchesAnyFilePattern(patterns, files) {
|
|
|
284
541
|
* Test-only seam to drive the `--gate-mode` / `MANDREL_GATE_MODE=1`
|
|
285
542
|
* detection; production callers leave unset and `isGateMode` reads
|
|
286
543
|
* `process.argv` / `process.env`.
|
|
544
|
+
* @param {typeof hasWebSurface} [params.hasWebSurfaceFn]
|
|
545
|
+
* Test-only seam overriding the web-surface probe behind the
|
|
546
|
+
* `target: "web"` applicability gate. Production callers leave unset.
|
|
287
547
|
*
|
|
288
548
|
* Returns either the success envelope (`{ selectedAudits, ticketId, gate, context }`)
|
|
289
549
|
* OR the degraded envelope (`{ ok: false, degraded: true, reason, detail }`)
|
|
@@ -294,11 +554,13 @@ export async function selectAudits({
|
|
|
294
554
|
ticketId,
|
|
295
555
|
gate,
|
|
296
556
|
provider,
|
|
557
|
+
changedFiles: injectedChangedFiles,
|
|
297
558
|
baseBranch = 'main',
|
|
298
559
|
headRef = 'HEAD',
|
|
299
560
|
injectedGitSpawn,
|
|
300
561
|
gitTimeoutMsOverride,
|
|
301
562
|
gateModeOpts,
|
|
563
|
+
hasWebSurfaceFn = hasWebSurface,
|
|
302
564
|
}) {
|
|
303
565
|
const config = resolveConfig();
|
|
304
566
|
const timeoutMs = gitTimeoutMsOverride ?? DEFAULT_GIT_TIMEOUT_MS;
|
|
@@ -323,6 +585,14 @@ export async function selectAudits({
|
|
|
323
585
|
|
|
324
586
|
const runGit = injectedGitSpawn ?? (async (...args) => gitSpawn(...args));
|
|
325
587
|
|
|
588
|
+
// A caller that already knows the change set supplies it, and no git ref is
|
|
589
|
+
// consulted at all — `baseBranch` / `headRef` describe a diff that is not
|
|
590
|
+
// being taken. The plan-run epilogue is the motivating caller (Story #4571):
|
|
591
|
+
// it resolves the run's landed diff from the Stories' squash-merges, and any
|
|
592
|
+
// `main...HEAD` range it could name from the main checkout post-land is
|
|
593
|
+
// empty by construction.
|
|
594
|
+
const hasInjectedChangedFiles = Array.isArray(injectedChangedFiles);
|
|
595
|
+
|
|
326
596
|
// Resolve `headRef` to a commit before diffing. A non-default `headRef`
|
|
327
597
|
// (Epic-mode callers pass `refs/heads/epic/<id>`) that the repo can't
|
|
328
598
|
// resolve means the requested Epic's branch is not present in this
|
|
@@ -331,7 +601,7 @@ export async function selectAudits({
|
|
|
331
601
|
// degraded signal instead of leaking the wrong scope. `HEAD` is always
|
|
332
602
|
// resolvable in a valid repo, so the default-path callers skip the probe
|
|
333
603
|
// cost on the common case.
|
|
334
|
-
if (headRef !== 'HEAD') {
|
|
604
|
+
if (!hasInjectedChangedFiles && headRef !== 'HEAD') {
|
|
335
605
|
let resolved;
|
|
336
606
|
try {
|
|
337
607
|
resolved = await withTimeout(
|
|
@@ -358,18 +628,22 @@ export async function selectAudits({
|
|
|
358
628
|
}
|
|
359
629
|
}
|
|
360
630
|
|
|
361
|
-
let changedFiles =
|
|
631
|
+
let changedFiles = hasInjectedChangedFiles
|
|
632
|
+
? injectedChangedFiles.map((f) => String(f).trim()).filter(Boolean)
|
|
633
|
+
: [];
|
|
362
634
|
try {
|
|
363
|
-
const diff =
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
635
|
+
const diff = hasInjectedChangedFiles
|
|
636
|
+
? null
|
|
637
|
+
: await withTimeout(
|
|
638
|
+
runGit(
|
|
639
|
+
process.cwd(),
|
|
640
|
+
'diff',
|
|
641
|
+
'--name-only',
|
|
642
|
+
`${baseBranch}...${headRef}`,
|
|
643
|
+
),
|
|
644
|
+
timeoutMs,
|
|
645
|
+
{ label: 'select-audits git diff' },
|
|
646
|
+
);
|
|
373
647
|
if (diff?.status === 0) {
|
|
374
648
|
changedFiles = diff.stdout
|
|
375
649
|
.split('\n')
|
|
@@ -393,16 +667,40 @@ export async function selectAudits({
|
|
|
393
667
|
|
|
394
668
|
const selectedAudits = [];
|
|
395
669
|
|
|
670
|
+
// Resolved at most once per call, and only if a `target: "web"` lens
|
|
671
|
+
// actually clears its gate — a Node-only project must not pay a filesystem
|
|
672
|
+
// scan on a roster with no web lens in it.
|
|
673
|
+
let webSurfaceMemo = null;
|
|
674
|
+
const projectIsWebCapable = () => {
|
|
675
|
+
if (webSurfaceMemo === null) {
|
|
676
|
+
webSurfaceMemo = hasWebSurfaceFn({ config });
|
|
677
|
+
}
|
|
678
|
+
return webSurfaceMemo;
|
|
679
|
+
};
|
|
680
|
+
|
|
396
681
|
for (const [auditName, ruleOpts] of Object.entries(rulesData.audits || {})) {
|
|
397
682
|
const triggers = ruleOpts.triggers || {};
|
|
398
683
|
|
|
399
684
|
const gateMatch = triggers.gates?.includes(gate);
|
|
400
685
|
if (!gateMatch) continue;
|
|
401
686
|
|
|
687
|
+
// Target-applicability gate (#4579). A lens declaring `target: "web"`
|
|
688
|
+
// has nothing to read on a project with no web surface, yet still
|
|
689
|
+
// whole-word-matches ordinary prose — `audit-seo` fires on the `meta`
|
|
690
|
+
// inside every Story body's `<!-- meta: {...} -->` machine comment. The
|
|
691
|
+
// roster's own instruction is that the host MUST walk every listed lens,
|
|
692
|
+
// so an inapplicable entry is not just wasted spend: it teaches operators
|
|
693
|
+
// to ignore the MUST. An absent `target` means "always applicable", so no
|
|
694
|
+
// existing lens changes behaviour.
|
|
695
|
+
if (ruleOpts.target === 'web' && !projectIsWebCapable()) continue;
|
|
696
|
+
|
|
402
697
|
const keywords = triggers.keywords || [];
|
|
403
698
|
let keywordMatch = false;
|
|
404
699
|
for (const kw of keywords) {
|
|
405
|
-
|
|
700
|
+
// Whole-word match: a bare substring test selects lenses on accidental
|
|
701
|
+
// fragments ("ui" inside "requires", "auth" inside "author" — #4579).
|
|
702
|
+
const escaped = kw.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
703
|
+
if (new RegExp(`\\b${escaped}\\b`).test(contentToSearch)) {
|
|
406
704
|
keywordMatch = true;
|
|
407
705
|
break;
|
|
408
706
|
}
|
|
@@ -430,8 +728,10 @@ export async function selectAudits({
|
|
|
430
728
|
changedFilesCount: changedFiles.length,
|
|
431
729
|
// The ref the change set was actually diffed against. Epic-mode callers
|
|
432
730
|
// assert this matches the requested Epic branch (Story #3362) so a
|
|
433
|
-
// mis-pinned diff never reaches the audit-lens selector silently.
|
|
434
|
-
|
|
731
|
+
// mis-pinned diff never reaches the audit-lens selector silently. `null`
|
|
732
|
+
// on the injected path: no ref was diffed, and naming one would invite
|
|
733
|
+
// exactly that assertion to pass against a diff nobody took.
|
|
734
|
+
resolvedRef: hasInjectedChangedFiles ? null : headRef,
|
|
435
735
|
ticketTitle: ticket.title,
|
|
436
736
|
},
|
|
437
737
|
};
|
|
@@ -53,20 +53,9 @@ export const MAINTAINABILITY_EXCLUSIONS = Object.freeze(
|
|
|
53
53
|
// escomplex: same "pattern" parse failure as acceptance-spec-reconciler;
|
|
54
54
|
// both files share the lifecycle-lint regex-driven scan helpers.
|
|
55
55
|
'.agents/scripts/check-lifecycle-lint.js',
|
|
56
|
-
// escomplex: "traveler[node.type] is not a function" — AST node type the
|
|
57
|
-
// upstream traveler table is missing (top-level await / private fields).
|
|
58
|
-
'.agents/scripts/lib/orchestration/epic-cleanup.js',
|
|
59
|
-
// escomplex: same "traveler[node.type] is not a function" — emitted by
|
|
60
|
-
// the spec-reconciler's `async` operator-comment IIFE.
|
|
61
|
-
'.agents/scripts/lib/orchestration/epic-spec-reconciler-ops.js',
|
|
62
56
|
// escomplex: "this[node.callee.type] is not a function" — the cyclomatic
|
|
63
57
|
// visitor lacks a handler for one of the quality-watch CLI's call shapes.
|
|
64
58
|
'.agents/scripts/quality-watch.js',
|
|
65
|
-
// escomplex: parse-fails (MI=0) on the spec-freshness phase's optional-
|
|
66
|
-
// chained destructuring fallthrough. Same upstream family as the other
|
|
67
|
-
// "pattern" / destructuring failures; carve-out so the phase split of
|
|
68
|
-
// epic-plan-spec doesn't poison the global rollup.
|
|
69
|
-
'.agents/scripts/lib/orchestration/epic-plan-spec/phases/spec-freshness.js',
|
|
70
59
|
// escomplex: "Cannot read properties of undefined (reading 'pattern')" —
|
|
71
60
|
// the audit-to-stories parser reuses the same regex-property scan
|
|
72
61
|
// patterns as acceptance-spec-reconciler.
|
|
@@ -13,14 +13,18 @@
|
|
|
13
13
|
* framework default at the next bump. The push-to-main job still passes
|
|
14
14
|
* `--full-scope` so a self-diff doesn't degrade to "no files in diff".
|
|
15
15
|
*
|
|
16
|
-
* 2. **
|
|
17
|
-
* `/deliver` the close-validation chain
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* `
|
|
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.
|
|
24
28
|
*
|
|
25
29
|
* The renderer is a pure string template so the integration test can assert
|
|
26
30
|
* on its exact shape without instantiating a YAML parser.
|
|
@@ -61,19 +65,15 @@ export function renderCiWorkflow(opts = {}) {
|
|
|
61
65
|
# Diff-scoped via the framework default (Story #1394 flipped
|
|
62
66
|
# --changed-since to '${baseRef}'). On PRs we let the default win;
|
|
63
67
|
# on push-to-main we pass --full-scope so a self-diff doesn't
|
|
64
|
-
# collapse to an empty file set.
|
|
65
|
-
#
|
|
66
|
-
#
|
|
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.
|
|
67
71
|
env:
|
|
68
|
-
|
|
72
|
+
BASELINE_REF: \${{ env.BASELINE_REF }}
|
|
69
73
|
run: |
|
|
70
74
|
mkdir -p temp
|
|
71
75
|
if [[ "\${{ github.event_name }}" == "pull_request" ]]; then
|
|
72
|
-
|
|
73
|
-
npm run crap:check -- --epic-ref "\${EPIC_REF}" --json temp/crap-report.json
|
|
74
|
-
else
|
|
75
|
-
npm run crap:check -- --json temp/crap-report.json
|
|
76
|
-
fi
|
|
76
|
+
npm run crap:check -- --json temp/crap-report.json
|
|
77
77
|
else
|
|
78
78
|
npm run crap:check -- --full-scope --json temp/crap-report.json
|
|
79
79
|
fi
|
|
@@ -112,15 +112,14 @@ jobs:
|
|
|
112
112
|
permissions:
|
|
113
113
|
contents: read
|
|
114
114
|
env:
|
|
115
|
-
# Story #1120:
|
|
116
|
-
#
|
|
117
|
-
#
|
|
118
|
-
#
|
|
119
|
-
#
|
|
120
|
-
#
|
|
121
|
-
#
|
|
122
|
-
|
|
123
|
-
EPIC_REF: \${{ github.head_ref && contains(github.head_ref, 'story') && format('epic/{0}', github.event.pull_request.base.ref) || '' }}
|
|
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) || '' }}
|
|
124
123
|
BASE_REF: ${baseRef}
|
|
125
124
|
steps:
|
|
126
125
|
- name: Checkout Code
|
|
@@ -147,16 +146,12 @@ jobs:
|
|
|
147
146
|
# the project's BASE_REF, so PRs no longer pass --changed-since
|
|
148
147
|
# explicitly. push-to-${baseRef} runs with --full-scope so the
|
|
149
148
|
# diff-scoped default doesn't degrade to a self-diff (= empty file
|
|
150
|
-
# set).
|
|
149
|
+
# set). BASELINE_REF pins the baseline read to the base branch tip.
|
|
151
150
|
env:
|
|
152
|
-
|
|
151
|
+
BASELINE_REF: \${{ env.BASELINE_REF }}
|
|
153
152
|
run: |
|
|
154
153
|
if [[ "\${{ github.event_name }}" == "pull_request" ]]; then
|
|
155
|
-
|
|
156
|
-
npm run maintainability:check -- --epic-ref "\${EPIC_REF}"
|
|
157
|
-
else
|
|
158
|
-
npm run maintainability:check
|
|
159
|
-
fi
|
|
154
|
+
npm run maintainability:check
|
|
160
155
|
else
|
|
161
156
|
npm run maintainability:check -- --full-scope
|
|
162
157
|
fi
|
|
@@ -110,9 +110,14 @@ export function buildMutationManifest(ctx = {}) {
|
|
|
110
110
|
const entries = [];
|
|
111
111
|
|
|
112
112
|
// --- ide-wiring -------------------------------------------------------
|
|
113
|
-
// Claude Code / IDE integration: the system-prompt import, the
|
|
114
|
-
//
|
|
115
|
-
//
|
|
113
|
+
// Claude Code / IDE integration: the system-prompt import, the generated
|
|
114
|
+
// flat command surface, and the .gitignore entry that keeps the
|
|
115
|
+
// generated tree out of git. (Story #4527/#4530: the UserPromptSubmit
|
|
116
|
+
// per-prompt re-sync hook entry was removed alongside the hook itself —
|
|
117
|
+
// it raced the harness's own read of the same directory and reported
|
|
118
|
+
// "0 file(s) synced" on effectively every invocation; the real sync
|
|
119
|
+
// points — `prepare`, `mandrel sync`/`update`, doctor's
|
|
120
|
+
// `commands-in-sync` — already cover every case.)
|
|
116
121
|
entries.push(
|
|
117
122
|
{
|
|
118
123
|
phaseGroup: PHASE_GROUPS.IDE_WIRING,
|
|
@@ -122,14 +127,6 @@ export function buildMutationManifest(ctx = {}) {
|
|
|
122
127
|
'Wire the @.agents/instructions.md system-prompt import so Claude Code hydrates the framework on cold start.',
|
|
123
128
|
reversible: true,
|
|
124
129
|
},
|
|
125
|
-
{
|
|
126
|
-
phaseGroup: PHASE_GROUPS.IDE_WIRING,
|
|
127
|
-
target: rel('.claude', 'settings.json'),
|
|
128
|
-
action: 'merge',
|
|
129
|
-
detail:
|
|
130
|
-
'Add the UserPromptSubmit re-sync hook so the /<command> tree stays current.',
|
|
131
|
-
reversible: true,
|
|
132
|
-
},
|
|
133
130
|
{
|
|
134
131
|
phaseGroup: PHASE_GROUPS.IDE_WIRING,
|
|
135
132
|
target: rel('.claude', 'commands'),
|