@sleepy_panda_srl/portulan 0.2.0 → 0.4.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 (100) hide show
  1. package/README.md +15 -4
  2. package/cli/README.md +61 -6
  3. package/cli/advisory.mjs +167 -53
  4. package/cli/cache-risk.mjs +69 -0
  5. package/cli/comments.mjs +8 -5
  6. package/cli/compile.mjs +781 -130
  7. package/cli/context.mjs +204 -43
  8. package/cli/doctor.mjs +248 -31
  9. package/cli/drills.mjs +56 -9
  10. package/cli/finish.md +34 -0
  11. package/cli/finish.mjs +8 -67
  12. package/cli/form.mjs +34 -18
  13. package/cli/frontmatter.mjs +39 -0
  14. package/cli/gate.mjs +14 -12
  15. package/cli/guidance-files.mjs +335 -0
  16. package/cli/handoff-policy.mjs +76 -0
  17. package/cli/html-entities.mjs +2130 -0
  18. package/cli/import-files.mjs +15 -0
  19. package/cli/index.mjs +77 -24
  20. package/cli/init-preview.mjs +553 -0
  21. package/cli/init.mjs +261 -46
  22. package/cli/instructions-journal.mjs +1185 -0
  23. package/cli/instructions.mjs +332 -57
  24. package/cli/ledger.mjs +230 -40
  25. package/cli/librarian.mjs +103 -19
  26. package/cli/persona-routing.mjs +209 -0
  27. package/cli/plugin-lint.mjs +77 -56
  28. package/cli/portulan.mjs +1 -1
  29. package/cli/recipe-output.mjs +91 -0
  30. package/cli/recipe-runner.md +68 -0
  31. package/cli/recipe-runner.mjs +131 -0
  32. package/cli/recipe-set.mjs +12 -3
  33. package/cli/review-meter.mjs +14 -9
  34. package/cli/ritual-contexts.mjs +103 -0
  35. package/cli/sessions.mjs +2 -1
  36. package/cli/spend-analysis.mjs +111 -0
  37. package/cli/spend-offer.md +53 -0
  38. package/cli/spend-offer.mjs +93 -0
  39. package/cli/stop-diagnostics.mjs +114 -0
  40. package/cli/stop-gate.mjs +133 -90
  41. package/cli/stop-policy.mjs +14 -0
  42. package/cli/symbols.mjs +55 -8
  43. package/cli/telemetry.mjs +66 -17
  44. package/cli/upgrade.mjs +62 -44
  45. package/cli/vendor.mjs +207 -125
  46. package/core/operating/context.md +401 -18
  47. package/core/operating/evolution.md +3 -2
  48. package/core/operating/loop.md +9 -4
  49. package/core/operating/sessions.md +6 -15
  50. package/core/personas/README.md +75 -0
  51. package/core/personas/implementer.md +3 -0
  52. package/core/personas/librarian.md +3 -0
  53. package/core/personas/reviewer.md +3 -0
  54. package/core/skills/clarify/SKILL.md +1 -1
  55. package/core/skills/consolidate/SKILL.md +1 -1
  56. package/core/templates/handoff.md +6 -2
  57. package/evals/README.md +23 -7
  58. package/evals/ab/personas/README.md +111 -0
  59. package/evals/ab/personas/acquisition.json +330 -0
  60. package/evals/ab/personas/bindings/implementer.txt +43 -0
  61. package/evals/ab/personas/bindings/librarian.txt +40 -0
  62. package/evals/ab/personas/bindings/reviewer.txt +41 -0
  63. package/evals/ab/personas/corpus.json +69 -0
  64. package/evals/ab/personas/grader.mjs +171 -0
  65. package/evals/ab/personas/implementer.json +774 -0
  66. package/evals/ab/personas/implementer.md +22 -0
  67. package/evals/ab/personas/librarian.json +758 -0
  68. package/evals/ab/personas/librarian.md +22 -0
  69. package/evals/ab/personas/protocol/continuation.mjs.txt +284 -0
  70. package/evals/ab/personas/protocol/first-outcome.mjs.txt +227 -0
  71. package/evals/ab/personas/protocol/first-probe.mjs.txt +172 -0
  72. package/evals/ab/personas/protocol/publisher.mjs.txt +126 -0
  73. package/evals/ab/personas/protocol/second-probe.mjs.txt +193 -0
  74. package/evals/ab/personas/repairs/first-control-answer.txt +23 -0
  75. package/evals/ab/personas/repairs/grader-before-decode-repair.mjs.txt +143 -0
  76. package/evals/ab/personas/reviewer.json +758 -0
  77. package/evals/ab/personas/reviewer.md +22 -0
  78. package/evals/releases/0.3.0.json +150 -0
  79. package/evals/releases/0.3.0.md +68 -0
  80. package/evals/releases/0.4.0.json +154 -0
  81. package/evals/releases/0.4.0.md +69 -0
  82. package/package.json +7 -2
  83. package/packs/.claude-plugin/plugin.json +2 -1
  84. package/packs/README.md +7 -4
  85. package/packs/rituals/checkpoints/README.md +35 -0
  86. package/packs/rituals/checkpoints/pack.json +3 -2
  87. package/packs/tools/README.md +8 -8
  88. package/packs/tools/local-llm/README.md +176 -0
  89. package/packs/tools/local-llm/pack.json +32 -0
  90. package/packs/tools/local-llm/personas/local-worker.md +44 -0
  91. package/packs/tools/local-llm/runner/local-llm.mjs +1779 -0
  92. package/packs/tools/local-llm/skills/delegate-local/SKILL.md +88 -0
  93. package/packs/tools/local-llm/verify/README.md +18 -0
  94. package/spec/README.md +37 -3
  95. package/spec/migrations/0009-instruction-sections-on-read.mjs +60 -11
  96. package/spec/migrations/0011-persona-bindings-compiled.mjs +23 -0
  97. package/spec/migrations/README.md +6 -0
  98. package/spec/pack.schema.json +12 -3
  99. package/spec/slots.md +272 -123
  100. package/spec/workspace.schema.json +67 -11
package/spec/slots.md CHANGED
@@ -50,7 +50,7 @@ experience a failure. *(Binding non-goal: no ceremony that can't scale down.)*
50
50
  | `slots.memory` | path | no | constitution — thesis 1 *(memory)*; [`memory.md`](../core/operating/memory.md) |
51
51
  | `slots.repos` | path | no | constitution — thesis 1 *(repo cards)*; [`repo-card.md`](../core/templates/repo-card.md) |
52
52
  | `slots.tasks` | path | no | content — [`tasks/`](../.portulan/tasks/); constitution — BMAD, the story file as atomic context unit |
53
- | `slots.handoffs` | path | no | content — [`handoffs/`](../.portulan/handoffs/); [`loop.md`](../core/operating/loop.md) — a session ending with work not committed and pushed ends with one |
53
+ | `slots.handoffs` | path | no | content — [`handoffs/`](../.portulan/handoffs/); [`loop.md`](../core/operating/loop.md) — when declared, the Stop-gate checks this series for open work; when absent, it asks to commit and push without a handoff |
54
54
  | `slots.proposals` | path | no | content — [`proposals/`](../.portulan/proposals/); [`evolution.md`](../core/operating/evolution.md) |
55
55
  | `slots.context` | path | no | criterion — milestone 12, *`compile` emits Claude Code's on-path and on-invoke targets*; [`context.md`](../core/operating/context.md) — the four load tiers |
56
56
  | `verify` | structured | **for a governing kind** | criterion — *verify recipes*; [`verification.md`](../core/operating/verification.md) — the workspace sets the default |
@@ -255,7 +255,7 @@ Two obligations, both learned from defects rather than designed in the abstract:
255
255
  ### `floor` — the platform floor this policy compiles to
256
256
 
257
257
  **Added in 2.2, optional**, inside the gate policy rather than the manifest — it is policy, and policy
258
- lives where the rules live. Four keys, and each one exists because it varies per repository and the
258
+ lives where the rules live. Its keys exist because it varies per repository and the
259
259
  floor backend would otherwise have to guess it:
260
260
 
261
261
  | Key | What it declares |
@@ -265,11 +265,15 @@ floor backend would otherwise have to guess it:
265
265
  | `reviews` | Required approving reviews. Declared rather than defaulted: 0 and 1 are a real difference, and a repository with one maintainer cannot use 1, because GitHub does not permit approving one's own pull request. |
266
266
  | `resolve_conversations` | Whether review threads must be resolved before merge. Omitting it would export a floor weaker than the one many repositories already run. |
267
267
 
268
- **What is deliberately NOT declarable is `strict`.** A pull request may not merge from behind its base
269
- ([proposal 0011](../.portulan/proposals/0011-no-merge-from-behind-main.md), applied live on
270
- 2026-07-27), so the export forces strict required status checks unconditionally. A policy able to
271
- declare `strict: false` would be a compiled artifact quietly undoing a ruling, in a diff nobody would
272
- read as one.
268
+ **Gate-policy 2.3 adds optional `strict`, a boolean defaulting to `true`.** It selects GitHub's native
269
+ strict-versus-loose required-status-check policy. Older gate-policy versions retain strict freshness
270
+ and refuse an explicit `strict` rather than silently ignoring it. A non-boolean is refused.
271
+
272
+ The earlier unconditional strict export encoded this repository's 2026-07-27 ruling as universal
273
+ mechanism. The maintainer's [2026-11-03 amendment](../.portulan/proposals/0040-allow-ten-commits-behind-main.md)
274
+ allows ten commits behind and blocks eleven here, which needs loose native freshness plus a separate
275
+ required check. The compiler emits the declared boolean; it neither implements that threshold for an
276
+ adopter nor applies the settings. Defaulting to `true` preserves every policy that omits the new key.
273
277
 
274
278
  A workspace with no `floor` compiles no floor: the backend refuses every rule *by name*, says the key
275
279
  is missing, and writes nothing. That is a legitimate shape — a workspace whose repositories are not on
@@ -304,12 +308,21 @@ demonstration that a structured slot earns being structured. Recorded in custome
304
308
  ```json
305
309
  "verify": {
306
310
  "default": "{recipe-id}",
311
+ "stopTimeout": 360,
307
312
  "recipes": [
308
313
  { "id": "{recipe-id}", "run": "{command}", "requires": ["{tool}"], "doc": "{path}" }
309
314
  ]
310
315
  }
311
316
  ```
312
317
 
318
+ `stopTimeout` is optional, added in 2.14: the Stop recipe deadline in seconds,
319
+ undeclared at 90. A whole number from 1 through 2,147,423 keeps both Node timers
320
+ in range. `doctor`, `compile` and the Stop runner refuse invalid values and
321
+ declarations below 2.14. Recompile after changing it: the outer Stop hook gets
322
+ sixty additional seconds. Both command and deadline come from the trusted
323
+ hook-told workspace; a payload cannot select executable policy. See
324
+ [execution and cap recovery](../cli/recipe-runner.md).
325
+
313
326
  `requires` exists because of an exit code. The recipes distinguish *red* (exit 1, the check ran and
314
327
  failed) from *could not run* (exit 2, a tool is missing), and that distinction is worthless if nothing
315
328
  declares what was needed. Customer zero now has two recipes with different needs — one POSIX-only, one
@@ -570,69 +583,50 @@ behind it, rather than left for a reader to assume the rail covers both halves.
570
583
  earlier, or later without a tighter cap, is the same hand edit, since it unbinds what the cap bound,
571
584
  and the same gate holds it for the same reason.
572
585
 
573
- ## `handoffs` — an index over a series nobody can consolidate
586
+ ## `handoffs` — an individual cap and a generated window
574
587
 
575
- Added at 2.5, and it is deliberately one key. `slots.handoffs` is the series; this object says where
576
- its generated index goes. The same split as `memory` above, built by the same generator. A workspace
577
- keeps its index or not, and a copy on disk says which: a kept copy is byte-compared by the same recipe
578
- and regenerated by a write; with none, `index --check` renders the series, which proves every handoff
579
- yields a line, and nothing is written. This repository keeps none since 2026-09-23: a committed copy
580
- conflicted on every merge that added a handoff.
588
+ `slots.handoffs` names the append-only series. `handoffs.index.path`, required whenever `handoffs`
589
+ is present, names the derived index outside that series. A kept copy is byte-compared and regenerated;
590
+ with none, `index --handoffs` prints it on demand. Without budgets the full series renders as before.
581
591
 
582
592
  | Field | What it is |
583
593
  |---|---|
584
- | `index.path` | Where the generated index is written. Must resolve **outside** `slots.handoffs` — see below. |
585
-
586
- `index` is **required whenever `handoffs` is present**, which is the one way this object differs from
587
- `memory`. There an object with no `index` is coherent: a workspace may rail its store's size and
588
- generate nothing. Here there is no budget and exactly one key, so `handoffs: {}` configures nothing —
589
- a no-op that reads as configured to anyone who greps for it, the same failure
590
- `librarian.staleness.proposal_days` without `slots.proposals` is refused for. Unlike the two
591
- conditional requirements below, this one *is* expressible in the declared subset, so the schema
592
- carries it rather than `doctor`.
593
-
594
- **Why it exists.** [`loop.md`](../core/operating/loop.md) has said since the handoff-cadence rule landed
595
- that *"the librarian that mines the series is milestone 5"*, and the series had no index at all while
596
- the store it sits beside had one. Measured on this repository the day the milestone-5 row was amended:
597
- the handoff series was **3.4×** the memory store. The layer with the index was the small one.
598
-
599
- **Every field on a line is derived, and the two carriers differ from the store's on purpose.** A
600
- record's title is its **filename** — [`memory-entry.md`](../core/templates/memory-entry.md) prescribes
601
- no heading, and most records carry none, so the filename is the only title every record has. A
602
- handoff's title is its **H1**: the filename leads with an ISO date and continues as a slug, so a
603
- filename-derived title reads `2026 07 28 the librarian goes on a cron` — a string no reader navigates
604
- by and no cross-reference uses. Every handoff in this repository carries an H1, so the heading is the
605
- carrier that actually exists here, and one that does not is **failed** rather than titled from its
606
- filename: a generated line whose title the generator invented is the one thing a generated file must
607
- not contain. The date is derived from the filename, which is where the cadence rule already puts it.
608
-
609
- **Its siting rule has two enforcers rather than one, and neither is redundant.** An index inside
610
- `slots.handoffs` is either a dated handoff to `docs.sh`'s `record` check or, carrying no date, failed
611
- by that check for exactly that. `index` refuses the siting outright, because it cannot render a series it would then be
612
- a member of. The second enforcer is not a second opinion: the generator physically cannot proceed,
613
- while the recipe is what catches a hand-placed file the generator never saw.
614
-
615
- **There is no budget here, and the absence is the argued half.** A budget's only permitted remedy is
616
- consolidation — merge, compress, split, retire ([`memory.md`](../core/operating/memory.md)) — and a
617
- handoff series is append-only by construction. Retiring a handoff to buy headroom destroys the record
618
- it exists to keep; **splitting one** — the move 2.8 added for the per-record rail — leaves the series
619
- no smaller; and raising the budget in the change that breached it is the one repair that doctrine rules out. Every
620
- remedy such a budget could ask for is barred, which makes it a rail designed to be broken — and a rail
621
- that fires with no legal repair is how a whole recipe gets switched off.
622
-
623
- _That test is the one the store's own aggregate byte budget later failed — which is why **this
624
- repository's workspace** swapped `kilobytes` for the per-record cap at 2.8 rather than widening it. The
625
- definition replaced nothing: both keys stay legal and a workspace may declare either. The argument this
626
- paragraph makes about handoffs is the argument `0025` makes about `kilobytes`, and it was written down
627
- before the case arrived._ Whether
628
- the series should be railed on some other axis is a **maintainer's question**, deferred to the
629
- reconciliation that follows milestone 5 rather than pre-answered here in the shape of an unused key.
630
-
631
- **What the pass reads that this object does not configure.** Record ages come from git for both series,
632
- and the handoff series is **reported** — count, oldest, total size — with no threshold and no demotion
633
- draft. That is not an omission: `librarian.staleness.record_days` drafts a demotion, and drafting one
634
- against an append-only series would recommend the single repair the paragraph above rules out, weekly,
635
- forever.
594
+ | `series.budget.record_tokens` | Optional positive integer cap, added in 2.15, counted at explicit `context.ratio`. |
595
+ | `series.budget.cutoff` | Required real YYYY-MM-DD date beside the cap; only later handoffs are bound. |
596
+ | `index.path` | Generated index path, outside `slots.handoffs`. |
597
+ | `index.budget.tokens` | Optional positive integer budget on the complete rendered index, added in 2.15. |
598
+
599
+ Accepted [proposal 0037](../.portulan/proposals/0037-a-handoff-is-budgeted-where-it-is-written.md)
600
+ keeps the series unbudgeted: deleting history cannot repair a size rail. An individual new handoff can
601
+ still be compressed before merge, or facts moved to their owning records and linked. Never split a
602
+ handoff, delete a decision's why, raise the cap or move the cutoff to repair a breach. The cutoff is set
603
+ at the declaring change's date or newest merged handoff's date, whichever is later; the cutoff day
604
+ itself stays unbound. Tightening the cap or lowering the ratio may advance it to exempt merged history;
605
+ it never moves backward. Review holds these history-dependent rules and prevents backdating new work.
606
+
607
+ `index` checks filename dates wherever a series is declared, with or without an index or cap: a real
608
+ YYYY-MM-DD prefix, no more than one day beyond UTC today. An H1 is required only when rendering an
609
+ index; titles come from that heading, dates from filenames. Each handoff's full UTF-8 byte length,
610
+ corrections included, is converted with the same rounded bytes/ratio estimate as the context report.
611
+ Every bound overage is red, with its count, cap, excess and repair; pre-cutoff overages are notes.
612
+ Either token budget without an explicit ratio is refused by doctor and direct index calls.
613
+
614
+ A budgeted index lists a contiguous newest-first prefix of whole lines. Header, separators, omission
615
+ notice and trailing newline all count. The notice names the omitted count and source directory; no
616
+ source record moves or disappears and no second index level exists. If the newest entry cannot fit,
617
+ none are listed. A budget too small even for the header and notice is refused with its minimum.
618
+ Neither series nor index is part of a boot or the always tier. Context and doctor report count, total,
619
+ median, the three largest and rendered index bytes/tokens even without a tree, naming estimated or
620
+ declared ratio and bound versus historical overages. Init offers independently optional 3000-token
621
+ budgets, requires a supplied ratio on acceptance and computes the cutoff at write time from the same
622
+ regular Markdown entries and filename-date boundary the index uses. Unrelated files cannot advance it;
623
+ invalid selected dates or file types, or an uninspectable series, stop record-cap adoption before
624
+ writing. Supplied ratio provenance is not proof of host calibration. Upgrade adopts neither policy
625
+ nor ratio automatically.
626
+
627
+ The librarian still ages the series from git and drafts no staleness demotion against it: retiring
628
+ history is no repair. The session-end gate checks existence and today's date only; the cap is the
629
+ index verify rail. The one-day cutoff gap and history-dependent no-raise rules remain review duties.
636
630
 
637
631
  ## `personas` — where a pack-declared memory scope lands, and how anyone can tell it did
638
632
 
@@ -709,8 +703,8 @@ one source for what each load tier holds; this object is its machine half, as `m
709
703
  | Field | What it is |
710
704
  |---|---|
711
705
  | `always.budget.tokens` | The most tokens the always tier may load, counted at the ratio below. |
712
- | `ratio.bytes_per_token` | Bytes per token, as a host's exact count measured them. At least 1. |
713
- | `ratio.calibrated_by` | The host whose exact count calibrated the ratio, by the id `compile` gives it: `claude-code`. |
706
+ | `ratio.bytes_per_token` | Manually declared bytes per token for the host's measured domain. At least 1; provider estimates are qualified below. |
707
+ | `ratio.calibrated_by` | The declared compile-host/domain ID. Doctor matches `claude-code` or `agents-md` to its reported format; a different or unknown valid ID is labeled as extrapolated. Provider/model provenance belongs in the count report. |
714
708
 
715
709
  ```json
716
710
  "context": {
@@ -725,30 +719,48 @@ it. Those are dependencies the subset can state, so they are the schema's and jo
725
719
  conditionals. A ratio alone is a legitimate shape: it is what a report in tokens needs, and the default
726
720
  is the report.
727
721
 
728
- **Declared, not measured per run.** The exact count asks the host, which is a network call no verify
729
- recipe may make, and a ratio that moved between runs would make one tree red on one run and green on the
730
- next. So the ratio is measured exactly on demand and declared, with the host that measured it. There is no
731
- date field, because the commit that changes the ratio dates it, and one ratio rather than one per host,
732
- because nothing yet reads a second.
722
+ **Declared, not measured per run.** On-demand counting is a network call no verify recipe may make,
723
+ and a ratio that moved between runs would make one tree red on one run and green on the next. There is
724
+ no date field, because the commit that changes the ratio dates it. One declared ratio is shared by the
725
+ two measured project formats. Reports compare `calibrated_by` with the format ID (`claude-code` or
726
+ `agents-md`); a nonmatching or unknown ID makes that use an explicit extrapolation. The declaration
727
+ still accepts any valid host slug. No second per-host ratio is declared.
728
+
729
+ For [#506](https://github.com/sleepy-panda-srl/portulan/issues/506), Marius accepted “Provider count with
730
+ stated limit” on 2026-11-09. `doctor --count-tokens MODEL_ID` and the direct context diagnostic submit
731
+ the measured Claude Code project corpus to Anthropic, which returns an estimate that may differ from actual host
732
+ usage and includes API framing. The effective ratio is approximate and manual; neither command writes
733
+ this object or invents a `calibrated_by` provider ID. An empty corpus produces no request or ratio.
734
+ The original exact-host wording is historical acceptance, not a claim of current host integration.
735
+ See [the counting boundary](../core/operating/context.md#the-always-tier-is-budgeted-in-tokens).
733
736
 
734
737
  **Named for its tier.** Only the always tier is budgeted, and the budget sits under `always` so a budget
735
738
  on a later tier would sit beside it rather than force a rename, which would be a MAJOR.
736
739
 
737
740
  **Nothing is defaulted**, on memory's rule: an undeclared budget is not checked. `doctor` refuses a
738
741
  declared token budget that is not a positive integer, as it refuses the memory budgets, and a ratio under
739
- 1, because a token covers at least one byte and a smaller figure is tokens per byte entered inverted.
742
+ 1, guarding against inverted units. Provider framing can put a tiny corpus below that floor; such a
743
+ ratio is reported but cannot be declared.
740
744
  Both are `doctor`'s because the subset has no `minimum` and cannot say `integer`. `doctor` also refuses
741
745
  `context` in a manifest declaring a version before 2.9, whose own validator refuses it as unknown: the
742
746
  key is gated from birth, when no manifest can newly fail the check.
743
747
 
744
748
  **What checks the budget itself.** `doctor` measures the always tier of every workspace that declares
745
- a `tree`, through [`../cli/context.mjs`](../cli/context.mjs), and fails it over a declared budget, or
746
- where a declared budget cannot be judged; with none declared it reports the figure. _(This paragraph read
749
+ a `tree`, through [`../cli/context.mjs`](../cli/context.mjs), for Claude Code and root vendored AGENTS.md.
750
+ Doctor reports both automatically and judges the same declared cap independently for each, never
751
+ summing separate contexts. AGENTS includes the file's inline guidance and pointer text without opening
752
+ targets or expanding at-sign text; personal, above-tree, nested and plugin files are excluded. An absent
753
+ optional root is zero; present unmeasurable sources, including an escaping AGENTS root, are reported
754
+ unmeasured. A declared budget then cannot be judged and fails. Without a budget, measurement does not
755
+ change the verdict. The default context/boot line and optional provider request stay Claude-specific;
756
+ doctor validates both applicable budgets before requesting that count. _(This paragraph read
747
757
  *"What nothing checks yet: the budget itself"* until that report landed, and it was true then:
748
758
  [`a-mandate-nothing-checks-is-already-broken.md`](../.portulan/memory/a-mandate-nothing-checks-is-already-broken.md)
749
- is why no workspace here declared the key before something checked it.)_ No workspace in this tree
750
- declares it now either: Portulan's own contribution is railed by this repository's recipes, not by a
751
- budget in its manifest. And, as with memory, no checker establishes that a budget was not raised in the
759
+ is why no workspace here declared the key before something checked it.)_ Neither this repository's
760
+ workspace nor the demo workspace declares it: Portulan's own contribution is railed by this repository's
761
+ recipes, not by a budget in its manifest. The synthetic
762
+ [budget demonstration fixture](../cli/fixtures/context-budget/) does declare one. And, as with memory,
763
+ no general checker establishes that a budget was not raised in the
752
764
  change that breached it.
753
765
 
754
766
  ## `sessions` — the host switches that change what a fresh session writes
@@ -771,17 +783,19 @@ Added at **2.11**, from `0038`'s rules 3 and 4 and row 12's cache-lifetime compi
771
783
  ```
772
784
 
773
785
  **Nothing is defaulted.** An undeclared switch is the host's own default and `compile` emits nothing for
774
- it, so a manifest without the key compiles byte for byte as before. A switch is declared once an A/B run
775
- has shown that it cuts spend with no loss, and [`sessions.md`](../core/operating/sessions.md) says which
786
+ it, so an existing policy-backed manifest without the key keeps the same settings bytes. A switch is
787
+ declared once an A/B run has shown that it cuts spend with no loss, and [`sessions.md`](../core/operating/sessions.md) says which
776
788
  sessions keep what a switch removes.
777
789
 
778
790
  **Two scopes, because the host has two.** `git_instructions` and `cache_lifetime` are project settings on
779
- Claude Code, so `compile` writes them into the settings it already generates from `gates`, and they reach
791
+ Claude Code, so `compile` writes them into its native settings, independently of a gate policy, and they reach
780
792
  every session there, interactive or not. The exclusion is no setting at all: Claude Code 2.1.281 takes it
781
793
  only as a command-line flag or an SDK option, so only whatever starts a session can ask for it. `headless`
782
794
  is therefore read by Portulan's own runners that start sessions, today [`../cli/warm.mjs`](../cli/warm.mjs),
783
- which the package does not ship, and never compiled. A workspace with no gate policy has no settings
784
- for the first two to ride, and `compile` says that it compiled neither.
795
+ which the package does not ship, and never compiled. Full compilation of an authored `repository`,
796
+ `demo` or `portfolio` workspace also emits native restart advice and these declared switches without a gate policy. The settings contain no permissions
797
+ or verification Stop hook; explicit `spend.restart: "block"` adds only the advisory Stop command.
798
+ Missing manifests and pointers do not become governing workspaces.
785
799
 
786
800
  **Offered to an adopter, and still never defaulted** (2026-09-24). `init` offers `cache_lifetime` `"5m"`
787
801
  with what it saves and what a pause costs, asking once at a terminal or taking `--cache-lifetime`, and on
@@ -807,6 +821,7 @@ workspace computes it at, and what a crossed threshold does.
807
821
  | `multipliers.write["5m"]` | What a five-minute cache write costs, as the same multiple. At least 1. |
808
822
  | `multipliers.write["1h"]` | What a one-hour cache write costs, as the same multiple. At least 1. |
809
823
  | `horizon.requests` | The requests still to go at which the restart threshold is judged. A positive integer. |
824
+ | `resume.tokens` | Additional restart expense in uncached-input-token equivalents, finite and nonnegative; absent means unknown. Added in 2.16. |
810
825
  | `restart` | `"advise"` or `"block"`: what a crossed threshold does. Undeclared, `"advise"`. |
811
826
 
812
827
  ```json
@@ -817,36 +832,40 @@ workspace computes it at, and what a crossed threshold does.
817
832
  }
818
833
  ```
819
834
 
820
- **What reads it.** A session is told to end into a handoff once its context passes
821
- `C* ≈ F × (1 + m_w / (n × m_r))`: `F` the fresh context, `m_r` the read multiplier, `m_w` the write multiplier
822
- at the lifetime the host recorded for the session's writes (the five-minute one where the records do not say),
823
- and `n` the horizon. [`../cli/ledger.mjs`](../cli/ledger.mjs) reads the key from `<dir>/workspace.json` when
824
- run with `--workspace <dir>`, and computes every threshold its report states at it. The restart advisory,
825
- [`../cli/advisory.mjs`](../cli/advisory.mjs), is handed its command and the host's payload and nothing else,
826
- so `compile` writes the figures onto each of its commands, the `PostToolUse` and `UserPromptSubmit` hooks, the
827
- status line and, where `restart` declares it, the block, as `--read`, `--write-5m`, `--write-1h` and
828
- `--horizon`, and its drift check holds them to the manifest: an edit to `spend` is drift until recompiled. A
829
- workspace with no gate policy has no settings for them to ride, and `compile` says that it compiled nothing.
830
- Every threshold the ledger or the advisory prints says whether its multipliers are declared, and so does a
831
- ledger report with no threshold to judge.
832
-
833
- **Declared, not defaulted.** Undeclared, both price at the general multipliers, a read at a tenth and writes
834
- at 1.25× and 2×, over a horizon of 20 requests, and say `undeclared`. That is ruling 2's answer to a spread no
835
- one figure fits: a model's cache reads cost between a fortieth and a tenth of an uncached input token. The
836
- general tenth puts the restart line early, which is the cheaper way to err, since a restart that comes too
837
- soon costs a fresh write and a handoff, and erring the other way, where reads cost a tenth, costs about four
838
- times more in excess reads over 20 requests. A declaration makes a team's contracted rates and its host's real
839
- prices one value. Either half may be declared alone, and the other keeps its general figures. The three
840
- multipliers are one set, because a threshold priced by one declared figure and two general ones would be
841
- priced by nobody's.
835
+ **What reads it.** `cli/ledger.mjs --workspace <dir>` and the compiled advisory
836
+ commands compute `C* = F + (F × m_w + R) / (n × m_r)`. Here `F` is the first
837
+ recorded context after startup or compaction, `n` requests remaining, and `R` is
838
+ `spend.resume.tokens` (optional since **2.16**). `R` is additional restart expense
839
+ in uncached-input-token equivalents, excluding the existing fresh-context write
840
+ and baseline reads. Include handoff output, boot/state recovery and additional
841
+ rereads over the selected horizon. These are weighted cost units: 2,000 output
842
+ tokens at 5× contribute 10,000 equivalents. A finite nonnegative number, including
843
+ explicit zero, is priced; absent is unknown and the reported threshold is only a
844
+ lower bound. No automatic predictor fills it.
845
+
846
+ `compile` writes `--read`, `--write-5m`, `--write-1h`, `--horizon` and
847
+ `--resume-tokens` on all advisory commands where declared, with drift checked.
848
+ A harness may supply its task's estimates on these commands; no task-file resolver
849
+ exists. A horizon is requests **remaining**, does not decrement automatically and
850
+ must be revised by the caller. For example, `spend.horizon: {"requests": 3}` and
851
+ `spend.resume: {"tokens": 10000}` price a short task's expected remainder.
852
+
853
+ **Declared, not defaulted.** Undeclared multipliers remain read 0.1× and writes
854
+ 1.25×/2×, with a default horizon of 20. Advisory, status and ledger name the horizon
855
+ and its origin, the multipliers and priced/unknown resume cost. Unknown cost or a
856
+ default horizon produces conditional advice, never a claim that restarting must
857
+ cost less. The caller supplies estimates; no configuration is silently installed.
842
858
 
843
859
  **`restart`: a block, only where declared.** `"advise"` is the advisory every workspace has: the line once,
844
860
  with a tool result or at a prompt. `"block"` keeps that line and adds a block at the end of a turn: at the
845
- first stop whose last recorded request is at or past the threshold and that no block provoked, the advisory's
846
- own `stop` command, which `compile` writes as a second `Stop` command beside the Stop-gate's, holds the turn's
847
- end with the line as its reason, once in a session and again after each compaction, and the turn goes on. A
848
- stop that a block provoked is never held, since a host that gives that retry a new session id would find no
849
- record of the hold and hold it again. It is the advisory's command rather than a reason inside the Stop-gate,
861
+ first stop with an explicit horizon and resume expense, finite strictly positive
862
+ estimated savings `n × read × (C − F) − write × F − R`, and no preceding hook
863
+ block. Equality never holds, and comparison uses unrounded arithmetic rather than
864
+ the displayed whole-token threshold. Without both estimates an existing block
865
+ declaration reports conditionally without holding. Retry protection uses the
866
+ host's `stop_hook_active` marker; omitting it while rotating session identities
867
+ can repeat holds. A hold is once per session and again after each compaction.
868
+ It is the advisory's command rather than a reason inside the Stop-gate,
850
869
  so the gate's counters and caps stay about the recipe and the handoff; and nothing ends the session, which
851
870
  stays the agent's or the human's act. `0038`'s *Enforcement* says the block comes *instead* of the line, and
852
871
  it is read here as *added*, because the line is what reaches an agent mid-stretch and a headless run's only
@@ -854,17 +873,20 @@ stop is its last (the coordinator session's delegated call of 2026-09-24); where
854
873
  the line, the block costs one request. Ruling 3 makes the block a workspace's own choice, so `init` asks
855
874
  nothing about it and `upgrade` offers nothing: the threshold is an estimate, and one priced at the general
856
875
  multipliers comes early for a model whose cache reads cost less than a tenth, where a block would hold a turn
857
- short of its real line. Undeclared or `"advise"`, the settings compile byte for byte as before; where
876
+ short of its real line. In the existing policy-backed path, undeclared or `"advise"` settings retain their bytes; where
858
877
  `"block"` writes the command, `compile` says so on every run.
859
878
 
860
- **What is not here.** Ruling 2 has `init` offer the declaration from the host's own pricing setting where the
861
- host exposes one, else from a dated per-host table in the engine. Both are keyed by the model a host records,
862
- so both wait for their own change: until then `init` offers no figures, and a workspace writes its own by
863
- hand. Nothing checks a declared figure against what the workspace is billed; keeping it true is the declaring
864
- workspace's work.
879
+ **Offered by `init`, never written without acceptance.** For Claude Code's last recorded main-session
880
+ model in this repository, `init` offers a representable file-based managed `modelPricing` override,
881
+ else its dated vendor table, else the general multipliers. It offers the horizon independently as
882
+ requests still to go. `--multipliers '<JSON>'` and `--horizon <requests>` (or the same string answers in
883
+ `--answers`) declare either key without an interview; a pointer refuses both. Accepting either drafts
884
+ spec 2.12. The [pricing offer](../cli/spend-offer.md) names the supported IDs, matching and file-source
885
+ limits. No network request is made. Nothing checks a declaration against a bill or a future model;
886
+ keeping it true is the declaring workspace's work.
865
887
 
866
888
  **Checked by the schema and by `doctor`, and refused by every reader.** The schema holds the shape: both
867
- halves of `multipliers`, both lifetimes of `write`, and no key it does not name. The subset types the four
889
+ halves of `multipliers`, both lifetimes of `write`, and no key it does not name. The subset types the numeric
868
890
  figures only as `number`, so `doctor` holds the read above 0 and at most 1, each write at least 1 and the
869
891
  horizon to a positive integer by hand: a token read from cache costs something and never more than the same
870
892
  token sent uncached, and writing one costs at least what sending it uncached does. Figures each in range can
@@ -872,11 +894,15 @@ still give no threshold, since it divides each write by the read, so `doctor` al
872
894
  divides past the largest number, which would put the restart line at Infinity, where no session reaches it.
873
895
  The ledger, `compile` and the advisory hold the same ranges themselves rather than trusting that `doctor` ran.
874
896
  The first two stop with exit 2; the advisory, whose exit 2 would erase the person's prompt, says once on
875
- stderr what it could not use and prices that half at the general figures. A threshold that a session's fresh
897
+ stderr what it could not use and reports conditionally with fallback figures; any invalid flag prevents a Stop hold. An invalid resume estimate stays unknown. A threshold that a session's fresh
876
898
  context still carries past the largest number is none: the ledger stops with exit 2, and the advisory says it
877
899
  is not known. `doctor` refuses the key in a manifest declaring a version before 2.12, gated from birth as
878
- `sessions` was, and `restart` in one before 2.13. `compile` refuses `restart` there too, with exit 2 and
900
+ `sessions` was, `restart` in one before 2.13, and `resume` before 2.16. Doctor and compile check the finite nonnegative resume bound independently. `compile` refuses `restart` there too, with exit 2 and
879
901
  nothing written, so that no block is compiled from a declaration its own version's validator refuses.
902
+ For declared `restart`, the compiler requires a string `MAJOR.MINOR` on major 2, minor at least 13;
903
+ missing or malformed versions and other majors stop with exit 2 before artifacts change. The compiler
904
+ still accepts future minors on major 2 for this key; this is not a general manifest-version check or
905
+ a claim that `doctor` accepts those versions.
880
906
  `restart` is one of two strings, which the subset types in full, and the ledger and `compile` refuse any other
881
907
  with exit 2.
882
908
 
@@ -903,14 +929,15 @@ The guidance itself, which is what the host loads.
903
929
  | Key | What it is |
904
930
  |---|---|
905
931
  | `tier` | `always`, `on-path`, `on-invoke` or `on-read`: `0036`'s four tiers, spelled as that proposal spells them. |
906
- | `paths` | On-path only, and required there: globs, relative to the repository the workspace governs, that load the unit when the agent first touches a match. A list, in flow form as above or as a block list of `- ` items. |
932
+ | `paths` | On-path only, as an alternative to `target`: globs, relative to the repository the workspace governs, that load the unit when the agent first touches a match. A list, in flow form as above or as a block list of `- ` items. |
933
+ | `target` | On-path only, as an alternative to `paths`: a literal child-directory `CLAUDE.md`, such as `api/CLAUDE.md`. Added within the existing 2.10 unit contract to complete accepted `0036`; older compilers reject this new unit field, so upgrade the compiler before using it. |
907
934
  | `description` | Every tier but `always`, and one line: what an agent reads to decide whether to open the unit. It is a skill's description on a host that lists skills, and the pointer line wherever the unit degrades to one. |
908
935
 
909
936
  **The tier is declared, never inferred**, and a unit sits in one tier, so what a unit costs is what its
910
937
  tier costs and nothing has to read the file to find out where it belongs.
911
938
 
912
939
  **`compile` emits it, per host.** For Claude Code, an `always` unit is an unscoped rule, loaded into every
913
- context; an `on-path` unit is a rule scoped by `paths:`; an `on-invoke` unit is a project skill, whose
940
+ context; an `on-path` unit is either a rule scoped by `paths:` or the nested instruction file declared by `target`; an `on-invoke` unit is a project skill, whose
914
941
  description is always loaded and whose body loads when it is invoked; and an `on-read` unit is one line in
915
942
  an unscoped index of pointers, naming its file, its size in whole KB and its description (the size since
916
943
  2026-09-24, so a session knows what it passes over), while its file stays where it is for the agent to
@@ -923,8 +950,54 @@ each rule it wrote: it rewrites or removes only a rule the marker lists, stops w
923
950
  replace one it does not list, and stops too where the directory holds Markdown files and no marker in its
924
951
  form. It writes and removes nothing through a link.
925
952
 
953
+ **Nested targets use two portable ownership records.** The first-line HTML mark binds the workspace,
954
+ source and canonical target relative to the project; `.claude/rules/portulan/.instructions` pairs that source with its
955
+ target. Both are committed, so clones can regenerate and check the same files. A missing owned file or
956
+ changed body is repairable drift (`--check` exits 1). A human file, missing/torn/foreign mark or registry,
957
+ link, nonregular output or ambiguous case-only move refuses before compilation writes anything.
958
+ Complete atomic writes and exclusive new claims prevent a pending target from replacing a human file
959
+ that arrives after planning. Intermediate registry rows retain old and new ownership until publication
960
+ and stale cleanup finish, so a later run can recover an interrupted move. Only proved-owned files are
961
+ retired when a target, tier or unit changes. Split/join rollback checks post-write bytes and file
962
+ identity, recreates a deleted file exclusively, and preserves later human changes while reporting
963
+ incomplete recovery. Complete prepared temporary files are recognized only by their reserved names
964
+ and validated ownership records or destination-bound marks, and removed on retry. Malformed or
965
+ unproved debris is preserved for manual review. Impossible stable multi-source records are refused.
966
+ When a target is absent after interruption, agreeing proved artifacts retain its observed mode.
967
+ Those witnesses and their source ownership survive until replacement publication, including a
968
+ source rename and another interruption. Disagreeing modes refuse. A new target without an observed
969
+ entry or proved permission witness uses ordinary creation permissions.
970
+ Existing physical path spelling must match the declared target; distinct case-sensitive paths remain
971
+ independent. Owned target sets use one spelling for every shared directory prefix, including pending
972
+ and retired rows. Init projects the atomic writer's parent-directory permissions, not a file-write check.
973
+ Read-only checking requires no mutation permission, and stable nested output
974
+ produces no registry transition. Source slots and compiler control directories are reserved.
975
+
976
+ A target has 1–32 child directories, each at most 128 ASCII characters, starting with a letter or digit,
977
+ then letters, digits, dots, underscores, spaces or hyphens; no trailing dot/space, device names, globs,
978
+ backslashes, traversal or a directory named `CLAUDE.md` (case-insensitive). That last exclusion
979
+ prevents a target from becoming another target's ancestor file. The full path is at most 1,024 characters and ends exactly `CLAUDE.md`.
980
+ This bounded portable subset avoids platform-dependent ownership aliases. Two units cannot claim one
981
+ target, including a spelling differing only in case. `target` and `paths` are mutually exclusive.
982
+ Nested bodies refuse actionable at-sign imports using the shared lexer, including absent, absolute,
983
+ home and target-relative paths, without probing them. Supported code spans and fences remain literal.
984
+ The body therefore cannot acquire a new import by moving beside a different file.
985
+
986
+ The on-path claim assumes Claude starts at the project root. A session started inside the subtree may
987
+ load its ancestor `CLAUDE.md` at startup; an explicit root import can load it eagerly too. Project-root
988
+ budget accounting excludes unimported nested files and includes files explicitly imported by its roots.
989
+ See [Claude's loading contract](https://code.claude.com/docs/en/memory#how-claude-md-files-load).
990
+ Prospective compilation includes pending pre-existing guidance and measures its after corpus when
991
+ representable. Native absolute imports may withhold that optional figure; a successful write measures
992
+ the actual emitted result. An explicitly imported nested body can change that domain.
993
+ Vendor switches recheck observed identities and bytes before retiring material, preserve changed or
994
+ newly arrived files, and never recursively remove the old residence. Their residue advice uses the
995
+ manifest's actual governed tree.
996
+ The nested file's pure body is emitted once; its derived subtree scope is used for hosts' pointer
997
+ fallbacks. The compiler does not emit a second scoped rule for it.
998
+
926
999
  **Checked by `compile`, not by the schema or `doctor`.** The subset cannot see inside a file, so the
927
- frontmatter is its consumer's to refuse: an unknown key, a tier outside the four, `paths` on a unit that is
1000
+ frontmatter is its consumer's to refuse: an unknown key, a tier outside the four, `paths` or `target` on a unit that is
928
1001
  not on-path or reaching outside the repository, a missing `description` or one on an `always` unit, a unit
929
1002
  with no guidance under its frontmatter, a link out of the workspace or into a directory `compile` writes into,
930
1003
  a slot in one of those (`.claude/`, or the workspace's `compile/`, neither of which `vendor` carries), a name
@@ -951,6 +1024,73 @@ in it still reads from the repository root, as it did in `CLAUDE.md`.
951
1024
  puts every section back as it stands, saying which one the digest shows edited since, and refusing a unit
952
1025
  re-tiered since, or a file not at the top of this slot, rather than undo its tier or remove it.
953
1026
 
1027
+ **The CLI split and join can resume an interrupted source migration.** Before claiming a source,
1028
+ `instructions --write` records the exact original operation in the reserved project-root directory
1029
+ `.portulan-instructions-journal` (mode 0700, metadata 0600). A retry with the original split or join
1030
+ command restores held originals and proves the saved operation again with the same planner before
1031
+ continuing. Units precede split roots; join roots precede unit retirement. Exact bytes, modes and
1032
+ recorded identities distinguish the operation's own publications from an independent save, even an
1033
+ identical replacement. A conflict preserves both entries and names the retained journal or claim.
1034
+ The intended whole source set and regenerated guidance must agree before commit is recorded.
1035
+ An unchanged or absent second root remains a dependency of the proof, rechecked before source edits
1036
+ and completion. Initial observations of both roots and original operands must be ordinary singly
1037
+ linked entries before a journal is created; an unsupported alias refuses without leaving a journal.
1038
+ A newly arrived reference cannot authorize retirement of the unit it names. Live references include
1039
+ moved markers and actionable native `@` imports, resolved relative to each root with the import lexer;
1040
+ inline and fenced examples stay literal. Normalized in-project paths and file or directory aliases
1041
+ are compared with direct operands even when the unit is absent. Reference discovery reads the two
1042
+ roots, without traversing imported bodies. Read-only split/join runs the structural source checks
1043
+ before offering a write; join checks both prospective root images for references to units it would
1044
+ retire. Write-time checks still revalidate those inputs. Split, join and upgrade form `0009` validate
1045
+ prospective source edits through the shared isolated
1046
+ guidance view before offering a write. The view applies creations and deletions together, recompiles
1047
+ all guidance and measures the resulting Claude corpus, including generated leads and index imports.
1048
+ Its compiler projection origin preserves engine/adopter identity and maps contained absolute lead
1049
+ and gate dependencies into the view. Aliases retain their directory entries and resolve to the same
1050
+ projected bytes, including dangling links after retirement. The view checks its emitted result again,
1051
+ retains retirements as absence through aliases, including case spellings first seen after emission,
1052
+ and distinguishes separate hard-link entries. Optional native directory aliases use the final file's
1053
+ project membership, including operands spelled outside and routes returning from outside. Their
1054
+ original lexical directory remains the base for later imports; outside bodies are not copied.
1055
+ Optional native non-files and missing imports follow
1056
+ the corpus reader; denied reads and required inputs remain checked. Linked ordinary outputs refuse
1057
+ a migration that would change or retire them; native compilation refuses to rewrite such an output,
1058
+ including a retiring marker's transitional write, before gate or persona publication. It derives
1059
+ the actual workspace, context slot, ownership and packs without reading another host's
1060
+ unrelated root. Unchanged read-only files retain their permissions. A deleted input cannot be copied
1061
+ back from the original tree by a later dependency lookup. Unrepresentable native absolute imports
1062
+ withhold only the optional numerical after forecast; successful writes measure the actual result.
1063
+ Publication snapshots retain their modes through rollback. If a changed root cannot be restored,
1064
+ rollback retains its dependent units and reports the pending operation instead of removing their text.
1065
+ Before unlinking a unit, the journal confirms its acquired public entry and pairs that fact with the
1066
+ exact claim intent, distinguished from preparation aliases. After interruption, a proved retired unit
1067
+ still referenced by either root is recreated exclusively when its path is absent, before the retry
1068
+ refuses an independently edited root. Handled failures restore the current attempt's safe images;
1069
+ the journal remains until the whole original operation has reached a proved terminal state.
1070
+ The saved proof contains only the two root observations and directly involved units; unrelated
1071
+ context bodies used to forecast index growth are not copied into the bounded journal.
1072
+
1073
+ This is a local process-interruption protocol, demonstrated with SIGKILL, rather than
1074
+ a power-loss durability guarantee. Exclusive immutable lease children permit one live writer or
1075
+ recoverer in the same verified boot/PID namespace; a live, inaccessible or reused PID prevents
1076
+ takeover. Another namespace refuses. A cut after a source claim converges automatically. Unproved
1077
+ partial metadata, an unrecorded preparation file (including a retry killed before its first durable
1078
+ record), an ownerless initializer or an empty directory left after final cleanup can require
1079
+ manual review of the exact reported file or directory: preserve any source or claim bodies, then remove only
1080
+ the reviewed residue and rerun the original command. At initialization the source set is original;
1081
+ at final cleanup it is fully committed. Do not delete a pending journal merely to bypass its refusal.
1082
+
1083
+ Read-only `instructions` reports a pending migration without changing it. Guidance compilation/check
1084
+ refuses a partial source set; a complete source set may still report stale generated guidance until
1085
+ the retry compiles it. `init`, `vendor` and `upgrade` refuse a pending journal; vendor also refuses
1086
+ instruction claim/staging residue rather than copying complete source bodies into another residence.
1087
+ The journal cannot overlap a current declared slot, checked again during recovery and terminal cleanup.
1088
+ Vendor refuses reserved journal paths found in its inventory and rechecks pending operations before
1089
+ publication and governance retirement. This recovery promise covers the CLI split/join only;
1090
+ upgrade form `0009` shares the source plan and guarded writes but has no process-restart journal.
1091
+ The distinction keeps the recovery record confined to this operation instead of creating a general
1092
+ workspace transaction format. Provenance: #629's independent source-interruption review.
1093
+
954
1094
  **An `always` unit may import whole files and write out another file's lead sentences or a gate policy's
955
1095
  gates**, so a card carries a fact without holding a second copy of it:
956
1096
 
@@ -967,6 +1107,15 @@ gates**, so a card carries a fact without holding a second copy of it:
967
1107
  a path-scoped rule's included. The vendored `AGENTS.md` carries each import as a pointer line to the
968
1108
  file in the vendored tree, and one for each file that file imports in turn, to the depth the host would
969
1109
  load (2026-09-24), and `vendor` refuses an import leaving the workspace, which is all that tree holds.
1110
+ **The shared reader refuses unsupported import-bearing regions by name**
1111
+ ([#471](https://github.com/sleepy-panda-srl/portulan/issues/471)): inline emphasis,
1112
+ escape or tag adjacency; code spans on comment-opening lines or across lines;
1113
+ HTML blocks, indented code and indented fences; blockquoted lists; and list
1114
+ continuations after nested lists, tags, indented code or HTML blocks. Refusal
1115
+ replaces a guessed import set, including in nested files and instruction
1116
+ rewriting. Syntax without an import and unrelated imports outside those regions
1117
+ retain their supported behavior. This is bounded validation, not general
1118
+ CommonMark or live-host lexer parity.
970
1119
  - **A line `<!-- leads: <path> -->`, alone on its line, is replaced by the first sentence of each item in
971
1120
  the first list of the file it names**, each item opening with a bold lead. The file stays the one
972
1121
  source of those sentences, so a change there is drift until the unit is recompiled. `compile` refuses