spice-harness 0.1.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 (135) hide show
  1. spice_harness-0.1.0/LICENSE +21 -0
  2. spice_harness-0.1.0/PKG-INFO +181 -0
  3. spice_harness-0.1.0/README.md +165 -0
  4. spice_harness-0.1.0/pyproject.toml +55 -0
  5. spice_harness-0.1.0/setup.cfg +4 -0
  6. spice_harness-0.1.0/spice/__main__.py +10 -0
  7. spice_harness-0.1.0/spice/agent/SKILL.md +42 -0
  8. spice_harness-0.1.0/spice/agent/activation.py +60 -0
  9. spice_harness-0.1.0/spice/agent/cli.py +173 -0
  10. spice_harness-0.1.0/spice/agent/driver.py +206 -0
  11. spice_harness-0.1.0/spice/agent/gitshadow.py +182 -0
  12. spice_harness-0.1.0/spice/agent/identity.py +48 -0
  13. spice_harness-0.1.0/spice/agent/lifecycle.py +724 -0
  14. spice_harness-0.1.0/spice/agent/maximcli.py +218 -0
  15. spice_harness-0.1.0/spice/agent/maxims.py +339 -0
  16. spice_harness-0.1.0/spice/agent/renewal.py +119 -0
  17. spice_harness-0.1.0/spice/agent/sidechannel.py +226 -0
  18. spice_harness-0.1.0/spice/agent/watchdog.py +242 -0
  19. spice_harness-0.1.0/spice/agent/wrap.py +546 -0
  20. spice_harness-0.1.0/spice/cli/entry.py +94 -0
  21. spice_harness-0.1.0/spice/cli/mounts.py +97 -0
  22. spice_harness-0.1.0/spice/cli/parser.py +81 -0
  23. spice_harness-0.1.0/spice/cli/recovery.py +86 -0
  24. spice_harness-0.1.0/spice/config.py +313 -0
  25. spice_harness-0.1.0/spice/configcli.py +151 -0
  26. spice_harness-0.1.0/spice/errors.py +11 -0
  27. spice_harness-0.1.0/spice/flexstate.py +119 -0
  28. spice_harness-0.1.0/spice/hooks/cli.py +120 -0
  29. spice_harness-0.1.0/spice/hooks/commitmsg.py +138 -0
  30. spice_harness-0.1.0/spice/hooks/doctor.py +572 -0
  31. spice_harness-0.1.0/spice/hooks/install.py +145 -0
  32. spice_harness-0.1.0/spice/hooks/precommit.py +538 -0
  33. spice_harness-0.1.0/spice/hooks/refguard.py +127 -0
  34. spice_harness-0.1.0/spice/locking.py +106 -0
  35. spice_harness-0.1.0/spice/mail/acks.py +592 -0
  36. spice_harness-0.1.0/spice/mail/attachments.py +247 -0
  37. spice_harness-0.1.0/spice/mail/inbox.py +663 -0
  38. spice_harness-0.1.0/spice/mail/readout.py +57 -0
  39. spice_harness-0.1.0/spice/mail/watch.py +384 -0
  40. spice_harness-0.1.0/spice/paths.py +171 -0
  41. spice_harness-0.1.0/spice/policy.py +96 -0
  42. spice_harness-0.1.0/spice/procs.py +211 -0
  43. spice_harness-0.1.0/spice/repocfg.py +62 -0
  44. spice_harness-0.1.0/spice/serve/agentapi.py +178 -0
  45. spice_harness-0.1.0/spice/serve/app.py +981 -0
  46. spice_harness-0.1.0/spice/serve/attachments.py +50 -0
  47. spice_harness-0.1.0/spice/serve/audio.py +108 -0
  48. spice_harness-0.1.0/spice/serve/browser/artifacts.py +25 -0
  49. spice_harness-0.1.0/spice/serve/cli.py +106 -0
  50. spice_harness-0.1.0/spice/serve/diagnostics.py +296 -0
  51. spice_harness-0.1.0/spice/serve/drive.py +9 -0
  52. spice_harness-0.1.0/spice/serve/filewatch.py +140 -0
  53. spice_harness-0.1.0/spice/serve/images.py +172 -0
  54. spice_harness-0.1.0/spice/serve/livebus.py +412 -0
  55. spice_harness-0.1.0/spice/serve/markdown.py +297 -0
  56. spice_harness-0.1.0/spice/serve/messages.py +751 -0
  57. spice_harness-0.1.0/spice/serve/payloads.py +432 -0
  58. spice_harness-0.1.0/spice/serve/static/app.audio.js +348 -0
  59. spice_harness-0.1.0/spice/serve/static/app.controls.js +123 -0
  60. spice_harness-0.1.0/spice/serve/static/app.groups.js +614 -0
  61. spice_harness-0.1.0/spice/serve/static/app.js +115 -0
  62. spice_harness-0.1.0/spice/serve/static/app.lanes.js +659 -0
  63. spice_harness-0.1.0/spice/serve/static/app.panes.js +583 -0
  64. spice_harness-0.1.0/spice/serve/static/app.render.js +599 -0
  65. spice_harness-0.1.0/spice/serve/static/app.shell.js +1265 -0
  66. spice_harness-0.1.0/spice/serve/static/app.stream.js +745 -0
  67. spice_harness-0.1.0/spice/serve/static/app.types.js +75 -0
  68. spice_harness-0.1.0/spice/serve/static/favicon.ico +0 -0
  69. spice_harness-0.1.0/spice/serve/static/index.css +1423 -0
  70. spice_harness-0.1.0/spice/serve/steering.py +72 -0
  71. spice_harness-0.1.0/spice/serve/teams.py +656 -0
  72. spice_harness-0.1.0/spice/serve/typecheck.py +77 -0
  73. spice_harness-0.1.0/spice/serve/web.py +67 -0
  74. spice_harness-0.1.0/spice/serve/websocket.py +182 -0
  75. spice_harness-0.1.0/spice/serve/worktrees.py +124 -0
  76. spice_harness-0.1.0/spice/sessions/analysis.py +520 -0
  77. spice_harness-0.1.0/spice/sessions/briefing.py +894 -0
  78. spice_harness-0.1.0/spice/sessions/cli.py +906 -0
  79. spice_harness-0.1.0/spice/sessions/commandaudit.py +209 -0
  80. spice_harness-0.1.0/spice/sessions/commandrecords.py +212 -0
  81. spice_harness-0.1.0/spice/sessions/meter.py +366 -0
  82. spice_harness-0.1.0/spice/sessions/records.py +361 -0
  83. spice_harness-0.1.0/spice/sessions/resolve.py +51 -0
  84. spice_harness-0.1.0/spice/sessions/slices.py +200 -0
  85. spice_harness-0.1.0/spice/sessions/util.py +91 -0
  86. spice_harness-0.1.0/spice/studies/cli.py +159 -0
  87. spice_harness-0.1.0/spice/studies/complexity.py +280 -0
  88. spice_harness-0.1.0/spice/studies/envpolicy.py +104 -0
  89. spice_harness-0.1.0/spice/studies/fileloc.py +256 -0
  90. spice_harness-0.1.0/spice/studies/localpaths.py +48 -0
  91. spice_harness-0.1.0/spice/studies/magicnums.py +270 -0
  92. spice_harness-0.1.0/spice/studies/shape.py +134 -0
  93. spice_harness-0.1.0/spice/studies/walk.py +192 -0
  94. spice_harness-0.1.0/spice/tasks/alloc.py +81 -0
  95. spice_harness-0.1.0/spice/tasks/cli.py +559 -0
  96. spice_harness-0.1.0/spice/tasks/config.py +419 -0
  97. spice_harness-0.1.0/spice/tasks/gitsync.py +418 -0
  98. spice_harness-0.1.0/spice/tasks/identity.py +104 -0
  99. spice_harness-0.1.0/spice/tasks/lanes.py +143 -0
  100. spice_harness-0.1.0/spice/tasks/ops.py +856 -0
  101. spice_harness-0.1.0/spice/tasks/render.py +354 -0
  102. spice_harness-0.1.0/spice/tasks/tw.py +114 -0
  103. spice_harness-0.1.0/spice/worktrees.py +131 -0
  104. spice_harness-0.1.0/spice_harness.egg-info/PKG-INFO +181 -0
  105. spice_harness-0.1.0/spice_harness.egg-info/SOURCES.txt +133 -0
  106. spice_harness-0.1.0/spice_harness.egg-info/dependency_links.txt +1 -0
  107. spice_harness-0.1.0/spice_harness.egg-info/entry_points.txt +2 -0
  108. spice_harness-0.1.0/spice_harness.egg-info/requires.txt +3 -0
  109. spice_harness-0.1.0/spice_harness.egg-info/top_level.txt +1 -0
  110. spice_harness-0.1.0/tests/test_acks.py +261 -0
  111. spice_harness-0.1.0/tests/test_agentshim.py +60 -0
  112. spice_harness-0.1.0/tests/test_clirecovery.py +69 -0
  113. spice_harness-0.1.0/tests/test_config.py +124 -0
  114. spice_harness-0.1.0/tests/test_doctor.py +217 -0
  115. spice_harness-0.1.0/tests/test_hooks.py +707 -0
  116. spice_harness-0.1.0/tests/test_images.py +125 -0
  117. spice_harness-0.1.0/tests/test_inbox.py +203 -0
  118. spice_harness-0.1.0/tests/test_lifecycle.py +823 -0
  119. spice_harness-0.1.0/tests/test_livebus.py +312 -0
  120. spice_harness-0.1.0/tests/test_markdown.py +120 -0
  121. spice_harness-0.1.0/tests/test_mounts.py +59 -0
  122. spice_harness-0.1.0/tests/test_payloads.py +257 -0
  123. spice_harness-0.1.0/tests/test_serve.py +1088 -0
  124. spice_harness-0.1.0/tests/test_servebrowserartifacts.py +33 -0
  125. spice_harness-0.1.0/tests/test_servediagnostics.py +104 -0
  126. spice_harness-0.1.0/tests/test_servefilewatch.py +109 -0
  127. spice_harness-0.1.0/tests/test_sessionanalysis.py +241 -0
  128. spice_harness-0.1.0/tests/test_sessioncommands.py +275 -0
  129. spice_harness-0.1.0/tests/test_sessions.py +619 -0
  130. spice_harness-0.1.0/tests/test_sessionslices.py +164 -0
  131. spice_harness-0.1.0/tests/test_speechprep.py +403 -0
  132. spice_harness-0.1.0/tests/test_studies.py +160 -0
  133. spice_harness-0.1.0/tests/test_taskcli.py +217 -0
  134. spice_harness-0.1.0/tests/test_tasks.py +632 -0
  135. spice_harness-0.1.0/tests/test_teams.py +79 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Infima Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,181 @@
1
+ Metadata-Version: 2.4
2
+ Name: spice-harness
3
+ Version: 0.1.0
4
+ Summary: Simultaneous Production, Integration, and Control Environment: an agent harness for coding repositories.
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/infimalabs/spice
7
+ Project-URL: Repository, https://github.com/infimalabs/spice
8
+ Project-URL: Issues, https://github.com/infimalabs/spice/issues
9
+ Requires-Python: >=3.12
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: lizard>=1.21
13
+ Requires-Dist: ruff>=0.11
14
+ Requires-Dist: watchfiles>=1.1
15
+ Dynamic: license-file
16
+
17
+ # spice
18
+
19
+ **Simultaneous Production, Integration, and Control Environment.**
20
+
21
+ spice is an installed agent harness: wrap, steer, supervise, coordinate, and
22
+ audit coding agents across the repos they work on. Install it once, point it at
23
+ a repository, and it provides a closed loop around the agents working there:
24
+
25
+ - the agent's **transcript is the single source of truth**, and
26
+ - the repo's **filesystem is the single channel of steering**;
27
+
28
+ supervision, coordination, conscience, and hygiene are derived mechanically
29
+ from those two surfaces.
30
+
31
+ ## Install
32
+
33
+ ```sh
34
+ pip install spice-harness # or: uv tool install spice-harness
35
+ cd /path/to/your/repo
36
+ spice init # hooks, spice.sh shim, state scaffolding
37
+ spice dev doctor # verify drivers, backends, and policy
38
+ ```
39
+
40
+ `spice init` writes machine-local git hook shims under `.spice/` (ignored via
41
+ `.git/info/exclude`) and a tracked `spice.sh` shim. Repo-tracked policy lives
42
+ in your `pyproject.toml` under `[tool.spice.*]` tables. Entrypoint resolution
43
+ is worktree-true: when the current repo is the spice source checkout, generated
44
+ shims and supervisor children put that checkout first on `PYTHONPATH` and run
45
+ `python -m spice`; ordinary target repos use the installed product.
46
+
47
+ A project can set its default supervised-agent launch model and thinking in
48
+ tracked config, either by editing `pyproject.toml` or by running
49
+ `spice config agent --scope project --model ... --thinking ...`:
50
+
51
+ ```toml
52
+ [tool.spice.agent]
53
+ model = "gpt-5.4"
54
+ thinking = "low"
55
+ ```
56
+
57
+ An operator can override those defaults for just the current worktree:
58
+
59
+ ```sh
60
+ spice config agent --scope worktree --model gpt-5.4 --thinking low
61
+ ```
62
+
63
+ Resolution order is explicit launch flags, then worktree config, then tracked
64
+ project config, then the driver defaults.
65
+
66
+ A repo can also mount its own tooling into the `spice` namespace:
67
+
68
+ ```toml
69
+ [tool.spice.commands]
70
+ deploy = "./scripts/deploy.sh"
71
+ bench = ["python", "-m", "myproj.bench"]
72
+ ```
73
+
74
+ `spice deploy --env staging` then runs the mounted command from the repo
75
+ root with the remaining arguments passed through verbatim. Built-in verbs
76
+ always win; a mount that shadows one fails loudly.
77
+
78
+ Mounted names are intentionally one-level verbs (`^[a-z][a-z0-9-]*$`), not
79
+ nested command paths. A repo with a large tool family mounts one namespace
80
+ owner and keeps family grouping inside that repo tool's own arguments:
81
+
82
+ ```toml
83
+ [tool.spice.commands]
84
+ toolbox = ["uv", "run", "toolbox"]
85
+ ```
86
+
87
+ `spice toolbox lint css --fix` then dispatches `lint css --fix` to `toolbox`.
88
+ Do not encode families as dotted, spaced, or ad-hoc hyphenated spice mount
89
+ names such as `lint.css`, `lint css`, or one mount per subcommand; those
90
+ groupings belong behind the mounted repo tool's explicit contract.
91
+
92
+ ### Library seam for repo tools
93
+
94
+ Mounted commands and tracked pre-commit extensions may import a deliberately
95
+ narrow Python seam from `spice` instead of vendoring harness scaffolding. This
96
+ surface is source-stable for target repos: public names in the modules listed
97
+ below are not removed or renamed silently, and incompatible changes require an
98
+ explicit contract update. Underscored names remain private.
99
+
100
+ - `spice.errors`: `SpiceError` for user-facing command failures.
101
+ - `spice.policy`: constitution constants and `flex_limit`.
102
+ - `spice.flexstate`: flex-limit sticky-state persistence and rename helpers.
103
+ - `spice.locking`: cross-platform advisory file locks.
104
+ - `spice.paths`: repo-root, state-dir, atomic write, and tool-resolution helpers.
105
+ - `spice.procs`: process-group spawn, liveness, and termination helpers.
106
+ - `spice.repocfg`: tracked `[tool.spice]` table readers.
107
+ - `spice.studies.walk`: tracked/staged path walkers, repo policy exclusions,
108
+ staged renames, and git blob reads.
109
+ - `spice.studies.fileloc`, `spice.studies.complexity`,
110
+ `spice.studies.magicnums`, and `spice.studies.envpolicy`: finding
111
+ dataclasses plus `scan_*`, `detect_*`, and `render_*_board` helpers for
112
+ project-specific studies.
113
+
114
+ Everything else is an internal implementation detail unless this section names
115
+ it. A repo tool that needs an unlisted module should either vendor that helper
116
+ or first add the helper to this seam with tests and a stability note.
117
+
118
+ ## The loop
119
+
120
+ | Surface | Command | What it does |
121
+ | --- | --- | --- |
122
+ | Wrapper | `spice agent run -- <cmd>` (or `./spice.sh`) | Runs shell commands with proxy routing, git-shadow env, and steering injection on stderr. |
123
+ | Lifecycle | `spice agent ensure` / `supervise` | One worktree-bound agent per worktree, started under a neutral skill prompt, watched by a durable supervisor. |
124
+ | Steering | filesystem inbox under `.spice/inbox/` | Durable operator messages; items retire only when the agent semantically ACKs their key in its transcript. |
125
+ | Tasks | `spice task …` | Phase-native Taskwarrior board shared by all worktrees; `task next` is allocator-owned; git sync happens at task boundaries. |
126
+ | Sessions | `spice session` | Transcript forensics: the no-arg briefing is the primary rehydration product, with context-pressure metering. |
127
+ | Cockpit | `spice serve` | Localhost web UI: lanes over worktrees, live transcript streams, lifetime control (Renew / Steer / Drive), task-filter routing, fused lane groups backed by server-side teams; `spice serve teams` and `spice serve browser-artifact-path <file>` expose operator diagnostics for smoke runs. |
128
+ | Conscience | `spice maxim …` | Builtin maxims judged against assistant prose by a local model; violations come back as inbox steering. |
129
+ | Constitution | `spice dev pre-commit` / `spice study …` | Namespace packages, path shape, LOC/byte/complexity flex+sticky gates, magic-number ratchet, env-literal inventory, commit-message policy. |
130
+
131
+ Session analysis is intentionally tiered. The current tier includes
132
+ `spice session phases` for contiguous working-phase spans and
133
+ `spice session messages` for message-level side/phase/flavor filtering.
134
+ Deeper report families that depend on richer topic/bucket modeling belong in
135
+ a separate analytics tier after the basic phase/message surfaces harden.
136
+
137
+ ## The constitution
138
+
139
+ The pre-commit gate is the executable form of the project's opinions — see
140
+ [spice/policy.py](spice/policy.py). Highlights:
141
+
142
+ - namespace packages only; no `__init__.py` under declared package roots;
143
+ - file names match `^_*[0-9a-z]+_*$`; splitting a file requires naming the
144
+ seam (no `*2.py`, no generic continuation shards);
145
+ - files flex to 1500 lines but a breach holds them to 1000 until they shrink;
146
+ routines flex the same way around CCN 20 / length 80;
147
+ - magic-number regressions are a ratchet against `HEAD`, not an amnesty;
148
+ - env-literal inventory covers `SPICE_*` and `CODEX_THREAD_ID` by default;
149
+ target repos can add tracked name regexes with
150
+ `[tool.spice.policy] env_name_patterns`;
151
+ - commit subjects fit in 100 columns; bodies are auto-folded.
152
+
153
+ The gate applies to spice itself: this repository is its own first target.
154
+ Target repos can keep their own tracked gate lanes under the same hook by
155
+ declaring mounted commands and pre-commit policy:
156
+
157
+ ```toml
158
+ [tool.spice.commands]
159
+ fmt-cs = ["dotnet", "format", "--verify-no-changes"]
160
+
161
+ [tool.spice.policy]
162
+ pre_commit = ["fmt-cs", { label = "assets", run = ["python3", "-m", "tools.assets"] }]
163
+ pre_commit_success = [{ label = "clear asset sticky state", run = ["python3", "-m", "tools.assets", "--clear-sticky"] }]
164
+
165
+ [tool.spice.policy.pre_commit_builtins]
166
+ formatters = false
167
+ "magic-numbers" = { label = "project magic", run = ["python3", "-m", "tools.magic"] }
168
+ ```
169
+
170
+ Built-in pre-commit keys are `repo-shape`, `staging`, `repo-docs`,
171
+ `formatters`, `env-policy`, `file-shape`, `complexity`, and `magic-numbers`.
172
+ They run before extension steps unless an individual built-in is disabled or
173
+ replaced in tracked policy. `pre_commit_success` uses the same command shape as
174
+ `pre_commit`, but runs only after the whole gate has passed, alongside sticky
175
+ state cleanup.
176
+
177
+ ## Status
178
+
179
+ Work in progress: an extraction-in-progress toward a standalone, releasable
180
+ product. Surfaces are still settling; the loop described above is real and
181
+ exercised daily, and this repository is its own first target.
@@ -0,0 +1,165 @@
1
+ # spice
2
+
3
+ **Simultaneous Production, Integration, and Control Environment.**
4
+
5
+ spice is an installed agent harness: wrap, steer, supervise, coordinate, and
6
+ audit coding agents across the repos they work on. Install it once, point it at
7
+ a repository, and it provides a closed loop around the agents working there:
8
+
9
+ - the agent's **transcript is the single source of truth**, and
10
+ - the repo's **filesystem is the single channel of steering**;
11
+
12
+ supervision, coordination, conscience, and hygiene are derived mechanically
13
+ from those two surfaces.
14
+
15
+ ## Install
16
+
17
+ ```sh
18
+ pip install spice-harness # or: uv tool install spice-harness
19
+ cd /path/to/your/repo
20
+ spice init # hooks, spice.sh shim, state scaffolding
21
+ spice dev doctor # verify drivers, backends, and policy
22
+ ```
23
+
24
+ `spice init` writes machine-local git hook shims under `.spice/` (ignored via
25
+ `.git/info/exclude`) and a tracked `spice.sh` shim. Repo-tracked policy lives
26
+ in your `pyproject.toml` under `[tool.spice.*]` tables. Entrypoint resolution
27
+ is worktree-true: when the current repo is the spice source checkout, generated
28
+ shims and supervisor children put that checkout first on `PYTHONPATH` and run
29
+ `python -m spice`; ordinary target repos use the installed product.
30
+
31
+ A project can set its default supervised-agent launch model and thinking in
32
+ tracked config, either by editing `pyproject.toml` or by running
33
+ `spice config agent --scope project --model ... --thinking ...`:
34
+
35
+ ```toml
36
+ [tool.spice.agent]
37
+ model = "gpt-5.4"
38
+ thinking = "low"
39
+ ```
40
+
41
+ An operator can override those defaults for just the current worktree:
42
+
43
+ ```sh
44
+ spice config agent --scope worktree --model gpt-5.4 --thinking low
45
+ ```
46
+
47
+ Resolution order is explicit launch flags, then worktree config, then tracked
48
+ project config, then the driver defaults.
49
+
50
+ A repo can also mount its own tooling into the `spice` namespace:
51
+
52
+ ```toml
53
+ [tool.spice.commands]
54
+ deploy = "./scripts/deploy.sh"
55
+ bench = ["python", "-m", "myproj.bench"]
56
+ ```
57
+
58
+ `spice deploy --env staging` then runs the mounted command from the repo
59
+ root with the remaining arguments passed through verbatim. Built-in verbs
60
+ always win; a mount that shadows one fails loudly.
61
+
62
+ Mounted names are intentionally one-level verbs (`^[a-z][a-z0-9-]*$`), not
63
+ nested command paths. A repo with a large tool family mounts one namespace
64
+ owner and keeps family grouping inside that repo tool's own arguments:
65
+
66
+ ```toml
67
+ [tool.spice.commands]
68
+ toolbox = ["uv", "run", "toolbox"]
69
+ ```
70
+
71
+ `spice toolbox lint css --fix` then dispatches `lint css --fix` to `toolbox`.
72
+ Do not encode families as dotted, spaced, or ad-hoc hyphenated spice mount
73
+ names such as `lint.css`, `lint css`, or one mount per subcommand; those
74
+ groupings belong behind the mounted repo tool's explicit contract.
75
+
76
+ ### Library seam for repo tools
77
+
78
+ Mounted commands and tracked pre-commit extensions may import a deliberately
79
+ narrow Python seam from `spice` instead of vendoring harness scaffolding. This
80
+ surface is source-stable for target repos: public names in the modules listed
81
+ below are not removed or renamed silently, and incompatible changes require an
82
+ explicit contract update. Underscored names remain private.
83
+
84
+ - `spice.errors`: `SpiceError` for user-facing command failures.
85
+ - `spice.policy`: constitution constants and `flex_limit`.
86
+ - `spice.flexstate`: flex-limit sticky-state persistence and rename helpers.
87
+ - `spice.locking`: cross-platform advisory file locks.
88
+ - `spice.paths`: repo-root, state-dir, atomic write, and tool-resolution helpers.
89
+ - `spice.procs`: process-group spawn, liveness, and termination helpers.
90
+ - `spice.repocfg`: tracked `[tool.spice]` table readers.
91
+ - `spice.studies.walk`: tracked/staged path walkers, repo policy exclusions,
92
+ staged renames, and git blob reads.
93
+ - `spice.studies.fileloc`, `spice.studies.complexity`,
94
+ `spice.studies.magicnums`, and `spice.studies.envpolicy`: finding
95
+ dataclasses plus `scan_*`, `detect_*`, and `render_*_board` helpers for
96
+ project-specific studies.
97
+
98
+ Everything else is an internal implementation detail unless this section names
99
+ it. A repo tool that needs an unlisted module should either vendor that helper
100
+ or first add the helper to this seam with tests and a stability note.
101
+
102
+ ## The loop
103
+
104
+ | Surface | Command | What it does |
105
+ | --- | --- | --- |
106
+ | Wrapper | `spice agent run -- <cmd>` (or `./spice.sh`) | Runs shell commands with proxy routing, git-shadow env, and steering injection on stderr. |
107
+ | Lifecycle | `spice agent ensure` / `supervise` | One worktree-bound agent per worktree, started under a neutral skill prompt, watched by a durable supervisor. |
108
+ | Steering | filesystem inbox under `.spice/inbox/` | Durable operator messages; items retire only when the agent semantically ACKs their key in its transcript. |
109
+ | Tasks | `spice task …` | Phase-native Taskwarrior board shared by all worktrees; `task next` is allocator-owned; git sync happens at task boundaries. |
110
+ | Sessions | `spice session` | Transcript forensics: the no-arg briefing is the primary rehydration product, with context-pressure metering. |
111
+ | Cockpit | `spice serve` | Localhost web UI: lanes over worktrees, live transcript streams, lifetime control (Renew / Steer / Drive), task-filter routing, fused lane groups backed by server-side teams; `spice serve teams` and `spice serve browser-artifact-path <file>` expose operator diagnostics for smoke runs. |
112
+ | Conscience | `spice maxim …` | Builtin maxims judged against assistant prose by a local model; violations come back as inbox steering. |
113
+ | Constitution | `spice dev pre-commit` / `spice study …` | Namespace packages, path shape, LOC/byte/complexity flex+sticky gates, magic-number ratchet, env-literal inventory, commit-message policy. |
114
+
115
+ Session analysis is intentionally tiered. The current tier includes
116
+ `spice session phases` for contiguous working-phase spans and
117
+ `spice session messages` for message-level side/phase/flavor filtering.
118
+ Deeper report families that depend on richer topic/bucket modeling belong in
119
+ a separate analytics tier after the basic phase/message surfaces harden.
120
+
121
+ ## The constitution
122
+
123
+ The pre-commit gate is the executable form of the project's opinions — see
124
+ [spice/policy.py](spice/policy.py). Highlights:
125
+
126
+ - namespace packages only; no `__init__.py` under declared package roots;
127
+ - file names match `^_*[0-9a-z]+_*$`; splitting a file requires naming the
128
+ seam (no `*2.py`, no generic continuation shards);
129
+ - files flex to 1500 lines but a breach holds them to 1000 until they shrink;
130
+ routines flex the same way around CCN 20 / length 80;
131
+ - magic-number regressions are a ratchet against `HEAD`, not an amnesty;
132
+ - env-literal inventory covers `SPICE_*` and `CODEX_THREAD_ID` by default;
133
+ target repos can add tracked name regexes with
134
+ `[tool.spice.policy] env_name_patterns`;
135
+ - commit subjects fit in 100 columns; bodies are auto-folded.
136
+
137
+ The gate applies to spice itself: this repository is its own first target.
138
+ Target repos can keep their own tracked gate lanes under the same hook by
139
+ declaring mounted commands and pre-commit policy:
140
+
141
+ ```toml
142
+ [tool.spice.commands]
143
+ fmt-cs = ["dotnet", "format", "--verify-no-changes"]
144
+
145
+ [tool.spice.policy]
146
+ pre_commit = ["fmt-cs", { label = "assets", run = ["python3", "-m", "tools.assets"] }]
147
+ pre_commit_success = [{ label = "clear asset sticky state", run = ["python3", "-m", "tools.assets", "--clear-sticky"] }]
148
+
149
+ [tool.spice.policy.pre_commit_builtins]
150
+ formatters = false
151
+ "magic-numbers" = { label = "project magic", run = ["python3", "-m", "tools.magic"] }
152
+ ```
153
+
154
+ Built-in pre-commit keys are `repo-shape`, `staging`, `repo-docs`,
155
+ `formatters`, `env-policy`, `file-shape`, `complexity`, and `magic-numbers`.
156
+ They run before extension steps unless an individual built-in is disabled or
157
+ replaced in tracked policy. `pre_commit_success` uses the same command shape as
158
+ `pre_commit`, but runs only after the whole gate has passed, alongside sticky
159
+ state cleanup.
160
+
161
+ ## Status
162
+
163
+ Work in progress: an extraction-in-progress toward a standalone, releasable
164
+ product. Surfaces are still settling; the loop described above is real and
165
+ exercised daily, and this repository is its own first target.
@@ -0,0 +1,55 @@
1
+ [build-system]
2
+ requires = ["setuptools>=80", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "spice-harness"
7
+ version = "0.1.0"
8
+ description = "Simultaneous Production, Integration, and Control Environment: an agent harness for coding repositories."
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = "MIT"
12
+ dependencies = [
13
+ "lizard>=1.21",
14
+ "ruff>=0.11",
15
+ "watchfiles>=1.1",
16
+ ]
17
+
18
+ [project.urls]
19
+ Homepage = "https://github.com/infimalabs/spice"
20
+ Repository = "https://github.com/infimalabs/spice"
21
+ Issues = "https://github.com/infimalabs/spice/issues"
22
+
23
+ [project.scripts]
24
+ spice = "spice.cli.entry:main"
25
+
26
+ [tool.setuptools.packages.find]
27
+ where = ["."]
28
+ include = ["spice*"]
29
+ namespaces = true
30
+
31
+ [tool.setuptools.package-data]
32
+ "spice.agent" = ["*.md"]
33
+ "spice.serve.static" = ["*.css", "*.js", "*.ico"]
34
+
35
+ [dependency-groups]
36
+ dev = [
37
+ "pytest>=8.0",
38
+ ]
39
+
40
+ [tool.ruff]
41
+ target-version = "py312"
42
+ line-length = 88
43
+
44
+ [tool.pytest.ini_options]
45
+ testpaths = ["tests"]
46
+
47
+ [tool.spice.policy]
48
+ package_roots = ["spice"]
49
+
50
+ [tool.spice.agent]
51
+ model = "gpt-5.5"
52
+ thinking = "xhigh"
53
+
54
+ [tool.spice.tasks]
55
+ stems = ["session", "mail", "hooks", "studies", "cli", "lifecycle", "tests"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,10 @@
1
+ """`python -m spice` — the installation-independent entrypoint.
2
+
3
+ The agent supervisor respawns through this module so a detached process finds
4
+ the same interpreter. Worktree source checkouts are put first on PYTHONPATH;
5
+ ordinary target repos use the installed package.
6
+ """
7
+
8
+ from spice.cli.entry import main
9
+
10
+ raise SystemExit(main())
@@ -0,0 +1,42 @@
1
+ ---
2
+ name: spice
3
+ description: Start or resume a worktree-bound spice agent from a neutral skill prompt. Use when a server or wrapper launches an agent for an existing worktree and the initial prompt must not contain operator prose.
4
+ metadata:
5
+ short-description: Worktree-bound spice agent bootstrap
6
+ ---
7
+
8
+ # spice
9
+
10
+ You were started by spice, the Simultaneous Production, Integration, and
11
+ Control Environment. The initial prompt is only a bootstrap signal, not the
12
+ operator's request.
13
+
14
+ Before sending any assistant prose, run these commands in this order:
15
+
16
+ 1. `spice agent activation`
17
+ 2. `spice session`
18
+ 3. `spice task status`
19
+
20
+ If a tool call is impossible, say only what prevented it and stop. Otherwise, let the command outputs establish context first, then respond to pending steering directly. Use those outputs, side-channel steering, and the active task board as your source of truth. Do not infer a durable task from this skill invocation.
21
+
22
+ If continuity is clipped, deepen with `spice session sweep --count N`, `spice session timeline --limit N`, `spice session turns --turn-id ... --view full`, `spice session compactions`, or `spice session commits`.
23
+
24
+ ## Working Rules
25
+
26
+ - Stay in the current worktree unless live steering explicitly changes scope.
27
+ - Recover lane identity from current repo state and `spice agent activation`; do not trust prior messages over current worktree state.
28
+ - Run shell commands through `./spice.sh` when the repo provides it, or `spice agent run -- <command>` otherwise.
29
+ - Use `./spice.sh proxy <command>` when a command must keep native argument and output semantics; with no proxy installed the wrapper drops `proxy` and runs the command directly, while steering injection still applies.
30
+ - Pull work with `spice task next`, not by eyeballing a board. `task next` returns the globally-best ready task across all open boards and claims it; the selected board is derived from the claimed task, not stored as a hidden default.
31
+ - Completing a task phase advances it: use `spice task done <handle> --validation "..."` to move a task from implementation into its review phase, then run `spice task next` for reviewer assignment. Do not manually claim your own review; if `task next` assigns it anyway, treat that as an allocator assignment and verify the task description is current before `spice task review <handle> --finding clean --note "description current; ..."`. Read the printed `advanced ... -> <phase>` / `completed ...` line, then run `task next` again; a task is not finished while later phases remain.
32
+ - Use `spice task add` for backlog items and `spice task note` for small observations attached to a task.
33
+ - When the tooling itself fights you (weak default, surprising output, a command that did not work as emitted), record it with `spice task oops "..." --severity ... --kind ...`. It files the friction as a task on the deferred `oops` triage board (a human works that hatch); capture the speed bump rather than silently working around it.
34
+ - Read side-channel steering before acting and acknowledge it through the normal agent workflow.
35
+ - Use `SAY:` in an assistant message only for genuine blockers, decisions worth operator attention, or important milestones. Do not shell out to `say` directly.
36
+ - Treat a dirty worktree as pressure toward commit, split, or cleanup.
37
+ - Do not spawn sub-agents.
38
+ - Keep going while progress is real, but let the selected task, its board, and task notes shape the work instead of treating this skill as a standing user demand.
39
+
40
+ ## Prompt Boundary
41
+
42
+ The wrapper must never pass operator prose as the initial prompt. If you need the current ask, recover it from `spice session`, `spice task status`, and side-channel messages.
@@ -0,0 +1,60 @@
1
+ """The activation packet: the contract an agent reads before doing anything.
2
+
3
+ `spice agent activation` is the first command a freshly started worktree
4
+ agent runs (the skill mandates it). It binds the ambient thread id into the
5
+ lane's agent state, installs the git hooks, refreshes the baseline when safe,
6
+ and prints the working contract: git hygiene, validation expectations, and
7
+ the command surface that is the agent's source of truth.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from pathlib import Path
13
+
14
+
15
+ def activation_git_hygiene_lines() -> list[str]:
16
+ return [
17
+ (
18
+ "work_commit_contract=commit your changes into coherent, validated "
19
+ "local history before completing a task; amend or reshape your own "
20
+ "commits freely while iterating — task done captures exactly the "
21
+ "commits you made"
22
+ ),
23
+ (
24
+ "work_focus_contract=you only ever manage your own local git state; "
25
+ "synchronizing it with everyone else's work happens automatically at "
26
+ "task boundaries, so there is nothing to pull or push — just build "
27
+ "tasks and complete them"
28
+ ),
29
+ ]
30
+
31
+
32
+ def activation_source_root_lines(repo_root: Path) -> list[str]:
33
+ return [f"project_source_root={repo_root.resolve()}"]
34
+
35
+
36
+ def activation_browser_validation_lines() -> list[str]:
37
+ return [
38
+ (
39
+ "browser_validation_contract=for executable live browser checks, "
40
+ "start with the Playwright MCP server named playwright; if no live "
41
+ "browser path is available, report that browser coverage did not "
42
+ "run and restore Playwright MCP before continuing; do not "
43
+ "substitute static tests or non-browser checks for required "
44
+ "browser coverage"
45
+ )
46
+ ]
47
+
48
+
49
+ def activation_command_surface_lines() -> list[str]:
50
+ return [
51
+ "session=spice session",
52
+ "task_status=spice task status",
53
+ "task_next=spice task next",
54
+ "task_show=spice task show <handle>",
55
+ "tasks=spice task list",
56
+ 'task_done=spice task done <handle> --validation "..."',
57
+ "inbox_steering=automatic wrapper/side-channel injection; no public mail command",
58
+ "side_channel=operator steering arrives through the supervisor socket",
59
+ "initial_prompt_policy=skill_invocation_only",
60
+ ]