@sleepy_panda_srl/portulan 0.3.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.
- package/README.md +15 -4
- package/cli/README.md +55 -3
- package/cli/advisory.mjs +167 -53
- package/cli/cache-risk.mjs +69 -0
- package/cli/comments.mjs +8 -5
- package/cli/compile.mjs +492 -146
- package/cli/context.mjs +204 -43
- package/cli/doctor.mjs +211 -33
- package/cli/drills.mjs +32 -7
- package/cli/finish.md +34 -0
- package/cli/finish.mjs +8 -67
- package/cli/form.mjs +34 -18
- package/cli/frontmatter.mjs +39 -0
- package/cli/gate.mjs +14 -12
- package/cli/guidance-files.mjs +335 -0
- package/cli/handoff-policy.mjs +76 -0
- package/cli/html-entities.mjs +2130 -0
- package/cli/import-files.mjs +15 -0
- package/cli/index.mjs +77 -24
- package/cli/init-preview.mjs +553 -0
- package/cli/init.mjs +261 -46
- package/cli/instructions-journal.mjs +1185 -0
- package/cli/instructions.mjs +332 -57
- package/cli/ledger.mjs +123 -37
- package/cli/librarian.mjs +103 -19
- package/cli/persona-routing.mjs +209 -0
- package/cli/plugin-lint.mjs +77 -56
- package/cli/recipe-output.mjs +91 -0
- package/cli/recipe-runner.md +68 -0
- package/cli/recipe-runner.mjs +131 -0
- package/cli/recipe-set.mjs +12 -3
- package/cli/ritual-contexts.mjs +103 -0
- package/cli/sessions.mjs +2 -1
- package/cli/spend-analysis.mjs +111 -0
- package/cli/spend-offer.md +53 -0
- package/cli/spend-offer.mjs +93 -0
- package/cli/stop-diagnostics.mjs +114 -0
- package/cli/stop-gate.mjs +133 -90
- package/cli/stop-policy.mjs +14 -0
- package/cli/symbols.mjs +55 -8
- package/cli/telemetry.mjs +19 -12
- package/cli/upgrade.mjs +62 -44
- package/cli/vendor.mjs +207 -125
- package/core/operating/context.md +368 -60
- package/core/operating/evolution.md +3 -2
- package/core/operating/loop.md +9 -4
- package/core/operating/sessions.md +6 -15
- package/core/personas/README.md +52 -10
- package/core/personas/implementer.md +3 -0
- package/core/personas/librarian.md +3 -0
- package/core/personas/reviewer.md +3 -0
- package/core/templates/handoff.md +6 -2
- package/evals/ab/personas/README.md +111 -0
- package/evals/ab/personas/acquisition.json +330 -0
- package/evals/ab/personas/bindings/implementer.txt +43 -0
- package/evals/ab/personas/bindings/librarian.txt +40 -0
- package/evals/ab/personas/bindings/reviewer.txt +41 -0
- package/evals/ab/personas/corpus.json +69 -0
- package/evals/ab/personas/grader.mjs +171 -0
- package/evals/ab/personas/implementer.json +774 -0
- package/evals/ab/personas/implementer.md +22 -0
- package/evals/ab/personas/librarian.json +758 -0
- package/evals/ab/personas/librarian.md +22 -0
- package/evals/ab/personas/protocol/continuation.mjs.txt +284 -0
- package/evals/ab/personas/protocol/first-outcome.mjs.txt +227 -0
- package/evals/ab/personas/protocol/first-probe.mjs.txt +172 -0
- package/evals/ab/personas/protocol/publisher.mjs.txt +126 -0
- package/evals/ab/personas/protocol/second-probe.mjs.txt +193 -0
- package/evals/ab/personas/repairs/first-control-answer.txt +23 -0
- package/evals/ab/personas/repairs/grader-before-decode-repair.mjs.txt +143 -0
- package/evals/ab/personas/reviewer.json +758 -0
- package/evals/ab/personas/reviewer.md +22 -0
- package/evals/releases/0.4.0.json +154 -0
- package/evals/releases/0.4.0.md +69 -0
- package/package.json +6 -2
- package/packs/rituals/checkpoints/README.md +35 -0
- package/packs/rituals/checkpoints/pack.json +3 -2
- package/packs/tools/local-llm/README.md +46 -6
- package/packs/tools/local-llm/runner/local-llm.mjs +127 -13
- package/packs/tools/local-llm/skills/delegate-local/SKILL.md +26 -2
- package/spec/README.md +37 -4
- package/spec/migrations/0009-instruction-sections-on-read.mjs +60 -11
- package/spec/migrations/0011-persona-bindings-compiled.mjs +23 -0
- package/spec/migrations/README.md +6 -0
- package/spec/pack.schema.json +12 -3
- package/spec/slots.md +262 -117
- package/spec/workspace.schema.json +67 -11
|
@@ -26,8 +26,9 @@ decision, proposed before it runs.
|
|
|
26
26
|
|
|
27
27
|
## When to use it
|
|
28
28
|
|
|
29
|
-
- **A verify-gated file transform:** a mechanical refactor
|
|
30
|
-
|
|
29
|
+
- **A verify-gated file transform longer than one short parent Edit:** a mechanical refactor or a
|
|
30
|
+
history-comment cleanup across a file, or a unit test the parent would otherwise iterate red→green,
|
|
31
|
+
where a verify command can prove the edit green or have it rolled back.
|
|
31
32
|
- **An explicitly selected candidate research digest:** reading a long file or log so only a short digest enters the parent context.
|
|
32
33
|
- **An explicitly selected candidate librarian or handoff draft:** compressing a handoff under its byte cap, or drafting a `codify` or
|
|
33
34
|
`consolidate` candidate for human review.
|
|
@@ -35,6 +36,29 @@ decision, proposed before it runs.
|
|
|
35
36
|
Not for multi-file architectural planning, ambiguous requirements (`clarify`), or pre-merge verdicts
|
|
36
37
|
(`reviewer`).
|
|
37
38
|
|
|
39
|
+
## When it pays
|
|
40
|
+
|
|
41
|
+
Each call is itself one parent request: the parent writes the instruction, waits and reads the receipt,
|
|
42
|
+
and that request re-reads the whole context like any other. In 0038's `m_r × (F·T + g·T²/2)`, a call that
|
|
43
|
+
replaces one parent Edit leaves `T` as it was and lowers `g` only by the output it saves, the difference
|
|
44
|
+
between writing the instruction and writing the code. A miss, a call that keeps nothing or a kept edit the
|
|
45
|
+
parent then touches up, adds a whole request. So:
|
|
46
|
+
|
|
47
|
+
- **Keep an edit the parent can write in one short Edit in the parent.** Delegate only where one call
|
|
48
|
+
replaces more parent work than the request it costs.
|
|
49
|
+
- **Whether a call pays turns on its miss rate and the context's size.** With `s` the output a kept call
|
|
50
|
+
saves, priced at 0038's five times input, and `c` a miss's re-read of the context `C` at the tenth of
|
|
51
|
+
input that `cli/ledger.mjs` takes for a cache read, a call pays only while its miss rate stays under
|
|
52
|
+
`s / (s + c)`, and under less once a miss's own output is counted. One run in
|
|
53
|
+
[#583](https://github.com/sleepy-panda-srl/portulan/issues/583) measured a 20% miss rate at a median `C`
|
|
54
|
+
of 236k. The output a kept call saved, derived from the parent's output on the edits it wrote itself, was
|
|
55
|
+
250–650 tokens and is overstated, since the parent kept its larger edits. That puts break-even at 5–12%,
|
|
56
|
+
a net loss, from one run, one local model and one parent model, and the verdict rests on the tenth: at
|
|
57
|
+
0038's fortieth, break-even is 17–35%.
|
|
58
|
+
- **Where a call should pay is reasoning, not measurement:** a red→green loop the parent would otherwise
|
|
59
|
+
iterate, a batch of calls inside one tool request, and a long mechanical replacement the parent would
|
|
60
|
+
otherwise write out.
|
|
61
|
+
|
|
38
62
|
## The pass
|
|
39
63
|
|
|
40
64
|
1. **Scope the input first.** Outline a file of 300 lines or more with
|
package/spec/README.md
CHANGED
|
@@ -228,15 +228,40 @@ number governing both would make a bump in either mean a change in the other:
|
|
|
228
228
|
|
|
229
229
|
| Schema | Manifest key | Current | What it governs |
|
|
230
230
|
|---|---|---|---|
|
|
231
|
-
| [`workspace.schema.json`](workspace.schema.json) | `portulan.spec` | **2.
|
|
232
|
-
| [`pack.schema.json`](pack.schema.json) | `portulan.pack` | **1.
|
|
231
|
+
| [`workspace.schema.json`](workspace.schema.json) | `portulan.spec` | **2.16** | the Workspace Definition — the manifest at a workspace root |
|
|
232
|
+
| [`pack.schema.json`](pack.schema.json) | `portulan.pack` | **1.1** | the Pack Definition — the manifest at a pack root. |
|
|
233
233
|
|
|
234
234
|
The rules below apply to each train independently. `portulan.spec` is `MAJOR.MINOR`, and the current
|
|
235
|
-
Workspace Definition version is **2.
|
|
235
|
+
Workspace Definition version is **2.16**. It did **not** move when the Pack Definition arrived, because
|
|
236
236
|
`workspace.schema.json` was byte-identical across that change: `packs` already existed as an array of
|
|
237
237
|
strings and was deliberately left that way, since tightening its items to the canonical `category/name`
|
|
238
238
|
form would be a constraint an existing manifest could newly fail, which is a MAJOR.
|
|
239
239
|
|
|
240
|
+
**2.16 adds optional `spend.resume.tokens`**, from [#503](https://github.com/sleepy-panda-srl/portulan/issues/503).
|
|
241
|
+
It prices additional restart expense in uncached-input-token equivalents. Doctor
|
|
242
|
+
checks its finite nonnegative bound by hand and gates it to 2.16; compile refuses
|
|
243
|
+
older declarations before writing. Index and librarian recognize the new minor.
|
|
244
|
+
Existing manifests need no change: unknown resume cost or a default horizon now
|
|
245
|
+
permits a conditional report only, including for an existing `restart: "block"`.
|
|
246
|
+
No estimate is installed automatically, and init gains no question.
|
|
247
|
+
|
|
248
|
+
**2.15 adds optional handoff budgets**, accepted proposal 0037: a per-record token cap with a required
|
|
249
|
+
cutoff, and a generated index window with a token budget. Existing manifests need no changes. Both
|
|
250
|
+
budgets require an explicit context ratio; doctor checks their positive integer bounds and real cutoff,
|
|
251
|
+
and direct index calls independently refuse malformed declarations. Doctor gates the keys to 2.15;
|
|
252
|
+
index and librarian add that supported version. Init opts into 2.15 only when a budget is accepted;
|
|
253
|
+
upgrade installs no cap or ratio unasked. This workspace defers its proposed 3000/3000 adoption until
|
|
254
|
+
it has a measured ratio. The series remains unbudgeted and the Stop-gate checks no length.
|
|
255
|
+
|
|
256
|
+
**2.14 adds optional `verify.stopTimeout`.** The workspace may declare the Stop
|
|
257
|
+
recipe's deadline in whole seconds; absent, it remains ninety seconds. `doctor`,
|
|
258
|
+
`compile` and the runner share its range and version check, and compile gives
|
|
259
|
+
that hook sixty additional seconds. No existing declaration is tightened or
|
|
260
|
+
rewritten. `KNOWN_SPECS` in index and librarian gains `2.14` by addition; writers
|
|
261
|
+
retain the oldest version their actual fields require. The schema subset types
|
|
262
|
+
this field as `number`; the shared reader checks the integer/range constraint.
|
|
263
|
+
This adds one hand-check to 2.12's fifteen, without expanding the schema subset.
|
|
264
|
+
|
|
240
265
|
**2.13 is a MINOR on 2.8's terms: optional keys only.** `spend.restart` is the last piece of item 5 of
|
|
241
266
|
proposal `0038`'s order of work, the declared block its ruling 3 allows: `"advise"` is the restart advisory as
|
|
242
267
|
it was, and `"block"` also holds a turn's end once, at the first stop at or past the threshold that no block
|
|
@@ -434,7 +459,7 @@ workspace with no constitution slot and a green report.
|
|
|
434
459
|
| A declared pack **resolves** | `doctor` | **Built**. Roots are derived from `tree`, and named roots may be given with `--pack-root`, which **replace** the derived one rather than preceding it, so a from-a-feed demonstration cannot be satisfied by a copy in the local tree at all. A workspace with no `tree` and no named root has its packs reported *unverifiable* rather than failed. Resolution **from a feed is demonstrated**: the checkpoint ritual pack installed from the private `portulan-internal` marketplace and resolved from the install root, which is the same code path the derived one takes. **Discovery is built** ([#123](https://github.com/sleepy-panda-srl/portulan/issues/123)): `--pack-root auto` reads the host's installed-plugin record — in **both** shapes a plugin lands in, packs under `packs/` for a repository-shaped one and categories at the install root for a flat one, which is what this project's own feed ships. Asked-for discovery is **unioned** with the tree-derived root, discovered first, so a workspace composing a cache-installed pack *and* one of its own resolves both. The **named**-root property above is untouched: a named root still replaces the derived one, so a from-a-feed demonstration given one cannot be satisfied by a local copy. What the union trades is the weaker guarantee that `auto` *alone* implied provenance; it no longer does, and each pack's resolution states which root answered instead. Asking for a named root **and** `auto` together is refused (exit 2) rather than silently resolved to one of them. Discovery runs on the **unasked** path too, because `--pack-root` is *optional where discovery finds a root*. `auto` now selects the strict degrade (asked-and-could-not-look is exit 2) rather than unlocking discovery. Two things bound it, and neither makes a bare run host-independent — `doctor` is a per-host capability report and a bare verdict does move with the machine. First, every **required** check **names** its root, which replaces every other source, so it cannot consult the host at all. Second, `doctor`'s note-vs-fail keying is on a root's **origin**, so discovery can turn a note into a **resolution** and never a **miss** into a failure — a miss under a root the workspace or the caller claimed stays a FAIL whatever is installed. It softens nothing: a discovered copy that resolves and is *invalid* still fails, with its origin named. |
|
|
435
460
|
| A pack manifest conforms to the Pack Definition | `doctor` | **Built**. Same validator, same subset, a different schema and version train. |
|
|
436
461
|
| A pack's skills and personas are opened and validated | `doctor` | **Built**, on the maintainer's ruling that row 7's validation reads broad. A skill's frontmatter (`name` kebab-case, `description` non-empty — the two a host reads before loading a body); a persona against the five-part contract, **including the memory scope** `core/operating/memory.md` promised would be validated at this milestone; and the tier no role may claim. Containment is checked **after resolution**, because opening these paths is what made an escaping value stop being inert. Three honest limits, each measured rather than assumed. The skills walk stops **three levels down**, matching `cli/plugin-lint.mjs`'s bound so two walkers over the same key cannot disagree, and it **reports the directories it did not descend into** rather than counting a root it never opened as empty. The Prohibited check is a prose heuristic defeated by any negation **in the same block** — a bullet list counts as one block — and the structural fix is a declared reach *field*, a contract change that is not built. And the five-part check is **presence**, not content: a persona whose sections are all empty passes, so a declared memory scope is read and not honoured. |
|
|
437
|
-
| A persona's declared context fields — `model`, `effort`, `omitClaudeMd`, `cacheLifetime` | `doctor` · `compile` | **Built** for
|
|
462
|
+
| A persona's declared context fields — `model`, `effort`, `omitClaudeMd`, `cacheLifetime` | `doctor` · `compile` | **Built** for composed personas and the core personas in this engine's own declared tree, with no schema version change. The optional fields belong to [`../core/personas/README.md`](../core/personas/README.md); neither reader defaults them. Compiler and doctor select existing `agents/<name>.md` bindings for plugins and `.claude/agents/<name>.md` for ordinary projects. `--check` byte-compares declared fields, including in a persona-only project. Missing adopter bindings remain unbound and are not created; malformed/nonregular/escaping marker and binding inputs are refused. A degradation line names each field an AGENTS.md host cannot express. This establishes source-derived files and selected locations, not host enablement or runtime honoring of a key. |
|
|
438
463
|
| Pack gate fragments merge **tighten-only** | [`../cli/compile.mjs`](../cli/compile.mjs) | **Built**. Two layers and two axes: `auto` is absent from the Pack Definition's tier enum, so a demotion to unattended is unexpressible; the comparison against the composed base is the compiler's, and both a weakened **tier** and a changed **action** throw rather than being dropped. The action half is the one a tier-only check misses — raising the tier while swapping the matcher removes the gate and still reads as a tightening. |
|
|
439
464
|
| A pack declaring a version ahead of `doctor` | `doctor` | **Built**. Refused rather than graded, symmetric with the workspace train, and on the Pack Definition's own `$id`. |
|
|
440
465
|
| Path slots resolve to real files | `doctor` | **Built.** File-versus-directory too; a slot resolving outside the workspace is reported, never failed. |
|
|
@@ -501,3 +526,11 @@ repositories are *not present* is what exposed that the claims lint had no way t
|
|
|
501
526
|
|
|
502
527
|
Illustrative fragments in [`slots.md`](slots.md) use `{braces}` for placeholders, the same convention as
|
|
503
528
|
[`../core/templates/`](../core/templates/).
|
|
529
|
+
|
|
530
|
+
Pack Definition 1.1 adds optional ritual `contributes.contexts` with
|
|
531
|
+
`max_concurrent_subagents`. Doctor and compiler require a positive safe integer
|
|
532
|
+
and the 1.1 contract; the schema subset expresses only its numeric shape.
|
|
533
|
+
Counts compose by minimum and compile to Claude project settings, with the
|
|
534
|
+
[admission scope and exceptions](../packs/rituals/checkpoints/README.md#context-admission)
|
|
535
|
+
stated separately from the ledger's actual lifetime count. Workspace Definition
|
|
536
|
+
is unchanged; rituals remain packs, as recorded in [slots](slots.md).
|
|
@@ -1,13 +1,29 @@
|
|
|
1
1
|
// A form step — each section a team marks in its own instruction file moves to an on-read unit.
|
|
2
2
|
|
|
3
3
|
import { ESTIMATED_BYTES_PER_TOKEN, alwaysTier, declaredContext, tokensOf } from "../../cli/context.mjs";
|
|
4
|
+
import { previewMigration } from "../../cli/init-preview.mjs";
|
|
4
5
|
import { notYetForm } from "../../cli/form.mjs";
|
|
5
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
INSTRUCTION_FILES,
|
|
8
|
+
contextDir,
|
|
9
|
+
grouped,
|
|
10
|
+
instructionReader,
|
|
11
|
+
linkRefusal,
|
|
12
|
+
marksOf,
|
|
13
|
+
planSplit,
|
|
14
|
+
splitLines,
|
|
15
|
+
} from "../../cli/instructions.mjs";
|
|
6
16
|
|
|
7
17
|
function splitOf(ws) {
|
|
8
18
|
const tree = ws.repository.dir;
|
|
9
19
|
const context = contextDir(tree, ws.dir, ws.manifest);
|
|
10
|
-
const taken =
|
|
20
|
+
const taken =
|
|
21
|
+
context === null
|
|
22
|
+
? []
|
|
23
|
+
: ws.repository
|
|
24
|
+
.names(context)
|
|
25
|
+
.filter((name) => name.endsWith(".md"))
|
|
26
|
+
.map((name) => name.slice(0, -3));
|
|
11
27
|
return planSplit({ tree, context, taken, read: instructionReader(tree, ws.repository.read) });
|
|
12
28
|
}
|
|
13
29
|
|
|
@@ -26,29 +42,62 @@ export const step = {
|
|
|
26
42
|
owed(ws, ctx) {
|
|
27
43
|
const behind = notYetForm(ws, ctx);
|
|
28
44
|
if (behind) return { owed: false, because: behind };
|
|
29
|
-
if (!ws.repository)
|
|
45
|
+
if (!ws.repository)
|
|
46
|
+
return {
|
|
47
|
+
owed: false,
|
|
48
|
+
because: "this workspace declares no tree, so there is no instruction file of its own",
|
|
49
|
+
};
|
|
30
50
|
const read = instructionReader(ws.repository.dir, ws.repository.read);
|
|
31
51
|
const marked = INSTRUCTION_FILES.filter((rel) => marksOf(read(rel) ?? "").marks.length > 0);
|
|
32
|
-
if (marked.length === 0)
|
|
52
|
+
if (marked.length === 0)
|
|
53
|
+
return {
|
|
54
|
+
owed: false,
|
|
55
|
+
because: "no section of CLAUDE.md or .claude/CLAUDE.md is marked `<!-- portulan: on-read -->`",
|
|
56
|
+
};
|
|
33
57
|
if (typeof ws.manifest?.slots?.context !== "string") {
|
|
34
|
-
return {
|
|
58
|
+
return {
|
|
59
|
+
owed: false,
|
|
60
|
+
because: `${marked.join(" and ")} marks a section, and \`slots.context\` is undeclared: \`0006\` declares it, and this step is asked again then`,
|
|
61
|
+
};
|
|
35
62
|
}
|
|
36
63
|
const linked = marked.map((rel) => linkRefusal(ws.repository.dir, rel)).filter((refusal) => refusal !== null);
|
|
37
|
-
if (linked.length)
|
|
64
|
+
if (linked.length)
|
|
65
|
+
return {
|
|
66
|
+
owed: true,
|
|
67
|
+
hand: true,
|
|
68
|
+
because: `a marked section cannot move, so none is moved — ${linked.join("; ")}, or take the marks out, then upgrade again`,
|
|
69
|
+
};
|
|
38
70
|
const split = splitOf(ws);
|
|
39
|
-
if (split.refusals.length)
|
|
71
|
+
if (split.refusals.length)
|
|
72
|
+
return { owed: true, because: `a marked section cannot move — ${split.refusals.join("; ")}` };
|
|
40
73
|
const ratio = declaredContext(ws.manifest).ratio ?? ESTIMATED_BYTES_PER_TOKEN;
|
|
41
|
-
const
|
|
42
|
-
|
|
74
|
+
const projected = previewMigration(ws.repository.dir, ws.dir, [
|
|
75
|
+
...split.files.map((f) => ({ file: f.rel, next: f.after })),
|
|
76
|
+
...split.units.map((u) => ({ file: u.source, next: u.text })),
|
|
77
|
+
]);
|
|
78
|
+
const before = projected.before[0],
|
|
79
|
+
after = projected.after?.[0];
|
|
43
80
|
return {
|
|
44
81
|
owed: true,
|
|
45
|
-
because: `${splitLines(split).join("; ")}; the always tier goes from ~${grouped(tokensOf(before, ratio))} to ~${grouped(tokensOf(after, ratio))} tokens`,
|
|
82
|
+
because: `${splitLines(split).join("; ")}; ${after === undefined ? "the after forecast is unavailable because native imports cannot be projected exactly" : `the always tier goes from ~${grouped(tokensOf(before, ratio))} to ~${grouped(tokensOf(after, ratio))} tokens`}`,
|
|
46
83
|
};
|
|
47
84
|
},
|
|
48
85
|
|
|
49
86
|
plan(ws) {
|
|
50
87
|
const split = splitOf(ws);
|
|
51
|
-
if (split.refusals.length)
|
|
88
|
+
if (split.refusals.length)
|
|
89
|
+
return {
|
|
90
|
+
ok: false,
|
|
91
|
+
reason: `a marked section cannot move, so none is moved — ${split.refusals.join("; ")}`,
|
|
92
|
+
};
|
|
93
|
+
try {
|
|
94
|
+
previewMigration(ws.repository.dir, ws.dir, [
|
|
95
|
+
...split.files.map((f) => ({ file: f.rel, next: f.after })),
|
|
96
|
+
...split.units.map((u) => ({ file: u.source, next: u.text })),
|
|
97
|
+
]);
|
|
98
|
+
} catch (error) {
|
|
99
|
+
return { ok: false, reason: `prospective guidance refuses — ${error.message}` };
|
|
100
|
+
}
|
|
52
101
|
return {
|
|
53
102
|
ok: true,
|
|
54
103
|
edits: [
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// Refresh existing persona context fields through the compiler's ownership planner.
|
|
2
|
+
import { personaEdits } from "../../cli/compile.mjs";
|
|
3
|
+
import { notYetForm } from "../../cli/form.mjs";
|
|
4
|
+
|
|
5
|
+
export const step = {
|
|
6
|
+
id: "0011-persona-bindings-compiled", kind: "form", from: null, to: null,
|
|
7
|
+
title: "existing eligible persona bindings carry the shipped context declarations",
|
|
8
|
+
why: "The shipped persona route and its A/B evidence must arrive on upgrade. Refresh existing compiler-owned context fields; never create a binding, widen core source selection, or write gate settings.",
|
|
9
|
+
owed(ws, ctx) {
|
|
10
|
+
const behind = notYetForm(ws, ctx);
|
|
11
|
+
if (behind) return { owed: false, because: behind };
|
|
12
|
+
if (!ws.repository) return { owed: false, because: "no declared repository tree holds persona bindings" };
|
|
13
|
+
const planned = personaEdits(ws.dir);
|
|
14
|
+
return {
|
|
15
|
+
owed: planned.edits.length > 0,
|
|
16
|
+
because: planned.edits.length ? `${planned.edits.map(edit => edit.file).join(", ")} differ from their persona declarations` :
|
|
17
|
+
planned.compared ? "existing eligible persona bindings agree with their declarations" : "no existing eligible binding is selected; missing or foreign core bindings are not installed",
|
|
18
|
+
};
|
|
19
|
+
},
|
|
20
|
+
plan(ws) {
|
|
21
|
+
return { ok: true, edits: personaEdits(ws.dir).edits.map(edit => ({ root: "tree", ...edit })) };
|
|
22
|
+
},
|
|
23
|
+
};
|
|
@@ -107,6 +107,7 @@ is a question that could not be answered.
|
|
|
107
107
|
| [`0008-card-reading.mjs`](0008-card-reading.mjs) | `form` | A card drafted before it carried the engine's rules on reading and the cache gets that section, which `compile` writes out from the installed Portulan's `core/operating/context.md`, and `0007` compiles it. A card lacking the section under any other head is owed it by hand: the run names the head the step looks for and the line to add, applies the rest of the chain and exits 1, and `doctor` names the card until the line is there. |
|
|
108
108
|
| [`0009-instruction-sections-on-read.mjs`](0009-instruction-sections-on-read.mjs) | `form` | Each section a team marks with a line `<!-- portulan: on-read -->` under its heading, in `CLAUDE.md` or `.claude/CLAUDE.md`, moves byte for byte into an on-read unit of `slots.context`, and a comment is left where it was. **Proved before it is offered**: the file must reassemble from its units byte for byte and each clause land once, or the step refuses; a section importing a file, a mark inside a marked section and a mark under no heading are refused too, and a mark in an instruction file that is a link is owed by hand while the rest of the chain runs. Owed only where a team marked a section, and put back by [`../../cli/instructions.mjs`](../../cli/instructions.mjs) `--join`. |
|
|
109
109
|
| [`0010-comment-rail.mjs`](0010-comment-rail.mjs) | `form` | Where a `repository` workspace's tree is one git lists and no `comments` recipe is declared, `verify/comments.sh` is drafted as `init` drafts it, holding the comment lines that record a change's history at the count the tree has, and declared. **Refused where `verify/comments.sh` is already there undeclared**, rather than written over. |
|
|
110
|
+
| [`0011-persona-bindings-compiled.mjs`](0011-persona-bindings-compiled.mjs) | `form` | Refresh existing eligible persona bindings through the compiler's ownership planner. Core sources participate only in the installed engine's own tree; composed pack sources retain their existing boundary. Missing bindings are not created and gate settings are untouched. |
|
|
110
111
|
|
|
111
112
|
**`0001` has no subject in this tree** — nothing declares 1.0 — and is exercised against a fixture,
|
|
112
113
|
which is said here rather than dressed up. **`0002` has no subject in this repository either**: this
|
|
@@ -118,6 +119,11 @@ either, since this repository keeps no `CLAUDE.md`, and is exercised against suc
|
|
|
118
119
|
instruction file the suite marks; `0010` against one `init` drafted where git listed no files, then
|
|
119
120
|
committed.
|
|
120
121
|
|
|
122
|
+
`0011` is exercised against exact v0.2.0 core bindings in a copy of the engine and against
|
|
123
|
+
foreign trees and absent bindings. The command-level demonstration holds unrelated bytes,
|
|
124
|
+
modes and settings constant, checks idempotence, and forces a staging collision after one
|
|
125
|
+
replacement to demonstrate rollback while preserving the unowned collision file.
|
|
126
|
+
|
|
121
127
|
## Adding one
|
|
122
128
|
|
|
123
129
|
Drop a module in, named `NNNN-slug.mjs`. `cli/upgrade.mjs` reads the directory and orders by filename,
|
package/spec/pack.schema.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
-
"$id": "https://portulan.dev/spec/pack/1.
|
|
4
|
-
"title": "Portulan Pack Definition v1.
|
|
3
|
+
"$id": "https://portulan.dev/spec/pack/1.1/pack.schema.json",
|
|
4
|
+
"title": "Portulan Pack Definition v1.1",
|
|
5
5
|
"description": "The manifest at the root of a pack. A pack is the cascade's middle layer — core < pack < workspace < repo card < task — and this manifest declares what it contributes to that cascade: skills, personas, verify recipes, and gate-policy fragments. It is an index, not a container, for the same reason the Workspace Definition is: the content lives in the files the paths point at. Written in the same deliberately small subset of JSON Schema 2020-12 — see spec/README.md for the subset, for what validates this, and for why a pack carries its own version train rather than the Workspace Definition's.",
|
|
6
6
|
"type": "object",
|
|
7
7
|
"additionalProperties": false,
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
},
|
|
50
50
|
|
|
51
51
|
"contributes": {
|
|
52
|
-
"description": "What this pack adds to the cascade. Every key is optional and an absent key contributes nothing of that kind — but the object itself is required, because a pack contributing nothing is a plugin, not a pack, and the manifest should say which it is rather than leaving it to be inferred from
|
|
52
|
+
"description": "What this pack adds to the cascade. Every key is optional and an absent key contributes nothing of that kind — but the object itself is required, because a pack contributing nothing is a plugin, not a pack, and the manifest should say which it is rather than leaving it to be inferred from optional absences.",
|
|
53
53
|
"type": "object",
|
|
54
54
|
"additionalProperties": false,
|
|
55
55
|
"properties": {
|
|
@@ -74,6 +74,15 @@
|
|
|
74
74
|
"minItems": 1,
|
|
75
75
|
"items": { "$ref": "#/$defs/recipe" }
|
|
76
76
|
},
|
|
77
|
+
"contexts": {
|
|
78
|
+
"description": "Since Pack Definition 1.1, ritual packs only: ordinary fresh native Agent admission within one session. Positive safe integer checked by doctor and compiler; number is the schema subset's numeric type. Multiple packs compose by minimum. Claude env compiles even without a gate policy. Parent, lifetime totals, resumes/forks and independent agent systems are outside the bound; see packs/rituals/checkpoints/README.md.",
|
|
79
|
+
"type": "object",
|
|
80
|
+
"additionalProperties": false,
|
|
81
|
+
"required": ["max_concurrent_subagents"],
|
|
82
|
+
"properties": {
|
|
83
|
+
"max_concurrent_subagents": { "type": "number" }
|
|
84
|
+
}
|
|
85
|
+
},
|
|
77
86
|
"gates": {
|
|
78
87
|
"description": "Gate-policy fragments — the cascade's missing middle, and the contribution that needs care, because an installed pack reaching the gate map means a dependency can change what an agent is allowed to do. The floor is the maintainer's ruling of 2026-07-27 recorded in .portulan/proposals/0010-prohibited-as-a-fourth-universal-tier.md: a pack may raise a tier or add a prohibition, and may NEVER demote another layer's classification. Enforcement is split between this schema and the merge step, and the split is stated rather than implied — see `$defs/fragment`. READ TODAY, like `personas` and unlike `skills` and `verify`: cli/compile.mjs is the merge step, and it resolves this key when a pack root is given.",
|
|
79
88
|
"type": "array",
|