create-pmos 0.38.4 → 0.38.6

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.
Files changed (35) hide show
  1. package/package.json +1 -1
  2. package/payload/AGENTS.md +1 -1
  3. package/payload/KIT-VERSION +2 -2
  4. package/payload/okf/core/templates/requirements-brief-template.md +61 -0
  5. package/payload/planning/evals/eval-calibration.md +1 -1
  6. package/payload/planning/okrs/health-budgets.md +1 -1
  7. package/payload/scripts/lane-defs.js +226 -0
  8. package/payload/scripts/workflow-state.js +108 -24
  9. package/payload/scripts/workflow-sweep.js +155 -3
  10. package/payload/skills/design-brief.skill +5 -6
  11. package/payload/skills/discovery.skill +1 -2
  12. package/payload/skills/eval-rubric.skill +2 -5
  13. package/payload/skills/pmos-router.skill +3 -2
  14. package/payload/skills/prd.skill +7 -13
  15. package/payload/skills/requirements-elicitation.skill +231 -0
  16. package/payload/skills-native/design-brief/SKILL.md +5 -6
  17. package/payload/skills-native/discovery/SKILL.md +1 -2
  18. package/payload/skills-native/eval-rubric/SKILL.md +2 -5
  19. package/payload/skills-native/pmos-router/SKILL.md +3 -2
  20. package/payload/skills-native/prd/SKILL.md +7 -13
  21. package/payload/skills-native/requirements-elicitation/SKILL.md +233 -0
  22. package/payload/state/discoveries.md +1 -1
  23. package/payload/state/log.md +1 -1
  24. package/workspace/lib/audit.js +54 -0
  25. package/workspace/lib/engine/lane-defs.js +226 -0
  26. package/workspace/lib/engine/workflow-state.js +108 -24
  27. package/workspace/lib/engine/workflow-sweep.js +934 -0
  28. package/workspace/package-lock.json +1 -1
  29. package/workspace/package.json +2 -2
  30. package/workspace/public/pm/data.js +67 -24
  31. package/workspace/public/pm/panel-detail.js +12 -2
  32. package/workspace/public/pm/panel-repos.js +36 -5
  33. package/workspace/public/pm/tab-task.js +4 -1
  34. package/workspace/server.js +22 -1
  35. package/workspace/skills/gather-requirements.skill +13 -1
@@ -76,6 +76,11 @@ const CLASSES = {
76
76
  'missing-record': { severity: DEFECT, repairable: true, why: 'work merged with no record is invisible to every board and to the gate' },
77
77
  'unresolvable-anchor': { severity: DEFECT, repairable: false, why: 'the anchor names something the OKR documents do not declare (D59)' },
78
78
  'unverifiable-delivery': { severity: DEFECT, repairable: false, why: 'delivered_by names a commit the trunk cannot reach, or one that changes no build path (R-2b)' },
79
+ 'unpointed-artifact': { severity: DEFECT, repairable: true, why: 'a document names this initiative and no path key in the record points at it, so the ladder reads the step as open over finished work (D77)' },
80
+ 'lane-outside-profile': { severity: DEFECT, repairable: false, why: 'the record names a lane its repository profile does not offer, so the engine applies a different lane than the one written (D68, D77)' },
81
+ 'acceptance-not-counted': { severity: DEFECT, repairable: false, why: 'an acceptances.jsonl row names an initiative and the ladder does not count it, so the ladder shows the gate open over a row that looks like an approval (D12)' },
82
+ 'done-without-basis': { severity: DEFECT, repairable: false, why: 'a step reads done and names no file, ledger or commit history, so the pane has nothing to open (D77)' },
83
+ 'status-claims-ahead': { severity: OWED, repairable: false, why: 'the record\'s status says accepted or complete while the ladder shows the acceptance step open' },
79
84
  'delivered-unlogged': { severity: OWED, repairable: false, why: 'the trunk attributes work to this initiative and no run was ever logged (AGENTS.md step 7)' },
80
85
  'delivered-unreviewed': { severity: OWED, repairable: false, why: 'delivered against a rubric with no Review-Gate verdict recorded — a skipped gate is recorded as skipped, never left absent' },
81
86
  'delivered-unaccepted': { severity: OWED, repairable: false, why: 'delivered with no acceptance row — the D12 gate is open' },
@@ -169,6 +174,43 @@ function candidatesFor(root, L, key, id) {
169
174
  return [...new Set(out)];
170
175
  }
171
176
 
177
+ /*
178
+ * D77 — documents that name an initiative. The engine reads only the paths a record points at and
179
+ * never searches, so a finished PRD that no record points at leaves its step open. This is the search
180
+ * the engine does not do. It reads Markdown under the conventional document folders, skips the
181
+ * folders that hold control records, ledgers, rubrics and explainers, and matches on the file's
182
+ * `initiative_id` (frontmatter or a leading bullet) or on a name that starts with the id.
183
+ */
184
+ const DOC_ROOTS = ['planning', 'docs', 'doc', 'prd', 'prds', 'briefs', 'specs', 'strategy', 'plans', 'reports', 'decisions', 'requirements', 'product'];
185
+ const DOC_SKIP = new Set(['node_modules', '.git', 'initiatives', 'runs', 'state', 'explainers', 'evals', 'okf']);
186
+ function buildDocIndex(root) {
187
+ const out = [];
188
+ const walk = (dirRel, depth) => {
189
+ if (depth > 4 || out.length > 5000) return;
190
+ let ents;
191
+ try { ents = fs.readdirSync(path.join(root, dirRel), { withFileTypes: true }); } catch { return; }
192
+ for (const e of ents) {
193
+ const rel = dirRel ? `${dirRel}/${e.name}` : e.name;
194
+ if (e.isDirectory()) { if (!DOC_SKIP.has(e.name)) walk(rel, depth + 1); continue; }
195
+ if (!/\.md$/i.test(e.name) || /^(index|log)\.md$/i.test(e.name)) continue;
196
+ let head = '';
197
+ try {
198
+ const fd = fs.openSync(path.join(root, rel), 'r');
199
+ const buf = Buffer.alloc(3000); const n = fs.readSync(fd, buf, 0, 3000, 0); fs.closeSync(fd);
200
+ head = buf.slice(0, n).toString('utf8');
201
+ } catch { continue; }
202
+ const m = head.match(/^[-\s]*initiative_id:\s*["']?([\w.-]+)/m);
203
+ out.push({ rel, base: e.name, iid: m ? m[1] : null });
204
+ }
205
+ };
206
+ for (const d of DOC_ROOTS) walk(d, 0);
207
+ return out;
208
+ }
209
+ function documentsNaming(index, id) {
210
+ return index.filter((f) => f.iid === id || f.base === `${id}.md` || f.base.startsWith(`${id}-`)).map((f) => f.rel);
211
+ }
212
+ const isBriefName = (rel) => /brief/i.test(path.basename(rel));
213
+
172
214
  // ── the sweep ──────────────────────────────────────────────────────────────────────────────────
173
215
 
174
216
  function sweep(opts) {
@@ -197,6 +239,11 @@ function sweep(opts) {
197
239
  };
198
240
  }
199
241
  const idset = new Set(ids);
242
+ const profileInfo = engine.lanesFor(root);
243
+ const pmProfile = profileInfo.profile === 'pm-artifacts';
244
+ let docIndex = null;
245
+ let wfRange = null;
246
+ try { wfRange = engine.historyRange(root); } catch { wfRange = null; }
200
247
 
201
248
  // ── the ledgers, read once ────────────────────────────────────────────────────────────────────
202
249
  const ledgerFiles = {
@@ -260,9 +307,23 @@ function sweep(opts) {
260
307
  }
261
308
  const evalIds = new Set(ledgers.evals.rows.map(({ row }) => row && row.initiative_id).filter(Boolean));
262
309
  const acceptedIds = new Set(ledgers.acceptances.rows
263
- .filter(({ row }) => row && (row.accepted === true || row.action === 'approve'))
310
+ .filter(({ row }) => row && row.accepted === true)
264
311
  .map(({ row }) => row.initiative_id).filter(Boolean));
265
312
 
313
+ // D77 — a row that looks like an approval and that the ladder does not count
314
+ for (const { row, line } of ledgers.acceptances.rows) {
315
+ if (!row || !row.initiative_id || !idset.has(row.initiative_id)) continue;
316
+ if (only && row.initiative_id !== only) continue;
317
+ if (row.accepted === true) continue;
318
+ if (row.action !== 'approve') continue;
319
+ findings.push(finding('acceptance-not-counted', {
320
+ id: row.initiative_id, file: path.relative(root, ledgerFiles.acceptances), line,
321
+ what: `an acceptances.jsonl row has action 'approve' and accepted '${row.accepted}' — the ladder counts only accepted: true`,
322
+ evidence: JSON.stringify(row).slice(0, 140),
323
+ hand: 'the sweep never writes a ledger: if this was an acceptance, the PM records it with scripts/record-acceptance.js; if it was something else, no action',
324
+ }));
325
+ }
326
+
266
327
  // ── delivery, read once ───────────────────────────────────────────────────────────────────────
267
328
  const del = delivery.deliveryIndex(root, ids);
268
329
  if (!del.ok) {
@@ -313,11 +374,18 @@ function sweep(opts) {
313
374
  const lane = (fm.lane || 'feature').trim() || 'feature';
314
375
 
315
376
  // dangling declared paths
316
- for (const key of ['prd_path', 'rubric_path', 'explainer_path']) {
377
+ for (const key of pmProfile
378
+ ? ['prd_path', 'brief_path', 'doc_path', 'rubric_path', 'explainer_path']
379
+ : ['prd_path', 'rubric_path', 'explainer_path']) {
317
380
  const v = String(fm[key] || '').trim();
318
381
  if (!v) continue;
319
382
  if (fs.existsSync(path.join(root, v))) continue;
320
- const cands = candidatesFor(root, L, key, id);
383
+ let cands;
384
+ if (key === 'brief_path' || key === 'doc_path') {
385
+ if (!docIndex) docIndex = buildDocIndex(root);
386
+ const named = documentsNaming(docIndex, id);
387
+ cands = named.filter((f) => (key === 'brief_path') === isBriefName(f));
388
+ } else cands = candidatesFor(root, L, key, id);
321
389
  findings.push(finding('dangling-path', {
322
390
  id, file: recRel,
323
391
  what: `${key} points at '${v}', which is not in the repository`,
@@ -332,6 +400,72 @@ function sweep(opts) {
332
400
  }));
333
401
  }
334
402
 
403
+ // D77 — a lane the profile does not offer
404
+ {
405
+ const declared = String(fm.lane || '').trim();
406
+ if (declared && !profileInfo.lanes.includes(declared)) {
407
+ const applied = pmProfile ? 'prd' : 'feature';
408
+ findings.push(finding('lane-outside-profile', {
409
+ id, file: recRel,
410
+ what: `lane '${declared}' is not a ${profileInfo.profile} lane`,
411
+ evidence: `the engine applies '${applied}' instead; this repository offers: ${profileInfo.lanes.join(', ')}`,
412
+ hand: `choose one of: ${profileInfo.lanes.join(', ')} — the lane is the PM's choice, so it is reported and never rewritten`,
413
+ }));
414
+ }
415
+ }
416
+
417
+ // D77 — a finished document that no record points at (document profile)
418
+ if (pmProfile) {
419
+ if (!docIndex) docIndex = buildDocIndex(root);
420
+ const named = documentsNaming(docIndex, id);
421
+ const declaredKeys = ['brief_path', 'prd_path', 'doc_path'].map((k) => String(fm[k] || '').trim()).filter(Boolean);
422
+ const unref = named.filter((f) => !declaredKeys.includes(f));
423
+ const briefEmpty = !String(fm.brief_path || fm.prd_path || '').trim();
424
+ const docEmpty = !String(fm.doc_path || '').trim();
425
+ if (unref.length && (briefEmpty || docEmpty)) {
426
+ let repair = null;
427
+ let hand = null;
428
+ if (unref.length === 1) {
429
+ const key = isBriefName(unref[0]) ? (briefEmpty ? 'brief_path' : null) : (docEmpty ? 'doc_path' : null);
430
+ if (key) repair = { kind: 'add-key', file: recRel, key, from: '', to: unref[0] };
431
+ else hand = `${unref[0]} names this initiative, but the key it would fill is already set — decide by hand`;
432
+ } else hand = `${unref.length} documents name this initiative (${unref.join(', ')}) — choosing which is the brief and which the deliverable is a judgement, so it is reported rather than guessed`;
433
+ findings.push(finding('unpointed-artifact', {
434
+ id, file: recRel,
435
+ what: `${unref.length} document${unref.length > 1 ? 's name' : ' names'} this initiative and no path key points at ${unref.length > 1 ? 'them' : 'it'}: ${unref.join(', ')}`,
436
+ evidence: `brief_path/prd_path ${briefEmpty ? 'is empty' : 'is set'}; doc_path ${docEmpty ? 'is empty' : 'is set'}`,
437
+ repair, hand,
438
+ }));
439
+ }
440
+ }
441
+
442
+ // D77 — what the ladder says against the record's own words, and against its own basis rule
443
+ if (wfRange) {
444
+ let wf = null;
445
+ try { wf = engine.computeWorkflow(id, { root, range: wfRange }); } catch { wf = null; }
446
+ if (wf && wf.ok && Array.isArray(wf.steps)) {
447
+ for (const st of wf.steps) {
448
+ if (st.status === 'done' && st.basis && st.basis.source === 'none' && st.key !== 'quality') {
449
+ findings.push(finding('done-without-basis', {
450
+ id, file: recRel,
451
+ what: `step '${st.key}' reads done and names no file, ledger or commit history`,
452
+ evidence: String(st.evidence || '').slice(0, 120),
453
+ hand: `the engine decided '${st.key}' from something the detail pane cannot open — fix the rung to name what it read`,
454
+ }));
455
+ }
456
+ }
457
+ const claim = String(fm.status || '').trim();
458
+ const gate = wf.steps.find((st) => st.key === 'accept' || st.key === 'approved');
459
+ if (/^(accepted|approved|complete|completed|done|delivered|shipped)\b/i.test(claim) && gate && gate.status !== 'done' && gate.status !== 'na') {
460
+ findings.push(finding('status-claims-ahead', {
461
+ id, file: recRel,
462
+ what: `status says '${claim.slice(0, 60)}' while the ${gate.key} step is ${gate.status}`,
463
+ evidence: String(gate.evidence || '').slice(0, 120),
464
+ }));
465
+ }
466
+ }
467
+ }
468
+
335
469
  // damaged frontmatter values
336
470
  for (const [key, v] of Object.entries(fm)) {
337
471
  const dmg = damagedValue(v);
@@ -553,6 +687,15 @@ function applyRepairs(res, opts) {
553
687
  for (const f of group) {
554
688
  const r = f.repair;
555
689
  const before = frontmatterValue(next, r.key);
690
+ if (r.kind === 'add-key') {
691
+ // One key, only when it is still absent or empty. A value the PM wrote since the sweep ran wins.
692
+ if (before !== null && before.trim() !== '') { skipped.push({ finding: f, why: `${r.key} was set since the sweep ran` }); continue; }
693
+ const edited = before === null ? insertFrontmatterKey(next, r.key, r.to) : setFrontmatterValue(next, r.key, r.to);
694
+ if (edited === next) { skipped.push({ finding: f, why: `${key} has no frontmatter to add ${r.key} to` }); continue; }
695
+ next = edited;
696
+ applied.push({ class: f.class, file: key, key: r.key, before: '(not set)', after: r.to.slice(0, 90) });
697
+ continue;
698
+ }
556
699
  if (before === null) { skipped.push({ finding: f, why: `${r.key} is no longer in the frontmatter` }); continue; }
557
700
  let after;
558
701
  if (r.kind === 'repoint') {
@@ -580,6 +723,15 @@ function frontmatterValue(text, key) {
580
723
  }
581
724
  return null;
582
725
  }
726
+ /** Insert `key: value` as the last line of the leading frontmatter; the text is returned unchanged when there is none. */
727
+ function insertFrontmatterKey(text, key, value) {
728
+ const lines = String(text).split('\n');
729
+ if (lines[0] !== '---') return text;
730
+ for (let i = 1; i < lines.length; i++) {
731
+ if (lines[i] === '---') { lines.splice(i, 0, `${key}: ${value}`); return lines.join('\n'); }
732
+ }
733
+ return text;
734
+ }
583
735
  function setFrontmatterValue(text, key, value) {
584
736
  const lines = String(text).split('\n');
585
737
  for (let i = 0; i < lines.length; i++) {
@@ -38,12 +38,11 @@ definition of *what to design* and the hard constraints, agreed before any desig
38
38
 
39
39
  ## Procedure
40
40
 
41
- 0. **Grill before you brief** (the [prd skill](/skills/prd.skill)'s step-0 discipline, applied
42
- to design intent): where the PM's aesthetic or structural intent is ambiguous, ask **one question
43
- at a time with a recommended answer**, stop at unambiguous — no cap; a batch questionnaire is not
44
- compliant. Look up what the environment answers (the capability ledger, the design system, the
45
- promoted prototype); the taste calls are the PM's (D12). The recorded cost of skipping this is the
46
- console-redesign series — "too many navigations" was stated once and unhonored twice.
41
+ 0. **Ask before you brief.** Where the PM's aesthetic or structural intent is ambiguous, follow the
42
+ [requirements-elicitation skill](/skills/requirements-elicitation.skill). Look up what the
43
+ environment answers (the capability ledger, the design system, the promoted prototype); the taste
44
+ calls are the PM's. The recorded cost of skipping this is the console-redesign series — "too many
45
+ navigations" was stated once and unhonored twice.
47
46
  1. **Confirm the anchor.** The initiative has a PRD and an anchor — a parent KR, or a declared
48
47
  health budget `health:<id>` (D59). The brief does not re-argue *whether*
49
48
  to build the surface; it says how it should look.
@@ -71,5 +71,4 @@ initiative; prototype code deleted or explicitly quarantined.
71
71
 
72
72
  ## Level 3 sub-cases
73
73
 
74
- - None yet. (Multi-question research programs spanning several spikes: author
75
- `discovery-program.md` on first need.)
74
+ - None yet.
@@ -79,11 +79,8 @@ Before any work begins, the generator and evaluator **agree on done-criteria**
79
79
  contract stage of the [SDLC loop](/okf/core/concepts/sdlc-loop.md). A rubric is incomplete without
80
80
  one.
81
81
 
82
- **Reach the criteria by grilling, not assumption** (the [prd skill](/skills/prd.skill)'s step-0
83
- discipline applies here too): where the PM's intent is ambiguous, ask **one question at a time with a
84
- recommended answer**, stop at unambiguous — no cap, and a batch questionnaire is not compliant. If a
85
- fact can be found by exploring the environment, look it up rather than asking — the decisions are the
86
- PM's (D12). **Write criteria for durability:** behavioral contracts and interfaces, never file paths
82
+ **Reach the criteria by asking, not assumption.** Where the PM's intent is ambiguous, follow the
83
+ [requirements-elicitation skill](/skills/requirements-elicitation.skill). **Write criteria for durability:** behavioral contracts and interfaces, never file paths
87
84
  or line numbers (they stale while work waits); each criterion independently verifiable.
88
85
 
89
86
  **Format:**
@@ -63,6 +63,7 @@ router is the bug (fix it via the weekly harness scan).
63
63
  | "**Production is broken**" | **Incident lane** | `type: incident`; fix now, record post-hoc within 48h at `prd_path` (the one sanctioned inversion; [incident template](/okf/core/templates/incident-record-template.md)) | Initiative Gate (record, post-hoc) |
64
64
  | A **text deliverable** (landing page, microcopy, release notes, campaign) | **Content lane** | `type: content`; content brief at `prd_path`, rubric recommended not required (D58); [copywriting skill](/skills/copywriting.skill). Copy for a prototype rides the prototype instead (D60) | Initiative Gate (content brief) |
65
65
  | **Upkeep** — refactor/ops/security with no honest KR | **Chore lane, health-anchored** | `type: chore`; one-paragraph intent in `status`; anchor `health:<id>` from [health-budgets](/planning/okrs/health-budgets.md) (D59) — never a faked KR | Initiative Gate (intent + anchor) |
66
+ | "**Gather**, **clarify** or **sharpen** the requirements" — the PM has a vague ask or notes from gathering and no brief yet | **Requirements elicitation** (before the lane's first artifact) | [requirements-elicitation skill](/skills/requirements-elicitation.skill) → `planning/briefs/<id>-requirements.md` at `brief_path`; reads the lane's input from `scripts/lane-defs.js` and the notes `gather-requirements` filed (D71); the PM confirms each note item. It does not choose the lane and does not write the PRD | none (the brief is never a gate) |
66
67
 
67
68
  **Every route** still takes the universal steps: anchored initiative row (`create_initiative`),
68
69
  branch `initiative/<id>`, and the human Acceptance Gate at the end (D12) — the router never bypasses
@@ -97,5 +98,5 @@ Check your position at any time with `node scripts/workflow-state.js <id>`.
97
98
  already decided to explore, go straight to prototype.
98
99
  - **Anchor-free trivia** (a typo, a broken link): the `no-initiative` label, no lane. Real work
99
100
  never takes the hatch.
100
- - **Unknown shape after the checks:** ask the PM one routing question with a recommended answer —
101
- do not guess a lane.
101
+ - **Unknown shape after the checks:** ask the PM one routing question, as the
102
+ [requirements-elicitation skill](/skills/requirements-elicitation.skill) describes. Do not guess a lane.
@@ -30,22 +30,17 @@ agreed.
30
30
 
31
31
  ## Procedure
32
32
 
33
- 0a. **Existing surface? Understand it first — before grilling.** If specifying this honestly
33
+ 0a. **Existing surface? Understand it first — before asking.** If specifying this honestly
34
34
  requires reading code that already exists, an [explain-surface](/skills/explain-surface.skill)
35
35
  pass runs on that region **first**, in a fresh context and **not by you**. Its *Open questions*
36
- section is a declared input to the grilling below: those are the things that could not be
36
+ section is a declared input to the questions in step 0: those are the things that could not be
37
37
  determined from the code, and they must be resolved with the PM or carried explicitly into the
38
38
  PRD's risks — never silently answered by assumption. Greenfield → skip. This exists because step 0b's
39
39
  boundary rule, on its own, gives the **agent** the understanding and the PM none.
40
- 0. **Grill before you write.** Before any authoring, interrogate the PM's intent as a dialogue —
41
- **one question at a time, a recommended answer offered with each**, no authoring until shared
42
- understanding is confirmed. Stop when the intent is **unambiguous**, not at a count — there is
43
- deliberately **no question cap** (under-specification needs unlimited digging; a redundant
44
- question is a quality bug, not a quantity bug). A batch questionnaire is **not** compliant with
45
- this step. Boundary rule, near-verbatim from AGENTS.md: *if a fact can be found by exploring the
46
- environment, look it up rather than asking — the decisions, though, are the PM's* (D12 at
47
- optimal altitude). A stated requirement that later goes unhonored in a build (the recorded
48
- "too many navigations" class) is exactly what this step exists to pin down in writing.
40
+ 0. **Ask before you write.** Follow the [requirements-elicitation skill](/skills/requirements-elicitation.skill):
41
+ its asking rule holds here, and no authoring starts until the PM confirms the shared understanding.
42
+ A stated requirement that later goes unhonored in a build (the recorded "too many navigations"
43
+ class) is what this step pins down in writing.
49
44
  1. **Anchor to strategy first.** Identify the parent KR in the
50
45
  [OKR tree](/okf/core/concepts/okr-tree.md) — or, for maintenance work with no honest KR, the
51
46
  declared [health budget](/okf/core/concepts/health-anchors.md) (`health:<id>`, D59). If the
@@ -107,8 +102,7 @@ its situating sentence (D58); the eval rubric is queued to be written first.
107
102
 
108
103
  ## Level 3 sub-cases
109
104
 
110
- - Multi-product or platform PRDs that span more than one `product_slug`: see `prd-multiproduct.md`
111
- (author on first need).
105
+ - None yet.
112
106
  - Spike / discovery work where the deliverable is a learning, not a feature, is **not a PRD
113
107
  case** — take the discovery lane ([D56](/okf/products/pmos/adr/d56-initiative-lanes.md)):
114
108
  [discovery skill](/skills/discovery.skill) +
@@ -0,0 +1,231 @@
1
+ ---
2
+ name: requirements-elicitation
3
+ description: Turn a vague ask, or notes the PM gathered, into a requirements brief by asking the PM questions one at a time. Use when the PM wants to gather, clarify or sharpen requirements, or before any lane writes its first artifact (PRD, spike brief, fix specification, content brief).
4
+ version: 1.0.0
5
+ owner: wawan
6
+ risk: low
7
+ category: planning
8
+ scope: read:okf, read:planning, read:state, write:planning/briefs
9
+ ---
10
+
11
+ # requirements-elicitation — Ask until the requirement is sharp
12
+
13
+ This skill is the one place where PMOS states how an agent asks the PM questions. It also
14
+ holds the questions each lane needs. Its output is a requirements brief that a PRD, a spike
15
+ brief or a fix specification starts from. It does not write those documents.
16
+
17
+ > **Format note:** this skill follows [SKILL-FORMAT](/skills/SKILL-FORMAT.md). The text is
18
+ > instructional (class A of [technical-writing-ste](/skills/technical-writing-ste.skill)).
19
+
20
+ ## When to use vs. not
21
+
22
+ - **Use** when the PM asks to gather, clarify or sharpen requirements.
23
+ - **Use** before the first artifact of any lane, when the intent is not yet unambiguous.
24
+ - **Do not use** to decide the lane. That is the [pmos-router](/skills/pmos-router.skill).
25
+ - **Do not use** to judge whether to build at all. That is the
26
+ [strategic-brief](/skills/strategic-brief.skill).
27
+ - **Do not use** when an instance of the idea is needed before the PM can judge it. Route to the
28
+ prototype lane first.
29
+
30
+ ## The asking rule
31
+
32
+ This rule holds in every step below. Other skills point here and do not repeat it.
33
+
34
+ 1. Ask one question at a time.
35
+ 2. Give a recommended answer with each question.
36
+ 3. Do not set a limit on the number of questions. Stop when the intent is unambiguous.
37
+ 4. Do not send a batch questionnaire. A batch does not satisfy this rule.
38
+ 5. Look up each fact that the repository or the record can answer. Do not ask the PM for it.
39
+ 6. Leave each decision to the PM.
40
+ 7. Write nothing until the PM confirms the shared understanding.
41
+
42
+ ## Procedure
43
+
44
+ 1. **Find the lane.** Read the initiative record and its `type`. Run
45
+ `node scripts/workflow-state.js --lanes --json` (in a kit repository the script is
46
+ `pmos/scripts/workflow-state.js`). Find the lane in `defs`.
47
+ State the lane's `input`, `output` and `done` in plain words before the first question.
48
+ *Done when:* the PM has seen the lane's input, output and done-rule.
49
+
50
+ If the script is missing or the lane is not in `defs`, say so. Ask the PM which lane applies.
51
+ Do not guess a question set.
52
+
53
+ 2. **Look up what exists.** Read the record, the PRD or brief if one exists, the OKR the
54
+ record names, and the explainer if the surface exists. Collect the facts you found.
55
+ *Done when:* every fact you will use has a source, and none of them is a question for the PM.
56
+
57
+ 3. **Read the gathered notes.** List `<state>/requirements/*.md`. `<state>` is `pmos/state/`
58
+ if it exists, and `planning/` if not. Select each item whose `initiative` names this
59
+ initiative. Show each item to the PM with its pointer and its `confidence`. Ask for one
60
+ answer per item: confirmed, rejected or deferred. Record the answer.
61
+ - A confirmed item goes in the brief under *Decisions*, with its pointer.
62
+ - A rejected item is dropped. Name it in *Gathered notes used* as rejected.
63
+ - A deferred item goes in *Open questions*.
64
+ - An item with `confidence: unconfirmed` stays unconfirmed until the PM answers. Never write
65
+ it as a fact.
66
+ - Name a note that does not parse. Skip it.
67
+ - If no notes exist, say so and continue.
68
+
69
+ Never edit a note. The notes are read-only drafts (D71).
70
+ *Done when:* every selected item has an answer, or the PM has said to skip the notes.
71
+
72
+ 4. **Ask the topic questions.** Work through the [question inventory](#question-inventory).
73
+ Skip a topic the lane's input does not need, and say that you skipped it.
74
+ Skip a question that step 2 or step 3 already answered.
75
+ *Done when:* each topic is answered, skipped with a reason, or listed in *Open questions*.
76
+
77
+ 5. **Ask the lane questions.** Find the lane in [Question sets by lane](#question-sets-by-lane).
78
+ Ask each question there that the earlier steps did not answer.
79
+ *Done when:* every element of the lane's `input` has an answer or an *Open questions* entry.
80
+
81
+ 6. **Confirm.** Say the shared understanding back to the PM in one short list. Ask the PM to
82
+ confirm or correct it.
83
+ *Done when:* the PM has confirmed. If the PM corrects it, go back to step 4.
84
+
85
+ 7. **Write the brief.** Copy the
86
+ [requirements-brief-template](/okf/core/templates/requirements-brief-template.md). Fill every
87
+ section from the confirmed answers, in the PM's words where the PM gave words. Write it to
88
+ `planning/briefs/<id>-requirements.md`. Set `brief_path` on the initiative record. If the lane
89
+ uses `prd_path` for its first artifact and that file does not exist yet, tell the PM that the
90
+ brief is the input and that the lane's own skill writes `prd_path`.
91
+ *Done when:* the file exists, no section is empty, and `brief_path` is set.
92
+
93
+ 8. **Hand over.** Name the next skill for the lane: [prd](/skills/prd.skill) for
94
+ feature and new-feature, [discovery](/skills/discovery.skill) for discovery,
95
+ [copywriting](/skills/copywriting.skill) for content, and the lane's own
96
+ template for the others. State the open questions that remain.
97
+ *Done when:* the PM knows what comes next and what is still open.
98
+
99
+ ## Question inventory
100
+
101
+ Each topic has an opening question. Add follow-up questions when the answer is vague.
102
+
103
+ | Topic | Opening question |
104
+ |---|---|
105
+ | Problem and user | Who has this problem, and what do they do today instead? |
106
+ | Evidence | What shows that the problem is real: a number, a ticket, a quote, an incident? |
107
+ | Constraints | What is fixed: a date, a budget, a technology, a rule or a promise already made? |
108
+ | Stakeholders and decision owner | Who is affected, who must agree, and who decides if they disagree? |
109
+ | Dependencies | What must exist, or be done by someone else, before this can work? |
110
+ | Data | What data does this read or change, and who owns it? |
111
+ | Non-functional needs | What must hold about speed, security, privacy, access or cost? |
112
+ | Risks | What is the worst way this fails, and how would we notice? |
113
+ | Success measure | Which measure moves if this works, from what value to what value, by when? |
114
+ | Out of scope | What will this not do, even though someone might expect it to? |
115
+
116
+ ## Question sets by lane
117
+
118
+ Each lane has its own heading. The headings use the lane names in `scripts/lane-defs.js`.
119
+ A lane name that both profiles use (discovery, chore) has one set.
120
+
121
+ ### Lane `feature`
122
+
123
+ 1. What behaviour changes for the user, in one sentence?
124
+ 2. Does the surface exist today? If yes, what does it do now?
125
+ 3. Which KR or health budget does this serve?
126
+ 4. Can the PM judge this from a description, or must the PM see an instance first?
127
+ 5. What would the PM check by hand to accept it?
128
+
129
+ ### Lane `new-feature`
130
+
131
+ 1. Who has the problem, and how often does it happen?
132
+ 2. What shows that it is real?
133
+ 3. What do users do today?
134
+ 4. What must a first version do, and what can wait?
135
+ 5. What would make the PM drop it?
136
+
137
+ ### Lane `improvement`
138
+
139
+ 1. What does the feature do now? Look it up first.
140
+ 2. What is wrong or missing, and for whom?
141
+ 3. Which measure should the change move, and by how much?
142
+ 4. What must stay the same?
143
+ 5. How will we tell that it worked?
144
+
145
+ ### Lane `bug-fix`
146
+
147
+ 1. What are the steps that reproduce it?
148
+ 2. What is the expected result?
149
+ 3. What is the actual result?
150
+ 4. How severe is it, and who is affected?
151
+ 5. Is there a workaround?
152
+
153
+ ### Lane `discovery`
154
+
155
+ 1. What is the question, in one sentence?
156
+ 2. What is unknown, and what is already known?
157
+ 3. What evidence would end the spike?
158
+ 4. What would make this worth picking up, and what would make it worth dropping?
159
+ 5. How long may it take?
160
+
161
+ ### Lane `chore`
162
+
163
+ 1. What is the intent, in one paragraph?
164
+ 2. Which health budget does it serve?
165
+ 3. What must not change?
166
+ 4. How will the PM see that it is done?
167
+
168
+ ### Lane `incident`
169
+
170
+ 1. What broke, and who noticed?
171
+ 2. What is the cause?
172
+ 3. What was the repair, and is it permanent?
173
+ 4. What stops it from happening again?
174
+ 5. When was it fixed? The record is due within 48 hours.
175
+
176
+ ### Lane `content`
177
+
178
+ 1. Who reads it, and what should they do after?
179
+ 2. What is the one message?
180
+ 3. What tone and length does the reader expect?
181
+ 4. What facts must it contain, and where are they?
182
+ 5. Does the PM want a rubric?
183
+
184
+ ### Lane `prototype`
185
+
186
+ 1. What judgement does the PM need to make?
187
+ 2. Which format fits the specimens: a screen, a sample output, a transcript or a schema?
188
+ 3. What should the 2–3 specimens differ on?
189
+ 4. Which conventions does the class need before the specimens (design system, skill format)?
190
+
191
+ ### Lane `prd`
192
+
193
+ 1. What is the question the document answers?
194
+ 2. Who reads it?
195
+ 3. Which decision does it inform?
196
+ 4. What do the readers already know?
197
+
198
+ ### Lane `strategy`
199
+
200
+ 1. What is the question the document answers?
201
+ 2. Who reads it?
202
+ 3. Which decision does it inform?
203
+ 4. Over what period?
204
+
205
+ ### Lane `project-plan`
206
+
207
+ 1. What is in scope, and what is out?
208
+ 2. Who is on it, and for how much of their time?
209
+ 3. Which dates are fixed, and who fixed them?
210
+ 4. What does it depend on?
211
+
212
+ ### Lane `decision-record`
213
+
214
+ 1. What are the options?
215
+ 2. Which one was chosen, and by whom?
216
+ 3. What is the reason?
217
+ 4. What would reverse the choice?
218
+
219
+ ### Lane `report`
220
+
221
+ 1. What period or subject does it cover?
222
+ 2. Who reads it?
223
+ 3. What should the reader do after reading?
224
+ 4. Which sources does it use, and which can it not reach?
225
+
226
+ ## Failure behaviour
227
+
228
+ - The lane definition cannot be read: say so, and ask the PM which lane applies.
229
+ - No gathered notes exist: say so, and continue.
230
+ - A note does not parse: name it, and skip it.
231
+ - The PM leaves a question open: write it under *Open questions*. Do not answer it.
@@ -40,12 +40,11 @@ definition of *what to design* and the hard constraints, agreed before any desig
40
40
 
41
41
  ## Procedure
42
42
 
43
- 0. **Grill before you brief** (the [prd skill](/skills/prd.skill)'s step-0 discipline, applied
44
- to design intent): where the PM's aesthetic or structural intent is ambiguous, ask **one question
45
- at a time with a recommended answer**, stop at unambiguous — no cap; a batch questionnaire is not
46
- compliant. Look up what the environment answers (the capability ledger, the design system, the
47
- promoted prototype); the taste calls are the PM's (D12). The recorded cost of skipping this is the
48
- console-redesign series — "too many navigations" was stated once and unhonored twice.
43
+ 0. **Ask before you brief.** Where the PM's aesthetic or structural intent is ambiguous, follow the
44
+ [requirements-elicitation skill](/skills/requirements-elicitation.skill). Look up what the
45
+ environment answers (the capability ledger, the design system, the promoted prototype); the taste
46
+ calls are the PM's. The recorded cost of skipping this is the console-redesign series — "too many
47
+ navigations" was stated once and unhonored twice.
49
48
  1. **Confirm the anchor.** The initiative has a PRD and an anchor — a parent KR, or a declared
50
49
  health budget `health:<id>` (D59). The brief does not re-argue *whether*
51
50
  to build the surface; it says how it should look.
@@ -73,5 +73,4 @@ initiative; prototype code deleted or explicitly quarantined.
73
73
 
74
74
  ## Level 3 sub-cases
75
75
 
76
- - None yet. (Multi-question research programs spanning several spikes: author
77
- `discovery-program.md` on first need.)
76
+ - None yet.
@@ -81,11 +81,8 @@ Before any work begins, the generator and evaluator **agree on done-criteria**
81
81
  contract stage of the [SDLC loop](/okf/core/concepts/sdlc-loop.md). A rubric is incomplete without
82
82
  one.
83
83
 
84
- **Reach the criteria by grilling, not assumption** (the [prd skill](/skills/prd.skill)'s step-0
85
- discipline applies here too): where the PM's intent is ambiguous, ask **one question at a time with a
86
- recommended answer**, stop at unambiguous — no cap, and a batch questionnaire is not compliant. If a
87
- fact can be found by exploring the environment, look it up rather than asking — the decisions are the
88
- PM's (D12). **Write criteria for durability:** behavioral contracts and interfaces, never file paths
84
+ **Reach the criteria by asking, not assumption.** Where the PM's intent is ambiguous, follow the
85
+ [requirements-elicitation skill](/skills/requirements-elicitation.skill). **Write criteria for durability:** behavioral contracts and interfaces, never file paths
89
86
  or line numbers (they stale while work waits); each criterion independently verifiable.
90
87
 
91
88
  **Format:**
@@ -65,6 +65,7 @@ router is the bug (fix it via the weekly harness scan).
65
65
  | "**Production is broken**" | **Incident lane** | `type: incident`; fix now, record post-hoc within 48h at `prd_path` (the one sanctioned inversion; [incident template](/okf/core/templates/incident-record-template.md)) | Initiative Gate (record, post-hoc) |
66
66
  | A **text deliverable** (landing page, microcopy, release notes, campaign) | **Content lane** | `type: content`; content brief at `prd_path`, rubric recommended not required (D58); [copywriting skill](/skills/copywriting.skill). Copy for a prototype rides the prototype instead (D60) | Initiative Gate (content brief) |
67
67
  | **Upkeep** — refactor/ops/security with no honest KR | **Chore lane, health-anchored** | `type: chore`; one-paragraph intent in `status`; anchor `health:<id>` from [health-budgets](/planning/okrs/health-budgets.md) (D59) — never a faked KR | Initiative Gate (intent + anchor) |
68
+ | "**Gather**, **clarify** or **sharpen** the requirements" — the PM has a vague ask or notes from gathering and no brief yet | **Requirements elicitation** (before the lane's first artifact) | [requirements-elicitation skill](/skills/requirements-elicitation.skill) → `planning/briefs/<id>-requirements.md` at `brief_path`; reads the lane's input from `scripts/lane-defs.js` and the notes `gather-requirements` filed (D71); the PM confirms each note item. It does not choose the lane and does not write the PRD | none (the brief is never a gate) |
68
69
 
69
70
  **Every route** still takes the universal steps: anchored initiative row (`create_initiative`),
70
71
  branch `initiative/<id>`, and the human Acceptance Gate at the end (D12) — the router never bypasses
@@ -99,5 +100,5 @@ Check your position at any time with `node scripts/workflow-state.js <id>`.
99
100
  already decided to explore, go straight to prototype.
100
101
  - **Anchor-free trivia** (a typo, a broken link): the `no-initiative` label, no lane. Real work
101
102
  never takes the hatch.
102
- - **Unknown shape after the checks:** ask the PM one routing question with a recommended answer —
103
- do not guess a lane.
103
+ - **Unknown shape after the checks:** ask the PM one routing question, as the
104
+ [requirements-elicitation skill](/skills/requirements-elicitation.skill) describes. Do not guess a lane.