mandrel 2.31.0 → 2.33.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 +12 -9
- package/.agents/docs/agentrc-reference.json +63 -58
- package/.agents/docs/configuration.md +275 -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 +2 -2
- package/.agents/schemas/agentrc.schema.json +1955 -1410
- 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 +16 -29
- package/.agents/scripts/apply-quality-bootstrap.js +27 -34
- package/.agents/scripts/audit-to-stories.js +160 -41
- 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 +159 -0
- package/.agents/scripts/check-lifecycle-lint.js +108 -124
- 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 +81 -35
- package/.agents/scripts/lib/audit-to-stories/wire-dependencies.js +185 -0
- 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/runners.js +38 -16
- package/.agents/scripts/lib/config/temp-paths.js +6 -5
- package/.agents/scripts/lib/config-settings-schema-delivery.js +245 -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/dependency-parser.js +20 -7
- 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/findings/provenance-field.js +135 -0
- package/.agents/scripts/lib/findings/route-finding.js +57 -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-config-resolver.js +181 -0
- package/.agents/scripts/lib/knip-entry-sync.js +508 -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 +7 -26
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +69 -5
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +69 -13
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +49 -0
- package/.agents/scripts/lib/orchestration/{lifecycle/listeners/watcher.js → pr-watch.js} +58 -208
- package/.agents/scripts/lib/orchestration/resolve-stories.js +77 -50
- 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-conflicts.js +116 -1
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +1 -1
- package/.agents/scripts/lib/orchestration/ticket-validator.js +38 -0
- 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/footer-block.js +97 -0
- package/.agents/scripts/lib/story-body/story-body.js +18 -81
- 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/wave-runner/footprint.js +306 -0
- package/.agents/scripts/lib/wave-runner/ready-set.js +198 -181
- 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/blocked-by-add.js +25 -10
- 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 +21 -7
- 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/stories-wave-tick.js +192 -9
- 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 +28 -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 +30 -2
- package/.agents/workflows/helpers/deliver-story-reference.md +84 -16
- package/.agents/workflows/helpers/plan-reference.md +76 -0
- 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 +58 -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 +14 -5
- 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
|
@@ -35,11 +35,11 @@ import { fingerprintSeverity } from './severity.js';
|
|
|
35
35
|
const SEP = '␟'; // unit separator — keeps fingerprint fields unambiguous
|
|
36
36
|
const MARKER = 'audit-fingerprints:';
|
|
37
37
|
const SEMANTIC_MARKER = 'audit-semantic-keys:';
|
|
38
|
-
const SHA1_RE = /^[0-9a-f]{40}$/;
|
|
38
|
+
export const SHA1_RE = /^[0-9a-f]{40}$/;
|
|
39
39
|
// A semantic key round-trips through a comma-joined footer, so it must not
|
|
40
40
|
// carry a comma or a `>` (which would truncate the HTML comment). Both are
|
|
41
41
|
// stripped when the key is built, so this guard is defence-in-depth.
|
|
42
|
-
const SEMANTIC_KEY_RE = /^[^,>]+$/;
|
|
42
|
+
export const SEMANTIC_KEY_RE = /^[^,>]+$/;
|
|
43
43
|
|
|
44
44
|
/**
|
|
45
45
|
* Normalise a single scalar identity field to a stable string.
|
|
@@ -343,12 +343,59 @@ function decisionForIssue(issue) {
|
|
|
343
343
|
return state === 'closed' ? 'regression-of-closed' : 'update-existing';
|
|
344
344
|
}
|
|
345
345
|
|
|
346
|
+
/**
|
|
347
|
+
* Resolve the pool that **attributes** a finding, out of everything that
|
|
348
|
+
* confirmed it (Story #5045).
|
|
349
|
+
*
|
|
350
|
+
* Confirmation admits two different strengths of claim, and collapsing them
|
|
351
|
+
* was the source of two wrong routes:
|
|
352
|
+
*
|
|
353
|
+
* - An issue carrying the finding's exact **fingerprint** owns it. That is
|
|
354
|
+
* identity: this issue tracks *this* finding.
|
|
355
|
+
* - An issue matching only on the location-based **semantic key** is merely
|
|
356
|
+
* adjacent: it tracks *a* finding at the same `area␟primaryFile`.
|
|
357
|
+
*
|
|
358
|
+
* Owners win outright when any exist. Location-only matches are not discarded
|
|
359
|
+
* — they are the whole point of the semantic key and remain the pool when
|
|
360
|
+
* nothing carries the fingerprint (a reworded finding at an unchanged
|
|
361
|
+
* location). The pool is sorted by issue number so a genuine tie resolves to
|
|
362
|
+
* the earliest-filed issue rather than to whatever order the search port
|
|
363
|
+
* happened to return.
|
|
364
|
+
*
|
|
365
|
+
* @param {Array<{ number: number, state: string, body?: string }>} confirmed
|
|
366
|
+
* @param {string} sha
|
|
367
|
+
* @returns {Array<{ number: number, state: string }>}
|
|
368
|
+
*/
|
|
369
|
+
function attributedPool(confirmed, sha) {
|
|
370
|
+
const owns = (issue) => issueCarriesFingerprint(issue, sha);
|
|
371
|
+
const owners = confirmed.filter(owns);
|
|
372
|
+
const pool = owners.length > 0 ? owners : confirmed.filter((i) => !owns(i));
|
|
373
|
+
return [...pool].sort((a, b) => (a?.number ?? 0) - (b?.number ?? 0));
|
|
374
|
+
}
|
|
375
|
+
|
|
346
376
|
/**
|
|
347
377
|
* Decide the final route from a confirmed-match pool (issues that both
|
|
348
|
-
* surfaced in the candidate/search pass AND carry
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
378
|
+
* surfaced in the candidate/search pass AND carry a confirming footer).
|
|
379
|
+
* Shared by both the semantic-first and fingerprint-only code paths so the
|
|
380
|
+
* decision enum is identical regardless of how candidates were gathered.
|
|
381
|
+
*
|
|
382
|
+
* **Attribution decides, not array order (Story #5045).** The pool used to be
|
|
383
|
+
* read flat, which produced two wrong answers whenever more than one issue
|
|
384
|
+
* confirmed:
|
|
385
|
+
*
|
|
386
|
+
* 1. Two open matches routed `duplicate` pinned to `open[0]` — whichever
|
|
387
|
+
* issue the search port happened to return first. With per-Story
|
|
388
|
+
* provenance that pick is answerable rather than arbitrary: the issue
|
|
389
|
+
* carrying the finding's own fingerprint owns it, and a sibling matching
|
|
390
|
+
* only by location does not.
|
|
391
|
+
* 2. Any open match at all masked a closed one, so a finding whose
|
|
392
|
+
* fingerprint is owned by a **closed** Story routed `update-existing`
|
|
393
|
+
* against an open neighbour — a genuine regression filed as a
|
|
394
|
+
* business-as-usual update. Attribution restores it: state is read off the
|
|
395
|
+
* owning issue, not off whatever else shares its location.
|
|
396
|
+
*
|
|
397
|
+
* {@link attributedPool} owns that selection; the decision below reads only
|
|
398
|
+
* the pool it returns.
|
|
352
399
|
*
|
|
353
400
|
* @param {Array<{ number: number, state: string }>} confirmed
|
|
354
401
|
* @param {string} sha
|
|
@@ -359,7 +406,8 @@ function decideFromConfirmed(confirmed, sha) {
|
|
|
359
406
|
return { decision: 'new', matchedIssue: null, fingerprint: sha };
|
|
360
407
|
}
|
|
361
408
|
|
|
362
|
-
const
|
|
409
|
+
const attributed = attributedPool(confirmed, sha);
|
|
410
|
+
const open = attributed.filter((h) => normaliseField(h.state) === 'open');
|
|
363
411
|
if (open.length > 1) {
|
|
364
412
|
return { decision: 'duplicate', matchedIssue: open[0], fingerprint: sha };
|
|
365
413
|
}
|
|
@@ -371,7 +419,7 @@ function decideFromConfirmed(confirmed, sha) {
|
|
|
371
419
|
};
|
|
372
420
|
}
|
|
373
421
|
|
|
374
|
-
const closed =
|
|
422
|
+
const closed = attributed[0];
|
|
375
423
|
return {
|
|
376
424
|
decision: decisionForIssue(closed),
|
|
377
425
|
matchedIssue: closed,
|
|
@@ -484,4 +532,5 @@ export const __testing = {
|
|
|
484
532
|
decideFromConfirmed,
|
|
485
533
|
issueCarriesSemanticKey,
|
|
486
534
|
parseSemanticKeyFooter,
|
|
535
|
+
attributedPool,
|
|
487
536
|
};
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* fs-walk.js — the one recursive file walker the lint scripts share.
|
|
3
|
+
*
|
|
4
|
+
* `check-lifecycle-lint.js` and `lint-label-vocabulary.js` each carried a
|
|
5
|
+
* private copy of this generator, differing only in the extension they
|
|
6
|
+
* matched — the second one's comment said so outright ("mirrors the walker
|
|
7
|
+
* shape in check-lifecycle-lint.js"). Story #5024 extracted it: shrinking
|
|
8
|
+
* check-lifecycle-lint.js pushed its duplication percentage up on an unchanged
|
|
9
|
+
* clone count, which is the ratchet correctly pointing at boilerplate that
|
|
10
|
+
* should not have been copied twice.
|
|
11
|
+
*
|
|
12
|
+
* A missing directory yields nothing rather than throwing. That is
|
|
13
|
+
* load-bearing, not lenient: the lint surfaces are directories a deletion can
|
|
14
|
+
* legitimately remove (Story #5024 deleted `lifecycle/listeners/`), and a scan
|
|
15
|
+
* of an absent path is vacuously clean, not an error. Every other `readdir`
|
|
16
|
+
* failure — a permission error, a file where a directory was expected —
|
|
17
|
+
* propagates, so a genuinely broken scan cannot read as clean.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readdirSync } from 'node:fs';
|
|
21
|
+
import path from 'node:path';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Walk a directory tree synchronously, yielding absolute paths of every file
|
|
25
|
+
* whose name ends with `extension`.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} dir Absolute directory to walk.
|
|
28
|
+
* @param {string} extension Extension to match, leading dot included (`.js`).
|
|
29
|
+
* @param {{ readDir?: typeof readdirSync }} [ports] Injectable for tests.
|
|
30
|
+
* @returns {Generator<string, void, void>}
|
|
31
|
+
*/
|
|
32
|
+
export function* walkFilesByExtension(
|
|
33
|
+
dir,
|
|
34
|
+
extension,
|
|
35
|
+
{ readDir = readdirSync } = {},
|
|
36
|
+
) {
|
|
37
|
+
let entries;
|
|
38
|
+
try {
|
|
39
|
+
entries = readDir(dir, { withFileTypes: true });
|
|
40
|
+
} catch (err) {
|
|
41
|
+
if (err.code === 'ENOENT') return;
|
|
42
|
+
throw err;
|
|
43
|
+
}
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
const p = path.join(dir, entry.name);
|
|
46
|
+
if (entry.isDirectory()) {
|
|
47
|
+
yield* walkFilesByExtension(p, extension, { readDir });
|
|
48
|
+
} else if (entry.isFile() && entry.name.endsWith(extension)) {
|
|
49
|
+
yield p;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -129,8 +129,8 @@ export function classifyBranchSeed({ localHas, remoteHas }) {
|
|
|
129
129
|
* throws (the v2 standalone path has no concurrent creator to race).
|
|
130
130
|
*
|
|
131
131
|
* The asymmetric surrounding wrappers (merged-sweep, fast-forward,
|
|
132
|
-
* donor-prime, workspace-verify
|
|
133
|
-
* in
|
|
132
|
+
* donor-prime, workspace-verify) are deliberately NOT folded in — they stay
|
|
133
|
+
* in their respective callers.
|
|
134
134
|
*
|
|
135
135
|
* Caller-specific log lines and error text are passed in as the `messages`
|
|
136
136
|
* data bag so behaviour stays byte-identical to the pre-extraction switches.
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
*/
|
|
29
29
|
|
|
30
30
|
import { execFileSync, spawnSync } from 'node:child_process';
|
|
31
|
+
import { execFileCapture, spawnCapture } from './child-exec.js';
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* Result of a `gitSpawn` invocation. Field semantics match `child_process.spawnSync`
|
|
@@ -40,23 +41,6 @@ import { execFileSync, spawnSync } from 'node:child_process';
|
|
|
40
41
|
* @property {string} stderr - Trimmed stderr.
|
|
41
42
|
*/
|
|
42
43
|
|
|
43
|
-
/**
|
|
44
|
-
* Explicit stdout ceiling for every git invocation in this module.
|
|
45
|
-
*
|
|
46
|
-
* Both child-process runners default `maxBuffer` to 1 MB, at which point the
|
|
47
|
-
* child is killed and the call fails with `ENOBUFS` for a reason unrelated to
|
|
48
|
-
* git. The push path is the sharp edge: `git push` relays the whole `pre-push`
|
|
49
|
-
* hook output, which is unbounded by design — this repo's hook emits a full
|
|
50
|
-
* `check-baselines` envelope, measured at 2,166,643 bytes, so every Story
|
|
51
|
-
* close failed at `phase: push` once hooks became reachable inside worktrees.
|
|
52
|
-
*
|
|
53
|
-
* 64 MB is the bound Story #4914 already set on `baselines/git-base.js` for
|
|
54
|
-
* the identical failure, matching `run-test-profile.js`,
|
|
55
|
-
* `audit-baselines/trend.js` and `audit-baselines/weights.js`. This module was
|
|
56
|
-
* missed by that sweep.
|
|
57
|
-
*/
|
|
58
|
-
const MAX_BUFFER_BYTES = 64 * 1024 * 1024;
|
|
59
|
-
|
|
60
44
|
let _execFileSync = execFileSync;
|
|
61
45
|
let _spawnSync = spawnSync;
|
|
62
46
|
|
|
@@ -129,26 +113,30 @@ export function __setGitRunners(exec, spawn) {
|
|
|
129
113
|
* the interface returned by {@link createGitInterface} route through this —
|
|
130
114
|
* they differ only in which `execFileSync` they hand it.
|
|
131
115
|
*
|
|
116
|
+
* Buffer, shell and encoding policy belong to
|
|
117
|
+
* [`child-exec.js`](child-exec.js); this function contributes only the git
|
|
118
|
+
* specifics (argv prefix, cwd, scrubbed env).
|
|
119
|
+
*
|
|
132
120
|
* @param {typeof execFileSync} exec
|
|
133
121
|
* @param {string} cwd
|
|
134
122
|
* @param {string[]} args
|
|
135
123
|
* @returns {string} Trimmed stdout text.
|
|
136
124
|
*/
|
|
137
125
|
function runGitSync(exec, cwd, args) {
|
|
138
|
-
return
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
126
|
+
return String(
|
|
127
|
+
execFileCapture('git', args, {
|
|
128
|
+
run: exec,
|
|
129
|
+
cwd,
|
|
130
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
131
|
+
env: cleanGitEnv(),
|
|
132
|
+
}),
|
|
133
|
+
).trim();
|
|
146
134
|
}
|
|
147
135
|
|
|
148
136
|
/**
|
|
149
137
|
* The **single** non-throwing git runner — the `spawnSync` counterpart of
|
|
150
138
|
* {@link runGitSync}, normalising `status`/stdout/stderr into a
|
|
151
|
-
* {@link GitResult}.
|
|
139
|
+
* {@link GitResult} via the shared exec surface.
|
|
152
140
|
*
|
|
153
141
|
* @param {typeof spawnSync} spawn
|
|
154
142
|
* @param {string} cwd
|
|
@@ -156,19 +144,11 @@ function runGitSync(exec, cwd, args) {
|
|
|
156
144
|
* @returns {GitResult}
|
|
157
145
|
*/
|
|
158
146
|
function runGitSpawn(spawn, cwd, args) {
|
|
159
|
-
|
|
147
|
+
return spawnCapture('git', args, {
|
|
148
|
+
run: spawn,
|
|
160
149
|
cwd,
|
|
161
|
-
stdio: 'pipe',
|
|
162
|
-
encoding: 'utf-8',
|
|
163
|
-
shell: false,
|
|
164
150
|
env: cleanGitEnv(),
|
|
165
|
-
maxBuffer: MAX_BUFFER_BYTES,
|
|
166
151
|
});
|
|
167
|
-
return {
|
|
168
|
-
status: result.status ?? 1,
|
|
169
|
-
stdout: (result.stdout ?? '').trim(),
|
|
170
|
-
stderr: (result.stderr ?? '').trim(),
|
|
171
|
-
};
|
|
172
152
|
}
|
|
173
153
|
|
|
174
154
|
/**
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* knip-config-resolver.js — resolve a repository's knip configuration the way
|
|
3
|
+
* knip itself would, and hand back its declared entry patterns.
|
|
4
|
+
*
|
|
5
|
+
* Split out of `knip-entry-sync.js` (Story #5039) because these are two
|
|
6
|
+
* concerns that change for different reasons: *what knip's config says* tracks
|
|
7
|
+
* knip's own releases, while *which CLIs something invokes* tracks this
|
|
8
|
+
* repository's callers. Keeping them in one file also pushed that module's
|
|
9
|
+
* maintainability index below its floor.
|
|
10
|
+
*
|
|
11
|
+
* Why not read the file ourselves: #5026 hardcoded
|
|
12
|
+
* `JSON.parse(<root>/knip.json)`, so all seven of knip's other config
|
|
13
|
+
* locations plus `package.json#knip` resolved to ENOENT and failed the gate
|
|
14
|
+
* closed. That made it unusable in exactly the repositories that adopt the
|
|
15
|
+
* shared `knip.base.json` — knip 6 has no root-level `extends`, so inheriting
|
|
16
|
+
* the base means spreading it inside a TypeScript module, which a static
|
|
17
|
+
* `knip.json` structurally cannot express.
|
|
18
|
+
*
|
|
19
|
+
* Going through `createOptions` also drops the assumption that `entry` is
|
|
20
|
+
* *statically declared*. A config may build the array programmatically, so
|
|
21
|
+
* even a purpose-built `knip.config.ts` parser would read the wrong thing;
|
|
22
|
+
* knip evaluates the module and returns the computed value.
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Load knip's own config resolver.
|
|
27
|
+
*
|
|
28
|
+
* Deliberately a dynamic import behind an injectable seam. `knip` is declared
|
|
29
|
+
* in `runtime-deps.json` as an **optional** dependency, mirroring `typescript`:
|
|
30
|
+
* the `.agents/` payload is materialized into consumers that may not run knip
|
|
31
|
+
* at all, so an unresolvable `knip` must degrade to the skip path rather than
|
|
32
|
+
* crash the gate at module load — and must never be preflight-blocked.
|
|
33
|
+
*
|
|
34
|
+
* @returns {Promise<{ createOptions?: Function }>}
|
|
35
|
+
*/
|
|
36
|
+
function importKnipSession() {
|
|
37
|
+
return import('knip/session');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Collect every declared entry pattern from a resolved knip configuration.
|
|
42
|
+
*
|
|
43
|
+
* Entries live in two places and #5026 read only the first. A pnpm-workspace
|
|
44
|
+
* config — the shape that made this gate unusable in `Beestera/swarm-os` —
|
|
45
|
+
* declares them per workspace, so a config with no top-level `entry` at all is
|
|
46
|
+
* still fully enumerated. A named workspace's patterns are workspace-relative
|
|
47
|
+
* and are joined to the workspace name; the root workspace (`.`) is already
|
|
48
|
+
* repo-relative. A leading `!` is knip's *negation* marker (as opposed to the
|
|
49
|
+
* trailing production marker) and stays at the front of the pattern rather
|
|
50
|
+
* than getting buried behind the workspace prefix.
|
|
51
|
+
*
|
|
52
|
+
* `sawEntryArray` separates "declared nothing under `.agents/scripts/`" — a
|
|
53
|
+
* legitimate result, reported as `missing` divergences — from "this config
|
|
54
|
+
* enumerates no entry points anywhere", which leaves the gate nothing to
|
|
55
|
+
* compare against.
|
|
56
|
+
*
|
|
57
|
+
* @param {object} parsedConfig knip's schema-parsed configuration
|
|
58
|
+
* @returns {{ patterns: string[], sawEntryArray: boolean }}
|
|
59
|
+
*/
|
|
60
|
+
function collectEntryPatterns(parsedConfig) {
|
|
61
|
+
const patterns = [];
|
|
62
|
+
let sawEntryArray = false;
|
|
63
|
+
|
|
64
|
+
const push = (value, prefix) => {
|
|
65
|
+
if (typeof value !== 'string') return;
|
|
66
|
+
if (!prefix) patterns.push(value);
|
|
67
|
+
else if (value.startsWith('!'))
|
|
68
|
+
patterns.push(`!${prefix}${value.slice(1)}`);
|
|
69
|
+
else patterns.push(`${prefix}${value}`);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const addAll = (entry, prefix) => {
|
|
73
|
+
if (!Array.isArray(entry)) return;
|
|
74
|
+
sawEntryArray = true;
|
|
75
|
+
for (const value of entry) push(value, prefix);
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
addAll(parsedConfig?.entry, '');
|
|
79
|
+
|
|
80
|
+
const workspaces = parsedConfig?.workspaces;
|
|
81
|
+
if (workspaces && typeof workspaces === 'object') {
|
|
82
|
+
for (const [name, workspace] of Object.entries(workspaces)) {
|
|
83
|
+
const trimmed = name.replace(/^\.\/+/, '').replace(/\/+$/, '');
|
|
84
|
+
const prefix = trimmed === '' || trimmed === '.' ? '' : `${trimmed}/`;
|
|
85
|
+
addAll(workspace?.entry, prefix);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { patterns, sawEntryArray };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Resolve the entry patterns knip would see for `repoRoot`.
|
|
94
|
+
*
|
|
95
|
+
* Three outcomes, deliberately distinct — collapsing the first two is how a
|
|
96
|
+
* broken configuration would come to look like an absent one:
|
|
97
|
+
*
|
|
98
|
+
* skipped — nothing to check (no config file, no `package.json#knip`, or no
|
|
99
|
+
* resolvable `knip`). The gate exits 0. This is what makes it safe to wire
|
|
100
|
+
* into every consumer, matching `qa.gherkinLint`'s opt-in posture.
|
|
101
|
+
* error — a configuration exists but could not be resolved, or enumerates no
|
|
102
|
+
* entry points at all. The gate exits 2.
|
|
103
|
+
* resolved — `patterns` carries every declared entry, top-level and
|
|
104
|
+
* per-workspace, with knip's trailing `!` production markers intact.
|
|
105
|
+
*
|
|
106
|
+
* @param {{ repoRoot: string, loadKnipSession?: () => Promise<object> }} opts
|
|
107
|
+
* @returns {Promise<{
|
|
108
|
+
* patterns: string[],
|
|
109
|
+
* configFilePath: string | null,
|
|
110
|
+
* skipped: string | null,
|
|
111
|
+
* error: string | null,
|
|
112
|
+
* }>}
|
|
113
|
+
*/
|
|
114
|
+
export async function resolveKnipEntryPatterns({
|
|
115
|
+
repoRoot,
|
|
116
|
+
loadKnipSession = importKnipSession,
|
|
117
|
+
}) {
|
|
118
|
+
const nothing = { patterns: [], configFilePath: null };
|
|
119
|
+
|
|
120
|
+
let createOptions;
|
|
121
|
+
try {
|
|
122
|
+
({ createOptions } = await loadKnipSession());
|
|
123
|
+
} catch (error) {
|
|
124
|
+
return {
|
|
125
|
+
...nothing,
|
|
126
|
+
skipped: `the "knip" package is not resolvable from ${repoRoot} (${error.message})`,
|
|
127
|
+
error: null,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (typeof createOptions !== 'function') {
|
|
131
|
+
return {
|
|
132
|
+
...nothing,
|
|
133
|
+
skipped: null,
|
|
134
|
+
error:
|
|
135
|
+
'the installed "knip" does not export createOptions from "knip/session" — this gate needs knip 6 or newer',
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let options;
|
|
140
|
+
try {
|
|
141
|
+
options = await createOptions({ cwd: repoRoot, args: {} });
|
|
142
|
+
} catch (error) {
|
|
143
|
+
return {
|
|
144
|
+
...nothing,
|
|
145
|
+
skipped: null,
|
|
146
|
+
error: `cannot resolve the knip configuration: ${error.message}`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const configFilePath = options?.configFilePath ?? null;
|
|
151
|
+
if (!configFilePath) {
|
|
152
|
+
return {
|
|
153
|
+
...nothing,
|
|
154
|
+
skipped: `no knip configuration found under ${repoRoot} (no config file, no package.json#knip)`,
|
|
155
|
+
error: null,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const { patterns, sawEntryArray } = collectEntryPatterns(
|
|
160
|
+
options?.parsedConfig,
|
|
161
|
+
);
|
|
162
|
+
if (!sawEntryArray) {
|
|
163
|
+
return {
|
|
164
|
+
patterns: [],
|
|
165
|
+
configFilePath,
|
|
166
|
+
skipped: null,
|
|
167
|
+
error: `${configFilePath} declares no "entry" array, at the top level or in any workspace`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { patterns, configFilePath, skipped: null, error: null };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Test-only seam. Not API — `collectEntryPatterns` is exercised directly so the
|
|
176
|
+
* workspace-prefix and negation rules can be pinned without a fixture tree.
|
|
177
|
+
*/
|
|
178
|
+
export const __testing = Object.freeze({
|
|
179
|
+
collectEntryPatterns,
|
|
180
|
+
importKnipSession,
|
|
181
|
+
});
|