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
|
@@ -23,22 +23,24 @@ import {
|
|
|
23
23
|
hasWebSurface,
|
|
24
24
|
matchesAnyFilePattern,
|
|
25
25
|
} from '../audit-suite/selector.js';
|
|
26
|
-
import { getLimits } from '../config-resolver.js';
|
|
27
26
|
import { findSimilarOpenStories } from '../duplicate-search.js';
|
|
28
27
|
import { Logger } from '../Logger.js';
|
|
29
28
|
import { parse as parseStoryBody } from '../story-body/story-body.js';
|
|
29
|
+
import {
|
|
30
|
+
renderStoryAuthorCore,
|
|
31
|
+
renderStorySplitRules,
|
|
32
|
+
ticketsModePromptField,
|
|
33
|
+
} from '../templates/decomposer-prompts.js';
|
|
30
34
|
import {
|
|
31
35
|
renderAcceptanceSpecSystemPrompt,
|
|
32
36
|
renderTechSpecSystemPrompt,
|
|
33
37
|
} from '../templates/spec-author-prompts.js';
|
|
34
38
|
import { concurrentMap, FANOUT_CONCURRENCY } from '../util/concurrent-map.js';
|
|
35
39
|
import { buildComplexitySignals } from './complexity-gate.js';
|
|
36
|
-
import { parseDeliverySlicingTable } from './consolidation-precondition.js';
|
|
37
40
|
import { findDependencyCandidates } from './dependency-candidates.js';
|
|
38
41
|
import { buildDocsDigest } from './docs-digest.js';
|
|
39
42
|
import { findOpenEpicCandidates } from './epic-candidates.js';
|
|
40
43
|
import { buildAuthoringContext } from './planning/authoring-context.js';
|
|
41
|
-
import { buildDecomposerSystemPrompt } from './planning/decomposer-context.js';
|
|
42
44
|
|
|
43
45
|
/**
|
|
44
46
|
* Envelope byte ceiling (regression guard for the design's named PR2 risk:
|
|
@@ -62,61 +64,108 @@ import { buildDecomposerSystemPrompt } from './planning/decomposer-context.js';
|
|
|
62
64
|
* so the seed remains the only field this ceiling leaves genuinely
|
|
63
65
|
* unbounded. 256 KB (~64K tokens at the ≈4-chars/token estimate) leaves
|
|
64
66
|
* roughly 2× headroom over the fixed-floor measurement above while staying
|
|
65
|
-
* well under the session budget.
|
|
66
|
-
*
|
|
67
|
-
* justification.
|
|
67
|
+
* well under the session budget. An envelope over it is truncated with a
|
|
68
|
+
* `truncated` note rather than refused (Story #5312) — raise the ceiling
|
|
69
|
+
* only with a measured justification.
|
|
68
70
|
*/
|
|
69
71
|
export const PLAN_CONTEXT_ENVELOPE_BYTE_CEILING = 256_000;
|
|
70
72
|
|
|
71
|
-
/**
|
|
72
|
-
const
|
|
73
|
+
/** Marker appended to a string field the cap had to cut. */
|
|
74
|
+
const TRUNCATION_MARKER =
|
|
75
|
+
'\n\n[… truncated by plan-context: PLAN_CONTEXT_ENVELOPE_BYTE_CEILING …]';
|
|
76
|
+
|
|
77
|
+
/** Bounded number of cap rounds — each round cuts the current largest field. */
|
|
78
|
+
const MAX_TRUNCATION_ROUNDS = 8;
|
|
73
79
|
|
|
74
80
|
/**
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* `bddScenarios` (repo-derived, not seed-derived), and "trim the seed" was a
|
|
80
|
-
* dead lever the operator had no way to act on. The remedy now follows the
|
|
81
|
-
* single largest field.
|
|
81
|
+
* Byte length of a JSON-serialised value.
|
|
82
|
+
*
|
|
83
|
+
* @param {unknown} value
|
|
84
|
+
* @returns {number}
|
|
82
85
|
*/
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
'Plan fewer --tickets source issues in one run — each source ticket body is carried verbatim.',
|
|
87
|
-
epic: 'Plan fewer --tickets source issues in one run, or re-plan with a shorter Epic body.',
|
|
88
|
-
bddScenarios:
|
|
89
|
-
"The project's .feature corpus is already capped near BDD_SCENARIOS_BYTE_BUDGET (lib/bdd-scenario-budget.js) — if this still dominates, another field is unusually small; check the full field breakdown.",
|
|
90
|
-
docsContext:
|
|
91
|
-
'Trim project.docsContextFiles — docsContext is a digest built from those files.',
|
|
92
|
-
systemPrompts:
|
|
93
|
-
'This field is a fixed framework prompt, not operator content — if it dominates, file a framework-gap issue rather than trying to trim it.',
|
|
94
|
-
});
|
|
86
|
+
function jsonBytes(value) {
|
|
87
|
+
return Buffer.byteLength(JSON.stringify(value) ?? '', 'utf-8');
|
|
88
|
+
}
|
|
95
89
|
|
|
96
|
-
|
|
97
|
-
|
|
90
|
+
/**
|
|
91
|
+
* Cut a string to fit `excess` fewer bytes, keeping a leading prefix and
|
|
92
|
+
* appending the truncation marker.
|
|
93
|
+
*
|
|
94
|
+
* @param {string} text
|
|
95
|
+
* @param {number} excess
|
|
96
|
+
* @returns {string}
|
|
97
|
+
*/
|
|
98
|
+
function truncateString(text, excess) {
|
|
99
|
+
// A second cut of the same field must not stack a second marker.
|
|
100
|
+
const bare = text.endsWith(TRUNCATION_MARKER)
|
|
101
|
+
? text.slice(0, -TRUNCATION_MARKER.length)
|
|
102
|
+
: text;
|
|
103
|
+
const keep = Math.max(
|
|
104
|
+
0,
|
|
105
|
+
Buffer.byteLength(bare, 'utf-8') - excess - TRUNCATION_MARKER.length,
|
|
106
|
+
);
|
|
107
|
+
return `${Buffer.from(bare, 'utf-8').subarray(0, keep).toString('utf-8')}${TRUNCATION_MARKER}`;
|
|
108
|
+
}
|
|
98
109
|
|
|
99
110
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
111
|
+
* Cut one envelope field down by roughly `excess` bytes. Three shapes are
|
|
112
|
+
* cuttable: a string (cut to a prefix), an array (drop tail entries until it
|
|
113
|
+
* fits), and an object whose largest string property is cut in place — which
|
|
114
|
+
* covers `seed.content`, `docsContext.digest` and every list field. Returns
|
|
115
|
+
* `null` for a shape nothing here can shrink.
|
|
102
116
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
* @param {unknown} value
|
|
118
|
+
* @param {number} excess
|
|
119
|
+
* @returns {{ value: unknown, note: string }|null}
|
|
120
|
+
*/
|
|
121
|
+
function truncateField(value, excess) {
|
|
122
|
+
if (typeof value === 'string') {
|
|
123
|
+
return {
|
|
124
|
+
value: truncateString(value, excess),
|
|
125
|
+
note: `text cut to a prefix (${excess} bytes over)`,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (Array.isArray(value)) {
|
|
129
|
+
let kept = value.length;
|
|
130
|
+
let bytes = jsonBytes(value);
|
|
131
|
+
const target = bytes - excess;
|
|
132
|
+
while (kept > 0 && bytes > target) {
|
|
133
|
+
kept -= 1;
|
|
134
|
+
bytes = jsonBytes(value.slice(0, kept));
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
value: value.slice(0, kept),
|
|
138
|
+
note: `kept ${kept} of ${value.length} entries`,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (value && typeof value === 'object') {
|
|
142
|
+
const [key] = Object.entries(value)
|
|
143
|
+
.filter(([, v]) => typeof v === 'string')
|
|
144
|
+
.map(([k, v]) => [k, Buffer.byteLength(v, 'utf-8')])
|
|
145
|
+
.sort((a, b) => b[1] - a[1])[0] ?? [null];
|
|
146
|
+
if (key === null) return null;
|
|
147
|
+
return {
|
|
148
|
+
value: { ...value, [key]: truncateString(value[key], excess) },
|
|
149
|
+
note: `.${key} cut to a prefix (${excess} bytes over)`,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Fit an assembled envelope under {@link PLAN_CONTEXT_ENVELOPE_BYTE_CEILING}
|
|
157
|
+
* by truncating its largest fields, recording every cut on a `truncated`
|
|
158
|
+
* field so the planner can see what it did not get.
|
|
112
159
|
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* that
|
|
160
|
+
* Until Story #5312 this refused the envelope outright and exited non-zero
|
|
161
|
+
* naming what to trim. That was the wrong direction for a bound whose only
|
|
162
|
+
* job is to keep the planner's context readable: an oversize seed or
|
|
163
|
+
* `--tickets` body is an operator's real input, and refusing to plan from it
|
|
164
|
+
* cost a re-run for a ceiling the operator had no way to act on (the seed is
|
|
165
|
+
* carried verbatim by design). A truncated envelope with a note is a plan
|
|
166
|
+
* that runs on the part that fits and says so; a refusal is no plan at all.
|
|
167
|
+
* The bound itself stays a fixed framework constant — a cap the operator can
|
|
168
|
+
* raise past what the model can read fails silently again.
|
|
120
169
|
*
|
|
121
170
|
* Deliberately **not** exported: its only external caller would be a test, and
|
|
122
171
|
* a test-only export is a production-dead one. It is reachable end to end
|
|
@@ -124,35 +173,57 @@ const DEFAULT_OVERSIZE_REMEDY =
|
|
|
124
173
|
*
|
|
125
174
|
* @param {object} envelope
|
|
126
175
|
* @param {{ ceiling?: number }} [opts]
|
|
127
|
-
* @returns {object} `envelope
|
|
176
|
+
* @returns {object} `envelope` unchanged when it fits; otherwise a truncated
|
|
177
|
+
* copy carrying `truncated: Array<{ field, originalBytes, keptBytes, note }>`.
|
|
128
178
|
*/
|
|
129
|
-
function
|
|
179
|
+
function capPlanContextEnvelope(envelope, opts = {}) {
|
|
130
180
|
const ceiling = opts.ceiling ?? PLAN_CONTEXT_ENVELOPE_BYTE_CEILING;
|
|
131
|
-
|
|
132
|
-
if (bytes <= ceiling) return envelope;
|
|
133
|
-
|
|
134
|
-
const sortedFields = Object.entries(envelope)
|
|
135
|
-
.map(([field, value]) => [
|
|
136
|
-
field,
|
|
137
|
-
Buffer.byteLength(JSON.stringify(value) ?? '', 'utf-8'),
|
|
138
|
-
])
|
|
139
|
-
.sort((a, b) => b[1] - a[1]);
|
|
181
|
+
if (jsonBytes(envelope) <= ceiling) return envelope;
|
|
140
182
|
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
183
|
+
const next = { ...envelope };
|
|
184
|
+
const truncated = [];
|
|
185
|
+
for (let round = 0; round < MAX_TRUNCATION_ROUNDS; round += 1) {
|
|
186
|
+
const total = jsonBytes({ ...next, truncated });
|
|
187
|
+
if (total <= ceiling) break;
|
|
188
|
+
// JSON escaping of the marker and the note itself cost a few bytes the
|
|
189
|
+
// raw cut cannot see; over-cut by a small margin so the dominant field
|
|
190
|
+
// absorbs the whole excess rather than a residual spilling onto the next
|
|
191
|
+
// largest one (which is the planner's own prompt).
|
|
192
|
+
const excess = total - ceiling + 128;
|
|
193
|
+
// The largest field that can be cut — re-cut on a later round rather than
|
|
194
|
+
// moving on to a smaller field it never had to touch.
|
|
195
|
+
const candidates = Object.entries(next)
|
|
196
|
+
.map(([field, value]) => [field, jsonBytes(value)])
|
|
197
|
+
.sort((a, b) => b[1] - a[1]);
|
|
198
|
+
let applied = false;
|
|
199
|
+
for (const [field, bytes] of candidates) {
|
|
200
|
+
const cut = truncateField(next[field], excess);
|
|
201
|
+
if (cut === null) continue;
|
|
202
|
+
next[field] = cut.value;
|
|
203
|
+
const record = truncated.find((t) => t.field === field);
|
|
204
|
+
if (record) {
|
|
205
|
+
record.keptBytes = jsonBytes(cut.value);
|
|
206
|
+
record.note = cut.note;
|
|
207
|
+
} else {
|
|
208
|
+
truncated.push({
|
|
209
|
+
field,
|
|
210
|
+
originalBytes: bytes,
|
|
211
|
+
keptBytes: jsonBytes(cut.value),
|
|
212
|
+
note: cut.note,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
applied = true;
|
|
216
|
+
break;
|
|
217
|
+
}
|
|
218
|
+
if (!applied) break;
|
|
219
|
+
}
|
|
220
|
+
Logger.warn(
|
|
221
|
+
`[plan-context] the assembled "${envelope?.mode}" envelope was over the ` +
|
|
222
|
+
`${Math.round(ceiling / 1024)} KB planner-context ceiling — truncated ` +
|
|
223
|
+
`${truncated.map((t) => `${t.field} (${t.note})`).join(', ')}. ` +
|
|
224
|
+
"See the envelope's `truncated` field.",
|
|
155
225
|
);
|
|
226
|
+
return { ...next, truncated };
|
|
156
227
|
}
|
|
157
228
|
|
|
158
229
|
/**
|
|
@@ -236,13 +307,11 @@ function buildTemplateChanges(complexitySignals) {
|
|
|
236
307
|
* / `verify[]` live at the ticket's top level — the machine contract persist
|
|
237
308
|
* syncs into the body.
|
|
238
309
|
*
|
|
239
|
-
* Correct-by-construction skeleton (Story #4723): the
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
* snapshot — a faithfully-filled skeleton passes the persist ticket
|
|
245
|
-
* validators without a mechanical round-trip. The persist gates stay
|
|
310
|
+
* Correct-by-construction skeleton (Story #4723): when the envelope's
|
|
311
|
+
* `complexitySignals` predicted a footprint the `changes[]` entries arrive
|
|
312
|
+
* pre-resolved to creates-vs-refactors against the repo snapshot — a
|
|
313
|
+
* faithfully-filled skeleton passes the persist ticket validators without a
|
|
314
|
+
* mechanical round-trip. The persist gates stay
|
|
246
315
|
* authoritative (they probe the base branch ref, not the working tree).
|
|
247
316
|
*
|
|
248
317
|
* Pure and deterministic; the output is valid JSON (parseable as-is), with
|
|
@@ -265,19 +334,16 @@ export function renderStoriesTemplate({ complexitySignals = null } = {}) {
|
|
|
265
334
|
'codes, security invariants, and load-bearing constraints with ' +
|
|
266
335
|
'their why. Implementation choices belong to the deliverer unless ' +
|
|
267
336
|
'load-bearing. No per-file behavior paragraphs, no current-state ' +
|
|
268
|
-
'narration.
|
|
269
|
-
'and it never fails the persist. ' +
|
|
337
|
+
'narration. As long as the work needs. ' +
|
|
270
338
|
'Delete this field when acceptance[] carries the whole contract.',
|
|
271
339
|
changes: buildTemplateChanges(complexitySignals),
|
|
272
340
|
non_goals: [],
|
|
273
|
-
reason_to_exist:
|
|
274
|
-
'Fill: the single coherent reason this Story exists (one sentence).',
|
|
275
341
|
},
|
|
276
342
|
acceptance: [
|
|
277
|
-
'Fill: a
|
|
343
|
+
'Fill: an outcome a PR reviewer can confirm from the diff and the verify output (three to six items)',
|
|
278
344
|
],
|
|
279
345
|
verify: [
|
|
280
|
-
'Fill: exact command or test path —
|
|
346
|
+
'Fill: exact command or test path — the mechanical check the acceptance item rests on',
|
|
281
347
|
],
|
|
282
348
|
depends_on: [],
|
|
283
349
|
},
|
|
@@ -314,14 +380,12 @@ const DELTA_VERB_RE =
|
|
|
314
380
|
* two skill Reads (`core/scope-triage` + the gate fragment's rubric pass)
|
|
315
381
|
* from the headless path; the attended path keeps the skill-based judgment.
|
|
316
382
|
*
|
|
317
|
-
* The heuristics anchor to the same
|
|
318
|
-
* `DELIVERABLE_GRANULARITY_GUIDANCE`
|
|
319
|
-
*
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
* the sizing validator catch an over-planned Story later), and `borderline`
|
|
324
|
-
* is a first-class output, not a forced call.
|
|
383
|
+
* The heuristics anchor to the same granularity SSOT the skill anchors to —
|
|
384
|
+
* `DELIVERABLE_GRANULARITY_GUIDANCE` in `ticket-validator-sizing.js` (one
|
|
385
|
+
* Story = one coherent capability slice; multiple independent capabilities =
|
|
386
|
+
* an Epic) — and to the skill's change-request delta rubric. Like the skill,
|
|
387
|
+
* the verdict is **advisory**: being wrong in the `epic` direction is cheap,
|
|
388
|
+
* and `borderline` is a first-class output, not a forced call.
|
|
325
389
|
*
|
|
326
390
|
* @param {{ seedText?: string }} args
|
|
327
391
|
* @returns {{ verdict: 'epic'|'story'|'borderline', reasons: string[], advisory: true, appliedBy: 'cli' }}
|
|
@@ -383,113 +447,6 @@ export function buildScopeTriageSignal({ seedText = '' } = {}) {
|
|
|
383
447
|
};
|
|
384
448
|
}
|
|
385
449
|
|
|
386
|
-
/**
|
|
387
|
-
* Resolve the planning risk heuristics list from the canonical config
|
|
388
|
-
* block (same resolution the decompose context uses).
|
|
389
|
-
*
|
|
390
|
-
* @param {object} config
|
|
391
|
-
* @returns {string[]}
|
|
392
|
-
*/
|
|
393
|
-
function resolveRiskHeuristics(config = {}) {
|
|
394
|
-
if (Array.isArray(config.planning?.riskHeuristics)) {
|
|
395
|
-
return config.planning.riskHeuristics;
|
|
396
|
-
}
|
|
397
|
-
return [];
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Ceilings a seed's advisory complexity signals must fit for the plan
|
|
402
|
-
* workflow to **suggest** the light path at Gate #1 (Story #4741 R3 plan-side
|
|
403
|
-
* handshake). Framework constants, not operator knobs — mirroring the
|
|
404
|
-
* conservative intent of `complexity-gate.js`'s `STORY_SHAPE_CEILINGS`
|
|
405
|
-
* (small, mostly-additive, non-sensitive) but read against the *seed-time*
|
|
406
|
-
* signals rather than an authored Story shape.
|
|
407
|
-
*
|
|
408
|
-
* The suggestion is **advisory only and never an automatic reroute**: it
|
|
409
|
-
* surfaces at Gate #1 for the operator to decide, and under `--yes` it is
|
|
410
|
-
* recorded on the envelope while planning proceeds unchanged.
|
|
411
|
-
*
|
|
412
|
-
* These ceilings are deliberately NOT the ones the light path itself applies
|
|
413
|
-
* (Story #4760). A confirmed suggestion routes into
|
|
414
|
-
* `workflows/helpers/deliver-light.md`, whose gate re-judges the *predicted
|
|
415
|
-
* shape* against `STORY_SHAPE_CEILINGS`. Two checks at two different stages:
|
|
416
|
-
* this one screens a seed, that one decides. Collapsing them would make a
|
|
417
|
-
* confirm a bypass.
|
|
418
|
-
*
|
|
419
|
-
* **Risk only, never cardinality (Story #4856).** This carried a
|
|
420
|
-
* `maxArtifacts: 2` ceiling — the second surviving artifact count after Story
|
|
421
|
-
* #4764 retired the axis from the routing gate, and the more misleading of the
|
|
422
|
-
* two, because the artifacts it counted were **paths scraped from seed prose**
|
|
423
|
-
* rather than a measured footprint. Observed on the seed that produced Story
|
|
424
|
-
* #4856: a change spanning four framework modules was suggested as light off
|
|
425
|
-
* two scraped paths, one of which did not exist at the scraped location. A count
|
|
426
|
-
* of guesses is not a size signal, so the screen now keys on risk alone.
|
|
427
|
-
*
|
|
428
|
-
* - `maxRiskHeuristicHits` — any risk-heuristic hit disqualifies: risk
|
|
429
|
-
* is exactly what a light path should not carry.
|
|
430
|
-
* - `maxSensitivePathClasses`— any sensitive-path class disqualifies, the
|
|
431
|
-
* same taxonomy close applies to a landed diff.
|
|
432
|
-
*/
|
|
433
|
-
const DELIVER_LIGHT_SUGGESTION_CEILINGS = Object.freeze({
|
|
434
|
-
maxRiskHeuristicHits: 0,
|
|
435
|
-
maxSensitivePathClasses: 0,
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
/**
|
|
439
|
-
* Derive the advisory light-path suggestion from a seed's complexity signals
|
|
440
|
-
* (Story #4741 AC-6). Pure and total: a malformed / missing signal bag fails
|
|
441
|
-
* conservative (not suggested), never throws.
|
|
442
|
-
*
|
|
443
|
-
* `automatic: false` is part of the contract — the suggestion is surfaced for
|
|
444
|
-
* the operator, never a silent reroute of a non-interactive run.
|
|
445
|
-
*
|
|
446
|
-
* @param {object|null|undefined} complexitySignals
|
|
447
|
-
* @returns {{
|
|
448
|
-
* suggested: boolean,
|
|
449
|
-
* automatic: false,
|
|
450
|
-
* advisory: true,
|
|
451
|
-
* ceilings: typeof DELIVER_LIGHT_SUGGESTION_CEILINGS,
|
|
452
|
-
* reasons: string[],
|
|
453
|
-
* }}
|
|
454
|
-
*/
|
|
455
|
-
export function buildDeliverLightSuggestion(complexitySignals) {
|
|
456
|
-
const ceilings = DELIVER_LIGHT_SUGGESTION_CEILINGS;
|
|
457
|
-
const advisory = /** @type {const} */ (true);
|
|
458
|
-
const automatic = /** @type {const} */ (false);
|
|
459
|
-
const s = complexitySignals ?? {};
|
|
460
|
-
const riskHits = Array.isArray(s.riskHeuristicHits)
|
|
461
|
-
? s.riskHeuristicHits.length
|
|
462
|
-
: Number.POSITIVE_INFINITY;
|
|
463
|
-
const sensitive = Array.isArray(s.sensitivePathClasses)
|
|
464
|
-
? s.sensitivePathClasses.length
|
|
465
|
-
: Number.POSITIVE_INFINITY;
|
|
466
|
-
|
|
467
|
-
const reasons = [];
|
|
468
|
-
if (riskHits > ceilings.maxRiskHeuristicHits) {
|
|
469
|
-
reasons.push(`seed hits ${riskHits} risk-heuristic phrase(s)`);
|
|
470
|
-
}
|
|
471
|
-
if (sensitive > ceilings.maxSensitivePathClasses) {
|
|
472
|
-
reasons.push(
|
|
473
|
-
`predicted footprint touches ${sensitive} sensitive-path class(es)`,
|
|
474
|
-
);
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
const suggested = reasons.length === 0;
|
|
478
|
-
return {
|
|
479
|
-
suggested,
|
|
480
|
-
automatic,
|
|
481
|
-
advisory,
|
|
482
|
-
ceilings,
|
|
483
|
-
reasons: suggested
|
|
484
|
-
? [
|
|
485
|
-
'seed carries no risk signal (no risk-heuristic hits, no ' +
|
|
486
|
-
'sensitive-path classes) — the operator may prefer /mandrel-deliver for ' +
|
|
487
|
-
"this scope; the light path's own gate and diff backstop decide size",
|
|
488
|
-
]
|
|
489
|
-
: reasons,
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
|
|
493
450
|
/**
|
|
494
451
|
* The `audit-rules.json` lens `target` value marking a lens applicable only to
|
|
495
452
|
* a project with a rendered frontend.
|
|
@@ -666,153 +623,39 @@ function buildUiSurfaceSignal({ complexitySignals, config, cwd } = {}) {
|
|
|
666
623
|
*
|
|
667
624
|
* @param {object} complexitySignals
|
|
668
625
|
* @param {{ config?: object, cwd?: string }} [context]
|
|
669
|
-
* @returns {object} the same signals plus `
|
|
670
|
-
* `uiSurface`.
|
|
626
|
+
* @returns {object} the same signals plus `uiSurface`.
|
|
671
627
|
*/
|
|
672
628
|
function withAdvisorySignals(complexitySignals, { config, cwd } = {}) {
|
|
673
629
|
return {
|
|
674
630
|
...complexitySignals,
|
|
675
|
-
deliverLightSuggestion: buildDeliverLightSuggestion(complexitySignals),
|
|
676
631
|
uiSurface: buildUiSurfaceSignal({ complexitySignals, config, cwd }),
|
|
677
632
|
};
|
|
678
633
|
}
|
|
679
634
|
|
|
680
635
|
/**
|
|
681
|
-
*
|
|
682
|
-
*
|
|
683
|
-
* Breakdown / Capabilities), up to the next `## ` heading. Returns `null`
|
|
684
|
-
* when no scope-shaped heading exists — the caller treats that as "no
|
|
685
|
-
* sizing signal" and defaults to fan-out.
|
|
686
|
-
*
|
|
687
|
-
* @param {string} body
|
|
688
|
-
* @returns {number|null}
|
|
689
|
-
*/
|
|
690
|
-
function countScopeItems(body) {
|
|
691
|
-
if (typeof body !== 'string' || body.length === 0) return null;
|
|
692
|
-
const lines = body.split(/\r?\n/);
|
|
693
|
-
const headingIdx = lines.findIndex((line) =>
|
|
694
|
-
/^##\s+(?:(?:MVP\s+|Proposed\s+)?Scope(?:\s+\([^)]+\))?|Work\s+Breakdown|Capabilities)\s*$/i.test(
|
|
695
|
-
line.trim(),
|
|
696
|
-
),
|
|
697
|
-
);
|
|
698
|
-
if (headingIdx === -1) return null;
|
|
699
|
-
let count = 0;
|
|
700
|
-
for (let i = headingIdx + 1; i < lines.length; i++) {
|
|
701
|
-
const line = lines[i];
|
|
702
|
-
if (/^##\s+/.test(line)) break;
|
|
703
|
-
if (/^\s*(?:[-*]|\d+\.)\s+\S/.test(line)) count += 1;
|
|
704
|
-
}
|
|
705
|
-
return count;
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
/**
|
|
709
|
-
* Advisory single-vs-fan-out delivery-shape signal (design § 1 step 1;
|
|
710
|
-
* routing pilot #4475). Derived from the same size/shape heuristics the
|
|
711
|
-
* scope-triage rubric anchors to — the Delivery Slicing table when the Epic
|
|
712
|
-
* body already carries one (slice count + "Independent?" chain shape,
|
|
713
|
-
* via the Phase 8.3 precondition parser), else a scope-enumeration count.
|
|
714
|
-
*
|
|
715
|
-
* **Advisory only, fan-out by default.** This signal changes no routing
|
|
716
|
-
* behaviour in this PR: the deliver-side reader is #4475's scope, and until
|
|
717
|
-
* it lands the recommendation defaults to `fan-out` for every ambiguous
|
|
718
|
-
* case. `single` is recommended only on clear one-pass indicators: a
|
|
719
|
-
* slicing table proposing ≤ 2 slices, a pure dependent chain (zero
|
|
720
|
-
* realized parallelism from the Story tier — the N=2 bench finding), or a
|
|
721
|
-
* scope enumeration of ≤ 2 capabilities.
|
|
722
|
-
*
|
|
723
|
-
* @param {{ body: string }} args
|
|
724
|
-
* @returns {{ recommendation: 'single'|'fan-out', reasons: string[], advisory: true }}
|
|
725
|
-
*/
|
|
726
|
-
export function buildDeliveryShapeSignal({ body } = {}) {
|
|
727
|
-
const advisory = /** @type {const} */ (true);
|
|
728
|
-
const rows = parseDeliverySlicingTable(body ?? '');
|
|
729
|
-
|
|
730
|
-
if (Array.isArray(rows) && rows.length > 0) {
|
|
731
|
-
if (rows.length <= 2) {
|
|
732
|
-
return {
|
|
733
|
-
recommendation: 'single',
|
|
734
|
-
reasons: [
|
|
735
|
-
`delivery-slicing table proposes ${rows.length} slice(s) — one-pass-sized`,
|
|
736
|
-
],
|
|
737
|
-
advisory,
|
|
738
|
-
};
|
|
739
|
-
}
|
|
740
|
-
const chain = rows.slice(1).every((r) => r.independent === false);
|
|
741
|
-
if (chain) {
|
|
742
|
-
return {
|
|
743
|
-
recommendation: 'single',
|
|
744
|
-
reasons: [
|
|
745
|
-
`delivery-slicing table is a pure dependent chain (${rows.length} slices, every non-first slice "Independent? No") — zero parallelism value from Story fan-out`,
|
|
746
|
-
],
|
|
747
|
-
advisory,
|
|
748
|
-
};
|
|
749
|
-
}
|
|
750
|
-
return {
|
|
751
|
-
recommendation: 'fan-out',
|
|
752
|
-
reasons: [
|
|
753
|
-
`delivery-slicing table proposes ${rows.length} slices with independent parallelism`,
|
|
754
|
-
],
|
|
755
|
-
advisory,
|
|
756
|
-
};
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
const scopeItems = countScopeItems(body ?? '');
|
|
760
|
-
if (scopeItems !== null && scopeItems > 0 && scopeItems <= 2) {
|
|
761
|
-
return {
|
|
762
|
-
recommendation: 'single',
|
|
763
|
-
reasons: [
|
|
764
|
-
`scope enumerates ${scopeItems} capability item(s) — one-pass-sized`,
|
|
765
|
-
],
|
|
766
|
-
advisory,
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
if (scopeItems !== null && scopeItems > 2) {
|
|
770
|
-
return {
|
|
771
|
-
recommendation: 'fan-out',
|
|
772
|
-
reasons: [`scope enumerates ${scopeItems} capability items`],
|
|
773
|
-
advisory,
|
|
774
|
-
};
|
|
775
|
-
}
|
|
776
|
-
return {
|
|
777
|
-
recommendation: 'fan-out',
|
|
778
|
-
reasons: [
|
|
779
|
-
'no delivery-slicing table or scope enumeration to size against — defaulting to fan-out',
|
|
780
|
-
],
|
|
781
|
-
advisory,
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
|
|
785
|
-
/**
|
|
786
|
-
* Render the three authoring system prompts the collapsed pipeline's
|
|
787
|
-
* single authoring pass consumes. The spec/acceptance prompts render from
|
|
636
|
+
* Render the authoring system prompts the collapsed pipeline's single
|
|
637
|
+
* authoring pass consumes. The spec/acceptance prompts render from
|
|
788
638
|
* `lib/templates/spec-author-prompts.js` (the M3/M8 handshake — envelope
|
|
789
|
-
* authoritative from day one); the
|
|
790
|
-
*
|
|
639
|
+
* authoritative from day one); the story prompt is the N=1 core from
|
|
640
|
+
* `lib/templates/decomposer-prompts.js`, with the schedule and partition
|
|
641
|
+
* rules a planner reads only when the default-single split policy clears
|
|
642
|
+
* carried separately as `storySplitRules` (Story #5312).
|
|
643
|
+
*
|
|
644
|
+
* `storyTicketsRules` is the one mode-conditional field (Story #5323): it
|
|
645
|
+
* only means anything when the seed is an existing ticket, and an envelope
|
|
646
|
+
* that carries it in every mode teaches the author to look for a source
|
|
647
|
+
* ticket that a `--seed` run does not have.
|
|
791
648
|
*
|
|
792
|
-
* @param {{
|
|
793
|
-
* @returns {{ spec: string, acceptance: string,
|
|
649
|
+
* @param {{ mode?: string }} [args]
|
|
650
|
+
* @returns {{ spec: string, acceptance: string, story: string, storySplitRules: string, storyTicketsRules?: string }}
|
|
794
651
|
*/
|
|
795
|
-
export function buildSystemPrompts({
|
|
796
|
-
const decompose = buildDecomposerSystemPrompt(heuristics, {
|
|
797
|
-
maxTickets,
|
|
798
|
-
});
|
|
652
|
+
export function buildSystemPrompts({ mode } = {}) {
|
|
799
653
|
return {
|
|
800
654
|
spec: renderTechSpecSystemPrompt(),
|
|
801
655
|
acceptance: renderAcceptanceSpecSystemPrompt(),
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
#### v2 DEFAULT-SINGLE SPLIT POLICY:
|
|
806
|
-
|
|
807
|
-
Emit **exactly one Story** in \`stories.json\` unless the pieces have
|
|
808
|
-
near-zero overlap or sit across an architectural seam. Coupled work stays
|
|
809
|
-
one Story — put intra-session checkpoints in \`## Slicing\` and fold the
|
|
810
|
-
Tech Spec into \`## Spec\` (inline only; over-budget Specs mean split or
|
|
811
|
-
tighten — never write under \`docs/\`). Do **not** emit \`deliveryShape\`.
|
|
812
|
-
When N>1, every acceptance criterion must belong to exactly one Story,
|
|
813
|
-
and each Story carries its own \`## Spec\` (no shared techspec.md fold).
|
|
814
|
-
`,
|
|
815
|
-
decompose,
|
|
656
|
+
story: renderStoryAuthorCore(),
|
|
657
|
+
storySplitRules: renderStorySplitRules(),
|
|
658
|
+
...ticketsModePromptField(mode),
|
|
816
659
|
};
|
|
817
660
|
}
|
|
818
661
|
|
|
@@ -986,20 +829,12 @@ async function buildSeedFileModeEnvelope({
|
|
|
986
829
|
);
|
|
987
830
|
}
|
|
988
831
|
|
|
989
|
-
const limits = getLimits(config);
|
|
990
|
-
const heuristics = resolveRiskHeuristics(config);
|
|
991
|
-
|
|
992
832
|
// Hoisted above the gather (Story #5155): the dependency-candidate lookup
|
|
993
833
|
// intersects against `predictedPaths`, so the signals have to exist before
|
|
994
834
|
// the fan-out starts. `buildComplexitySignals` is synchronous and reads
|
|
995
835
|
// nothing the gather produces, so hoisting it changes cost, not output.
|
|
996
836
|
const complexitySignals = withAdvisorySignals(
|
|
997
|
-
buildComplexitySignals({
|
|
998
|
-
seedText: content,
|
|
999
|
-
config,
|
|
1000
|
-
riskHeuristics: heuristics,
|
|
1001
|
-
cwd,
|
|
1002
|
-
}),
|
|
837
|
+
buildComplexitySignals({ seedText: content, cwd }),
|
|
1003
838
|
{ config, cwd },
|
|
1004
839
|
);
|
|
1005
840
|
|
|
@@ -1025,12 +860,9 @@ async function buildSeedFileModeEnvelope({
|
|
|
1025
860
|
return {
|
|
1026
861
|
mode: modeLabel,
|
|
1027
862
|
seed: { path: seedFilePath ?? null, content },
|
|
1028
|
-
// Advisory complexity signals only
|
|
1029
|
-
//
|
|
1030
|
-
//
|
|
1031
|
-
// `deliverLightSuggestion` is the advisory plan-side routing handshake
|
|
1032
|
-
// (Story #4741 AC-6) and `uiSurface` the advisory /prototype offer —
|
|
1033
|
-
// neither is ever an automatic reroute.
|
|
863
|
+
// Advisory complexity signals only: no route, no routing authority. The
|
|
864
|
+
// nested `uiSurface` is the advisory /prototype offer — never an
|
|
865
|
+
// automatic reroute.
|
|
1034
866
|
complexitySignals,
|
|
1035
867
|
duplicates,
|
|
1036
868
|
epicCandidates,
|
|
@@ -1041,12 +873,7 @@ async function buildSeedFileModeEnvelope({
|
|
|
1041
873
|
memoryPoolAdvisory: authoring.memoryPoolAdvisory,
|
|
1042
874
|
priorFeedback: authoring.priorFeedback,
|
|
1043
875
|
ticketSchema: TICKET_SCHEMA_DESCRIPTOR,
|
|
1044
|
-
|
|
1045
|
-
riskHeuristics: heuristics,
|
|
1046
|
-
systemPrompts: buildSystemPrompts({
|
|
1047
|
-
heuristics,
|
|
1048
|
-
maxTickets: limits.maxTickets,
|
|
1049
|
-
}),
|
|
876
|
+
systemPrompts: buildSystemPrompts(),
|
|
1050
877
|
planState: null,
|
|
1051
878
|
// N=1 default: author one Story; skip Epic-scale decompose ceremony.
|
|
1052
879
|
planProfile: 'story-default',
|
|
@@ -1148,17 +975,9 @@ async function buildTicketsModeEnvelope({
|
|
|
1148
975
|
.map((t) => `# ${t.title}\n\n${t.body}`)
|
|
1149
976
|
.join('\n\n---\n\n');
|
|
1150
977
|
|
|
1151
|
-
const limits = getLimits(config);
|
|
1152
|
-
const heuristics = resolveRiskHeuristics(config);
|
|
1153
|
-
|
|
1154
978
|
// Hoisted for the same reason as seed-file mode (Story #5155).
|
|
1155
979
|
const complexitySignals = withAdvisorySignals(
|
|
1156
|
-
buildComplexitySignals({
|
|
1157
|
-
seedText: seed,
|
|
1158
|
-
config,
|
|
1159
|
-
riskHeuristics: heuristics,
|
|
1160
|
-
cwd,
|
|
1161
|
-
}),
|
|
980
|
+
buildComplexitySignals({ seedText: seed, cwd }),
|
|
1162
981
|
{ config, cwd },
|
|
1163
982
|
);
|
|
1164
983
|
|
|
@@ -1196,12 +1015,7 @@ async function buildTicketsModeEnvelope({
|
|
|
1196
1015
|
memoryPoolAdvisory: authoring.memoryPoolAdvisory,
|
|
1197
1016
|
priorFeedback: authoring.priorFeedback,
|
|
1198
1017
|
ticketSchema: TICKET_SCHEMA_DESCRIPTOR,
|
|
1199
|
-
|
|
1200
|
-
riskHeuristics: heuristics,
|
|
1201
|
-
systemPrompts: buildSystemPrompts({
|
|
1202
|
-
heuristics,
|
|
1203
|
-
maxTickets: limits.maxTickets,
|
|
1204
|
-
}),
|
|
1018
|
+
systemPrompts: buildSystemPrompts({ mode: 'tickets' }),
|
|
1205
1019
|
planState: null,
|
|
1206
1020
|
planProfile:
|
|
1207
1021
|
ticketIds.length === 1 ? 'story-default' : 'story-from-tickets',
|
|
@@ -1250,8 +1064,8 @@ function extractPriorArtifacts(priorBody) {
|
|
|
1250
1064
|
* shape already shipped.
|
|
1251
1065
|
*
|
|
1252
1066
|
* The semantic steps that reach the ticket are preserved: the open-Story
|
|
1253
|
-
* duplicate search (excluding the amended Story itself)
|
|
1254
|
-
*
|
|
1067
|
+
* duplicate search (excluding the amended Story itself) and the authoring
|
|
1068
|
+
* system prompts still ride the envelope. What is
|
|
1255
1069
|
* dropped is only the from-scratch repo interrogation the prior artifacts
|
|
1256
1070
|
* already stand in for — that is the round-trip diet, not an amputation.
|
|
1257
1071
|
*
|
|
@@ -1285,8 +1099,6 @@ async function buildAmendmentModeEnvelope({
|
|
|
1285
1099
|
const priorBody = typeof prior.body === 'string' ? prior.body : '';
|
|
1286
1100
|
const { priorAcceptance, deliveredFiles } = extractPriorArtifacts(priorBody);
|
|
1287
1101
|
|
|
1288
|
-
const heuristics = resolveRiskHeuristics(config);
|
|
1289
|
-
const limits = getLimits(config);
|
|
1290
1102
|
// Story #4952 — this builder's independent-gather set has exactly one
|
|
1291
1103
|
// member. `provider.getTicket` above is a hard data dependency (the prior
|
|
1292
1104
|
// body IS the seed), and the mode deliberately carries no authoring-context
|
|
@@ -1320,12 +1132,7 @@ async function buildAmendmentModeEnvelope({
|
|
|
1320
1132
|
// The prior body is the seed the delta is authored against.
|
|
1321
1133
|
seed: { text: priorBody, path: null },
|
|
1322
1134
|
complexitySignals: withAdvisorySignals(
|
|
1323
|
-
buildComplexitySignals({
|
|
1324
|
-
seedText: priorBody,
|
|
1325
|
-
config,
|
|
1326
|
-
riskHeuristics: heuristics,
|
|
1327
|
-
cwd,
|
|
1328
|
-
}),
|
|
1135
|
+
buildComplexitySignals({ seedText: priorBody, cwd }),
|
|
1329
1136
|
{ config, cwd },
|
|
1330
1137
|
),
|
|
1331
1138
|
duplicates,
|
|
@@ -1333,12 +1140,7 @@ async function buildAmendmentModeEnvelope({
|
|
|
1333
1140
|
// artifacts are the grounding, so there is no docs digest to anchor.
|
|
1334
1141
|
docsContext: null,
|
|
1335
1142
|
ticketSchema: TICKET_SCHEMA_DESCRIPTOR,
|
|
1336
|
-
|
|
1337
|
-
riskHeuristics: heuristics,
|
|
1338
|
-
systemPrompts: buildSystemPrompts({
|
|
1339
|
-
heuristics,
|
|
1340
|
-
maxTickets: limits.maxTickets,
|
|
1341
|
-
}),
|
|
1143
|
+
systemPrompts: buildSystemPrompts(),
|
|
1342
1144
|
planState: null,
|
|
1343
1145
|
planProfile: 'story-amendment',
|
|
1344
1146
|
};
|
|
@@ -1349,7 +1151,7 @@ async function buildAmendmentModeEnvelope({
|
|
|
1349
1151
|
*
|
|
1350
1152
|
* Every mode returns through here, which makes this the one place the
|
|
1351
1153
|
* envelope's total size is decided — and therefore the only honest place to
|
|
1352
|
-
* bound it (see {@link
|
|
1154
|
+
* bound it (see {@link capPlanContextEnvelope}).
|
|
1353
1155
|
*
|
|
1354
1156
|
* @param {{
|
|
1355
1157
|
* mode: 'seed-file'|'seed'|'tickets'|'amends',
|
|
@@ -1377,7 +1179,7 @@ export async function buildPlanContext({
|
|
|
1377
1179
|
settings = {},
|
|
1378
1180
|
cwd,
|
|
1379
1181
|
}) {
|
|
1380
|
-
return
|
|
1182
|
+
return capPlanContextEnvelope(
|
|
1381
1183
|
await buildPlanContextEnvelope({
|
|
1382
1184
|
mode,
|
|
1383
1185
|
seedFilePath,
|
|
@@ -1394,7 +1196,7 @@ export async function buildPlanContext({
|
|
|
1394
1196
|
}
|
|
1395
1197
|
|
|
1396
1198
|
/**
|
|
1397
|
-
* Mode dispatch for {@link buildPlanContext}. Split out so the ceiling
|
|
1199
|
+
* Mode dispatch for {@link buildPlanContext}. Split out so the ceiling cap
|
|
1398
1200
|
* wraps every mode exactly once.
|
|
1399
1201
|
*/
|
|
1400
1202
|
async function buildPlanContextEnvelope({
|