@r1z4x/tezgah 1.5.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CHANGELOG.md +47 -1
  2. package/MANIFEST +6 -0
  3. package/bin/consult +13 -10
  4. package/bin/tezgah-docs +1 -1
  5. package/bin/tezgah-research +53 -3
  6. package/bin/tezgah-setup +1 -1
  7. package/bin/tezgah-task +192 -39
  8. package/docs/citations-baseline.json +1 -1
  9. package/docs/clarity-baseline.json +3 -3
  10. package/docs/contract.md +4 -1
  11. package/docs/feature-audit.md +24 -3
  12. package/docs/gate.md +10 -11
  13. package/docs/index.json +21 -7
  14. package/docs/research.md +46 -0
  15. package/docs/skills.md +23 -5
  16. package/hooks/tezgah_agents.py +3 -2
  17. package/hooks/tezgah_components.py +2 -1
  18. package/hooks/tezgah_context.py +65 -14
  19. package/hooks/tezgah_gate.py +62 -76
  20. package/hooks/tezgah_policy.py +38 -14
  21. package/hooks/tezgah_product.py +107 -0
  22. package/hooks/tezgah_research.py +3 -3
  23. package/hooks/tezgah_skill_pick.py +58 -17
  24. package/hooks/tezgah_task.py +2 -2
  25. package/hosts/opencode/plugins/tezgah.js +17 -37
  26. package/package.json +1 -1
  27. package/skills/analyze-app/SKILL.md +57 -0
  28. package/skills/design-contract/EVALS.md +24 -1
  29. package/skills/design-contract/SKILL.md +17 -1
  30. package/skills/design-contract/data-views.md +86 -0
  31. package/skills/feature-audit/SKILL.md +32 -3
  32. package/skills/plan-add/EVALS.md +8 -8
  33. package/skills/plan-add/SKILL.md +20 -21
  34. package/skills/plan-sync/EVALS.md +2 -2
  35. package/skills/plan-sync/SKILL.md +11 -7
  36. package/skills/pm-frameworks/SKILL.md +6 -0
  37. package/skills/product-analysis/EVALS.md +23 -1
  38. package/skills/product-analysis/SKILL.md +50 -11
  39. package/skills/product-flow/EVALS.md +81 -0
  40. package/skills/product-flow/SKILL.md +91 -0
  41. package/skills/research/SKILL.md +10 -1
  42. package/skills/tezgah-contract/SKILL.md +63 -39
@@ -21,7 +21,7 @@
21
21
  "unjudged": 2
22
22
  },
23
23
  "docs/gate.md": {
24
- "unjudged": 40
24
+ "unjudged": 39
25
25
  },
26
26
  "docs/glossary.md": {
27
27
  "unjudged": 12
@@ -42,10 +42,10 @@
42
42
  "semicolons": 11
43
43
  },
44
44
  "docs/gate.md": {
45
- "long_sentences": 67,
46
- "passive": 32,
45
+ "long_sentences": 66,
46
+ "passive": 31,
47
47
  "replace_words": 1,
48
- "semicolons": 45
48
+ "semicolons": 44
49
49
  },
50
50
  "docs/glossary.md": {
51
51
  "long_sentences": 51,
package/docs/contract.md CHANGED
@@ -276,7 +276,10 @@ sentence of the rule.
276
276
 
277
277
  `PROMPT_HINTS` (`hooks/tezgah_context.py::PROMPT_HINTS`) is one compiled pattern per key
278
278
  — `spec`, `consult`, `research`, `product`, `graph`, `fanout` — and `classify_prompt()` returns the keys
279
- a prompt matches (`hooks/tezgah_context.py::classify_prompt`). `fanout` arms on the
279
+ a prompt matches (`hooks/tezgah_context.py::classify_prompt`). It also adds `product`
280
+ for a data ask: a dashboard, a chart or a metric to choose. The check reads each
281
+ clause on its own (`hooks/tezgah_context.py::_data_ask`). A code marker in that
282
+ clause keeps the ask out: a file name, an identifier, SQL or a build word. `fanout` arms on the
280
283
  shape of the request, not a word. Two list entries on their own lines arm it.
281
284
  An entry starts with `1.`, `1)`, `1 -`, `1:`, `a)` or a bullet. An inline
282
285
  `1. ... 2. ...` arms it too. So do three files named as one list, "First, ...
@@ -1,7 +1,8 @@
1
1
  # Feature audit: one feature, every layer
2
2
 
3
3
  Audience: the agent and the maintainer answering "why does this screen do that"
4
- or "make this feature better" on an existing product surface.
4
+ or "make this feature better" on an existing product surface. It also serves
5
+ whoever specifies a feature before the build.
5
6
 
6
7
  A screen review judges what is on the screen. Most of what goes wrong in a real
7
8
  feature is not on the screen: it is a disagreement between two layers - the surface
@@ -20,7 +21,7 @@ sourced to WAI-ARIA APG and WCAG 2.2.
20
21
  It is reached the same way `product-analysis` is: the `product` task class arms the
21
22
  rule (`hooks/tezgah_context.py::PROMPT_HINTS`), whose coherence paragraph
22
23
  (`hooks/tezgah_policy.py::CORE`) names the feature unit and the matrices, and
23
- `skills/product-analysis/SKILL.md:51` runs the coherence pass before its axes.
24
+ `skills/product-analysis/SKILL.md:74` runs the coherence pass before its axes.
24
25
  `skills/tezgah-contract/SKILL.md:725-737` carries the long form for a session that
25
26
  reads the contract.
26
27
 
@@ -38,6 +39,24 @@ step flow, a notification. It is not a form-only method.
38
39
  | Interaction dependency | the controls whose change alters another's valid set | dependents, required action on change, state owner, announcement | a stale dependent value; a silent reset; a filter showing a state the view no longer has |
39
40
  | Surface pattern | the controls, data views, flows and notifications | the decidable rule, with its source | a combobox with no expanded state; a results count announced nowhere; a destructive confirmation that hides what it destroys |
40
41
 
42
+ ## Forward mode: the spec before the build
43
+
44
+ For a feature that does not exist yet, fill the five matrices from the decision
45
+ first. They go into `.tezgah/analysis/<feature>/spec.md` before the build. Mark a
46
+ cell the decision leaves open, and ask it. Never guess it.
47
+
48
+ After the build, run the normal audit and compare it with `spec.md` row by row.
49
+ Every disagreement is a finding. Examples: a capability no layer delivers, a field
50
+ no view renders, a missing state, an unasked layer. The method lives
51
+ in `skills/feature-audit/SKILL.md`, "Forward mode".
52
+
53
+ The decision the spec builds from is the one written under `## decision` in
54
+ `.tezgah/analysis/<topic>/flow.md`. `spec.md` names it, and so does the audit
55
+ after the build. The `product-flow` skill orders the steps around the audit:
56
+ `tezgah-research next --topic <t>` reads the forward spec as done once `spec.md`
57
+ exists (`hooks/tezgah_product.py::next_steps`). See "The next step of a product
58
+ flow" in `docs/research.md`.
59
+
41
60
  ## The gate that makes it more than advice
42
61
 
43
62
  A finding whose fix names a layer that does not exist yet is not a UI finding. It
@@ -128,7 +147,7 @@ rubric, then three passes of the feature-audit skill:
128
147
  ## Source of truth
129
148
 
130
149
  - `skills/feature-audit/SKILL.md` - the method: the five matrices, the proposal
131
- gate, the per-surface rule table and the hand-check list
150
+ gate, the forward-mode spec, the per-surface rule table and the hand-check list
132
151
  - `skills/product-analysis/SKILL.md` - the coherence pass and the artifact element
133
152
  that carries the matrices
134
153
  - `hooks/tezgah_context.py` - `PROMPT_HINTS`, whose `product` alternation carries
@@ -137,3 +156,5 @@ rubric, then three passes of the feature-audit skill:
137
156
  `skills/tezgah-contract/SKILL.md` as its long form
138
157
  - `tests/test_context.py`, `tests/test_setup.py` - the arming table and the router
139
158
  line that pin the wiring
159
+ - `skills/product-flow/SKILL.md` and `hooks/tezgah_product.py::FLOWS` - the step
160
+ order around the forward spec and the audit, printed by `tezgah-research next`
package/docs/gate.md CHANGED
@@ -93,7 +93,7 @@ It reads the arguments of `touch`, `rm`, `rmdir`, `unlink`, `mv`, `chmod`, `mkdi
93
93
  It reads the value of `cp -t`, `curl -o`, `wget -O`, `tar -C` on extraction and `unzip -d` (`OPTION_TARGETS`). It expands `~`, `$HOME` and the XDG
94
94
  variables first. It follows a `cd` within the line and reads past `if`/`then`/`do`/`!`/`{`. It opens `eval` and `bash -c`, and any flag cluster holding
95
95
  `c` is `-c`. A path counts as written and with its last link resolved. A quoted word is a word, so a grep or a commit message that names a switch passes. `SHELL_WRITE` stays as it was: it feeds the
96
- task phase rule and the opencode mirror. The task CLI stays the task rule's (below), so `task-off` still removes all of that rule. Told: what the call would
96
+ task phase rule and the opencode mirror. The task CLI is not a control-plane target: the session runs it itself (see the task rule below). Told: what the call would
97
97
  change, and that the user changes it in their own terminal (`CONTROL_DENY` `hooks/tezgah_gate.py::CONTROL_DENY`). The text names no command. Standing. No switch
98
98
  but `pretooluse-off` removes the rule, and that switch is a protected file. The rule cannot tell a change the user asked for from one the agent chose. It
99
99
  refuses both inside the session, and it never sees the user's own terminal. Plan 050's replay of three weeks of real ledgers found the two evidence-only
@@ -159,22 +159,21 @@ Scratch is skipped: a temp file or a device (`hooks/tezgah_integrity.py::scratch
159
159
  Told: re-read the file and re-apply the change to what is on disk now (`RACE_DENY`
160
160
  `hooks/tezgah_gate.py::RACE_DENY`). Standing (`RACE_REFUSE = True` `hooks/tezgah_gate.py::RACE_REFUSE`) — a notice would not close a silent overwrite — and it lifts only by time.
161
161
 
162
- ### Task — the user's own phase and path allowlist
162
+ ### Task — the active plan's phase and path allowlist
163
163
 
164
164
  Trigger: a write tool, or a shell command that writes a file, while an active task is set — a plan file under `.tezgah/plans/open` whose frontmatter carries a valid
165
165
  `phase` (`tezgah_task.active`). The phase refuses a write in a reading phase (`hooks/tezgah_gate.py::TASK_PHASE_DENY`) and a shell command that writes one (`hooks/tezgah_gate.py::TASK_SHELL_DENY`,
166
- against `hooks/tezgah_gate.py::SHELL_WRITE`); the allowlist refuses a target outside it (`hooks/tezgah_gate.py::TASK_SCOPE_DENY`) — a write tool's path, and a shell command's redirect or `tee` target (`hooks/tezgah_gate.py::write_paths`); and the record itself is refused twice over — a write whose target IS it, by a write tool or a shell redirect/`tee`
167
- (`hooks/tezgah_gate.py::TASK_RECORD_DENY`) and a shell command that would change it through the CLI, named by its basename with or without `.py` (`hooks/tezgah_gate.py::TASK_UNLOCK_DENY`, matched on the masked text by `hooks/tezgah_gate.py::TASK_CHANGE`). Told:
168
- none of them names the command that lifts it, on purpose — with the unlock printed, an armed arm removed or disabled the gate in 25 of 25 runs and obeyed it in none
169
- (`hooks/tezgah_gate.py:882-886`) — so each names the phase or the allowlist as the user's to change. Standing: only the record changing lifts it.
166
+ against `hooks/tezgah_gate.py::SHELL_WRITE`); the allowlist refuses a target outside it (`hooks/tezgah_gate.py::TASK_SCOPE_DENY`) — a write tool's path, and a shell command's redirect or `tee` target (`hooks/tezgah_gate.py::write_paths`); and a write whose target IS the record, by a write tool or a shell redirect/`tee`, is refused
167
+ (`hooks/tezgah_gate.py::TASK_RECORD_DENY`), because only `bin/tezgah-task` writes it and that CLI makes the checks a retyped frontmatter would skip. Told:
168
+ the move that lifts it, made by the session itself — `tezgah-task phase implementation` or `tezgah-task allow <glob> ... --why "<reason>"` — since the orchestrator owns the plan lifecycle and the CLI refuses
169
+ the move until the plan's Acceptance, spike and checkpoint are in order, and records the reason for a widened scope (`allowed_why:`). Standing: only the record changing lifts it.
170
170
  Scratch meets no allowlist: a temp file or a device, such as `pytest > /tmp/check.log` (`hooks/tezgah_gate.py::task_reason`). Build output inside the repo stays held. Off: `task-off`
171
- (`hooks/tezgah_gate.py::decision`). It fails open wherever the question cannot be answered: no active task, no readable record; the record is written by `bin/tezgah-task` and
172
- never by the agent, so this rule refuses on a decision the user made rather than one the gate inferred.
171
+ (`hooks/tezgah_gate.py::decision`). It fails open wherever the question cannot be answered: no active task, no readable record.
173
172
 
174
- The fifth refusal under the same rule is the checkpoint. Trigger: a write, or a shell command that writes a file (`hooks/tezgah_gate.py::SHELL_WRITE`), while the task is in
173
+ The fourth refusal under the same rule is the checkpoint. Trigger: a write, or a shell command that writes a file (`hooks/tezgah_gate.py::SHELL_WRITE`), while the task is in
175
174
  `phase: implementation`, its record reads `checkpoint: pending <sha>`, that sha is still the checkout's HEAD and the tree is dirty (`checkpoint_reason()`
176
175
  `hooks/tezgah_gate.py::checkpoint_reason`, `hooks/tezgah_gate.py::CHECKPOINT_DENY`). Told: the task it read, that the record holds a `pending` checkpoint, and the one command
177
- that makes the boundary real - unlike the refusals above it names that command, because running it *satisfies* the rule rather than taking it off. Standing: the recorded sha is the switch, not the tree - the commit
176
+ that makes the boundary real - it names that command because running it *satisfies* the rule rather than taking it off. Standing: the recorded sha is the switch, not the tree - the commit
178
177
  the refusal names moves HEAD off that sha, so the refusal clears itself, where keying on the word `pending` alone left the phase waiting on a record only
179
178
  `bin/tezgah-task` rewrites. Off, with the rest of the rule: `task-off`. It fails open wherever the question cannot be answered: no record, no `checkpoint:` key, a
180
179
  checkpoint that is a plain sha (the boundary is already real), a `pending` with no sha, a sha this checkout's HEAD does not name, and a repo whose HEAD cannot be read.
@@ -236,7 +235,7 @@ tail still shows that failure, under `verify-off` (`hooks/tezgah_gate.py::decisi
236
235
  ### Loop — the identical attempt that already failed twice
237
236
 
238
237
  Trigger: the same call (same `call_id`) failed `LOOP_ATTEMPTS = 2` times already in the current user turn (`hooks/tezgah_gate.py::LOOP_ATTEMPTS`, `hooks/tezgah_gate.py::loop_reason`). Told: attempt N
239
- of an identical call, the failure class read from the ledger (`transient`, `user`, `permanent`, `unknown`; the cap is the same for every class, and a `user` failure - a 401/403, a missing credential, a login - says to ask the user), "change the approach or stop" (`hooks/tezgah_gate.py::loop_reason`). Standing while the ledger tail still shows those
238
+ of an identical call, the failure class read from the ledger (`transient`, `user`, `permanent`, `unknown`; the cap is the same for every class, and a `user` failure - a 401/403, a missing credential, a login - says to take a route that needs no credential first and ask the user once only when none is left), "change the approach or stop" (`hooks/tezgah_gate.py::loop_reason`). Standing while the ledger tail still shows those
240
239
  failures. Under `verify-off` (`hooks/tezgah_gate.py::decision`).
241
240
 
242
241
  ### Retry — the session-wide ceiling
package/docs/index.json CHANGED
@@ -267,7 +267,8 @@
267
267
  "what do init, claim, migrate and source write",
268
268
  "how does a session learn that a research line is broken",
269
269
  "what lights the research mark and how is the layer switched off",
270
- "why may a new research line not be opened while another is unfinished"
270
+ "why may a new research line not be opened while another is unfinished",
271
+ "what does tezgah-research next print for a product flow, and when is a decision due for re-measure"
271
272
  ],
272
273
  "answers_tr": [
273
274
  "bir araştırma hattı nerede yaşar ve ne tutar",
@@ -275,11 +276,13 @@
275
276
  "init, claim, migrate ve source ne yazar",
276
277
  "bir oturum bir araştırma hattının bozuk olduğunu nasıl öğrenir",
277
278
  "araştırma işaretini ne yakar ve katman nasıl kapatılır",
278
- "başka biri bitmemişken neden yeni bir araştırma hattı açılamaz"
279
+ "başka biri bitmemişken neden yeni bir araştırma hattı açılamaz",
280
+ "tezgah-research next bir ürün akışı için ne yazdırır ve bir karar ne zaman yeniden ölçülmeli"
279
281
  ],
280
282
  "sources": [
281
283
  "hooks/tezgah_research.py",
282
284
  "bin/tezgah-research",
285
+ "hooks/tezgah_product.py",
283
286
  "hooks/tezgah_policy.py",
284
287
  "hooks/tezgah_context.py",
285
288
  "skills/research/SKILL.md",
@@ -294,12 +297,18 @@
294
297
  "answers": [
295
298
  "what is a skill here and how does it reach a host",
296
299
  "which skills ship and what does each do",
297
- "how do I add a skill without breaking the router or the install report"
300
+ "how do I add a skill without breaking the router or the install report",
301
+ "which skill designs a metric dashboard and where do its data-view rules live",
302
+ "does the local skill search reach design-library's nested entries",
303
+ "which skill carries a new metric dashboard or feature from research to re-measure"
298
304
  ],
299
305
  "answers_tr": [
300
306
  "burada beceri nedir ve bir hosta nasıl ulaşır",
301
307
  "hangi beceriler gelir ve her biri ne yapar",
302
- "yönlendiriciyi veya kurulum raporunu bozmadan nasıl beceri eklerim"
308
+ "yönlendiriciyi veya kurulum raporunu bozmadan nasıl beceri eklerim",
309
+ "metrik dashboard tasarımını hangi beceri yapar ve veri görünümü kuralları nerede",
310
+ "yerel beceri araması design-library'nin iç girdilerine ulaşır mı",
311
+ "yeni bir metrik dashboard'u veya özelliği araştırmadan yeniden ölçüme hangi beceri taşır"
303
312
  ],
304
313
  "sources": [
305
314
  "bin/tezgah-setup",
@@ -309,7 +318,10 @@
309
318
  "commands/",
310
319
  "NOTICE",
311
320
  "tests/test_setup.py",
312
- "tests/test_skills.py"
321
+ "tests/test_skills.py",
322
+ "hooks/tezgah_skill_pick.py",
323
+ "skills/design-contract/data-views.md",
324
+ "skills/product-flow/SKILL.md"
313
325
  ]
314
326
  },
315
327
  {
@@ -320,12 +332,14 @@
320
332
  "answers": [
321
333
  "what does a feature-level audit cover that a screen review misses",
322
334
  "what are the five matrices and the capability-change proposal",
323
- "how is the feature-audit skill reached and what evidence backs it"
335
+ "how is the feature-audit skill reached and what evidence backs it",
336
+ "how do I specify a feature before it is built and audit it against that spec"
324
337
  ],
325
338
  "answers_tr": [
326
339
  "özellik düzeyinde bir denetim, ekran incelemesinin kaçırdığı neyi kapsar",
327
340
  "beş matris ve yetenek değişikliği önerisi nedir",
328
- "özellik denetimi becerisine nasıl ulaşılır ve onu hangi kanıt destekler"
341
+ "özellik denetimi becerisine nasıl ulaşılır ve onu hangi kanıt destekler",
342
+ "bir özelliği yapılmadan önce nasıl tanımlarım ve yapıldıktan sonra o tanıma göre nasıl denetlerim"
329
343
  ],
330
344
  "sources": [
331
345
  "skills/feature-audit/SKILL.md",
package/docs/research.md CHANGED
@@ -238,14 +238,59 @@ through its seal.
238
238
  | `tezgah-research source <slug> <hypothesis> --run <orxRunId> [--command "..."] [--scope real\|fixture\|derived] [--fixture "<what was generated>"]` | keeps the receipt: runs `orx logs <runId>`, writes `raw/<runId>.log`, appends the results row `{"source": "orx:<runId>", ...}` and the scope and fixture description the filer states (`source_run`, `hooks/tezgah_research.py::source_run`). A `--scope fixture` filed without `--fixture` still writes the row and prints the field it still owes, so the tool is never the thing that makes its own checker warn silently; a given `--fixture` that is empty is misuse | 0, 1 nothing filed, 2 without orx |
239
239
  | `tezgah-research compare <slug> <decision>` | reads one variants x criteria cell from stdin and appends it to `decisions/<decision>/comparison.jsonl` under the lock, or refuses it by the rule `check` applies (`append_comparison` and `comparison_problems`, `hooks/tezgah_research.py`); it notes when `criteria.json` is not committed yet | 0, 1 refused, 2 misuse |
240
240
  | `tezgah-research close <slug> --limit "<reason>"` | concludes the line as a deliberate limit, writing the reasons it was still open into `state.json` `closed` and `log.md`, and seals it (`close_line`) | 0, 1 unreadable state, 2 misuse |
241
+ | `tezgah-research verdict <slug> <id> met\|not-met\|unanswerable --evidence "<ref>"` | records one success criterion's verdict and the evidence it rests on in the line's `state.json`, or refuses it with one `FAIL verdict:` line per problem (`verdict_problems`, `hooks/tezgah_research.py::verdict_problems`) | 0, 1 refused or unreadable state, 2 misuse |
242
+ | `tezgah-research conclude <slug>` | concludes a line once every success criterion carries a verdict, seals it and moves it to `done/` (`conclude_line`, `hooks/tezgah_research.py::conclude_line`) | 0, 1 refused, 2 misuse |
243
+ | `tezgah-research migrate-layout` | moves flat `research/<slug>` directories into `open/` or `done/` by each line's state, and is idempotent (`migrate_layout`, `hooks/tezgah_research.py::migrate_layout`) | 0, 2 misuse |
241
244
  | `tezgah-research seal <slug> [--history-lost] --ack "<owner decision>"` | seals a line under `done/` that was concluded before seals existed: plainly, as `conclude` would, when every experiment's order still checks; with `--history-lost`, with that verdict for every experiment whose order the lost history left undecidable. Refuses a line with a real order violation, a plain seal on any order finding, and a `history-lost` seal with nothing lost (`retro_seal`, `hooks/tezgah_research.py::retro_seal`) | 0, 1 refused, 2 misuse |
242
245
  | `tezgah-research import <checkout> [<slug>] [--allow-open "<reason>"]` | brings a line - or every line this checkout lacks - from another checkout's `.tezgah` repository with its history; an open line arrives beside open ones only with `--allow-open` (`import_line`, `hooks/tezgah_research.py::import_line`) | 0, 1 refused, 2 misuse |
246
+ | `tezgah-research next --topic <t> [--flow f] [--json]` | prints every step of a product flow with its status from the topic's files under `.tezgah/analysis/<topic>/`, then the first missing or due step's skill and command; `--flow` picks `metric-dashboard` (the default) or `feature`, and `--json` prints the same steps as data (`next_steps`, `hooks/tezgah_product.py::next_steps`) | 0, 2 unknown flow or no `--topic` |
243
247
 
244
248
  Exit code 2 is always misuse, so a caller can tell it from a line that fails the
245
249
  checks (`misuse`, `bin/tezgah-research::misuse`). `check` asks nothing at all - no
246
250
  network, no model - so its answer is reproducible on a machine with no
247
251
  credential; only `--orx` asks the one question that leaves the machine.
248
252
 
253
+ ## The next step of a product flow
254
+
255
+ `tezgah-research next --topic <t>` tells the session which step of a product flow
256
+ comes next. Its step table is `FLOWS` (`hooks/tezgah_product.py::FLOWS`).
257
+ Each step names its `id`, the `skill` that runs it, what it `leaves` behind and
258
+ the `command` that writes it. The `product-flow` skill lists the same nine steps
259
+ in prose and names `next` as their status source.
260
+
261
+ Two flows exist. `metric-dashboard` is the default and has nine steps. `feature`
262
+ drops the metrics and instrumentation steps, unless the topic's `flow.md` already
263
+ holds `## metrics`. `--flow <f>` picks one.
264
+
265
+ `next_steps` (`hooks/tezgah_product.py::next_steps`) computes each status from the
266
+ topic's folder, `.tezgah/analysis/<topic>/`, and reads nothing else. Nothing
267
+ stores a status. A step is `done`, `missing` or `due`:
268
+
269
+ | Step | Done when |
270
+ |---|---|
271
+ | `forward-spec` | `.tezgah/analysis/<topic>/spec.md` exists |
272
+ | `re-measure` | `flow.md` holds `## re-measure`. Before that it reads `due` once the `observe after: YYYY-MM-DD` date under `## decision` has passed |
273
+ | every other step | `flow.md` holds its `## <step>` heading |
274
+
275
+ The `design` and `build` commands start with `tezgah-design check`. The design
276
+ check and the Stop UI rule judge those two steps. The flow only asks for their
277
+ note. A topic that is not kebab-case (at most 64 characters) reads no file. A
278
+ topic with no folder yet lists every step as `missing`.
279
+
280
+ The text output prints one line per step, then one `next:` line:
281
+
282
+ ```
283
+ <n>. <id> [<status>] <skill> - <detail>
284
+ next: <skill> - <command>
285
+ ```
286
+
287
+ The `next:` line names the first `missing` or `due` step's skill and command.
288
+ With no such step it reads `next: nothing left`. `--json` prints the same steps
289
+ as a list of objects with `id`, `skill`, `status`, `detail` and `command`.
290
+
291
+ `next` exits 0 when it prints the steps. It exits 2 for an unknown flow or a
292
+ missing `--topic`.
293
+
249
294
  ## The pair the order rule compares
250
295
 
251
296
  The order rule is decided on the commit graph, so it needs both files committable
@@ -832,6 +877,7 @@ armed. It removes the rule, not just the mark - the diff is in `core_split`,
832
877
  component, the keys a prediction row names and the rules a row's outcome is
833
878
  attributed to.
834
879
  - `bin/tezgah-research` - the CLI, its subcommands and its exit codes.
880
+ - `hooks/tezgah_product.py` - the product-flow steps and the `next` status reader.
835
881
  - `hooks/tezgah_policy.py` - the `RESEARCH` paragraph a session is armed with.
836
882
  - `hooks/tezgah_context.py` - the prompt hint, the missing-orx note, the broken
837
883
  line note and the `research` mark.
package/docs/skills.md CHANGED
@@ -128,6 +128,12 @@ rebuilds that index only when a SKILL.md's mtime or size moves
128
128
  (`hooks/tezgah_skill_pick.py::_open_index`). A prompt reads its own terms'
129
129
  postings and the length column, never the corpus.
130
130
 
131
+ Some skills route through their own `INDEX.md`, like design-library's 39 entries.
132
+ The search indexes each routed entry as one row for the whole file, addressed
133
+ `skill://<skill>/<path>` (`hooks/tezgah_skill_pick.py::_entries`). The vendored
134
+ sections are short, and per section BM25 let them outrank whole skills on two
135
+ common words.
136
+
131
137
  The hook then appends one line, `A local skill search matched this request:
132
138
  skill://<name>:<start>-<end>`, with the section title. Every host but omp also
133
139
  gets the absolute path. The top section must carry 60% of the prompt's
@@ -174,15 +180,16 @@ it (`tests/test_skills.py::SkillStandards`).
174
180
  | `ponytail` | Forces the laziest solution that works, in levels lite/full/ultra; fires on any coding task, or "be lazy", "yagni", "shortest path". |
175
181
  | `i-have-adhd` | Shapes output for a reader who acts on it - action first, numbered steps, errors as location/cause/fix; fires on any answer the reader must act on, not on code, commits or docs. |
176
182
  | `no-ai-slop` | Edits a draft sharper and more human without losing the voice, or flags slop; fires on a draft review, on a draft in a language other than English, on text that must keep terms, identifiers or unit symbols verbatim, and before publishing English prose tezgah writes itself. |
177
- | `analyze-app` | Inspects and drives a running web or mobile app from its accessibility / DOM / view tree, its rendered screen (screenshots, blur and grayscale tests, breakpoints) and measurements taken in the page; fires when a task must verify behaviour or read the UI, not just read source. |
183
+ | `analyze-app` | Inspects and drives a running web or mobile app from its accessibility / DOM / view tree, its rendered screen (screenshots, blur and grayscale tests, breakpoints) and measurements taken in the page, and writes the `.tezgah/design/measurement.json` that `design-contract`'s check reads; fires when a task must verify behaviour or read the UI, not just read source. |
178
184
  | `plan-add` | Creates a plan under `.tezgah/plans/open/` with its own `allowed_paths:` scope, bootstraps the `.tezgah/plans/` layer if missing, commits it in the private `.tezgah` repository (`git -C .tezgah`, never the project's git); fires on `/tezgah:plan-add`, "track this as a plan". |
179
185
  | `plan-status` | Reads every open plan and `tezgah-task status` (each plan's Acceptance boxes ticked, the plans its `after:` waits on, the ready plans that can run at once), enriches rows with live PR state from `gh`, rewrites the README table (committed in the private `.tezgah` repository) and recommends the next plan; fires on `/tezgah:plan-status` or "what plans are open". |
180
- | `plan-sync` | Closes finished plans whose PR merged or closed through `tezgah-task close`, which moves a plan to `.tezgah/plans/done/` as done only when it records an approving `review:`; commits one message per plan in the private `.tezgah` repository; fires on `/tezgah:plan-sync`, "sync plans". |
186
+ | `plan-sync` | Closes finished plans whose PR merged or closed through `tezgah-task close`, which moves a plan to `.tezgah/plans/done/` as done only when it records an approving `review:`, clears an active task's phase and removes every merged worktree that holds nothing else; commits one message per plan in the private `.tezgah` repository; fires on `/tezgah:plan-sync`, "sync plans". |
181
187
  | `research` | Runs an auditable research line - two loops, protocol before run, six-dimension review, provenance; fires when the deliverable is evidence: a lit review, a hypothesis, a benchmark or ablation. |
182
- | `product-analysis` | Runs a product analysis across five axes - value (HEART), usability (heuristics, the cognitive walkthrough, WCAG 2.2, axe-core, the running app read through `analyze-app` down to component x state, tree and image both), feasibility (a cited `path:line`), competition (a dated teardown) and triage (keep/fix/cut/bet) - with one named evidence class per finding, executed as a research line; fires on a product, feature, retention or roadmap question rather than a code change. |
188
+ | `product-analysis` | Runs a product analysis across five axes - value (HEART), usability (heuristics, the cognitive walkthrough, WCAG 2.2, axe-core, the running app read through `analyze-app` down to component x state, tree and image both), feasibility (a cited `path:line`), competition (a dated teardown) and triage (keep/fix/cut/bet) - with one named evidence class per finding and a metric record per metric (North Star and inputs; goal, definition, window, baseline, logged or not), executed as a research line; fires on a product, feature, retention or roadmap question rather than a code change. |
189
+ | `product-flow` | Carries a new product capability - a metric dashboard, a metric, a feature - from research to re-measure by running `tezgah-research next --topic <t>` and doing the first missing step with the skill it names; the skill lists the nine steps in prose and `FLOWS` in `hooks/tezgah_product.py` holds their done rules; fires on "bu veriye uygun metric dashboard tasarla" or a new-feature ask, not on an audit of an existing feature. |
183
190
  | `pm-frameworks` | The two vendored product methods the analysis skill defers to - intended-vs-implemented and the Opportunity Solution Tree - kept offline so an offline session reads the method instead of citing its name. |
184
- | `feature-audit` | Audits ONE feature across every layer it is spread over - the surfaces a person uses (a control, a list or table, a detail view, a filter, a picker, a step flow, a notification), the route, the authorization, the service and the data - by filling the capability, field-contract, flow/step and interaction-dependency matrices, where a disagreement between layers is a finding and a fix needing an absent capability becomes a capability-change proposal; fires on an admin screen, CRUD, form, wizard, data table, filter or permission question about an existing feature. |
185
- | `design-contract` | The artifact shape of the per-repo `.tezgah/design-contract.md` - colour roles, type scale, spacing unit and rhythm, the component inventory, the state set every interactive control and data view owes - the derivation rule that reads it out of the repository's own tokens, and the rules `bin/tezgah-design check` fails on; fires on a UI or component turn that needs a floor, a token, a state set, or the design check itself. |
191
+ | `feature-audit` | Audits ONE feature across every layer it is spread over - the surfaces a person uses (a control, a list or table, a detail view, a filter, a picker, a step flow, a notification), the route, the authorization, the service and the data - by filling the capability, field-contract, flow/step and interaction-dependency matrices, where a disagreement between layers is a finding and a fix needing an absent capability becomes a capability-change proposal; fires on an admin screen, CRUD, form, wizard, data table, filter or permission question about an existing feature, and in forward mode specifies a feature not built yet so the post-build audit diffs against it. |
192
+ | `design-contract` | The artifact shape of the per-repo `.tezgah/design-contract.md` - colour roles, type scale, spacing unit and rhythm, the component inventory, the state set every interactive control and data view owes - the derivation rule that reads it out of the repository's own tokens, the rules `bin/tezgah-design check` fails on, and the data-view rules for dashboards (`skills/design-contract/data-views.md`); fires on a UI or component turn that needs a floor, a token, a state set, or the design check itself. |
186
193
  | `design-library` | The vendored MC Dean design/UX subset - 39 skills in the `cognitive-accessibility`, `adaptive-interfaces`, `inclusive-interaction`, `visual-critique` and `design-research` plugins - read one entry at a time through its own `INDEX.md`, never the tree; fires when a UI or product turn needs a cognitive-load, inclusive-interaction or visual-critique method tezgah has no counterpart for. |
187
194
  | `ai-research` | The vendored 98-skill library for AI/ML machinery - training and serving a model, benchmarks, interpretability, retrieval pipelines; read one entry, never the tree. |
188
195
  | `rl-env` | The vendored FineEnvs skill set for authoring an RL environment from a description - OpenEnv, OpenReward/ORS, Verifiers and NeMo Gym, plus the orchestrator that ports one description across all four; read one entry, never the tree. |
@@ -198,6 +205,17 @@ The generated specialists load skills first (`hooks/tezgah_agents.py::SPECIALIST
198
205
  | `tezgah-security` | the host's `wstg-*`, `attack-*`, `ci-assessment` and `llm-security` skills, which tezgah does not ship |
199
206
  | `tezgah-tester` | none |
200
207
 
208
+ ### The product chain
209
+
210
+ A new product capability runs as one chain: research -> `product-analysis` ->
211
+ decision -> forward spec -> `design-contract` -> build -> re-measure. Each link
212
+ leaves a file under `.tezgah/analysis/<topic>/` that names the one before it.
213
+ `flow.md` holds one `## <step>` note per step. The forward spec
214
+ (`feature-audit`'s forward mode) is `spec.md`. `FLOWS`
215
+ (`hooks/tezgah_product.py::FLOWS`) holds the steps and their done rules.
216
+ `tezgah-research next` prints them with their status (docs/research.md, "The
217
+ next step of a product flow").
218
+
201
219
  ## Vendored material
202
220
 
203
221
  `NOTICE` records every third-party skill and its terms: it opens by saying the
@@ -222,8 +222,9 @@ def _reviewer_body(host):
222
222
  "Scope and budget. One review per plan, over its whole diff in the\n"
223
223
  "verification phase; at most two rounds - only a confirmed critical or\n"
224
224
  "major finding opens round two, a minor or suggestion one is recorded\n"
225
- "fix-later, and after round two what is left goes to the user or is\n"
226
- "recorded fix-later, never a third round. A round may be a fan-out of\n"
225
+ "fix-later, and after round two what is left is fixed by the session\n"
226
+ "and closed with `--resolved`, or recorded fix-later, never a third\n"
227
+ "round. A round may be a fan-out of\n"
227
228
  "reviewers spawned at once, one per dimension (correctness and contract,\n"
228
229
  "security, tests and performance) or per file group: when your brief\n"
229
230
  "names a dimension or files, review only those - a sibling owns the rest.\n"
@@ -103,7 +103,8 @@ COMPONENTS = [
103
103
  "switch": "research-off", "editable": False, "revert": "git"},
104
104
  {"key": "product", "label": "product",
105
105
  "files": ["hooks/tezgah_policy.py", "skills/product-analysis/SKILL.md",
106
- "skills/feature-audit/SKILL.md"],
106
+ "skills/feature-audit/SKILL.md", "skills/product-flow/SKILL.md",
107
+ "hooks/tezgah_product.py"],
107
108
  "switch": "research-off", "editable": True, "revert": "git"},
108
109
  {"key": "attribution", "label": "attribution",
109
110
  "files": ["hooks/tezgah_policy.py", "hooks/tezgah_gate.py"],
@@ -787,16 +787,16 @@ def task_line(task):
787
787
  and refuses a write the phase or the allowlist excludes, but a refusal
788
788
  costs a turn - so the phase rides every user turn, and the refusal is never
789
789
  the first the session hears of it. One line because it is paid every turn;
790
- the allowlist is the globs as written, and the phase is stated as the user's
791
- to move rather than as a command to run: the line used to print the CLI the
792
- user types, and E7b watched the session run that command five times in a row
793
- against a gate that refuses it every time. A line that names an act the gate
794
- refuses is an invitation to a loop."""
790
+ the allowlist is the globs as written, and the phase is stated as the
791
+ orchestrator's own move: the session runs `tezgah-task` itself when the
792
+ work reaches the next phase and closes the plan when it is done, never
793
+ handing those steps to the user."""
795
794
  paths = ", ".join(task.get("allowed_paths") or []) or "any path in the repo"
796
- return ("Active task %s is in phase `%s`; writes allowed on: %s. The phase "
797
- "belongs to the user - ask them for the one this work needs, and "
798
- "do not move it yourself." % (task.get("id"), task.get("phase"),
799
- paths))
795
+ return ("Active task %s is in phase `%s`; writes allowed on: %s. Move the "
796
+ "phase yourself with `tezgah-task phase <next>` when the work reaches "
797
+ "it, and `tezgah-task close %s done` when it is merged - never hand "
798
+ "these steps to the user." % (task.get("id"), task.get("phase"),
799
+ paths, task.get("id")))
800
800
 
801
801
 
802
802
  # --- the resume block: the live turn state a compact loses -------------------
@@ -1186,10 +1186,61 @@ def constraint_notice(cwd, session_id):
1186
1186
  return " ".join(render(subagent_core(core_for(cwd)[0])).split())
1187
1187
 
1188
1188
 
1189
+ # A data ask said by its output: a dashboard, a chart, a KPI, a metric to choose.
1190
+ # "Bu veriye uygun bir metric dashboard tasarla" armed nothing before plan 072.
1191
+ # These words are code words as often as product words (`dashboard.json`, a
1192
+ # metrics endpoint, a progress `gösterge`, the clipboard `pano`), and two rounds
1193
+ # of noun denylists inside the product regex kept missing the next code sense.
1194
+ # So the ask is judged per clause: it arms `product` only when no code marker -
1195
+ # a file name, an identifier, SQL, a build, test or runtime word - shares its
1196
+ # clause (decision 006: qualify the stem, never drop it).
1197
+ DATA_ASK = re.compile(
1198
+ r"\b(?:(?:design|create|make|build|sketch|propose|need|want)\w* (?:\w+ ){0,4}"
1199
+ r"(?:dashboard|charts?|visuali[sz]ation)|"
1200
+ r"(?:metri[ck]s?|kpis?|data|sales|revenue|veri\w*|satış\w*|gelir\w*) "
1201
+ r"(?:\w+ ){0,2}(?:dashboard|pano)|"
1202
+ r"(?:dashboard|gösterge panel)\w* (?:\w+ ){0,4}(?:tasarla|oluştur|hazırla|"
1203
+ r"yap|göster)\w*|"
1204
+ r"(?:what|which) (?:\w+ ){0,3}(?:metri[ck]s|kpis|charts?) (?:\w+ ){0,3}"
1205
+ r"(?:should|to|do|matter|would)|"
1206
+ r"what should (?:\w+ ){0,3}dashboard show|"
1207
+ r"(?:choose|pick|define|track|design)(?:s|ing)? (?:\w+ ){0,3}(?:metri[ck]s|kpis)|"
1208
+ r"(?:metri[ck]s|kpis) (?:should|to|do|would) (?:\w+ ){0,2}"
1209
+ r"(?:track|measure|use|watch)|success metrics?|"
1210
+ r"(?:suggest|recommend)\w* (?:\w+ ){0,3}charts?|öner\w* (?:\w+ ){0,3}grafi\w*|"
1211
+ r"hangi (?:metri[ck]\w*|kpi\w*|grafi\w*|göstergeleri?)|"
1212
+ r"(?:metri[ck]\w*|kpi\w*) (?:\w+ ){0,3}(?:tasarla|seç|tanımla|belirle|takip|izle)\w*|"
1213
+ r"gösterge panel\w*|visuali[sz]\w* (?:\w+ ){0,3}(?:data|numbers|veri\w*)|"
1214
+ r"data visuali[sz]ation|veri\w* (?:\w+ ){0,3}görselleştir\w*)")
1215
+ CODE_SENSE = re.compile(
1216
+ r"\w\.(?:py|tsx?|jsx?|json|go|rs|ya?ml|sql|java|rb|css|html)\b|\b\w+_\w+\b|"
1217
+ r"/metrics\b|\bselect\b.{0,200}?\bfrom\b|\b(?:tables?|columns?|schema|sql|queries|"
1218
+ r"query|bundle|webpack|vite|tests?|ci|counters?|registry|pipeline|backend|"
1219
+ r"handlers?|structs?|endpoints?|exporters?|export\w*|servers?|librar\w*|"
1220
+ r"packages?|modules?|database|index\w*|indeks\w*|"
1221
+ r"middleware|debugger|data (?:structure|flow)s?|clipboard|spinner|threads?|"
1222
+ r"commit|git|functions?|class|component|prop|hooks?|prometheus|grafana|"
1223
+ r"kibana|statsd|opentelemetry|config\w*|provision\w*|api|routes?|snake_case|"
1224
+ r"veri yapı\w*|fonksiyon\w*|bileşen\w*|modül\w*|dosya\w*|kopyala\w*|"
1225
+ r"seçici\w*|yükleme|ilerleme|paket\w*)\b")
1226
+
1227
+
1228
+ def _data_ask(low):
1229
+ """Does one clause of the prompt ask for a data view or a metric choice with
1230
+ no code marker beside it? A clause ends at `!`, `?`, `;`, a newline or a full
1231
+ stop followed by a space, so `dashboard.json` stays one clause."""
1232
+ return any(DATA_ASK.search(clause) and not CODE_SENSE.search(clause)
1233
+ for clause in re.split(r"[!?;\n]|\.\s", low))
1234
+
1235
+
1189
1236
  def classify_prompt(text):
1190
- """The conditional rule keys a prompt arms, from the shared hint table."""
1237
+ """The conditional rule keys a prompt arms, from the shared hint table, plus
1238
+ `product` for a data ask (DATA_ASK, gated per clause by CODE_SENSE)."""
1191
1239
  low = (text or "").lower()
1192
- return {key for key, pattern in PROMPT_HINTS if re.search(pattern, low)}
1240
+ keys = {key for key, pattern in PROMPT_HINTS if re.search(pattern, low)}
1241
+ if "product" not in keys and _data_ask(low):
1242
+ keys.add("product")
1243
+ return keys
1193
1244
 
1194
1245
 
1195
1246
  def prompt_text(payload):
@@ -1435,8 +1486,8 @@ DEFAULT_BUDGET = 13000
1435
1486
  # exists to prevent, not one it may cause.
1436
1487
  DROP_ORDER = ("knowledge", "worktrees", "lessons", "skill", "lessons_turn",
1437
1488
  "taste", "plans", "subagents", "steer", "orca", "consult", "research",
1438
- "research_broken", "graph", "offnote", "orchestrate", "index",
1439
- "resume", "scratch", "task", "delta", "pointer")
1489
+ "research_broken", "graph", "offnote", "orchestrate",
1490
+ "index", "resume", "scratch", "task", "delta", "pointer")
1440
1491
 
1441
1492
 
1442
1493
  def _first_item(text):
@@ -1577,7 +1628,7 @@ GATE_INACTIVE = (
1577
1628
  "tezgah gate inactive on this host: %d gated tool call(s) since this "
1578
1629
  "session's first turn and not one row from the gate, so the shortcut "
1579
1630
  "denials and the Stop check are not running. Say so before claiming a check "
1580
- "passed, and ask the user to run `tezgah-setup --report`.")
1631
+ "passed, run `tezgah-setup --report` yourself and quote its MISS rows.")
1581
1632
 
1582
1633
 
1583
1634
  def _iso_epoch(value):