create-pmos 0.5.0 → 0.6.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 +114 -0
- package/bin/create-pmos.js +93 -7
- package/package.json +7 -3
- package/payload/AGENTS-HUMAN.md +6 -0
- package/payload/AGENTS.md +35 -1
- package/payload/KIT-VERSION +2 -2
- package/payload/README.md +6 -0
- package/payload/SECURITY.md +6 -0
- package/payload/okf/core/concepts/frontmatter-format.md +75 -0
- package/payload/okf/core/concepts/index.md +3 -0
- package/payload/okf/core/concepts/log.md +13 -0
- package/payload/okf/core/templates/eval-rubric-template.md +12 -10
- package/payload/okf/core/templates/prd-template.md +6 -0
- package/payload/okf/core/templates/prototype-brief-template.md +45 -21
- package/payload/planning/evals/agent-run-eval-rubric.md +10 -7
- package/payload/scripts/metrics.py +142 -121
- package/payload/skills/SKILL-FORMAT.md +6 -0
- package/payload/skills/discovery.skill +4 -0
- package/payload/skills/eval-rubric.skill +1 -1
- package/payload/skills/explain-surface.skill +156 -0
- package/payload/skills/pmos-router.skill +25 -3
- package/payload/skills/prd.skill +8 -0
- package/payload/skills/prototype.skill +106 -41
- package/payload/skills-native/discovery/SKILL.md +4 -0
- package/payload/skills-native/eval-rubric/SKILL.md +1 -1
- package/payload/skills-native/explain-surface/SKILL.md +158 -0
- package/payload/skills-native/pmos-router/SKILL.md +25 -3
- package/payload/skills-native/prd/SKILL.md +8 -0
- package/payload/skills-native/prototype/SKILL.md +106 -41
- package/payload/templates/agent-run-eval-rubric.md +10 -7
- package/payload/templates/ticket-template.md +2 -0
- package/workspace/README.md +112 -0
- package/workspace/REMOTE.md +131 -0
- package/workspace/bin/pmos.js +206 -0
- package/workspace/doctor.js +99 -0
- package/workspace/lib/console-state.js +117 -0
- package/workspace/lib/file-mode-parse.js +58 -0
- package/workspace/lib/harness-parse.js +222 -0
- package/workspace/lib/preview-kind.js +98 -0
- package/workspace/lib/rate-limit.js +26 -0
- package/workspace/lib/remote-access.js +235 -0
- package/workspace/lib/review-gate.js +39 -0
- package/workspace/lib/schedule-logic.js +132 -0
- package/workspace/lib/skill-store.js +143 -0
- package/workspace/lib/surface-contract.js +79 -0
- package/workspace/lib/transcript-read.js +247 -0
- package/workspace/package-lock.json +94 -0
- package/workspace/package.json +23 -0
- package/workspace/public/app.js +1710 -0
- package/workspace/public/index.html +175 -0
- package/workspace/public/styles.css +766 -0
- package/workspace/server.js +1753 -0
- package/workspace/skills/backlog-triage.skill +13 -0
- package/workspace/skills/weekly-report.skill +19 -0
|
@@ -1,24 +1,28 @@
|
|
|
1
1
|
---
|
|
2
2
|
type: Template
|
|
3
3
|
title: Prototype Brief Template
|
|
4
|
-
description: The scaffold for a QUICK leg-A prototype brief (D60) — the idea, the user, the moment, the feel, draft copy seeds. Deliberately short; if it needs PRD-level detail the flow has failed its purpose. Copy into prototype/<initiative-id>/brief.md and set it as the initiative's prd_path.
|
|
4
|
+
description: The scaffold for a QUICK leg-A prototype brief (D60) — the idea, the judgement it exercises, the user, the moment, the feel, draft copy seeds. Works for any medium, not just UI (2026-07-24 amendment). Deliberately short; if it needs PRD-level detail the flow has failed its purpose. Copy into prototype/<initiative-id>/brief.md and set it as the initiative's prd_path.
|
|
5
5
|
tags: [template, prototype, design, lane]
|
|
6
|
-
timestamp: 2026-07-
|
|
6
|
+
timestamp: 2026-07-24
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Prototype Brief Template
|
|
10
10
|
|
|
11
11
|
**Situating context:** The [prototype lane](/okf/products/pmos/adr/d60-prototype-loop.md)'s contract
|
|
12
|
-
artifact. The PM writes THIS — quick, sketchy, incomplete is fine — and the **
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
committing to a spec. If filling it in starts to feel like writing requirements, stop — that
|
|
17
|
-
belongs in the PRD that follows a successful prototype, not here.
|
|
12
|
+
artifact. The PM writes THIS — quick, sketchy, incomplete is fine — and the **agent does the rest**:
|
|
13
|
+
picks up the artifact class's conventions (a design system for UI; SKILL-FORMAT, D19 ACI,
|
|
14
|
+
migration/RLS conventions elsewhere), applies that class's craft, drafts real content, and returns
|
|
15
|
+
divergent specimens. The brief is deliberately NOT a PRD: it exists so the PM can examine something
|
|
16
|
+
*before* committing to a spec. If filling it in starts to feel like writing requirements, stop — that
|
|
17
|
+
detail belongs in the PRD that follows a successful prototype, not here.
|
|
18
|
+
|
|
19
|
+
**Any medium** (2026-07-24 amendment to D60): the lane is not UI-gated. A specimen may be a lookable
|
|
20
|
+
screen, a worked sample output, call/response transcripts, or a schema sketch — whichever is the
|
|
21
|
+
cheapest thing that lets the PM exercise the judgement below.
|
|
18
22
|
|
|
19
23
|
> Usage: copy into `prototype/<initiative-id>/brief.md`; set it as the initiative's `prd_path`
|
|
20
24
|
> (`create_initiative`, `type: 'prototype'`). Every section may be a single sentence. Only the first
|
|
21
|
-
>
|
|
25
|
+
> three are required.
|
|
22
26
|
|
|
23
27
|
---
|
|
24
28
|
|
|
@@ -35,30 +39,50 @@ belongs in the PRD that follows a successful prototype, not here.
|
|
|
35
39
|
## Who it's for, and the moment they'd use it (required)
|
|
36
40
|
<!-- The user and the situation — "a PM opening the board on Monday morning", not a persona doc. -->
|
|
37
41
|
|
|
42
|
+
## The judgement you need to make (required)
|
|
43
|
+
<!-- One line: what you cannot decide from a description alone, and so need to SEE an instance of.
|
|
44
|
+
"Whether this actually reads as calm rather than sparse." "Whether this explainer actually
|
|
45
|
+
teaches someone who's never seen the region." "Whether this API feels right to call."
|
|
46
|
+
This is the specimen test written down — it steers the format, the grilling, and the
|
|
47
|
+
disposition. If you can't name such a judgement, this may not be a prototype (see the
|
|
48
|
+
discovery lane, whose deliverable is a learning, not a validated direction). -->
|
|
49
|
+
|
|
50
|
+
## Specimen format
|
|
51
|
+
<!-- Leave blank to let the agent propose it and say why. The principle is the CHEAPEST thing that
|
|
52
|
+
lets you exercise the judgement above: lookable HTML, a worked sample output on real input,
|
|
53
|
+
call/response transcripts, a schema sketch with example rows, the actual strings. -->
|
|
54
|
+
|
|
38
55
|
## How it should feel
|
|
39
56
|
<!-- Adjectives and references beat specification: "calm, dense, fast to scan", "more like a
|
|
40
|
-
cockpit than a report".
|
|
57
|
+
cockpit than a report". Applies beyond visuals — an API can feel obvious or fiddly, a
|
|
58
|
+
sample output can feel patronising or brisk. Leave blank to let the agent propose. -->
|
|
41
59
|
|
|
42
60
|
## Copy seeds
|
|
43
|
-
<!-- Any words you already hear: a headline, a button label, the tone. The agent
|
|
44
|
-
(D60 — copy rides the prototype, no lorem
|
|
61
|
+
<!-- Any words you already hear: a headline, a button label, an error string, the tone. The agent
|
|
62
|
+
drafts REAL content (D60 — copy rides the prototype, no lorem, and no fake inputs/outputs for
|
|
63
|
+
non-visual specimens: placeholders hide the friction the specimen exists to surface). Blank is
|
|
64
|
+
fine. -->
|
|
45
65
|
|
|
46
66
|
## What this is NOT
|
|
47
67
|
<!-- One line if you already know a direction to avoid. -->
|
|
48
68
|
|
|
49
|
-
## Existing
|
|
50
|
-
<!-- If this improves
|
|
69
|
+
## Existing thing?
|
|
70
|
+
<!-- If this improves something that already exists — a screen, a skill, an endpoint — name it.
|
|
71
|
+
Brand-new idea → leave blank. -->
|
|
51
72
|
```
|
|
52
73
|
|
|
53
74
|
---
|
|
54
75
|
|
|
55
76
|
## What the agent does with this (not the PM's job)
|
|
56
77
|
|
|
57
|
-
1. **
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
78
|
+
1. **Conventions check first (blocking, D60(c) as generalised):** the artifact class has no
|
|
79
|
+
conventions and no craft to reason from → the agent proposes a minimal set from this same brief
|
|
80
|
+
and the PM approves it *before* any prototype. For UI that is the design system; for a skill,
|
|
81
|
+
SKILL-FORMAT; for a tool or API, D19's ACI principle; for a schema, the migration/RLS conventions.
|
|
82
|
+
2. Produces **2–3 divergent low-fidelity specimens** — one safe direction is a weaker outcome than
|
|
83
|
+
three arguable ones — with **real content**, into `prototype/<initiative-id>/`.
|
|
84
|
+
3. Submits to a **craft-grade grilling** (dialogue, never a score — D60(d)): one question at a time,
|
|
85
|
+
a recommended answer with each. This holds for non-visual specimens too, where the temptation to
|
|
86
|
+
score is sharper.
|
|
63
87
|
4. The PM **disposes**: promote (→ a new feature-lane initiative; PRD + rubric derived from the
|
|
64
|
-
validated
|
|
88
|
+
validated specimen) / iterate / kill.
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Rubric
|
|
3
|
+
title: "Eval Rubric: Agent-run capability baseline (kit seed — copy per initiative)"
|
|
4
|
+
timestamp: 2026-07-19
|
|
5
|
+
eval_type: capability
|
|
6
|
+
initiative_id: (set per initiative)
|
|
7
|
+
pass_threshold: 0.80
|
|
8
|
+
canonical_path: pmos/planning/evals/agent-run-eval-rubric.md
|
|
9
|
+
---
|
|
10
|
+
|
|
1
11
|
# Eval Rubric: Agent-run capability baseline (kit seed — copy per initiative)
|
|
2
12
|
|
|
3
13
|
> Kit-seeded generic rubric (D54). Two purposes: (1) it is the TEMPLATE to copy to
|
|
@@ -6,13 +16,6 @@
|
|
|
6
16
|
> meaningful from day one. It scores a generic agent run outcome-only (D14) with weighted partial
|
|
7
17
|
> credit (D15).
|
|
8
18
|
|
|
9
|
-
## Frontmatter
|
|
10
|
-
- type: Rubric
|
|
11
|
-
- eval_type: capability
|
|
12
|
-
- initiative_id: (set per initiative)
|
|
13
|
-
- pass_threshold: 0.80
|
|
14
|
-
- canonical_path: pmos/planning/evals/agent-run-eval-rubric.md
|
|
15
|
-
|
|
16
19
|
## Gate assignment
|
|
17
20
|
- **Quality Gate (deterministic):** repo CI green; `pmos-gate` (anchored + rubrics) green.
|
|
18
21
|
- **Review Gate (adversarial):** the weighted dimensions below — evaluator skill, default-to-refute,
|
|
@@ -5,7 +5,7 @@ Reads pmos/state/{runs,evals,acceptances}.jsonl and prints:
|
|
|
5
5
|
B1 north star — human corrections per ACCEPTED run (lower is better; D10, counted over
|
|
6
6
|
accepted runs only so leniency can't deflate it)
|
|
7
7
|
B2 throughput — accepted runs (guards against winning B1 by doing less)
|
|
8
|
-
-- eval pass rate — share of evals
|
|
8
|
+
-- eval pass rate — share of evals passing the Review-Gate rule (see review_gate_pass)
|
|
9
9
|
B4 calibration — evaluator verdict vs PM label, joined by run_id: raw agreement, TPR, TNR,
|
|
10
10
|
Cohen's kappa (the D40 classifier method, computed locally). κ is broken out
|
|
11
11
|
PER evaluator_model, with a mixed-model warning — a κ is a property of the
|
|
@@ -14,12 +14,52 @@ Reads pmos/state/{runs,evals,acceptances}.jsonl and prints:
|
|
|
14
14
|
pass-happy-judge signal; anti-optimism: read the red first)
|
|
15
15
|
|
|
16
16
|
No network, no secrets. Exit 0 always (a reporting tool, not a gate). Anti-optimism: reds print first.
|
|
17
|
+
|
|
18
|
+
Import-safe (D64 Track D1): every helper is module-level and the report runs only under __main__, so
|
|
19
|
+
kit/fixtures/test_parsers.py can import review_gate_pass + leading_frontmatter and prove the Python
|
|
20
|
+
parsers agree with the JavaScript ones in workspace/lib/.
|
|
17
21
|
"""
|
|
18
22
|
import datetime, glob, json, os, re, sys
|
|
19
23
|
|
|
20
24
|
KIT = os.environ.get("KIT_ROOT") or os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
21
25
|
STATE = os.path.join(KIT, "state")
|
|
22
26
|
|
|
27
|
+
# ── CANONICAL Review-Gate pass rule — the MIRROR of workspace/lib/review-gate.js ──────────────
|
|
28
|
+
# Rule: verdict ILIKE 'pass%' OR score >= 0.85. This is the single source of truth's Python half;
|
|
29
|
+
# the JS half is workspace/lib/review-gate.js. kit/fixtures/test_parsers.py runs BOTH against
|
|
30
|
+
# kit/fixtures/review-gate-pass-rule.json and fails if they ever disagree — the mirror can't drift.
|
|
31
|
+
PASS_VERDICT_PREFIX = "pass"
|
|
32
|
+
SCORE_THRESHOLD = 0.85
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def review_gate_pass(verdict, score):
|
|
36
|
+
"""A recorded verdict is AUTHORITATIVE (pass iff it starts with "pass"); an explicit FAIL is never
|
|
37
|
+
overridden by a high score. Only when NO verdict was recorded does the score decide.
|
|
38
|
+
AMENDED 2026-07-26: was `verdict ILIKE 'pass%' OR score >= 0.85`, which let a FAIL at score 0.93
|
|
39
|
+
through the gate (blind-plant round 2, specimen be58). Mirror of workspace/lib/review-gate.js
|
|
40
|
+
isReviewPass; agreement enforced by kit/fixtures/test_parsers.py."""
|
|
41
|
+
v = str("" if verdict is None else verdict).strip().lower()
|
|
42
|
+
if v:
|
|
43
|
+
return v.startswith(PASS_VERDICT_PREFIX)
|
|
44
|
+
try:
|
|
45
|
+
return float(score) >= SCORE_THRESHOLD
|
|
46
|
+
except (TypeError, ValueError):
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def leading_frontmatter(path):
|
|
51
|
+
"""The LEADING frontmatter block only — body text cannot fake a declaration. Mirror of
|
|
52
|
+
workspace/lib/file-mode-parse.js parseLeadingFrontmatter (agreement enforced by the fixture test)."""
|
|
53
|
+
fm = {}
|
|
54
|
+
with open(path, encoding="utf-8") as fh:
|
|
55
|
+
for line in fh:
|
|
56
|
+
m = re.match(r"^([a-z_]+):\s*(.+?)\s*$", line.rstrip("\n"))
|
|
57
|
+
if m:
|
|
58
|
+
fm.setdefault(m.group(1), m.group(2))
|
|
59
|
+
elif line.strip() and not line.startswith("---"):
|
|
60
|
+
break # frontmatter is the LEADING block only
|
|
61
|
+
return fm
|
|
62
|
+
|
|
23
63
|
|
|
24
64
|
def load(name):
|
|
25
65
|
path = os.path.join(STATE, name)
|
|
@@ -36,33 +76,14 @@ def load(name):
|
|
|
36
76
|
return rows
|
|
37
77
|
|
|
38
78
|
|
|
39
|
-
|
|
40
|
-
evals
|
|
41
|
-
acc = load("acceptances.jsonl")
|
|
42
|
-
|
|
43
|
-
accepted = [a for a in acc if a.get("accepted") is True]
|
|
44
|
-
corrections_by_run = {r.get("run_id"): int(r.get("human_corrections") or 0) for r in runs}
|
|
45
|
-
acc_corr = [corrections_by_run.get(a.get("run_id"), 0) for a in accepted]
|
|
46
|
-
|
|
47
|
-
passing = ("PASS", "PASS-WITH-FINDINGS")
|
|
48
|
-
n_pass = sum(1 for e in evals if str(e.get("verdict", "")).upper() in passing)
|
|
49
|
-
pass_rate = (n_pass / len(evals)) if evals else None
|
|
50
|
-
|
|
51
|
-
# --- B4: verdict-vs-PM-label confusion matrix, joined by run_id (D40) ---
|
|
52
|
-
# Calibration is a property of the EVALUATOR MODEL (harness-model-agnosticism): a κ earned by one
|
|
53
|
-
# model is not inherited by another. Compute the matrix OVERALL and PER evaluator_model, and warn
|
|
54
|
-
# when one aggregate κ silently pools verdicts from more than one model.
|
|
55
|
-
label_by_run = {a.get("run_id"): str(a.get("pm_label", "")).lower() for a in acc if a.get("pm_label")}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def confusion(rows):
|
|
59
|
-
"""(tp, tn, fp, fn) over evals joined to a pass/fail PM label by run_id."""
|
|
79
|
+
def confusion(rows, label_by_run):
|
|
80
|
+
"""(tp, tn, fp, fn) over evals joined to a pass/fail PM label by run_id, using the canonical rule."""
|
|
60
81
|
tp = tn = fp = fn = 0
|
|
61
82
|
for e in rows:
|
|
62
83
|
lab = label_by_run.get(e.get("run_id"))
|
|
63
84
|
if lab not in ("pass", "fail"):
|
|
64
85
|
continue
|
|
65
|
-
pred_pass =
|
|
86
|
+
pred_pass = review_gate_pass(e.get("verdict"), e.get("score"))
|
|
66
87
|
if pred_pass and lab == "pass":
|
|
67
88
|
tp += 1
|
|
68
89
|
elif pred_pass and lab == "fail":
|
|
@@ -83,102 +104,102 @@ def kappa_of(tp, tn, fp, fn):
|
|
|
83
104
|
return None if pe == 1 else (po - pe) / (1 - pe)
|
|
84
105
|
|
|
85
106
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
print(f"
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
for
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
print(f"
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
for
|
|
156
|
-
|
|
157
|
-
if
|
|
158
|
-
fm.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if not fm.get("id"):
|
|
107
|
+
def main():
|
|
108
|
+
runs = load("runs.jsonl")
|
|
109
|
+
evals = load("evals.jsonl")
|
|
110
|
+
acc = load("acceptances.jsonl")
|
|
111
|
+
|
|
112
|
+
accepted = [a for a in acc if a.get("accepted") is True]
|
|
113
|
+
corrections_by_run = {r.get("run_id"): int(r.get("human_corrections") or 0) for r in runs}
|
|
114
|
+
acc_corr = [corrections_by_run.get(a.get("run_id"), 0) for a in accepted]
|
|
115
|
+
|
|
116
|
+
n_pass = sum(1 for e in evals if review_gate_pass(e.get("verdict"), e.get("score")))
|
|
117
|
+
pass_rate = (n_pass / len(evals)) if evals else None
|
|
118
|
+
|
|
119
|
+
# --- B4: verdict-vs-PM-label confusion matrix, joined by run_id (D40) ---
|
|
120
|
+
label_by_run = {a.get("run_id"): str(a.get("pm_label", "")).lower() for a in acc if a.get("pm_label")}
|
|
121
|
+
tp, tn, fp, fn = confusion(evals, label_by_run)
|
|
122
|
+
n = tp + tn + fp + fn
|
|
123
|
+
k = kappa_of(tp, tn, fp, fn)
|
|
124
|
+
|
|
125
|
+
avg_corr = (sum(corrections_by_run.values()) / len(corrections_by_run)) if corrections_by_run else None
|
|
126
|
+
tripwire = pass_rate is not None and avg_corr is not None and pass_rate >= 0.8 and avg_corr >= 1.5
|
|
127
|
+
|
|
128
|
+
print(f"PMOS kit metrics — {len(runs)} run(s), {len(evals)} eval(s), {len(acc)} acceptance record(s)\n")
|
|
129
|
+
if tripwire:
|
|
130
|
+
print("🔴 GOODHART TRIPWIRE FIRED — evaluator passes >=80% while humans average >=1.5 corrections/run.")
|
|
131
|
+
print(" The north star is being gamed; treat the Review Gate as NOT calibrated (D40).\n")
|
|
132
|
+
if not accepted:
|
|
133
|
+
print("🔴 B1 north star: UNMEASURABLE — no accepted runs yet (record acceptances to pmos/state/acceptances.jsonl).")
|
|
134
|
+
else:
|
|
135
|
+
ns = sum(acc_corr) / len(accepted)
|
|
136
|
+
print(f"B1 north star: {ns:.2f} human corrections per accepted run (over {len(accepted)} accepted run(s); lower is better)")
|
|
137
|
+
print(f"B2 throughput: {len(accepted)} accepted run(s)")
|
|
138
|
+
print(f" eval pass rate: {'n/a — no evals yet' if pass_rate is None else f'{pass_rate:.0%} ({n_pass}/{len(evals)})'}")
|
|
139
|
+
if n == 0:
|
|
140
|
+
print("🔴 B4 calibration: UNMEASURABLE — no eval joined to a PM-labelled acceptance yet; the Review Gate is uncalibrated.")
|
|
141
|
+
else:
|
|
142
|
+
tpr = tp / (tp + fn) if (tp + fn) else None
|
|
143
|
+
tnr = tn / (tn + fp) if (tn + fp) else None
|
|
144
|
+
print(f"B4 calibration (n={n}): raw agreement {(tp + tn) / n:.0%}, "
|
|
145
|
+
f"TPR {'n/a' if tpr is None else f'{tpr:.0%}'}, "
|
|
146
|
+
f"TNR {'n/a — no PM fails seen (leniency blind spot)' if tnr is None else f'{tnr:.0%}'}, "
|
|
147
|
+
f"kappa {'undefined' if k is None else f'{k:.2f}'} (target >= 0.60 — verdict ADVISORY below it, D40)")
|
|
148
|
+
|
|
149
|
+
# --- per-evaluator-model calibration (harness-model-agnosticism) -------------
|
|
150
|
+
labeled_evals = [e for e in evals if label_by_run.get(e.get("run_id")) in ("pass", "fail")]
|
|
151
|
+
by_model = {}
|
|
152
|
+
for e in labeled_evals:
|
|
153
|
+
by_model.setdefault(e.get("evaluator_model") or "unknown", []).append(e)
|
|
154
|
+
if len(by_model) > 1:
|
|
155
|
+
print(f"\n🔴 MIXED-MODEL CALIBRATION — labeled evals span {len(by_model)} evaluator models; the "
|
|
156
|
+
"aggregate κ above POOLS them and is not any single evaluator's calibration (κ is per "
|
|
157
|
+
"evaluator model, D40). Per model:")
|
|
158
|
+
for m in sorted(by_model):
|
|
159
|
+
c = confusion(by_model[m], label_by_run)
|
|
160
|
+
km = kappa_of(*c)
|
|
161
|
+
print(f" - {m}: n={sum(c)}, kappa {'undefined' if km is None else f'{km:.2f}'} "
|
|
162
|
+
f"(tp{c[0]} tn{c[1]} fp{c[2]} fn{c[3]})")
|
|
163
|
+
elif by_model and set(by_model) == {"unknown"}:
|
|
164
|
+
print("\n⚠ evaluator_model unrecorded on every labeled eval — κ cannot be attributed to a model "
|
|
165
|
+
"and silently pools across any model swaps. Pass the evaluator model as the last arg to "
|
|
166
|
+
"`pmos-log.sh eval` (harness-model-agnosticism).")
|
|
167
|
+
|
|
168
|
+
# --- D57 outcome checks (accepted-but-unvalidated; due prints red) -----------
|
|
169
|
+
checks = load("outcome_checks.jsonl")
|
|
170
|
+
latest_disp = {}
|
|
171
|
+
for c in checks:
|
|
172
|
+
if c.get("initiative_id"):
|
|
173
|
+
latest_disp[c["initiative_id"]] = str(c.get("disposition", "")).lower()
|
|
174
|
+
|
|
175
|
+
declared = []
|
|
176
|
+
for path in sorted(glob.glob(os.path.join(STATE, "initiatives", "*.md"))):
|
|
177
|
+
fm = leading_frontmatter(path)
|
|
178
|
+
if not fm.get("id"):
|
|
179
|
+
if fm.get("outcome_metric"):
|
|
180
|
+
print(f"⚠ {os.path.basename(path)} declares an outcome but has no id: — it can never be disposed; fix the frontmatter")
|
|
181
|
+
continue
|
|
162
182
|
if fm.get("outcome_metric"):
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
for
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
declared.append(fm)
|
|
184
|
+
|
|
185
|
+
if declared:
|
|
186
|
+
today = datetime.date.today().isoformat()
|
|
187
|
+
undisposed = [f for f in declared if f.get("id") not in latest_disp]
|
|
188
|
+
# a missing horizon errs RED (counts as due): a bet with no judgment date can never come due
|
|
189
|
+
# by itself — horizon-gaming-by-omission (mirrors the hosted CHECK requiring a horizon).
|
|
190
|
+
due = [f for f in undisposed if str(f.get("outcome_horizon", "0000-00-00")) < today]
|
|
191
|
+
pending = [f for f in undisposed if f not in due]
|
|
192
|
+
print()
|
|
193
|
+
if due:
|
|
194
|
+
print(f"🔴 D57 outcome checks DUE ({len(due)}) — accepted bets past horizon, never validated:")
|
|
195
|
+
for f in due:
|
|
196
|
+
print(f" - {f.get('id')}: \"{f.get('outcome_metric')}\" (horizon {f.get('outcome_horizon', '?')})"
|
|
197
|
+
" -> dispose validated|refuted|inconclusive to state/outcome_checks.jsonl")
|
|
198
|
+
refuted = sum(1 for v in latest_disp.values() if v == "refuted")
|
|
199
|
+
print(f"D57 outcomes: {len(declared)} declared — {len(pending)} pending, {len(due)} due, "
|
|
200
|
+
f"{len(latest_disp)} disposed"
|
|
201
|
+
+ (f" ({refuted} refuted — recorded, not hidden)" if refuted else ""))
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
if __name__ == "__main__":
|
|
205
|
+
main()
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Reference
|
|
3
|
+
title: "SKILL-FORMAT.md — Required Format for PMOS Skills"
|
|
4
|
+
timestamp: 2026-07-23
|
|
5
|
+
---
|
|
6
|
+
|
|
1
7
|
# SKILL-FORMAT.md — Required Format for PMOS Skills
|
|
2
8
|
|
|
3
9
|
This document defines the **required** structure for every `.skill` file in PMOS. It is the
|
|
@@ -35,6 +35,10 @@ fake rubric, or escaped the record entirely.
|
|
|
35
35
|
1. **Anchor and create the initiative** with `type: discovery`
|
|
36
36
|
(`create_initiative({id, title, parent_kr, type: 'discovery'})`; file mode: `type: discovery`
|
|
37
37
|
in the initiative frontmatter).
|
|
38
|
+
1b. **Existing surface? Understand it first.** If answering the question honestly requires reading
|
|
39
|
+
code that already exists, run an [explain-surface](/build-skills/explain-surface.skill) pass on
|
|
40
|
+
that region — in a fresh context, **before** writing the brief. A spike aimed at a system the PM
|
|
41
|
+
does not understand produces a learning they cannot act on. Greenfield → skip.
|
|
38
42
|
2. **Write the spike brief** from
|
|
39
43
|
[spike-brief-template](/okf/core/templates/spike-brief-template.md) into
|
|
40
44
|
`/planning/prd/<id>-spike.md`; set it as the initiative's `prd_path`. The Initiative Gate
|
|
@@ -43,7 +43,7 @@ Acceptance Gate is the PM's and is not graded by the rubric.
|
|
|
43
43
|
|
|
44
44
|
Every rubric document carries, at minimum:
|
|
45
45
|
|
|
46
|
-
- `type: Rubric` (
|
|
46
|
+
- **OKF frontmatter** — the file opens with `type: Rubric`, `title`, `timestamp` (repo-wide convention, D63; [frontmatter-format](/okf/core/concepts/frontmatter-format.md)).
|
|
47
47
|
- **`eval_type: capability | regression`** —
|
|
48
48
|
- `capability` — tests whether the agent can do something new / harder. Used to push the frontier.
|
|
49
49
|
- `regression` — guards an already-passing capability against backsliding. Saturated capability
|