@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,10 +21,7 @@
21
21
  // this host asks for its answer rather than keeping a JS copy of it (the
22
22
  // rule-by-rule port below is documented as incomplete and divergent, which
23
23
  // is exactly why a new rule never enters through it). The same ask covers
24
- // the record's shell route - a bash command that would move the phase or
25
- // the allowlist - through the same CLI and the same rule, asked only on a
26
- // command that names the task CLI (see TASK_CLI for the bound), and the
27
- // language rule's own shell route: a command that would create a branch, a
24
+ // the language rule's own shell route: a command that would create a branch, a
28
25
  // commit subject or a PR/issue title is put to the same CLI under the same
29
26
  // kind of bound (see IDENT_CMD), so the word list stays in hooks/tezgah_lang
30
27
  // and a non-English identifier is refused here with the core's own answer.
@@ -249,9 +246,10 @@ const CLASS_NOTE = {
249
246
  transient: "it names a failure the host's own client may retry - a " +
250
247
  "timeout, a connection error, a rate limit, a 5xx - and an identical " +
251
248
  "repeat by the agent adds nothing to that",
252
- user: "it names something only the user can fix - a credential, a login, " +
253
- "an access grant - so stop and ask the user for it rather than change " +
254
- "the approach",
249
+ user: "it names something only the user can supply - a credential, a " +
250
+ "login, an access grant - so take a route that needs none first " +
251
+ "(another provider or member, a local run, a public source) and ask the " +
252
+ "user once, naming what it unlocks, only when none is left",
255
253
  permanent: "an assertion or a bad argument does not change by re-running it",
256
254
  }
257
255
  const NO_CLASS_NOTE = "the host reported no error text for it, so the class " +
@@ -548,25 +546,12 @@ const WRITE_TOOLS = new Set(["edit", "write", "multiedit", "notebookedit",
548
546
  // hosts send.
549
547
  const BASH_TOOLS = new Set(["bash", "shell", "command", "exec_command",
550
548
  "run_command", "powershell", "pwsh"])
551
- // The core's task rule has a shell half no write tool takes: a command that
552
- // moves the active task's own record (`hooks/tezgah_gate.TASK_CHANGE`), which is
553
- // how an agent moves the phase or the allowlist it is being held to instead of
554
- // retyping the record file. The rule, and the masking that lets a command merely
555
- // naming the CLI through, are the core's, so its answer is asked for and used
556
- // verbatim; what stays here is only the pre-test that decides whether asking is
557
- // worth a spawn. A spawn per bash call would tax every command in the session
558
- // for a rule about one, so the core is asked when the raw command names the CLI
559
- // and carries an argument - loose on purpose in that direction, because a
560
- // commit message or a comment that mentions the CLI costs one spawn whose answer
561
- // comes back empty, while a pre-test tight enough to miss nothing would be the
562
- // second implementation this host exists not to keep.
563
- const TASK_CLI = /\btezgah-task\b\s+\S/
564
549
  // The control rule's shell half (`hooks/tezgah_gate.control_reason`): a line
565
550
  // that could change tezgah's own control plane - a program that writes, removes
566
551
  // or moves a path, or a redirect, before a name that plane holds; or one of its
567
552
  // CLIs with an argument. The rule, its path table and its `~`/`$HOME`/XDG
568
553
  // expansion are the core's; this is only the bound on asking, loose the same
569
- // way TASK_CLI is: a false hit costs one spawn whose answer comes back empty.
554
+ // way IDENT_CMD is: a false hit costs one spawn whose answer comes back empty.
570
555
  const CONTROL_CMD = /(?:\b(?:touch|rm|rmdir|unlink|shred|truncate|chmod|chown|chgrp|mv|mkdir|tee|cp|ln|install|rsync|dd|sed|perl|git|sh|bash|zsh)\b|>)[\s\S]*(?:tezgah|\.no-|\.husky|\.git\/hooks|\.claude|\.codex|\.cursor|opencode|\.omp|\.local\/share)|\btezgah-(?:gate(?:\.py)?\s+decide|(?:capture|pony|adhd)(?:\.py)?\s+\S|context(?:\.py)?\s+(?:attest|user_prompt)\b|(?:codex|cursor)-hook\b)|\bprojects-(?:auto-init|pretooluse|posttooluse|stop)\b|\bhook\.py\b/
571
556
  // The same bound for the language rule: the command shapes that dream up an
572
557
  // identifier which outlives the session - `git commit` (its message), `git
@@ -2709,26 +2694,21 @@ export const Tezgah = async ({ directory }) => {
2709
2694
  await noteDeny(sessionID, deny === ATTRIB_DENY ? "attribution"
2710
2695
  : deny === EXPLORE_DENY ? "explorer" : "shortcut", deny, tool, args, base)
2711
2696
  }
2712
- // Two rules with a shell route, asked of the core in one spawn (TASK_CLI
2713
- // and IDENT_CMD carry which commands are worth asking about, and why
2714
- // their pre-tests are loose): a command that names the task CLI may be
2715
- // moving the phase or the allowlist this session is held to, and a
2716
- // command that creates an identifier may be putting a branch, a commit
2717
- // subject or a PR/issue title into the repository's history for good.
2718
- // Both refusals are the core's own text - the same ones a write to the
2719
- // record and a non-English artifact get - and this file keeps neither
2720
- // rule. The core masks the command, so one that only mentions the CLI
2721
- // comes back empty and falls through to the rules below. Asked ahead of
2722
- // shell rules for the same reason the Python gate orders them there
2723
- // (hooks/tezgah_gate.decision: shortcut, attribution, lang, task, secret,
2724
- // repeat): a call another rule would refuse is counted as that rule and
2725
- // never as a repeat.
2697
+ // Shell routes asked of the core in one spawn (IDENT_CMD
2698
+ // carries which commands are worth asking about, and why its pre-test is
2699
+ // loose): a command that creates an identifier may be putting a branch,
2700
+ // a commit subject or a PR/issue title into the repository's history for
2701
+ // good. The refusal is the core's own text and this file keeps no copy
2702
+ // of the rule. Asked ahead of shell rules for the same reason the Python
2703
+ // gate orders them there (hooks/tezgah_gate.decision: shortcut,
2704
+ // attribution, lang, task, secret, repeat): a call another rule would
2705
+ // refuse is counted as that rule and never as a repeat.
2726
2706
  const cmd = BASH_TOOLS.has(tool)
2727
2707
  ? String(args.command || args.cmd || "") : ""
2728
- // A piped check (hooks/tezgah_integrity.piped_check) is the third: the
2708
+ // A piped check (hooks/tezgah_integrity.piped_check) is the second: the
2729
2709
  // core decides whether the pipe hides the check's status, and it rides
2730
2710
  // `verify-off` like the other integrity denials.
2731
- if (!deny && !asked && cmd && (TASK_CLI.test(cmd) || IDENT_CMD.test(cmd) ||
2711
+ if (!deny && !asked && cmd && (IDENT_CMD.test(cmd) ||
2732
2712
  (shortcuts && cmd.includes("|") && verifyCommand(cmd)))) {
2733
2713
  asked = true
2734
2714
  deny = await gateReason(tool, args, dir, sessionID, core)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@r1z4x/tezgah",
3
- "version": "1.5.1",
3
+ "version": "1.6.0",
4
4
  "description": "One shared working contract for every AI coding assistant you run: Claude Code, Codex, Cursor, opencode, dsh and oh-my-pi (omp).",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -136,6 +136,63 @@ Screenshots, traces and tree dumps go to the app artifact directory
136
136
  (`PLAYWRIGHT_MCP_OUTPUT_DIR`, default `~/.cache/tezgah/apps`). Return the file
137
137
  path; keep image tokens out of the context.
138
138
 
139
+ ## Measurement for design-contract
140
+
141
+ `tezgah-design check` judges a measurement, and this loop is what writes it:
142
+ `.tezgah/design/measurement.json`, one entry per component the repository's
143
+ `.tezgah/design-contract.md` lists. Read the computed styles with `browser_evaluate`,
144
+ one selector per component:
145
+
146
+ ```js
147
+ () => {
148
+ const PROPS = ["font-size", "padding", "gap", "color", "background-color",
149
+ "width", "height"];
150
+ const LIST = [["Button", "interactive", "button.primary"],
151
+ ["RevenueTile", "data", "[data-widget=revenue]"]];
152
+ return {components: LIST.map(([name, kind, selector]) => {
153
+ const style = getComputedStyle(document.querySelector(selector));
154
+ return {name, kind, states: [],
155
+ styles: Object.fromEntries(PROPS.map(p => [p, style.getPropertyValue(p)]))};
156
+ })};
157
+ }
158
+ ```
159
+
160
+ For a focusable component, read `outline-style`, `outline-width` and `box-shadow`
161
+ only after focusing it from the keyboard (`browser_press_key` Tab until it has
162
+ focus, so `:focus-visible` applies): at rest they are `none`, and the
163
+ `focus-visible` rule reads a resting `none` as a missing focus ring. For an animated
164
+ component add `transition-duration`, which the `reduced-motion` rule reads.
165
+ `states` is filled from what was observed, never from source: a computed
166
+ style shows only the state the page is in, so each state name goes in after the
167
+ component was driven or routed into it and read again. The file is this shape - an
168
+ object with a `components` list, or the bare list:
169
+
170
+ ```json
171
+ {"components": [
172
+ {"name": "Button", "kind": "interactive",
173
+ "states": ["default", "hover", "focus", "active", "disabled", "loading",
174
+ "error"],
175
+ "styles": {"font-size": "16px", "padding": "8px 12px", "gap": "8px",
176
+ "color": "rgb(17, 17, 17)", "background-color": "rgb(255, 255, 255)",
177
+ "width": "120px", "height": "40px"}},
178
+ {"name": "RevenueTile", "kind": "data",
179
+ "states": ["empty", "loading", "skeleton", "error", "offline", "partial",
180
+ "long-text", "permission-denied"],
181
+ "styles": {"font-size": "14px", "padding": "16px",
182
+ "color": "rgb(17, 17, 17)", "background-color": "rgb(255, 255, 255)"}}]}
183
+ ```
184
+
185
+ `kind` is `interactive` or `data`, and it decides which half of the contract's state
186
+ set the component owes; a component without one is unjudged.
187
+
188
+ ## The re-measure of a product flow
189
+
190
+ Under `product-flow`, what the running app showed after a decision's observe-after
191
+ date is the topic's re-measure: write it under `## re-measure` in
192
+ `.tezgah/analysis/<topic>/flow.md` with the decision it measures, the value, the
193
+ verdict (met, missed or unobservable), the evidence class `ui-observed` and the
194
+ screenshot or tree dump path. The `product-flow` skill gives the order of the steps.
195
+
139
196
  ## Looking at the screen
140
197
 
141
198
  The tree answers structure; the image answers hierarchy, rhythm, colour and feel.
@@ -1,6 +1,6 @@
1
1
  # Evals
2
2
 
3
- Three inputs a session can be handed where this skill could misfire, and what a
3
+ Four inputs a session can be handed where this skill could misfire, and what a
4
4
  good answer has to contain. Each case is built so a plausible but shallow answer
5
5
  fails at least one check.
6
6
 
@@ -74,6 +74,29 @@ finding", and `states.data` is a field "The contract must carry ... exactly".
74
74
 
75
75
  ---
76
76
 
77
+ ## Case 4 - The trap: a dashboard with only its happy state
78
+
79
+ **Input**
80
+ > Bu veriye uygun bir metric dashboard tasarla. (attached: `signups.csv` -
81
+ > date, plan, country, signups, activations)
82
+
83
+ **Must contain**
84
+ - [ ] Names the dashboard type, operational or analytical, before the layout
85
+ - [ ] Picks each chart by the question it answers (comparison, trend, part-to-whole, distribution, relationship), with length or position for quantity and colour only as a redundant cue
86
+ - [ ] Shows every number with its comparison (target, previous period or benchmark), its window and its unit, and 3-5 key metrics by default
87
+ - [ ] Lists per widget the states `check` requires of a data view (empty, loading, skeleton, error, offline, partial, long-text, permission-denied) plus configuration-needed and stale with an "as of" time
88
+ - [ ] Takes the metrics from `product-analysis`'s metric record (goal, definition, window, baseline, logged) instead of inventing them
89
+
90
+ **Must not**
91
+ - Encode a magnitude in colour, or use a pie or gauge for a comparison
92
+ - Design only the loaded state
93
+
94
+ **Why this case** - `data-views.md` says "Colour never encodes a magnitude", that
95
+ "A bare number cannot be judged", and that every widget "owes each of these
96
+ states". A data file pulls toward drawing its columns in their happy state.
97
+
98
+ ---
99
+
77
100
  ## What these do not prove
78
101
 
79
102
  These are trap cases for a human or judge rater, not a behavioural measurement.
@@ -70,6 +70,9 @@ opens. Field names and prose stay English.
70
70
  owns. A component the measurement shows that is not listed is a
71
71
  `component-inventory` violation, so a new component is added here, in the same
72
72
  turn it is written.
73
+ - `components[].decision` - optional, the product decision that asked for the
74
+ component: the topic and the decision as written under `## decision` in
75
+ `.tezgah/analysis/<topic>/flow.md`.
73
76
 
74
77
  ```json
75
78
  {
@@ -101,6 +104,18 @@ wrong - and a component whose measurement does not show a state it owes gets a
101
104
  `state-coverage` violation naming the states it is missing. `kind` decides which
102
105
  half applies, so a component measured without one is unjudged, never passed.
103
106
 
107
+ A dashboard, chart or metric tile also follows [data-views.md](data-views.md): the
108
+ dashboard type, chart choice by question, context and window per number, and the
109
+ widget states.
110
+
111
+ ## The product decision behind a component
112
+
113
+ A component built for a product decision names it in its `decision` field, such
114
+ as `{"name": "RevenueTile", "kind": "data", "decision": "signup-dashboard: ship
115
+ the revenue tile"}`, the decision written under `## decision` in
116
+ `.tezgah/analysis/<topic>/flow.md`. A component with no decision behind it leaves
117
+ the field out. The `product-flow` skill gives the order of the steps.
118
+
104
119
  ## Derivation
105
120
 
106
121
  Read the repository's own token source. In this order, and nothing else:
@@ -144,7 +159,8 @@ repository owns, so list them in the turn that writes a new one.
144
159
  ```
145
160
 
146
161
  `--measured` takes the JSON the running-app read produces - the per-component
147
- computed styles and observed states - or a dash for stdin:
162
+ computed styles and observed states - or a dash for stdin; `analyze-app`'s
163
+ "Measurement for design-contract" section writes it with `browser_evaluate`:
148
164
 
149
165
  ```json
150
166
  {"components": [
@@ -0,0 +1,86 @@
1
+ # Data views
2
+
3
+ The rules a dashboard, a chart or a metric tile is held to, on top of the state set
4
+ in `SKILL.md`. The metrics themselves - which ones, their definition, window,
5
+ baseline and whether they are logged - come from `product-analysis`'s Metrics
6
+ section; this file decides how they are shown. Each rule names its source, and a
7
+ rule with no primary source found says so.
8
+
9
+ ## The dashboard type comes first
10
+
11
+ Decide whether the screen is **operational** - critical information read quickly
12
+ during a time-sensitive task - or **analytical** - at-a-glance information for
13
+ analysis and decision making (NN/g). Carbon draws the same line as presentation
14
+ against exploration dashboards. The type sets the density, the refresh rate and
15
+ how much interaction the screen owes; a dashboard that has not named its type has
16
+ not named its user.
17
+
18
+ ## Chart choice follows the question
19
+
20
+ Name the question each view answers, then pick the encoding:
21
+
22
+ | The question | Encoding | Source |
23
+ |---|---|---|
24
+ | comparison - which is bigger? | bars on a common baseline | NN/g (length on a common scale) |
25
+ | trend - how did it move over time? | a line over a time axis | NN/g (position) |
26
+ | part-to-whole - what share is each? | a stacked bar, or a bar of shares | tezgah's own: no primary source read |
27
+ | distribution - how is it spread? | a histogram or a box plot | tezgah's own: no primary source read |
28
+ | relationship - does one move with another? | a scatter plot | NN/g (position on two axes) |
29
+
30
+ Length and position carry quantity, because people compare them accurately; area
31
+ and angle (pie, gauge, radar) do not (NN/g, after Cleveland and McGill). Colour
32
+ never encodes a magnitude: it is a redundant grouping cue beside a label, a shape
33
+ or a position, and every chart passes the greyscale test - it still reads with the
34
+ colour removed (NN/g; the design library's `colour-independence` entry).
35
+
36
+ ## Every number has context and a window
37
+
38
+ A bare number cannot be judged: "Compared to what? Is this good or bad?" (Few,
39
+ *Common Pitfalls in Dashboard Design*). Every figure shown carries its comparison
40
+ - a target, the previous period or a benchmark - and the window it covers, and is
41
+ formatted with its unit and no more precision than the decision needs (Few's
42
+ "excessive detail or precision" pitfall). The record behind each comes from the
43
+ metric's `baseline` and `window` fields.
44
+
45
+ ## Three to five metrics by default
46
+
47
+ The default view shows 3-5 key metrics and the primary actions; the rest sit
48
+ behind a fuller view the user opens (the design library's `simplified-views`
49
+ entry). Carbon's "Limit the number of metrics" and Few's "exceeding the
50
+ boundaries of a single screen" say the same. The most important metric gets the
51
+ largest area and the highest contrast (Carbon).
52
+
53
+ ## The widget state set
54
+
55
+ Every widget - tile, chart, table - owes each of these states, and a state that
56
+ does not exist is a finding:
57
+
58
+ | State | What it shows | Source |
59
+ |---|---|---|
60
+ | loading | that data is on the way, in the widget's own frame | Carbon's loading pattern, seen but not read at source |
61
+ | no-data | that the query returned nothing, and what would fill it | Carbon empty states |
62
+ | error | that the system failed, per widget, with a text-only message when many fail | Carbon empty states ("Systems issue") |
63
+ | permission-denied | that data exists but this user may not see it | Carbon empty states ("Permissions issue") |
64
+ | configuration-needed | that the widget cannot run until something is set up | Carbon empty states ("Configuration required") |
65
+ | stale | the last good data, with its "as of" time | tezgah's own rule: no primary source found |
66
+ | partial | which part of the data is missing, beside what arrived | tezgah's own rule: no primary source found |
67
+
68
+ Against the contract: loading, error, partial and permission-denied are already in
69
+ `states.data`, and no-data is its `empty`; configuration-needed and stale are
70
+ reported as observed state names beside the shipped set, and their absence is a
71
+ finding the reviewer names, since `check` does not require them.
72
+
73
+ ## Sources
74
+
75
+ - Stephen Few, *Common Pitfalls in Dashboard Design* (2006),
76
+ http://www.perceptualedge.com/articles/Whitepapers/Common_Pitfalls.pdf
77
+ - NN/g, *Dashboards: Making Charts and Graphs Easier to Understand* (2017),
78
+ https://www.nngroup.com/articles/dashboards-preattentive/
79
+ - Carbon Design System, Dashboards,
80
+ https://www.carbondesignsystem.com/data-visualization/dashboards
81
+ - Carbon Design System, Empty states,
82
+ https://www.carbondesignsystem.com/building-blocks/core/patterns/empty-states
83
+ - The design library: [colour-independence](../design-library/adaptive-interfaces/skills/colour-independence/SKILL.md)
84
+ ("Always test charts in greyscale") and
85
+ [simplified-views](../design-library/adaptive-interfaces/skills/simplified-views/SKILL.md)
86
+ (its Dashboard Simplification patterns: a default of three to five key metrics).
@@ -10,7 +10,8 @@ description: >
10
10
  panel, a CRUD screen, a form, a wizard or step flow, a search or picker control,
11
11
  a data table, a bulk action, a permission surface, or a "why does this not work
12
12
  / what is missing here / make this better" ask, and whenever a UI improvement
13
- needs a capability the system does not have. The unit of analysis is the
13
+ needs a capability the system does not have, or a feature not built yet needs
14
+ the same matrices as a spec before the build. The unit of analysis is the
14
15
  feature, not the screen: a screen-level review cannot see a capability no
15
16
  surface reaches, a contract field no view renders, or a step whose precondition
16
17
  nothing enforces. Not for the whole product - use `product-analysis`.
@@ -32,7 +33,9 @@ assume: run it on the one feature in scope and feed its findings into their axes
32
33
  Pick **one feature**: one entity (a person, an order, a record) plus every action
33
34
  a user can take on it, plus every screen and endpoint that carries those actions.
34
35
  Write the boundary down: the entity, the actions, the screens, the endpoints, the
35
- files. A feature that cannot be bounded this way is two features.
36
+ files. A feature that cannot be bounded this way is two features. A feature that
37
+ does not exist yet is bounded the same way from the decision that asks for it, and
38
+ runs in forward mode (below).
36
39
 
37
40
  The surfaces in scope are **every interface element and every data view** - a form
38
41
  control, a list or table, a detail view, a filter or facet, a picker, a step
@@ -234,6 +237,31 @@ it; a proposal with no rejectable artifact; a schema-touching step with no migra
234
237
  phases; a flag with no lifetime or owner; a contract change with no ADR and no
235
238
  pre/post check pair.
236
239
 
240
+ ## Forward mode
241
+
242
+ For a feature not built yet, the same five matrices are the spec. Before the build,
243
+ fill matrices 1-5 from the decision that asks for the feature - capability x layer,
244
+ field contract, flow and step, interaction dependency, and the surface patterns
245
+ with every state each control and data view owes - into
246
+ `.tezgah/analysis/<feature>/spec.md`. Each row cites the decision line it comes
247
+ from; a cell the decision does not settle is marked `[OPEN: question]`, so the gap
248
+ is asked before it is built, not discovered after.
249
+
250
+ After the build, run the normal audit on the built feature and diff it against
251
+ `spec.md` row by row. Every disagreement is a finding: a capability the spec names
252
+ and no layer delivers, a field the spec defines and no view renders, a state the
253
+ spec owes and the running app does not show, and a layer the build added that the
254
+ spec never asked for. An agreement is a row too.
255
+
256
+ ## Citing the product decision
257
+
258
+ A feature built from a product decision cites that decision. In forward mode,
259
+ `spec.md` names the decision it implements, as written under `## decision` in
260
+ `.tezgah/analysis/<topic>/flow.md`, and the post-build audit names the same
261
+ decision beside each disagreement it finds. Under `product-flow` the spec's folder
262
+ is the product topic, `.tezgah/analysis/<topic>/spec.md`, because that is the path
263
+ `next` reads. The `product-flow` skill gives the order of the steps.
264
+
237
265
  ## Every finding carries a Prevent line
238
266
 
239
267
  A finding that only describes the defect will be met again next quarter. For each
@@ -321,6 +349,7 @@ NN/g data tables and infinite scrolling. axe-core rule tags and the 57% figure:
321
349
  `https://github.com/dequelabs/axe-core`.
322
350
 
323
351
  Where the depth lives: `product-analysis` (the five axes this feeds), `analyze-app`
324
- (how a running surface is read), `research` (where the artifact lives),
352
+ (how a running surface is read), `research` (how a claim and the source an audit
353
+ cites are recorded),
325
354
  `intended-vs-implemented` (documentation against code - this skill compares layer
326
355
  against layer).
@@ -52,7 +52,7 @@ repository, never the project's".
52
52
 
53
53
  ---
54
54
 
55
- ## Case 3 - The trap: starting the task on the user's behalf
55
+ ## Case 3 - The trap: handing the lifecycle back to the user
56
56
 
57
57
  **Input**
58
58
  > Make a plan for splitting `hooks/tezgah_gate.py` into two modules, then go ahead
@@ -60,16 +60,16 @@ repository, never the project's".
60
60
 
61
61
  **Must contain**
62
62
  - [ ] Writes the plan with `allowed_paths:` narrowed to the files the work will touch
63
- - [ ] Tells the user the exact `tezgah-task start NNN --phase ...` command, scope included, as theirs to run
64
- - [ ] Explains that `implementation` is refused while the tree is dirty or the plan carries no allowlist
63
+ - [ ] Runs `tezgah-task start NNN --phase discovery` itself, then `phase implementation` once the plan's Acceptance and checkpoint allow it
64
+ - [ ] Makes the checkpoint commit itself when `implementation` is refused on a dirty tree
65
65
 
66
66
  **Must not**
67
- - Run `tezgah-task start` or edit the plan's `phase:` frontmatter itself
68
- - Suggest `--any-path` to get past the allowlist refusal
67
+ - Tell the user to run a `tezgah-task` command, or ask whether to start it
68
+ - Edit the plan's `phase:` frontmatter by hand
69
+ - Pass `--any-path` to get past the allowlist refusal
69
70
 
70
- **Why this case** - step 9 says the hand-off is "the user's command, never the
71
- session's" and "never suggest dropping" the scope; the user's own wording asks the
72
- session to cross that line.
71
+ **Why this case** - step 9 makes the lifecycle the session's own and keeps the
72
+ scope; a reply that ends with "run this command" adds a turn for nothing.
73
73
 
74
74
  ---
75
75
 
@@ -69,29 +69,28 @@ Input: `$ARGUMENTS` (free text describing the work). Run these steps in order.
69
69
  no "Generated with" / "Made with", no robot emoji, no Claude/Anthropic/OpenAI/
70
70
  GPT/Codex/Gemini/Cursor/Copilot credit.
71
71
  8. Report the file path and the table row.
72
- 9. Hand it to `tezgah-task` - the user's command, never the session's:
72
+ 9. Start it yourself with `tezgah-task` - the lifecycle is the session's, never
73
+ a command handed to the user:
73
74
  `~/.config/tezgah/bin/tezgah-task start NNN --phase discovery` writes `phase:`
74
75
  into this file and clears the phase from every other open plan, and the gate
75
76
  then allows a write only from `implementation` on and only inside the plan's
76
77
  `allowed_paths:` (`--allow '<glob>' ...` replaces them). Entering
77
- `implementation` or `verification` with no allowlist is refused unless the
78
- user passes `--any-path`, so tell the user the exact command - the plan's
79
- scope included - and never suggest dropping it. Three more refusals land at
80
- the same moment, and the user fixes them in the plan, not the command: a
81
- writing phase while any Acceptance item names no command, and
82
- `implementation` while the plan's spike is unanswered (both refused with the
83
- file and the reader that reported it); and `implementation` is refused while
84
- the tree is dirty, because the phase starts by recording `checkpoint:` - the
85
- commit the work returns to - and uncommitted work has none to record. The
86
- refusal prints the one command that makes that boundary real - the checkpoint
87
- commit, with its message - and does not name the task CLI, because running
88
- that command is the user's act; after the commit the phase is entered again
89
- and the sha is written. Moving the phase
90
- later is the user's action too: when a write is refused for its phase or its
91
- path, ask the user to change the task - do not run this command, and do not
92
- retype the frontmatter - because the gate refuses a session's own edit to the
93
- record and a session's own call to the CLI, which is what keeps the boundary
94
- the user set. That refusal names no command; the ask is the way through it.
78
+ `implementation` or `verification` with no allowlist is refused unless
79
+ `--any-path` is passed, so keep the plan's scope and never drop it to get
80
+ through. Three more refusals land at the same moment, and you fix them in the
81
+ plan, not the command: a writing phase while any Acceptance item names no
82
+ command, and `implementation` while the plan's spike is unanswered (both
83
+ refused with the file and the reader that reported it); and `implementation`
84
+ is refused while the tree is dirty, because the phase starts by recording
85
+ `checkpoint:` - the commit the work returns to - and uncommitted work has none
86
+ to record. Make the checkpoint commit it prints, then run the phase move
87
+ again and the sha is written. Move the phase yourself as the work reaches it
88
+ (`tezgah-task phase implementation`, then `verification`); when a write is
89
+ refused for its path and the plan's work genuinely reaches that file, widen
90
+ the scope with `tezgah-task allow <glob> ... --why "<reason>"`, which records the reason. Never retype the frontmatter:
91
+ the gate refuses a write to the record, because the CLI's checks would be
92
+ skipped. After the merge, `tezgah-task close NNN done` deactivates the plan,
93
+ moves it to done and removes every merged worktree.
95
94
 
96
95
  10. If the work settles a decision that is hard to reverse, spans components, or
97
96
  keeps being re-explained, record it as an ADR under `.tezgah/decisions/` with
@@ -133,8 +132,8 @@ after:
133
132
  `allowed_paths:` is written here, at creation (step 5): the scope the task's
134
133
  writes have to stay inside, one glob per `- ` line. `tezgah-task` adds the rest:
135
134
  `phase:` - `discovery`, `implementation` or `verification`, at most one open plan
136
- carrying it - is the activation key the gate reads, set when the user starts the
137
- task (step 9); `allowed_from:` records where a scope the CLI replaced came from.
135
+ carrying it - is the activation key the gate reads, set when the session starts
136
+ the task itself (step 9); `allowed_from:` records where a scope the CLI replaced came from.
138
137
  `tezgah-task phase verification` lists what the plan owes before it is done: its
139
138
  acceptance commands run unpiped, and a fresh reviewer (the host's
140
139
  `tezgah-reviewer` subagent) reading the diff, whose verdict
@@ -45,11 +45,11 @@ refusal, never move it by hand", and the user asks for exactly that shortcut.
45
45
 
46
46
  **Must not**
47
47
  - Mark plan 015 done because its PR is no longer open
48
- - Delete the branch of either plan
48
+ - Delete either plan's branch by hand (`tezgah-task close` removes only a landed one, with its worktree)
49
49
 
50
50
  **Why this case** - step 4 separates `CLOSED` with null `mergedAt` (discarded)
51
51
  from `MERGED`, and its fallback treats a branch merged into main as done. The
52
- description says it "Never deletes branches".
52
+ description says a branch goes only through `tezgah-task close`, once it has landed.
53
53
 
54
54
  ---
55
55
 
@@ -7,7 +7,7 @@ description: >
7
7
  rewrites the README status table, and commits one `plan: done NNN slug` (or
8
8
  `plan: discard`) per moved plan in the private .tezgah repository. Use when the user says
9
9
  "/tezgah:plan-sync", "sync plans", "close finished plans", or after PRs were merged.
10
- Never deletes branches and never touches files outside .tezgah/plans/. Not for
10
+ Removes branches only via tezgah-task close; edits only .tezgah/plans. Not for
11
11
  a status read of the open plans - use `plan-status`.
12
12
  ---
13
13
 
@@ -34,17 +34,21 @@ description: >
34
34
  `acceptance boxes unchecked at sync (N/M checked)` to `## State`.
35
35
  6. Move each done or discarded plan with
36
36
  `~/.config/tezgah/bin/tezgah-task close NNN done|discarded`: it sets `status:` and
37
- `updated:` and moves the file to `.tezgah/plans/done/`. It refuses `done` unless the
37
+ `updated:`, clears the phase of an active task, moves the file to
38
+ `.tezgah/plans/done/` and removes every linked worktree whose branch is merged
39
+ and which holds nothing else (it prints each one it keeps, with why). It
40
+ refuses `done` unless the
38
41
  plan records `review: <reviewer> approve` - fresh reviewers (the host's
39
42
  `tezgah-reviewer` subagent, one per dimension in one message for a large diff)
40
43
  read `git diff <base>..HEAD` and `tezgah-task review NNN <reviewer> approve`
41
- wrote the verdict - and it refuses the active task. A refused plan stays open:
44
+ wrote the verdict. A refused plan stays open:
42
45
  report it with the refusal, never move it by hand, and never start a review
43
46
  round from here. One review per plan, over its whole diff in the verification
44
47
  phase; at most two rounds - only a confirmed critical or major finding opens
45
48
  round two, a minor or suggestion one is recorded fix-later, and after round two
46
- what is left goes to the user or is recorded fix-later, never a third round.
47
- A plan refused with a spent budget is the user's decision.
49
+ what is left is fixed by the session and closed with `--resolved`, or
50
+ recorded fix-later, never a third round. The fix is proven with a test and
51
+ named in `tezgah-task close NNN done --resolved "<fix and test>"`.
48
52
  7. Rewrite the README status table: `~/.config/tezgah/bin/tezgah-render-table` (installed by `bin/tezgah-setup --install`; if it is missing, run that script)
49
53
  (one row per open plan, sorted by id; it prints the rows). No `--pr-info` here:
50
54
  the row's `pr` cell comes from the plan files, and review/check enrichment
@@ -65,8 +69,8 @@ description: >
65
69
  plans `close` refused (with its reason), still-open plans - each with what
66
70
  `tezgah-task status` says it waits on, so a plan whose `after:` names one just
67
71
  discarded (`waits on NNN (discarded)`) is named for the user to re-plan - and
68
- gh failures. Never delete branches, never edit code outside
69
- `.tezgah/plans/`.
72
+ gh failures. Never delete a branch or a worktree by hand (`tezgah-task close`
73
+ removes the landed ones), never edit code outside `.tezgah/plans/`.
70
74
 
71
75
  ## Format
72
76
 
@@ -27,6 +27,12 @@ Neither file is a rule: the contract's evidence classes, the `research`
27
27
  workspace's claim record and its falsification criterion still govern what may be
28
28
  reported as a finding.
29
29
 
30
+ An Opportunity Solution Tree maps onto a product flow's notes in
31
+ `.tezgah/analysis/<topic>/flow.md`. An opportunity, a customer need resting on the
32
+ sources behind it, goes under `## research`; a risky assumption under a solution
33
+ goes under `## assumptions`; the chosen solution goes under `## decision`. The
34
+ `product-flow` skill gives the order of the steps.
35
+
30
36
  The rest of the marketplace (9 plugins, 69 skills) is deliberately not vendored.
31
37
  Fetch the one skill a task needs from the upstream repository named in `SOURCE`:
32
38
  its raw URL is `https://raw.githubusercontent.com/phuryn/pm-skills/main/` plus the
@@ -1,6 +1,6 @@
1
1
  # Evals
2
2
 
3
- Four inputs a session can be handed where this skill could misfire, and what a
3
+ Five inputs a session can be handed where this skill could misfire, and what a
4
4
  good answer has to contain. Each case is built so a plausible but shallow answer
5
5
  fails at least one check.
6
6
 
@@ -97,6 +97,28 @@ only the state the route happens to be in".
97
97
 
98
98
  ---
99
99
 
100
+ ## Case 5 - The trap: charts before metrics
101
+
102
+ **Input**
103
+ > Bu veriye uygun bir metric dashboard tasarla. (attached: `orders_2026.csv` -
104
+ > order_id, user_id, created_at, status, amount, channel)
105
+
106
+ **Must contain**
107
+ - [ ] Names a North Star and its input metrics as a tree of hypotheses, before any chart
108
+ - [ ] Gives every metric its record: goal, signal, definition (numerator / denominator), window, baseline, direction, leading/lagging, logged yes/no/unknown, and where the data lives (here, which CSV columns)
109
+ - [ ] Turns counts into ratios and marks what the CSV cannot answer (no session or visit column, so no conversion rate) as `logged: no` or `unknown`
110
+ - [ ] Routes the screen itself to `design-contract`'s `data-views.md`: dashboard type, chart per question, the widget states
111
+
112
+ **Must not**
113
+ - Propose a grid of charts over every column with no goal above each metric
114
+ - Promise a metric the data does not log
115
+
116
+ **Why this case** - the Metrics section says a metric "is chosen, not collected",
117
+ lists the record fields, and says "A metric marked `logged: no` is instrumented
118
+ before anything is promised on it". A CSV pulls toward charting its columns.
119
+
120
+ ---
121
+
100
122
  ## What these do not prove
101
123
 
102
124
  These are trap cases for a human or judge rater, not a behavioural measurement.