create-pmos 0.33.0 → 0.36.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -0
- package/bin/create-pmos.js +15 -3
- package/package.json +1 -1
- package/payload/KIT-VERSION +2 -2
- package/payload/okf/core/concepts/agent-harness.md +2 -2
- package/payload/okf/core/concepts/design-loop.md +5 -5
- package/payload/okf/core/concepts/eval-driven-pm.md +1 -1
- package/payload/okf/core/concepts/index.md +2 -2
- package/payload/okf/core/concepts/okr-anti-optimism.md +1 -1
- package/payload/okf/core/concepts/okr-tree.md +2 -2
- package/payload/okf/core/concepts/output-eval.md +2 -2
- package/payload/okf/core/concepts/sdlc-loop.md +1 -1
- package/payload/okf/core/concepts/self-refining-harness.md +3 -3
- package/payload/okf/core/concepts/simplified-technical-english.md +74 -0
- package/payload/okf/core/templates/content-brief-template.md +1 -1
- package/payload/okf/core/templates/design-brief-template.md +1 -1
- package/payload/okf/core/templates/eval-rubric-template.md +1 -1
- package/payload/okf/core/templates/log.md +1 -1
- package/payload/okf/core/templates/prd-template.md +1 -1
- package/payload/okf/core/templates/spike-brief-template.md +1 -1
- package/payload/scripts/frontier.sh +6 -2
- package/payload/scripts/gate-initiative.sh +24 -3
- package/payload/scripts/metrics.py +20 -4
- package/payload/scripts/read-jsonl.js +53 -0
- package/payload/scripts/workflow-state.js +144 -35
- package/payload/scripts/workflow-sweep.js +39 -22
- package/payload/skills/SKILL-FORMAT.md +1 -1
- package/payload/skills/copywriting.skill +1 -1
- package/payload/skills/correction-signal-derive.skill +1 -1
- package/payload/skills/design-brief.skill +4 -4
- package/payload/skills/design-reconcile.skill +1 -1
- package/payload/skills/discovery.skill +8 -6
- package/payload/skills/eval-rubric.skill +4 -4
- package/payload/skills/evaluator.skill +1 -1
- package/payload/skills/explain-run.skill +1 -1
- package/payload/skills/explain-surface.skill +6 -6
- package/payload/skills/harness-refine.skill +1 -1
- package/payload/skills/ingest-repo.skill +6 -6
- package/payload/skills/okr.skill +2 -2
- package/payload/skills/pmos-router.skill +8 -8
- package/payload/skills/prd.skill +5 -5
- package/payload/skills/prototype-grill.skill +8 -8
- package/payload/skills/prototype.skill +7 -7
- package/payload/skills/skill-creator.skill +1 -1
- package/payload/skills/source-to-concept.skill +1 -1
- package/payload/skills/ste-term-map.md +69 -0
- package/payload/skills/strategic-brief.skill +4 -4
- package/payload/skills/technical-writing-ste.skill +176 -0
- package/payload/skills-native/copywriting/SKILL.md +1 -1
- package/payload/skills-native/correction-signal-derive/SKILL.md +1 -1
- package/payload/skills-native/design-brief/SKILL.md +4 -4
- package/payload/skills-native/design-reconcile/SKILL.md +1 -1
- package/payload/skills-native/discovery/SKILL.md +8 -6
- package/payload/skills-native/eval-rubric/SKILL.md +4 -4
- package/payload/skills-native/evaluator/SKILL.md +1 -1
- package/payload/skills-native/explain-run/SKILL.md +1 -1
- package/payload/skills-native/explain-surface/SKILL.md +6 -6
- package/payload/skills-native/harness-refine/SKILL.md +1 -1
- package/payload/skills-native/ingest-repo/SKILL.md +6 -6
- package/payload/skills-native/okr/SKILL.md +2 -2
- package/payload/skills-native/pmos-router/SKILL.md +8 -8
- package/payload/skills-native/prd/SKILL.md +5 -5
- package/payload/skills-native/prototype/SKILL.md +7 -7
- package/payload/skills-native/prototype-grill/SKILL.md +8 -8
- package/payload/skills-native/skill-creator/SKILL.md +1 -1
- package/payload/skills-native/source-to-concept/SKILL.md +1 -1
- package/payload/skills-native/strategic-brief/SKILL.md +4 -4
- package/payload/skills-native/technical-writing-ste/SKILL.md +178 -0
- package/workspace/bin/pmos.js +9 -3
- package/workspace/lib/engine/read-jsonl.js +53 -0
- package/workspace/lib/engine/workflow-state.js +144 -35
- package/workspace/lib/file-mode-parse.js +19 -4
- package/workspace/lib/tree-share.js +84 -2
- package/workspace/package-lock.json +2 -2
- package/workspace/package.json +1 -1
- package/workspace/public/pm/board-read.js +38 -0
- package/workspace/public/pm/data.js +102 -1
- package/workspace/public/pm/modes.js +50 -28
- package/workspace/public/pm/panel-detail.js +2 -17
- package/workspace/public/pm/panel-initiatives.js +3 -20
- package/workspace/public/pm/panel-newsess.js +14 -1
- package/workspace/public/pm/panel-tabs.js +53 -14
- package/workspace/public/pm/tab-term.js +30 -2
- package/workspace/server.js +95 -70
- package/workspace/test-run.js +51 -9
package/README.md
CHANGED
|
@@ -232,6 +232,14 @@ npx create-pmos@latest --update
|
|
|
232
232
|
|
|
233
233
|
Refreshes the skills, concepts, and scripts only. Your records — `state/`, `planning/`, `okf/product/` — are yours, and an update never touches them. `pmos/KIT-VERSION` always says exactly what you're running.
|
|
234
234
|
|
|
235
|
+
## What's new in 0.34.0
|
|
236
|
+
|
|
237
|
+
**A feature this README advertised for fifteen releases and never shipped.** Run `npx create-pmos@latest workspace`; the version bump means an existing install auto-upgrades.
|
|
238
|
+
|
|
239
|
+
- **The session chooser now shows the agent's last words.** It has been listed under "What's new in 0.19.0" since that release and never rendered once. The chooser read `doing` off each session row; the rows carry `lastWords`, and the server mapped between the two only on the path the *pane* uses, not the path the *chooser* uses. So the line was always blank. If you are on 0.19.0 through 0.33.0, that one line was missing from the chooser and nothing else on that list was affected.
|
|
240
|
+
- **How it was found, and why that matters more than the fix.** An independent evaluation of the original run — commissioned because that run had been merged and published without ever passing a Review Gate — drove a real browser against the shipped code and measured the field. The test that was supposed to cover this passed the whole time: it handed the function a hand-written row with a literal `doing` key that no real row has, so it tested a passthrough rather than the feature. The new test uses the wire shape and asserts the key is absent, so a rename on the server cannot quietly re-break it while the suite stays green.
|
|
241
|
+
- **Known broken, and not fixed here: `end it` in the chooser.** In a freshly opened window with no terminal tab yet, the chooser is populated over HTTP while the terminal socket is still closed, so clicking `end it` **silently does nothing** — the button disarms as though it worked and the session keeps running. It has no test coverage anywhere. It is named here rather than left for you to discover. `open it` and `+ start another session here anyway` are unaffected.
|
|
242
|
+
|
|
235
243
|
## What's new in 0.33.0
|
|
236
244
|
|
|
237
245
|
**Three shipped documents stop telling you to do things that were never possible in your repo.** Run `npx create-pmos@latest --update`. No behaviour changes — this is a correctness release for the instructions the kit hands your agent.
|
|
@@ -363,8 +371,10 @@ Refreshes the skills, concepts, and scripts only. Your records — `state/`, `pl
|
|
|
363
371
|
- **Opening a long document no longer scrolls the whole window.** Scrolling a document in the right panel moved the entire interface — the top bar slid away, the status bar rose from below — while the panel's own scrollbar never engaged. Every panel now scrolls inside itself and the app frame never moves. (If you are curious: the shell's middle row was written `1fr`, which is `minmax(auto, 1fr)`, so a tall document enlarged the row rather than overflowing it. Every style involved read as correct, which is why it survived review.)
|
|
364
372
|
- **`+ session` now asks before it starts one, when the checkout is already busy.** It used to start the session and *then* tell you how many others were in there — a count arriving after the decision, naming something you cannot act on. Now you get a chooser first, listing every session in that checkout.
|
|
365
373
|
- **It says what each one is.** Its name, its agent, the same word the sidebar uses (`working` · `quiet 12m` · `needs you` · `maybe blocked`), the agent's own last words, and who holds the writer claim.
|
|
374
|
+
- **Correction (0.34.0):** the last-words line was advertised here from 0.19.0 and **never rendered** — the chooser read a field name the session rows do not carry, so it was always blank. Fixed in 0.34.0; if you are on any version from 0.19.0 to 0.33.0, that one line was missing and nothing else on this list was affected.
|
|
366
375
|
- **…and what ending it would cost, without over-claiming.** Only `needs you` reads as settled — its turn finished and it is waiting on you. `working` says output is arriving right now. `quiet` and `maybe blocked` say, in as many words, that the workspace *cannot tell* a finished turn from a permission prompt from a stuck tool. A confident "safe to end" that discarded a running turn would be worse than no reading at all.
|
|
367
376
|
- **You can take the running session instead of making another.** `open it` attaches a tab to it and starts nothing. `end it` is there too — the first click arms it and names what it will end, the second does it. `+ start another session here anyway` is always one click: being told is never being blocked, and no session is ever refused because another is there.
|
|
377
|
+
- **Correction (0.34.0):** `end it` does not work reliably and is being repaired. In a freshly opened window with no terminal tab yet, the chooser is populated over HTTP while the terminal socket is still closed, so the click **silently does nothing** — the button disarms as though it had worked and the session keeps running. If you use it, confirm in the sidebar that the session actually ended. `open it` and `start another anyway` are unaffected.
|
|
368
378
|
- **The same detail is written into the new session's scrollback**, so scrolling back tomorrow answers the same question the chooser did.
|
|
369
379
|
|
|
370
380
|
## What's new in 0.18.1
|
package/bin/create-pmos.js
CHANGED
|
@@ -129,15 +129,27 @@ function workspaceOrDie() {
|
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
|
|
132
|
+
// A spawn that never RAN is a failure, not a success: spawnSync's .status is null when the child
|
|
133
|
+
// could not be spawned (r.error set — e.g. a broken global `pmos` shim after a partial uninstall;
|
|
134
|
+
// onPath only proves resolvability, not runnability) or died on a signal. The old `.status || 0`
|
|
135
|
+
// coerced both to exit 0, so a launch that launched nothing reported success to whatever invoked it.
|
|
136
|
+
function launchStatus(r) {
|
|
137
|
+
if (r.error) {
|
|
138
|
+
console.error(`create-pmos: ERROR — launch failed: ${r.error.message}`);
|
|
139
|
+
return 1;
|
|
140
|
+
}
|
|
141
|
+
return r.status === null ? 1 : r.status;
|
|
142
|
+
}
|
|
143
|
+
|
|
132
144
|
function launchWorkspace(targetDir, viaGlobal) {
|
|
133
145
|
if (viaGlobal && onPath('pmos')) {
|
|
134
146
|
// `pmos` owns the whole lifecycle: start the server detached, open the browser on this repo.
|
|
135
|
-
return spawnSync('pmos', [targetDir], { stdio: 'inherit', shell: process.platform === 'win32' })
|
|
147
|
+
return launchStatus(spawnSync('pmos', [targetDir], { stdio: 'inherit', shell: process.platform === 'win32' }));
|
|
136
148
|
}
|
|
137
149
|
// Fallback: run the bundled launcher directly. Its ensureInstalled() pulls runtime deps on first run.
|
|
138
150
|
console.log('Launching from the bundled copy (the `pmos` command is not on PATH).');
|
|
139
|
-
return spawnSync(process.execPath, [path.join(WORKSPACE, 'bin', 'pmos.js'), targetDir],
|
|
140
|
-
{ stdio: 'inherit', env: process.env })
|
|
151
|
+
return launchStatus(spawnSync(process.execPath, [path.join(WORKSPACE, 'bin', 'pmos.js'), targetDir],
|
|
152
|
+
{ stdio: 'inherit', env: process.env }));
|
|
141
153
|
}
|
|
142
154
|
|
|
143
155
|
// Compare two dotted numeric versions. Returns -1 if a<b, 0 if equal, 1 if a>b. Missing/non-numeric
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "create-pmos",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.36.0",
|
|
4
4
|
"description": "Scaffold the PMOS portable kit — run the PMOS product-management workflow (initiative → lane-fit spec → contract → build → 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/KIT-VERSION
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
kernel-source: pmos@
|
|
2
|
-
assembled: 2026-08-
|
|
1
|
+
kernel-source: pmos@f04854e
|
|
2
|
+
assembled: 2026-08-08
|
|
3
3
|
mode: vendor
|
|
@@ -24,12 +24,12 @@ The harness includes:
|
|
|
24
24
|
- **Knowledge** — the [OKF bundle](/okf/core/concepts/okf-governance.md): concepts, templates,
|
|
25
25
|
playbooks.
|
|
26
26
|
- **Skills** — procedural competence loaded on demand
|
|
27
|
-
([SKILL-FORMAT](/skills/
|
|
27
|
+
([SKILL-FORMAT](/skills/SKILL-FORMAT.md)).
|
|
28
28
|
- **Tools / MCP** — the [ACI surface](/build-skills/MCP-SPEC.md) the agent acts through.
|
|
29
29
|
- **Sandboxes** — where work runs safely.
|
|
30
30
|
- **Orchestration** — sequencing, concurrency, routing.
|
|
31
31
|
- **Guardrails** — the [three-tier eval gate](/okf/core/concepts/output-eval.md), kill conditions.
|
|
32
|
-
- **Observability** — `agent_runs`, [discoveries.md](/discoveries.md), transcript review.
|
|
32
|
+
- **Observability** — `agent_runs`, [discoveries.md](/state/discoveries.md), transcript review.
|
|
33
33
|
|
|
34
34
|
**PMOS builds the harness, not the app.** Improving any harness element improves every future run —
|
|
35
35
|
that is the leverage the [control plane](/okf/core/concepts/control-plane.md) exists to capture.
|
|
@@ -29,12 +29,12 @@ An initiative with a user-facing surface runs four legs *inside* Build:
|
|
|
29
29
|
build, web-ci) library back)
|
|
30
30
|
```
|
|
31
31
|
|
|
32
|
-
1. **Brief** — the [design-brief skill](/skills/
|
|
32
|
+
1. **Brief** — the [design-brief skill](/skills/design-brief.skill) writes the context handoff: what
|
|
33
33
|
to design and the hard, presentation-only constraints, each surface tied to a real data source. It is to
|
|
34
|
-
design what the [PRD](/skills/
|
|
34
|
+
design what the [PRD](/skills/prd.skill) is to build.
|
|
35
35
|
2. **Design** — the design is made in an external design surface (PMOS-self uses claude.ai/design;
|
|
36
36
|
any capable design surface fits — external to this repo).
|
|
37
|
-
3. **Reconcile + build** — the [design-reconcile skill](/skills/
|
|
37
|
+
3. **Reconcile + build** — the [design-reconcile skill](/skills/design-reconcile.skill) classifies
|
|
38
38
|
every surface against **the product's capability ledger** and the PM signs off before any code; then the
|
|
39
39
|
design is built into the product's app on its design system, verified by the product's CI. (For PMOS's own
|
|
40
40
|
app the ledger is [web-capability-ledger.md](/planning/web-capability-ledger.md), the system is D38, and
|
|
@@ -98,8 +98,8 @@ time such that the board hides real "designed-but-not-built" state, the lighter
|
|
|
98
98
|
|
|
99
99
|
The loop is a **method**; PMOS's own app is just its first instance. What is **shared** vs **per-product** (D50):
|
|
100
100
|
|
|
101
|
-
- **Shared, authored once, read by every PM** — this concept, the [design-brief](/skills/
|
|
102
|
-
and [design-reconcile](/skills/
|
|
101
|
+
- **Shared, authored once, read by every PM** — this concept, the [design-brief](/skills/design-brief.skill)
|
|
102
|
+
and [design-reconcile](/skills/design-reconcile.skill) skills, the
|
|
103
103
|
[design-brief template](/okf/core/templates/design-brief-template.md), and the **generic dimensions** of the
|
|
104
104
|
[design-fidelity rubric](/planning/evals/design-fidelity-eval-rubric.md). These live in PMOS-self's knowledge
|
|
105
105
|
and are already reachable by tenants (repo-managed OKF rows are shared-read, D37; the `skills` table is
|
|
@@ -52,7 +52,7 @@ this the right thing?* — and on whether the rubric itself was right.
|
|
|
52
52
|
## How it shapes the loop
|
|
53
53
|
|
|
54
54
|
- **Spec stage** authors the rubric alongside the PRD — the
|
|
55
|
-
[eval-rubric skill](/skills/
|
|
55
|
+
[eval-rubric skill](/skills/eval-rubric.skill) and [prd skill](/skills/prd.skill) are
|
|
56
56
|
paired for this reason.
|
|
57
57
|
- **Contract stage** turns the rubric into agreed, testable done-criteria the generator and evaluator
|
|
58
58
|
both sign off on before work starts.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# core/concepts — Index
|
|
2
2
|
|
|
3
|
-
OKF reserved file (no frontmatter). Directory listing for progressive disclosure — read this
|
|
4
|
-
to pick concepts, then fetch the file(s) you need.
|
|
3
|
+
OKF reserved file (no frontmatter). Directory listing for progressive disclosure — read this to pick concepts, then fetch the file(s) you need.
|
|
5
4
|
|
|
6
5
|
- [agent-harness](agent-harness.md) — Everything wrapping the model that lets it finish work — and the Summary Gate, the input gate that compresses prior-session context into essential state.
|
|
7
6
|
- [control-plane](control-plane.md) — What it means for PMOS to be a control plane — it orchestrates, specs, evaluates, and routes agent work rather than generating production code itself.
|
|
@@ -18,4 +17,5 @@ to pick concepts, then fetch the file(s) you need.
|
|
|
18
17
|
- [product-slug-convention](product-slug-convention.md) — The rules for product_slug — the stable lowercase identifier that scopes products across OKF namespaces, planning artifacts, and agent records.
|
|
19
18
|
- [sdlc-loop](sdlc-loop.md) — The closed agent-driven software development lifecycle PMOS orchestrates — spec, contract, build, evaluate, accept, learn — and how it loops.
|
|
20
19
|
- [self-refining-harness](self-refining-harness.md) — The pattern where the harness improves from its own run evidence (evals, friction, calibration) via a detect → draft → PM-gate loop, bounded at the L3 ceiling — never L4 self-mutation.
|
|
20
|
+
- [simplified-technical-english](simplified-technical-english.md) — ASD-STE100: the controlled-language standard (53 rules / 9 sections + a ~900-word one-meaning dictionary), what it deliberately does not cover, and why its register boundary matters to any house adoption.
|
|
21
21
|
- [watermelon-flag](watermelon-flag.md) — A status that reads green on automated metrics but is red underneath on strategic alignment — and silent drift, the L4 failure mode it signals.
|
|
@@ -36,7 +36,7 @@ report the more pessimistic reading and make the uncertainty explicit.
|
|
|
36
36
|
4. **Name the gap to intent.** Where the work has bent away from the parent objective, say so
|
|
37
37
|
explicitly, even if every individual run passed.
|
|
38
38
|
5. **Surface friction.** Anything that fought the harness goes to
|
|
39
|
-
[discoveries.md](/discoveries.md) tagged `#friction`, not buried in a success summary.
|
|
39
|
+
[discoveries.md](/state/discoveries.md) tagged `#friction`, not buried in a success summary.
|
|
40
40
|
|
|
41
41
|
## Why this is structural, not cultural
|
|
42
42
|
|
|
@@ -29,8 +29,8 @@ Objective (qualitative, directional — the "what we want to be true")
|
|
|
29
29
|
- **Key Result** — a quantitative, measurable outcome that proves the objective is being met.
|
|
30
30
|
Each objective has 2–5 KRs. *"Human correction rate per agent run < 0.5 by end of Q3."*
|
|
31
31
|
- **Initiative** — a concrete body of work expected to move one or more KRs. Each initiative owns a
|
|
32
|
-
PRD ([prd skill](/skills/
|
|
33
|
-
([eval-rubric skill](/skills/
|
|
32
|
+
PRD ([prd skill](/skills/prd.skill)) and an eval rubric
|
|
33
|
+
([eval-rubric skill](/skills/eval-rubric.skill)).
|
|
34
34
|
- **Agent run** — the leaf: one execution against an initiative's pre-run contract, logged to
|
|
35
35
|
`agent_runs`.
|
|
36
36
|
|
|
@@ -10,7 +10,7 @@ timestamp: 2026-06-28
|
|
|
10
10
|
|
|
11
11
|
**Situating context:** Every turn of the [SDLC loop](/okf/core/concepts/sdlc-loop.md) ends by
|
|
12
12
|
judging what an agent produced. This concept defines the three-tier gate that judgment runs through,
|
|
13
|
-
so the [eval-rubric skill](/skills/
|
|
13
|
+
so the [eval-rubric skill](/skills/eval-rubric.skill), AGENTS.md, and the PM all use the same
|
|
14
14
|
vocabulary. It informs where automation stops and human judgment begins.
|
|
15
15
|
|
|
16
16
|
## The three tiers
|
|
@@ -72,7 +72,7 @@ green Review Gate auto-accept — removes the only structural defense against dr
|
|
|
72
72
|
call sequences.
|
|
73
73
|
- **Weighted, with partial credit:** the Review Gate scores weighted dimensions and totals against a
|
|
74
74
|
threshold; it is not all-or-nothing. (See the
|
|
75
|
-
[eval-rubric skill](/skills/
|
|
75
|
+
[eval-rubric skill](/skills/eval-rubric.skill) and
|
|
76
76
|
[template](/okf/core/templates/eval-rubric-template.md).)
|
|
77
77
|
|
|
78
78
|
## Relationship to autonomy
|
|
@@ -26,7 +26,7 @@ PMOS drives a closed lifecycle. Each turn of the loop moves one initiative throu
|
|
|
26
26
|
Gate)
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
-
1. **Spec** — the initiative gets a PRD via the [prd skill](/skills/
|
|
29
|
+
1. **Spec** — the initiative gets a PRD via the [prd skill](/skills/prd.skill). The PRD says
|
|
30
30
|
what to build and why, anchored to a KR in the [OKR tree](/okf/core/concepts/okr-tree.md) —
|
|
31
31
|
or, for maintenance work, a declared [health budget](/okf/core/concepts/health-anchors.md) (D59).
|
|
32
32
|
2. **Contract** — generator and evaluator agree a **pre-run contract**: testable done-criteria,
|
|
@@ -14,7 +14,7 @@ transcripts, `eval-calibration.md`). What it lacked was a named pattern — and
|
|
|
14
14
|
that evidence back into harness improvements. This concept names it. It was extracted from the
|
|
15
15
|
2026-07-01 "Agent = Model + Harness" research (which found the field converging on *harness engineering*
|
|
16
16
|
as its own discipline, and "agents improving their own tools" as its highest-leverage loop) and it
|
|
17
|
-
informs the [harness-refine skill](/
|
|
17
|
+
informs the [harness-refine skill](/skills/harness-refine.skill), [D47](/okf/products/pmos/adr/d47-self-refining-harness.md),
|
|
18
18
|
and the [D23](/okf/products/pmos/adr/d23-quarterly-cadence.md) quarterly harness review.
|
|
19
19
|
|
|
20
20
|
## The pattern
|
|
@@ -36,7 +36,7 @@ one level up — applied to the harness itself:
|
|
|
36
36
|
1. **Detect** — a deterministic signal shows a gap: a `v_friction_recurrence` theme with no resolving
|
|
37
37
|
decision, a recurring `evals` defect class, a persistent `eval-calibration.md` disagreement, a
|
|
38
38
|
context-budget signal.
|
|
39
|
-
2. **Draft** — the [harness-refine skill](/
|
|
39
|
+
2. **Draft** — the [harness-refine skill](/skills/harness-refine.skill) reads that evidence and
|
|
40
40
|
drafts concrete, **evidence-cited** edits, each mapped to one artifact: an MCP tool description
|
|
41
41
|
([ACI](/build-skills/MCP-SPEC.md), D19), a skill body, an OKF concept, or an `AGENTS.md` heuristic.
|
|
42
42
|
It **prunes** as readily as it adds (D23 / [prune the scaffolding](/PRINCIPLES.md)).
|
|
@@ -73,5 +73,5 @@ sources.
|
|
|
73
73
|
- [control-plane](/okf/core/concepts/control-plane.md) — why PMOS edits the harness, not the app.
|
|
74
74
|
- [output-eval](/okf/core/concepts/output-eval.md) — the Acceptance Gate this loop submits to.
|
|
75
75
|
- [watermelon-flag](/okf/core/concepts/watermelon-flag.md) — the L4 failure mode the L3 bounds prevent.
|
|
76
|
-
- Skill: [harness-refine](/
|
|
76
|
+
- Skill: [harness-refine](/skills/harness-refine.skill). Decision:
|
|
77
77
|
[D47](/okf/products/pmos/adr/d47-self-refining-harness.md).
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Concept
|
|
3
|
+
title: "ASD-STE100 Simplified Technical English"
|
|
4
|
+
description: The controlled-language standard for technical documentation — 53 writing rules in 9 sections plus a ~900-word controlled dictionary, owned by ASD. What it is, what it constrains, what it deliberately does not cover, and why its scope boundary matters to anyone adopting it as a house standard.
|
|
5
|
+
tags: [writing, controlled-language, technical-writing, standards, ste, documentation]
|
|
6
|
+
timestamp: 2026-07-30
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# ASD-STE100 Simplified Technical English
|
|
10
|
+
|
|
11
|
+
**Situating context:** Extracted from the free official Issue 9 specification (2025-01-15) and the STEMG's own FAQ during the [ste-house-standard-spike](/planning/prd/ste-house-standard-spike.md) discovery spike, which asked whether STE should become the standard for PMOS output. This concept holds the durable half — what the standard *is*. The PMOS-specific adoption question, its scope boundary, and the enforcement design live in the spike brief, not here.
|
|
12
|
+
|
|
13
|
+
## What it is
|
|
14
|
+
|
|
15
|
+
**ASD-STE100 Simplified Technical English (STE)** is a *controlled natural language*: a restricted subset of English defined by an explicit rule set and an explicit approved vocabulary. It exists to make technical documentation unambiguous on first read — originally for aircraft maintenance manuals read by mechanics whose first language is not English.
|
|
16
|
+
|
|
17
|
+
It has exactly two parts:
|
|
18
|
+
|
|
19
|
+
| Part | Content | Size (Issue 9) |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| **Part 1 — Writing rules** | 53 numbered rules in 9 sections, plus 8 general recommendations (GR-1…GR-8) | 53 rules / 9 sections |
|
|
22
|
+
| **Part 2 — Dictionary** | Approved words, each with **one meaning and one part of speech**, plus unapproved words with their approved replacements | ≈900 approved words |
|
|
23
|
+
|
|
24
|
+
The dictionary is the expensive half and the reason STE is a *controlled* language rather than a style guide: `test` is approved as a noun only, `oil` as a noun only, and a writer who wants the verb must use the approved one. Words outside the dictionary are legal only as **technical nouns** or **technical verbs** — domain terms admitted under rules 1.5–1.13, which is how STE stays usable in a field it has never seen.
|
|
25
|
+
|
|
26
|
+
## The nine sections
|
|
27
|
+
|
|
28
|
+
Rule statements below are **paraphrased**, with rule numbers as pointers into the specification. The document is ASD copyright and is not reproduced here (see *Licensing* below).
|
|
29
|
+
|
|
30
|
+
1. **Words (1.1–1.14).** Use only approved words, each as its listed part of speech and listed meaning, in its approved verb/adjective forms (1.1–1.4). Domain terms enter as technical nouns (1.5–1.11) or technical verbs (1.12–1.13): keep them short, never use a technical noun as a verb or a technical verb as a noun, never two names for one thing, no slang/regional/jargon terms. American English spelling unless a directive says otherwise (1.14).
|
|
31
|
+
2. **Multi-word nouns (2.1–2.2).** At most three words in a noun cluster; a longer term is written in full once and then shortened or hyphenated.
|
|
32
|
+
3. **Verbs (3.1–3.7).** Only the dictionary's verb forms (3.1), and only infinitive, imperative, simple present, simple past, simple future, and past participle-as-adjective (3.2–3.3). No auxiliary stacks / complex constructions (3.4). `-ing` only inside a technical noun (3.5). **Active voice** — passive only in descriptive text when the agent is genuinely unknown (3.6). Describe an action with an approved verb, not a nominalization (3.7).
|
|
33
|
+
4. **Sentences (4.1–4.5).** Short and clear (4.1). Never drop words or use contractions to shorten (4.2). Vertical lists for complex material (4.3). Explicit connecting words between related sentences (4.4). Keep articles and demonstratives in front of nouns (4.5).
|
|
34
|
+
5. **Procedural writing (5.1–5.5).** **Maximum 20 words per sentence** (5.1). One instruction per sentence unless the actions are simultaneous (5.2). Imperative form (5.3). A precondition is stated first, then comma, then the command (5.4). Notes inform; they never instruct (5.5).
|
|
35
|
+
6. **Descriptive writing (6.1–6.6).** Information gradually (6.1); key words/phrases carry the structure (6.2); **maximum 25 words per sentence** (6.3); paragraphs group related information (6.4), **one topic each** (6.5), **at most six sentences each** (6.6). The imperative is not permitted here — description is not instruction.
|
|
36
|
+
7. **Safety instructions (7.1–7.3).** Name the risk level with the applicable word ("warning", "caution") (7.1); open with a clear command or condition (7.2); explain the risk or the consequence (7.3).
|
|
37
|
+
8. **Punctuation and word count (8.1–8.7).** All standard punctuation **except the semicolon** (8.1); hyphens join directly-related words (8.2); parentheses for references, identifiers, work steps, abbreviations (8.3). Then the counting rules that make the length limits decidable: in a vertical list a colon ends a sentence (8.4); a parenthetical counts as one word (8.5); numbers, numbers-with-units, abbreviations, alphanumeric identifiers and proper nouns count as one word each (8.6); a hyphenated word counts as one (8.7).
|
|
38
|
+
9. **Writing practices (9.1–9.4) + GR-1…GR-8.** Restructure the sentence when word-for-word replacement fails (9.1); use approved words correctly (9.2); no invented phrasal verbs (9.3); consistent terminology and style (9.4). The general recommendations cover the conjunction "that", the preposition "with", pronoun use and antecedents (GR-3/GR-4), false friends, Latin abbreviations, and — new in Issue 9 — **inclusive language** (GR-7) and possessive form (GR-8).
|
|
39
|
+
|
|
40
|
+
## What STE deliberately does not do
|
|
41
|
+
|
|
42
|
+
The specification's own FAQ is unusually explicit about its boundaries, and this is the part most adopters miss:
|
|
43
|
+
|
|
44
|
+
- **It does not regulate formatting** — typeface, numbering, lettering belong to a publications specification or style guide.
|
|
45
|
+
- **It does not regulate abbreviations or units of measurement** — projects decide.
|
|
46
|
+
- **It cannot be used alone.** In the FAQ's words: *"No. It is intended to be used with other applicable specifications for technical publications, style guides, and official directives. A high standard of professionalism is necessary to use STE correctly."*
|
|
47
|
+
- **It is not general-purpose English.** STE targets maintenance and technical documentation. The STEMG's position is that STE's *principles* — short sentences, one topic per sentence, active voice — transfer to other writing; the full rule set and dictionary do not.
|
|
48
|
+
- **No tool can certify compliance.** Checkers flag violations; they cannot convert non-STE text into STE, and the writer remains accountable.
|
|
49
|
+
|
|
50
|
+
## The one-meaning trade
|
|
51
|
+
|
|
52
|
+
STE's power and its cost are the same mechanism. Because every approved word has one meaning, ambiguity collapses — and so does nuance. The dictionary's recurring-errors list replaces `should` and `may` with `MUST`, and `shall` with `MUST`; `CAN`, `CANNOT`, `WILL` and `MUST` are approved, while `may`, `might`, `should` and `would` are not. That is correct for a maintenance manual, where a hedged instruction is a defect. It is destructive for text whose payload *is* graded confidence — a recommendation, an estimate, a risk statement — because STE's replacement converts a recommendation into an obligation and a possibility into a certainty. Adopting STE for such text does not make it clearer; it makes it more certain than the author was.
|
|
53
|
+
|
|
54
|
+
This is the boundary any adoption must draw explicitly. It is also why STE splits **procedural** (section 5) from **descriptive** (section 6) writing and gives them different rules: it already recognizes that one register's rule is another register's error. Argumentative text — a decision record, a strategy memo, an evaluation verdict — is a third register the standard never addressed.
|
|
55
|
+
|
|
56
|
+
## History, status, licensing
|
|
57
|
+
|
|
58
|
+
- Originated 1986 as the *AECMA Simplified English Guide*, requested by the European airline industry for aircraft maintenance documentation; the name honors the ATA100 publications specification.
|
|
59
|
+
- Maintained by the **Simplified Technical English Maintenance Group (STEMG)**, a working group of **ASD** (Aerospace, Security and Defence Industries Association of Europe), formed 1983.
|
|
60
|
+
- **Issue 9, 2025-01-15** is current. Free of charge to anyone on request since Issue 6 (2013).
|
|
61
|
+
- **Copyright is fully owned by ASD.** The document states that no reproduction or publication in whole or in part may be made without ASD's written authority; irrevocable free reproduction rights are granted only to named categories (ASD/AIA/AIAC/ICCAIA members and their customers, member-state Ministries of Defense, and similar). **Practical consequence:** an adopter outside those categories may apply and cite STE freely, but must not vendor the rule text or the dictionary into its own repository — reference it by rule number and link to the source.
|
|
62
|
+
- Adoption has spread well beyond aerospace (machinery, automotive, electronics, medical, IT, and language services). Most non-aerospace organizations adopt the writing rules and skip the controlled dictionary, which is a partial adoption, not compliance.
|
|
63
|
+
|
|
64
|
+
## Known limitations
|
|
65
|
+
|
|
66
|
+
- **Correct application is hard.** It demands strong English *and* domain knowledge; research on STE adoption consistently finds writers apply it incorrectly without training.
|
|
67
|
+
- **It is often misused as a substitute for a style guide** rather than a layer on top of one — the failure mode the FAQ answer above exists to prevent.
|
|
68
|
+
- **A clean check is not a correct document.** STE-compliant prose that is factually wrong reads *more* authoritative than the sloppy version — a controlled language raises the confidence a reader places in the text without touching its truth.
|
|
69
|
+
|
|
70
|
+
## Related
|
|
71
|
+
|
|
72
|
+
- [output-eval](/okf/core/concepts/output-eval.md) — the three-tier gate any writing standard is graded through; a clean lint is a Quality-tier signal, never an Acceptance-tier one.
|
|
73
|
+
- [okr-anti-optimism](/okf/core/concepts/okr-anti-optimism.md) — the reporting stance that STE's modal replacements would flatten; the reason the register boundary above is load-bearing in PMOS.
|
|
74
|
+
- Sources: [asd-ste100.org](https://www.asd-ste100.org/) (specification + FAQ, free on request), [ASD](https://www.asd-europe.org/standards-specifications/simplified-technical-english/).
|
|
@@ -10,7 +10,7 @@ timestamp: 2026-07-18
|
|
|
10
10
|
|
|
11
11
|
**Situating context:** Authored with [D58](/okf/products/pmos/adr/d58-builder-profiles.md) as the
|
|
12
12
|
content lane's spec artifact — the contract copy work is drafted and judged against. The
|
|
13
|
-
[copywriting skill](/skills/
|
|
13
|
+
[copywriting skill](/skills/copywriting.skill) instantiates it. A rubric is recommended,
|
|
14
14
|
not required (PM call at D58 ratification); the optional section below is the recommended shape.
|
|
15
15
|
|
|
16
16
|
> Usage: replace every `<…>` placeholder. An empty audience or an unsourced claim list means the
|
|
@@ -9,7 +9,7 @@ timestamp: 2026-07-01
|
|
|
9
9
|
# Design Brief Template
|
|
10
10
|
|
|
11
11
|
**Situating context:** This template is the canonical scaffold the
|
|
12
|
-
[design-brief skill](/skills/
|
|
12
|
+
[design-brief skill](/skills/design-brief.skill) instantiates — leg 1 of the
|
|
13
13
|
[design loop](/okf/core/concepts/design-loop.md). It is to design what the
|
|
14
14
|
[PRD template](/okf/core/templates/prd-template.md) is to build: the shared definition before work starts.
|
|
15
15
|
Copy the block into `/planning/design/<slug>-design-brief.md` and fill it; the copy is an operational
|
|
@@ -9,7 +9,7 @@ timestamp: 2026-06-28
|
|
|
9
9
|
# Eval Rubric Template
|
|
10
10
|
|
|
11
11
|
**Situating context:** This template was authored as the canonical scaffold the
|
|
12
|
-
[eval-rubric skill](/skills/
|
|
12
|
+
[eval-rubric skill](/skills/eval-rubric.skill) instantiates, so every rubric uses the same
|
|
13
13
|
weighted-scoring math and carries its sprint contract. Copy the block into
|
|
14
14
|
`/planning/evals/<slug>-eval-rubric.md` and fill it; the copy is operational and does not live in the
|
|
15
15
|
OKF bundle.
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
OKF reserved file (no frontmatter). Change history, newest first, ISO 8601 dates.
|
|
4
4
|
|
|
5
5
|
## 2026-07-01
|
|
6
|
-
- Added `design-brief-template` — the design-brief scaffold (leg 1 of the design loop, D48), instantiated by the [design-brief skill](/skills/
|
|
6
|
+
- Added `design-brief-template` — the design-brief scaffold (leg 1 of the design loop, D48), instantiated by the [design-brief skill](/skills/design-brief.skill). Distilled from the first instance, `planning/design-brief.md`.
|
|
7
7
|
|
|
8
8
|
## 2026-06-29
|
|
9
9
|
- Creation — per-folder index/log added (OKF recursive progressive disclosure; D29 reversal direction).
|
|
@@ -9,7 +9,7 @@ timestamp: 2026-06-28
|
|
|
9
9
|
# PRD Template
|
|
10
10
|
|
|
11
11
|
**Situating context:** This template was authored as the canonical scaffold the
|
|
12
|
-
[prd skill](/skills/
|
|
12
|
+
[prd skill](/skills/prd.skill) instantiates, so every PRD in PMOS has the same shape and the
|
|
13
13
|
same anchoring discipline. Copy the block below into `/planning/prd/<slug>-prd.md` and fill it; the
|
|
14
14
|
copy is an operational instance and does not live in the OKF bundle.
|
|
15
15
|
|
|
@@ -11,7 +11,7 @@ timestamp: 2026-07-17
|
|
|
11
11
|
**Situating context:** Authored with [D56](/okf/products/pmos/adr/d56-initiative-lanes.md) as the
|
|
12
12
|
discovery lane's spec artifact, resolving the eval-first paradox the SDLC stress test named: a
|
|
13
13
|
prototype exists to *discover* its own done-criteria, so the lane contracts on a **question and a
|
|
14
|
-
timebox**, not on requirements. The [discovery skill](/skills/
|
|
14
|
+
timebox**, not on requirements. The [discovery skill](/skills/discovery.skill) instantiates
|
|
15
15
|
this template. Copy the block below into `/planning/prd/<id>-spike.md` and fill; the copy is an
|
|
16
16
|
operational instance, not OKF knowledge.
|
|
17
17
|
|
|
@@ -18,8 +18,12 @@ DIR="${1:-state/tickets}"
|
|
|
18
18
|
# --- field readers (frontmatter only: stop at the closing --- fence) ----------------------------
|
|
19
19
|
fm() { awk 'NR>1 && /^---[[:space:]]*$/{exit} {print}' "$1"; } # lines between the opening and closing ---
|
|
20
20
|
field() { printf '%s\n' "$1" | sed -n "s/^$2:[[:space:]]*//p" | head -1 | sed 's/^["'\'']//;s/["'\'']$//;s/[[:space:]]*$//'; }
|
|
21
|
-
# blocked_by is an inline list "[\"002\", \"005\"]"; extract the bare ids
|
|
22
|
-
|
|
21
|
+
# blocked_by is an inline list "[\"002\", \"005\"]"; extract the bare ids in order. Ids are NOT
|
|
22
|
+
# assumed numeric — idof below derives slug ids (an explicit `id:` field, or a digitless filename),
|
|
23
|
+
# and a digits-only extraction silently DROPPED every such blocker, listing a blocked ticket as
|
|
24
|
+
# workable (fail-open). Every non-empty token survives instead: a token that matches no done ticket
|
|
25
|
+
# keeps the ticket off the frontier, so a malformed or unknown blocker fails CLOSED.
|
|
26
|
+
blockers() { printf '%s\n' "$1" | sed -n 's/^blocked_by:[[:space:]]*//p' | head -1 | sed 's/[][]//g' | tr ',' '\n' | sed 's/["'\'']//g' | tr -d ' \t\r' | grep -v '^$' || true; }
|
|
23
27
|
# derive an id: explicit `id:` field, else the leading NNN of the filename.
|
|
24
28
|
idof() { local i; i="$(field "$1" id)"; [ -n "$i" ] && { printf '%s' "$i"; return; }; basename "$2" | grep -oE '^[0-9]+' || basename "$2" .md; }
|
|
25
29
|
|
|
@@ -49,9 +49,30 @@ printf '%s' "$id" | grep -qE '^[a-z0-9][a-z0-9._-]*$' || fail "Initiative id '$i
|
|
|
49
49
|
init_file="$KIT_ROOT/state/initiatives/$id.md"
|
|
50
50
|
[ -f "$init_file" ] || fail "No initiative file at $init_file. Create it first — an initiative must exist before its PR. This is the loop rule the gate enforces."
|
|
51
51
|
|
|
52
|
-
frontval() { # frontval <key> —
|
|
53
|
-
#
|
|
54
|
-
|
|
52
|
+
frontval() { # frontval <key> — the key's value from the LEADING frontmatter block, trimmed.
|
|
53
|
+
# The LEADING block only, mirroring workflow-state.js / file-mode-parse.js / metrics.py: the gate
|
|
54
|
+
# used to grep the whole file, so a body line starting `parent_kr: ...` (a pasted example) could
|
|
55
|
+
# fake the declaration the gate exists to check. Same rules as the canonical parsers: an opening
|
|
56
|
+
# --- fence (before any key) is skipped and the next --- ends the block; blank lines are skipped;
|
|
57
|
+
# the first non-key line ends the block (fenceless records, e.g. the kit-ci smoke fixture, keep
|
|
58
|
+
# parsing from line 1); first value wins; ONE pair of surrounding double quotes is stripped. An
|
|
59
|
+
# absent key prints nothing and falls through to the explicit fail() below.
|
|
60
|
+
awk -v want="$1" '
|
|
61
|
+
/^---/ { if (fence || seen) exit; fence = 1; next }
|
|
62
|
+
/^[[:space:]]*$/ { next }
|
|
63
|
+
{
|
|
64
|
+
i = index($0, ":")
|
|
65
|
+
key = (i > 0) ? substr($0, 1, i - 1) : ""
|
|
66
|
+
if (key !~ /^[a-z_]+$/) exit
|
|
67
|
+
seen = 1
|
|
68
|
+
if (key != want) next
|
|
69
|
+
val = substr($0, i + 1)
|
|
70
|
+
gsub(/\r/, "", val)
|
|
71
|
+
gsub(/^[[:space:]]+|[[:space:]]+$/, "", val)
|
|
72
|
+
if (length(val) >= 2 && val ~ /^".*"$/) val = substr(val, 2, length(val) - 2)
|
|
73
|
+
print val
|
|
74
|
+
exit
|
|
75
|
+
}' "$init_file"
|
|
55
76
|
}
|
|
56
77
|
prd="$(frontval prd_path)"
|
|
57
78
|
rubric="$(frontval rubric_path)"
|
|
@@ -56,14 +56,30 @@ def leading_frontmatter(path):
|
|
|
56
56
|
body line, the loop breaks, and every key below it is dropped. Agreement with the JS parser was
|
|
57
57
|
never sufficient to catch this — both carried the same wrong regex, so the fixture compared two
|
|
58
58
|
identical mistakes and passed. See kit/fixtures/state/initiatives/epsilon-empty-value.md.
|
|
59
|
+
|
|
60
|
+
The closing --- fence is TERMINAL, never skipped: stepping over it absorbed a key-shaped first
|
|
61
|
+
body line into the block (zeta-fence-fake.md pins this). Fenceless files parse from line 1 and
|
|
62
|
+
stop at the first non-key line. First value wins, and ONE pair of surrounding double quotes is
|
|
63
|
+
stripped, exactly as the JS parsers do (eta-quoted-value.md pins the quoted case).
|
|
59
64
|
"""
|
|
60
65
|
fm = {}
|
|
66
|
+
fence_open = False
|
|
61
67
|
with open(path, encoding="utf-8") as fh:
|
|
62
|
-
for
|
|
63
|
-
|
|
68
|
+
for raw in fh:
|
|
69
|
+
line = raw.rstrip("\n")
|
|
70
|
+
if line.startswith("---"):
|
|
71
|
+
# Only the FIRST ---, before any key, opens the block; every later --- ends it.
|
|
72
|
+
if fence_open or fm:
|
|
73
|
+
break
|
|
74
|
+
fence_open = True
|
|
75
|
+
continue
|
|
76
|
+
m = re.match(r"^([a-z_]+):\s*(.*?)\s*$", line)
|
|
64
77
|
if m:
|
|
65
|
-
|
|
66
|
-
|
|
78
|
+
val = m.group(2)
|
|
79
|
+
if len(val) >= 2 and val.startswith('"') and val.endswith('"'):
|
|
80
|
+
val = val[1:-1]
|
|
81
|
+
fm.setdefault(m.group(1), val)
|
|
82
|
+
elif line.strip():
|
|
67
83
|
break # frontmatter is the LEADING block only
|
|
68
84
|
return fm
|
|
69
85
|
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/*
|
|
3
|
+
* scripts/read-jsonl.js — THE JSONL reader for the scripts/ checkers and the workflow engine.
|
|
4
|
+
*
|
|
5
|
+
* Six near-identical readers grew here, with three different torn-line contracts, and the repo's
|
|
6
|
+
* own comments record the cost twice: a silently-dropped torn line re-opened a verified entry
|
|
7
|
+
* (check-verify-queue.js) and made an accepted initiative read as never accepted (workflow-state.js).
|
|
8
|
+
* Each new checker re-decided the torn-line question from scratch and got it wrong at least twice.
|
|
9
|
+
* One reader, one contract — the strictest one found:
|
|
10
|
+
*
|
|
11
|
+
* - a torn line is DROPPED, never guessed at — a truncated row parsed "as far as it goes" would
|
|
12
|
+
* fabricate a record nobody wrote;
|
|
13
|
+
* - and it is REPORTED, never swallowed: every defect comes back with its file, 1-based line,
|
|
14
|
+
* parse reason and a bounded slice of the text, so the caller can say out loud what it could
|
|
15
|
+
* not read. "Assuming someone else counts is how nobody counts."
|
|
16
|
+
* - a MISSING (or unreadable) file is its own state, distinguishable from an empty one — the
|
|
17
|
+
* cadence checker needs the difference and a gate must not confuse "no ledger" with "no rows".
|
|
18
|
+
*
|
|
19
|
+
* Returns { rows, defects, torn, missing }: rows are the parsed objects in file order; defects is
|
|
20
|
+
* [{ file, line, reason, text }]; torn === defects.length, kept as a count because most callers
|
|
21
|
+
* only need the number; missing is true when the file could not be read at all (rows/defects empty).
|
|
22
|
+
*
|
|
23
|
+
* DELIBERATELY FLAT in scripts/, not scripts/lib/: the kit and the workspace bundle both vendor the
|
|
24
|
+
* engine's require closure by BASENAME into a flat directory (kit/build-kit.sh, scripts/
|
|
25
|
+
* vendor-workspace-engine.js), so a `./lib/` require would resolve in the checkout and break in
|
|
26
|
+
* every vendored copy — the exact incident class the scripts/lib/sink.js re-export note records.
|
|
27
|
+
* workspace/lib/file-mode-parse.js does NOT delegate here for the mirrored reason: the workspace
|
|
28
|
+
* bundle ships alone, cannot require across the bundle boundary, and its silent-drop reader is the
|
|
29
|
+
* locked mirror of kit/scripts/metrics.py (kit/fixtures/test_parsers.py holds that agreement).
|
|
30
|
+
*
|
|
31
|
+
* Zero dependencies, stock Node.
|
|
32
|
+
*/
|
|
33
|
+
const fs = require('fs');
|
|
34
|
+
|
|
35
|
+
function readJsonl(file) {
|
|
36
|
+
let text;
|
|
37
|
+
try { text = fs.readFileSync(file, 'utf8'); }
|
|
38
|
+
catch { return { rows: [], defects: [], torn: 0, missing: true }; }
|
|
39
|
+
const rows = [];
|
|
40
|
+
const defects = [];
|
|
41
|
+
const lines = text.split('\n');
|
|
42
|
+
for (let i = 0; i < lines.length; i++) {
|
|
43
|
+
const t = lines[i].trim();
|
|
44
|
+
if (!t) continue;
|
|
45
|
+
try { rows.push(JSON.parse(t)); }
|
|
46
|
+
catch (e) {
|
|
47
|
+
defects.push({ file, line: i + 1, reason: String((e && e.message) || 'not valid JSON').slice(0, 120), text: t.slice(0, 120) });
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { rows, defects, torn: defects.length, missing: false };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = { readJsonl };
|