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.
- quality_loop-0.2.1/PKG-INFO +133 -0
- quality_loop-0.2.1/README.md +102 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/pyproject.toml +10 -4
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/OPERATOR.md +64 -12
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/__init__.py +1 -1
- quality_loop-0.2.1/quality_loop/__main__.py +10 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/adapter.py +36 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/audit.py +17 -1
- quality_loop-0.2.1/quality_loop/cli.py +375 -0
- quality_loop-0.2.1/quality_loop/compare.py +111 -0
- quality_loop-0.2.1/quality_loop/config.py +115 -0
- quality_loop-0.2.1/quality_loop/drive/__init__.py +395 -0
- quality_loop-0.2.1/quality_loop/drive/_mac.py +220 -0
- quality_loop-0.2.1/quality_loop/drive/_windows.py +248 -0
- quality_loop-0.2.1/quality_loop/drive/keys.py +165 -0
- quality_loop-0.2.1/quality_loop/drive/terminal.py +184 -0
- quality_loop-0.2.1/quality_loop/install.py +228 -0
- quality_loop-0.2.1/quality_loop/mcp.py +297 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/operator.py +60 -3
- quality_loop-0.2.1/quality_loop/processes.py +53 -0
- quality_loop-0.2.1/quality_loop/progress.py +256 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/report.py +21 -3
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/room.py +7 -5
- quality_loop-0.2.1/quality_loop/runner.py +479 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/screens/__init__.py +9 -4
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/session.py +13 -3
- quality_loop-0.2.1/quality_loop/skill/SKILL.md +230 -0
- quality_loop-0.2.1/quality_loop/skill/references/filing.md +50 -0
- quality_loop-0.2.1/quality_loop/skill/references/setup.md +124 -0
- quality_loop-0.2.1/quality_loop.egg-info/PKG-INFO +133 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop.egg-info/SOURCES.txt +17 -0
- quality_loop-0.2.1/quality_loop.egg-info/entry_points.txt +2 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop.egg-info/requires.txt +4 -0
- quality_loop-0.2.1/tests/test_drive.py +223 -0
- quality_loop-0.2.1/tests/test_loop.py +423 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/tests/test_operator.py +2 -2
- {quality_loop-0.1.0 → quality_loop-0.2.1}/tests/test_scenario_report.py +18 -0
- quality_loop-0.1.0/PKG-INFO +0 -74
- quality_loop-0.1.0/README.md +0 -46
- quality_loop-0.1.0/quality_loop/__main__.py +0 -48
- quality_loop-0.1.0/quality_loop/mcp.py +0 -161
- quality_loop-0.1.0/quality_loop/runner.py +0 -160
- quality_loop-0.1.0/quality_loop.egg-info/PKG-INFO +0 -74
- {quality_loop-0.1.0 → quality_loop-0.2.1}/LICENSE +0 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/pages.py +0 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/scenario.py +0 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/screens/_mac.py +0 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/screens/_windows.py +0 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/standin.py +0 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop/transcript.py +0 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop.egg-info/dependency_links.txt +0 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/quality_loop.egg-info/top_level.txt +0 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/setup.cfg +0 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/tests/test_core.py +0 -0
- {quality_loop-0.1.0 → quality_loop-0.2.1}/tests/test_mcp.py +0 -0
- {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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
5
|
-
tools. It was started so it cannot read the
|
|
6
|
-
does with what it was given is the
|
|
7
|
-
measurement possible and then report
|
|
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
|
|
10
|
-
says which one it is, and gives you
|
|
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,
|
|
43
|
-
`audit.json
|
|
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.
|
|
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
|
|
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
|
|
99
|
-
|
|
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
|
|
@@ -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
|
|
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))
|