quality-loop 0.1.0__tar.gz → 0.2.1__tar.gz

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 (56) hide show
  1. quality_loop-0.2.1/PKG-INFO +133 -0
  2. quality_loop-0.2.1/README.md +102 -0
  3. {quality_loop-0.1.0 → quality_loop-0.2.1}/pyproject.toml +10 -4
  4. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/OPERATOR.md +64 -12
  5. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/__init__.py +1 -1
  6. quality_loop-0.2.1/quality_loop/__main__.py +10 -0
  7. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/adapter.py +36 -0
  8. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/audit.py +17 -1
  9. quality_loop-0.2.1/quality_loop/cli.py +375 -0
  10. quality_loop-0.2.1/quality_loop/compare.py +111 -0
  11. quality_loop-0.2.1/quality_loop/config.py +115 -0
  12. quality_loop-0.2.1/quality_loop/drive/__init__.py +395 -0
  13. quality_loop-0.2.1/quality_loop/drive/_mac.py +220 -0
  14. quality_loop-0.2.1/quality_loop/drive/_windows.py +248 -0
  15. quality_loop-0.2.1/quality_loop/drive/keys.py +165 -0
  16. quality_loop-0.2.1/quality_loop/drive/terminal.py +184 -0
  17. quality_loop-0.2.1/quality_loop/install.py +228 -0
  18. quality_loop-0.2.1/quality_loop/mcp.py +297 -0
  19. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/operator.py +60 -3
  20. quality_loop-0.2.1/quality_loop/processes.py +53 -0
  21. quality_loop-0.2.1/quality_loop/progress.py +256 -0
  22. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/report.py +21 -3
  23. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/room.py +7 -5
  24. quality_loop-0.2.1/quality_loop/runner.py +479 -0
  25. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/screens/__init__.py +9 -4
  26. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/session.py +13 -3
  27. quality_loop-0.2.1/quality_loop/skill/SKILL.md +230 -0
  28. quality_loop-0.2.1/quality_loop/skill/references/filing.md +50 -0
  29. quality_loop-0.2.1/quality_loop/skill/references/setup.md +124 -0
  30. quality_loop-0.2.1/quality_loop.egg-info/PKG-INFO +133 -0
  31. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop.egg-info/SOURCES.txt +17 -0
  32. quality_loop-0.2.1/quality_loop.egg-info/entry_points.txt +2 -0
  33. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop.egg-info/requires.txt +4 -0
  34. quality_loop-0.2.1/tests/test_drive.py +223 -0
  35. quality_loop-0.2.1/tests/test_loop.py +423 -0
  36. {quality_loop-0.1.0 → quality_loop-0.2.1}/tests/test_operator.py +2 -2
  37. {quality_loop-0.1.0 → quality_loop-0.2.1}/tests/test_scenario_report.py +18 -0
  38. quality_loop-0.1.0/PKG-INFO +0 -74
  39. quality_loop-0.1.0/README.md +0 -46
  40. quality_loop-0.1.0/quality_loop/__main__.py +0 -48
  41. quality_loop-0.1.0/quality_loop/mcp.py +0 -161
  42. quality_loop-0.1.0/quality_loop/runner.py +0 -160
  43. quality_loop-0.1.0/quality_loop.egg-info/PKG-INFO +0 -74
  44. {quality_loop-0.1.0 → quality_loop-0.2.1}/LICENSE +0 -0
  45. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/pages.py +0 -0
  46. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/scenario.py +0 -0
  47. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/screens/_mac.py +0 -0
  48. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/screens/_windows.py +0 -0
  49. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/standin.py +0 -0
  50. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/transcript.py +0 -0
  51. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop.egg-info/dependency_links.txt +0 -0
  52. {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop.egg-info/top_level.txt +0 -0
  53. {quality_loop-0.1.0 → quality_loop-0.2.1}/setup.cfg +0 -0
  54. {quality_loop-0.1.0 → quality_loop-0.2.1}/tests/test_core.py +0 -0
  55. {quality_loop-0.1.0 → quality_loop-0.2.1}/tests/test_mcp.py +0 -0
  56. {quality_loop-0.1.0 → quality_loop-0.2.1}/tests/test_screens.py +0 -0
@@ -0,0 +1,133 @@
1
+ Metadata-Version: 2.4
2
+ Name: quality-loop
3
+ Version: 0.2.1
4
+ Summary: Measure what a product teaches - its skill, tools or manual - with isolated agent runs of real tasks, reported by an operator agent
5
+ Author: craftware
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/crftwr/quality-loop
8
+ Project-URL: Repository, https://github.com/crftwr/quality-loop
9
+ Project-URL: Issues, https://github.com/crftwr/quality-loop/issues
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Operating System :: MacOS
13
+ Classifier: Operating System :: Microsoft :: Windows
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Programming Language :: Python :: 3.14
19
+ Classifier: Topic :: Software Development :: Quality Assurance
20
+ Classifier: Topic :: Software Development :: Testing
21
+ Requires-Python: >=3.11
22
+ Description-Content-Type: text/markdown
23
+ License-File: LICENSE
24
+ Provides-Extra: tui
25
+ Requires-Dist: pyte>=0.8; extra == "tui"
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=8; extra == "dev"
28
+ Requires-Dist: pytest-timeout>=2; extra == "dev"
29
+ Requires-Dist: pyte>=0.8; extra == "dev"
30
+ Dynamic: license-file
31
+
32
+ # quality-loop
33
+
34
+ Measure what a product teaches.
35
+
36
+ A skill bundle, an MCP server, a CLI - or a manual and the app it describes -
37
+ is only as good as what someone with nothing else can do with it.
38
+ quality-loop runs an agent on a real task, in a **room** that holds what the
39
+ product gives its users and nothing more. The room is started so it cannot
40
+ read the repository the answers are in. Afterwards an **operator** agent
41
+ reports the problems it finds in what the room did, each with a severity and
42
+ the evidence for it.
43
+
44
+ There is no pass/fail. The point is **discovery**: each run should surface
45
+ something the last one did not, and leave a record good enough for a person,
46
+ or a development-time agent, to reproduce what it found. Every problem
47
+ carries a stable id, so the same problem is recognised across runs:
48
+ de-duplicated, seen to be gone, seen to come back.
49
+
50
+ ```
51
+ pip install quality-loop # 'quality-loop[tui]' to drive terminal apps
52
+ ```
53
+
54
+ Python 3.11 or later, no runtime dependencies (driving a terminal app needs
55
+ `pyte`, the `tui` extra). It drives [Claude Code](https://claude.com/claude-code)
56
+ (`claude`) as subprocesses, so that has to be installed and signed in. macOS
57
+ and Windows; terminal apps on macOS and Linux.
58
+
59
+ ## See it work
60
+
61
+ The repository has two products small enough to read in a minute.
62
+
63
+ ```
64
+ git clone https://github.com/crftwr/quality-loop && cd quality-loop
65
+ make test # the harness's own tests
66
+ .venv/bin/python examples/toy/run.py # an agent-facing skill: one room, a few cents
67
+ .venv/bin/python examples/tui/run.py # a terminal app and its manual
68
+ ```
69
+
70
+ The toy is a "greeter" skill that reads a house style and writes one file.
71
+ The TUI example is a note list whose manual is the only thing the room is
72
+ given; the room drives the app itself, and the operator checks it live. The
73
+ output is `rooms/<version>-<time>/FINDINGS.md`.
74
+
75
+ ## A run, per scenario
76
+
77
+ ```
78
+ build the room - the bundle, RULES.md, and TASK.md holding the scenario's Prompt, verbatim
79
+ stand fixtures in for the user's files
80
+ operator, phase 1 - put the scenario's screen up, by name
81
+ the room's session - isolated, non-interactive
82
+ operator, live check - drive the real app, when the scenario asks for one
83
+ collect what the room made, put the user's files back, audit the transcript
84
+ operator, phase 2 - read the record, check the Must lists with evidence, write REPORT.md
85
+ ```
86
+
87
+ ## The loop
88
+
89
+ ```
90
+ quality-loop init # in the product: the conductor skill and settings
91
+ quality-loop run 11 12 # starts a run in the background, prints its id
92
+ quality-loop watch # follow it from a terminal
93
+ quality-loop findings # new / still there / gone / back, against the run before
94
+ ```
95
+
96
+ `init` installs a skill for the **conductor**: a Claude Code session in the
97
+ product's repository that plans with the person, sets the product up, runs,
98
+ files findings as GitHub Issues, fixes what the person chooses, and retests,
99
+ under a policy the person sets. See [Running the loop](https://github.com/crftwr/quality-loop/blob/main/docs/running.md).
100
+
101
+ ## Documentation
102
+
103
+ - [Concepts](https://github.com/crftwr/quality-loop/blob/main/docs/concepts.md): rooms, operators, the conductor, scenarios,
104
+ reports, and which parts are code rather than an agent's judgement.
105
+ - [Running the loop](https://github.com/crftwr/quality-loop/blob/main/docs/running.md): the command line, watching a run,
106
+ parallel runs, comparing findings, the conductor and its policy.
107
+ - [Writing scenarios](https://github.com/crftwr/quality-loop/blob/main/docs/scenarios.md): the Markdown format, doc-driven
108
+ scenarios, and what makes a scenario measure something.
109
+ - [Writing an adapter](https://github.com/crftwr/quality-loop/blob/main/docs/adapters.md): what a product supplies, from a
110
+ launch command and a redirected home up to evidence tools of its own.
111
+ - [Reports](https://github.com/crftwr/quality-loop/blob/main/docs/reports.md): problems, severities, kinds and ids, the
112
+ reproduction record, and `FINDINGS.md` / `findings.json`.
113
+ - [Isolation and permissions](https://github.com/crftwr/quality-loop/blob/main/docs/isolation.md): what keeps the room, the
114
+ operator and the conductor in bounds, why each rule exists, and the known limits.
115
+ - [Development](https://github.com/crftwr/quality-loop/blob/main/docs/development.md): tests, live tests, releasing.
116
+
117
+ ## Products using it
118
+
119
+ - [Keyhac](https://github.com/crftwr/keyhac): `qa/`, `make qa`
120
+ - [XeFM](https://github.com/crftwr/xefm) (planned)
121
+
122
+ ## Status
123
+
124
+ 0.x, early. It has been run end to end on macOS against the two examples,
125
+ Keyhac and XeFM (including XeFM's terminal UI in a live check), and on
126
+ Windows (11, ARM64) against the toy, Keyhac and XeFM, with real page and
127
+ terminal screens. Not yet run anywhere for real: synthetic keys and
128
+ screenshots of a desktop app (`drive.DesktopApp`); a terminal app is not
129
+ driven on Windows yet. The design discussion is
130
+ [crftwr/keyhac#205](https://github.com/crftwr/keyhac/issues/205) and this
131
+ repository's issues.
132
+
133
+ MIT licensed.
@@ -0,0 +1,102 @@
1
+ # quality-loop
2
+
3
+ Measure what a product teaches.
4
+
5
+ A skill bundle, an MCP server, a CLI - or a manual and the app it describes -
6
+ is only as good as what someone with nothing else can do with it.
7
+ quality-loop runs an agent on a real task, in a **room** that holds what the
8
+ product gives its users and nothing more. The room is started so it cannot
9
+ read the repository the answers are in. Afterwards an **operator** agent
10
+ reports the problems it finds in what the room did, each with a severity and
11
+ the evidence for it.
12
+
13
+ There is no pass/fail. The point is **discovery**: each run should surface
14
+ something the last one did not, and leave a record good enough for a person,
15
+ or a development-time agent, to reproduce what it found. Every problem
16
+ carries a stable id, so the same problem is recognised across runs:
17
+ de-duplicated, seen to be gone, seen to come back.
18
+
19
+ ```
20
+ pip install quality-loop # 'quality-loop[tui]' to drive terminal apps
21
+ ```
22
+
23
+ Python 3.11 or later, no runtime dependencies (driving a terminal app needs
24
+ `pyte`, the `tui` extra). It drives [Claude Code](https://claude.com/claude-code)
25
+ (`claude`) as subprocesses, so that has to be installed and signed in. macOS
26
+ and Windows; terminal apps on macOS and Linux.
27
+
28
+ ## See it work
29
+
30
+ The repository has two products small enough to read in a minute.
31
+
32
+ ```
33
+ git clone https://github.com/crftwr/quality-loop && cd quality-loop
34
+ make test # the harness's own tests
35
+ .venv/bin/python examples/toy/run.py # an agent-facing skill: one room, a few cents
36
+ .venv/bin/python examples/tui/run.py # a terminal app and its manual
37
+ ```
38
+
39
+ The toy is a "greeter" skill that reads a house style and writes one file.
40
+ The TUI example is a note list whose manual is the only thing the room is
41
+ given; the room drives the app itself, and the operator checks it live. The
42
+ output is `rooms/<version>-<time>/FINDINGS.md`.
43
+
44
+ ## A run, per scenario
45
+
46
+ ```
47
+ build the room - the bundle, RULES.md, and TASK.md holding the scenario's Prompt, verbatim
48
+ stand fixtures in for the user's files
49
+ operator, phase 1 - put the scenario's screen up, by name
50
+ the room's session - isolated, non-interactive
51
+ operator, live check - drive the real app, when the scenario asks for one
52
+ collect what the room made, put the user's files back, audit the transcript
53
+ operator, phase 2 - read the record, check the Must lists with evidence, write REPORT.md
54
+ ```
55
+
56
+ ## The loop
57
+
58
+ ```
59
+ quality-loop init # in the product: the conductor skill and settings
60
+ quality-loop run 11 12 # starts a run in the background, prints its id
61
+ quality-loop watch # follow it from a terminal
62
+ quality-loop findings # new / still there / gone / back, against the run before
63
+ ```
64
+
65
+ `init` installs a skill for the **conductor**: a Claude Code session in the
66
+ product's repository that plans with the person, sets the product up, runs,
67
+ files findings as GitHub Issues, fixes what the person chooses, and retests,
68
+ under a policy the person sets. See [Running the loop](https://github.com/crftwr/quality-loop/blob/main/docs/running.md).
69
+
70
+ ## Documentation
71
+
72
+ - [Concepts](https://github.com/crftwr/quality-loop/blob/main/docs/concepts.md): rooms, operators, the conductor, scenarios,
73
+ reports, and which parts are code rather than an agent's judgement.
74
+ - [Running the loop](https://github.com/crftwr/quality-loop/blob/main/docs/running.md): the command line, watching a run,
75
+ parallel runs, comparing findings, the conductor and its policy.
76
+ - [Writing scenarios](https://github.com/crftwr/quality-loop/blob/main/docs/scenarios.md): the Markdown format, doc-driven
77
+ scenarios, and what makes a scenario measure something.
78
+ - [Writing an adapter](https://github.com/crftwr/quality-loop/blob/main/docs/adapters.md): what a product supplies, from a
79
+ launch command and a redirected home up to evidence tools of its own.
80
+ - [Reports](https://github.com/crftwr/quality-loop/blob/main/docs/reports.md): problems, severities, kinds and ids, the
81
+ reproduction record, and `FINDINGS.md` / `findings.json`.
82
+ - [Isolation and permissions](https://github.com/crftwr/quality-loop/blob/main/docs/isolation.md): what keeps the room, the
83
+ operator and the conductor in bounds, why each rule exists, and the known limits.
84
+ - [Development](https://github.com/crftwr/quality-loop/blob/main/docs/development.md): tests, live tests, releasing.
85
+
86
+ ## Products using it
87
+
88
+ - [Keyhac](https://github.com/crftwr/keyhac): `qa/`, `make qa`
89
+ - [XeFM](https://github.com/crftwr/xefm) (planned)
90
+
91
+ ## Status
92
+
93
+ 0.x, early. It has been run end to end on macOS against the two examples,
94
+ Keyhac and XeFM (including XeFM's terminal UI in a live check), and on
95
+ Windows (11, ARM64) against the toy, Keyhac and XeFM, with real page and
96
+ terminal screens. Not yet run anywhere for real: synthetic keys and
97
+ screenshots of a desktop app (`drive.DesktopApp`); a terminal app is not
98
+ driven on Windows yet. The design discussion is
99
+ [crftwr/keyhac#205](https://github.com/crftwr/keyhac/issues/205) and this
100
+ repository's issues.
101
+
102
+ MIT licensed.
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
  name = "quality-loop"
7
7
  # version -> single-sourced from quality_loop/__init__.py's __version__.
8
8
  dynamic = ["version"]
9
- description = "Measure what an agent-facing product teaches: isolated agent runs of real tasks, reported by an operator agent"
9
+ description = "Measure what a product teaches - its skill, tools or manual - with isolated agent runs of real tasks, reported by an operator agent"
10
10
  readme = "README.md"
11
11
  license = "MIT"
12
12
  authors = [{ name = "craftware" }]
@@ -29,7 +29,12 @@ classifiers = [
29
29
  dependencies = []
30
30
 
31
31
  [project.optional-dependencies]
32
- dev = ["pytest>=8", "pytest-timeout>=2"]
32
+ # Driving a terminal app in a pty needs a terminal emulator; only for that.
33
+ tui = ["pyte>=0.8"]
34
+ dev = ["pytest>=8", "pytest-timeout>=2", "pyte>=0.8"]
35
+
36
+ [project.scripts]
37
+ quality-loop = "quality_loop.cli:main"
33
38
 
34
39
  [project.urls]
35
40
  Homepage = "https://github.com/crftwr/quality-loop"
@@ -43,8 +48,9 @@ version = { attr = "quality_loop.__version__" }
43
48
  include = ["quality_loop*"]
44
49
 
45
50
  [tool.setuptools.package-data]
46
- # The operator agent's instructions, read from the installed package.
47
- quality_loop = ["OPERATOR.md"]
51
+ # The operator agent's instructions, read from the installed package, and the
52
+ # conductor's skill, which `quality-loop init` copies into a product.
53
+ quality_loop = ["OPERATOR.md", "skill/*.md", "skill/references/*.md"]
48
54
 
49
55
  [tool.pytest.ini_options]
50
56
  testpaths = ["tests"]
@@ -1,13 +1,15 @@
1
1
  # You are the operator of one room
2
2
 
3
3
  A **room** is a separate Claude Code session that was given a task and nothing
4
- but a product's agent-facing surface - a skill bundle and the product's own
5
- tools. It was started so it cannot read the product's repository. What it
6
- does with what it was given is the measurement. Your job is to make that
7
- measurement possible and then report what it shows.
4
+ but what the product gives its users - a skill bundle and the product's own
5
+ tools, or a manual and the app itself. It was started so it cannot read the
6
+ product's repository. What it does with what it was given is the
7
+ measurement. Your job is to make that measurement possible and then report
8
+ what it shows.
8
9
 
9
- You work in two phases of one session. The message that starts each phase
10
- says which one it is, and gives you the paths.
10
+ You work in phases of one session: phase 1, sometimes a live check, then
11
+ phase 2. The message that starts each says which one it is, and gives you
12
+ the paths.
11
13
 
12
14
  ## Phase 1: put up the screen
13
15
 
@@ -31,6 +33,29 @@ after this phase, and a changed byte cancels the room. In particular, the
31
33
  room's TASK.md is copied from the scenario by the runner and must reach the
32
34
  room exactly as written.
33
35
 
36
+ ## Live check: drive the real app
37
+
38
+ Only when the scenario has a **Live check** section. The room has finished,
39
+ and what it made is still in place - its config, its extension, its files.
40
+
41
+ 1. Read the scenario's **Live check** and **Must** sections. They say what
42
+ to try on the app.
43
+ 2. `app_list`, then `app_start` the app. Every `app_keys` call carries an
44
+ **intent**: what you expect the step to do. The answer is the screen
45
+ afterwards (text, or an image) and any lines the app wrote to its event
46
+ log.
47
+ 3. Decide each step from what the screen shows. When the app answers
48
+ differently from what you expected - a dialog you did not plan for, a
49
+ different state - look, and choose the next key from there. That
50
+ difference is often the finding.
51
+ 4. Do not fix anything, and do not try to make a Must line pass. You are
52
+ checking, not doing the task.
53
+ 5. `app_stop` every app you started. Then write `live.md` in your directory:
54
+ what you tried, by step number, and what you saw.
55
+
56
+ Every step is recorded in the room's `evidence/live/` (`steps.jsonl` and a
57
+ screen per step). Cite those step numbers in your report.
58
+
34
59
  ## Phase 2: report
35
60
 
36
61
  The room has finished. In the room directory you have:
@@ -39,8 +64,11 @@ The room has finished. In the room directory you have:
39
64
  record. The room's own account of what it did is only a claim.
40
65
  - `QUESTIONS.md`: what the room noticed it had to guess.
41
66
  - `evidence/`: what the runner collected. That includes the product's files
42
- before and after, anything the room created, the screen's event record, and
43
- `audit.json`, the runner's isolation audit.
67
+ before and after, anything the room created, the screen's event record,
68
+ `audit.json` (the runner's isolation audit) and `run.json` (where the run
69
+ started from: product version and commit, OS, fixtures). `live/` is your
70
+ live check's record, and `app/` the room's own steps when it drove the app
71
+ itself.
44
72
 
45
73
  Then:
46
74
 
@@ -55,9 +83,27 @@ Then:
55
83
  unsure or was wrong. A problem the room never mentioned in QUESTIONS.md is
56
84
  the most valuable kind.
57
85
  3. Read **QUESTIONS.md**. Each question is a candidate problem.
58
- 4. Write **REPORT.md** in the room, in the format below. Then close every
86
+ 4. If you were given `known.md`, read it before naming anything. A problem
87
+ on it that you see again is reported under **the same id**, briefly, with
88
+ this run's evidence. Do not go looking for it beyond what the Must lines
89
+ take you to; spend your attention on what is not on the list.
90
+ 5. Write **REPORT.md** in the room, in the format below. Then close every
59
91
  screen you opened (`screen_close`).
60
92
 
93
+ **When the room worked from a manual** rather than a skill, it played a
94
+ first-time user with only the docs. Look in particular for:
95
+
96
+ - **the docs and the app disagree**: a key, a menu name or a setting that is
97
+ not what the app has, steps in the wrong order, a description out of date;
98
+ - **the docs are missing something**: every place the room had to guess.
99
+ A guess that came from habit (vim keys, the usual shortcut) and happened to
100
+ work is still a gap - the docs did not say it;
101
+ - **the app itself**: an unhelpful error, a destructive operation without a
102
+ confirmation, state the screen does not show.
103
+
104
+ You are not a human tester: you do not see contrast or a confusing layout the
105
+ way a person does. Report what the record shows.
106
+
61
107
  **Friction is not a problem by itself.** A deploy's own "DEPLOY FAILED" on a
62
108
  screen built to show a failed deploy is the task working. Report what a
63
109
  product change would improve, and say why.
@@ -76,6 +122,7 @@ One paragraph: what the room was asked, what it did, and what it left.
76
122
  - **Evidence:** <tool output quoted, or transcript step and what it shows>
77
123
  - **Where it would be fixed:** <file and section, in the product>
78
124
  - **How a fix is confirmed:** <the evidence that would show it fixed>
125
+ - **Reproduce:** <optional: the steps in the record - transcript turns, live steps 3-7>
79
126
 
80
127
  <Free text: what happened, and why it matters.>
81
128
  ```
@@ -89,14 +136,19 @@ One paragraph: what the room was asked, what it did, and what it left.
89
136
  | medium | Met, but only by guessing something the product should have said, or after rework a user's agent would repeat. |
90
137
  | low | Friction that cost little: an empty search, a retry, an unclear message. |
91
138
 
92
- **Kind**: `skill gap` (the product's agent-facing text should have said it),
139
+ **Kind**: `skill gap` (the product's text - its skill, tool descriptions or
140
+ manual - should have said it, or says something the product does not do),
93
141
  `product` (the product behaves wrongly), `harness` (the runner, or the room's
94
142
  tools), `out of scope` (true of the task or the application, not of the
95
143
  product).
96
144
 
97
145
  **Id** names the problem, not the run: `fn-f1-not-bound`, not `room-3-issue`.
98
- A later run reports the same problem under the same id, which is how a fix is
99
- seen to have worked.
146
+ A later run reports the same problem under the same id, which is how the same
147
+ problem is recognised across runs.
148
+
149
+ **No problem without evidence.** Every one cites what a tool answered, a
150
+ transcript step, or a live step and its screen, so a person can see quickly
151
+ whether it is real. A screen you may have misread is said to be uncertain.
100
152
 
101
153
  There is no verdict. Write problems, most severe first. If you found none,
102
154
  say so in the opening paragraph and say what you checked. Zero problems is
@@ -12,4 +12,4 @@ room is handed, putting the user's machine back - is code here, not an
12
12
  agent's discretion.
13
13
  """
14
14
 
15
- __version__ = "0.1.0"
15
+ __version__ = "0.2.1"
@@ -0,0 +1,10 @@
1
+ """``python -m quality_loop``: the same as the ``quality-loop`` command.
2
+
3
+ See `quality_loop.cli`. The operator's ``screen`` subcommand is one of them:
4
+ everything it takes is a name from the manifest the runner wrote.
5
+ """
6
+
7
+ from quality_loop.cli import main
8
+
9
+ if __name__ == "__main__":
10
+ raise SystemExit(main())
@@ -33,6 +33,20 @@ class Adapter:
33
33
  arguments of its choosing and no shell.
34
34
  evidence_help: One paragraph on what those tools answer.
35
35
  python: The interpreter the operator's CLIs run under.
36
+ bundle_place: The directory the bundle goes under in a room:
37
+ ``skills`` for an agent-facing skill, ``docs`` for a manual.
38
+ apps: ``{name: drive.TerminalApp | drive.DesktopApp}`` - the product's
39
+ app, launched by the framework (see `quality_loop.drive`). The
40
+ operator drives them in a scenario's live check.
41
+ room_apps: Names from ``apps`` the room itself may drive, for a
42
+ human-facing product whose UI is the surface under test. Empty
43
+ for an agent-facing one: there, driving the app is for checking.
44
+ default_variant: What the variant ``""`` is called on the command
45
+ line (``--variant``), e.g. Keyhac's ``template`` config.
46
+ parallel: How many rooms may run at once. 1, the default, is one
47
+ after another, exactly as before parallel runs existed.
48
+ resources: ``{resource: capacity}`` - what rooms share, and how many
49
+ may hold each at once: a desktop's focus, the user's home.
36
50
  """
37
51
  name = "product"
38
52
  version = "0"
@@ -45,6 +59,12 @@ class Adapter:
45
59
  evidence: dict = {}
46
60
  evidence_help = ""
47
61
  python = sys.executable
62
+ bundle_place = "skills"
63
+ apps: dict = {}
64
+ room_apps: tuple = ()
65
+ default_variant = ""
66
+ parallel = 1
67
+ resources: dict = {}
48
68
 
49
69
  def bundle(self) -> pathlib.Path:
50
70
  """The surface under test - a zip or a directory - built for this run."""
@@ -86,3 +106,19 @@ class Adapter:
86
106
  def alive(self) -> bool:
87
107
  """Whether the product is still answering, asked on the heartbeat."""
88
108
  return True
109
+
110
+ def needs(self, case: scenario.Scenario, variant: str) -> set[str]:
111
+ """The resources one room holds while it runs. The default is every
112
+ declared resource, so declaring a resource without saying who needs
113
+ it never lets two rooms share it."""
114
+ return set(self.resources)
115
+
116
+ def product_for(self, room: pathlib.Path):
117
+ """A context manager for one room: a product instance of its own, for
118
+ a product that can run one per room. None uses ``product()``'s."""
119
+ return None
120
+
121
+ def restore(self) -> list[str]:
122
+ """Put back what a run that could not unwind left set aside - the
123
+ stand-in's marker. One line per thing done."""
124
+ raise NotImplementedError("this adapter does not say how to restore")
@@ -81,6 +81,10 @@ class Policy:
81
81
  the room reported on the outage, not the product.
82
82
  leaked_skill: Says whether a slash command in the init event is one of
83
83
  the checkout's own skills - the scoring key among them.
84
+ run_directory: The run's own directory - run.log, status.json, the
85
+ other rooms and their operators. The runner sets it. A path in it
86
+ and outside the room is a breach: a room once read its own log to
87
+ reconstruct its task, and a sibling's report is the answer key.
84
88
  """
85
89
  checkout: pathlib.Path
86
90
  forbidden: dict[str, str] = dataclasses.field(default_factory=dict)
@@ -88,6 +92,14 @@ class Policy:
88
92
  bash: bool = False
89
93
  servers: tuple[str, ...] = ()
90
94
  leaked_skill: Callable[[str], bool] | None = None
95
+ run_directory: pathlib.Path | None = None
96
+
97
+
98
+ def under(path: str, root: str) -> bool:
99
+ """Whether ``path`` is ``root`` or inside it - not merely spelled like it:
100
+ a room ``11_f1`` is not inside ``11_f1.operator``."""
101
+ root = root.rstrip("/")
102
+ return path == root or path.startswith(root + "/")
91
103
 
92
104
 
93
105
  def isolation_held(transcript: pathlib.Path, policy: Policy) -> list[str]:
@@ -124,6 +136,8 @@ def audit(room: pathlib.Path, policy: Policy) -> tuple[list[str], list[str]]:
124
136
  breaches = isolation_held(transcript, policy)
125
137
  refused: list[str] = []
126
138
  checkout, inside = norm(str(policy.checkout.resolve())), norm(str(room))
139
+ records = (norm(str(pathlib.Path(policy.run_directory).resolve()))
140
+ if policy.run_directory else None)
127
141
  forbidden = {norm(path): what for path, what in policy.forbidden.items()}
128
142
  for call in tr.tool_calls(transcript):
129
143
  landed = refused if call.is_error else breaches
@@ -137,8 +151,10 @@ def audit(room: pathlib.Path, policy: Policy) -> tuple[list[str], list[str]]:
137
151
  for path, what in forbidden.items():
138
152
  if path in spelled:
139
153
  landed.append(f"{call.name} reached into {what}: {aimed}")
140
- if spelled.startswith(checkout) and not spelled.startswith(inside):
154
+ if under(spelled, checkout) and not under(spelled, inside):
141
155
  landed.append(f"{call.name} reached into the checkout: {aimed}")
156
+ elif records and under(spelled, records) and not under(spelled, inside):
157
+ landed.append(f"{call.name} reached into the run's own records: {aimed}")
142
158
  if pathlib.PurePath(aimed.replace("\\", "/")).name in policy.foreign:
143
159
  landed.append(f"{call.name} read another author's work: {aimed}")
144
160
  return sorted(set(breaches)), sorted(set(refused))