@sleepy_panda_srl/portulan 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -5
- package/cli/README.md +142 -358
- package/cli/advisory.mjs +354 -0
- package/cli/comments.mjs +932 -0
- package/cli/compile.mjs +1322 -1607
- package/cli/context.mjs +883 -0
- package/cli/control-chars.mjs +12 -301
- package/cli/discover.mjs +17 -574
- package/cli/doctor.mjs +224 -1074
- package/cli/drills.mjs +68 -502
- package/cli/feedback.mjs +16 -237
- package/cli/finish.mjs +437 -0
- package/cli/form.mjs +644 -0
- package/cli/fuzz-shell.mjs +20 -305
- package/cli/gate.mjs +7 -199
- package/cli/goldens.mjs +26 -184
- package/cli/index.mjs +305 -655
- package/cli/init.mjs +243 -566
- package/cli/inside.mjs +3 -28
- package/cli/instructions.mjs +829 -0
- package/cli/ledger.mjs +778 -0
- package/cli/librarian.mjs +135 -599
- package/cli/manifest.mjs +2 -39
- package/cli/mutants.mjs +9 -170
- package/cli/new.mjs +12 -243
- package/cli/pack-identity.mjs +5 -74
- package/cli/pack-version.mjs +13 -255
- package/cli/plugin-lint.mjs +42 -658
- package/cli/portulan.mjs +5 -124
- package/cli/recipe-set.mjs +15 -245
- package/cli/release-eval.mjs +28 -348
- package/cli/review-meter.mjs +17 -258
- package/cli/rule-carriers.mjs +12 -175
- package/cli/sessions.mjs +82 -0
- package/cli/skill-goldens.mjs +10 -176
- package/cli/skills-set.mjs +12 -414
- package/cli/stop-gate.mjs +83 -514
- package/cli/symbols.mjs +1132 -0
- package/cli/telemetry.mjs +27 -492
- package/cli/upgrade.mjs +171 -314
- package/cli/vendor.mjs +146 -476
- package/cli/version-carriers.mjs +8 -86
- package/core/README.md +1 -1
- package/core/engine.md +3 -9
- package/core/operating/autonomy.md +1 -1
- package/core/operating/context.md +183 -0
- package/core/operating/evolution.md +3 -3
- package/core/operating/loop.md +13 -20
- package/core/operating/memory.md +12 -3
- package/core/operating/sessions.md +100 -0
- package/core/personas/librarian.md +2 -2
- package/core/skills/consolidate/SKILL.md +4 -2
- package/core/templates/handoff.md +28 -15
- package/core/templates/memory-entry.md +1 -0
- package/evals/README.md +255 -845
- package/evals/releases/0.2.0.json +146 -0
- package/evals/releases/0.2.0.md +67 -0
- package/evals/releases/README.md +3 -3
- package/package.json +4 -2
- package/packs/rituals/checkpoints/README.md +1 -1
- package/packs/tools/github/verify/actions-pinned.sh +10 -55
- package/spec/README.md +112 -6
- package/spec/migrations/0001-repository-declares-its-tree.mjs +1 -26
- package/spec/migrations/0002-bundle-fallback-path.mjs +13 -74
- package/spec/migrations/0003-handoff-index-not-kept.mjs +66 -0
- package/spec/migrations/0004-changelog-fragments.mjs +53 -0
- package/spec/migrations/0005-session-log-retired.mjs +69 -0
- package/spec/migrations/0006-boot-card.mjs +77 -0
- package/spec/migrations/0007-guidance-compiled.mjs +31 -0
- package/spec/migrations/0008-card-reading.mjs +55 -0
- package/spec/migrations/0009-instruction-sections-on-read.mjs +60 -0
- package/spec/migrations/0010-comment-rail.mjs +57 -0
- package/spec/migrations/README.md +45 -10
- package/spec/pack.schema.json +1 -1
- package/spec/slots.md +333 -21
- package/spec/workspace.schema.json +145 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://portulan.dev/spec/2.
|
|
4
|
-
"title": "Portulan Workspace Definition v2.
|
|
3
|
+
"$id": "https://portulan.dev/spec/2.13/workspace.schema.json",
|
|
4
|
+
"title": "Portulan Workspace Definition v2.13",
|
|
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,
|
|
@@ -127,6 +127,10 @@
|
|
|
127
127
|
"personas": {
|
|
128
128
|
"description": "Optional, added in 2.6. The workspace's own per-persona layer: one directory per persona whose memory scope a composed pack declares. It is NOT where personas are defined — those live in `core/personas/` and in the packs — it is where the memory each of them scopes lands, in the layer its owner controls (docs/vision.md thesis 6: storage follows ownership). Declared rather than derived from `slots.memory`, and both halves of that are deliberate. Not derived, because a path this specification computed would be Portulan choosing a location inside every adopter's workspace in a key nobody typed — the same reasoning that leaves every memory budget undefaulted. And not a subdirectory of the store, because `core/operating/memory.md` holds per-agent memory apart precisely so a reviewer's recall does not spend the implementer's budget, and a nested store would be counted by the store's own KB rail and invisible to the walk that counts it (issue #76). Each directory is **empty until earned**: the pack declares the scope and carries none of its contents.",
|
|
129
129
|
"$ref": "#/$defs/dirPath"
|
|
130
|
+
},
|
|
131
|
+
"context": {
|
|
132
|
+
"description": "Optional, added in 2.10. The guidance a host loads, one unit per Markdown file, each declaring in its own frontmatter the load tier it belongs in: `always`, `on-path` (with the `paths` it is scoped to), `on-invoke` or `on-read`, the four tiers of core/operating/context.md. Its pair is the top-level `context`: this slot holds the guidance and that key its budget, as `slots.memory` holds the records and `memory` their configuration. `compile` reads it and emits each host's own form of each tier, and a tier a host cannot express degrades to an on-read pointer, never to nothing. The frontmatter is `compile`'s to check, since this subset cannot see inside a file, and so is that the slot lies outside `.claude/` and the workspace's `compile/`, where `compile` writes; spec/slots.md carries the contract.",
|
|
133
|
+
"$ref": "#/$defs/dirPath"
|
|
130
134
|
}
|
|
131
135
|
}
|
|
132
136
|
},
|
|
@@ -227,8 +231,13 @@
|
|
|
227
231
|
"type": "number"
|
|
228
232
|
},
|
|
229
233
|
"record_kilobytes": {
|
|
230
|
-
"description": "Optional, added in 2.8. The most kilobytes (1024 bytes) any ONE record may hold. This is to `kilobytes` what `memory.index.budget.columns` is to `memory.index.budget.lines`: an aggregate cannot see inside its units, so one enormous record absorbs what a store budget counts, exactly as one enormous line absorbs what a line budget counts. Breaches are LOCAL — record X over its cap never blocks writer Y — and the repair menu is live rather than barred: SPLIT the record where it holds more than one fact (which spends the count axis, `memory.index.budget.lines`), COMPRESS it, or DEMOTE its narrative to the provenance layer. Never a raise in the change that breached it (core/operating/memory.md). A positive integer, on the same terms as `lines`.",
|
|
234
|
+
"description": "Optional, added in 2.8. The most kilobytes (1024 bytes) any ONE record may hold. This is to `kilobytes` what `memory.index.budget.columns` is to `memory.index.budget.lines`: an aggregate cannot see inside its units, so one enormous record absorbs what a store budget counts, exactly as one enormous line absorbs what a line budget counts. Breaches are LOCAL — record X over its cap never blocks writer Y — and the repair menu is live rather than barred: SPLIT the record where it holds more than one fact (which spends the count axis, `memory.index.budget.lines`), COMPRESS it, or DEMOTE its narrative to the provenance layer. Never a raise in the change that breached it (core/operating/memory.md). A positive integer, on the same terms as `lines`. With `cutoff` beside it (2.11), it binds only the records dated after the cutoff.",
|
|
231
235
|
"type": "number"
|
|
236
|
+
},
|
|
237
|
+
"cutoff": {
|
|
238
|
+
"description": "Optional, added in 2.11, and only beside `record_kilobytes`, which `doctor` checks because the subset has no `dependentRequired`. The day from which the per-record cap binds, forward only, in proposal 0037's shape for a handoff: a record dated after it and over the cap is red, one dated on or before it is reported and never railed. Every record then carries a `**dated:** YYYY-MM-DD` line, the day its text last changed, because a verify rail may not read git; an edit re-dates a record, so an old one meets the cap in the change that next rewrites it. A missing or unreal date is refused, and so is one more than a day after the UTC date the check runs on. The cutoff is set to the declaring change's date or the newest record's, whichever is later, and moves only in a change that tightens the cap; it never moves earlier, a rule the change's review holds, since no rail reads git history. A real day written YYYY-MM-DD, which `doctor` checks past this pattern.",
|
|
239
|
+
"type": "string",
|
|
240
|
+
"pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$"
|
|
232
241
|
}
|
|
233
242
|
}
|
|
234
243
|
}
|
|
@@ -238,7 +247,7 @@
|
|
|
238
247
|
},
|
|
239
248
|
|
|
240
249
|
"handoffs": {
|
|
241
|
-
"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
|
|
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.",
|
|
242
251
|
"type": "object",
|
|
243
252
|
"additionalProperties": false,
|
|
244
253
|
"required": ["index"],
|
|
@@ -304,6 +313,138 @@
|
|
|
304
313
|
}
|
|
305
314
|
}
|
|
306
315
|
}
|
|
316
|
+
},
|
|
317
|
+
"context": {
|
|
318
|
+
"description": "Optional, added in 2.9. What the always tier may cost, and the ratio it is counted at: the machine half of core/operating/context.md, the way `memory` is of core/operating/memory.md, and the page is the one source for what each load tier holds. Requires `ratio`, because a token budget with nothing to count it by is not a budget; that is the one dependency here the subset can state, so it is the schema's rather than `doctor`'s. Nothing is defaulted, on the memory budgets' reasoning: a default would be this specification setting a policy for every workspace that adopts it, in a key nobody typed.",
|
|
319
|
+
"type": "object",
|
|
320
|
+
"additionalProperties": false,
|
|
321
|
+
"required": ["ratio"],
|
|
322
|
+
"properties": {
|
|
323
|
+
"always": {
|
|
324
|
+
"description": "The always tier, as core/operating/context.md defines it. Named for its tier, so a budget on a later tier would sit beside it rather than force a rename.",
|
|
325
|
+
"type": "object",
|
|
326
|
+
"additionalProperties": false,
|
|
327
|
+
"required": ["budget"],
|
|
328
|
+
"properties": {
|
|
329
|
+
"budget": {
|
|
330
|
+
"description": "A rail, not an aim, and never repaired by a raise in the same change (core/operating/context.md): a breach is repaired by demotion to a later tier, by merge or by retirement.",
|
|
331
|
+
"type": "object",
|
|
332
|
+
"additionalProperties": false,
|
|
333
|
+
"required": ["tokens"],
|
|
334
|
+
"properties": {
|
|
335
|
+
"tokens": {
|
|
336
|
+
"description": "The most tokens the always tier may load, counted at `ratio.bytes_per_token`. Must be a POSITIVE INTEGER: the subset has no `minimum` and cannot say `integer`, so `doctor` refuses zero, negative and non-integer values.",
|
|
337
|
+
"type": "number"
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
"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.",
|
|
345
|
+
"type": "object",
|
|
346
|
+
"additionalProperties": false,
|
|
347
|
+
"required": ["bytes_per_token", "calibrated_by"],
|
|
348
|
+
"properties": {
|
|
349
|
+
"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`.",
|
|
351
|
+
"type": "number"
|
|
352
|
+
},
|
|
353
|
+
"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.",
|
|
355
|
+
"$ref": "#/$defs/slug"
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
"sessions": {
|
|
362
|
+
"description": "Optional, added in 2.11. The host switches that change what a fresh session writes into the prompt cache: the machine half of core/operating/sessions.md, from proposal 0038's rules 3 and 4. Nothing is defaulted: an undeclared switch is the host's own default and `compile` emits nothing for it, and a switch is declared once an A/B run has shown it cuts spend with no loss.",
|
|
363
|
+
"type": "object",
|
|
364
|
+
"additionalProperties": false,
|
|
365
|
+
"properties": {
|
|
366
|
+
"git_instructions": {
|
|
367
|
+
"description": "`false` drops the host's git instructions from every session in the repository: on Claude Code, the startup git snapshot and the commit and pull-request instructions, compiled into the project settings as `includeGitInstructions`. A session that needs them is started with them restored (core/operating/sessions.md).",
|
|
368
|
+
"type": "boolean"
|
|
369
|
+
},
|
|
370
|
+
"cache_lifetime": {
|
|
371
|
+
"description": "The main conversation's prompt-cache lifetime, compiled into the project settings as Claude Code's `promptCacheTtl`: five minutes where the next use of the prefix comes within five minutes, an hour where sessions idle.",
|
|
372
|
+
"type": "string",
|
|
373
|
+
"enum": ["5m", "1h"]
|
|
374
|
+
},
|
|
375
|
+
"headless": {
|
|
376
|
+
"description": "What Portulan's own runners apply to the sessions they start, and never compiled: the exclusion below is no host setting, only a flag or an SDK option, so only whatever starts a session can ask for it.",
|
|
377
|
+
"type": "object",
|
|
378
|
+
"additionalProperties": false,
|
|
379
|
+
"properties": {
|
|
380
|
+
"git_instructions": {
|
|
381
|
+
"description": "`false` starts those sessions without the host's git instructions.",
|
|
382
|
+
"type": "boolean"
|
|
383
|
+
},
|
|
384
|
+
"cache_lifetime": {
|
|
385
|
+
"description": "Their prompt-cache lifetime.",
|
|
386
|
+
"type": "string",
|
|
387
|
+
"enum": ["5m", "1h"]
|
|
388
|
+
},
|
|
389
|
+
"exclude_dynamic_sections": {
|
|
390
|
+
"description": "`true` starts them with the per-machine sections (working directory, platform, memory paths, the git snapshot) moved from the system prompt into the first message, so runs in different directories or on different machines share the system prompt.",
|
|
391
|
+
"type": "boolean"
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
},
|
|
397
|
+
"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.",
|
|
399
|
+
"type": "object",
|
|
400
|
+
"additionalProperties": false,
|
|
401
|
+
"properties": {
|
|
402
|
+
"multipliers": {
|
|
403
|
+
"description": "What each class of input costs, as a multiple of an uncached input token. Both halves or neither: a threshold priced by one declared multiplier and one general one would be priced by nobody's figures.",
|
|
404
|
+
"type": "object",
|
|
405
|
+
"additionalProperties": false,
|
|
406
|
+
"required": ["read", "write"],
|
|
407
|
+
"properties": {
|
|
408
|
+
"read": {
|
|
409
|
+
"description": "A cache read's cost as a multiple of an uncached input token. Above 0 and at most 1: the threshold divides by it, and a token read from cache never costs more than the same token sent uncached. The subset has neither `minimum` nor `maximum`, so `doctor` refuses a value outside that range.",
|
|
410
|
+
"type": "number"
|
|
411
|
+
},
|
|
412
|
+
"write": {
|
|
413
|
+
"description": "A cache write's cost as a multiple of an uncached input token, for each lifetime a write can be made at. Both lifetimes: the host records which one a session's writes used, and the threshold takes the write of the lifetime it recorded.",
|
|
414
|
+
"type": "object",
|
|
415
|
+
"additionalProperties": false,
|
|
416
|
+
"required": ["5m", "1h"],
|
|
417
|
+
"properties": {
|
|
418
|
+
"5m": {
|
|
419
|
+
"description": "A five-minute cache write's cost. At least 1: writing a token to the cache costs at least what sending it uncached does, and `doctor` refuses a smaller figure, since the subset has no `minimum`.",
|
|
420
|
+
"type": "number"
|
|
421
|
+
},
|
|
422
|
+
"1h": {
|
|
423
|
+
"description": "A one-hour cache write's cost. At least 1, on the same terms as `5m`.",
|
|
424
|
+
"type": "number"
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
},
|
|
430
|
+
"horizon": {
|
|
431
|
+
"description": "How far ahead the threshold looks: the `n` of the arithmetic above.",
|
|
432
|
+
"type": "object",
|
|
433
|
+
"additionalProperties": false,
|
|
434
|
+
"required": ["requests"],
|
|
435
|
+
"properties": {
|
|
436
|
+
"requests": {
|
|
437
|
+
"description": "The requests still to go at which the restart threshold is judged. Must be a POSITIVE INTEGER: the subset has no `minimum` and cannot say `integer`, so `doctor` refuses zero, negative and non-integer values.",
|
|
438
|
+
"type": "number"
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
"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.",
|
|
444
|
+
"type": "string",
|
|
445
|
+
"enum": ["advise", "block"]
|
|
446
|
+
}
|
|
447
|
+
}
|
|
307
448
|
}
|
|
308
449
|
},
|
|
309
450
|
|