@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.
Files changed (89) hide show
  1. package/README.md +7 -6
  2. package/cli/README.md +145 -358
  3. package/cli/advisory.mjs +354 -0
  4. package/cli/comments.mjs +932 -0
  5. package/cli/compile.mjs +1719 -1699
  6. package/cli/context.mjs +883 -0
  7. package/cli/control-chars.mjs +12 -301
  8. package/cli/discover.mjs +17 -574
  9. package/cli/doctor.mjs +269 -1080
  10. package/cli/drills.mjs +92 -504
  11. package/cli/feedback.mjs +16 -237
  12. package/cli/finish.mjs +437 -0
  13. package/cli/form.mjs +644 -0
  14. package/cli/fuzz-shell.mjs +20 -305
  15. package/cli/gate.mjs +7 -199
  16. package/cli/goldens.mjs +26 -184
  17. package/cli/index.mjs +305 -655
  18. package/cli/init.mjs +243 -566
  19. package/cli/inside.mjs +3 -28
  20. package/cli/instructions.mjs +829 -0
  21. package/cli/ledger.mjs +882 -0
  22. package/cli/librarian.mjs +135 -599
  23. package/cli/manifest.mjs +2 -39
  24. package/cli/mutants.mjs +9 -170
  25. package/cli/new.mjs +12 -243
  26. package/cli/pack-identity.mjs +5 -74
  27. package/cli/pack-version.mjs +13 -255
  28. package/cli/plugin-lint.mjs +42 -658
  29. package/cli/portulan.mjs +6 -125
  30. package/cli/recipe-set.mjs +15 -245
  31. package/cli/release-eval.mjs +28 -348
  32. package/cli/review-meter.mjs +29 -265
  33. package/cli/rule-carriers.mjs +12 -175
  34. package/cli/sessions.mjs +82 -0
  35. package/cli/skill-goldens.mjs +10 -176
  36. package/cli/skills-set.mjs +12 -414
  37. package/cli/stop-gate.mjs +83 -514
  38. package/cli/symbols.mjs +1132 -0
  39. package/cli/telemetry.mjs +82 -505
  40. package/cli/upgrade.mjs +171 -314
  41. package/cli/vendor.mjs +146 -476
  42. package/cli/version-carriers.mjs +8 -86
  43. package/core/README.md +1 -1
  44. package/core/engine.md +3 -9
  45. package/core/operating/autonomy.md +1 -1
  46. package/core/operating/context.md +258 -0
  47. package/core/operating/evolution.md +3 -3
  48. package/core/operating/loop.md +13 -20
  49. package/core/operating/memory.md +12 -3
  50. package/core/operating/sessions.md +100 -0
  51. package/core/personas/README.md +33 -0
  52. package/core/personas/librarian.md +2 -2
  53. package/core/skills/clarify/SKILL.md +1 -1
  54. package/core/skills/consolidate/SKILL.md +5 -3
  55. package/core/templates/handoff.md +28 -15
  56. package/core/templates/memory-entry.md +1 -0
  57. package/evals/README.md +271 -845
  58. package/evals/releases/0.2.0.json +146 -0
  59. package/evals/releases/0.2.0.md +67 -0
  60. package/evals/releases/0.3.0.json +150 -0
  61. package/evals/releases/0.3.0.md +68 -0
  62. package/evals/releases/README.md +3 -3
  63. package/package.json +5 -2
  64. package/packs/.claude-plugin/plugin.json +2 -1
  65. package/packs/README.md +7 -4
  66. package/packs/rituals/checkpoints/README.md +1 -1
  67. package/packs/tools/README.md +8 -8
  68. package/packs/tools/github/verify/actions-pinned.sh +10 -55
  69. package/packs/tools/local-llm/README.md +136 -0
  70. package/packs/tools/local-llm/pack.json +32 -0
  71. package/packs/tools/local-llm/personas/local-worker.md +44 -0
  72. package/packs/tools/local-llm/runner/local-llm.mjs +1665 -0
  73. package/packs/tools/local-llm/skills/delegate-local/SKILL.md +64 -0
  74. package/packs/tools/local-llm/verify/README.md +18 -0
  75. package/spec/README.md +113 -6
  76. package/spec/migrations/0001-repository-declares-its-tree.mjs +1 -26
  77. package/spec/migrations/0002-bundle-fallback-path.mjs +13 -74
  78. package/spec/migrations/0003-handoff-index-not-kept.mjs +66 -0
  79. package/spec/migrations/0004-changelog-fragments.mjs +53 -0
  80. package/spec/migrations/0005-session-log-retired.mjs +69 -0
  81. package/spec/migrations/0006-boot-card.mjs +77 -0
  82. package/spec/migrations/0007-guidance-compiled.mjs +31 -0
  83. package/spec/migrations/0008-card-reading.mjs +55 -0
  84. package/spec/migrations/0009-instruction-sections-on-read.mjs +60 -0
  85. package/spec/migrations/0010-comment-rail.mjs +57 -0
  86. package/spec/migrations/README.md +45 -10
  87. package/spec/pack.schema.json +1 -1
  88. package/spec/slots.md +343 -27
  89. package/spec/workspace.schema.json +145 -4
@@ -0,0 +1,146 @@
1
+ {
2
+ "portulan": {
3
+ "releaseEval": "1"
4
+ },
5
+ "version": "0.2.0",
6
+ "captured": "2026-09-30",
7
+ "source": {
8
+ "commit": "0b150002910d34a0a79408e82439cb81e1a0a0b9",
9
+ "clean": true
10
+ },
11
+ "host": {
12
+ "node": "v26.9.0",
13
+ "platform": "darwin"
14
+ },
15
+ "recipes": [
16
+ {
17
+ "id": "docs",
18
+ "exit": 0
19
+ },
20
+ {
21
+ "id": "json",
22
+ "exit": 0
23
+ },
24
+ {
25
+ "id": "doctor",
26
+ "exit": 0
27
+ },
28
+ {
29
+ "id": "tests",
30
+ "exit": 0
31
+ },
32
+ {
33
+ "id": "plugin",
34
+ "exit": 0
35
+ },
36
+ {
37
+ "id": "compile",
38
+ "exit": 0
39
+ },
40
+ {
41
+ "id": "workflow-filters",
42
+ "exit": 0
43
+ },
44
+ {
45
+ "id": "index",
46
+ "exit": 0
47
+ },
48
+ {
49
+ "id": "context",
50
+ "exit": 0
51
+ },
52
+ {
53
+ "id": "ledger",
54
+ "exit": 0
55
+ },
56
+ {
57
+ "id": "control-chars",
58
+ "exit": 0
59
+ },
60
+ {
61
+ "id": "comments",
62
+ "exit": 0
63
+ },
64
+ {
65
+ "id": "rule-carriers",
66
+ "exit": 0
67
+ },
68
+ {
69
+ "id": "pack-version",
70
+ "exit": 0
71
+ },
72
+ {
73
+ "id": "pack-identity",
74
+ "exit": 0
75
+ },
76
+ {
77
+ "id": "payload",
78
+ "exit": 0
79
+ },
80
+ {
81
+ "id": "eval-bundle",
82
+ "exit": 0
83
+ },
84
+ {
85
+ "id": "goldens",
86
+ "exit": 0
87
+ },
88
+ {
89
+ "id": "mutants",
90
+ "exit": 0
91
+ },
92
+ {
93
+ "id": "fuzz-shell",
94
+ "exit": 0
95
+ },
96
+ {
97
+ "id": "version-carriers",
98
+ "exit": 0
99
+ },
100
+ {
101
+ "id": "skill-goldens",
102
+ "exit": 0
103
+ },
104
+ {
105
+ "id": "review-loop",
106
+ "exit": 0
107
+ },
108
+ {
109
+ "id": "telemetry",
110
+ "exit": 0
111
+ },
112
+ {
113
+ "id": "drills",
114
+ "exit": 0
115
+ },
116
+ {
117
+ "id": "ab",
118
+ "exit": 0
119
+ },
120
+ {
121
+ "id": "ab-grade",
122
+ "exit": 0
123
+ },
124
+ {
125
+ "id": "ab-run",
126
+ "exit": 0
127
+ },
128
+ {
129
+ "id": "tools/github:actions-pinned",
130
+ "exit": 0
131
+ }
132
+ ],
133
+ "excluded": [
134
+ {
135
+ "id": "release-eval",
136
+ "why": "a capture cannot be accurate about the record it is inside — captured before the record is written it is red for the record's absence, and captured after it is stale the moment the register is rendered. Its verdict for this release is the rail's own run on the pull request carrying this file."
137
+ }
138
+ ],
139
+ "abBaseline": {
140
+ "snapshot": "evals/ab/baseline.json",
141
+ "register": "evals/ab/baseline.md",
142
+ "captured": "2026-08-31T09:05:32.699Z",
143
+ "commit": "a642d551eec6c146c67ef0acfe3f8ba535c5ce6e",
144
+ "clean": false
145
+ }
146
+ }
@@ -0,0 +1,67 @@
1
+ # Eval result — Portulan 0.2.0
2
+
3
+ > **Generated. Do not edit.** Rendered from [`evals/releases/0.2.0.json`](0.2.0.json) by [`cli/release-eval.mjs`](../../cli/release-eval.mjs) and byte-compared by the `release-eval` verify recipe. This is the eval result milestone 8 requires a release to carry — `docs/plan.md`, Protocol → Versioning.
4
+
5
+ ## What this release was measured at
6
+
7
+ | | |
8
+ |---|---|
9
+ | Version | `0.2.0` |
10
+ | Captured | 2026-09-30 |
11
+ | Commit | `0b150002910d34a0a79408e82439cb81e1a0a0b9` |
12
+ | Tree at capture | clean |
13
+ | Node | `v26.9.0` |
14
+ | Platform | `darwin` |
15
+
16
+ ## The rails, and what each returned
17
+
18
+ 29 of the 30 recipes this workspace yielded at capture. The set is derived from [`cli/recipe-set.mjs`](../../cli/recipe-set.mjs) and never listed by hand.
19
+
20
+ | Recipe | Verdict |
21
+ |---|---|
22
+ | `docs` | green |
23
+ | `json` | green |
24
+ | `doctor` | green |
25
+ | `tests` | green |
26
+ | `plugin` | green |
27
+ | `compile` | green |
28
+ | `workflow-filters` | green |
29
+ | `index` | green |
30
+ | `context` | green |
31
+ | `ledger` | green |
32
+ | `control-chars` | green |
33
+ | `comments` | green |
34
+ | `rule-carriers` | green |
35
+ | `pack-version` | green |
36
+ | `pack-identity` | green |
37
+ | `payload` | green |
38
+ | `eval-bundle` | green |
39
+ | `goldens` | green |
40
+ | `mutants` | green |
41
+ | `fuzz-shell` | green |
42
+ | `version-carriers` | green |
43
+ | `skill-goldens` | green |
44
+ | `review-loop` | green |
45
+ | `telemetry` | green |
46
+ | `drills` | green |
47
+ | `ab` | green |
48
+ | `ab-grade` | green |
49
+ | `ab-run` | green |
50
+ | `tools/github:actions-pinned` | green |
51
+
52
+ **`release-eval` is excluded**, and the reason is printed rather than the row silently dropped: a capture cannot be accurate about the record it is inside — captured before the record is written it is red for the record's absence, and captured after it is stale the moment the register is rendered. Its verdict for this release is the rail's own run on the pull request carrying this file.
53
+
54
+ ## The A/B baseline this release ships against
55
+
56
+ [`evals/ab/baseline.md`](https://github.com/sleepy-panda-srl/portulan/blob/v0.2.0/evals/ab/baseline.md), rendered from `evals/ab/baseline.json` — captured 2026-08-31T09:05:32.699Z at `a642d551eec6c146c67ef0acfe3f8ba535c5ce6e`, over a tree that was **not clean**.
57
+
58
+ **Its figures are not repeated here.** They have one carrier and this is not it.
59
+
60
+ ## What this record does not establish
61
+
62
+ - **A green says this record agrees with its own capture — never that the release is good.** Every line below is what the recipes returned at one commit, and a recipe's own green establishes only what that recipe's documentation says it establishes.
63
+ - **The recipes were not run at the tag.** They ran at `0b150002910d34a0a79408e82439cb81e1a0a0b9`, and this record is committed *in* the cut change — so the tagged tree is this commit plus whatever that change still adds after the capture, the record itself at minimum. A record cannot be captured at a commit that does not exist yet, and printing the one it was captured at is the only honest form.
64
+ - **`release-eval` is excluded from the rows above**, because a capture cannot be accurate about the record it is inside. Its verdict for this release is the rail's own run on the pull request that carries this file.
65
+ - **This record measures the Portulan repository's own build**, at the commit named above — never the workspace, project or package this release is installed into. Nothing here has looked at your tree.
66
+ - **The tree was clean at capture**, so the commit named above is what was measured.
67
+ - **The A/B baseline's figures are NOT restated here.** This record names which baseline the release ships against; the figures and everything that may not be concluded from them live in that register alone.
@@ -0,0 +1,150 @@
1
+ {
2
+ "portulan": {
3
+ "releaseEval": "1"
4
+ },
5
+ "version": "0.3.0",
6
+ "captured": "2026-11-03",
7
+ "source": {
8
+ "commit": "c08515fee0693cb0fc07fc6077fb152aa46bc28b",
9
+ "clean": false
10
+ },
11
+ "host": {
12
+ "node": "v26.12.1",
13
+ "platform": "darwin"
14
+ },
15
+ "recipes": [
16
+ {
17
+ "id": "docs",
18
+ "exit": 0
19
+ },
20
+ {
21
+ "id": "json",
22
+ "exit": 0
23
+ },
24
+ {
25
+ "id": "doctor",
26
+ "exit": 0
27
+ },
28
+ {
29
+ "id": "tests",
30
+ "exit": 0
31
+ },
32
+ {
33
+ "id": "plugin",
34
+ "exit": 0
35
+ },
36
+ {
37
+ "id": "compile",
38
+ "exit": 0
39
+ },
40
+ {
41
+ "id": "workflow-filters",
42
+ "exit": 0
43
+ },
44
+ {
45
+ "id": "index",
46
+ "exit": 0
47
+ },
48
+ {
49
+ "id": "context",
50
+ "exit": 0
51
+ },
52
+ {
53
+ "id": "ledger",
54
+ "exit": 0
55
+ },
56
+ {
57
+ "id": "control-chars",
58
+ "exit": 0
59
+ },
60
+ {
61
+ "id": "comments",
62
+ "exit": 0
63
+ },
64
+ {
65
+ "id": "rule-carriers",
66
+ "exit": 0
67
+ },
68
+ {
69
+ "id": "pack-version",
70
+ "exit": 0
71
+ },
72
+ {
73
+ "id": "pack-identity",
74
+ "exit": 0
75
+ },
76
+ {
77
+ "id": "payload",
78
+ "exit": 0
79
+ },
80
+ {
81
+ "id": "eval-bundle",
82
+ "exit": 0
83
+ },
84
+ {
85
+ "id": "goldens",
86
+ "exit": 0
87
+ },
88
+ {
89
+ "id": "mutants",
90
+ "exit": 0
91
+ },
92
+ {
93
+ "id": "fuzz-shell",
94
+ "exit": 0
95
+ },
96
+ {
97
+ "id": "version-carriers",
98
+ "exit": 0
99
+ },
100
+ {
101
+ "id": "skill-goldens",
102
+ "exit": 0
103
+ },
104
+ {
105
+ "id": "review-loop",
106
+ "exit": 0
107
+ },
108
+ {
109
+ "id": "telemetry",
110
+ "exit": 0
111
+ },
112
+ {
113
+ "id": "drills",
114
+ "exit": 0
115
+ },
116
+ {
117
+ "id": "ab",
118
+ "exit": 0
119
+ },
120
+ {
121
+ "id": "ab-grade",
122
+ "exit": 0
123
+ },
124
+ {
125
+ "id": "ab-run",
126
+ "exit": 0
127
+ },
128
+ {
129
+ "id": "tools/github:actions-pinned",
130
+ "exit": 0
131
+ },
132
+ {
133
+ "id": "tools/local-llm:runner-rails",
134
+ "exit": 0
135
+ }
136
+ ],
137
+ "excluded": [
138
+ {
139
+ "id": "release-eval",
140
+ "why": "a capture cannot be accurate about the record it is inside — captured before the record is written it is red for the record's absence, and captured after it is stale the moment the register is rendered. Its verdict for this release is the rail's own run on the pull request carrying this file."
141
+ }
142
+ ],
143
+ "abBaseline": {
144
+ "snapshot": "evals/ab/baseline.json",
145
+ "register": "evals/ab/baseline.md",
146
+ "captured": "2026-08-31T09:05:32.699Z",
147
+ "commit": "a642d551eec6c146c67ef0acfe3f8ba535c5ce6e",
148
+ "clean": false
149
+ }
150
+ }
@@ -0,0 +1,68 @@
1
+ # Eval result — Portulan 0.3.0
2
+
3
+ > **Generated. Do not edit.** Rendered from [`evals/releases/0.3.0.json`](0.3.0.json) by [`cli/release-eval.mjs`](../../cli/release-eval.mjs) and byte-compared by the `release-eval` verify recipe. This is the eval result milestone 8 requires a release to carry — `docs/plan.md`, Protocol → Versioning.
4
+
5
+ ## What this release was measured at
6
+
7
+ | | |
8
+ |---|---|
9
+ | Version | `0.3.0` |
10
+ | Captured | 2026-11-03 |
11
+ | Commit | `c08515fee0693cb0fc07fc6077fb152aa46bc28b` |
12
+ | Tree at capture | **not clean** |
13
+ | Node | `v26.12.1` |
14
+ | Platform | `darwin` |
15
+
16
+ ## The rails, and what each returned
17
+
18
+ 30 of the 31 recipes this workspace yielded at capture. The set is derived from [`cli/recipe-set.mjs`](../../cli/recipe-set.mjs) and never listed by hand.
19
+
20
+ | Recipe | Verdict |
21
+ |---|---|
22
+ | `docs` | green |
23
+ | `json` | green |
24
+ | `doctor` | green |
25
+ | `tests` | green |
26
+ | `plugin` | green |
27
+ | `compile` | green |
28
+ | `workflow-filters` | green |
29
+ | `index` | green |
30
+ | `context` | green |
31
+ | `ledger` | green |
32
+ | `control-chars` | green |
33
+ | `comments` | green |
34
+ | `rule-carriers` | green |
35
+ | `pack-version` | green |
36
+ | `pack-identity` | green |
37
+ | `payload` | green |
38
+ | `eval-bundle` | green |
39
+ | `goldens` | green |
40
+ | `mutants` | green |
41
+ | `fuzz-shell` | green |
42
+ | `version-carriers` | green |
43
+ | `skill-goldens` | green |
44
+ | `review-loop` | green |
45
+ | `telemetry` | green |
46
+ | `drills` | green |
47
+ | `ab` | green |
48
+ | `ab-grade` | green |
49
+ | `ab-run` | green |
50
+ | `tools/github:actions-pinned` | green |
51
+ | `tools/local-llm:runner-rails` | green |
52
+
53
+ **`release-eval` is excluded**, and the reason is printed rather than the row silently dropped: a capture cannot be accurate about the record it is inside — captured before the record is written it is red for the record's absence, and captured after it is stale the moment the register is rendered. Its verdict for this release is the rail's own run on the pull request carrying this file.
54
+
55
+ ## The A/B baseline this release ships against
56
+
57
+ [`evals/ab/baseline.md`](https://github.com/sleepy-panda-srl/portulan/blob/v0.3.0/evals/ab/baseline.md), rendered from `evals/ab/baseline.json` — captured 2026-08-31T09:05:32.699Z at `a642d551eec6c146c67ef0acfe3f8ba535c5ce6e`, over a tree that was **not clean**.
58
+
59
+ **Its figures are not repeated here.** They have one carrier and this is not it.
60
+
61
+ ## What this record does not establish
62
+
63
+ - **A green says this record agrees with its own capture — never that the release is good.** Every line below is what the recipes returned at one commit, and a recipe's own green establishes only what that recipe's documentation says it establishes.
64
+ - **The recipes were not run at the tag.** They ran at `c08515fee0693cb0fc07fc6077fb152aa46bc28b`, and this record is committed *in* the cut change — so the tagged tree is this commit plus whatever that change still adds after the capture, the record itself at minimum. A record cannot be captured at a commit that does not exist yet, and printing the one it was captured at is the only honest form.
65
+ - **`release-eval` is excluded from the rows above**, because a capture cannot be accurate about the record it is inside. Its verdict for this release is the rail's own run on the pull request that carries this file.
66
+ - **This record measures the Portulan repository's own build**, at the commit named above — never the workspace, project or package this release is installed into. Nothing here has looked at your tree.
67
+ - **The tree was NOT clean at capture** — the diff of the cut change is the only record of what was uncommitted, which is weaker than a sha and is printed rather than hidden.
68
+ - **The A/B baseline's figures are NOT restated here.** This record names which baseline the release ships against; the figures and everything that may not be concluded from them live in that register alone.
@@ -1,8 +1,8 @@
1
1
  # `evals/releases/`
2
2
 
3
- The eval result each release carries, one pair per version — milestone 8's ninth clause. The argument,
4
- the limits and the two designs a fresh context reversed are in [`../README.md`](../README.md); the
5
- mechanism is [`../../cli/release-eval.mjs`](../../cli/release-eval.mjs). This file does not restate
3
+ The eval result each release carries, one pair per version — milestone 8's ninth clause. The limits are
4
+ in [`../README.md`](../README.md); the argument, the mechanism and the two designs a fresh context
5
+ reversed are in [`../../cli/release-eval.mjs`](../../cli/release-eval.mjs). This file does not restate
6
6
  either, because a rule with two carriers is obeyed at the narrower one.
7
7
 
8
8
  - `<version>.json` — the **capture**: the verdict every recipe the workspace yielded returned, at a named
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sleepy_panda_srl/portulan",
3
- "version": "0.1.3",
3
+ "version": "0.3.0",
4
4
  "description": "Portulan — an agentic-engineering framework whose product is its files. The CLI wraps the file-based mechanics for hosts beyond Claude Code.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -26,7 +26,10 @@
26
26
  "!cli/ab.mjs",
27
27
  "!cli/ab-run.mjs",
28
28
  "!cli/ab-grade.mjs",
29
- "!cli/payload.mjs"
29
+ "!cli/local-routing.mjs",
30
+ "!cli/warm.mjs",
31
+ "!cli/payload.mjs",
32
+ "!cli/roster.mjs"
30
33
  ],
31
34
  "repository": {
32
35
  "type": "git",
@@ -17,6 +17,7 @@
17
17
  "supervision"
18
18
  ],
19
19
  "skills": [
20
- "./rituals/checkpoints/skills/"
20
+ "./rituals/checkpoints/skills/",
21
+ "./tools/local-llm/skills/"
21
22
  ]
22
23
  }
package/packs/README.md CHANGED
@@ -31,7 +31,9 @@ registers nothing, silently, while a validator walking deeper counts three;
31
31
  manifests therefore sit in this tree and they answer different questions: the repository's own at
32
32
  [`../.claude-plugin/plugin.json`](../.claude-plugin/plugin.json), which declares
33
33
  `./packs/rituals/checkpoints/skills/` because *its* root is the repository; and this one, whose root is
34
- `packs/`. Neither is redundant, and the paths differ by exactly the prefix their roots differ by.
34
+ `packs/`. Neither is redundant, and the paths differ by exactly the prefix their roots differ by. Both
35
+ also declare `tools/local-llm`'s `skills/` root, the same way, since this repository's workspace composes
36
+ that pack too; the plugin's name and version here are the checkpoints pack's.
35
37
 
36
38
  **The version here mirrors the pack's own** — `portulan.version` in
37
39
  [`rituals/checkpoints/pack.json`](rituals/checkpoints/pack.json), which the Pack Definition names as how
@@ -45,7 +47,8 @@ observed through a *local* marketplace over a copy of this directory. The quoted
45
47
  exists and the feed's pin moves to it. Until then the green is demonstrated on the payload and inferred
46
48
  for the feed — a distinction this repository would rather write down than round off.
47
49
 
48
- > **Two packs exist.** [`rituals/checkpoints`](rituals/checkpoints/README.md), the supervised-build
49
- > ritual; and [`tools/github`](tools/github/README.md), one verify recipe. `stacks/` is still empty and
50
- > says why.
50
+ > **Three packs exist.** [`rituals/checkpoints`](rituals/checkpoints/README.md), the supervised-build
51
+ > ritual; [`tools/github`](tools/github/README.md), one verify recipe; and
52
+ > [`tools/local-llm`](tools/local-llm/README.md), hybrid local-model execution under a zero-floor input
53
+ > budget and a verify-gated rollback loop. `stacks/` is still empty and says why.
51
54
 
@@ -100,7 +100,7 @@ under, and the checkpoint section of that project's own gate map.
100
100
 
101
101
  **The evidence is that project's build record**, cited as provenance rather than as procedure: the
102
102
  fresh-context checkpoints changed *designs* rather than wording at two consecutive milestones — the
103
- story is in [milestone 4's record](https://github.com/sleepy-panda-srl/portulan/blob/main/docs/milestones/m04.md),
103
+ story is in [milestone 4's record](https://github.com/sleepy-panda-srl/portulan/blob/8a33f9b/docs/milestones/m04.md),
104
104
  beside [the protocol itself](https://github.com/sleepy-panda-srl/portulan/blob/main/docs/plan.md) —
105
105
  while adopters received no artifact for the ritual that produced them. This pack is that artifact.
106
106
 
@@ -3,12 +3,12 @@
3
3
  Tool packs — integrations for the tools and MCP servers an agent drives (issue trackers, cloud
4
4
  CLIs, design tools, databases), with the how-to and the gate classification for each.
5
5
 
6
- > **One tool pack exists** — [`github/`](github/README.md). The machinery that reads one is a gate policy in a
7
- > host-neutral vocabulary, dispatched on by [`../../cli/compile.mjs`](../../cli/compile.mjs) — and the
8
- > cascade's missing middle (`core < pack < workspace`) is now bridged: a pack's `contributes.gates`
9
- > fragments are resolved and merged into the policy, **tighten-only**, by the same compiler.
6
+ > **Two tool packs exist** — [`github/`](github/README.md) and [`local-llm/`](local-llm/README.md). The
7
+ > machinery that reads one is a gate policy in a host-neutral vocabulary, dispatched on by
8
+ > [`../../cli/compile.mjs`](../../cli/compile.mjs) — and the cascade's missing middle (`core < pack < workspace`)
9
+ > is bridged: a pack's `contributes.gates` fragments are resolved and merged into the policy,
10
+ > **tighten-only**, by the same compiler.
10
11
  >
11
- > The first pack to use it is a *ritual* one ([`../rituals/checkpoints/`](../rituals/checkpoints/)), so
12
- > the promise this file has carried since it was written — that a tool pack ships "with the gate
13
- > classification for each" — is now buildable and still unbuilt. A tool pack becomes worth writing when
14
- > a workspace here drives a tool that needs one.
12
+ > [`local-llm/`](local-llm/README.md) is the first tool pack to ship a gate classification
13
+ > (`local-llm-off-loopback` at `propose`) alongside a persona and an offline verify recipe,
14
+ > fulfilling the promise this file has carried since it was written.
@@ -1,34 +1,13 @@
1
1
  #!/usr/bin/env bash
2
- #
3
2
  # Every `uses:` in a GitHub Actions workflow is pinned to a full 40-character commit SHA.
4
- #
5
- # Exit 0 green · 1 red · 2 could not run. The third is load-bearing here, as everywhere in this
6
- # project: a recipe that could not look must never report what a recipe that looked and found nothing
7
- # reports. See `README.md` beside this file for what the check does NOT cover and why it exists at all
8
- # given that GitHub can enforce pinning itself.
9
- #
10
- # Composed into an adopting workspace through `pack.json`'s `contributes.verify`, whose `run` names
11
- # this file through `${PACK_ROOT}` — the pack does not know the adopter's layout and must not guess it.
3
+ # Exit 0 green · 1 red · 2 could not run.
12
4
 
13
5
  set -uo pipefail
14
6
 
7
+ # Relative to the working directory, the adopter's root: where the pack sits says nothing about it.
15
8
  WORKFLOWS=".github/workflows"
16
9
 
17
- # ---------------------------------------------------------------------------------------------
18
- # The tools this recipe invokes, checked before it invokes them.
19
- #
20
- # Without this the failure is SILENT and GREEN: the two loops below read from process substitutions,
21
- # so a missing `find` or `grep` yields no lines at all, every counter stays zero, `status` stays 0 —
22
- # and the recipe reports a pass having examined nothing. That is the false green — "nothing looked"
23
- # reported as "nothing wrong" — and it is precisely what `.portulan/memory/verify-preconditions-fail-closed.md`
24
- # exists to refuse. The header below already cited that rule for the workflows DIRECTORY and did not
25
- # apply it to the recipe's own dependencies: a rule obeyed at one of its two sites, in a file that
26
- # quotes the rule. Copilot round 5.
27
- #
28
- # `requires` in `pack.json` declares the same list. Two carriers, deliberately — the manifest's is
29
- # what a host reads to decide whether to run at all, this one is what fires when it ran anyway — and
30
- # they are checked against each other by nothing, which is stated here rather than implied.
31
- # ---------------------------------------------------------------------------------------------
10
+ # `pack.json`'s `requires` lists these tools too, and nothing checks that the two agree.
32
11
  missing=""
33
12
  for tool in find grep sed sort cut wc tr; do
34
13
  command -v "$tool" >/dev/null 2>&1 || missing="$missing $tool"
@@ -40,16 +19,6 @@ if [ -n "$missing" ]; then
40
19
  exit 2
41
20
  fi
42
21
 
43
- # ---------------------------------------------------------------------------------------------
44
- # Preconditions fail closed. Enumeration is the precondition here exactly as it is in this project's
45
- # own recipes (`.portulan/memory/verify-preconditions-fail-closed.md`): a green computed over a
46
- # directory that was not there is a green about nothing.
47
- #
48
- # Both the absent directory and the present-but-empty one are could-not-run rather than vacuously
49
- # green, and the distinction between them is kept in the MESSAGE rather than in the exit code —
50
- # a workspace with no workflows has composed a pack that cannot do its job, and should hear that
51
- # sentence rather than a green it might mistake for coverage.
52
- # ---------------------------------------------------------------------------------------------
53
22
  if [ ! -d "$WORKFLOWS" ]; then
54
23
  printf 'actions-pinned: COULD NOT RUN — there is no %s directory in this repository.\n' "$WORKFLOWS" >&2
55
24
  printf ' A workspace with no GitHub Actions workflows has nothing for this recipe to check;\n' >&2
@@ -57,8 +26,6 @@ if [ ! -d "$WORKFLOWS" ]; then
57
26
  exit 2
58
27
  fi
59
28
 
60
- # `find` rather than a glob, so a directory holding only subdirectories is not mistaken for one
61
- # holding workflows. Sorted so the report is stable between runs and between machines.
62
29
  files=$(find "$WORKFLOWS" -maxdepth 1 -type f \( -name '*.yml' -o -name '*.yaml' \) | sort)
63
30
 
64
31
  if [ -z "$files" ]; then
@@ -69,30 +36,21 @@ fi
69
36
  status=0
70
37
  examined=0
71
38
  pinned=0
72
- local_refs=0
39
+ exempt=0
73
40
 
74
41
  while IFS= read -r file; do
75
- # One `uses:` per line is the shape every workflow in the wild uses; a folded or flow-mapping
76
- # spelling would slip past this, which is stated in README.md as a known limit rather than
77
- # covered by a matcher that would claim more than it reads.
42
+ # One `uses:` per line: a folded or flow-mapping spelling is not seen.
78
43
  while IFS= read -r line; do
79
44
  value=$(printf '%s\n' "$line" | sed -E 's/^[[:space:]]*-?[[:space:]]*uses:[[:space:]]*//; s/[[:space:]]*(#.*)?$//; s/^["'"'"']//; s/["'"'"']$//')
80
45
  [ -n "$value" ] || continue
81
46
 
82
- # `examined` counts only references this recipe can actually judge, and the exempt ones are
83
- # counted separately below. Counting them here made the summary print a ratio like `2 of 3
84
- # pinned` on a run where every non-exempt reference WAS pinned — a green whose own number
85
- # looked like a partial pass. Copilot round 2.
86
47
  case "$value" in
87
48
  ./*)
88
- # A local action lives in this repository and moves with it — there is no third-party
89
- # commit to pin, and demanding one would be a check that cannot be satisfied.
90
- local_refs=$((local_refs + 1))
49
+ exempt=$((exempt + 1))
91
50
  continue
92
51
  ;;
93
52
  docker://*)
94
- # A container reference is pinned by digest, not by commit. Out of scope and named.
95
- local_refs=$((local_refs + 1))
53
+ exempt=$((exempt + 1))
96
54
  continue
97
55
  ;;
98
56
  esac
@@ -102,10 +60,7 @@ while IFS= read -r file; do
102
60
  if [ "$ref" = "$value" ]; then
103
61
  printf ' UNPINNED %s: `uses: %s` carries no `@ref` at all\n' "$file" "$value" >&2
104
62
  status=1
105
- # Case-insensitive: a git object name is hex and hex is case-insensitive, so an uppercase or
106
- # mixed-case SHA is a pinned SHA. Matching lowercase only would have reported a correctly
107
- # pinned action as unpinned — a FALSE RED, which is the failure mode this project treats as
108
- # worse than a missed catch, because it is the one that gets a check switched off.
63
+ # Case-insensitive, as hex is: an uppercase SHA is still a pinned one.
109
64
  elif printf '%s' "$ref" | grep -Eqi '^[0-9a-f]{40}$'; then
110
65
  pinned=$((pinned + 1))
111
66
  else
@@ -120,8 +75,8 @@ count=$(printf '%s\n' "$files" | wc -l | tr -d ' ')
120
75
  if [ "$status" -eq 0 ]; then
121
76
  printf 'actions-pinned: %s of %s `uses:` reference(s) pinned to a full commit SHA across %s workflow file(s)' \
122
77
  "$pinned" "$examined" "$count"
123
- if [ "$local_refs" -gt 0 ]; then
124
- printf ', %s local or container reference(s) exempt' "$local_refs"
78
+ if [ "$exempt" -gt 0 ]; then
79
+ printf ', %s local or container reference(s) exempt' "$exempt"
125
80
  fi
126
81
  printf '\n\nGREEN — verify recipe passed.\n'
127
82
  else