create-pmos 0.30.1 → 0.31.1

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-pmos",
3
- "version": "0.30.1",
3
+ "version": "0.31.1",
4
4
  "description": "Scaffold the PMOS portable kit \u2014 run the PMOS product-management workflow (initiative \u2192 lane-fit spec \u2192 contract \u2192 build \u2192 three gates, with work-type lanes for features, research, maintenance, incidents, and content) entirely inside your own repo, with all state as git-versioned files. Includes `workflow-sweep`, which audits the state every board derives from and separates real defects from work you simply have not done. `create-pmos workspace` also installs + launches the local Terminal Workspace GUI. No database, no network, no telemetry.",
5
5
  "license": "MIT",
6
6
  "bin": {
package/payload/AGENTS.md CHANGED
@@ -6,19 +6,28 @@ timestamp: 2026-07-22
6
6
 
7
7
  # AGENTS.md — PMOS Portable Kit (file mode)
8
8
 
9
- > This file must be read at optimal altitude — concrete heuristics that guide behavior, not brittle
10
- > if-else rules engines, and not vague mandates that assume shared context.
9
+ > This file must be read at optimal altitude — concrete heuristics that guide behavior, not brittle if-else rules engines, and not vague mandates that assume shared context.
11
10
 
12
- You are working in a product repo that vendors the **PMOS portable kit** (D54). PMOS here is a
13
- **control plane** for how work is specced, evaluated, and accepted — it generates no application
14
- code by itself; you build the product, PMOS disciplines the loop.
11
+ You are working in a product repo that vendors the **PMOS portable kit** (D54). PMOS here is a **control plane** for how work is specced, evaluated, and accepted: you build the product, PMOS disciplines the loop.
12
+
13
+ (PMOS itself is a control plane **and a factory** since D65 — but the factory half is not in this kit. What you have vendored is the governing half. If a future kit ships execution, it will arrive propose-only: opening pull requests, never holding push or merge rights.)
14
+
15
+ ## Writing markdown — one paragraph, one line
16
+
17
+ **Do not hard-wrap prose.** A paragraph is one line, a list item is one line, however long. Leave a blank line above a list, a table, a fence and a heading.
18
+
19
+ A newline inside a paragraph is invisible in markdown — every renderer joins the lines — and it is a **line break** in Jira wiki markup, in a Google Doc, and in the markdown→ADF converters an agent uses to publish. A wrapped spec arrives at your tracker as ragged half-lines, and nothing on the far side can undo it: an accidental wrap and a deliberate break are the same byte in the source.
20
+
21
+ ```bash
22
+ node pmos/scripts/md-newlines.js <path> # what is wrong, and where
23
+ node pmos/scripts/md-newlines.js --fix <path> # repair it — paragraphs only, never code or tables
24
+ ```
25
+
26
+ It refuses frontmatter, fenced and indented code, table rows, headings, deliberate two-space hard breaks and setext underlines, and it is idempotent: running it twice changes nothing the second time.
15
27
 
16
28
  ## The containment rule (non-negotiable)
17
29
 
18
- **This deployment is file-mode. Make NO network call to any PMOS backend — no MCP connection, no
19
- `get_skill`, no telemetry, nothing.** Every piece of PMOS state lives in this repo, under `pmos/`.
20
- This boundary exists because this product's data must not leave its own remote. If you find
21
- yourself wanting an MCP tool, use the state adapter below instead.
30
+ **This deployment is file-mode. Make NO network call to any PMOS backend — no MCP connection, no `get_skill`, no telemetry, nothing.** Every piece of PMOS state lives in this repo, under `pmos/`. This boundary exists because this product's data must not leave its own remote. If you find yourself wanting an MCP tool, use the state adapter below instead.
22
31
 
23
32
  ## The state adapter — every PMOS tool is a file convention
24
33
 
@@ -39,17 +48,11 @@ yourself wanting an MCP tool, use the state adapter below instead.
39
48
  | `log_friction` / `log_discovery` | append to `pmos/state/discoveries.md` (ISO date, `#friction` tag where applicable) |
40
49
  | `get_agent_runs` / `get_friction` | read those same files (your episodic memory — consult before contracting) |
41
50
 
42
- State files are **append-only**: never rewrite or delete an existing line or initiative record;
43
- correct by appending. When a vendored skill names a hosted tool, apply this table — the skill
44
- bodies are single-sourced from the PMOS kernel and are not forked for file mode.
51
+ State files are **append-only**: never rewrite or delete an existing line or initiative record; correct by appending. When a vendored skill names a hosted tool, apply this table — the skill bodies are single-sourced from the PMOS kernel and are not forked for file mode.
45
52
 
46
53
  ## Work-type lanes (D56)
47
54
 
48
- Every initiative carries a `type` — the lane picks what the gate requires **before** work; the
49
- kernel (anchor — a KR **or** `health:<id>` from `planning/okrs/health-budgets.md` (D59) —
50
- eval-before-work in lane-appropriate form, human Acceptance, append-only state)
51
- holds in every lane. Unknown/missing type = `feature`, the strictest lane. The enum is closed —
52
- never invent a lane; a work shape with no lane escalates to the PM.
55
+ Every initiative carries a `type` — the lane picks what the gate requires **before** work; the kernel (anchor — a KR **or** `health:<id>` from `planning/okrs/health-budgets.md` (D59) — eval-before-work in lane-appropriate form, human Acceptance, append-only state) holds in every lane. Unknown/missing type = `feature`, the strictest lane. The enum is closed — never invent a lane; a work shape with no lane escalates to the PM.
53
56
 
54
57
  | type | spec artifact the gate requires | eval form |
55
58
  |---|---|---|
@@ -60,165 +63,65 @@ never invent a lane; a work shape with no lane escalates to the PM.
60
63
  | `content` | content brief at `prd_path` (audience, message, claims + sources — `skills/copywriting.skill`, template in `okf/core/templates/`) | copy rubric recommended, not required (D58); PM judges at the gate otherwise |
61
64
  | `prototype` | quick prototype brief at `prd_path` (the judgement exercised + the specimen format — `skills/prototype.skill`, template in `okf/core/templates/`), **before** the PRD | **NO rubric, BY DESIGN** — grilled as dialogue, never scored (D60(d)); closes on a PM disposition: promote / iterate / kill |
62
65
 
63
- > **Known gap in this kit build:** `scripts/gate-initiative.sh` does not yet parameterise the
64
- > `prototype` lane — a prototype initiative falls through to the `feature` branch and is asked for a
65
- > `rubric_path` it must not have. Until that is fixed, set the lane's `type: prototype` in the
66
- > initiative file as normal and expect to carry the gate failure knowingly, or run the prototype
67
- > before opening the record. Tracked upstream; not a licence to invent a rubric for leg A.
68
-
69
- **Routing rule (D60, as amended 2026-07-24) — an idea the PM cannot judge from a DESCRIPTION goes to
70
- the `prototype` lane BEFORE anyone writes a PRD.** The entry rule is the **specimen test**: *can the
71
- PM judge this from a description, or do they need an instance of it in front of them?* It is **not
72
- gated on the work having a user interface** — *does this skill's output actually teach?* and *does
73
- this API feel right to call?* qualify exactly as an unseen screen does. The PM briefs; an agent
74
- produces **2–3 divergent specimens in whatever format fits** (lookable UI, a worked sample output,
75
- call/response transcripts, a schema sketch); a craft-grade grilling sharpens them as **dialogue,
76
- never a score**; a promote-disposition derives the feature-lane PRD + rubric from what was validated.
77
- The test must discriminate **both ways** — most work (a tool, a migration, a CI gate) *is*
78
- description-judgeable and routes straight to its PRD untaxed. Contrast `discovery`, which holds a
79
- **question** and delivers a **learning**; this lane holds a **candidate** and delivers a **validated
80
- direction**. Prototypes live in `prototype/` and never ship.
81
-
82
- `no-initiative` label = anchor-free trivia ONLY (a typo, a broken link). A chore, spike,
83
- incident, or piece of content is real work and takes its lane — that is what keeps the record
84
- honest.
66
+ > **Known gap in this kit build:** `scripts/gate-initiative.sh` does not yet parameterise the `prototype` lane — a prototype initiative falls through to the `feature` branch and is asked for a `rubric_path` it must not have. Until that is fixed, set the lane's `type: prototype` in the initiative file as normal and expect to carry the gate failure knowingly, or run the prototype before opening the record. Tracked upstream; not a licence to invent a rubric for leg A.
67
+
68
+ **Routing rule (D60, as amended 2026-07-24) — an idea the PM cannot judge from a DESCRIPTION goes to the `prototype` lane BEFORE anyone writes a PRD.** The entry rule is the **specimen test**: *can the PM judge this from a description, or do they need an instance of it in front of them?* It is **not gated on the work having a user interface** — *does this skill's output actually teach?* and *does this API feel right to call?* qualify exactly as an unseen screen does. The PM briefs; an agent produces **2–3 divergent specimens in whatever format fits** (lookable UI, a worked sample output, call/response transcripts, a schema sketch); a craft-grade grilling sharpens them as **dialogue, never a score**; a promote-disposition derives the feature-lane PRD + rubric from what was validated. The test must discriminate **both ways** — most work (a tool, a migration, a CI gate) *is* description-judgeable and routes straight to its PRD untaxed. Contrast `discovery`, which holds a **question** and delivers a **learning**; this lane holds a **candidate** and delivers a **validated direction**. Prototypes live in `prototype/` and never ship.
69
+
70
+ `no-initiative` label = anchor-free trivia ONLY (a typo, a broken link). A chore, spike, incident, or piece of content is real work and takes its lane — that is what keeps the record honest.
85
71
 
86
72
  ## Agent run protocol (every run, in order)
87
73
 
88
74
  1. Read this file.
89
- 2. Read the relevant `pmos/skills/*.skill` for the task type (L1 index first; body when relevant).
90
- No skill fits → escalate to the PM; do not improvise a procedure.
91
- 3. Retrieve context: `pmos/okf/` for concepts, `pmos/okf/product/` for this product's knowledge,
92
- `pmos/state/runs.jsonl` + `discoveries.md` for what prior runs tried (episodic memory).
93
- - **Existing surface → the PM needs the understanding too.** That retrieval warms *you*. When the
94
- work touches something that already exists — the test is *would specifying this honestly require
95
- reading code that already exists?* — run `skills/explain-surface.skill` on that region **before**
96
- the brief/PRD is authored, in a fresh context and **not** by the agent that will write it.
97
- Standard for existing surfaces, skipped for greenfield. Without it, the agent acquires the
98
- understanding and the **PM approves a spec built on facts they never saw**.
99
- 4. Anchor: the initiative file must exist at stage `intake`/`spec` with its **lane's** spec/eval
100
- artifacts authored **before** build (eval-first, D13, generalized per lane — see the lane table
101
- above; `feature` = PRD + rubric; the incident lane alone records post-hoc). Contract per lane:
102
- `feature` = the rubric's sprint-contract section with the PM's explicit `PM_approved: true`;
103
- `discovery` = the PM-approved spike brief (question + timebox); `chore` = the stated intent;
104
- `content` = the PM-approved content brief; `prototype` = the PM-approved quick brief (the
105
- judgement exercised + the specimen format) — and **no rubric**, which is the decision, not an
106
- omission. No work before the lane's approval. Feature-lane
107
- `test_method`s SHOULD be executable checks written before build (D58) — wire
108
- `templates/product-ci-starter.yml` so tier 1 is never silently empty.
75
+ 2. Read the relevant `pmos/skills/*.skill` for the task type (L1 index first; body when relevant). No skill fits → escalate to the PM; do not improvise a procedure.
76
+ 3. Retrieve context: `pmos/okf/` for concepts, `pmos/okf/product/` for this product's knowledge, `pmos/state/runs.jsonl` + `discoveries.md` for what prior runs tried (episodic memory).
77
+ - **Existing surface → the PM needs the understanding too.** That retrieval warms *you*. When the work touches something that already exists — the test is *would specifying this honestly require reading code that already exists?* — run `skills/explain-surface.skill` on that region **before** the brief/PRD is authored, in a fresh context and **not** by the agent that will write it. Standard for existing surfaces, skipped for greenfield. Without it, the agent acquires the understanding and the **PM approves a spec built on facts they never saw**.
78
+ 4. Anchor: the initiative file must exist at stage `intake`/`spec` with its **lane's** spec/eval artifacts authored **before** build (eval-first, D13, generalized per lane — see the lane table above; `feature` = PRD + rubric; the incident lane alone records post-hoc). Contract per lane: `feature` = the rubric's sprint-contract section with the PM's explicit `PM_approved: true`; `discovery` = the PM-approved spike brief (question + timebox); `chore` = the stated intent; `content` = the PM-approved content brief; `prototype` = the PM-approved quick brief (the judgement exercised + the specimen format) — and **no rubric**, which is the decision, not an omission. No work before the lane's approval. Feature-lane `test_method`s SHOULD be executable checks written before build (D58) — wire `templates/product-ci-starter.yml` so tier 1 is never silently empty.
109
79
  5. Branch `initiative/<id>`; execute at control altitude; one commit per artifact.
110
- 6. Self-evaluate against the contract — self-eval is unreliable; it is an input to the gates, never
111
- a verdict.
80
+ 6. Self-evaluate against the contract — self-eval is unreliable; it is an input to the gates, never a verdict.
112
81
  7. Close the gates: **Quality** = this repo's CI plus the kit gate (`pmos/scripts/gate-initiative.sh`
113
- + `pmos/planning/evals/check_rubrics.py`); **Review** = score against the rubric with the evaluator
114
- skill (adversarial, outcome-only, default-to-refute) and append to `evals.jsonl`; **Acceptance**
115
- = the human PM merges and disposes — final and non-delegable. Never merge your own PR.
82
+ + `pmos/planning/evals/check_rubrics.py`); **Review** = score against the rubric with the evaluator skill (adversarial, outcome-only, default-to-refute) and append to `evals.jsonl`; **Acceptance** = the human PM merges and disposes — final and non-delegable. Never merge your own PR.
116
83
  8. Log the run (`runs.jsonl`) and any friction (`discoveries.md`, `#friction`).
117
84
 
118
85
  ## Human builders & multi-repo initiatives (D58)
119
86
 
120
- - **A human building instead of an agent?** Same loop, plain terms: [AGENTS-HUMAN.md](AGENTS-HUMAN.md).
121
- State is written with `scripts/pmos-log.sh` (validated, append-only) — never hand-edited. Mixed
122
- human+agent teams write the same state through the same conventions.
123
- - **An initiative spanning multiple repos?** ONE repo is its **home** — the initiative record,
124
- brief/PRD, rubric, and state live there. Every sibling repo's PR anchors by declaring
125
- `Initiative: <id>` in the PR body (the gate's body-key path); records land in the home state
126
- layer. There is no cross-repo registry — the body key IS the join. **Sibling repos either do
127
- not install the pmos gate, or mirror the initiative file locally** — a vendored sibling gate
128
- fails closed on a missing local record by design (S5 Review Gate finding).
87
+ - **A human building instead of an agent?** Same loop, plain terms: [AGENTS-HUMAN.md](AGENTS-HUMAN.md). State is written with `scripts/pmos-log.sh` (validated, append-only) — never hand-edited. Mixed human+agent teams write the same state through the same conventions.
88
+ - **An initiative spanning multiple repos?** ONE repo is its **home** — the initiative record, brief/PRD, rubric, and state live there. Every sibling repo's PR anchors by declaring `Initiative: <id>` in the PR body (the gate's body-key path); records land in the home state layer. There is no cross-repo registry — the body key IS the join. **Sibling repos either do not install the pmos gate, or mirror the initiative file locally** — a vendored sibling gate fails closed on a missing local record by design (S5 Review Gate finding).
129
89
 
130
90
  ## Decomposing an initiative into tickets (file mode has no board)
131
91
 
132
- When an initiative is bigger than one run, break it into **tickets** — the plain-file equivalent of
133
- the hosted product's SDLC board. One file per ticket in `pmos/state/tickets/NNN-slug.md`, numbered
134
- in **dependency order**, from [templates/ticket-template.md](templates/ticket-template.md):
135
-
136
- - Frontmatter: `id` (the NNN), `title`, `status` (`todo | doing | done`), and `blocked_by` — an
137
- explicit list of the ticket ids that must be `done` first. Blocking edges are **text**, not a
138
- hidden graph.
139
- - **Sweep the state you derive from.** `node pmos/scripts/workflow-sweep.js` audits the records and
140
- the three ledgers everything else reads. Run it before a review and after any bulk edit. It reports
141
- **defects** (state that contradicts reality — a torn ledger line, a row naming no initiative, a
142
- duplicate `run_id`, a record pointing at a file nobody wrote) separately from **owed** work (a
143
- merged initiative with no run logged, an open Acceptance Gate). `--fix` repairs only the mechanical
144
- defects and writes no `.jsonl` and no judgement — closing your own loops is your job and the
145
- Acceptance Gate is yours alone (D12).
146
- - **Work the frontier.** `pmos/scripts/frontier.sh` lists exactly the tickets that are not done and
147
- whose every blocker is done — the workable set. Start only from there. If it reports the frontier
148
- is *blocked*, there is a cycle or an undone blocker to resolve before more work is startable.
149
- - Each ticket names **one deliverable**, behaviorally (interfaces/types/contracts, never file paths
150
- or line numbers — they stale while the ticket waits). Tickets decompose an initiative; they do not
151
- replace it — the initiative record, PRD, rubric, and the three gates are unchanged.
92
+ When an initiative is bigger than one run, break it into **tickets** — the plain-file equivalent of the hosted product's SDLC board. One file per ticket in `pmos/state/tickets/NNN-slug.md`, numbered in **dependency order**, from [templates/ticket-template.md](templates/ticket-template.md):
93
+
94
+ - Frontmatter: `id` (the NNN), `title`, `status` (`todo | doing | done`), and `blocked_by` — an explicit list of the ticket ids that must be `done` first. Blocking edges are **text**, not a hidden graph.
95
+ - **Sweep the state you derive from.** `node pmos/scripts/workflow-sweep.js` audits the records and the three ledgers everything else reads. Run it before a review and after any bulk edit. It reports **defects** (state that contradicts reality — a torn ledger line, a row naming no initiative, a duplicate `run_id`, a record pointing at a file nobody wrote) separately from **owed** work (a merged initiative with no run logged, an open Acceptance Gate). `--fix` repairs only the mechanical defects and writes no `.jsonl` and no judgement — closing your own loops is your job and the Acceptance Gate is yours alone (D12).
96
+ - **Work the frontier.** `pmos/scripts/frontier.sh` lists exactly the tickets that are not done and whose every blocker is done — the workable set. Start only from there. If it reports the frontier is *blocked*, there is a cycle or an undone blocker to resolve before more work is startable.
97
+ - Each ticket names **one deliverable**, behaviorally (interfaces/types/contracts, never file paths or line numbers — they stale while the ticket waits). Tickets decompose an initiative; they do not replace it — the initiative record, PRD, rubric, and the three gates are unchanged.
152
98
 
153
99
  ## New agent paths — the lethal-trifecta audit
154
100
 
155
- Before creating any new agent path or widening a tool grant, verify it does **not** hold all three
156
- of: **private data access + untrusted-content exposure + external communication**. An agent holding
157
- all three can be tricked by content it reads into exfiltrating what it knows — remove a leg
158
- (preferably external communication) structurally; prompt-level defenses alone are bypassed by
159
- adaptive attacks. If a path genuinely needs all three, its PRD/brief must name the compensating
160
- gate; silence is not a pass. The evaluator is the standing example: it reads adversarial diffs, so
161
- it stays read-only with its verdict append as the only write.
101
+ Before creating any new agent path or widening a tool grant, verify it does **not** hold all three of: **private data access + untrusted-content exposure + external communication**. An agent holding all three can be tricked by content it reads into exfiltrating what it knows — remove a leg (preferably external communication) structurally; prompt-level defenses alone are bypassed by adaptive attacks. If a path genuinely needs all three, its PRD/brief must name the compensating gate; silence is not a pass. The evaluator is the standing example: it reads adversarial diffs, so it stays read-only with its verdict append as the only write.
162
102
 
163
103
  ## Escalation (stop and ask the PM)
164
104
 
165
- An architectural choice with no recorded decision; a wrong or unsatisfiable contract; scope growth;
166
- any destructive/irreversible action; anything that would send product data outside this repo's
167
- remote. Confirm-first regardless of framing.
105
+ An architectural choice with no recorded decision; a wrong or unsatisfiable contract; scope growth; any destructive/irreversible action; anything that would send product data outside this repo's remote. Confirm-first regardless of framing.
168
106
 
169
107
  ## Evaluator leniency warning
170
108
 
171
- The evaluator defaults to generous scoring. Treat the Review Gate as **uncalibrated** in this
172
- deployment until this repo has accumulated its own PM-vs-evaluator record (append overrides to
173
- `pmos/planning/evals/eval-calibration.md`; the D40 classifier method applies locally).
174
- `pmos/scripts/metrics.py` computes the north star (human corrections per accepted run), pass rates,
175
- and the Goodhart tripwire from `pmos/state/`.
176
-
177
- **Model-awareness (any provider).** PMOS is provider/model-agnostic — any capable coding agent can
178
- run this loop — but calibration is a property of the **evaluator model**, not of "the Review Gate"
179
- abstractly. Record the model on every run/eval (the `model` / `evaluator_model` fields above); a
180
- model swap **forks** the calibration record — you do not inherit a κ another model earned, and
181
- `metrics.py` warns when labeled evals pool more than one evaluator model. The high-judgment roles
182
- (the adversarial evaluator, `explain-run`) assume a **frontier-tier reasoner**: a weaker model
183
- degrades *silently* to leniency and grep-clean while its output still looks well-formed — the
184
- per-model κ is what makes that degradation visible.
109
+ The evaluator defaults to generous scoring. Treat the Review Gate as **uncalibrated** in this deployment until this repo has accumulated its own PM-vs-evaluator record (append overrides to `pmos/planning/evals/eval-calibration.md`; the D40 classifier method applies locally). `pmos/scripts/metrics.py` computes the north star (human corrections per accepted run), pass rates, and the Goodhart tripwire from `pmos/state/`.
110
+
111
+ **Model-awareness (any provider).** PMOS is provider/model-agnostic — any capable coding agent can run this loop — but calibration is a property of the **evaluator model**, not of "the Review Gate" abstractly. Record the model on every run/eval (the `model` / `evaluator_model` fields above); a model swap **forks** the calibration record — you do not inherit a κ another model earned, and `metrics.py` warns when labeled evals pool more than one evaluator model. The high-judgment roles (the adversarial evaluator, `explain-run`) assume a **frontier-tier reasoner**: a weaker model degrades *silently* to leniency and grep-clean while its output still looks well-formed — the per-model κ is what makes that degradation visible.
185
112
 
186
113
  ## PM protocol (weekly)
187
114
 
188
- Scan `discoveries.md`; a friction theme seen twice with no resolving decision must be promoted to a
189
- decision. Read run records since last review; log overrides to `eval-calibration.md`. Run
190
- `pmos/scripts/metrics.py` and read the red first. **Scan the lane mix (D56):** count initiatives
191
- per `type` — a chore share climbing review over review, discovery spikes that never close their
192
- disposition, or `no-initiative` PRs that should have been chores are drift signals; a lane misused
193
- twice is promoted to a decision like any recurring friction. **Scan the anchor mix (D59):** feature-vs-health share of the period's initiatives; a budget over
194
- its cap or health-shaped work with a KR anchor is a drift signal; quarterly, review the caps.
195
- **Scan outcome checks (D57):**
196
- metrics.py lists due outcomes (accepted, past horizon, undisposed) — dispose each
197
- (`validated | refuted | inconclusive`, evidence for the first two) or consciously defer; a check
198
- deferred twice, or a horizon moved twice, is promoted like recurring friction. An accepted
199
- initiative with a declared outcome is NOT finished until disposed. **Read the run's explainer where
200
- one exists** — the `explain-run` skill writes an independently-authored literate explainer + quiz to
201
- `pmos/planning/evals/explainers/<run_id>-explainer.md`; start there instead of the raw transcript,
202
- and for a high-risk run pass its quiz as a comprehension self-check before you accept. Recommended
203
- discipline, not an enforced gate — accepting runs you cannot explain is the PM's own watermelon.
115
+ Scan `discoveries.md`; a friction theme seen twice with no resolving decision must be promoted to a decision. Read run records since last review; log overrides to `eval-calibration.md`. Run `pmos/scripts/metrics.py` and read the red first. **Scan the lane mix (D56):** count initiatives per `type` — a chore share climbing review over review, discovery spikes that never close their disposition, or `no-initiative` PRs that should have been chores are drift signals; a lane misused twice is promoted to a decision like any recurring friction. **Scan the anchor mix (D59):** feature-vs-health share of the period's initiatives; a budget over its cap or health-shaped work with a KR anchor is a drift signal; quarterly, review the caps. **Scan outcome checks (D57):** metrics.py lists due outcomes (accepted, past horizon, undisposed) — dispose each (`validated | refuted | inconclusive`, evidence for the first two) or consciously defer; a check deferred twice, or a horizon moved twice, is promoted like recurring friction. An accepted initiative with a declared outcome is NOT finished until disposed. **Read the run's explainer where one exists** — the `explain-run` skill writes an independently-authored literate explainer + quiz to `pmos/planning/evals/explainers/<run_id>-explainer.md`; start there instead of the raw transcript, and for a high-risk run pass its quiz as a comprehension self-check before you accept. Recommended discipline, not an enforced gate — accepting runs you cannot explain is the PM's own watermelon.
204
116
 
205
117
  **Anti-atrophy forcing functions (complacency is structural, not a willpower problem):**
206
- - **Name one hand-verified fact at every Acceptance** — the `record_acceptance` note states something
207
- *you* checked directly (ran it, read the hunk, reproduced it), not the evaluator's verdict restated.
208
- - **Watch the acceptance-friction meta-watermelon** — a run of zero-comment, fast acceptances is
209
- itself a flag; mandated oversight decays into rubber-stamping that legitimizes the system.
210
- - **Guard the four satisficing heuristics**: plan-as-proxy, tests-as-guarantee (CI-green ≠ correct —
211
- the Quality Gate is not the Acceptance Gate), eyeballing-suffices, deference-in-unfamiliar-domains.
212
- - **Adaptive autonomy** — drop an initiative to every-diff review on a failed eval, a reverted merge,
213
- a gate-touching change, or a subsystem you haven't personally touched in ~3 months.
214
- - **Monthly**, do one hands-on rep — personally diagnose one defect or re-derive one diff from its
215
- spec before reading the agent's version (diagnosis-shaped; comprehension decays faster than typing).
216
- **Quarterly**, ask *"what would I no longer know how to fix by hand?"* — supervision cost rises as
217
- runs improve.
118
+
119
+ - **Name one hand-verified fact at every Acceptance** — the `record_acceptance` note states something *you* checked directly (ran it, read the hunk, reproduced it), not the evaluator's verdict restated.
120
+ - **Watch the acceptance-friction meta-watermelon** — a run of zero-comment, fast acceptances is itself a flag; mandated oversight decays into rubber-stamping that legitimizes the system.
121
+ - **Guard the four satisficing heuristics**: plan-as-proxy, tests-as-guarantee (CI-green ≠ correct — the Quality Gate is not the Acceptance Gate), eyeballing-suffices, deference-in-unfamiliar-domains.
122
+ - **Adaptive autonomy** — drop an initiative to every-diff review on a failed eval, a reverted merge, a gate-touching change, or a subsystem you haven't personally touched in ~3 months.
123
+ - **Monthly**, do one hands-on rep — personally diagnose one defect or re-derive one diff from its spec before reading the agent's version (diagnosis-shaped; comprehension decays faster than typing). **Quarterly**, ask *"what would I no longer know how to fix by hand?"* — supervision cost rises as runs improve.
218
124
 
219
125
  ## Kit maintenance
220
126
 
221
- `pmos/KIT-VERSION` records the kernel source. Update the kernel with
222
- `pmos/scripts/kit-update.sh <path-to-pmos-checkout>` — it re-vendors ONLY `skills/`, `okf/core/`,
223
- `scripts/`, `AGENTS.md`, `README.md` and never touches `state/`, `planning/`, or `okf/product/`.
224
- Review the update as a normal PR.
127
+ `pmos/KIT-VERSION` records the kernel source. Update the kernel with `pmos/scripts/kit-update.sh <path-to-pmos-checkout>` — it re-vendors ONLY `skills/`, `okf/core/`, `scripts/`, `AGENTS.md`, `README.md` and never touches `state/`, `planning/`, or `okf/product/`. Review the update as a normal PR.
@@ -1,3 +1,3 @@
1
- kernel-source: pmos@f5b480a
1
+ kernel-source: pmos@9d15bfe
2
2
  assembled: 2026-08-05
3
3
  mode: vendor
@@ -8,10 +8,16 @@ timestamp: 2026-06-28
8
8
 
9
9
  # The Control Plane
10
10
 
11
- **Situating context:** "PMOS is a control plane, not a code generator" is the load-bearing identity
12
- claim of the whole product, and it is easy to forget mid-build. This concept fixes what that claim
13
- means so design decisions stay on the right side of the line. It informs every artifact: if a thing
14
- generates production app code, it is not PMOS's job.
11
+ **Situating context:** "PMOS is a control plane, not a code generator" was the load-bearing identity
12
+ claim of the whole product. **As of D65 (PM-ratified 2026-08-04) the second half of that sentence is
13
+ retired:** PMOS is a control plane *and a factory*. This concept still fixes what "control plane"
14
+ means — the distinction is more useful now, not less, because there are two graphs and you must know
15
+ which one you are in.
16
+
17
+ The line the old phrasing drew is still drawn, in a different place. It is no longer "PMOS does not
18
+ emit app code"; it is **"the executing half may not write the governing half's records, and may not
19
+ push or merge."** If a thing anchors, accepts, disposes or approves a contract, it belongs to the
20
+ control graph. If it opens a pull request against a product, it belongs to the factory.
15
21
 
16
22
  ## Control plane vs. data plane
17
23
 
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /*
4
+ * md-newlines — check, and repair, the newlines in PMOS markdown.
5
+ *
6
+ * THE DEFECT. This repository hard-wraps prose at ~100 columns: 30,345 continuation lines across
7
+ * 1,013 of 1,022 tracked files, a ruler written down nowhere and transmitted by imitation. A newline
8
+ * inside a paragraph is not a line break in markdown, so this looks right in the repo, on GitHub and
9
+ * in the workspace — and stops looking right the moment a file is published. Jira wiki markup treats
10
+ * a newline as a line break, a Google Doc keeps every newline, several markdown→ADF converters emit
11
+ * a hard break per `\n`. The paragraph arrives as a stack of ragged short lines, and nothing on the
12
+ * far side can undo it: an accidental wrap and a deliberate break are the same byte in the source.
13
+ *
14
+ * THE RULES ARE NOT HERE. They are in workspace/lib/md-normalize.js — one implementation shared with
15
+ * the workspace's `normalize` button (the browser loads the same file), so the command, the gate and
16
+ * the button cannot disagree about what a normalized document is. This file is argument handling,
17
+ * file walking and reporting.
18
+ *
19
+ * node scripts/md-newlines.js check every tracked .md
20
+ * node scripts/md-newlines.js <paths…> check just these
21
+ * node scripts/md-newlines.js --fix <paths…> repair them in place
22
+ * node scripts/md-newlines.js --fix --root <dir> repair another repository (an adopter's, a kit)
23
+ * node scripts/md-newlines.js --diff <paths…> show what --fix would change, change nothing
24
+ * node scripts/md-newlines.js --changed <ref> only the .md a diff touches — what CI runs
25
+ *
26
+ * WHY --changed EXISTS, AND WHY THERE IS NO REPO-WIDE GATE. A gate that failed on the existing
27
+ * corpus would fail every PR until a thousand files were rewritten, and would therefore be turned
28
+ * off — so CI ratchets on what the diff touches. New work is normalized from here; old work is
29
+ * repaired when someone touches it, or in one deliberate pass the PM chooses.
30
+ *
31
+ * Exit 0 = nothing to do (or --fix repaired it). Exit 1 = files need normalizing (check/--diff), or
32
+ * a write failed. Exit 2 = the invocation itself was wrong. Stdlib only, no network, no secrets.
33
+ */
34
+ const fs = require('fs');
35
+ const path = require('path');
36
+ const { execFileSync } = require('child_process');
37
+
38
+ /* The normalizer, from the checkout or from a flattened kit payload — the same two-path resolution
39
+ * console-state.js uses for the workflow engine. A kit ships scripts/ with the module beside it. */
40
+ function loadNormalizer() {
41
+ const tried = [];
42
+ for (const rel of ['../workspace/lib/md-normalize.js', './md-normalize.js']) {
43
+ const p = path.join(__dirname, rel);
44
+ tried.push(p);
45
+ if (fs.existsSync(p)) return require(p);
46
+ }
47
+ console.error('md-newlines: could not find md-normalize.js. Looked in:\n ' + tried.join('\n '));
48
+ process.exit(2);
49
+ return null;
50
+ }
51
+
52
+ function parseArgs(argv) {
53
+ const opts = { fix: false, diff: false, root: null, changed: null, paths: [] };
54
+ for (let i = 0; i < argv.length; i++) {
55
+ const a = argv[i];
56
+ if (a === '--fix') opts.fix = true;
57
+ else if (a === '--diff') opts.diff = true;
58
+ else if (a === '--root') { opts.root = argv[++i]; if (!opts.root) return { error: '--root needs a directory' }; }
59
+ else if (a === '--changed') { opts.changed = argv[++i]; if (!opts.changed) return { error: '--changed needs a git ref' }; }
60
+ else if (a === '--help' || a === '-h') return { help: true };
61
+ else if (a.startsWith('-')) return { error: `unknown option '${a}'` };
62
+ else opts.paths.push(a);
63
+ }
64
+ return opts;
65
+ }
66
+
67
+ const USAGE = `md-newlines — check and repair the newlines in markdown documents
68
+
69
+ node scripts/md-newlines.js check every tracked .md
70
+ node scripts/md-newlines.js <paths…> check just these
71
+ node scripts/md-newlines.js --fix <paths…> repair them in place
72
+ node scripts/md-newlines.js --fix --root <dir> repair another repository
73
+ node scripts/md-newlines.js --diff <paths…> show what --fix would change, change nothing
74
+ node scripts/md-newlines.js --changed <ref> only the .md a diff touches (the CI ratchet)
75
+
76
+ A paragraph becomes one line, a list item becomes one line, and a list, table, fence or heading that
77
+ follows prose gets the blank line it needs. Frontmatter, fenced and indented code, table rows,
78
+ headings, deliberate hard breaks (two trailing spaces) and setext underlines are never touched.`;
79
+
80
+ /** Every tracked markdown file under `root`, from git — never a filesystem walk, so nothing untracked or vendored is touched. */
81
+ function trackedMarkdown(root) {
82
+ try {
83
+ return execFileSync('git', ['-C', root, 'ls-files', '*.md', '*.markdown', '*.mdown', '*.mkd'],
84
+ { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 })
85
+ .split('\n').filter(Boolean);
86
+ } catch (e) {
87
+ console.error(`md-newlines: could not list tracked files in ${root} (${(e && e.message) || e}).`);
88
+ console.error(' Pass explicit paths, or run inside a git repository.');
89
+ process.exit(2);
90
+ return [];
91
+ }
92
+ }
93
+
94
+ /** The markdown files a diff touches — added or modified, never deleted. */
95
+ function changedMarkdown(root, ref) {
96
+ let out;
97
+ try {
98
+ out = execFileSync('git', ['-C', root, 'diff', '--name-only', '--diff-filter=AM', `${ref}...HEAD`],
99
+ { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
100
+ } catch {
101
+ // A shallow clone has no merge base. Fall back to a two-dot diff rather than reporting nothing:
102
+ // an empty list here would print a green tick over a check that looked at no files at all.
103
+ try {
104
+ out = execFileSync('git', ['-C', root, 'diff', '--name-only', '--diff-filter=AM', ref],
105
+ { encoding: 'utf8', maxBuffer: 64 * 1024 * 1024 });
106
+ } catch (e) {
107
+ console.error(`md-newlines: could not diff against '${ref}' (${(e && e.message) || e}).`);
108
+ process.exit(2);
109
+ return [];
110
+ }
111
+ }
112
+ return out.split('\n').filter((f) => /\.(md|markdown|mdown|mkd)$/i.test(f));
113
+ }
114
+
115
+ /** A minimal unified-ish diff: the lines that differ, with their numbers. Enough to read, no deps. */
116
+ function showDiff(rel, before, after) {
117
+ const a = before.split('\n');
118
+ const b = after.split('\n');
119
+ const max = Math.max(a.length, b.length);
120
+ const rows = [];
121
+ for (let i = 0, j = 0; i < max || j < max;) {
122
+ if (a[i] === b[j]) { i++; j++; continue; }
123
+ // A join: one output line equals several input lines glued with spaces.
124
+ if (b[j] !== undefined && a[i] !== undefined && b[j].startsWith(a[i])) {
125
+ const from = i;
126
+ let glued = a[i];
127
+ while (glued !== b[j] && i + 1 < a.length && b[j].startsWith(glued)) { i++; glued += ' ' + a[i].trim(); }
128
+ rows.push(` ${from + 1}-${i + 1} joined → ${b[j].slice(0, 96)}${b[j].length > 96 ? '…' : ''}`);
129
+ i++; j++; continue;
130
+ }
131
+ if (b[j] === '') { rows.push(` ${j + 1} blank line inserted above: ${(b[j + 1] || '').slice(0, 72)}`); j++; continue; }
132
+ rows.push(` ${i + 1} - ${String(a[i]).slice(0, 96)}`);
133
+ rows.push(` ${j + 1} + ${String(b[j]).slice(0, 96)}`);
134
+ i++; j++;
135
+ }
136
+ if (rows.length) console.log(`\n${rel}`), console.log(rows.slice(0, 40).join('\n'));
137
+ if (rows.length > 40) console.log(` … ${rows.length - 40} more`);
138
+ }
139
+
140
+ function main() {
141
+ const opts = parseArgs(process.argv.slice(2));
142
+ if (opts.error) { console.error(`md-newlines: ${opts.error}\n\n${USAGE}`); process.exit(2); }
143
+ if (opts.help) { console.log(USAGE); process.exit(0); }
144
+
145
+ const { normalize } = loadNormalizer();
146
+ const root = path.resolve(opts.root || path.join(__dirname, '..'));
147
+ if (!fs.existsSync(root)) { console.error(`md-newlines: no such directory: ${root}`); process.exit(2); }
148
+
149
+ let files;
150
+ if (opts.paths.length) files = opts.paths.map((p) => path.relative(root, path.resolve(p)));
151
+ else if (opts.changed) files = changedMarkdown(root, opts.changed);
152
+ else files = trackedMarkdown(root);
153
+
154
+ const need = [];
155
+ let repaired = 0, joins = 0, blanks = 0, unreadable = 0;
156
+
157
+ for (const rel of files) {
158
+ const full = path.join(root, rel);
159
+ let src;
160
+ try { src = fs.readFileSync(full, 'utf8'); }
161
+ catch (e) {
162
+ // A file named on the command line that cannot be read is a FAILURE, not a silent skip: a
163
+ // repair tool that quietly passes over what it could not open reports success over nothing.
164
+ console.error(`✗ ${rel} — could not read: ${(e && e.message) || e}`);
165
+ unreadable++; continue;
166
+ }
167
+ const r = normalize(src);
168
+ if (!r.changed) continue;
169
+ need.push(rel);
170
+ joins += r.joins; blanks += r.blanks;
171
+ if (opts.diff) showDiff(rel, src, r.text);
172
+ if (opts.fix) {
173
+ try { fs.writeFileSync(full, r.text); repaired++; }
174
+ catch (e) { console.error(`✗ ${rel} — could not write: ${(e && e.message) || e}`); unreadable++; continue; }
175
+ console.log(`✓ ${rel} — ${r.joins} line(s) joined, ${r.blanks} blank line(s) inserted`);
176
+ } else if (!opts.diff) {
177
+ console.log(`✗ ${rel} — ${r.joins} wrapped line(s), ${r.blanks} missing blank line(s)`);
178
+ }
179
+ }
180
+
181
+ const scope = opts.changed ? `changed since ${opts.changed}` : (opts.paths.length ? 'named' : 'tracked');
182
+ if (unreadable) {
183
+ console.error(`\nmd-newlines: ${unreadable} file(s) could not be read or written.`);
184
+ process.exit(1);
185
+ }
186
+ if (!need.length) {
187
+ console.log(`✅ md-newlines: ${files.length} ${scope} markdown file(s) checked — every paragraph is already one line.`);
188
+ process.exit(0);
189
+ }
190
+ if (opts.fix) {
191
+ console.log(`\n✅ md-newlines: repaired ${repaired} of ${files.length} ${scope} file(s) — ${joins} line(s) joined, ${blanks} blank line(s) inserted.`);
192
+ process.exit(0);
193
+ }
194
+ console.log(`\n❌ md-newlines: ${need.length} of ${files.length} ${scope} markdown file(s) carry hard-wrapped prose`
195
+ + ` (${joins} wrapped line(s), ${blanks} missing blank line(s)).`);
196
+ console.log(' A newline inside a paragraph is invisible here and a line break in Jira, a Google Doc or any');
197
+ console.log(' plain-text destination. Repair them with:');
198
+ console.log(` node scripts/md-newlines.js --fix ${need.slice(0, 3).join(' ')}${need.length > 3 ? ' …' : ''}`);
199
+ console.log(' or inspect first with --diff. See planning/prd/markdown-newlines-prd.md.');
200
+ process.exit(1);
201
+ }
202
+
203
+ if (require.main === module) main();
204
+ module.exports = { parseArgs, trackedMarkdown, changedMarkdown };