okstra 0.25.1 → 0.27.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.
Files changed (48) hide show
  1. package/README.kr.md +16 -0
  2. package/README.md +16 -0
  3. package/docs/kr/architecture.md +3 -7
  4. package/docs/kr/cli.md +47 -4
  5. package/docs/kr/performance-improvement-plan-v2.md +23 -0
  6. package/docs/kr/performance-improvement-plan.md +22 -0
  7. package/docs/superpowers/specs/2026-05-15-implementation-plan-verification-design.md +254 -0
  8. package/package.json +1 -1
  9. package/runtime/BUILD.json +2 -2
  10. package/runtime/agents/SKILL.md +30 -2
  11. package/runtime/bin/okstra.sh +1 -1
  12. package/runtime/prompts/profiles/_common-contract.md +30 -1
  13. package/runtime/prompts/profiles/error-analysis.md +12 -0
  14. package/runtime/prompts/profiles/implementation-planning.md +23 -0
  15. package/runtime/prompts/profiles/requirements-discovery.md +20 -0
  16. package/runtime/python/lib/okstra/cli.sh +8 -7
  17. package/runtime/python/lib/okstra/globals.sh +3 -1
  18. package/runtime/python/lib/okstra/usage.sh +8 -4
  19. package/runtime/python/okstra_ctl/render.py +35 -0
  20. package/runtime/python/okstra_ctl/run.py +27 -6
  21. package/runtime/python/okstra_ctl/run_context.py +1 -1
  22. package/runtime/python/okstra_ctl/wizard.py +259 -10
  23. package/runtime/python/okstra_token_usage/blocks.py +5 -1
  24. package/runtime/python/okstra_token_usage/claude.py +16 -1
  25. package/runtime/python/okstra_token_usage/collect.py +17 -3
  26. package/runtime/python/okstra_token_usage/pricing.py +159 -24
  27. package/runtime/skills/okstra-brief/SKILL.md +532 -65
  28. package/runtime/skills/okstra-context-loader/SKILL.md +25 -11
  29. package/runtime/skills/okstra-convergence/SKILL.md +235 -8
  30. package/runtime/skills/okstra-history/SKILL.md +68 -37
  31. package/runtime/skills/okstra-logs/SKILL.md +26 -4
  32. package/runtime/skills/okstra-report-finder/SKILL.md +49 -22
  33. package/runtime/skills/okstra-report-writer/SKILL.md +59 -64
  34. package/runtime/skills/okstra-run/SKILL.md +53 -39
  35. package/runtime/skills/okstra-schedule/SKILL.md +51 -20
  36. package/runtime/skills/okstra-setup/SKILL.md +31 -12
  37. package/runtime/skills/okstra-status/SKILL.md +20 -8
  38. package/runtime/skills/okstra-team-contract/SKILL.md +27 -15
  39. package/runtime/skills/okstra-time-summary/SKILL.md +53 -16
  40. package/runtime/templates/reports/final-report.template.md +34 -0
  41. package/runtime/templates/reports/settings.template.json +7 -4
  42. package/runtime/validators/lib/fixtures.sh +10 -2
  43. package/runtime/validators/lib/validate-assets.sh +50 -24
  44. package/runtime/validators/validate-brief.py +385 -0
  45. package/runtime/validators/validate-brief.sh +35 -0
  46. package/runtime/validators/validate-run.py +71 -0
  47. package/runtime/validators/validate-workflow.sh +7 -33
  48. package/src/wizard.mjs +21 -5
@@ -9,11 +9,34 @@ Generate a single `task brief` markdown file under the okstra project domain
9
9
  (`.project-docs/okstra/briefs/<task-group>/<task-id>.md`) so it can be fed to
10
10
  [`okstra-run`](../okstra-run/SKILL.md) as `--task-brief`.
11
11
 
12
- This skill produces **only the brief** a structured "request slip". It does
13
- NOT write a PRD, decompose work into vertical slices, or decide modules.
14
- Those belong to later okstra phases (`requirements-discovery`,
15
- `implementation-planning`) which use cross-verification and would conflict
16
- with anything decided here.
12
+ **Purpose pre-discovery artifact.** A brief is the **pre-discovery** stage
13
+ of an okstra task: it absorbs domain alignment, codebase evidence
14
+ collection, and reporter intent capture so that the downstream expert
15
+ phases (`requirements-discovery` / `error-analysis` /
16
+ `implementation-planning`) can run with **zero fill-in questions** to the
17
+ operator. A brief is written from a report by a domain reporter (PM, CS,
18
+ ops, QA, end-user, …) **or** by a developer themselves — the input
19
+ provenance does not change the output contract.
20
+
21
+ This is a **labelled-handoff** to the next phase, built on two
22
+ non-negotiables:
23
+
24
+ - **Verbatim-preserving capture** — the reporter's words are kept
25
+ byte-for-byte under `Source Material`. Format-only transformations
26
+ (e.g. Jira ADF → Markdown) and codebase cross-references are mechanical
27
+ and reversible; they are recorded with the `format-conversion` and
28
+ `evidence-link` Augmentation labels.
29
+ - **Labelled interpretation** — anywhere the skill resolves a reporter
30
+ word to a project-canonical term or infers reporter meaning that was
31
+ not literally stated, the addition lives in `Augmentation` with the
32
+ `terminology-mapping` or `intent-inference` label. Unlabelled
33
+ augmentation is forbidden — the label is what makes the interpretation
34
+ auditable and (for `intent-inference`) verifiable against the reporter.
35
+
36
+ A brief still does NOT write a PRD, decompose work into vertical slices,
37
+ or decide modules. Those belong to later okstra phases
38
+ (`requirements-discovery`, `implementation-planning`) which use
39
+ cross-verification and would conflict with anything decided here.
17
40
 
18
41
  ## Intended chain
19
42
 
@@ -37,11 +60,33 @@ okstra-brief
37
60
  ## When NOT to use
38
61
 
39
62
  - The user already has a brief file at a known path → skip to `okstra-run`.
40
- - The user wants to write a full PRD with user stories and implementation
41
- decisions that's `to-prd`, not this. okstra phases will generate the
42
- equivalent artifacts with cross-verification.
43
- - The user wants to split work into issues that's `to-issues`, and within
44
- okstra it's the job of `implementation-planning`.
63
+ - The user wants a full PRD with user stories and implementation decisions
64
+ that's beyond a brief; okstra phases generate the equivalent artifacts
65
+ with cross-verification.
66
+ - The user wants to split work into issues within okstra that's the job
67
+ of `implementation-planning`.
68
+
69
+ ## Artifact-home rule (okstra-wide)
70
+
71
+ **All okstra-generated artifacts live under `<PROJECT_ROOT>/.project-docs/okstra/`.**
72
+ This includes briefs, run manifests, reports, worker results, status,
73
+ sessions, and any other run output. okstra itself never writes to `.scratch/`
74
+ or other project paths.
75
+
76
+ All writes by this skill stay inside `.project-docs/okstra/`. When domain
77
+ alignment (Step 3b / Step 4.5) yields a glossary change that the user
78
+ approves inline, the change is written to:
79
+
80
+ - `<PROJECT_ROOT>/.project-docs/okstra/glossary.md` — okstra's internal
81
+ glossary. Created if absent; appended to a `## Glossary` section
82
+ otherwise.
83
+
84
+ External `<PROJECT_ROOT>/CONTEXT.md` / `<PROJECT_ROOT>/CONTEXT-MAP.md` /
85
+ `<PROJECT_ROOT>/docs/adr/` are read-only references; this skill never
86
+ writes to them. Decision files (when raised by `implementation-planning`)
87
+ also live inside okstra's subtree at
88
+ `<PROJECT_ROOT>/.project-docs/okstra/decisions/<NNNN>-<slug>.md`, never
89
+ at external `docs/adr/`.
45
90
 
46
91
  ## Authority files
47
92
 
@@ -53,9 +98,12 @@ okstra-brief
53
98
  (tracker child ticket; at depth N, `sub/` is nested N times) — output
54
99
  location for this skill. `<ticket-id>` is the raw issue-id when the source
55
100
  is a tracker, otherwise free-text supplied by the user. `<file-title>` is
56
- auto-slugified from the tracker title, otherwise from user input. Never
57
- write outside this tree. Never use `.scratch/` (that belongs to `to-prd` /
58
- `to-issues`).
101
+ auto-slugified from the tracker title, otherwise from user input.
102
+ - okstra-internal glossary (write target of Step 4.5):
103
+ `<PROJECT_ROOT>/.project-docs/okstra/glossary.md`. Created if absent.
104
+ - okstra-internal decisions (write target of `implementation-planning`
105
+ via `implementation`):
106
+ `<PROJECT_ROOT>/.project-docs/okstra/decisions/<NNNN>-<slug>.md`.
59
107
 
60
108
  ## Step 0: Resolve project root
61
109
 
@@ -65,6 +113,11 @@ Reuse the same disk-only resolution rule as `okstra-run`:
65
113
  if command -v okstra >/dev/null 2>&1; then
66
114
  OKSTRA_CMD="okstra"
67
115
  else
116
+ # `~/.okstra/bin/okstra.sh` exists on every installed machine but it is
117
+ # the bash run-wrapper, NOT the Node CLI — it does not implement
118
+ # `check-project`. Do not branch into it as a fallback. Go straight to
119
+ # `npx` so the user always reaches a working `check-project` even when
120
+ # the Node CLI is off-$PATH.
68
121
  OKSTRA_CMD="npx -y okstra@latest"
69
122
  fi
70
123
  $OKSTRA_CMD check-project --cwd "$(pwd)"
@@ -104,6 +157,15 @@ in Source Material.
104
157
 
105
158
  Order of operations:
106
159
 
160
+ 0. **Task-group precondition (REQUIRED before any recursion)** — if the
161
+ tracker fetch may yield child tickets (which is always possible until the
162
+ first fetch proves otherwise), acquire `task_group` first by running Step
163
+ 2a now. Step 2a is otherwise re-entered later for non-tracker sources;
164
+ when reached again it MUST reuse the value collected here without asking
165
+ again. Rationale: the recursion in sub-step 6 records descendant brief
166
+ paths under `<task-group>/sub.../`, and those paths cannot be formed
167
+ until `task_group` is known. Without this preflight the recursive walk
168
+ would either stall mid-collection or invent a placeholder group.
107
169
  1. `AskUserQuestion` (free text):
108
170
  `"Ticket key or URL (e.g. LIN-1234, PROJ-42, https://linear.app/..., https://your.atlassian.net/browse/...)"` →
109
171
  `ticket_ref`.
@@ -140,26 +202,54 @@ Order of operations:
140
202
  - Look at child references in the fetched ticket response:
141
203
  - Linear: `children` / `subIssues` field
142
204
  - Jira: `subtasks` (or `issuelinks` with the `is parent of` relation)
143
- - GitHub: task-list checkbox `#NNN` references + `tracked_issues`
144
- (when present)
205
+ - GitHub: `gh issue view --json title,body,comments,labels,state` only
206
+ returns the issue body and comments — child relations are NOT in that
207
+ JSON. Resolve children via two paths:
208
+ 1. **task-list parsing** — scan the fetched body and comments for
209
+ markdown task-list checkboxes referencing other issues
210
+ (`- [ ] #NNN`, `- [x] owner/repo#NNN`, `- [ ] https://github.com/...`).
211
+ Capture each as a child candidate.
212
+ 2. **tracked / sub-issue GraphQL** — for GitHub Projects v2
213
+ "tracked issues" / sub-issue relations, the REST `gh issue view`
214
+ response does NOT carry them. Query GraphQL explicitly, e.g.
215
+ `gh api graphql -f query='query($o:String!,$r:String!,$n:Int!){repository(owner:$o,name:$r){issue(number:$n){trackedIssues(first:50){nodes{number repository{nameWithOwner}}} subIssues:trackedInIssues(first:50){nodes{number repository{nameWithOwner}}}}}}' -f o=<owner> -f r=<repo> -F n=<num>`.
216
+ If the GraphQL request fails (auth scope missing, feature
217
+ unavailable on this repo, network error), **do NOT silently
218
+ continue with full recursion**. Disable the "Generate the full
219
+ tree recursively" option for this branch, default to
220
+ `Parent only; list children in Related Artifacts`, and surface the
221
+ GraphQL failure to the user in one line so they can decide whether
222
+ to paste child bodies manually.
145
223
  - Notion: child pages / sub-pages
146
224
  - If there is **one or more** child, ask **once at the top parent**:
147
225
  - `AskUserQuestion` (single-select)
148
226
  - **Label**: `"This ticket has sub-tickets. How should the child tree be handled?"`
149
227
  - **Options**:
150
- 1. `Generate the full tree recursively (recommended)` — walk
151
- children, grandchildren, … via BFS/DFS and emit one brief per
152
- node.
153
- 2. `Parent only; list children in Related Artifacts` — single
154
- brief. Children are not fetched, only their keys/URLs are
155
- recorded.
156
- 3. `Generate selected children only` — multi-select the direct
157
- children; for each chosen child, apply option-1 policy
158
- recursively to that branch.
228
+ 1. `Full tree` (recommended) — walk children, grandchildren, …
229
+ via BFS/DFS and emit one brief per node.
230
+ 2. `Parent only` — single brief. Children are not fetched; their
231
+ keys/URLs are recorded under Related Artifacts.
232
+ 3. `Selected` multi-select the direct children; for each chosen
233
+ child, apply option-1 policy recursively to that branch.
159
234
  - For options 1 / 3, **recurse into Step 1b sub-steps 3–5** for every
160
235
  descendant. No depth limit. Maintain a visited set of
161
236
  `<tracker>:<ticket-id>` to prevent cycles; on revisit, do not emit a
162
237
  new brief — only add a link back to the existing brief.
238
+ - **Visited-set across re-runs (rebuild from disk)** — the in-memory
239
+ visited set is per-run and disappears between invocations. When this
240
+ skill runs again over a tree that already has briefs on disk, it MUST
241
+ reseed the visited set by scanning
242
+ `<PROJECT_ROOT>/.project-docs/okstra/briefs/<task-group>/` recursively
243
+ and reading each brief's frontmatter `ticket-id` + `source-type`.
244
+ Reseed precedence:
245
+ 1. On-disk frontmatter (`ticket-id` ≠ "") populates visited entries
246
+ as `<source-type>:<ticket-id>` — these win.
247
+ 2. Step 2c collision policy (`Skip` / `Append timestamp` /
248
+ `Overwrite`) applies to any node whose path is already taken.
249
+ 3. The fresh in-memory walk fills any gaps for tickets not yet on
250
+ disk.
251
+ A ticket present on disk is NOT refetched unless the user explicitly
252
+ answers `Overwrite` for that path.
163
253
  - Each descendant's brief file at depth N is created under
164
254
  `<task-group>/<sub/ nested N times>/<ticket-id>-<file-title>.md` per
165
255
  Step 2b.
@@ -181,20 +271,21 @@ Order of operations:
181
271
  3. On fetch failure or auth required (login wall, intranet-only, etc.):
182
272
  - Ask the user via `AskUserQuestion` to paste the body.
183
273
  - Never invent URL content.
184
- 4. Preserve the core content (title + body + quotes/examples) **verbatim**
185
- in Source Material. For very large pages, respect the ~400-line per-brief
186
- guideline and **excerpt key paragraphs only**, but do not modify or
187
- summarize the excerpted text by a single character. Annotate the excerpt
188
- with `[excerpt full: <URL>]`.
274
+ 4. Preserve the fetched content (title + body + quotes/examples) **verbatim**
275
+ in Source Material. Do not excerpt or summarize long pages to fit a line
276
+ budget. If the fetch tool returns truncated content or cannot expose the
277
+ full body, ask the user to paste/provide the missing body before writing
278
+ the brief, or record the fetched text exactly and add a
279
+ `conversion-block:` row that names the missing portion.
189
280
 
190
281
  ### 1d. `User input`
191
282
 
192
283
  Two sub-modes combined under one option.
193
284
 
194
285
  - **Conversation synthesis**: do not re-interview. Synthesize from the
195
- current conversation (mirrors `to-prd`'s "synthesize, don't interview").
196
- Label Source Material as `conversation synthesis` and quote key
197
- utterances verbatim wherever possible.
286
+ current conversation ("synthesize, don't interview"). Label Source
287
+ Material as `conversation synthesis` and quote key utterances verbatim
288
+ wherever possible.
198
289
  - **Inline input**: if conversation context is empty or thin, take one
199
290
  free-text `AskUserQuestion`. The received text is preserved in Source
200
291
  Material without changing a character.
@@ -213,6 +304,9 @@ Material.
213
304
  Slug rule: same as `okstra-run` Step 3 — slugify, must have at least one
214
305
  alphanumeric character.
215
306
 
307
+ If `task_group` was already collected by Step 1b sub-step 0 (tracker
308
+ preflight), reuse that value silently — do NOT ask again.
309
+
216
310
  ### 2b. Filename per source type
217
311
 
218
312
  Final brief path rule (fixed; one extra `sub/` per depth):
@@ -242,13 +336,16 @@ relations are also tracked via the brief's `parent-id` frontmatter
242
336
  - `"Ticket / identifier (e.g. dev-9043, INV-1234, login-error)"` →
243
337
  `ticket_id`
244
338
 
245
- `<file-title>` resolution:
339
+ `<file-title>` resolution (filename slug only — the verbatim rule applies
340
+ to Source Material body, not to the filename):
246
341
 
247
342
  - **Issue-tracker sources**: auto-generated from the ticket title / summary —
248
343
  do not ask the user.
249
344
  1. Keep alphanumerics, spaces, Hangul; drop everything else
250
345
  2. Replace spaces with `-`
251
- 3. Lowercase (Hangul untouched)
346
+ 3. Lowercase (Hangul untouched) — **filename normalization only**.
347
+ The Source Material section MUST preserve the original title's case
348
+ byte-for-byte; lowercase applies strictly to the on-disk filename.
252
349
  4. Cut to 60 chars at a word boundary (hard-cut at 60 if no boundary)
253
350
  5. Trim leading/trailing `-`
254
351
  - Example: Linear `LIN-1234 "Fix flaky login retry on 5xx"` →
@@ -263,44 +360,168 @@ alphanumeric character after slugification. Apply Step 2c on collision.
263
360
 
264
361
  ### 2c. Collision handling
265
362
 
266
- If a brief file already exists, `AskUserQuestion` with (`Skip if exists` /
267
- `Append timestamp suffix` / `Overwrite`). Default recommendation: `Skip if
268
- exists` — protects briefs the user has already edited.
363
+ If a brief file already exists, `AskUserQuestion` with (`Skip` /
364
+ `Append suffix` / `Overwrite`). Default recommendation: `Skip`
365
+ protects briefs the user has already edited. `Append suffix` adds a
366
+ timestamp suffix to the filename stem.
269
367
 
270
368
  When multiple collisions occur in tracker multi-generation mode:
271
369
 
272
370
  - First echo the colliding file list to the user.
273
371
  - Ask **once** with `AskUserQuestion` for a bulk policy.
274
372
  - **Even when `Overwrite` is selected, confirm one more time for each
275
- colliding file**, or warn that downgrading to `Append timestamp suffix`
276
- is safer. Silent bulk overwrite is forbidden.
277
-
278
- ## Step 3: Light context scan (optional but recommended)
373
+ colliding file**, or warn that downgrading to `Append suffix` is safer. Silent bulk overwrite is forbidden.
279
374
 
280
- Only when the source material references project-specific terms or files:
375
+ ## Step 3: Domain alignment scan (recommended)
281
376
 
282
- - Read `CONTEXT.md` / `docs/adr/` at repo root if present (domain vocabulary).
283
- - Resolve any file paths or symbols mentioned in the source so the brief uses
284
- the project's domain language, not generic phrasing.
377
+ Always check whether the project carries a domain context, then compare the
378
+ Source Material's terminology against it. This step exists to *surface*
379
+ mismatches it never edits user-owned domain docs and never rewrites Source
380
+ Material.
285
381
 
286
- Skip this step for purely external request material.
382
+ ### 3a. Locate domain assets
383
+
384
+ Read in this order — absent files are the normal state; skip silently and
385
+ never error:
386
+
387
+ 1. **okstra-internal (authoritative)** — always check first:
388
+ - `<PROJECT_ROOT>/.project-docs/okstra/glossary.md` if present
389
+ - `<PROJECT_ROOT>/.project-docs/okstra/decisions/` titles if present
390
+ 2. **External (supplementary read-only reference)** — read if present:
391
+ - `<PROJECT_ROOT>/CONTEXT.md` (or `CONTEXT-MAP.md` → per-context
392
+ `CONTEXT.md`)
393
+ - `<PROJECT_ROOT>/docs/adr/` titles
394
+ - `<PROJECT_ROOT>/.scratch/CONTEXT.md` (output of external skills like
395
+ grill-with-docs, if any)
396
+
397
+ If none of these exist, skip 3b/3c silently.
398
+
399
+ ### 3b. Term comparison
400
+
401
+ For each domain-significant noun in Source Material, classify:
402
+
403
+ - **conflict** — the source uses a term that `CONTEXT.md` defines
404
+ differently. Example: source says "cancellation" meaning refund, glossary
405
+ says "cancellation" means order-state transition.
406
+ - **fuzzy / overloaded** — a term that has no canonical definition yet but
407
+ is being used in ≥ 2 different senses in the source(s).
408
+ - **aligned** — matches the glossary; no action.
409
+
410
+ ### 3c. Output — translation as a first-class duty
411
+
412
+ The pre-discovery skill's job is to land every external-vocabulary noun
413
+ on a project-internal concrete object whenever possible.
414
+
415
+ - Each `conflict` or `fuzzy` term becomes:
416
+ - One entry under `Open Questions` with the prefix `terminology:`
417
+ (so the next phase can resolve it during `requirements-discovery`).
418
+ - One line under `Augmentation > Domain alignment` with the
419
+ `terminology-mapping` label (Step 4 label rules), recording the
420
+ observation (what the source said vs. what CONTEXT.md says).
421
+ - **File paths and symbols are resolved to absolute, in-repo references.**
422
+ When the source mentions a module / class / endpoint / config key, run
423
+ `Read` / `Grep` to locate the concrete file path (relative to
424
+ `<PROJECT_ROOT>`) and the canonical symbol name. Record the resolution
425
+ under `Augmentation > Domain alignment` with the `evidence-link` label,
426
+ and link the absolute path from `Related Artifacts`. This is what makes
427
+ the brief portable across downstream workers (Claude / Codex / Gemini)
428
+ that may operate in separate worktrees and need to point at the same
429
+ object.
430
+ - **Conversion-block signal** — when a reporter statement cannot be
431
+ mapped to project vocabulary even after codebase exploration (e.g. the
432
+ reporter names a UI element that does not exist by that name, or
433
+ describes behavior no module currently implements), do **not** guess
434
+ the target. Emit a `conversion-block:` entry under `Open Questions`
435
+ describing exactly what could not be translated, so the next phase
436
+ knows to query the reporter directly rather than infer.
437
+ - See Step 4.5 for the approval-gated path to actually write the
438
+ okstra-internal glossary at
439
+ `<PROJECT_ROOT>/.project-docs/okstra/glossary.md`. External
440
+ `CONTEXT.md` / `CONTEXT-MAP.md` / `docs/adr/` are never edited.
441
+
442
+ Skip 3b/3c for purely external request material with no overlap to the
443
+ project's domain.
287
444
 
288
445
  ## Step 4: Fill in missing fields (NO full interview)
289
446
 
290
447
  > **Verbatim-source rule**: Source Material collected in Step 1 must never
291
448
  > be paraphrased, summarized, or restructured. When supplementing missing
292
449
  > information, write only into the dedicated `Augmentation` section (see
293
- > Step 5); never modify Source Material by a single byte.
450
+ > Step 5); never modify Source Material by a single byte. **Anything
451
+ > learned by grilling in this step lands in `Augmentation` or `Open
452
+ > Questions` only.** brief is a pre-discovery artifact, not a PRD —
453
+ > decisions belong to later phases.
294
454
 
295
455
  Inspect the synthesized brief draft. For each REQUIRED section below that is
296
456
  empty or trivially thin **after** reading Source Material verbatim, ask **at
297
- most one** `AskUserQuestion` (free text) to fill it. Never ask about sections
298
- already covered by the source material.
457
+ most one** `AskUserQuestion` to fill it. Never ask about sections already
458
+ covered by the source material.
459
+
460
+ ### Sharpening pass (bounded grill)
461
+
462
+ The following rules absorb the useful parts of `grill-me` /
463
+ `grill-with-docs` without turning brief into a decision-making interview.
464
+
465
+ 1. **Codebase-first** — if a gap can be answered by `Read` / `Grep` / file
466
+ inspection (e.g. "does this symbol exist?", "what does this endpoint
467
+ return today?"), do that **instead of** asking the user. Only ask when
468
+ the codebase cannot answer.
469
+ 2. **Question carries a recommended answer** — every `AskUserQuestion`
470
+ issued in this step must put the recommended option **first** with the
471
+ suffix `(Recommended)`. Free-text fallbacks are still allowed, but the
472
+ recommendation must be visible.
473
+ 3. **One at a time** — never batch multiple questions into one
474
+ `AskUserQuestion` call. Ask, wait for the answer, fold it into the
475
+ draft, then ask the next.
476
+ 4. **Bounded budget** — at most **1** sharpening question per empty
477
+ required section, plus **at most 2** disambiguation questions arising
478
+ from Step 3b (terminology conflicts / fuzzy terms). Hard cap: **6
479
+ questions total** across the whole brief. Anything beyond the cap is
480
+ recorded under `Open Questions` instead of asked.
481
+ 5. **Scenario probe (optional)** — when `Desired Outcome` is empty or one
482
+ thin line, you may use **one** of the budgeted questions to pose a
483
+ single boundary scenario ("if X happens, is that in scope?") with a
484
+ recommended yes/no. Do not run a full edge-case sweep.
485
+ 6. **Stop conditions** — exit the sharpening pass when any of these holds:
486
+ (a) every required section has Source content or one Augmentation
487
+ entry, (b) the budget is exhausted, (c) the user signals "enough".
488
+ Remaining gaps → `_(none)_` for that section; remaining ambiguities →
489
+ `Open Questions`.
299
490
 
300
491
  Augmentation content must always be confined to the `Augmentation` section
301
- or to a `> augmented:` blockquote inside the required section, so it is
302
- clear that the content is the skill's / user's added interpretation rather
303
- than the original source.
492
+ or to a `> augmented: <label>` blockquote inside the required section, so
493
+ it is clear that the content is the skill's / user's added interpretation
494
+ rather than the original source.
495
+
496
+ ### Augmentation labels (REQUIRED — no unlabelled augmentation)
497
+
498
+ Every augmentation — both inline `> augmented: …` blockquotes and
499
+ `Augmentation` section bullet points — must carry exactly one of these
500
+ labels. Unlabelled augmentation is rejected as half-formed translation.
501
+
502
+ | Label | When to use | Downstream treatment |
503
+ |---|---|---|
504
+ | `evidence-link` | Cross-reference / file path / symbol resolved from the source against the actual codebase. Pure fact, verified by `Read` / `Grep`. | Trust without verification. |
505
+ | `format-conversion` | Format-only transform (Jira ADF → Markdown, HTML → Markdown, etc.). Semantics unchanged. | Trust without verification. |
506
+ | `terminology-mapping` | Reporter's word mapped to a project-canonical term (from `CONTEXT.md` or resolved during Step 3b). | Verify against `CONTEXT.md`; if mismatch, treat as a clarification candidate. |
507
+ | `intent-inference` | Reporter did NOT literally state this — the skill inferred meaning from context (e.g. classifying "가끔 안 됨" as "intermittent failure on a specific path"). Qualitative only — **never** invent quantitative thresholds (numbers, latencies, percentages, counts). | **Verify with the reporter before acting.** Auto-mirrored into `Open Questions`. |
508
+
509
+ **Inline form** — `> augmented: intent-inference — <one line>`.
510
+ **Section form** — `- intent-inference: <one line>` under `Augmentation`.
511
+
512
+ ### Auto-mirroring rule
513
+
514
+ Every `intent-inference` entry must have a paired entry in `Open Questions`
515
+ with the prefix `intent-check:` describing what the reporter must confirm.
516
+ This is what makes the inference auditable end-to-end: the inference is
517
+ visible (Augmentation), and the verification need is visible (Open
518
+ Questions). If a downstream worker resolves the inference without going
519
+ back to the reporter, the `intent-check:` row stays open as a record of
520
+ unverified translation.
521
+
522
+ `terminology-mapping` and `conversion-block:` follow the same dual-record
523
+ discipline (Augmentation observation ↔ `Open Questions` `terminology:` /
524
+ `conversion-block:` row).
304
525
 
305
526
  ### Step 4 policy under multi-brief (tracker recursion) mode
306
527
 
@@ -334,6 +555,66 @@ Sections **deliberately omitted** (do NOT add them, do NOT prompt for them):
334
555
 
335
556
  Those belong to later okstra phases.
336
557
 
558
+ ## Step 4.5: Domain proposals (with approval-gated edits)
559
+
560
+ For each `conflict` / `fuzzy` term collected in Step 3b that *was not*
561
+ resolved by a budgeted Step 4 question, draft a glossary proposal and
562
+ offer it to the user. This step writes to the **okstra-internal
563
+ glossary** at `<PROJECT_ROOT>/.project-docs/okstra/glossary.md` only —
564
+ external `<PROJECT_ROOT>/CONTEXT.md` / `CONTEXT-MAP.md` are never edited
565
+ by this skill.
566
+
567
+ > **Decision scope**: this skill does NOT evaluate decision candidates
568
+ > and does NOT draft decision files. Decision creation requires context
569
+ > that brief lacks (the recommended option, named alternatives,
570
+ > trade-off analysis). Brief only emits the signal — every candidate
571
+ > goes to `Open Questions` with the `adr-candidate:` prefix and is
572
+ > evaluated by `implementation-planning` against the three-criteria
573
+ > gate. Approved decision files land at
574
+ > `<PROJECT_ROOT>/.project-docs/okstra/decisions/<NNNN>-<slug>.md`,
575
+ > never at external `<PROJECT_ROOT>/docs/adr/`.
576
+
577
+ ### 4.5a. Draft glossary proposals
578
+
579
+ - **Glossary entry**: `proposed glossary entry: <term> = <one-line definition>`
580
+ — destined for `<PROJECT_ROOT>/.project-docs/okstra/glossary.md`.
581
+
582
+ ### 4.5b. Approval flow (per entry)
583
+
584
+ For each draft, **first echo the target absolute path on one line** (so
585
+ the user sees the exact file that will be written), then `AskUserQuestion`:
586
+
587
+ - **Label**: `"Apply glossary entry?"` (keep the label short — absolute
588
+ paths belong in the echo line above, not in the option chip)
589
+ - **Options**:
590
+ 1. `Apply (Recommended)` — write the change now.
591
+ 2. `Skip — keep as Augmentation note only` — leave the proposal in the
592
+ brief's `Augmentation > Domain alignment` section, do not edit the
593
+ file.
594
+ 3. `Edit first` — show the rendered content and accept a free-text
595
+ correction, then re-confirm.
596
+
597
+ Ask once per entry. Never batch. If the user picks `Apply`:
598
+
599
+ - Append the entry to
600
+ `<PROJECT_ROOT>/.project-docs/okstra/glossary.md` under an existing
601
+ relevant heading, or create a `## Glossary` section if none exists. If
602
+ the file does not exist, create it with a `## Glossary` heading and
603
+ the entry. Preserve all existing content byte-for-byte.
604
+
605
+ ### 4.5c. Brief record
606
+
607
+ Regardless of Apply / Skip, record the outcome under `Augmentation >
608
+ Domain alignment`:
609
+
610
+ - `terminology-mapping: applied glossary: <term> → <PROJECT_ROOT>/.project-docs/okstra/glossary.md` or
611
+ - `terminology-mapping: skipped glossary: <term> = <definition>`
612
+
613
+ Decision candidates are recorded under `Open Questions` as
614
+ `adr-candidate: <topic>` (targeting
615
+ `<PROJECT_ROOT>/.project-docs/okstra/decisions/`) — not here. This keeps
616
+ the brief itself self-documenting about what side effects it caused.
617
+
337
618
  ## Step 5: Write the brief(s)
338
619
 
339
620
  Use the same template per brief file. In tracker mode producing a parent
@@ -348,13 +629,14 @@ collide with the inner 3-backtick code block.)
348
629
  ---
349
630
  type: brief
350
631
  brief-id: <ticket-id>-<file-title> # equals the filename stem
351
- parent-id: self # `self` at root, otherwise parent's brief-id
632
+ parent-id: self # always `self` at root; child briefs use parent's brief-id
352
633
  ticket-id: <LIN-1234 | PROJ-42 | gh-repo-123 | notion-abcdef12 | "">
353
634
  source-type: <file | linear | jira | github | notion | url | user-input>
354
635
  task-group: <task-group>
355
636
  depth: 0 # 0=parent/single, 1=child, 2=grandchild, ...
356
637
  created: <YYYY-MM-DD>
357
638
  generator: okstra-brief
639
+ reporter-confirmations: <complete | partial | pending | skipped> # set by Step 6.5
358
640
  ---
359
641
 
360
642
  # Task Brief: <task_group>/<filename-without-ext>
@@ -364,6 +646,7 @@ generator: okstra-brief
364
646
  > Tracker key (if any): <LIN-1234 | PROJ-42 | gh-repo-123 | notion-abcdef12>
365
647
  > Parent brief (child briefs only): <relative path>
366
648
  > Recommended next phase: <requirements-discovery | error-analysis> ← from Step 6
649
+ > Handoff contract: see `prompts/profiles/_common-contract.md` § "Brief handoff contract"
367
650
 
368
651
  ## Source Material (verbatim — do not modify)
369
652
 
@@ -376,7 +659,7 @@ must be annotated in the header meta.
376
659
  - ref: <abs file path | LIN-1234 | https://... | "conversation synthesis">
377
660
  - fetched-via: <Read | mcp__linear__getIssue | mcp__notion__... | gh issue view | WebFetch | user-paste>
378
661
  - fetched-at: <YYYY-MM-DD HH:MM>
379
- - format: <as-is | "Jira ADF → Markdown (semantics preserved)" | "excerptfull: <URL>">
662
+ - format: <as-is | "Jira ADF → Markdown (semantics preserved)" | "tool-truncatedmissing body requested from reporter">
380
663
 
381
664
  ```
382
665
  <Paste the raw source here without changing a single character.>
@@ -391,8 +674,10 @@ must be annotated in the header meta.
391
674
  <Background / scope / why now. If self-evident from Source Material, quote
392
675
  it briefly and stop. Use the blockquote below when augmentation is needed.>
393
676
 
394
- > augmented: <Interpretation added by the skill or user. Do NOT add any
395
- > extra interpretation outside this blockquote.>
677
+ > augmented: <label> — <Interpretation added by the skill or user. Label
678
+ > MUST be one of: `evidence-link` / `format-conversion` /
679
+ > `terminology-mapping` / `intent-inference`. Do NOT add any extra
680
+ > interpretation outside this blockquote.>
396
681
 
397
682
  ## Problem / Symptom
398
683
 
@@ -416,7 +701,39 @@ none.>
416
701
 
417
702
  ## Open Questions
418
703
 
419
- - <Unresolved questions the user flagged. _(none)_ if none.>
704
+ Prefix every row with one of these signals so the next phase knows how to
705
+ handle it. Free-form rows are allowed only as `general:`.
706
+
707
+ - `general: <unresolved question the user flagged>`
708
+ - `terminology: <reporter word> — needs canonical resolution against <PROJECT_ROOT>/.project-docs/okstra/glossary.md`
709
+ - `intent-check: <restated inference> — confirm with reporter`
710
+ (auto-paired with every `intent-inference` augmentation)
711
+ - `conversion-block: <reporter statement> — could not be mapped to project vocabulary; reporter query required`
712
+ - `adr-candidate: <topic>` — signal only; `implementation-planning`
713
+ evaluates and, if accepted, drafts a decision file at
714
+ `<PROJECT_ROOT>/.project-docs/okstra/decisions/<NNNN>-<slug>.md`.
715
+
716
+ Use `_(none)_` only if every signal is empty. `intent-check:` and
717
+ `conversion-block:` rows that are answered in Step 6.5 are NOT removed
718
+ from this list — they receive a `[CONFIRMED <YYYY-MM-DD> → RC-N]`
719
+ marker that links to the corresponding entry under
720
+ `## Reporter Confirmations`.
721
+
722
+ ## Reporter Confirmations
723
+
724
+ Populated by Step 6.5. Each subsection records one reporter answer
725
+ verbatim, with a link back to the originating `Open Questions` row.
726
+
727
+ _(none — pending or skipped)_
728
+
729
+ <!-- when populated, the shape is:
730
+ ### RC-1 — <intent-check: or conversion-block: row id / topic>
731
+ - asked: <YYYY-MM-DD HH:MM>
732
+ - linked-row: `<exact Open Questions row text>`
733
+ - answer (verbatim):
734
+
735
+ > <reporter's answer, byte-for-byte>
736
+ -->
420
737
 
421
738
  ## Augmentation
422
739
 
@@ -424,7 +741,47 @@ Cross-references / interpretation / context added by the user or skill that
424
741
  is not in the original source. May be empty. Keep this section visually
425
742
  separated from Source Material — never inline it inside Source Material.
426
743
 
427
- - _(none)_ or `- <augmentation>`
744
+ Every entry below must start with one of the four labels:
745
+ `evidence-link` / `format-conversion` / `terminology-mapping` /
746
+ `intent-inference`. Unlabelled entries are forbidden.
747
+
748
+ ### Domain alignment
749
+
750
+ Observations from Step 3b and the outcome of Step 4.5 (glossary
751
+ applied vs. skipped). The actual glossary edits live in
752
+ `<PROJECT_ROOT>/.project-docs/okstra/glossary.md` when applied; this
753
+ section records what happened. Decision candidates are NOT recorded
754
+ here — they flow through `Open Questions` as `adr-candidate:` rows for
755
+ `implementation-planning` to evaluate (and, if accepted, draft into
756
+ `<PROJECT_ROOT>/.project-docs/okstra/decisions/`).
757
+
758
+ - `terminology-mapping: <reporter word> → <okstra glossary canonical>` —
759
+ routine glossary alignment, paired with `terminology:` in Open Questions
760
+ when unresolved.
761
+ - `terminology-mapping: applied glossary: <term> → <PROJECT_ROOT>/.project-docs/okstra/glossary.md` /
762
+ `terminology-mapping: skipped glossary: <term> = <definition>` — Step
763
+ 4.5 outcomes.
764
+ - Use `_(none)_` if every alignment entry is empty.
765
+
766
+ ### Evidence links (file / symbol resolution)
767
+
768
+ - `evidence-link: <reporter phrase> → <relative path>:<line>` /
769
+ `evidence-link: <reporter phrase> → <symbol> in <relative path>`
770
+ - `_(none)_` if none.
771
+
772
+ ### Intent inferences
773
+
774
+ Every entry here is an unverified hypothesis. Each one MUST have a paired
775
+ `intent-check:` row under Open Questions.
776
+
777
+ - `intent-inference: <reporter phrase> → <qualitative restatement>`
778
+ (qualitative only — never invent numeric thresholds)
779
+ - `_(none)_` if none.
780
+
781
+ ### Format conversions
782
+
783
+ - `format-conversion: <ref> — <e.g. Jira ADF → Markdown, semantics preserved>`
784
+ - `_(none)_` if none.
428
785
  ````
429
786
 
430
787
  ### Frontmatter rules
@@ -434,7 +791,7 @@ separated from Source Material — never inline it inside Source Material.
434
791
  - `brief-id` must match the filename stem (`<ticket-id>-<file-title>`)
435
792
  exactly. If the file is moved/renamed, update both.
436
793
  - `parent-id`:
437
- - The root (depth 0) brief uses `self` (or its own `brief-id`).
794
+ - The root (depth 0) brief always uses the literal string `self`.
438
795
  - Descendant briefs use the direct parent's `brief-id`.
439
796
  - `depth` must equal the number of `sub/` segments in the path (consistency
440
797
  check point). On mismatch, the file location wins — correct the
@@ -465,6 +822,110 @@ Write the chosen recommendation into the `Recommended next phase:` header
465
822
  line of the brief file (Step 5). Do NOT auto-spawn `okstra-run` — leave the
466
823
  trigger to the user.
467
824
 
825
+ ## Step 6.5: Reporter batch confirmation (option B precondition)
826
+
827
+ This is the boundary between brief and the downstream phases: every
828
+ question that only the reporter can answer is collected and answered
829
+ **here, once**, so the next phase runs without operator fill-ins.
830
+
831
+ ### 6.5a. Collect reporter-only rows
832
+
833
+ Scan the finalized brief's `Open Questions` and gather every row prefixed
834
+ with one of these signals — these are the only rows that the codebase
835
+ cannot resolve:
836
+
837
+ - `intent-check:` (auto-mirrored from `intent-inference` augmentations)
838
+ - `conversion-block:` (translation failed in Step 3c)
839
+
840
+ If the resulting list is empty, set `reporter-confirmations: complete` in
841
+ the brief's frontmatter and skip the rest of this step.
842
+
843
+ ### 6.5b. Ask once: collect now or later
844
+
845
+ If the list is non-empty, run **one** `AskUserQuestion`:
846
+
847
+ - **Label**: `"This brief has <N> question(s) only the reporter can answer. Collect their answers now?"`
848
+ - **Options**:
849
+ 1. `Yes — collect now (Recommended)` — proceed to 6.5c.
850
+ 2. `No — leave for the downstream phase` — set
851
+ `reporter-confirmations: skipped`. The phase will promote each
852
+ pending row into its own `## 5. Clarification Items` as
853
+ `Blocks=next-phase` (`Blocks=approval` only in
854
+ `implementation-planning`); see each phase profile's "Brief
855
+ consumption" addendum.
856
+
857
+ ### 6.5c. Batch the questions
858
+
859
+ For each pending row, formulate one question. Because the
860
+ `AskUserQuestion` tool caps options at 4 per call and questions per call
861
+ also at 4, split into batches of ≤ 4 questions.
862
+
863
+ **Hard cap — 12 questions per Step 6.5 run.** When the pending list
864
+ exceeds 12 rows, sort by priority and ask only the top 12 this round:
865
+
866
+ 1. `conversion-block:` rows first (translation failure — highest
867
+ downstream risk).
868
+ 2. `intent-check:` rows next, in the order they appear in `Open
869
+ Questions`.
870
+
871
+ Any rows beyond the cap are left unanswered in this run; set
872
+ `reporter-confirmations: partial` in the frontmatter and tell the user
873
+ which rows remain. The next `okstra-brief` re-run (or the downstream
874
+ phase that consumes `Blocks=next-phase`) handles the remainder.
875
+
876
+ Each question carries:
877
+
878
+ - The reporter-facing phrasing (plain language, no jargon).
879
+ - A recommended answer drawn from the brief's `intent-inference` /
880
+ `conversion-block:` content, marked `(Recommended)`.
881
+ - 2–3 alternative options when the answer space is naturally enumerable;
882
+ otherwise a free-text fallback.
883
+
884
+ ### 6.5d. Record verbatim
885
+
886
+ Create a new top-level section `## Reporter Confirmations` in the brief
887
+ (if not present) with one subsection per answered row:
888
+
889
+ ```markdown
890
+ ## Reporter Confirmations
891
+
892
+ ### RC-1 — <intent-check: or conversion-block: row id / topic>
893
+ - asked: <YYYY-MM-DD HH:MM>
894
+ - linked-row: `<exact Open Questions row text>`
895
+ - answer (verbatim):
896
+
897
+ > <reporter's answer, byte-for-byte>
898
+ ```
899
+
900
+ Mark the linked `Open Questions` row by appending `[CONFIRMED <YYYY-MM-DD> → RC-N]` to the end of the row's line. Do NOT delete the row — the
901
+ `CONFIRMED` marker keeps the audit trail intact.
902
+
903
+ For every `intent-inference` augmentation whose paired `intent-check:`
904
+ was confirmed, add one new line directly below the `> augmented:
905
+ intent-inference …` blockquote:
906
+
907
+ ```
908
+ > confirmed-by-reporter: RC-N — <one-line summary of confirmation>
909
+ ```
910
+
911
+ This converts the inference from "unverified hypothesis" to "verified
912
+ hypothesis" without rewriting the original augmentation.
913
+
914
+ ### 6.5e. Frontmatter state
915
+
916
+ Update `reporter-confirmations` in the brief frontmatter:
917
+
918
+ - `complete` — every pending row was answered.
919
+ - `partial` — some rows answered, some skipped within this run.
920
+ - `skipped` — user picked option 2 in 6.5b.
921
+ - `pending` — should never appear at this point; if it does, treat the
922
+ brief as not yet handed off and refuse to proceed.
923
+
924
+ Step 6.5 ends here. The brief now has either zero pending reporter-only
925
+ rows (`complete` / `partial` with remainder in the same file) or an
926
+ explicit `skipped` signal that downstream phases consume per the
927
+ "Brief consumption" addendum in `_common-contract.md`.
928
+
468
929
  ## Step 7: Hand off
469
930
 
470
931
  Single brief:
@@ -492,8 +953,14 @@ started.
492
953
 
493
954
  ## Output Rules
494
955
 
495
- - Never write outside `<PROJECT_ROOT>/.project-docs/okstra/briefs/`.
496
- - Never write to `.scratch/` — that path belongs to `to-prd` / `to-issues`.
956
+ - All okstra-generated artifacts live under
957
+ `<PROJECT_ROOT>/.project-docs/okstra/`. This skill's brief files go
958
+ under `.project-docs/okstra/briefs/`; its glossary writes go to
959
+ `.project-docs/okstra/glossary.md` (Step 4.5 approval flow).
960
+ - External paths (`<PROJECT_ROOT>/CONTEXT.md`,
961
+ `<PROJECT_ROOT>/CONTEXT-MAP.md`, `<PROJECT_ROOT>/docs/adr/`,
962
+ `<PROJECT_ROOT>/.scratch/`) are read-only references. This skill
963
+ never writes to them. Absent external files are the normal state.
497
964
  - **Verbatim source**: never paraphrase, summarize, restructure, or reorder
498
965
  the Source Material section. Only format conversion (ADF → MD, HTML → MD)
499
966
  is allowed, and the conversion must be annotated in the `format:` meta.
@@ -505,10 +972,10 @@ started.
505
972
  paste.
506
973
  - Never fabricate content for empty sections; use `_(none)_`.
507
974
  - Echo each `AskUserQuestion` outcome on one short line.
508
- - Aim for ~400 lines per brief total. If the source body is longer, **do
509
- not modify** it instead excerpt key paragraphs with the
510
- `[excerpt full: <URL>]` marker and record the original location in
511
- `Related Artifacts`.
975
+ - No line budget overrides the verbatim-source rule. If source content is
976
+ large, preserve it exactly as separate `Source Material` entries; if a
977
+ tool only returns a truncated body, ask the user for the missing content
978
+ or emit a `conversion-block:` row. Never silently excerpt or summarize.
512
979
 
513
980
  ## Failure Modes
514
981