mandrel 2.59.0 → 2.60.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 +11 -9
- package/.agents/agents/acceptance-critic.md +24 -43
- package/.agents/agents/story-worker.md +18 -19
- package/.agents/docs/SDLC.md +6 -6
- package/.agents/docs/agentrc-reference.json +1 -2
- package/.agents/docs/configuration.md +29 -46
- package/.agents/docs/quality-gates.md +8 -4
- package/.agents/docs/workflows.md +1 -1
- package/.agents/instructions.md +4 -5
- package/.agents/rules/ci-remediation.md +41 -8
- package/.agents/rules/known-tooling-behavior.md +65 -15
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +6 -11
- package/.agents/schemas/story-deliver-terminal.schema.json +3 -3
- package/.agents/scripts/README.md +11 -1
- package/.agents/scripts/acceptance-eval.js +25 -27
- package/.agents/scripts/ceremony-derive.js +15 -10
- package/.agents/scripts/check-context-budget.js +148 -228
- package/.agents/scripts/check-schema-references.js +5 -3
- package/.agents/scripts/check-workflow-citations.js +33 -147
- package/.agents/scripts/coverage-capture.js +7 -4
- package/.agents/scripts/deliver-light.js +41 -100
- package/.agents/scripts/deliver-run.js +631 -0
- package/.agents/scripts/file-ci-gap.js +59 -11
- package/.agents/scripts/lib/baselines/crap-preview-incremental.js +6 -2
- package/.agents/scripts/lib/changed-files.js +30 -0
- package/.agents/scripts/lib/config/delivery-routing.js +5 -4
- package/.agents/scripts/lib/config/explain.js +1 -3
- package/.agents/scripts/lib/config/gates/crap-incremental-coverage.schema.js +1 -1
- package/.agents/scripts/lib/config-resolver.js +1 -0
- package/.agents/scripts/lib/config-settings-schema-delivery.js +28 -21
- package/.agents/scripts/lib/coverage-capture-fullscope.js +10 -2
- package/.agents/scripts/lib/coverage-capture-incremental.js +3 -2
- package/.agents/scripts/lib/coverage-capture-usage.js +4 -1
- package/.agents/scripts/lib/doc-tiers.js +4 -2
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +7 -6
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +7 -5
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/gh-exec.js +160 -0
- package/.agents/scripts/lib/observability/source-classifier.js +1 -0
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +74 -132
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +123 -12
- package/.agents/scripts/lib/orchestration/complexity-gate.js +180 -352
- package/.agents/scripts/lib/orchestration/light-suitability.js +71 -136
- package/.agents/scripts/lib/orchestration/plan-context.js +13 -25
- package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +8 -6
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +76 -95
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +35 -18
- package/.agents/scripts/lib/orchestration/plan-persist/summary.js +11 -11
- package/.agents/scripts/lib/orchestration/plan-persist/supersede-ops.js +63 -29
- package/.agents/scripts/lib/orchestration/review-depth.js +14 -11
- package/.agents/scripts/lib/orchestration/run-epilogue.js +260 -182
- package/.agents/scripts/lib/orchestration/run-scoped-config.js +63 -99
- package/.agents/scripts/lib/orchestration/single-story-close/phases/base-sync.js +3 -3
- package/.agents/scripts/lib/orchestration/single-story-close/phases/graphql-preflight.js +137 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +105 -18
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +4 -3
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +156 -39
- package/.agents/scripts/lib/orchestration/story-init-envelope.js +71 -0
- package/.agents/scripts/lib/orchestration/task-body-validator.js +8 -17
- package/.agents/scripts/lib/orchestration/ticket-validator.js +44 -183
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +14 -25
- package/.agents/scripts/lib/story-body/body-format-lints.js +58 -12
- package/.agents/scripts/lib/story-body/story-body.js +83 -29
- package/.agents/scripts/lib/templates/decomposer-prompts.js +7 -15
- package/.agents/scripts/lib/wave-runner/live-probe.js +31 -5
- package/.agents/scripts/merge-baseline.js +4 -5
- package/.agents/scripts/plan-context.js +117 -28
- package/.agents/scripts/plan-persist.js +79 -28
- package/.agents/scripts/plan-run-epilogue.js +11 -8
- package/.agents/scripts/pr-watch-with-update.js +9 -2
- package/.agents/scripts/run-verify.js +13 -6
- package/.agents/scripts/single-story-init.js +7 -57
- package/.agents/scripts/stories-wave-tick.js +160 -26
- package/.agents/skills/core/gates-and-baselines/reference.md +0 -1
- package/.agents/skills/skills.index.json +2 -2
- package/.agents/skills/stack/qa/playwright/SKILL.md +26 -0
- package/.agents/workflows/helpers/acceptance-self-eval.md +84 -157
- package/.agents/workflows/helpers/code-review.md +4 -2
- package/.agents/workflows/helpers/deliver-digest.md +31 -24
- package/.agents/workflows/helpers/deliver-light.md +92 -101
- package/.agents/workflows/helpers/deliver-reference.md +116 -100
- package/.agents/workflows/helpers/deliver-story-reference.md +58 -124
- package/.agents/workflows/helpers/deliver-story.md +17 -18
- package/.agents/workflows/helpers/plan-reference.md +65 -54
- package/.agents/workflows/mandrel-deliver.md +47 -31
- package/.agents/workflows/mandrel-plan.md +22 -21
- package/.agents/workflows/mandrel-update.md +36 -21
- package/docs/CHANGELOG.md +35 -0
- package/lib/cli/update.js +376 -17
- package/lib/migrations/index.js +2 -0
- package/lib/migrations/steps/2.60.0-retire-audit-results-autofile.js +40 -0
- package/package.json +2 -1
- package/.agents/schemas/model-attribution.schema.json +0 -53
- package/.agents/scripts/lib/orchestration/model-attribution.js +0 -418
- package/.agents/scripts/lib/orchestration/story-plan-state.js +0 -33
- package/.agents/scripts/lib/orchestration/structured-comment-parser.js +0 -67
|
@@ -107,6 +107,18 @@
|
|
|
107
107
|
* deterministic config source (`delivery.deliverRunner.concurrencyCap`) and
|
|
108
108
|
* one scheduling kernel with every `/mandrel-deliver` multi-Story invocation.
|
|
109
109
|
*
|
|
110
|
+
* **The cap is clamped to 1 when worktree isolation resolves off** (Story
|
|
111
|
+
* #5357). Concurrent dispatch is safe only because each Story gets its own
|
|
112
|
+
* checkout; with isolation off every Story resolves to the same `workCwd`, so
|
|
113
|
+
* two workers contend for `HEAD` and one takes the branch from under the
|
|
114
|
+
* other — the #5339/#5340 web run, where both wave-1 workers were initialised
|
|
115
|
+
* into the same tree. The clamp reads `resolveWorktreeEnabled`'s result, so all
|
|
116
|
+
* three routes to off (`CLAUDE_CODE_REMOTE=true`, `AP_WORKTREE_ENABLED=false`,
|
|
117
|
+
* `delivery.worktreeIsolation.enabled: false`) are covered by construction. It
|
|
118
|
+
* is a safety floor rather than a preference, so it is the one case where an
|
|
119
|
+
* explicit `--concurrency` does NOT win — and `capPrecedence` names
|
|
120
|
+
* `worktree-clamp` as the winning source and carries the requested value.
|
|
121
|
+
*
|
|
110
122
|
* Exit codes: 0 ok · 1 input error · 2 dependency cycle (`cycleError`) ·
|
|
111
123
|
* 3 wedged (`wedged`) — ready is empty, nothing is in flight, and undone
|
|
112
124
|
* Stories are waiting on blockers that are not done · 4 blocked (`blocked`) —
|
|
@@ -124,7 +136,13 @@ import { readFileSync } from 'node:fs';
|
|
|
124
136
|
import { parseArgs } from 'node:util';
|
|
125
137
|
|
|
126
138
|
import { runAsCli } from './lib/cli-utils.js';
|
|
127
|
-
import {
|
|
139
|
+
import {
|
|
140
|
+
getPaths,
|
|
141
|
+
getRunners,
|
|
142
|
+
getWorktreeIsolation,
|
|
143
|
+
resolveConfig,
|
|
144
|
+
resolveWorktreeEnabled,
|
|
145
|
+
} from './lib/config-resolver.js';
|
|
128
146
|
import { detectCycle } from './lib/Graph.js';
|
|
129
147
|
import { Logger } from './lib/Logger.js';
|
|
130
148
|
import { AGENT_LABELS } from './lib/label-constants.js';
|
|
@@ -206,6 +224,13 @@ Options:
|
|
|
206
224
|
WINS over the configured value, and the envelope's
|
|
207
225
|
capPrecedence records that it did — including when the
|
|
208
226
|
request exceeds the configured cap.
|
|
227
|
+
ONE EXCEPTION: when worktree isolation resolves off
|
|
228
|
+
(CLAUDE_CODE_REMOTE=true, AP_WORKTREE_ENABLED=false, or
|
|
229
|
+
delivery.worktreeIsolation.enabled: false), the cap is
|
|
230
|
+
clamped to 1 and the clamp outranks this flag — every
|
|
231
|
+
Story would otherwise run in the same checkout and two
|
|
232
|
+
workers would contend for HEAD. capPrecedence reports
|
|
233
|
+
source "worktree-clamp" with the requested value.
|
|
209
234
|
--done <csv> Comma-separated Story IDs already completed this run.
|
|
210
235
|
Their dependents become eligible; they are never
|
|
211
236
|
re-dispatched. Defaults to empty.
|
|
@@ -638,6 +663,15 @@ export function parseInFlight(raw) {
|
|
|
638
663
|
return { value: num, error: null };
|
|
639
664
|
}
|
|
640
665
|
|
|
666
|
+
/**
|
|
667
|
+
* The per-beat cap a run is clamped to when worktree isolation resolves off
|
|
668
|
+
* (Story #5357). `delivery.deliverRunner.concurrencyCap` is a throughput
|
|
669
|
+
* preference; this is a safety floor, so it outranks even an explicit
|
|
670
|
+
* `--concurrency`. Why the two are coupled, and why the clamp is never silent:
|
|
671
|
+
* ADR `20260917-5357` in `docs/decisions.md`.
|
|
672
|
+
*/
|
|
673
|
+
export const WORKTREE_DISABLED_CONCURRENCY_CAP = 1;
|
|
674
|
+
|
|
641
675
|
/**
|
|
642
676
|
* Validate a raw `--concurrency` value into a positive integer.
|
|
643
677
|
*
|
|
@@ -675,7 +709,9 @@ export function parseConcurrencyOverride(raw) {
|
|
|
675
709
|
* @param {object} [opts.config] Pre-resolved config (injected by tests so
|
|
676
710
|
* they never depend on a real `.agentrc`).
|
|
677
711
|
* @param {number} [opts.override] Validated positive integer from
|
|
678
|
-
* `--concurrency`; wins over config
|
|
712
|
+
* `--concurrency`; wins over config, unless
|
|
713
|
+
* the worktree-isolation floor clamps it.
|
|
714
|
+
* @param {NodeJS.ProcessEnv} [opts.env] Environment (test injection).
|
|
679
715
|
* @returns {number} The resolved positive-integer concurrency cap.
|
|
680
716
|
*/
|
|
681
717
|
export function resolveConcurrencyCap(opts = {}) {
|
|
@@ -702,44 +738,115 @@ export function resolveConcurrencyCap(opts = {}) {
|
|
|
702
738
|
* safety limit, and refusing a deliberate operator escalation would trade a
|
|
703
739
|
* silent override for a silent stall.
|
|
704
740
|
*
|
|
741
|
+
* **The one exception (Story #5357).** When worktree isolation resolves off,
|
|
742
|
+
* the cap is clamped to {@link WORKTREE_DISABLED_CONCURRENCY_CAP} and the
|
|
743
|
+
* clamp outranks the flag. That is not a reversal of the paragraph above: the
|
|
744
|
+
* configured cap is a preference and a flag may outrank a preference, but a
|
|
745
|
+
* shared checkout is not a preference at all. Dispatching two workers into one
|
|
746
|
+
* working tree corrupts the run whatever number either source asked for, so
|
|
747
|
+
* the floor wins — loudly, per {@link resolveWorktreeClamp}.
|
|
748
|
+
*
|
|
705
749
|
* @param {object} [opts]
|
|
706
750
|
* @param {string} [opts.cwd] Repo root for config resolution.
|
|
707
751
|
* @param {object} [opts.config] Pre-resolved config (test injection).
|
|
708
752
|
* @param {number} [opts.override] Validated positive integer from
|
|
709
753
|
* `--concurrency`.
|
|
754
|
+
* @param {NodeJS.ProcessEnv} [opts.env] Environment the worktree-isolation
|
|
755
|
+
* rules read (test injection).
|
|
710
756
|
* @returns {{
|
|
711
757
|
* cap: number,
|
|
712
|
-
* source: 'flag'|'config',
|
|
758
|
+
* source: 'flag'|'config'|'worktree-clamp',
|
|
713
759
|
* configuredCap: number,
|
|
714
760
|
* requestedCap: number|null,
|
|
715
761
|
* exceedsConfigured: boolean,
|
|
716
762
|
* note: string,
|
|
717
763
|
* }}
|
|
718
764
|
*/
|
|
719
|
-
export function resolveCapPrecedence({ cwd, config, override } = {}) {
|
|
765
|
+
export function resolveCapPrecedence({ cwd, config, override, env } = {}) {
|
|
720
766
|
const resolved = config ?? resolveConfig({ cwd });
|
|
721
767
|
const { deliverRunner } = getRunners(resolved);
|
|
722
768
|
const configuredCap = deliverRunner.concurrencyCap;
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
769
|
+
const requested =
|
|
770
|
+
override == null
|
|
771
|
+
? {
|
|
772
|
+
cap: configuredCap,
|
|
773
|
+
source: 'config',
|
|
774
|
+
configuredCap,
|
|
775
|
+
requestedCap: null,
|
|
776
|
+
exceedsConfigured: false,
|
|
777
|
+
note: `cap ${configuredCap} from delivery.deliverRunner.concurrencyCap (no --concurrency given)`,
|
|
778
|
+
}
|
|
779
|
+
: {
|
|
780
|
+
cap: override,
|
|
781
|
+
source: 'flag',
|
|
782
|
+
configuredCap,
|
|
783
|
+
requestedCap: override,
|
|
784
|
+
exceedsConfigured: override > configuredCap,
|
|
785
|
+
note:
|
|
786
|
+
override > configuredCap
|
|
787
|
+
? `cap ${override} from --concurrency, which OVERRIDES and EXCEEDS the configured delivery.deliverRunner.concurrencyCap ${configuredCap} — this run is deliberately above the project default`
|
|
788
|
+
: `cap ${override} from --concurrency, which overrides the configured delivery.deliverRunner.concurrencyCap ${configuredCap}`,
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
return resolveWorktreeClamp({ requested, config: resolved, env });
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
/**
|
|
795
|
+
* Apply the worktree-isolation safety floor to an already-resolved precedence
|
|
796
|
+
* record (Story #5357).
|
|
797
|
+
*
|
|
798
|
+
* **Keyed off the resolved boolean, never one env var.** `CLAUDE_CODE_REMOTE`
|
|
799
|
+
* is only one of three routes to isolation-off — `AP_WORKTREE_ENABLED=false`
|
|
800
|
+
* and `delivery.worktreeIsolation.enabled: false` are the others and are
|
|
801
|
+
* exactly as unsafe — so this reads {@link resolveWorktreeEnabled}'s result and
|
|
802
|
+
* all three are covered by construction rather than by enumeration.
|
|
803
|
+
*
|
|
804
|
+
* **The config is normalised through `getWorktreeIsolation` first**, and that
|
|
805
|
+
* is load-bearing. `resolveWorktreeEnabled` reads
|
|
806
|
+
* `Boolean(config.delivery.worktreeIsolation.enabled)` raw, so a config that
|
|
807
|
+
* simply omits the block — every test-injected partial, and any consumer who
|
|
808
|
+
* never wrote one — reads `Boolean(undefined) === false` and would be clamped
|
|
809
|
+
* as though an operator had disabled isolation. `getWorktreeIsolation` applies
|
|
810
|
+
* the framework default (`enabled: true`) the same way `resolveConfig` does for
|
|
811
|
+
* a real `.agentrc`, so the clamp fires on a deliberate off, never on an
|
|
812
|
+
* absent key.
|
|
813
|
+
*
|
|
814
|
+
* The record is emitted whenever isolation is off, **including when the
|
|
815
|
+
* requested cap was already 1**: the point is that the run's sequentiality is
|
|
816
|
+
* always attributable to the reason that forced it, not left to coincide with
|
|
817
|
+
* a preference that happened to agree.
|
|
818
|
+
*
|
|
819
|
+
* @param {object} args
|
|
820
|
+
* @param {{cap: number, source: string, configuredCap: number, requestedCap: number|null, exceedsConfigured: boolean, note: string}} args.requested
|
|
821
|
+
* The precedence record the flag-vs-config rules produced.
|
|
822
|
+
* @param {object|null} [args.config] Resolved config.
|
|
823
|
+
* @param {NodeJS.ProcessEnv} [args.env] Environment (test injection).
|
|
824
|
+
* @returns {typeof args.requested} The record unchanged when isolation is on,
|
|
825
|
+
* or the clamped record naming `worktree-clamp` as the winning source.
|
|
826
|
+
*/
|
|
827
|
+
export function resolveWorktreeClamp({ requested, config, env } = {}) {
|
|
828
|
+
const worktreeEnabled = resolveWorktreeEnabled(
|
|
829
|
+
{
|
|
830
|
+
config: { delivery: { worktreeIsolation: getWorktreeIsolation(config) } },
|
|
831
|
+
},
|
|
832
|
+
env ?? process.env,
|
|
833
|
+
);
|
|
834
|
+
if (worktreeEnabled) return requested;
|
|
835
|
+
|
|
836
|
+
const from =
|
|
837
|
+
requested.source === 'flag'
|
|
838
|
+
? '--concurrency'
|
|
839
|
+
: 'delivery.deliverRunner.concurrencyCap';
|
|
734
840
|
return {
|
|
735
|
-
cap:
|
|
736
|
-
source: '
|
|
737
|
-
configuredCap,
|
|
738
|
-
requestedCap:
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
841
|
+
cap: WORKTREE_DISABLED_CONCURRENCY_CAP,
|
|
842
|
+
source: 'worktree-clamp',
|
|
843
|
+
configuredCap: requested.configuredCap,
|
|
844
|
+
requestedCap: requested.cap,
|
|
845
|
+
// The run is at 1, so it is not above the project default whatever was
|
|
846
|
+
// asked for — the escalation never took effect and must not be reported
|
|
847
|
+
// as though it had.
|
|
848
|
+
exceedsConfigured: false,
|
|
849
|
+
note: `cap clamped to ${WORKTREE_DISABLED_CONCURRENCY_CAP} because worktree isolation resolved OFF — requested ${requested.cap} from ${from}. Concurrent dispatch shares one checkout without per-Story worktrees, so two workers would contend for HEAD; this floor outranks an explicit --concurrency.`,
|
|
743
850
|
};
|
|
744
851
|
}
|
|
745
852
|
|
|
@@ -1000,6 +1107,8 @@ export function detectWedge({ nodes, doneIds, ready, inFlight }) {
|
|
|
1000
1107
|
* @param {string|number} [args.inFlight] Raw --in-flight count.
|
|
1001
1108
|
* @param {string} [args.cwd] Repo root for config resolution.
|
|
1002
1109
|
* @param {object} [args.config] Pre-resolved config (test injection).
|
|
1110
|
+
* @param {NodeJS.ProcessEnv} [args.env] Environment the worktree-isolation
|
|
1111
|
+
* clamp reads (test injection).
|
|
1003
1112
|
* @returns {{
|
|
1004
1113
|
* envelope: {kind: string, ready: number[], totalStories: number, concurrencyCap: number, inFlight: number, cycleError: string|null},
|
|
1005
1114
|
* exitCode: number
|
|
@@ -1013,6 +1122,7 @@ export function runStoriesWaveTick({
|
|
|
1013
1122
|
inFlight,
|
|
1014
1123
|
cwd,
|
|
1015
1124
|
config,
|
|
1125
|
+
env,
|
|
1016
1126
|
} = {}) {
|
|
1017
1127
|
// Validate the --concurrency override before resolving config so an invalid
|
|
1018
1128
|
// value fails fast with exit code 1 regardless of DAG validity.
|
|
@@ -1033,7 +1143,7 @@ export function runStoriesWaveTick({
|
|
|
1033
1143
|
return inputErrorResult(doneError, null, inFlightValue);
|
|
1034
1144
|
}
|
|
1035
1145
|
|
|
1036
|
-
const capPrecedence = resolveCapPrecedence({ cwd, config, override });
|
|
1146
|
+
const capPrecedence = resolveCapPrecedence({ cwd, config, override, env });
|
|
1037
1147
|
const concurrencyCap = capPrecedence.cap;
|
|
1038
1148
|
|
|
1039
1149
|
let rawJson;
|
|
@@ -1099,6 +1209,10 @@ export function runStoriesWaveTick({
|
|
|
1099
1209
|
* the run-end signal for `plan-run-epilogue.js`.
|
|
1100
1210
|
* - `blocked` — ids carrying `agent::blocked` (Story #4601). Non-empty means
|
|
1101
1211
|
* the loop must END, not poll: see `BLOCKED_EXIT_CODE`.
|
|
1212
|
+
* - `stalledDispatch` — `--dispatched` ids live state still reports as
|
|
1213
|
+
* `agent::ready`. They stay withheld; the field exists so a caller keeping
|
|
1214
|
+
* an append-only dispatched list can see an id that is pinned rather than
|
|
1215
|
+
* merely initializing (Story #5363).
|
|
1102
1216
|
*
|
|
1103
1217
|
* @param {object} args
|
|
1104
1218
|
* @param {string} args.stories Raw `--stories` CSV of Story ids.
|
|
@@ -1107,9 +1221,14 @@ export function runStoriesWaveTick({
|
|
|
1107
1221
|
* has spawned but may not yet have observed labelled.
|
|
1108
1222
|
* @param {string} [args.cwd] Repo root for config resolution.
|
|
1109
1223
|
* @param {object} [args.config] Pre-resolved config (test injection).
|
|
1224
|
+
* @param {NodeJS.ProcessEnv} [args.env] Environment the worktree-isolation
|
|
1225
|
+
* clamp reads (test injection).
|
|
1110
1226
|
* @param {Function} [args.probe] Probe seam (test injection).
|
|
1111
1227
|
* @param {Function} [args.context] Provider-context seam (test injection).
|
|
1112
|
-
* @returns {Promise<{ envelope: object, exitCode: number }>}
|
|
1228
|
+
* @returns {Promise<{ envelope: object, exitCode: number, records: object[] }>}
|
|
1229
|
+
* `records` are the probed nodes (id, dependsOn, files, body, labels) — the
|
|
1230
|
+
* bodies this beat already fetched, handed to the caller beside the envelope
|
|
1231
|
+
* rather than serialized into it.
|
|
1113
1232
|
*/
|
|
1114
1233
|
export async function runProbedStoriesWaveTick({
|
|
1115
1234
|
stories,
|
|
@@ -1117,6 +1236,7 @@ export async function runProbedStoriesWaveTick({
|
|
|
1117
1236
|
dispatched,
|
|
1118
1237
|
cwd,
|
|
1119
1238
|
config,
|
|
1239
|
+
env,
|
|
1120
1240
|
probe = probeLiveState,
|
|
1121
1241
|
context = createProbeContext,
|
|
1122
1242
|
} = {}) {
|
|
@@ -1141,7 +1261,7 @@ export async function runProbedStoriesWaveTick({
|
|
|
1141
1261
|
return inputErrorResult(dispatchedError);
|
|
1142
1262
|
}
|
|
1143
1263
|
|
|
1144
|
-
const capPrecedence = resolveCapPrecedence({ cwd, config, override });
|
|
1264
|
+
const capPrecedence = resolveCapPrecedence({ cwd, config, override, env });
|
|
1145
1265
|
const concurrencyCap = capPrecedence.cap;
|
|
1146
1266
|
|
|
1147
1267
|
let probed;
|
|
@@ -1171,6 +1291,7 @@ export async function runProbedStoriesWaveTick({
|
|
|
1171
1291
|
doneIds,
|
|
1172
1292
|
inFlight,
|
|
1173
1293
|
blockedIds = [],
|
|
1294
|
+
stalledDispatch = [],
|
|
1174
1295
|
foreignHeld = [],
|
|
1175
1296
|
inFlightRecords = [],
|
|
1176
1297
|
} = probed;
|
|
@@ -1198,12 +1319,25 @@ export async function runProbedStoriesWaveTick({
|
|
|
1198
1319
|
epilogueDue,
|
|
1199
1320
|
blocked: blockedIds,
|
|
1200
1321
|
blockedReason: blockedReasonFor(blockedIds),
|
|
1322
|
+
// Ids the caller listed in `--dispatched` that live state still reports
|
|
1323
|
+
// as `agent::ready`. Withheld as in flight (that is the flag's whole
|
|
1324
|
+
// job) and named here, because the same reading covers a healthy init
|
|
1325
|
+
// window and a spawn that died before init — and only the caller, who
|
|
1326
|
+
// owns the dispatched list, can tell those apart (Story #5363).
|
|
1327
|
+
stalledDispatch,
|
|
1201
1328
|
// Stories another operator's lease holds — withheld from dispatch this
|
|
1202
1329
|
// beat (folded into in-flight) and surfaced so the run can report
|
|
1203
1330
|
// "#<id> held by @<holder>" instead of dispatching into an init refusal.
|
|
1204
1331
|
foreignHeld,
|
|
1205
1332
|
foreignHeldReason: foreignHeldReasonFor(foreignHeld),
|
|
1206
1333
|
},
|
|
1334
|
+
// The probed nodes, beside the envelope rather than inside it. The
|
|
1335
|
+
// envelope is a list of ids by design — a Story body is several KB and has
|
|
1336
|
+
// no business on stdout — but the probe has already fetched every body,
|
|
1337
|
+
// and the one caller that needs them (`deliver-run.js`, building each
|
|
1338
|
+
// ready Story's dispatch prompt from its declared `changes[]`) would
|
|
1339
|
+
// otherwise re-fetch the whole set to read what this beat already holds.
|
|
1340
|
+
records: nodes,
|
|
1207
1341
|
// A blocked Story outranks the scheduler's own verdict — including a
|
|
1208
1342
|
// wedge, whose named blockers are moot while a human owes a decision.
|
|
1209
1343
|
// A cycle (2) does not yield: a self-referential DAG is a planning error
|
|
@@ -19,7 +19,6 @@ file is the roster, the per-kind command table, and the procedure.
|
|
|
19
19
|
| `arch-cycles.json` | `npm run check:arch` |
|
|
20
20
|
| `cyclomatic.json` | `npm run check:cyclomatic` |
|
|
21
21
|
| `context-budget.json` | `npm run check:context-budget` |
|
|
22
|
-
| `workflow-citations.json` | `npm run check:workflow-citations` |
|
|
23
22
|
| `agents-loc.csv` | `npm run baseline:agents-loc` |
|
|
24
23
|
|
|
25
24
|
A refresh is **not** a regression entry — it tells the ratchet that the new
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"generatedAt": "2026-09-
|
|
2
|
+
"generatedAt": "2026-09-17T13:37:08.250Z",
|
|
3
3
|
"generator": "generate-skills-index.js@1",
|
|
4
4
|
"skills": [
|
|
5
5
|
{
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"tier": "stack",
|
|
88
88
|
"category": "qa",
|
|
89
89
|
"path": ".agents/skills/stack/qa/playwright/SKILL.md",
|
|
90
|
-
"description": "Robust E2E browser testing with Playwright. Use when writing browser-driven tests — leverage auto-waiting (no `waitForTimeout`), prefer user-visible locators (`getByRole`, `getByText`, `getByLabel`) over CSS/XPath, reuse `storageState` for auth, and enable trace-on-first-retry for CI debugging.",
|
|
90
|
+
"description": "Robust E2E browser testing with Playwright. Use when writing browser-driven tests — leverage auto-waiting (no `waitForTimeout`), prefer user-visible locators (`getByRole`, `getByText`, `getByLabel`) over CSS/XPath, reuse `storageState` for auth, and enable trace-on-first-retry for CI debugging. Also carries the `data-testid` contract a UI change states in its acceptance criteria.",
|
|
91
91
|
"policyCapsuleBullets": 8,
|
|
92
92
|
"allowedTools": null,
|
|
93
93
|
"vendor": "playwright"
|
|
@@ -5,6 +5,8 @@ description:
|
|
|
5
5
|
tests — leverage auto-waiting (no `waitForTimeout`), prefer user-visible
|
|
6
6
|
locators (`getByRole`, `getByText`, `getByLabel`) over CSS/XPath, reuse
|
|
7
7
|
`storageState` for auth, and enable trace-on-first-retry for CI debugging.
|
|
8
|
+
Also carries the `data-testid` contract a UI change states in its acceptance
|
|
9
|
+
criteria.
|
|
8
10
|
vendor: playwright
|
|
9
11
|
---
|
|
10
12
|
|
|
@@ -21,6 +23,30 @@ vendor: playwright
|
|
|
21
23
|
- Use a unique data set per test run, or tear down state explicitly, to prevent cross-test contamination.
|
|
22
24
|
- Never let Playwright own the lifetime of a dev server it did not start: boot the server out-of-band, point the suite at the running origin, and set `reuseExistingServer` so `webServer` only probes readiness.
|
|
23
25
|
|
|
26
|
+
## The testid contract
|
|
27
|
+
|
|
28
|
+
A `data-testid` is a selector two artifacts share: the component that renders
|
|
29
|
+
it and the spec that queries it. Renaming one without the other is a silent
|
|
30
|
+
break — the suite still compiles, and the failure arrives as a missing element
|
|
31
|
+
at run time, in CI, attributed to whichever change happened to land next.
|
|
32
|
+
|
|
33
|
+
So a change touching UI (`*.tsx`, `*.astro`, `*.svelte`, `*.vue`, a components
|
|
34
|
+
folder) states which side of that contract it is on, in its own acceptance
|
|
35
|
+
criteria, as one of:
|
|
36
|
+
|
|
37
|
+
- **`data-testid invariance: <the testids that MUST be preserved>`** — the
|
|
38
|
+
change reshapes markup and renames nothing, and the list says what the
|
|
39
|
+
reshape may not touch.
|
|
40
|
+
- **`data-testid changes: <old> -> <new>, with the matching
|
|
41
|
+
tests/e2e/*.spec.ts selector updated`** — the rename is deliberate, and the
|
|
42
|
+
spec file that queries it is edited in the same change (or in one ordered
|
|
43
|
+
ahead of it, when the work is split).
|
|
44
|
+
|
|
45
|
+
Renaming a testid without the matching spec edit is forbidden either way. When
|
|
46
|
+
a change deliberately renames nothing, say so in its negative-scope prose too:
|
|
47
|
+
a preserved set stated only once reads as an omission the next author is free
|
|
48
|
+
to revise.
|
|
49
|
+
|
|
24
50
|
## Running a `webServer`-backed suite outside CI
|
|
25
51
|
|
|
26
52
|
Playwright's `webServer` block **watches the process it spawned**. That
|