simplicio-loop 3.20.0__tar.gz → 3.21.0__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 (71) hide show
  1. {simplicio_loop-3.20.0/simplicio_loop.egg-info → simplicio_loop-3.21.0}/PKG-INFO +3 -3
  2. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/README.md +3 -3
  3. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/pyproject.toml +3 -3
  4. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/README.md +1 -3
  5. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/hooks.claude.json +1 -2
  6. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/hooks.json +1 -2
  7. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/loop_stop.py +259 -14
  8. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/scripts/cross_agent_wiki.py +2 -1
  9. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-loop/SKILL.md +58 -14
  10. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/SKILL.md +2 -1
  11. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/extension-points.md +22 -2
  12. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/quality-safety-delivery.md +3 -2
  13. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/tests/test_loop_e2e.py +165 -3
  14. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0/simplicio_loop.egg-info}/PKG-INFO +3 -3
  15. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop.egg-info/SOURCES.txt +9 -6
  16. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop.egg-info/requires.txt +2 -2
  17. simplicio_loop-3.21.0/tests/test_agentsview_adapter.py +188 -0
  18. simplicio_loop-3.21.0/tests/test_az_boards_adapter.py +184 -0
  19. simplicio_loop-3.21.0/tests/test_claims_audit.py +173 -0
  20. simplicio_loop-3.21.0/tests/test_dashboard_hook.py +180 -0
  21. simplicio_loop-3.21.0/tests/test_doctor_smoke.py +104 -0
  22. simplicio_loop-3.21.0/tests/test_evidence_chain.py +168 -0
  23. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/tests/test_flow_audit.py +31 -0
  24. simplicio_loop-3.21.0/tests/test_hooks_coverage.py +226 -0
  25. simplicio_loop-3.21.0/tests/test_install_lib.py +143 -0
  26. simplicio_loop-3.21.0/tests/test_learn_pipeline_removed.py +49 -0
  27. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/tests/test_loop_e2e.py +165 -3
  28. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/tests/test_worker_selftests.py +24 -0
  29. simplicio_loop-3.20.0/simplicio_loop/_bundle/hooks/learn_stop.py +0 -38
  30. simplicio_loop-3.20.0/simplicio_loop/_bundle/scripts/__pycache__/cross_agent_wiki.cpython-314.pyc +0 -0
  31. simplicio_loop-3.20.0/simplicio_loop/_bundle/scripts/__pycache__/hierarchical_planner.cpython-314.pyc +0 -0
  32. simplicio_loop-3.20.0/simplicio_loop/_bundle/tests/__pycache__/_selfrun.cpython-314.pyc +0 -0
  33. simplicio_loop-3.20.0/simplicio_loop/_bundle/tests/__pycache__/test_loop_e2e.cpython-314-pytest-9.0.3.pyc +0 -0
  34. simplicio_loop-3.20.0/simplicio_loop/_bundle/tests/__pycache__/test_loop_e2e.cpython-314.pyc +0 -0
  35. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/LICENSE +0 -0
  36. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/MANIFEST.in +0 -0
  37. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/PYPI.md +0 -0
  38. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/setup.cfg +0 -0
  39. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/__init__.py +0 -0
  40. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/action_gate.py +0 -0
  41. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/loop_capture.py +0 -0
  42. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/orient_clamp.py +0 -0
  43. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/orient_rewrite.py +0 -0
  44. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/simplicio_dashboard.py +0 -0
  45. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/simplicio_watch.py +0 -0
  46. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/scripts/hierarchical_planner.py +0 -0
  47. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-compress/SKILL.md +0 -0
  48. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-learn/SKILL.md +0 -0
  49. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-orient/SKILL.md +0 -0
  50. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-review/SKILL.md +0 -0
  51. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/agentsview-adapter.md +0 -0
  52. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/azure-devops-adapter.md +0 -0
  53. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/lmcache-adapter.md +0 -0
  54. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/orchestration.md +0 -0
  55. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/standing-loop-247.md +0 -0
  56. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/token-capture.md +0 -0
  57. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/token-economy.md +0 -0
  58. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/understand-anything-adapter.md +0 -0
  59. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/video-evidence.md +0 -0
  60. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/web-evidence.md +0 -0
  61. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/tests/_selfrun.py +0 -0
  62. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/tests/test_cross_agent_wiki.py +0 -0
  63. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/cli.py +0 -0
  64. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop.egg-info/dependency_links.txt +0 -0
  65. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop.egg-info/entry_points.txt +0 -0
  66. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop.egg-info/top_level.txt +0 -0
  67. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/tests/test_action_gate.py +0 -0
  68. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/tests/test_cross_agent_wiki.py +0 -0
  69. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/tests/test_hierarchical_planner.py +0 -0
  70. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/tests/test_impact_audit.py +0 -0
  71. {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/tests/test_worker_smoke.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simplicio-loop
3
- Version: 3.20.0
3
+ Version: 3.21.0
4
4
  Summary: The Universal Looping AI Orchestrator — a runtime-agnostic super-plugin (6 skills) that drains any queue of work end-to-end on any LLM/runtime.
5
5
  Author-email: Wesley Simplicio <wesleybob4@gmail.com>
6
6
  License: MIT
@@ -19,8 +19,8 @@ Classifier: Operating System :: OS Independent
19
19
  Requires-Python: >=3.8
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
- Requires-Dist: simplicio-mapper>=0.11.0
23
- Requires-Dist: simplicio-cli>=0.7.1
22
+ Requires-Dist: simplicio-mapper>=0.14.0
23
+ Requires-Dist: simplicio-cli>=0.9.1
24
24
  Provides-Extra: dev
25
25
  Requires-Dist: pytest>=7; extra == "dev"
26
26
  Provides-Extra: ml
@@ -6,7 +6,7 @@
6
6
 
7
7
  <p align="center">
8
8
  <a href="https://github.com/wesleysimplicio/simplicio-loop/stargazers"><img src="https://img.shields.io/github/stars/wesleysimplicio/simplicio-loop?style=social" alt="Stars"></a>
9
- <a href="#-the-11-skills--accelerators"><img src="https://img.shields.io/badge/skills-11-7C3AED" alt="11 skills"></a>
9
+ <a href="#-the-6-skills--5-accelerators"><img src="https://img.shields.io/badge/skills-6-7C3AED" alt="6 skills"></a>
10
10
  <a href="#-source-adapters"><img src="https://img.shields.io/badge/source%20adapters-5-00E08A" alt="5 source adapters"></a>
11
11
  <a href="#-11-runtimes-one-protocol"><img src="https://img.shields.io/badge/runtimes-11-2563EB" alt="11 runtimes"></a>
12
12
  <a href="#-the-48-extension-points"><img src="https://img.shields.io/badge/extension%20points-48-00E08A" alt="48 extension points"></a>
@@ -17,7 +17,7 @@
17
17
 
18
18
  <p align="center">
19
19
  <a href="#-tldr">TL;DR</a> ·
20
- <a href="#-the-11-skills--accelerators">11 Skills</a> ·
20
+ <a href="#-the-6-skills--5-accelerators">6 Skills</a> ·
21
21
  <a href="#-source-adapters">Source Adapters</a> ·
22
22
  <a href="#-11-runtimes-one-protocol">11 Runtimes</a> ·
23
23
  <a href="#-the-loop">The Loop</a> ·
@@ -120,7 +120,7 @@ re-query stays empty K rounds). Both still obey the universal exits (promise+evi
120
120
 
121
121
  ---
122
122
 
123
- ## 🧠 The 11 skills & accelerators
123
+ ## 🧠 The 6 skills + 5 accelerators
124
124
 
125
125
  The orchestrator core + five satellites + five accelerators/integrations. Each satellite is
126
126
  **optional** — when loaded, the orchestrator delegates to it (richer + cheaper); when absent, the
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simplicio-loop"
7
- version = "3.20.0"
7
+ version = "3.21.0"
8
8
  description = "The Universal Looping AI Orchestrator — a runtime-agnostic super-plugin (6 skills) that drains any queue of work end-to-end on any LLM/runtime."
9
9
  readme = "PYPI.md"
10
10
  requires-python = ">=3.8"
@@ -25,8 +25,8 @@ classifiers = [
25
25
  # simplicio-mapper -> the repo-survey step (binds `orient`)
26
26
  # simplicio-cli -> the operator that applies+verifies changes (binds `execute`/`deterministic_edit`)
27
27
  dependencies = [
28
- "simplicio-mapper>=0.11.0",
29
- "simplicio-cli>=0.7.1",
28
+ "simplicio-mapper>=0.14.0",
29
+ "simplicio-cli>=0.9.1",
30
30
  ]
31
31
 
32
32
  # Optional: the real embedding backend for `simplicio-cli semantic --ml` / `simplicio-cli rag --ml`.
@@ -15,7 +15,6 @@ not a pass. It still lets every benign command through, so it never bricks norma
15
15
  | `action_gate.py` | safety: **fail-closed** — block irreversible ops + secret-laden commits/pushes BEFORE they run | `PreToolUse` (Bash) / git pre-push |
16
16
  | `orient_clamp.py` | simplicio-orient: **wrapper** — run a command, return reduced output + tee-on-failure | called directly, any runtime |
17
17
  | `orient_rewrite.py` | simplicio-orient: auto-route heavy read-only commands through the clamp (opt-in) | `PreToolUse` |
18
- | `learn_stop.py` | simplicio-learn: queue the finished run for a retrospective | `stop` / `SubagentStop` |
19
18
 
20
19
  ## The safety gate (`action_gate.py`)
21
20
 
@@ -65,8 +64,7 @@ Add (paths relative to the repo root, or absolute):
65
64
  "hooks": {
66
65
  "Stop": [
67
66
  { "hooks": [
68
- { "type": "command", "command": "python3 ./hooks/loop_stop.py" },
69
- { "type": "command", "command": "python3 ./hooks/learn_stop.py" }
67
+ { "type": "command", "command": "python3 ./hooks/loop_stop.py" }
70
68
  ] }
71
69
  ],
72
70
  "PreToolUse": [
@@ -3,8 +3,7 @@
3
3
  "Stop": [
4
4
  {
5
5
  "hooks": [
6
- { "type": "command", "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/hooks/loop_stop.py\"" },
7
- { "type": "command", "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/hooks/learn_stop.py\"" }
6
+ { "type": "command", "command": "python3 \"${CLAUDE_PLUGIN_ROOT}/hooks/loop_stop.py\"" }
8
7
  ]
9
8
  }
10
9
  ],
@@ -5,8 +5,7 @@
5
5
  { "command": "python3 ./hooks/loop_capture.py" }
6
6
  ],
7
7
  "stop": [
8
- { "command": "python3 ./hooks/loop_stop.py", "loop_limit": null },
9
- { "command": "python3 ./hooks/learn_stop.py" }
8
+ { "command": "python3 ./hooks/loop_stop.py", "loop_limit": null }
10
9
  ]
11
10
  }
12
11
  }
@@ -21,9 +21,11 @@ the dead-end attempts. A successful (promise-fulfilled) stop needs no handoff.
21
21
  import json
22
22
  import os
23
23
  import re
24
+ import shutil
24
25
  import subprocess
25
26
  import sys
26
27
  import time
28
+ import uuid
27
29
 
28
30
  LOOP_DIR = os.path.join(".orchestrator", "loop")
29
31
  SCRATCHPAD = os.path.join(LOOP_DIR, "scratchpad.md")
@@ -38,8 +40,13 @@ BUDGET = os.path.join(".orchestrator", "loop-budget.json")
38
40
  GATE_LOCK = os.path.join(LOOP_DIR, "gate.lock")
39
41
  GATE_TTL_SEC = 1800 # 30 min — a stale lock must NEVER permanently trap the loop (fail-open)
40
42
  WATCHER_STATE = os.path.join(LOOP_DIR, "watcher_state.json")
43
+ WATCHER_CHALLENGE = os.path.join(LOOP_DIR, "watcher_challenge.json")
41
44
  SPINDLE_STATE = os.path.join(LOOP_DIR, "spindle_state.json")
42
45
  PHASE_FILE = os.path.join(LOOP_DIR, "phase.json")
46
+ FLOW_AUDIT_RECEIPT = os.path.join(".orchestrator", "flow-audit.json")
47
+ SIMPLICIO_LOOP_SKILL_MARKER = os.path.join(".claude", "skills", "simplicio-loop", "SKILL.md")
48
+ BOUND_OPERATORS = ("simplicio-mapper", "simplicio-dev-cli")
49
+ WEB_EXTS = {".tsx", ".jsx", ".vue", ".svelte", ".html"}
43
50
 
44
51
  EVIDENCE_RE = re.compile(
45
52
  r"(https?://\S+/pull/\d+)" # a PR URL
@@ -57,7 +64,7 @@ def allow_stop():
57
64
 
58
65
 
59
66
  def cleanup_and_stop():
60
- for p in (SCRATCHPAD, DONE_FLAG, LEGACY_DONE_FLAG, LAST_RESP, WATCHER_STATE):
67
+ for p in (SCRATCHPAD, DONE_FLAG, LEGACY_DONE_FLAG, LAST_RESP, WATCHER_STATE, WATCHER_CHALLENGE):
61
68
  try:
62
69
  if os.path.exists(p):
63
70
  os.remove(p)
@@ -270,7 +277,12 @@ def write_handoff(reason, meta=None, body=None):
270
277
  with open(tmp, "w", encoding="utf-8") as f:
271
278
  f.write("\n".join(lines))
272
279
  os.replace(tmp, HANDOFF)
273
- refresh_cross_agent_wiki(include_handoff=True)
280
+ # include_handoff=False: this function just wrote the RICH handoff (frozen goal + AC
281
+ # checklist + last attempts) directly above. cross_agent_wiki.py's own `handoff` command
282
+ # writes the SAME path with a thinner layout — calling it here would immediately clobber
283
+ # what we just wrote (the three-writer bug, #68). Only capture + summary run; HANDOFF.md
284
+ # keeps a single owner for an INCOMPLETE stop.
285
+ refresh_cross_agent_wiki(include_handoff=False)
274
286
  except Exception:
275
287
  pass # fail-open: a broken handoff write must never block the stop
276
288
 
@@ -290,6 +302,160 @@ def budget_halted():
290
302
  return False # fail-open: budget unreadable ≠ trap
291
303
 
292
304
 
305
+ def missing_bound_operators():
306
+ """Return the bound-operator binaries missing from PATH, or [] if not applicable.
307
+
308
+ CLAUDE.md / `simplicio-loop` SKILL.md: when a body-of-work loop is driven by the
309
+ `simplicio-loop` companion skill, `simplicio-mapper` (survey) and `simplicio-dev-cli`
310
+ (operate) are REQUIRED — "the loop BLOCKS if either is absent". That contract was previously
311
+ enforced only at install/doctor time (#83); the running driver never checked it, so a
312
+ marketplace install, a PATH mismatch, or an operator uninstalled after setup silently
313
+ degraded to LLM hand-survey/hand-edit — exactly what the operators exist to prevent.
314
+
315
+ Scoped to repos that actually ship the `simplicio-loop` skill (its SKILL.md is the marker) —
316
+ a bare `simplicio-tasks` loop with no `simplicio-loop` companion has no operator requirement.
317
+ Fail-open: any probe error is treated as "present" (never trap the loop over a probe bug).
318
+ """
319
+ try:
320
+ if not os.path.exists(SIMPLICIO_LOOP_SKILL_MARKER):
321
+ return []
322
+ return [b for b in BOUND_OPERATORS if shutil.which(b) is None]
323
+ except Exception:
324
+ return []
325
+
326
+
327
+ def _flow_audit_module():
328
+ """Best-effort import of scripts/flow_audit.py for its FRONT_HINTS/BACK_HINTS. None on failure."""
329
+ try:
330
+ repo_root = os.getcwd()
331
+ scripts_dir = os.path.join(repo_root, "scripts")
332
+ if scripts_dir not in sys.path:
333
+ sys.path.insert(0, scripts_dir)
334
+ import flow_audit as _fa # noqa: local import, optional dependency
335
+ return _fa
336
+ except Exception:
337
+ return None
338
+
339
+
340
+ def _changed_files():
341
+ """Best-effort set of files touched in the working tree (uncommitted + untracked + last
342
+ commit). A heuristic, not a precise "since loop start" diff — fail-open: {} on any error.
343
+
344
+ Excludes `.orchestrator/` (the loop's own state files — never source, and would otherwise
345
+ make the receipt's own write, or a sibling state write, look like a "later" source change)
346
+ and build/cache noise (`__pycache__`, `.pyc`) that this very check's own module import can
347
+ create — that self-inflicted false-positive is exactly why both are filtered here.
348
+ """
349
+ out = set()
350
+ for args in (
351
+ ["git", "diff", "--name-only", "HEAD"],
352
+ ["git", "diff", "--name-only", "HEAD~1", "HEAD"],
353
+ ["git", "ls-files", "--others", "--exclude-standard"],
354
+ ):
355
+ try:
356
+ r = subprocess.run(args, capture_output=True, text=True, timeout=10)
357
+ if r.returncode == 0:
358
+ out.update(ln.strip() for ln in r.stdout.splitlines() if ln.strip())
359
+ except Exception:
360
+ continue
361
+ return {
362
+ f for f in out
363
+ if not f.startswith(".orchestrator/") and "__pycache__" not in f and not f.endswith((".pyc", ".pyo"))
364
+ }
365
+
366
+
367
+ def _touches_web_surface(files):
368
+ fa = _flow_audit_module()
369
+ front = tuple(getattr(fa, "FRONT_HINTS", ()) or ())
370
+ back = tuple(getattr(fa, "BACK_HINTS", ()) or ())
371
+ for f in files:
372
+ ext = os.path.splitext(f)[1].lower()
373
+ if ext in WEB_EXTS:
374
+ return True
375
+ fl = "/" + f.replace(os.sep, "/").lower()
376
+ if any(h in fl for h in front) or any(h in fl for h in back):
377
+ return True
378
+ return False
379
+
380
+
381
+ def flow_audit_gap():
382
+ """Return a human-readable gap string when a web-touching diff lacks a fresh, passing
383
+ `.orchestrator/flow-audit.json` receipt; None when there is nothing to require (#80).
384
+
385
+ Mechanizes what was previously prose-only (SKILL.md instructions the agent could skip under
386
+ context pressure): the anchor gate, watcher gate, cap, and budget are all enforced IN this
387
+ hook — the front→back integration gate now is too. Fail-open only when `flow_audit.py` itself
388
+ is absent (a bare `simplicio-tasks` repo with no flow_audit worker) or the diff/receipt can't
389
+ be read — never trap the loop over an audit-plumbing error.
390
+ """
391
+ try:
392
+ fa = _flow_audit_module()
393
+ if fa is None:
394
+ return None
395
+ files = _changed_files()
396
+ if not _touches_web_surface(files):
397
+ return None
398
+ if not os.path.exists(FLOW_AUDIT_RECEIPT):
399
+ return ("flow audit missing — run `python3 scripts/flow_audit.py audit . "
400
+ "--fail-on high --json > .orchestrator/flow-audit.json`")
401
+ receipt_mtime = os.path.getmtime(FLOW_AUDIT_RECEIPT)
402
+ for f in files:
403
+ try:
404
+ if os.path.getmtime(f) > receipt_mtime:
405
+ return ("flow audit stale — re-run `python3 scripts/flow_audit.py audit . "
406
+ "--fail-on high --json > .orchestrator/flow-audit.json`")
407
+ except OSError:
408
+ continue
409
+ with open(FLOW_AUDIT_RECEIPT, encoding="utf-8") as f:
410
+ receipt = json.load(f)
411
+ if not receipt.get("ok", False):
412
+ high = receipt.get("counts", {}).get("high_issues", "?")
413
+ return "flow audit failing (%s high issue(s)) — fix and re-run flow_audit.py" % high
414
+ return None
415
+ except Exception:
416
+ return None # fail-open: a broken audit read must never trap the loop
417
+
418
+
419
+ def auto_record_journal(iteration, has_evidence):
420
+ """Fallback journal record so the hierarchical planner is never blind (#67).
421
+
422
+ `scripts/hierarchical_planner.py` derives `iterations_run` from
423
+ `.orchestrator/loop/journal.jsonl` — but nothing auto-writes that file; only the manual
424
+ `loop_journal.py record` call (SKILL.md Step 4) does. An agent that forgets it leaves the
425
+ planner permanently frozen at "no history". This writes a minimal fallback record for THIS
426
+ iteration if — and only if — the agent hasn't already recorded one itself this turn (checked
427
+ by inspecting the last row), so a rich manual record is never overwritten or double-counted.
428
+ Gate is intentionally "blocked", never "fail": this is a presence signal for phase timing, not
429
+ a substitute for the real attempt-memory the agent records on a genuine failure. Fail-open.
430
+ """
431
+ try:
432
+ if os.path.exists(JOURNAL):
433
+ with open(JOURNAL, encoding="utf-8") as f:
434
+ lines = [ln for ln in f if ln.strip()]
435
+ if lines:
436
+ try:
437
+ last = json.loads(lines[-1])
438
+ if int(last.get("iteration", -1)) == iteration:
439
+ return # agent already recorded this turn manually
440
+ except Exception:
441
+ pass
442
+ repo_root = os.getcwd()
443
+ script = os.path.join(repo_root, "scripts", "loop_journal.py")
444
+ if not os.path.exists(script):
445
+ return
446
+ gate = "pass" if has_evidence else "blocked"
447
+ subprocess.run(
448
+ [sys.executable, script, "record",
449
+ "--iteration", str(iteration),
450
+ "--action", "auto: turn %d (no manual loop_journal record)" % iteration,
451
+ "--gate", gate,
452
+ "--note", "auto-recorded fallback"],
453
+ capture_output=True, timeout=10, cwd=repo_root,
454
+ )
455
+ except Exception:
456
+ pass
457
+
458
+
293
459
  def _discover_simplicio_cli():
294
460
  """Probe for simplicio CLI in priority order. Returns (binary, sub) or (None, None).
295
461
  Silent-fail: any probe error returns (None, None) — never blocks.
@@ -347,6 +513,44 @@ def _call_simplicio_nest():
347
513
  continue
348
514
 
349
515
 
516
+ def read_watcher_challenge():
517
+ """Return the current per-iteration watcher challenge dict, or None. Fail-open."""
518
+ try:
519
+ with open(WATCHER_CHALLENGE, encoding="utf-8") as f:
520
+ return json.load(f)
521
+ except Exception:
522
+ return None
523
+
524
+
525
+ def write_watcher_challenge(iteration):
526
+ """Issue a fresh per-iteration nonce the watcher receipt must echo (#82 challenge binding).
527
+
528
+ Written at the END of a turn that re-feeds (so it is on disk BEFORE the next turn's agent
529
+ acts, letting `scripts/watcher_verify.py` — run mid-turn, before the Stop hook fires — read
530
+ and echo it). A promise turn's receipt is checked against whatever challenge is on disk when
531
+ THIS Stop hook runs; a receipt that predates the challenge, or answers a different one, fails
532
+ the gate. This is what makes the watcher gate more than a plain unauthenticated JSON write:
533
+ the agent would have to notice, read, and correctly echo a value it does not control the
534
+ timing of — a visible, journaled action, not a silent one-call spoof. Fail-open: any write
535
+ error must never block the re-feed itself.
536
+ """
537
+ try:
538
+ anchor = read_anchor() or {}
539
+ os.makedirs(LOOP_DIR, exist_ok=True)
540
+ payload = {
541
+ "challenge": uuid.uuid4().hex[:20],
542
+ "iteration": iteration,
543
+ "goal_fp": anchor.get("goal_fp", ""),
544
+ "written_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
545
+ }
546
+ tmp = WATCHER_CHALLENGE + ".tmp"
547
+ with open(tmp, "w", encoding="utf-8") as f:
548
+ json.dump(payload, f)
549
+ os.replace(tmp, WATCHER_CHALLENGE)
550
+ except Exception:
551
+ pass
552
+
553
+
350
554
  def watcher_verify():
351
555
  """Run pre-promise watcher verification per Asolaria N-Nest Corrective Gate pattern.
352
556
 
@@ -354,6 +558,12 @@ def watcher_verify():
354
558
  agent/PID that independently re-executes the work and compares results against the agent's
355
559
  reported output). Gate: `reported == watcher.recomputed_truth`.
356
560
 
561
+ Challenge binding (#82): a receipt must additionally echo the CURRENT per-iteration
562
+ `watcher_challenge.json` nonce (and the frozen anchor's `goal_fp`, when an anchor exists) —
563
+ otherwise it is rejected even if `match: true`. This closes the plain self-attestation gap: a
564
+ receipt hand-written once at iteration 1 (or copied from a stale run) can no longer satisfy the
565
+ gate on a later, different iteration/goal.
566
+
357
567
  Returns (passed: bool, tag: str) where tag is "MEASURED" (verified) or "UNVERIFIED" (not
358
568
  verified or mismatch). If no watcher state exists → UNVERIFIED (gate fails). Fail-open:
359
569
  a corrupt or missing watcher state NEVER traps the loop — it simply gates the promise.
@@ -365,9 +575,21 @@ def watcher_verify():
365
575
  state = json.load(f)
366
576
  match = bool(state.get("match", False))
367
577
  status = str(state.get("status", "UNVERIFIED"))
368
- if match and status == "MEASURED":
369
- return True, "MEASURED"
370
- return False, "UNVERIFIED"
578
+ if not (match and status == "MEASURED"):
579
+ return False, "UNVERIFIED"
580
+ challenge = read_watcher_challenge()
581
+ if not challenge:
582
+ return False, "UNVERIFIED" # no challenge on disk — nothing valid to echo yet
583
+ if state.get("challenge") != challenge.get("challenge"):
584
+ return False, "UNVERIFIED"
585
+ expected_fp = challenge.get("goal_fp") or ""
586
+ if expected_fp and state.get("goal_fp") != expected_fp:
587
+ return False, "UNVERIFIED"
588
+ checked_at = state.get("checked_at") or ""
589
+ written_at = challenge.get("written_at") or ""
590
+ if checked_at and written_at and checked_at < written_at:
591
+ return False, "UNVERIFIED" # receipt predates the challenge it claims to answer
592
+ return True, "MEASURED"
371
593
  except Exception:
372
594
  return False, "UNVERIFIED"
373
595
 
@@ -510,8 +732,23 @@ def main():
510
732
  promise = None if promise in (None, "null", "") else promise
511
733
  evidence_required = str(meta.get("evidence_required", "true")).lower() != "false"
512
734
 
735
+ # (2b) Bound operators required (#83) — when this repo ships the simplicio-loop
736
+ # companion skill, `simplicio-mapper`/`simplicio-dev-cli` are hard deps of the running
737
+ # loop, not just the installer. A genuine BLOCK (handoff + stop), mirroring the cap and
738
+ # budget gates, so a marketplace install / PATH gap can never silently degrade to LLM
739
+ # hand-survey/hand-edit.
740
+ missing_ops = missing_bound_operators()
741
+ if missing_ops:
742
+ write_handoff("bound operator missing: %s" % ", ".join(missing_ops), meta, body)
743
+ cleanup_and_stop()
744
+
513
745
  stdin = read_stdin_json()
514
746
  resp = last_assistant_text(stdin)
747
+ has_evidence = bool(resp and EVIDENCE_RE.search(resp))
748
+
749
+ # Fallback attempt-memory record (#67) so the hierarchical planner is never blind to
750
+ # this turn even if the agent forgot the manual `loop_journal.py record` call.
751
+ auto_record_journal(iteration, has_evidence)
515
752
 
516
753
  # HRM-style hierarchical planner: re-assess phase on stall or every N iterations.
517
754
  # Runs BEFORE the promise gate so the phase context is available.
@@ -522,20 +759,23 @@ def main():
522
759
  # independently re-computes the truth. Gate: reported == watcher.recomputed_truth.
523
760
  watcher_pass, watcher_tag = watcher_verify()
524
761
 
762
+ # Pre-promise: front→back flow-audit gate (#80) — mechanical, not prose-only.
763
+ flow_gap = flow_audit_gap()
764
+
525
765
  # Completion detection (capture folded in for single-hook runtimes like Claude).
526
766
  if promise and resp:
527
767
  m = PROMISE_RE.search(resp)
528
768
  if m and m.group(1).strip() == promise.strip():
529
- has_evidence = bool(EVIDENCE_RE.search(resp))
530
769
  # The promise is honored only with evidence AND watcher verification AND no
531
- # acceptance criterion still open in the task anchor. The watcher-gate ensures
532
- # the agent's result was independently re-executed and matched before the
533
- # promise is accepted — corrective gate per Asolaria.
534
- if ((not evidence_required) or has_evidence) and watcher_pass and not anchor_pending():
770
+ # acceptance criterion still open in the task anchor AND no open flow-audit gap.
771
+ # The watcher-gate ensures the agent's result was independently re-executed and
772
+ # matched before the promise is accepted — corrective gate per Asolaria.
773
+ if (((not evidence_required) or has_evidence) and watcher_pass
774
+ and not anchor_pending() and not flow_gap):
535
775
  refresh_cross_agent_wiki(include_handoff=False)
536
776
  cleanup_and_stop() # (3) promise fulfilled → stop, no handoff needed
537
- # promise without evidence, or watcher disagrees, or anchor still has open ACs
538
- # → ignore, keep looping
777
+ # promise without evidence, or watcher disagrees, or anchor still has open ACs,
778
+ # or a flow-audit gap remains → ignore, keep looping
539
779
  # (3') Cursor capture may have raised the flag.
540
780
  if os.path.exists(DONE_FLAG) or os.path.exists(LEGACY_DONE_FLAG):
541
781
  cleanup_and_stop()
@@ -588,9 +828,14 @@ def main():
588
828
  if pending
589
829
  else ""
590
830
  )
591
- header = "[simplicio-loop iteration %d.%s%s%s %s]" % (
592
- nxt, promise_hint, ac_hint, phase_header_hint(), watcher_tag
831
+ flow_hint = " Flow-audit gap: %s." % flow_gap if flow_gap else ""
832
+ header = "[simplicio-loop iteration %d.%s%s%s%s %s]" % (
833
+ nxt, promise_hint, ac_hint, flow_hint, phase_header_hint(), watcher_tag
593
834
  )
835
+ # Issue the NEXT iteration's watcher challenge before re-feeding (#82) — must be on disk
836
+ # before the next turn's agent acts, so a mid-turn `watcher_verify.py` run can read and
837
+ # echo it.
838
+ write_watcher_challenge(nxt)
594
839
  refresh_cross_agent_wiki(include_handoff=False)
595
840
  emit_refeed(header + "\n\n" + (body or ""))
596
841
  except Exception:
@@ -126,7 +126,8 @@ def _read_watcher_state():
126
126
  except FileNotFoundError:
127
127
  return {
128
128
  "state": "missing",
129
- "line": "UNVERIFIED|watcher: no receipt (.orchestrator/loop/watcher_state.json missing)",
129
+ "line": "UNVERIFIED|watcher: no receipt (producer: `python3 scripts/watcher_verify.py "
130
+ "verify` — not yet run this turn, or the challenge hasn't been issued yet)",
130
131
  }
131
132
  except (OSError, json.JSONDecodeError):
132
133
  return {
@@ -49,7 +49,7 @@ hard dependencies of the `simplicio-loop` package (`pip install simplicio-loop`
49
49
 
50
50
  | Operator | CLI (binary) | Binds | Role in the loop |
51
51
  |---|---|---|---|
52
- | **simplicio-mapper** | `simplicio-mapper` | `orient` / `recall` | **Survey** — maps the repo(s) into `.simplicio/*.json` (project-map, precedent-index, symbol-index, call-graph, docs). Two-tier (v0.9+): `macro` is an instant shallow skeleton (no content reads), `scan` returns that skeleton now and runs the deep index in the background, `status` reports the deep-pass phase. This survey, not an ad-hoc LLM read, is what feeds the goal each turn. |
52
+ | **simplicio-mapper** | `simplicio-mapper` | `orient` / `recall` | **Survey** — maps the repo(s) into `.simplicio/*.json` (project-map, precedent-index, symbol-index, call-graph, docs). Two-tier (v0.9+): `macro` is an instant shallow skeleton (no content reads), `scan` returns that skeleton now and runs the deep index in the background, `status` reports the deep-pass phase. v0.13+ adds `inspect` (machine-readable evidence that the artifacts actually exist — the survey's own evidence gate) and `handoff` (a compact context-pack — files, symbols, deps, `pack_hash` — that feeds the goal instead of re-reading the tree). v0.14+ adds the flow-docs engine: `ask` (low-token structured queries over the artifacts), `sync --check`/`drift --check` (docs-staleness + spec-drift gates), `flows`/`survey`/`business`/`history`/`diff` (flow inventory, onboarding report, business rules, architecture history). This survey, not an ad-hoc LLM read, is what feeds the goal each turn. |
53
53
  | **simplicio-dev-cli** | `simplicio-dev-cli` | `execute` / `deterministic_edit` / `validate` / `diagnostics` | **Operate** — applies a DECIDED change through its 6-layer contract (mapper context → precedent → prompt → diff → test → verify, ≤3 retries). The CLI edits and verifies; the AI does not hand-write the diff. |
54
54
 
55
55
  **Preflight (MANDATORY, BLOCKING).** Before iteration 1, auto-update both operators to their latest
@@ -81,6 +81,38 @@ long runs) remains the synchronous full (re)build of `.simplicio/`. Read the sur
81
81
  never re-scan the tree by hand when a fresh map exists. For a multi-repo survey, run the mapper per
82
82
  repo root and aggregate the JSON.
83
83
 
84
+ **Survey evidence gate + context-pack (v0.13+).** Before trusting the deep artifacts, gate on
85
+ `simplicio-mapper inspect . --json [--await]` (`simplicio.map-inspection/v1`): it reports, per
86
+ artifact (project-map, precedent-index, symbol-index, call-graph, index-state, map-job,
87
+ context-cache), whether the file **exists on disk** with size + mtime, plus `warnings`. An artifact
88
+ the inspection says is missing must be treated as absent — re-run `scan`/`index`, don't guess its
89
+ content. This is the same evidence-not-claims discipline the promise gate applies, applied to the
90
+ survey itself. Then feed the goal from `simplicio-mapper handoff . --json [--await]`
91
+ (`simplicio.map-handoff/v1`): its `context_pack` carries the relevant files with symbols, imports,
92
+ dependencies, `recent_changes` and a `pack_hash` — a pre-compressed orientation bundle that
93
+ substitutes for re-reading the tree (token economy: pack first, raw `Read` only for the few files
94
+ the pack points at). Honor `context_pack.llm_directives` (no-think / no-internet / minimal tools)
95
+ for the mechanical steps, and use `needs_broader_context` as the signal that the pack alone is not
96
+ enough.
97
+
98
+ **Structured queries + docs gates (v0.14+).** For triage questions the map alone doesn't answer,
99
+ query the built artifacts instead of grepping the tree:
100
+ `simplicio-mapper ask . <callers|callees|reaches|impact|flows|rules|tests-for|term> <arg> --json`
101
+ (`simplicio.ask/v1`) — e.g. `ask . impact src/api.py` before an edit (which flows/dependents does
102
+ this touch — feeds the `dependency_graph` widening), `ask . tests-for <symbol>` to pick the
103
+ affected tests to run, `ask . callers <symbol>` during review. Two mechanical verify-side gates
104
+ join the DoD pass: `simplicio-mapper sync . --check --json` (`simplicio.docs-sync/v1`) reports
105
+ generated docs now stale relative to the diff, and `simplicio-mapper drift . --check --json`
106
+ (`simplicio.spec-drift/v1`) reports spec↔code drift (orphan spec refs, unresolved placeholders,
107
+ stale docs) — surface their findings in the turn report; they BLOCK only when the task's own AC is
108
+ documentation. For an explicit docs/onboarding task, the producers are `flows` (end-to-end flow
109
+ inventory), `survey` (new-developer onboarding report), `business` (observable business rules +
110
+ glossary), and `history`/`diff` (architecture snapshots + semantic deltas).
111
+
112
+ If the installed mapper predates 0.13 (`inspect`/`handoff` absent from `--help`), the
113
+ preflight auto-update already pulls a current build; offline, fall back to `status` + reading
114
+ `.simplicio/*.json` directly — the gate is then the file-existence check you do by hand.
115
+
84
116
  **Operate step (every turn that mutates code).** Once the AC and the change are DECIDED, delegate
85
117
  the mutation to the operator, one decided change at a time:
86
118
  ```bash
@@ -97,8 +129,9 @@ merge/close gates); the operators do survey + apply:
97
129
  | Phase | Operator | Command |
98
130
  |---|---|---|
99
131
  | Preflight (before iteration 1) | both | `python3 -m pip install -qU simplicio-mapper simplicio-cli` (auto-update to latest, fail-open) → `simplicio-mapper --version` · `simplicio-dev-cli --help` → BLOCK if missing |
100
- | Survey (loop start; multi-repo: per root) | mapper | `simplicio-mapper scan . --json` (instant macro + deep index in background; `--sync`/`--await` to block) → `.simplicio/*.json`. `index . --json` for a forced synchronous build |
101
- | Loop contract step 2 — Triage (every turn) | mapper | re-read `.simplicio/*.json`; `simplicio-mapper macro . --json` for an instant skeleton, or `scan`/`status` to refresh if the tree changed |
132
+ | Survey (loop start; multi-repo: per root) | mapper | `simplicio-mapper scan . --json` (instant macro + deep index in background; `--sync`/`--await` to block) → `.simplicio/*.json`. `index . --json` for a forced synchronous build. Gate: `inspect . --json` (artifacts exist on disk) → feed goal: `handoff . --json` (context-pack) |
133
+ | Loop contract step 2 — Triage (every turn) | mapper | `simplicio-mapper handoff . --json` → work from the `context_pack` (symbols/deps/recent_changes); `ask . impact\|tests-for\|callers <arg> --json` for targeted questions; `macro . --json` for an instant skeleton, or `scan`/`status` + `inspect` to refresh/re-gate if the tree changed |
134
+ | Verify / DoD pass | mapper | `simplicio-mapper sync . --check --json` (stale generated docs) + `drift . --check --json` (spec↔code drift) — findings go in the turn report; BLOCK only when the AC itself is documentation |
102
135
  | Loop contract step 3 — Work the goal | dev-cli | `simplicio-dev-cli task "<decided change>" --target <file> [--json]` |
103
136
  | Evidence-gated `<promise>` / `simplicio-tasks` Step 4b | dev-cli | the operator's passing test+verify pass = in-turn evidence |
104
137
 
@@ -190,11 +223,17 @@ detector below. It is the difference between a loop that converges and one that
190
223
  AC-scoped change; the **`simplicio-dev-cli` operator APPLIES and verifies it**
191
224
  (`simplicio-dev-cli task "<change>" --target <file>`) — do not hand-edit inside the loop. End EVERY
192
225
  iteration with a short, concrete verification — the operator's passing test run, or one gate /
193
- command / `file:line` receipt. **After the operator passes, the watcher-gate re-runs
194
- independently** — a separate agent/PID re-executes the work and writes
195
- `.orchestrator/loop/watcher_state.json` with `{"match": true, "status": "MEASURED"}` only when
196
- `reported == watcher.recomputed_truth`. A `match: false` or missing watcher state is treated as
197
- `UNVERIFIED` and gates the promise. If the actual edit surface expands, rerun `impact_audit.py` with
226
+ command / `file:line` receipt. **After the operator passes, run the watcher producer**:
227
+ `python3 scripts/watcher_verify.py verify` — it reads the per-iteration challenge the stop-hook
228
+ issued (`.orchestrator/loop/watcher_challenge.json`) and independently recomputes the frozen
229
+ anchor's done/pending state from disk (never trusting anything asserted in-context), then
230
+ writes `.orchestrator/loop/watcher_state.json` with `{"match": true, "status": "MEASURED",
231
+ "challenge": ..., "goal_fp": ...}` only when `reported == watcher.recomputed_truth` AND the
232
+ receipt echoes the current challenge. **Never hand-write `watcher_state.json` directly** — a
233
+ hand-written receipt cannot know the current challenge and will be rejected by the gate; this is
234
+ the mechanical fix for the plain-unauthenticated-JSON self-attestation gap. A `match: false`,
235
+ missing, or unchallenged watcher state is treated as `UNVERIFIED` and gates the promise. If the
236
+ actual edit surface expands, rerun `impact_audit.py` with
198
237
  the new seeds/cover and treat uncovered reverse dependents as failed verification; use
199
238
  `--fail-on medium` for shared/public contracts or signature changes. If the change crosses
200
239
  UI/API/service boundaries, rerun
@@ -363,15 +402,20 @@ The classic Ralph loop trusts the model to be honest. We do not. A `<promise>` i
363
402
  only if, in the SAME turn, there is concrete evidence the work is truly done, AND the
364
403
  **watcher-gate** has independently verified the result:
365
404
 
366
- - the **watcher-gate** itself (Asolaria N-Nest Corrective Gate) — a separate agent/PID
367
- re-executes the work and compares results; `.orchestrator/loop/watcher_state.json` is written
368
- with `{"match": true, "status": "MEASURED"}` only when `reported == watcher.recomputed_truth`,
369
- or
405
+ - the **watcher-gate** itself (Asolaria N-Nest Corrective Gate) — `python3
406
+ scripts/watcher_verify.py verify` independently re-executes the anchor's recompute and writes
407
+ `.orchestrator/loop/watcher_state.json` with `{"match": true, "status": "MEASURED", "challenge":
408
+ ..., "goal_fp": ...}` only when `reported == watcher.recomputed_truth`; the receipt must ALSO
409
+ echo the current per-iteration challenge the stop-hook issued (`watcher_challenge.json`) — a
410
+ receipt that doesn't, or predates the challenge, is rejected even if `match: true` (closes the
411
+ plain-unauthenticated-JSON self-attestation gap — never hand-write this file), or
370
412
  - the run-verification gate passed ("works, not just compiles" — `simplicio-tasks` Step 4b) —
371
413
  the `simplicio-dev-cli` operator's passing test+verify pass (its contract step 5/6) satisfies this, or
372
414
  - the flow coverage gate passed for a mixed front/back/service change —
373
415
  `python3 scripts/flow_audit.py audit <root> --fail-on high` (or `--fail-on medium` for ACs that
374
- promise backend integration) found no unhandled UI/API/service gaps, or
416
+ promise backend integration) found no unhandled UI/API/service gaps — the stop-hook mechanically
417
+ requires a fresh, green `.orchestrator/flow-audit.json` receipt before honoring the promise
418
+ whenever the diff touches web-surface files, so this is enforced, not prose-only, or
375
419
  - the scope/impact gate passed for the changed shared files —
376
420
  `python3 scripts/impact_audit.py audit <root> --file <seed> ...` found no uncovered reverse
377
421
  dependents (and, for shared/public contracts, no uncovered local deps/tests under `--fail-on medium`), or
@@ -463,7 +507,7 @@ Where the host runtime supports lifecycle hooks, bind the two cross-platform hoo
463
507
  | Hook | Fires | Job |
464
508
  |---|---|---|
465
509
  | `afterAgentResponse` → `loop_capture.py` | after every turn | extract `<promise>…</promise>`; if it exactly equals `completion_promise` AND in-turn evidence exists → `touch .orchestrator/loop/done`. Fire-and-forget, `exit 0`. Never stops the loop itself. |
466
- | `stop` → `loop_stop.py` | when the turn ends | guard clauses, each ends the loop cleanly (remove state, `exit 0`): (1) no scratchpad → stop; (2) corrupt frontmatter → stop; (3) `done` flag present → stop (promise fulfilled); (4) `iteration >= max_iterations > 0` → write `HANDOFF.md`, then stop (cap); (5) budget halted → write `HANDOFF.md` (frozen goal + AC status + last attempts) for a different agent to resume, then stop; (6) **spindle handoff latched** → write `HANDOFF.md` and stop (the next agent will pick up); **before promise check: runs watcher-gate** — reads `.orchestrator/loop/watcher_state.json` and rejects the promise if `match: false` or `status: UNVERIFIED`; the re-feed header is tagged with `MEASURED`/`UNVERIFIED` accordingly; else increment `iteration` in place and emit `{"followup_message": "<header>\\n\\n<goal body>"}` to re-feed. |
510
+ | `stop` → `loop_stop.py` | when the turn ends | guard clauses, each ends the loop cleanly (remove state, `exit 0`): (1) no scratchpad → stop; (2) corrupt frontmatter → stop; (2b) **bound operator missing** (when this repo ships `simplicio-loop`, `simplicio-mapper`/`simplicio-dev-cli` absent from PATH) → write `HANDOFF.md`, then stop (never silently degrade to LLM hand-survey/hand-edit); (3) `done` flag present → stop (promise fulfilled); (4) `iteration >= max_iterations > 0` → write `HANDOFF.md`, then stop (cap); (5) budget halted → write `HANDOFF.md` (frozen goal + AC status + last attempts) for a different agent to resume, then stop; (6) **spindle handoff latched** → write `HANDOFF.md` and stop (the next agent will pick up); **before promise check: runs watcher-gate** — reads `.orchestrator/loop/watcher_state.json` and rejects the promise if `match: false`, `status: UNVERIFIED`, or the receipt doesn't echo the current `watcher_challenge.json` nonce/goal_fp; also runs the **flow-audit gate** — a web-touching diff with no fresh, green `.orchestrator/flow-audit.json` rejects the promise too; the re-feed header is tagged with `MEASURED`/`UNVERIFIED` and names any flow-audit gap; a fallback `loop_journal.py record` fires if the agent didn't record one manually this turn, so the phase planner is never blind; a fresh watcher challenge is written before the re-feed; else increment `iteration` in place and emit `{"followup_message": "<header>\\n\\n<goal body>"}` to re-feed. |
467
511
 
468
512
  Detection (`capture`) and termination (`stop`) are split on purpose — neither parses the
469
513
  other's inline state. Iteration carries forward through git history + the working tree, not
@@ -298,7 +298,8 @@ short evidence comment (PR link + verification). **Assemble the PR body mechanic
298
298
  carries prints + an item-by-item AC check** — `python3 scripts/pr_evidence.py build --item <id>
299
299
  --title "<t>" --summary "<s>" --require-evidence --out .orchestrator/pr_body.md` pulls the
300
300
  item-by-item acceptance-criteria checklist from the task anchor (Step 2b/4) AND embeds the
301
- screenshots/recordings captured by `web_verify`/`video_evidence` under `.orchestrator/tee/web`; with
301
+ screenshots (`web_verify`, under `.orchestrator/tee/web`) and recordings (`video_evidence`, under
302
+ `.orchestrator/tee/video`); with
302
303
  `--require-evidence` it EXITS 3 (blocked) rather than open a PR with no prints and no checklist (the
303
304
  "PR sem evidência" fix). It honors the discovered `.github/PULL_REQUEST_TEMPLATE.md` (the
304
305
  `pr_template` extension point) — appending the checklist + prints under the maintainer's sections —