simplicio-loop 3.22.6__tar.gz → 3.23.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 (101) hide show
  1. {simplicio_loop-3.22.6/simplicio_loop.egg-info → simplicio_loop-3.23.0}/PKG-INFO +2 -2
  2. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/pyproject.toml +2 -2
  3. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/SKILL.md +27 -4
  4. {simplicio_loop-3.22.6/simplicio_loop/_bundle/skills/simplicio-tasks → simplicio_loop-3.23.0/simplicio_loop/_bundle/skills/simplicio-loop}/references/extension-points.md +5 -4
  5. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/orchestration.md +5 -1
  6. {simplicio_loop-3.22.6/simplicio_loop/_bundle/skills/simplicio-loop → simplicio_loop-3.23.0/simplicio_loop/_bundle/skills/simplicio-tasks}/references/extension-points.md +5 -4
  7. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/orchestration.md +5 -1
  8. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0/simplicio_loop.egg-info}/PKG-INFO +2 -2
  9. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop.egg-info/SOURCES.txt +1 -0
  10. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop.egg-info/requires.txt +1 -1
  11. simplicio_loop-3.23.0/tests/test_task_backlog.py +225 -0
  12. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_worker_cli_contract.py +1 -0
  13. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_worker_selftests.py +7 -0
  14. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/LICENSE +0 -0
  15. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/MANIFEST.in +0 -0
  16. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/PYPI.md +0 -0
  17. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/README.md +0 -0
  18. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/setup.cfg +0 -0
  19. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/__init__.py +0 -0
  20. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/hooks/README.md +0 -0
  21. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/hooks/action_gate.py +0 -0
  22. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/hooks/hooks.claude.json +0 -0
  23. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/hooks/hooks.json +0 -0
  24. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/hooks/loop_capture.py +0 -0
  25. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/hooks/loop_stop.py +0 -0
  26. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/hooks/orient_clamp.py +0 -0
  27. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/hooks/orient_rewrite.py +0 -0
  28. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/hooks/pre-commit.py +0 -0
  29. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/hooks/simplicio_dashboard.py +0 -0
  30. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/hooks/simplicio_watch.py +0 -0
  31. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/scripts/cross_agent_wiki.py +0 -0
  32. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/scripts/hierarchical_planner.py +0 -0
  33. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-autoresearch/SKILL.md +0 -0
  34. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-compress/SKILL.md +0 -0
  35. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-learn/SKILL.md +0 -0
  36. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/agentsview-adapter.md +0 -0
  37. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/azure-devops-adapter.md +0 -0
  38. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/bound-operators.md +0 -0
  39. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/claims-gate.md +0 -0
  40. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/cross-agent-wiki.md +0 -0
  41. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/cross-repo-integration.md +0 -0
  42. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/hierarchical-planner.md +0 -0
  43. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/lmcache-adapter.md +0 -0
  44. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/quality-safety-delivery.md +0 -0
  45. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/run-journal-stall-detector.md +0 -0
  46. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/spindle-handoff.md +0 -0
  47. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/standing-loop-247.md +0 -0
  48. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/token-capture.md +0 -0
  49. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/token-economy.md +0 -0
  50. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/triage-verify-detail.md +0 -0
  51. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/understand-anything-adapter.md +0 -0
  52. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/video-evidence.md +0 -0
  53. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-loop/references/web-evidence.md +0 -0
  54. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-orient/SKILL.md +0 -0
  55. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-review/SKILL.md +0 -0
  56. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-tasks/SKILL.md +0 -0
  57. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/agentsview-adapter.md +0 -0
  58. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/azure-devops-adapter.md +0 -0
  59. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/lmcache-adapter.md +0 -0
  60. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/quality-safety-delivery.md +0 -0
  61. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/standing-loop-247.md +0 -0
  62. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/token-capture.md +0 -0
  63. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/token-economy.md +0 -0
  64. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/understand-anything-adapter.md +0 -0
  65. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/video-evidence.md +0 -0
  66. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/skills/simplicio-tasks/references/web-evidence.md +0 -0
  67. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/tests/_selfrun.py +0 -0
  68. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/tests/test_cross_agent_wiki.py +0 -0
  69. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/_bundle/tests/test_loop_e2e.py +0 -0
  70. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop/cli.py +0 -0
  71. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop.egg-info/dependency_links.txt +0 -0
  72. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop.egg-info/entry_points.txt +0 -0
  73. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/simplicio_loop.egg-info/top_level.txt +0 -0
  74. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_action_gate.py +0 -0
  75. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_agentsview_adapter.py +0 -0
  76. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_autoresearch.py +0 -0
  77. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_az_boards_adapter.py +0 -0
  78. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_claims_audit.py +0 -0
  79. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_claims_manifest_unit.py +0 -0
  80. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_cross_agent_wiki.py +0 -0
  81. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_dashboard_hook.py +0 -0
  82. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_doctor_smoke.py +0 -0
  83. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_evidence_chain.py +0 -0
  84. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_fan_out_flow.py +0 -0
  85. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_fan_out_unit.py +0 -0
  86. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_flow_audit.py +0 -0
  87. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_hierarchical_planner.py +0 -0
  88. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_hooks_coverage.py +0 -0
  89. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_impact_audit.py +0 -0
  90. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_install_lib.py +0 -0
  91. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_learn_pipeline_removed.py +0 -0
  92. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_loop_e2e.py +0 -0
  93. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_precommit_bundle_sync.py +0 -0
  94. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_repo_conventions_architecture.py +0 -0
  95. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_schema_verify_integration.py +0 -0
  96. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_schema_verify_unit.py +0 -0
  97. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_system_check.py +0 -0
  98. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_token_budget.py +0 -0
  99. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_toon_codec.py +0 -0
  100. {simplicio_loop-3.22.6 → simplicio_loop-3.23.0}/tests/test_verify_adapters_integration.py +0 -0
  101. {simplicio_loop-3.22.6 → simplicio_loop-3.23.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.22.6
3
+ Version: 3.23.0
4
4
  Summary: The Universal Looping AI Orchestrator — a runtime-agnostic super-plugin (7 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
@@ -20,7 +20,7 @@ Requires-Python: >=3.8
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
22
  Requires-Dist: simplicio-mapper>=0.17.0
23
- Requires-Dist: simplicio-cli>=0.9.5
23
+ Requires-Dist: simplicio-cli>=0.9.6
24
24
  Provides-Extra: dev
25
25
  Requires-Dist: pytest>=7; extra == "dev"
26
26
  Provides-Extra: ml
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simplicio-loop"
7
- version = "3.22.6"
7
+ version = "3.23.0"
8
8
  description = "The Universal Looping AI Orchestrator — a runtime-agnostic super-plugin (7 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"
@@ -26,7 +26,7 @@ classifiers = [
26
26
  # simplicio-cli -> the operator that applies+verifies changes (binds `execute`/`deterministic_edit`)
27
27
  dependencies = [
28
28
  "simplicio-mapper>=0.17.0",
29
- "simplicio-cli>=0.9.5",
29
+ "simplicio-cli>=0.9.6",
30
30
  ]
31
31
 
32
32
  # Optional: the real embedding backend for `simplicio-cli semantic --ml` / `simplicio-cli rag --ml`.
@@ -158,9 +158,11 @@ detector below. It is the difference between a loop that converges and one that
158
158
  Before deciding the change, refresh the impact map (`python3 scripts/impact_audit.py audit <root>
159
159
  --file <seed> --cover <known-file> --json`, `--fail-on medium` for shared/public contracts) and,
160
160
  for cross-surface flows, the flow map (`python3 scripts/flow_audit.py audit <root> --fail-on
161
- high --json`). Journal = memory for ATTEMPTS; anchor = SCOPE; impact audit = BLAST RADIUS; flow
162
- audit = INTEGRATION. Act only on what's still genuinely open (idempotency). Full rationale +
163
- extra flags: **`references/triage-verify-detail.md`**.
161
+ high --json`). When a Phase 0 decomposition exists, also re-read the backlog (`python3
162
+ scripts/task_backlog.py status`). Journal = memory for ATTEMPTS; anchor = SCOPE; backlog =
163
+ THE DECOMPOSITION; impact audit = BLAST RADIUS; flow audit = INTEGRATION. Act only on what's
164
+ still genuinely open (idempotency). Full rationale + extra flags:
165
+ **`references/triage-verify-detail.md`**.
164
166
  3. **Work the goal** each turn against that triaged state. The model DECIDES the AC-scoped change;
165
167
  the **`simplicio-dev-cli` operator APPLIES and verifies it** — never hand-edit inside the loop.
166
168
  End EVERY iteration with a concrete verification. **After the operator passes, run the watcher
@@ -193,7 +195,7 @@ for a single goal, `drain` for a work-queue.
193
195
  | | `converge` (single hard task) | `drain` (a queue of items) |
194
196
  |---|---|---|
195
197
  | Wants | depth — keep changing strategy until ONE thing passes | breadth — clear many independent items, idempotently |
196
- | Each turn | triage `since` last turn (incremental) → one AC-scoped change → verify → watcher-gate → journal | claim next open item → implement → deliver → re-query source |
198
+ | Each turn | triage `since` last turn (incremental) → one AC-scoped change → verify → watcher-gate → journal | claim next open item → implement → deliver → re-query source (the local Phase 0 backlog is a first-class source: re-query = `task_backlog.py next`; an `empty` print counts as a dry round, `dry≥2` unchanged) |
197
199
  | **Termination** | the evidence-gated `<promise>` fires, OR the **stall detector** says STALLED and escalates (below) | the source re-query returns empty for **K consecutive rounds** (`dry≥2`) AND the working set is idle |
198
200
  | Anti-pattern it avoids | oscillation (retrying the same dead-end) | missing late-arriving work (stops too early) |
199
201
 
@@ -204,6 +206,27 @@ genuinely stuck; `drain` is done when the queue stays empty across rounds. Don't
204
206
  and don't apply `converge`'s stall-escalation to a queue (a stuck item should be quarantined, not
205
207
  halt the whole drain). `simplicio-tasks` Step 3 routes fast-path/heavy-path on top of this.
206
208
 
209
+ ## Phase 0 — intake & decomposition (no source / vague goal / genesis)
210
+
211
+ When the goal is vague and no external source (board/issues) supplies items, the LLM brainstorms
212
+ the decomposition — subtasks with ≥1 acceptance criterion each, `depends_on`, risks — and MUST
213
+ freeze it BEFORE any edit: `python3 scripts/task_backlog.py init --goal "<goal verbatim>"
214
+ --items-file plan.json` (the worker refuses an empty plan, a zero-AC item, or an unknown/cyclic
215
+ dependency — the AI decides, the worker freezes, orders and gates). State:
216
+ `.orchestrator/backlog/backlog.jsonl`.
217
+
218
+ **Genesis.** First run `python3 scripts/task_backlog.py genesis --exit-code` — exit 10 means a
219
+ repo with NO code yet. There `init` demands `--genesis` plus exactly one item tagged `scaffold`
220
+ (structure + toolchain + one minimal green test as its ACs); the worker reorders it to T1 and
221
+ makes every other item depend on it. After the scaffold item's gate passes, re-run
222
+ `simplicio-mapper scan . --json` before claiming the next item — the survey only feeds the goal
223
+ once there is something to map.
224
+
225
+ **Per-item cycle**: `python3 scripts/task_backlog.py next` claims one item (re-prints the one in
226
+ flight) and prints the ready `task_anchor.py set` arming command → work it under the normal
227
+ converge contract → `python3 scripts/task_backlog.py done --id T1` (exit 12 unless the ARMED
228
+ anchor is this item with every AC verified) → `next` again; exactly `empty` = drained.
229
+
207
230
  ## HRM-style hierarchical planner (two-level reasoning loop)
208
231
 
209
232
  Inspired by the **Hierarchical Reasoning Model** (arXiv:2506.21734, JesseBrown1980/HRM), the loop
@@ -12,7 +12,7 @@ fallback with standard tools. The skill depends on the ABSTRACTION, never on a s
12
12
  | `normalize` | Work-item → canonical schema | LLM maps fields by hand |
13
13
  | `deterministic_edit` | Mechanical file writer (zero-token apply of a decided change) | LLM applies edit with file tool |
14
14
  | `autoscale` | Safe fleet size from machine profile | formula in orchestration.md |
15
- | `plan` / `decide` | Plan / decision support | LLM reasons it out |
15
+ | `plan` / `decide` | Plan / decision support. Runnable freeze: a vague goal's LLM decomposition (subtasks, per-item ACs, `depends_on`) is frozen, ordered and gated by `scripts/task_backlog.py init` — genesis-aware (an empty repo demands `--genesis` and a leading `scaffold` item) | LLM reasons it out |
16
16
  | `execute` | Local agent fan-out for mass/mechanical work | LLM does it or spawns host sub-agents |
17
17
  | `issue_factory` | Full orchestrator loop: discover→claim→implement→PR | manual pipeline (Steps 2–6) |
18
18
  | `claim` | Atomic claim on a work-item (cross-session safe) | `gh label "in-progress"` + lockfile |
@@ -31,10 +31,10 @@ fallback with standard tools. The skill depends on the ABSTRACTION, never on a s
31
31
  | `retry` | Classified retry+backoff by failure class | manual retry loop |
32
32
  | `status` | Live observability dashboard | `gh` queries |
33
33
  | `security` | Supply-chain / secret scan | `rg` for secrets |
34
- | `intake` | Ingest work from sprint/board link | `gh issue list` |
35
- | `dependency_graph` | Inter-item ordering as a resumable DAG (B after A; independents fan out); re-run skips done nodes. At code level, it also means "what else breaks if I touch this?" — runnable form: `scripts/impact_audit.py audit <root> --file <seed> --cover <planned-file> --json > .orchestrator/impact-audit.json` maps local dependencies, reverse dependents, and related tests for the planned task surface. `high` gaps block missing callers/dependents; `--fail-on medium` blocks uncovered local deps/tests for shared contracts. | LLM topo-sorts by depends-on/blocked-by, runs ready first, journals done node-ids to resume; for code impact, it uses `rg`/imports/git grep to enumerate local dependencies, reverse callers, and related tests, then widens the plan before editing |
34
+ | `intake` | Ingest work from sprint/board link — or, with no board, the local backlog (`task_backlog.py next`) | `gh issue list` |
35
+ | `dependency_graph` | Inter-item ordering as a resumable DAG (B after A; independents fan out); re-run skips done nodes (at item level, the frozen backlog's `depends_on` — `scripts/task_backlog.py` — IS this DAG for Phase 0 decompositions). At code level, it also means "what else breaks if I touch this?" — runnable form: `scripts/impact_audit.py audit <root> --file <seed> --cover <planned-file> --json > .orchestrator/impact-audit.json` maps local dependencies, reverse dependents, and related tests for the planned task surface. `high` gaps block missing callers/dependents; `--fail-on medium` blocks uncovered local deps/tests for shared contracts. | LLM topo-sorts by depends-on/blocked-by, runs ready first, journals done node-ids to resume; for code impact, it uses `rg`/imports/git grep to enumerate local dependencies, reverse callers, and related tests, then widens the plan before editing |
36
36
  | `durable_workflow` | Per-item pipeline (intake→plan→edit→validate→deliver) as a resumable phase state-machine; retry skips done phases | LLM drives phases, journals which phase each item reached, resumes from last completed |
37
- | `work_queue` | Durable priority queue that runs+auto-retries+requeues-stuck, with a write-serialization lock for shared checkouts | LLM keeps queue in JSONL/SQLite, pops by priority, re-enqueues on fail, lockfile+TTL guards shared-tree writes |
37
+ | `work_queue` | Durable priority queue that runs+auto-retries+requeues-stuck, with a write-serialization lock for shared checkouts. Local binding: `scripts/task_backlog.py` — `next` honors `depends_on` and re-prints the one claimed item; `done` is exit-12-gated on the verified task anchor; `skip` quarantines with a reason; an exact `empty` print is the drain dry signal | LLM keeps queue in JSONL/SQLite, pops by priority, re-enqueues on fail, lockfile+TTL guards shared-tree writes |
38
38
  | `resource_governor` | Dynamic mid-loop throttle: decide when to back off + machine-tier ceilings before scaling a wave | LLM re-probes CPU/RAM/load each tick, reduces fleet / sleeps longer under load, degrades tiers |
39
39
  | `delivery_gate` | One DoD gate: AC check + run-verification + regression guard + diff self-review + delivery certificate. Runnable form: the **task anchor** (`scripts/task_anchor.py`) freezes the ACs at intake, `check` flags goal-drift each turn (anti-deviation), `mark` records a per-AC receipt, and `gate` (exit 12) BLOCKS "done"/PR-open while any AC is unverified. | LLM walks the AC checklist, runs affected tests, reviews own diff, writes a certificate into the receipt |
40
40
  | `action_gate` | Risk-classify every mutation (safe/auto/ask) vs allow/deny + hardline blocklist before it runs | LLM pattern-matches action vs irreversible-op list, secret-scans, proceeds/auto-runs/escalates to `human_gate` |
@@ -92,6 +92,7 @@ state file" bug class (the 685600b spindle/latch mismatch; the HANDOFF.md three-
92
92
  | `anchor.json` | `scripts/task_anchor.py set`/`mark` (agent-invoked, once per item + per AC) | `hooks/loop_stop.py` (`anchor_pending()`, drift/AC gate), `scripts/watcher_verify.py` (independent recompute) | Frozen at intake; re-`set` with an unchanged goal preserves progress, a changed goal requires `--force`. |
93
93
  | `journal.jsonl` | `scripts/loop_journal.py record` (agent-invoked); `hooks/loop_stop.py` (`auto_record_journal`, a minimal fallback record only when the agent didn't record one this turn) | `scripts/hierarchical_planner.py` (phase timing/stall), `hooks/loop_stop.py` (journal tail in `write_handoff`) | Append-only. The fallback record uses `gate: blocked` (never `fail`) so it can never itself trigger stall detection — it is a presence signal, not attempt memory. |
94
94
  | `phase.json` | `scripts/hierarchical_planner.py plan` | `hooks/loop_stop.py` (re-feed header hint) | Re-planned every `DEFAULT_PLAN_INTERVAL` iterations or on stall. |
95
+ | `backlog.jsonl` (`.orchestrator/backlog/`) | `scripts/task_backlog.py` (`init` freezes; `next`/`done`/`skip` transition item states — agent-invoked) | the Phase 0 per-item cycle (SKILL.md § Phase 0), drain-mode dry detection (an exact `empty` from `next`) | Frozen at Phase 0 intake; re-`init` with the same master goal preserves per-item progress, a changed goal requires `--force`; `done` only closes an item whose armed anchor gate passed. |
95
96
 
96
97
  If a machine has no `.claude/skills/simplicio-loop/SKILL.md` (a bare `simplicio-tasks` loop with
97
98
  no `simplicio-loop` companion), none of the watcher/anchor/journal producers are expected to run —
@@ -11,6 +11,7 @@ and authed, then use it. Never claim a source works without a live connector.
11
11
  | Trello / Azure DevOps | host connector, else the `az boards` adapter (`scripts/az_boards_adapter.py`, see `azure-devops-adapter.md`) |
12
12
  | agentsview sessions | `scripts/agentsview_adapter.py` (see `agentsview-adapter.md`) | session observability, recovery of stalled sessions |
13
13
  | local files / CI queue | filesystem / CI API |
14
+ | vague goal / no reachable board | the LOCAL BACKLOG — `scripts/task_backlog.py` (the frozen LLM decomposition, `.orchestrator/backlog/`; SKILL.md § Phase 0) |
14
15
 
15
16
  If the target source has no reachable adapter, STOP and report it as a blocker (do not silently
16
17
  fall back to GitHub). Each adapter exposes: list_ready (metadata-only), get_details, claim,
@@ -30,7 +31,10 @@ Triage is metadata-only; implementation is NOT. An agent that skips this produce
30
31
  assignees, milestone, acceptance_criteria, comments, linked_prs, linked_items.
31
32
  - Extract explicit **acceptance criteria** (numbered, checklists, "done when…"). If none stated,
32
33
  derive + record them. An item that obviously should have ACs but has none is a BLOCKER — ask
33
- ONE line, don't guess.
34
+ ONE line, don't guess. With a VAGUE goal and no source at all, decompose first (brainstorm the
35
+ subtasks + per-item ACs + `depends_on`) and freeze the plan with `python3
36
+ scripts/task_backlog.py init --goal "<goal>" --items-file plan.json` (SKILL.md § Phase 0); a
37
+ GENESIS repo (`task_backlog.py genesis --exit-code` exits 10) leads with the `scaffold` item.
34
38
  - Extract design decisions/constraints/rejections from comments ("don't use X", "must integrate
35
39
  with Y", reviewer requests) — these override naive title reading.
36
40
  - Note linked items/PRs and check status — a blocked dependency is flagged, not ignored.
@@ -12,7 +12,7 @@ fallback with standard tools. The skill depends on the ABSTRACTION, never on a s
12
12
  | `normalize` | Work-item → canonical schema | LLM maps fields by hand |
13
13
  | `deterministic_edit` | Mechanical file writer (zero-token apply of a decided change) | LLM applies edit with file tool |
14
14
  | `autoscale` | Safe fleet size from machine profile | formula in orchestration.md |
15
- | `plan` / `decide` | Plan / decision support | LLM reasons it out |
15
+ | `plan` / `decide` | Plan / decision support. Runnable freeze: a vague goal's LLM decomposition (subtasks, per-item ACs, `depends_on`) is frozen, ordered and gated by `scripts/task_backlog.py init` — genesis-aware (an empty repo demands `--genesis` and a leading `scaffold` item) | LLM reasons it out |
16
16
  | `execute` | Local agent fan-out for mass/mechanical work | LLM does it or spawns host sub-agents |
17
17
  | `issue_factory` | Full orchestrator loop: discover→claim→implement→PR | manual pipeline (Steps 2–6) |
18
18
  | `claim` | Atomic claim on a work-item (cross-session safe) | `gh label "in-progress"` + lockfile |
@@ -31,10 +31,10 @@ fallback with standard tools. The skill depends on the ABSTRACTION, never on a s
31
31
  | `retry` | Classified retry+backoff by failure class | manual retry loop |
32
32
  | `status` | Live observability dashboard | `gh` queries |
33
33
  | `security` | Supply-chain / secret scan | `rg` for secrets |
34
- | `intake` | Ingest work from sprint/board link | `gh issue list` |
35
- | `dependency_graph` | Inter-item ordering as a resumable DAG (B after A; independents fan out); re-run skips done nodes. At code level, it also means "what else breaks if I touch this?" — runnable form: `scripts/impact_audit.py audit <root> --file <seed> --cover <planned-file> --json > .orchestrator/impact-audit.json` maps local dependencies, reverse dependents, and related tests for the planned task surface. `high` gaps block missing callers/dependents; `--fail-on medium` blocks uncovered local deps/tests for shared contracts. | LLM topo-sorts by depends-on/blocked-by, runs ready first, journals done node-ids to resume; for code impact, it uses `rg`/imports/git grep to enumerate local dependencies, reverse callers, and related tests, then widens the plan before editing |
34
+ | `intake` | Ingest work from sprint/board link — or, with no board, the local backlog (`task_backlog.py next`) | `gh issue list` |
35
+ | `dependency_graph` | Inter-item ordering as a resumable DAG (B after A; independents fan out); re-run skips done nodes (at item level, the frozen backlog's `depends_on` — `scripts/task_backlog.py` — IS this DAG for Phase 0 decompositions). At code level, it also means "what else breaks if I touch this?" — runnable form: `scripts/impact_audit.py audit <root> --file <seed> --cover <planned-file> --json > .orchestrator/impact-audit.json` maps local dependencies, reverse dependents, and related tests for the planned task surface. `high` gaps block missing callers/dependents; `--fail-on medium` blocks uncovered local deps/tests for shared contracts. | LLM topo-sorts by depends-on/blocked-by, runs ready first, journals done node-ids to resume; for code impact, it uses `rg`/imports/git grep to enumerate local dependencies, reverse callers, and related tests, then widens the plan before editing |
36
36
  | `durable_workflow` | Per-item pipeline (intake→plan→edit→validate→deliver) as a resumable phase state-machine; retry skips done phases | LLM drives phases, journals which phase each item reached, resumes from last completed |
37
- | `work_queue` | Durable priority queue that runs+auto-retries+requeues-stuck, with a write-serialization lock for shared checkouts | LLM keeps queue in JSONL/SQLite, pops by priority, re-enqueues on fail, lockfile+TTL guards shared-tree writes |
37
+ | `work_queue` | Durable priority queue that runs+auto-retries+requeues-stuck, with a write-serialization lock for shared checkouts. Local binding: `scripts/task_backlog.py` — `next` honors `depends_on` and re-prints the one claimed item; `done` is exit-12-gated on the verified task anchor; `skip` quarantines with a reason; an exact `empty` print is the drain dry signal | LLM keeps queue in JSONL/SQLite, pops by priority, re-enqueues on fail, lockfile+TTL guards shared-tree writes |
38
38
  | `resource_governor` | Dynamic mid-loop throttle: decide when to back off + machine-tier ceilings before scaling a wave | LLM re-probes CPU/RAM/load each tick, reduces fleet / sleeps longer under load, degrades tiers |
39
39
  | `delivery_gate` | One DoD gate: AC check + run-verification + regression guard + diff self-review + delivery certificate. Runnable form: the **task anchor** (`scripts/task_anchor.py`) freezes the ACs at intake, `check` flags goal-drift each turn (anti-deviation), `mark` records a per-AC receipt, and `gate` (exit 12) BLOCKS "done"/PR-open while any AC is unverified. | LLM walks the AC checklist, runs affected tests, reviews own diff, writes a certificate into the receipt |
40
40
  | `action_gate` | Risk-classify every mutation (safe/auto/ask) vs allow/deny + hardline blocklist before it runs | LLM pattern-matches action vs irreversible-op list, secret-scans, proceeds/auto-runs/escalates to `human_gate` |
@@ -92,6 +92,7 @@ state file" bug class (the 685600b spindle/latch mismatch; the HANDOFF.md three-
92
92
  | `anchor.json` | `scripts/task_anchor.py set`/`mark` (agent-invoked, once per item + per AC) | `hooks/loop_stop.py` (`anchor_pending()`, drift/AC gate), `scripts/watcher_verify.py` (independent recompute) | Frozen at intake; re-`set` with an unchanged goal preserves progress, a changed goal requires `--force`. |
93
93
  | `journal.jsonl` | `scripts/loop_journal.py record` (agent-invoked); `hooks/loop_stop.py` (`auto_record_journal`, a minimal fallback record only when the agent didn't record one this turn) | `scripts/hierarchical_planner.py` (phase timing/stall), `hooks/loop_stop.py` (journal tail in `write_handoff`) | Append-only. The fallback record uses `gate: blocked` (never `fail`) so it can never itself trigger stall detection — it is a presence signal, not attempt memory. |
94
94
  | `phase.json` | `scripts/hierarchical_planner.py plan` | `hooks/loop_stop.py` (re-feed header hint) | Re-planned every `DEFAULT_PLAN_INTERVAL` iterations or on stall. |
95
+ | `backlog.jsonl` (`.orchestrator/backlog/`) | `scripts/task_backlog.py` (`init` freezes; `next`/`done`/`skip` transition item states — agent-invoked) | the Phase 0 per-item cycle (SKILL.md § Phase 0), drain-mode dry detection (an exact `empty` from `next`) | Frozen at Phase 0 intake; re-`init` with the same master goal preserves per-item progress, a changed goal requires `--force`; `done` only closes an item whose armed anchor gate passed. |
95
96
 
96
97
  If a machine has no `.claude/skills/simplicio-loop/SKILL.md` (a bare `simplicio-tasks` loop with
97
98
  no `simplicio-loop` companion), none of the watcher/anchor/journal producers are expected to run —
@@ -11,6 +11,7 @@ and authed, then use it. Never claim a source works without a live connector.
11
11
  | Trello / Azure DevOps | host connector, else the `az boards` adapter (`scripts/az_boards_adapter.py`, see `azure-devops-adapter.md`) |
12
12
  | agentsview sessions | `scripts/agentsview_adapter.py` (see `agentsview-adapter.md`) | session observability, recovery of stalled sessions |
13
13
  | local files / CI queue | filesystem / CI API |
14
+ | vague goal / no reachable board | the LOCAL BACKLOG — `scripts/task_backlog.py` (the frozen LLM decomposition, `.orchestrator/backlog/`; SKILL.md § Phase 0) |
14
15
 
15
16
  If the target source has no reachable adapter, STOP and report it as a blocker (do not silently
16
17
  fall back to GitHub). Each adapter exposes: list_ready (metadata-only), get_details, claim,
@@ -30,7 +31,10 @@ Triage is metadata-only; implementation is NOT. An agent that skips this produce
30
31
  assignees, milestone, acceptance_criteria, comments, linked_prs, linked_items.
31
32
  - Extract explicit **acceptance criteria** (numbered, checklists, "done when…"). If none stated,
32
33
  derive + record them. An item that obviously should have ACs but has none is a BLOCKER — ask
33
- ONE line, don't guess.
34
+ ONE line, don't guess. With a VAGUE goal and no source at all, decompose first (brainstorm the
35
+ subtasks + per-item ACs + `depends_on`) and freeze the plan with `python3
36
+ scripts/task_backlog.py init --goal "<goal>" --items-file plan.json` (SKILL.md § Phase 0); a
37
+ GENESIS repo (`task_backlog.py genesis --exit-code` exits 10) leads with the `scaffold` item.
34
38
  - Extract design decisions/constraints/rejections from comments ("don't use X", "must integrate
35
39
  with Y", reviewer requests) — these override naive title reading.
36
40
  - Note linked items/PRs and check status — a blocked dependency is flagged, not ignored.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simplicio-loop
3
- Version: 3.22.6
3
+ Version: 3.23.0
4
4
  Summary: The Universal Looping AI Orchestrator — a runtime-agnostic super-plugin (7 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
@@ -20,7 +20,7 @@ Requires-Python: >=3.8
20
20
  Description-Content-Type: text/markdown
21
21
  License-File: LICENSE
22
22
  Requires-Dist: simplicio-mapper>=0.17.0
23
- Requires-Dist: simplicio-cli>=0.9.5
23
+ Requires-Dist: simplicio-cli>=0.9.6
24
24
  Provides-Extra: dev
25
25
  Requires-Dist: pytest>=7; extra == "dev"
26
26
  Provides-Extra: ml
@@ -90,6 +90,7 @@ tests/test_repo_conventions_architecture.py
90
90
  tests/test_schema_verify_integration.py
91
91
  tests/test_schema_verify_unit.py
92
92
  tests/test_system_check.py
93
+ tests/test_task_backlog.py
93
94
  tests/test_token_budget.py
94
95
  tests/test_toon_codec.py
95
96
  tests/test_verify_adapters_integration.py
@@ -1,5 +1,5 @@
1
1
  simplicio-mapper>=0.17.0
2
- simplicio-cli>=0.9.5
2
+ simplicio-cli>=0.9.6
3
3
 
4
4
  [dev]
5
5
  pytest>=7
@@ -0,0 +1,225 @@
1
+ """End-to-end tests for `scripts/task_backlog.py` — the frozen multi-item decomposition ABOVE the
2
+ per-item task anchor (Phase 0):
3
+
4
+ * `init` is fail-closed: no items / zero-AC item refused; a GENESIS (no-code) repo refuses a
5
+ plan without `--genesis`, and with it forces the scaffold item to lead.
6
+ * `genesis --exit-code` is the deterministic greenfield detector (exit 10).
7
+ * `next` honors `depends_on`, re-prints the claimed item (one in flight), and prints exactly
8
+ `empty` when everything drained (the drain-mode dry signal).
9
+ * `done` is GATED on the real anchor: it refuses (exit 12) with no anchor, with another item's
10
+ anchor, or with pending ACs — and passes only after a real `task_anchor.py set` + `mark`.
11
+
12
+ Every test isolates state via SIMPLICIO_BACKLOG_FILE / SIMPLICIO_ANCHOR_FILE into a tmp dir and
13
+ pins the genesis detector with --root.
14
+ """
15
+ import json
16
+ import os
17
+ import subprocess
18
+ import sys
19
+
20
+ REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
21
+
22
+
23
+ def _env(tmp_path):
24
+ return dict(os.environ,
25
+ SIMPLICIO_BACKLOG_FILE=str(tmp_path / "backlog.jsonl"),
26
+ SIMPLICIO_ANCHOR_FILE=str(tmp_path / "anchor.json"))
27
+
28
+
29
+ def _run(script, args, env):
30
+ return subprocess.run([sys.executable, os.path.join(REPO, "scripts", script)] + args,
31
+ capture_output=True, text=True, cwd=REPO, env=env)
32
+
33
+
34
+ def _backlog(args, env):
35
+ return _run("task_backlog.py", args, env)
36
+
37
+
38
+ def _anchor(args, env):
39
+ return _run("task_anchor.py", args, env)
40
+
41
+
42
+ def _plan(tmp_path, items, name="plan.json"):
43
+ p = tmp_path / name
44
+ p.write_text(json.dumps(items), encoding="utf-8")
45
+ return str(p)
46
+
47
+
48
+ def _code_root(tmp_path):
49
+ """A --root that is NOT genesis (has one code file)."""
50
+ root = tmp_path / "coderoot"
51
+ root.mkdir(exist_ok=True)
52
+ (root / "app.py").write_text("print('x')\n", encoding="utf-8")
53
+ return str(root)
54
+
55
+
56
+ def _drive_anchor_done(goal, acs, env):
57
+ """Arm the real anchor for one item and verify every AC — the flow `next` prescribes."""
58
+ s = _anchor(["set", "--goal", goal, "--force"] + sum([["--ac", a] for a in acs], []), env)
59
+ assert s.returncode == 0, s.stdout + s.stderr
60
+ for i in range(len(acs)):
61
+ m = _anchor(["mark", "--id", "AC%d" % (i + 1), "--status", "done",
62
+ "--evidence", "receipt-%d" % (i + 1)], env)
63
+ assert m.returncode == 0, m.stdout + m.stderr
64
+
65
+
66
+ def test_init_refuses_zero_ac_and_empty(tmp_path):
67
+ env = _env(tmp_path)
68
+ root = _code_root(tmp_path)
69
+ r = _backlog(["init", "--goal", "g", "--root", root,
70
+ "--items-file", _plan(tmp_path, [])], env)
71
+ assert r.returncode == 2, "empty plan must refuse (2), got %d:\n%s" % (r.returncode, r.stdout)
72
+ r = _backlog(["init", "--goal", "g", "--root", root,
73
+ "--items-file", _plan(tmp_path, [{"goal": "x", "acs": []}])], env)
74
+ assert r.returncode == 2, "zero-AC item must refuse (2), got %d:\n%s" % (r.returncode, r.stdout)
75
+ assert "acceptance criteria" in r.stdout, r.stdout
76
+
77
+
78
+ def test_genesis_enforcement_and_scaffold_reorder(tmp_path):
79
+ env = _env(tmp_path)
80
+ empty = tmp_path / "empty"
81
+ empty.mkdir()
82
+ plan = [{"goal": "add the endpoint", "acs": ["endpoint responds"]},
83
+ {"goal": "bootstrap the project", "acs": ["structure exists", "one test green"],
84
+ "tags": ["scaffold"]},
85
+ {"goal": "add the docs page", "acs": ["page renders"]}]
86
+ # a genesis repo without --genesis is BLOCKED (12)
87
+ r = _backlog(["init", "--goal", "greenfield app", "--root", str(empty),
88
+ "--items-file", _plan(tmp_path, plan)], env)
89
+ assert r.returncode == 12, "expected BLOCKED 12, got %d:\n%s" % (r.returncode, r.stdout)
90
+ assert "genesis" in r.stdout.lower(), r.stdout
91
+ # --genesis without a scaffold-tagged item refuses (2)
92
+ r = _backlog(["init", "--goal", "greenfield app", "--genesis", "--root", str(empty),
93
+ "--items-file", _plan(tmp_path, [{"goal": "x", "acs": ["a"]}])], env)
94
+ assert r.returncode == 2, r.stdout
95
+ assert "scaffold" in r.stdout, r.stdout
96
+ # --genesis with the scaffold given NOT first: freezes AND auto-orders it to T1
97
+ r = _backlog(["init", "--goal", "greenfield app", "--genesis", "--root", str(empty),
98
+ "--items-file", _plan(tmp_path, plan)], env)
99
+ assert r.returncode == 0, r.stdout + r.stderr
100
+ n = _backlog(["next", "--json"], env)
101
+ assert n.returncode == 0, n.stdout + n.stderr
102
+ got = json.loads(n.stdout)
103
+ assert got["id"] == "T1" and got["goal"] == "bootstrap the project", n.stdout
104
+ assert "task_anchor.py set" in got["arm"], n.stdout
105
+
106
+
107
+ def test_genesis_exit_code(tmp_path):
108
+ env = _env(tmp_path)
109
+ empty = tmp_path / "empty"
110
+ empty.mkdir()
111
+ r = _backlog(["genesis", "--root", str(empty), "--exit-code"], env)
112
+ assert r.returncode == 10, "expected genesis exit 10, got %d:\n%s" % (r.returncode, r.stdout)
113
+ assert "genesis" in r.stdout, r.stdout
114
+ (empty / "main.py").write_text("pass\n", encoding="utf-8")
115
+ r = _backlog(["genesis", "--root", str(empty), "--exit-code"], env)
116
+ assert r.returncode == 0, "one .py must clear genesis, got %d:\n%s" % (r.returncode, r.stdout)
117
+ assert "code" in r.stdout, r.stdout
118
+
119
+
120
+ def test_next_honors_depends_on_and_reprints_claimed(tmp_path):
121
+ env = _env(tmp_path)
122
+ plan = [{"id": "T1", "goal": "first thing", "acs": ["a1"]},
123
+ {"id": "T2", "goal": "second thing", "acs": ["a2"], "depends_on": ["T1"]}]
124
+ r = _backlog(["init", "--goal", "two steps", "--root", _code_root(tmp_path),
125
+ "--items-file", _plan(tmp_path, plan)], env)
126
+ assert r.returncode == 0, r.stdout + r.stderr
127
+ first = json.loads(_backlog(["next", "--json"], env).stdout)
128
+ assert first["id"] == "T1", first
129
+ again = json.loads(_backlog(["next", "--json"], env).stdout)
130
+ assert again["id"] == "T1" and again["verdict"] == "claimed", again # one in flight
131
+ _drive_anchor_done("first thing", ["a1"], env)
132
+ d = _backlog(["done", "--id", "T1"], env)
133
+ assert d.returncode == 0, d.stdout + d.stderr
134
+ nxt = json.loads(_backlog(["next", "--json"], env).stdout)
135
+ assert nxt["id"] == "T2", "depends_on not honored:\n%s" % nxt
136
+
137
+
138
+ def test_done_is_gated_on_the_real_anchor(tmp_path):
139
+ env = _env(tmp_path)
140
+ plan = [{"goal": "item one", "acs": ["ac one", "ac two"]},
141
+ {"goal": "item two", "acs": ["other ac"]}]
142
+ r = _backlog(["init", "--goal", "gated", "--root", _code_root(tmp_path),
143
+ "--items-file", _plan(tmp_path, plan)], env)
144
+ assert r.returncode == 0, r.stdout + r.stderr
145
+ # no anchor armed at all -> BLOCKED 12
146
+ d = _backlog(["done", "--id", "T1"], env)
147
+ assert d.returncode == 12, "no-anchor done must block (12), got %d:\n%s" % (d.returncode, d.stdout)
148
+ assert "blocked" in d.stdout.lower(), d.stdout
149
+ # anchor armed for ANOTHER item's goal -> BLOCKED 12 (wrong fingerprint)
150
+ _drive_anchor_done("item two", ["other ac"], env)
151
+ d = _backlog(["done", "--id", "T1"], env)
152
+ assert d.returncode == 12, "wrong-fp done must block (12), got %d:\n%s" % (d.returncode, d.stdout)
153
+ # anchor armed for THIS item but ACs still pending -> BLOCKED 12
154
+ s = _anchor(["set", "--goal", "item one", "--force",
155
+ "--ac", "ac one", "--ac", "ac two"], env)
156
+ assert s.returncode == 0, s.stdout + s.stderr
157
+ d = _backlog(["done", "--id", "T1"], env)
158
+ assert d.returncode == 12, "pending-AC done must block (12), got %d:\n%s" % (d.returncode, d.stdout)
159
+ # every AC verified on the real anchor -> done, evidence copied
160
+ _drive_anchor_done("item one", ["ac one", "ac two"], env)
161
+ d = _backlog(["done", "--id", "T1"], env)
162
+ assert d.returncode == 0, d.stdout + d.stderr
163
+ st = _backlog(["status"], env)
164
+ assert "[done ] T1" in st.stdout, st.stdout
165
+
166
+
167
+ def test_drain_dry_prints_exactly_empty(tmp_path):
168
+ env = _env(tmp_path)
169
+ plan = [{"goal": "only item", "acs": ["one ac"]},
170
+ {"goal": "doomed item", "acs": ["never"]}]
171
+ r = _backlog(["init", "--goal", "drain me", "--root", _code_root(tmp_path),
172
+ "--items-file", _plan(tmp_path, plan)], env)
173
+ assert r.returncode == 0, r.stdout + r.stderr
174
+ _backlog(["next"], env)
175
+ _drive_anchor_done("only item", ["one ac"], env)
176
+ assert _backlog(["done", "--id", "T1"], env).returncode == 0
177
+ sk = _backlog(["skip", "--id", "T2"], env)
178
+ assert sk.returncode == 2, "skip without --reason must refuse (2):\n%s" % sk.stdout
179
+ assert _backlog(["skip", "--id", "T2", "--reason", "out of scope"], env).returncode == 0
180
+ n = _backlog(["next"], env)
181
+ assert n.returncode == 0, n.stdout + n.stderr
182
+ assert n.stdout.strip() == "empty", "dry signal must be exactly 'empty':\n%r" % n.stdout
183
+
184
+
185
+ def test_check_drift_and_idempotent_reinit(tmp_path):
186
+ env = _env(tmp_path)
187
+ plan = [{"goal": "stable item", "acs": ["an ac"]}]
188
+ r = _backlog(["init", "--goal", "the frozen goal", "--root", _code_root(tmp_path),
189
+ "--items-file", _plan(tmp_path, plan)], env)
190
+ assert r.returncode == 0, r.stdout + r.stderr
191
+ d = _backlog(["check", "--goal", "a totally different goal", "--exit-code"], env)
192
+ assert d.returncode == 11, "expected DRIFT 11, got %d:\n%s" % (d.returncode, d.stdout)
193
+ assert "DRIFT" in d.stdout, d.stdout
194
+ ok = _backlog(["check", "--goal", "the frozen goal", "--exit-code"], env)
195
+ assert ok.returncode == 0, ok.stdout
196
+ assert "BACKLOG_OK" in ok.stdout, ok.stdout
197
+ # progress, then re-init the SAME goal: idempotent, done state preserved
198
+ _backlog(["next"], env)
199
+ _drive_anchor_done("stable item", ["an ac"], env)
200
+ assert _backlog(["done", "--id", "T1"], env).returncode == 0
201
+ r = _backlog(["init", "--goal", "the frozen goal", "--root", _code_root(tmp_path),
202
+ "--items-file", _plan(tmp_path, plan)], env)
203
+ assert r.returncode == 0, r.stdout + r.stderr
204
+ st = _backlog(["status"], env)
205
+ assert "[done ] T1" in st.stdout, "re-init lost progress:\n%s" % st.stdout
206
+ # a CHANGED goal without --force is BLOCKED 12
207
+ r = _backlog(["init", "--goal", "a swapped goal", "--root", _code_root(tmp_path),
208
+ "--items-file", _plan(tmp_path, plan)], env)
209
+ assert r.returncode == 12, "goal swap without --force must block (12):\n%s" % r.stdout
210
+
211
+
212
+ def test_selftest_and_unknown_verb(tmp_path):
213
+ env = _env(tmp_path)
214
+ r = _backlog(["selftest"], env)
215
+ assert r.returncode == 0, r.stdout + r.stderr
216
+ assert "PASS" in r.stdout, r.stdout
217
+ r = _backlog(["definitely-not-a-real-verb-xyz"], env)
218
+ assert r.returncode == 2, r.stdout
219
+ assert "Traceback" not in r.stderr, "unknown verb crashed:\n%s" % r.stderr
220
+
221
+
222
+ if __name__ == "__main__":
223
+ sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
224
+ from _selfrun import run_module
225
+ run_module(globals(), "test_task_backlog")
@@ -19,6 +19,7 @@ WORKERS_WITH_SELFTEST = [
19
19
  "loop_journal.py",
20
20
  "billing_aggregator.py",
21
21
  "task_anchor.py",
22
+ "task_backlog.py",
22
23
  "pr_evidence.py",
23
24
  "flow_audit.py",
24
25
  "impact_audit.py",
@@ -16,6 +16,7 @@ SELFTESTS = [
16
16
  ("scripts/billing_aggregator.py", "selftest"),
17
17
  ("scripts/savings_harness.py", "selftest"),
18
18
  ("scripts/task_anchor.py", "selftest"),
19
+ ("scripts/task_backlog.py", "selftest"),
19
20
  ("scripts/pr_evidence.py", "selftest"),
20
21
  ("scripts/flow_audit.py", "selftest"),
21
22
  ("scripts/impact_audit.py", "selftest"),
@@ -54,6 +55,12 @@ def test_task_anchor_selftest():
54
55
  assert "PASS" in r.stdout, r.stdout
55
56
 
56
57
 
58
+ def test_task_backlog_selftest():
59
+ r = _run("scripts/task_backlog.py", "selftest")
60
+ assert r.returncode == 0, "task_backlog selftest failed:\n%s%s" % (r.stdout, r.stderr)
61
+ assert "PASS" in r.stdout, r.stdout
62
+
63
+
57
64
  def test_pr_evidence_selftest():
58
65
  r = _run("scripts/pr_evidence.py", "selftest")
59
66
  assert r.returncode == 0, "pr_evidence selftest failed:\n%s%s" % (r.stdout, r.stderr)
File without changes
File without changes