@sleepy_panda_srl/portulan 0.2.0 → 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.
- package/README.md +2 -2
- package/cli/README.md +6 -3
- package/cli/compile.mjs +318 -13
- package/cli/doctor.mjs +46 -7
- package/cli/drills.mjs +26 -4
- package/cli/ledger.mjs +110 -6
- package/cli/portulan.mjs +1 -1
- package/cli/review-meter.mjs +14 -9
- package/cli/telemetry.mjs +57 -15
- package/core/operating/context.md +79 -4
- package/core/personas/README.md +33 -0
- package/core/skills/clarify/SKILL.md +1 -1
- package/core/skills/consolidate/SKILL.md +1 -1
- package/evals/README.md +23 -7
- package/evals/releases/0.3.0.json +150 -0
- package/evals/releases/0.3.0.md +68 -0
- package/package.json +2 -1
- package/packs/.claude-plugin/plugin.json +2 -1
- package/packs/README.md +7 -4
- package/packs/tools/README.md +8 -8
- package/packs/tools/local-llm/README.md +136 -0
- package/packs/tools/local-llm/pack.json +32 -0
- package/packs/tools/local-llm/personas/local-worker.md +44 -0
- package/packs/tools/local-llm/runner/local-llm.mjs +1665 -0
- package/packs/tools/local-llm/skills/delegate-local/SKILL.md +64 -0
- package/packs/tools/local-llm/verify/README.md +18 -0
- package/spec/README.md +1 -0
- package/spec/slots.md +10 -6
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: delegate-local
|
|
3
|
+
description: Offload a verify-gated one-file edit, a file digest or a draft to a local model. Use only where the workspace composes tools/local-llm and a server answers on loopback.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Skill — Delegate Local
|
|
7
|
+
|
|
8
|
+
> Every turn in a frontier session re-reads its whole context (`m_r × (F·T + g·T²/2)`, proposal
|
|
9
|
+
> [`0038`](../../../../../.portulan/proposals/0038-what-a-change-spends-is-measured.md)). This skill moves a
|
|
10
|
+
> bounded, high-turn or read-heavy sub-task off that context and onto a local model on loopback, through
|
|
11
|
+
> [`runner/local-llm.mjs`](../../runner/local-llm.mjs) and under a verify command.
|
|
12
|
+
|
|
13
|
+
## Before you start
|
|
14
|
+
|
|
15
|
+
`<runner>` below is `runner/local-llm.mjs` in this pack's directory, two levels above this file:
|
|
16
|
+
`<plugin root>/packs/tools/local-llm/runner/local-llm.mjs` where the plugin is the repository root, and
|
|
17
|
+
`<plugin root>/tools/local-llm/runner/local-llm.mjs` where it is the `packs/` payload.
|
|
18
|
+
|
|
19
|
+
The runner talks to an OpenAI-compatible server on loopback: LM Studio on `127.0.0.1:1234` by default,
|
|
20
|
+
or `--base-url` (or `PORTULAN_LOCAL_LLM_URL`) for another loopback port such as Ollama's `11434`. Run
|
|
21
|
+
`node <runner> status` first. Exit `0` lists the models the server holds; exit `1` is an endpoint
|
|
22
|
+
refused as off loopback; exit `2` is could-not-run — no server answered, it answered with an HTTP
|
|
23
|
+
error, or `--base-url` is not a URL. On anything but `0`, do the work in the parent session. Never pass
|
|
24
|
+
`--allow-remote` on your own authority: an endpoint off loopback is the `local-llm-off-loopback`
|
|
25
|
+
decision, proposed before it runs.
|
|
26
|
+
|
|
27
|
+
## When to use it
|
|
28
|
+
|
|
29
|
+
- **A verify-gated file transform:** a mechanical refactor, a history-comment cleanup, or a unit test
|
|
30
|
+
added, where a verify command can prove the edit green or have it rolled back.
|
|
31
|
+
- **An explicitly selected candidate research digest:** reading a long file or log so only a short digest enters the parent context.
|
|
32
|
+
- **An explicitly selected candidate librarian or handoff draft:** compressing a handoff under its byte cap, or drafting a `codify` or
|
|
33
|
+
`consolidate` candidate for human review.
|
|
34
|
+
|
|
35
|
+
Not for multi-file architectural planning, ambiguous requirements (`clarify`), or pre-merge verdicts
|
|
36
|
+
(`reviewer`).
|
|
37
|
+
|
|
38
|
+
## The pass
|
|
39
|
+
|
|
40
|
+
1. **Scope the input first.** Outline a file of 300 lines or more with
|
|
41
|
+
`node <plugin root>/cli/symbols.mjs <file>` and pass `--file <file> --lines <start>-<end>` (to
|
|
42
|
+
`transform`, `chat` and `summarize` alike) rather than the whole file. `cli/` ships with the
|
|
43
|
+
repository-root plugin and not in the `packs/` payload; there, find the span's line numbers with
|
|
44
|
+
`grep -n`. Prefill scales with prompt bytes: proposal 0039 measured prompts of about 400–470 tokens
|
|
45
|
+
prefilling in 0.85–1.88 s, and the `32 KB` default of `--max-input-bytes` (about 8k tokens) is a
|
|
46
|
+
ceiling, not a measured latency.
|
|
47
|
+
2. **Use an earned role.** Code transforms and strict syntax use `--role code`
|
|
48
|
+
(`qwen/qwen3.8-27b`) at `--effort low`, supported by the
|
|
49
|
+
[captured A/B](../../../../../evals/ab/local-worker.md)'s L5/H5 code cell. Shared `--role prose`
|
|
50
|
+
(`google/gemma-4-31b`) is disabled: codify scored L0/H3, despite handoff's L5/H5. Keep digests and
|
|
51
|
+
librarian drafts in the parent session. `summarize` defaults to prose and refuses; explicit roles,
|
|
52
|
+
model overrides and environment mismatches cannot bypass the guard. An operator can select a
|
|
53
|
+
candidate with `--model <id>` and no `--role`; that is deliberate evaluation, not earned routing.
|
|
54
|
+
Screen a candidate with `node <runner> eval --model <id>`; it needs `cli/` beside `packs/` and exits
|
|
55
|
+
`2` without it. Passing a screen never enables a role.
|
|
56
|
+
3. **Gate every edit.** `node <runner> transform --file <path> --verify "<cmd>" --prompt "<instruction>"`,
|
|
57
|
+
where `--verify` is required. The runner applies the edit, runs `<cmd>`, retries with the end of its
|
|
58
|
+
output on exit `1`, and keeps the edit only on exit `0`; any other exit, and SIGINT, SIGTERM or
|
|
59
|
+
SIGHUP, restores the file byte for byte. It is a rollback, not an atomic write: any other signal that
|
|
60
|
+
ends the runner, SIGKILL and SIGQUIT among them, or a power loss between the edit and its verdict
|
|
61
|
+
leaves the edit in place.
|
|
62
|
+
4. **Read only the receipt back.** On exit `0`, take the one-line receipt and do not re-read the file
|
|
63
|
+
unless a later edit depends on its new line numbers. On exit `1` or `2`, escalate to the frontier
|
|
64
|
+
implementer rather than looping locally again.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# `runner-rails` — what it checks, and what it cannot
|
|
2
|
+
|
|
3
|
+
One recipe: `node ${PACK_ROOT}/runner/local-llm.mjs --verify-offline`, run from the adopting repository's root.
|
|
4
|
+
|
|
5
|
+
## The three exits
|
|
6
|
+
|
|
7
|
+
| Exit | Means |
|
|
8
|
+
|---|---|
|
|
9
|
+
| `0` | all offline runner rails hold: non-loopback URLs are refused without `--allow-remote`, oversized inputs above `--max-input-bytes` are refused before network I/O, a verify command ending in any exit but `0` or `1` is could-not-run with the file restored, a green verify command keeps the edit with a one-line receipt, transform rollback restores original file bytes on a red verify command, and `dedup` refuses a non-loopback endpoint and a file over the ceiling before any request |
|
|
10
|
+
| `1` | at least one runner rail failed to fire (a forced-red check passed when it should have been refused, rollback left modified bytes behind, a green verify did not keep its edit with a one-line receipt, or `dedup` reached its request) |
|
|
11
|
+
| `2` | **could not run** — [`runner/local-llm.mjs`](../runner/local-llm.mjs) or the Node.js runtime could not be executed |
|
|
12
|
+
|
|
13
|
+
## Why `--verify-offline` makes no network or loopback call
|
|
14
|
+
|
|
15
|
+
A verify recipe in Portulan may never make a network call, and a local LLM server (`localhost:1234`) is
|
|
16
|
+
machine-specific state that is absent in CI. `--verify-offline` exercises the runner's guards against
|
|
17
|
+
in-process synthetic fixtures and three temporary-file transform drills (red, no verdict, green) and a `dedup` drill over two temporary files, proving the rails hold without
|
|
18
|
+
touching a socket. Live model health checks and role grading use `status` or `eval` outside recipes.
|
package/spec/README.md
CHANGED
|
@@ -434,6 +434,7 @@ workspace with no constitution slot and a green report.
|
|
|
434
434
|
| 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
435
|
| A pack manifest conforms to the Pack Definition | `doctor` | **Built**. Same validator, same subset, a different schema and version train. |
|
|
436
436
|
| 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 a persona a composed pack ships, with no schema version moved, since no persona file is governed by either schema: the fields belong to the persona contract [`../core/personas/README.md`](../core/personas/README.md) fixes, and are optional. `doctor` fails a malformed value and `compile` refuses one; neither defaults any. `compile` writes the declared fields into the role's hand-written binding, `agents/<name>.md` in the tree, byte-compared under `--check`, and prints a degradation line for each field a host cannot express. Claude Code loads `agents/` only from a plugin root, so in a tree that is not one the compiled block is loaded by nothing, and that degradation line says so; in one that is, it loads only while the plugin is installed and enabled, which `compile` cannot see. **Not built**: writing `.claude/agents/`, where a repository that is not a plugin loads its agents; a binding where none exists, which is reported unbound; and the compiling of a core persona's binding. |
|
|
437
438
|
| 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. |
|
|
438
439
|
| 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`. |
|
|
439
440
|
| Path slots resolve to real files | `doctor` | **Built.** File-versus-directory too; a slot resolving outside the workspace is reported, never failed. |
|
package/spec/slots.md
CHANGED
|
@@ -255,7 +255,7 @@ Two obligations, both learned from defects rather than designed in the abstract:
|
|
|
255
255
|
### `floor` — the platform floor this policy compiles to
|
|
256
256
|
|
|
257
257
|
**Added in 2.2, optional**, inside the gate policy rather than the manifest — it is policy, and policy
|
|
258
|
-
lives where the rules live.
|
|
258
|
+
lives where the rules live. Its keys exist because it varies per repository and the
|
|
259
259
|
floor backend would otherwise have to guess it:
|
|
260
260
|
|
|
261
261
|
| Key | What it declares |
|
|
@@ -265,11 +265,15 @@ floor backend would otherwise have to guess it:
|
|
|
265
265
|
| `reviews` | Required approving reviews. Declared rather than defaulted: 0 and 1 are a real difference, and a repository with one maintainer cannot use 1, because GitHub does not permit approving one's own pull request. |
|
|
266
266
|
| `resolve_conversations` | Whether review threads must be resolved before merge. Omitting it would export a floor weaker than the one many repositories already run. |
|
|
267
267
|
|
|
268
|
-
**
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
268
|
+
**Gate-policy 2.3 adds optional `strict`, a boolean defaulting to `true`.** It selects GitHub's native
|
|
269
|
+
strict-versus-loose required-status-check policy. Older gate-policy versions retain strict freshness
|
|
270
|
+
and refuse an explicit `strict` rather than silently ignoring it. A non-boolean is refused.
|
|
271
|
+
|
|
272
|
+
The earlier unconditional strict export encoded this repository's 2026-07-27 ruling as universal
|
|
273
|
+
mechanism. The maintainer's [2026-11-03 amendment](../.portulan/proposals/0040-allow-ten-commits-behind-main.md)
|
|
274
|
+
allows ten commits behind and blocks eleven here, which needs loose native freshness plus a separate
|
|
275
|
+
required check. The compiler emits the declared boolean; it neither implements that threshold for an
|
|
276
|
+
adopter nor applies the settings. Defaulting to `true` preserves every policy that omits the new key.
|
|
273
277
|
|
|
274
278
|
A workspace with no `floor` compiles no floor: the backend refuses every rule *by name*, says the key
|
|
275
279
|
is missing, and writes nothing. That is a legitimate shape — a workspace whose repositories are not on
|