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.
- {simplicio_loop-3.20.0/simplicio_loop.egg-info → simplicio_loop-3.21.0}/PKG-INFO +3 -3
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/README.md +3 -3
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/pyproject.toml +3 -3
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/README.md +1 -3
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/hooks.claude.json +1 -2
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/hooks.json +1 -2
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/loop_stop.py +259 -14
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/scripts/cross_agent_wiki.py +2 -1
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-loop/SKILL.md +58 -14
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/SKILL.md +2 -1
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/extension-points.md +22 -2
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/quality-safety-delivery.md +3 -2
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/tests/test_loop_e2e.py +165 -3
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0/simplicio_loop.egg-info}/PKG-INFO +3 -3
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop.egg-info/SOURCES.txt +9 -6
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop.egg-info/requires.txt +2 -2
- simplicio_loop-3.21.0/tests/test_agentsview_adapter.py +188 -0
- simplicio_loop-3.21.0/tests/test_az_boards_adapter.py +184 -0
- simplicio_loop-3.21.0/tests/test_claims_audit.py +173 -0
- simplicio_loop-3.21.0/tests/test_dashboard_hook.py +180 -0
- simplicio_loop-3.21.0/tests/test_doctor_smoke.py +104 -0
- simplicio_loop-3.21.0/tests/test_evidence_chain.py +168 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/tests/test_flow_audit.py +31 -0
- simplicio_loop-3.21.0/tests/test_hooks_coverage.py +226 -0
- simplicio_loop-3.21.0/tests/test_install_lib.py +143 -0
- simplicio_loop-3.21.0/tests/test_learn_pipeline_removed.py +49 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/tests/test_loop_e2e.py +165 -3
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/tests/test_worker_selftests.py +24 -0
- simplicio_loop-3.20.0/simplicio_loop/_bundle/hooks/learn_stop.py +0 -38
- simplicio_loop-3.20.0/simplicio_loop/_bundle/scripts/__pycache__/cross_agent_wiki.cpython-314.pyc +0 -0
- simplicio_loop-3.20.0/simplicio_loop/_bundle/scripts/__pycache__/hierarchical_planner.cpython-314.pyc +0 -0
- simplicio_loop-3.20.0/simplicio_loop/_bundle/tests/__pycache__/_selfrun.cpython-314.pyc +0 -0
- simplicio_loop-3.20.0/simplicio_loop/_bundle/tests/__pycache__/test_loop_e2e.cpython-314-pytest-9.0.3.pyc +0 -0
- simplicio_loop-3.20.0/simplicio_loop/_bundle/tests/__pycache__/test_loop_e2e.cpython-314.pyc +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/LICENSE +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/MANIFEST.in +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/PYPI.md +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/setup.cfg +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/__init__.py +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/action_gate.py +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/loop_capture.py +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/orient_clamp.py +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/orient_rewrite.py +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/simplicio_dashboard.py +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/simplicio_watch.py +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/scripts/hierarchical_planner.py +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-compress/SKILL.md +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-learn/SKILL.md +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-orient/SKILL.md +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-review/SKILL.md +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/agentsview-adapter.md +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/azure-devops-adapter.md +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/lmcache-adapter.md +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/orchestration.md +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/standing-loop-247.md +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/token-capture.md +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/token-economy.md +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/understand-anything-adapter.md +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/video-evidence.md +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/web-evidence.md +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/tests/_selfrun.py +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/tests/test_cross_agent_wiki.py +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/cli.py +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop.egg-info/dependency_links.txt +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop.egg-info/entry_points.txt +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop.egg-info/top_level.txt +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/tests/test_action_gate.py +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/tests/test_cross_agent_wiki.py +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/tests/test_hierarchical_planner.py +0 -0
- {simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/tests/test_impact_audit.py +0 -0
- {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.
|
|
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.
|
|
23
|
-
Requires-Dist: simplicio-cli>=0.
|
|
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-
|
|
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-
|
|
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
|
|
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.
|
|
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.
|
|
29
|
-
"simplicio-cli>=0.
|
|
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": [
|
{simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/hooks/hooks.claude.json
RENAMED
|
@@ -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
|
-
|
|
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
|
|
370
|
-
|
|
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
|
|
532
|
-
# the agent's result was independently re-executed and
|
|
533
|
-
# promise is accepted — corrective gate per Asolaria.
|
|
534
|
-
if ((not evidence_required) or has_evidence) and watcher_pass
|
|
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
|
-
|
|
592
|
-
|
|
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:
|
{simplicio_loop-3.20.0 → simplicio_loop-3.21.0}/simplicio_loop/_bundle/scripts/cross_agent_wiki.py
RENAMED
|
@@ -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 (
|
|
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 |
|
|
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
|
|
194
|
-
|
|
195
|
-
`.orchestrator/loop/
|
|
196
|
-
|
|
197
|
-
|
|
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) —
|
|
367
|
-
re-executes the
|
|
368
|
-
with `{"match": true, "status": "MEASURED"
|
|
369
|
-
|
|
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
|
|
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
|
|
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
|
|
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 —
|