shikiru-cli 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (319) hide show
  1. shikiru/__init__.py +1 -0
  2. shikiru/__main__.py +8 -0
  3. shikiru/agents/__init__.py +3 -0
  4. shikiru/agents/contracts.py +590 -0
  5. shikiru/agents/registry.py +87 -0
  6. shikiru/agents/sdk/__init__.py +17 -0
  7. shikiru/agents/sdk/reset_time.py +137 -0
  8. shikiru/agents/sdk/turn.py +335 -0
  9. shikiru/agents/settings.py +151 -0
  10. shikiru/agents/testing/__init__.py +26 -0
  11. shikiru/agents/testing/contract_suite.py +653 -0
  12. shikiru/agents/testing/resume.py +15 -0
  13. shikiru/bootstrap.py +1277 -0
  14. shikiru/cli/__init__.py +1 -0
  15. shikiru/cli/app.py +770 -0
  16. shikiru/cli/context.py +63 -0
  17. shikiru/cli/render.py +1475 -0
  18. shikiru/cli/report.py +156 -0
  19. shikiru/cli/session_prompt.py +63 -0
  20. shikiru/cli/view.py +131 -0
  21. shikiru/clock.py +41 -0
  22. shikiru/config/__init__.py +8 -0
  23. shikiru/config/defaults.toml +131 -0
  24. shikiru/config/loader.py +178 -0
  25. shikiru/config/settings.py +392 -0
  26. shikiru/dashboard/__init__.py +3 -0
  27. shikiru/dashboard/actions_list.py +799 -0
  28. shikiru/dashboard/activity.py +150 -0
  29. shikiru/dashboard/app.py +1765 -0
  30. shikiru/dashboard/blocker_evidence.py +122 -0
  31. shikiru/dashboard/blocker_rows.py +84 -0
  32. shikiru/dashboard/briefing_view.py +871 -0
  33. shikiru/dashboard/contract.py +1086 -0
  34. shikiru/dashboard/covered.py +32 -0
  35. shikiru/dashboard/detail.py +1209 -0
  36. shikiru/dashboard/detail_fold.py +244 -0
  37. shikiru/dashboard/detail_hint.py +68 -0
  38. shikiru/dashboard/details_list_view.py +150 -0
  39. shikiru/dashboard/diagnostics.py +80 -0
  40. shikiru/dashboard/diagnostics_sections.py +186 -0
  41. shikiru/dashboard/evidence_table.py +123 -0
  42. shikiru/dashboard/extra_details.py +214 -0
  43. shikiru/dashboard/fault_guidance.py +216 -0
  44. shikiru/dashboard/fold_bodies.py +276 -0
  45. shikiru/dashboard/help_keys.py +166 -0
  46. shikiru/dashboard/help_screen.py +204 -0
  47. shikiru/dashboard/interaction.py +17 -0
  48. shikiru/dashboard/ladder_editor.py +544 -0
  49. shikiru/dashboard/live_settings.py +62 -0
  50. shikiru/dashboard/motion.py +368 -0
  51. shikiru/dashboard/orchestration_fields.py +201 -0
  52. shikiru/dashboard/palette.py +1606 -0
  53. shikiru/dashboard/palette_app.py +89 -0
  54. shikiru/dashboard/palette_drafts.py +33 -0
  55. shikiru/dashboard/resolution.py +573 -0
  56. shikiru/dashboard/resolution_view.py +302 -0
  57. shikiru/dashboard/row_list.py +256 -0
  58. shikiru/dashboard/section_rows.py +173 -0
  59. shikiru/dashboard/services.py +665 -0
  60. shikiru/dashboard/session_history.py +402 -0
  61. shikiru/dashboard/shikiru_init_ascii_art.txt +40 -0
  62. shikiru/dashboard/task_picker.py +358 -0
  63. shikiru/dashboard/tick.py +59 -0
  64. shikiru/dashboard/timeline_filter.py +129 -0
  65. shikiru/dashboard/timeline_section.py +385 -0
  66. shikiru/dashboard/usage_body.py +269 -0
  67. shikiru/dashboard/views.py +2314 -0
  68. shikiru/dashboard/wizard.py +1389 -0
  69. shikiru/domain/__init__.py +1 -0
  70. shikiru/domain/acceptance.py +130 -0
  71. shikiru/domain/activity.py +28 -0
  72. shikiru/domain/attempts.py +333 -0
  73. shikiru/domain/briefing.py +257 -0
  74. shikiru/domain/commands.py +219 -0
  75. shikiru/domain/decisions.py +231 -0
  76. shikiru/domain/docker.py +262 -0
  77. shikiru/domain/engine_uptime.py +87 -0
  78. shikiru/domain/escalation.py +194 -0
  79. shikiru/domain/escalation_evidence.py +186 -0
  80. shikiru/domain/escalation_panel.py +144 -0
  81. shikiru/domain/events.py +71 -0
  82. shikiru/domain/evidence_export.py +89 -0
  83. shikiru/domain/faults.py +86 -0
  84. shikiru/domain/feedback.py +180 -0
  85. shikiru/domain/feedback_rounds.py +172 -0
  86. shikiru/domain/fencing.py +31 -0
  87. shikiru/domain/ladder.py +102 -0
  88. shikiru/domain/ladder_text.py +44 -0
  89. shikiru/domain/levels.py +44 -0
  90. shikiru/domain/motion.py +199 -0
  91. shikiru/domain/packet_evidence.py +377 -0
  92. shikiru/domain/pushes.py +47 -0
  93. shikiru/domain/quota_blocks.py +44 -0
  94. shikiru/domain/quota_waits.py +219 -0
  95. shikiru/domain/recovery.py +194 -0
  96. shikiru/domain/reports.py +498 -0
  97. shikiru/domain/results.py +217 -0
  98. shikiru/domain/runs.py +85 -0
  99. shikiru/domain/scheduling.py +231 -0
  100. shikiru/domain/status_tasks.py +40 -0
  101. shikiru/domain/steps.py +325 -0
  102. shikiru/domain/tasks.py +174 -0
  103. shikiru/domain/timeline.py +208 -0
  104. shikiru/domain/timeline_phrases.py +271 -0
  105. shikiru/domain/versions.py +18 -0
  106. shikiru/domain/workflow.py +372 -0
  107. shikiru/engine/__init__.py +0 -0
  108. shikiru/engine/acceptance.py +90 -0
  109. shikiru/engine/agents.py +61 -0
  110. shikiru/engine/cosmetic_decorator.py +99 -0
  111. shikiru/engine/daemon.py +409 -0
  112. shikiru/engine/dependency.py +37 -0
  113. shikiru/engine/escalation_filing.py +110 -0
  114. shikiru/engine/escalations.py +401 -0
  115. shikiru/engine/events.py +285 -0
  116. shikiru/engine/evidence.py +81 -0
  117. shikiru/engine/fault_reporter.py +144 -0
  118. shikiru/engine/feedback.py +577 -0
  119. shikiru/engine/feedback_audit.py +80 -0
  120. shikiru/engine/feedback_intake.py +95 -0
  121. shikiru/engine/feedback_snapshot.py +148 -0
  122. shikiru/engine/flow.py +87 -0
  123. shikiru/engine/handlers/__init__.py +33 -0
  124. shikiru/engine/handlers/base.py +148 -0
  125. shikiru/engine/handlers/engine.py +24 -0
  126. shikiru/engine/handlers/escalations.py +174 -0
  127. shikiru/engine/handlers/operator.py +81 -0
  128. shikiru/engine/handlers/tasks.py +237 -0
  129. shikiru/engine/history.py +128 -0
  130. shikiru/engine/integration.py +1234 -0
  131. shikiru/engine/integration_turns.py +337 -0
  132. shikiru/engine/layout_guard.py +362 -0
  133. shikiru/engine/lifecycle.py +60 -0
  134. shikiru/engine/monitor.py +317 -0
  135. shikiru/engine/notification_delivery.py +124 -0
  136. shikiru/engine/notification_feed.py +57 -0
  137. shikiru/engine/notifier.py +91 -0
  138. shikiru/engine/pull_request_closing.py +117 -0
  139. shikiru/engine/push_service.py +177 -0
  140. shikiru/engine/quota_probe_threads.py +79 -0
  141. shikiru/engine/quota_reconciler.py +210 -0
  142. shikiru/engine/quota_schedule.py +24 -0
  143. shikiru/engine/quota_waits.py +102 -0
  144. shikiru/engine/reconcile.py +91 -0
  145. shikiru/engine/recovery.py +284 -0
  146. shikiru/engine/retention.py +102 -0
  147. shikiru/engine/root_preparation.py +86 -0
  148. shikiru/engine/scheduler.py +320 -0
  149. shikiru/engine/session_guard.py +46 -0
  150. shikiru/engine/sessions.py +95 -0
  151. shikiru/engine/signalled.py +75 -0
  152. shikiru/engine/step_runner.py +140 -0
  153. shikiru/engine/supervisor.py +101 -0
  154. shikiru/engine/takeover.py +223 -0
  155. shikiru/engine/task_facts.py +46 -0
  156. shikiru/engine/tick_guard.py +95 -0
  157. shikiru/engine/titles.py +297 -0
  158. shikiru/engine/transitions.py +79 -0
  159. shikiru/engine/turn_context.py +206 -0
  160. shikiru/engine/turn_execution.py +30 -0
  161. shikiru/engine/turn_loop.py +230 -0
  162. shikiru/engine/turn_processes.py +79 -0
  163. shikiru/engine/turns.py +457 -0
  164. shikiru/errors.py +144 -0
  165. shikiru/helper/__init__.py +3 -0
  166. shikiru/helper/helper.py +182 -0
  167. shikiru/helper/models.py +11 -0
  168. shikiru/mcp/__init__.py +1 -0
  169. shikiru/mcp/server.py +196 -0
  170. shikiru/operator/__init__.py +33 -0
  171. shikiru/operator/attachment.py +24 -0
  172. shikiru/operator/commands.py +319 -0
  173. shikiru/operator/detectors.py +374 -0
  174. shikiru/operator/doctor.py +351 -0
  175. shikiru/operator/events.py +57 -0
  176. shikiru/operator/evidence_export.py +498 -0
  177. shikiru/operator/evidence_writer.py +78 -0
  178. shikiru/operator/lifecycle.py +294 -0
  179. shikiru/operator/plugin_checks.py +194 -0
  180. shikiru/operator/probe.py +287 -0
  181. shikiru/operator/queries.py +1601 -0
  182. shikiru/operator/services.py +36 -0
  183. shikiru/operator/sessions.py +378 -0
  184. shikiru/operator/strip_views.py +162 -0
  185. shikiru/operator/timeline.py +416 -0
  186. shikiru/operator/toml_text.py +62 -0
  187. shikiru/palette.py +18 -0
  188. shikiru/paths.py +91 -0
  189. shikiru/plugins/__init__.py +0 -0
  190. shikiru/plugins/agents/__init__.py +1 -0
  191. shikiru/plugins/agents/claude/__init__.py +5 -0
  192. shikiru/plugins/agents/claude/agent.py +251 -0
  193. shikiru/plugins/agents/claude/context.py +116 -0
  194. shikiru/plugins/agents/claude/headless.py +92 -0
  195. shikiru/plugins/agents/claude/hook.py +57 -0
  196. shikiru/plugins/agents/claude/model_catalog.py +105 -0
  197. shikiru/plugins/agents/claude/options.py +62 -0
  198. shikiru/plugins/agents/claude/turn.py +1018 -0
  199. shikiru/plugins/agents/codex/__init__.py +5 -0
  200. shikiru/plugins/agents/codex/agent.py +486 -0
  201. shikiru/plugins/agents/codex/headless.py +166 -0
  202. shikiru/plugins/agents/codex/home.py +14 -0
  203. shikiru/plugins/agents/codex/model_catalog.py +128 -0
  204. shikiru/plugins/agents/codex/options.py +94 -0
  205. shikiru/plugins/agents/codex/quota.py +195 -0
  206. shikiru/plugins/agents/codex/sandbox.py +31 -0
  207. shikiru/plugins/agents/codex/sandbox_probe.py +97 -0
  208. shikiru/plugins/agents/codex/turn.py +767 -0
  209. shikiru/plugins/agents/codex/usage.py +106 -0
  210. shikiru/plugins/agents/shared_settings.py +62 -0
  211. shikiru/plugins/widgets/__init__.py +3 -0
  212. shikiru/plugins/widgets/dialogue.py +144 -0
  213. shikiru/plugins/widgets/disk.py +123 -0
  214. shikiru/plugins/widgets/docker.py +225 -0
  215. shikiru/plugins/widgets/escalations.py +402 -0
  216. shikiru/plugins/widgets/faults.py +204 -0
  217. shikiru/plugins/widgets/header.py +321 -0
  218. shikiru/plugins/widgets/queue.py +1140 -0
  219. shikiru/plugins/widgets/queue_hint.py +93 -0
  220. shikiru/plugins/widgets/resources.py +105 -0
  221. shikiru/plugins/widgets/strips.py +82 -0
  222. shikiru/plugins/widgets/usage.py +176 -0
  223. shikiru/private.py +94 -0
  224. shikiru/process.py +535 -0
  225. shikiru/prompts/__init__.py +3 -0
  226. shikiru/prompts/renderer.py +85 -0
  227. shikiru/prompts/templates/_base.md.j2 +17 -0
  228. shikiru/prompts/templates/_macros.md.j2 +61 -0
  229. shikiru/prompts/templates/conflicts.md.j2 +22 -0
  230. shikiru/prompts/templates/consult.md.j2 +5 -0
  231. shikiru/prompts/templates/continue.md.j2 +4 -0
  232. shikiru/prompts/templates/feedback_analysis.md.j2 +29 -0
  233. shikiru/prompts/templates/feedback_rework.md.j2 +33 -0
  234. shikiru/prompts/templates/feedback_verify.md.j2 +33 -0
  235. shikiru/prompts/templates/handoff.md.j2 +23 -0
  236. shikiru/prompts/templates/implement.md.j2 +13 -0
  237. shikiru/prompts/templates/plan.md.j2 +30 -0
  238. shikiru/prompts/templates/rework.md.j2 +13 -0
  239. shikiru/prompts/templates/triage.md.j2 +20 -0
  240. shikiru/prompts/templates/verify.md.j2 +29 -0
  241. shikiru/store/__init__.py +1 -0
  242. shikiru/store/commands.py +242 -0
  243. shikiru/store/database.py +297 -0
  244. shikiru/store/escalations.py +240 -0
  245. shikiru/store/events.py +213 -0
  246. shikiru/store/feedback.py +266 -0
  247. shikiru/store/migrations/001_initial.sql +63 -0
  248. shikiru/store/migrations/002_headless_turns.sql +31 -0
  249. shikiru/store/migrations/003_drop_command_handled_at.sql +1 -0
  250. shikiru/store/migrations/004_repo_base_ref.sql +3 -0
  251. shikiru/store/migrations/005_complexity_explicit.sql +3 -0
  252. shikiru/store/migrations/006_events_kind_index.sql +1 -0
  253. shikiru/store/migrations/007_notification_cursor.sql +1 -0
  254. shikiru/store/migrations/008_reported_usage.sql +1 -0
  255. shikiru/store/migrations/009_billing_declarations.sql +2 -0
  256. shikiru/store/migrations/010_canonical_quota_windows.sql +3 -0
  257. shikiru/store/migrations/012_drop_task_quota_evidence.sql +2 -0
  258. shikiru/store/migrations/013_push_targets.sql +7 -0
  259. shikiru/store/migrations/014_agent_block_time.sql +6 -0
  260. shikiru/store/migrations/015_repair_orphan_agent_blocks.sql +3 -0
  261. shikiru/store/migrations/016_feedback_rounds.sql +29 -0
  262. shikiru/store/migrations/017_feedback_round_comments.sql +5 -0
  263. shikiru/store/migrations/__init__.py +1 -0
  264. shikiru/store/notification_cursor.py +29 -0
  265. shikiru/store/pushes.py +117 -0
  266. shikiru/store/quotas.py +153 -0
  267. shikiru/store/reader.py +310 -0
  268. shikiru/store/retention.py +92 -0
  269. shikiru/store/runs.py +227 -0
  270. shikiru/store/tasks.py +949 -0
  271. shikiru/telemetry/__init__.py +1 -0
  272. shikiru/telemetry/disk.py +167 -0
  273. shikiru/telemetry/docker.py +164 -0
  274. shikiru/telemetry/resources.py +296 -0
  275. shikiru/tmux/__init__.py +3 -0
  276. shikiru/tmux/client.py +637 -0
  277. shikiru/tmux/decorator.py +225 -0
  278. shikiru/tmux/layout.py +200 -0
  279. shikiru/tmux/session.py +381 -0
  280. shikiru/ui/__init__.py +9 -0
  281. shikiru/ui/age.py +45 -0
  282. shikiru/ui/bubbles.py +41 -0
  283. shikiru/ui/compact.py +1209 -0
  284. shikiru/ui/console.py +49 -0
  285. shikiru/ui/fold.py +50 -0
  286. shikiru/ui/gated_static.py +117 -0
  287. shikiru/ui/gauge.py +40 -0
  288. shikiru/ui/glyphs.py +109 -0
  289. shikiru/ui/keys.py +57 -0
  290. shikiru/ui/live_interval.py +42 -0
  291. shikiru/ui/popup.py +311 -0
  292. shikiru/ui/quota_status.py +26 -0
  293. shikiru/ui/recent_window.py +78 -0
  294. shikiru/ui/screen.py +57 -0
  295. shikiru/ui/section.py +193 -0
  296. shikiru/ui/session.py +65 -0
  297. shikiru/ui/size.py +17 -0
  298. shikiru/ui/splash.py +42 -0
  299. shikiru/ui/status.py +47 -0
  300. shikiru/ui/status_strip.py +149 -0
  301. shikiru/ui/stream.py +152 -0
  302. shikiru/ui/strip.py +332 -0
  303. shikiru/ui/theme.py +83 -0
  304. shikiru/ui/tokens.py +51 -0
  305. shikiru/view_log.py +126 -0
  306. shikiru/workspace/__init__.py +3 -0
  307. shikiru/workspace/base_refs.py +160 -0
  308. shikiru/workspace/caches.py +136 -0
  309. shikiru/workspace/facts.py +118 -0
  310. shikiru/workspace/git.py +601 -0
  311. shikiru/workspace/github.py +744 -0
  312. shikiru/workspace/messages.py +107 -0
  313. shikiru/workspace/reports.py +145 -0
  314. shikiru/workspace/roots.py +180 -0
  315. shikiru_cli-0.2.0.dist-info/METADATA +428 -0
  316. shikiru_cli-0.2.0.dist-info/RECORD +319 -0
  317. shikiru_cli-0.2.0.dist-info/WHEEL +4 -0
  318. shikiru_cli-0.2.0.dist-info/entry_points.txt +18 -0
  319. shikiru_cli-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,22 @@
1
+ {% extends "_base.md.j2" %}
2
+ {% import "_macros.md.j2" as macros %}
3
+ {% block body %}
4
+ You are the Implementer resolving a rebase conflict during integration of this task.
5
+
6
+ {% if conflict %}
7
+ The latest conflict or verification guidance is:
8
+
9
+ {{ conflict | fenced("text") }}
10
+ {% endif %}
11
+
12
+ {{ macros.facts_section(facts) }}
13
+
14
+ Resolve the conflict, keeping the intent of both sides, then commit. Report `completed` only
15
+ when the rebase can proceed cleanly; report `needs_decision` or `blocked` with a concrete
16
+ `question` otherwise.
17
+
18
+ Before committing, check the target project's instructions and documentation for its commit-message
19
+ convention. Follow that convention when one is documented; use Conventional Commits if none is
20
+ documented. Keep the subject concise and summarizing the conflict resolution, and end the commit
21
+ message with the trailer `Task: {{ task_id }}` on its own line.
22
+ {% endblock %}
@@ -0,0 +1,5 @@
1
+ {% extends "_base.md.j2" %}
2
+ {% import "_macros.md.j2" as macros %}
3
+ {% block body %}
4
+ {{ macros.consult_body(question, operator_decision) }}
5
+ {% endblock %}
@@ -0,0 +1,4 @@
1
+ {% extends "_base.md.j2" %}
2
+ {% block body %}
3
+ Continue this task's current turn; nothing about it has changed.
4
+ {% endblock %}
@@ -0,0 +1,29 @@
1
+ {% extends "_base.md.j2" %}
2
+ {% import "_macros.md.j2" as macros %}
3
+ {% block body %}
4
+ You are the Leader analysing the pull request review feedback below for this task. Task mode: `{{ mode }}`.
5
+
6
+ {{ macros.facts_section(facts) }}
7
+
8
+ ## Feedback
9
+
10
+ {{ feedback_snapshot | fenced("json") }}
11
+
12
+ Decide every source item exactly once, by its `id`. A discussion is given whole, with the earlier
13
+ answers that were already posted in it. Its messages marked `"new": true` are the ones to answer;
14
+ the unmarked ones are context, and `reply` answers only the marked ones. Each item may raise several points: list all of
15
+ them in `points` and cover each in `detail` and `reply`. Choose `address` for a request to accept,
16
+ `already_addressed` when the Git facts or files show it is done (a moved or outdated location is
17
+ not proof), `disagree` for a request to decline with a specific reason, and `needs_operator`
18
+ when evidence is missing or requirements conflict; never invent a fix for those.
19
+
20
+ Put each piece of implementation work in `work` with a concrete instruction, and point every
21
+ `address` decision at it by `work_id`. Duplicate requests share one `work` entry but keep their own
22
+ decision and `reply`. Do not change code or files. Replies are proposals; the engine publishes
23
+ them after verification.
24
+
25
+ {% if mode == "plan" %}
26
+ This is a planning task: accepted work stays within its planning and documentation scope. A request
27
+ to implement the planned product is a `needs_operator` decision about scope.
28
+ {% endif %}
29
+ {% endblock %}
@@ -0,0 +1,33 @@
1
+ {% extends "_base.md.j2" %}
2
+ {% import "_macros.md.j2" as macros %}
3
+ {% block body %}
4
+ You are the Implementer applying the accepted work from pull request review feedback. Task mode: `{{ mode }}`.
5
+
6
+ {{ macros.facts_section(facts) }}
7
+
8
+ ## Work to do
9
+ {% for work in feedback_work %}
10
+
11
+ {{ work | fenced("json") }}
12
+ {% endfor %}
13
+
14
+ ## Feedback it answers
15
+
16
+ {{ feedback_snapshot | fenced("json") }}
17
+
18
+ In a discussion, the messages marked `"new": true` are the ones the work answers; the unmarked ones
19
+ are context.
20
+
21
+ Do exactly this work, commit it, run the project's tests and keep the trackers current. Do not
22
+ reply to the reviewers or touch other feedback.
23
+
24
+ Before committing, check the target project's instructions and documentation for its commit-message
25
+ convention. Follow that convention when one is documented; use Conventional Commits if none is
26
+ documented. Keep each subject concise and summarizing its commit. End every commit message with the
27
+ trailer `Task: {{ task_id }}` on its own line.
28
+ {% if mode == "plan" %}
29
+ This is a planning task: stay within planning and documentation changes.
30
+ {% endif %}
31
+ Report `completed` only when nothing remains; report `needs_decision` or `blocked` with a concrete
32
+ `question` otherwise.
33
+ {% endblock %}
@@ -0,0 +1,33 @@
1
+ {% extends "_base.md.j2" %}
2
+ {% import "_macros.md.j2" as macros %}
3
+ {% block body %}
4
+ You are the Leader verifying the result of a pull request review feedback round. Task mode: `{{ mode }}`.
5
+
6
+ {{ macros.facts_section(facts) }}
7
+
8
+ ## Feedback snapshot
9
+
10
+ {{ feedback_snapshot | fenced("json") }}
11
+
12
+ ## Your analysis
13
+
14
+ {{ feedback_analysis | fenced("json") }}
15
+
16
+ Review the changes with `git diff {{ base }}...HEAD` in the workspace. The facts above outrank the
17
+ Implementer's claims. Check that each accepted request is really satisfied, that each
18
+ `already_addressed` claim is true, and that each reply is accurate against the snapshot and the
19
+ Git and check evidence. Do not change code or tests.
20
+
21
+ In a discussion, the messages marked `"new": true` are the ones each reply must answer; the
22
+ unmarked ones are context, including earlier answers.
23
+
24
+ Choose `accept` only when every change and reply is right; give the final reply for each source
25
+ item that has one in `replies`. Check the target project's instructions and documentation for its
26
+ commit-message convention. On `accept`, also give `commit_subject`: a concise subject for the whole
27
+ change following that convention, or Conventional Commits if none is documented (no task reference).
28
+ Choose `changes_requested` with `issues` for further rework, or `escalate` with options for the
29
+ Operator. `supersede` is not available.
30
+ {% if final_round %}
31
+ This is the final round: `changes_requested` is not available.
32
+ {% endif %}
33
+ {% endblock %}
@@ -0,0 +1,23 @@
1
+ {% extends "_base.md.j2" %}
2
+ {% import "_macros.md.j2" as macros %}
3
+ {% block body %}
4
+ You are picking up this task in a fresh session. Read the earlier reports and facts below to
5
+ catch up before doing anything else.
6
+
7
+ {{ macros.facts_section(facts) }}
8
+
9
+ {% if operator_decision is defined %}
10
+ {{ macros.consult_body(question, operator_decision) }}
11
+ {% else %}
12
+ Continue the task from here.
13
+ {% endif %}
14
+
15
+ For commits you make, check the target project's instructions and documentation for its
16
+ commit-message convention. Follow that convention when one is documented; use Conventional Commits
17
+ if none is documented. Keep each subject concise and summarizing its commit. End every commit message
18
+ with the trailer `Task: {{ task_id }}` on its own line.
19
+
20
+ For a plan or an accepted review, give `commit_subject` and `summary` for the whole planned or
21
+ accepted change. Keep the summary short. For an accepted review, draw on every implementation and
22
+ rework report above, not only the latest review.
23
+ {% endblock %}
@@ -0,0 +1,13 @@
1
+ {% extends "_base.md.j2" %}
2
+ {% block body %}
3
+ You are the Implementer, starting this task's first implementation turn.
4
+
5
+ Implement the request, commit your work, run the project's tests and keep the trackers
6
+ current. Report `completed` only when nothing remains; report `needs_decision` or `blocked`
7
+ with a concrete `question` otherwise.
8
+
9
+ Before committing, check the target project's instructions and documentation for its commit-message
10
+ convention. Follow that convention when one is documented; use Conventional Commits if none is
11
+ documented. Keep each subject concise and summarizing its commit. End every commit message with the
12
+ trailer `Task: {{ task_id }}` on its own line.
13
+ {% endblock %}
@@ -0,0 +1,30 @@
1
+ {% extends "_base.md.j2" %}
2
+ {% block body %}
3
+ You are the Leader planning a task.
4
+
5
+ Shape the plan in the trackers listed above and commit it. Fill `new_tasks` only when the
6
+ request itself asks you to execute the plan, not merely to produce one; otherwise leave it
7
+ empty. A plan turn implements nothing, so when the request asks for work to be done (for
8
+ example "implement ..."), `new_tasks` is the only way that work happens, even when the
9
+ trackers already describe it: leaving it empty ends this task with nothing implemented.
10
+ When you fill `new_tasks`, restructure the tracker entries so each new task has its own
11
+ entry and together they replace this task's entry; each new task ticks its own entry when done.
12
+ Record any tracker changes outside the plan itself in `scope_changes`.
13
+
14
+ Check the target project's instructions and documentation for its commit-message convention. Give
15
+ `commit_subject` a concise subject for the whole plan change using that convention, or Conventional
16
+ Commits if none is documented (no task reference). Keep `summary` short and describe the whole
17
+ committed plan change.
18
+ Use that subject for the plan commit, and end the commit message with the trailer
19
+ `Task: {{ task_id }}` on its own line.
20
+
21
+ Give each tracker entry for a task, and each `new_tasks` request, an acceptance-criteria section
22
+ in exactly this form: the heading verbatim, then one independently verifiable criterion per bullet.
23
+
24
+ ## {{ acceptance_criteria_heading }}
25
+ - <criterion>
26
+ - <criterion>
27
+
28
+ Put nothing else under that heading: a prose line or another heading ends the section, and
29
+ criteria written after it are not read.
30
+ {% endblock %}
@@ -0,0 +1,13 @@
1
+ {% extends "_base.md.j2" %}
2
+ {% block body %}
3
+ You are the Implementer, reworking this task after a verdict requested changes.
4
+
5
+ Address every issue raised in the verdict above, commit your work, run the project's tests
6
+ and keep the trackers current. Report `completed` only when nothing remains; report
7
+ `needs_decision` or `blocked` with a concrete `question` otherwise.
8
+
9
+ Before committing, check the target project's instructions and documentation for its commit-message
10
+ convention. Follow that convention when one is documented; use Conventional Commits if none is
11
+ documented. Keep each subject concise and summarizing its commit. End every commit message with the
12
+ trailer `Task: {{ task_id }}` on its own line.
13
+ {% endblock %}
@@ -0,0 +1,20 @@
1
+ {% extends "_base.md.j2" %}
2
+ {% block body %}
3
+ You are the Leader triaging a task, before any work starts.
4
+
5
+ This turn is read-only: make no edits to the repository or the trackers.
6
+
7
+ Decide:
8
+ - `mode`: `implement` for a self-contained change, `plan` when a plan turn should shape the
9
+ work first, including when the request needs decomposing into several tasks, as it does
10
+ when it spans several tracker entries: each entry becomes its own task. An open
11
+ decision is not a reason to plan: a plan turn cannot raise it, while an implement task
12
+ raises it through consult to you and, if you cannot decide it, to the Operator.
13
+ - `complexity`, `acceptance_criteria` and `references`.
14
+
15
+ When the request has an acceptance-criteria section, carry its bullets into
16
+ `acceptance_criteria` unchanged. The section starts at a line holding only one of
17
+ `## {{ acceptance_criteria_heading }}`, `{{ acceptance_criteria_heading }}:`,
18
+ `**{{ acceptance_criteria_heading }}**` or `**{{ acceptance_criteria_heading }}:**`, in any
19
+ letter case.
20
+ {% endblock %}
@@ -0,0 +1,29 @@
1
+ {% extends "_base.md.j2" %}
2
+ {% import "_macros.md.j2" as macros %}
3
+ {% block body %}
4
+ You are the Leader verifying the Implementer's work on this task.
5
+
6
+ {{ macros.facts_section(facts) }}
7
+
8
+ Review the change with `git diff {{ base }}...HEAD` in the workspace. The facts above outrank
9
+ the Implementer's claims. Do not change code or tests: when something is missing or wrong,
10
+ choose `changes_requested` with the issues. The only files you may edit are the trackers, for
11
+ scope changes, and you commit those edits. Accept only when no issues remain;
12
+ non-blocking residual risks go into `residual_risks` or `new_tasks`, not `issues`.
13
+
14
+ If you commit tracker scope changes, follow the target project's documented commit-message
15
+ convention, using Conventional Commits if none is documented. Summarize the changes and end the
16
+ message with the trailer `Task: {{ task_id }}` on its own line.
17
+
18
+ Check the target project's instructions and documentation for its commit-message convention. When
19
+ you `accept`, `commit_subject` is a concise subject for the whole change following that convention,
20
+ or Conventional Commits if none is documented (no task reference). Keep `summary` short and describe
21
+ the entire change as it will land, drawing on every implementation and rework turn in the reports
22
+ above, not only this review.
23
+ {% if final_round %}
24
+ This is the final round: choose `accept`, `supersede` (decompose into `new_tasks`) or
25
+ `escalate`. `changes_requested` is not available this round.
26
+ {% endif %}
27
+ When you `escalate`, give the Operator options, exactly one `recommended`, and set `needs_input` on
28
+ an option that asks the Operator to provide something (a file, a result, access, a value).
29
+ {% endblock %}
@@ -0,0 +1 @@
1
+ """SQLite state store: schema, migrations, per-aggregate stores and the read-only reader."""
@@ -0,0 +1,242 @@
1
+ """Operator command queue: append, dispatch and acknowledge."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import TYPE_CHECKING, Any
7
+
8
+ from pydantic import TypeAdapter
9
+
10
+ from shikiru.domain.commands import (
11
+ COMMAND_ID_KEY,
12
+ Command,
13
+ CommandStatus,
14
+ IdentifiedCommand,
15
+ Submit,
16
+ )
17
+ from shikiru.store.database import (
18
+ dump_json,
19
+ format_timestamp,
20
+ load_json,
21
+ next_sequence,
22
+ require_after_insert,
23
+ transaction,
24
+ )
25
+ from shikiru.store.tasks import TASK_ID_PREFIX, TASK_SEQUENCE
26
+
27
+ if TYPE_CHECKING:
28
+ import sqlite3 # noqa: TID251
29
+
30
+ from shikiru.clock import Clock
31
+
32
+ _COMMAND_ADAPTER: TypeAdapter[Command] = TypeAdapter(Command)
33
+ _RESULT_ADAPTER: TypeAdapter[dict[str, Any] | None] = TypeAdapter(dict[str, Any] | None)
34
+ _SUBMIT_TASK_ID_JSON_PATH = "$.task_id"
35
+
36
+
37
+ def _identified(command: Command, command_id: int) -> Command:
38
+ if isinstance(command, IdentifiedCommand):
39
+ return command.model_copy(update={COMMAND_ID_KEY: command_id})
40
+ return command
41
+
42
+
43
+ @dataclass(frozen=True, slots=True)
44
+ class QueuedCommand:
45
+ """One pending command, as `CommandQueue.pending()` hands it to the engine."""
46
+
47
+ id: int
48
+ command: Command
49
+
50
+
51
+ @dataclass(frozen=True, slots=True)
52
+ class CommandOutcome:
53
+ """A command's lifecycle status, for `OperatorCommands` to wait on its ack."""
54
+
55
+ id: int
56
+ status: CommandStatus
57
+ result: dict[str, Any] | None
58
+
59
+
60
+ @dataclass(frozen=True, slots=True)
61
+ class StoredCommand:
62
+ """One durable command row read for bounded operator history."""
63
+
64
+ id: int
65
+ ts: str
66
+ command: Command
67
+ status: CommandStatus
68
+ result: dict[str, Any] | None
69
+
70
+
71
+ @dataclass(frozen=True, slots=True)
72
+ class CommandHistoryPage:
73
+ """One bounded page of durable command rows."""
74
+
75
+ entries: list[StoredCommand]
76
+ offset: int
77
+ limit: int
78
+ total_count: int
79
+ has_more: bool
80
+
81
+
82
+ def fetch_command_history(
83
+ connection: sqlite3.Connection,
84
+ *,
85
+ limit: int,
86
+ offset: int = 0,
87
+ status: CommandStatus | None = None,
88
+ ) -> CommandHistoryPage:
89
+ """Read one bounded newest-first page with its stored status and result."""
90
+ if status is None:
91
+ total_count = connection.execute("SELECT COUNT(*) FROM commands").fetchone()[0]
92
+ rows = connection.execute(
93
+ "SELECT id, ts, payload, status, result FROM commands "
94
+ "ORDER BY id DESC LIMIT ? OFFSET ?",
95
+ (limit, offset),
96
+ ).fetchall()
97
+ else:
98
+ total_count = connection.execute(
99
+ "SELECT COUNT(*) FROM commands WHERE status = ?", (status.value,)
100
+ ).fetchone()[0]
101
+ rows = connection.execute(
102
+ "SELECT id, ts, payload, status, result FROM commands WHERE status = ? "
103
+ "ORDER BY id DESC LIMIT ? OFFSET ?",
104
+ (status.value, limit, offset),
105
+ ).fetchall()
106
+ entries = [_stored_command(row) for row in rows]
107
+ return CommandHistoryPage(
108
+ entries=entries,
109
+ offset=offset,
110
+ limit=limit,
111
+ total_count=total_count,
112
+ has_more=offset + len(entries) < total_count,
113
+ )
114
+
115
+
116
+ def fetch_command(
117
+ connection: sqlite3.Connection,
118
+ command_id: int,
119
+ ) -> StoredCommand | None:
120
+ """Read one durable command status by id without changing its lifecycle."""
121
+ row = connection.execute(
122
+ "SELECT id, ts, payload, status, result FROM commands WHERE id = ?",
123
+ (command_id,),
124
+ ).fetchone()
125
+ return _stored_command(row) if row is not None else None
126
+
127
+
128
+ def fetch_pending_command(
129
+ connection: sqlite3.Connection,
130
+ command: Command,
131
+ ) -> StoredCommand | None:
132
+ """Find an identical still-pending command without scanning history into memory."""
133
+ payload = dump_json(_COMMAND_ADAPTER, command)
134
+ if isinstance(command, Submit):
135
+ row = connection.execute(
136
+ "SELECT id, ts, payload, status, result FROM commands "
137
+ "WHERE status = ? AND kind = ? AND "
138
+ "json_remove(payload, ?) = json_remove(?, ?) ORDER BY id DESC LIMIT 1",
139
+ (
140
+ CommandStatus.PENDING.value,
141
+ command.kind.value,
142
+ _SUBMIT_TASK_ID_JSON_PATH,
143
+ payload,
144
+ _SUBMIT_TASK_ID_JSON_PATH,
145
+ ),
146
+ ).fetchone()
147
+ else:
148
+ row = connection.execute(
149
+ "SELECT id, ts, payload, status, result FROM commands "
150
+ "WHERE status = ? AND kind = ? AND payload = ? ORDER BY id DESC LIMIT 1",
151
+ (CommandStatus.PENDING.value, command.kind.value, payload),
152
+ ).fetchone()
153
+ return _stored_command(row) if row is not None else None
154
+
155
+
156
+ def _stored_command(row: sqlite3.Row) -> StoredCommand:
157
+ result = row["result"]
158
+ return StoredCommand(
159
+ id=row["id"],
160
+ ts=row["ts"],
161
+ command=load_json(_COMMAND_ADAPTER, row["payload"]),
162
+ status=CommandStatus(row["status"]),
163
+ result=load_json(_RESULT_ADAPTER, result) if result is not None else None,
164
+ )
165
+
166
+
167
+ class CommandQueue:
168
+ """Appends operator commands and lets the engine dispatch and acknowledge them."""
169
+
170
+ def __init__(self, connection: sqlite3.Connection, clock: Clock) -> None:
171
+ """Use `connection` and `clock` for every write below.
172
+
173
+ Unlike the other stores, this connection isn't only the engine's: clients (CLI, MCP)
174
+ append commands and, for `Submit`, allocate the task id here too.
175
+ """
176
+ self._connection = connection
177
+ self._clock = clock
178
+
179
+ def append(self, command: Command) -> QueuedCommand:
180
+ """Append `command`; a `Submit` is given its `T-n` id here, atomically."""
181
+ ts = format_timestamp(self._clock.now())
182
+ with transaction(self._connection):
183
+ if isinstance(command, Submit):
184
+ seq = next_sequence(self._connection, TASK_SEQUENCE)
185
+ command = command.model_copy(update={"task_id": f"{TASK_ID_PREFIX}{seq}"})
186
+ cursor = self._connection.execute(
187
+ "INSERT INTO commands (ts, kind, payload) VALUES (?, ?, ?)",
188
+ (ts, command.kind.value, dump_json(_COMMAND_ADAPTER, command)),
189
+ )
190
+ command_id = cursor.lastrowid
191
+ command_id = require_after_insert(
192
+ command_id, "sqlite did not allocate a rowid for the new command"
193
+ )
194
+ return QueuedCommand(id=command_id, command=command)
195
+
196
+ def pending(self) -> list[QueuedCommand]:
197
+ """Read every pending command, oldest first, as typed `domain.commands.Command` models."""
198
+ rows = self._connection.execute(
199
+ "SELECT id, payload FROM commands WHERE status = ? ORDER BY id",
200
+ (CommandStatus.PENDING.value,),
201
+ ).fetchall()
202
+ return [
203
+ QueuedCommand(
204
+ id=row["id"],
205
+ command=_identified(load_json(_COMMAND_ADAPTER, row["payload"]), row["id"]),
206
+ )
207
+ for row in rows
208
+ ]
209
+
210
+ def mark_done(self, command_id: int, result: dict[str, Any] | None = None) -> None:
211
+ """Mark `command_id` done, recording `result` if the client needs it."""
212
+ self._set_status(command_id, CommandStatus.DONE, result)
213
+
214
+ def mark_rejected(self, command_id: int, result: dict[str, Any] | None = None) -> None:
215
+ """Mark `command_id` rejected, recording `result` (e.g. an error message)."""
216
+ self._set_status(command_id, CommandStatus.REJECTED, result)
217
+
218
+ def _set_status(
219
+ self, command_id: int, status: CommandStatus, result: dict[str, Any] | None
220
+ ) -> None:
221
+ self._connection.execute(
222
+ "UPDATE commands SET status = ?, result = ? WHERE id = ?",
223
+ (
224
+ status.value,
225
+ dump_json(_RESULT_ADAPTER, result),
226
+ command_id,
227
+ ),
228
+ )
229
+
230
+ def status(self, command_id: int) -> CommandOutcome | None:
231
+ """Read `command_id`'s current lifecycle status, or `None` if it doesn't exist."""
232
+ row = self._connection.execute(
233
+ "SELECT id, status, result FROM commands WHERE id = ?", (command_id,)
234
+ ).fetchone()
235
+ if row is None:
236
+ return None
237
+ result = row["result"]
238
+ return CommandOutcome(
239
+ id=row["id"],
240
+ status=CommandStatus(row["status"]),
241
+ result=load_json(_RESULT_ADAPTER, result) if result is not None else None,
242
+ )