mandrel 2.56.0 → 2.58.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/agents/plan-critic.md +13 -18
- package/.agents/agents/story-worker.md +25 -33
- package/.agents/docs/agentrc-reference.json +0 -30
- package/.agents/docs/configuration.md +8 -28
- package/.agents/docs/execution-reference.md +5 -5
- package/.agents/docs/quality-gates.md +8 -7
- package/.agents/instructions.md +9 -10
- package/.agents/schemas/agentrc.schema.json +9 -185
- package/.agents/schemas/story-deliver-terminal.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +107 -17
- package/.agents/scripts/ceremony-derive.js +191 -0
- package/.agents/scripts/check-context-budget.js +28 -33
- package/.agents/scripts/check-cyclomatic.js +4 -3
- package/.agents/scripts/deliver-light.js +31 -94
- package/.agents/scripts/evidence-gate.js +17 -1
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +15 -2
- package/.agents/scripts/lib/baselines/coverage-updater-cli.js +110 -0
- package/.agents/scripts/lib/baselines/crap-preview-scan.js +25 -0
- package/.agents/scripts/lib/baselines/crap-updater-cli.js +223 -0
- package/.agents/scripts/lib/bdd-scenario-budget.js +21 -3
- package/.agents/scripts/lib/bootstrap/quality-bootstrap.js +0 -1
- package/.agents/scripts/lib/close-validation/gates.js +52 -1
- package/.agents/scripts/lib/config/acceptance-eval.js +25 -57
- package/.agents/scripts/lib/config/delivery-routing.js +7 -33
- package/.agents/scripts/lib/config/explain.js +0 -19
- package/.agents/scripts/lib/config/limits.js +18 -78
- package/.agents/scripts/lib/config/quality.js +6 -3
- package/.agents/scripts/lib/config/runners.js +3 -2
- package/.agents/scripts/lib/config-settings-schema-delivery.js +15 -68
- package/.agents/scripts/lib/config-settings-schema-quality.js +0 -14
- package/.agents/scripts/lib/config-settings-schema.js +16 -143
- package/.agents/scripts/lib/crap-engine.js +35 -4
- package/.agents/scripts/lib/crap-utils.js +17 -1
- package/.agents/scripts/lib/cyclomatic-ceiling.js +19 -7
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +1 -1
- package/.agents/scripts/lib/observability/source-classifier.js +1 -0
- package/.agents/scripts/lib/orchestration/acceptance-eval-decision.js +5 -4
- package/.agents/scripts/lib/orchestration/ceremony-routing.js +19 -73
- package/.agents/scripts/lib/orchestration/code-review.js +7 -3
- package/.agents/scripts/lib/orchestration/complexity-gate.js +46 -212
- package/.agents/scripts/lib/orchestration/file-assumptions.js +32 -17
- package/.agents/scripts/lib/orchestration/light-escalation.js +3 -3
- package/.agents/scripts/lib/orchestration/light-suitability.js +66 -233
- package/.agents/scripts/lib/orchestration/pinned-identifier-lint.js +137 -0
- package/.agents/scripts/lib/orchestration/plan-context.js +189 -387
- package/.agents/scripts/lib/orchestration/plan-critic-conditions.js +42 -153
- package/.agents/scripts/lib/orchestration/plan-critics-evaluate.js +14 -70
- package/.agents/scripts/lib/orchestration/plan-persist/acceptance-handle-repair.js +107 -0
- package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +305 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +138 -170
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +128 -297
- package/.agents/scripts/lib/orchestration/plan-persist/soft-findings.js +55 -0
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +16 -65
- package/.agents/scripts/lib/orchestration/plan-persist/wave-serialisation.js +22 -35
- package/.agents/scripts/lib/orchestration/plan-text-hygiene.js +36 -135
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +61 -223
- package/.agents/scripts/lib/orchestration/review-base-ref.js +138 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +37 -5
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +46 -16
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +6 -1
- package/.agents/scripts/lib/orchestration/story-close/context-budget-writeback.js +213 -0
- package/.agents/scripts/lib/orchestration/task-body-validator.js +10 -63
- package/.agents/scripts/lib/orchestration/ticket-validator-conflicts.js +33 -539
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +21 -414
- package/.agents/scripts/lib/orchestration/ticket-validator.js +54 -118
- package/.agents/scripts/lib/orchestration/verify-credit.js +69 -24
- package/.agents/scripts/lib/story-body/body-format-lints.js +15 -85
- package/.agents/scripts/lib/story-body/story-body.js +54 -240
- package/.agents/scripts/lib/templates/decomposer-prompts.js +133 -121
- package/.agents/scripts/lib/test-isolate/cli-options.js +93 -0
- package/.agents/scripts/lib/test-isolate/progress-log.js +45 -0
- package/.agents/scripts/lib/test-isolate/render-report.js +97 -0
- package/.agents/scripts/lib/test-isolate/run-isolate.js +87 -0
- package/.agents/scripts/lib/test-run-credit.js +277 -0
- package/.agents/scripts/lib/wave-runner/footprint.js +48 -358
- package/.agents/scripts/lib/wave-runner/ready-set.js +6 -5
- package/.agents/scripts/lib/workers/crap-worker.js +32 -41
- package/.agents/scripts/plan-context.js +7 -9
- package/.agents/scripts/plan-critics.js +28 -54
- package/.agents/scripts/plan-persist.js +25 -68
- package/.agents/scripts/quality-preview.js +51 -0
- package/.agents/scripts/run-tests.js +12 -0
- package/.agents/scripts/stories-wave-tick.js +23 -45
- package/.agents/scripts/test-isolate.js +13 -180
- package/.agents/scripts/update-coverage-baseline.js +25 -70
- package/.agents/scripts/update-crap-baseline.js +19 -123
- package/.agents/skills/core/scope-triage/SKILL.md +3 -3
- package/.agents/workflows/audit-clean-code.md +4 -3
- package/.agents/workflows/helpers/acceptance-self-eval.md +41 -41
- package/.agents/workflows/helpers/code-quality-guardrails.md +4 -4
- package/.agents/workflows/helpers/code-review.md +2 -3
- package/.agents/workflows/helpers/deliver-digest.md +46 -55
- package/.agents/workflows/helpers/deliver-light.md +40 -105
- package/.agents/workflows/helpers/deliver-reference.md +1 -1
- package/.agents/workflows/helpers/deliver-story-reference.md +54 -55
- package/.agents/workflows/helpers/deliver-story.md +10 -13
- package/.agents/workflows/helpers/plan-reference.md +163 -221
- package/.agents/workflows/mandrel-plan.md +31 -40
- package/.agents/workflows/memory-consolidate.md +9 -13
- package/docs/CHANGELOG.md +36 -0
- package/lib/cli/registry.js +98 -2
- package/lib/migrations/index.js +4 -0
- package/lib/migrations/steps/2.57.0-retire-delivery-limit-knobs.js +45 -0
- package/lib/migrations/steps/2.57.0-retire-planning-limit-knobs.js +59 -0
- package/package.json +1 -1
- package/.agents/scripts/lib/framework-version.js +0 -39
- package/.agents/scripts/lib/orchestration/consolidation-precondition.js +0 -223
- package/.agents/scripts/lib/orchestration/plan-persist/fan-out-gate.js +0 -97
- package/.agents/scripts/lib/orchestration/planning/decomposer-context.js +0 -26
- package/.agents/scripts/lib/orchestration/spec-budget.js +0 -89
- package/.agents/scripts/lib/orchestration/spec-spill.js +0 -74
- package/.agents/scripts/lib/orchestration/verify-tier-repair.js +0 -107
|
@@ -1,71 +1,61 @@
|
|
|
1
|
-
import { LIMITS_DEFAULTS } from '../config/limits.js';
|
|
2
1
|
import {
|
|
3
2
|
AUTHORING_ALTITUDE_GUIDANCE,
|
|
4
|
-
DEFAULT_MODEL_CAPACITY,
|
|
5
3
|
DELIVERABLE_GRANULARITY_GUIDANCE,
|
|
6
|
-
resolveCapacityCeilings,
|
|
7
4
|
} from '../orchestration/ticket-validator-sizing.js';
|
|
8
5
|
import { BODY_FORMAT_LINTS } from '../story-body/body-format-lints.js';
|
|
9
6
|
|
|
10
7
|
/**
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* `DEFAULT_MAX_TICKETS = 40` literal allowed the prompt to drift out of sync
|
|
14
|
-
* with `planning.maxTickets` when call sites forgot to pass the
|
|
15
|
-
* resolved value; importing it here means a fallback path (no caller-supplied
|
|
16
|
-
* value) still tracks the framework default in `lib/config/limits.js`.
|
|
17
|
-
*
|
|
18
|
-
* 2-tier is the only published hierarchy after Story #4041 removed the
|
|
19
|
-
* Feature tier: the prompt emits Stories only (direct Epic children) and
|
|
20
|
-
* asks the planner to carry acceptance/verify as top-level ticket arrays.
|
|
8
|
+
* The story-author system prompt (Story #5312 — rendered from the draft's
|
|
9
|
+
* Story count).
|
|
21
10
|
*
|
|
22
11
|
* **Single source of the prompt body (Story #4162).** This module is the sole
|
|
23
|
-
* carrier of the
|
|
24
|
-
*
|
|
25
|
-
*
|
|
12
|
+
* carrier of the story-author system prompt, delivered to the host LLM in the
|
|
13
|
+
* `systemPrompts.story` field of the `/mandrel-plan` context envelope (via
|
|
14
|
+
* `lib/orchestration/plan-context.js#buildSystemPrompts`), so no second
|
|
26
15
|
* verbatim copy can drift.
|
|
16
|
+
*
|
|
17
|
+
* Two layers, composed by {@link renderStoryAuthorPrompt}:
|
|
18
|
+
*
|
|
19
|
+
* - **The N=1 core** ({@link renderStoryAuthorCore}) — what every draft
|
|
20
|
+
* needs: the body schema, the contract-level Spec rule, the deterministic
|
|
21
|
+
* body-format lints, and acceptance defined as outcomes a PR reviewer can
|
|
22
|
+
* confirm from the diff and the verify output. It carries no delivery
|
|
23
|
+
* schedule, no per-file behavior paragraphs, no reviewability budget and
|
|
24
|
+
* no verify-tier suffix — every one of those either scored a shape the
|
|
25
|
+
* authoring model already judges or prescribed a proxy that became the
|
|
26
|
+
* goal.
|
|
27
|
+
* - **The N>1 rules** ({@link renderStorySplitRules}) — the schedule and
|
|
28
|
+
* partition rules that only mean anything once a draft has siblings:
|
|
29
|
+
* every Story must earn its slot in the wave schedule, and every
|
|
30
|
+
* acceptance criterion belongs to exactly one Story.
|
|
31
|
+
* - **The tickets-mode rules** ({@link ticketsModePromptField}, Story
|
|
32
|
+
* #5323) — what to re-derive rather than carry when the seed is an
|
|
33
|
+
* existing ticket whose body is already in Story shape.
|
|
34
|
+
*
|
|
35
|
+
* The envelope carries the core as `systemPrompts.story`, the split rules as
|
|
36
|
+
* `systemPrompts.storySplitRules` and the tickets rules as
|
|
37
|
+
* `systemPrompts.storyTicketsRules`; a planner reads the second only when the
|
|
38
|
+
* default-single split policy clears, and the third only in tickets mode.
|
|
27
39
|
*/
|
|
28
|
-
export function renderDecomposerSystemPrompt({
|
|
29
|
-
maxTickets = LIMITS_DEFAULTS.maxTickets,
|
|
30
|
-
} = {}) {
|
|
31
|
-
return render2TierPrompt({ maxTickets });
|
|
32
|
-
}
|
|
33
40
|
|
|
34
41
|
/**
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* ticket. Thematic grouping lives as prose in the Epic body / Tech Spec.
|
|
42
|
+
* The N=1 core of the story-author prompt.
|
|
43
|
+
*
|
|
44
|
+
* @returns {string}
|
|
39
45
|
*/
|
|
40
|
-
function
|
|
41
|
-
// v2 Stage 3: default-single — emit one Story unless the split policy clears.
|
|
42
|
-
// Capacity thresholds are sourced from the single DEFAULT_MODEL_CAPACITY
|
|
43
|
-
// constant (ticket-validator-sizing.js) so the prompt and the validator
|
|
44
|
-
// cannot drift.
|
|
45
|
-
const { softSessionTokens, hardSessionTokens } = resolveCapacityCeilings(
|
|
46
|
-
DEFAULT_MODEL_CAPACITY,
|
|
47
|
-
);
|
|
48
|
-
// Deliverable-granularity definition + single-consumer merge rule + the
|
|
49
|
-
// thin-dependent merge heuristic are sourced from the single
|
|
50
|
-
// DELIVERABLE_GRANULARITY_GUIDANCE constant (ticket-validator-sizing.js) so
|
|
51
|
-
// the prompt and the authoring SKILL cannot drift (Story #3777; the
|
|
52
|
-
// envelope-floor sentence added by Story #4313).
|
|
46
|
+
export function renderStoryAuthorCore() {
|
|
53
47
|
const {
|
|
54
48
|
definition: granularityDefinition,
|
|
55
49
|
singleConsumerRule,
|
|
56
50
|
envelopeFloor,
|
|
57
51
|
} = DELIVERABLE_GRANULARITY_GUIDANCE;
|
|
58
|
-
// The binding-vs-advisory authoring altitude + the New-File Contract are
|
|
59
|
-
// sourced from the single AUTHORING_ALTITUDE_GUIDANCE constant
|
|
60
|
-
// (ticket-validator-sizing.js) so the prompt and the authoring SKILL cannot
|
|
61
|
-
// drift (Story #4272).
|
|
62
52
|
const {
|
|
63
53
|
altitude: authoringAltitude,
|
|
64
54
|
advisoryCaveat,
|
|
65
55
|
newFileContract,
|
|
66
56
|
} = AUTHORING_ALTITUDE_GUIDANCE;
|
|
67
57
|
// The deterministic body-format lints (structured `## Changes` bullet shape,
|
|
68
|
-
//
|
|
58
|
+
// non-empty sections) rendered example-first from their single source
|
|
69
59
|
// (`lib/story-body/body-format-lints.js`) so an authored draft is lint-clean
|
|
70
60
|
// by construction rather than discovered as a persist dry-run failure and
|
|
71
61
|
// re-authored at resident-context prices (Story #4684).
|
|
@@ -84,10 +74,9 @@ Your job is to turn a plan seed / Tech Spec into a Story ticket array for an AI
|
|
|
84
74
|
- Thematic grouping is prose in the Story's folded \`## Spec\` / \`## Slicing\`, never sibling tickets for coupled work.
|
|
85
75
|
|
|
86
76
|
### LABEL CONVENTIONS:
|
|
87
|
-
- \`type::story\` is applied automatically by persist — you do not need to emit it, and no other type label is allowed
|
|
77
|
+
- \`type::story\` is applied automatically by persist — you do not need to emit it, and no other type label is allowed.
|
|
88
78
|
- \`labels[]\` is **optional**. Emit it only to request an *additional* label; persist sanitizes the list before applying it.
|
|
89
79
|
- Do **not** emit \`agent::*\` labels — lifecycle state is runtime-owned, and persist applies \`agent::ready\` itself once every checkpoint is on the ticket.
|
|
90
|
-
- Do **not** emit \`persona::*\` labels — the behavioral persona concept (and its label axis) was removed in v2.
|
|
91
80
|
|
|
92
81
|
### OUTPUT FORMAT:
|
|
93
82
|
You MUST respond ONLY with a valid JSON array of objects. No prose, no markdown blocks.
|
|
@@ -99,8 +88,8 @@ You MUST respond ONLY with a valid JSON array of objects. No prose, no markdown
|
|
|
99
88
|
"type": "story",
|
|
100
89
|
"title": "Short descriptive title",
|
|
101
90
|
"body": <string — see STORY BODY SCHEMA below>,
|
|
102
|
-
"acceptance": ["<
|
|
103
|
-
"verify": ["<exact command or test path>
|
|
91
|
+
"acceptance": ["<outcome a PR reviewer can confirm>", ...],
|
|
92
|
+
"verify": ["<exact command or test path>", ...],
|
|
104
93
|
"labels": ["<extra-label>"] (optional — type::story is applied automatically; omit this field unless you need an additional label),
|
|
105
94
|
"depends_on": ["slug-of-blocking-dependency"] (optional array of Story slugs that block execution)
|
|
106
95
|
}
|
|
@@ -109,7 +98,7 @@ You MUST respond ONLY with a valid JSON array of objects. No prose, no markdown
|
|
|
109
98
|
**Slug format**: \`^[a-z0-9][a-z0-9-]*$\` — hyphen-case only. Underscores are rejected by the validator.
|
|
110
99
|
|
|
111
100
|
### STORY BODY SCHEMA (REQUIRED FOR EVERY STORY):
|
|
112
|
-
\`body\` is either the serialized markdown **string** (the section format below) or a **structured object** carrying the same fields (\`goal\`, optional \`slicing\` / \`spec\`, \`changes\`, optional \`non_goals\`
|
|
101
|
+
\`body\` is either the serialized markdown **string** (the section format below) or a **structured object** carrying the same fields (\`goal\`, optional \`slicing\` / \`spec\`, \`changes\`, optional \`non_goals\`) — persist parses either shape and serializes the canonical markdown itself, so you never need to read \`story-body.js\` or hand-assemble the markdown (the \`stories.template.json\` file emitted next to the plan-context envelope is a ready-to-fill structured-object skeleton). Stories are consumed by non-interactive sub-agents that must self-verify from the Story ticket alone — so the ticket must carry everything an agent needs to execute and self-verify.
|
|
113
102
|
|
|
114
103
|
The \`acceptance[]\` and \`verify[]\` arrays live at the **top level** of the Story ticket object — that is the machine contract the validator reads. Author each list **once, at top level**, and **omit** the \`## Acceptance\` / \`## Verify\` sections from the authored \`body\` string: persist syncs the top-level arrays into those sections so the GitHub issue stays a complete executable document. The validator resolves both fields from the top level, so an omitted section is the expected shape, not a violation.
|
|
115
104
|
|
|
@@ -124,18 +113,18 @@ The **persisted** \`body\` renders these markdown sections (in order) — you au
|
|
|
124
113
|
<optional ordered intra-session checkpoints — not a second Spec or AC table>
|
|
125
114
|
|
|
126
115
|
## Spec
|
|
127
|
-
<optional
|
|
116
|
+
<optional technical approach at contract level — do NOT restate Goal / Acceptance / Verify>
|
|
128
117
|
|
|
129
118
|
## Changes
|
|
130
119
|
- {"path": "<file path>", "assumption": "creates" | "refactors-existing" | "deletes"}
|
|
131
120
|
- ...
|
|
132
121
|
|
|
133
122
|
## Acceptance <-- synthesized by persist from acceptance[]; do not author
|
|
134
|
-
- [ ] <
|
|
123
|
+
- [ ] <outcome a PR reviewer can confirm>
|
|
135
124
|
- ...
|
|
136
125
|
|
|
137
126
|
## Verify <-- synthesized by persist from verify[]; do not author
|
|
138
|
-
- <exact command or test path>
|
|
127
|
+
- <exact command or test path>
|
|
139
128
|
- ...
|
|
140
129
|
|
|
141
130
|
## Non-Goals
|
|
@@ -145,16 +134,12 @@ The **persisted** \`body\` renders these markdown sections (in order) — you au
|
|
|
145
134
|
#### STORY BODY RULES:
|
|
146
135
|
|
|
147
136
|
- **goal** (in body string): One sentence stating WHY this Story exists.
|
|
148
|
-
- **spec** (optional, in body string as \`## Spec\`):
|
|
149
|
-
- **slicing** (optional): Ordered intra-session checkpoints for one Story. Not a fan-out table and not a duplicate of Acceptance.
|
|
150
|
-
- **changes** (in body string): Each entry is an object \`{ path, assumption }\` where \`assumption\` is one of \`creates | refactors-existing | deletes\`. Acceptable path shapes include explicit files (\`src/components/Foo.tsx\`), glob patterns (\`tests/e2e/*.spec.ts\`, \`**/*.astro\`), and module identifiers that resolve to files. Use \`refactors-existing\` for in-place edits to a file already on \`main\`; \`creates\` for net-new files; \`deletes\` for removals.
|
|
151
|
-
- **acceptance** (top-level array on the ticket object):
|
|
152
|
-
- **verify** (top-level array on the ticket object):
|
|
153
|
-
- **
|
|
154
|
-
- **Observed-behavior claims open with \`Current state (verified <date>)\`.** Any Spec claim about how the codebase behaves today MUST open with that preamble (e.g. \`Current state (verified 2026-07-17): …\`) so a reader can tell a verified observation from an assumption, and can tell when the observation went stale.
|
|
155
|
-
- **Intent-then-proxy acceptance shape.** When an acceptance item verifies through a proxy check (a grep, a file-exists probe, an exit-code test), state the intent clause before the proxy check — what outcome the check stands in for — so the proxy never becomes the goal (e.g. "the workflow names hygiene findings as re-author input: \`grep -n "textHygiene" …\` exits 0").
|
|
156
|
-
- **Slicing checkpoints are one line each.** Each \`## Slicing\` checkpoint is a single line naming the checkpoint; implementation detail lives in \`## Spec\`, never duplicated into Slicing. A Slicing section outweighing its Spec is a defect the text-hygiene lint flags.
|
|
157
|
-
- **Bodies record decisions, never questions to the operator.** Never persist an open question ("Flag if…", "TBD", "confirm with the operator") into a Story body — the executing sub-agent is non-interactive and cannot answer it. Triage each unknown by who can resolve it: an AFK-shaped unknown (a fact in docs, a third-party API surface, observable repo behavior) MUST be resolved by your own research before authoring — never restated as an assumption; only a HITL-shaped unknown (a genuine product or architecture call the operator owns) may be restated as a declarative Key Assumption the agent can act on, stating the default chosen (a decision-made-by-default).
|
|
137
|
+
- **spec** (optional, in body string as \`## Spec\`): The technical approach at the altitude the SPEC PROSE CONTRACT below fixes — contract and invariants, never implementation narration. Write as much as the work needs and no more; persist keeps Specs inline at any length and never writes them under \`docs/\`.
|
|
138
|
+
- **slicing** (optional): Ordered intra-session checkpoints for one Story, one line each. Not a fan-out table and not a duplicate of Acceptance.
|
|
139
|
+
- **changes** (in body string): Each entry is an object \`{ path, assumption }\` where \`assumption\` is one of \`creates | refactors-existing | deletes\`. **Name the files the deliverer authors, and omit generated artifacts** — quality baselines, generated test indexes, migration journals, lockfiles and the like are regenerated by the work itself, the refresh is a close-gate concern, and declaring one needlessly reserves a footprint that serializes sibling Stories at dispatch. Acceptable path shapes include explicit files (\`src/components/Foo.tsx\`), glob patterns (\`tests/e2e/*.spec.ts\`, \`**/*.astro\`), and module identifiers that resolve to files. Use \`refactors-existing\` for in-place edits to a file already on \`main\`; \`creates\` for net-new files; \`deletes\` for removals. Persist probes every path against the base branch and repairs a plain-string bullet or a trailing parenthetical into the object form for you; a \`creates\` on an existing path or a \`refactors-existing\` on an absent one is a dry-run warning, and only a \`deletes\` naming an absent path is refused.
|
|
140
|
+
- **acceptance** (top-level array on the ticket object): Each item is an **outcome a PR reviewer can confirm from the diff and the verify output** — what is true of the codebase once the Story lands, stated at the altitude of the capability (a command that now exits 0 against a named input, a behavior a named test now asserts, a config that now fails validation on a retired key, a document that now records a decision). Aim for **three to six** items: fewer than three usually means the outcome is under-specified; more than six usually means acceptance is re-listing the footprint or the mechanical checks that belong in \`verify[]\`. Push grep-shaped probes, file-exists checks and exit-code tests down into \`verify[]\`; never pin an internal helper name or a private file path into an acceptance item the advisory \`changes[]\` is free to reshape. UNACCEPTABLE: "verify by reading the diff", "looks good", "matches the spec".
|
|
141
|
+
- **verify** (top-level array on the ticket object): The **mechanical checks** — exact commands or test paths the deliverer runs and the acceptance critic consumes as evidence: \`node --test tests/x.test.js\`, \`npm run lint\`, \`npm run validate\`, a scoped grep. Every acceptance item should be confirmable from at least one verify entry's output plus the diff. Stories with zero verify entries fail validation.
|
|
142
|
+
- **Bodies record decisions, never questions to the operator.** Never persist an open question ("Flag if…", "TBD", "confirm with the operator") into a Story body — the executing sub-agent is non-interactive and cannot answer it, and the dry-run warns on every one it finds. Triage each unknown by who can resolve it: an AFK-shaped unknown (a fact in docs, a third-party API surface, observable repo behavior) MUST be resolved by your own research before authoring — never restated as an assumption; only a HITL-shaped unknown (a genuine product or architecture call the operator owns) may be restated as a declarative Key Assumption the agent can act on, stating the default chosen (a decision-made-by-default).
|
|
158
143
|
- **non_goals** (OPTIONAL, in body string as the \`## Non-Goals\` section): A short list of capabilities or changes this Story explicitly does NOT deliver — an advisory negative-scope bound that fences the executing agent away from adjacent work. It is **advisory and NON-GATING**: the validator does not require, count, or reject on it, and an absent or empty section renders nothing. Use the EXACT single-word hyphenated heading spelling \`## Non-Goals\` (a space-separated heading like \`## Out of Scope\` is NOT recognized by the parser and will be dropped). Reach for it when a Story's negative boundary is non-obvious from its \`acceptance[]\` alone; omit it otherwise.
|
|
159
144
|
|
|
160
145
|
#### SPEC PROSE CONTRACT — state the contract, not the implementation:
|
|
@@ -164,13 +149,13 @@ The Story is executed by a frontier-model deliverer that reads the codebase itse
|
|
|
164
149
|
- **Spec states the contract and invariants**: interfaces, status codes, security invariants, and load-bearing constraints — each with its why. That is the whole job of the Spec.
|
|
165
150
|
- **Implementation choices belong to the deliverer** unless a choice is load-bearing; a load-bearing choice is stated as a constraint (with why it binds), never as a walkthrough of how to code it.
|
|
166
151
|
- **No per-file behavior paragraphs.** The \`## Changes\` list already names the footprint; do not narrate what each file will do.
|
|
167
|
-
- **No current-state narration.** Do not describe how the codebase works today as scene-setting; the deliverer reads the code.
|
|
152
|
+
- **No current-state narration.** Do not describe how the codebase works today as scene-setting; the deliverer reads the code. State only the claims a decision depends on.
|
|
168
153
|
- **Do not author a \`## References\` section.** Read-only context the deliverer needs is discoverable from the contract and the footprint.
|
|
169
154
|
- **Acceptance criteria remain the binding contract** — the Spec constrains and explains; \`acceptance[]\` binds.
|
|
170
155
|
|
|
171
156
|
#### DETERMINISTIC BODY-FORMAT LINTS — author lint-clean by construction:
|
|
172
157
|
|
|
173
|
-
Persist enforces the deterministic body-format rules below and **rejects** an authored body that violates any of them. Author every Story to satisfy all of them on the FIRST draft — each rule is stated example-first so there is nothing to discover by trial-and-error. \`
|
|
158
|
+
Persist enforces the deterministic body-format rules below and **rejects** an authored body that violates any of them. Author every Story to satisfy all of them on the FIRST draft — each rule is stated example-first so there is nothing to discover by trial-and-error. \`changes-path-entry-shape\` is the one rule persist repairs for you: a plain-string bullet whose path can be salvaged is rewritten into the object form by probing the base branch, and the repair is reported in the dry-run output.
|
|
174
159
|
|
|
175
160
|
${bodyFormatLintChecklist}
|
|
176
161
|
|
|
@@ -182,51 +167,17 @@ ${newFileContract}
|
|
|
182
167
|
|
|
183
168
|
${advisoryCaveat}
|
|
184
169
|
|
|
185
|
-
#### STORY SIZING — COHESION
|
|
170
|
+
#### STORY SIZING — COHESION, NOT COUNT:
|
|
186
171
|
|
|
187
172
|
**Decompose at deliverable granularity, not module/task level.** ${granularityDefinition}
|
|
188
173
|
|
|
189
|
-
The
|
|
174
|
+
The only sizing question is **cohesion**: *is this one coherent change with one reason to exist?* There is no ceiling on a Story's footprint, Spec length or acceptance count — a broad contract cutover is one Story when every changed site changes for the same reason. Frontier models one-shot capability-sized work in a single pass; do not fragment a coherent capability into dependent slices to stay "small", and do not pad a Story with adjacent work to look "complete".
|
|
190
175
|
|
|
191
176
|
${envelopeFloor}
|
|
192
177
|
|
|
193
|
-
- **One Story = one coherent change with one reason to exist.** If you cannot state that reason in a sentence, the Story is probably two Stories — or two Stories that should be one.
|
|
178
|
+
- **One Story = one coherent change with one reason to exist.** If you cannot state that reason in a sentence, the Story is probably two Stories — or two Stories that should be one.
|
|
194
179
|
- ${singleConsumerRule}
|
|
195
180
|
- **Split independent, parallelizable work** into sibling Stories — but only when the pieces genuinely have separate reasons to exist.
|
|
196
|
-
- **Declare \`wide\` with a one-line reason when a change is legitimately broad** (a cohesive cutover whose authored ticket mass is high for one reason). Declaring \`wide\` lifts the hard session-mass ceiling — see below.
|
|
197
|
-
|
|
198
|
-
**Capacity backstop (validator-enforced).** Absolute authored-token ceilings from the single \`DEFAULT_MODEL_CAPACITY\` constant in \`ticket-validator-sizing.js\` — not operator-tunable. They catch Spec novels, not capability-sized Stories:
|
|
199
|
-
|
|
200
|
-
- Soft advisory (**${softSessionTokens} tokens**): authored session mass above this emits a nudge to check cohesion or declare \`wide\`.
|
|
201
|
-
- Hard ceiling (**${hardSessionTokens} tokens**): authored session mass above this is **rejected** unless the Story declares \`wide\` with a reason.
|
|
202
|
-
- Session mass = **authored tokens only** (goal / reason / Spec / acceptance / verify / slicing / change-path text). File count and AC count do **not** inflate mass — a long binding contract or a broad file footprint is never by itself a reason to fragment one coherent capability into dependent slices.
|
|
203
|
-
|
|
204
|
-
#### DELIVERY-SCHEDULE SIMULATION — the story count must earn itself:
|
|
205
|
-
|
|
206
|
-
Before emitting, simulate the delivery schedule your plan implies, and judge the plan by its schedule — not by how tidy the taxonomy looks:
|
|
207
|
-
|
|
208
|
-
1. **Build the wave schedule.** A Story runs only after every \`depends_on\` completes, and two Stories that name the same file in \`changes[]\` cannot run in the same wave (the scheduler serializes file-overlapping Stories even when no \`depends_on\` edge links them).
|
|
209
|
-
2. **Compute the parallelism yield**: story count ÷ critical-path length in waves. A yield near 1.0 means the plan is a serial chain — N Stories that deliver no faster than one Story while paying N delivery sessions (branch, PR, review, CI).
|
|
210
|
-
3. **Every Story must earn its slot** by at least one of:
|
|
211
|
-
- **(a) parallelism** — it actually runs concurrently with a sibling in the schedule you just built ("logically independent" does not count; *schedule*-independent does);
|
|
212
|
-
- **(b) risk isolation** — it isolates a consumer-facing behavior change or high-risk cutover into its own reviewable, revertable unit;
|
|
213
|
-
- **(c) cohesion break** — merged into its neighbor it would no longer be one coherent change with one reason to exist.
|
|
214
|
-
4. **A dependent link with none of those justifications merges into its consumer.** This generalizes the single-consumer merge rule from pairs to chains.
|
|
215
|
-
5. **Hot-file rule.** When one file appears in the \`changes[]\` of more than a third of your Stories, the slicing axis cuts across a shared seam — merge the Stories that co-edit it, or re-slice along the seam so each Story owns its files.
|
|
216
|
-
|
|
217
|
-
End each Story's \`reason_to_exist\` with its justification letter and one clause, e.g. "… (a: runs in wave 1 alongside <slug>)" or "(b: isolates the auto-merge default change)". A reason that names only a topic ("config work", "docs") with no justification is a merge signal.
|
|
218
|
-
|
|
219
|
-
#### \`wide\` DECLARATION (optional — for legitimately broad changes):
|
|
220
|
-
|
|
221
|
-
A Story whose footprint is legitimately broad declares \`wide\` carrying a one-line human-readable reason. Encode it in the \`<!-- meta: {"wide": {"reason": "..."}} -->\` comment that \`serialize()\` appends to the body string — it is NOT a top-level ticket field:
|
|
222
|
-
|
|
223
|
-
\`\`\`json
|
|
224
|
-
"wide": { "reason": "hard contract cutover: migrate every <X> call site in one PR" }
|
|
225
|
-
\`\`\`
|
|
226
|
-
|
|
227
|
-
Declaring \`wide\` with a non-empty reason **lifts the hard session-mass rejection** — no Story is rejected for width when it states why it is broad. Omit \`wide\` for ordinary Stories; a wide footprint with no \`wide\` declaration emits only an advisory nudge (check cohesion or declare \`wide\`), never a rejection on its own.
|
|
228
|
-
|
|
229
|
-
**Glob entries** in \`changes[]\` (bullets containing \`*\`) mark the Story footprint as \`unknown-width\`: the numeric ceiling cannot bound a glob, so it is skipped. A Story carrying glob changes with no \`wide\` declaration emits an advisory nudge.
|
|
230
181
|
|
|
231
182
|
#### UI / TESTID INVARIANCE (per CLAUDE.md safety rule):
|
|
232
183
|
|
|
@@ -242,31 +193,92 @@ Every \`changes[]\` entry is a \`{ path, assumption }\` object — a prose bulle
|
|
|
242
193
|
|
|
243
194
|
- Stories that touch user-visible copy, brand assets, or visual style MUST cite the relevant section of \`docs/style-guide.md\` in \`acceptance\` (e.g. \`"acceptance": ["Hero copy matches docs/style-guide.md §3 (voice & tone)"]\`). If \`docs/style-guide.md\` does not exist or has no relevant section, state that explicitly: \`"acceptance": ["docs/style-guide.md absent — copy reviewed against the inline brand brief in the plan seed"]\`. Silence on style sourcing is a smell.
|
|
244
195
|
|
|
245
|
-
|
|
246
|
-
|
|
196
|
+
CRITICAL: Dependencies should follow execution blockers. There is no parent ticket — never emit a 'parent_slug' field.
|
|
197
|
+
IMPORTANT DEPENDENCY RULE: Story-to-Story dependencies are expressed via \`depends_on\` (one Story depends_on another Story's slug). Use this to express execution ordering across the plan.
|
|
198
|
+
**Never stop mid-array.** Always emit complete JSON — partial arrays are rejected by the validator.`;
|
|
199
|
+
}
|
|
247
200
|
|
|
248
|
-
|
|
201
|
+
/**
|
|
202
|
+
* The rules that only apply once a draft has more than one Story: the
|
|
203
|
+
* delivery-schedule simulation that makes each Story earn its slot, and the
|
|
204
|
+
* acceptance partition persist enforces at N>1.
|
|
205
|
+
*
|
|
206
|
+
* @returns {string}
|
|
207
|
+
*/
|
|
208
|
+
export function renderStorySplitRules() {
|
|
209
|
+
return `#### MULTI-STORY DRAFT — the story count must earn itself:
|
|
249
210
|
|
|
250
|
-
-
|
|
251
|
-
- **depends_on**: EMPTY (\`[]\`) — it runs first, in wave 0.
|
|
252
|
-
- **changes**: one entry per distinct absent \`.feature\` file, each \`{ "path": "<feature file path>", "assumption": "creates" }\`.
|
|
253
|
-
- **acceptance**: MUST assert (a) every new \`.feature\` file exists AND (b) every new scenario within them carries an \`@skip\` tag. Keep these observable (a grep/validate command exits 0, a file exists at a path).
|
|
254
|
-
- **verify**: a grep/validate command (tier \`validate\`), NOT an e2e runner — verifying that a file exists with the required tags needs no browser/playwright run. Example: \`grep -rL '@skip' tests/features/<area>/*.feature (validate)\` paired with an existence check.
|
|
255
|
-
- Each implementation Story whose \`verify[]\` references one of these scaffolded \`.feature\` paths MUST \`depends_on\` the scaffold Story (so the scaffold lands in an earlier wave). Omitting the link trips the soft \`missing-bdd-scaffold\` validator finding.
|
|
211
|
+
You are splitting past the default-single policy, so simulate the delivery schedule your plan implies and judge the plan by its schedule — not by how tidy the taxonomy looks:
|
|
256
212
|
|
|
257
|
-
|
|
213
|
+
1. **Build the wave schedule.** A Story runs only after every \`depends_on\` completes, and two Stories that name the same file in \`changes[]\` cannot run in the same wave (the scheduler serializes file-overlapping Stories even when no \`depends_on\` edge links them).
|
|
214
|
+
2. **Every Story must earn its slot** by at least one of:
|
|
215
|
+
- **(a) parallelism** — it actually runs concurrently with a sibling in the schedule you just built ("logically independent" does not count; *schedule*-independent does);
|
|
216
|
+
- **(b) risk isolation** — it isolates a consumer-facing behavior change or high-risk cutover into its own reviewable, revertable unit;
|
|
217
|
+
- **(c) cohesion break** — merged into its neighbor it would no longer be one coherent change with one reason to exist.
|
|
218
|
+
3. **A dependent link with none of those justifications merges into its consumer.** This generalizes the single-consumer merge rule from pairs to chains: N Stories that deliver no faster than one Story pay N delivery sessions (branch, PR, review, CI) for nothing.
|
|
219
|
+
4. **When one file appears in the \`changes[]\` of most of your Stories, the slicing axis cuts across a shared seam** — merge the Stories that co-edit it, or re-slice along the seam so each Story owns its files.
|
|
258
220
|
|
|
259
|
-
|
|
260
|
-
When a "docs update" / "runbook" / "README" Story appears downstream of an earlier Story in the same plan whose AC already covers updating the same document (e.g. a "config + runbook" Story followed by a "docs" Story touching the same runbook), the downstream Story's deliverable may be fully absorbed by the earlier Story. Flag the risk directly in the Story's top-level \`acceptance\` array by appending an item of the form:
|
|
261
|
-
"Scope verification note: this story's deliverable may already be satisfied by Story #<slug-or-id>'s AC — before implementing, \`git diff main -- <path>\` against the upstream Story branch and confirm whether a substantive edit is still required, or whether only a cross-reference remains."
|
|
262
|
-
This prevents the executing agent from redoing work the upstream Story already merged.
|
|
221
|
+
#### ACCEPTANCE PARTITION (persist-enforced at N>1):
|
|
263
222
|
|
|
264
|
-
|
|
265
|
-
|
|
223
|
+
- Every acceptance criterion of the plan belongs to **exactly one** Story — no criterion is shared, and none is dropped. Persist refuses a draft whose criteria overlap or leave a plan-level criterion unclaimed.
|
|
224
|
+
- Each Story carries its **own** \`## Spec\`; a shared \`techspec.md\` cannot be folded into N>1 Stories.
|
|
225
|
+
- Express ordering with \`depends_on\` (a sibling slug, or \`#<id>\` for an open Story from an earlier plan). A Story whose \`verify[]\` runs against a file a sibling creates MUST \`depends_on\` that sibling, so the file exists when verification runs.`;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* The rules that only apply when the seed is an existing ticket (Story
|
|
230
|
+
* #5323).
|
|
231
|
+
*
|
|
232
|
+
* A `--tickets` seed arrives already in Story shape — rendered `AC-<n>:`
|
|
233
|
+
* checkboxes, a `## Verify` list, a `## Changes` footprint — and an author
|
|
234
|
+
* reading it as a template carries that shape forward instead of re-deriving
|
|
235
|
+
* it. The observed failure (swarm-os #2707 / #2708, planned from #2542 under
|
|
236
|
+
* mandrel 2.57.0) was a Story whose acceptance list was the source's, handles
|
|
237
|
+
* and all, and whose verify entries carried a tier suffix retired two
|
|
238
|
+
* releases earlier. The source ticket is **evidence**, not a draft.
|
|
239
|
+
*
|
|
240
|
+
* @returns {string}
|
|
241
|
+
*/
|
|
242
|
+
function renderStoryTicketsRules() {
|
|
243
|
+
return `#### TICKETS-MODE DRAFT — the source ticket is evidence, not a template:
|
|
266
244
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
245
|
+
You are planning from one or more existing tickets. Read them for **what the work is** — the problem, the constraints, the commands that verify it — and re-derive everything else. Specifically:
|
|
246
|
+
|
|
247
|
+
1. **Re-derive \`acceptance[]\` from the goal.** Do not copy the source's \`## Acceptance\` list, and never carry its \`AC-<n>:\` handles — the body renderer numbers the checkboxes itself, so a copied handle renders doubled. A source ticket carrying fifteen criteria is telling you its acceptance was over-specified, not that yours must be: state the three to six outcomes a PR reviewer can confirm, and let the rest fall to \`verify[]\`.
|
|
248
|
+
2. **A mechanical check is a \`verify[]\` command, not an acceptance item.** "Baselines refreshed", "lint exits 0", "the generated index is regenerated", "the quality gate passes" are commands the deliverer runs and the critic reads as evidence. Carrying them as acceptance items inflates the binding contract with work every close already gates.
|
|
249
|
+
3. **Read the source's \`verify[]\` for the commands it names, not for its shape.** Take the test paths and scripts; drop any trailing tier suffix (\`(unit)\`, \`(contract)\`, \`(e2e)\`, \`(validate)\`) and any \`manual:<reason>\` escape — a verify entry is a bare command.
|
|
250
|
+
4. **Re-derive the footprint against the tree as it is now.** The source ticket's \`## Changes\` predicted a repository that has since moved; probe the paths you cite and omit the generated artifacts it listed.
|
|
251
|
+
5. **Do not carry the source's prose wholesale.** Its current-state narration and per-file walkthroughs are exactly what the SPEC PROSE CONTRACT above forbids. Restate the contract and the invariants; the deliverer reads the code for the rest.`;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Render the story-author prompt for a draft of `storyCount` Stories: the
|
|
256
|
+
* N=1 core, plus the schedule and partition rules when the draft has
|
|
257
|
+
* siblings.
|
|
258
|
+
*
|
|
259
|
+
* @param {{ storyCount?: number }} [args]
|
|
260
|
+
* @returns {string}
|
|
261
|
+
*/
|
|
262
|
+
export function renderStoryAuthorPrompt({ storyCount = 1 } = {}) {
|
|
263
|
+
const core = renderStoryAuthorCore();
|
|
264
|
+
return storyCount > 1 ? `${core}\n\n${renderStorySplitRules()}` : core;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* The mode-conditional slice of `systemPrompts`.
|
|
269
|
+
*
|
|
270
|
+
* `storyTicketsRules` only means anything when the seed is an existing
|
|
271
|
+
* ticket, and an envelope carrying it in every mode teaches the author to
|
|
272
|
+
* look for a source ticket a `--seed` run does not have. Returning a
|
|
273
|
+
* spreadable object rather than a nullable string keeps the decision here,
|
|
274
|
+
* beside the prompt it selects, instead of as a branch in the envelope
|
|
275
|
+
* builder.
|
|
276
|
+
*
|
|
277
|
+
* @param {string|undefined} mode The plan-context mode.
|
|
278
|
+
* @returns {{ storyTicketsRules?: string }}
|
|
279
|
+
*/
|
|
280
|
+
export function ticketsModePromptField(mode) {
|
|
281
|
+
return mode === 'tickets'
|
|
282
|
+
? { storyTicketsRules: renderStoryTicketsRules() }
|
|
283
|
+
: {};
|
|
272
284
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/test-isolate/cli-options.js — argv → options for the `test-isolate` CLI.
|
|
3
|
+
*
|
|
4
|
+
* Story #5316: this lived inside `.agents/scripts/test-isolate.js`, a file no
|
|
5
|
+
* test imports, so it scored the CRAP formula's untested maximum (210 at
|
|
6
|
+
* cyclomatic 14 and 0% coverage) and carried the tree's `test-isolate.js`
|
|
7
|
+
* cyclomatic breach row. It sits here beside `list-files.js`, `parse-tap.js`
|
|
8
|
+
* and `runner.js` for the same reason they do: the CLI shell is unreachable
|
|
9
|
+
* from a test, and everything reachable belongs under `lib/`.
|
|
10
|
+
*
|
|
11
|
+
* The long `else if` ladder it replaces was cyclomatic 14, above the repo's
|
|
12
|
+
* must-fix ceiling of 12. Dispatching through two flag tables is the same
|
|
13
|
+
* parse — same precedence, same tolerance for a value-taking flag that ends
|
|
14
|
+
* the argv — at roughly half the branch count.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Flags that consume the following argv entry as a number, keyed by the
|
|
19
|
+
* option each one sets.
|
|
20
|
+
*/
|
|
21
|
+
const NUMERIC_FLAGS = {
|
|
22
|
+
'--workers': 'workers',
|
|
23
|
+
'--max-bisect-depth': 'maxBisectDepth',
|
|
24
|
+
'--max-bisect-targets': 'maxBisectTargets',
|
|
25
|
+
'--suite-concurrency': 'suiteConcurrency',
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/** Flags that are their own value. */
|
|
29
|
+
const BOOLEAN_FLAGS = {
|
|
30
|
+
'--json': 'json',
|
|
31
|
+
'--quiet': 'quiet',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Defaults every parse starts from. Deliberately module-private: nothing in
|
|
36
|
+
* production reads it, and exporting it so a test could assert against it
|
|
37
|
+
* would both make that test tautological and land a test-only export on the
|
|
38
|
+
* `dead-exports:production` ratchet.
|
|
39
|
+
*/
|
|
40
|
+
function defaultOptions() {
|
|
41
|
+
return {
|
|
42
|
+
pattern: undefined,
|
|
43
|
+
workers: undefined,
|
|
44
|
+
maxBisectDepth: 8,
|
|
45
|
+
maxBisectTargets: 5,
|
|
46
|
+
suiteConcurrency: 8,
|
|
47
|
+
json: false,
|
|
48
|
+
quiet: false,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Parse the `test-isolate` CLI's argv.
|
|
54
|
+
*
|
|
55
|
+
* Precedence, unchanged from the ladder this replaces:
|
|
56
|
+
* - a known numeric flag consumes the next entry, but only when there IS a
|
|
57
|
+
* next entry — a trailing `--workers` is ignored rather than setting NaN;
|
|
58
|
+
* - a known boolean flag sets its option;
|
|
59
|
+
* - the FIRST non-flag argument becomes the pattern, and later ones are
|
|
60
|
+
* ignored;
|
|
61
|
+
* - anything else is ignored, so an unknown `--flag` is never mistaken for
|
|
62
|
+
* the pattern.
|
|
63
|
+
*
|
|
64
|
+
* @param {string[]} [argv]
|
|
65
|
+
* @returns {{
|
|
66
|
+
* pattern: string|undefined,
|
|
67
|
+
* workers: number|undefined,
|
|
68
|
+
* maxBisectDepth: number,
|
|
69
|
+
* maxBisectTargets: number,
|
|
70
|
+
* suiteConcurrency: number,
|
|
71
|
+
* json: boolean,
|
|
72
|
+
* quiet: boolean,
|
|
73
|
+
* }}
|
|
74
|
+
*/
|
|
75
|
+
export function parseIsolateArgv(argv = []) {
|
|
76
|
+
const options = defaultOptions();
|
|
77
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
78
|
+
const arg = argv[i];
|
|
79
|
+
const numericKey = NUMERIC_FLAGS[arg];
|
|
80
|
+
if (numericKey && argv[i + 1]) {
|
|
81
|
+
options[numericKey] = Number(argv[i + 1]);
|
|
82
|
+
i += 1;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const booleanKey = BOOLEAN_FLAGS[arg];
|
|
86
|
+
if (booleanKey) {
|
|
87
|
+
options[booleanKey] = true;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (!arg.startsWith('--') && !options.pattern) options.pattern = arg;
|
|
91
|
+
}
|
|
92
|
+
return options;
|
|
93
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/test-isolate/progress-log.js — the `onProgress` sink for a
|
|
3
|
+
* `diagnoseIsolation` run.
|
|
4
|
+
*
|
|
5
|
+
* Story #5316: this was an anonymous arrow inlined into `runTestIsolate`'s
|
|
6
|
+
* argument list, which is exactly why it scored CRAP 72 (cyclomatic 8 at 0%
|
|
7
|
+
* coverage) under a name — `<anon runTestIsolate/(stage,payload)#0>` — that no
|
|
8
|
+
* test could address. As a named factory it is a plain table lookup.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/** How many bisection suspects are named inline before eliding the rest. */
|
|
12
|
+
const SUSPECT_PREVIEW = 3;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Per-stage line formatters. A stage with no entry here is ignored, which is
|
|
16
|
+
* what the `else if` ladder this replaces did by falling off the end — so a
|
|
17
|
+
* new stage emitted by the runner stays silent rather than throwing.
|
|
18
|
+
*/
|
|
19
|
+
const STAGE_LINES = {
|
|
20
|
+
'isolated:start': (p) => `[test-isolate] isolated phase: ${p.count} file(s)`,
|
|
21
|
+
'isolated:done': () => '[test-isolate] isolated phase: done',
|
|
22
|
+
'suite:start': (p) => `[test-isolate] suite phase: ${p.count} file(s)`,
|
|
23
|
+
'suite:done': () => '[test-isolate] suite phase: done',
|
|
24
|
+
'bisect:start': (p) => `[test-isolate] bisecting flipper: ${p.target}`,
|
|
25
|
+
'bisect:done': (p) => {
|
|
26
|
+
const list = p.suspects.slice(0, SUSPECT_PREVIEW).join(', ');
|
|
27
|
+
const hidden = p.suspects.length - SUSPECT_PREVIEW;
|
|
28
|
+
const more = hidden > 0 ? ` (+${hidden} more)` : '';
|
|
29
|
+
return `[test-isolate] suspects: ${list}${more}`;
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Build the progress callback `diagnoseIsolation` calls as each phase starts
|
|
35
|
+
* and finishes.
|
|
36
|
+
*
|
|
37
|
+
* @param {(line: string) => void} onLog
|
|
38
|
+
* @returns {(stage: string, payload: object) => void}
|
|
39
|
+
*/
|
|
40
|
+
export function createProgressLogger(onLog) {
|
|
41
|
+
return (stage, payload) => {
|
|
42
|
+
const format = STAGE_LINES[stage];
|
|
43
|
+
if (format) onLog(format(payload ?? {}));
|
|
44
|
+
};
|
|
45
|
+
}
|