mandrel 2.20.0 → 2.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/README.md +1 -1
- package/.agents/agents/story-worker.md +15 -0
- package/.agents/instructions.md +14 -17
- package/.agents/rules/git-conventions.md +1 -1
- package/.agents/rules/known-tooling-behavior.md +114 -0
- package/.agents/scripts/check-context-budget.js +134 -2
- package/.agents/scripts/deliver-light.js +72 -8
- package/.agents/scripts/lib/audit-suite/selector.js +275 -162
- package/.agents/scripts/lib/config/temp-paths.js +113 -7
- package/.agents/scripts/lib/feedback-loop/graduator-core.js +604 -57
- package/.agents/scripts/lib/feedback-loop/retro-proposals-graduator.js +72 -21
- package/.agents/scripts/lib/label-constants.js +12 -1
- package/.agents/scripts/lib/observability/runtime-friction.js +13 -1
- package/.agents/scripts/lib/observability/signals-writer.js +133 -14
- package/.agents/scripts/lib/observability/source-classifier.js +131 -1
- package/.agents/scripts/lib/orchestration/code-review.js +12 -0
- package/.agents/scripts/lib/orchestration/complexity-gate.js +119 -52
- package/.agents/scripts/lib/orchestration/deliver-recover.js +253 -6
- package/.agents/scripts/lib/orchestration/light-suitability.js +194 -11
- package/.agents/scripts/lib/orchestration/resolve-stories.js +17 -14
- package/.agents/scripts/lib/orchestration/retro-proposals.js +0 -0
- package/.agents/scripts/lib/orchestration/review-providers/degraded-gates.js +222 -0
- package/.agents/scripts/lib/orchestration/review-providers/findings-renderer.js +18 -3
- package/.agents/scripts/lib/orchestration/review-providers/native.js +82 -126
- package/.agents/scripts/lib/orchestration/review-providers/review-provider-factory.js +10 -0
- package/.agents/scripts/lib/orchestration/review-providers/scoped-lint.js +300 -0
- package/.agents/scripts/lib/orchestration/run-epilogue.js +51 -1
- package/.agents/scripts/lib/orchestration/single-story-close/gate-log.js +11 -1
- package/.agents/scripts/lib/orchestration/single-story-close/phases/code-review.js +18 -8
- package/.agents/scripts/lib/orchestration/single-story-close/phases/review-outcome.js +66 -0
- package/.agents/scripts/lib/orchestration/single-story-close/runner.js +1 -1
- package/.agents/scripts/lib/orchestration/story-close/phases/code-review.js +5 -1
- package/.agents/scripts/lib/orchestration/story-deliver-terminal.js +117 -4
- package/.agents/scripts/lib/orchestration/story-follow-ups.js +305 -10
- package/.agents/scripts/lib/story-body/story-body.js +248 -174
- package/.agents/scripts/lib/temp-retention.js +23 -8
- package/.agents/scripts/resolve-stories.js +52 -33
- package/.agents/scripts/single-story-confirm-merge.js +6 -8
- package/.agents/workflows/helpers/deliver-digest.md +8 -6
- package/.agents/workflows/helpers/deliver-light.md +45 -5
- package/.agents/workflows/helpers/deliver-reference.md +15 -12
- package/.agents/workflows/helpers/deliver-story-reference.md +56 -21
- package/.agents/workflows/helpers/deliver-story.md +8 -5
- package/.agents/workflows/helpers/plan-reference.md +5 -4
- package/docs/CHANGELOG.md +28 -0
- package/package.json +1 -1
|
@@ -31,15 +31,16 @@
|
|
|
31
31
|
* cardinality is deliberately not an axis (Story #4764).
|
|
32
32
|
* 4. **Deliver re-derives.** `/deliver` computes the route from the fetched
|
|
33
33
|
* Story body via the **same** shape function at dispatch
|
|
34
|
-
* ({@link resolveStoryDispatchMode}) and
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
34
|
+
* ({@link resolveStoryDispatchMode}) and **reports** it, while the
|
|
35
|
+
* dispatch *mode* answers a different question: may the engine run in the
|
|
36
|
+
* router's own session? Only a **single-Story run** may (Story #4736) —
|
|
37
|
+
* sub-agent isolation buys nothing when there is no concurrent sibling to
|
|
38
|
+
* isolate from. Shape cannot grant that session (Story #4829): a lite body
|
|
39
|
+
* makes work cheap, it does not conjure a second session for a sibling to
|
|
40
|
+
* run in. The `route::lite` label is a **human-visible hint only**, never
|
|
41
|
+
* the control signal: a lost label or an unread marker can no longer
|
|
42
|
+
* misroute delivery. Either way every `single-story-close.js` gate runs
|
|
43
|
+
* unchanged.
|
|
43
44
|
*
|
|
44
45
|
* The shape taxonomy is deliberately the one `review-depth.js` already
|
|
45
46
|
* applies to the landed diff at close (`deriveChangeLevel` over the
|
|
@@ -259,23 +260,65 @@ function spansMigrationAndConsumers(paths) {
|
|
|
259
260
|
return migrations.length > 0 && migrations.length < paths.length;
|
|
260
261
|
}
|
|
261
262
|
|
|
263
|
+
/**
|
|
264
|
+
* Stable machine-readable identifiers for every reason a shape routes `full` —
|
|
265
|
+
* the `code` field on a {@link deriveStoryShape} decision (Story #4815).
|
|
266
|
+
*
|
|
267
|
+
* The prose in `reasons[]` is written for a human reading a gate envelope and
|
|
268
|
+
* is free to be re-worded; a caller that must **branch** on *which* rule
|
|
269
|
+
* objected reads this code instead. That distinction is load-bearing for the
|
|
270
|
+
* light path's operator override
|
|
271
|
+
* ({@link module:lib/orchestration/light-suitability.OVERRIDABLE_SHAPE_CODES}),
|
|
272
|
+
* which may waive a size *prediction* but never a risk rule: keying that
|
|
273
|
+
* decision off reason text would make a copy-edit a security change.
|
|
274
|
+
*
|
|
275
|
+
* Split three ways, and the grouping is the contract:
|
|
276
|
+
*
|
|
277
|
+
* - **Ceiling rules** — `change-kinds`, `magnitude`, `uncertainty`,
|
|
278
|
+
* `deployable-span`. Coarse predictions about size, enforced for real
|
|
279
|
+
* against ground truth by the diff backstop.
|
|
280
|
+
* - **Absolute rules** — `migration-span`, `sensitive-path`. Risk, not size.
|
|
281
|
+
* - **Unknown-footprint rejections** — `no-changes`, `unreadable-changes`,
|
|
282
|
+
* `glob-footprint`, `no-acceptance`, `classification-unavailable`,
|
|
283
|
+
* `unparseable-body`. Nothing was judged, so there is nothing to waive.
|
|
284
|
+
*
|
|
285
|
+
* A `lite` route carries `code: null`.
|
|
286
|
+
*/
|
|
287
|
+
export const SHAPE_CODES = Object.freeze({
|
|
288
|
+
CHANGE_KINDS: 'change-kinds',
|
|
289
|
+
MAGNITUDE: 'magnitude',
|
|
290
|
+
UNCERTAINTY: 'uncertainty',
|
|
291
|
+
DEPLOYABLE_SPAN: 'deployable-span',
|
|
292
|
+
MIGRATION_SPAN: 'migration-span',
|
|
293
|
+
SENSITIVE_PATH: 'sensitive-path',
|
|
294
|
+
NO_CHANGES: 'no-changes',
|
|
295
|
+
UNREADABLE_CHANGES: 'unreadable-changes',
|
|
296
|
+
GLOB_FOOTPRINT: 'glob-footprint',
|
|
297
|
+
NO_ACCEPTANCE: 'no-acceptance',
|
|
298
|
+
CLASSIFICATION_UNAVAILABLE: 'classification-unavailable',
|
|
299
|
+
UNPARSEABLE_BODY: 'unparseable-body',
|
|
300
|
+
});
|
|
301
|
+
|
|
262
302
|
/**
|
|
263
303
|
* Ordered effort/risk rules, evaluated in order; the first hit is the recorded
|
|
264
304
|
* reason for a `full` route. Every rule names an effort, risk, or uncertainty
|
|
265
305
|
* property of the work — none counts artifacts.
|
|
266
306
|
*
|
|
267
307
|
* @type {ReadonlyArray<{
|
|
308
|
+
* code: string,
|
|
268
309
|
* when: (shape: object, ceilings: typeof STORY_SHAPE_CEILINGS) => boolean,
|
|
269
310
|
* reason: (shape: object, ceilings: typeof STORY_SHAPE_CEILINGS) => string,
|
|
270
311
|
* }>}
|
|
271
312
|
*/
|
|
272
313
|
const EFFORT_RULES = Object.freeze([
|
|
273
314
|
{
|
|
315
|
+
code: SHAPE_CODES.CHANGE_KINDS,
|
|
274
316
|
when: (s, c) => s.kindCount > c.maxChangeKinds,
|
|
275
317
|
reason: (s, c) =>
|
|
276
318
|
`${s.kindCount} distinct change kinds (${s.changeKinds.join(', ')}) > maxChangeKinds ${c.maxChangeKinds} — an explicit multi-capability enumeration, not one capability; full route`,
|
|
277
319
|
},
|
|
278
320
|
{
|
|
321
|
+
code: SHAPE_CODES.MAGNITUDE,
|
|
279
322
|
when: (s, c) =>
|
|
280
323
|
MAGNITUDE_SCALE.indexOf(s.magnitude) >
|
|
281
324
|
MAGNITUDE_SCALE.indexOf(c.maxMagnitude),
|
|
@@ -283,6 +326,7 @@ const EFFORT_RULES = Object.freeze([
|
|
|
283
326
|
`declared magnitude "${s.magnitude}" > maxMagnitude "${c.maxMagnitude}" — a substantial rewrite is effort a single inline pass should not absorb, however few files it touches; full route`,
|
|
284
327
|
},
|
|
285
328
|
{
|
|
329
|
+
code: SHAPE_CODES.UNCERTAINTY,
|
|
286
330
|
when: (s, c) =>
|
|
287
331
|
UNCERTAINTY_SCALE.indexOf(s.uncertainty) >
|
|
288
332
|
UNCERTAINTY_SCALE.indexOf(c.maxUncertainty),
|
|
@@ -290,16 +334,19 @@ const EFFORT_RULES = Object.freeze([
|
|
|
290
334
|
`the shape is not determined by the request (uncertainty "${s.uncertainty}") — the design decisions /plan exists to resolve are still open; full route`,
|
|
291
335
|
},
|
|
292
336
|
{
|
|
337
|
+
code: SHAPE_CODES.DEPLOYABLE_SPAN,
|
|
293
338
|
when: (s, c) => s.deployables.length > c.maxDeployables,
|
|
294
339
|
reason: (s, c) =>
|
|
295
340
|
`footprint spans ${s.deployables.length} deployables (${s.deployables.join(', ')}) > maxDeployables ${c.maxDeployables} — clearly-epic scope; full route`,
|
|
296
341
|
},
|
|
297
342
|
{
|
|
343
|
+
code: SHAPE_CODES.MIGRATION_SPAN,
|
|
298
344
|
when: (s) => s.migrationSpan,
|
|
299
345
|
reason: () =>
|
|
300
346
|
'footprint pairs a migration with its consumers — clearly-epic scope; full route',
|
|
301
347
|
},
|
|
302
348
|
{
|
|
349
|
+
code: SHAPE_CODES.SENSITIVE_PATH,
|
|
303
350
|
when: (s) => s.sensitiveClasses.length > 0,
|
|
304
351
|
reason: (s) =>
|
|
305
352
|
`footprint intersects sensitive-path class(es) ${s.sensitiveClasses.join(', ')} — sensitivity wins over a small shape; full route (fresh acceptance critic retained)`,
|
|
@@ -307,15 +354,18 @@ const EFFORT_RULES = Object.freeze([
|
|
|
307
354
|
]);
|
|
308
355
|
|
|
309
356
|
/**
|
|
310
|
-
* First effort/risk rule the shape violates
|
|
357
|
+
* First effort/risk rule the shape violates as a `{ code, reason }` pair, or
|
|
358
|
+
* `null` when it clears them all.
|
|
311
359
|
*
|
|
312
360
|
* @param {object} shape
|
|
313
361
|
* @param {typeof STORY_SHAPE_CEILINGS} ceilings
|
|
314
|
-
* @returns {string|null}
|
|
362
|
+
* @returns {{ code: string, reason: string }|null}
|
|
315
363
|
*/
|
|
316
364
|
function firstEffortViolation(shape, ceilings) {
|
|
317
365
|
for (const rule of EFFORT_RULES) {
|
|
318
|
-
if (rule.when(shape, ceilings))
|
|
366
|
+
if (rule.when(shape, ceilings)) {
|
|
367
|
+
return { code: rule.code, reason: rule.reason(shape, ceilings) };
|
|
368
|
+
}
|
|
319
369
|
}
|
|
320
370
|
return null;
|
|
321
371
|
}
|
|
@@ -654,10 +704,13 @@ function buildEffortShape({
|
|
|
654
704
|
* @returns {{
|
|
655
705
|
* route: ComplexityRoute,
|
|
656
706
|
* reasons: string[],
|
|
707
|
+
* code: string|null,
|
|
657
708
|
* shape: ReturnType<typeof buildEffortShape>|null,
|
|
658
709
|
* ceilings: typeof STORY_SHAPE_CEILINGS,
|
|
659
710
|
* preserves: typeof LITE_PATH_INVARIANTS,
|
|
660
|
-
* }}
|
|
711
|
+
* }} `code` is the stable {@link SHAPE_CODES} identifier for the rule that
|
|
712
|
+
* rejected the shape (`null` on `lite`) — the field a caller branches on,
|
|
713
|
+
* since `reasons[]` is human prose and free to be re-worded.
|
|
661
714
|
*/
|
|
662
715
|
export function deriveStoryShape({
|
|
663
716
|
changes,
|
|
@@ -670,9 +723,10 @@ export function deriveStoryShape({
|
|
|
670
723
|
} = {}) {
|
|
671
724
|
const ceilings = STORY_SHAPE_CEILINGS;
|
|
672
725
|
const preserves = LITE_PATH_INVARIANTS;
|
|
673
|
-
const decide = (route, reason, shape = null) => ({
|
|
726
|
+
const decide = (route, code, reason, shape = null) => ({
|
|
674
727
|
route,
|
|
675
728
|
reasons: [reason],
|
|
729
|
+
code,
|
|
676
730
|
shape,
|
|
677
731
|
ceilings,
|
|
678
732
|
preserves,
|
|
@@ -681,6 +735,7 @@ export function deriveStoryShape({
|
|
|
681
735
|
if (!Array.isArray(changes) || changes.length === 0) {
|
|
682
736
|
return decide(
|
|
683
737
|
'full',
|
|
738
|
+
SHAPE_CODES.NO_CHANGES,
|
|
684
739
|
'no changes[] declared — the footprint is unknown, so the work cannot be judged trivial; conservative full route',
|
|
685
740
|
);
|
|
686
741
|
}
|
|
@@ -691,6 +746,7 @@ export function deriveStoryShape({
|
|
|
691
746
|
} catch (err) {
|
|
692
747
|
return decide(
|
|
693
748
|
'full',
|
|
749
|
+
SHAPE_CODES.UNREADABLE_CHANGES,
|
|
694
750
|
`changes[] could not be read (${err?.message ?? err}) — unknown footprint; conservative full route`,
|
|
695
751
|
);
|
|
696
752
|
}
|
|
@@ -714,6 +770,7 @@ export function deriveStoryShape({
|
|
|
714
770
|
if (entries.some((e) => e.isGlob)) {
|
|
715
771
|
return decide(
|
|
716
772
|
'full',
|
|
773
|
+
SHAPE_CODES.GLOB_FOOTPRINT,
|
|
717
774
|
'changes[] contains a glob path — unknown footprint width; conservative full route',
|
|
718
775
|
shape,
|
|
719
776
|
);
|
|
@@ -721,13 +778,16 @@ export function deriveStoryShape({
|
|
|
721
778
|
if (shape.acceptanceCount === 0) {
|
|
722
779
|
return decide(
|
|
723
780
|
'full',
|
|
781
|
+
SHAPE_CODES.NO_ACCEPTANCE,
|
|
724
782
|
'no acceptance criteria — the contract cannot be judged trivial; conservative full route',
|
|
725
783
|
shape,
|
|
726
784
|
);
|
|
727
785
|
}
|
|
728
786
|
|
|
729
787
|
const violation = firstEffortViolation(shape, ceilings);
|
|
730
|
-
if (violation !== null)
|
|
788
|
+
if (violation !== null) {
|
|
789
|
+
return decide('full', violation.code, violation.reason, shape);
|
|
790
|
+
}
|
|
731
791
|
|
|
732
792
|
if (level !== 'low') {
|
|
733
793
|
// `deriveChangeLevel` degraded to its null fail-safe (unreadable
|
|
@@ -735,6 +795,7 @@ export function deriveStoryShape({
|
|
|
735
795
|
// non-sensitive, and a classification failure must never buy lite.
|
|
736
796
|
return decide(
|
|
737
797
|
'full',
|
|
798
|
+
SHAPE_CODES.CLASSIFICATION_UNAVAILABLE,
|
|
738
799
|
'sensitive-path classification unavailable — cannot verify the footprint is non-sensitive; conservative full route',
|
|
739
800
|
shape,
|
|
740
801
|
);
|
|
@@ -742,6 +803,7 @@ export function deriveStoryShape({
|
|
|
742
803
|
|
|
743
804
|
return decide(
|
|
744
805
|
'lite',
|
|
806
|
+
null,
|
|
745
807
|
`trivial shape: ${shape.kindCount} change kind(s) (${shape.changeKinds.join(', ')}) ≤ ${ceilings.maxChangeKinds} across ${shape.siteCount} site(s), magnitude ${shape.magnitude} ≤ ${ceilings.maxMagnitude}, shape ${shape.uncertainty}, no epic-scope span, no sensitive-path class — inline-eligible; non-negotiables preserved`,
|
|
746
808
|
shape,
|
|
747
809
|
);
|
|
@@ -771,6 +833,7 @@ function deriveStoryRouteFromBody(body, opts = {}) {
|
|
|
771
833
|
reasons: [
|
|
772
834
|
`Story body is unparseable (${err?.message ?? err}) — shape unknown; conservative full route`,
|
|
773
835
|
],
|
|
836
|
+
code: SHAPE_CODES.UNPARSEABLE_BODY,
|
|
774
837
|
shape: null,
|
|
775
838
|
ceilings: STORY_SHAPE_CEILINGS,
|
|
776
839
|
preserves: LITE_PATH_INVARIANTS,
|
|
@@ -802,27 +865,34 @@ function routeForReporting(body, opts) {
|
|
|
802
865
|
/**
|
|
803
866
|
* Decide how `/deliver` executes a Story.
|
|
804
867
|
*
|
|
805
|
-
*
|
|
806
|
-
*
|
|
807
|
-
*
|
|
808
|
-
*
|
|
809
|
-
*
|
|
810
|
-
*
|
|
811
|
-
*
|
|
812
|
-
*
|
|
813
|
-
*
|
|
814
|
-
*
|
|
815
|
-
*
|
|
816
|
-
*
|
|
817
|
-
*
|
|
818
|
-
*
|
|
819
|
-
*
|
|
820
|
-
*
|
|
821
|
-
*
|
|
822
|
-
*
|
|
823
|
-
*
|
|
824
|
-
*
|
|
825
|
-
*
|
|
868
|
+
* **`inline` names one indivisible resource: the router's own session.** Two
|
|
869
|
+
* Stories cannot both own it, so exactly one premise can grant it —
|
|
870
|
+
* **run topology (Story #4736)**: a run resolving a *single* Story executes
|
|
871
|
+
* inline whatever its shape, because sub-agent isolation is load-bearing only
|
|
872
|
+
* for CONCURRENT dispatch (two workers sharing a checkout race on worktrees and
|
|
873
|
+
* branch refs) and a one-Story run has no sibling to race. It therefore pays
|
|
874
|
+
* the spawn premium (a boot is a cache WRITE at full rate, where an inline
|
|
875
|
+
* continuation is a cache read at ~10%; ~$1.43/M vs ~$1.07/M on comparable
|
|
876
|
+
* bench work) for nothing. That is a fact about the run, not about the work, so
|
|
877
|
+
* the shape gate's `enabled` switch — which governs *shape derivation* — does
|
|
878
|
+
* not reach it.
|
|
879
|
+
*
|
|
880
|
+
* **Shape cannot grant it (Story #4829).** The shape read used to return
|
|
881
|
+
* `inline` for any lite-shaped body in a multi-Story run, inheriting no
|
|
882
|
+
* topology guard. Measured twice on 2026-07-29: a two-Story and a three-Story
|
|
883
|
+
* run came back `inline` for *every* Story while `stories-wave-tick.js`
|
|
884
|
+
* reported the whole set ready under a concurrency cap of five — a router
|
|
885
|
+
* following both signals literally runs several engines over one session and
|
|
886
|
+
* one checkout, the precise hazard the sub-agent path exists to prevent. Both
|
|
887
|
+
* runs were completed only by an operator overriding the verdict by hand, which
|
|
888
|
+
* is an invariant held by judgment rather than by code. So this function has
|
|
889
|
+
* exactly **one** `inline` exit, guarded by the topology premise; every path
|
|
890
|
+
* below it returns `subagent`, and the derived shape is carried on `route` for
|
|
891
|
+
* reporting only. A lite shape makes the work cheap — it does not conjure a
|
|
892
|
+
* second session for a sibling to run in.
|
|
893
|
+
*
|
|
894
|
+
* The label is read only to report hint consistency in `reasons`; it never
|
|
895
|
+
* routes on either premise (Story #4722 AC-4/AC-5).
|
|
826
896
|
*
|
|
827
897
|
* Inline execution removes model-side fan-out only — it changes **where** the
|
|
828
898
|
* engine runs, never **what** runs. Every deterministic
|
|
@@ -839,7 +909,8 @@ function routeForReporting(body, opts) {
|
|
|
839
909
|
* selectSensitivePathClassesFn?: Function,
|
|
840
910
|
* }} [args] `storyCount` is the number of Stories the invoking `/deliver` run
|
|
841
911
|
* resolved. Omitted (or not a positive integer) means "unknown run size",
|
|
842
|
-
* which
|
|
912
|
+
* which cannot be shown sibling-free and therefore dispatches as a sub-agent
|
|
913
|
+
* — never an assumed 1.
|
|
843
914
|
* @returns {{ mode: 'inline'|'subagent', reasons: string[], route: ReturnType<typeof deriveStoryShape>|null }}
|
|
844
915
|
*/
|
|
845
916
|
export function resolveStoryDispatchMode({
|
|
@@ -858,6 +929,8 @@ export function resolveStoryDispatchMode({
|
|
|
858
929
|
? `the ${LITE_ROUTE_LABEL} label is present (hint only — the derived shape is the control signal)`
|
|
859
930
|
: `the ${LITE_ROUTE_LABEL} label is absent (hint only — the derived shape is the control signal)`;
|
|
860
931
|
|
|
932
|
+
// The ONLY `inline` exit in this function, and the guard is the whole
|
|
933
|
+
// contract: an inline verdict must mean the engine can actually run inline.
|
|
861
934
|
if (storyCount === 1) {
|
|
862
935
|
return {
|
|
863
936
|
mode: 'inline',
|
|
@@ -894,23 +967,17 @@ export function resolveStoryDispatchMode({
|
|
|
894
967
|
};
|
|
895
968
|
}
|
|
896
969
|
|
|
970
|
+
// Past the topology guard a sibling may be dispatched concurrently, so the
|
|
971
|
+
// router's session is not available to anyone. The shape is still derived and
|
|
972
|
+
// returned on `route` — ceremony and reporting read it — but it decides
|
|
973
|
+
// nothing here: both shapes dispatch as a sub-agent.
|
|
897
974
|
const route = deriveStoryRouteFromBody(body, {
|
|
898
975
|
injectedRules,
|
|
899
976
|
selectSensitivePathClassesFn,
|
|
900
977
|
});
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
hintNote,
|
|
907
|
-
],
|
|
908
|
-
route,
|
|
909
|
-
};
|
|
910
|
-
}
|
|
911
|
-
return {
|
|
912
|
-
mode: 'subagent',
|
|
913
|
-
reasons: [`full-shaped Story — ${route.reasons[0]}`, hintNote],
|
|
914
|
-
route,
|
|
915
|
-
};
|
|
978
|
+
const shapeNote =
|
|
979
|
+
route.route === 'lite'
|
|
980
|
+
? `lite-shaped Story in a multi-Story run — the shape is inline-eligible but the router's session is not: a concurrent sibling would have to share it, racing worktrees and branch refs; sub-agent dispatch (${route.reasons[0]})`
|
|
981
|
+
: `full-shaped Story — ${route.reasons[0]}`;
|
|
982
|
+
return { mode: 'subagent', reasons: [shapeNote, hintNote], route };
|
|
916
983
|
}
|