mandrel 2.56.0 → 2.57.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 -34
- 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/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/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/plan-context.js +181 -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/changes-repair.js +300 -0
- package/.agents/scripts/lib/orchestration/plan-persist/persist-helpers.js +131 -168
- package/.agents/scripts/lib/orchestration/plan-persist/run-plan-persist.js +118 -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 +30 -139
- package/.agents/scripts/lib/orchestration/planning/memory-pool-advisory.js +61 -223
- package/.agents/scripts/lib/orchestration/single-story-close/phases/close-validation.js +5 -0
- package/.agents/scripts/lib/orchestration/single-story-close/phases/pre-gate-steps.js +46 -16
- 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 +17 -237
- package/.agents/scripts/lib/templates/decomposer-prompts.js +84 -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 +266 -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 +41 -57
- 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 +37 -58
- package/.agents/workflows/helpers/deliver-story.md +9 -13
- package/.agents/workflows/helpers/plan-reference.md +132 -219
- package/.agents/workflows/mandrel-plan.md +27 -40
- package/.agents/workflows/memory-consolidate.md +9 -13
- package/docs/CHANGELOG.md +23 -0
- 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,97 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fan-out / soft-conflict gates used by plan-persist (extracted from the
|
|
3
|
-
* retired epic-plan-decompose persist phase in Stage 5).
|
|
4
|
-
*
|
|
5
|
-
* @module lib/orchestration/plan-persist/fan-out-gate
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { Logger } from '../../Logger.js';
|
|
9
|
-
import {
|
|
10
|
-
CONFLICT_KINDS,
|
|
11
|
-
renderFanOutEvidence,
|
|
12
|
-
renderFanOutRemedy,
|
|
13
|
-
renderHardConflictError,
|
|
14
|
-
} from '../ticket-validator-conflicts.js';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @param {object[]} findings
|
|
18
|
-
* @param {boolean} allowLargeFanOut
|
|
19
|
-
* @param {string} [tag]
|
|
20
|
-
*/
|
|
21
|
-
export function enforceFanOutGate(
|
|
22
|
-
findings,
|
|
23
|
-
allowLargeFanOut,
|
|
24
|
-
tag = 'plan-persist',
|
|
25
|
-
) {
|
|
26
|
-
const fanOut = (findings ?? []).filter((f) => f.kind === 'fan-out-warning');
|
|
27
|
-
if (fanOut.length === 0) return;
|
|
28
|
-
if (allowLargeFanOut) {
|
|
29
|
-
for (const f of fanOut) {
|
|
30
|
-
Logger.warn(
|
|
31
|
-
`[${tag}] Persisting a large-fan-out deletion: ` +
|
|
32
|
-
`Story "${f.storySlug}" deletes "${f.path}" with ${f.callSiteCount} ` +
|
|
33
|
-
`importer(s) (threshold ${f.threshold}). Operator override --allow-large-fan-out.` +
|
|
34
|
-
renderFanOutEvidence(f),
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
const lines = fanOut
|
|
40
|
-
.map(
|
|
41
|
-
(f) =>
|
|
42
|
-
` - Story "${f.storySlug}" deletes "${f.path}" — ` +
|
|
43
|
-
`${f.callSiteCount} importer(s) (threshold ${f.threshold})` +
|
|
44
|
-
renderFanOutEvidence(f),
|
|
45
|
-
)
|
|
46
|
-
.join('\n');
|
|
47
|
-
// Each finding carries its own remedy — a rename-shaped deletion has no
|
|
48
|
-
// subsystems to split across, so a blanket "split it up" footer would be
|
|
49
|
-
// wrong advice for it (Story #4547).
|
|
50
|
-
const remedies = [...new Set(fanOut.map((f) => renderFanOutRemedy(f)))];
|
|
51
|
-
throw new Error(
|
|
52
|
-
`[${tag}] ${fanOut.length} Task(s) declare large-fan-out deletions:\n${lines}\n\n` +
|
|
53
|
-
remedies.join('\n\n'),
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Report every soft finding the validator produced, each under its own kind.
|
|
59
|
-
*
|
|
60
|
-
* Only the {@link CONFLICT_KINDS} are cross-Story conflicts. The advisory
|
|
61
|
-
* kinds — `spec-word-budget` and the sizing findings — are single-Story
|
|
62
|
-
* nudges, and announcing them as conflicts overstated them and taught readers
|
|
63
|
-
* to discount the whole channel (Story #4907). `fan-out-warning` is excluded
|
|
64
|
-
* throughout: {@link enforceFanOutGate} owns it and has already either thrown
|
|
65
|
-
* or logged the override.
|
|
66
|
-
*
|
|
67
|
-
* @param {object[]} findings
|
|
68
|
-
* @param {string} [tag]
|
|
69
|
-
*/
|
|
70
|
-
export function surfaceSoftConflictFindings(findings, tag = 'plan-persist') {
|
|
71
|
-
const soft = (findings ?? []).filter(
|
|
72
|
-
(f) => f?.severity === 'soft' && f?.kind !== 'fan-out-warning',
|
|
73
|
-
);
|
|
74
|
-
if (soft.length === 0) return;
|
|
75
|
-
const conflicts = soft.filter((f) => CONFLICT_KINDS.has(f?.kind));
|
|
76
|
-
const advisories = soft.filter((f) => !CONFLICT_KINDS.has(f?.kind));
|
|
77
|
-
if (conflicts.length > 0) {
|
|
78
|
-
Logger.warn(
|
|
79
|
-
`[${tag}] ${conflicts.length} soft cross-Story conflict finding(s) — review before approving the plan:`,
|
|
80
|
-
);
|
|
81
|
-
for (const finding of conflicts) {
|
|
82
|
-
Logger.warn(
|
|
83
|
-
`[${tag}] soft conflict: ${renderHardConflictError(finding)}`,
|
|
84
|
-
);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (advisories.length > 0) {
|
|
88
|
-
Logger.warn(
|
|
89
|
-
`[${tag}] ${advisories.length} advisory finding(s) — the persist proceeds:`,
|
|
90
|
-
);
|
|
91
|
-
for (const finding of advisories) {
|
|
92
|
-
Logger.warn(
|
|
93
|
-
`[${tag}] advisory (${finding.kind}): ${renderHardConflictError(finding)}`,
|
|
94
|
-
);
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* decomposer-context.js — system-prompt builder for the `/mandrel-plan` authoring
|
|
3
|
-
* pass (Story #2466; v2 keeps the prompt helper, drops the Epic-fetch
|
|
4
|
-
* decomposition envelope).
|
|
5
|
-
*
|
|
6
|
-
* `buildDecomposerSystemPrompt` is the live surface consumed by
|
|
7
|
-
* `plan-context.js#buildSystemPrompts`. The retired
|
|
8
|
-
* `buildDecompositionContext` Epic-fetch path lived here until the
|
|
9
|
-
* Story-only cutover removed every caller.
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { renderDecomposerSystemPrompt } from '../../templates/decomposer-prompts.js';
|
|
13
|
-
|
|
14
|
-
export function buildDecomposerSystemPrompt(
|
|
15
|
-
heuristics = [],
|
|
16
|
-
{ maxTickets } = {},
|
|
17
|
-
) {
|
|
18
|
-
const base = renderDecomposerSystemPrompt({
|
|
19
|
-
maxTickets,
|
|
20
|
-
});
|
|
21
|
-
const heuristicsStr =
|
|
22
|
-
heuristics.length > 0
|
|
23
|
-
? `### RISK HEURISTICS (planning metadata if any apply):\n- ${heuristics.join('\n- ')}`
|
|
24
|
-
: '';
|
|
25
|
-
return `${base}${heuristicsStr ? `\n\n${heuristicsStr}` : ''}`;
|
|
26
|
-
}
|
|
@@ -1,89 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* lib/orchestration/spec-budget.js — the soft `## Spec` word-budget pass
|
|
3
|
-
* (Story #4723), extracted from `ticket-validator.js` so the advisory
|
|
4
|
-
* length nudge lives beside neither the hard validators nor their error
|
|
5
|
-
* channel: everything here is `'soft'` by construction and can never fail
|
|
6
|
-
* a persist.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { parse as parseStoryBody } from '../story-body/story-body.js';
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Warn threshold (words) for a Story's inline `## Spec` (Story #4723,
|
|
13
|
-
* retuned by Story #4907). The budget carries **two distinct numbers**, and
|
|
14
|
-
* both belong in any surface that documents it:
|
|
15
|
-
*
|
|
16
|
-
* - **~250 words — the authoring target.** The #4707 contract-level-prose
|
|
17
|
-
* aim: interfaces, invariants, and load-bearing constraints, not
|
|
18
|
-
* route-by-route behavior narration. It lives in the story-author template
|
|
19
|
-
* placeholder (`plan-context.js`), which is what an author actually reads.
|
|
20
|
-
* - **350 words — this warn threshold.** Deliberately slack against the
|
|
21
|
-
* target so the warning marks a real outlier rather than ordinary
|
|
22
|
-
* authoring variance. Measured over the 45 most recent Stories carrying a
|
|
23
|
-
* Spec, a 250-word threshold fired on 22% of *accepted* work (median 233,
|
|
24
|
-
* p75 249) — a warning that common trains its readers to ignore it.
|
|
25
|
-
*
|
|
26
|
-
* Distinct again from the hard ~1500-token fail-closed ceiling in
|
|
27
|
-
* `spec-spill.js`: this budget only warns; it never fails the persist.
|
|
28
|
-
*/
|
|
29
|
-
export const SPEC_SOFT_WORD_BUDGET = 350;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Resolve a Story's Spec prose across both authoring shapes: the canonical
|
|
33
|
-
* serialized string body (parsed; `## Spec` text block) and the
|
|
34
|
-
* pre-serialize structured object body (`body.spec`). Returns `''` when
|
|
35
|
-
* absent — or when a string body does not parse: this pass is advisory, so
|
|
36
|
-
* an unreadable body contributes no finding here and is left to the hard
|
|
37
|
-
* parse gate (`assertStoryBodiesParse`) to reject.
|
|
38
|
-
*
|
|
39
|
-
* @param {object} story
|
|
40
|
-
* @returns {string}
|
|
41
|
-
*/
|
|
42
|
-
function resolveSpecText(story) {
|
|
43
|
-
const body = story?.body;
|
|
44
|
-
if (typeof body === 'string' && body.trim().length > 0) {
|
|
45
|
-
let spec;
|
|
46
|
-
try {
|
|
47
|
-
spec = parseStoryBody(body).body.spec;
|
|
48
|
-
} catch {
|
|
49
|
-
return '';
|
|
50
|
-
}
|
|
51
|
-
return typeof spec === 'string' ? spec : '';
|
|
52
|
-
}
|
|
53
|
-
if (body !== null && typeof body === 'object') {
|
|
54
|
-
return typeof body?.spec === 'string' ? body.spec : '';
|
|
55
|
-
}
|
|
56
|
-
return '';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Advisory `## Spec` length pass (Story #4723). Emits one `'soft'` finding
|
|
61
|
-
* per Story whose Spec prose exceeds {@link SPEC_SOFT_WORD_BUDGET} words,
|
|
62
|
-
* nudging the author toward contract-level prose (#4707). Soft only — the
|
|
63
|
-
* findings never reach the validator's `errors[]` channel, so an
|
|
64
|
-
* over-budget Spec never fails the persist.
|
|
65
|
-
*
|
|
66
|
-
* @param {{ stories: object[] }} opts
|
|
67
|
-
* @returns {object[]} Zero or more `spec-word-budget` findings.
|
|
68
|
-
*/
|
|
69
|
-
export function computeSpecBudgetFindings({ stories }) {
|
|
70
|
-
const findings = [];
|
|
71
|
-
for (const story of stories ?? []) {
|
|
72
|
-
const words = resolveSpecText(story).split(/\s+/).filter(Boolean).length;
|
|
73
|
-
if (words <= SPEC_SOFT_WORD_BUDGET) continue;
|
|
74
|
-
findings.push({
|
|
75
|
-
kind: 'spec-word-budget',
|
|
76
|
-
severity: 'soft',
|
|
77
|
-
ticketSlug: story.slug ?? '<unknown>',
|
|
78
|
-
words,
|
|
79
|
-
budget: SPEC_SOFT_WORD_BUDGET,
|
|
80
|
-
message:
|
|
81
|
-
`Story "${story.slug ?? '<unknown>'}" ## Spec is ~${words} words ` +
|
|
82
|
-
`(soft budget ${SPEC_SOFT_WORD_BUDGET}). Prefer contract-level prose ` +
|
|
83
|
-
'(interfaces, invariants, load-bearing constraints with their why) ' +
|
|
84
|
-
'over per-file behavior narration — advisory only; the persist ' +
|
|
85
|
-
'proceeds.',
|
|
86
|
-
});
|
|
87
|
-
}
|
|
88
|
-
return findings;
|
|
89
|
-
}
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* v2 Story `## Spec` budget gate.
|
|
3
|
-
*
|
|
4
|
-
* The Story body is the single executable document: Tech Spec prose stays
|
|
5
|
-
* inline under `## Spec`. There is no spill-to-`docs/` path — an over-budget
|
|
6
|
-
* Spec is treated as a sizing smell (the Story should be split or the Spec
|
|
7
|
-
* tightened), not as a reason to write temporary product docs.
|
|
8
|
-
*
|
|
9
|
-
* This module also **owns** the §2 FinOps token estimator
|
|
10
|
-
* ({@link estimateTokens}, ~4 chars/token) — the one shared approximation every
|
|
11
|
-
* fixed framework ceiling speaks in (the Spec budget below, the plan-time
|
|
12
|
-
* sizing ceilings in `ticket-validator-sizing.js`, and the audit checklist
|
|
13
|
-
* threading budget). It used to live in a `context-envelope.js` SDK whose
|
|
14
|
-
* remaining surface had no live caller; the estimator is all that survived.
|
|
15
|
-
*
|
|
16
|
-
* @module lib/orchestration/spec-spill
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Rough token estimate: ~4 characters per token. Deliberately cheap and
|
|
21
|
-
* deterministic — every ceiling that quotes "tokens" is quoting this number,
|
|
22
|
-
* so the approximation matters far less than every caller sharing one.
|
|
23
|
-
*
|
|
24
|
-
* @param {string} text
|
|
25
|
-
* @returns {number}
|
|
26
|
-
*/
|
|
27
|
-
export function estimateTokens(text) {
|
|
28
|
-
return Math.ceil(String(text ?? '').length / 4);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Soft budget (estimated tokens) for an inline `## Spec`. ~1500 tokens ≈ 6KB —
|
|
33
|
-
* enough for a real approach section, well under issue-body bloat.
|
|
34
|
-
*/
|
|
35
|
-
export const DEFAULT_SPEC_BODY_TOKEN_BUDGET = 1500;
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* @typedef {object} SpecBudgetResult
|
|
39
|
-
* @property {number} estimatedTokens
|
|
40
|
-
* @property {string} content
|
|
41
|
-
*/
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Keep Spec inline when it fits; throw when it exceeds the budget so the
|
|
45
|
-
* planner splits or tightens instead of writing docs/.
|
|
46
|
-
*
|
|
47
|
-
* @param {object} args
|
|
48
|
-
* @param {string} args.storyId Story slug/id for error context.
|
|
49
|
-
* @param {string} args.spec Folded Tech Spec markdown.
|
|
50
|
-
* @param {object} [opts]
|
|
51
|
-
* @param {number} [opts.tokenBudget=DEFAULT_SPEC_BODY_TOKEN_BUDGET]
|
|
52
|
-
* @returns {SpecBudgetResult}
|
|
53
|
-
*/
|
|
54
|
-
export function assertSpecWithinBudget({ storyId, spec }, opts = {}) {
|
|
55
|
-
const { tokenBudget = DEFAULT_SPEC_BODY_TOKEN_BUDGET } = opts;
|
|
56
|
-
const content = typeof spec === 'string' ? spec : '';
|
|
57
|
-
const estimatedTokens = estimateTokens(content);
|
|
58
|
-
|
|
59
|
-
if (estimatedTokens <= tokenBudget) {
|
|
60
|
-
return { estimatedTokens, content };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const label =
|
|
64
|
-
typeof storyId === 'string' && storyId.trim() !== ''
|
|
65
|
-
? storyId.trim()
|
|
66
|
-
: 'unknown';
|
|
67
|
-
|
|
68
|
-
throw new Error(
|
|
69
|
-
`[plan-persist] Story "${label}" ## Spec is ~${estimatedTokens} tokens ` +
|
|
70
|
-
`(budget ${tokenBudget}). An over-budget Spec usually means the Story ` +
|
|
71
|
-
`is too large — split it, or tighten ## Spec so the Story body stays ` +
|
|
72
|
-
`the single executable document. Specs are never written to docs/.`,
|
|
73
|
-
);
|
|
74
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* verify-tier-repair.js — repair-before-judging for the `verify[]` tier suffix
|
|
3
|
-
* (Story #5005).
|
|
4
|
-
*
|
|
5
|
-
* The tier suffix (`… (unit)`) is a deterministic, mechanically-derivable
|
|
6
|
-
* formality. `task-body-validator.js` already computed the corrected entry in
|
|
7
|
-
* order to print it as a "Suggested fix" — then rejected the plan anyway and
|
|
8
|
-
* charged the author a full re-drafting round to paste that exact string back.
|
|
9
|
-
* This module applies the inference the validator already trusts, so the hard
|
|
10
|
-
* error is reserved for the entries only the author can resolve.
|
|
11
|
-
*
|
|
12
|
-
* It lives beside the validator rather than inside it because the validator's
|
|
13
|
-
* job is to *judge*: mixing a mutating repair pass into a module of pure
|
|
14
|
-
* collectors muddies both. `persist-helpers.js#validateTickets` calls this
|
|
15
|
-
* first, then `validateTaskBodies`.
|
|
16
|
-
*
|
|
17
|
-
* @module lib/orchestration/verify-tier-repair
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { suggestVerifyFix } from '../story-body/body-format-lints.js';
|
|
21
|
-
import {
|
|
22
|
-
parse as parseStoryBody,
|
|
23
|
-
serialize as serializeStoryBody,
|
|
24
|
-
} from '../story-body/story-body.js';
|
|
25
|
-
import { VERIFY_TIER_VALUES } from './task-body-validator.js';
|
|
26
|
-
|
|
27
|
-
/** A parenthesised tier drawn from the canonical vocabulary, at end of entry. */
|
|
28
|
-
const VERIFY_TIER_RE = new RegExp(
|
|
29
|
-
`\\((?:${VERIFY_TIER_VALUES.join('|')})\\)\\s*$`,
|
|
30
|
-
);
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Rewrite one `verify[]` list, appending the inferable testing tier to any
|
|
34
|
-
* entry missing one. Returns `null` when nothing changed, so callers can keep
|
|
35
|
-
* an already-compliant list byte-identical (and skip re-serializing the body
|
|
36
|
-
* it came from).
|
|
37
|
-
*
|
|
38
|
-
* @param {unknown} rawVerify
|
|
39
|
-
* @returns {string[]|null} The corrected list, or `null` when no fix applied.
|
|
40
|
-
*/
|
|
41
|
-
function repairVerifyList(rawVerify) {
|
|
42
|
-
if (!Array.isArray(rawVerify)) return null;
|
|
43
|
-
let changed = false;
|
|
44
|
-
const next = rawVerify.map((entry) => {
|
|
45
|
-
if (typeof entry !== 'string') return entry;
|
|
46
|
-
if (entry.startsWith('manual:') || VERIFY_TIER_RE.test(entry)) return entry;
|
|
47
|
-
const fix = suggestVerifyFix(entry);
|
|
48
|
-
if (fix === null) return entry;
|
|
49
|
-
changed = true;
|
|
50
|
-
return fix;
|
|
51
|
-
});
|
|
52
|
-
return changed ? next : null;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Repair the `## Verify` section of a serialized (string) body, in place on
|
|
57
|
-
* the ticket. Only re-serializes when a fix actually applied, so a compliant
|
|
58
|
-
* body is never round-tripped through `parse → serialize`.
|
|
59
|
-
*
|
|
60
|
-
* Leaving the body untouched while the top level was repaired would be worse
|
|
61
|
-
* than not repairing at all: `syncContractFieldFromTopLevel` fails closed on a
|
|
62
|
-
* body section that disagrees with its top-level array, so both sides must be
|
|
63
|
-
* repaired by the same rule or neither.
|
|
64
|
-
*
|
|
65
|
-
* @param {object} ticket
|
|
66
|
-
*/
|
|
67
|
-
function repairStringBodyVerify(ticket) {
|
|
68
|
-
let parsed;
|
|
69
|
-
try {
|
|
70
|
-
parsed = parseStoryBody(ticket.body).body;
|
|
71
|
-
} catch {
|
|
72
|
-
// An unparseable body is already a hard error in `validateTaskBodyShape`;
|
|
73
|
-
// there is nothing to repair and nothing to report twice.
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const repaired = repairVerifyList(parsed.verify);
|
|
77
|
-
if (repaired === null) return;
|
|
78
|
-
ticket.body = serializeStoryBody({ ...parsed, verify: repaired });
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* Auto-append the testing tier to `verify[]` entries that omit one, wherever
|
|
83
|
-
* `suggestVerifyFix` can infer it from the command. An entry whose tier cannot
|
|
84
|
-
* be inferred is left untouched and still hard-errors downstream.
|
|
85
|
-
*
|
|
86
|
-
* Mutates `tickets` in place (the persist pipeline threads this same array on
|
|
87
|
-
* to assembly) and returns it. Already-compliant tickets are untouched, so
|
|
88
|
-
* their persisted output stays byte-identical. Total — a non-array argument
|
|
89
|
-
* and non-Story tickets are no-ops.
|
|
90
|
-
*
|
|
91
|
-
* @param {object[]} tickets
|
|
92
|
-
* @returns {object[]} `tickets`
|
|
93
|
-
*/
|
|
94
|
-
export function normalizeVerifyTiers(tickets) {
|
|
95
|
-
for (const ticket of Array.isArray(tickets) ? tickets : []) {
|
|
96
|
-
if (!ticket || ticket.type !== 'story' || ticket.body == null) continue;
|
|
97
|
-
const topLevel = repairVerifyList(ticket.verify);
|
|
98
|
-
if (topLevel !== null) ticket.verify = topLevel;
|
|
99
|
-
if (typeof ticket.body === 'string') {
|
|
100
|
-
repairStringBodyVerify(ticket);
|
|
101
|
-
continue;
|
|
102
|
-
}
|
|
103
|
-
const bodyLevel = repairVerifyList(ticket.body.verify);
|
|
104
|
-
if (bodyLevel !== null) ticket.body.verify = bodyLevel;
|
|
105
|
-
}
|
|
106
|
-
return tickets;
|
|
107
|
-
}
|