@sleepy_panda_srl/portulan 0.1.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/LICENSE +201 -0
- package/NOTICE +5 -0
- package/README.md +87 -0
- package/cli/README.md +337 -0
- package/cli/compile.mjs +2209 -0
- package/cli/control-chars.mjs +570 -0
- package/cli/discover.mjs +985 -0
- package/cli/doctor.mjs +2803 -0
- package/cli/feedback.mjs +970 -0
- package/cli/gate.mjs +271 -0
- package/cli/index.mjs +1390 -0
- package/cli/init.mjs +1729 -0
- package/cli/librarian.mjs +1384 -0
- package/cli/manifest.mjs +59 -0
- package/cli/new.mjs +803 -0
- package/cli/pack-version.mjs +543 -0
- package/cli/plugin-lint.mjs +1566 -0
- package/cli/portulan.mjs +266 -0
- package/cli/recipe-set.mjs +425 -0
- package/cli/rule-carriers.mjs +489 -0
- package/cli/skills-set.mjs +715 -0
- package/cli/stop-gate.mjs +434 -0
- package/cli/upgrade.mjs +897 -0
- package/cli/vendor.mjs +1504 -0
- package/core/README.md +46 -0
- package/core/engine.md +47 -0
- package/core/operating/autonomy.md +100 -0
- package/core/operating/evolution.md +84 -0
- package/core/operating/loop.md +83 -0
- package/core/operating/memory.md +161 -0
- package/core/operating/safety.md +42 -0
- package/core/operating/verification.md +76 -0
- package/core/personas/README.md +40 -0
- package/core/personas/implementer.md +51 -0
- package/core/personas/librarian.md +73 -0
- package/core/personas/reviewer.md +52 -0
- package/core/skills/README.md +52 -0
- package/core/skills/clarify/SKILL.md +42 -0
- package/core/skills/codify/SKILL.md +65 -0
- package/core/skills/consolidate/SKILL.md +121 -0
- package/core/templates/gate-policy.md +76 -0
- package/core/templates/handoff.md +29 -0
- package/core/templates/memory-entry.md +44 -0
- package/core/templates/pack.md +94 -0
- package/core/templates/persona.md +69 -0
- package/core/templates/proposal.md +27 -0
- package/core/templates/repo-card.md +28 -0
- package/core/templates/skill.md +57 -0
- package/core/templates/task.md +26 -0
- package/core/templates/workspace.md +81 -0
- package/package.json +41 -0
- package/packs/.claude-plugin/plugin.json +22 -0
- package/packs/README.md +61 -0
- package/packs/rituals/README.md +13 -0
- package/packs/rituals/checkpoints/README.md +105 -0
- package/packs/rituals/checkpoints/pack.json +36 -0
- package/packs/rituals/checkpoints/personas/supervisor.md +64 -0
- package/packs/rituals/checkpoints/skills/milestone-close/SKILL.md +75 -0
- package/packs/rituals/checkpoints/skills/pre-commit/SKILL.md +75 -0
- package/packs/rituals/checkpoints/skills/session-open/SKILL.md +57 -0
- package/packs/stacks/README.md +8 -0
- package/packs/tools/README.md +17 -0
- package/packs/tools/github/README.md +65 -0
- package/packs/tools/github/pack.json +29 -0
- package/packs/tools/github/verify/README.md +51 -0
- package/packs/tools/github/verify/actions-pinned.sh +132 -0
- package/spec/README.md +399 -0
- package/spec/migrations/0001-repository-declares-its-tree.mjs +100 -0
- package/spec/migrations/0002-bundle-fallback-path.mjs +179 -0
- package/spec/migrations/README.md +95 -0
- package/spec/pack.schema.json +201 -0
- package/spec/slots.md +796 -0
- package/spec/workspace.schema.json +438 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Template — Gate policy
|
|
2
|
+
|
|
3
|
+
> A **gate policy** binds this team's concrete actions to the engine's tiers — Auto / Propose / Gated /
|
|
4
|
+
> Prohibited — in machine-readable form, so the gate map stops being prose and starts being enforcement.
|
|
5
|
+
> `compile` reads it and emits host hooks and permissions, plus a repository-ruleset export for the
|
|
6
|
+
> platform floor. Copy the skeleton below into `gates.json` and fill it. (Placeholders are in `{braces}`.)
|
|
7
|
+
> _(Provenance: the safety culture inherited from the predecessor practice — gates classified by
|
|
8
|
+
> undoability; 12-Factor Agents — compact, auditable policy. Tier semantics are fixed by
|
|
9
|
+
> [`../operating/autonomy.md`](../operating/autonomy.md), which is the authority.)_
|
|
10
|
+
>
|
|
11
|
+
> **Write the `why` before you write a rule.** A policy with no rationale is taste, and the `reason` on
|
|
12
|
+
> each rule is what an agent reads when it is refused — a refusal whose reason is a rule id is
|
|
13
|
+
> indistinguishable from a bug.
|
|
14
|
+
>
|
|
15
|
+
> **The compiler adds restriction only.** It emits `ask` and `deny` and never `allow`, so `auto` rules
|
|
16
|
+
> compile to nothing by design: an action nobody restricted is already permitted, and emitting a grant
|
|
17
|
+
> would let a policy widen what a host allows. That asymmetry is the whole safety property — read it
|
|
18
|
+
> before concluding a rule "did not work".
|
|
19
|
+
>
|
|
20
|
+
> **A compiled gate matches a spelling, not an intent.** A command reaching a gated action by another
|
|
21
|
+
> route escapes it, and one wrapper level is peeled and no more. What is indifferent to spelling is the
|
|
22
|
+
> **platform floor** beneath — branch protection, required checks, PR-as-gate — which is why the floor is
|
|
23
|
+
> a separate block below and not a rule among the others.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
```json
|
|
28
|
+
{
|
|
29
|
+
"portulan": { "gates": "1.0" },
|
|
30
|
+
|
|
31
|
+
"why": "{Why this team gates what it gates — the sentence a new agent reads first. Name the blast radius you are protecting, not the tools you are restricting.}",
|
|
32
|
+
|
|
33
|
+
"floor": {
|
|
34
|
+
"branch": "{default branch}",
|
|
35
|
+
"require_pull_request": true,
|
|
36
|
+
"block_force_push": true,
|
|
37
|
+
"checks": []
|
|
38
|
+
},
|
|
39
|
+
|
|
40
|
+
"rules": [
|
|
41
|
+
{
|
|
42
|
+
"id": "{rule-id}",
|
|
43
|
+
"tier": "prohibited",
|
|
44
|
+
"action": { "write": "{path that must never be machine-edited}" },
|
|
45
|
+
"reason": "{Why, and what to do instead. Prohibited means no yes makes this acceptable — if a human could approve it, it is Gated, not Prohibited.}"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "{rule-id}",
|
|
49
|
+
"tier": "gated",
|
|
50
|
+
"action": { "shell": "{command prefix}" },
|
|
51
|
+
"reason": "{Why a human must approve each time. Prefer the undoability test: what does it cost if this was wrong and nobody noticed for a week?}"
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"id": "{rule-id}",
|
|
55
|
+
"tier": "propose",
|
|
56
|
+
"action": { "write": "{path}" },
|
|
57
|
+
"reason": "{Why this lands as a proposal rather than a direct change.}"
|
|
58
|
+
}
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
> **`checks: []` is deliberate and you should probably leave it that way at first.** A floor requiring a
|
|
64
|
+
> status check that no workflow job reports is a red nobody caused — a fresh repository reports no checks,
|
|
65
|
+
> and a real one reports something else, so any name guessed here fails on the first run. Add the check
|
|
66
|
+
> once it exists and you know its exact context name. The consequence, stated because it is not obvious:
|
|
67
|
+
> with no checks declared, every `propose` rule compiles to nothing in the floor backend.
|
|
68
|
+
>
|
|
69
|
+
> **Which tiers compile depends on the backend, and there are two.** For a host-hook backend, `gated`
|
|
70
|
+
> prompts and `prohibited` refuses, while `auto` and `propose` emit nothing. For the **floor** backend the
|
|
71
|
+
> partition inverts: `propose` is exactly what a repository ruleset enforces. Run `compile --matrix` to
|
|
72
|
+
> print every rule against both rather than reasoning about it — the refusals are printed, never silent.
|
|
73
|
+
>
|
|
74
|
+
> **Start with the rules that destroy rather than create.** A ref deletion and a force-push are the two
|
|
75
|
+
> the floor backend can express, they are the two whose damage is hardest to undo, and they are a policy's
|
|
76
|
+
> honest first pair.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Template — Handoff
|
|
2
|
+
|
|
3
|
+
> A **handoff** records a decision and its *why*, so the next agent — or the next context window —
|
|
4
|
+
> inherits the reasoning and not just the result. Write one **at the close of every session** — that part
|
|
5
|
+
> is not conditional, and short is fine — and also when compacting, when hitting an iteration cap, or when
|
|
6
|
+
> passing work across a persona boundary. Decisions without their why get silently re-litigated or
|
|
7
|
+
> reversed. **Drop any section that has nothing to say** — the date and the decisions-with-why are the
|
|
8
|
+
> only irreducible parts, and a handoff of five lines is a valid handoff. The date lives in the
|
|
9
|
+
> **filename**, leading, in ISO form — `YYYY-MM-DD-{slug}.md` — not in a field inside the document: that
|
|
10
|
+
> is what a gate checks and what makes the series sort chronologically. That is what keeps the
|
|
11
|
+
> every-session rule from becoming ceremony: it scales down here, inside the artifact, rather than by
|
|
12
|
+
> being skipped. _(Provenance: Cognition — handoffs record decisions and rationale. The every-session rule
|
|
13
|
+
> and why it is binary rather than discretionary are in `../operating/loop.md`.)_
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
# Handoff — {task / thread}
|
|
18
|
+
|
|
19
|
+
**State.** {where things actually are right now — done, in-progress, blocked — in a few lines.}
|
|
20
|
+
|
|
21
|
+
**Decisions + why.** {each decision taken and the reason it was taken; the reasons are the payload.}
|
|
22
|
+
- {decision} — because {why}; alternatives considered: {…}.
|
|
23
|
+
|
|
24
|
+
**Open questions.** {what is undecided, and who decides it — flag the human-owned ones.}
|
|
25
|
+
|
|
26
|
+
**Next action.** {the single next step, concrete enough to start from cold.}
|
|
27
|
+
|
|
28
|
+
**Recoverability.** {anything left in a partial state, and how to make it safe; see
|
|
29
|
+
`../operating/safety.md`.}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Template — Memory entry
|
|
2
|
+
|
|
3
|
+
> A **memory** is one durable fact with its provenance, written so the right one can be recalled later
|
|
4
|
+
> without loading them all. One fact per entry; agents draft, humans accept into the curated layer.
|
|
5
|
+
> _(Provenance: Letta — per-agent memory; compounding engineering — provenance on every rule; ETH Zurich
|
|
6
|
+
> — curated beats generated. See `../operating/memory.md`.)_
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
**type:** rule | decision | reference | glossary
|
|
11
|
+
**scope:** {the persona / repo / workspace this belongs to — memory is per-agent, not global}
|
|
12
|
+
**provenance:** {one of the two forms below — a rule carrying neither is taste, and `doctor` rejects it}
|
|
13
|
+
|
|
14
|
+
**Which types this binds.** `doctor` *requires* a two-form stamp on `type: rule` and reports it on the
|
|
15
|
+
others, because thesis 4 and the mandate it comes from are rule-scoped. Write one on every entry anyway:
|
|
16
|
+
a decision you cannot trace is no easier to retire than a rule you cannot trace, and the difference is
|
|
17
|
+
only in what a machine is willing to fail you for.
|
|
18
|
+
|
|
19
|
+
Provenance takes one of exactly two forms, and the Workspace Definition
|
|
20
|
+
(`../../spec/workspace.schema.json`, `$defs/provenance`) makes both machine-checkable:
|
|
21
|
+
|
|
22
|
+
**provenance:** `form=link` `href={ticket | PR | postmortem | commit}`
|
|
23
|
+
— the incident is public, or at least visible to everyone who can read this rule.
|
|
24
|
+
|
|
25
|
+
**provenance:** `form=sealed` `owner={who can re-validate}` `date={YYYY-MM-DD}` `shape={the de-identified failure}`
|
|
26
|
+
— for an incident that cannot leave its owner's layer. The chain stays in the owner's workspace; what
|
|
27
|
+
travels is the stamp and the mechanism, never the episode. `shape` must carry the inputs, the wrong
|
|
28
|
+
outcome, and why the obvious guard misses — the bar is that someone who never saw the incident can still
|
|
29
|
+
write the rule's test. _(Sealed is the weaker form and is meant to look it: `doctor` reports what
|
|
30
|
+
proportion of a workspace's rules are sealed, because a workspace where everything is sealed has opted
|
|
31
|
+
out of retirement altogether. What no machine checks is whether a stamp is **true** — a fabricated one
|
|
32
|
+
passes exactly as a real one does, which is why the bar in `../skills/codify/SKILL.md` step 1 and human
|
|
33
|
+
review are what actually guard the content. See also `../operating/memory.md`.)_
|
|
34
|
+
|
|
35
|
+
The stamp is parsed out of backticked `key=value` tokens on this line, so annotation prose may follow it
|
|
36
|
+
freely — and should: the fields carry what a machine needs, the sentence after them carries what a reader
|
|
37
|
+
does.
|
|
38
|
+
|
|
39
|
+
{The fact, in one or two sentences. If it is a rule, state the rule and — on their own lines — **why**
|
|
40
|
+
it holds and **when to apply** it. Link related memories so recall can follow the thread.}
|
|
41
|
+
|
|
42
|
+
**Retire when:** {for a rule, the condition under which it stops being true — e.g. "the generated client
|
|
43
|
+
is deleted" — so the librarian can demote it instead of it living forever.} _(`doctor` reports an entry
|
|
44
|
+
that states none — a record no condition can demote is how a store only grows.)_
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# Template — Pack
|
|
2
|
+
|
|
3
|
+
> A **pack** is the cascade's middle layer — `core < pack < workspace < repo card < task` — and its
|
|
4
|
+
> manifest declares what it contributes to that cascade: skills, personas, verify recipes, and
|
|
5
|
+
> gate-policy fragments. It is an **index, not a container**: the content lives in the files the paths
|
|
6
|
+
> point at. Copy the skeleton below into `pack.json` at the pack's root and fill it. (Placeholders are in
|
|
7
|
+
> `{braces}`.) _(Provenance: platform engineering — golden paths for packs. The binding contract is
|
|
8
|
+
> [`../../spec/pack.schema.json`](../../spec/pack.schema.json), which is the authority; this template is
|
|
9
|
+
> a starting point and does not restate its rules.)_
|
|
10
|
+
>
|
|
11
|
+
> **Three things this template cannot decide for you, and each has bitten somebody.**
|
|
12
|
+
>
|
|
13
|
+
> **The category is not decorative.** A `stacks` pack profiles a language or framework, a `tools` pack
|
|
14
|
+
> integrates a tool or MCP server and ships the gate classification for each, and a `rituals` pack
|
|
15
|
+
> carries a repeatable procedure that composes onto the loop. It is spelled as the directory that holds
|
|
16
|
+
> the pack, so the canonical `category/name` reference and the tree agree by construction rather than by
|
|
17
|
+
> discipline.
|
|
18
|
+
>
|
|
19
|
+
> **`contributes` is required even when it is empty of a given kind** — a pack contributing nothing is a
|
|
20
|
+
> plugin, not a pack, and the manifest should say which it is rather than leaving it to be inferred from
|
|
21
|
+
> four absences.
|
|
22
|
+
>
|
|
23
|
+
> **A gate fragment may only ever ADD restriction.** It may raise a tier or add a prohibition and may
|
|
24
|
+
> never demote another layer's classification, and a fragment naming an id a lower layer already carries
|
|
25
|
+
> must keep that rule's `action` **unaltered** — raising the tier while replacing the matcher passes
|
|
26
|
+
> every tier comparison, is reported as a tightening, and removes the gate. That was measured on a real
|
|
27
|
+
> policy, not imagined. `auto` is absent from the tier enum for the same reason: it is the absence of
|
|
28
|
+
> restriction, so a pack declaring it could only be attempting a demotion.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"portulan": {
|
|
35
|
+
"pack": "1.0",
|
|
36
|
+
"version": "0.1.0"
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
"name": "{kebab-case-name}",
|
|
40
|
+
"category": "{stacks | tools | rituals}",
|
|
41
|
+
"summary": "{One line an agent can read before loading anything else.}",
|
|
42
|
+
"doc": "README.md",
|
|
43
|
+
|
|
44
|
+
"contributes": {
|
|
45
|
+
"skills": ["skills/"]
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
> **Why `personas`, `verify` and `gates` are absent from the skeleton above rather than present as placeholders.**
|
|
51
|
+
> A scaffold has to **validate on the run after it is written**. An id placeholder like `{recipe-id}`
|
|
52
|
+
> fails `$defs/slug`, and `{propose | gated | prohibited}` fails the tier enum — so a manifest carrying
|
|
53
|
+
> them is red the first time anybody checks it, on a workspace the tool just created. `personas` joins
|
|
54
|
+
> them for the same reason found one layer along: a `personas/{role}.md` placeholder *passes* the schema,
|
|
55
|
+
> because it is a well-formed relative path — and then fails the check that **opens** it, which the same
|
|
56
|
+
> change that wrote this template also added. Validating the shape and forgetting the opening is the
|
|
57
|
+
> narrower version of the very mistake this paragraph is about. That is a red the
|
|
58
|
+
> author did not cause and cannot act on except by deleting what was handed to them, and it is the exact
|
|
59
|
+
> shape this project has already shipped once and had to fix. Add the three keys below when you have real
|
|
60
|
+
> values for them; an absent key contributes nothing of that kind, which is the honest state until then.
|
|
61
|
+
>
|
|
62
|
+
> ```json
|
|
63
|
+
> "personas": ["personas/my-role.md"],
|
|
64
|
+
>
|
|
65
|
+
> "verify": [
|
|
66
|
+
> {
|
|
67
|
+
> "id": "my-recipe",
|
|
68
|
+
> "run": "{the command, as it would be typed from the ADOPTER's repository root}",
|
|
69
|
+
> "requires": ["bash"],
|
|
70
|
+
> "doc": "README.md"
|
|
71
|
+
> }
|
|
72
|
+
> ],
|
|
73
|
+
>
|
|
74
|
+
> "gates": [
|
|
75
|
+
> {
|
|
76
|
+
> "id": "my-rule",
|
|
77
|
+
> "tier": "gated",
|
|
78
|
+
> "action": { "shell": "{command prefix}" },
|
|
79
|
+
> "reason": "{Why the rule exists, in your own words, and what the agent should do instead. This sentence is what an adopter reads when a pack they installed refuses something — a refusal whose reason is a pack name is indistinguishable from a bug.}"
|
|
80
|
+
> }
|
|
81
|
+
> ]
|
|
82
|
+
> ```
|
|
83
|
+
|
|
84
|
+
> **Delete the keys you do not contribute.** Every one is optional, and an empty array is not the same as
|
|
85
|
+
> an absent key: `minItems: 1` refuses the empty array, so a pack that declares `"verify": []` fails
|
|
86
|
+
> validation rather than declaring nothing. Say nothing instead.
|
|
87
|
+
>
|
|
88
|
+
> **What a composed recipe may and may not do**, because it is the part that can be got wrong silently:
|
|
89
|
+
> composition is **additive only** — a pack may add a recipe and may never redefine, remove or replace
|
|
90
|
+
> one the workspace declares, nor become the workspace's `verify.default` — and a composed recipe is
|
|
91
|
+
> namespaced by its pack, so a collision is impossible rather than resolved. These limits are **not a
|
|
92
|
+
> security boundary** and should not be read as one: a recipe's `run` is arbitrary shell, and a workspace
|
|
93
|
+
> that lists your pack has already consented to running your code. What they prevent is narrower and is
|
|
94
|
+
> the thing worth preventing — a pack silently changing what the adopting workspace's **green means**.
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Template — Persona
|
|
2
|
+
|
|
3
|
+
> A **persona** is a role an agent takes, packaged as a **context firewall**: its own attention window,
|
|
4
|
+
> its own memory, and a `tools:` allow-list that grants only what the role needs. The parent loop fans
|
|
5
|
+
> work out to a persona and gets back a conclusion, not the persona's whole transcript. Copy the skeleton
|
|
6
|
+
> below and fill it. (Placeholders are in `{braces}`.) _(Provenance: HumanLayer — subagents as context
|
|
7
|
+
> firewalls; Cognition — read-parallel / write-isolated; Letta — per-agent memory.)_
|
|
8
|
+
>
|
|
9
|
+
> **The five-part contract is fixed by [`../personas/README.md`](../personas/README.md)**, which is the
|
|
10
|
+
> authority — this template is a starting point and deliberately does not restate the rule, for the
|
|
11
|
+
> reason [`../operating/evolution.md`](../operating/evolution.md) gives: one carrier, and the others
|
|
12
|
+
> reach it. All five parts are required: a persona missing one is not a lighter persona, it is an
|
|
13
|
+
> unenforceable one.
|
|
14
|
+
>
|
|
15
|
+
> **Prohibited is never a reach.** It is the one tier no role may act in, so a persona declaring it
|
|
16
|
+
> claims a permission that does not exist for anyone. Stated here, above the skeleton, rather than inside
|
|
17
|
+
> the *Autonomy reach* section — a scaffold whose reach section contains the word is a scaffold that
|
|
18
|
+
> `doctor` has to distinguish from a persona actually claiming it, and the cheapest way to keep that
|
|
19
|
+
> distinction clean is for the generated file not to say it at all. The rule is enforced by `doctor`, not
|
|
20
|
+
> by this sentence.
|
|
21
|
+
>
|
|
22
|
+
> **Two things worth knowing before you write the `tools:` line.** It is a set of **capability classes**,
|
|
23
|
+
> not one host's tool names — binding classes to a host's vocabulary happens in that host's adapter, and
|
|
24
|
+
> writing tool names here is what makes a persona stop being portable. And the binding is **lossy**: some
|
|
25
|
+
> charters survive translation into a tool grant and some do not. A charter that says *does not edit the
|
|
26
|
+
> code under review* becomes a rail; one that says *drafts everything, accepts nothing* is a constraint
|
|
27
|
+
> on what the role may conclude, which no tool list can express. Say which yours is, rather than letting
|
|
28
|
+
> a reader take the frontmatter for the gate.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
---
|
|
32
|
+
name: {kebab-case-name}
|
|
33
|
+
description: {The role in one line — what it is for, and what it must not do.}
|
|
34
|
+
tools: {capability classes, comma-separated — default-deny, least privilege for this role}
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
# Persona — {Name}
|
|
38
|
+
|
|
39
|
+
## Charter
|
|
40
|
+
|
|
41
|
+
{What this role is for. Then, just as load-bearing, what it must **not** do — the boundary is the half
|
|
42
|
+
that makes a firewall a firewall. Name the neighbouring role this is easiest to confuse with.}
|
|
43
|
+
|
|
44
|
+
## Autonomy reach
|
|
45
|
+
|
|
46
|
+
{The highest tier this role may act in, in **tier vocabulary** — Auto / Propose / Gated — never concrete
|
|
47
|
+
actions. The gate map that binds actions to tiers is workspace policy, and naming an action here would
|
|
48
|
+
move policy into a role. There are only three reaches, and the fourth tier is not one of them; see
|
|
49
|
+
[`core/personas/README.md`](../personas/README.md) for why.}
|
|
50
|
+
|
|
51
|
+
## Memory scope
|
|
52
|
+
|
|
53
|
+
{What this role remembers, and where it lands — a directory in the **adopting workspace's own layer**,
|
|
54
|
+
never in the pack or in core. Storage follows ownership. Declared here rather than in a pack manifest
|
|
55
|
+
because the scope is the persona's own field, and a pack distributing per-persona material its adopter
|
|
56
|
+
does not own is the one direction the cascade does not run. Empty until earned is the correct state: a
|
|
57
|
+
scope is a place for facts, not a claim that facts exist.}
|
|
58
|
+
|
|
59
|
+
## Read / write posture
|
|
60
|
+
|
|
61
|
+
{Most personas read in parallel; writes are isolated to one place so two agents never clobber the same
|
|
62
|
+
file. Say which this is, and — if it writes — the one place it writes.}
|
|
63
|
+
|
|
64
|
+
## What this persona's tool grant does NOT enforce
|
|
65
|
+
|
|
66
|
+
{Required. The half of the charter that is a constraint on judgement rather than on capability, and is
|
|
67
|
+
therefore held by the workspace's gate map and the platform floor rather than by the list above. A
|
|
68
|
+
persona whose limits section is empty is claiming its frontmatter is the gate, which is the one reading
|
|
69
|
+
[`../personas/README.md`](../personas/README.md) exists to prevent.}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Template — Rule-change proposal
|
|
2
|
+
|
|
3
|
+
> A **proposal** is how the curated layer changes: agent-drafted, human-owned, merged only through a
|
|
4
|
+
> reviewed (and where possible eval-gated) PR. No rule enters core, a pack, or a workspace except through
|
|
5
|
+
> one of these. _(Provenance: compounding engineering — codify incidents into rules; spec-driven — the
|
|
6
|
+
> human-owned constitution; see `../operating/evolution.md`.)_
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Proposal — {the rule, in one line}
|
|
11
|
+
|
|
12
|
+
**Incident.** {what went wrong, or the recurring friction that motivates this — with a link. A proposal
|
|
13
|
+
without a triggering incident is usually taste, not a rule.}
|
|
14
|
+
|
|
15
|
+
**Proposed rule.** {the rule as it would read in core / pack / workspace, at the right altitude.}
|
|
16
|
+
|
|
17
|
+
**Enforcement.** {how it earns its place: hook / compiled gate / Stop-gate / eval — or, if none, the
|
|
18
|
+
argument for why it is worth context-window tokens as prose. Rules that enforce or measure themselves
|
|
19
|
+
beat rules you must remember.}
|
|
20
|
+
|
|
21
|
+
**Provenance.** {the source or precedent, carried into the rule so it can later be retired — in one of
|
|
22
|
+
the two forms the Workspace Definition defines (`../../spec/workspace.schema.json`, `$defs/provenance`):
|
|
23
|
+
`form=link href={…}` when the incident is visible to everyone who can read the rule, or
|
|
24
|
+
`form=sealed owner={…} date={YYYY-MM-DD} shape={the de-identified failure}` when it cannot leave its
|
|
25
|
+
owner's layer. Neither form present means the proposal is taste; see `../templates/memory-entry.md`.}
|
|
26
|
+
|
|
27
|
+
**Decision.** {human owner} — accepted | rejected | revised, on {date} — because {…}.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Template — Repo card
|
|
2
|
+
|
|
3
|
+
> A **repo card** is the per-repo layer of the resolution cascade (`core < pack < workspace < repo card
|
|
4
|
+
> < task`): the small, high-value facts an agent needs to work in *this* repository and cannot infer
|
|
5
|
+
> safely. Keep it short — it is loaded often. Copy the skeleton below into a repo and fill it; delete
|
|
6
|
+
> lines that don't apply rather than leaving them blank. (Placeholders are in `{braces}`.)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Repo — {name}
|
|
11
|
+
|
|
12
|
+
**What it is.** {one or two sentences: the repo's job and its blast radius.}
|
|
13
|
+
|
|
14
|
+
**Build / test / run.**
|
|
15
|
+
- build: `{command}`
|
|
16
|
+
- test: `{command}` ← the verify recipe the Stop-gate runs (see `../operating/verification.md`)
|
|
17
|
+
- run: `{command}`
|
|
18
|
+
|
|
19
|
+
**Gates.** {actions here that deviate from the workspace gate map — e.g. "migrations require a DBA
|
|
20
|
+
review." Leave empty to inherit the workspace default; see `../operating/autonomy.md`.}
|
|
21
|
+
|
|
22
|
+
**Layout.** {the 3–6 paths a newcomer agent most needs — not a full tree.}
|
|
23
|
+
|
|
24
|
+
**Quirks.** {the non-obvious facts that cause wrong changes: the flaky test to rerun, the generated file
|
|
25
|
+
never to hand-edit, the module that looks dead but isn't.}
|
|
26
|
+
|
|
27
|
+
**Provenance.** {link the card, or any rule in it, back to the incident that earned it — so it can be
|
|
28
|
+
retired when it no longer applies.}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Template — Skill
|
|
2
|
+
|
|
3
|
+
> A **skill** is a procedure the engine pulls into context on demand, in `SKILL.md` form. Skills are how
|
|
4
|
+
> the engine stays small while still knowing a lot: the kernel is always loaded, a skill costs nothing
|
|
5
|
+
> until its trigger fires. Copy the skeleton below and fill it; delete lines that don't apply rather than
|
|
6
|
+
> leaving them blank. (Placeholders are in `{braces}`.) _(Provenance: AAIF — `SKILL.md` as the
|
|
7
|
+
> distribution plane; HumanLayer — skills-first progressive disclosure. The contract this template obeys
|
|
8
|
+
> is fixed by [`../skills/README.md`](../skills/README.md), which is the authority; this file is a
|
|
9
|
+
> starting point, not a second statement of the rule.)_
|
|
10
|
+
>
|
|
11
|
+
> **The bar, before you write anything.** A skill earns its place only if it **enforces** something,
|
|
12
|
+
> **measures** something, or is worth the tokens it costs when loaded. A procedure that is really a
|
|
13
|
+
> standing rule belongs in [`../operating/`](../operating/) as doctrine; a one-off belongs in a task. A
|
|
14
|
+
> skill nobody triggers is demoted by the librarian, so a trigger you cannot describe concretely is the
|
|
15
|
+
> signal to stop and write something else.
|
|
16
|
+
>
|
|
17
|
+
> **`name` and `description` are the contract, not decoration.** They are the only two fields a host
|
|
18
|
+
> reads before deciding whether to load the body, so `description` has to carry the trigger — the
|
|
19
|
+
> circumstances under which this skill is the right thing to reach for. A description that summarises the
|
|
20
|
+
> body instead of naming its trigger produces a skill that is never selected and then demoted for it.
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
---
|
|
24
|
+
name: {kebab-case-name}
|
|
25
|
+
description: {What this does, AND when to use it — the trigger belongs here, because this line is what a host reads before loading anything else. One or two sentences.}
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
# Skill — {Name}
|
|
29
|
+
|
|
30
|
+
> {One line: what this procedure is for, and the one thing it is not for.}
|
|
31
|
+
|
|
32
|
+
## When to use it
|
|
33
|
+
|
|
34
|
+
{The concrete trigger. Prefer something machine-produced — a rail went red, a budget was breached, a
|
|
35
|
+
command exited non-zero — over a judgement call, because a trigger that depends on noticing is a trigger
|
|
36
|
+
that gets missed. If it genuinely is a judgement call, say what the judgement is.}
|
|
37
|
+
|
|
38
|
+
**When not to use it.** {The neighbouring case this is easy to confuse with, and what to reach for
|
|
39
|
+
instead. Skipping this line is how two skills end up competing for the same trigger.}
|
|
40
|
+
|
|
41
|
+
## The pass
|
|
42
|
+
|
|
43
|
+
1. {Step. Imperative, and specific enough that two people following it produce the same artifact.}
|
|
44
|
+
2. {Step.}
|
|
45
|
+
3. {Step.}
|
|
46
|
+
|
|
47
|
+
## What it produces
|
|
48
|
+
|
|
49
|
+
{The artifact, and where it lands. If the skill changes no file, say so — a skill whose output is a
|
|
50
|
+
conclusion in the caller's context is legitimate, and leaving it implied is how it acquires an imagined
|
|
51
|
+
side effect.}
|
|
52
|
+
|
|
53
|
+
## Honest limits
|
|
54
|
+
|
|
55
|
+
{What this skill does NOT check, cover, or guarantee. Required rather than optional: a procedure that
|
|
56
|
+
lists only what it catches reads as covering everything, and the gap is discovered by whoever trusted it.
|
|
57
|
+
If you cannot name a limit, you have not found it yet.}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Template — Task
|
|
2
|
+
|
|
3
|
+
> A **task** is the atomic unit of work and the atomic unit of context: everything an agent needs to do
|
|
4
|
+
> one thing, and nothing it doesn't. Self-contained, so it can be handed to a fresh window or a subagent
|
|
5
|
+
> without the surrounding conversation. _(Provenance: BMAD — the story file as atomic context unit;
|
|
6
|
+
> spec-driven — testable acceptance criteria; verification-first — the failing test as spec.)_
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Task — {title}
|
|
11
|
+
|
|
12
|
+
**Goal.** {the outcome in one or two sentences — what is true when this is done.}
|
|
13
|
+
|
|
14
|
+
**Acceptance criteria.** {testable, EARS-style — "when {trigger}, the system shall {response}." Each
|
|
15
|
+
line must map to an observable check.}
|
|
16
|
+
- [ ] …
|
|
17
|
+
|
|
18
|
+
**Verify.** {the failing test or command that is red now and green when the task is met — this is the
|
|
19
|
+
definition of done, not a description of it. See `../operating/verification.md`.}
|
|
20
|
+
|
|
21
|
+
**Constraints.** {the gates in the way, the files that must not change, the altitude to match.}
|
|
22
|
+
|
|
23
|
+
**Context.** {the few links that matter: the repo card, related memory, the incident or ticket. Not a
|
|
24
|
+
document dump — the point of the task file is that it stays small.}
|
|
25
|
+
|
|
26
|
+
**Lane.** full | triage {see `../operating/loop.md`.}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Template — Workspace
|
|
2
|
+
|
|
3
|
+
> A **workspace** is the per-team layer of the resolution cascade: identity, principles, gate map, verify
|
|
4
|
+
> recipes, definition of done, memory. Core and packs carry the universal best practice; the workspace is
|
|
5
|
+
> where a team's own specifics live, and **they live only here** — core and packs never absorb them.
|
|
6
|
+
> Copy the skeleton below into `workspace.json` at the workspace root and fill it. (Placeholders are in
|
|
7
|
+
> `{braces}`.) _(The binding contract is
|
|
8
|
+
> [`../../spec/workspace.schema.json`](../../spec/workspace.schema.json), with per-slot rationale in
|
|
9
|
+
> [`../../spec/slots.md`](../../spec/slots.md). Those are the authority; this template is a starting
|
|
10
|
+
> point.)_
|
|
11
|
+
>
|
|
12
|
+
> **`init` versus this template.** `init` onboards a **repository** — it asks where that repository's
|
|
13
|
+
> workspace resides, scans the tree, and drafts a workspace shaped by what it found. This template is for
|
|
14
|
+
> the case `init` does not cover: authoring a workspace by hand, or one that governs from outside a
|
|
15
|
+
> repository (a feed-side workspace, a portfolio workspace covering several products). If you are
|
|
16
|
+
> onboarding a repository, run `init` and curate what it drafts; you will get more than this skeleton.
|
|
17
|
+
>
|
|
18
|
+
> **A slot must be a whole file.** Not a fragment, not a heading inside a larger document — because a
|
|
19
|
+
> fragment-addressed slot cannot be link-checked, and a slot nothing can validate is a slot that drifts.
|
|
20
|
+
> That constraint did most of the design work in the Workspace Definition and it binds your workspace too.
|
|
21
|
+
>
|
|
22
|
+
> **`kind` decides what else is required.** A `repository` workspace must declare `tree` — the path to
|
|
23
|
+
> the repository whose claims its repo cards describe — because without it the claims lint has no ground
|
|
24
|
+
> to check against and degrades an entire check class to notes. A `pointer` workspace declares
|
|
25
|
+
> `governed_by` instead and materialises nothing: **one repository is governed by exactly one workspace**,
|
|
26
|
+
> and a pointer is how a repository says which one.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"portulan": { "spec": "2.7" },
|
|
33
|
+
|
|
34
|
+
"name": "{kebab-case-name}",
|
|
35
|
+
"summary": "{One line: whose workspace this is and what it governs.}",
|
|
36
|
+
"kind": "{repository | demo | portfolio | pointer}",
|
|
37
|
+
"tree": "../",
|
|
38
|
+
|
|
39
|
+
"slots": {
|
|
40
|
+
"identity": "identity.md",
|
|
41
|
+
"principles": "principles.md",
|
|
42
|
+
"gates": "gate-map.md",
|
|
43
|
+
"dod": "dod.md",
|
|
44
|
+
"memory": "memory/",
|
|
45
|
+
"repos": "repos/",
|
|
46
|
+
"tasks": "tasks/",
|
|
47
|
+
"handoffs": "handoffs/",
|
|
48
|
+
"proposals": "proposals/"
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
"gates": "gates.json",
|
|
52
|
+
|
|
53
|
+
"verify": {
|
|
54
|
+
"default": "{recipe-id}",
|
|
55
|
+
"recipes": [
|
|
56
|
+
{
|
|
57
|
+
"id": "{recipe-id}",
|
|
58
|
+
"run": "{the command, as it would be typed from the repository root}",
|
|
59
|
+
"requires": ["{tool}"],
|
|
60
|
+
"doc": "verify/README.md"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
"packs": []
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
> **Delete the optional slots you do not have**, rather than pointing them at empty directories — a slot
|
|
70
|
+
> that resolves to nothing is a promise the tree cannot keep, and `doctor` fails a slot pointing at
|
|
71
|
+
> something absent. Add them back when the directory has content.
|
|
72
|
+
>
|
|
73
|
+
> **The `verify.default` is the recipe the Stop-gate runs**, so it is the one that decides what *done*
|
|
74
|
+
> means here. Two properties it must have, both learned the expensive way: it must exit **2** rather than
|
|
75
|
+
> 0 when it could not run — a recipe that cannot run must never look like one that ran and passed — and
|
|
76
|
+
> it must not be a stub that exits 0 while you decide, because that puts a false green under every gate
|
|
77
|
+
> from the day the workspace was created.
|
|
78
|
+
>
|
|
79
|
+
> **`packs` is a list of `category/name` references, and declaring one is what composes it.** A pack you
|
|
80
|
+
> list can add gate restriction and verify recipes to this workspace, so list only packs whose code you
|
|
81
|
+
> are willing to run.
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@sleepy_panda_srl/portulan",
|
|
3
|
+
"version": "0.1.0",
|
|
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
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"portulan": "cli/portulan.mjs"
|
|
9
|
+
},
|
|
10
|
+
"engines": {
|
|
11
|
+
"node": ">=20"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"cli/",
|
|
15
|
+
"core/",
|
|
16
|
+
"spec/",
|
|
17
|
+
"packs/",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE",
|
|
20
|
+
"NOTICE",
|
|
21
|
+
"!cli/*.test.mjs",
|
|
22
|
+
"!cli/fixtures",
|
|
23
|
+
"!cli/eval-bundle.mjs",
|
|
24
|
+
"!cli/eval-license.template.md"
|
|
25
|
+
],
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/sleepy-panda-srl/portulan.git"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/sleepy-panda-srl/portulan",
|
|
31
|
+
"bugs": {
|
|
32
|
+
"url": "https://github.com/sleepy-panda-srl/portulan/issues"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"agents",
|
|
36
|
+
"ai",
|
|
37
|
+
"agentic-engineering",
|
|
38
|
+
"claude-code",
|
|
39
|
+
"workspace"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/claude-code-plugin-manifest.json",
|
|
3
|
+
"name": "portulan-checkpoints",
|
|
4
|
+
"displayName": "Portulan checkpoints",
|
|
5
|
+
"version": "0.2.1",
|
|
6
|
+
"description": "The supervised-build checkpoint ritual as a pack: session-open, pre-commit and milestone-close, each graded by a context that has not seen the implementation, in a fixed verdict vocabulary.",
|
|
7
|
+
"author": {
|
|
8
|
+
"name": "Sleepy Panda SRL",
|
|
9
|
+
"url": "https://sleepypanda.ro"
|
|
10
|
+
},
|
|
11
|
+
"repository": "https://github.com/sleepy-panda-srl/portulan",
|
|
12
|
+
"license": "Apache-2.0",
|
|
13
|
+
"keywords": [
|
|
14
|
+
"portulan",
|
|
15
|
+
"pack",
|
|
16
|
+
"rituals",
|
|
17
|
+
"supervision"
|
|
18
|
+
],
|
|
19
|
+
"skills": [
|
|
20
|
+
"./rituals/checkpoints/skills/"
|
|
21
|
+
]
|
|
22
|
+
}
|