scar-cli 0.20.0__tar.gz → 0.22.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 (158) hide show
  1. scar_cli-0.22.0/.github/ISSUE_TEMPLATE/bug_report.yml +112 -0
  2. scar_cli-0.22.0/.github/ISSUE_TEMPLATE/config.yml +18 -0
  3. scar_cli-0.22.0/.github/ISSUE_TEMPLATE/feature_request.yml +70 -0
  4. scar_cli-0.22.0/.github/PULL_REQUEST_TEMPLATE.md +70 -0
  5. {scar_cli-0.20.0 → scar_cli-0.22.0}/.github/workflows/ci.yml +33 -2
  6. {scar_cli-0.20.0 → scar_cli-0.22.0}/.gitignore +5 -0
  7. scar_cli-0.22.0/.release-please-manifest.json +3 -0
  8. scar_cli-0.22.0/.scars/0018-cli-subparsers-register-through-_add-wrapper.landmine.md +35 -0
  9. scar_cli-0.20.0/.scars/candidates/token-in-scar-body-cannot-prove-hook-delivery.md → scar_cli-0.22.0/.scars/0019-token-in-scar-body-cannot-prove-hook-delivery.deadend.md +3 -8
  10. scar_cli-0.22.0/.scars/candidates/one-directional-health-check-leaves-flattering-direction-unwatched.md +42 -0
  11. scar_cli-0.22.0/.scars/candidates/posttool-fix-in-hooks-does-not-reach-codex-or-cascade.md +36 -0
  12. {scar_cli-0.20.0/src/scar/skills/scar-authoring/assets → scar_cli-0.22.0/.scars}/template.md +1 -1
  13. {scar_cli-0.20.0 → scar_cli-0.22.0}/CHANGELOG.md +47 -0
  14. {scar_cli-0.20.0 → scar_cli-0.22.0}/PKG-INFO +1 -1
  15. {scar_cli-0.20.0 → scar_cli-0.22.0}/SCAR-FORMAT.md +5 -2
  16. scar_cli-0.22.0/SPEC.md +466 -0
  17. scar_cli-0.22.0/codecov.yml +71 -0
  18. {scar_cli-0.20.0 → scar_cli-0.22.0}/plugin/plugin.json +1 -1
  19. {scar_cli-0.20.0/.scars → scar_cli-0.22.0/plugin/skills/scar-authoring/assets}/template.md +1 -1
  20. {scar_cli-0.20.0 → scar_cli-0.22.0}/pyproject.toml +2 -2
  21. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/cascade.py +32 -13
  22. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/cli.py +501 -16
  23. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/codex.py +53 -18
  24. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/hooks.py +200 -27
  25. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/installer.py +63 -2
  26. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/lint.py +17 -0
  27. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/match.py +146 -23
  28. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/model.py +46 -1
  29. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/orphan.py +8 -1
  30. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/reanchor.py +15 -0
  31. scar_cli-0.22.0/src/scar/review.py +173 -0
  32. {scar_cli-0.20.0/plugin → scar_cli-0.22.0/src/scar}/skills/scar-authoring/assets/template.md +1 -1
  33. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/store.py +30 -7
  34. scar_cli-0.22.0/tests/test_anchor_kind.py +275 -0
  35. scar_cli-0.22.0/tests/test_block_capability.py +175 -0
  36. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_cascade.py +67 -1
  37. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_cli.py +519 -7
  38. scar_cli-0.22.0/tests/test_codex.py +447 -0
  39. scar_cli-0.22.0/tests/test_context_size.py +159 -0
  40. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_hooks.py +185 -1
  41. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_installer.py +96 -0
  42. scar_cli-0.22.0/tests/test_json_contract.py +273 -0
  43. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_lint.py +26 -0
  44. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_match.py +80 -0
  45. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_model.py +53 -0
  46. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_orphan.py +52 -7
  47. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_reanchor.py +26 -0
  48. scar_cli-0.22.0/tests/test_review.py +362 -0
  49. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_store.py +138 -6
  50. scar_cli-0.22.0/tests/test_verdict_observability.py +368 -0
  51. {scar_cli-0.20.0 → scar_cli-0.22.0}/uv.lock +150 -1
  52. {scar_cli-0.20.0 → scar_cli-0.22.0}/website/docs/methodology.md +24 -1
  53. scar_cli-0.20.0/.release-please-manifest.json +0 -3
  54. scar_cli-0.20.0/SPEC.md +0 -164
  55. scar_cli-0.20.0/tests/test_codex.py +0 -253
  56. {scar_cli-0.20.0 → scar_cli-0.22.0}/.claude-plugin/marketplace.json +0 -0
  57. {scar_cli-0.20.0 → scar_cli-0.22.0}/.github/workflows/docs.yml +0 -0
  58. {scar_cli-0.20.0 → scar_cli-0.22.0}/.github/workflows/pr-validation.yml +0 -0
  59. {scar_cli-0.20.0 → scar_cli-0.22.0}/.github/workflows/release.yml +0 -0
  60. {scar_cli-0.20.0 → scar_cli-0.22.0}/.pre-commit-config.yaml +0 -0
  61. {scar_cli-0.20.0 → scar_cli-0.22.0}/.pre-commit-hooks.yaml +0 -0
  62. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0001-git-grep-ere-pitfalls.landmine.md +0 -0
  63. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0002-agent-direct-hook-install.deadend.md +0 -0
  64. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0003-installer-binds-to-active-venv-scar.landmine.md +0 -0
  65. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0004-promote-roundtrip-drops-expires-evidence.landmine.md +0 -0
  66. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0005-history-rewrite-orphans-commit-evidence.landmine.md +0 -0
  67. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0006-yaml-pattern-anchor-over-escaping.landmine.md +0 -0
  68. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0007-release-please-config-change-skips-open-pr.landmine.md +0 -0
  69. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0008-rich-output-nontty-must-bypass-rich.landmine.md +0 -0
  70. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0009-anchor-survival-harness-measures-symbol-not-path-anchors.deadend.md +0 -0
  71. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0010-git-returncode-128-vs-empty-tracked-set.landmine.md +0 -0
  72. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0011-lint-compile-check-lets-redos-anchors-hang-hotpath.landmine.md +0 -0
  73. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0012-firing-log-readers-need-blanket-guards.landmine.md +0 -0
  74. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0013-docusaurus-future-v4-requires-faster-dep.landmine.md +0 -0
  75. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0014-inject-flag-dest-collides-with-subcommand-dispatch.landmine.md +0 -0
  76. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0015-zero-checkruns-means-approval-gate-not-workflow-bug.deadend.md +0 -0
  77. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0016-hook-ownership-must-be-kind-scoped.landmine.md +0 -0
  78. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/0017-codex-pretool-context-can-replay-apply-patch.landmine.md +0 -0
  79. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/README.md +0 -0
  80. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/candidates/cascade-block-exit-collides-with-argparse.md +0 -0
  81. {scar_cli-0.20.0 → scar_cli-0.22.0}/.scars/candidates/codex-hook-delivery-needs-user-file-and-trust.md +0 -0
  82. {scar_cli-0.20.0 → scar_cli-0.22.0}/AGENTS.md +0 -0
  83. {scar_cli-0.20.0 → scar_cli-0.22.0}/CONTRIBUTING.md +0 -0
  84. {scar_cli-0.20.0 → scar_cli-0.22.0}/IDEA.md +0 -0
  85. {scar_cli-0.20.0 → scar_cli-0.22.0}/LICENSE +0 -0
  86. {scar_cli-0.20.0 → scar_cli-0.22.0}/README.md +0 -0
  87. {scar_cli-0.20.0 → scar_cli-0.22.0}/ROADMAP.md +0 -0
  88. {scar_cli-0.20.0 → scar_cli-0.22.0}/STRESS-TEST.md +0 -0
  89. {scar_cli-0.20.0 → scar_cli-0.22.0}/action.yml +0 -0
  90. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/anchor-survival/PROTOCOL.md +0 -0
  91. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/anchor-survival/RESULTS.md +0 -0
  92. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/anchor-survival/long_replay.py +0 -0
  93. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/anchor-survival/replay.py +0 -0
  94. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/anchor-survival/replay_shipped.py +0 -0
  95. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/anchor-survival/test_replay_shipped.py +0 -0
  96. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/auto-authorship/FINDINGS.md +0 -0
  97. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/auto-authorship/PROTOCOL.md +0 -0
  98. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/fence-honor/.gitignore +0 -0
  99. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/fence-honor/PROTOCOL.md +0 -0
  100. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/fence-honor/RESULTS.md +0 -0
  101. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/fence-honor/fixture/.scars/0001-vendor-retry-window.fence.md +0 -0
  102. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/fence-honor/fixture/.scars/0002-evicting-session-store.deadend.md +0 -0
  103. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/fence-honor/fixture/.scars/0003-export-column-order.landmine.md +0 -0
  104. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/fence-honor/fixture/README.md +0 -0
  105. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/fence-honor/fixture/payments/retry.py +0 -0
  106. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/fence-honor/fixture/reports/export.py +0 -0
  107. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/fence-honor/fixture/services/sessions.py +0 -0
  108. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/fence-honor/grade.py +0 -0
  109. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/harvest/PROTOCOL.md +0 -0
  110. {scar_cli-0.20.0 → scar_cli-0.22.0}/experiments/harvest/harvest.py +0 -0
  111. {scar_cli-0.20.0 → scar_cli-0.22.0}/hook/scar-hooks.py +0 -0
  112. {scar_cli-0.20.0 → scar_cli-0.22.0}/plugin/hooks/hooks.json +0 -0
  113. {scar_cli-0.20.0 → scar_cli-0.22.0}/plugin/hooks/run.sh +0 -0
  114. {scar_cli-0.20.0 → scar_cli-0.22.0}/plugin/skills/scar-authoring/SKILL.md +0 -0
  115. {scar_cli-0.20.0 → scar_cli-0.22.0}/release-please-config.json +0 -0
  116. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/__init__.py +0 -0
  117. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/agent.py +0 -0
  118. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/draftcheck.py +0 -0
  119. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/evidence.py +0 -0
  120. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/gc.py +0 -0
  121. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/harvest.py +0 -0
  122. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/mcp.py +0 -0
  123. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/output.py +0 -0
  124. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/renames.py +0 -0
  125. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/render.py +0 -0
  126. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/skills/scar-authoring/SKILL.md +0 -0
  127. {scar_cli-0.20.0 → scar_cli-0.22.0}/src/scar/symbols.py +0 -0
  128. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/conftest.py +0 -0
  129. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_action_yml.py +0 -0
  130. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_docs.py +0 -0
  131. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_draftcheck.py +0 -0
  132. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_evidence.py +0 -0
  133. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_gc.py +0 -0
  134. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_git_hook.py +0 -0
  135. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_harvest.py +0 -0
  136. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_lifecycle.py +0 -0
  137. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_mcp.py +0 -0
  138. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_output.py +0 -0
  139. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_plugin.py +0 -0
  140. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_precommit_hooks.py +0 -0
  141. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_renames.py +0 -0
  142. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_render.py +0 -0
  143. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_skill.py +0 -0
  144. {scar_cli-0.20.0 → scar_cli-0.22.0}/tests/test_symbols.py +0 -0
  145. {scar_cli-0.20.0 → scar_cli-0.22.0}/website/.gitignore +0 -0
  146. {scar_cli-0.20.0 → scar_cli-0.22.0}/website/docs/agents.md +0 -0
  147. {scar_cli-0.20.0 → scar_cli-0.22.0}/website/docs/concepts.md +0 -0
  148. {scar_cli-0.20.0 → scar_cli-0.22.0}/website/docs/index.md +0 -0
  149. {scar_cli-0.20.0 → scar_cli-0.22.0}/website/docs/quickstart.md +0 -0
  150. {scar_cli-0.20.0 → scar_cli-0.22.0}/website/docusaurus.config.ts +0 -0
  151. {scar_cli-0.20.0 → scar_cli-0.22.0}/website/package-lock.json +0 -0
  152. {scar_cli-0.20.0 → scar_cli-0.22.0}/website/package.json +0 -0
  153. {scar_cli-0.20.0 → scar_cli-0.22.0}/website/scripts/sync-changelog.mjs +0 -0
  154. {scar_cli-0.20.0 → scar_cli-0.22.0}/website/sidebars.ts +0 -0
  155. {scar_cli-0.20.0 → scar_cli-0.22.0}/website/src/css/custom.css +0 -0
  156. {scar_cli-0.20.0 → scar_cli-0.22.0}/website/static/img/favicon.svg +0 -0
  157. {scar_cli-0.20.0 → scar_cli-0.22.0}/website/static/llms.txt +0 -0
  158. {scar_cli-0.20.0 → scar_cli-0.22.0}/website/tsconfig.json +0 -0
@@ -0,0 +1,112 @@
1
+ name: Bug report
2
+ description: Something broke — anchoring, hooks, injection, CLI, lint, or stats
3
+ title: "fix: "
4
+ labels: ["bug", "type:fix"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ Scar is entirely local — no service, no accounts, no telemetry. That means
10
+ nothing about your setup reaches us unless you paste it, and the diagnostics
11
+ below answer most questions in one round-trip.
12
+
13
+ **A maintainer must add `status:approved` to this issue before a PR can be
14
+ opened against it.** CI enforces that, so please wait for the label rather
15
+ than opening a PR alongside the report.
16
+
17
+ - type: textarea
18
+ id: what-happened
19
+ attributes:
20
+ label: What happened
21
+ description: What you did and what broke. Quote error text exactly.
22
+ validations:
23
+ required: true
24
+
25
+ - type: textarea
26
+ id: expected
27
+ attributes:
28
+ label: What you expected
29
+ validations:
30
+ required: true
31
+
32
+ - type: textarea
33
+ id: repro
34
+ attributes:
35
+ label: Steps to reproduce
36
+ placeholder: |
37
+ 1. scar init
38
+ 2. scar hook install
39
+ 3. edit a file matching an anchor
40
+ 4. ...
41
+ validations:
42
+ required: false
43
+
44
+ - type: dropdown
45
+ id: runtime
46
+ attributes:
47
+ label: Agent runtime
48
+ description: Where Scar was running when this happened.
49
+ options:
50
+ - Claude Code
51
+ - Codex
52
+ - Cursor
53
+ - opencode
54
+ - Windsurf / Cascade
55
+ - An MCP client (scar mcp)
56
+ - No agent — scar CLI directly
57
+ - Other (say which in "What happened")
58
+ validations:
59
+ required: true
60
+
61
+ - type: dropdown
62
+ id: install
63
+ attributes:
64
+ label: How is scar installed?
65
+ description: Matters more than it looks — a local editable install never upgrades.
66
+ options:
67
+ - uv tool install
68
+ - pipx
69
+ - pip
70
+ - editable / from source
71
+ - don't know
72
+ validations:
73
+ required: false
74
+
75
+ - type: textarea
76
+ id: diagnostics
77
+ attributes:
78
+ label: Diagnostics
79
+ description: |
80
+ Paste the output of these. All local; nothing is transmitted by running them.
81
+
82
+ scar --version
83
+ scar status
84
+ scar hook status
85
+ scar lint
86
+
87
+ If the symbol/tree-sitter extra is involved, add whether you installed
88
+ `scar-cli[symbols]` — `--force` reinstalls have been known to drop extras.
89
+ render: text
90
+ validations:
91
+ required: true
92
+
93
+ - type: markdown
94
+ attributes:
95
+ value: |
96
+ ### ⚠️ Before you paste anything else
97
+
98
+ **Do not paste `scar stats --all-repos` or the raw firing log.** Both are
99
+ machine-global: they name **every repository on your machine**, including
100
+ private ones. Plain `scar stats` is scoped to the current repo and is safe.
101
+
102
+ If a machine-global number is genuinely relevant, redact the repo names and
103
+ keep only the counts.
104
+
105
+ - type: textarea
106
+ id: stats
107
+ attributes:
108
+ label: "`scar stats` (optional)"
109
+ description: Current repo only. Useful for injection, retrieval or firing-log bugs.
110
+ render: text
111
+ validations:
112
+ required: false
@@ -0,0 +1,18 @@
1
+ # Blank issues are disabled: the two templates cover the cases that become
2
+ # work, and anything else is a question, which belongs in Discussions.
3
+ #
4
+ # This is only safe BECAUSE Discussions is enabled (turned on 2026-08-29). If
5
+ # Discussions is ever turned off again, flip this back to true — otherwise
6
+ # someone with a plain "is this the intended behavior?" has no route in at all.
7
+ blank_issues_enabled: false
8
+
9
+ contact_links:
10
+ - name: Question or discussion
11
+ url: https://github.com/Daily-Nerd/Scar/discussions
12
+ about: "Not sure it's a bug? Asking how something is meant to work? Start here — questions get answered faster than they do in issues."
13
+ - name: Documentation
14
+ url: https://daily-nerd.github.io/Scar/
15
+ about: Quickstart, concepts, agent setup, and the methodology behind the measurements.
16
+ - name: Agent-readable docs (llms.txt)
17
+ url: https://daily-nerd.github.io/Scar/llms.txt
18
+ about: If you are an AI agent reading this, start here rather than scraping the site.
@@ -0,0 +1,70 @@
1
+ name: Feature request
2
+ description: A capability Scar should have, or a pain it should remove
3
+ title: "feat: "
4
+ labels: ["enhancement", "type:feature"]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: |
9
+ **A maintainer must add `status:approved` to this issue before a PR can be
10
+ opened against it.** CI enforces that, so please let the idea get a verdict
11
+ before writing code against it.
12
+
13
+ - type: textarea
14
+ id: problem
15
+ attributes:
16
+ label: Problem
17
+ description: |
18
+ The pain, as concretely as you can. What happens today that shouldn't, or
19
+ can't happen that should? Prefer a real case you hit over a hypothetical.
20
+ validations:
21
+ required: true
22
+
23
+ - type: textarea
24
+ id: solution
25
+ attributes:
26
+ label: Proposed solution
27
+ description: How it would work from your side — commands, flags, output, behavior. Rough is fine.
28
+ validations:
29
+ required: true
30
+
31
+ - type: textarea
32
+ id: alternatives
33
+ attributes:
34
+ label: Alternatives considered
35
+ description: Workarounds you use today, or other shapes this could take.
36
+ validations:
37
+ required: false
38
+
39
+ - type: dropdown
40
+ id: area
41
+ attributes:
42
+ label: Area
43
+ options:
44
+ - Anchoring and matching (path, pattern, symbol, fingerprint, command)
45
+ - Hooks and agent integration
46
+ - CLI surface and output
47
+ - Harvest and authoring
48
+ - Lint, orphan detection, lifecycle
49
+ - Measurement — firing log, stats, compliance
50
+ - Docs and website
51
+ - Packaging and install
52
+ - Other / not sure
53
+ validations:
54
+ required: false
55
+
56
+ - type: markdown
57
+ attributes:
58
+ value: |
59
+ ### Before you write it up
60
+
61
+ Two things this project is deliberate about, worth knowing so a proposal
62
+ does not get bounced for a reason that isn't obvious:
63
+
64
+ - **Machine-readable output is a contract.** The `--json` payloads are
65
+ documented in `SPEC.md` §9. Adding keys is fine; removing or retyping a
66
+ documented key is breaking.
67
+ - **Numbers are refused rather than flattered.** Where a metric's
68
+ denominator is absent, Scar reports `null` instead of a number the data
69
+ cannot support. Proposals that would report a confident number from thin
70
+ evidence tend to get pushed back on for that reason alone.
@@ -0,0 +1,70 @@
1
+ Closes #
2
+
3
+ <!--
4
+ CI gates this PR (.github/workflows/pr-validation.yml). All of these are checked
5
+ BEFORE review, so getting them right up front saves a round-trip:
6
+
7
+ 1. The body links an issue: `Closes #N` / `Fixes #N` / `Resolves #N`.
8
+ "Refs #N" does NOT satisfy this check — the regex only accepts those three
9
+ verbs. If a PR is one stage of a multi-PR issue and you don't want merging it
10
+ to close the issue, say so in the body and reopen the issue after merge.
11
+
12
+ 2. That linked issue carries the `status:approved` label. A maintainer adds it.
13
+ If it isn't there yet, the check fails — wait for approval rather than
14
+ opening the PR early.
15
+
16
+ 3. Exactly ONE `type:*` label on the PR. Note the names, they are not the same
17
+ as the conventional-commit types:
18
+ type:feature type:fix type:docs type:refactor type:chore
19
+ ("type:feat" does not exist and will not apply.)
20
+
21
+ 4. Conventional title: `type(scope): description`. Squash-merge makes this the
22
+ commit subject on main, so release-please reads THIS, not your local commit
23
+ messages. A breaking change needs the `!` in the title (`feat!:`,
24
+ `fix(scope)!:`) — a local commit footer does not survive the squash.
25
+
26
+ 5. Branch name matches `type/description`, lowercase, `[a-z0-9._-]` only:
27
+ feat fix chore docs style refactor perf test build ci revert
28
+ Pick it from the KIND of change, not the content area — `research/foo` fails.
29
+ A PR's head branch cannot be retargeted, so a wrong name means closing and
30
+ reopening the PR. Worse, renaming a branch mid-flight can resurrect the old
31
+ ref. Get the name right before the first push.
32
+
33
+ Two exemptions, both automatic:
34
+ - Scars-only PRs (every changed file under `.scars/`) skip gates 1 and 2 —
35
+ human sign-off already happened at `scar promote`. Gates 3-5 still apply.
36
+ - release-please branches skip the whole job.
37
+
38
+ Seeing NO checks at all, rather than failing ones? That is not a broken
39
+ workflow. Pull requests from forks and from bots wait on a maintainer to
40
+ approve the Actions run, and until someone clicks, zero check-runs is the
41
+ expected state. Nothing on your side to fix — say so in a comment if it sits.
42
+ -->
43
+
44
+ ## What
45
+
46
+ ## Why
47
+
48
+ ## Tests
49
+
50
+ <!--
51
+ What you ran and what it proves. New behavior needs a test that failed before
52
+ the change.
53
+
54
+ Run before pushing — CI runs all three:
55
+
56
+ uv run pytest
57
+ uv run scar lint # a gate pytest does NOT cover: dead anchors, rot, ReDoS
58
+ uv run ruff check src/scar tests
59
+
60
+ If you touched a `--json` payload, re-read `SPEC.md` §9 first. Those keys are a
61
+ published contract: adding is fine, removing or retyping a documented key is
62
+ breaking, and `tests/test_json_contract.py` will tell you which.
63
+ -->
64
+
65
+ ## Checklist
66
+
67
+ - [ ] Linked issue above has `status:approved`
68
+ - [ ] Exactly one `type:*` label applied
69
+ - [ ] `uv run pytest`, `uv run scar lint` and `ruff` pass locally
70
+ - [ ] If an approach was tried and abandoned here, a candidate scar was written to `.scars/candidates/`
@@ -39,15 +39,46 @@ jobs:
39
39
  # fixture (tests/conftest.py) fails loudly here instead of silently
40
40
  # writing into a real ~/.claude/scar-state firing log. 92 of 105 records
41
41
  # were once pytest artifacts — see website/docs/methodology.md (#228).
42
+ # Coverage is collected across BOTH legs and combined (--cov-append),
43
+ # not just the richer one: the no-symbols run is the only place the
44
+ # "symbols unavailable" fallbacks execute, so measuring the extras run
45
+ # alone would report those branches as dead when they are the default
46
+ # install's real path. One .coverage file, written here, appended below,
47
+ # rendered to XML at the end.
42
48
  - name: Run tests
43
49
  run: |
44
50
  mkdir -p "$RUNNER_TEMP/fake-home"
45
- HOME="$RUNNER_TEMP/fake-home" uv run pytest -q
51
+ HOME="$RUNNER_TEMP/fake-home" uv run pytest -q \
52
+ --cov=scar --cov-report= --junitxml=junit-core.xml
46
53
 
47
54
  - name: Run tests (with symbols extra)
48
55
  run: |
49
56
  mkdir -p "$RUNNER_TEMP/fake-home"
50
- HOME="$RUNNER_TEMP/fake-home" uv run --extra symbols pytest -q
57
+ HOME="$RUNNER_TEMP/fake-home" uv run --extra symbols pytest -q \
58
+ --cov=scar --cov-append --cov-report=xml --junitxml=junit-symbols.xml
59
+
60
+ # Reporting only — no coverage threshold gates the build, and
61
+ # fail_ci_if_error stays false so a missing or rotated CODECOV_TOKEN
62
+ # degrades to "no coverage comment" rather than a red build on a PR
63
+ # that changed nothing about the tests (#273).
64
+ - name: Upload test results to Codecov
65
+ if: ${{ matrix.python-version == '3.13' && !cancelled() }}
66
+ uses: codecov/test-results-action@v1
67
+ with:
68
+ token: ${{ secrets.CODECOV_TOKEN }}
69
+ files: ./junit-core.xml,./junit-symbols.xml
70
+ flags: py${{ matrix.python-version }}
71
+ fail_ci_if_error: false
72
+
73
+ - name: Upload coverage to Codecov
74
+ if: ${{ matrix.python-version == '3.13' && !cancelled() }}
75
+ uses: codecov/codecov-action@v7
76
+ with:
77
+ token: ${{ secrets.CODECOV_TOKEN }}
78
+ slug: Daily-Nerd/Scar
79
+ files: ./coverage.xml
80
+ flags: py${{ matrix.python-version }}
81
+ fail_ci_if_error: false
51
82
 
52
83
  # Dogfood: the repo's own scar graph must always parse and lint clean.
53
84
  # A malformed scar silently never fires (the daimon 0004 incident) —
@@ -18,3 +18,8 @@ experiments/fence-honor/runs/
18
18
  .claude/
19
19
 
20
20
  .scars/candidates/fp-log.txt
21
+
22
+ # Coverage artifacts (#273) — written by pytest-cov in CI and locally.
23
+ .coverage
24
+ coverage.xml
25
+ junit-*.xml
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.22.0"
3
+ }
@@ -0,0 +1,35 @@
1
+ ---
2
+ id: 18
3
+ type: landmine
4
+ title: cli.py subparsers register via _add(), not add_parser — and a test's drift gate parses that exact call shape
5
+ severity: medium
6
+ confidence: 0.8
7
+ created: 2026-08-29
8
+ authors: ["claude-code", "Kibukx"]
9
+ anchors:
10
+ - path: src/scar/cli.py
11
+ - path: tests/test_json_contract.py
12
+ - pattern: "_add\(\s*sub,"
13
+ evidence:
14
+ - pr: 257
15
+ - issue: 255
16
+ - issue: 259
17
+ expires:
18
+ condition: "subcommand registration stops going through a wrapper, or the drift gate stops parsing source text"
19
+ review_after: 2027-08-29
20
+ status: active
21
+ ---
22
+
23
+ Every subcommand is registered through the local `_add()` wrapper (which wires
24
+ RichHelpFormatter), so `add_parser` appears only twice in cli.py — inside that
25
+ helper's own definition. Grepping for `add_parser` to enumerate the CLI surface
26
+ returns nothing useful and reads as "no subcommands here". Search for
27
+ `_add(sub, "name"` instead.
28
+
29
+ The coupling: `tests/test_json_contract.py::test_spec_documents_every_json_subcommand`
30
+ parses cli.py as TEXT with that regex to prove SPEC §9.3 documents every
31
+ `--json` command. Renaming `_add`, changing its argument order, or building
32
+ parsers in a loop makes the regex match nothing — the test then compares an
33
+ empty set against a populated SPEC table and fails with a confusing message
34
+ that points at the spec rather than at the refactor. If you change the
35
+ registration shape, update that helper in the same commit (PR #257).
@@ -1,19 +1,14 @@
1
1
  ---
2
- id: 0
2
+ id: 19
3
3
  type: deadend
4
4
  title: A token in a scar body cannot prove hook delivery — the agent can just read the file
5
5
  severity: high
6
6
  confidence: 0.95
7
7
  created: 2026-08-28
8
- authors: ["claude-code"]
8
+ authors: ["claude-code", "Kibukx"]
9
9
  anchors:
10
10
  - path: src/scar/codex.py
11
11
  - path: src/scar/hooks.py
12
- # No pattern anchor on purpose: the candidate phrasings ("verification
13
- # token", "delivery proof") matched nothing but this file, and "A/B" alone
14
- # is too loose to earn one. Path anchors are the honest fire condition —
15
- # this hazard applies whenever someone edits a hook adapter and wants to
16
- # prove it works. See scar #35 on ghost patterns that only self-match.
17
12
  evidence:
18
13
  - pr: 244
19
14
  - pr: 247
@@ -21,7 +16,7 @@ evidence:
21
16
  expires:
22
17
  condition: "a delivery check exists that observes hook execution directly (firing-log row or marker) rather than model output"
23
18
  review_after: 2027-02-28
24
- status: candidate
19
+ status: active
25
20
  ---
26
21
 
27
22
  Putting a private token in an active scar's body and looking for it in the
@@ -0,0 +1,42 @@
1
+ ---
2
+ id: 0
3
+ type: landmine
4
+ title: A health check that watches one direction of pipeline failure leaves the flattering direction unwatched
5
+ severity: high
6
+ confidence: 0.9
7
+ created: 2026-08-30
8
+ authors: ["claude-code"]
9
+ anchors:
10
+ - path: src/scar/cli.py
11
+ - path: src/scar/hooks.py
12
+ - pattern: "instrument_disconnected|posttool_silent|verdict_observed"
13
+ evidence:
14
+ - issue: 277
15
+ - issue: 237
16
+ expires:
17
+ condition: "the pipeline stops having two independently-installable halves"
18
+ status: candidate
19
+ ---
20
+
21
+ The precheck and posttool hooks install separately and fail separately. #237
22
+ added a guard for one of those failures: violations recorded with zero precheck
23
+ rows means the precheck half is dead. That guard was correct and it shipped
24
+ after a real four-week outage.
25
+
26
+ Nobody wrote the mirror, and the mirror was the dangerous one. Posttool used to
27
+ `return 0` before logging anything when it found no violation, so a clean edit
28
+ left no trace. That made a dead posttool hook produce zero violations against a
29
+ full firing count, which is byte-identical to perfect compliance. The guarded
30
+ direction was the one that made the numbers look bad. The unguarded direction
31
+ was the one that made them look good, and it stayed unguarded for months.
32
+
33
+ Two rules follow, and both are load-bearing. First, when a pipeline has halves
34
+ that fail independently, a health check for one half is not a health check.
35
+ Ask which failure the existing guard CANNOT see, and assume it is the flattering
36
+ one until proven otherwise. Second, absence of a record is only evidence when
37
+ the writer emits a record in the negative case too. Posttool now logs on the
38
+ clean path whenever a violation was possible, and that is the only reason
39
+ `verdicts_unresolved` can mean anything.
40
+
41
+ Do not "simplify" posttool by restoring the early return on no-violation. It
42
+ looks like dead work and it is the entire mechanism.
@@ -0,0 +1,36 @@
1
+ ---
2
+ id: 0
3
+ type: landmine
4
+ title: A posttool change in hooks.py does not reach codex.py or cascade.py, which carry their own copy of the same logic
5
+ severity: high
6
+ confidence: 0.8
7
+ created: 2026-09-02
8
+ authors: ["claude-code"]
9
+ anchors:
10
+ - path: src/scar/hooks.py
11
+ - path: src/scar/codex.py
12
+ - path: src/scar/cascade.py
13
+ violation: "if not violations:"
14
+ evidence:
15
+ - issue: 277
16
+ - issue: 293
17
+ - pr: 297
18
+ expires:
19
+ condition: "the three posttool paths share one implementation, or the adapters delegate to hooks.posttool"
20
+ review_after: 2026-12-01
21
+ status: candidate
22
+ ---
23
+
24
+ The posttool verdict logic exists three times: `hooks.posttool` for Claude
25
+ Code, `codex.posttool` for Codex, and `cascade.post_write_code` for Windsurf.
26
+ They are copies, not callers. #277 fixed the flattering-direction blind spot
27
+ (a clean edit must still write a verdict row, or a dead hook reads as perfect
28
+ compliance) in hooks.py only. The adapters kept the pre-#277 early return, so
29
+ on Codex one clean armed edit trips the "broken install" warning on a healthy
30
+ install (#293). The #277 test suite asserted the Codex row SHAPE and never
31
+ exercised the Codex clean path, so green CI proved nothing about the copies.
32
+
33
+ When you change what a posttool writes, reads, or skips in hooks.py, open
34
+ codex.py and cascade.py in the same PR and make the same change, or make them
35
+ delegate. A bare `if not violations: return` in an adapter is the exact shape
36
+ that reintroduces the blind spot, which is why it is the tripwire here.
@@ -10,7 +10,7 @@ title: One line, searchable, says the constraint
10
10
  severity: medium # low | medium | high | critical
11
11
  confidence: 0.7 # 0..1 — how sure are we this still holds
12
12
  created: 1970-01-01
13
- authors: ["claude-code"] # add the human reviewer at promotion
13
+ authors: ["claude-code"] # who DRAFTED it; the promoting human goes in promoted_by, written by promote
14
14
  anchors:
15
15
  - path: src/module/ # file or directory this protects
16
16
  - pattern: "regex" # optional: fires when matching code appears in ANY new/edited file
@@ -1,5 +1,52 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.22.0](https://github.com/Daily-Nerd/Scar/compare/v0.21.0...v0.22.0) (2026-09-03)
4
+
5
+
6
+ ### Features
7
+
8
+ * **lint:** escalate a chronically-firing scar into an explicit review flag ([#276](https://github.com/Daily-Nerd/Scar/issues/276)) ([e26884b](https://github.com/Daily-Nerd/Scar/commit/e26884b0b01938420c137673e1c9e01eb4c6dc63))
9
+ * **promote:** record whether promoted_by was typed or read from git config ([#298](https://github.com/Daily-Nerd/Scar/issues/298)) ([bcdc4d7](https://github.com/Daily-Nerd/Scar/commit/bcdc4d7b6d82c5d1b9b38365648f23ed45050064))
10
+ * **reanchor:** record an applied rewrite as an evidence note on the scar ([#299](https://github.com/Daily-Nerd/Scar/issues/299)) ([2aa1d12](https://github.com/Daily-Nerd/Scar/commit/2aa1d12230e7c8b379dcd967a780e0c2cf4a3b2e))
11
+ * **stats:** record blocking capability on the firing row ([#281](https://github.com/Daily-Nerd/Scar/issues/281)) ([a341b48](https://github.com/Daily-Nerd/Scar/commit/a341b48eb365bd8522175570f72b767af1b50c37))
12
+ * **stats:** record context size on the firing row ([#283](https://github.com/Daily-Nerd/Scar/issues/283)) ([99ba9c3](https://github.com/Daily-Nerd/Scar/commit/99ba9c3e8e63aa64ed2c925cba4b4a59895139e0))
13
+ * **stats:** record the match census before top_k truncates it ([#289](https://github.com/Daily-Nerd/Scar/issues/289)) ([715e42c](https://github.com/Daily-Nerd/Scar/commit/715e42c4f73f512b80854f6d68152c5108256246))
14
+
15
+
16
+ ### Bug Fixes
17
+
18
+ * **hooks:** Codex and Windsurf posttool record a clean verdict too ([#297](https://github.com/Daily-Nerd/Scar/issues/297)) ([823af3c](https://github.com/Daily-Nerd/Scar/commit/823af3c8e463d6ee89990de0f0fe8606e5dde86f)), closes [#293](https://github.com/Daily-Nerd/Scar/issues/293)
19
+ * **promote:** record who vouched in its own field, not inside authors ([#290](https://github.com/Daily-Nerd/Scar/issues/290)) ([d1e649a](https://github.com/Daily-Nerd/Scar/commit/d1e649a03ef7a76349da48f7772ee4755c332009)), closes [#287](https://github.com/Daily-Nerd/Scar/issues/287)
20
+ * **stats:** make a dead posttool hook distinguishable from compliance ([#280](https://github.com/Daily-Nerd/Scar/issues/280)) ([13f0b6a](https://github.com/Daily-Nerd/Scar/commit/13f0b6ae255dfb1e89f0f61296f4f25d5890acda))
21
+ * **stats:** record why a scar was demoted, not just that it was ([#288](https://github.com/Daily-Nerd/Scar/issues/288)) ([d6dafba](https://github.com/Daily-Nerd/Scar/commit/d6dafbab8ed389815e28535fdc929cd906f657ef)), closes [#284](https://github.com/Daily-Nerd/Scar/issues/284)
22
+ * **stats:** stop counting a command firing as an observed edit ([#300](https://github.com/Daily-Nerd/Scar/issues/300)) ([7abeeed](https://github.com/Daily-Nerd/Scar/commit/7abeeedba8b43634c6d4ccfafbf7eac7fd173269)), closes [#294](https://github.com/Daily-Nerd/Scar/issues/294)
23
+
24
+
25
+ ### Documentation
26
+
27
+ * **format:** update the stale veritrail link to vitni ([#269](https://github.com/Daily-Nerd/Scar/issues/269)) ([a842449](https://github.com/Daily-Nerd/Scar/commit/a842449ca0d2eb2fde9310dcef244c299316a84f)), closes [#268](https://github.com/Daily-Nerd/Scar/issues/268)
28
+ * **stats:** state that the armed-window number needs git archaeology ([#291](https://github.com/Daily-Nerd/Scar/issues/291)) ([4097081](https://github.com/Daily-Nerd/Scar/commit/4097081b40fa56bcb255dbda8bdf03f8ac0ab203)), closes [#285](https://github.com/Daily-Nerd/Scar/issues/285)
29
+
30
+ ## [0.21.0](https://github.com/Daily-Nerd/Scar/compare/v0.20.0...v0.21.0) (2026-08-29)
31
+
32
+
33
+ ### Features
34
+
35
+ * **stats:** add --since/--until so a window is a first-class operation ([#263](https://github.com/Daily-Nerd/Scar/issues/263)) ([f1b262b](https://github.com/Daily-Nerd/Scar/commit/f1b262b600394d206022ce7f129a168f518254e1)), closes [#258](https://github.com/Daily-Nerd/Scar/issues/258)
36
+ * **stats:** record which fired scars were violation-armed ([#267](https://github.com/Daily-Nerd/Scar/issues/267)) ([206d6af](https://github.com/Daily-Nerd/Scar/commit/206d6afff0094b72ec01ff004a274442738617d0)), closes [#266](https://github.com/Daily-Nerd/Scar/issues/266)
37
+
38
+
39
+ ### Bug Fixes
40
+
41
+ * **codex:** let the duplicate-channel warning be silenced ([#253](https://github.com/Daily-Nerd/Scar/issues/253)) ([e060656](https://github.com/Daily-Nerd/Scar/commit/e06065631f9fd3054d7d86d1a528d464a15af432)), closes [#252](https://github.com/Daily-Nerd/Scar/issues/252)
42
+ * **match:** scan whole file bodies for pattern liveness ([#262](https://github.com/Daily-Nerd/Scar/issues/262)) ([fe177ca](https://github.com/Daily-Nerd/Scar/commit/fe177ca55bd05dc69a9a486b89bb8dca0e24e514)), closes [#259](https://github.com/Daily-Nerd/Scar/issues/259)
43
+ * **stats:** report the applied window in the --all-repos payload ([#265](https://github.com/Daily-Nerd/Scar/issues/265)) ([b0e8c1f](https://github.com/Daily-Nerd/Scar/commit/b0e8c1f4a138b2c8847acdd1e7ba8c09269a4069)), closes [#264](https://github.com/Daily-Nerd/Scar/issues/264)
44
+
45
+
46
+ ### Documentation
47
+
48
+ * **spec:** publish a stability contract for the nine --json payloads ([#257](https://github.com/Daily-Nerd/Scar/issues/257)) ([c3e5c1b](https://github.com/Daily-Nerd/Scar/commit/c3e5c1b94060fc551aaece7ad98085464b9603d7)), closes [#255](https://github.com/Daily-Nerd/Scar/issues/255)
49
+
3
50
  ## [0.20.0](https://github.com/Daily-Nerd/Scar/compare/v0.19.0...v0.20.0) (2026-08-28)
4
51
 
5
52
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: scar-cli
3
- Version: 0.20.0
3
+ Version: 0.22.0
4
4
  Summary: SCAR — version control for negative knowledge (deadends, fences, landmines)
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -41,11 +41,14 @@ is deliberate: consumers in hook hot-paths parse with zero dependencies.
41
41
  | `severity` | yes | `low` \| `medium` \| `high` \| `critical` | ranking input |
42
42
  | `confidence` | yes | float 0..1 | static authored ranking weight; dynamic decay deferred (SPEC §5, issue #95) |
43
43
  | `created` | yes | ISO date | |
44
- | `authors` | yes | inline list | agents as `"claude-code"` etc.; reviewer appended at promotion |
44
+ | `authors` | yes | inline list | who drafted it: agents as `"claude-code"` etc. The reviewer is no longer appended here |
45
+ | `promoted_by` | active scars | string | the human who promoted it. Written only by `scar promote`, which overwrites anything a candidate carried. Empty on scars promoted before the field existed, and never inferred from `authors` |
46
+ | `promoted_by_source` | optional | `explicit` \| `git-config` | whether `promoted_by` came from `--reviewer` or from `git config user.name`. Written only alongside `promoted_by`, never on its own. Absent on scars promoted before the field existed |
45
47
  | `anchors` | yes, ≥1 | list of `- path:`, `- pattern:`, `- symbol:`, and/or `- command:` | §4 |
46
48
  | `evidence` | recommended | list of `- commit:`/`- pr:`/`- issue:`/`- incident:`/`- note:`/`- url:` | absent ⇒ challengeable on sight; `issue:`/`url:` are durable forms that survive squash-merge |
47
49
  | `expires.condition` | recommended | quoted string | what change obsoletes this scar |
48
50
  | `expires.review_after` | recommended | ISO date | forces periodic freshness contact |
51
+ | `expires.review_after_firings` | optional | non-negative integer | forces a review once the scar has fired this many times since its last revision; absent ⇒ per-type default (`landmine` 10, `fence` 15, `deadend` disabled), `0` ⇒ never escalate |
49
52
  | `status` | yes | `candidate` \| `active` \| `challenged` \| `archived` \| `orphaned` \| `template` | lifecycle §5 |
50
53
  | `receipt_id` | reserved | string ref | **reserved — not yet parsed.** Optional pointer to a cryptographic provenance receipt; see note below. |
51
54
 
@@ -54,7 +57,7 @@ trust model is social by design (git history, `authors`, evidence-by-reference);
54
57
  that is sufficient within one repo or org. It does **not** carry across orgs that
55
58
  share no git history — the future cross-org / org-graph layer where "this dead end
56
59
  hit N teams" must be attributable. `receipt_id` reserves the slot for a signed,
57
- content-addressed receipt (e.g. [veritrail](https://github.com/Daily-Nerd/veritrail))
60
+ content-addressed receipt (e.g. [vitni](https://github.com/Daily-Nerd/vitni))
58
61
  bound to an authorship (`scar_draft`) or promotion event. No tool emits, requires,
59
62
  or validates it today, and the line-wise parser ignores it like any unknown key —
60
63
  so existing scars are unaffected. It will not become required in v0.x.