stayfixed 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 (441) hide show
  1. stayfixed-0.1.0/.claude-plugin/marketplace.json +12 -0
  2. stayfixed-0.1.0/.claude-plugin/plugin.json +29 -0
  3. stayfixed-0.1.0/.codex-plugin/plugin.json +14 -0
  4. stayfixed-0.1.0/CHANGELOG.md +373 -0
  5. stayfixed-0.1.0/CODE_OF_CONDUCT.md +38 -0
  6. stayfixed-0.1.0/CONTRIBUTING.md +311 -0
  7. stayfixed-0.1.0/LICENSE +21 -0
  8. stayfixed-0.1.0/PKG-INFO +421 -0
  9. stayfixed-0.1.0/README.md +395 -0
  10. stayfixed-0.1.0/RELEASING.md +349 -0
  11. stayfixed-0.1.0/SECURITY.md +74 -0
  12. stayfixed-0.1.0/agents/code-navigator.md +56 -0
  13. stayfixed-0.1.0/changelog.d/.gitkeep +0 -0
  14. stayfixed-0.1.0/docs/cli.md +2921 -0
  15. stayfixed-0.1.0/docs/methodology/README.md +66 -0
  16. stayfixed-0.1.0/docs/methodology/principles.md +250 -0
  17. stayfixed-0.1.0/docs/methodology/sources.md +40 -0
  18. stayfixed-0.1.0/docs/plans/2026-09-05-agent-harness-foundation.md +2672 -0
  19. stayfixed-0.1.0/docs/plans/2026-09-05-agent-harness-p0-spikes.md +2715 -0
  20. stayfixed-0.1.0/docs/plans/2026-09-12-memory-engine.md +4258 -0
  21. stayfixed-0.1.0/docs/plans/2026-09-12-scaffold.md +3157 -0
  22. stayfixed-0.1.0/docs/plans/2026-09-15-guards.md +4144 -0
  23. stayfixed-0.1.0/docs/plans/2026-09-16-wave-2-closure-ledger-docs-skills.md +6150 -0
  24. stayfixed-0.1.0/docs/plans/2026-09-16-wave-2-closure-readme-notes.md +2434 -0
  25. stayfixed-0.1.0/docs/plans/2026-09-17-wave-3-install-path.md +3391 -0
  26. stayfixed-0.1.0/docs/plans/2026-09-19-wave-3-closure.md +5610 -0
  27. stayfixed-0.1.0/docs/plans/2026-09-28-rename-stayfixed.md +385 -0
  28. stayfixed-0.1.0/docs/plans/README.md +14 -0
  29. stayfixed-0.1.0/hooks/hashes.json +8 -0
  30. stayfixed-0.1.0/hooks/hooks.json +101 -0
  31. stayfixed-0.1.0/hooks/run-hook.sh +323 -0
  32. stayfixed-0.1.0/mutations.toml +12504 -0
  33. stayfixed-0.1.0/pyproject.toml +165 -0
  34. stayfixed-0.1.0/pyproject.toml.orig +160 -0
  35. stayfixed-0.1.0/scripts/check_artifacts.py +132 -0
  36. stayfixed-0.1.0/scripts/mutation_oracle.py +935 -0
  37. stayfixed-0.1.0/scripts/smoke_exfiltration.py +413 -0
  38. stayfixed-0.1.0/scripts/smoke_hooks.py +377 -0
  39. stayfixed-0.1.0/scripts/stayfixed +18 -0
  40. stayfixed-0.1.0/skills/README.md +32 -0
  41. stayfixed-0.1.0/skills/attach/SKILL.md +31 -0
  42. stayfixed-0.1.0/skills/attribute-failure/SKILL.md +29 -0
  43. stayfixed-0.1.0/skills/attribute-failure/references/baselines.md +32 -0
  44. stayfixed-0.1.0/skills/close-bug/SKILL.md +21 -0
  45. stayfixed-0.1.0/skills/doctor/SKILL.md +35 -0
  46. stayfixed-0.1.0/skills/file-bug/SKILL.md +29 -0
  47. stayfixed-0.1.0/skills/init/SKILL.md +58 -0
  48. stayfixed-0.1.0/skills/init/references/adoption.md +53 -0
  49. stayfixed-0.1.0/skills/init/references/asking.md +25 -0
  50. stayfixed-0.1.0/skills/memory-sweep/SKILL.md +31 -0
  51. stayfixed-0.1.0/skills/memory-sweep/references/protocol.md +54 -0
  52. stayfixed-0.1.0/skills/retro-to-guard/SKILL.md +35 -0
  53. stayfixed-0.1.0/skills/review-plan-three-lenses/SKILL.md +35 -0
  54. stayfixed-0.1.0/skills/review-plan-three-lenses/references/lenses.md +26 -0
  55. stayfixed-0.1.0/skills/run-correctness-audit/SKILL.md +32 -0
  56. stayfixed-0.1.0/skills/run-correctness-audit/references/protocol.md +26 -0
  57. stayfixed-0.1.0/skills/setup/SKILL.md +44 -0
  58. stayfixed-0.1.0/skills/sweep-defect-class/SKILL.md +30 -0
  59. stayfixed-0.1.0/skills/sweep-defect-class/references/vacuous-oracles.md +31 -0
  60. stayfixed-0.1.0/skills/uninstall/SKILL.md +46 -0
  61. stayfixed-0.1.0/skills/upgrade/SKILL.md +59 -0
  62. stayfixed-0.1.0/src/stayfixed/__init__.py +9 -0
  63. stayfixed-0.1.0/src/stayfixed/__main__.py +3 -0
  64. stayfixed-0.1.0/src/stayfixed/areas.py +56 -0
  65. stayfixed-0.1.0/src/stayfixed/assess/__init__.py +1 -0
  66. stayfixed-0.1.0/src/stayfixed/assess/assessment.py +206 -0
  67. stayfixed-0.1.0/src/stayfixed/assess/commands.py +385 -0
  68. stayfixed-0.1.0/src/stayfixed/assess/gates.py +317 -0
  69. stayfixed-0.1.0/src/stayfixed/assess/model.py +48 -0
  70. stayfixed-0.1.0/src/stayfixed/assess/probes.py +704 -0
  71. stayfixed-0.1.0/src/stayfixed/assess/report.py +211 -0
  72. stayfixed-0.1.0/src/stayfixed/assess/rule.py +426 -0
  73. stayfixed-0.1.0/src/stayfixed/assess/state.py +265 -0
  74. stayfixed-0.1.0/src/stayfixed/assess/tracked.py +321 -0
  75. stayfixed-0.1.0/src/stayfixed/attach/__init__.py +7 -0
  76. stayfixed-0.1.0/src/stayfixed/attach/api.py +68 -0
  77. stayfixed-0.1.0/src/stayfixed/attach/binding.py +231 -0
  78. stayfixed-0.1.0/src/stayfixed/attach/commands.py +176 -0
  79. stayfixed-0.1.0/src/stayfixed/attach/hooks.py +225 -0
  80. stayfixed-0.1.0/src/stayfixed/attach/permissions.py +332 -0
  81. stayfixed-0.1.0/src/stayfixed/attach/write.py +1257 -0
  82. stayfixed-0.1.0/src/stayfixed/cli.py +183 -0
  83. stayfixed-0.1.0/src/stayfixed/command.py +91 -0
  84. stayfixed-0.1.0/src/stayfixed/config/__init__.py +5 -0
  85. stayfixed-0.1.0/src/stayfixed/config/layout.py +60 -0
  86. stayfixed-0.1.0/src/stayfixed/config/loader.py +600 -0
  87. stayfixed-0.1.0/src/stayfixed/config/machine.py +57 -0
  88. stayfixed-0.1.0/src/stayfixed/config/owned.py +157 -0
  89. stayfixed-0.1.0/src/stayfixed/config/paths.py +165 -0
  90. stayfixed-0.1.0/src/stayfixed/config/schema.py +263 -0
  91. stayfixed-0.1.0/src/stayfixed/docs/__init__.py +5 -0
  92. stayfixed-0.1.0/src/stayfixed/docs/api.py +88 -0
  93. stayfixed-0.1.0/src/stayfixed/docs/commands.py +181 -0
  94. stayfixed-0.1.0/src/stayfixed/docs/graph.py +51 -0
  95. stayfixed-0.1.0/src/stayfixed/docs/hygiene.py +207 -0
  96. stayfixed-0.1.0/src/stayfixed/docs/plans.py +413 -0
  97. stayfixed-0.1.0/src/stayfixed/docs/themes.py +153 -0
  98. stayfixed-0.1.0/src/stayfixed/docs/trail.py +402 -0
  99. stayfixed-0.1.0/src/stayfixed/doctor/__init__.py +37 -0
  100. stayfixed-0.1.0/src/stayfixed/doctor/api.py +36 -0
  101. stayfixed-0.1.0/src/stayfixed/doctor/checks.py +1806 -0
  102. stayfixed-0.1.0/src/stayfixed/doctor/commands.py +113 -0
  103. stayfixed-0.1.0/src/stayfixed/errors.py +13 -0
  104. stayfixed-0.1.0/src/stayfixed/findings.py +66 -0
  105. stayfixed-0.1.0/src/stayfixed/fsops.py +448 -0
  106. stayfixed-0.1.0/src/stayfixed/gitenv.py +272 -0
  107. stayfixed-0.1.0/src/stayfixed/guards/__init__.py +5 -0
  108. stayfixed-0.1.0/src/stayfixed/guards/api.py +57 -0
  109. stayfixed-0.1.0/src/stayfixed/guards/attribute.py +203 -0
  110. stayfixed-0.1.0/src/stayfixed/guards/audit.py +562 -0
  111. stayfixed-0.1.0/src/stayfixed/guards/bashscan.py +823 -0
  112. stayfixed-0.1.0/src/stayfixed/guards/bgcleanup.py +739 -0
  113. stayfixed-0.1.0/src/stayfixed/guards/commands.py +340 -0
  114. stayfixed-0.1.0/src/stayfixed/guards/commit.py +488 -0
  115. stayfixed-0.1.0/src/stayfixed/guards/githooks.py +180 -0
  116. stayfixed-0.1.0/src/stayfixed/guards/hooks.py +111 -0
  117. stayfixed-0.1.0/src/stayfixed/guards/hygiene.py +253 -0
  118. stayfixed-0.1.0/src/stayfixed/guards/roots.py +69 -0
  119. stayfixed-0.1.0/src/stayfixed/harnesses.py +88 -0
  120. stayfixed-0.1.0/src/stayfixed/hooks/__init__.py +1 -0
  121. stayfixed-0.1.0/src/stayfixed/hooks/api.py +178 -0
  122. stayfixed-0.1.0/src/stayfixed/hooks/commands.py +97 -0
  123. stayfixed-0.1.0/src/stayfixed/hooks/dispatch.py +295 -0
  124. stayfixed-0.1.0/src/stayfixed/hooks/policy.py +29 -0
  125. stayfixed-0.1.0/src/stayfixed/hooks/registry.py +54 -0
  126. stayfixed-0.1.0/src/stayfixed/hooks/sink.py +209 -0
  127. stayfixed-0.1.0/src/stayfixed/identifiers.py +80 -0
  128. stayfixed-0.1.0/src/stayfixed/ledger/__init__.py +6 -0
  129. stayfixed-0.1.0/src/stayfixed/ledger/api.py +62 -0
  130. stayfixed-0.1.0/src/stayfixed/ledger/check.py +368 -0
  131. stayfixed-0.1.0/src/stayfixed/ledger/commands.py +136 -0
  132. stayfixed-0.1.0/src/stayfixed/ledger/entries.py +307 -0
  133. stayfixed-0.1.0/src/stayfixed/ledger/index.py +281 -0
  134. stayfixed-0.1.0/src/stayfixed/ledger/scan.py +307 -0
  135. stayfixed-0.1.0/src/stayfixed/ledger/write.py +363 -0
  136. stayfixed-0.1.0/src/stayfixed/memory/__init__.py +1 -0
  137. stayfixed-0.1.0/src/stayfixed/memory/api.py +164 -0
  138. stayfixed-0.1.0/src/stayfixed/memory/bundles.py +326 -0
  139. stayfixed-0.1.0/src/stayfixed/memory/commands.py +488 -0
  140. stayfixed-0.1.0/src/stayfixed/memory/hooks.py +126 -0
  141. stayfixed-0.1.0/src/stayfixed/memory/index.py +547 -0
  142. stayfixed-0.1.0/src/stayfixed/memory/inventory.py +77 -0
  143. stayfixed-0.1.0/src/stayfixed/memory/notes.py +447 -0
  144. stayfixed-0.1.0/src/stayfixed/memory/refs.py +173 -0
  145. stayfixed-0.1.0/src/stayfixed/memory/store.py +572 -0
  146. stayfixed-0.1.0/src/stayfixed/memory/trust.py +381 -0
  147. stayfixed-0.1.0/src/stayfixed/memory/worktree.py +647 -0
  148. stayfixed-0.1.0/src/stayfixed/overlay/__init__.py +1 -0
  149. stayfixed-0.1.0/src/stayfixed/overlay/api.py +87 -0
  150. stayfixed-0.1.0/src/stayfixed/overlay/commands.py +155 -0
  151. stayfixed-0.1.0/src/stayfixed/overlay/create.py +373 -0
  152. stayfixed-0.1.0/src/stayfixed/overlay/identity.py +108 -0
  153. stayfixed-0.1.0/src/stayfixed/overlay/layout.py +69 -0
  154. stayfixed-0.1.0/src/stayfixed/overlay/publish.py +329 -0
  155. stayfixed-0.1.0/src/stayfixed/overlay/requires.py +127 -0
  156. stayfixed-0.1.0/src/stayfixed/overlay/sync.py +37 -0
  157. stayfixed-0.1.0/src/stayfixed/overlay/template.py +80 -0
  158. stayfixed-0.1.0/src/stayfixed/overlay/upgrade.py +100 -0
  159. stayfixed-0.1.0/src/stayfixed/presets/__init__.py +54 -0
  160. stayfixed-0.1.0/src/stayfixed/presets/recommended.toml +184 -0
  161. stayfixed-0.1.0/src/stayfixed/printed.py +61 -0
  162. stayfixed-0.1.0/src/stayfixed/profiles/__init__.py +71 -0
  163. stayfixed-0.1.0/src/stayfixed/profiles/evaluate.py +248 -0
  164. stayfixed-0.1.0/src/stayfixed/profiles/model.py +197 -0
  165. stayfixed-0.1.0/src/stayfixed/profiles/python/profile.toml +129 -0
  166. stayfixed-0.1.0/src/stayfixed/profiles/python/rules.md +52 -0
  167. stayfixed-0.1.0/src/stayfixed/project/__init__.py +8 -0
  168. stayfixed-0.1.0/src/stayfixed/project/api.py +53 -0
  169. stayfixed-0.1.0/src/stayfixed/project/commands.py +463 -0
  170. stayfixed-0.1.0/src/stayfixed/project/detect.py +198 -0
  171. stayfixed-0.1.0/src/stayfixed/project/footprint.py +255 -0
  172. stayfixed-0.1.0/src/stayfixed/project/ignored.py +151 -0
  173. stayfixed-0.1.0/src/stayfixed/project/init.py +431 -0
  174. stayfixed-0.1.0/src/stayfixed/project/layout.py +25 -0
  175. stayfixed-0.1.0/src/stayfixed/project/questions.py +168 -0
  176. stayfixed-0.1.0/src/stayfixed/project/rewrite.py +62 -0
  177. stayfixed-0.1.0/src/stayfixed/project/templates.py +644 -0
  178. stayfixed-0.1.0/src/stayfixed/project/uninstall.py +442 -0
  179. stayfixed-0.1.0/src/stayfixed/project/upgrade.py +243 -0
  180. stayfixed-0.1.0/src/stayfixed/prose.py +95 -0
  181. stayfixed-0.1.0/src/stayfixed/release/__init__.py +1 -0
  182. stayfixed-0.1.0/src/stayfixed/release/api.py +45 -0
  183. stayfixed-0.1.0/src/stayfixed/release/commands.py +83 -0
  184. stayfixed-0.1.0/src/stayfixed/release/hashes.py +133 -0
  185. stayfixed-0.1.0/src/stayfixed/release/notes.py +33 -0
  186. stayfixed-0.1.0/src/stayfixed/release/pins.py +75 -0
  187. stayfixed-0.1.0/src/stayfixed/release/versions.py +239 -0
  188. stayfixed-0.1.0/src/stayfixed/result.py +13 -0
  189. stayfixed-0.1.0/src/stayfixed/runner.py +152 -0
  190. stayfixed-0.1.0/src/stayfixed/scaffold/__init__.py +97 -0
  191. stayfixed-0.1.0/src/stayfixed/scaffold/engine.py +715 -0
  192. stayfixed-0.1.0/src/stayfixed/scaffold/entries.py +168 -0
  193. stayfixed-0.1.0/src/stayfixed/scaffold/local.py +182 -0
  194. stayfixed-0.1.0/src/stayfixed/scaffold/manifest.py +182 -0
  195. stayfixed-0.1.0/src/stayfixed/scaffold/model.py +94 -0
  196. stayfixed-0.1.0/src/stayfixed/scaffold/regions.py +108 -0
  197. stayfixed-0.1.0/src/stayfixed/scaffold/report.py +59 -0
  198. stayfixed-0.1.0/src/stayfixed/setup/__init__.py +7 -0
  199. stayfixed-0.1.0/src/stayfixed/setup/api.py +40 -0
  200. stayfixed-0.1.0/src/stayfixed/setup/commands.py +189 -0
  201. stayfixed-0.1.0/src/stayfixed/setup/machine.py +176 -0
  202. stayfixed-0.1.0/src/stayfixed/setup/run.py +954 -0
  203. stayfixed-0.1.0/src/stayfixed/templates/__init__.py +23 -0
  204. stayfixed-0.1.0/src/stayfixed/templates/overlay/.claude-plugin/marketplace.json +11 -0
  205. stayfixed-0.1.0/src/stayfixed/templates/overlay/.claude-plugin/plugin.json +8 -0
  206. stayfixed-0.1.0/src/stayfixed/templates/overlay/.codex-plugin/plugin.json +10 -0
  207. stayfixed-0.1.0/src/stayfixed/templates/overlay/.github/dependabot.yml +15 -0
  208. stayfixed-0.1.0/src/stayfixed/templates/overlay/.github/workflows/scan.yml +29 -0
  209. stayfixed-0.1.0/src/stayfixed/templates/overlay/.gitignore +6 -0
  210. stayfixed-0.1.0/src/stayfixed/templates/overlay/.pre-commit-config.yaml +8 -0
  211. stayfixed-0.1.0/src/stayfixed/templates/overlay/README.md +58 -0
  212. stayfixed-0.1.0/src/stayfixed/templates/overlay/common/claude/hooks.json +3 -0
  213. stayfixed-0.1.0/src/stayfixed/templates/overlay/common/claude/permissions.json +3 -0
  214. stayfixed-0.1.0/src/stayfixed/templates/overlay/common/codex/common.rules +2 -0
  215. stayfixed-0.1.0/src/stayfixed/templates/overlay/common/memory/README.md +11 -0
  216. stayfixed-0.1.0/src/stayfixed/templates/overlay/common/rules/README.md +12 -0
  217. stayfixed-0.1.0/src/stayfixed/templates/overlay/hooks/hooks.json +3 -0
  218. stayfixed-0.1.0/src/stayfixed/templates/overlay/projects/README.md +11 -0
  219. stayfixed-0.1.0/src/stayfixed/templates/overlay/skills/attach/SKILL.md +20 -0
  220. stayfixed-0.1.0/src/stayfixed/templates/project/adr-template.md +22 -0
  221. stayfixed-0.1.0/src/stayfixed/templates/project/agents-region.md +8 -0
  222. stayfixed-0.1.0/src/stayfixed/templates/project/agents-skeleton.md +9 -0
  223. stayfixed-0.1.0/src/stayfixed/templates/project/audits-readme.md +9 -0
  224. stayfixed-0.1.0/src/stayfixed/templates/project/bug-reports-runbook.md +14 -0
  225. stayfixed-0.1.0/src/stayfixed/templates/project/claude.md +1 -0
  226. stayfixed-0.1.0/src/stayfixed/templates/project/documentation.md +26 -0
  227. stayfixed-0.1.0/src/stayfixed/templates/project/gitkeep +0 -0
  228. stayfixed-0.1.0/src/stayfixed/templates/project/roadmap-history.md +3 -0
  229. stayfixed-0.1.0/src/stayfixed/templates/project/roadmap.md +27 -0
  230. stayfixed-0.1.0/src/stayfixed/templates/project/stayfixed.yml +34 -0
  231. stayfixed-0.1.0/src/stayfixed/templates/project/trail.toml +9 -0
  232. stayfixed-0.1.0/src/stayfixed/tomlout.py +190 -0
  233. stayfixed-0.1.0/tests/__init__.py +0 -0
  234. stayfixed-0.1.0/tests/assess/__init__.py +0 -0
  235. stayfixed-0.1.0/tests/assess/baserepo.py +54 -0
  236. stayfixed-0.1.0/tests/assess/smoke.py +35 -0
  237. stayfixed-0.1.0/tests/assess/test_assessment.py +138 -0
  238. stayfixed-0.1.0/tests/assess/test_command.py +268 -0
  239. stayfixed-0.1.0/tests/assess/test_gate_command.py +967 -0
  240. stayfixed-0.1.0/tests/assess/test_gates.py +484 -0
  241. stayfixed-0.1.0/tests/assess/test_neutral_rows.py +68 -0
  242. stayfixed-0.1.0/tests/assess/test_principles.py +36 -0
  243. stayfixed-0.1.0/tests/assess/test_probes.py +1106 -0
  244. stayfixed-0.1.0/tests/assess/test_read_base.py +331 -0
  245. stayfixed-0.1.0/tests/assess/test_report.py +231 -0
  246. stayfixed-0.1.0/tests/assess/test_rule.py +437 -0
  247. stayfixed-0.1.0/tests/assess/test_state.py +800 -0
  248. stayfixed-0.1.0/tests/assess/test_tracked.py +701 -0
  249. stayfixed-0.1.0/tests/attach/__init__.py +0 -0
  250. stayfixed-0.1.0/tests/attach/test_binding.py +473 -0
  251. stayfixed-0.1.0/tests/attach/test_commands.py +394 -0
  252. stayfixed-0.1.0/tests/attach/test_detach.py +662 -0
  253. stayfixed-0.1.0/tests/attach/test_hooks.py +482 -0
  254. stayfixed-0.1.0/tests/attach/test_links.py +406 -0
  255. stayfixed-0.1.0/tests/attach/test_surface.py +47 -0
  256. stayfixed-0.1.0/tests/attach/test_write.py +1143 -0
  257. stayfixed-0.1.0/tests/cli.py +32 -0
  258. stayfixed-0.1.0/tests/config/__init__.py +0 -0
  259. stayfixed-0.1.0/tests/config/test_deep_toml.py +130 -0
  260. stayfixed-0.1.0/tests/config/test_layout.py +53 -0
  261. stayfixed-0.1.0/tests/config/test_loader.py +757 -0
  262. stayfixed-0.1.0/tests/config/test_machine.py +61 -0
  263. stayfixed-0.1.0/tests/config/test_owned.py +206 -0
  264. stayfixed-0.1.0/tests/config/test_paths.py +394 -0
  265. stayfixed-0.1.0/tests/config/test_schema.py +29 -0
  266. stayfixed-0.1.0/tests/config/test_symlinked_document.py +91 -0
  267. stayfixed-0.1.0/tests/conftest.py +75 -0
  268. stayfixed-0.1.0/tests/crafted.py +19 -0
  269. stayfixed-0.1.0/tests/docs/__init__.py +0 -0
  270. stayfixed-0.1.0/tests/docs/test_commands.py +369 -0
  271. stayfixed-0.1.0/tests/docs/test_graph.py +93 -0
  272. stayfixed-0.1.0/tests/docs/test_hygiene.py +226 -0
  273. stayfixed-0.1.0/tests/docs/test_plans.py +589 -0
  274. stayfixed-0.1.0/tests/docs/test_prose.py +70 -0
  275. stayfixed-0.1.0/tests/docs/test_surface.py +43 -0
  276. stayfixed-0.1.0/tests/docs/test_themes.py +123 -0
  277. stayfixed-0.1.0/tests/docs/test_trail.py +624 -0
  278. stayfixed-0.1.0/tests/doctor/__init__.py +0 -0
  279. stayfixed-0.1.0/tests/doctor/test_checks.py +2478 -0
  280. stayfixed-0.1.0/tests/doctor/test_command.py +173 -0
  281. stayfixed-0.1.0/tests/doctor/test_surface.py +34 -0
  282. stayfixed-0.1.0/tests/fixtures/hostile-project/.claude/settings.json +6 -0
  283. stayfixed-0.1.0/tests/fixtures/hostile-project/README.md +21 -0
  284. stayfixed-0.1.0/tests/fixtures/hostile-project/docs/memory/MEMORY.md +3 -0
  285. stayfixed-0.1.0/tests/fixtures/hostile-project/docs/memory/developer/canary.md +9 -0
  286. stayfixed-0.1.0/tests/fixtures/hostile-project/stayfixed.toml +14 -0
  287. stayfixed-0.1.0/tests/fixtures/smoke-project/.gitignore +5 -0
  288. stayfixed-0.1.0/tests/fixtures/smoke-project/.stayfixed/manifest.json +78 -0
  289. stayfixed-0.1.0/tests/fixtures/smoke-project/AGENTS.md +19 -0
  290. stayfixed-0.1.0/tests/fixtures/smoke-project/CLAUDE.md +1 -0
  291. stayfixed-0.1.0/tests/fixtures/smoke-project/README.md +5 -0
  292. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/adr/0000-template.md +22 -0
  293. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/adr/README.md +4 -0
  294. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/architecture/README.md +4 -0
  295. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/architecture/documentation.md +26 -0
  296. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/bug-reports.md +31 -0
  297. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/bugs/BR-001.md +27 -0
  298. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/bugs/audits/README.md +9 -0
  299. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/memory/MEMORY.md +4 -0
  300. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/memory/developer/house-style.md +11 -0
  301. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/memory/volatile/in-flight.md +10 -0
  302. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/plans/.gitkeep +0 -0
  303. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/plans/2026-09-19-the-fixtures-own-plan.md +29 -0
  304. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/plans/README.md +4 -0
  305. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/roadmap-history.md +4 -0
  306. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/roadmap.md +25 -0
  307. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/runbooks/bug-reports.md +7 -0
  308. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/specs/.gitkeep +0 -0
  309. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/specs/README.md +4 -0
  310. stayfixed-0.1.0/tests/fixtures/smoke-project/docs/trail.toml +13 -0
  311. stayfixed-0.1.0/tests/fixtures/smoke-project/stayfixed.toml +21 -0
  312. stayfixed-0.1.0/tests/floor.py +39 -0
  313. stayfixed-0.1.0/tests/gitfixture.py +224 -0
  314. stayfixed-0.1.0/tests/guards/__init__.py +0 -0
  315. stayfixed-0.1.0/tests/guards/test_attribute.py +528 -0
  316. stayfixed-0.1.0/tests/guards/test_audit.py +392 -0
  317. stayfixed-0.1.0/tests/guards/test_bashscan.py +441 -0
  318. stayfixed-0.1.0/tests/guards/test_bgcleanup.py +718 -0
  319. stayfixed-0.1.0/tests/guards/test_commands.py +615 -0
  320. stayfixed-0.1.0/tests/guards/test_commit.py +713 -0
  321. stayfixed-0.1.0/tests/guards/test_githooks.py +398 -0
  322. stayfixed-0.1.0/tests/guards/test_hooks.py +338 -0
  323. stayfixed-0.1.0/tests/guards/test_hygiene.py +408 -0
  324. stayfixed-0.1.0/tests/guards/test_surface.py +39 -0
  325. stayfixed-0.1.0/tests/hooks/__init__.py +0 -0
  326. stayfixed-0.1.0/tests/hooks/test_dispatch.py +606 -0
  327. stayfixed-0.1.0/tests/hooks/test_hook_command.py +240 -0
  328. stayfixed-0.1.0/tests/hooks/test_hooks_json.py +187 -0
  329. stayfixed-0.1.0/tests/hooks/test_registry.py +87 -0
  330. stayfixed-0.1.0/tests/hooks/test_sink.py +194 -0
  331. stayfixed-0.1.0/tests/hooks/test_wrapper.py +794 -0
  332. stayfixed-0.1.0/tests/ledger/__init__.py +0 -0
  333. stayfixed-0.1.0/tests/ledger/test_check.py +722 -0
  334. stayfixed-0.1.0/tests/ledger/test_commands.py +349 -0
  335. stayfixed-0.1.0/tests/ledger/test_entries.py +272 -0
  336. stayfixed-0.1.0/tests/ledger/test_index.py +322 -0
  337. stayfixed-0.1.0/tests/ledger/test_scan.py +251 -0
  338. stayfixed-0.1.0/tests/ledger/test_surface.py +31 -0
  339. stayfixed-0.1.0/tests/ledger/test_write.py +565 -0
  340. stayfixed-0.1.0/tests/memory/__init__.py +0 -0
  341. stayfixed-0.1.0/tests/memory/test_bundles.py +656 -0
  342. stayfixed-0.1.0/tests/memory/test_commands.py +998 -0
  343. stayfixed-0.1.0/tests/memory/test_hooks.py +249 -0
  344. stayfixed-0.1.0/tests/memory/test_index.py +754 -0
  345. stayfixed-0.1.0/tests/memory/test_notes.py +415 -0
  346. stayfixed-0.1.0/tests/memory/test_refs.py +450 -0
  347. stayfixed-0.1.0/tests/memory/test_store.py +628 -0
  348. stayfixed-0.1.0/tests/memory/test_surface.py +75 -0
  349. stayfixed-0.1.0/tests/memory/test_trust.py +419 -0
  350. stayfixed-0.1.0/tests/memory/test_worktree.py +1226 -0
  351. stayfixed-0.1.0/tests/overlay/__init__.py +0 -0
  352. stayfixed-0.1.0/tests/overlay/test_create.py +338 -0
  353. stayfixed-0.1.0/tests/overlay/test_publish.py +333 -0
  354. stayfixed-0.1.0/tests/overlay/test_requires.py +154 -0
  355. stayfixed-0.1.0/tests/overlay/test_surface.py +60 -0
  356. stayfixed-0.1.0/tests/overlay/test_sync.py +62 -0
  357. stayfixed-0.1.0/tests/overlay/test_template.py +375 -0
  358. stayfixed-0.1.0/tests/overlay/test_upgrade.py +138 -0
  359. stayfixed-0.1.0/tests/profiles/__init__.py +0 -0
  360. stayfixed-0.1.0/tests/profiles/test_evaluate.py +189 -0
  361. stayfixed-0.1.0/tests/profiles/test_model.py +152 -0
  362. stayfixed-0.1.0/tests/profiles/test_python.py +190 -0
  363. stayfixed-0.1.0/tests/project/__init__.py +0 -0
  364. stayfixed-0.1.0/tests/project/repos.py +95 -0
  365. stayfixed-0.1.0/tests/project/test_answer_flags.py +197 -0
  366. stayfixed-0.1.0/tests/project/test_command.py +715 -0
  367. stayfixed-0.1.0/tests/project/test_detect.py +250 -0
  368. stayfixed-0.1.0/tests/project/test_fixture.py +119 -0
  369. stayfixed-0.1.0/tests/project/test_footprint_commands.py +321 -0
  370. stayfixed-0.1.0/tests/project/test_footprint_invariant.py +576 -0
  371. stayfixed-0.1.0/tests/project/test_gates.py +104 -0
  372. stayfixed-0.1.0/tests/project/test_ignored.py +373 -0
  373. stayfixed-0.1.0/tests/project/test_init.py +676 -0
  374. stayfixed-0.1.0/tests/project/test_journey.py +215 -0
  375. stayfixed-0.1.0/tests/project/test_planning_seam.py +189 -0
  376. stayfixed-0.1.0/tests/project/test_questions.py +171 -0
  377. stayfixed-0.1.0/tests/project/test_rewrite.py +157 -0
  378. stayfixed-0.1.0/tests/project/test_round_trip.py +91 -0
  379. stayfixed-0.1.0/tests/project/test_templates.py +743 -0
  380. stayfixed-0.1.0/tests/project/test_uninstall.py +965 -0
  381. stayfixed-0.1.0/tests/project/test_upgrade.py +754 -0
  382. stayfixed-0.1.0/tests/release/__init__.py +0 -0
  383. stayfixed-0.1.0/tests/release/test_hashes.py +218 -0
  384. stayfixed-0.1.0/tests/release/test_notes.py +62 -0
  385. stayfixed-0.1.0/tests/release/test_pins.py +74 -0
  386. stayfixed-0.1.0/tests/release/test_surface.py +41 -0
  387. stayfixed-0.1.0/tests/release/test_versions.py +584 -0
  388. stayfixed-0.1.0/tests/scaffold/__init__.py +0 -0
  389. stayfixed-0.1.0/tests/scaffold/test_containment.py +406 -0
  390. stayfixed-0.1.0/tests/scaffold/test_engine.py +1131 -0
  391. stayfixed-0.1.0/tests/scaffold/test_entries.py +259 -0
  392. stayfixed-0.1.0/tests/scaffold/test_local.py +350 -0
  393. stayfixed-0.1.0/tests/scaffold/test_manifest.py +160 -0
  394. stayfixed-0.1.0/tests/scaffold/test_model.py +60 -0
  395. stayfixed-0.1.0/tests/scaffold/test_regions.py +141 -0
  396. stayfixed-0.1.0/tests/scaffold/test_report.py +82 -0
  397. stayfixed-0.1.0/tests/scaffold/test_surface.py +69 -0
  398. stayfixed-0.1.0/tests/scaffold/test_writes.py +196 -0
  399. stayfixed-0.1.0/tests/scripts/__init__.py +0 -0
  400. stayfixed-0.1.0/tests/scripts/test_check_artifacts.py +175 -0
  401. stayfixed-0.1.0/tests/scripts/test_mutation_oracle.py +1028 -0
  402. stayfixed-0.1.0/tests/scripts/test_smoke_scripts.py +334 -0
  403. stayfixed-0.1.0/tests/setup/__init__.py +0 -0
  404. stayfixed-0.1.0/tests/setup/test_commands.py +220 -0
  405. stayfixed-0.1.0/tests/setup/test_git_hooks.py +168 -0
  406. stayfixed-0.1.0/tests/setup/test_machine.py +220 -0
  407. stayfixed-0.1.0/tests/setup/test_setup.py +1839 -0
  408. stayfixed-0.1.0/tests/setup/test_surface.py +27 -0
  409. stayfixed-0.1.0/tests/skills/__init__.py +0 -0
  410. stayfixed-0.1.0/tests/skills/test_skills.py +272 -0
  411. stayfixed-0.1.0/tests/snapshot.py +81 -0
  412. stayfixed-0.1.0/tests/test_areas.py +267 -0
  413. stayfixed-0.1.0/tests/test_check_workflow.py +758 -0
  414. stayfixed-0.1.0/tests/test_cli.py +335 -0
  415. stayfixed-0.1.0/tests/test_command.py +129 -0
  416. stayfixed-0.1.0/tests/test_documents.py +650 -0
  417. stayfixed-0.1.0/tests/test_findings.py +59 -0
  418. stayfixed-0.1.0/tests/test_fixtures.py +855 -0
  419. stayfixed-0.1.0/tests/test_floor.py +72 -0
  420. stayfixed-0.1.0/tests/test_fork_points.py +97 -0
  421. stayfixed-0.1.0/tests/test_fsops.py +480 -0
  422. stayfixed-0.1.0/tests/test_git_answers.py +234 -0
  423. stayfixed-0.1.0/tests/test_git_run.py +409 -0
  424. stayfixed-0.1.0/tests/test_gitfixture.py +195 -0
  425. stayfixed-0.1.0/tests/test_harnesses.py +42 -0
  426. stayfixed-0.1.0/tests/test_identifiers.py +64 -0
  427. stayfixed-0.1.0/tests/test_import_boundary.py +46 -0
  428. stayfixed-0.1.0/tests/test_install_path.py +765 -0
  429. stayfixed-0.1.0/tests/test_launcher.py +96 -0
  430. stayfixed-0.1.0/tests/test_manifests.py +167 -0
  431. stayfixed-0.1.0/tests/test_name.py +40 -0
  432. stayfixed-0.1.0/tests/test_neutral.py +630 -0
  433. stayfixed-0.1.0/tests/test_printed.py +81 -0
  434. stayfixed-0.1.0/tests/test_public_anchors.py +330 -0
  435. stayfixed-0.1.0/tests/test_runner.py +141 -0
  436. stayfixed-0.1.0/tests/test_snapshot.py +110 -0
  437. stayfixed-0.1.0/tests/test_surfaces.py +192 -0
  438. stayfixed-0.1.0/tests/test_tomlout.py +152 -0
  439. stayfixed-0.1.0/tests/test_undecodable.py +316 -0
  440. stayfixed-0.1.0/tests/test_workflow_yaml.py +137 -0
  441. stayfixed-0.1.0/tests/workflow_yaml.py +256 -0
@@ -0,0 +1,12 @@
1
+ {
2
+ "name": "stayfixed-marketplace",
3
+ "description": "stayfixed, a methodology harness for coding agents, published from its own repository.",
4
+ "owner": { "name": "stayfixed" },
5
+ "plugins": [
6
+ {
7
+ "name": "stayfixed",
8
+ "source": "./",
9
+ "description": "A methodology harness for coding agents: bug ledger, curated working memory behind a trust gate, and fail-closed guards."
10
+ }
11
+ ]
12
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "stayfixed",
3
+ "version": "0.1.0",
4
+ "description": "A methodology harness for coding agents: bug ledger, curated working memory behind a trust gate, and fail-closed guards.",
5
+ "author": { "name": "Nezhinskiy" },
6
+ "repository": "https://github.com/stayfixed/stayfixed",
7
+ "license": "MIT",
8
+ "keywords": ["methodology", "bug-ledger", "memory", "hooks", "guards", "codex"],
9
+ "userConfig": {
10
+ "reply_language": {
11
+ "type": "string",
12
+ "title": "Reply language",
13
+ "description": "Language for chat replies; durable artifacts stay in the artifact language.",
14
+ "default": ""
15
+ },
16
+ "artifact_language": {
17
+ "type": "string",
18
+ "title": "Artifact language",
19
+ "description": "Language for documents, comments, commits and pull requests.",
20
+ "default": "en"
21
+ },
22
+ "preset": {
23
+ "type": "string",
24
+ "title": "Preset",
25
+ "description": "Preset applied by setup and init.",
26
+ "default": "recommended"
27
+ }
28
+ }
29
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "name": "stayfixed",
3
+ "version": "0.1.0",
4
+ "description": "A methodology harness for coding agents: bug ledger, curated working memory behind a trust gate, and fail-closed guards.",
5
+ "author": { "name": "Nezhinskiy" },
6
+ "repository": "https://github.com/stayfixed/stayfixed",
7
+ "license": "MIT",
8
+ "interface": {
9
+ "displayName": "stayfixed",
10
+ "shortDescription": "Bug ledger, working memory behind a trust gate, and fail-closed guards for coding agents",
11
+ "developerName": "Nezhinskiy",
12
+ "category": "Developer Tools"
13
+ }
14
+ }
@@ -0,0 +1,373 @@
1
+ # Changelog
2
+
3
+ <!-- towncrier release notes start -->
4
+
5
+ ## 0.1.0 (2026-09-28)
6
+
7
+ ### Added
8
+
9
+ - **Installation.** stayfixed installs as a Claude Code and Codex plugin and as a command-line
10
+ tool (`uv tool install stayfixed`). It needs Python 3.11 or newer on Linux or macOS, and
11
+ imports nothing outside the standard library at runtime, so a hook works wherever `python3`
12
+ does. The plugin ships `hooks/hooks.json`, which both harnesses read with no configuration:
13
+ installing the plugin is enough for the guards to fire and the memory bundles to arrive at
14
+ session start.
15
+ - **What a repository can and cannot make stayfixed do.** stayfixed treats a repository as
16
+ untrusted input, since a clone you have not read can commit a `stayfixed.toml`, notes, a
17
+ manifest, a `.claude/settings.json` `env` block and symlinks. Every file stayfixed replaces is
18
+ replaced atomically and keeps its permissions. Every write into a repository walks its path
19
+ one component at a time, refuses a symlink at any component and never leaves the project root,
20
+ so a committed link cannot redirect a write. Your machine configuration is
21
+ `~/.config/stayfixed/config.toml`; neither `STAYFIXED_CONFIG` nor `XDG_CONFIG_HOME` moves it,
22
+ because a repository's settings file could set them, and `--machine` names another file
23
+ explicitly.
24
+
25
+ Text a repository chose (file names, note names, configuration keys and values) is never
26
+ printed as found, so it cannot put escape sequences into your terminal or a workflow command
27
+ such as `::error::` into a CI log. A name made of ASCII letters, digits, `.`, `_` and `-` in
28
+ `/`-separated segments prints as itself; any other prints on a summary line as `<unprintable
29
+ name; see --json>`, with the full name escaped in `--json`, and is quoted with control
30
+ characters escaped in a refusal. Refusals over a configuration value name the key and the rule
31
+ in words, never the value. Lists a repository can make arbitrarily long name at most eight
32
+ entries and count the rest (`--json` has them all). A TOML file that does not parse is
33
+ reported as the file and the parser's line and column only. File names that are not UTF-8 are
34
+ handled as their own bytes, and when `git` cannot run or answer, commands say so rather than
35
+ reading the silence as "nothing".
36
+
37
+ Exit codes are the same everywhere: `0` success, `1` findings, `2` a refusal or an internal
38
+ error, which a caller must never read as permission. Two exceptions are deliberate: `stayfixed
39
+ test audit-entrypoints` exits `0` with findings, and `stayfixed hook` refuses on an internal
40
+ error only for `PreToolUse`, so a bug in stayfixed never erases a prompt you typed.
41
+ - **Machine setup: `stayfixed setup`.** `stayfixed setup --preset recommended` writes your
42
+ personal languages and the preset name into the machine configuration, merges the preset's
43
+ deny rules and your personal values into `~/.claude/settings.json`, registers and installs the
44
+ preset's plugins on every harness that declares a marketplace for them (a missing harness is a
45
+ note, not a failure), and tells you whether `stayfixed` is on `PATH`. `--overlay <path>`
46
+ records a private overlay you already have; `--overlay create:<owner>/<name> --yes` creates
47
+ one on GitHub and records it. Creating a repository is the one outward-facing act `setup`
48
+ performs and the only thing `--yes` confirms.
49
+
50
+ Everything `setup` can refuse, it refuses before its first write. An `--overlay` path must be
51
+ a real overlay (both `.claude-plugin/` manifests naming it `stayfixed-overlay[-<owner>]` and
52
+ `stayfixed-overlay-marketplace[-<owner>]`) and must lie outside every checkout of the
53
+ repository `--root` names, linked worktrees included. The machine configuration is merged key
54
+ by key: a value you or an earlier run recorded is never overwritten by a preset default, and
55
+ unknown tables are carried through, but comments do not survive the rewrite. A
56
+ `~/.claude/settings.json` reached through a symlink (stow, chezmoi, a synced home) is refused
57
+ rather than written through; the refusal names the link, and `--settings <path>` writes the
58
+ file where it really is. `--home` and `--machine` point a run at a scratch destination; they
59
+ are not a dry run.
60
+
61
+ `stayfixed setup --git-hooks` installs the commit-message hook into this repository's own
62
+ hooks directory, never `core.hooksPath`. An existing hook is kept as
63
+ `prepare-commit-msg.local` and chained to, and `--git-hooks --uninstall` restores it exactly.
64
+ - **Starting a project: `stayfixed init`.** `stayfixed init --questions` prints the six values
65
+ `stayfixed init --yes` would take (name, base branch, agents, profile, memory mode, files kept
66
+ out of git), where each came from and the flag that replaces it (`--name`, `--base-branch`,
67
+ `--agent`, `--profile`, `--memory-mode`, `--local`); under `--json` it is a JSON Schema. `init
68
+ --yes` writes the footprint: `stayfixed.toml`, a `CLAUDE.md` pointing at whatever `[paths]
69
+ agents_md` names, an `AGENTS.md` skeleton (or a managed section in an existing one) stating
70
+ your effective budgets, a `.gitignore` block, the documentation skeleton the other commands
71
+ expect, and a CI workflow calling the reusable gate pinned to the commit of the stayfixed
72
+ release that wrote it. Every file is recorded in `.stayfixed/manifest.json`. `--dry-run` shows
73
+ every file first, and a refusal anywhere writes nothing. A file already present, an existing
74
+ `CLAUDE.md` included, is left alone. `init` runs once per repository; after that, use
75
+ `stayfixed upgrade`.
76
+
77
+ The base branch comes from `origin/HEAD` only when that names a plain branch under
78
+ `refs/remotes/origin/`, otherwise it is `main`. With no remote, it is the branch checked out.
79
+ A repository whose remote has no `origin/HEAD` (created locally and pushed, or whose only
80
+ remote is `upstream`) gets `main`, never the branch you are on; a `note:` line names
81
+ `--base-branch` and, for `origin`, `git remote set-head origin --auto`.
82
+
83
+ A `stayfixed.toml` you wrote by hand is read as the answers, not replaced: `init` adds at most
84
+ a missing `[stayfixed] version`, refuses a file the next command could not load, and names the
85
+ custom gates it configures, whose commands `stayfixed assess` runs unless given `--builtin`.
86
+ The workflow is rendered from the `[ci] ref` your file records, so a file with no ref gets no
87
+ workflow: write a released commit into `[ci] ref` before `init`, or run `stayfixed upgrade`
88
+ afterwards. `--no-ci` writes no workflow and records `[ci] mode = "none"` when this run writes
89
+ the file. When no released tag matches the running version, or the public repository cannot be
90
+ reached, the workflow is skipped with a sentence saying why and which command can still act.
91
+
92
+ The first stack profile is `python`. When `[stayfixed] profile` names it (from `--profile`, or
93
+ detected), `init` writes its rules once to `docs/stayfixed/rules/python.md`, the copy the
94
+ project edits; every harness reading `AGENTS.md` gets a pointer and the profile's must-know
95
+ lines, and Claude Code also a path-scoped pointer under `.claude/rules/`. `stayfixed assess`
96
+ runs the profile's eight checks: a lockfile committed or deliberately ignored,
97
+ `requires-python` declared without a cap, one type checker configured, strict pytest markers
98
+ in whichever file pytest reads, marker selection kept out of `addopts`, and Ruff's defaults
99
+ not hidden by a legacy `select`.
100
+ - **Keeping the footprint current: `stayfixed upgrade` and `stayfixed uninstall`.** `stayfixed
101
+ upgrade [--dry-run] [--force PATH]` refreshes untouched files after a stayfixed update; files
102
+ you edited, and files stayfixed never wrote, are skipped and named, and `--force` with a path
103
+ overwrites that one. `[stayfixed] version` moves in place, every other byte of
104
+ `stayfixed.toml` kept. When the workflow pins a commit, `[ci] ref`, the pin and the version
105
+ move together or not at all; a `[ci] ref` that is not a commit is left alone. A caller
106
+ workflow you edited is reported `skip_modified`, and `upgrade` then holds the version and
107
+ `[ci] ref` until `--force .github/workflows/stayfixed.yml` or a hand edit brings it in line. A
108
+ project recording a newer stayfixed (a pre-release included) or a version stayfixed cannot
109
+ order is refused. Each run decides first and writes second, so a dry run shows exactly what a
110
+ real one will do; a run stopped by a failed write keeps and records what it did, and the next
111
+ run continues.
112
+
113
+ `stayfixed uninstall [--dry-run] [--force PATH]` removes files whose bytes are still
114
+ stayfixed's, takes stayfixed's sections out of files holding other text (shown as `(retired;
115
+ stayfixed's part only, the file stays)`), removes the manifest, and keeps and lists every file
116
+ you edited. It refuses while `.stayfixed/local/` holds notes or edited files, and while the
117
+ manifest records a `stayfixed.toml` that is missing. A `stayfixed.toml` you wrote before
118
+ `init` stays.
119
+
120
+ `[artifacts] local` keeps chosen artifacts out of git: they are written under
121
+ `.stayfixed/local/artifacts/`, recorded in `.stayfixed/local/artifacts.json`, refreshed while
122
+ unchanged and never overwritten once edited. It cannot name `config` or `gitignore`. `init`,
123
+ `upgrade` and `uninstall` also refuse, before writing, a `[paths]` layout that aims two
124
+ artifacts at one file (paths differing only in case count as one) and a `[paths]` value
125
+ pointing at an existing file git ignores, such as a `.env`.
126
+ - **The `stayfixed.toml` grammar.** The configuration is committed and repository-controlled, so
127
+ it is a closed grammar: unknown sections and keys are refused, and a file that looks valid can
128
+ be refused on the rules below. Each refusal names the key and states the rule.
129
+
130
+ `[paths]` values are plain relative paths: segments of letters, digits, `.`, `_` and `-`
131
+ joined by single `/`, none empty, `.` or `..`. A leading `./`, a doubled `//` and a trailing
132
+ `/` are refused, not normalised (except in `ledger.code_roots`, where `"./src"` and `"src/"`
133
+ read as `src`). No value may contain a `.git` component, at any depth or case, or point inside
134
+ `.stayfixed/`; `.github/` and `.gitignore` are unaffected. `[paths] stayfixed` (default
135
+ `docs/stayfixed`) is where stayfixed's own project files go.
136
+
137
+ `[project] name` and custom gate names are one lowercase path segment of letters, digits, `.`,
138
+ `_` and `-`, led by a letter or digit. `[project] base_branch`, `release_branch` and `[ci]
139
+ gate_branch` use letters, digits, `.`, `_`, `-` and `/`, led by a letter or digit, and never a
140
+ name git refuses; `release/2.0` is accepted, a name with `+`, `@`, a leading `_` or a
141
+ non-ASCII letter is not. `[ci] gate_branch` defaults to `[project] base_branch`. `[stayfixed]
142
+ preset` is matched exactly, case included, against the shipped presets, and `profile` must be
143
+ a shipped profile. `[stayfixed] state`, `[memory] mode` and `[ci] mode` take only their listed
144
+ values. `[ci] ref` is the full-length commit sha of a released stayfixed tag. Each
145
+ `memory.groups` entry names a subdirectory of `paths.memory` (`""`, `"."` and doubled or
146
+ trailing slashes are refused); a duplicate counts once.
147
+
148
+ `[gates]` names the gates a project runs: the built-in `docs`, `bugs`, `plan`, `commit` and
149
+ `trail` by default, and argv commands under `[gates.custom.<name>]`. `[stayfixed] enforced`,
150
+ written by `stayfixed adopt promote`, lists the gates promoted so far; an `installed` project
151
+ whose `enforced` lists only some gates is refused.
152
+
153
+ **A `stayfixed.toml` that is a symbolic link is refused by every command**, before anything is
154
+ read through it, and while it is one every agent tool call in that checkout is refused at
155
+ `PreToolUse`. Replace the link with the file.
156
+ - **Working memory: `stayfixed memory`.** Notes are Markdown files with frontmatter; `MEMORY.md`
157
+ is a routing index generated from them. Session-start bundles arrive whole: a bundle too large
158
+ for its slot is withheld with a notice rather than cut in half, and `stayfixed memory fit`
159
+ says what fits. In a git worktree the store is linked in, not copied, and every link goes
160
+ through the same contained walk as a file write. `stayfixed memory refs` reports backticked
161
+ repository paths in your notes that no longer exist.
162
+
163
+ Notes committed to a repository you cloned reach the model only after `stayfixed memory trust
164
+ --in-repo-memory`, and then inside a marked region that says they are data. When the
165
+ repository changes what it ships, you are asked again, and the harness's link to the store is
166
+ withdrawn until you are. Notes in your own overlay need no approval.
167
+
168
+ `stayfixed memory session-context --bundle index` emits `MEMORY.md` on Codex only, since
169
+ Claude Code reads it natively; the harness is read from the environment, so the same command
170
+ answers differently in each. The `recommended` preset's five standing rules (ask at a decision
171
+ fork, work in a worktree by default, keep research sources within two months, read CI after a
172
+ push, split languages by audience) arrive at session start through the `preset-rules` bundle;
173
+ they are never repository content and need no trust record.
174
+ - **The private overlay: `stayfixed overlay`.** An overlay is your own repository of standing
175
+ rules, cross-project notes and one record per bound repository. `stayfixed overlay create`
176
+ renders one locally with `--local` (no network call) or on GitHub with `--template`, private
177
+ from the start; neither is a default, so an omitted flag never creates a repository on your
178
+ account. `--template` generates from `<owner>/stayfixed-overlay-template`, which `stayfixed
179
+ overlay publish-template` publishes from your own authenticated checkout. Without `--yes`,
180
+ `publish-template` only reports what it would create, mark and push; a same-named repository
181
+ that is not public is refused rather than flipped, and a `gh` or `git` that cannot answer is
182
+ reported as such, not as "nothing to push".
183
+
184
+ `stayfixed overlay init` names the overlay after you in all three plugin manifests so two
185
+ overlays never collide, and installs a commit-time secret scan beside the template's push-time
186
+ one, whose GitHub Actions are pinned at full commit shas and kept current by
187
+ `.github/dependabot.yml`. `stayfixed overlay upgrade` refreshes files you have not edited and
188
+ always names the two files that can grant a capability, permission rules and hook entries,
189
+ whatever their hashes say; run `--dry-run` first to see that list before anything changes. The
190
+ shipped template has deny-only permissions and no hooks. Both commands refuse a `--root` that
191
+ is not an overlay. An overlay may declare the stayfixed it needs as `stayfixed.requires` in
192
+ its plugin manifest, each version component at most nine ASCII digits.
193
+ - **Binding a repository: `stayfixed attach` and `stayfixed detach`.** `stayfixed attach --store
194
+ <overlay>/projects/<name>/memory` checks that the overlay recorded this repository's remote
195
+ (`--trust-remote` accepts a different one), prints the permissions and hook entries the
196
+ overlay would add, merges them into `.claude/settings.local.json`, copies your Codex rules
197
+ into `.codex/rules/`, and links your notes into the checkout and every worktree. `--check`
198
+ reports the same and writes nothing. Every refusal `attach` can foresee happens before its
199
+ first write: `--store` must be your overlay's directory for this project, since the overlay
200
+ root comes from your machine configuration and never from the path you type; a new permission
201
+ needs `--yes`; `--machine` is accepted only from an interactive shell; a note group that is
202
+ still a real directory must be moved into the overlay first (`--check` counts them); and a
203
+ `~/.claude` that is a symlink into a dotfiles tree is refused by name, here, in `detach` and
204
+ in the worktree session hook. Make that directory real and let your dotfiles manager adopt the
205
+ files inside it.
206
+
207
+ `stayfixed detach` removes exactly what `attach` added, from a ledger under
208
+ `.stayfixed/local/`, and leaves the overlay's binding record so a re-attach does not ask
209
+ again. Run it from the checkout you attached from. The ledger is never an authority: a field
210
+ `attach` could not have written is refused with nothing removed. Directories `attach` created
211
+ are removed only when empty, except the note link directory, and on a repository `stayfixed
212
+ init` set up the `.gitignore` block stays. The overlay's own `hooks/hooks.json` stays empty;
213
+ its hook entries reach a repository only through `attach`'s explicit merge.
214
+
215
+ In a repository with `[memory] mode = "overlay"`, a session hears once at its start what
216
+ stands between it and its notes: no overlay recorded, not attached, a different remote, a
217
+ group still a real directory, an overlay requiring a newer stayfixed, or, when nothing else is
218
+ wrong, a branch with no upstream or unpushed commits and uncommitted changes. Each line is
219
+ fixed text naming the command to run; a healthy repository hears nothing. On a compaction the
220
+ unpushed-work check is skipped, so an overlay that becomes unpushed mid-session is reported at
221
+ the next start or resume, or by `stayfixed doctor`.
222
+ - **The bug ledger: `stayfixed bugs`.** One Markdown file per bug under the directory
223
+ `stayfixed.toml` names, plus a generated index that is a pure function of those files and
224
+ refuses to delete content it did not write. `stayfixed bugs check` holds every entry to a flat
225
+ frontmatter contract, every high-severity entry to a filled evidence-boundary line, and every
226
+ identifier under your code roots to an existing entry. `stayfixed bugs new` files an entry at
227
+ the next number free across every branch this checkout can see, and warns when it could not
228
+ read that history. `stayfixed bugs renumber` moves an entry and every reference, leaving a
229
+ pointer at the old number. Entries are append-only: with `--base`, which the `bugs` gate
230
+ passes, a deleted ledger is a `ledger-removed` finding and a deleted or renamed entry an
231
+ `entry-removed` finding, and a branch is not blamed for an entry filed after it forked.
232
+ - **Documents and plans.** `stayfixed docs check` holds your always-loaded agent document and
233
+ the roadmap's prose to the preset's budgets (a project may lower them, never raise them),
234
+ checks that every local link resolves, and advises, without failing, on broken or repeated
235
+ wiki-links and bracketed bug identifiers in the memory store. `stayfixed docs trail`
236
+ regenerates the design-and-plan listing in your roadmap from the tracked documents and a
237
+ `trail.toml` you own, and will not let a renamed or new document claim it shipped. A
238
+ `trail.toml` theme `pattern` accepts only literal text, `.`, `.*`, `|`, `^`, `$` and `\`
239
+ before punctuation, at most 256 characters, with at most 32 themes; other syntax is refused.
240
+ Inside a git work tree, `docs trail` writes nothing and exits `1` when `git` cannot say which
241
+ documents are ignored, so a local-only document never reaches the committed roadmap.
242
+
243
+ `stayfixed plan check` lints the plans a change touches: every path they name exists, no
244
+ verification step presumes its answer, and a mutation's outcome stays an expectation until
245
+ someone has watched it. It lints every plan the merge could alter, across all merge bases. The
246
+ README places stayfixed beside the tools it sits next to and says what each command writes,
247
+ and `docs/methodology/` states the ten principles behind it with dated sources.
248
+ - **Hooks and guards.** The hook wrapper finds a Python 3.11 or newer, runs from your project
249
+ root, and refuses with a named `SF_*` token rather than letting a broken guard read as
250
+ permission. Its environment may say where something goes, never which program runs:
251
+ `STAYFIXED_PYTHON_CANDIDATES` is honoured only from an interactive terminal, because a
252
+ committed `env` block applies without a trust prompt; no `python3` inside any checkout of the
253
+ repository is ever run, however `PATH` reached it; and `git` comes from a fixed list of
254
+ absolute paths, never `PATH`. If your only Python 3.11 lives inside your checkout, you will
255
+ get `SF_NO_PY` and a red `wrapper` row in `stayfixed doctor` with the remedy.
256
+
257
+ The `PreToolUse` guard (also runnable as `stayfixed guard bg-cleanup`) refuses a backgrounded
258
+ command that would leave an `&` job running past the harness's reap or that begins with
259
+ `sleep`. It advises when a restore line may never be reached, and when a backgrounded command
260
+ ends in `; echo …`, which makes the reported exit code the echo's. `stayfixed commit check`
261
+ fails a pull request whose commit messages carry an AI attribution trailer; `stayfixed commit
262
+ strip`, run by the hook `setup --git-hooks` installs, removes one before the commit exists.
263
+ `stayfixed test hygiene` names the two environment faults that make a red run unattributable,
264
+ and `stayfixed test audit-entrypoints` lists tests that never exercise what they name.
265
+
266
+ `stayfixed test attribute --command CMD` decides whether a failing test is your change or your
267
+ environment. It runs the command on the working tree, on `HEAD`'s committed tree and on the
268
+ merge base with your base branch (the last two extracted with `git archive` into a scratch
269
+ directory) and prints the verdict the three exit codes give. It never moves your checkout, but
270
+ the first run does execute your command in your working tree. Put your environment sync in the
271
+ command (`uv sync --locked && uv run pytest …`). With several merge bases it reports the
272
+ attribution as undetermined and runs nothing.
273
+ - **Assessing and adopting: `assess`, `gate` and `adopt`.** `stayfixed assess` inventories what
274
+ stands between a repository and enforcement: every configured gate and what no gate runs, such
275
+ as marker comments, tracked `.env` files, committed notes, foreign hooks and workflows, a
276
+ CODEOWNERS file that leaves a workflow or the rest of `.github/` without an owner, commit
277
+ subjects outside the vocabulary, and the profile's checks. The inventory goes to
278
+ `.stayfixed/assessment.json` and `--json`. A gate that could not judge the tree is `could not
279
+ run`, never a finding; when the base is not in the checkout, the summary says why `plan`,
280
+ `commit` and `bugs` could not run and suggests `--base refs/heads/<base_branch>`. `--builtin`
281
+ on `assess`, `gate` and `adopt promote` runs none of the commands `[gates.custom]` names, for
282
+ a clone whose commands you have not agreed to run; without it those commands run, as the
283
+ repository's test suite would.
284
+
285
+ The `docs` and `trail` gates judge tracked files, because CI checks out nothing else. When a
286
+ file they read is on disk but untracked, or tracked under a name differing only in case,
287
+ `assess` reports the gate as unable to judge the tree as CI will and `adopt promote` does not
288
+ enforce it. Commit the file, or remove the gate from `[gates] builtin`.
289
+
290
+ `stayfixed gate` is the pull-request gate, runnable locally. It reads the base's
291
+ `stayfixed.toml` at an exact commit, runs the configuration check and every configured gate,
292
+ advisory or enforcing, and prints the verdict. A pull request may only tighten: enforce more
293
+ gates, move the state forward, add a gate, drop or re-command one the base does not enforce,
294
+ lower a budget; the preset, profile, harnesses and project name are free, and an upgrade may
295
+ move the version to the running stayfixed and the pin to a released commit. While the base
296
+ enforces any gate, any other change is refused and lands only by a direct push to the base
297
+ branch. A project's first pull request, with no `stayfixed.toml` on the base, is judged by the
298
+ tree's copy. A custom gate runs only the command the base gives it, so one a pull request adds
299
+ or changes is not run until it lands; the base's enforced custom gates run first, no custom
300
+ gate starts once the run has failed, and each command runs in its own process group.
301
+
302
+ `stayfixed adopt begin PLAN` starts an adoption with a plan that passes `plan check`.
303
+ `stayfixed adopt promote [GATE…]` enforces the named gates together once they pass, or, with
304
+ no names, every gate that passes now; when all enforce, the project is `installed`. A custom
305
+ gate is promoted only once the base branch has its command.
306
+ - **The reusable CI workflow.** Your CI calls stayfixed's gates through one reusable workflow
307
+ pinned at a commit sha; `stayfixed init` writes the caller, `.github/workflows/stayfixed.yml`.
308
+ One `gates` job runs `stayfixed gate` with no resolver and no build backend, in two steps: the
309
+ configuration check and built-in gates, then your `[gates.custom]` gates only if the first
310
+ step passed, so no command your repository wrote runs where the verdict is decided. Python
311
+ starts as `python3 -P -s`, so no module in the checkout and no user-site `.pth` file loads.
312
+ Custom gates run on the bare runner image and install their own toolchain.
313
+
314
+ The caller runs only for pull requests into the gate branch, re-runs when one is retargeted,
315
+ reports for that branch's merge queue, and passes the branch as a literal `base:`, so a check
316
+ cannot be won against a looser branch and then retargeted. The base resolves to a commit from
317
+ `refs/remotes/origin/<base>`, so a tag of the same name cannot stand in for it, and the
318
+ stayfixed that runs is checked against the commit your `uses:` line pins before any gate runs.
319
+ The tree under review cannot turn off the gates it faces, but the caller file is one a pull
320
+ request can edit, so protect `.github/` with code-owner review as usual. The `only` input runs
321
+ a chosen few gates (the configuration check always runs), and `timeout-minutes` sets the job
322
+ limit, 15 by default, a whole number from 5 to 60; any other value fails the job, and a job
323
+ that times out is cancelled, never passed.
324
+ - **Diagnosing an installation: `stayfixed doctor`.** Sixteen checks over the configuration, the
325
+ hook wrapper, the overlay binding and memory path, every hook entry with its provenance,
326
+ budgets, bundle fit, the overlay's secret scan and the stayfixed version it requires, the note
327
+ store, recent hook failures, `[ci] ref`, and ignored environment variables. It writes and
328
+ repairs nothing; every finding carries the command that fixes it, and `--json` carries all
329
+ sixteen. It exits `1` when any check is red; a `skip` never reaches the exit code. The `files`
330
+ row compares the installed wrapper, hook table and launcher with the hashes the release
331
+ shipped, and says whether a file is modified, absent or unrecorded. The `ci-ref` row checks
332
+ that `[ci] ref` is a released commit and matches the workflow's pin; a missing workflow under
333
+ `[ci] mode = "reusable"` is a warning.
334
+
335
+ `doctor --json` is relayed to a model, so no repository-authored text reaches it: hook entries
336
+ are named by position, the hook log is counted and never quoted, and a file it cannot read is
337
+ a warning naming the file, not a red row a clone could force. It reads the workflow only as a
338
+ regular file up to 256 KiB, bounds its one network call at 30 seconds, runs only the hook
339
+ wrapper of its own installation, and trusts the overlay's record over a ledger a clone could
340
+ commit.
341
+ - **Skills and agents.** The plugin ships `close-bug` and `memory-sweep`; six procedures an
342
+ agent can be handed (`file-bug`, `sweep-defect-class`, `review-plan-three-lenses`,
343
+ `attribute-failure`, `run-correctness-audit`, `retro-to-guard`); short wrappers for `init`,
344
+ `upgrade`, `uninstall`, `attach`, `setup` and `doctor`; and a read-only `code-navigator`
345
+ agent. Every `stayfixed` invocation in a skill is checked against the real parser. The `init`
346
+ skill asks what `stayfixed init --questions` lets you answer, shows the dry run, writes only
347
+ on an explicit yes, and then starts the adoption (`stayfixed assess`, a plan, and `stayfixed
348
+ adopt begin` on a second explicit yes). Silence, a timeout or an empty answer is never a yes.
349
+ If you decline to run the repository's own gate commands, it uses `--builtin`.
350
+ - **Release tooling for this repository.** `stayfixed release` is stayfixed's discipline for its
351
+ own repository, not something it asks of yours. `release check` holds one version across
352
+ `pyproject.toml`, `uv.lock`, the package, both plugin manifests and `CHANGELOG.md`, with
353
+ `--tag` adding the tag and treating a pending changelog fragment as a finding. `release notes
354
+ --version X.Y.Z [--draft]` assembles `CHANGELOG.md` through towncrier. `release hashes`
355
+ records the sha256 of the three files a harness runs in `hooks/hashes.json`, which `release
356
+ check` verifies on every run and `stayfixed doctor` reads on your machine.
357
+
358
+ ### Known limitations
359
+
360
+ - No moving `v1` alias exists for the reusable workflow during `0.x`, because a `0.x` minor may
361
+ break what came before. Pin the full commit `stayfixed init` writes; `stayfixed doctor`
362
+ reports a `[ci] ref` of `v1` red until the first `1.x` release creates it.
363
+ - The `uvx` form of the gate is not shipped yet.
364
+ - Windows is not supported: path containment relies on `openat` with `O_NOFOLLOW` and
365
+ `O_DIRECTORY`.
366
+ - Ending a custom gate's process group does not end a descendant that left it (a new session or
367
+ a job-control shell's own group) or one stayfixed may not signal (a `sudo` or setuid
368
+ descendant). The base's enforced custom gates share one checkout; only a matrix leg per gate
369
+ separates them.
370
+ - A shallow clone cannot be judged by the `plan` and `bugs` gates or by `stayfixed test
371
+ attribute`: use a full-depth checkout.
372
+ - Each area's `api.py` is its only supported Python import surface; other names may move without
373
+ notice.
@@ -0,0 +1,38 @@
1
+ # Code of Conduct
2
+
3
+ This project adopts the [Contributor Covenant, version 2.1][covenant] in full. Please read it
4
+ there rather than here, so that this file cannot drift out of step with the version it claims
5
+ to be.
6
+
7
+ [covenant]: https://www.contributor-covenant.org/version/2/1/code_of_conduct/
8
+
9
+ In short: everyone taking part in this project's issues, pull requests, discussions and reviews
10
+ is expected to be respectful, to assume good faith, and to accept that technical disagreement is
11
+ normal and personal attacks are not.
12
+
13
+ ## A note specific to this project
14
+
15
+ stayfixed's reviews are direct. A finding will be stated as a finding, a disagreement about an
16
+ approach will be argued on its merits, and "this is wrong because X" is not rudeness. The line
17
+ is the usual one: criticise the code, the design or the argument — never the person who wrote
18
+ it, and never their competence, background or identity.
19
+
20
+ ## Reporting
21
+
22
+ To report behaviour that breaks this code, open a private report through GitHub:
23
+
24
+ <https://github.com/stayfixed/stayfixed/security/advisories/new>
25
+
26
+ That channel is private to the maintainers. It is meant for security reports, and using it for
27
+ a conduct report is a deliberate reuse until a dedicated contact address is published here —
28
+ say in the first line that it is a conduct report rather than a vulnerability.
29
+
30
+ Reports are handled confidentially. The maintainers will respond as promptly as a single-
31
+ maintainer project can, and will not discuss a report with anyone who does not need to know.
32
+
33
+ ## Enforcement
34
+
35
+ The maintainers may edit, hide or delete comments, commits, issues and pull requests that break
36
+ this code, and may temporarily or permanently ban a contributor. Where a response is not
37
+ obvious, the Contributor Covenant's own enforcement ladder — correction, warning, temporary ban,
38
+ permanent ban — is the one used.