sidegraph 0.4.0__tar.gz → 0.5.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 (365) hide show
  1. {sidegraph-0.4.0 → sidegraph-0.5.0}/.github/workflows/ci.yml +0 -1
  2. {sidegraph-0.4.0 → sidegraph-0.5.0}/CHANGELOG.md +61 -0
  3. {sidegraph-0.4.0 → sidegraph-0.5.0}/CLAUDE.md +1 -1
  4. {sidegraph-0.4.0 → sidegraph-0.5.0}/PKG-INFO +4 -3
  5. {sidegraph-0.4.0 → sidegraph-0.5.0}/README.md +3 -2
  6. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/getting-started/installation.md +8 -5
  7. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/reference/cli.md +61 -19
  8. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/reference/releasing.md +30 -4
  9. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/.claude-plugin/plugin.json +1 -1
  10. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/.codex-plugin/plugin.json +1 -1
  11. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/import-adrs/SKILL.md +7 -2
  12. {sidegraph-0.4.0 → sidegraph-0.5.0}/pyproject.toml +1 -1
  13. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/__init__.py +1 -1
  14. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/cli.py +26 -0
  15. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/doc_import.py +23 -6
  16. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/verify.py +110 -23
  17. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/conftest.py +11 -7
  18. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_cli_import.py +66 -0
  19. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_cli_init.py +67 -0
  20. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_doc_import.py +67 -0
  21. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_env_isolation.py +3 -1
  22. sidegraph-0.5.0/tests/test_sandbox_hygiene.py +328 -0
  23. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_verify_transitions.py +368 -0
  24. {sidegraph-0.4.0 → sidegraph-0.5.0}/uv.lock +5 -4
  25. sidegraph-0.4.0/tests/test_sandbox_hygiene.py +0 -133
  26. {sidegraph-0.4.0 → sidegraph-0.5.0}/.agents/plugins/marketplace.json +0 -0
  27. {sidegraph-0.4.0 → sidegraph-0.5.0}/.claude-plugin/marketplace.json +0 -0
  28. {sidegraph-0.4.0 → sidegraph-0.5.0}/.github/CODEOWNERS +0 -0
  29. {sidegraph-0.4.0 → sidegraph-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  30. {sidegraph-0.4.0 → sidegraph-0.5.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  31. {sidegraph-0.4.0 → sidegraph-0.5.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  32. {sidegraph-0.4.0 → sidegraph-0.5.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  33. {sidegraph-0.4.0 → sidegraph-0.5.0}/.github/secret_scanning.yml +0 -0
  34. {sidegraph-0.4.0 → sidegraph-0.5.0}/.github/workflows/publish.yml +0 -0
  35. {sidegraph-0.4.0 → sidegraph-0.5.0}/.github/workflows/scorecard.yml +0 -0
  36. {sidegraph-0.4.0 → sidegraph-0.5.0}/.gitignore +0 -0
  37. {sidegraph-0.4.0 → sidegraph-0.5.0}/.gitleaks.toml +0 -0
  38. {sidegraph-0.4.0 → sidegraph-0.5.0}/.pre-commit-config.yaml +0 -0
  39. {sidegraph-0.4.0 → sidegraph-0.5.0}/.python-version +0 -0
  40. {sidegraph-0.4.0 → sidegraph-0.5.0}/AGENTS.md +0 -0
  41. {sidegraph-0.4.0 → sidegraph-0.5.0}/CODE_OF_CONDUCT.md +0 -0
  42. {sidegraph-0.4.0 → sidegraph-0.5.0}/CONTRIBUTING.md +0 -0
  43. {sidegraph-0.4.0 → sidegraph-0.5.0}/LICENSE +0 -0
  44. {sidegraph-0.4.0 → sidegraph-0.5.0}/SECURITY.md +0 -0
  45. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/README.md +0 -0
  46. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/concepts/anchoring.md +0 -0
  47. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/concepts/data-model.md +0 -0
  48. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/concepts/decision-memory.md +0 -0
  49. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/concepts/mind-model.md +0 -0
  50. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/concepts/retrieval.md +0 -0
  51. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/getting-started/bootstrap.md +0 -0
  52. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/getting-started/claude-code-setup.md +0 -0
  53. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/getting-started/codex-setup.md +0 -0
  54. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/getting-started/quickstart.md +0 -0
  55. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/guides/capturing-decisions.md +0 -0
  56. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/guides/ci-cd-maintenance.md +0 -0
  57. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/guides/naming-your-domains.md +0 -0
  58. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/guides/retrieval-in-sessions.md +0 -0
  59. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/guides/semantic-docs.md +0 -0
  60. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/guides/surviving-refactors.md +0 -0
  61. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/guides/team-workflow.md +0 -0
  62. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/guides/verifying-your-setup.md +0 -0
  63. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/integrations/claude-code.md +0 -0
  64. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/integrations/codex.md +0 -0
  65. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/integrations/graphify.md +0 -0
  66. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/llms.txt +0 -0
  67. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/pilot-kit/README.md +0 -0
  68. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/pilot-kit/corpus_fit.py +0 -0
  69. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/pilot-kit/judge-prompt.md +0 -0
  70. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/pilot-kit/questions-prompt.md +0 -0
  71. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/pilot-kit/rubric-template.md +0 -0
  72. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/reference/configuration.md +0 -0
  73. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/reference/git-bindings.md +0 -0
  74. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/reference/hooks.md +0 -0
  75. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/reference/mcp-tools.md +0 -0
  76. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/reference/operations.md +0 -0
  77. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/reference/stability.md +0 -0
  78. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/reference/store-format.md +0 -0
  79. {sidegraph-0.4.0 → sidegraph-0.5.0}/docs/whitepaper/index.md +0 -0
  80. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/.mcp.json +0 -0
  81. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/codex/hooks.json +0 -0
  82. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/codex/mcp.json +0 -0
  83. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/hooks/hooks.json +0 -0
  84. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/check-plan/SKILL.md +0 -0
  85. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/check-plan/agents/openai.yaml +0 -0
  86. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/explain-why/SKILL.md +0 -0
  87. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/explain-why/agents/openai.yaml +0 -0
  88. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/heal-anchors/SKILL.md +0 -0
  89. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/heal-anchors/agents/openai.yaml +0 -0
  90. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/import-adrs/agents/openai.yaml +0 -0
  91. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/manage-domains/SKILL.md +0 -0
  92. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/manage-domains/agents/openai.yaml +0 -0
  93. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/name-domains/SKILL.md +0 -0
  94. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/name-domains/agents/openai.yaml +0 -0
  95. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/ratify-decisions/SKILL.md +0 -0
  96. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/ratify-decisions/agents/openai.yaml +0 -0
  97. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/record-decision/SKILL.md +0 -0
  98. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/record-decision/agents/openai.yaml +0 -0
  99. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/record-fact/SKILL.md +0 -0
  100. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/record-fact/agents/openai.yaml +0 -0
  101. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/setup/SKILL.md +0 -0
  102. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/setup/agents/openai.yaml +0 -0
  103. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/stats/SKILL.md +0 -0
  104. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/stats/agents/openai.yaml +0 -0
  105. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/triage-drift/SKILL.md +0 -0
  106. {sidegraph-0.4.0 → sidegraph-0.5.0}/plugin/sidegraph/skills/triage-drift/agents/openai.yaml +0 -0
  107. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/anchoring.py +0 -0
  108. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/bootstrap/__init__.py +0 -0
  109. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/bootstrap/apply.py +0 -0
  110. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/bootstrap/catalog.py +0 -0
  111. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/bootstrap/cli.py +0 -0
  112. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/bootstrap/integrations.py +0 -0
  113. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/bootstrap/model.py +0 -0
  114. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/bootstrap/planner.py +0 -0
  115. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/bootstrap/proof.py +0 -0
  116. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/bootstrap/review.py +0 -0
  117. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/bootstrap/scan.py +0 -0
  118. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/capture.py +0 -0
  119. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/config.py +0 -0
  120. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/doctor.py +0 -0
  121. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/domains.py +0 -0
  122. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/engine/__init__.py +0 -0
  123. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/engine/reader.py +0 -0
  124. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/gitio.py +0 -0
  125. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/host/__init__.py +0 -0
  126. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/host/claude_settings.py +0 -0
  127. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/host/hooks.py +0 -0
  128. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/importer.py +0 -0
  129. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/okf.py +0 -0
  130. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/profiles.py +0 -0
  131. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/retrieval.py +0 -0
  132. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/schema.py +0 -0
  133. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/server.py +0 -0
  134. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/stats/__init__.py +0 -0
  135. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/stats/model.py +0 -0
  136. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/stats/render.py +0 -0
  137. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/store.py +0 -0
  138. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/sync.py +0 -0
  139. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/viz/__init__.py +0 -0
  140. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/viz/assets/vis-network.min.js +0 -0
  141. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/viz/model.py +0 -0
  142. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/viz/render.py +0 -0
  143. {sidegraph-0.4.0 → sidegraph-0.5.0}/src/sidegraph/viz/template.html +0 -0
  144. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/__init__.py +0 -0
  145. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/auto_policy/parity_goldens.json +0 -0
  146. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/bitfinex_slice.json +0 -0
  147. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/bootstrap/flows/bmad/_bmad-output/planning-artifacts/architecture/cache/ARCHITECTURE-SPINE.md +0 -0
  148. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/bootstrap/flows/generic-adr/docs/adr/001-retry.md +0 -0
  149. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/bootstrap/flows/genkovich-sdd/docs/features/cache/adr/001.md +0 -0
  150. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/bootstrap/flows/spec-kit/specs/cache/plan.md +0 -0
  151. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/bootstrap/flows/superpowers/docs/superpowers/specs/2026-07-01-cache-design.md +0 -0
  152. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/bootstrap/graph.json +0 -0
  153. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/bootstrap/hosts/claude/.claude/settings.json +0 -0
  154. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/bootstrap/hosts/claude/.mcp.json +0 -0
  155. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/bootstrap/hosts/codex/.codex/config.toml +0 -0
  156. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/bootstrap/hosts/codex/.codex/hooks/hooks.json +0 -0
  157. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/bootstrap/scan/docs/adr/001-safe.md +0 -0
  158. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/corpus/PUBLIC_CORPORA.txt +0 -0
  159. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/corpus/openspec/_provenance.json +0 -0
  160. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/corpus/openspec/graph.json +0 -0
  161. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/corpus/self-corpus/_provenance.json +0 -0
  162. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/corpus/self-corpus/expected/retrieval-engine-reader.md +0 -0
  163. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/corpus/self-corpus/graph.json +0 -0
  164. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/adr/0001-use-sessions.md +0 -0
  165. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/bmad/ARCHITECTURE-SPINE.md +0 -0
  166. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/bmad/tree/_bmad-output/planning-artifacts/architecture/architecture-payments-2026-07-30/.memlog.md +0 -0
  167. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/bmad/tree/_bmad-output/planning-artifacts/architecture/architecture-payments-2026-07-30/ARCHITECTURE-SPINE.md +0 -0
  168. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/bmad/tree/_bmad-output/planning-artifacts/prds/prd-payments-2026-07-30/prd.md +0 -0
  169. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/genkovich/0001-queue-backpressure.md +0 -0
  170. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/genkovich/sad.md +0 -0
  171. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/genkovich/tree/docs/features/payments/adr/0001-queue-backpressure.md +0 -0
  172. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/genkovich/tree/docs/features/payments/sad.md +0 -0
  173. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/genkovich/tree/docs/features/payments/spec.md +0 -0
  174. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/openspec/design-empty.md +0 -0
  175. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/openspec/design-h3-split.md +0 -0
  176. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/openspec/design-with-summary.md +0 -0
  177. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/openspec/design.md +0 -0
  178. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/openspec/proposal-alternatives.md +0 -0
  179. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/openspec/proposal.md +0 -0
  180. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/openspec/tree/openspec/changes/add-thing/design.md +0 -0
  181. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/openspec/tree/openspec/changes/add-thing/proposal.md +0 -0
  182. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/openspec/tree/openspec/changes/add-thing/specs/cap/spec.md +0 -0
  183. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/openspec/tree/openspec/changes/add-thing/tasks.md +0 -0
  184. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/openspec/tree/openspec/changes/archive/2026-01-01-add-thing/design.md +0 -0
  185. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/openspec/tree/openspec/changes/archive/2026-01-01-add-thing/proposal.md +0 -0
  186. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/openspec/tree/openspec/config.yaml +0 -0
  187. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/openspec/tree/openspec/specs/cap/spec.md +0 -0
  188. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/spec-kit/plan.md +0 -0
  189. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/spec-kit/tree/specs/003-payment-retries/plan.md +0 -0
  190. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/spec-kit/tree/specs/003-payment-retries/spec.md +0 -0
  191. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/superpowers/2026-07-01-example-design.md +0 -0
  192. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/flows/superpowers/2026-07-01-thin-design.md +0 -0
  193. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/fixtures/mini_graph.json +0 -0
  194. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_anchoring.py +0 -0
  195. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_anchoring_mapping_refresh.py +0 -0
  196. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_bootstrap_apply.py +0 -0
  197. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_bootstrap_catalog.py +0 -0
  198. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_bootstrap_cli.py +0 -0
  199. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_bootstrap_integrations.py +0 -0
  200. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_bootstrap_model.py +0 -0
  201. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_bootstrap_planner.py +0 -0
  202. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_bootstrap_proof.py +0 -0
  203. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_bootstrap_review.py +0 -0
  204. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_bootstrap_scan.py +0 -0
  205. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_capture_auto_accept.py +0 -0
  206. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_capture_facts.py +0 -0
  207. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_capture_integration.py +0 -0
  208. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_capture_neighbors.py +0 -0
  209. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_capture_propose.py +0 -0
  210. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_capture_redact.py +0 -0
  211. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_cli_compact.py +0 -0
  212. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_cli_doctor.py +0 -0
  213. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_cli_domains.py +0 -0
  214. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_cli_okf.py +0 -0
  215. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_cli_ratify.py +0 -0
  216. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_cli_ratify_facts.py +0 -0
  217. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_cli_stats.py +0 -0
  218. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_cli_sync.py +0 -0
  219. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_cli_sync_check.py +0 -0
  220. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_cli_viz.py +0 -0
  221. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_codex_plugin.py +0 -0
  222. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_community_baseline.py +0 -0
  223. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_config.py +0 -0
  224. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_corpus_expected_renders.py +0 -0
  225. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_corpus_self_corpus.py +0 -0
  226. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_coverage_telemetry_e2e.py +0 -0
  227. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_digest_integrity.py +0 -0
  228. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_doc_corpus_integration.py +0 -0
  229. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_docs_claims.py +0 -0
  230. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_doctor.py +0 -0
  231. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_doctor_auto_share.py +0 -0
  232. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_doctor_code_drift.py +0 -0
  233. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_doctor_dangling_supports.py +0 -0
  234. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_doctor_duplicate_entity.py +0 -0
  235. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_doctor_graph_root_mismatch.py +0 -0
  236. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_doctor_never_surfaced.py +0 -0
  237. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_doctor_scan.py +0 -0
  238. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_doctor_stale_instructions.py +0 -0
  239. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_doctor_unratified_accept.py +0 -0
  240. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_domains.py +0 -0
  241. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_entity_duplicate_resolution.py +0 -0
  242. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_entity_get_or_create_race.py +0 -0
  243. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_fact_reachability_gate.py +0 -0
  244. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_flow_profile.py +0 -0
  245. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_git_bindings_blame.py +0 -0
  246. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_git_bindings_hook.py +0 -0
  247. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_git_bindings_provenance.py +0 -0
  248. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_host_claude_settings.py +0 -0
  249. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_host_drift_nudge.py +0 -0
  250. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_host_pretool.py +0 -0
  251. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_host_session_key.py +0 -0
  252. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_host_session_start.py +0 -0
  253. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_host_stop.py +0 -0
  254. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_host_touch_events.py +0 -0
  255. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_importer.py +0 -0
  256. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_mind_model_additive_fields.py +0 -0
  257. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_okf.py +0 -0
  258. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_openspec_profile.py +0 -0
  259. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_pathless_descriptor_adoption.py +0 -0
  260. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_pilot_kit_corpus_fit.py +0 -0
  261. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_proposal_lifecycle.py +0 -0
  262. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_ratify_listing_gone_dark.py +0 -0
  263. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_ratify_policy.py +0 -0
  264. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_reader_community_labels.py +0 -0
  265. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_reader_doc_nodes.py +0 -0
  266. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_reader_graph.py +0 -0
  267. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_reader_integration.py +0 -0
  268. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_reader_nodes_in_file.py +0 -0
  269. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_reader_perf.py +0 -0
  270. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_reader_rationale.py +0 -0
  271. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_reader_read.py +0 -0
  272. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_reader_resolve.py +0 -0
  273. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_reader_subdir_mismatch.py +0 -0
  274. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_reader_version.py +0 -0
  275. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_redaction_seeded.py +0 -0
  276. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_repoint_integration.py +0 -0
  277. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_retrieval_budget_counters.py +0 -0
  278. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_retrieval_context.py +0 -0
  279. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_retrieval_drift_marker.py +0 -0
  280. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_retrieval_drilldown.py +0 -0
  281. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_retrieval_facts.py +0 -0
  282. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_retrieval_ids.py +0 -0
  283. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_retrieval_integration.py +0 -0
  284. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_retrieval_rank.py +0 -0
  285. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_retrieval_seeds.py +0 -0
  286. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_retrieval_shown_ids.py +0 -0
  287. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_retrieval_terminal_evidence.py +0 -0
  288. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_retrieval_thin_tools.py +0 -0
  289. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_retrieval_toc.py +0 -0
  290. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_retrieval_toptier.py +0 -0
  291. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_retrieval_unratified.py +0 -0
  292. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_schema_descriptor.py +0 -0
  293. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_schema_domain.py +0 -0
  294. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_schema_fact.py +0 -0
  295. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_add_anchors.py +0 -0
  296. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_anchoring.py +0 -0
  297. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_capture.py +0 -0
  298. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_coverage_events.py +0 -0
  299. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_domains.py +0 -0
  300. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_drilldown.py +0 -0
  301. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_facts.py +0 -0
  302. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_find_entity.py +0 -0
  303. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_get_store.py +0 -0
  304. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_get_task_context.py +0 -0
  305. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_import.py +0 -0
  306. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_mcp_smoke.py +0 -0
  307. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_prewrite_anchor_validation.py +0 -0
  308. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_ratify_facts.py +0 -0
  309. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_redaction.py +0 -0
  310. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_render_events.py +0 -0
  311. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_supersede.py +0 -0
  312. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_sync_anchors.py +0 -0
  313. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_telemetry.py +0 -0
  314. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_thin_tools.py +0 -0
  315. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_server_verify.py +0 -0
  316. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_stats_graph.py +0 -0
  317. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_stats_model.py +0 -0
  318. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_stats_render.py +0 -0
  319. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_stats_skill.py +0 -0
  320. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_stats_snapshot.py +0 -0
  321. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store.py +0 -0
  322. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_atomic_write_tmp_names.py +0 -0
  323. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_canonical_stat.py +0 -0
  324. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_compact.py +0 -0
  325. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_compact_review.py +0 -0
  326. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_concurrent_open.py +0 -0
  327. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_coverage_telemetry.py +0 -0
  328. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_derived_community.py +0 -0
  329. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_domains.py +0 -0
  330. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_entities.py +0 -0
  331. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_fact_cascade.py +0 -0
  332. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_facts.py +0 -0
  333. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_meta.py +0 -0
  334. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_migration.py +0 -0
  335. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_mutation_guard.py +0 -0
  336. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_mutation_immediate.py +0 -0
  337. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_opens_with_merged_duplicate.py +0 -0
  338. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_persistence.py +0 -0
  339. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_ratification.py +0 -0
  340. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_rebuild_atomicity.py +0 -0
  341. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_render_events.py +0 -0
  342. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_retrieval.py +0 -0
  343. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_slug_conflicts.py +0 -0
  344. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_stamping_marker.py +0 -0
  345. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_telemetry.py +0 -0
  346. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_threading.py +0 -0
  347. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_store_tmp_sweep_age.py +0 -0
  348. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_sync_clean.py +0 -0
  349. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_sync_domains.py +0 -0
  350. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_sync_drift_cache.py +0 -0
  351. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_sync_fresh_clone.py +0 -0
  352. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_sync_integration.py +0 -0
  353. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_sync_rebind.py +0 -0
  354. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_sync_repoint.py +0 -0
  355. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_sync_report_has_findings.py +0 -0
  356. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_sync_run.py +0 -0
  357. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_sync_toc_cache.py +0 -0
  358. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_sync_volatile_heal.py +0 -0
  359. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_sync_wiring.py +0 -0
  360. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_telemetry_retention.py +0 -0
  361. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_verify_snapshot.py +0 -0
  362. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_viz_asset_packaged.py +0 -0
  363. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_viz_model.py +0 -0
  364. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_viz_render_html.py +0 -0
  365. {sidegraph-0.4.0 → sidegraph-0.5.0}/tests/test_viz_render_json.py +0 -0
@@ -105,7 +105,6 @@ jobs:
105
105
  exit 0
106
106
  fi
107
107
  uv sync --locked
108
- git fetch origin "$GITHUB_BASE_REF"
109
108
  base="$(git merge-base "origin/$GITHUB_BASE_REF" HEAD)"
110
109
  uv run sidegraph-verify --against "$base" --json
111
110
 
@@ -7,6 +7,67 @@ interfaces, exactly, and what each one promises: [`docs/reference/stability.md`]
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.5.0] — 2026-09-23
11
+
12
+ ### Changed
13
+
14
+ - **The public `main`, which the plugin manifests install from, moves only at releases.**
15
+ The plugin runs whatever `main` holds, so a snapshot pushed between releases used to reach
16
+ every plugin user at once. The release script now pushes `main` only for a release: it
17
+ refuses unless the version it carries is untagged in the public repository and
18
+ `CHANGELOG.md` dates it.
19
+
20
+ ### Fixed
21
+
22
+ - **`sidegraph-init` now says to restart the Claude Code session after it writes
23
+ `SIDEGRAPH_RATIFY_POLICY`.** The Sidegraph MCP server reads the policy from the
24
+ environment it started with, so a server started before `sidegraph-init` kept proposing
25
+ under the old policy until the session restarted, and nothing said so.
26
+ - **`sidegraph-verify --against` no longer flags a ratification of an already-committed
27
+ proposal, or a supersede/drop that touches a record written before a later schema addition
28
+ (`Provenance.commit`, `Domain.seed_anchors`/`path_prefixes`) existed.** Ratifying a
29
+ decision, fact, or domain stamps `ratified_at`/`ratified_by`, and neither field was on the
30
+ transition layer's mutable allow-list, so the stamp alone was reported as an illegal field
31
+ change. Separately, rewriting an old record that predates a field added to its schema
32
+ serializes that field back in as its default, which a plain top-level or whole-list
33
+ comparison also read as a change. The ratifier stamp may now be set once, from a proposed
34
+ record, landing on any state reached through accepted within the diffed range; an absent
35
+ field now counts as the same value as an explicit `null` or an empty list/object, at every
36
+ nesting depth and inside list items. A field whose default is a non-empty value is not
37
+ covered by this.
38
+ - **`sidegraph-import --docs` no longer aborts on a file that is not valid UTF-8.** It
39
+ decoded every document as strict UTF-8 with no handler, so one document saved in
40
+ `cp1251` (or carrying a stray non-UTF-8 byte) raised and stopped the whole run, with
41
+ nothing after it imported and no report printed. The file is now a named skip
42
+ (`skipped_undecodable`) instead: the run continues, and both the real run and
43
+ `--dry-run` print the skipped path(s), last.
44
+ - **`sidegraph-import --docs` now reads a UTF-8 document that starts with a byte-order mark
45
+ (BOM).** The BOM used to survive into the parsed text, so the H1 and any frontmatter never
46
+ matched, and a real decision document was silently counted `skipped_not_decision`. It is
47
+ now stripped before parsing. `sidegraph-bootstrap` still reads such a file without
48
+ stripping it.
49
+
50
+ Re-importing a BOM document that an earlier version did import can change its record,
51
+ once:
52
+ - On the `openspec` profile, a BOM `proposal.md` had imported under a title built from its
53
+ path. The re-import supersedes it with the real H1 title.
54
+ - Frontmatter the BOM hid is now honoured. A document marked `status: superseded` now
55
+ counts `skipped_superseded_frontmatter`, and its earlier live record stays as it is.
56
+ - A document marked as a draft, proposed, pending or under review keeps its earlier
57
+ `accepted` record: a re-import compares content, not status, and the content is
58
+ unchanged.
59
+
60
+ Retiring or re-proposing such a record is manual.
61
+
62
+ ### Security
63
+
64
+ - **The lockfile moves `anyio` from 4.14.1 to 4.15.1**, past three advisories fixed in
65
+ 4.14.2: TLS certificate spoofing through IDNA 2003 host-name encoding (critical),
66
+ `run_process` keeping the parent's supplementary groups (high), and process-pool workers
67
+ blocking on undrained stderr (moderate). The published package does not pin `anyio`, so
68
+ this changes development and CI environments and installs made from `uv.lock`; a fresh
69
+ install already resolves a fixed version.
70
+
10
71
  ## [0.4.0] — 2026-09-22
11
72
 
12
73
  ### Fixed
@@ -13,7 +13,7 @@ task-aware retrieval + decision memory riding on the engine's entity graph.
13
13
  continuing participant in the project. A core feature must preserve, organize, or deliver
14
14
  accumulated project understanding. Document search by itself is not the product.
15
15
 
16
- Sidegraph is pre-1.0 (v0.4.0) — the full loop (capture, ratification, mistakes-first
16
+ Sidegraph is pre-1.0 (v0.5.0) — the full loop (capture, ratification, mistakes-first
17
17
  retrieval, refactor-surviving re-anchoring, semantic docs layer, mind-model domains, and the
18
18
  facts evidence layer) ships and is exercised end-to-end, but interfaces may still move before
19
19
  a stable release. See
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: sidegraph
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Own the memory, rent the graph — a durable decision/lessons layer as a sidecar over a code-graph engine.
5
5
  Project-URL: Homepage, https://github.com/SantyagoSeaman/sidegraph
6
6
  Project-URL: Repository, https://github.com/SantyagoSeaman/sidegraph
@@ -84,9 +84,10 @@ only ever reads `graph.json`, so the plain install above is all it needs.
84
84
  uv tool install sidegraph # from PyPI — puts sidegraph-init / sidegraph-mcp / … on PATH
85
85
  sidegraph-init
86
86
 
87
- # Prefer the latest unreleased build straight from git instead of PyPI? Swap step 3 for:
87
+ # Prefer running straight from git instead of PyPI? Swap step 3 for:
88
88
  # uvx --from git+https://github.com/SantyagoSeaman/sidegraph.git@main sidegraph-init
89
- # `@main` is a mutable ref — it moves under you. Pin a tag or a SHA for CI.
89
+ # `@main` moves only when a release is cut. It is still a mutable ref: a later cache refresh can
90
+ # install a newer one. Pin a tag or a SHA for CI.
90
91
  ```
91
92
 
92
93
  4. **Name your domains** — turns the graph's communities into a described table of
@@ -56,9 +56,10 @@ only ever reads `graph.json`, so the plain install above is all it needs.
56
56
  uv tool install sidegraph # from PyPI — puts sidegraph-init / sidegraph-mcp / … on PATH
57
57
  sidegraph-init
58
58
 
59
- # Prefer the latest unreleased build straight from git instead of PyPI? Swap step 3 for:
59
+ # Prefer running straight from git instead of PyPI? Swap step 3 for:
60
60
  # uvx --from git+https://github.com/SantyagoSeaman/sidegraph.git@main sidegraph-init
61
- # `@main` is a mutable ref — it moves under you. Pin a tag or a SHA for CI.
61
+ # `@main` moves only when a release is cut. It is still a mutable ref: a later cache refresh can
62
+ # install a newer one. Pin a tag or a SHA for CI.
62
63
  ```
63
64
 
64
65
  4. **Name your domains** — turns the graph's communities into a described table of
@@ -17,7 +17,8 @@ The `git+…@main` examples below track a mutable branch: a later `uvx` cache re
17
17
  install different code. That is useful while trying Sidegraph. For CI, shared setup, or a
18
18
  measured pilot, replace `@main` with a release tag or commit SHA, for example
19
19
  `git+https://github.com/SantyagoSeaman/sidegraph.git@<sha>`. The plugin marketplace is the
20
- exception: its public manifests intentionally track `main`. See
20
+ exception: its public manifests intentionally track `main`, and the release process moves
21
+ that `main` only when it cuts a release. See
21
22
  [`reference/stability.md`](../reference/stability.md) for the promises made by each surface.
22
23
 
23
24
  ## Install Sidegraph
@@ -54,11 +55,13 @@ PyPI package. See
54
55
  the integration details for [Claude Code](../integrations/claude-code.md#plugin-install-path)
55
56
  or [Codex](../integrations/codex.md#plugin-install-path).
56
57
 
57
- ### Option C — uvx directly from git (latest / unreleased)
58
+ ### Option C — uvx directly from git
58
59
 
59
- Want the latest unreleased build, prefer manual wiring, or need an entry point outside Claude
60
- Code (e.g. `sidegraph-init` from a plain terminal)? Run it straight from the repository with
61
- [`uvx`](https://docs.astral.sh/uv/guides/tools/) — no persistent install, no local checkout:
60
+ Prefer manual wiring, or need an entry point outside Claude Code (e.g. `sidegraph-init` from
61
+ a plain terminal)? Run it straight from the repository with
62
+ [`uvx`](https://docs.astral.sh/uv/guides/tools/), with no persistent install and no local
63
+ checkout. `@main` moves only when a release is cut. For reproducibility, pin a tag or commit SHA instead
64
+ (see "Mutable development references" above):
62
65
 
63
66
  ```bash
64
67
  uvx --from git+https://github.com/SantyagoSeaman/sidegraph.git@main sidegraph-init
@@ -132,6 +132,11 @@ the Sidegraph checkout):
132
132
  `--ratify-policy VALUE` sets the value with no prompt, still never overwriting an
133
133
  existing one, for a fully scripted setup that wants an explicit answer.
134
134
 
135
+ When the value is written, the line after it says to restart the Claude Code session:
136
+ the Sidegraph MCP server reads the policy from the environment it started with, so a
137
+ server started before `sidegraph-init` keeps proposing under the old policy until the
138
+ session restarts.
139
+
135
140
  Whatever happens above, a host without `.claude/settings.json` (Codex CLI, CI) gets the
136
141
  equivalent as a plain `export SIDEGRAPH_RATIFY_POLICY=<value>` line. A settings-file
137
142
  problem never fails the store creation in step 1.
@@ -632,24 +637,39 @@ import on their own. Both the real run and `--dry-run` print an extra line whene
632
637
  `skipped_degenerate_parent > 0`: `N split parent(s) skipped as degenerate (echoed context
633
638
  or empty choice) — children imported on their own`.
634
639
 
640
+ **A file that is not valid UTF-8 is skipped, named, and the run continues.** Every document
641
+ is decoded as `utf-8-sig` — plain UTF-8, with a leading byte-order mark stripped when one is
642
+ present. No encoding is guessed. A file whose bytes don't decode (for example, one saved in
643
+ `cp1251`) is counted `skipped_undecodable` instead of raising and aborting the whole run.
644
+ Both the real run and `--dry-run` print an extra block, last, whenever
645
+ `skipped_undecodable > 0`:
646
+
647
+ ```
648
+ N file(s) skipped: not valid UTF-8, re-save as UTF-8 to import:
649
+ path/to/bad-file.md
650
+ ```
651
+
635
652
  **Real run:** `imported N decision(s), superseded M (skipped: A existing, B unanchorable,
636
653
  C not-decision-shaped, D unparseable, E superseded-frontmatter, F outside-profile)`, followed
637
654
  by the status-derived-proposed line above when applicable, followed by the template-skip line
638
- and the degenerate-parent line above when applicable. A non-zero `F` is the profile scope
639
- filter, not a parse failure — see the `--any-doc` note under the flag table above. Under a
640
- non-`manual` `SIDEGRAPH_RATIFY_POLICY`: `imported N decision(s), superseded M, auto-ratified K
641
- (skipped: …)` — only fresh `--propose` writes that land as a new `proposed` record are
642
- eligible; status-derived and `superseded` re-imports never auto-ratify; each failed
643
- auto-ratify attempt prints `auto-ratify failure: <id>: <reason>` on stderr; `--dry-run`
644
- never carries the segment.
655
+ and the degenerate-parent line above when applicable, and the undecodable-file block above
656
+ last when applicable. A non-zero `F` is the profile scope filter, not a parse failure — see
657
+ the `--any-doc` note under the flag table above. Under a non-`manual`
658
+ `SIDEGRAPH_RATIFY_POLICY`: `imported N decision(s), superseded M, auto-ratified K (skipped:
659
+ …)` — only fresh `--propose` writes that land as a new `proposed` record are eligible;
660
+ status-derived and `superseded` re-imports never auto-ratify; each failed auto-ratify attempt
661
+ prints `auto-ratify failure: <id>: <reason>` on stderr; `--dry-run` never carries the
662
+ segment.
645
663
 
646
664
  **`--dry-run`:** one line per candidate — `<path>: [imported|superseded] <title>`, with any
647
665
  skipped-anchor reasons indented underneath (` anchor skipped: <name> (<reason>)`) —
648
666
  followed by a blank line, `would import N decision(s), supersede M (...)` (same skip breakdown
649
667
  as the real-run line above), the status-derived-proposed line when applicable (`N would land
650
668
  proposed (...)`), the template-skip line and the degenerate-parent line above when
651
- applicable, and a per-file breakdown. No decision or binding is written; opening the store
652
- may still migrate supported legacy data or rebuild the derived index.
669
+ applicable, and a per-file breakdown. The undecodable-file block above prints last, after
670
+ the per-file breakdown. Both indent their lines by two spaces, so breakdown lines printed
671
+ after the block would read as more undecodable files. No decision or binding is written; opening the
672
+ store may still migrate supported legacy data or rebuild the derived index.
653
673
 
654
674
  **Exit code:** `0` on success, including an empty run and every `--dry-run` invocation.
655
675
  Returns `1` before touching the graph or store if `--profile` names an unknown profile
@@ -903,21 +923,28 @@ plain-text line):
903
923
  | `illegal-deletion` | transition | a record file — or an already-published archive segment — was deleted with no sanctioned reason. The one exception: a decision/domain whose id is present in an `archive/*.jsonl` segment in the new tree (a legitimate `sidegraph-compact`) |
904
924
 
905
925
  **What's legally mutable** (transition layer only; derived from `store.py`'s write methods,
906
- not invented — see `verify.py`'s module docstring for the line-numbered derivation):
926
+ not invented — see `verify.py`'s module comment above the mutable-field tables for exactly
927
+ which methods):
907
928
 
908
929
  - **Decisions:** `status` (only along a real transition — `proposed→accepted`,
909
930
  `proposed→rejected`, `proposed→superseded`, `accepted→superseded`, `accepted→deprecated`),
910
- `valid_to` (`null → value`, once). Everything else (`title`, `kind`, `context`, `choice`,
911
- `rejected`, `consequences`, `layer`, `valid_from`, `supersedes`, `provenance`) is immutable.
912
- - **Facts:** the same shape, `status`/`valid_to`, but **without** `accepted→deprecated`
913
- — no live write path ever sets a fact `deprecated` (`schema.py`: "DEPRECATED unused for
914
- facts", unlike the decision-side forward-compat carve-out above), so that jump is *not*
915
- legal here and is flagged `illegal-status-jump` if it appears. Legal: `proposed→accepted`,
916
- `proposed→rejected`, `proposed→superseded`, `accepted→superseded`. Everything else
917
- (`statement`, `source`, `supports`, `valid_from`, `supersedes`, `provenance`) is immutable.
931
+ `valid_to` (`null → value`, once). `ratified_at`/`ratified_by` may be set once, when a
932
+ proposed record is accepted — even if it moves on to a later state (`superseded`) within the
933
+ same diffed range — and never changes again after that. Everything else (`title`, `kind`,
934
+ `context`, `choice`, `rejected`, `consequences`, `layer`, `valid_from`, `supersedes`,
935
+ `provenance`) is immutable.
936
+ - **Facts:** the same shape, `status`/`valid_to`/the ratifier stamp, but **without**
937
+ `accepted→deprecated` — no live write path ever sets a fact `deprecated` (`schema.py`:
938
+ "DEPRECATED unused for facts", unlike the decision-side forward-compat carve-out above), so
939
+ that jump is *not* legal here and is flagged `illegal-status-jump` if it appears. Legal:
940
+ `proposed→accepted`, `proposed→rejected`, `proposed→superseded`, `accepted→superseded`.
941
+ Everything else (`statement`, `source`, `supports`, `valid_from`, `supersedes`,
942
+ `provenance`) is immutable.
918
943
  - **Domains:** `status` (`proposed→accepted`, `proposed→dropped`, `accepted→dropped`,
919
944
  `proposed→superseded`, `accepted→superseded`, `dropped→superseded`). `communities` is
920
- index-only and never appears in the canonical file.
945
+ index-only and never appears in the canonical file. `ratified_at`/`ratified_by` may be set
946
+ once, when a proposed record is accepted — even if it moves on to a later state (`dropped`,
947
+ `superseded`) within the same diffed range — and never changes again after that.
921
948
  Everything else (`domain_id`, `slug`, `title`, `summary`, `parent_id`, `path_prefixes`,
922
949
  `seed_anchors`, `provenance`) is immutable.
923
950
  - **Entities:** `descriptor` may change when sync confirms a durable leaf-file move.
@@ -933,6 +960,21 @@ not invented — see `verify.py`'s module docstring for the line-numbered deriva
933
960
  `A`) is legal; any modification or deletion of an already-published one is always
934
961
  illegal.
935
962
 
963
+ A field an older file was written before its schema even had is simply absent from that
964
+ file — whether its default is `null` (`Provenance.commit` today) or an empty list/object
965
+ (`Domain.seed_anchors`/`path_prefixes` today). The next legal rewrite re-serializes the record
966
+ and fills the field back in with that default. That alone is never a change: an absent field
967
+ counts as the same value as an explicit `null` or an empty list/object, inside nested objects
968
+ and inside the items of a list, everywhere this table calls a field immutable. A field whose
969
+ default is a *non-empty* value (`Decision.scope`, `"repo"`) is **not** covered by this — an
970
+ absent key there still reads as changed — but no file in this store lacks `scope` today.
971
+
972
+ **Known limit:** `verify_against` compares only the net change across the whole diffed range
973
+ (see "diffs the CURRENT WORKING TREE" below), not each intermediate commit. So a ratifier
974
+ stamp appearing on a record that ends up `dropped`/`superseded` in that same range cannot be
975
+ told apart from a record that was legally accepted and only later dropped or superseded — the
976
+ store's own git history, not this lint, is the evidence for who ratified what and when.
977
+
936
978
  **`--against <ref>` diffs the CURRENT WORKING TREE against `<ref>`'s snapshot — not two fixed
937
979
  points in history.** Passing a moving branch name directly (`--against origin/main`) is only
938
980
  correct when your checkout's `HEAD` actually sits at that branch's tip; on a PR branch that's
@@ -58,15 +58,41 @@ Prepare the release on internal `main`, then run the clean-tree preflight:
58
58
  7. Commit the release preparation on internal `main`. Both the internal and public checkouts
59
59
  must now be clean.
60
60
  8. Run `uv run python tools/preflight_release.py --online`. It verifies version agreement,
61
- changelog shape, clean branches, target repository, and tag availability. Run it **after**
62
- the release changes are committed: running it first can only report the work you have not
63
- prepared yet.
61
+ changelog shape, clean branches, and target repository, and that `vX.Y.Z` is not already
62
+ tagged in the **internal** checkout or its origin. Run it **after** the release changes are
63
+ committed: running it first can only report the work you have not prepared yet.
64
64
  9. Cut the allowlisted public snapshot with
65
65
  `tools/release-public.sh "release: vX.Y.Z" --push`. This creates and pushes the public
66
- `main` commit that will receive the release tag. Refresh `demo` separately when required.
66
+ `main` commit that will receive the release tag. `--push` itself refuses unless `vX.Y.Z`
67
+ is untagged in the **public** repository and `CHANGELOG.md` dates it (see "The public
68
+ main moves only at releases" below). Refresh `demo` separately when required.
67
69
 
68
70
  Only after all nine steps pass is the public snapshot ready to tag.
69
71
 
72
+ ### The public main moves only at releases
73
+
74
+ Every plugin manifest installs from the public repository's `main`, and `uv` re-resolves that
75
+ branch reference on every call, so pushing `main` reaches every plugin user at once. For that
76
+ reason the public `main` moves only as part of a release: `tools/release-public.sh --push`
77
+ refuses to push it unless the version being published is untagged in the public repository
78
+ and `CHANGELOG.md` dates it, checked before anything is written and again right before the
79
+ push. A docs-only or other non-release change waits for the next release, or ships in an
80
+ urgent patch release.
81
+
82
+ Two consequences follow. First, bump the version in `pyproject.toml` and date its
83
+ `CHANGELOG.md` heading only as part of release preparation (steps 1 and 2 above). Once both
84
+ are on `main`, every snapshot counts as a release until the tag exists, so doing them ahead of
85
+ the release defeats the guard. Second, re-cutting a version whose tag-driven publish failed still needs
86
+ its tag deleted in both the public checkout and its origin before the next `--push`;
87
+ otherwise the push is refused.
88
+
89
+ Between the release-preparation commit and the tag, every `--push` is a re-cut of that same
90
+ release, and the tag goes on the last one. Tag promptly after the push, so the window stays
91
+ short.
92
+
93
+ Never push the public `main` by hand: the gate lives in `tools/release-public.sh`, and a
94
+ manual `git push` from the public checkout bypasses it.
95
+
70
96
  ## Tag-driven publish flow
71
97
 
72
98
  PyPI publishing is driven entirely by a git tag. There is deliberately no manual dispatch: a
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "sidegraph",
3
3
  "displayName": "Sidegraph",
4
- "version": "0.4.0",
4
+ "version": "0.5.0",
5
5
  "description": "Durable decision/lessons memory over your code graph — ADRs, gotchas, and constraints that survive refactors, retrieved mistakes-first.",
6
6
  "author": { "name": "SantyagoSeaman", "url": "https://github.com/SantyagoSeaman" },
7
7
  "homepage": "https://github.com/SantyagoSeaman/sidegraph",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sidegraph",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Durable decision/lessons memory over your code graph — ADRs, gotchas, and constraints that survive refactors, retrieved mistakes-first.",
5
5
  "license": "Apache-2.0",
6
6
  "skills": "./skills/",
@@ -49,8 +49,8 @@ Costs nothing, writes nothing, and shows exactly what a real run would do. Read
49
49
  report closely:
50
50
 
51
51
  - `would import N decision(s), supersede M (skipped: A existing, B unanchorable, C
52
- not-decision-shaped, D unparseable, E superseded-frontmatter)` — plus per-file breakdown
53
- and per-anchor `anchor skipped: <name> (<reason>)` lines.
52
+ not-decision-shaped, D unparseable, E superseded-frontmatter, F outside-profile)` — plus
53
+ per-file breakdown and per-anchor `anchor skipped: <name> (<reason>)` lines.
54
54
  - **Lots of `unanchorable` + you passed an absolute path?** That's the wrong-cwd trap from
55
55
  preflight — the command itself warns when ≥ half of anchor-attempted docs miss. Re-run
56
56
  from the repo root with a relative path.
@@ -63,6 +63,11 @@ report closely:
63
63
  (Context/Decision/Status/... or Trigger/Design/...); narrative or table-structured docs
64
64
  don't qualify (by design, for now) — record their content via
65
65
  `sidegraph:record-decision` if it matters.
66
+ - **`N file(s) skipped: not valid UTF-8, re-save as UTF-8 to import:`** (printed last, with
67
+ the listed paths) — a file whose bytes aren't valid UTF-8 is skipped and named instead of
68
+ aborting the run. Re-save the listed file(s) as UTF-8 and re-run to import them. No
69
+ encoding is guessed on your behalf. A UTF-8 byte-order mark is fine: it is stripped and the
70
+ file imports, so it never appears in this list.
66
71
 
67
72
  ## Gate the real run on the human
68
73
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "sidegraph"
3
- version = "0.4.0"
3
+ version = "0.5.0"
4
4
  description = "Own the memory, rent the graph — a durable decision/lessons layer as a sidecar over a code-graph engine."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.13"
@@ -19,7 +19,7 @@ from .schema import (
19
19
  )
20
20
  from .store import Store
21
21
 
22
- __version__ = "0.4.0"
22
+ __version__ = "0.5.0"
23
23
 
24
24
  __all__ = [
25
25
  "SCHEMA_VERSION",
@@ -145,6 +145,12 @@ def _report_ratify_policy_write(
145
145
  which already know `value` (whatever they decided to write) before calling this."""
146
146
  if result.outcome == "written":
147
147
  print(f"wrote {rel_settings}: env.{RATIFY_POLICY_ENV_VAR}={value}")
148
+ # The MCP server reads the policy once, from the environment it started with; a
149
+ # server already running keeps proposing under the old value.
150
+ print(
151
+ " restart your Claude Code session so the Sidegraph MCP server picks it up: "
152
+ "a running server keeps the environment it started with"
153
+ )
148
154
  elif result.outcome == "already_set":
149
155
  print(
150
156
  f"{rel_settings} already sets "
@@ -923,6 +929,16 @@ def _import_docs_mode(args: argparse.Namespace) -> int:
923
929
  )
924
930
  for fp, n in sorted(report.by_file().items()):
925
931
  print(f" {fp}: {n}")
932
+ # D4 (design/superpowers/specs/2026-09-23-doc-import-encoding-design.md): printed
933
+ # LAST. The by_file lines share the block's two-space indent, so any printed after
934
+ # the block would read as more undecodable files.
935
+ if report.skipped_undecodable:
936
+ print(
937
+ f"{report.skipped_undecodable} file(s) skipped: not valid UTF-8, re-save as "
938
+ "UTF-8 to import:"
939
+ )
940
+ for fp in report.undecodable_files:
941
+ print(f" {fp}")
926
942
  return 0
927
943
 
928
944
  # Design D6: the count is added to the non-dry-run summary line only, only when the
@@ -954,6 +970,16 @@ def _import_docs_mode(args: argparse.Namespace) -> int:
954
970
  f"{report.skipped_degenerate_parent} split parent(s) skipped as degenerate "
955
971
  "(echoed context or empty choice) — children imported on their own"
956
972
  )
973
+ # D4 (design/superpowers/specs/2026-09-23-doc-import-encoding-design.md): printed last
974
+ # on stdout, after the degenerate-parent line — the auto-ratify-failures loop below
975
+ # goes to stderr, so this stays the last stdout line either way.
976
+ if report.skipped_undecodable:
977
+ print(
978
+ f"{report.skipped_undecodable} file(s) skipped: not valid UTF-8, re-save as "
979
+ "UTF-8 to import:"
980
+ )
981
+ for fp in report.undecodable_files:
982
+ print(f" {fp}")
957
983
  for entry in report.auto_ratify_failures:
958
984
  print(f"auto-ratify failure: {entry}", file=sys.stderr)
959
985
  return 0
@@ -290,11 +290,8 @@ class _DocDisposition:
290
290
  class DocImportReport(BaseModel):
291
291
  """Counts (+ dry-run-only listing) for one ``import_docs`` run. Mirrors
292
292
  ``importer.ImportReport``'s shape, widened for doc-import's extra outcomes: a doc can
293
- be *superseded* (edited since its last import) as well as freshly *imported*, and can
294
- be skipped for four distinct reasons (existing/unanchorable/not-decision-shaped/
295
- unparseable) plus a fifth doc-import-only reason (superseded/deprecated frontmatter —
296
- a history doc, never (re-)imported as live) and a sixth (a document TEMPLATE — see
297
- ``skipped_template``)."""
293
+ be *superseded* (edited since its last import) as well as freshly *imported*, and each
294
+ reason a document can be skipped for has its own ``skipped_*`` counter below."""
298
295
 
299
296
  imported: int = 0
300
297
  superseded: int = 0
@@ -332,6 +329,15 @@ class DocImportReport(BaseModel):
332
329
  # `any_doc=True` was NOT passed and at least one enumerated file falls outside the
333
330
  # profile's globs (the CLI's `--any-doc` flag restores today's no-filter behavior).
334
331
  skipped_outside_profile: int = 0
332
+ # design/superpowers/specs/2026-09-23-doc-import-encoding-design.md D1/D2/D3: a file
333
+ # that passed the profile-glob check above but whose bytes are not valid `utf-8-sig`
334
+ # (plain UTF-8, plus a stripped BOM — no encoding guessing). The decode is wrapped in a
335
+ # narrow try/except right where it happens, AFTER the profile check, so the file is a
336
+ # named skip and the run continues instead of raising `UnicodeDecodeError` and aborting
337
+ # the whole import. Distinct from `skipped_unparseable` (decision-shaped but no usable
338
+ # `choice`): this file was never even decoded far enough to be parsed.
339
+ skipped_undecodable: int = 0
340
+ undecodable_files: list[str] = Field(default_factory=list) # on-disk rel_path, in order
335
341
  # E3 (design note §7, review Major 4/Blocker N1): a split-produced parent that is
336
342
  # never written because it is degenerate — either an echo of its own `context` (rule
337
343
  # 1) or empty even after every fallback (rule 2, which previously discarded the whole
@@ -1645,7 +1651,18 @@ def import_docs(
1645
1651
 
1646
1652
  source_bytes = Path(rel_path).read_bytes()
1647
1653
  source_hash = hashlib.sha256(source_bytes).hexdigest()
1648
- raw_text = source_bytes.decode().replace("\r\n", "\n").replace("\r", "\n")
1654
+ # D1/D2 (design/superpowers/specs/2026-09-23-doc-import-encoding-design.md): decode
1655
+ # as utf-8-sig (plain UTF-8 plus a stripped BOM, never guessed) inside a narrow
1656
+ # try/except right where the decode happens — a file that fails is a named skip,
1657
+ # not an aborted run. D3: this sits AFTER the profile-scope check above, so an
1658
+ # out-of-profile file is still never even opened.
1659
+ try:
1660
+ decoded = source_bytes.decode("utf-8-sig")
1661
+ except UnicodeDecodeError:
1662
+ report.skipped_undecodable += 1
1663
+ report.undecodable_files.append(rel_path)
1664
+ continue
1665
+ raw_text = decoded.replace("\r\n", "\n").replace("\r", "\n")
1649
1666
  # E2 (design note §6): the NORMALIZED path — not the on-disk `rel_path` — is what
1650
1667
  # gets stamped into `context`/children's `effective_ref`/`provenance.ref` (via
1651
1668
  # `DocWriteRequest.rel_path` below), so an unedited doc re-imported after an