mandrel 2.55.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 +4 -30
- package/.agents/docs/configuration.md +11 -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/rules/ci-remediation.md +39 -21
- package/.agents/schemas/agentrc.schema.json +28 -185
- package/.agents/schemas/story-deliver-terminal.schema.json +1 -1
- package/.agents/scripts/acceptance-eval.js +107 -17
- package/.agents/scripts/audit-to-stories.js +222 -75
- 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/file-ci-gap.js +306 -0
- package/.agents/scripts/lib/audit-suite/checklist-threading.js +15 -2
- package/.agents/scripts/lib/audit-to-stories/audit-label-taxonomy.js +25 -1
- package/.agents/scripts/lib/audit-to-stories/dedupe-against-github.js +40 -52
- package/.agents/scripts/lib/audit-to-stories/finding-adapter.js +5 -1
- package/.agents/scripts/lib/audit-to-stories/issue-corpus.js +162 -0
- package/.agents/scripts/lib/audit-to-stories/issues-file.js +121 -0
- package/.agents/scripts/lib/audit-to-stories/ledger-commit.js +1 -1
- package/.agents/scripts/lib/audit-to-stories/ledger-record.js +126 -0
- package/.agents/scripts/lib/audit-to-stories/seed-from-findings.js +11 -0
- 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 +49 -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/feedback-loop/graduator-core.js +53 -13
- package/.agents/scripts/lib/feedback-loop/prior-feedback-fetcher.js +71 -25
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +18 -25
- package/.agents/scripts/lib/{audit-to-stories/ledger.js → findings/audit-ledger.js} +131 -24
- package/.agents/scripts/lib/findings/route-finding.js +38 -0
- package/.agents/scripts/lib/generated/agentrc-validator.js +1 -1
- package/.agents/scripts/lib/github/framework-repo.js +148 -2
- package/.agents/scripts/lib/label-constants.js +6 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +1 -1
- package/.agents/scripts/lib/observability/source-classifier.js +2 -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/ci-gap-intake.js +605 -0
- package/.agents/scripts/lib/orchestration/ci-rerun-guard.js +13 -8
- 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/audit-provenance.js +197 -0
- 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 +133 -299
- 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/run-epilogue.js +4 -4
- 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/story-follow-ups.js +32 -20
- 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/pr-watch-with-update.js +3 -2
- 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/audit-to-stories.md +63 -27
- 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 +56 -62
- package/.agents/workflows/helpers/deliver-story.md +9 -13
- package/.agents/workflows/helpers/plan-reference.md +132 -196
- package/.agents/workflows/mandrel-plan.md +28 -41
- package/.agents/workflows/memory-consolidate.md +9 -13
- package/docs/CHANGELOG.md +33 -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
|
@@ -23,22 +23,23 @@ 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
|
+
} from '../templates/decomposer-prompts.js';
|
|
30
33
|
import {
|
|
31
34
|
renderAcceptanceSpecSystemPrompt,
|
|
32
35
|
renderTechSpecSystemPrompt,
|
|
33
36
|
} from '../templates/spec-author-prompts.js';
|
|
34
37
|
import { concurrentMap, FANOUT_CONCURRENCY } from '../util/concurrent-map.js';
|
|
35
38
|
import { buildComplexitySignals } from './complexity-gate.js';
|
|
36
|
-
import { parseDeliverySlicingTable } from './consolidation-precondition.js';
|
|
37
39
|
import { findDependencyCandidates } from './dependency-candidates.js';
|
|
38
40
|
import { buildDocsDigest } from './docs-digest.js';
|
|
39
41
|
import { findOpenEpicCandidates } from './epic-candidates.js';
|
|
40
42
|
import { buildAuthoringContext } from './planning/authoring-context.js';
|
|
41
|
-
import { buildDecomposerSystemPrompt } from './planning/decomposer-context.js';
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
45
|
* Envelope byte ceiling (regression guard for the design's named PR2 risk:
|
|
@@ -62,61 +63,108 @@ import { buildDecomposerSystemPrompt } from './planning/decomposer-context.js';
|
|
|
62
63
|
* so the seed remains the only field this ceiling leaves genuinely
|
|
63
64
|
* unbounded. 256 KB (~64K tokens at the ≈4-chars/token estimate) leaves
|
|
64
65
|
* roughly 2× headroom over the fixed-floor measurement above while staying
|
|
65
|
-
* well under the session budget.
|
|
66
|
-
*
|
|
67
|
-
* justification.
|
|
66
|
+
* well under the session budget. An envelope over it is truncated with a
|
|
67
|
+
* `truncated` note rather than refused (Story #5312) — raise the ceiling
|
|
68
|
+
* only with a measured justification.
|
|
68
69
|
*/
|
|
69
70
|
export const PLAN_CONTEXT_ENVELOPE_BYTE_CEILING = 256_000;
|
|
70
71
|
|
|
71
|
-
/**
|
|
72
|
-
const
|
|
72
|
+
/** Marker appended to a string field the cap had to cut. */
|
|
73
|
+
const TRUNCATION_MARKER =
|
|
74
|
+
'\n\n[… truncated by plan-context: PLAN_CONTEXT_ENVELOPE_BYTE_CEILING …]';
|
|
75
|
+
|
|
76
|
+
/** Bounded number of cap rounds — each round cuts the current largest field. */
|
|
77
|
+
const MAX_TRUNCATION_ROUNDS = 8;
|
|
73
78
|
|
|
74
79
|
/**
|
|
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.
|
|
80
|
+
* Byte length of a JSON-serialised value.
|
|
81
|
+
*
|
|
82
|
+
* @param {unknown} value
|
|
83
|
+
* @returns {number}
|
|
82
84
|
*/
|
|
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
|
-
});
|
|
85
|
+
function jsonBytes(value) {
|
|
86
|
+
return Buffer.byteLength(JSON.stringify(value) ?? '', 'utf-8');
|
|
87
|
+
}
|
|
95
88
|
|
|
96
|
-
|
|
97
|
-
|
|
89
|
+
/**
|
|
90
|
+
* Cut a string to fit `excess` fewer bytes, keeping a leading prefix and
|
|
91
|
+
* appending the truncation marker.
|
|
92
|
+
*
|
|
93
|
+
* @param {string} text
|
|
94
|
+
* @param {number} excess
|
|
95
|
+
* @returns {string}
|
|
96
|
+
*/
|
|
97
|
+
function truncateString(text, excess) {
|
|
98
|
+
// A second cut of the same field must not stack a second marker.
|
|
99
|
+
const bare = text.endsWith(TRUNCATION_MARKER)
|
|
100
|
+
? text.slice(0, -TRUNCATION_MARKER.length)
|
|
101
|
+
: text;
|
|
102
|
+
const keep = Math.max(
|
|
103
|
+
0,
|
|
104
|
+
Buffer.byteLength(bare, 'utf-8') - excess - TRUNCATION_MARKER.length,
|
|
105
|
+
);
|
|
106
|
+
return `${Buffer.from(bare, 'utf-8').subarray(0, keep).toString('utf-8')}${TRUNCATION_MARKER}`;
|
|
107
|
+
}
|
|
98
108
|
|
|
99
109
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
110
|
+
* Cut one envelope field down by roughly `excess` bytes. Three shapes are
|
|
111
|
+
* cuttable: a string (cut to a prefix), an array (drop tail entries until it
|
|
112
|
+
* fits), and an object whose largest string property is cut in place — which
|
|
113
|
+
* covers `seed.content`, `docsContext.digest` and every list field. Returns
|
|
114
|
+
* `null` for a shape nothing here can shrink.
|
|
102
115
|
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
* @param {unknown} value
|
|
117
|
+
* @param {number} excess
|
|
118
|
+
* @returns {{ value: unknown, note: string }|null}
|
|
119
|
+
*/
|
|
120
|
+
function truncateField(value, excess) {
|
|
121
|
+
if (typeof value === 'string') {
|
|
122
|
+
return {
|
|
123
|
+
value: truncateString(value, excess),
|
|
124
|
+
note: `text cut to a prefix (${excess} bytes over)`,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
if (Array.isArray(value)) {
|
|
128
|
+
let kept = value.length;
|
|
129
|
+
let bytes = jsonBytes(value);
|
|
130
|
+
const target = bytes - excess;
|
|
131
|
+
while (kept > 0 && bytes > target) {
|
|
132
|
+
kept -= 1;
|
|
133
|
+
bytes = jsonBytes(value.slice(0, kept));
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
value: value.slice(0, kept),
|
|
137
|
+
note: `kept ${kept} of ${value.length} entries`,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (value && typeof value === 'object') {
|
|
141
|
+
const [key] = Object.entries(value)
|
|
142
|
+
.filter(([, v]) => typeof v === 'string')
|
|
143
|
+
.map(([k, v]) => [k, Buffer.byteLength(v, 'utf-8')])
|
|
144
|
+
.sort((a, b) => b[1] - a[1])[0] ?? [null];
|
|
145
|
+
if (key === null) return null;
|
|
146
|
+
return {
|
|
147
|
+
value: { ...value, [key]: truncateString(value[key], excess) },
|
|
148
|
+
note: `.${key} cut to a prefix (${excess} bytes over)`,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Fit an assembled envelope under {@link PLAN_CONTEXT_ENVELOPE_BYTE_CEILING}
|
|
156
|
+
* by truncating its largest fields, recording every cut on a `truncated`
|
|
157
|
+
* field so the planner can see what it did not get.
|
|
112
158
|
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
119
|
-
* that
|
|
159
|
+
* Until Story #5312 this refused the envelope outright and exited non-zero
|
|
160
|
+
* naming what to trim. That was the wrong direction for a bound whose only
|
|
161
|
+
* job is to keep the planner's context readable: an oversize seed or
|
|
162
|
+
* `--tickets` body is an operator's real input, and refusing to plan from it
|
|
163
|
+
* cost a re-run for a ceiling the operator had no way to act on (the seed is
|
|
164
|
+
* carried verbatim by design). A truncated envelope with a note is a plan
|
|
165
|
+
* that runs on the part that fits and says so; a refusal is no plan at all.
|
|
166
|
+
* The bound itself stays a fixed framework constant — a cap the operator can
|
|
167
|
+
* raise past what the model can read fails silently again.
|
|
120
168
|
*
|
|
121
169
|
* Deliberately **not** exported: its only external caller would be a test, and
|
|
122
170
|
* a test-only export is a production-dead one. It is reachable end to end
|
|
@@ -124,35 +172,57 @@ const DEFAULT_OVERSIZE_REMEDY =
|
|
|
124
172
|
*
|
|
125
173
|
* @param {object} envelope
|
|
126
174
|
* @param {{ ceiling?: number }} [opts]
|
|
127
|
-
* @returns {object} `envelope
|
|
175
|
+
* @returns {object} `envelope` unchanged when it fits; otherwise a truncated
|
|
176
|
+
* copy carrying `truncated: Array<{ field, originalBytes, keptBytes, note }>`.
|
|
128
177
|
*/
|
|
129
|
-
function
|
|
178
|
+
function capPlanContextEnvelope(envelope, opts = {}) {
|
|
130
179
|
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]);
|
|
180
|
+
if (jsonBytes(envelope) <= ceiling) return envelope;
|
|
140
181
|
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
182
|
+
const next = { ...envelope };
|
|
183
|
+
const truncated = [];
|
|
184
|
+
for (let round = 0; round < MAX_TRUNCATION_ROUNDS; round += 1) {
|
|
185
|
+
const total = jsonBytes({ ...next, truncated });
|
|
186
|
+
if (total <= ceiling) break;
|
|
187
|
+
// JSON escaping of the marker and the note itself cost a few bytes the
|
|
188
|
+
// raw cut cannot see; over-cut by a small margin so the dominant field
|
|
189
|
+
// absorbs the whole excess rather than a residual spilling onto the next
|
|
190
|
+
// largest one (which is the planner's own prompt).
|
|
191
|
+
const excess = total - ceiling + 128;
|
|
192
|
+
// The largest field that can be cut — re-cut on a later round rather than
|
|
193
|
+
// moving on to a smaller field it never had to touch.
|
|
194
|
+
const candidates = Object.entries(next)
|
|
195
|
+
.map(([field, value]) => [field, jsonBytes(value)])
|
|
196
|
+
.sort((a, b) => b[1] - a[1]);
|
|
197
|
+
let applied = false;
|
|
198
|
+
for (const [field, bytes] of candidates) {
|
|
199
|
+
const cut = truncateField(next[field], excess);
|
|
200
|
+
if (cut === null) continue;
|
|
201
|
+
next[field] = cut.value;
|
|
202
|
+
const record = truncated.find((t) => t.field === field);
|
|
203
|
+
if (record) {
|
|
204
|
+
record.keptBytes = jsonBytes(cut.value);
|
|
205
|
+
record.note = cut.note;
|
|
206
|
+
} else {
|
|
207
|
+
truncated.push({
|
|
208
|
+
field,
|
|
209
|
+
originalBytes: bytes,
|
|
210
|
+
keptBytes: jsonBytes(cut.value),
|
|
211
|
+
note: cut.note,
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
applied = true;
|
|
215
|
+
break;
|
|
216
|
+
}
|
|
217
|
+
if (!applied) break;
|
|
218
|
+
}
|
|
219
|
+
Logger.warn(
|
|
220
|
+
`[plan-context] the assembled "${envelope?.mode}" envelope was over the ` +
|
|
221
|
+
`${Math.round(ceiling / 1024)} KB planner-context ceiling — truncated ` +
|
|
222
|
+
`${truncated.map((t) => `${t.field} (${t.note})`).join(', ')}. ` +
|
|
223
|
+
"See the envelope's `truncated` field.",
|
|
155
224
|
);
|
|
225
|
+
return { ...next, truncated };
|
|
156
226
|
}
|
|
157
227
|
|
|
158
228
|
/**
|
|
@@ -236,13 +306,11 @@ function buildTemplateChanges(complexitySignals) {
|
|
|
236
306
|
* / `verify[]` live at the ticket's top level — the machine contract persist
|
|
237
307
|
* syncs into the body.
|
|
238
308
|
*
|
|
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
|
|
309
|
+
* Correct-by-construction skeleton (Story #4723): when the envelope's
|
|
310
|
+
* `complexitySignals` predicted a footprint the `changes[]` entries arrive
|
|
311
|
+
* pre-resolved to creates-vs-refactors against the repo snapshot — a
|
|
312
|
+
* faithfully-filled skeleton passes the persist ticket validators without a
|
|
313
|
+
* mechanical round-trip. The persist gates stay
|
|
246
314
|
* authoritative (they probe the base branch ref, not the working tree).
|
|
247
315
|
*
|
|
248
316
|
* Pure and deterministic; the output is valid JSON (parseable as-is), with
|
|
@@ -265,19 +333,16 @@ export function renderStoriesTemplate({ complexitySignals = null } = {}) {
|
|
|
265
333
|
'codes, security invariants, and load-bearing constraints with ' +
|
|
266
334
|
'their why. Implementation choices belong to the deliverer unless ' +
|
|
267
335
|
'load-bearing. No per-file behavior paragraphs, no current-state ' +
|
|
268
|
-
'narration.
|
|
269
|
-
'and it never fails the persist. ' +
|
|
336
|
+
'narration. As long as the work needs. ' +
|
|
270
337
|
'Delete this field when acceptance[] carries the whole contract.',
|
|
271
338
|
changes: buildTemplateChanges(complexitySignals),
|
|
272
339
|
non_goals: [],
|
|
273
|
-
reason_to_exist:
|
|
274
|
-
'Fill: the single coherent reason this Story exists (one sentence).',
|
|
275
340
|
},
|
|
276
341
|
acceptance: [
|
|
277
|
-
'Fill: a
|
|
342
|
+
'Fill: an outcome a PR reviewer can confirm from the diff and the verify output (three to six items)',
|
|
278
343
|
],
|
|
279
344
|
verify: [
|
|
280
|
-
'Fill: exact command or test path —
|
|
345
|
+
'Fill: exact command or test path — the mechanical check the acceptance item rests on',
|
|
281
346
|
],
|
|
282
347
|
depends_on: [],
|
|
283
348
|
},
|
|
@@ -314,14 +379,12 @@ const DELTA_VERB_RE =
|
|
|
314
379
|
* two skill Reads (`core/scope-triage` + the gate fragment's rubric pass)
|
|
315
380
|
* from the headless path; the attended path keeps the skill-based judgment.
|
|
316
381
|
*
|
|
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.
|
|
382
|
+
* The heuristics anchor to the same granularity SSOT the skill anchors to —
|
|
383
|
+
* `DELIVERABLE_GRANULARITY_GUIDANCE` in `ticket-validator-sizing.js` (one
|
|
384
|
+
* Story = one coherent capability slice; multiple independent capabilities =
|
|
385
|
+
* an Epic) — and to the skill's change-request delta rubric. Like the skill,
|
|
386
|
+
* the verdict is **advisory**: being wrong in the `epic` direction is cheap,
|
|
387
|
+
* and `borderline` is a first-class output, not a forced call.
|
|
325
388
|
*
|
|
326
389
|
* @param {{ seedText?: string }} args
|
|
327
390
|
* @returns {{ verdict: 'epic'|'story'|'borderline', reasons: string[], advisory: true, appliedBy: 'cli' }}
|
|
@@ -383,113 +446,6 @@ export function buildScopeTriageSignal({ seedText = '' } = {}) {
|
|
|
383
446
|
};
|
|
384
447
|
}
|
|
385
448
|
|
|
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
449
|
/**
|
|
494
450
|
* The `audit-rules.json` lens `target` value marking a lens applicable only to
|
|
495
451
|
* a project with a rendered frontend.
|
|
@@ -666,153 +622,32 @@ function buildUiSurfaceSignal({ complexitySignals, config, cwd } = {}) {
|
|
|
666
622
|
*
|
|
667
623
|
* @param {object} complexitySignals
|
|
668
624
|
* @param {{ config?: object, cwd?: string }} [context]
|
|
669
|
-
* @returns {object} the same signals plus `
|
|
670
|
-
* `uiSurface`.
|
|
625
|
+
* @returns {object} the same signals plus `uiSurface`.
|
|
671
626
|
*/
|
|
672
627
|
function withAdvisorySignals(complexitySignals, { config, cwd } = {}) {
|
|
673
628
|
return {
|
|
674
629
|
...complexitySignals,
|
|
675
|
-
deliverLightSuggestion: buildDeliverLightSuggestion(complexitySignals),
|
|
676
630
|
uiSurface: buildUiSurfaceSignal({ complexitySignals, config, cwd }),
|
|
677
631
|
};
|
|
678
632
|
}
|
|
679
633
|
|
|
680
634
|
/**
|
|
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
|
|
635
|
+
* Render the authoring system prompts the collapsed pipeline's single
|
|
636
|
+
* authoring pass consumes. The spec/acceptance prompts render from
|
|
788
637
|
* `lib/templates/spec-author-prompts.js` (the M3/M8 handshake — envelope
|
|
789
|
-
* authoritative from day one); the
|
|
790
|
-
*
|
|
638
|
+
* authoritative from day one); the story prompt is the N=1 core from
|
|
639
|
+
* `lib/templates/decomposer-prompts.js`, with the schedule and partition
|
|
640
|
+
* rules a planner reads only when the default-single split policy clears
|
|
641
|
+
* carried separately as `storySplitRules` (Story #5312).
|
|
791
642
|
*
|
|
792
|
-
* @
|
|
793
|
-
* @returns {{ spec: string, acceptance: string, decompose: string }}
|
|
643
|
+
* @returns {{ spec: string, acceptance: string, story: string, storySplitRules: string }}
|
|
794
644
|
*/
|
|
795
|
-
export function buildSystemPrompts(
|
|
796
|
-
const decompose = buildDecomposerSystemPrompt(heuristics, {
|
|
797
|
-
maxTickets,
|
|
798
|
-
});
|
|
645
|
+
export function buildSystemPrompts() {
|
|
799
646
|
return {
|
|
800
647
|
spec: renderTechSpecSystemPrompt(),
|
|
801
648
|
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,
|
|
649
|
+
story: renderStoryAuthorCore(),
|
|
650
|
+
storySplitRules: renderStorySplitRules(),
|
|
816
651
|
};
|
|
817
652
|
}
|
|
818
653
|
|
|
@@ -986,20 +821,12 @@ async function buildSeedFileModeEnvelope({
|
|
|
986
821
|
);
|
|
987
822
|
}
|
|
988
823
|
|
|
989
|
-
const limits = getLimits(config);
|
|
990
|
-
const heuristics = resolveRiskHeuristics(config);
|
|
991
|
-
|
|
992
824
|
// Hoisted above the gather (Story #5155): the dependency-candidate lookup
|
|
993
825
|
// intersects against `predictedPaths`, so the signals have to exist before
|
|
994
826
|
// the fan-out starts. `buildComplexitySignals` is synchronous and reads
|
|
995
827
|
// nothing the gather produces, so hoisting it changes cost, not output.
|
|
996
828
|
const complexitySignals = withAdvisorySignals(
|
|
997
|
-
buildComplexitySignals({
|
|
998
|
-
seedText: content,
|
|
999
|
-
config,
|
|
1000
|
-
riskHeuristics: heuristics,
|
|
1001
|
-
cwd,
|
|
1002
|
-
}),
|
|
829
|
+
buildComplexitySignals({ seedText: content, cwd }),
|
|
1003
830
|
{ config, cwd },
|
|
1004
831
|
);
|
|
1005
832
|
|
|
@@ -1025,12 +852,9 @@ async function buildSeedFileModeEnvelope({
|
|
|
1025
852
|
return {
|
|
1026
853
|
mode: modeLabel,
|
|
1027
854
|
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.
|
|
855
|
+
// Advisory complexity signals only: no route, no routing authority. The
|
|
856
|
+
// nested `uiSurface` is the advisory /prototype offer — never an
|
|
857
|
+
// automatic reroute.
|
|
1034
858
|
complexitySignals,
|
|
1035
859
|
duplicates,
|
|
1036
860
|
epicCandidates,
|
|
@@ -1041,12 +865,7 @@ async function buildSeedFileModeEnvelope({
|
|
|
1041
865
|
memoryPoolAdvisory: authoring.memoryPoolAdvisory,
|
|
1042
866
|
priorFeedback: authoring.priorFeedback,
|
|
1043
867
|
ticketSchema: TICKET_SCHEMA_DESCRIPTOR,
|
|
1044
|
-
|
|
1045
|
-
riskHeuristics: heuristics,
|
|
1046
|
-
systemPrompts: buildSystemPrompts({
|
|
1047
|
-
heuristics,
|
|
1048
|
-
maxTickets: limits.maxTickets,
|
|
1049
|
-
}),
|
|
868
|
+
systemPrompts: buildSystemPrompts(),
|
|
1050
869
|
planState: null,
|
|
1051
870
|
// N=1 default: author one Story; skip Epic-scale decompose ceremony.
|
|
1052
871
|
planProfile: 'story-default',
|
|
@@ -1148,17 +967,9 @@ async function buildTicketsModeEnvelope({
|
|
|
1148
967
|
.map((t) => `# ${t.title}\n\n${t.body}`)
|
|
1149
968
|
.join('\n\n---\n\n');
|
|
1150
969
|
|
|
1151
|
-
const limits = getLimits(config);
|
|
1152
|
-
const heuristics = resolveRiskHeuristics(config);
|
|
1153
|
-
|
|
1154
970
|
// Hoisted for the same reason as seed-file mode (Story #5155).
|
|
1155
971
|
const complexitySignals = withAdvisorySignals(
|
|
1156
|
-
buildComplexitySignals({
|
|
1157
|
-
seedText: seed,
|
|
1158
|
-
config,
|
|
1159
|
-
riskHeuristics: heuristics,
|
|
1160
|
-
cwd,
|
|
1161
|
-
}),
|
|
972
|
+
buildComplexitySignals({ seedText: seed, cwd }),
|
|
1162
973
|
{ config, cwd },
|
|
1163
974
|
);
|
|
1164
975
|
|
|
@@ -1196,12 +1007,7 @@ async function buildTicketsModeEnvelope({
|
|
|
1196
1007
|
memoryPoolAdvisory: authoring.memoryPoolAdvisory,
|
|
1197
1008
|
priorFeedback: authoring.priorFeedback,
|
|
1198
1009
|
ticketSchema: TICKET_SCHEMA_DESCRIPTOR,
|
|
1199
|
-
|
|
1200
|
-
riskHeuristics: heuristics,
|
|
1201
|
-
systemPrompts: buildSystemPrompts({
|
|
1202
|
-
heuristics,
|
|
1203
|
-
maxTickets: limits.maxTickets,
|
|
1204
|
-
}),
|
|
1010
|
+
systemPrompts: buildSystemPrompts(),
|
|
1205
1011
|
planState: null,
|
|
1206
1012
|
planProfile:
|
|
1207
1013
|
ticketIds.length === 1 ? 'story-default' : 'story-from-tickets',
|
|
@@ -1250,8 +1056,8 @@ function extractPriorArtifacts(priorBody) {
|
|
|
1250
1056
|
* shape already shipped.
|
|
1251
1057
|
*
|
|
1252
1058
|
* The semantic steps that reach the ticket are preserved: the open-Story
|
|
1253
|
-
* duplicate search (excluding the amended Story itself)
|
|
1254
|
-
*
|
|
1059
|
+
* duplicate search (excluding the amended Story itself) and the authoring
|
|
1060
|
+
* system prompts still ride the envelope. What is
|
|
1255
1061
|
* dropped is only the from-scratch repo interrogation the prior artifacts
|
|
1256
1062
|
* already stand in for — that is the round-trip diet, not an amputation.
|
|
1257
1063
|
*
|
|
@@ -1285,8 +1091,6 @@ async function buildAmendmentModeEnvelope({
|
|
|
1285
1091
|
const priorBody = typeof prior.body === 'string' ? prior.body : '';
|
|
1286
1092
|
const { priorAcceptance, deliveredFiles } = extractPriorArtifacts(priorBody);
|
|
1287
1093
|
|
|
1288
|
-
const heuristics = resolveRiskHeuristics(config);
|
|
1289
|
-
const limits = getLimits(config);
|
|
1290
1094
|
// Story #4952 — this builder's independent-gather set has exactly one
|
|
1291
1095
|
// member. `provider.getTicket` above is a hard data dependency (the prior
|
|
1292
1096
|
// body IS the seed), and the mode deliberately carries no authoring-context
|
|
@@ -1320,12 +1124,7 @@ async function buildAmendmentModeEnvelope({
|
|
|
1320
1124
|
// The prior body is the seed the delta is authored against.
|
|
1321
1125
|
seed: { text: priorBody, path: null },
|
|
1322
1126
|
complexitySignals: withAdvisorySignals(
|
|
1323
|
-
buildComplexitySignals({
|
|
1324
|
-
seedText: priorBody,
|
|
1325
|
-
config,
|
|
1326
|
-
riskHeuristics: heuristics,
|
|
1327
|
-
cwd,
|
|
1328
|
-
}),
|
|
1127
|
+
buildComplexitySignals({ seedText: priorBody, cwd }),
|
|
1329
1128
|
{ config, cwd },
|
|
1330
1129
|
),
|
|
1331
1130
|
duplicates,
|
|
@@ -1333,12 +1132,7 @@ async function buildAmendmentModeEnvelope({
|
|
|
1333
1132
|
// artifacts are the grounding, so there is no docs digest to anchor.
|
|
1334
1133
|
docsContext: null,
|
|
1335
1134
|
ticketSchema: TICKET_SCHEMA_DESCRIPTOR,
|
|
1336
|
-
|
|
1337
|
-
riskHeuristics: heuristics,
|
|
1338
|
-
systemPrompts: buildSystemPrompts({
|
|
1339
|
-
heuristics,
|
|
1340
|
-
maxTickets: limits.maxTickets,
|
|
1341
|
-
}),
|
|
1135
|
+
systemPrompts: buildSystemPrompts(),
|
|
1342
1136
|
planState: null,
|
|
1343
1137
|
planProfile: 'story-amendment',
|
|
1344
1138
|
};
|
|
@@ -1349,7 +1143,7 @@ async function buildAmendmentModeEnvelope({
|
|
|
1349
1143
|
*
|
|
1350
1144
|
* Every mode returns through here, which makes this the one place the
|
|
1351
1145
|
* envelope's total size is decided — and therefore the only honest place to
|
|
1352
|
-
* bound it (see {@link
|
|
1146
|
+
* bound it (see {@link capPlanContextEnvelope}).
|
|
1353
1147
|
*
|
|
1354
1148
|
* @param {{
|
|
1355
1149
|
* mode: 'seed-file'|'seed'|'tickets'|'amends',
|
|
@@ -1377,7 +1171,7 @@ export async function buildPlanContext({
|
|
|
1377
1171
|
settings = {},
|
|
1378
1172
|
cwd,
|
|
1379
1173
|
}) {
|
|
1380
|
-
return
|
|
1174
|
+
return capPlanContextEnvelope(
|
|
1381
1175
|
await buildPlanContextEnvelope({
|
|
1382
1176
|
mode,
|
|
1383
1177
|
seedFilePath,
|
|
@@ -1394,7 +1188,7 @@ export async function buildPlanContext({
|
|
|
1394
1188
|
}
|
|
1395
1189
|
|
|
1396
1190
|
/**
|
|
1397
|
-
* Mode dispatch for {@link buildPlanContext}. Split out so the ceiling
|
|
1191
|
+
* Mode dispatch for {@link buildPlanContext}. Split out so the ceiling cap
|
|
1398
1192
|
* wraps every mode exactly once.
|
|
1399
1193
|
*/
|
|
1400
1194
|
async function buildPlanContextEnvelope({
|