mandrel 2.58.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 +17 -12
- package/.agents/agents/acceptance-critic.md +24 -43
- package/.agents/agents/story-worker.md +18 -19
- package/.agents/docs/SDLC.md +12 -13
- package/.agents/docs/agentrc-reference.json +1 -2
- package/.agents/docs/configuration.md +29 -46
- package/.agents/docs/quality-gates.md +9 -5
- package/.agents/docs/workflows.md +1 -1
- package/.agents/instructions.md +5 -7
- package/.agents/rules/ci-remediation.md +41 -8
- package/.agents/rules/known-tooling-behavior.md +65 -15
- package/.agents/runtime-deps.json +7 -2
- package/.agents/schemas/acceptance-eval-verdict.schema.json +1 -1
- package/.agents/schemas/agentrc.schema.json +6 -11
- package/.agents/schemas/crap-baseline.schema.json +1 -1
- package/.agents/schemas/crap-report.schema.json +1 -1
- 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/install-matrix-assert.js +48 -3
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +51 -33
- package/.agents/scripts/lib/baselines/crap-preview-incremental.js +6 -2
- package/.agents/scripts/lib/baselines/kinds/_crap-read.js +0 -8
- package/.agents/scripts/lib/baselines/kinds/crap.js +35 -18
- 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/crap-engine.js +2 -2
- package/.agents/scripts/lib/crap-utils.js +21 -5
- package/.agents/scripts/lib/doc-tiers.js +4 -2
- package/.agents/scripts/lib/escomplex-ast-compat.js +39 -17
- package/.agents/scripts/lib/escomplex-kernel.js +298 -0
- 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/maintainability-engine.js +3 -3
- 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 +44 -50
- package/.agents/scripts/lib/orchestration/plan-persist/changes-repair.js +8 -6
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +104 -119
- package/.agents/scripts/lib/orchestration/plan-persist/story-ops.js +41 -25
- 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/plan-persist/wave-collision-gate.js +107 -0
- 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-conflicts.js +25 -209
- package/.agents/scripts/lib/orchestration/ticket-validator-sizing.js +8 -5
- package/.agents/scripts/lib/orchestration/ticket-validator.js +44 -183
- package/.agents/scripts/lib/orchestration/ticketing/reads.js +14 -25
- package/.agents/scripts/lib/runtime-deps/dep-resolution.js +155 -0
- package/.agents/scripts/lib/runtime-deps/ensure-installed.js +44 -9
- package/.agents/scripts/lib/runtime-deps/parser-major.js +110 -0
- package/.agents/scripts/lib/runtime-deps/preflight.js +6 -25
- package/.agents/scripts/lib/runtime-deps/scan-imports.js +46 -1
- package/.agents/scripts/lib/skills/walk-skill-files.js +1 -1
- 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 +28 -33
- 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 -39
- 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 -12
- package/.agents/skills/stack/qa/playwright/SKILL.md +26 -0
- package/.agents/workflows/audit-to-stories.md +14 -11
- 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 +82 -60
- package/.agents/workflows/mandrel-deliver.md +47 -31
- package/.agents/workflows/mandrel-plan.md +32 -30
- package/.agents/workflows/mandrel-update.md +36 -21
- package/README.md +3 -3
- package/docs/CHANGELOG.md +43 -0
- package/lib/cli/registry.js +45 -25
- 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 +8 -2
- package/.agents/schemas/model-attribution.schema.json +0 -53
- package/.agents/scripts/lib/orchestration/model-attribution.js +0 -418
- package/.agents/scripts/lib/orchestration/split-policy-validator.js +0 -188
- package/.agents/scripts/lib/orchestration/story-plan-state.js +0 -33
- package/.agents/scripts/lib/orchestration/structured-comment-parser.js +0 -67
- package/.agents/scripts/lib/templates/spec-author-prompts.js +0 -76
- package/.agents/skills/core/scope-triage/SKILL.md +0 -48
|
@@ -7,23 +7,23 @@
|
|
|
7
7
|
*
|
|
8
8
|
* 1. `changes[]` repair + ticket validator + file-assumption + DAG
|
|
9
9
|
* 2. Draft reachability (named soft failure, exit 3)
|
|
10
|
-
* 3.
|
|
10
|
+
* 3. Same-wave collision refusal (`assertNoWaveCollisions`) + spec fold
|
|
11
11
|
* 4. Create Story issues (`type::story` + sanitized authored labels —
|
|
12
12
|
* deliberately NOT `agent::ready`), resumably via a plan fingerprint
|
|
13
|
-
* 5. Upsert `story-plan-state` on every created Story
|
|
14
|
-
*
|
|
13
|
+
* 5. Upsert the `story-plan-state` comment on every created Story — since
|
|
14
|
+
* Story #5343 the one comment persist posts, carrying the plan summary
|
|
15
15
|
* 6. Flip every Story to `agent::ready` — the terminal step, so `ready`
|
|
16
|
-
* always implies "
|
|
16
|
+
* always implies "plan comments written"
|
|
17
17
|
* 7. Comment on + close the superseded `--tickets` source issues
|
|
18
18
|
* (Story #4535) — bookkeeping only; never fails the run
|
|
19
19
|
* 8. Temp cleanup at terminal success + a stale-plan-dir reap
|
|
20
20
|
*
|
|
21
21
|
* **Why `agent::ready` moved to the end (Story #4541).** Issues used to be
|
|
22
|
-
* born `agent::ready` in the creating POST while the
|
|
22
|
+
* born `agent::ready` in the creating POST while the plan comments were
|
|
23
23
|
* written afterwards. Anything picking a Story up in that window — or after
|
|
24
|
-
* a comment failure aborted the loop —
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* a comment failure aborted the loop — found a ready Story carrying none of
|
|
25
|
+
* the operator's delivery instructions. Creating unlabelled, writing the
|
|
26
|
+
* comments, then flipping closes that race.
|
|
27
27
|
*
|
|
28
28
|
* **No authored risk artifact (Story #4542).** Persist neither requires nor
|
|
29
29
|
* accepts a risk verdict, derives no envelope from one, and computes no
|
|
@@ -75,48 +75,48 @@ import {
|
|
|
75
75
|
createStoryIssues,
|
|
76
76
|
markStoriesReady,
|
|
77
77
|
} from './story-ops.js';
|
|
78
|
-
import {
|
|
79
|
-
buildPlanSummaryCommentBody,
|
|
80
|
-
buildWaveTable,
|
|
81
|
-
PLAN_SUMMARY_COMMENT_TYPE,
|
|
82
|
-
} from './summary.js';
|
|
78
|
+
import { buildPlanSummaryCommentBody, buildWaveTable } from './summary.js';
|
|
83
79
|
import { closeSupersededTickets } from './supersede-ops.js';
|
|
84
|
-
import {
|
|
85
|
-
|
|
86
|
-
/** Checkpoint schema version written on each Story's story-plan-state. */
|
|
87
|
-
const PLAN_CHECKPOINT_SCHEMA_VERSION_V2 = 2;
|
|
80
|
+
import { assertNoWaveCollisions } from './wave-collision-gate.js';
|
|
88
81
|
|
|
89
|
-
/** Structured-comment type for the per-plan Story
|
|
82
|
+
/** Structured-comment type for the per-plan Story comment. */
|
|
90
83
|
const STORY_PLAN_STATE_TYPE = 'story-plan-state';
|
|
91
84
|
|
|
92
85
|
/**
|
|
93
|
-
* Write the
|
|
86
|
+
* Write the plan comment on a Story — the **one** comment persist posts per
|
|
87
|
+
* Story (Story #5343).
|
|
88
|
+
*
|
|
89
|
+
* It carries the human plan summary: the created Story set, the delivery order
|
|
90
|
+
* and the exact deliver command. That summary used to be a second
|
|
91
|
+
* `plan-summary` comment on the primary Story only, which meant the operator's
|
|
92
|
+
* instructions lived on a different marker from the rest — and on a different
|
|
93
|
+
* ticket from four Stories out of five.
|
|
94
|
+
*
|
|
95
|
+
* **No machine payload (Story #5367).** The comment used to lead with a fenced
|
|
96
|
+
* JSON checkpoint (a persist receipt: completion time, Story count, the
|
|
97
|
+
* cohort). Nothing ever read it back — the one module that parsed it had no
|
|
98
|
+
* production importer — so the payload and its readers are gone. The
|
|
99
|
+
* `story-plan-state` marker stays: it is what makes a re-persist upsert this
|
|
100
|
+
* comment in place instead of appending a second one.
|
|
94
101
|
*
|
|
95
102
|
* @param {object} provider
|
|
96
103
|
* @param {number} storyId
|
|
97
|
-
* @param {
|
|
104
|
+
* @param {string} summary Rendered plan-summary markdown. The comment exists
|
|
105
|
+
* to carry it, so an empty summary is a caller bug rather than a degenerate
|
|
106
|
+
* comment worth posting.
|
|
107
|
+
* @returns {Promise<void>}
|
|
98
108
|
*/
|
|
99
|
-
export async function
|
|
109
|
+
export async function writePlanSummaryComment(provider, storyId, summary) {
|
|
100
110
|
if (!Number.isInteger(storyId)) {
|
|
101
|
-
throw new TypeError('
|
|
111
|
+
throw new TypeError('writePlanSummaryComment requires a numeric storyId');
|
|
112
|
+
}
|
|
113
|
+
const body = summary?.trim();
|
|
114
|
+
if (!body) {
|
|
115
|
+
throw new TypeError(
|
|
116
|
+
'writePlanSummaryComment requires a non-empty plan summary',
|
|
117
|
+
);
|
|
102
118
|
}
|
|
103
|
-
const body = [
|
|
104
|
-
'### story-plan-state',
|
|
105
|
-
'',
|
|
106
|
-
'```json',
|
|
107
|
-
JSON.stringify(
|
|
108
|
-
{
|
|
109
|
-
version: PLAN_CHECKPOINT_SCHEMA_VERSION_V2,
|
|
110
|
-
storyId,
|
|
111
|
-
...state,
|
|
112
|
-
},
|
|
113
|
-
null,
|
|
114
|
-
2,
|
|
115
|
-
),
|
|
116
|
-
'```',
|
|
117
|
-
].join('\n');
|
|
118
119
|
await upsertStructuredComment(provider, storyId, STORY_PLAN_STATE_TYPE, body);
|
|
119
|
-
return state;
|
|
120
120
|
}
|
|
121
121
|
|
|
122
122
|
/**
|
|
@@ -130,7 +130,7 @@ export async function writeCheckpointV2(provider, storyId, state) {
|
|
|
130
130
|
* the helper applied are reported alongside so the operator sees what was
|
|
131
131
|
* rewritten.
|
|
132
132
|
*
|
|
133
|
-
* The returned freshness counts feed the posted
|
|
133
|
+
* The returned freshness counts feed the posted summary's freshness
|
|
134
134
|
* line: every warning is a reference the base branch disagreed with, so it
|
|
135
135
|
* counts as `stale` there rather than the comment reading "clean" on a run
|
|
136
136
|
* that had something to say.
|
|
@@ -197,8 +197,9 @@ function collectTextHygieneWarnings(rawStories) {
|
|
|
197
197
|
|
|
198
198
|
/**
|
|
199
199
|
* Print every warning the run collected under one heading. The dry-run is
|
|
200
|
-
* where an operator reads these; the persist prints the same list so
|
|
201
|
-
*
|
|
200
|
+
* where an operator reads these; the persist prints the same list so the
|
|
201
|
+
* chained run loses nothing on stderr, as the chain's envelope merge keeps
|
|
202
|
+
* it from losing anything on stdout (Story #5361).
|
|
202
203
|
*
|
|
203
204
|
* @param {string[]} warnings
|
|
204
205
|
* @returns {void}
|
|
@@ -248,7 +249,7 @@ async function runSupersedePhase(args) {
|
|
|
248
249
|
}
|
|
249
250
|
|
|
250
251
|
/**
|
|
251
|
-
* Render the plan-metrics line for the **posted**
|
|
252
|
+
* Render the plan-metrics line for the **posted** summary section,
|
|
252
253
|
* scoped to this invocation.
|
|
253
254
|
*
|
|
254
255
|
* The ordering hazard this closes: the ledger record for the current run is
|
|
@@ -313,7 +314,7 @@ async function renderRunScopedPlanMetricsLine({
|
|
|
313
314
|
* writes — and the passes that scan `body.acceptance` / `body.verify` were
|
|
314
315
|
* inert on the canonical top-level authoring shape as a result. Findings the
|
|
315
316
|
* raw pass already reported are dropped so the same collision is not
|
|
316
|
-
* announced twice per run; the rest are returned for the
|
|
317
|
+
* announced twice per run; the rest are returned for the summary
|
|
317
318
|
* comment, which is where these findings stop being a stderr line nobody
|
|
318
319
|
* keeps. Every finding is advisory (Story #5312).
|
|
319
320
|
*
|
|
@@ -419,56 +420,33 @@ async function enforceReachability(reachability, config) {
|
|
|
419
420
|
}
|
|
420
421
|
|
|
421
422
|
/**
|
|
422
|
-
* Write the per-Story
|
|
423
|
-
* every created Story to `agent::ready`.
|
|
424
|
-
* `agent::ready` lands last so it can
|
|
425
|
-
* (Story #4541). A dry run performs none of it.
|
|
423
|
+
* Write the per-Story plan comment — one comment per Story, carrying the plan
|
|
424
|
+
* summary (Story #5343) — and flip every created Story to `agent::ready`.
|
|
425
|
+
* Terminal ordering is load-bearing: `agent::ready` lands last so it can
|
|
426
|
+
* honestly mean "fully persisted" (Story #4541). A dry run performs none of it.
|
|
426
427
|
*
|
|
427
|
-
* **The
|
|
428
|
+
* **The comments fan out; the phase boundary does not** (Story #4952). The
|
|
428
429
|
* per-Story upserts are independent of one another and run under bounded
|
|
429
430
|
* concurrency, but the `await` on that whole fan-out is what keeps the
|
|
430
|
-
* Story #4541 invariant intact: *every*
|
|
431
|
-
* first `agent::ready` flip is issued, so `ready` still means "fully
|
|
432
|
-
* persisted" and
|
|
433
|
-
*
|
|
434
|
-
* the race this ordering exists to close.
|
|
431
|
+
* Story #4541 invariant intact: *every* Story carries its plan comment before
|
|
432
|
+
* the first `agent::ready` flip is issued, so `ready` still means "fully
|
|
433
|
+
* persisted" and the operator's instructions are never missing from a ticket
|
|
434
|
+
* something else may already be picking up. Concurrency inside the phase is
|
|
435
|
+
* safe; overlapping the phases is the race this ordering exists to close.
|
|
435
436
|
*
|
|
436
437
|
* @param {object} args
|
|
437
438
|
* @returns {Promise<void>}
|
|
438
439
|
*/
|
|
439
|
-
async function persistStoryArtifacts({
|
|
440
|
-
provider,
|
|
441
|
-
created,
|
|
442
|
-
primary,
|
|
443
|
-
summaryBody,
|
|
444
|
-
}) {
|
|
445
|
-
const cohort = created.map((createdStory) => ({
|
|
446
|
-
slug: createdStory.slug,
|
|
447
|
-
id: createdStory.id,
|
|
448
|
-
}));
|
|
440
|
+
async function persistStoryArtifacts({ provider, created, summaryBody }) {
|
|
449
441
|
await concurrentMap(
|
|
450
442
|
created,
|
|
451
|
-
(story) =>
|
|
452
|
-
|
|
453
|
-
persist: {
|
|
454
|
-
completedAt: new Date().toISOString(),
|
|
455
|
-
storyCount: created.length,
|
|
456
|
-
primaryStoryId: primary.id,
|
|
457
|
-
stories: cohort,
|
|
458
|
-
},
|
|
459
|
-
}),
|
|
460
|
-
// The per-Story checkpoint upserts (Story #4952): each targets a
|
|
443
|
+
(story) => writePlanSummaryComment(provider, story.id, summaryBody),
|
|
444
|
+
// The per-Story comment upserts (Story #4952): each targets a
|
|
461
445
|
// different issue and reads nothing another writes, so this loop was
|
|
462
446
|
// serial only by construction — but see {@link persistStoryArtifacts}
|
|
463
447
|
// for the phase ordering that is *not* incidental.
|
|
464
448
|
{ concurrency: FANOUT_CONCURRENCY },
|
|
465
449
|
);
|
|
466
|
-
await upsertStructuredComment(
|
|
467
|
-
provider,
|
|
468
|
-
primary.id,
|
|
469
|
-
PLAN_SUMMARY_COMMENT_TYPE,
|
|
470
|
-
summaryBody,
|
|
471
|
-
);
|
|
472
450
|
await markStoriesReady({ provider, created });
|
|
473
451
|
}
|
|
474
452
|
|
|
@@ -543,7 +521,6 @@ function logPersistEpilogue({
|
|
|
543
521
|
* artifacts: {
|
|
544
522
|
* stories: Array<object>,
|
|
545
523
|
* techSpecContent?: string|null,
|
|
546
|
-
* planAcceptance?: string[]|null,
|
|
547
524
|
* planContextEnvelope?: object|null,
|
|
548
525
|
* },
|
|
549
526
|
* config?: object,
|
|
@@ -569,7 +546,6 @@ export async function runPlanPersist({
|
|
|
569
546
|
const {
|
|
570
547
|
stories: rawStories = null,
|
|
571
548
|
techSpecContent = null,
|
|
572
|
-
planAcceptance = null,
|
|
573
549
|
planContextEnvelope = null,
|
|
574
550
|
} = artifacts ?? {};
|
|
575
551
|
const {
|
|
@@ -623,21 +599,28 @@ export async function runPlanPersist({
|
|
|
623
599
|
epicId: opts.adoptEpicId ?? null,
|
|
624
600
|
});
|
|
625
601
|
|
|
626
|
-
//
|
|
602
|
+
// Inline Spec fold (Specs stay inline, never under docs/).
|
|
627
603
|
const seedContent = planContextEnvelope?.seed?.content ?? '';
|
|
628
|
-
const { stories: assembled } =
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
604
|
+
const { stories: assembled, warnings: supersedeWarnings } =
|
|
605
|
+
assemblePlanStories(rawStories, {
|
|
606
|
+
sharedSpec: techSpecContent,
|
|
607
|
+
sourceTicketIds,
|
|
608
|
+
// The seed this plan was authored from: an audit sweep's Single-plan seed
|
|
609
|
+
// carries the `audit-fingerprints` / `audit-semantic-keys` footers, and
|
|
610
|
+
// assembly copies them into the persisted Story bodies so the next sweep
|
|
611
|
+
// recognises what it already planned (Story #4877). Since Story #5045 this
|
|
612
|
+
// is the **fallback** — it is carried onto every Story that did not
|
|
613
|
+
// attribute its own `provenance`, which keeps an un-attributed plan exactly
|
|
614
|
+
// as recall-safe as it was. Empty for a `--tickets` run, a no-op there.
|
|
615
|
+
provenanceSource: seedContent,
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
// Story #5342: the source ids assembly assigned to the primary Story by
|
|
619
|
+
// default. They ride the same list every other dry-run warning does, so a
|
|
620
|
+
// default-assigned supersede is visible in the output the operator already
|
|
621
|
+
// reads rather than only in the tracker afterwards.
|
|
622
|
+
warnings.push(...supersedeWarnings);
|
|
623
|
+
logWarnings(supersedeWarnings);
|
|
641
624
|
|
|
642
625
|
// Stamp the `audit::*` labels the dedup corpus is listed by. Without them a
|
|
643
626
|
// Story this path files is absent from the pool an indexed sweep matches
|
|
@@ -654,6 +637,31 @@ export async function runPlanPersist({
|
|
|
654
637
|
rawFindings: validated.findings,
|
|
655
638
|
});
|
|
656
639
|
|
|
640
|
+
// Story #5332 — the split gate, ahead of the first create. `buildWaveTable`
|
|
641
|
+
// needs only `{slug, title, depends_on}` and the prediction needs only the
|
|
642
|
+
// assembled bodies, so both can run before anything is written; they used
|
|
643
|
+
// to sit *after* creation, which is why the collision table could only ever
|
|
644
|
+
// be a receipt for a plan already live. The same computed value is handed
|
|
645
|
+
// to the summary rendering below rather than recomputed, so the refusal and
|
|
646
|
+
// the receipt can never disagree.
|
|
647
|
+
const waveTable = buildWaveTable(
|
|
648
|
+
stories.map((s) => ({
|
|
649
|
+
slug: s.slug,
|
|
650
|
+
title: s.title,
|
|
651
|
+
depends_on: s.depends_on,
|
|
652
|
+
})),
|
|
653
|
+
);
|
|
654
|
+
|
|
655
|
+
// Story #5265: the table says which Stories share an order; the dispatcher
|
|
656
|
+
// decides which of those actually run together. Run its own predicate over
|
|
657
|
+
// the assembled bodies — the exact artifact the tick will read back off
|
|
658
|
+
// GitHub — so an N>1 draft it would serialize is refused here, and the
|
|
659
|
+
// summary comment names the serialisation instead of promising parallelism
|
|
660
|
+
// the next tick refuses. One enumeration feeds both.
|
|
661
|
+
const waveCollisions = assertNoWaveCollisions(waveTable, stories, {
|
|
662
|
+
tempRoot: getPaths(config).tempRoot,
|
|
663
|
+
});
|
|
664
|
+
|
|
657
665
|
const { created, planRunLabel, planRunLabelApplied } =
|
|
658
666
|
await createStoryIssues({
|
|
659
667
|
provider,
|
|
@@ -666,24 +674,6 @@ export async function runPlanPersist({
|
|
|
666
674
|
recordAuditFilings({ stories, created, tickets: rawStories, dryRun });
|
|
667
675
|
|
|
668
676
|
const primary = created[0];
|
|
669
|
-
const waveTable = buildWaveTable(
|
|
670
|
-
stories.map((s) => ({
|
|
671
|
-
slug: s.slug,
|
|
672
|
-
title: s.title,
|
|
673
|
-
depends_on: s.depends_on,
|
|
674
|
-
})),
|
|
675
|
-
);
|
|
676
|
-
|
|
677
|
-
// Story #5265: the table says which Stories share an order; the dispatcher
|
|
678
|
-
// decides which of those actually run together, and it decides on the
|
|
679
|
-
// evidence-widened footprint. Run its own predicate over the assembled
|
|
680
|
-
// bodies — the exact artifact the tick will read back off GitHub — so the
|
|
681
|
-
// comment names the serialisation instead of promising parallelism the
|
|
682
|
-
// next tick refuses. `tempRoot` is threaded for the same reason the tick
|
|
683
|
-
// threads it: the scrape must ignore this project's scratch root.
|
|
684
|
-
const waveCollisions = predictWaveSerialisation(waveTable, stories, {
|
|
685
|
-
tempRoot: getPaths(config).tempRoot,
|
|
686
|
-
});
|
|
687
677
|
|
|
688
678
|
// Story #4541: `readPlanMetrics` is declared `(epicId, config)` but was
|
|
689
679
|
// called with `config` first, so the ledger path resolver received the
|
|
@@ -718,12 +708,7 @@ export async function runPlanPersist({
|
|
|
718
708
|
});
|
|
719
709
|
|
|
720
710
|
if (!dryRun) {
|
|
721
|
-
await persistStoryArtifacts({
|
|
722
|
-
provider,
|
|
723
|
-
created,
|
|
724
|
-
primary,
|
|
725
|
-
summaryBody,
|
|
726
|
-
});
|
|
711
|
+
await persistStoryArtifacts({ provider, created, summaryBody });
|
|
727
712
|
}
|
|
728
713
|
|
|
729
714
|
// Story #5139 — the container Epic is created LAST among the writes: its
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Under the Story collapse (`docs/roadmap.md` § Stage 3), `/mandrel-plan` persists
|
|
5
5
|
* zero-or-more Story issues directly — no Epic parent, no reconciler tree,
|
|
6
6
|
* no `deliveryShape` mode matrix. Default is **one Story**; N>1 is gated by
|
|
7
|
-
* the
|
|
7
|
+
* the supersede partition check.
|
|
8
8
|
*
|
|
9
9
|
* Each Story body is the single executable document: Tech Spec stays inline
|
|
10
10
|
* under `## Spec`, at whatever length the work needs (Story #5312 deleted
|
|
@@ -34,14 +34,13 @@ import {
|
|
|
34
34
|
concurrentMap,
|
|
35
35
|
FANOUT_CONCURRENCY,
|
|
36
36
|
} from '../../util/concurrent-map.js';
|
|
37
|
-
import { assertAcceptancePartition } from '../split-policy-validator.js';
|
|
38
37
|
import {
|
|
39
38
|
externalDependencyId,
|
|
40
39
|
isExternalDependencyRef,
|
|
41
40
|
} from './external-deps.js';
|
|
42
41
|
import {
|
|
43
|
-
assertSupersedePartition,
|
|
44
42
|
normalizeSupersedes,
|
|
43
|
+
resolveSupersedePartition,
|
|
45
44
|
} from './supersede-ops.js';
|
|
46
45
|
|
|
47
46
|
/**
|
|
@@ -158,7 +157,7 @@ const PLAN_FINGERPRINT_LENGTH = 16;
|
|
|
158
157
|
*
|
|
159
158
|
* 1. A later, unrelated plan that reused a slug **and** title adopted the
|
|
160
159
|
* stale open Story — never rewriting its body or Spec, and landing this
|
|
161
|
-
* run's
|
|
160
|
+
* run's plan comments, ready-flip, and supersede comments on the wrong
|
|
162
161
|
* issue.
|
|
163
162
|
* 2. A legitimate resume after the operator edited `stories.json` adopted
|
|
164
163
|
* the pre-edit Story and kept its stale body, discarding the edit.
|
|
@@ -514,17 +513,30 @@ function assertSharedSpecAllowed(tickets, sharedSpec) {
|
|
|
514
513
|
|
|
515
514
|
/**
|
|
516
515
|
* Assemble markdown bodies for every Story: normalize → fold spec →
|
|
517
|
-
*
|
|
516
|
+
* order by dependency → resolveSupersedePartition → serialize.
|
|
518
517
|
*
|
|
519
|
-
*
|
|
520
|
-
*
|
|
518
|
+
* **The dependency sort runs here, once** (Story #5361). It used to run again
|
|
519
|
+
* inside the create loop, which meant "the primary Story" was derived twice
|
|
520
|
+
* from two different orderings: supersede assignment took the first *authored*
|
|
521
|
+
* Story, while the plan summary took the first *created*
|
|
522
|
+
* one. A draft whose authoring order differed from its dependency order made
|
|
523
|
+
* the `superseded-by` comment name a different Story from the summary. One
|
|
524
|
+
* sort, one ordered list threaded on to every consumer, so `stories[0]` is the
|
|
525
|
+
* only primary there is. The sort also refuses an unknown sibling or a cycle,
|
|
526
|
+
* which now fails the write-free pass rather than the first create.
|
|
527
|
+
*
|
|
528
|
+
* The partition pass runs **before** any GitHub write so a mis-authored
|
|
529
|
+
* plan never leaves Stories live against an inconsistent tracker. Story #5332
|
|
530
|
+
* retired the acceptance partition that used to run beside it: it refused
|
|
531
|
+
* only byte-identical acceptance text across siblings, and the split gate is
|
|
532
|
+
* now `assertNoWaveCollisions` in `run-plan-persist.js`, ahead of the first
|
|
533
|
+
* create.
|
|
521
534
|
*
|
|
522
535
|
* @param {object[]} tickets
|
|
523
536
|
* @param {object} [opts]
|
|
524
537
|
* @param {string|null} [opts.sharedSpec]
|
|
525
|
-
* @param {string[]} [opts.planAcceptance]
|
|
526
538
|
* @param {number[]} [opts.sourceTicketIds] Ids passed to `/mandrel-plan --tickets`.
|
|
527
|
-
* @returns {{ stories: Array<{ slug: string, title: string, body: string, acceptance: string[], depends_on: string[], supersedes: Array<{ id: number, note: string|null }> }
|
|
539
|
+
* @returns {{ stories: Array<{ slug: string, title: string, body: string, acceptance: string[], depends_on: string[], supersedes: Array<{ id: number, note: string|null }> }>, warnings: string[] }}
|
|
528
540
|
*/
|
|
529
541
|
export function assemblePlanStories(tickets, opts = {}) {
|
|
530
542
|
if (!Array.isArray(tickets) || tickets.length === 0) {
|
|
@@ -535,16 +547,16 @@ export function assemblePlanStories(tickets, opts = {}) {
|
|
|
535
547
|
|
|
536
548
|
assertSharedSpecAllowed(tickets, opts.sharedSpec);
|
|
537
549
|
|
|
538
|
-
const stories =
|
|
539
|
-
(ticket) => assembleOnePlanStory(ticket, opts).story,
|
|
550
|
+
const stories = orderStoriesByDependencies(
|
|
551
|
+
tickets.map((ticket) => assembleOnePlanStory(ticket, opts).story),
|
|
540
552
|
);
|
|
541
553
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
554
|
+
const warnings = resolveSupersedePartition(
|
|
555
|
+
stories,
|
|
556
|
+
opts.sourceTicketIds ?? [],
|
|
557
|
+
);
|
|
546
558
|
|
|
547
|
-
return { stories };
|
|
559
|
+
return { stories, warnings };
|
|
548
560
|
}
|
|
549
561
|
|
|
550
562
|
function orderStoriesByDependencies(stories) {
|
|
@@ -902,12 +914,13 @@ async function ensurePersistLabel({
|
|
|
902
914
|
* Create Story issues via `provider.createIssue`, resumably.
|
|
903
915
|
*
|
|
904
916
|
* **Stories are born without `agent::ready`** (Story #4541). They used to
|
|
905
|
-
* carry it in the creating POST while the `story-plan-state`
|
|
917
|
+
* carry it in the creating POST while the `story-plan-state` comment was
|
|
906
918
|
* upserted afterwards, so anything that picked a Story up inside that window —
|
|
907
|
-
* or after a comment failure aborted the loop —
|
|
908
|
-
*
|
|
909
|
-
* `
|
|
910
|
-
*
|
|
919
|
+
* or after a comment failure aborted the loop — found a ready Story carrying
|
|
920
|
+
* none of the operator's delivery instructions. Creation now applies
|
|
921
|
+
* `type::story` plus the sanitized authored labels only; `markStoriesReady`
|
|
922
|
+
* performs the flip as the terminal step, once every plan comment is on the
|
|
923
|
+
* ticket.
|
|
911
924
|
*
|
|
912
925
|
* **The loop is resumable, and adoption is content-keyed.** Each body carries
|
|
913
926
|
* a plan-fingerprint marker, and the open `type::story` backlog is indexed by
|
|
@@ -1003,7 +1016,10 @@ export async function createStoryIssues({ provider, stories, opts = {} }) {
|
|
|
1003
1016
|
const created = [];
|
|
1004
1017
|
const idBySlug = new Map();
|
|
1005
1018
|
|
|
1006
|
-
|
|
1019
|
+
// Already in dependency order: `assemblePlanStories` sorted once, and
|
|
1020
|
+
// sorting again here is what gave the run a second, disagreeing notion of
|
|
1021
|
+
// which Story is primary (Story #5361).
|
|
1022
|
+
for (const story of list) {
|
|
1007
1023
|
const already = byFingerprint.get(story.fingerprint);
|
|
1008
1024
|
if (!already) warnOnDivergentSameTitleStory(story, idsByTitle);
|
|
1009
1025
|
if (already) {
|
|
@@ -1088,8 +1104,8 @@ export async function createStoryIssues({ provider, stories, opts = {} }) {
|
|
|
1088
1104
|
* (Story #4541).
|
|
1089
1105
|
*
|
|
1090
1106
|
* This is what makes `agent::ready` *mean* "fully persisted": by the time it
|
|
1091
|
-
* lands, the Story's `story-plan-state`
|
|
1092
|
-
*
|
|
1107
|
+
* lands, the Story's `story-plan-state` comment is already on the ticket, so a
|
|
1108
|
+
* `/mandrel-deliver` that picks it up always has the plan summary beside it.
|
|
1093
1109
|
*
|
|
1094
1110
|
* Fails closed: an un-flipped Story is invisible to `/mandrel-deliver`, which is the
|
|
1095
1111
|
* safe direction — the operator is told exactly which ids need the label.
|
|
@@ -1145,7 +1161,7 @@ export async function markStoriesReady({ provider, created }) {
|
|
|
1145
1161
|
if (failed.length > 0) {
|
|
1146
1162
|
throw new Error(
|
|
1147
1163
|
`[plan-persist] ${failed.length} Story(ies) were created with their ` +
|
|
1148
|
-
'
|
|
1164
|
+
'plan comments but could not be flipped to agent::ready:\n' +
|
|
1149
1165
|
`${failed.map((f) => ` - ${f}`).join('\n')}\n` +
|
|
1150
1166
|
'They are invisible to /mandrel-deliver until the label lands. Re-run persist ' +
|
|
1151
1167
|
'(it resumes rather than duplicating) or add the label by hand.',
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* summary.js — plan-persist terminal summary (v2 Stage 3).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Renders the persist receipts — the created Story set, whether the operator
|
|
5
|
+
* forced a review stop, the `depends_on` ordering table and the exact deliver
|
|
6
|
+
* command. Story #5343 moved that rendering onto the **`story-plan-state`**
|
|
7
|
+
* marker every created Story already carries: it used to be a second,
|
|
8
|
+
* primary-Story-only `plan-summary` comment, which cost one more write per
|
|
9
|
+
* plan and split the operator's reading between two markers. Story #5367
|
|
10
|
+
* deleted the machine checkpoint that shared the marker with it, so this
|
|
11
|
+
* rendering is now the whole body of the one comment persist posts.
|
|
8
12
|
*
|
|
9
13
|
* Story #4542 removed the risk / review-routing line: no risk level, gate
|
|
10
14
|
* decision, or acceptance disposition is computed at plan time any more, so
|
|
@@ -16,11 +20,6 @@
|
|
|
16
20
|
import { computeStoryWaves } from '../dependency-analyzer.js';
|
|
17
21
|
import { renderPredictedSerialisationLines } from './wave-serialisation.js';
|
|
18
22
|
|
|
19
|
-
/**
|
|
20
|
-
* Structured-comment type for the persist summary.
|
|
21
|
-
*/
|
|
22
|
-
export const PLAN_SUMMARY_COMMENT_TYPE = 'plan-summary';
|
|
23
|
-
|
|
24
23
|
/**
|
|
25
24
|
* Compute the dry-run wave assignment for a validated ticket set.
|
|
26
25
|
*
|
|
@@ -114,7 +113,8 @@ function renderSharedEditorLines(conflictFindings) {
|
|
|
114
113
|
}
|
|
115
114
|
|
|
116
115
|
/**
|
|
117
|
-
* Build the `plan-
|
|
116
|
+
* Build the body of each Story's `story-plan-state` comment (Story #5343;
|
|
117
|
+
* Story #5367 made it the whole body).
|
|
118
118
|
*
|
|
119
119
|
* @param {object} input
|
|
120
120
|
* @returns {string}
|
|
@@ -169,7 +169,7 @@ export function buildPlanSummaryCommentBody({
|
|
|
169
169
|
: '/mandrel-deliver <storyId> [<storyId> ...]';
|
|
170
170
|
|
|
171
171
|
return [
|
|
172
|
-
|
|
172
|
+
`#### 📋 Plan Summary — Story #${epicId} is \`agent::ready\``,
|
|
173
173
|
'',
|
|
174
174
|
`- ${ticketCount} Story ticket(s) persisted: ${storyList}.`,
|
|
175
175
|
...reviewLines,
|