create-pmos 0.1.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +97 -9
- package/package.json +2 -2
- package/payload/AGENTS-HUMAN.md +43 -0
- package/payload/AGENTS.md +80 -5
- package/payload/KIT-VERSION +2 -2
- package/payload/README.md +23 -4
- package/payload/okf/core/concepts/health-anchors.md +41 -0
- package/payload/okf/core/concepts/index.md +1 -0
- package/payload/okf/core/concepts/okr-anti-optimism.md +7 -0
- package/payload/okf/core/concepts/okr-tree.md +7 -0
- package/payload/okf/core/concepts/output-eval.md +2 -1
- package/payload/okf/core/concepts/sdlc-loop.md +2 -1
- package/payload/okf/core/templates/content-brief-template.md +70 -0
- package/payload/okf/core/templates/design-brief-template.md +1 -1
- package/payload/okf/core/templates/eval-rubric-template.md +24 -4
- package/payload/okf/core/templates/incident-record-template.md +66 -0
- package/payload/okf/core/templates/index.md +3 -0
- package/payload/okf/core/templates/prd-template.md +13 -4
- package/payload/okf/core/templates/spike-brief-template.md +68 -0
- package/payload/planning/evals/agent-run-eval-rubric.md +5 -1
- package/payload/planning/okrs/health-budgets.md +7 -0
- package/payload/scripts/gate-initiative.sh +50 -11
- package/payload/scripts/metrics.py +46 -1
- package/payload/scripts/pmos-log.sh +76 -0
- package/payload/skills/SKILL-FORMAT.md +103 -1
- package/payload/skills/copywriting.skill +55 -0
- package/payload/skills/discovery.skill +69 -0
- package/payload/skills/eval-rubric.skill +29 -1
- package/payload/skills/evaluator.skill +68 -14
- package/payload/skills/explain-run.skill +116 -0
- package/payload/skills/prd.skill +21 -10
- package/payload/state/outcome_checks.jsonl +0 -0
- package/payload/templates/agent-run-eval-rubric.md +5 -1
- package/payload/templates/product-ci-starter.yml +36 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Template
|
|
3
|
+
title: Incident Record Template
|
|
4
|
+
description: The scaffold for an incident-lane record (D56) — timeline, impact, root cause, learnings, follow-ups. Authored post-hoc (within 48h of resolution), copied into /planning/prd/ as <id>-incident.md and set as the initiative's prd_path.
|
|
5
|
+
tags: [template, incident, lanes, learning]
|
|
6
|
+
timestamp: 2026-07-17
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Incident Record Template
|
|
10
|
+
|
|
11
|
+
**Situating context:** Authored with [D56](/okf/products/pmos/adr/d56-initiative-lanes.md) to give
|
|
12
|
+
incidents an honest lane: before lanes existed, urgent fixes took the `no-initiative` bypass and
|
|
13
|
+
PMOS's most learning-rich events left **no record at all**. The incident lane inverts eval-first
|
|
14
|
+
by design — fix first, record within 48h — the one sanctioned inversion. Copy the block below into
|
|
15
|
+
`/planning/prd/<id>-incident.md` and fill; set it as the initiative's `prd_path`.
|
|
16
|
+
|
|
17
|
+
> Usage: create the incident initiative (`type: incident`) as soon as work starts — the record may
|
|
18
|
+
> follow the fix, the anchor may not. The 48h window is protocol (AGENTS.md), not CI-enforced:
|
|
19
|
+
> honoring it is what keeps the lane honest.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
```markdown
|
|
24
|
+
# Incident Record: <short title>
|
|
25
|
+
|
|
26
|
+
## Identity
|
|
27
|
+
- initiative_id: <id>
|
|
28
|
+
- type: incident
|
|
29
|
+
- product_slug: <product_slug>
|
|
30
|
+
- pm_slug: <pm_slug>
|
|
31
|
+
- parent_KR: <objective/KR reference OR health:<id> (D59)> <!-- REQUIRED — incidents often anchor health:ops. -->
|
|
32
|
+
- severity: critical | major | minor
|
|
33
|
+
- detected: <YYYY-MM-DD HH:MM UTC>
|
|
34
|
+
- resolved: <YYYY-MM-DD HH:MM UTC>
|
|
35
|
+
- recorded: <YYYY-MM-DD> <!-- within 48h of resolution -->
|
|
36
|
+
|
|
37
|
+
## What happened (timeline)
|
|
38
|
+
<!-- Detection → diagnosis → mitigation → resolution, timestamped. Terse; facts over narrative. -->
|
|
39
|
+
|
|
40
|
+
## Impact
|
|
41
|
+
<!-- Who/what was affected, for how long, what it cost. -->
|
|
42
|
+
|
|
43
|
+
## Root cause
|
|
44
|
+
<!-- The actual cause, not the trigger. "Five whys" depth without the ceremony. -->
|
|
45
|
+
|
|
46
|
+
## Resolution
|
|
47
|
+
<!-- What fixed it — link the PR(s)/commits on branch initiative/<id>. -->
|
|
48
|
+
|
|
49
|
+
## Learnings
|
|
50
|
+
<!-- What this taught us about the system or the harness. Also append to discoveries.md
|
|
51
|
+
(#friction where applicable) — this section is the retro; discoveries is the searchable log. -->
|
|
52
|
+
|
|
53
|
+
## Follow-ups
|
|
54
|
+
<!-- Each REAL follow-up is its own lane initiative (usually feature or chore) — listed here by id,
|
|
55
|
+
not silently absorbed into the incident. Empty is honest if the fix is complete. -->
|
|
56
|
+
- <initiative id or "none">
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Authoring checklist
|
|
62
|
+
|
|
63
|
+
- [ ] Recorded within 48h of resolution (the lane's honesty rule).
|
|
64
|
+
- [ ] `parent_KR` present — incidents anchor like everything else.
|
|
65
|
+
- [ ] Root cause is a cause, not a symptom or a person.
|
|
66
|
+
- [ ] Learnings mirrored to discoveries.md; follow-ups are their own initiatives.
|
|
@@ -5,4 +5,7 @@ to pick concepts, then fetch the file(s) you need.
|
|
|
5
5
|
|
|
6
6
|
- [design-brief-template](design-brief-template.md) — The scaffold for a design brief instance — the context handoff from Claude Code to claude.ai/design (leg 1 of the design loop). Copy into /planning/design/ and fill; presentation-only.
|
|
7
7
|
- [eval-rubric-template](eval-rubric-template.md) — The scaffold for an evaluation rubric instance, including weighted dimensions, partial credit, and the sprint contract. Copy into /planning/evals/ and fill.
|
|
8
|
+
- [incident-record-template](incident-record-template.md) — The incident-lane post-hoc record (D56): timeline, impact, root cause, learnings, follow-ups. Copy into /planning/prd/ as `<id>-incident.md` within 48h of resolution.
|
|
8
9
|
- [prd-template](prd-template.md) — The scaffold for a Product Requirements Document instance. Copy into /planning/prd/ and fill; do not author PRDs from scratch.
|
|
10
|
+
- [spike-brief-template](spike-brief-template.md) — The discovery-lane spec artifact (D56): question + timebox + spend cap + disposition (learned/promoted/discarded). Copy into /planning/prd/ as `<id>-spike.md`.
|
|
11
|
+
- [content-brief-template](content-brief-template.md) — The content-lane spec artifact (D58): audience, message, voice, sourced claims, deliverables + an optional weighted copy rubric. Copy into /planning/prd/ as `<id>-content-brief.md`.
|
|
@@ -14,7 +14,8 @@ same anchoring discipline. Copy the block below into `/planning/prd/<slug>-prd.m
|
|
|
14
14
|
copy is an operational instance and does not live in the OKF bundle.
|
|
15
15
|
|
|
16
16
|
> Usage: replace every `<…>` placeholder. Delete guidance comments (`<!-- … -->`) once filled.
|
|
17
|
-
> Keep section order. An empty out-of-scope list or a missing parent KR
|
|
17
|
+
> Keep section order. An empty out-of-scope list or a missing anchor (parent KR, or `health:<id>`
|
|
18
|
+
> per D59) means the PRD is not ready.
|
|
18
19
|
|
|
19
20
|
---
|
|
20
21
|
|
|
@@ -25,7 +26,7 @@ copy is an operational instance and does not live in the OKF bundle.
|
|
|
25
26
|
- initiative_id: <id>
|
|
26
27
|
- product_slug: <product_slug>
|
|
27
28
|
- pm_slug: <pm_slug>
|
|
28
|
-
- parent_KR: <objective.kr reference> <!-- REQUIRED.
|
|
29
|
+
- parent_KR: <objective.kr reference OR health:<id> (D59)> <!-- REQUIRED. Unanchored work does not proceed. -->
|
|
29
30
|
- status: draft | approved
|
|
30
31
|
- date: <YYYY-MM-DD>
|
|
31
32
|
|
|
@@ -47,6 +48,9 @@ copy is an operational instance and does not live in the OKF bundle.
|
|
|
47
48
|
|
|
48
49
|
## Success criteria (outcomes, not output)
|
|
49
50
|
<!-- Tied to the parent KR. A change in the world, not a list of artifacts produced. -->
|
|
51
|
+
<!-- D57: if a criterion is measurable only AFTER ship, declare it on the initiative as
|
|
52
|
+
outcome_metric + outcome_horizon (create_initiative / initiative frontmatter) — acceptance
|
|
53
|
+
at merge will then NOT be the last word; an Outcome Check disposes it at the horizon. -->
|
|
50
54
|
- <outcome criterion>
|
|
51
55
|
- <outcome criterion>
|
|
52
56
|
|
|
@@ -59,8 +63,12 @@ copy is an operational instance and does not live in the OKF bundle.
|
|
|
59
63
|
- rubric: <link to /planning/evals/<slug>-eval-rubric.md>
|
|
60
64
|
|
|
61
65
|
## Links
|
|
62
|
-
-
|
|
63
|
-
|
|
66
|
+
<!-- Dual-audience rule (D58): ONE situating sentence per link — what it is and why it matters
|
|
67
|
+
here — and quote the parent KR's TEXT, not just its id (health-anchored: name the budget and
|
|
68
|
+
why this work fits its coverage). A stakeholder without the OKF bundle
|
|
69
|
+
must be able to follow every reference. -->
|
|
70
|
+
- parent KR: <link> — *"<the KR text, quoted>"; <why this initiative advances it>*
|
|
71
|
+
- relevant OKF concepts: <link> — *<one situating sentence>*
|
|
64
72
|
```
|
|
65
73
|
|
|
66
74
|
---
|
|
@@ -72,4 +80,5 @@ copy is an operational instance and does not live in the OKF bundle.
|
|
|
72
80
|
- [ ] Non-goals non-empty.
|
|
73
81
|
- [ ] Success criteria are outcomes tied to the KR, not output counts.
|
|
74
82
|
- [ ] Paired eval rubric queued to be written first.
|
|
83
|
+
- [ ] Every link carries one situating sentence; parent-KR text quoted (dual-audience, D58).
|
|
75
84
|
- [ ] Stays at control altitude — specs *what/why*, not application code or the agent's path.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
type: Template
|
|
3
|
+
title: Spike Brief Template
|
|
4
|
+
description: The scaffold for a discovery-lane spike brief (D56) — question, timebox, spend cap, disposition. Copy into /planning/prd/ as <id>-spike.md and set it as the initiative's prd_path; the lighter spec artifact the discovery lane requires instead of a PRD.
|
|
5
|
+
tags: [template, discovery, spike, lanes]
|
|
6
|
+
timestamp: 2026-07-17
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Spike Brief Template
|
|
10
|
+
|
|
11
|
+
**Situating context:** Authored with [D56](/okf/products/pmos/adr/d56-initiative-lanes.md) as the
|
|
12
|
+
discovery lane's spec artifact, resolving the eval-first paradox the SDLC stress test named: a
|
|
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/core/discovery.skill) instantiates
|
|
15
|
+
this template. Copy the block below into `/planning/prd/<id>-spike.md` and fill; the copy is an
|
|
16
|
+
operational instance, not OKF knowledge.
|
|
17
|
+
|
|
18
|
+
> Usage: replace every `<…>` placeholder. An empty question or a missing timebox means the brief is
|
|
19
|
+
> not ready. The disposition section stays open until the spike closes — closing it is mandatory.
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
```markdown
|
|
24
|
+
# Spike Brief: <initiative title>
|
|
25
|
+
|
|
26
|
+
## Identity
|
|
27
|
+
- initiative_id: <id>
|
|
28
|
+
- type: discovery
|
|
29
|
+
- product_slug: <product_slug>
|
|
30
|
+
- pm_slug: <pm_slug>
|
|
31
|
+
- parent_KR: <objective/KR reference OR health:<id> (D59)> <!-- REQUIRED — anchoring holds in every lane. -->
|
|
32
|
+
- status: open | closed
|
|
33
|
+
- date: <YYYY-MM-DD>
|
|
34
|
+
|
|
35
|
+
## Question
|
|
36
|
+
<!-- The ONE question this spike answers. If you can write done-criteria instead, it is not a
|
|
37
|
+
spike — take the feature lane. -->
|
|
38
|
+
|
|
39
|
+
## Why now
|
|
40
|
+
<!-- What decision or initiative is blocked on this answer. -->
|
|
41
|
+
|
|
42
|
+
## Timebox & spend cap
|
|
43
|
+
- timebox: <e.g. 2 days / 3 runs> <!-- REQUIRED. The spike ends when the box ends, answered or not. -->
|
|
44
|
+
- spend_cap: <e.g. token/infra budget, if relevant>
|
|
45
|
+
|
|
46
|
+
## Method sketch
|
|
47
|
+
<!-- How you'll attack it — rough is fine; the path is not graded (D14). Throwaway code is
|
|
48
|
+
expected and stays on the initiative branch. -->
|
|
49
|
+
|
|
50
|
+
## Out of scope
|
|
51
|
+
<!-- What this spike deliberately does NOT try to answer or build. -->
|
|
52
|
+
|
|
53
|
+
## Disposition (fill at close — mandatory)
|
|
54
|
+
- disposition: learned | promoted | discarded
|
|
55
|
+
- evidence: <what was learned, one paragraph — link artifacts/branch>
|
|
56
|
+
- follow-up: <for `promoted`: link the NEW feature-lane initiative + PRD; a spike never silently
|
|
57
|
+
becomes the product. For `discarded`: one line on why.>
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Authoring checklist
|
|
63
|
+
|
|
64
|
+
- [ ] `parent_KR` present — discovery is anchored work, not free exploration.
|
|
65
|
+
- [ ] The question is singular and answerable inside the timebox.
|
|
66
|
+
- [ ] Timebox stated; the spike ends when it ends (inconclusive is a valid, recordable outcome).
|
|
67
|
+
- [ ] Disposition closed before the initiative leaves the board — `promoted` links a real
|
|
68
|
+
feature-lane PRD; prototype code is deleted or explicitly quarantined, never shipped as-is.
|
|
@@ -20,7 +20,11 @@
|
|
|
20
20
|
- **Acceptance Gate (PM):** the human PM merges and disposes — final and non-delegable (D12).
|
|
21
21
|
|
|
22
22
|
## Weighted dimensions (Review Gate)
|
|
23
|
-
Outcome-only (D14); partial credit (D15); anchors 0 / 0.5 / 1.0.
|
|
23
|
+
Outcome-only (D14); partial credit (D15); anchors 0 / 0.5 / 1.0. **Evidence rule:** the verdict
|
|
24
|
+
must cite, per scored dimension, a short verbatim quote or precise pointer (file+line, command
|
|
25
|
+
output) from the evaluated artifact; a dimension without evidence is marked `unverified` and
|
|
26
|
+
scored conservatively (≤ 0.5) — never high on trust (evidence-anchored scoring,
|
|
27
|
+
llm-judge-calibration research).
|
|
24
28
|
|
|
25
29
|
| Dimension | Weight | Score | Wtd |
|
|
26
30
|
|---|---:|---:|---:|
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Health budgets (D59) — closed registry, PM-edited only. parent_kr: health:<id>
|
|
2
|
+
|
|
3
|
+
| id | covers | quarterly cap | review trigger |
|
|
4
|
+
|---|---|---:|---|
|
|
5
|
+
| health:tech | refactors, debt, tooling | 20% | over cap / recurring debt theme |
|
|
6
|
+
| health:security | hardening, rotations, audits | 10% | over cap / security work misfiled under a KR |
|
|
7
|
+
| health:ops | pipelines, deploys, monitoring upkeep | 10% | over cap / an ops gap recurring after its fix |
|
|
@@ -4,9 +4,15 @@
|
|
|
4
4
|
# "anchored" only when:
|
|
5
5
|
# 1. the branch is initiative/<id> (or the PR body declares 'Initiative: <id>'), and
|
|
6
6
|
# 2. pmos/state/initiatives/<id>.md EXISTS in the checkout, and
|
|
7
|
-
# 3. its
|
|
8
|
-
#
|
|
9
|
-
#
|
|
7
|
+
# 3. the file satisfies its LANE's artifact requirements (D56) — `type:` frontmatter picks it:
|
|
8
|
+
# feature (default) -> prd_path + rubric_path set and both files exist (the original contract)
|
|
9
|
+
# discovery -> prd_path = the spike brief, set and exists; rubric optional
|
|
10
|
+
# chore -> a non-empty `intent:` line in the initiative file; no PRD/rubric
|
|
11
|
+
# incident -> prd_path = the incident record, set and exists (post-hoc is sanctioned)
|
|
12
|
+
# content -> prd_path = the content brief, set and exists; rubric recommended (D58)
|
|
13
|
+
# Unknown/missing type = feature: the strictest lane. Lane variation is never a skip.
|
|
14
|
+
# Escape hatch (explicit, logged): the `no-initiative` label — anchor-free trivia ONLY, real work
|
|
15
|
+
# takes a lane (D56).
|
|
10
16
|
# Inputs via env: HEAD_REF, PR_BODY, PR_LABELS. Optional: KIT_ROOT (default: pmos).
|
|
11
17
|
set -euo pipefail
|
|
12
18
|
|
|
@@ -14,9 +20,9 @@ KIT_ROOT="${KIT_ROOT:-pmos}"
|
|
|
14
20
|
fail() { echo "::error::initiative-gate: $*"; echo "❌ initiative gate FAILED — $*"; exit 1; }
|
|
15
21
|
note() { echo "::notice::initiative-gate: $*"; echo "ℹ️ $*"; }
|
|
16
22
|
|
|
17
|
-
# ---- escape hatch (
|
|
23
|
+
# ---- escape hatch (anchor-free trivia only — real work takes a lane) --------
|
|
18
24
|
case " ${PR_LABELS:-} " in
|
|
19
|
-
*" no-initiative "*) note "PR labelled 'no-initiative' — gate skipped (
|
|
25
|
+
*" no-initiative "*) note "PR labelled 'no-initiative' — gate skipped (anchor-free trivia only; a chore/incident/discovery belongs in its lane, D56)."; exit 0 ;;
|
|
20
26
|
esac
|
|
21
27
|
|
|
22
28
|
# ---- fail closed if the state layer is missing ------------------------------
|
|
@@ -32,7 +38,7 @@ if [ -z "$id" ]; then
|
|
|
32
38
|
# below ever prints (silent exit 1 on the most common failure path).
|
|
33
39
|
id="$(printf '%s' "${PR_BODY:-}" | { grep -ioE 'Initiative:[[:space:]]*[A-Za-z0-9._-]+' || true; } | head -1 | sed -E 's/.*:[[:space:]]*//')"
|
|
34
40
|
fi
|
|
35
|
-
[ -n "$id" ] || fail "Not anchored. Name the branch 'initiative/<id>' or add 'Initiative: <id>' to the PR body, or label the PR 'no-initiative' for
|
|
41
|
+
[ -n "$id" ] || fail "Not anchored. Name the branch 'initiative/<id>' or add 'Initiative: <id>' to the PR body, or label the PR 'no-initiative' for anchor-free trivia. See $KIT_ROOT/AGENTS.md."
|
|
36
42
|
|
|
37
43
|
# ---- validate the id (also guards the path interpolation below) -------------
|
|
38
44
|
printf '%s' "$id" | grep -qE '^[a-z0-9][a-z0-9._-]*$' || fail "Initiative id '$id' is malformed (expected ^[a-z0-9][a-z0-9._-]*$)."
|
|
@@ -47,10 +53,43 @@ frontval() { # frontval <key> — first 'key: value' line in the file, value tri
|
|
|
47
53
|
}
|
|
48
54
|
prd="$(frontval prd_path)"
|
|
49
55
|
rubric="$(frontval rubric_path)"
|
|
56
|
+
lane="$(frontval type)"
|
|
57
|
+
intent="$(frontval intent)"
|
|
58
|
+
anchor="$(frontval parent_kr)"
|
|
59
|
+
[ -n "$lane" ] || lane="feature"
|
|
50
60
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
[ -
|
|
61
|
+
# ---- kernel invariant: an initiative anchors (D21/D59) -----------------------
|
|
62
|
+
# Hosted parity: create_initiative rejects a missing parent_kr at row creation; in the kit the
|
|
63
|
+
# file IS the row, so the gate is where the same rule lands. A KR or health:<id> — never blank.
|
|
64
|
+
[ -n "$anchor" ] || fail "initiative '$id' has no parent_kr in $init_file — anchor it to a KR, or to a declared health budget (parent_kr: health:<id> from $KIT_ROOT/planning/okrs/health-budgets.md, D59). Unanchored work is rejected (D21)."
|
|
55
65
|
|
|
56
|
-
|
|
66
|
+
# ---- lane-parameterized artifact checks (D56) --------------------------------
|
|
67
|
+
# The kernel invariants already held above (anchored record; the human merge stays the Acceptance
|
|
68
|
+
# Gate); what varies per lane is the spec/eval artifact. In EVERY lane a rubric_path that is set
|
|
69
|
+
# must point at a real file — optional never means dangling.
|
|
70
|
+
case "$lane" in
|
|
71
|
+
discovery|incident|content)
|
|
72
|
+
# if-form, not `[ … ] && …`: under set -e a false test as a bare && would kill the script.
|
|
73
|
+
what="spike brief"
|
|
74
|
+
if [ "$lane" = incident ]; then what="incident record (post-hoc within 48h is the sanctioned lane rule)"; fi
|
|
75
|
+
if [ "$lane" = content ]; then what="content brief (rubric recommended, not required — D58)"; fi
|
|
76
|
+
[ -n "$prd" ] || fail "initiative '$id' (type $lane) has no prd_path in $init_file — set it to the $what (see $KIT_ROOT/okf/core/templates/)."
|
|
77
|
+
[ -f "$prd" ] || fail "prd_path '$prd' (from $init_file, type $lane) does not exist in the repo."
|
|
78
|
+
[ -z "$rubric" ] || [ -f "$rubric" ] || fail "rubric_path '$rubric' (from $init_file) does not exist in the repo (a rubric is optional in the $lane lane — set the path only to a real file)."
|
|
79
|
+
echo "✅ initiative gate PASSED — '$id' ($lane lane): $init_file exists, $what ($prd) present."
|
|
80
|
+
;;
|
|
81
|
+
chore)
|
|
82
|
+
[ -n "$intent" ] || fail "initiative '$id' (type chore) has no intent in $init_file — the chore lane requires a one-paragraph 'intent:' line. Anchor-free trivia takes the no-initiative label; real work states its intent."
|
|
83
|
+
[ -z "$prd" ] || [ -f "$prd" ] || fail "prd_path '$prd' (from $init_file) does not exist in the repo (not required in the chore lane — set the path only to a real file)."
|
|
84
|
+
[ -z "$rubric" ] || [ -f "$rubric" ] || fail "rubric_path '$rubric' (from $init_file) does not exist in the repo (not required in the chore lane — set the path only to a real file)."
|
|
85
|
+
echo "✅ initiative gate PASSED — '$id' (chore lane): $init_file exists, intent stated."
|
|
86
|
+
;;
|
|
87
|
+
*)
|
|
88
|
+
[ "$lane" = "feature" ] || note "unknown initiative type '$lane' on '$id' — enforcing the feature lane (strictest, fail-closed). The enum widens only with a ratified lane decision (D56)."
|
|
89
|
+
[ -n "$prd" ] || fail "initiative '$id' has no prd_path in $init_file. Author a PRD and set prd_path. Feature lane: PRD + rubric before build (eval-first, D13); other work shapes take their lane (D56)."
|
|
90
|
+
[ -f "$prd" ] || fail "prd_path '$prd' (from $init_file) does not exist in the repo."
|
|
91
|
+
[ -n "$rubric" ] || fail "initiative '$id' has no rubric_path in $init_file. Author an eval rubric and set rubric_path."
|
|
92
|
+
[ -f "$rubric" ] || fail "rubric_path '$rubric' (from $init_file) does not exist in the repo."
|
|
93
|
+
echo "✅ initiative gate PASSED — '$id' (feature lane): $init_file exists, PRD ($prd) and rubric ($rubric) present."
|
|
94
|
+
;;
|
|
95
|
+
esac
|
|
@@ -13,7 +13,7 @@ Reads pmos/state/{runs,evals,acceptances}.jsonl and prints:
|
|
|
13
13
|
|
|
14
14
|
No network, no secrets. Exit 0 always (a reporting tool, not a gate). Anti-optimism: reds print first.
|
|
15
15
|
"""
|
|
16
|
-
import json, os, sys
|
|
16
|
+
import datetime, glob, json, os, re, sys
|
|
17
17
|
|
|
18
18
|
KIT = os.environ.get("KIT_ROOT") or os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
|
19
19
|
STATE = os.path.join(KIT, "state")
|
|
@@ -97,3 +97,48 @@ else:
|
|
|
97
97
|
f"TPR {'n/a' if tpr is None else f'{tpr:.0%}'}, "
|
|
98
98
|
f"TNR {'n/a — no PM fails seen (leniency blind spot)' if tnr is None else f'{tnr:.0%}'}, "
|
|
99
99
|
f"kappa {'undefined' if k is None else f'{k:.2f}'} (target >= 0.60 — verdict ADVISORY below it, D40)")
|
|
100
|
+
|
|
101
|
+
# --- D57 outcome checks (accepted-but-unvalidated; due prints red) -----------
|
|
102
|
+
# Declarations live in initiative frontmatter (outcome_metric / outcome_horizon); dispositions are
|
|
103
|
+
# appended to state/outcome_checks.jsonl ({initiative_id, disposition, evidence, date}). The latest
|
|
104
|
+
# line per initiative wins; history is never rewritten. Auto-validation is prohibited (D57) — this
|
|
105
|
+
# section only REPORTS; recording a disposition is always the PM's word.
|
|
106
|
+
checks = load("outcome_checks.jsonl")
|
|
107
|
+
latest_disp = {}
|
|
108
|
+
for c in checks:
|
|
109
|
+
if c.get("initiative_id"):
|
|
110
|
+
latest_disp[c["initiative_id"]] = str(c.get("disposition", "")).lower()
|
|
111
|
+
|
|
112
|
+
declared = []
|
|
113
|
+
for path in sorted(glob.glob(os.path.join(STATE, "initiatives", "*.md"))):
|
|
114
|
+
fm = {}
|
|
115
|
+
for line in open(path, encoding="utf-8"):
|
|
116
|
+
m = re.match(r"^([a-z_]+):\s*(.+?)\s*$", line)
|
|
117
|
+
if m:
|
|
118
|
+
fm.setdefault(m.group(1), m.group(2))
|
|
119
|
+
elif line.strip() and not line.startswith("---"):
|
|
120
|
+
break # frontmatter is the LEADING block only — body text cannot fake a declaration
|
|
121
|
+
if not fm.get("id"):
|
|
122
|
+
if fm.get("outcome_metric"):
|
|
123
|
+
print(f"⚠ {os.path.basename(path)} declares an outcome but has no id: — it can never be disposed; fix the frontmatter")
|
|
124
|
+
continue
|
|
125
|
+
if fm.get("outcome_metric"):
|
|
126
|
+
declared.append(fm)
|
|
127
|
+
|
|
128
|
+
if declared:
|
|
129
|
+
today = datetime.date.today().isoformat()
|
|
130
|
+
undisposed = [f for f in declared if f.get("id") not in latest_disp]
|
|
131
|
+
# a missing horizon errs RED (counts as due): a bet with no judgment date can never come due
|
|
132
|
+
# by itself — horizon-gaming-by-omission (mirrors the hosted CHECK requiring a horizon).
|
|
133
|
+
due = [f for f in undisposed if str(f.get("outcome_horizon", "0000-00-00")) < today]
|
|
134
|
+
pending = [f for f in undisposed if f not in due]
|
|
135
|
+
print()
|
|
136
|
+
if due:
|
|
137
|
+
print(f"🔴 D57 outcome checks DUE ({len(due)}) — accepted bets past horizon, never validated:")
|
|
138
|
+
for f in due:
|
|
139
|
+
print(f" - {f.get('id')}: \"{f.get('outcome_metric')}\" (horizon {f.get('outcome_horizon', '?')})"
|
|
140
|
+
" -> dispose validated|refuted|inconclusive to state/outcome_checks.jsonl")
|
|
141
|
+
refuted = sum(1 for v in latest_disp.values() if v == "refuted")
|
|
142
|
+
print(f"D57 outcomes: {len(declared)} declared — {len(pending)} pending, {len(due)} due, "
|
|
143
|
+
f"{len(latest_disp)} disposed"
|
|
144
|
+
+ (f" ({refuted} refuted — recorded, not hidden)" if refuted else ""))
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# PMOS portable kit — pmos-log.sh (D58): the human builder's one-command state writer.
|
|
3
|
+
# Appends ONE validated JSON line to the right pmos/state/ file. Mirrors the hosted rules:
|
|
4
|
+
# verdict enum (save_eval), pm_label enum (record_acceptance), disposition enum + the D57
|
|
5
|
+
# evidence-required-for-terminal rule (record_outcome_check). Append-only by construction — this
|
|
6
|
+
# script only ever `>>`s; a correction is a NEW record. No dependencies beyond POSIX + python3
|
|
7
|
+
# (used solely for safe JSON string escaping).
|
|
8
|
+
#
|
|
9
|
+
# pmos-log.sh run <run_id> <initiative_id> [human_corrections]
|
|
10
|
+
# pmos-log.sh eval <run_id> <initiative_id> <score 0..1> <PASS|PASS-WITH-FINDINGS|FAIL> <rubric_path>
|
|
11
|
+
# pmos-log.sh accept <run_id> <initiative_id> <pass|fail> [note]
|
|
12
|
+
# pmos-log.sh outcome <initiative_id> <validated|refuted|inconclusive> [evidence]
|
|
13
|
+
# pmos-log.sh friction <note...>
|
|
14
|
+
set -euo pipefail
|
|
15
|
+
|
|
16
|
+
KIT="${KIT_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
|
17
|
+
STATE="$KIT/state"
|
|
18
|
+
fail() { echo "pmos-log: ERROR — $*" >&2; exit 1; }
|
|
19
|
+
[ -d "$STATE" ] || fail "state dir not found at $STATE (run from a vendored kit, or set KIT_ROOT)."
|
|
20
|
+
|
|
21
|
+
js() { python3 -c 'import json,sys; print(json.dumps(sys.argv[1]))' "$1"; } # safe JSON string
|
|
22
|
+
nonempty() { # nonempty <label> <value> — ids must not be blank (hosted tools reject them too)
|
|
23
|
+
[ -n "$(printf '%s' "$2" | tr -d '[:space:]')" ] || fail "$1 must not be empty."
|
|
24
|
+
}
|
|
25
|
+
today="$(date -u +%Y-%m-%d)"
|
|
26
|
+
|
|
27
|
+
append() { # append <file> <json-line>
|
|
28
|
+
printf '%s\n' "$2" >> "$STATE/$1"
|
|
29
|
+
echo "✅ appended to state/$1:"
|
|
30
|
+
echo " $2"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
cmd="${1:-}"; shift || true
|
|
34
|
+
case "$cmd" in
|
|
35
|
+
run)
|
|
36
|
+
[ $# -ge 2 ] || fail "usage: run <run_id> <initiative_id> [human_corrections]"
|
|
37
|
+
nonempty run_id "$1"; nonempty initiative_id "$2"
|
|
38
|
+
corr="${3:-0}"
|
|
39
|
+
printf '%s' "$corr" | grep -qE '^[0-9]+$' || fail "human_corrections must be a non-negative integer, got '$corr'."
|
|
40
|
+
append runs.jsonl "{\"run_id\":$(js "$1"),\"initiative_id\":$(js "$2"),\"human_corrections\":$corr,\"date\":\"$today\"}"
|
|
41
|
+
;;
|
|
42
|
+
eval)
|
|
43
|
+
[ $# -ge 5 ] || fail "usage: eval <run_id> <initiative_id> <score 0..1> <PASS|PASS-WITH-FINDINGS|FAIL> <rubric_path>"
|
|
44
|
+
nonempty run_id "$1"; nonempty initiative_id "$2"
|
|
45
|
+
printf '%s' "$3" | grep -qE '^(0?\.[0-9]+|0|1(\.0+)?)$' || fail "score must be 0..1 written as 0, 1, N.N or .N — got '$3'."
|
|
46
|
+
case "$4" in PASS|PASS-WITH-FINDINGS|FAIL) ;; *) fail "verdict must be PASS | PASS-WITH-FINDINGS | FAIL, got '$4'." ;; esac
|
|
47
|
+
score="$3"; case "$score" in .*) score="0$score" ;; esac # ".5" -> "0.5": bare .5 is not valid JSON
|
|
48
|
+
[ -f "$KIT/$5" ] || [ -f "$5" ] || fail "rubric_path '$5' not found (relative to the kit or the repo)."
|
|
49
|
+
append evals.jsonl "{\"run_id\":$(js "$1"),\"initiative_id\":$(js "$2"),\"score\":$score,\"verdict\":$(js "$4"),\"rubric_path\":$(js "$5"),\"date\":\"$today\"}"
|
|
50
|
+
;;
|
|
51
|
+
accept)
|
|
52
|
+
[ $# -ge 3 ] || fail "usage: accept <run_id> <initiative_id> <pass|fail> [note]"
|
|
53
|
+
nonempty run_id "$1"; nonempty initiative_id "$2"
|
|
54
|
+
case "$3" in pass|fail) ;; *) fail "pm_label must be pass | fail, got '$3'." ;; esac
|
|
55
|
+
acc=true; [ "$3" = fail ] && acc=false
|
|
56
|
+
append acceptances.jsonl "{\"run_id\":$(js "$1"),\"initiative_id\":$(js "$2"),\"accepted\":$acc,\"pm_label\":$(js "$3"),\"note\":$(js "${4:-}"),\"date\":\"$today\"}"
|
|
57
|
+
;;
|
|
58
|
+
outcome)
|
|
59
|
+
[ $# -ge 2 ] || fail "usage: outcome <initiative_id> <validated|refuted|inconclusive> [evidence]"
|
|
60
|
+
nonempty initiative_id "$1"
|
|
61
|
+
case "$2" in validated|refuted|inconclusive) ;; *) fail "disposition must be validated | refuted | inconclusive, got '$2'." ;; esac
|
|
62
|
+
ev="${3:-}"
|
|
63
|
+
if [ "$2" != "inconclusive" ] && [ -z "$(printf '%s' "$ev" | tr -d '[:space:]')" ]; then
|
|
64
|
+
fail "evidence is REQUIRED for a validated/refuted disposition (D57: no rubber-stamps; use 'inconclusive' for an honest non-answer)."
|
|
65
|
+
fi
|
|
66
|
+
append outcome_checks.jsonl "{\"initiative_id\":$(js "$1"),\"disposition\":$(js "$2"),\"evidence\":$(js "$ev"),\"date\":\"$today\"}"
|
|
67
|
+
;;
|
|
68
|
+
friction)
|
|
69
|
+
[ $# -ge 1 ] || fail "usage: friction <note...>"
|
|
70
|
+
printf -- '- %s %s #friction\n' "$today" "$*" >> "$STATE/discoveries.md"
|
|
71
|
+
echo "✅ appended to state/discoveries.md: - $today $* #friction"
|
|
72
|
+
;;
|
|
73
|
+
*)
|
|
74
|
+
fail "unknown command '${cmd:-}' — one of: run | eval | accept | outcome | friction (see AGENTS-HUMAN.md)."
|
|
75
|
+
;;
|
|
76
|
+
esac
|
|
@@ -62,6 +62,97 @@ Example reference from a body: *"For multi-product PRDs, follow `prd-multiproduc
|
|
|
62
62
|
Use Level 3 to keep Level 2 focused. If a section applies to <20% of runs, it is a Level 3
|
|
63
63
|
candidate.
|
|
64
64
|
|
|
65
|
+
## Quality — what makes a skill good (not just well-formed)
|
|
66
|
+
|
|
67
|
+
The three levels are *structure*. This section is the *bar*. A skill can be perfectly structured and
|
|
68
|
+
still be junk; these are the tests that keep it worth its context.
|
|
69
|
+
|
|
70
|
+
### Predictability is the root virtue
|
|
71
|
+
|
|
72
|
+
A skill exists to **wring determinism out of a stochastic system**: it makes the agent take the same
|
|
73
|
+
*process* every run — not produce the same *output* (an evaluator should predictably *judge*; its
|
|
74
|
+
words vary, its behavior doesn't). Every rule below serves predictability; cost and readability are
|
|
75
|
+
symptoms of it, not rival goals. When unsure whether a line belongs, ask: *does it make the agent's
|
|
76
|
+
process more repeatable?* If not, cut it.
|
|
77
|
+
|
|
78
|
+
### Per-step completion criteria
|
|
79
|
+
|
|
80
|
+
Every procedural step ends on a **checkable done-condition** — the agent must be able to tell done
|
|
81
|
+
from not-done. Where it matters, make the criterion **exhaustive**, not vague: *"every living mirror
|
|
82
|
+
of the rule accounted for"*, not *"produce a change list"*. A fuzzy criterion invites **premature
|
|
83
|
+
completion** — the agent stops when it feels done, not when it is. The
|
|
84
|
+
[semantic-pass rule](/build-skills/evaluator.skill) ("enumerate the live-doc set, then read each for
|
|
85
|
+
meaning — a grep is evidence, never done") is the exemplar this generalizes: it exists because a
|
|
86
|
+
vague "sweep it" criterion let real stale mirrors survive five "clean" passes.
|
|
87
|
+
|
|
88
|
+
### Pruning — hunt these named failure modes
|
|
89
|
+
|
|
90
|
+
A skill without a pruning discipline only ever grows. Hunt, sentence by sentence:
|
|
91
|
+
|
|
92
|
+
- **No-op** — a line the model already obeys by default, so you pay context to say nothing. Test:
|
|
93
|
+
does it change behavior vs. the default? *"Be thorough"* to an already-thorough agent is a no-op;
|
|
94
|
+
the fix is a stronger word (a leading word), not more words.
|
|
95
|
+
- **Sediment** — stale layers that settle because adding feels safe and removing feels risky. The
|
|
96
|
+
default fate of any skill without this discipline.
|
|
97
|
+
- **Sprawl** — simply too long, even when every line is live. Cure: disclose reference to Level 3;
|
|
98
|
+
split by branch.
|
|
99
|
+
- **Duplication** — the same meaning in more than one place. Costs tokens and maintenance, and
|
|
100
|
+
inflates a meaning's apparent importance. Keep one source of truth.
|
|
101
|
+
- **Negation** — steering by prohibition backfires: *"don't think of an elephant"* names the
|
|
102
|
+
elephant. State the **positive** target behavior; keep a prohibition only as a hard guardrail you
|
|
103
|
+
can't phrase positively, and pair it with what to do instead.
|
|
104
|
+
|
|
105
|
+
**This is the checklist the quarterly harness-assumption review runs** (DECISIONS.md cadence): the
|
|
106
|
+
review's question — *what scaffolding can be removed because the model now handles it natively?* — is
|
|
107
|
+
the no-op and sediment hunt applied across every skill.
|
|
108
|
+
|
|
109
|
+
### Leading words
|
|
110
|
+
|
|
111
|
+
A **leading word** is a compact concept already in the model's pretraining that anchors a whole
|
|
112
|
+
region of behavior in one token — *tight* (loop), *watermelon*, *default-to-refute*, *silent drift*,
|
|
113
|
+
*Summary Gate*. Repeated across a skill, it accumulates a distributed definition and recruits priors
|
|
114
|
+
the model already holds. A triad spelled out at three sites (*"fast, deterministic, low-overhead"*)
|
|
115
|
+
is begging to **collapse** into one word (*tight*). You win twice: fewer tokens, and a sharper hook
|
|
116
|
+
the agent hangs its behavior on. Hunt every skill for restatements a leading word retires.
|
|
117
|
+
|
|
118
|
+
### Invocation (advisory)
|
|
119
|
+
|
|
120
|
+
The Agent-Skills world splits skills by *who can invoke them* — **model-invoked** (description always
|
|
121
|
+
loaded, agent can auto-fire it, pays permanent context load) vs **user-invoked** (invisible to the
|
|
122
|
+
agent, human-only, zero context load). PMOS's model differs — skills are **retrieved on demand**
|
|
123
|
+
(see the retrieval rationale under *Ecosystem compatibility* below), so Level-1 frontmatter is the
|
|
124
|
+
always-available trigger and there is no auto-fire to gate. Treat the axis as an authoring lens —
|
|
125
|
+
*is this skill's description earning its keep as an always-available trigger?* — **not** a required
|
|
126
|
+
field. (A formal `invocation:` key is a deferred registry question, not part of this format.)
|
|
127
|
+
|
|
128
|
+
## Ecosystem compatibility — the Agent Skills standard
|
|
129
|
+
|
|
130
|
+
The open **Agent Skills standard** (Claude Code, Codex, and other harnesses) packages a skill as a
|
|
131
|
+
**directory** `<skill-name>/SKILL.md` with YAML frontmatter (`name`, `description`, optionally
|
|
132
|
+
`license`, `allowed-tools`) plus bundled sub-files. It **validates PMOS's three-level disclosure** —
|
|
133
|
+
same frontmatter-as-trigger, body-as-procedure, sub-files-on-demand shape.
|
|
134
|
+
|
|
135
|
+
PMOS **diverges** deliberately: skills are flat `<name>.skill` files, **not** `<name>/SKILL.md`
|
|
136
|
+
directories. The rationale is PMOS's retrieval model — skills are fetched on demand via `get_skill`
|
|
137
|
+
or referenced from AGENTS.md, **not** auto-discovered by a harness scanning for `SKILL.md`, so the
|
|
138
|
+
directory ceremony buys nothing here. The **mapping is lossless**, which is what keeps the divergence
|
|
139
|
+
cheap:
|
|
140
|
+
|
|
141
|
+
- a `<name>.skill` file **≅** a standard `SKILL.md` body;
|
|
142
|
+
- PMOS's required `name` + `description` frontmatter is **already** the standard's required pair;
|
|
143
|
+
- Level-3 sub-files **≅** the standard's bundled files.
|
|
144
|
+
|
|
145
|
+
(One nuance for the follow-on below: PMOS's *optional* D49 registry keys —
|
|
146
|
+
`version`/`owner`/`risk`/`category`/`scope` — are top-level custom keys; the standard would expect
|
|
147
|
+
producer-defined extensions under a `metadata:` map. The **core** shape is lossless; relocating the
|
|
148
|
+
registry keys belongs to the native-format packaging follow-on, not this flat form.)
|
|
149
|
+
|
|
150
|
+
**Named follow-on (not built here):** shipping the **kit** in native Agent-Skills directory format so
|
|
151
|
+
a stranger's Claude Code / Codex harness auto-discovers PMOS skills. It touches `build-kit.sh`,
|
|
152
|
+
`get_skill`, `gen_skills_seed.sh`, and every skill path, so it is its own initiative — revisit when
|
|
153
|
+
native-discovery demand in kit repos is proven. (This mirrors the mattpocock/skills plugin decision:
|
|
154
|
+
keep the editable flat-file form, add a native-package path only when the demand is real.)
|
|
155
|
+
|
|
65
156
|
## File naming
|
|
66
157
|
|
|
67
158
|
- Skill files use the `.skill` extension: `prd.skill`, `eval-rubric.skill`.
|
|
@@ -86,4 +177,15 @@ A skill is well-formed when:
|
|
|
86
177
|
4. The body (Level 2) carries the full procedure and is self-sufficient for the common case.
|
|
87
178
|
5. Edge cases are pushed to Level 3 sub-files and referenced by filename.
|
|
88
179
|
|
|
89
|
-
|
|
180
|
+
**Quality bar** (the "good", not just "well-formed" — see Quality above):
|
|
181
|
+
|
|
182
|
+
6. **Predictability served** — every line makes the agent's *process* more repeatable; cut what
|
|
183
|
+
doesn't.
|
|
184
|
+
7. **Per-step completion criteria** — each procedural step ends on a checkable (and where it matters
|
|
185
|
+
exhaustive) done-condition, not a vague "do X".
|
|
186
|
+
8. **Pruned** — no no-ops, no sediment, no duplication; prohibitions phrased as positive targets;
|
|
187
|
+
sprawl disclosed to Level 3.
|
|
188
|
+
9. **Leading words** — restated qualities collapsed into pretrained tokens where one earns its keep.
|
|
189
|
+
|
|
190
|
+
All seed skills are authored against this checklist; the quarterly harness-assumption review re-runs
|
|
191
|
+
items 6–9 across every skill.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: copywriting
|
|
3
|
+
description: Produce marketing/UX/product copy through the content lane (D58) — content brief first, then draft against audience/message/voice, judged on audience-fit, clarity, voice, and claim accuracy. Use for landing pages, UX microcopy, release notes, docs intros, campaign text; the first domain skill.
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
owner: wawan
|
|
6
|
+
risk: low
|
|
7
|
+
category: domain
|
|
8
|
+
scope: read:okf, write:planning/prd
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# copywriting — Produce copy through the content lane
|
|
12
|
+
|
|
13
|
+
The first `/skills/domains/` skill: how copy work runs through PMOS's **content lane**
|
|
14
|
+
([D58](/okf/products/pmos/adr/d58-builder-profiles.md), on the
|
|
15
|
+
[D56](/okf/products/pmos/adr/d56-initiative-lanes.md) lane substrate). Copy is real, anchored work
|
|
16
|
+
— never the `no-initiative` hatch — but it iterates fast, so the lane contracts on a **brief**,
|
|
17
|
+
and a rubric is *recommended, not required* (a required one recreates fake-rubric pressure).
|
|
18
|
+
|
|
19
|
+
> **Format:** three-level progressive disclosure ([SKILL-FORMAT](/skills/core/SKILL-FORMAT.md)).
|
|
20
|
+
|
|
21
|
+
## When to use vs. not
|
|
22
|
+
|
|
23
|
+
- **Use** for deliverable text: landing/marketing pages, UX microcopy, release notes, docs
|
|
24
|
+
introductions, campaign/launch copy.
|
|
25
|
+
- **Do not use** for PRDs/specs (prd skill), OKF knowledge (source-to-concept), or code comments.
|
|
26
|
+
- Strategy-of-the-message questions ("should we even say this?") belong to the PM/strategic-brief
|
|
27
|
+
altitude — the brief records the answer; this skill executes it.
|
|
28
|
+
|
|
29
|
+
## Procedure
|
|
30
|
+
|
|
31
|
+
1. **Open a content initiative** (`type: content`, anchored like everything else — a KR, or a
|
|
32
|
+
declared health budget `health:<id>` (D59)).
|
|
33
|
+
2. **Write the content brief** from
|
|
34
|
+
[content-brief-template](/okf/core/templates/content-brief-template.md) into
|
|
35
|
+
`/planning/prd/<id>-content-brief.md`; set it as `prd_path` (the gate requires it). The PM
|
|
36
|
+
approves the brief — that approval is the lane's contract.
|
|
37
|
+
3. **Draft against the brief**: one variant per stated audience/placement; every factual claim
|
|
38
|
+
traceable to a source named in the brief (claim accuracy is the non-negotiable dimension —
|
|
39
|
+
wrong claims in public copy are incidents, not nits).
|
|
40
|
+
4. **Judge it.** Recommended: a small weighted rubric (template section in the brief) scoring
|
|
41
|
+
**audience-fit / clarity / voice / claim accuracy**; minimum bar without one: the PM reads the
|
|
42
|
+
draft against the brief line-by-line at the Acceptance Gate.
|
|
43
|
+
5. **Ship and record** as usual (run log; merge = Acceptance). Reusable phrasing/voice learnings
|
|
44
|
+
go to discoveries; a durable voice guide graduates into OKF as a concept.
|
|
45
|
+
|
|
46
|
+
## Quality bar
|
|
47
|
+
|
|
48
|
+
Brief approved before drafting; every claim sourced; each audience/placement served by a variant
|
|
49
|
+
(or its absence stated); no invented product facts; the voice section of the brief is followed or
|
|
50
|
+
the deviation is argued in the PR.
|
|
51
|
+
|
|
52
|
+
## Level 3 sub-cases
|
|
53
|
+
|
|
54
|
+
- None yet. (Localization campaigns and A/B copy variants: author `copywriting-variants.md` on
|
|
55
|
+
first need — variant outcomes belong to D57 outcome checks.)
|