@sleepy_panda_srl/portulan 0.1.3 → 0.3.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 (89) hide show
  1. package/README.md +7 -6
  2. package/cli/README.md +145 -358
  3. package/cli/advisory.mjs +354 -0
  4. package/cli/comments.mjs +932 -0
  5. package/cli/compile.mjs +1719 -1699
  6. package/cli/context.mjs +883 -0
  7. package/cli/control-chars.mjs +12 -301
  8. package/cli/discover.mjs +17 -574
  9. package/cli/doctor.mjs +269 -1080
  10. package/cli/drills.mjs +92 -504
  11. package/cli/feedback.mjs +16 -237
  12. package/cli/finish.mjs +437 -0
  13. package/cli/form.mjs +644 -0
  14. package/cli/fuzz-shell.mjs +20 -305
  15. package/cli/gate.mjs +7 -199
  16. package/cli/goldens.mjs +26 -184
  17. package/cli/index.mjs +305 -655
  18. package/cli/init.mjs +243 -566
  19. package/cli/inside.mjs +3 -28
  20. package/cli/instructions.mjs +829 -0
  21. package/cli/ledger.mjs +882 -0
  22. package/cli/librarian.mjs +135 -599
  23. package/cli/manifest.mjs +2 -39
  24. package/cli/mutants.mjs +9 -170
  25. package/cli/new.mjs +12 -243
  26. package/cli/pack-identity.mjs +5 -74
  27. package/cli/pack-version.mjs +13 -255
  28. package/cli/plugin-lint.mjs +42 -658
  29. package/cli/portulan.mjs +6 -125
  30. package/cli/recipe-set.mjs +15 -245
  31. package/cli/release-eval.mjs +28 -348
  32. package/cli/review-meter.mjs +29 -265
  33. package/cli/rule-carriers.mjs +12 -175
  34. package/cli/sessions.mjs +82 -0
  35. package/cli/skill-goldens.mjs +10 -176
  36. package/cli/skills-set.mjs +12 -414
  37. package/cli/stop-gate.mjs +83 -514
  38. package/cli/symbols.mjs +1132 -0
  39. package/cli/telemetry.mjs +82 -505
  40. package/cli/upgrade.mjs +171 -314
  41. package/cli/vendor.mjs +146 -476
  42. package/cli/version-carriers.mjs +8 -86
  43. package/core/README.md +1 -1
  44. package/core/engine.md +3 -9
  45. package/core/operating/autonomy.md +1 -1
  46. package/core/operating/context.md +258 -0
  47. package/core/operating/evolution.md +3 -3
  48. package/core/operating/loop.md +13 -20
  49. package/core/operating/memory.md +12 -3
  50. package/core/operating/sessions.md +100 -0
  51. package/core/personas/README.md +33 -0
  52. package/core/personas/librarian.md +2 -2
  53. package/core/skills/clarify/SKILL.md +1 -1
  54. package/core/skills/consolidate/SKILL.md +5 -3
  55. package/core/templates/handoff.md +28 -15
  56. package/core/templates/memory-entry.md +1 -0
  57. package/evals/README.md +271 -845
  58. package/evals/releases/0.2.0.json +146 -0
  59. package/evals/releases/0.2.0.md +67 -0
  60. package/evals/releases/0.3.0.json +150 -0
  61. package/evals/releases/0.3.0.md +68 -0
  62. package/evals/releases/README.md +3 -3
  63. package/package.json +5 -2
  64. package/packs/.claude-plugin/plugin.json +2 -1
  65. package/packs/README.md +7 -4
  66. package/packs/rituals/checkpoints/README.md +1 -1
  67. package/packs/tools/README.md +8 -8
  68. package/packs/tools/github/verify/actions-pinned.sh +10 -55
  69. package/packs/tools/local-llm/README.md +136 -0
  70. package/packs/tools/local-llm/pack.json +32 -0
  71. package/packs/tools/local-llm/personas/local-worker.md +44 -0
  72. package/packs/tools/local-llm/runner/local-llm.mjs +1665 -0
  73. package/packs/tools/local-llm/skills/delegate-local/SKILL.md +64 -0
  74. package/packs/tools/local-llm/verify/README.md +18 -0
  75. package/spec/README.md +113 -6
  76. package/spec/migrations/0001-repository-declares-its-tree.mjs +1 -26
  77. package/spec/migrations/0002-bundle-fallback-path.mjs +13 -74
  78. package/spec/migrations/0003-handoff-index-not-kept.mjs +66 -0
  79. package/spec/migrations/0004-changelog-fragments.mjs +53 -0
  80. package/spec/migrations/0005-session-log-retired.mjs +69 -0
  81. package/spec/migrations/0006-boot-card.mjs +77 -0
  82. package/spec/migrations/0007-guidance-compiled.mjs +31 -0
  83. package/spec/migrations/0008-card-reading.mjs +55 -0
  84. package/spec/migrations/0009-instruction-sections-on-read.mjs +60 -0
  85. package/spec/migrations/0010-comment-rail.mjs +57 -0
  86. package/spec/migrations/README.md +45 -10
  87. package/spec/pack.schema.json +1 -1
  88. package/spec/slots.md +343 -27
  89. package/spec/workspace.schema.json +145 -4
@@ -0,0 +1,100 @@
1
+ # Sessions
2
+
3
+ > Core doctrine — on-read. Every fresh context pays for the host's own system prompt and tools, and for
4
+ > the always tier, at the cache's write price, unless an earlier session left that prefix in the cache.
5
+ > On Claude Code that prefix comes before any workspace file, and cache writes were most of what a task
6
+ > cost on this repository ([`../../evals/ab/warm.md`](../../evals/ab/warm.md) has the figures). So a task
7
+ > runs in as few fresh contexts as it can, each starts warm where it can, the cache lifetime fits the gap
8
+ > between uses, and a host switch that changes the prefix is earned by an A/B run.
9
+ > [`context.md`](context.md) governs what the host loads; this page governs how sessions start and end
10
+ > around it. _(Proposal [`0038`](../../.portulan/proposals/0038-what-a-change-spends-is-measured.md), rules
11
+ > 2 to 4, and row 12 of [`../../docs/plan.md`](../../docs/plan.md), whose compile targets carry the cache
12
+ > lifetime.)_
13
+
14
+ ## A session starts warm, or it pays the prefix again
15
+
16
+ **A fresh session starts warm when an earlier one left the same prefix in the cache**: the same host
17
+ version, tools, model and effort, the same instruction files and rules, the same working directory, and
18
+ nothing that varies per session ahead of the task, within the cache lifetime. A warm start reads the
19
+ prefix at the read price; a cold one writes it again. Sessions running side by side in one checkout
20
+ already share it. Sessions that follow one another share it only while nothing between them changed it,
21
+ and on a local checkout the host's startup git snapshot changes with every commit.
22
+
23
+ What moves the break earlier is anything per session in front of the task: a git snapshot, a date, a
24
+ working directory made fresh for each run, a hook's per-session output, a session's own id. Rule 3 of
25
+ `0038` keeps these out of Portulan's own always tier and hook text. A hosted session whose system prompt
26
+ names the session itself starts cold whatever a workspace compiles; that is inferred from such a prompt,
27
+ not documented.
28
+
29
+ ## Fewer fresh contexts per task
30
+
31
+ - **Continue until the restart threshold, then restart.** `0038`'s threshold is computed from the multipliers,
32
+ the fresh context and a horizon, which a workspace declares in `spend` (Workspace Definition 2.12) and which
33
+ are the general ones where it does not, and [`../../cli/advisory.mjs`](../../cli/advisory.mjs) says it once.
34
+ A restart that finds its prefix still cached costs less than the threshold assumes.
35
+ - **Resume a finished worker within its cache lifetime** rather than start a new one: its context is
36
+ read again rather than written again.
37
+ - **A subagent is a fresh context**, on a five-minute cache unless set. Fan out when the reads it keeps
38
+ out of the parent cost more than the prefix it writes ([`loop.md`](loop.md)'s firewall), not for one
39
+ read.
40
+ - A checkpoint, persona or ritual states what it loads ([`context.md`](context.md)), and the host's
41
+ prefix is part of that price.
42
+
43
+ ## The cache lifetime fits the gap
44
+
45
+ **Five minutes where the next use of the prefix comes within five minutes**: bursts, and headless runs that
46
+ follow one another. **An hour where sessions idle or the next one starts later.** A five-minute write costs
47
+ 1.25 times an uncached input token and an hour's 2, and every read renews either; a gap past the lifetime
48
+ writes the whole context again. On this repository's own tasks, run straight through, five-minute writes cut a
49
+ boot's cost by 22 to 30% and an edit's by about 18% against an hour's
50
+ ([`../../evals/ab/warm.md`](../../evals/ab/warm.md), row 2 and the measurement beside it); in a long session,
51
+ one pause past five minutes, while a person reads or a review or CI runs, can cost more than every write the
52
+ shorter lifetime saved. A subagent's lifetime is set apart from the session's.
53
+
54
+ ## A switch that changes the prefix is earned
55
+
56
+ Claude Code has three switches that change what a session writes: the git instructions (the startup
57
+ snapshot together with the commit and pull-request instructions), the cache lifetime, and, for a run
58
+ started headless, moving the per-machine sections (working directory, platform, memory paths, the
59
+ snapshot) out of the system prompt and into the first message, so runs in different directories or on
60
+ different machines share the system prompt. **Each stays off until an A/B run shows it cuts spend with no
61
+ loss**, which is `0038` rule 4's gate applied to sessions, and **a session that needs what a switch
62
+ removes keeps it**:
63
+
64
+ - **The git instructions are dropped only for sessions that neither commit nor open a pull request**, or
65
+ where the workspace's own guidance carries its commit and pull-request workflow. A session that needs
66
+ them is started with `CLAUDE_CODE_DISABLE_GIT_INSTRUCTIONS=0`, which outranks the compiled setting, and
67
+ a runner sets that per run.
68
+ - **The per-machine sections are moved, never dropped**: the exclusion puts them in the first message. It
69
+ applies to headless runs only, because only whatever starts a session can ask for it.
70
+ - **The cache lifetime removes nothing.** It trades the write price against the gap.
71
+
72
+ ## What a workspace declares
73
+
74
+ **`sessions` in the manifest**, since Workspace Definition 2.11
75
+ ([`../../spec/slots.md`](../../spec/slots.md)): `git_instructions` and `cache_lifetime` compile into the
76
+ host's project settings and reach every session in the repository; `headless` is what Portulan's own runners
77
+ apply to the sessions they start. Nothing is defaulted: an undeclared switch is the host's own default. This
78
+ repository declares one switch, five-minute writes for its headless runs, which row 2 of
79
+ [`../../evals/ab/warm.md`](../../evals/ab/warm.md) earned. Its interactive sessions keep the host's default,
80
+ because they commit and idle on reviews and CI; the git instructions wait on that page's row 3, and the
81
+ per-machine sections on its row 4.
82
+
83
+ ## What is machinery today, and what is not
84
+
85
+ `compile` emits the two interactive keys and says so on every run, and writes `spend`'s figures onto the
86
+ restart advisory's commands; `doctor` checks the shape and version of `sessions` and `spend`, and reports
87
+ every switch's state in one line; `init` offers an adopter's repository the five-minute lifetime with its
88
+ reason and its trade-off, and writes the key on a yes for `compile` to carry, and `upgrade` prints the same
89
+ offer and writes nothing. [`../../cli/warm.mjs`](../../cli/warm.mjs) runs fresh headless sessions in sequence
90
+ and prices each from the host's own usage records, warm against cold, and
91
+ [`../../evals/ab/warm.md`](../../evals/ab/warm.md) is its specification and its record. **Still to land**: the
92
+ interactive A/B, which needs real local sessions and so runs on a maintainer's device; a report of how often
93
+ sessions start warm, from the ledger's first request per context; an offer of the other switches, once their
94
+ rows have run; and carrying the key through `vendor`. Until each lands, the rule it would enforce is held by
95
+ the human gate.
96
+
97
+ _(Provenance: the maintainer's five-run measurement of 2026-09-24, whose figures the record in
98
+ [`../../evals/ab/warm.md`](../../evals/ab/warm.md) carries as its before, and the cache test of the same day,
99
+ which ran its rows 1 and 2; the techniques survey before it, from Claude Code's documentation on prompt
100
+ caching, settings and the CLI; and each switch read in Claude Code 2.1.281's program text on 2026-09-24.)_
@@ -21,6 +21,39 @@ context firewalls; Cognition — read-parallel / write-isolated.)_
21
21
  - **Read / write posture** — most personas read in parallel; writes are isolated to one place so two
22
22
  agents never clobber the same file.
23
23
 
24
+ ## What a persona may declare about its context
25
+
26
+ Four optional frontmatter fields say what a fresh context for the role should cost, per
27
+ [`../operating/context.md`](../operating/context.md): `model`, one model name or alias as a single
28
+ token; `effort`, one of `low`, `medium`, `high`, `xhigh` or `max`; `omitClaudeMd`, `true` or `false`
29
+ unquoted, whether the role runs without the project's instruction files; and `cacheLifetime`, `5m` or
30
+ `1h`. The effort levels and the name `omitClaudeMd` are adopted from Claude Code's vocabulary, the first
31
+ host to express them. `effort` sets the host subagent's reasoning effort, and is not the thinking cap the
32
+ `tools/local-llm` runner's `--effort` sends with each request it makes. `doctor` fails a
33
+ malformed value and `compile` refuses one, and neither supplies a value the persona did not write. For a
34
+ persona a composed pack ships, `compile` writes the declared fields into the frontmatter of the role's
35
+ hand-written host binding, `agents/<name>.md` in the workspace's tree, and leaves every other line of
36
+ that file as its author wrote it; `--check` byte-compares the result, and a line inside the compiled
37
+ block that `compile` did not write is refused rather than removed. One it cannot catch: a line added inside
38
+ the block in exactly the spelling and order `compile` writes reads as compiled, and is dropped once the
39
+ persona no longer declares that field, since the two cannot be told apart. And once a persona declares any
40
+ field, `compile` owns every top-level key it could write, so a hand-written `experimental:` is refused
41
+ even for a key other than `cacheTtl`. A field a host cannot express is
42
+ named, with the persona and the host, in the degradation report `compile` prints. Claude Code takes all
43
+ four on an agent (`cacheLifetime` as `experimental.cacheTtl`), and a vendored `AGENTS.md` takes none.
44
+ **Claude Code loads `agents/` only from a plugin root**, and a repository that is not a plugin loads its
45
+ agents from `.claude/agents/`, which `compile` does not write. So outside a plugin the compiled block is
46
+ loaded by nothing, and the degradation report says so for each persona it applies to. Where the tree is
47
+ a plugin root the report is silent, which is not proof of loading: a plugin's agents load only while it
48
+ is installed and enabled, and `compile` cannot see that. _(Provenance:
49
+ proposal `0036`'s order of work, item 7, and proposal `0039`'s first two rules. The host's keys, values
50
+ and agent locations were read in Claude Code 2.1.317's program text on 2026-11-03.)_
51
+
52
+ A binding has one persona owner across composed packs, including an unbound persona or one declaring
53
+ no fields; duplicate destinations are refused before any artifact is written. The binding must be a
54
+ regular file whose real path stays in the real declared tree, including its parent directories.
55
+ `omitClaudeMd` accepts only unquoted `true` or `false`, in both declarations and compiled blocks.
56
+
24
57
  ## Status
25
58
 
26
59
  **Milestone 3 — contract, exemplars, and one host binding.** This README fixes the persona contract; the
@@ -49,8 +49,8 @@ Re-expressed from public practice (Letta — consolidation; compounding engineer
49
49
  — curated beats generated). Invocable on demand, and **schedulable as of milestone 5**: `cli/librarian.mjs`
50
50
  runs reindex over both generated indexes, staleness (the sealed-stamp re-validation nag, and record age
51
51
  from git), proposal nagging, demotion drafts, **mining** and **consolidation**, and renders what it
52
- found as a record a workspace's scheduler files as a pull request — this persona is the contract it
53
- executes.
52
+ found as a report, which a workspace's scheduler files with the pull request the pass opens when it
53
+ changed the tree — this persona is the contract it executes.
54
54
 
55
55
  **Every subject in the charter above is now reached on a cadence, and one of them is reached less
56
56
  deeply than the charter describes — which is the distinction between this persona and that tool.** A
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: clarify
3
- description: Run a bounded clarification pass before planning a full-lane task whose intent is ambiguous or whose acceptance criteria are not yet testable. Use when the ask is underspecified, the constraints conflict, or the criteria can't be written as observable checks — surface the smallest set of decision-questions, get human answers, and fold them into EARS-shaped acceptance criteria before any code is planned.
3
+ description: Run a bounded clarification pass before planning a full-lane task. Use when the ask is underspecified, the constraints conflict, or the criteria can't be written as observable checks — surface the smallest set of decision-questions, get human answers, and fold them into EARS-shaped acceptance criteria before any code is planned.
4
4
  ---
5
5
 
6
6
  # Skill — Clarify
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: consolidate
3
- description: Bring a memory store back under its budget by merging, compressing, splitting, and retiring records — the repair a breached memory budget calls for. Use when the index verify recipe reports a breach, or on the librarian's scheduled pass. The output is drafts through the human gate; raising the budget is not one of the moves.
3
+ description: Bring a memory store back under its budget by merging, compressing, splitting, and retiring records. Use when the index verify recipe reports a breach, or on the librarian's scheduled pass. The output is drafts through the human gate; raising the budget is not one of the moves.
4
4
  ---
5
5
 
6
6
  # Skill — Consolidate
@@ -18,8 +18,10 @@ description: Bring a memory store back under its budget by merging, compressing,
18
18
  decides which move applies**, and the finding names it: the index's `lines` (too many records —
19
19
  merge or retire; compressing removes no line and splitting adds one), the store's `kilobytes` (too
20
20
  much in total), or a record's `record_kilobytes` (one record too large — split, compress, or demote;
21
- step 2 and step 4). A fourth red, the index's `columns`, is **not** this pass's work: one over-long
22
- line is repaired by renaming the record, and nothing is consolidated.
21
+ step 2 and step 4), which binds only the records dated after a declared `cutoff`: one dated on or
22
+ before it is reported, never red, and meets the cap when its text next changes. A fourth red, the
23
+ index's `columns`, is **not** this pass's work: one over-long line is repaired by renaming the record,
24
+ and nothing is consolidated.
23
25
  - Two records say the same thing, or say opposite things.
24
26
  - A record's `Retire when:` condition has fired.
25
27
  - The librarian's scheduled pass runs.
@@ -1,25 +1,23 @@
1
1
  # Template — Handoff
2
2
 
3
- > A **handoff** records a decision and its *why*, so the next agent — or the next context window —
4
- > inherits the reasoning and not just the result. Write one **at the close of every session** — that part
5
- > is not conditional, and short is fine — and also when compacting, when hitting an iteration cap, or when
6
- > passing work across a persona boundary. Decisions without their why get silently re-litigated or
7
- > reversed. **Drop any section that has nothing to say** — the date and the decisions-with-why are the
8
- > only irreducible parts, and a handoff of five lines is a valid handoff. The date lives in the
9
- > **filename**, leading, in ISO form — `YYYY-MM-DD-{slug}.md` — not in a field inside the document: that
10
- > is what a gate checks and what makes the series sort chronologically. That is what keeps the
11
- > every-session rule from becoming ceremony: it scales down here, inside the artifact, rather than by
12
- > being skipped. _(Provenance: Cognition — handoffs record decisions and rationale. The every-session rule
13
- > and why it is binary rather than discretionary are in `../operating/loop.md`.)_
3
+ > A **handoff** carries the work still open, so the next agent — or the next context window — starts
4
+ > from where things stand rather than from the transcript. Write one when a session ends with work not
5
+ > committed and pushed, when compacting, when hitting an iteration cap, or when passing work across a
6
+ > persona boundary. Work that is committed carries its own why, in its commit message, so a handoff
7
+ > says nothing the diff or a commit already says. **Drop any section with nothing to say**; five lines
8
+ > is a valid handoff. The date leads the **filename**, in ISO form — `YYYY-MM-DD-{slug}.md` — because
9
+ > that is what a gate checks and what sorts the series. _(Provenance: Cognition — handoffs record
10
+ > decisions and their rationale. When one is owed, and why the commit carries the rest:
11
+ > `../operating/loop.md`.)_
12
+ >
13
+ > **The commit a change lands in:** a subject that says what changed, in the present tense and under 72
14
+ > characters; a blank line; a body of a few lines that says why, and what was rejected when that matters.
14
15
 
15
16
  ---
16
17
 
17
18
  # Handoff — {task / thread}
18
19
 
19
- **State.** {where things actually are right now — done, in-progress, blocked — in a few lines.}
20
-
21
- **Decisions + why.** {each decision taken and the reason it was taken; the reasons are the payload.}
22
- - {decision} — because {why}; alternatives considered: {…}.
20
+ **State.** {where things stand: what is done and committed, what is in progress and uncommitted.}
23
21
 
24
22
  **Open questions.** {what is undecided, and who decides it — flag the human-owned ones.}
25
23
 
@@ -27,3 +25,18 @@
27
25
 
28
26
  **Recoverability.** {anything left in a partial state, and how to make it safe; see
29
27
  `../operating/safety.md`.}
28
+
29
+ ---
30
+
31
+ _A filled one, for a session that stopped mid-change:_
32
+
33
+ # Handoff — retry the flaky upload test
34
+
35
+ **State.** The retry wrapper is written and its unit test passes; it is stashed, not committed, because
36
+ the integration suite still fails once in about ten runs.
37
+
38
+ **Open questions.** Whether three attempts is the right cap — the owner of the upload service decides.
39
+
40
+ **Next action.** `git stash pop`, run the integration suite twenty times, and read the one failure's log.
41
+
42
+ **Recoverability.** Nothing was pushed; dropping the stash loses only the wrapper.
@@ -8,6 +8,7 @@
8
8
  ---
9
9
 
10
10
  **type:** rule | decision | reference | glossary
11
+ **dated:** {YYYY-MM-DD — the day this record's text last changed; an edit re-dates it. The per-record cap binds by it where the workspace declares a cutoff}
11
12
  **scope:** {the persona / repo / workspace this belongs to — memory is per-agent, not global}
12
13
  **provenance:** {one of the two forms below — a rule carrying neither is taste, and `doctor` rejects it}
13
14