create-pmos 0.7.2 → 0.8.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.
@@ -1,162 +0,0 @@
1
- /* finding-contract — what a review finding must be, and what the PM may do with it.
2
- * (workspace-review-and-edit · R3/R7)
3
- *
4
- * Used on BOTH sides, like preview-kind:
5
- * - server.js requires it to validate a reviewer skill's output before it reaches the client
6
- * - the browser loads it as /lib/finding-contract.js so the renderer asks the SAME question
7
- * so the contract, the endpoint and the UI can never disagree about what is applyable.
8
- *
9
- * The one security-critical function is isApplyable(). It decides whether an Apply control
10
- * exists at all. R7's rule is that a proposal which would close a recorded open question or
11
- * overwrite a PM decision is surfaced as CONTEXT with no Apply affordance — the absence of the
12
- * control is the mechanism, and a warning label is explicitly not a substitute. The rubric makes
13
- * this a hard floor (dim 2 must = 1: zero Apply controls ever rendered for a withheld finding),
14
- * so this boolean is the whole containment story and it is unit-tested directly.
15
- *
16
- * The second load-bearing rule is self_undetectable (R3). A review that emits lint findings
17
- * becomes a linter the PM stops reading, and "judgement-grade" is not enforceable by inspecting
18
- * the finding's text. So the contract inverts it: every finding must NAME what the artifact
19
- * cannot see about itself. A reviewer that cannot fill that line has not found this class of
20
- * defect, and the finding is rejected structurally rather than by taste.
21
- */
22
- 'use strict';
23
- (function (root, factory) {
24
- const api = factory();
25
- if (typeof module !== 'undefined' && module.exports) module.exports = api;
26
- else root.FindingContract = api;
27
- })(typeof self !== 'undefined' ? self : this, function () {
28
-
29
- /* Finding kinds the review must NOT emit (R3). These are owned by the Frontmatter Gate and the
30
- * linters; re-reporting them here is how counsel decays into noise. Declared by the reviewer
31
- * and rejected on sight — a blocklist catches the honest mistake, and self_undetectable below
32
- * catches the dishonest one. */
33
- const MECHANICAL_KINDS = new Set([
34
- 'frontmatter', 'schema', 'link', 'broken-link', 'missing-path', 'path',
35
- 'string-match', 'regex', 'count', 'occurrence', 'spelling', 'typo', 'formatting', 'lint',
36
- ]);
37
-
38
- const isStr = (v) => typeof v === 'string' && v.trim().length > 0;
39
-
40
- /** Reject a finding that a linter should have owned (R3). Kind is declared, not sniffed. */
41
- function isMechanical(f) {
42
- return !!f && isStr(f.kind) && MECHANICAL_KINDS.has(f.kind.trim().toLowerCase());
43
- }
44
-
45
- /* THE guardrail (R7). An Apply control may exist only when the finding is not withheld AND
46
- * actually carries a change to make. Both halves matter: `withheld` is the PM-decision guard,
47
- * and a null proposal is the context class (nothing to apply). Anything unexpected — a missing
48
- * field, a malformed proposal, a non-boolean withheld — resolves to NOT applyable. This
49
- * function fails closed on purpose; an unparseable finding must never grow a button. */
50
- function isApplyable(f) {
51
- if (!f || typeof f !== 'object') return false;
52
- if (f.withheld === true) return false;
53
- if (f.withheld !== false) return false; // must be explicitly declared, never inferred
54
- const p = f.proposal;
55
- if (!p || typeof p !== 'object') return false;
56
- return isStr(p.before) || isStr(p.after); // a pure insertion has no `before`
57
- }
58
-
59
- /** Validate one finding against the contract. Returns { ok, errors[] } — never throws. */
60
- function validateFinding(f) {
61
- const e = [];
62
- if (!f || typeof f !== 'object') return { ok: false, errors: ['finding is not an object'] };
63
-
64
- if (!isStr(f.title)) e.push('title is required');
65
- if (!isStr(f.file)) e.push('file is required');
66
- if (!isStr(f.argument)) e.push('argument is required — a finding without reasoning is an assertion');
67
-
68
- // R3: name what the artifact cannot see about itself, or this is not the class of defect
69
- // this review exists to surface.
70
- if (!isStr(f.self_undetectable)) {
71
- e.push('self_undetectable is required — name what this artifact cannot detect about itself (R3)');
72
- }
73
- if (isMechanical(f)) {
74
- e.push(`kind '${f.kind}' is mechanically findable and belongs to lint, not to this review (R3)`);
75
- }
76
-
77
- // Evidence is file:line, plural, and cited — an un-sourced finding is a guess.
78
- if (!Array.isArray(f.evidence) || f.evidence.length === 0) {
79
- e.push('evidence[] is required — at least one file:line citation');
80
- } else {
81
- f.evidence.forEach((ev, i) => {
82
- if (!ev || typeof ev !== 'object') { e.push(`evidence[${i}] is not an object`); return; }
83
- if (!isStr(ev.file)) e.push(`evidence[${i}].file is required`);
84
- if (!Number.isInteger(ev.line) || ev.line < 1) e.push(`evidence[${i}].line must be a positive integer`);
85
- });
86
- }
87
-
88
- // withheld is declared, never inferred — the containment guard must be explicit (R7).
89
- if (typeof f.withheld !== 'boolean') {
90
- e.push('withheld must be an explicit boolean (R7) — it is never inferred');
91
- }
92
- // A withheld finding carries no proposal at all. Carrying one invites a later refactor to
93
- // "just render it disabled", which is exactly the failure dim 2 forbids.
94
- if (f.withheld === true && f.proposal != null) {
95
- e.push('a withheld finding must carry proposal: null — it has no change to offer (R7)');
96
- }
97
- if (f.withheld === true && !isStr(f.withheld_reason)) {
98
- e.push('withheld_reason is required — say why this is yours to settle (R7)');
99
- }
100
-
101
- if (f.proposal != null) {
102
- const p = f.proposal;
103
- if (typeof p !== 'object') e.push('proposal must be an object or null');
104
- else {
105
- if (p.before != null && typeof p.before !== 'string') e.push('proposal.before must be a string or absent');
106
- if (!isStr(p.after) && !isStr(p.before)) e.push('proposal must change something (before and/or after)');
107
- }
108
- }
109
-
110
- return { ok: e.length === 0, errors: e };
111
- }
112
-
113
- /** Validate a whole review payload. One bad finding fails the review — partial trust is worse
114
- * than none on a surface whose only job is to be believed. */
115
- function validateReview(r) {
116
- const e = [];
117
- if (!r || typeof r !== 'object') return { ok: false, errors: ['review is not an object'] };
118
- if (!Array.isArray(r.read) || r.read.length === 0) {
119
- e.push('read[] is required — the review states what it actually read (R2)');
120
- }
121
- if (!Array.isArray(r.findings)) {
122
- e.push('findings[] is required (an empty array is a valid, and good, review — R10)');
123
- } else {
124
- r.findings.forEach((f, i) => {
125
- const v = validateFinding(f);
126
- if (!v.ok) v.errors.forEach((msg) => e.push(`findings[${i}]: ${msg}`));
127
- });
128
- }
129
- return { ok: e.length === 0, errors: e };
130
- }
131
-
132
- /** Quiet-rate over a scored corpus (R10, rubric dim 1). Documents read with zero findings
133
- * against them, divided by documents believed clean. The rubric's binding number. */
134
- function quietRate(reviewed, cleanPaths) {
135
- const clean = new Set(cleanPaths || []);
136
- if (clean.size === 0) return null; // no clean class ⇒ no valid precision number
137
- let quiet = 0;
138
- for (const p of clean) {
139
- const noisy = (reviewed || []).some((f) => f && f.file === p);
140
- if (!noisy) quiet++;
141
- }
142
- return quiet / clean.size;
143
- }
144
-
145
- /** Action rate (PRD success signal): Apply+Edit ÷ all dispositions. Dismiss-heavy means the
146
- * review is noise the PM is politely declining, whatever it scored. */
147
- function actionRate(dispositions) {
148
- const d = (dispositions || []).filter((x) => x === 'apply' || x === 'edit' || x === 'dismiss');
149
- if (d.length === 0) return null;
150
- return d.filter((x) => x !== 'dismiss').length / d.length;
151
- }
152
-
153
- return {
154
- MECHANICAL_KINDS,
155
- isMechanical,
156
- isApplyable,
157
- validateFinding,
158
- validateReview,
159
- quietRate,
160
- actionRate,
161
- };
162
- });
@@ -1,114 +0,0 @@
1
- ---
2
- name: doc-review
3
- label: "Review documents"
4
- description: "Read the planning documents you name and report only defects the artifact cannot detect about itself — never lint. Each finding carries its evidence and, where one exists, a proposed change you dispose of."
5
- run: agent
6
- agent: claude
7
- output:
8
- - Read
9
- - Findings
10
- - Withheld
11
- ---
12
-
13
- You are running the "Review documents" skill in this PMOS workspace (local, file mode). Work only
14
- from this repository's own files, git, and CLIs — no network (D54).
15
-
16
- Goal: surface defects in the named documents that **the artifact cannot detect about itself**, and
17
- file them as a contract-checked review the PM disposes of one at a time.
18
-
19
- ## What counts as a finding — and what does not
20
-
21
- The whole value of this review is that the PM keeps reading it. A review that reports what a linter
22
- already reports becomes a linter with manners, and gets ignored by Thursday.
23
-
24
- **Report only defects the document cannot detect about itself.** The four kinds that recur:
25
-
26
- - **A metric that does not measure its own claim** — a PRD bets on an outcome, then defines success as
27
- a capability demo, so it can score full marks and move the outcome not at all.
28
- - **An anchor that cannot score wrong** — a rubric dimension whose anchors award credit for descoping,
29
- or that cannot distinguish a correct implementation from an absent one.
30
- - **A gate that scripts away its own question** — a rubric prescribing the Acceptance Gate as the same
31
- checklist it already scored, turning D12's strategic judgement into a second review pass.
32
- - **A constraint contradicted elsewhere** — a non-goal or design rule in one shipped document that
33
- silently rules out what another document proposes.
34
-
35
- **NEVER report** — these belong to lint and the Frontmatter Gate, and emitting them fails the
36
- contract outright: frontmatter or schema validity · broken links or missing paths · string/regex
37
- matches · counting occurrences · spelling, typos, formatting.
38
-
39
- **Say nothing when there is nothing to say.** An empty findings list is a correct and valued outcome.
40
- Do not pad a review to look thorough — a false finding costs the PM more than a missed one, because
41
- they pay for it every time.
42
-
43
- ## The withheld class — where you must NOT propose a fix
44
-
45
- Some defects are not defects. Before proposing any change, ask: **would applying this close a
46
- question the PM has parked, or overwrite a decision they recorded?** Check `pm-decisions.md`,
47
- `DECISIONS.md`, and the document's own history.
48
-
49
- If yes, the finding is **withheld**: `"withheld": true`, `"proposal": null`, and a
50
- `"withheld_reason"` saying why it is the PM's to settle. The client renders no Apply control for it —
51
- the absence of the button is the guard, so a withheld finding carrying a proposal is rejected by the
52
- contract, not merely discouraged.
53
-
54
- The recorded instance: `CLAUDE.md` points file-mode state at `pmos/state/*.jsonl`, which looks stale
55
- and is not — it is a measured open question (`pm-decisions.md` 491–503).
56
-
57
- ## Procedure
58
-
59
- 1. **Take the scope.** Read exactly the documents the PM named. Do not widen it.
60
- *Done when:* every named path has been read in full, and you can list them.
61
-
62
- 2. **Find, then cut.** For each document, ask the four questions above. Then remove every candidate
63
- that a regex could have found, and every one you cannot evidence to a specific line.
64
- *Done when:* each surviving finding names, in one sentence, what the artifact cannot see about
65
- itself — the `self_undetectable` field. If you cannot write that sentence, drop the finding.
66
-
67
- 3. **Propose, or withhold.** Where a change would genuinely fix it, write `proposal.before` as text
68
- copied **verbatim** from the file and `proposal.after` as its replacement. `before` must appear in
69
- the file **exactly once** — the apply path refuses a stale or ambiguous anchor rather than
70
- guessing. Where the withheld test fires, propose nothing.
71
- *Done when:* every finding either carries an exact-match proposal or is explicitly withheld.
72
-
73
- 4. **File the review.** Pick a run id (`doc-review-<UTC-timestamp>`), then write
74
- `workspace/state/reviews/<run-id>.json` with exactly this shape:
75
-
76
- ```json
77
- {
78
- "at": "<ISO 8601>",
79
- "read": ["planning/prd/example-prd.md"],
80
- "findings": [
81
- {
82
- "title": "One sentence, the defect stated plainly.",
83
- "file": "planning/prd/example-prd.md",
84
- "kind": "metric-claim-mismatch",
85
- "argument": "Why this is a defect — the reasoning, not a restatement.",
86
- "self_undetectable": "What this artifact cannot see about itself.",
87
- "evidence": [{ "file": "planning/prd/example-prd.md", "line": 17 }],
88
- "withheld": false,
89
- "proposal": { "before": "<verbatim text from the file>", "after": "<replacement>" }
90
- }
91
- ]
92
- }
93
- ```
94
-
95
- `withheld` is a required boolean on every finding and is never inferred. A withheld finding sets
96
- `"proposal": null` and adds `"withheld_reason"`.
97
- *Done when:* the file is valid JSON and `GET /api/review` returns `ok: true` for it. If it returns
98
- `422`, the contract rejected a finding — fix it; do not file it anyway.
99
-
100
- 5. **Tell the PM it is ready.** Read `workspace/state/needs.json` (a missing file is `[]`), append
101
- exactly this object, and write the whole array back — it must stay a valid JSON array:
102
-
103
- ```json
104
- {"run_id":"<run-id>","kind":"review","initiative_id":"workspace-review-and-edit","question":"Document review — <n> findings"}
105
- ```
106
- *Done when:* `needs.json` is a valid array containing the new object.
107
-
108
- ## Output contract — produce EXACTLY these sections
109
-
110
- - **Read** — the documents actually read, one per line.
111
- - **Findings** — one line per finding: the title and its `file:line`. `None this review.` if empty,
112
- which is a good outcome, not a failed run.
113
- - **Withheld** — findings deliberately left for the PM, with the one-line reason. `None this review.`
114
- if empty.