@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.
- package/README.md +7 -6
- package/cli/README.md +145 -358
- package/cli/advisory.mjs +354 -0
- package/cli/comments.mjs +932 -0
- package/cli/compile.mjs +1719 -1699
- package/cli/context.mjs +883 -0
- package/cli/control-chars.mjs +12 -301
- package/cli/discover.mjs +17 -574
- package/cli/doctor.mjs +269 -1080
- package/cli/drills.mjs +92 -504
- 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 +882 -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 +6 -125
- package/cli/recipe-set.mjs +15 -245
- package/cli/release-eval.mjs +28 -348
- package/cli/review-meter.mjs +29 -265
- 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 +82 -505
- 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 +258 -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/README.md +33 -0
- package/core/personas/librarian.md +2 -2
- package/core/skills/clarify/SKILL.md +1 -1
- package/core/skills/consolidate/SKILL.md +5 -3
- package/core/templates/handoff.md +28 -15
- package/core/templates/memory-entry.md +1 -0
- package/evals/README.md +271 -845
- package/evals/releases/0.2.0.json +146 -0
- package/evals/releases/0.2.0.md +67 -0
- package/evals/releases/0.3.0.json +150 -0
- package/evals/releases/0.3.0.md +68 -0
- package/evals/releases/README.md +3 -3
- package/package.json +5 -2
- package/packs/.claude-plugin/plugin.json +2 -1
- package/packs/README.md +7 -4
- package/packs/rituals/checkpoints/README.md +1 -1
- package/packs/tools/README.md +8 -8
- package/packs/tools/github/verify/actions-pinned.sh +10 -55
- 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 +113 -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 +343 -27
- package/spec/workspace.schema.json +145 -4
package/cli/version-carriers.mjs
CHANGED
|
@@ -1,50 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
// ## The defect this exists for
|
|
5
|
-
//
|
|
6
|
-
// `README.md` and `.portulan/products/portulan/product.md` both said the newest release entry was
|
|
7
|
-
// `0.2.0` while `package.json`, both plugin manifests and the registry said `0.1.0`. They were fixed
|
|
8
|
-
// together — and then, one release later, the SAME PAIR drifted again: `README.md` still read
|
|
9
|
-
// "Current release: `0.1.0`" inside the change that cut `0.1.1`. Twice, with the pairing already
|
|
10
|
-
// written down in `docs/plan.md` both times.
|
|
11
|
-
//
|
|
12
|
-
// **A sibling list in prose is not a rail.** This is the rail.
|
|
13
|
-
//
|
|
14
|
-
// It matters more than a tidy number because `README.md` is in `package.json`'s `files`, and npm
|
|
15
|
-
// FREEZES a README per published version: a wrong current-version sentence that reaches a publish
|
|
16
|
-
// cannot be corrected by any edit to `main`, only by cutting another release. That is exactly what
|
|
17
|
-
// `0.1.1` was for.
|
|
18
|
-
//
|
|
19
|
-
// ## Why the record layer is excluded, which is the whole design
|
|
20
|
-
//
|
|
21
|
-
// This repository writes about its own defects, so the retired sentences are QUOTED in the files that
|
|
22
|
-
// record fixing them: `CHANGELOG.md` carries *"The newest release entry is `0.2.0`"* inside the entry
|
|
23
|
-
// explaining that it was retired, and two handoffs plus `docs/plan.md` do the same. A matcher over
|
|
24
|
-
// everything reds on the repository's own account of the repair — the failure mode #286 measured,
|
|
25
|
-
// where both arms it proposed died on this repository's own review corpus.
|
|
26
|
-
//
|
|
27
|
-
// So the scan is scoped to LIVE prose and the record layer is excluded BY PATH. The exclusion is not
|
|
28
|
-
// a convenience: a dated record is supposed to preserve a sentence that is no longer true, and a rail
|
|
29
|
-
// that forbids that would forbid the record.
|
|
30
|
-
//
|
|
31
|
-
// ## What it cannot do, stated rather than discovered
|
|
32
|
-
//
|
|
33
|
-
// A current-version claim spelled a NEW way, in any file, is invisible to it. There is no way to tell
|
|
34
|
-
// "this prose asserts the current version" from "this prose mentions a version" without a matcher, and
|
|
35
|
-
// a matcher only knows the spellings it was given. The accepted practice for that residual is #286's,
|
|
36
|
-
// ruled by the maintainer: **a new spelling gets a pattern here and a fixture first.**
|
|
37
|
-
//
|
|
38
|
-
// The half that IS closed beyond drift: every file in MUST_CARRY has to produce at least one match, so
|
|
39
|
-
// deleting or rewording a known carrier reds instead of silently shrinking the rail's reach.
|
|
40
|
-
//
|
|
41
|
-
// Three further residuals, named because an unstated limit is the one a reader assumes away:
|
|
42
|
-
// · A live claim placed UNDER an excluded prefix is invisible. `.portulan/memory/` in particular
|
|
43
|
-
// holds doctrine-shaped notes rather than only dated records, so a current-version sentence
|
|
44
|
-
// written there would not be checked.
|
|
45
|
-
// · An UNTRACKED carrier is invisible: enumeration is `git ls-files`, deliberately, because a rail
|
|
46
|
-
// that reads the working tree grades files no commit will ever contain.
|
|
47
|
-
// · A NON-MARKDOWN carrier is invisible: the pathspec is `*.md`.
|
|
2
|
+
// Every prose statement of the current version agrees with `package.json`.
|
|
48
3
|
//
|
|
49
4
|
// Exit 0 green · 1 a finding · 2 could not run.
|
|
50
5
|
|
|
@@ -53,35 +8,21 @@ import { join } from "node:path";
|
|
|
53
8
|
import { pathToFileURL } from "node:url";
|
|
54
9
|
import { execFileSync } from "node:child_process";
|
|
55
10
|
|
|
56
|
-
/** Raised when a precondition fails. Always exit 2 — never a finding about the prose. */
|
|
57
11
|
export class CouldNotRun extends Error {}
|
|
58
12
|
|
|
59
|
-
//
|
|
13
|
+
// Each captures the version in group 1; a claim spelled any other way goes unseen until it gets a pattern here.
|
|
60
14
|
export const PATTERNS = [
|
|
61
15
|
{ id: "current-release", re: /\*\*Current release:\s*`([^`]+)`\*\*/g },
|
|
62
16
|
{ id: "newest-entry", re: /newest release entry is\s*`([^`]+)`/g },
|
|
63
17
|
{ id: "supported-current", re: /\|\s*`([^`]+)`\s*\|\s*Yes\s*—\s*the current release\s*\|/g },
|
|
64
18
|
];
|
|
65
19
|
|
|
66
|
-
// Files that MUST carry at least one claim. Losing one is a finding: it means a carrier was deleted
|
|
67
|
-
// or reworded, which is how this rail would otherwise quietly stop covering what it was built for.
|
|
68
20
|
export const MUST_CARRY = ["README.md", "SECURITY.md", ".portulan/products/portulan/product.md"];
|
|
69
21
|
|
|
70
22
|
// The record layer. These files preserve sentences that are no longer true, on purpose.
|
|
71
23
|
export const RECORD_PREFIXES = [
|
|
72
24
|
"CHANGELOG.md",
|
|
73
|
-
|
|
74
|
-
// than left to whether a pattern happens to match one. `evals/releases/<version>.md` is generated
|
|
75
|
-
// per release and preserves that release's conditions forever — so the moment `0.1.4` exists,
|
|
76
|
-
// `0.1.3`'s register is a document stating, correctly and permanently, facts about a version that
|
|
77
|
-
// is no longer current. That is precisely what this exclusion is for. Its own drift is held by the
|
|
78
|
-
// `release-eval` recipe, which byte-compares each register against the capture it was rendered
|
|
79
|
-
// from — so for `<version>.json` and `<version>.md` this moves coverage to the rail that can
|
|
80
|
-
// actually read them rather than removing it. **`evals/releases/README.md` is the exception and is
|
|
81
|
-
// named rather than glossed:** `release-eval` reads only the two version-keyed files, so that
|
|
82
|
-
// README now sits under an excluded prefix with nothing covering it. Harmless today — it states no
|
|
83
|
-
// current version — and it is exactly this module's own third named residual, a live claim under an
|
|
84
|
-
// excluded prefix, so it is recorded here instead of being discovered by the next reader.
|
|
25
|
+
"changes/",
|
|
85
26
|
"evals/releases/",
|
|
86
27
|
"docs/plan.md",
|
|
87
28
|
"docs/milestones/",
|
|
@@ -93,7 +34,6 @@ export const RECORD_PREFIXES = [
|
|
|
93
34
|
|
|
94
35
|
export const isRecord = (p) => RECORD_PREFIXES.some((r) => (r.endsWith("/") ? p.startsWith(r) : p === r));
|
|
95
36
|
|
|
96
|
-
/** Every tracked Markdown file that is not part of the record layer. Derived, never a written list. */
|
|
97
37
|
export function liveProseFiles(root) {
|
|
98
38
|
let out;
|
|
99
39
|
try {
|
|
@@ -109,11 +49,7 @@ export function liveProseFiles(root) {
|
|
|
109
49
|
export function declaredVersion(root) {
|
|
110
50
|
let raw;
|
|
111
51
|
try {
|
|
112
|
-
// From the
|
|
113
|
-
// worktree while reading carriers from the index compares two different trees: a staged
|
|
114
|
-
// version bump with an unchanged worktree would grade staged prose against the OLD version,
|
|
115
|
-
// reporting drift that is not there — or, reversed, greening drift that is. Half a fix is
|
|
116
|
-
// how this rail's own index change first shipped.
|
|
52
|
+
// From the index, like the prose: a version read from the worktree would grade staged prose against another tree.
|
|
117
53
|
raw = execFileSync("git", ["-C", root, "show", ":package.json"], { encoding: "utf8" });
|
|
118
54
|
} catch (e) {
|
|
119
55
|
throw new CouldNotRun(`could not read package.json from the index: ${e.message}`);
|
|
@@ -128,7 +64,6 @@ export function declaredVersion(root) {
|
|
|
128
64
|
return v;
|
|
129
65
|
}
|
|
130
66
|
|
|
131
|
-
/** Every current-version claim in one file's text, with the line it sits on. */
|
|
132
67
|
export function claimsIn(text) {
|
|
133
68
|
const found = [];
|
|
134
69
|
for (const { id, re } of PATTERNS) {
|
|
@@ -152,15 +87,10 @@ export function inspect(root) {
|
|
|
152
87
|
for (const rel of files) {
|
|
153
88
|
let text;
|
|
154
89
|
try {
|
|
155
|
-
// The
|
|
156
|
-
// reason. Enumerating from the index and then reading the worktree lets a staged drift
|
|
157
|
-
// with a reverted worktree copy report green while the commit ships the drift.
|
|
90
|
+
// The index, not the worktree: a reverted worktree copy would hide a staged drift the commit ships.
|
|
158
91
|
text = execFileSync("git", ["-C", root, "show", `:${rel}`], { encoding: "utf8", maxBuffer: 64 * 1024 * 1024 });
|
|
159
92
|
} catch (e) {
|
|
160
|
-
//
|
|
161
|
-
// the rail could not examine live prose it is responsible for — and skipping it would
|
|
162
|
-
// report GREEN over a file never looked at. That is this session's defect class exactly,
|
|
163
|
-
// and it was sitting inside the rail built against it. Could-not-run, never a green.
|
|
93
|
+
// Not `continue`: skipping a file git just listed would report green over prose never read.
|
|
164
94
|
throw new CouldNotRun(`could not read ${rel} from the index: ${e.message}`);
|
|
165
95
|
}
|
|
166
96
|
for (const c of claimsIn(text)) {
|
|
@@ -200,12 +130,7 @@ export function main(argv, stdout = process.stdout, stderr = process.stderr) {
|
|
|
200
130
|
return 0;
|
|
201
131
|
}
|
|
202
132
|
|
|
203
|
-
//
|
|
204
|
-
// wrong twice. `file://${argv[1]}` is NOT that form: `import.meta.url` percent-encodes, this working
|
|
205
|
-
// copy lives under a path with spaces, and the comparison failed — so the tool exited 0 having run
|
|
206
|
-
// nothing. **A green that is the tool never starting**, shipped inside the rail built against exactly
|
|
207
|
-
// that. The realpath fallback covers the symlink case an npm `bin` produces, in a `try` because a
|
|
208
|
-
// missing path must answer no rather than throw.
|
|
133
|
+
// URLs on both sides, since `import.meta.url` percent-encodes; the realpath covers a symlinked npm `bin`.
|
|
209
134
|
function isMain() {
|
|
210
135
|
const invoked = process.argv[1];
|
|
211
136
|
if (!invoked) return false;
|
|
@@ -217,8 +142,5 @@ function isMain() {
|
|
|
217
142
|
}
|
|
218
143
|
}
|
|
219
144
|
|
|
220
|
-
// `
|
|
221
|
-
// repository: exiting outright can truncate a pipe that has not drained. It matters more here than
|
|
222
|
-
// stylistically — a truncated `ok` line IS exit 0 with no output, which is the precise shape of the
|
|
223
|
-
// false green this file's entry guard was fixed for. The same defect, one door along.
|
|
145
|
+
// `exitCode`, not `exit()`, so a pipe that has not drained is not cut short.
|
|
224
146
|
if (isMain()) process.exitCode = main(process.argv.slice(2));
|
package/core/README.md
CHANGED
|
@@ -13,7 +13,7 @@ loads on demand.
|
|
|
13
13
|
| Path | What lives here |
|
|
14
14
|
|---|---|
|
|
15
15
|
| [`engine.md`](engine.md) | The kernel: cascade, loop, non-negotiable gates, and the map. Always loaded. |
|
|
16
|
-
| [`operating/`](operating/) | The doctrine, one concern per file: loop · autonomy · verification · memory · evolution · safety. |
|
|
16
|
+
| [`operating/`](operating/) | The doctrine, one concern per file: loop · autonomy · verification · memory · evolution · safety · context · sessions. |
|
|
17
17
|
| [`templates/`](templates/) | The artifacts the doctrine references: repo-card · task · handoff · proposal · memory-entry. |
|
|
18
18
|
| [`personas/`](personas/) | Roles an agent takes, as context firewalls — each with a `tools:` allow-list. |
|
|
19
19
|
| [`skills/`](skills/) | Progressive-disclosure procedures in `SKILL.md` form. |
|
package/core/engine.md
CHANGED
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
# Portulan engine
|
|
2
2
|
|
|
3
3
|
> The always-loaded kernel: the invariants every agent carries, and the map to the doctrine behind
|
|
4
|
-
> them. Kept small on purpose — everything here is load-bearing; everything else loads on demand.
|
|
5
|
-
> file is the core's source contribution to the always-loaded layer; `portulan vendor --host` inlines it
|
|
6
|
-
> verbatim into a vendored `AGENTS.md` beside the workspace's own layer, for a host that cannot install
|
|
7
|
-
> the plugin. The **pack** layer is named in that file rather than composed into it — a pack resolves
|
|
8
|
-
> from a feed at a pinned version and **vendoring resolves nothing**, which is unchanged. Pack-cache
|
|
9
|
-
> discovery landed at milestone 7: the CLI reads the host's record, a vendored file cannot.
|
|
4
|
+
> them. Kept small on purpose — everything here is load-bearing; everything else loads on demand.
|
|
10
5
|
|
|
11
6
|
## Resolution cascade
|
|
12
7
|
|
|
@@ -38,10 +33,9 @@ scale down. → `operating/loop.md`
|
|
|
38
33
|
|
|
39
34
|
## The map
|
|
40
35
|
|
|
41
|
-
- `operating/` — the doctrine: loop · autonomy · verification · memory · evolution · safety.
|
|
36
|
+
- `operating/` — the doctrine: loop · autonomy · verification · memory · evolution · safety · context · sessions.
|
|
42
37
|
- `templates/` — the artifacts: repo-card · task · handoff · proposal · memory-entry.
|
|
43
38
|
- `personas/` — roles as context firewalls, each with a `tools:` allow-list.
|
|
44
39
|
- `skills/` — progressive-disclosure procedures; a skill must enforce, measure, or earn its tokens.
|
|
45
40
|
|
|
46
|
-
Every rule here carries its rationale and provenance in the doc it links to.
|
|
47
|
-
prose for its own sake: when a line stops being load-bearing, it moves out of the kernel.
|
|
41
|
+
Every rule here carries its rationale and provenance in the doc it links to.
|
|
@@ -54,7 +54,7 @@ else safe.)_
|
|
|
54
54
|
by changing the rule itself through the evolution gate — never by runtime approval.
|
|
55
55
|
|
|
56
56
|
That distinction is the whole reason the fourth tier exists rather than being folded into Gated, and it
|
|
57
|
-
is not academic: a compiler with three tiers must file *"no
|
|
57
|
+
is not academic: a compiler with three tiers must file *"no session certifies its own checkpoint"* under Gated,
|
|
58
58
|
and Gated compiles to a prompt. *Never* would become *unless somebody clicks yes* — a difference no
|
|
59
59
|
reader of the gate map would see, produced entirely by the vocabulary being one word short. _(Found by
|
|
60
60
|
building the compiler: milestone 4 needed a fourth class before it had a schema, and the workspace layer
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
# Context
|
|
2
|
+
|
|
3
|
+
> Core doctrine — loaded on demand, which on this page's own terms is the on-read tier. What a host
|
|
4
|
+
> loads into every context is paid for again by every fresh context: each session, subagent, checkpoint
|
|
5
|
+
> and persona. So guidance sits in load tiers, the always tier is budgeted like memory, and a demotion
|
|
6
|
+
> is measured before and after. `loop.md` manages the window while a task runs; this page governs what
|
|
7
|
+
> the host loads into it, and when. _(Proposal
|
|
8
|
+
> [`0036`](../../.portulan/proposals/0036-what-a-host-loads-into-every-context-is-budgeted.md).)_
|
|
9
|
+
|
|
10
|
+
## Four load tiers
|
|
11
|
+
|
|
12
|
+
**Every piece of guidance sits in a load tier, and belongs in the latest tier that still reaches the
|
|
13
|
+
agent in time.** There are four:
|
|
14
|
+
|
|
15
|
+
- **Always**: loaded into every context, which covers project instruction files and their imports,
|
|
16
|
+
unscoped rules, and skill and agent descriptions.
|
|
17
|
+
- **On-path**: loaded when the agent first touches a matching path, which covers nested instruction files
|
|
18
|
+
and path-scoped rules.
|
|
19
|
+
- **On-invoke**: loaded when a skill, persona or ritual runs.
|
|
20
|
+
- **On-read**: loaded when the agent opens the file.
|
|
21
|
+
|
|
22
|
+
The always tier is paid for by every context, whether or not its task needs what the tier holds; a later
|
|
23
|
+
tier is paid for only by the contexts that reach it. *In time* is the limit: guidance that arrives after
|
|
24
|
+
the moment it applies has not reached the agent.
|
|
25
|
+
|
|
26
|
+
A workspace sets its own guidance in these tiers in `slots.context`, since Workspace Definition 2.10: one
|
|
27
|
+
unit per file, each declaring its tier, and `compile` emits each unit in its host's form of that tier.
|
|
28
|
+
_(See [`../../spec/slots.md`](../../spec/slots.md).)_
|
|
29
|
+
|
|
30
|
+
## One level of index
|
|
31
|
+
|
|
32
|
+
**One level of index only**: an always-tier line may point at content, and a pointer may not point at
|
|
33
|
+
another pointer. A **pointer** is a line the agent reads to decide whether to open something; a file a
|
|
34
|
+
procedure always reads is content loaded in that procedure's tier, not a pointer. So the kernel's map,
|
|
35
|
+
whose lines point at directories of content, is one level, and so is the boot's path to a memory record:
|
|
36
|
+
the boot always reads the memory index, and each index line is the one pointer to its record.
|
|
37
|
+
|
|
38
|
+
_(Motivated, not established, by arXiv 2607.17598, *Is Progressive Disclosure All You Need for
|
|
39
|
+
Long-Context Agents?* (He, Zhao, Wang and Chen, 2026), whose abstract finds that a second, deeper routing
|
|
40
|
+
level never helps and sometimes breaks accuracy. It measured long-document question answering, not
|
|
41
|
+
instruction files.)_
|
|
42
|
+
|
|
43
|
+
## The always tier is budgeted in tokens
|
|
44
|
+
|
|
45
|
+
**The budget is declared in the manifest and never defaulted**, as `context.always.budget.tokens`. A
|
|
46
|
+
breach is repaired by **demotion** to a later tier, by **merge** or by **retirement**, and never by
|
|
47
|
+
raising the budget in the change that breached it. The shape is memory's on purpose, its limit included:
|
|
48
|
+
a budget is a rail, not an aim, and the no-raise half is a rule no checker establishes, for the reason
|
|
49
|
+
[`memory.md`](memory.md) gives.
|
|
50
|
+
|
|
51
|
+
**Bytes per token are declared too**, as `context.ratio`: measured exactly on demand, by asking the host
|
|
52
|
+
for the true count, and recorded with the host whose exact count calibrated it. The exact measurement
|
|
53
|
+
never runs inside a recipe, because no verify recipe may make a network call, and a ratio that moved
|
|
54
|
+
between runs would make one tree red on one run and green on the next. The keys are argued in
|
|
55
|
+
[`../../spec/slots.md`](../../spec/slots.md).
|
|
56
|
+
|
|
57
|
+
**A report by default, a rail by declaration.** A workspace is failed only against a budget it declared.
|
|
58
|
+
|
|
59
|
+
## Portulan budgets its own contribution
|
|
60
|
+
|
|
61
|
+
**Portulan budgets its own contribution by default, because it owns it.** That is the kernel, the boot
|
|
62
|
+
skill and the engine half of the boot read-set, skill and persona prompts, hook-injected text, and the
|
|
63
|
+
vendored `AGENTS.md` skeleton. The default is a rail in this repository, not a budget in any adopter's
|
|
64
|
+
manifest, so *never defaulted* holds. This is what lets every install benefit on upgrade without
|
|
65
|
+
configuring anything.
|
|
66
|
+
|
|
67
|
+
**The kernel, [`../engine.md`](../engine.md), is the core's source contribution to the always-loaded
|
|
68
|
+
layer**; `portulan vendor --host` inlines it verbatim into a vendored `AGENTS.md` beside the workspace's
|
|
69
|
+
own layer, for a host that cannot install the plugin. The **pack** layer is named in that file rather
|
|
70
|
+
than composed into it — a pack resolves from a feed at a pinned version and **vendoring resolves
|
|
71
|
+
nothing**, which is unchanged. Pack-cache discovery landed at milestone 7: the CLI reads the host's
|
|
72
|
+
record, a vendored file cannot. Nothing in this kernel is prose for its own sake: when a line stops being
|
|
73
|
+
load-bearing, it moves out of the kernel.
|
|
74
|
+
|
|
75
|
+
## A fresh context is priced where it is created
|
|
76
|
+
|
|
77
|
+
Whatever spawns one (a checkpoint, a persona, a ritual) states what it loads, and the compiled agent
|
|
78
|
+
carries that. **Reviewers keep the conventions they grade against**: the remedy for an expensive always
|
|
79
|
+
tier is a smaller always tier, never a blind reviewer.
|
|
80
|
+
|
|
81
|
+
## No optimisation may lower context or code quality
|
|
82
|
+
|
|
83
|
+
Prefer moving content to a later tier over deleting it, keep one source per fact, and measure before and
|
|
84
|
+
after. **A demotion states both figures.**
|
|
85
|
+
|
|
86
|
+
**What this does not ask for**: a defaulted workspace rail, lossy compression, a codebase vector index,
|
|
87
|
+
switching model or effort mid-session, output-shortening tricks, or any change to a host. Portulan
|
|
88
|
+
compiles to what hosts load and does not change how they load it.
|
|
89
|
+
|
|
90
|
+
## Every request pays for what the session has read
|
|
91
|
+
|
|
92
|
+
Each tool call ends a request, and the next request sends the whole context again: the host's own prefix,
|
|
93
|
+
the always tier, and everything read and printed so far. The prompt cache makes the repeat cheaper, not
|
|
94
|
+
free. In Claude Code 2.1.281 the main conversation's cache lives an hour on a subscription within its
|
|
95
|
+
usage limits and five minutes on an API key or a cloud provider, a subagent's five minutes unless set
|
|
96
|
+
(its settings `promptCacheTtl` and `subagentPromptCacheTtl`, read in its program text on 2026-09-24).
|
|
97
|
+
So what a session adds, it pays for on every request after, and a task done in fewer requests re-sends
|
|
98
|
+
less. `compile` writes the lead of each rule below onto every boot card, Portulan's and each consumer's:
|
|
99
|
+
|
|
100
|
+
- **Send independent tool calls in one request.** Reads, searches and commands whose inputs do not wait
|
|
101
|
+
on each other's output cost one request together and one each apart, and each request re-sends the
|
|
102
|
+
context.
|
|
103
|
+
- **Read a file once; reuse what your context holds.** A second read of an unchanged file adds a second
|
|
104
|
+
copy, paid for on every later request. After an Edit, do not read the file back to check it: the host
|
|
105
|
+
refuses an edit it cannot apply, and says so.
|
|
106
|
+
- **Open a file only when the task needs it, and one of 300 lines or more only where it does:
|
|
107
|
+
`node <plugin root>/cli/symbols.mjs <file>` outlines it, and `<file>#<heading>` prints one section.**
|
|
108
|
+
The outline gives each part's lines, and a Markdown heading's anchor and size; it costs a line a part
|
|
109
|
+
where a doctrine page or an instruction file runs to thousands of tokens and a task needs one section.
|
|
110
|
+
A link to a heading names its section, so `<file>#<anchor>` reads what the link points at without an
|
|
111
|
+
outline. A shorter file the task changes is read whole, as below. Read a section you will edit with the
|
|
112
|
+
Read tool's `offset` and `limit`, which an Edit may follow. `<plugin root>` is where Portulan is
|
|
113
|
+
installed, the directory whose `core/engine.md` the boot reads; in Portulan's own repository it is the
|
|
114
|
+
repository, and `compile` writes the command as run from there.
|
|
115
|
+
- **Ask for only the output you need: a range, a count, the failures.** Output enters the context as a
|
|
116
|
+
read does. A passing run needs its summary and a failing one the failing check and the fact to act on,
|
|
117
|
+
as [`loop.md`](loop.md)'s compacted error does; `git diff --stat`, `grep -c` and a line range ask for
|
|
118
|
+
less than a whole log, file or tree.
|
|
119
|
+
- **The prompt cache lasts five minutes or an hour: a longer pause, or a fresh subagent where one could
|
|
120
|
+
be resumed, writes the whole context again.** A subagent's context is its own and starts with the
|
|
121
|
+
host's prefix, so start one only where the reads it keeps out of this context cost more than that
|
|
122
|
+
prefix, and resume one rather than start another; [`sessions.md`](sessions.md) holds both, and the
|
|
123
|
+
lifetime a workspace declares. The cost table in the same program text prices a cache write above
|
|
124
|
+
sending the tokens fresh and a cache read below it.
|
|
125
|
+
- **Code should speak for itself: a comment is paid for on every read of its file, so add one only when
|
|
126
|
+
truly needed, concise, meaningful and valuable (less is more), and keep a change's history in its
|
|
127
|
+
commit message.** Claude Code 2.1.281's system prompt tells a model to match the surrounding code's
|
|
128
|
+
comment density, so what a file carries, the next change copies. Truly needed is what no name or
|
|
129
|
+
structure can show, in a line: a host's behaviour at its version, an ordering or a safety constraint the
|
|
130
|
+
code does not make obvious, a limit of the algorithm. A comment never carries a date, a proposal or
|
|
131
|
+
milestone number, a review round or a reviewer's credit, a pull request or issue number, or a narration
|
|
132
|
+
of what the code does; where the code needs one to be understood, rename or extract instead.
|
|
133
|
+
`node <plugin root>/cli/comments.mjs` counts the comment lines that record a change's history, in code
|
|
134
|
+
and in a Markdown file's HTML comments other than the lines a tool reads, and a workspace's `comments`
|
|
135
|
+
recipe holds that count at a limit that only falls.
|
|
136
|
+
|
|
137
|
+
## Code is read by symbol
|
|
138
|
+
|
|
139
|
+
A session outlines a code file of 300 lines or more before it opens it, then reads the spans it needs: in
|
|
140
|
+
Claude Code, the Read tool with `offset` at a span's first line and `limit` its length. **A file under 300
|
|
141
|
+
lines that the task changes is read whole**, since most of it is context for the change and the saving is
|
|
142
|
+
smallest there, and so is any file whose outline cannot say where a change goes. A span read the host does
|
|
143
|
+
not cut is a full read of those lines, so an Edit may follow it: in Claude Code 2.1.281 a Read is marked
|
|
144
|
+
partial, and an Edit or a Write after it refused, only when the Read's token cap, 25,000 by default, cut it
|
|
145
|
+
(read in its program text on 2026-09-24). [`cli/symbols.mjs`](../../cli/symbols.mjs) prints the outline,
|
|
146
|
+
one line per symbol, and `--find <name>` locates a definition; it outlines Markdown by its headings, with
|
|
147
|
+
each section's anchor and size, the same way. It prints from the file as it is, so no map is committed to
|
|
148
|
+
go stale. Nothing checks that a session outlined first; what the rule saves is measured by rerunning a
|
|
149
|
+
task, never by a recipe, which may not read the host's usage records.
|
|
150
|
+
|
|
151
|
+
## A local model runs without the host floor
|
|
152
|
+
|
|
153
|
+
A model served on the machine itself costs nothing per token, so `0038`'s dollar arithmetic gives it no
|
|
154
|
+
restart threshold. What it does cost is time to prefill, memory for each context it holds, and recall that
|
|
155
|
+
still sags in a long context. And every turn it takes is a turn the parent session does not send again on
|
|
156
|
+
each request after. So a local model is called as a context firewall, and four rules hold it:
|
|
157
|
+
|
|
158
|
+
1. **A local worker runs without the host floor (`omitClaudeMd`) and under a hard input-byte ceiling.** It
|
|
159
|
+
is given one bounded input: a symbol span from [`cli/symbols.mjs`](../../cli/symbols.mjs), a file under
|
|
160
|
+
300 lines, a diff, or a log to compress, and never the parent session's instruction files. A prompt over
|
|
161
|
+
the ceiling is refused before a socket opens. _Why: a host floor of 44k to 85k tokens is prefilled into
|
|
162
|
+
a local model's cache on every cold context, where `0039` measured prompts of about 400 to 470 tokens
|
|
163
|
+
prefilling in 0.85 to 1.88 seconds._
|
|
164
|
+
2. **Thinking effort is set explicitly at invocation**: low or off for a mechanical transform, bounded for
|
|
165
|
+
prose or review, never left to the chat template's default. _Why: one template `0039` read sets
|
|
166
|
+
reasoning effort to `xhigh` unless thinking is switched off, and under a 1,500-token cap that model
|
|
167
|
+
returned no answer on any of three tasks, every token spent thinking; at low effort it passed both
|
|
168
|
+
tasks it was rerun on._
|
|
169
|
+
3. **A local edit is kept only on its verify command's exit `0`.** The runner applies the edit and runs the
|
|
170
|
+
command. On exit `0` one receipt line is all that enters the parent context (`0038` rule 5). On exit `1`
|
|
171
|
+
after its retries, or at once on any other exit, which is no verdict, it restores the file's original
|
|
172
|
+
bytes, and it does the same on SIGINT, SIGTERM and SIGHUP. **This is a rollback, not an atomic write**:
|
|
173
|
+
the verify needs the edited file in place, so any other signal that ends the runner, SIGKILL and SIGQUIT
|
|
174
|
+
among them, or a power loss between the edit and its verdict leaves the edit, and a transform with no
|
|
175
|
+
receipt is unverified. _Why not atomic: a scratch worktree would judge the last commit plus the edit
|
|
176
|
+
rather than the working copy plus the edit, would lack the untracked files a verify reads, and moving
|
|
177
|
+
the edit back after exit `0` would not be atomic either (`0039` ruling 4)._
|
|
178
|
+
4. **Loopback is a rail, and routing is earned.** A local runner refuses an endpoint outside loopback
|
|
179
|
+
(`127.0.0.1`, `localhost`, `[::1]`) unless leaving it is proposed and approved. A screen grades an
|
|
180
|
+
installed model once per task against Portulan's own predicates, from
|
|
181
|
+
[`cli/comments.mjs`](../../cli/comments.mjs), [`cli/doctor.mjs`](../../cli/doctor.mjs) and
|
|
182
|
+
[`cli/skill-goldens.mjs`](../../cli/skill-goldens.mjs), and a model that fails it is not routed. Passing
|
|
183
|
+
it earns nothing. A role is routed to a local model by an A/B record under `0038` rule 4: the local model
|
|
184
|
+
as routed against the host's model on the same tasks, `k` runs per cell, captured once and railed to its
|
|
185
|
+
register in `evals/ab/`. _Why: an endpoint off the machine sends workspace context away under a local
|
|
186
|
+
tool's name, and the screen takes one sample per task with no baseline arm, while a cheaper model for a
|
|
187
|
+
role is earned by an A/B run, never assumed (`0039` ruling 5)._
|
|
188
|
+
|
|
189
|
+
**What the runner holds today.** The [`tools/local-llm`](../../packs/tools/local-llm/README.md) pack's
|
|
190
|
+
runner holds rules 1 to 3 and rule 4's loopback rail for the calls it makes. It sends the model its own
|
|
191
|
+
short instructions and the input it was given, with the end of a red verify's output on a retry, and
|
|
192
|
+
nothing else. It refuses a prompt over its ceiling before a socket opens, names the effort on every chat
|
|
193
|
+
request, low unless set, requires a verify command for an edit and rolls back as rule 3 says, and refuses
|
|
194
|
+
a host off loopback and a redirect away from it. Which inputs it is given is the caller's: only their
|
|
195
|
+
bytes are checked. `--allow-remote` lifts the loopback rail on the caller's word, and the pack's
|
|
196
|
+
`local-llm-off-loopback` gate has no matcher, so approval to leave loopback is the human's. Its `eval`
|
|
197
|
+
subcommand is the screen. Its `runner-rails` recipe holds the loopback and ceiling refusals and the
|
|
198
|
+
rollback on a red or no-verdict verify offline, with no network call; the rollback on a signal is held by
|
|
199
|
+
the runner's tests. This repository composes the pack and registers its skill; the ledger reports local
|
|
200
|
+
receipts beside the host's tokens. `doctor` includes that branch-owned usage report by default,
|
|
201
|
+
with unavailable data reported as unmeasured; `--static` omits usage-record discovery for verification.
|
|
202
|
+
The [captured A/B](../../evals/ab/local-worker.md) supports code
|
|
203
|
+
at low effort (L5/H5), while shared prose is disabled because codify scored L0/H3, despite handoff's
|
|
204
|
+
L5/H5. Operational defaults and explicit roles require the measured model at low effort; explicit
|
|
205
|
+
model-only calls remain candidates. The record and register are regraded and byte-compared in CI,
|
|
206
|
+
with shipped routing rederived from their turns. The `local-worker` persona declares
|
|
207
|
+
`omitClaudeMd: true` and `effort: low`, and `compile` writes both into a workspace's binding against a
|
|
208
|
+
committed golden.
|
|
209
|
+
|
|
210
|
+
_(Provenance: proposal
|
|
211
|
+
[`0039`](../../.portulan/proposals/0039-a-local-model-runs-without-the-host-floor-and-under-a-thinking-cap.md)'s
|
|
212
|
+
measurement of two local models against three of this repository's tasks on 2026-10-27, and its rulings 4
|
|
213
|
+
and 5 of 2026-10-30.)_
|
|
214
|
+
|
|
215
|
+
## What is machinery today, and what is not
|
|
216
|
+
|
|
217
|
+
As of Workspace Definition 2.9 a workspace can declare the budget and the ratio, and both are checked
|
|
218
|
+
for shape, by the schema and by `doctor`; the manifest key refuses a budget with no ratio to count it by.
|
|
219
|
+
**The always tier is measured and reported.** [`cli/context.mjs`](../../cli/context.mjs) measures what
|
|
220
|
+
Claude Code loads into every context from a workspace's `tree`; `doctor` reports that for every workspace
|
|
221
|
+
and fails it only over a declared budget or where a declared budget cannot be judged; the boot closes
|
|
222
|
+
with the same line; and this repository's `context` recipe rails Portulan's own footprint. **A boot can
|
|
223
|
+
read a card instead of the slots**: the `boot` unit of `slots.context` (Workspace Definition 2.10) is
|
|
224
|
+
compiled into every context, imports whole the files it needs, writes out the lead sentences of others,
|
|
225
|
+
and names the file behind each line, so the rest loads when a rule's path or the card sends a session to
|
|
226
|
+
it. **A team's own instruction file can be split the same way, section by section**: a line
|
|
227
|
+
`<!-- portulan: on-read -->` under a heading of `CLAUDE.md` or `.claude/CLAUDE.md` is the team's word that
|
|
228
|
+
the section may leave every context, and `portulan upgrade` moves it, byte for byte and proved to
|
|
229
|
+
reassemble, into an on-read unit whose index line names the heading and its size in whole KB. `doctor`, the
|
|
230
|
+
boot and `init` offer it where the file is over `0036`'s 8,000-token offer floor, and `doctor` and the boot
|
|
231
|
+
wherever a declared budget is breached, naming there the command that splits while `upgrade` will not run;
|
|
232
|
+
a section is never moved for its size alone. Claude Code 2.1.281 drops a comment standing on its own lines
|
|
233
|
+
before it loads an instruction file or rule (read in its program text on 2026-09-24), so the mark and the
|
|
234
|
+
marker left behind cost nothing. This repository's own card holds its boot under the 7,500-token line of
|
|
235
|
+
2026-09-23, railed at today's figure plus 2% by the same recipe. A pointer's repository is not measured,
|
|
236
|
+
because a pointer declares no tree. **A persona can declare what its context costs**: `model`, `effort`,
|
|
237
|
+
`omitClaudeMd` and `cacheLifetime`, refused when malformed and never defaulted, which `compile` writes into
|
|
238
|
+
the hand-written binding of a persona a composed pack ships and byte-compares under `--check`, printing
|
|
239
|
+
each field a host cannot express, with the persona and the host, as its degradation report for personas.
|
|
240
|
+
Claude Code 2.1.317 takes all four on an agent, and loads `agents/` only from a plugin root (both read
|
|
241
|
+
in its program text on 2026-11-03), so in a tree that is not a plugin the compiled block is loaded by
|
|
242
|
+
nothing, and the degradation report says so; in one that is, only while the plugin is installed and
|
|
243
|
+
enabled, which `compile` cannot see. That stops short of the row: `compile` writes no
|
|
244
|
+
`.claude/agents/`, where a repository that is not a plugin loads its agents, and no binding where none
|
|
245
|
+
exists; a core persona's binding in this plugin's `agents/` is not compiled; and no host has been shown
|
|
246
|
+
honouring a compiled field. **Still to land**: the
|
|
247
|
+
exact mode that calibrates a ratio, the degradation report's half that says which tiers a host can
|
|
248
|
+
express, the Stop-gate's named demotion and the librarian pass, one change at a time, in `0036`'s order
|
|
249
|
+
of work and under row 12 of
|
|
250
|
+
[`../../docs/plan.md`](../../docs/plan.md).
|
|
251
|
+
Until each lands, the rule it would enforce is held by the human gate, and saying so is the point: a rule
|
|
252
|
+
nothing checks, presented as one that something does, is the failure this page exists to prevent.
|
|
253
|
+
|
|
254
|
+
_(Provenance: proposal `0036`'s sealed incident, in which an adopting repository's always-loaded
|
|
255
|
+
instruction files measured 130k tokens per fresh context, cache writes were most of the spend, and every
|
|
256
|
+
subagent and checkpoint paid it again, because nothing measured, budgeted or demoted what the host
|
|
257
|
+
loads. Host facts from the Claude Code documentation on costs, prompt caching and memory, checked
|
|
258
|
+
2026-09-21.)_
|
|
@@ -67,9 +67,9 @@ rather than a story about this repository. **Work is agent-drafted and fresh-con
|
|
|
67
67
|
moments**: the plan before building starts, the diff before it goes outward, and the exit criteria at
|
|
68
68
|
the close — where done is *demonstrated, not asserted* (`verification.md`). Two conditions hold the
|
|
69
69
|
cycle up and neither is optional. The unit of work carries **hard exit criteria** fixed before it
|
|
70
|
-
begins, so the close has a standard to grade rather than an impression. And **every
|
|
71
|
-
|
|
72
|
-
instead of re-deriving it — a graded moment whose grader must first reconstruct what was decided is
|
|
70
|
+
begins, so the close has a standard to grade rather than an impression. And **every change writes its
|
|
71
|
+
record** (`loop.md`), its why in the commit and any open work in a handoff, so the context arriving at
|
|
72
|
+
the next moment inherits the reasoning instead of re-deriving it — a graded moment whose grader must first reconstruct what was decided is
|
|
73
73
|
grading its own reconstruction.
|
|
74
74
|
|
|
75
75
|
The altitudes stay separate, and the separation is the part that travels. Core states that the three
|
package/core/operating/loop.md
CHANGED
|
@@ -26,26 +26,19 @@ The loop runs inside a finite attention window, so context is managed, not hoard
|
|
|
26
26
|
next action — and drop the transcript. The loop is a stateless reducer over that state, so it resumes
|
|
27
27
|
from the compaction without losing the thread. _(Provenance: HumanLayer ACE-FCA — the loop and
|
|
28
28
|
compaction; 12-Factor Agents — stateless-reducer resumability.)_
|
|
29
|
-
- **
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
librarian that **reads** the series, in three ways that only an unbroken one allows. It generates an
|
|
43
|
-
index over it, every field derived. It ages it from git. And it mines it: an incident nothing in the
|
|
44
|
-
curated layer points back at is a candidate for the `codify` skill, because a rule whose incident
|
|
45
|
-
cannot be traced can never be retired on evidence. The pass also **writes** to the series — a
|
|
46
|
-
scheduled pass is a session, so it ends with a dated handoff like any other — which is why nothing
|
|
47
|
-
regenerates that index until the pass's own record is on disk. _(Provenance: Cognition. See
|
|
48
|
-
`../templates/handoff.md`.)_
|
|
29
|
+
- **A change's why goes in its commit; a handoff carries only the work still open.** A commit's subject
|
|
30
|
+
says what changed and its body says why, in a few lines: the reasoning the next agent cannot
|
|
31
|
+
reconstruct from the diff, kept beside the diff. A session (one bounded working stretch, however the
|
|
32
|
+
host names it) that ends with work not committed and pushed ends with a dated handoff: where things
|
|
33
|
+
stand, the open questions and the single next action, and nothing the diff or a commit already says.
|
|
34
|
+
**Dated** means the filename leads with an ISO date, `YYYY-MM-DD-{slug}.md`, because a rule justified by
|
|
35
|
+
being checkable has to say what the checker looks at, and the series then sorts chronologically for
|
|
36
|
+
free. The session-end gate (milestone 4) checks exactly that: while the tree holds uncommitted or
|
|
37
|
+
unpushed work, a handoff dated today exists, never its length or its shape. The librarian (milestone 5)
|
|
38
|
+
reads the series: it indexes it, ages it from git, and mines it, since an incident nothing in the
|
|
39
|
+
curated layer points back at is a candidate for the `codify` skill.
|
|
40
|
+
_(Provenance: Cognition — handoffs record decisions and their rationale; Git's own SubmittingPatches —
|
|
41
|
+
a commit's body explains the problem and justifies the change. See `../templates/handoff.md`.)_
|
|
49
42
|
- **Subagents are context firewalls.** Fan work out to a persona with its own window and return only
|
|
50
43
|
the conclusion; the parent's budget stays clean. Read in parallel, write from one place. _(See
|
|
51
44
|
`../personas/`. Provenance: HumanLayer; Cognition — read-parallel / write-isolated.)_
|
package/core/operating/memory.md
CHANGED
|
@@ -46,6 +46,15 @@ which is worse than no check at all. So the breach is a rail and the *remedy* is
|
|
|
46
46
|
gate holds. Written down rather than implied, because a rule nothing checks that is presented as one
|
|
47
47
|
that does is the failure this page would otherwise be demonstrating.
|
|
48
48
|
|
|
49
|
+
**The cap can bind forward only** (Workspace Definition 2.11): with `memory.store.budget.cutoff`
|
|
50
|
+
declared beside it, it binds the records dated after the cutoff, and one dated on or before it is
|
|
51
|
+
reported, never railed, in proposal `0037`'s shape for a handoff. A record's date is its `**dated:**`
|
|
52
|
+
line, the day its text last changed, because a verify rail may not read git; an edit re-dates it, so
|
|
53
|
+
an old record meets the cap in the change that next rewrites it, while compression is that change's
|
|
54
|
+
to do. A record whose date cannot be read is refused. The cutoff never moves earlier, and moves later
|
|
55
|
+
only with a cap that tightens: like the raise above, a rule the human gate holds, because a moved
|
|
56
|
+
cutoff unbinds what the cap bound and only history shows the move.
|
|
57
|
+
|
|
49
58
|
## One fact per memory, with provenance
|
|
50
59
|
|
|
51
60
|
A memory holds one fact and states where it came from. A rule links to the **incident** that created
|
|
@@ -62,8 +71,8 @@ curated context improves it, and the difference is measurable. So capture is age
|
|
|
62
71
|
consolidation into the curated layer is a reviewed step. _(Provenance: ETH Zurich, arXiv:2602.11988 —
|
|
63
72
|
generated context hurts, curated helps. The scheduled librarian arrived in milestone 5: a pass that
|
|
64
73
|
regenerates each generated index, ages every record from git, nags a sealed stamp's owner, chases
|
|
65
|
-
undecided proposals, drafts demotions, and **runs consolidation** — filed
|
|
66
|
-
to remember to open. What consolidation means on a schedule is bounded rather than claimed: the pass
|
|
74
|
+
undecided proposals, drafts demotions, and **runs consolidation** — its report filed with a pull request
|
|
75
|
+
nobody has to remember to open whenever the pass changed the tree. What consolidation means on a schedule is bounded rather than claimed: the pass
|
|
67
76
|
reports how close each budget is to its rail, and surfaces records citing one incident as a
|
|
68
77
|
**question**, because `../skills/consolidate/SKILL.md` merges records that are one *mechanism* and one
|
|
69
78
|
incident routinely teaches several. Its steps 3 and 4 — surfacing contradictions and compressing what
|
|
@@ -145,7 +154,7 @@ where a reader already knows to look is stated at the narrowest carrier.
|
|
|
145
154
|
was put to him twice and answered two different ways. The record of both, and of the third option that
|
|
146
155
|
reconciles them, is
|
|
147
156
|
[#98](https://github.com/sleepy-panda-srl/portulan/issues/98) and
|
|
148
|
-
[`docs/milestones/m06.md`](
|
|
157
|
+
[`docs/milestones/m06.md`](https://github.com/sleepy-panda-srl/portulan/blob/8a33f9b/docs/milestones/m06.md). Neither row needed a criterion edit to carry
|
|
149
158
|
this: both already demanded their half. Said plainly because the
|
|
150
159
|
alternative is a page describing an enforcement that does not exist — the failure a workspace's
|
|
151
160
|
definition of done exists to catch (`.portulan/dod.md` condition 4, in this repository): a document may
|