loopagent-gate 0.4.2__py3-none-any.whl

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 (40) hide show
  1. loopagent_gate-0.4.2.dist-info/METADATA +374 -0
  2. loopagent_gate-0.4.2.dist-info/RECORD +40 -0
  3. loopagent_gate-0.4.2.dist-info/WHEEL +5 -0
  4. loopagent_gate-0.4.2.dist-info/entry_points.txt +2 -0
  5. loopagent_gate-0.4.2.dist-info/licenses/LICENSE +21 -0
  6. loopagent_gate-0.4.2.dist-info/top_level.txt +1 -0
  7. loopgate/__init__.py +7 -0
  8. loopgate/__main__.py +8 -0
  9. loopgate/cli.py +147 -0
  10. loopgate/gates/anchor_witness.py +249 -0
  11. loopgate/gates/assert_dims.py +88 -0
  12. loopgate/gates/bootstrap_state.py +111 -0
  13. loopgate/gates/change_health.py +232 -0
  14. loopgate/gates/check_loop.py +1267 -0
  15. loopgate/gates/check_product.py +463 -0
  16. loopgate/gates/check_visual.py +224 -0
  17. loopgate/gates/doctor.py +130 -0
  18. loopgate/gates/estimate_task.py +137 -0
  19. loopgate/gates/export_public.py +289 -0
  20. loopgate/gates/guard_card.py +519 -0
  21. loopgate/gates/loop_watch.py +118 -0
  22. loopgate/gates/regressions.py +463 -0
  23. loopgate/gates/render_status_page.py +151 -0
  24. loopgate/gates/render_task_ledger.py +312 -0
  25. loopgate/gates/review_pr.py +370 -0
  26. loopgate/gates/sync_issues_to_kanban.py +593 -0
  27. loopgate/gates/tree_parity.py +307 -0
  28. loopgate/gates/worker_brief.py +396 -0
  29. loopgate/init.py +128 -0
  30. loopgate/paths.py +43 -0
  31. loopgate/py.typed +0 -0
  32. loopgate/templates/__init__.py +1 -0
  33. loopgate/templates/configs/anchor_witness.example.json +14 -0
  34. loopgate/templates/configs/fixtures/issues_sample.json +75 -0
  35. loopgate/templates/configs/fixtures/review_approve.json +5 -0
  36. loopgate/templates/configs/fixtures/review_changes.json +5 -0
  37. loopgate/templates/configs/fixtures/review_no_verdict.json +5 -0
  38. loopgate/templates/configs/repo_profiles.example.json +63 -0
  39. loopgate/templates/configs/tree_parity.example.json +16 -0
  40. loopgate/templates/skills/loop-engineering/SKILL.md +281 -0
@@ -0,0 +1,374 @@
1
+ Metadata-Version: 2.4
2
+ Name: loopagent-gate
3
+ Version: 0.4.2
4
+ Summary: Accept agent work by proof, not by confidence: gates that print a marker and refuse to take the agent's word.
5
+ Author: loopagent contributors
6
+ License: MIT
7
+ Project-URL: Repository, https://github.com/mjeksonpx/loopagent
8
+ Project-URL: Issues, https://github.com/mjeksonpx/loopagent/issues
9
+ Keywords: ai-agents,code-review,evidence,gates,ci,verification
10
+ Classifier: License :: OSI Approved :: MIT License
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3 :: Only
13
+ Classifier: Topic :: Software Development :: Quality Assurance
14
+ Classifier: Topic :: Software Development :: Testing
15
+ Classifier: Environment :: Console
16
+ Requires-Python: >=3.9
17
+ Description-Content-Type: text/markdown
18
+ License-File: LICENSE
19
+ Provides-Extra: contracts
20
+ Requires-Dist: jsonschema>=4.18; extra == "contracts"
21
+ Dynamic: license-file
22
+
23
+ <div align="center">
24
+
25
+ # loopagent
26
+
27
+ ### Accept agent work by proof, not by confidence.
28
+
29
+ A stdlib-only gate that refuses to take an agent's word: it runs the commands, prints one marker,
30
+ and exits non-zero until the evidence matches the claim. It ships as a pip package (`loopgate`) with the
31
+ gates inside, plus a skill (`loop-engineering`) that tells an agent how to work inside the loop.
32
+
33
+ [![license](https://img.shields.io/badge/license-MIT-3DA639?style=for-the-badge)](LICENSE)
34
+ [![python](https://img.shields.io/badge/python-3.9%2B%20%7C%203.12-3776AB?style=for-the-badge&logo=python&logoColor=white)](#install)
35
+ [![dependencies](https://img.shields.io/badge/dependencies-0-6E56CF?style=for-the-badge)](#install)
36
+ [![dimensions](https://img.shields.io/badge/dimensions-14-0B7285?style=for-the-badge)](#the-fourteen-dimensions)
37
+ [![falsifications](https://img.shields.io/badge/falsification%20cases-26-FF6B35?style=for-the-badge)](#sixty-seconds)
38
+
39
+ </div>
40
+
41
+ ```
42
+ $ python3 -m loopgate.gates.check_loop --strict # abridged: fourteen dimensions, six shown here
43
+ D01 contract_skill [OK] .agents/skills/loop-engineering/SKILL.md contract complete
44
+ D02 sources_of_truth [OK] AGENTS.md + PLANS.md + README.md carry the contract and the gate command
45
+ D03 loop_state [OK] artifacts/loop_state.json schema + monotone history ok
46
+ D04 rounds [OK] 6 round artifact(s) with bound evidence
47
+ D05 handoff [OK] handoff bound to the gate's machine report
48
+ D06 guardrail_no_direct_cli [OK] no direct CLI-agent invocation in repo code or pasteable commands
49
+ ...
50
+ dims: 13 ok, 0 fail, 1 n/a (D08) (mandatory: 12, optional: 2)
51
+ acceptance: strict-complete
52
+ LOOP_ENGINEERING_PASS_0001
53
+ ```
54
+
55
+ > **Two names, one project.** The repository is `loopagent`; the CLI and the Python package are
56
+ > `loopgate` — the name it grew under. Stated once so nobody hunts for a missing package.
57
+
58
+ ---
59
+
60
+ ## Why it exists
61
+
62
+ An agent produces a plausible pull request faster than a human can read it. *"Tests pass"* is the
63
+ sentence that ends review, and it is not evidence: the tests may not have run, may have run against
64
+ the old tree, may have been skipped, or may cover nothing that changed. The second failure is
65
+ subtler — everything is green and the product does not start.
66
+
67
+ This repository turns that discipline into commands. Not a checklist, not a prompt: a gate with
68
+ fourteen dimensions, a marker, and an exit code.
69
+
70
+ ## Three verdicts, three questions
71
+
72
+ | Verdict | Question it answers | Command |
73
+ |---|---|---|
74
+ | **Technical gate** | was the work actually done, on every gate this repository requires? | `python3 -m loopgate.gates.check_loop --strict` |
75
+ | **Product gate** | can a user do their job with it? | `python3 -m loopgate.gates.check_product` |
76
+ | **Change health** | did this change make the tree worse, even though every gate stayed green? | `python3 -m loopgate.gates.change_health` |
77
+
78
+ Each verdict prints exactly one marker and one exit code. A marker is printed **only** by the gate that
79
+ earned it — an agent that prints `LOOP_ENGINEERING_PASS_0001` itself is a bug, and one of the dimensions
80
+ exists to catch that class of cheating.
81
+
82
+ ## The cycle
83
+
84
+ ```mermaid
85
+ flowchart LR
86
+ A["Plan: contract filled"] --> B["Build: smallest honest change"]
87
+ B --> C{"Gate red?"}
88
+ C -->|"yes: name the dimension"| R["Repair: one gap per round"]
89
+ R --> C
90
+ C -->|"no: every dimension ok"| P["Prove: marker + exit 0"]
91
+ P --> V["Review: a different engine"]
92
+ V -->|"reject"| R
93
+ V -->|"accept"| H["Handoff: synthesis + signed anchor"]
94
+ ```
95
+
96
+ `Build → Fail → Repair → Prove → Review → Handoff`. Every round leaves a file with the command, the
97
+ verbatim output and the exit code; knowledge that stays only in a model's context dies with the
98
+ context. The gate is read-only — it may write only when handed an explicit `--report` path — so a
99
+ check can never repair the thing it is checking.
100
+
101
+ ## The fourteen dimensions
102
+
103
+ | ID | Dimension | What must be true |
104
+ |---|---|---|
105
+ | `D01` | contract_skill | the run contract exists: goal, sources of truth, constraints, done-when, stop-if |
106
+ | `D02` | sources_of_truth | `AGENTS.md`, `PLANS.md` and this README carry the contract and the gate command |
107
+ | `D03` | loop_state | `artifacts/loop_state.json`: schema, `0..100` ranges, phase, marker |
108
+ | `D04` | rounds | ≥3 round artifacts with `Gap / Change / Evidence / Metrics / Next` |
109
+ | `D05` | handoff | handoff bound to the gate's machine report; hash recomputed without `D05` itself |
110
+ | `D06` | guardrail_no_direct_cli | no direct CLI-agent invocation in code or pasteable commands |
111
+ | `D07` | guardrail_no_secrets | no credential-shaped strings anywhere in the tree |
112
+ | `D08` | course_corpus | the study corpus is assembled (N/A — not shipped in this tree) |
113
+ | `D09` | roles_fanout | four role artifacts, each ≥300 chars, each attributed to a run |
114
+ | `D10` | issue_bridge | the brief *inside* the task carries every gate, and the validator is attacked |
115
+ | `D11` | product_gate | user scenarios, each with a `why`; the evaluator is mutated, not trusted |
116
+ | `D12` | anchor_witness | the verdict is signed, and the anchor cannot certify itself |
117
+ | `D13` | visual_gate | headless browser present, screenshots required, 23 fake pages rejected |
118
+ | `D14` | change_health | countable metrics compared to a baseline, in the direction that means "worse" |
119
+
120
+ A dimension is worth having only if it can fail: every one of them comes with an injection in
121
+ `loopgate/gates/regressions.py` that reddens the gate on *that* dimension. A dimension with no falsification
122
+ case is a slogan.
123
+
124
+ ## Install
125
+
126
+ ```bash
127
+ git clone https://github.com/mjeksonpx/loopagent.git && cd loopagent
128
+ python3 -m loopgate.gates.doctor # what this interpreter can run, before yellow is a mystery
129
+ make check # what CI runs: compile + code dimensions + regressions
130
+ ```
131
+
132
+ Nothing to install: no third-party dependency in the core, no daemon, no database, no schema to
133
+ migrate. Optional extras (`jsonschema` for one product scenario) degrade to a `SKIP` **with a stated
134
+ reason**, never to a silent PASS.
135
+
136
+ One machine prerequisite is not optional: the visual dimension (`D13`) needs **Chrome or Chromium**, and
137
+ `make check` fails without it (`CI_DIMS_STOP_0001`). That is deliberate — a visual check that silently
138
+ skips is worse than none — and `python3 -m loopgate.gates.doctor` names what this interpreter found before
139
+ a red dimension looks like a mystery. `make check` also needs `make` and Python 3.9+.
140
+
141
+ ```bash
142
+ python3 -m loopgate.gates.bootstrap_state --init # first run: declare the cold start
143
+ # -> LOOP_STATE_COLD_0001 wrote artifacts/loop_state.json: state=cold, round=0
144
+
145
+ python3 -m loopgate.gates.check_loop --strict
146
+ # -> 8 ok, 0 fail, 5 cold, 1 n/a
147
+ # cold start: 5 state dimension(s) cannot be proven in this tree (D03, D04, D05, D09, D12)
148
+ # LOOP_ENGINEERING_COLD_0001 exit 0
149
+ ```
150
+
151
+ A fresh clone answers `COLD`, not `PASS`. That is the design: round artifacts, the handoff and the
152
+ signed anchor are evidence of *a run*, not source, so they live in gitignored state. As soon as one
153
+ round exists, the declaration is refused — a cold start cannot hide a broken loop behind an empty
154
+ directory.
155
+
156
+ ```bash
157
+ pip install -e ".[contracts]" # optional: the `loopgate` command
158
+ loopgate gate --strict # identical to the module; the command only names it
159
+ loopgate status # both verdicts + the queue on one operator page
160
+ loopgate --repo ../other gate --strict # judge another project instead of the working directory
161
+ ```
162
+
163
+ ## Use it in another project
164
+
165
+ The gates ship inside the package, so a project does not need a copy of this repository:
166
+
167
+ ```bash
168
+ pip install loopagent-gate # from PyPI
169
+ # pip install "git+https://github.com/mjeksonpx/loopagent.git@v0.4.2" # or straight from the tag
170
+ cd your-project # a git repository, or one with AGENTS.md
171
+ loopgate init # writes the contract skill, AGENTS.md / PLANS.md / README.md,
172
+ # and declares the cold start (never a fabricated PASS)
173
+ loopgate gate --strict # -> STOP: names what the project still has to configure
174
+ ```
175
+
176
+ The install names a tag on purpose: `git+…` without one follows `main`, so the code under you would change
177
+ under your feet. Use `@v0.4.0` (or a later tag) and pin it in your own requirements.
178
+
179
+ > **The distribution is `loopagent-gate`; the command is `loopgate`.** Neither `loopgate` (taken by an
180
+ > unrelated project) nor `loopagent` (too similar to `loop-agent`) can be used on PyPI, so the name says both
181
+ > things at once. `pip install loopgate` would install somebody else's code.
182
+
183
+ `init` never overwrites an existing `AGENTS.md`, `PLANS.md` or `README.md`, and it writes no round, handoff
184
+ or anchor: those are evidence of a run.
185
+
186
+ ### The first run is red, and that is the design
187
+
188
+ A project that has just been through `init` answers `STOP`, exit 1, and lists what is missing:
189
+
190
+ ```
191
+ D10 issue_bridge [FAIL] issue bridge incomplete
192
+ - configs/repo_profiles.json missing
193
+ D11 product_gate [FAIL] product gate incomplete
194
+ ...
195
+ LOOP_ENGINEERING_STOP_0001
196
+ ```
197
+
198
+ `COLD` is a different, rarer verdict: it appears only when every red dimension is *history* (D03–D05, D09,
199
+ D12) and nothing else is wrong — that is a tree that has never run the loop. `STOP` here means the project's
200
+ own facts are not configured yet, and no template can stand in for them:
201
+
202
+ | Red dimension | What only the project can supply |
203
+ |---|---|
204
+ | `D10` | `configs/repo_profiles.json` — the commands a worker must prove |
205
+ | `D11` | `configs/product_scenarios.json` — what a user must be able to do, and why it matters |
206
+ | `D12` | `configs/allowed_signers` + `configs/anchor_witness.json` — the key that signs a verdict |
207
+ | `D13` | `configs/visual_scenarios.json` — what a person must see; plus Chrome on the machine |
208
+ | `D14` | `configs/change_health.json` + a recorded baseline — which direction of each metric is worse |
209
+
210
+ A green gate is therefore work, not a default, and the gate says which work.
211
+
212
+ ### The skill
213
+
214
+ `loopgate init` writes `.agents/skills/loop-engineering/SKILL.md`. It is the procedure an agent follows: when
215
+ to start a loop, how to fill the run contract, how to repair one dimension per round, and when to stop.
216
+ Read it before a task that needs more than one pass.
217
+
218
+ Hermes loads project skills only from a trusted repository. The first run in a repo with project skills shows
219
+ a notice; trust the repo once:
220
+
221
+ ```bash
222
+ hermes skills trust # from inside the project
223
+ ```
224
+
225
+ Then start the session inside the project and ask for the task with the trigger words from the skill
226
+ (`loop engineering`, `bounded loop`, `iterate until green`). The skill reads the gate; it does not replace it.
227
+
228
+ Use the skill when the work needs rounds with evidence: a release gate, a refactor that must stay green,
229
+ a multi-step fix. Skip it for a one-off edit that one command verifies.
230
+
231
+ Python **3.9+** on paper, **3.12** in CI: one interpreter, so a green badge means "this runs", not
232
+ "this may run somewhere". `loopgate <cmd>` forwards every argument to the gate of the same name —
233
+ the command is a convenience, never a requirement.
234
+
235
+ `--repo PATH` goes before the command name and runs the script that lives in that checkout, so the
236
+ gate judges that tree instead of this one. It is deliberately not recognised after the command:
237
+ `worker_brief.py` has its own `--repo` (the repository slug for the issue URL), and the wrapper must
238
+ not steal it.
239
+
240
+ ## Sixty seconds
241
+
242
+ ```bash
243
+ python3 -m loopgate.gates.check_loop
244
+ # -> prints every dimension. State that belongs to a run is red here, because this tree has never
245
+ # run: the strict marker is not something you can clone.
246
+
247
+ python3 -m loopgate.gates.assert_dims --require D01 D02 D06 D07 D10 D11 D13
248
+ # -> CI_DIMS_PASS_0001. These depend only on the code — which is what CI can honestly assert.
249
+
250
+ python3 -m loopgate.gates.regressions
251
+ # -> REGRESSION_SUITE_PASS_0001. 26 defects are injected into copies of this repository and the gate
252
+ # must redden on the expected dimension each time. A case that targets loop state prints [skip]
253
+ # with its reason in a tree without that state: never counted as a pass, never a fail.
254
+ ```
255
+
256
+ The regression suite is the point of the project. It is easy to write a check that prints `OK`; it is
257
+ rare to prove that the check can fail.
258
+
259
+ ## Wiring it into your repository
260
+
261
+ 1. **Describe your gates** in `configs/repo_profiles.json` (start from
262
+ `configs/repo_profiles.example.json`). The bundle is the machine-readable form of "a worker must
263
+ prove *every* gate this repository requires — not the subset it remembered", and it names the file
264
+ the gates were derived from, so the list is a floor rather than a claim of completeness.
265
+ 2. **Turn issues into cards** with the bundle inside them:
266
+
267
+ ```bash
268
+ python3 -m loopgate.gates.sync_issues_to_kanban --profile sample-service # dry run
269
+ python3 -m loopgate.gates.sync_issues_to_kanban --profile sample-service --target backlog --apply
270
+ # -> backlog/<key>.md: task spec + mandatory commands + transcript contract, in git
271
+ ```
272
+
273
+ `--target kanban` hands the card to the dispatcher instead; `--target backlog` needs no execution
274
+ backend at all. Issues do not have to come from GitHub: `--issues-file issues.json --exported-by jira`
275
+ declares a file the authoritative export of a named tracker, and its idempotency keys carry that name.
276
+ 3. **Check the transcript that comes back**:
277
+
278
+ ```bash
279
+ python3 -m loopgate.gates.worker_brief verify --profile sample-service --evidence /tmp/transcript.md
280
+ # -> WORKER_EVIDENCE_PASS_0001 only when every mandatory command appears with its own exit code
281
+ ```
282
+
283
+ The format is deliberately dull: command, verbatim output, `exit=0` on its own line, per command.
284
+ One borrowed exit code at the end of the file proves nothing and is rejected.
285
+
286
+ ## What it is not
287
+
288
+ - **not a test runner** — it does not replace your tests, it demands their transcripts;
289
+ - **not a linter** — it does not judge style, it judges whether the claim matches the run;
290
+ - **not a framework** — no daemon, no state to migrate; scripts and JSON;
291
+ - **not a proof of correctness** — a gate proves the checks it names actually ran. Nothing more.
292
+
293
+ ## Honest comparison
294
+
295
+ The "evidence bundle for an agent PR" idea is not novel, and this project is not the first:
296
+
297
+ | Project | Focus | What loopgate adds, if anything |
298
+ |---|---|---|
299
+ | [Pramaan](https://github.com/marketplace/actions/pramaan-evidence-bundle-verifier) | receipt-first verification of a PR, GitHub Action, Rust CLI | our gate also attacks *itself* (26 injections), and it does not only look at the change |
300
+ | [Assay](https://haserjian.github.io/assay/) | proof packs + a signed review decision per PR | we sign the **verdict over the loop's state**, not only over an artifact |
301
+ | [hashgate](https://pypi.org/project/hashgate/) | fail-closed approval gate bound to a canonical hash | different question: ours is "did the work get proven", theirs is "is this the action I approved" |
302
+ | [aelitium](https://github.com/aelitium-dev/aelitium-v3) | offline-verifiable bundles for recorded LLM interactions | ours is about code acceptance, not model interaction records |
303
+ | [Backlog.md](https://github.com/MrLesk/Backlog.md) | markdown task ledger in git, three review checkpoints | complementary: `--target backlog` is a ledger; the gate is the proof layer on top |
304
+ | [worktrunk](https://github.com/max-sixty/worktrunk) | git worktree lifecycle for parallel agents | complementary: isolation is their job, acceptance is ours |
305
+
306
+ The three things that are actually ours: **a second verdict about the product** (start it, probe it,
307
+ kill it, refuse a bad config — "green CI, broken product" is the failure nobody else gates on), **a
308
+ third verdict about the direction of the tree** (a change that stays green while losing dimensions or
309
+ gaining untested surface is refused, because "nothing broke" and "nothing got worse" are different
310
+ sentences), and **a gate that proves it can fail**. If those do not matter to you, use one of the
311
+ projects above.
312
+
313
+ ## Limits, stated plainly
314
+
315
+ - **The strict marker is not reproducible from a clone, and a clone says so.** `bootstrap_state.py --init`
316
+ declares `cold`; the gate prints those dimensions as `COLD` and answers `LOOP_ENGINEERING_COLD_0001`,
317
+ never `PASS`. Proof of a run has to come from a run.
318
+ - **The product gate needs a product.** Fail-closed: no product path → STOP, not a silent PASS. Point it
319
+ at your own service with `--repo-path` or the profile's `local_path_env`.
320
+ - **The visual gate needs Chrome.** Headless Chrome/Chromium is required; absent → the check fails loudly
321
+ instead of skipping quietly. The default paths are macOS-first (`/Applications/...`).
322
+ - **Money is not measured.** Token and call counts are readable per run; a price is not, because most
323
+ agent backends do not report it.
324
+ - **`AGENTS.md`, `PLANS.md` and the skill under `.agents/` are the operator's notes (Russian).** They ship
325
+ because they are the contract this gate grew out of; the tool itself is documented in English.
326
+
327
+ ## Where the project stands
328
+
329
+ Eleven stages are closed in [PLANS.md](PLANS.md) — from the skeleton and the role fan-out to the second
330
+ and third verdicts, publication, and parity between two checkouts. The acceptance criterion for the whole
331
+ project is not a sentence in a plan: it is `python3 -m loopgate.gates.check_loop --strict` printing
332
+ `LOOP_ENGINEERING_PASS_0001` with `exit 0` on a tree that carries the loop's state.
333
+
334
+ ## Layout
335
+
336
+ <details>
337
+ <summary>19 scripts, one dispatcher, JSON configs — expand</summary>
338
+
339
+ ```
340
+ loopgate/gates/check_loop.py technical gate: 14 dimensions, STOP/PASS marker, read-only
341
+ loopgate/gates/check_product.py product gate: user-facing scenarios with a stated consequence
342
+ loopgate/gates/check_visual.py visual gate: headless screenshot + DOM rules
343
+ loopgate/gates/change_health.py third verdict: countable metrics against a baseline or a git ref
344
+ loopgate/gates/regressions.py 26 injections: proves the gate reddens on real defects
345
+ loopgate/gates/assert_dims.py CI: assert named dimensions are OK
346
+ loopgate/gates/worker_brief.py renders/verifies a worker brief (evidence bundle inside)
347
+ loopgate/gates/sync_issues_to_kanban.py issues -> queue (--target kanban | backlog)
348
+ loopgate/gates/review_pr.py second-level review of a PR, with a print-only verdict
349
+ loopgate/gates/guard_card.py per-card budget: tokens, runs, the progress clock
350
+ loopgate/gates/anchor_witness.py signs the verdict; publishes its digest to an external journal
351
+ loopgate/gates/tree_parity.py two checkouts must not drift — a command, not an observation
352
+ loopgate/gates/estimate_task.py measured p50/p90 -> a runtime cap for the queue
353
+ loopgate/gates/loop_watch.py fingerprints the gate; silent unless something changed (cron-friendly)
354
+ loopgate/gates/render_status_page.py both verdicts and the queue as one operator page
355
+ loopgate/gates/render_task_ledger.py finished cards -> backlog/*.md (git is the memory)
356
+ loopgate/gates/export_public.py assembles a publication-ready tree, fail-closed
357
+ loopgate/gates/bootstrap_state.py cold start for a clean clone: state=cold, not a fake history
358
+ loopgate/gates/doctor.py interpreter, jsonschema, Chrome, gh, git — before yellow reads as a defect
359
+ configs/ repository profiles, scenarios, rules, example identities
360
+ loopgate/ the `loopgate` command: names for the scripts above
361
+ ```
362
+
363
+ </details>
364
+
365
+ ## Contributing
366
+
367
+ The one rule: **a claim without a command is not accepted** — not from a human, not from an agent, not
368
+ from a pull request description. [CONTRIBUTING.md](CONTRIBUTING.md) says what CI runs, how to add a
369
+ dimension (check + falsification case + table row, in that order), and which habits keep the suite
370
+ honest. Security reports: [SECURITY.md](SECURITY.md).
371
+
372
+ ## License
373
+
374
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,40 @@
1
+ loopagent_gate-0.4.2.dist-info/licenses/LICENSE,sha256=ADaH17Fadzx_Sw6fk0bqf-ee4EwI32lXbZUAODO4cxY,1079
2
+ loopgate/__init__.py,sha256=ZfEJvP9AZdwTa6s3c4m8nOtkI06rJg65f37ccwv9V88,297
3
+ loopgate/__main__.py,sha256=WQyHiFnQeODATYpr9D-kqB2kG-TgRnAoYjFsh5Lwszw,167
4
+ loopgate/cli.py,sha256=x3d5w_Tookwpe9aPmO1JvuC17S3KNNWK_cPejXw1VdE,6559
5
+ loopgate/init.py,sha256=56bMOKqjOeQ6V6L2nkw5hd7y89tXLgyuO1FjOw_AnHA,6604
6
+ loopgate/paths.py,sha256=I_uUDTDJppZ0ytxVo8HT6W1RH0eUuYvo2v7WRqTFxX4,1733
7
+ loopgate/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ loopgate/gates/anchor_witness.py,sha256=RDeL1OhRwGj-6KWIkAXWaKTxptXPVuOu3BC_0yWVJ54,12512
9
+ loopgate/gates/assert_dims.py,sha256=rwOPAMdohbZLhzrwjHbqlYe2G3sK5oiP4Ft11yjqkxY,3369
10
+ loopgate/gates/bootstrap_state.py,sha256=FX_ptqG68ncEZxjmPGbK0YwiYh5Mp-7Ey7tz0EgsKis,4651
11
+ loopgate/gates/change_health.py,sha256=Kojk3e7zNTIzeHJacZGu5GspL_s11G7q-DthTMU711c,10496
12
+ loopgate/gates/check_loop.py,sha256=QGCjTNW1SWjTzObF1m3vQXOhTxBTYOAU13tygTiCyNI,67775
13
+ loopgate/gates/check_product.py,sha256=KOMEPkYibF-QqVz2r0ccQcHYWrkeFM8HBC9uSbdLztE,21212
14
+ loopgate/gates/check_visual.py,sha256=DNFMWaWFvjlfd7l-bOuLbdZjRnTTv2L-HZLJ4knn0Xs,9873
15
+ loopgate/gates/doctor.py,sha256=zx95199ekWQXn-8SmW47FnpwXC-pgjQVIb4G7aUEJmI,5135
16
+ loopgate/gates/estimate_task.py,sha256=Cu-9fskA001N8DRxxNo0N4_QuuEcfEGIKEWCZC7gMt0,5855
17
+ loopgate/gates/export_public.py,sha256=pNpF0rNDgbhpevD2Jr_CyVa2DZTDicTgyz7P-LAeg5o,12887
18
+ loopgate/gates/guard_card.py,sha256=NaVD6tytq3AJXVkE2j60qixqcp5LFnCiqqfIZWVUOzc,24755
19
+ loopgate/gates/loop_watch.py,sha256=hEMu0uyRjLGhv5Fgvm0qZHczeVs47Eig6SB7isVa8MM,4651
20
+ loopgate/gates/regressions.py,sha256=S6YbAo7BKInFquvyXQ8tHDurYFsaRHu59rkH4Rgiack,24808
21
+ loopgate/gates/render_status_page.py,sha256=dq_Gbsb3ax5vPgAdvGTAiAnQmMzDZfHK_7V99WGt0S0,6656
22
+ loopgate/gates/render_task_ledger.py,sha256=nF2Pe0e8dahaIExUflxWhh1hbSp2ibfQoxxkD9LtQhI,11537
23
+ loopgate/gates/review_pr.py,sha256=5bbU6KssFzkcYC_TMh8nMdX3IBxlW_qN81kON-WeSAs,18384
24
+ loopgate/gates/sync_issues_to_kanban.py,sha256=6VcyY8au8kYz4Yf8wOTORVCne06Px4WBJ3CNRdJM9To,29351
25
+ loopgate/gates/tree_parity.py,sha256=lNT81NOmxrMZ31BV8zuDS8tDwsPGBmVS-l3tExVAJFw,14333
26
+ loopgate/gates/worker_brief.py,sha256=uaoO4eAN4BD5y7mplq72YIJ7fyvbxm4PwjBJCGRFQ1U,20154
27
+ loopgate/templates/__init__.py,sha256=A-bv5IXVr0b-6E2WdgDgSbjp8EEETJfz0sFqegtQ4ho,95
28
+ loopgate/templates/configs/anchor_witness.example.json,sha256=8NVpxyG84z9LBYNsxaz1zEvH_s5LHjm5JS2sALzKZ2s,957
29
+ loopgate/templates/configs/repo_profiles.example.json,sha256=m_za0GcVFl2-v_aJm7-dvGT82niJCAhtPn4YkqPOy7Y,2719
30
+ loopgate/templates/configs/tree_parity.example.json,sha256=ugMGwS3u-65nW9UfV4Po0OGVg-v961UfGTFgR5gVHMU,1047
31
+ loopgate/templates/configs/fixtures/issues_sample.json,sha256=CvWeyuB_zlT4iCxeoJ2tsDotj5HLZBpLySi3FNaTnn0,3287
32
+ loopgate/templates/configs/fixtures/review_approve.json,sha256=w_rLfFu6LMp9WbjWDWEauN_fSjiUzqXFo01eKIjWtsQ,318
33
+ loopgate/templates/configs/fixtures/review_changes.json,sha256=bK8Sc1ibp3RibmFIHepbswOfXJd6Qm2uJyrGTNc_qFs,400
34
+ loopgate/templates/configs/fixtures/review_no_verdict.json,sha256=eGFmyzPgA8R-DBdX4Hc1741XFc2y5eqQpINBMfWyiW0,224
35
+ loopgate/templates/skills/loop-engineering/SKILL.md,sha256=ajCdQiuntFDhpEYWcVNQ4wqDFquc-vQbFp1ICxA5VnM,32916
36
+ loopagent_gate-0.4.2.dist-info/METADATA,sha256=vIuXbMYuQ0IYWVD04KZ1JjlJsy8ztjUK8bmO88Py1VI,21246
37
+ loopagent_gate-0.4.2.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
38
+ loopagent_gate-0.4.2.dist-info/entry_points.txt,sha256=4srAkKQ38EuS9YJAbG6WbN2MKetK3eA6s2_ocnq8e7o,47
39
+ loopagent_gate-0.4.2.dist-info/top_level.txt,sha256=UKbjU7cVUcG8pdkd82Xf4U7MZ_WpDwvU9I3eH6U-EYw,9
40
+ loopagent_gate-0.4.2.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ loopgate = loopgate.cli:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 loopagent contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ loopgate
loopgate/__init__.py ADDED
@@ -0,0 +1,7 @@
1
+ """loopgate — gates that accept agent work by proof, not by confidence.
2
+
3
+ The gates live in ``loopgate.gates`` and run as modules (``python -m loopgate.gates.check_loop``). The
4
+ ``loopgate`` command names them and runs them against the project in the working directory.
5
+ """
6
+
7
+ __version__ = "0.4.2"
loopgate/__main__.py ADDED
@@ -0,0 +1,8 @@
1
+ """Allow `python -m loopgate <command>` without installing the package."""
2
+
3
+ import sys
4
+
5
+ from loopgate.cli import main
6
+
7
+ if __name__ == "__main__":
8
+ sys.exit(main())
loopgate/cli.py ADDED
@@ -0,0 +1,147 @@
1
+ #!/usr/bin/env python3
2
+ """Single entry point for every gate and tool.
3
+
4
+ The gates are packaged under ``loopgate.gates``; this module only gives them one discoverable name and
5
+ runs them as modules. It never re-implements a check: it forwards the arguments and returns the exit code.
6
+
7
+ Usage:
8
+ loopgate # list the commands
9
+ loopgate init # add the contract, the skill and cold-start state to this project
10
+ loopgate gate --strict # technical gate -> LOOP_ENGINEERING_PASS_0001
11
+ loopgate product # product verdict -> PRODUCT_ACCEPTANCE_PASS_0001
12
+ loopgate regress # the gate's own regression suite
13
+ loopgate --repo ../other gate # judge another project instead of the working directory
14
+
15
+ ``--repo`` is global and must come *before* the command name. Without it the project is the nearest
16
+ ancestor of the working directory that holds ``.git`` or ``AGENTS.md`` (``loopgate.paths.project_root``).
17
+ Unknown arguments are forwarded verbatim, so ``loopgate gate --help`` is the gate's own help.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import os
23
+ import subprocess
24
+ import sys
25
+
26
+ from loopgate import __version__
27
+ from loopgate.paths import gate_module, project_root
28
+
29
+ # command -> (module under loopgate.gates, one-line description)
30
+ COMMANDS: dict[str, tuple[str, str]] = {
31
+ "gate": ("check_loop",
32
+ "technical gate: is the loop disciplined? -> LOOP_ENGINEERING_PASS_0001 / _STOP_0001"),
33
+ "product": ("check_product",
34
+ "product gate: can a user do their job? -> PRODUCT_ACCEPTANCE_PASS_0001 / _STOP_0001"),
35
+ "visual": ("check_visual",
36
+ "visual gate: headless screenshot + DOM rules -> VISUAL_CHECK_PASS_0001"),
37
+ "regress": ("regressions",
38
+ "inject defects into a copy: the gate must redden on the expected dimension"),
39
+ "brief": ("worker_brief",
40
+ "render/verify a worker brief (the evidence bundle is inside it)"),
41
+ "bridge": ("sync_issues_to_kanban",
42
+ "issues -> queue: --target kanban (dispatcher) or --target backlog (markdown ledger)"),
43
+ "estimate": ("estimate_task",
44
+ "measured p50/p90 -> a per-role runtime cap"),
45
+ "status": ("render_status_page",
46
+ "render both verdicts and the queue into one operator page"),
47
+ "witness": ("anchor_witness",
48
+ "sign/verify/publish the verdict anchor (external witness)"),
49
+ "watch": ("loop_watch",
50
+ "fingerprint the gate; stay silent unless it changed (cron-friendly)"),
51
+ "dims": ("assert_dims",
52
+ "assert that named dimensions are OK in a gate run (used by CI)"),
53
+ "doctor": ("doctor",
54
+ "report what this interpreter can run: jsonschema, Chrome, gh, git"),
55
+ "guard": ("guard_card",
56
+ "report (or --enforce) cards that exceed their token/run/progress budget"),
57
+ "ledger": ("render_task_ledger",
58
+ "render board cards into backlog/<task-id>.md (git is the memory)"),
59
+ "health": ("change_health",
60
+ "third verdict: compare countable metrics against the baseline or a git ref"),
61
+ "review": ("review_pr",
62
+ "second-level review of a pull request through ywcrew (--post to leave it on the PR)"),
63
+ "parity": ("tree_parity",
64
+ "two checkouts must not drift -> TREE_PARITY_PASS_0001 (--policy <json>)"),
65
+ "export": ("export_public",
66
+ "build a publication-ready tree with the private parts removed"),
67
+ }
68
+
69
+
70
+ def usage() -> str:
71
+ width = max(len(name) for name in list(COMMANDS) + ["init"])
72
+ lines = [f"loopgate {__version__} — gates that accept work by proof, not by confidence", "",
73
+ "usage: loopgate [--repo PATH] <command> [args...]", "",
74
+ f" {'init':<{width}} add the contract, the skill and cold-start state to this project"]
75
+ for name, (_, doc) in sorted(COMMANDS.items()):
76
+ lines.append(f" {name:<{width}} {doc}")
77
+ lines += ["",
78
+ "Each command forwards every extra argument to the underlying gate.",
79
+ "--repo PATH judges that project instead of the one found from the working directory;",
80
+ "it goes before the command name (after it, --repo belongs to the gate)."]
81
+ return "\n".join(lines)
82
+
83
+
84
+ def split_repo_flag(args: list[str]) -> tuple[str | None, list[str], str | None]:
85
+ """Consume a leading ``--repo <path>`` / ``--repo=<path>``.
86
+
87
+ Only the global position is recognised: ``worker_brief`` declares its own ``--repo`` (the repository
88
+ slug for the issue URL), so scanning the forwarded tail would silently steal that argument.
89
+ """
90
+ repo: str | None = None
91
+ index = 0
92
+ while index < len(args):
93
+ arg = args[index]
94
+ if arg == "--repo":
95
+ if index + 1 >= len(args):
96
+ return None, args, "--repo needs a path"
97
+ repo = args[index + 1]
98
+ index += 2
99
+ continue
100
+ if arg.startswith("--repo="):
101
+ repo = arg.split("=", 1)[1]
102
+ index += 1
103
+ continue
104
+ break
105
+ if repo is not None and not repo.strip():
106
+ return None, args, "--repo needs a path"
107
+ return repo, args[index:], None
108
+
109
+
110
+ def main(argv: list[str] | None = None) -> int:
111
+ args = list(sys.argv[1:] if argv is None else argv)
112
+ repo, args, repo_error = split_repo_flag(args)
113
+ if repo_error:
114
+ print(repo_error, file=sys.stderr)
115
+ return 2
116
+ if not args or args[0] in {"-h", "--help", "help"}:
117
+ print(usage())
118
+ return 0 if args else 2
119
+ if args[0] in {"--version", "version"}:
120
+ print(__version__)
121
+ return 0
122
+ name, rest = args[0], args[1:]
123
+
124
+ if repo is not None:
125
+ root = os.path.abspath(os.path.expanduser(repo))
126
+ if not os.path.isdir(root):
127
+ print(f"--repo is not a directory: {root}", file=sys.stderr)
128
+ return 2
129
+ else:
130
+ root = project_root()
131
+
132
+ if name == "init":
133
+ from loopgate.init import init_project
134
+ return init_project(root, rest)
135
+
136
+ entry = COMMANDS.get(name)
137
+ if entry is None:
138
+ print(f"unknown command: {name}", file=sys.stderr)
139
+ print(usage(), file=sys.stderr)
140
+ return 2
141
+ # The gate code comes from this installed package (`-m` resolves it from the interpreter); the
142
+ # project's own files (configs/, artifacts/, handoff/, AGENTS.md) are read from `root`, the cwd.
143
+ return subprocess.call([sys.executable, "-m", gate_module(entry[0]), *rest], cwd=root)
144
+
145
+
146
+ if __name__ == "__main__":
147
+ sys.exit(main())