@mgiles/perk 1.0.1 → 2.0.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.
Files changed (157) hide show
  1. package/README.md +67 -57
  2. package/extension/adapters/planAdapterPlannotator.ts +27 -41
  3. package/extension/adapters/planAdapterTombell.ts +15 -28
  4. package/extension/adapters/todoAdapterJuicesharp.ts +10 -13
  5. package/extension/checkpoints/checkpoints.ts +21 -13
  6. package/extension/doors/address.ts +6 -5
  7. package/extension/doors/askUser.ts +12 -8
  8. package/extension/doors/ciExecutor.ts +23 -15
  9. package/extension/doors/hunkHandoff.ts +202 -0
  10. package/extension/doors/land.ts +33 -10
  11. package/extension/doors/learn.ts +240 -34
  12. package/extension/doors/learnFactory.ts +144 -0
  13. package/extension/doors/lifecycleGates.ts +2 -1
  14. package/extension/doors/plannotatorHandoff.ts +509 -0
  15. package/extension/doors/prReview.ts +22 -37
  16. package/extension/doors/prReviewBrowser.ts +341 -0
  17. package/extension/doors/prReviewTerminal.ts +267 -0
  18. package/extension/doors/ready.ts +2 -1
  19. package/extension/doors/selfcheck.ts +240 -6
  20. package/extension/doors/submit.ts +37 -19
  21. package/extension/doors/submitPrReview.ts +408 -0
  22. package/extension/factories/implementHere.ts +116 -0
  23. package/extension/factories/objective.ts +17 -6
  24. package/extension/factories/objectiveAuthor.ts +15 -32
  25. package/extension/factories/objectiveDraft.ts +1 -1
  26. package/extension/factories/objectivePlan.ts +16 -34
  27. package/extension/factories/objectiveSave.ts +7 -17
  28. package/extension/factories/planMode.ts +24 -38
  29. package/extension/factories/planReview.ts +291 -177
  30. package/extension/factories/planSave.ts +23 -8
  31. package/extension/index.ts +117 -20
  32. package/extension/substrate/bindingDelivery.ts +32 -10
  33. package/extension/substrate/bindings.ts +4 -2
  34. package/extension/substrate/cache.ts +36 -9
  35. package/extension/substrate/clipboard.ts +81 -0
  36. package/extension/substrate/command.ts +25 -0
  37. package/extension/substrate/config.ts +98 -76
  38. package/extension/substrate/consoleCapture.ts +90 -0
  39. package/extension/substrate/git.ts +77 -0
  40. package/extension/substrate/miniJinja.ts +480 -0
  41. package/extension/substrate/paths.ts +38 -0
  42. package/extension/substrate/prompts.ts +15 -24
  43. package/extension/substrate/providers.ts +62 -8
  44. package/extension/substrate/sessionData.ts +1 -1
  45. package/extension/substrate/sessionPointers.ts +184 -0
  46. package/extension/substrate/structuredOutput.ts +3 -1
  47. package/extension/substrate/terminalLaunch.ts +178 -0
  48. package/extension/substrate/toolGating.ts +332 -73
  49. package/extension/substrate/toolParams.ts +7 -0
  50. package/extension/substrate/workflowState.ts +54 -2
  51. package/extension/surfaces/footerProvider.ts +8 -4
  52. package/extension/surfaces/surfaces.ts +338 -13
  53. package/extension/vendor/btw/btw.ts +10 -0
  54. package/extension/worker/readOnlySession.ts +19 -6
  55. package/extension/worker/worker.ts +187 -31
  56. package/extension/workerMain.ts +14 -13
  57. package/package.json +3 -7
  58. package/prompts/README.md +56 -5
  59. package/prompts/_fixtures/cases.yaml +52 -131
  60. package/prompts/_fixtures/golden/cond_elif-a.txt +3 -0
  61. package/prompts/_fixtures/golden/cond_elif-b.txt +3 -0
  62. package/prompts/_fixtures/golden/cond_elif-c.txt +3 -0
  63. package/prompts/_fixtures/golden/cond_if-false.txt +3 -0
  64. package/prompts/_fixtures/golden/cond_if-true.txt +3 -0
  65. package/prompts/_fixtures/golden/cond_ops-1.txt +3 -0
  66. package/prompts/_fixtures/golden/cond_ops-2.txt +3 -0
  67. package/prompts/_fixtures/golden/no_trailing_nl.txt +2 -0
  68. package/prompts/_fixtures/golden/trailing_nl.txt +2 -0
  69. package/prompts/_fixtures/golden/trim_block.txt +3 -0
  70. package/prompts/_fixtures/golden/trim_inline.txt +1 -0
  71. package/prompts/_fixtures/live.yaml +396 -0
  72. package/prompts/_fixtures/templates/cond_elif.md +9 -0
  73. package/prompts/_fixtures/templates/cond_if.md +7 -0
  74. package/prompts/_fixtures/templates/cond_ops.md +3 -0
  75. package/prompts/_fixtures/templates/no_trailing_nl.md +2 -0
  76. package/prompts/_fixtures/templates/trailing_nl.md +2 -0
  77. package/prompts/_fixtures/templates/trim_block.md +5 -0
  78. package/prompts/_fixtures/templates/trim_inline.md +1 -0
  79. package/prompts/contexts/adapters/juicesharp-todo.md +7 -0
  80. package/prompts/contexts/adapters/plannotator-objective.md +7 -0
  81. package/prompts/contexts/adapters/plannotator-plan.md +6 -0
  82. package/prompts/contexts/adapters/tombell-plan.md +17 -0
  83. package/prompts/contexts/objective-authoring.md +20 -0
  84. package/prompts/contexts/plan-authoring.md +24 -0
  85. package/prompts/contexts/read-only.md +10 -0
  86. package/prompts/stages/conflict-resolution.md +4 -0
  87. package/prompts/stages/learn-code.md +8 -0
  88. package/prompts/stages/learn-docs.md +7 -6
  89. package/prompts/stages/learn-orchestrate.md +6 -0
  90. package/prompts/stages/learn.md +1 -1
  91. package/prompts/stages/objective-author/adopt.md +12 -0
  92. package/prompts/stages/objective-author/file.md +9 -0
  93. package/prompts/stages/objective-author/seed.md +9 -0
  94. package/prompts/stages/objective-plan/guidance.md +1 -1
  95. package/prompts/stages/objective-plan/seed.md +3 -2
  96. package/prompts/stages/objective-reconcile.md +7 -0
  97. package/prompts/stages/objective-replan.md +14 -0
  98. package/prompts/stages/objective-save.md +9 -0
  99. package/prompts/stages/plan-from/adopt.md +10 -0
  100. package/prompts/stages/plan-from/file.md +9 -0
  101. package/prompts/stages/pr-review-browser/active.md +11 -0
  102. package/prompts/stages/pr-review-browser/foreign.md +11 -0
  103. package/prompts/stages/pr-review-terminal/active.md +12 -0
  104. package/prompts/stages/pr-review-terminal/foreign.md +13 -0
  105. package/prompts/stages/pr-review-terminal/local.md +4 -0
  106. package/prompts/stages/pr-review.md +6 -0
  107. package/prompts/stages/replan.md +13 -0
  108. package/prompts/stages/skills/create-from.md +15 -0
  109. package/prompts/stages/skills/create.md +9 -0
  110. package/prompts/stages/skills/refine.md +9 -0
  111. package/shared/README.md +23 -13
  112. package/shared/bindings.yaml +22 -2
  113. package/shared/contracts-history.md +191 -0
  114. package/shared/contracts.md +2258 -1638
  115. package/shared/providers.yaml +8 -1
  116. package/shared/registry.yaml +10 -11
  117. package/shared/schemas/contracts/bindings.schema.json +38 -0
  118. package/shared/schemas/contracts/providers.schema.json +89 -0
  119. package/shared/schemas/contracts/registry.schema.json +98 -0
  120. package/shared/schemas/inputs/handoff-arg.schema.json +6 -0
  121. package/shared/schemas/inputs/resolve-threads-batch.schema.json +37 -0
  122. package/shared/schemas/inputs/review-post-batch.schema.json +84 -0
  123. package/shared/schemas/inputs/review-submit-batch.schema.json +66 -0
  124. package/shared/schemas/inputs/structured-roadmap-node.schema.json +102 -0
  125. package/shared/schemas/outputs/doctor-report.schema.json +236 -0
  126. package/shared/schemas/outputs/init-report.schema.json +419 -0
  127. package/shared/schemas/outputs/learn-capture.schema.json +90 -0
  128. package/shared/schemas/outputs/learn-skip.schema.json +59 -0
  129. package/shared/schemas/outputs/plan-save.schema.json +209 -0
  130. package/shared/schemas/outputs/pr-feedback.schema.json +334 -0
  131. package/shared/schemas/outputs/pr-land.schema.json +187 -0
  132. package/shared/schemas/outputs/pr-ready.schema.json +75 -0
  133. package/shared/schemas/outputs/pr-review-checkout.schema.json +69 -0
  134. package/shared/schemas/outputs/pr-review-cleanup.schema.json +54 -0
  135. package/shared/schemas/outputs/pr-review-context.schema.json +86 -0
  136. package/shared/schemas/outputs/pr-review-submit.schema.json +64 -0
  137. package/shared/schemas/outputs/pr-submit.schema.json +147 -0
  138. package/extension/doors/learnDocs.ts +0 -99
  139. package/prompts/_fixtures/golden/address-action-model.txt +0 -10
  140. package/prompts/_fixtures/golden/address-action.txt +0 -10
  141. package/prompts/_fixtures/golden/address-preview-model.txt +0 -6
  142. package/prompts/_fixtures/golden/address-preview.txt +0 -6
  143. package/prompts/_fixtures/golden/implement-github.txt +0 -8
  144. package/prompts/_fixtures/golden/learn-docs.txt +0 -8
  145. package/prompts/_fixtures/golden/learn-github.txt +0 -11
  146. package/prompts/_fixtures/golden/learn-linear.txt +0 -11
  147. package/prompts/_fixtures/golden/learn-no-ref.txt +0 -8
  148. package/prompts/_fixtures/golden/learn-other.txt +0 -8
  149. package/prompts/_fixtures/golden/objective-plan-guidance-linear.txt +0 -8
  150. package/prompts/_fixtures/golden/objective-plan-guidance.txt +0 -8
  151. package/prompts/_fixtures/golden/objective-plan-seed-linear.txt +0 -20
  152. package/prompts/_fixtures/golden/objective-plan-seed.txt +0 -15
  153. package/prompts/_fixtures/golden/objective-read-linear-nourl.txt +0 -1
  154. package/prompts/_fixtures/golden/objective-read-linear.txt +0 -1
  155. package/prompts/_fixtures/golden/plan-read-github.txt +0 -1
  156. package/prompts/_fixtures/golden/plan-read-linear.txt +0 -1
  157. package/prompts/_fixtures/golden/plan-read-other.txt +0 -1
@@ -20,8 +20,10 @@
20
20
  # trigger — the "<kind>:<id>" string above.
21
21
  # skill — the skill name (a skills/*/ dir name today).
22
22
  # mode — per-binding delivery mode:
23
- # nudge — deliver a short pointer to follow the named skill (the skill
24
- # body stays ambient / Pi-discovered).
23
+ # nudge — deliver a short pointer to follow the named skill. The pointer
24
+ # carries the skill's read path (.agents/skills/<skill>/SKILL.md):
25
+ # perk's own stage skills are hidden from the ambient system prompt
26
+ # (disable-model-invocation: true) and are reached only through it.
25
27
  # transclude — inline the skill body into the prompt/context.
26
28
  # mode is per-binding (not global): one skill may be a nudge at one trigger
27
29
  # and a transclude at another.
@@ -56,9 +58,27 @@ bindings:
56
58
  - trigger: "command:objective-reconcile"
57
59
  skill: perk-objective-reconcile
58
60
  mode: nudge
61
+ - trigger: "command:objective-replan"
62
+ skill: perk-objective-replan
63
+ mode: nudge
59
64
  - trigger: "command:learn-docs"
60
65
  skill: perk-learn-docs
61
66
  mode: nudge
67
+ - trigger: "command:learn-code"
68
+ skill: perk-learn-code
69
+ mode: nudge
62
70
  - trigger: "command:pr-review"
63
71
  skill: perk-pr-review
64
72
  mode: nudge
73
+ - trigger: "command:pr-review-terminal"
74
+ skill: perk-pr-review-terminal
75
+ mode: nudge
76
+ - trigger: "command:pr-review-browser"
77
+ skill: perk-pr-review-browser
78
+ mode: nudge
79
+ - trigger: "command:skills-create"
80
+ skill: perk-skill-author
81
+ mode: nudge
82
+ - trigger: "command:skills-refine"
83
+ skill: perk-skill-author
84
+ mode: nudge
@@ -401,3 +401,194 @@ bundled — the Python wheel as package data `perk/_shared/`, the npm package un
401
401
  > selection** — it is pi-web-access-specific (it depends on `fetch_content`'s GitHub-clone path),
402
402
  > documented and not re-homed. Catalog entries carry no `package_filter` (each package's sole
403
403
  > extension is its root `./index.ts`, verified via `npm view <pkg> pi`).
404
+ >
405
+ > **Status (review — the sixth seam, the first DISPATCH seam; RETIRED 2026-07, Objective #1261
406
+ > Node 4.1):** a sixth seam, **`review`**, existed from the toolbox objective's nodes 1.x through
407
+ > 4.1 and is now **retired**. Its shape while live: the first **DISPATCH posture** — no adapter
408
+ > (the seam produces no durable artifact) and nothing to vacate (perk owned no prior guest-review
409
+ > surface); the selection drove **protocol dispatch** inside a dispatching `/review` door — which
410
+ > review surface the door drove (`hunk`, the default: the hunk session CLI handshake, with perk's
411
+ > `submit_pr_review` tool as the only posting path; or `plannotator-review`: the `code-review`
412
+ > `pi.events` bridge behind an `open_plannotator_review` tool plus the agent-driven
413
+ > external-annotations HTTP stream, with the UI's native platform-posting as the GitHub path) and
414
+ > which posting path was primary. The default `hunk` was the first `default: true` entry whose
415
+ > substrate is an **external CLI** (npm `hunkdiff`) rather than a Pi package;
416
+ > `plannotator-review` shared `npm:@plannotator/pi-extension` with `plannotator-plan`, the
417
+ > cross-seam instance of the desired-**union** package convergence. **The retirement (Node 4.1):**
418
+ > once the surface-named doors (`/pr-review-terminal`, `/pr-review-browser`) absorbed both arms
419
+ > (nodes 2.1–3.1), the dispatch had no job left — the command IS the selection. `/review`, its
420
+ > dispatch, `open_plannotator_review`, and the seam rows were deleted; `SEAMS`/`PROVIDER_SEAMS`
421
+ > shrank back to five; the retired `[providers] review` key got the legacy-tripwire treatment
422
+ > (hard-fail on the Python plane, silent-ignore on the TS plane); the hunk-CLI init/doctor
423
+ > handling was already unconditional (a review *surface*, not a selection consequence) and is
424
+ > unchanged; the plannotator package is desired via `plannotator-plan` alone (the union mechanism
425
+ > stays generic, its only cross-seam instance retired). `submit_pr_review` re-homed to
426
+ > `extension/doors/submitPrReview.ts` with an unchanged contract; the `perk-review` skill split
427
+ > into `perk-pr-review-terminal` + `perk-pr-review-browser`.
428
+
429
+ ## §8.31 · The prompt render seam + golden parity (Objective #791, Node 1.2)
430
+
431
+ **First prompt moved onto the seam — the plan-read instruction (Node 2.1).** The cross-plane
432
+ plan-read instruction (the "how do I read the saved plan" SSOT) is the first real (non-fixture)
433
+ consumer of the render seam. Its three arm templates live at
434
+ `prompts/common/plan-read/{github,linear,other}.md` — one file per provider arm, no
435
+ conditionals/loops in the frozen subset. **Branching stays in code**: `perk/run/launch/prompts.py::
436
+ _plan_read_instruction` and `extension/doors/lifecycleGates.ts::planReadInstruction` keep their
437
+ `(provider, pr_id/prId, url)` signature and the same if/elif/else, each arm now a `render(...)` call
438
+ selecting its arm template (passing `{pr_id, url}`; jinja2/nunjucks ignore unused vars). The helpers
439
+ still branch on `cache.plan-ref.provider` — only the **wording source** moved.
440
+
441
+ The arm templates (and their golden files) carry **no trailing newline** — the helper returns
442
+ single-line strings embedded mid-prompt, so the render output must equal the prior literal exactly
443
+ (a deliberate departure from the fixture convention of trailing newlines). The three `plan-read-*`
444
+ golden cases in `cases.yaml` prove cross-plane byte-identity for each arm; a thin per-arm selection
445
+ test in each plane (`tests/test_worker_prompt_parity.py`, `extension/doors/lifecycleGates.test.ts`)
446
+ proves the code picks the right arm and `render()` is wired. This golden-fixture parity (plus the
447
+ selection tests) **replaces the prior dedicated substring parity** for plan-read; the
448
+ implement/learn prompt parity suites are untouched (they embed the byte-identical helper output, so
449
+ they keep passing — the downstream prompts move in nodes 2.2/2.4).
450
+
451
+ **Second prompt moved — the implement primer (Node 2.2).** The implement-stage primer wording lives
452
+ at `prompts/stages/implement.md`, the second real consumer of the render seam. All three sites that
453
+ used to hand-duplicate it — cold `perk/run/launch/prompts.py::_implement_prompt`, worker
454
+ `extension/worker/worker.ts::initialPromptFor` (implement arm), and warm
455
+ `extension/doors/lifecycleGates.ts::implementHandoffPrompt` — are now thin `render("stages/
456
+ implement.md", {provider, pr_id, url, read_cmd})` calls. The prior warm/cold variance (the warm
457
+ handoff omitting the "Progress markers:" tail) is reconciled by **unifying**: all three render the
458
+ one template with the same vars, so they are **byte-identical** and the warm handoff now carries the
459
+ progress markers too. `read_cmd` is the provider-selected plan-read instruction computed in code via
460
+ the Node-2.1 helper — branching stays in code, no `{% if %}`/second template. The template and its
461
+ golden (`implement-github`) carry **no trailing newline** (matching the prior cold/worker literal).
462
+ One golden case proves the template renders identically in both planes; thin per-plane composition
463
+ tests (start-with / contains read_cmd / ends-with the progress tail) prove each helper wires the
464
+ right template + vars — together these **replace `IMPLEMENT_SUBSTRINGS`**. The pre-objective audit
465
+ `docs/design/prompt-language-audit.md` (still describing warm as a shorter near-copy) is left as a
466
+ frozen snapshot; this paragraph is the authoritative current-state note.
467
+
468
+ **The address prompt moved onto the seam — converging three consumers (Node 2.3).** The
469
+ address-stage wording lives in two canonical templates `prompts/stages/address/{action,preview}.md`
470
+ (each a complete body, no template logic; vars `{{ provider }}`, `{{ pr_id }}`, `{{ url }}`,
471
+ `{{ model_clause }}`), rendered identically by **all three** address consumers via the shared
472
+ render seam: the cold `perk/run/launch/prompts.py::_address_prompt`, the worker
473
+ `extension/worker/worker.ts::initialPromptFor("address")`, and the warm
474
+ `extension/doors/address.ts::addressGuidance`. Before this node the warm `/address` loop used a
475
+ *different* wording; the three were **converged** onto one canonical body — the cold/worker
476
+ structure (the PR-identity header a fresh headless worker needs) **plus** warm's Plan File Mode
477
+ step, which now upgrades the cold/worker path too; warm loses its divergent framing. This is a
478
+ deliberate wording change to all three surfaces; the *command/flag/config* surface of `/address`
479
+ and `perk pr address` is unchanged.
480
+
481
+ **Branching stays in code** (the frozen subset has no conditionals): preview vs action is a
482
+ template *selection* (`preview.md` for `--preview`, which omits the action steps including Plan
483
+ File Mode; `action.md` otherwise), and the classifier present/absent split builds the
484
+ `model_clause` render var in code (empty string when no `[subagents] review-classifier` model) —
485
+ the clause's own wording is deferred to node 3.3. The worker has **no preview path** (preview is a
486
+ warm/cold flag), so it always renders `action.md`.
487
+
488
+ **The warm door is now ref-aware and null-guarded.** The converged body carries the PR identity, so
489
+ `addressGuidance` takes the active `PlanRef`; the `/address` handler resolves it via the same
490
+ helper `doors/learn.ts` uses (`readPlanRef(ctx.cwd)` → fallback
491
+ `rebuildWorkflowState(branchOf(ctx)).active_plan_ref`). A null ref reports a `warning` (mirroring
492
+ the `/implement` guard) and sends no guidance — a strict improvement, since `/address` cannot
493
+ function without a plan-ref regardless (the classifier child's `perk pr feedback` hard-errors
494
+ `no_plan_ref`).
495
+
496
+ The two address templates (and their golden files) carry **no trailing newline** (the builders
497
+ return mid-prompt strings). Four `address-*` golden cases in `cases.yaml` (action/preview × model
498
+ present/absent) prove cross-plane byte-identity; thin per-plane selection tests prove each caller
499
+ picks the right template and injects/omits the model clause, and the warm null-ref guard is
500
+ covered. This golden-fixture parity **replaces the prior `ADDRESS_SUBSTRINGS` substring parity**.
501
+
502
+ **The learn primer moved onto the seam (Node 2.4).** The learn-stage primer wording moved off its
503
+ two hand-concatenated twins onto the render seam — one canonical `prompts/stages/learn.md` rendered
504
+ byte-identical by cold `perk/run/launch/prompts.py::_learn_prompt` and warm
505
+ `extension/doors/learn.ts::learnGuidance` (learn has **no worker twin** — only cold + warm). Cold
506
+ and warm are **unified onto the cold body**: warm `/learn` wording changed from its prior numbered
507
+ "perk /learn —" style to the cold bullet "You are in the learn step…" body, the `other` arm
508
+ collapsed to a single "Open the plan and its merged change" line (warm **lost** its prior `other`
509
+ merged-PR derivation — an accepted change for the effectively-unreachable provider arm), and warm's
510
+ no-plan-ref fallback folded into the same template. This node is the **first template to use
511
+ conditionals**: the `{% if pr_id %}` header split and the no-ref / github+linear / other structure
512
+ selection are the template's conditional on `provider` (+ `pr_id` presence); the provider read-line
513
+ text is supplied as the `read_cmd` var from the node-2.1 plan-read helper (`_plan_read_instruction`
514
+ / `planReadInstruction`), `read_cmd` passed always (empty string when absent) so it is defined. The
515
+ template keeps each `{% if %}`/`{% elif %}`/`{% else %}`/`{% endif %}` tag on its **own line** (off
516
+ the content lines) — enabled by the `trim_blocks` env flip above, which swallows the single newline
517
+ after each block tag so the indented bullet content renders intact (whitespace-control `{%- -%}`
518
+ markers alone could not — they also strip the bullets' leading indentation). The
519
+ template and all four golden files carry **no trailing newline** (matching the cold literal). Four
520
+ `learn-*` golden cases in `cases.yaml` (`learn-github`, `learn-linear`, `learn-other`,
521
+ `learn-no-ref`) prove cross-plane byte-identity and **replace the dedicated learn substring parity**;
522
+ thin per-plane selection/composition tests remain. nunjucks stays the TS engine — the golden suite
523
+ is the byte-parity proof that jinja2 and nunjucks render the conditional template identically (the
524
+ tag-hugging whitespace discipline keeps them equal with `trim_blocks`/`lstrip_blocks` off).
525
+
526
+ **The objective-read instruction moved onto the seam (Node 2.5).** The cross-plane objective-read
527
+ clause (the supplemental wording telling the model how to inspect a Linear-Project-backed
528
+ objective's node-issues) moved off its two hand-duplicated twins onto the render seam, mirroring the
529
+ plan-read move. The wording lives in a subdirectory at `prompts/common/objective-read/linear.md` —
530
+ one arm file for the **linear** arm only (github and any non-linear backend return `""` directly in
531
+ code without rendering, since `perk objective show` already covers them). **Branching stays in
532
+ code**: `perk/cli/commands/objective/shared.py::objective_read_instruction` and
533
+ `extension/factories/objectivePlan.ts::objectiveReadInstruction` keep their `(backend,
534
+ objective_id/objectiveId, url)` signature and the `backend != "linear" → ""` early return; the
535
+ linear arm computes the two **url-presence** render vars `where`/`fallback` in code (the frozen
536
+ subset has no conditionals — mirroring the `model_clause` precedent) and renders the one template.
537
+
538
+ The template (and its golden files) carry **no trailing newline** — the helper returns a single-line
539
+ string embedded mid-prompt, so the render output must equal the prior literal exactly (the
540
+ `_seed_prompt`/`factoryGuidance`/`reconcileGuidance` composition tests embed it and keep passing).
541
+ Two `objective-read-*` golden cases in `cases.yaml` (the linear arm, both url sub-variants) prove
542
+ cross-plane byte-identity; the empty github/other arm stays code-only (no render → no golden) and is
543
+ covered by the per-plane selection tests. Per-plane selection tests in each plane
544
+ (`tests/test_objective_prompt_parity.py`, `extension/factories/objectivePlan.test.ts`) prove the
545
+ code picks the right arm + computes where/fallback. This golden-fixture parity **replaces the prior
546
+ `OBJECTIVE_LINEAR_SUBSTRINGS` substring lockstep** (which remains only as a local constant for the
547
+ per-plane + seed-composition tests, no longer a cross-plane invariant). The `_seed_prompt` /
548
+ `factoryGuidance` / `reconcileGuidance` body moves are deferred to Node 2.6.
549
+
550
+ **The objective-plan factory seed + warm guidance moved onto the seam (Node 2.6).** The two
551
+ hand-built objective-plan-factory prompt bodies — the **cold** seed
552
+ (`perk/cli/commands/objective/plan_cmd.py::_seed_prompt`) and the **warm** guidance
553
+ (`extension/factories/objectivePlan.ts::factoryGuidance`) — moved onto the render seam as the sixth
554
+ real consumer. Unlike the implement (2.2) / learn (2.4) moves, they are **NOT unified**: the cold
555
+ seed launches a *fresh* read-only session, so it **injects** the objective title + node description
556
+ (the `<untrusted_objective>` block) and the pre-planning node-engagement block as DATA, and its node
557
+ is already marked `planning` by the cold door; the warm guidance runs *in-session*, so it
558
+ **instructs** the model to fetch the objective + node engagement and to mark the node `planning`
559
+ itself. This **cold-injects / warm-instructs** asymmetry makes them genuinely different bodies, so
560
+ they become **two arm files in a subdirectory** — `prompts/stages/objective-plan/{seed,guidance}.md`
561
+ (filenames mirror the function names) — like 2.1/2.3/2.5 landed despite singular node titles. The
562
+ **branching moved INTO the templates** as `{% if %}` conditionals (the learn-2.4 pattern, enabled by
563
+ `trim_blocks`): block-level tags on their own lines (the cold engagement block, the warm
564
+ node-selection line) and inline tags mid-line (the read clause, the explorer/model clause). The
565
+ helpers now pass **raw** vars — `node_engagement` (the rendered block, `""` when absent),
566
+ `read_clause` (the rendered linear clause, `""` for github/other), `model` (`""` when unset), and
567
+ (warm) `node` (`""` → select-next) — while the in-code arm SELECTION
568
+ (`objective_read_instruction` / `objectiveReadInstruction` backend logic) is unchanged. Both
569
+ templates and their golden files carry **no trailing newline** (the prior literals had none). Four
570
+ `objective-plan-*` golden cases in `cases.yaml` (seed/guidance × github/linear) prove cross-plane
571
+ byte-parity across both arms of every conditional. The per-plane composition tests are **retained**
572
+ (`OBJECTIVE_LINEAR_SUBSTRINGS` survives as a local constant feeding the per-plane selection +
573
+ seed-composition tests); no cross-plane substring lockstep existed between the two different prompts,
574
+ so none is removed.
575
+
576
+ **The learned-docs factory seed + warm guidance moved onto the seam (Node 2.7).** The two
577
+ hand-built learned-docs-factory prompt bodies — the **cold** seed
578
+ (`perk/cli/commands/learn/docs_cmd.py::_seed_prompt`) and the **warm** guidance
579
+ (`extension/doors/learnDocs.ts::learnDocsGuidance`) — moved onto the render seam as the seventh real
580
+ consumer. **Unlike 2.6 they are UNIFIED** (the implement-2.2 / learn-2.4 pattern): the cold/warm
581
+ differences were all **superficial factory house-style** — header wording, a header blank line,
582
+ step-number indentation, a cold-only "from this read-only session" qualifier, and the
583
+ closing-paragraph phrasing — none load-bearing, so they were **converged away** onto the **cold-seed
584
+ orientation form** rather than preserved behind conditionals. The warm guidance gained the "You are
585
+ running…" header + the standalone closing paragraph ("Judgment, user interaction, and durable writes
586
+ stay with you — never delegate them."), and the cold seed lost the `" "` step indent + the "from
587
+ this read-only session" qualifier (the warm session is not read-only, so the qualifier was
588
+ cold-only-accurate anyway; the bare "NEVER write the docs directly" is correct in both planes). The
589
+ result is a single **flat** template `prompts/stages/learn-docs.md` with **zero `{% if %}`
590
+ conditionals**; both planes pass the same two vars (`inbox_path`, `num_list`). The template and its
591
+ golden carry **no trailing newline**. One `learn-docs` golden case in `cases.yaml` proves cross-plane
592
+ byte-parity. No cross-plane substring lockstep existed between cold and warm, so none is removed; the
593
+ per-plane composition tests are retained (one warm header assertion updated from `"perk /learn-docs"`
594
+ to `"learned-docs plan factory"`).