@sleepy_panda_srl/portulan 0.3.0 → 0.4.1

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 (90) hide show
  1. package/README.md +18 -6
  2. package/cli/README.md +56 -4
  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 +492 -146
  7. package/cli/context.mjs +204 -43
  8. package/cli/doctor.mjs +211 -33
  9. package/cli/drills.mjs +32 -7
  10. package/cli/finish.md +34 -0
  11. package/cli/finish.mjs +9 -67
  12. package/cli/form.mjs +35 -19
  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 +123 -37
  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 +7 -2
  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/ritual-contexts.mjs +103 -0
  34. package/cli/sessions.mjs +2 -1
  35. package/cli/spend-analysis.mjs +111 -0
  36. package/cli/spend-offer.md +53 -0
  37. package/cli/spend-offer.mjs +93 -0
  38. package/cli/stop-diagnostics.mjs +114 -0
  39. package/cli/stop-gate.mjs +133 -90
  40. package/cli/stop-policy.mjs +14 -0
  41. package/cli/symbols.mjs +55 -8
  42. package/cli/telemetry.mjs +19 -12
  43. package/cli/upgrade.mjs +62 -44
  44. package/cli/vendor.mjs +210 -126
  45. package/core/operating/context.md +368 -60
  46. package/core/operating/evolution.md +3 -2
  47. package/core/operating/loop.md +9 -4
  48. package/core/operating/sessions.md +6 -15
  49. package/core/personas/README.md +52 -10
  50. package/core/personas/implementer.md +3 -0
  51. package/core/personas/librarian.md +3 -0
  52. package/core/personas/reviewer.md +3 -0
  53. package/core/templates/handoff.md +6 -2
  54. package/evals/ab/personas/README.md +111 -0
  55. package/evals/ab/personas/acquisition.json +330 -0
  56. package/evals/ab/personas/bindings/implementer.txt +43 -0
  57. package/evals/ab/personas/bindings/librarian.txt +40 -0
  58. package/evals/ab/personas/bindings/reviewer.txt +41 -0
  59. package/evals/ab/personas/corpus.json +69 -0
  60. package/evals/ab/personas/grader.mjs +171 -0
  61. package/evals/ab/personas/implementer.json +774 -0
  62. package/evals/ab/personas/implementer.md +22 -0
  63. package/evals/ab/personas/librarian.json +758 -0
  64. package/evals/ab/personas/librarian.md +22 -0
  65. package/evals/ab/personas/protocol/continuation.mjs.txt +284 -0
  66. package/evals/ab/personas/protocol/first-outcome.mjs.txt +227 -0
  67. package/evals/ab/personas/protocol/first-probe.mjs.txt +172 -0
  68. package/evals/ab/personas/protocol/publisher.mjs.txt +126 -0
  69. package/evals/ab/personas/protocol/second-probe.mjs.txt +193 -0
  70. package/evals/ab/personas/repairs/first-control-answer.txt +23 -0
  71. package/evals/ab/personas/repairs/grader-before-decode-repair.mjs.txt +143 -0
  72. package/evals/ab/personas/reviewer.json +758 -0
  73. package/evals/ab/personas/reviewer.md +22 -0
  74. package/evals/releases/0.4.0.json +154 -0
  75. package/evals/releases/0.4.0.md +69 -0
  76. package/evals/releases/0.4.1.json +154 -0
  77. package/evals/releases/0.4.1.md +69 -0
  78. package/package.json +6 -2
  79. package/packs/rituals/checkpoints/README.md +35 -0
  80. package/packs/rituals/checkpoints/pack.json +3 -2
  81. package/packs/tools/local-llm/README.md +46 -6
  82. package/packs/tools/local-llm/runner/local-llm.mjs +127 -13
  83. package/packs/tools/local-llm/skills/delegate-local/SKILL.md +26 -2
  84. package/spec/README.md +37 -4
  85. package/spec/migrations/0009-instruction-sections-on-read.mjs +60 -11
  86. package/spec/migrations/0011-persona-bindings-compiled.mjs +23 -0
  87. package/spec/migrations/README.md +6 -0
  88. package/spec/pack.schema.json +12 -3
  89. package/spec/slots.md +262 -117
  90. package/spec/workspace.schema.json +67 -11
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
- "$id": "https://portulan.dev/spec/2.13/workspace.schema.json",
4
- "title": "Portulan Workspace Definition v2.13",
3
+ "$id": "https://portulan.dev/spec/2.16/workspace.schema.json",
4
+ "title": "Portulan Workspace Definition v2.16",
5
5
  "description": "The manifest at the root of a workspace. It is an index, not a container: it names the slots the engine resolves against and points at the Markdown that holds each slot's content and rationale. Written in a deliberately small subset of JSON Schema 2020-12 — see spec/README.md for the subset and for what validates it.",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
@@ -145,6 +145,10 @@
145
145
  "description": "The `id` of the recipe the Stop-gate runs when nothing more specific applies. Constrained to the same shape as an id, so a value no recipe could ever carry is rejected by the schema rather than only by `doctor`.",
146
146
  "$ref": "#/$defs/slug"
147
147
  },
148
+ "stopTimeout": {
149
+ "description": "Optional, added in 2.14. Stop recipe deadline in seconds; undeclared, 90. A positive integer whose recipe and outer hook timers fit Node's timer range, checked by doctor and every reader. Compile gives the Stop hook sixty additional seconds for other checks and cleanup.",
150
+ "type": "number"
151
+ },
148
152
  "recipes": {
149
153
  "type": "array",
150
154
  "minItems": 1,
@@ -247,20 +251,65 @@
247
251
  },
248
252
 
249
253
  "handoffs": {
250
- "description": "Optional, added in 2.5. What the handoff series' generated index is called — the same split as `memory` above, and for the same reason: `slots.handoffs` is the series, this is the machine configuration over it. One key and no more. There is deliberately NO budget here, and the absence is the considered half: a budget's only permitted remedy is consolidation — merge, compress, split, retire (core/operating/memory.md) — and a handoff series is append-only by construction. Retiring a handoff to buy headroom destroys the record it exists to keep; SPLITTING one — the move 2.8 added for the per-record rail — leaves the series no smaller; and raising the budget in the change that breached it is the one repair that doctrine rules out. Every remedy such a budget could ask for is barred, which makes it a rail designed to be broken. Whether the series should be budgeted on some other axis is a maintainer's question, deferred rather than answered here — and a key that exists but must never be set would be this specification pre-empting that answer in the shape of an invitation.",
254
+ "description": "Optional. Configuration over slots.handoffs. Since 2.15 an individual new handoff may have a token cap and the generated index may carry a token-budgeted window. The append-only series has no aggregate budget. Proposal 0037 supplies the rule and repair boundary.",
251
255
  "type": "object",
252
256
  "additionalProperties": false,
253
- "required": ["index"],
257
+ "required": [
258
+ "index"
259
+ ],
254
260
  "properties": {
255
261
  "index": {
256
- "description": "Required whenever `handoffs` is present, which is the one way this object differs from `memory` above. There, an object with no `index` is coherent — a workspace may rail its store's size and generate nothing. Here the object has exactly one key and no budget, so `handoffs: {}` configures nothing at all: a no-op that reads as configured to anyone who greps for it, which is the same failure `librarian.staleness.proposal_days` without `slots.proposals` is refused for. The generated index over the handoff series. Built by `cli/index.mjs` and byte-compared by a verify recipe on the same terms as the memory index: every field on a line is derived from the series — the ISO date off the filename, the title off the record's own H1 — so nothing in the file can be edited into disagreement with what it indexes. Its path must resolve OUTSIDE `slots.handoffs`: a file in that directory whose name carries no date already fails the `record` check, and one that did carry a date would be counted as a handoff by the correspondence check. Sited beside the series, both stay true without either check learning a filename to ignore.",
262
+ "description": "Required whenever handoffs is present. The derived newest-first index lives outside slots.handoffs. A kept copy is byte-compared; absent means render on demand. An optional token budget selects a contiguous newest-first window, preserving every source handoff.",
257
263
  "type": "object",
258
264
  "additionalProperties": false,
259
- "required": ["path"],
265
+ "required": [
266
+ "path"
267
+ ],
260
268
  "properties": {
261
269
  "path": {
262
270
  "description": "Where the generated index is written, relative to the workspace root.",
263
271
  "$ref": "#/$defs/filePath"
272
+ },
273
+ "budget": {
274
+ "type": "object",
275
+ "additionalProperties": false,
276
+ "required": [
277
+ "tokens"
278
+ ],
279
+ "properties": {
280
+ "tokens": {
281
+ "type": "number",
282
+ "description": "Added in 2.15. Positive integer, explicitly declared, measured at context.ratio. The entire index, header and omission notice included, must fit. Too small for overhead is refused with the minimum required budget."
283
+ }
284
+ }
285
+ }
286
+ }
287
+ },
288
+ "series": {
289
+ "type": "object",
290
+ "additionalProperties": false,
291
+ "required": [
292
+ "budget"
293
+ ],
294
+ "properties": {
295
+ "budget": {
296
+ "type": "object",
297
+ "additionalProperties": false,
298
+ "required": [
299
+ "record_tokens",
300
+ "cutoff"
301
+ ],
302
+ "properties": {
303
+ "record_tokens": {
304
+ "type": "number",
305
+ "description": "Added in 2.15. Positive integer token cap on each handoff dated strictly after cutoff, at the explicit context.ratio. Compression or moving facts to their owning records repairs a breach; never splitting or deleting history."
306
+ },
307
+ "cutoff": {
308
+ "type": "string",
309
+ "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
310
+ "description": "Required real YYYY-MM-DD day. Set to the declaring change date or newest merged handoff date, whichever is later. The cutoff day stays unbound. Moves only with a tighter cap or lower ratio, never backward; review holds those history-dependent rules."
311
+ }
312
+ }
264
313
  }
265
314
  }
266
315
  }
@@ -341,17 +390,17 @@
341
390
  }
342
391
  },
343
392
  "ratio": {
344
- "description": "How bytes become tokens. Declared, and measured exactly on demand rather than on every run: the exact count asks the host, which is a network call no verify recipe may make, and a ratio that moved between runs would make one tree red on one run and green on the next.",
393
+ "description": "How bytes become tokens. Manually declared for a compile host rather than recalibrated on every run. The on-demand provider counter returns an estimate of the measured project corpus, including API framing, not actual host usage; the maintainer accepted this limit for #506 on 2026-11-09. No verify recipe may invoke the network mode.",
345
394
  "type": "object",
346
395
  "additionalProperties": false,
347
396
  "required": ["bytes_per_token", "calibrated_by"],
348
397
  "properties": {
349
398
  "bytes_per_token": {
350
- "description": "Bytes per token, as the host's exact count measured them. At least 1: a token covers at least one byte, so a smaller figure is tokens per byte entered inverted, and `doctor` refuses it, since the subset has no `minimum`.",
399
+ "description": "Manually assessed bytes per token for the host's measured domain. At least 1, guarding against inverted units; doctor refuses a smaller declaration because the subset has no minimum. A tiny corpus counted with API framing can yield an effective ratio below this floor, which the provider report labels not declarable.",
351
400
  "type": "number"
352
401
  },
353
402
  "calibrated_by": {
354
- "description": "The host whose exact count calibrated the ratio, by the id `compile` gives that host. No date is recorded here: the commit that changes the ratio dates it.",
403
+ "description": "The compile host whose measurement domain the manually declared ratio describes, by its compile ID. Provider/model attribution stays in the count report; it is not a new compile host. Doctor checks a non-empty string, not the calibration's provenance. The commit that changes the ratio dates it.",
355
404
  "$ref": "#/$defs/slug"
356
405
  }
357
406
  }
@@ -395,7 +444,7 @@
395
444
  }
396
445
  },
397
446
  "spend": {
398
- "description": "Optional, added in 2.12. The multipliers and the horizon the restart threshold is computed at, `F × (1 + m_w / (n × m_r))` in proposal 0038's arithmetic, from its ruling 2: the manifest declares the multipliers, as `context.ratio` declares bytes per token, so a team's contracted rates and its host's real prices are one declared value. Nothing is defaulted in the manifest: undeclared, `cli/ledger.mjs` and the restart advisory use the general multipliers, a cache read at a tenth of an uncached input token and a write at 1.25× for five minutes or 2× for an hour, and the 20-request horizon of ruling 3, and say `undeclared`. Declared, `compile` writes the figures onto the advisory's commands, and `cli/ledger.mjs --workspace` reads them. `restart`, added in 2.13, says what a crossed threshold does. The subset has neither `minimum` nor `maximum`, so every range below is `doctor`'s.",
447
+ "description": "Optional, added in 2.12. The multipliers and the horizon the restart threshold is computed at, `F × (1 + m_w / (n × m_r))` in proposal 0038's arithmetic, from its ruling 2: the manifest declares the multipliers, as `context.ratio` declares bytes per token, so a team's contracted rates and its host's real prices are one declared value. Nothing is defaulted in the manifest: undeclared, `cli/ledger.mjs` and the restart advisory use the general multipliers, a cache read at a tenth of an uncached input token and a write at 1.25× for five minutes or 2× for an hour, and the 20-request horizon of ruling 3, and say `undeclared`. Declared, `compile` writes the figures onto the advisory's commands, and `cli/ledger.mjs --workspace` reads them. `restart`, added in 2.13, says what a crossed threshold does. `resume.tokens`, added in 2.16, prices additional restart expense; the threshold gains resume / (horizon × read). Unknown resume expense makes the comparison a lower bound and never holds a Stop. The subset has neither `minimum` nor `maximum`, so every range below is `doctor`'s.",
399
448
  "type": "object",
400
449
  "additionalProperties": false,
401
450
  "properties": {
@@ -439,8 +488,15 @@
439
488
  }
440
489
  }
441
490
  },
491
+ "resume": {
492
+ "description": "Optional, added in 2.16. Additional restart expense in uncached-input-token equivalents, excluding the fresh-context write and baseline reads already priced. Include handoff output, boot/state recovery and additional rereads over the horizon. These are weighted cost units: 2,000 output tokens at 5x contribute 10,000 equivalents. Absent means unknown; explicit zero is priced. Doctor checks the finite nonnegative bound.",
493
+ "type": "object",
494
+ "additionalProperties": false,
495
+ "required": ["tokens"],
496
+ "properties": { "tokens": { "type": "number" } }
497
+ },
442
498
  "restart": {
443
- "description": "Optional, added in 2.13. What a crossed restart threshold does: `advise`, what an undeclared workspace gets, says the advisory's line once with a tool result or at a prompt; `block` also holds a turn's end once, at the first stop at or past the threshold that no block provoked, with the same line as the reason, compiled as a second `Stop` command beside the Stop-gate's. Nothing ends the session either way. Proposal 0038's ruling 3: a report by default, a rail by declaration.",
499
+ "description": "Optional, added in 2.13. What a crossed restart threshold does: `advise`, what an undeclared workspace gets, says the advisory's line once with a tool result or at a prompt; `block` also holds a turn's end once, only with an explicit horizon and resume expense and finite strictly positive estimated savings at a stop that no block provoked, with the same line as the reason, compiled as a second `Stop` command beside the Stop-gate's. Nothing ends the session either way. Proposal 0038's ruling 3: a report by default, a rail by declaration.",
444
500
  "type": "string",
445
501
  "enum": ["advise", "block"]
446
502
  }