scar-cli 0.4.0__tar.gz → 0.6.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 (87) hide show
  1. scar_cli-0.6.0/.claude-plugin/marketplace.json +11 -0
  2. {scar_cli-0.4.0 → scar_cli-0.6.0}/.scars/0001-git-grep-ere-pitfalls.landmine.md +3 -5
  3. {scar_cli-0.4.0 → scar_cli-0.6.0}/.scars/0002-agent-direct-hook-install.deadend.md +4 -4
  4. {scar_cli-0.4.0 → scar_cli-0.6.0}/.scars/0005-history-rewrite-orphans-commit-evidence.landmine.md +13 -8
  5. scar_cli-0.6.0/.scars/0006-yaml-pattern-anchor-over-escaping.landmine.md +41 -0
  6. scar_cli-0.6.0/.scars/candidates/fp-log.txt +6 -0
  7. {scar_cli-0.4.0 → scar_cli-0.6.0}/AGENTS.md +17 -0
  8. scar_cli-0.6.0/CHANGELOG.md +74 -0
  9. {scar_cli-0.4.0 → scar_cli-0.6.0}/PKG-INFO +11 -1
  10. {scar_cli-0.4.0 → scar_cli-0.6.0}/README.md +10 -0
  11. {scar_cli-0.4.0 → scar_cli-0.6.0}/ROADMAP.md +4 -4
  12. scar_cli-0.6.0/experiments/harvest/PROTOCOL.md +76 -0
  13. scar_cli-0.6.0/plugin/plugin.json +29 -0
  14. scar_cli-0.6.0/plugin/skills/scar-authoring/SKILL.md +93 -0
  15. scar_cli-0.6.0/plugin/skills/scar-authoring/assets/template.md +25 -0
  16. {scar_cli-0.4.0 → scar_cli-0.6.0}/pyproject.toml +1 -1
  17. {scar_cli-0.4.0 → scar_cli-0.6.0}/src/scar/agent.py +7 -0
  18. scar_cli-0.6.0/src/scar/cli.py +622 -0
  19. scar_cli-0.6.0/src/scar/evidence.py +86 -0
  20. scar_cli-0.6.0/src/scar/harvest.py +384 -0
  21. {scar_cli-0.4.0 → scar_cli-0.6.0}/src/scar/installer.py +43 -0
  22. {scar_cli-0.4.0 → scar_cli-0.6.0}/src/scar/match.py +20 -7
  23. {scar_cli-0.4.0 → scar_cli-0.6.0}/src/scar/mcp.py +8 -1
  24. scar_cli-0.6.0/src/scar/orphan.py +233 -0
  25. scar_cli-0.6.0/src/scar/skills/scar-authoring/SKILL.md +93 -0
  26. scar_cli-0.6.0/src/scar/skills/scar-authoring/assets/template.md +25 -0
  27. scar_cli-0.6.0/tests/test_cli.py +659 -0
  28. scar_cli-0.6.0/tests/test_docs.py +14 -0
  29. scar_cli-0.6.0/tests/test_evidence.py +134 -0
  30. scar_cli-0.6.0/tests/test_harvest.py +338 -0
  31. {scar_cli-0.4.0 → scar_cli-0.6.0}/tests/test_installer.py +33 -0
  32. {scar_cli-0.4.0 → scar_cli-0.6.0}/tests/test_mcp.py +11 -0
  33. scar_cli-0.6.0/tests/test_orphan.py +398 -0
  34. scar_cli-0.6.0/tests/test_plugin.py +43 -0
  35. scar_cli-0.6.0/tests/test_skill.py +36 -0
  36. {scar_cli-0.4.0 → scar_cli-0.6.0}/uv.lock +1 -1
  37. scar_cli-0.4.0/.scars/candidates/fp-log.txt +0 -1
  38. scar_cli-0.4.0/CHANGELOG.md +0 -44
  39. scar_cli-0.4.0/src/scar/cli.py +0 -288
  40. scar_cli-0.4.0/src/scar/harvest.py +0 -111
  41. scar_cli-0.4.0/tests/test_cli.py +0 -151
  42. scar_cli-0.4.0/tests/test_harvest.py +0 -63
  43. {scar_cli-0.4.0 → scar_cli-0.6.0}/.github/workflows/ci.yml +0 -0
  44. {scar_cli-0.4.0 → scar_cli-0.6.0}/.github/workflows/pr-validation.yml +0 -0
  45. {scar_cli-0.4.0 → scar_cli-0.6.0}/.github/workflows/release.yml +0 -0
  46. {scar_cli-0.4.0 → scar_cli-0.6.0}/.gitignore +0 -0
  47. {scar_cli-0.4.0 → scar_cli-0.6.0}/.scars/0003-installer-binds-to-active-venv-scar.landmine.md +0 -0
  48. {scar_cli-0.4.0 → scar_cli-0.6.0}/.scars/0004-promote-roundtrip-drops-expires-evidence.landmine.md +0 -0
  49. {scar_cli-0.4.0 → scar_cli-0.6.0}/.scars/README.md +0 -0
  50. {scar_cli-0.4.0 → scar_cli-0.6.0}/.scars/template.md +0 -0
  51. {scar_cli-0.4.0 → scar_cli-0.6.0}/CONTRIBUTING.md +0 -0
  52. {scar_cli-0.4.0 → scar_cli-0.6.0}/IDEA.md +0 -0
  53. {scar_cli-0.4.0 → scar_cli-0.6.0}/LICENSE +0 -0
  54. {scar_cli-0.4.0 → scar_cli-0.6.0}/SCAR-FORMAT.md +0 -0
  55. {scar_cli-0.4.0 → scar_cli-0.6.0}/SPEC.md +0 -0
  56. {scar_cli-0.4.0 → scar_cli-0.6.0}/STRESS-TEST.md +0 -0
  57. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/anchor-survival/PROTOCOL.md +0 -0
  58. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/anchor-survival/RESULTS.md +0 -0
  59. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/anchor-survival/long_replay.py +0 -0
  60. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/anchor-survival/replay.py +0 -0
  61. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/auto-authorship/FINDINGS.md +0 -0
  62. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/auto-authorship/PROTOCOL.md +0 -0
  63. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/fence-honor/.gitignore +0 -0
  64. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/fence-honor/PROTOCOL.md +0 -0
  65. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/fence-honor/RESULTS.md +0 -0
  66. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/fence-honor/fixture/.scars/0001-vendor-retry-window.fence.md +0 -0
  67. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/fence-honor/fixture/.scars/0002-evicting-session-store.deadend.md +0 -0
  68. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/fence-honor/fixture/.scars/0003-export-column-order.landmine.md +0 -0
  69. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/fence-honor/fixture/README.md +0 -0
  70. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/fence-honor/fixture/payments/retry.py +0 -0
  71. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/fence-honor/fixture/reports/export.py +0 -0
  72. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/fence-honor/fixture/services/sessions.py +0 -0
  73. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/fence-honor/grade.py +0 -0
  74. {scar_cli-0.4.0 → scar_cli-0.6.0}/experiments/harvest/harvest.py +0 -0
  75. {scar_cli-0.4.0 → scar_cli-0.6.0}/hook/scar-hooks.py +0 -0
  76. {scar_cli-0.4.0 → scar_cli-0.6.0}/src/scar/__init__.py +0 -0
  77. {scar_cli-0.4.0 → scar_cli-0.6.0}/src/scar/hooks.py +0 -0
  78. {scar_cli-0.4.0 → scar_cli-0.6.0}/src/scar/lint.py +0 -0
  79. {scar_cli-0.4.0 → scar_cli-0.6.0}/src/scar/model.py +0 -0
  80. {scar_cli-0.4.0 → scar_cli-0.6.0}/src/scar/render.py +0 -0
  81. {scar_cli-0.4.0 → scar_cli-0.6.0}/src/scar/store.py +0 -0
  82. {scar_cli-0.4.0 → scar_cli-0.6.0}/tests/test_hooks.py +0 -0
  83. {scar_cli-0.4.0 → scar_cli-0.6.0}/tests/test_lifecycle.py +0 -0
  84. {scar_cli-0.4.0 → scar_cli-0.6.0}/tests/test_lint.py +0 -0
  85. {scar_cli-0.4.0 → scar_cli-0.6.0}/tests/test_match.py +0 -0
  86. {scar_cli-0.4.0 → scar_cli-0.6.0}/tests/test_model.py +0 -0
  87. {scar_cli-0.4.0 → scar_cli-0.6.0}/tests/test_store.py +0 -0
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "scar",
3
+ "owner": { "name": "Daily-Nerd" },
4
+ "plugins": [
5
+ {
6
+ "name": "scar",
7
+ "source": "./plugin",
8
+ "description": "Read and author negative-knowledge scars: auto-injected scars before edits, plus the scar-authoring skill."
9
+ }
10
+ ]
11
+ }
@@ -8,12 +8,10 @@ created: 2026-06-09
8
8
  authors: ["claude-code", kibukx]
9
9
  anchors:
10
10
  - path: experiments/anchor-survival/
11
- - path: hook/scar-precheck.py
12
- - pattern: "git.{0,20}grep.{0,40}\\\\b"
11
+ - path: src/scar/harvest.py
12
+ - pattern: "git.{0,20}grep"
13
13
  evidence:
14
- - commit: 5c63b14
15
- - note: "produced a fake 0% anchor-survival run before diagnosis (gate 0.2)"
16
- - note: "history rewritten at v0.1.0 public release; pre-release SHAs resolve on GitHub by URL but not in fresh clones"
14
+ - note: "orphaned receipt — pre-v0.1.0 commit 5c63b14 produced a fake 0% anchor-survival run before diagnosis (gate 0.2); resolves at github.com/Daily-Nerd/Scar/commit/5c63b14 until GC, not in fresh clones (rewritten at the v0.1.0 release)"
17
15
  expires:
18
16
  condition: "resolver layer gains integration tests over its git invocations"
19
17
  review_after: 2027-06-09
@@ -8,11 +8,11 @@ created: 2026-06-09
8
8
  authors: ["claude-code", kibukx]
9
9
  anchors:
10
10
  - path: hook/
11
- - pattern: "settings\\.json.{0,80}hooks|hooks.{0,80}settings\\.json"
11
+ - path: src/scar/installer.py
12
12
  evidence:
13
- - commit: faad8f6
14
- - commit: bcd3864
15
- - note: "history rewritten at v0.1.0 public release; pre-release SHAs resolve on GitHub by URL but not in fresh clones"
13
+ - note: "orphaned receipt — pre-v0.1.0 installer commit faad8f6 at github.com/Daily-Nerd/Scar/commit/faad8f6 (unreachable in fresh clones)"
14
+ - note: "orphaned receipt — pre-v0.1.0 hooks commit bcd3864 at github.com/Daily-Nerd/Scar/commit/bcd3864 (unreachable in fresh clones)"
15
+ - note: "both SHAs orphaned by the fresh-start force-push at the v0.1.0 public release; resolve on GitHub by URL until GC, not in fresh clones"
16
16
  status: active
17
17
  ---
18
18
 
@@ -8,7 +8,6 @@ created: 2026-06-11
8
8
  authors: ["claude-code", "Kibukx"]
9
9
  anchors:
10
10
  - path: .scars/
11
- - pattern: "push.{0,30}(--force|\\+[a-zA-Z]).{0,40}main|filter-repo|checkout --orphan"
12
11
  evidence:
13
12
  - note: v0.1.0 public release (2026-06-11): fresh-start force-push orphaned 3 commit SHAs cited by scars 0001 and 0002; SHAs still resolve on GitHub by URL but fail in any fresh clone, and GitHub may GC them eventually
14
13
  expires:
@@ -19,7 +18,7 @@ status: active
19
18
 
20
19
  Scars cite commit SHAs as evidence receipts. Those receipts implicitly assume
21
20
  the SHA stays reachable in the repo's history forever. Any history rewrite —
22
- fresh-start orphan branch, force-push, filter-repo scrub — silently breaks
21
+ fresh-start orphan branch, force-push, filter-repo scrub, or a routine squash-/rebase-merge — silently breaks
23
22
  that assumption: the scar still lints clean and still fires (anchors are
24
23
  paths/patterns, not commits), but `git show <sha>` fails in every fresh clone,
25
24
  so the receipt is unverifiable exactly where strangers would check it.
@@ -29,9 +28,15 @@ Observed at the v0.1.0 public release: the fresh-start force-push orphaned
29
28
  until after the push because nothing in the toolchain connects "history
30
29
  operation" to "evidence integrity."
31
30
 
32
- Before any history rewrite in a repo with scars: grep `.scars/` for
33
- `commit:` evidence and either (a) amend those scars with a note explaining the
34
- rewrite, (b) replace bare SHAs with full GitHub commit URLs (survive as
35
- unreachable objects, at GC's mercy), or (c) inline the relevant diff/fact into
36
- a note so the scar is self-contained. Longer term: `scar lint` could warn
37
- when a cited SHA is unreachable from HEAD.
31
+ The everyday trigger is the merge strategy itself: this repo squash-merges, so a
32
+ feature-branch commit exactly the SHA you cite while drafting a scar mid-PR
33
+ is orphaned the moment that PR lands. Rebase-merge does the same; only a true
34
+ merge-commit preserves branch SHAs. So PREFER `pr:`/`issue:` evidence (it
35
+ resolves on GitHub regardless of merge strategy) or a SHA already on the default
36
+ branch, and avoid citing transient feature-branch SHAs at all.
37
+
38
+ Before any deliberate history rewrite: grep `.scars/` for `commit:` evidence and
39
+ either (a) amend with a note explaining the rewrite, (b) replace bare SHAs with
40
+ full GitHub commit URLs (at GC's mercy), or (c) inline the fact so the scar is
41
+ self-contained. `scar lint` now warns when a cited SHA is unreachable from HEAD
42
+ (#43) — but it fires after the fact; the durable fix is not citing branch SHAs.
@@ -0,0 +1,41 @@
1
+ ---
2
+ id: 6
3
+ type: landmine
4
+ title: Pattern anchors over-escape through YAML double-quotes and silently only self-match
5
+ severity: medium
6
+ confidence: 0.9
7
+ created: 2026-06-13
8
+ authors: ["claude-code", "kibukx"]
9
+ anchors:
10
+ - path: src/scar/orphan.py
11
+ - path: src/scar/match.py
12
+ - path: .scars/
13
+ evidence:
14
+ - pr: 40
15
+ - note: scar 1 grep pattern matched only its own body, never experiments/anchor-survival/RESULTS.md
16
+ expires:
17
+ condition: "pattern anchors are authored through a validated path (e.g. scar draft) that escapes regex correctly, OR lint rejects a pattern whose only pre-exclusion match is the scar's own file"
18
+ review_after: 2027-06-13
19
+ status: active
20
+ ---
21
+
22
+ A regex written in a scar's `pattern:` field passes through YAML double-quoted
23
+ string parsing before it ever reaches the matcher. Backslashes collapse: what
24
+ you type as a four-backslash word boundary in the file becomes a regex needing
25
+ *literal* backslashes, not a word boundary. The intended code almost never
26
+ contains literal backslashes, so the pattern matches nothing real.
27
+
28
+ The trap is that it still reads as LIVE. Pattern anchors are matched against ALL
29
+ tracked content, including the scar's own `.scars/` body, and the body quotes
30
+ the pattern verbatim, so the scar keeps itself alive by self-reference. Orphan
31
+ detection sees a live anchor and stays quiet. The protection is dead; the gauge
32
+ says green. On this repo, scars 1 and 5 were pure ghosts (own-body only) and
33
+ scar 2 matched zero files at all, none visible until self-referential exclusion
34
+ was added in PR #40 (`_pattern_anchor_live(..., exclude_path=self_path)`).
35
+
36
+ What a future editor must do: when adding a `pattern:` anchor, verify it matches
37
+ the REAL code with `scar lint` (it must NOT appear under partial-rot), not just
38
+ that the scar parses. Prefer a `path:` anchor when the target is a file or dir;
39
+ path anchors do not go through regex escaping and cannot self-match. If you must
40
+ use a regex with escapes, test it against tracked content excluding the scar's
41
+ own file before trusting it.
@@ -0,0 +1,6 @@
1
+ 2026-06-12 false trigger: meta-session — we tuned the revert-language detector itself, so assistant prose ('revert language', 'reverting' in test fixtures/PR text) matched REVERT_RE; nothing abandoned (tool_errors were expected CLI probes/rejections). First post-tune FP pattern: self-referential sessions about the drafter trip the drafter.
2
+ 2026-06-13 false trigger: tool_errors were external API hiccups (pypistats rate-limit/404, bq schema field); no code approach tried-and-abandoned this session (design-only work)
3
+ 2026-06-12 false trigger: orphan-detection impl — 'revert' is feature-domain ('revert case' reverse hint = anchors-live-again) + a planned AC#1 refactor swapping batch-1 copied anchor logic for a shared match.py primitive; replacement was design-mandated, not a deadend discovered by failure
4
+ 2026-06-13 — false trigger: signals from iterative re-anchoring + a caught staging bug (fixed #51) + gh CLI flakiness; all genuine lessons already captured as scars #5/#6 and issues #50
5
+ 2026-06-13 false trigger: harvest-labeling session — 'revert/reverted/abandoned' prose describes ANOTHER repo's history (homelab-apps reverts judged as scar candidates), not an approach abandoned here; tool_errors were a wc-on-wrong-path + benign exit-1 during label recording. Curation work trips the drafter like meta-sessions do (FP #1).
6
+ 2026-06-24 false trigger: #54 scorer investigation — 'revert' is feature-domain (the revert heuristic) + 'user_corrections'/'wrong' is me correcting the ISSUE BODY's mechanism claim (flapping base vs uncapped osc bonus), not abandoning a tried code approach. Read-only mapping + scope question; no code written, nothing deadended. Same self-ref class as FP #1/#5: sessions reasoning about the scorer trip the scorer.
@@ -18,6 +18,23 @@ frontmatter.
18
18
  - Do not silently ignore broken scar files. Run `scar lint` when changing scar
19
19
  format, parsing, promotion, lifecycle, or candidate-writing behavior.
20
20
 
21
+ ## Authoring scars
22
+
23
+ When you abandon an approach (deadend), keep intentional-looking weirdness
24
+ (fence), or discover non-obvious coupling (landmine), record it as a scar.
25
+ The full authoring contract — qualification criteria, the candidates-only write
26
+ path, mandatory YAML frontmatter, and the regex over-escaping trap — is packaged
27
+ as the `scar-authoring` skill.
28
+
29
+ - **Claude Code:** install the plugin (recommended) or `scar skill install` to
30
+ drop the skill into `~/.claude/skills/`. It auto-loads on trigger.
31
+ - **MCP agents (Cursor/Windsurf/opencode):** the `scar_draft` tool enforces the
32
+ candidates-only path and lints before writing; its description carries the
33
+ digest.
34
+ - **Any runtime / manual:** run `scar agent skill` to print the full skill body
35
+ and load it into context. The canonical file is
36
+ `src/scar/skills/scar-authoring/SKILL.md`.
37
+
21
38
  ## Agent Integrations
22
39
 
23
40
  - MCP-capable agents can launch the local server with `scar mcp`.
@@ -0,0 +1,74 @@
1
+ # Changelog
2
+
3
+ ## [0.6.0](https://github.com/Daily-Nerd/Scar/compare/v0.5.0...v0.6.0) (2026-06-26)
4
+
5
+
6
+ ### Features
7
+
8
+ * **agent:** Packaged scar-authoring skill + Claude Code plugin ([#32](https://github.com/Daily-Nerd/Scar/issues/32)) ([#58](https://github.com/Daily-Nerd/Scar/issues/58)) ([b1eaca7](https://github.com/Daily-Nerd/Scar/commit/b1eaca7f702d86a4e5429c8c085c1537f5505d0d))
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **harvest:** exclude .scars/ tree from candidates (self-ref noise) ([#56](https://github.com/Daily-Nerd/Scar/issues/56)) ([29c2d61](https://github.com/Daily-Nerd/Scar/commit/29c2d6116e23a748df8455170c311023c6578c5c)), closes [#55](https://github.com/Daily-Nerd/Scar/issues/55)
14
+
15
+ ## [0.5.0](https://github.com/Daily-Nerd/Scar/compare/v0.4.0...v0.5.0) (2026-06-13)
16
+
17
+
18
+ ### Features
19
+
20
+ * **harvest:** precision@N reporting CLI — close the measurement loop ([#53](https://github.com/Daily-Nerd/Scar/issues/53)) ([100bd1d](https://github.com/Daily-Nerd/Scar/commit/100bd1d46bbac981a3629b74c237fc0584f5ce05))
21
+ * **harvest:** ranking layer — heuristic scorer + label-capture instrument ([#39](https://github.com/Daily-Nerd/Scar/issues/39)) ([7369f73](https://github.com/Daily-Nerd/Scar/commit/7369f738d3fe356a0290cbf05f0654a48587ee9f))
22
+ * **lifecycle:** lint warns on evidence commit SHAs unreachable from HEAD ([#44](https://github.com/Daily-Nerd/Scar/issues/44)) ([714357e](https://github.com/Daily-Nerd/Scar/commit/714357e9b6366ec67d71d086cf62d8dafbcae976))
23
+ * **lifecycle:** orphan detection — resolution failure, loud in CI ([#34](https://github.com/Daily-Nerd/Scar/issues/34)) ([421a12a](https://github.com/Daily-Nerd/Scar/commit/421a12aae25cc46f6aa40593a6274bb755d4b81b))
24
+ * **lifecycle:** partial-anchor rot — surface dead anchors on firing scars ([#40](https://github.com/Daily-Nerd/Scar/issues/40)) ([85fd57e](https://github.com/Daily-Nerd/Scar/commit/85fd57e397055576bd754c3d606417274d6a9d5c))
25
+
26
+
27
+ ### Bug Fixes
28
+
29
+ * **scars:** drop [#6](https://github.com/Daily-Nerd/Scar/issues/6) orphaned receipt, broaden scar [#5](https://github.com/Daily-Nerd/Scar/issues/5) for squash-merge ([#51](https://github.com/Daily-Nerd/Scar/issues/51)) ([4c63ac5](https://github.com/Daily-Nerd/Scar/commit/4c63ac50c648d8ec47190c6045987a276c9fb9bf))
30
+ * **scars:** re-anchor 3 ghost pattern anchors to real code ([#42](https://github.com/Daily-Nerd/Scar/issues/42)) ([00a2fcb](https://github.com/Daily-Nerd/Scar/commit/00a2fcb5c41c165f260019ec95bc636b18d17491))
31
+ * **scars:** replace 3 orphaned bare commit-SHA receipts with self-contained notes ([#46](https://github.com/Daily-Nerd/Scar/issues/46)) ([a224619](https://github.com/Daily-Nerd/Scar/commit/a224619f47387cce401039bf9ddbb93cb3841641))
32
+
33
+ ## [0.4.0](https://github.com/Daily-Nerd/Scar/compare/v0.3.0...v0.4.0) (2026-06-12)
34
+
35
+
36
+ ### Features
37
+
38
+ * **format:** reserve optional receipt_id field ([#29](https://github.com/Daily-Nerd/Scar/issues/29)) ([47ce933](https://github.com/Daily-Nerd/Scar/commit/47ce933cde02fa1155d0474e98101804cb7b1a80))
39
+
40
+
41
+ ### Bug Fixes
42
+
43
+ * **hooks:** expose lifecycle commands ([#31](https://github.com/Daily-Nerd/Scar/issues/31)) ([dba2c0d](https://github.com/Daily-Nerd/Scar/commit/dba2c0d1c1bd8a0f73880bfab0ff17187eec2fb9)), closes [#30](https://github.com/Daily-Nerd/Scar/issues/30)
44
+
45
+
46
+ ### Documentation
47
+
48
+ * **roadmap:** truth pass — gates resolved, Phase 1 shipped, Phase 2 in progress ([#26](https://github.com/Daily-Nerd/Scar/issues/26)) ([7701a97](https://github.com/Daily-Nerd/Scar/commit/7701a97610f470e7726e7f5fc86932a5101eb255))
49
+
50
+ ## [0.3.0](https://github.com/Daily-Nerd/Scar/compare/v0.2.0...v0.3.0) (2026-06-12)
51
+
52
+
53
+ ### Features
54
+
55
+ * **agents:** multi-agent scar integration — AGENTS.md, MCP server, agent helpers ([#21](https://github.com/Daily-Nerd/Scar/issues/21)) ([52c817f](https://github.com/Daily-Nerd/Scar/commit/52c817fc963f8f829b70de60b772c1097c6f0334))
56
+
57
+ ## [0.2.0](https://github.com/Daily-Nerd/Scar/compare/v0.1.1...v0.2.0) (2026-06-12)
58
+
59
+
60
+ ### Features
61
+
62
+ * **cli:** lifecycle v0 — challenge, archive, review_after surfacing ([#16](https://github.com/Daily-Nerd/Scar/issues/16)) ([0c6fb05](https://github.com/Daily-Nerd/Scar/commit/0c6fb05fbdbb57f8ac9b2a5b558e4cf121c3d5c0)), closes [#14](https://github.com/Daily-Nerd/Scar/issues/14)
63
+
64
+
65
+ ### Documentation
66
+
67
+ * **readme:** scar challenge is planned, not shipped — point to lifecycle issue ([8c6b021](https://github.com/Daily-Nerd/Scar/commit/8c6b021c95299cf40bf6c2d978a0421bb9705cb6))
68
+
69
+ ## [0.1.1](https://github.com/Daily-Nerd/Scar/compare/v0.1.0...v0.1.1) (2026-06-12)
70
+
71
+
72
+ ### Bug Fixes
73
+
74
+ * **hooks:** drafter triggers on revert language only ([#12](https://github.com/Daily-Nerd/Scar/issues/12)) ([547c4bb](https://github.com/Daily-Nerd/Scar/commit/547c4bb21e3521682b6a4046602d6703d88c2cf1)), closes [#11](https://github.com/Daily-Nerd/Scar/issues/11)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scar-cli
3
- Version: 0.4.0
3
+ Version: 0.6.0
4
4
  Summary: SCAR — version control for negative knowledge (deadends, fences, landmines)
5
5
  License: MIT
6
6
  License-File: LICENSE
@@ -95,6 +95,16 @@ stop all automatic injection and drafting while keeping the repository's
95
95
  scar hook uninstall
96
96
  ```
97
97
 
98
+ **Recommended (Claude Code): install the plugin** so the hooks *and* the
99
+ scar-authoring skill arrive together via the marketplace.
100
+
101
+ **Fallback / non-marketplace:** `scar hook install` registers the hooks, and
102
+ `scar skill install` drops the authoring skill into `~/.claude/skills/`. Both are
103
+ explicit — you run them, nothing is installed as a side effect.
104
+
105
+ Non-Claude agents: `scar agent skill` prints the authoring skill for any runtime;
106
+ MCP agents get the digest via the `scar_draft` tool description.
107
+
98
108
  Wiring MCP-capable agents:
99
109
 
100
110
  ```bash
@@ -86,6 +86,16 @@ stop all automatic injection and drafting while keeping the repository's
86
86
  scar hook uninstall
87
87
  ```
88
88
 
89
+ **Recommended (Claude Code): install the plugin** so the hooks *and* the
90
+ scar-authoring skill arrive together via the marketplace.
91
+
92
+ **Fallback / non-marketplace:** `scar hook install` registers the hooks, and
93
+ `scar skill install` drops the authoring skill into `~/.claude/skills/`. Both are
94
+ explicit — you run them, nothing is installed as a side effect.
95
+
96
+ Non-Claude agents: `scar agent skill` prints the authoring skill for any runtime;
97
+ MCP agents get the digest via the `scar_draft` tool description.
98
+
89
99
  Wiring MCP-capable agents:
90
100
 
91
101
  ```bash
@@ -29,11 +29,11 @@ Public at [github.com/Daily-Nerd/Scar](https://github.com/Daily-Nerd/Scar), `sca
29
29
 
30
30
  - ✅ **MCP server** (`scar_query`, `scar_why`, `scar_draft`) — shipped v0.3.0, dependency-free stdio, drafts gated to candidates/. First non-Claude agent (Codex) arrived and contributed the implementation — the deferral condition resolving itself.
31
31
  - ✅ Multi-agent surface: committed `AGENTS.md`, `scar inject --diff`, `scar agent doctor/config` for Codex, Cursor, Windsurf, opencode (v0.3.0)
32
- - 🔶 CI surface: expiry warnings shipped (`lint`/`status`, v0.2.0); **orphan detection is the next milestone** content-fingerprint drift → `orphaned` status, loud in CI (principle 3 is not yet enforced by code)
33
- - Harvest ranking layer (gate 0.1 verdict: required raw precision 13% without it)
34
- - ⬜ Re-anchoring agent workflow: orphaned scar + orphaning diff → proposed new anchors as a PR
32
+ - CI surface: expiry warnings (`lint`/`status`, v0.2.0); orphan detection — all-anchors-dead firing scars → `orphaned`, loud in CI (#34); partial-rot advisory firing scars with ≥1 dead anchor among live ones, named in `lint`/`status`/`orphan` (#35). Principle 3 now enforced by code for both total and partial rot.
33
+ - Harvest ranking layer — heuristic weighted scorer + label-capture instrument, zero-dep, deterministic (#39). Weights remain intuition until real-repo labels calibrate precision.
34
+ - ⬜ Re-anchoring agent workflow: orphaned/partially-rotted scar + orphaning diff → proposed new anchors as a PR
35
35
  - ⬜ Editor surfaces (VS Code gutter marks, LSP code lens) — fences visible to humans, not only agents
36
- - Lint warning on evidence commit SHAs unreachable from HEAD (scar #5's expiry condition)
36
+ - Lint warning on evidence commit SHAs unreachable from HEAD (#43) — scar #5's expiry condition, now enforced; advisory in `lint`, skipped on shallow clones
37
37
 
38
38
  ## Phase 3 — The org graph ⏸ parked by design
39
39
 
@@ -0,0 +1,76 @@
1
+ # Experiment: Harvest Ranking + Label Instrument (Issue #38)
2
+
3
+ **Question.** Can a cheap, explainable heuristic score RANK harvested candidates so the human curator reads the real scars first — without normalizing away the precision signal carried by candidate type?
4
+
5
+ **Why it matters.** Raw harvest precision sits at ~13% on real history. If a curator must read every candidate in arbitrary order, the tool costs more attention than it saves. Ranking earns its place only if the top-N is denser in real scars than the tail. This experiment builds the *instrument* (labels + precision@N) so that claim becomes measurable instead of asserted.
6
+
7
+ ## What the ranker does
8
+
9
+ Each candidate gets a deterministic `score` (see `src/scar/harvest.py`). The score is a sum of calibration priors — base weight per signal type plus small bonuses (PR/issue ref on reverts, files-deleted threshold, oscillation count, comment specificity, recency). All weights are **priors, unvalidated until labels exist**; this experiment is how they get validated.
10
+
11
+ **Cross-section ranking uses RAW score, no normalization** (`scar harvest --top-k N`). The per-type base constants order `comment < flapping < deleted_component < revert`. That ordering is an intentional precision prior: signal *type* predicts precision, so a revert outranks a grep hit by design. Normalizing scores across types would erase exactly the signal we want to exploit. If the labels later show the ordering is wrong, fix the base constants — do not add normalization.
12
+
13
+ ## Label JSONL format
14
+
15
+ Path: `experiments/harvest/labels.jsonl` (committed — instrument/data, like the anchor-survival replay). Written one line at a time by:
16
+
17
+ ```
18
+ scar harvest <repo> --label <id> keep|discard [--note "..."]
19
+ ```
20
+
21
+ Each line is one JSON object:
22
+
23
+ | Field | Type | Meaning |
24
+ |---------|--------|---------|
25
+ | `id` | string | the candidate's stable id (see below) |
26
+ | `label` | string | **exactly** `"keep"` or `"discard"` — nothing else is accepted |
27
+ | `note` | string | free-text rationale (may be empty) |
28
+ | `date` | string | `YYYY-MM-DD`, from `time.strftime` (monkeypatchable in tests) |
29
+ | `repo` | string | the harvested repo's name (provenance) |
30
+
31
+ **Only `keep`/`discard` are valid.** The CLI rejects any other label value with a non-zero exit and writes nothing. This is load-bearing: `precision_at_n` reads `label == "keep"` and counts everything labeled as the denominator — a third value (`"maybe"`, `"skip"`) would silently corrupt precision by inflating the denominator without ever counting toward the numerator.
32
+
33
+ **Id validation.** `--label` runs `harvest(repo)` for the target repo, collects every candidate id, and **rejects an id not in that set** (mirrors `scar orphan --apply` rejecting an unknown `--id`). You cannot label a candidate that the current harvest does not produce.
34
+
35
+ ## Candidate-id stability rule
36
+
37
+ `harvest.candidate_id(signal_type, candidate)` = first 10 hex of `sha1(signal_type + identifying-fields)`. The id is a hash of the **identifying fields only — NOT the score, NOT the id itself**, so the same candidate gets the same id across runs and a re-scored candidate keeps its label.
38
+
39
+ Identifying fields per type:
40
+
41
+ | Type | Hashed fields |
42
+ |---------------------|---------------|
43
+ | `revert` | `commit` |
44
+ | `deleted_component` | `component` |
45
+ | `flapping` | `file` + `key` |
46
+ | `comment` | `location` + `text[:40]` |
47
+
48
+ **Comment ids use `text[:40]`** — the first 40 characters of the comment text. Keep those 40 chars stable: editing the tail of a long comment preserves the id; editing the start changes it (and orphans any prior label). This deliberately tolerates the 120-char display truncation in `_comment_archaeology` without making the id depend on it.
49
+
50
+ ## Precision@N
51
+
52
+ `harvest.precision_at_n(ranked, labels, n)`:
53
+ - `ranked` — candidates pre-sorted by score descending (caller's responsibility; `scar harvest --top-k` produces this order).
54
+ - `labels` — a `{id: "keep"|"discard"}` dict built from the JSONL (group by id; last write wins if a candidate was labeled twice).
55
+ - Take the first `n`. Among them, consider **only** candidates whose id is in `labels`. Return the fraction of that labeled subset where `label == "keep"`.
56
+
57
+ **Contract: unlabeled candidates in the top-N are excluded from BOTH numerator and denominator.** They neither help nor hurt the score — precision@N measures "of the ones we judged in the top-N, how many were real". If no candidate in the top-N is labeled, the result is `0.0` (not NaN, not an error).
58
+
59
+ ## Method (to run once labels accrue)
60
+
61
+ 1. Harvest a real repo; curate the top-N by hand, recording `keep`/`discard` via `--label`.
62
+ 2. Build `{id: label}` from `labels.jsonl`.
63
+ 3. Compute `precision_at_n` at several N (e.g. 5, 10, 20) and compare against the ~13% raw base rate.
64
+ 4. Compare per-type precision to validate (or refute) the base-constant ordering.
65
+
66
+ ## Pre-registered claim
67
+
68
+ - **Ranking earns its place** if precision@N for small N is materially above the ~13% raw base rate — i.e. the top of the ranked list is denser in real scars than the unranked pool.
69
+ - If precision@N ≈ base rate at every N, the score adds no signal and the constants need rework (or the heuristic is the wrong instrument).
70
+
71
+ ## Limitations (declared)
72
+
73
+ 1. Weights are hand-set priors, not fit to data — this instrument exists to replace the guess with a measurement, but until ~50 labels accrue the ranking is an assertion.
74
+ 2. Single-curator labels carry that curator's bias; `keep`/`discard` is a coarse binary over what is really a confidence gradient.
75
+ 3. `precision_at_n` ignores recall — a candidate the harvester never surfaced cannot be labeled, so a missed real scar is invisible here.
76
+ 4. Recency scoring reads the wall clock at harvest time; the same candidate scored months apart can shift rank (id stays stable, so labels still attach correctly).
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "scar",
3
+ "version": "0.5.0",
4
+ "description": "SCAR — version control for negative knowledge (deadends, fences, landmines)",
5
+ "hooks": {
6
+ "PreToolUse": [
7
+ {
8
+ "matcher": "Edit|Write|MultiEdit|NotebookEdit",
9
+ "hooks": [
10
+ { "type": "command", "command": "scar hook precheck", "timeout": 10 }
11
+ ]
12
+ }
13
+ ],
14
+ "SessionStart": [
15
+ {
16
+ "hooks": [
17
+ { "type": "command", "command": "scar hook session-notice", "timeout": 10 }
18
+ ]
19
+ }
20
+ ],
21
+ "Stop": [
22
+ {
23
+ "hooks": [
24
+ { "type": "command", "command": "scar hook stop-drafter", "timeout": 15 }
25
+ ]
26
+ }
27
+ ]
28
+ }
29
+ }
@@ -0,0 +1,93 @@
1
+ ---
2
+ name: scar-authoring
3
+ description: >
4
+ Author negative-knowledge scars (deadend/fence/landmine) for a repo's .scars/
5
+ directory — qualification criteria, the candidates-only write path, and the
6
+ mandatory-frontmatter / regex-escaping traps that silently break scars.
7
+ Trigger: when you abandon an approach after trying it, when you keep code that
8
+ looks wrong on purpose, or when you discover that changing one thing breaks
9
+ another non-obviously — and you want to record it so the next agent does not
10
+ repeat the pain.
11
+ license: MIT
12
+ metadata:
13
+ author: Daily-Nerd
14
+ version: "1.0"
15
+ ---
16
+
17
+ # Authoring Scars
18
+
19
+ A scar records *negative knowledge*: a thing that was tried and failed, code
20
+ that looks wrong but is intentional, or a non-obvious coupling. Scars fire
21
+ automatically — the next editor sees the relevant scar injected before they
22
+ touch anchored code. Your job here is to write a good one.
23
+
24
+ ## When to Use
25
+
26
+ - You **abandoned an approach** after trying it → `deadend`
27
+ - You **kept code that looks wrong on purpose** → `fence`
28
+ - You **found that changing A breaks B non-obviously** → `landmine`
29
+
30
+ Not a scar: routine debugging that eventually succeeded. If you tried something,
31
+ it worked, and you moved on — there is nothing to record.
32
+
33
+ ## The Three Types
34
+
35
+ **`deadend` — tried and failed.** Protects against re-attempting a dead path.
36
+ Primary anchor is usually `pattern` (the shape of the failed approach).
37
+ *Example (scar #2):* an agent tried to install Claude Code hooks by writing
38
+ `~/.claude/settings.json` directly; the permission classifier denies it. Anchor
39
+ `path: src/scar/installer.py`; body says the *user* must run `scar hook install`.
40
+
41
+ **`fence` — looks wrong, is intentional.** Protects existing code from a
42
+ "cleanup" that would break it. Primary anchor is `path`.
43
+ *Example (scar #3):* the installer deliberately ignores an active virtualenv so
44
+ hooks bind to a stable `scar` on PATH, not a venv shim that disappears. Anchor
45
+ `path: src/scar/installer.py`; body says "do not 'simplify' this to plain
46
+ `shutil.which`."
47
+
48
+ **`landmine` — touching A breaks B.** Anchor the trigger site; the body names
49
+ the blast radius. *Example (scar #6):* a regex in a scar's `pattern:` field is
50
+ double-quoted YAML, so `\b` collapses and the anchor silently self-matches only
51
+ its own `.scars/` body — the protection is dead but the gauge reads green.
52
+
53
+ ## The Write Contract (non-negotiable)
54
+
55
+ 1. COPY `.scars/template.md` (or this skill's `assets/template.md`) — do not
56
+ edit the template itself.
57
+ 2. Write to `.scars/candidates/<slug>.md` with `status: candidate`.
58
+ 3. **Never** write into `.scars/*.md` directly. A human promotes via
59
+ `scar promote`.
60
+ 4. If an MCP server is wired, prefer the `scar_draft` tool — it enforces the
61
+ path and runs lint before writing.
62
+
63
+ ## Mandatory Frontmatter
64
+
65
+ A file without `---`-fenced YAML frontmatter is **not a scar at all** — it never
66
+ fires. Minimum valid block: `type`, `title`, `severity`, `confidence`,
67
+ `created`, `authors`, at least one `anchors` entry, and `status: candidate`.
68
+
69
+ ## Anti-Over-Escape (the #1 silent failure)
70
+
71
+ Prefer a `path:` anchor — it cannot self-match and needs no escaping. If you
72
+ must use a `pattern:` regex: backslashes in double-quoted YAML collapse (`\b`
73
+ dies), and the pattern is matched against all tracked content **including the
74
+ scar's own body**, so a broken pattern keeps itself alive by self-reference.
75
+ Run `scar lint` and confirm the scar does NOT appear under partial-rot
76
+ (self-match only).
77
+
78
+ Wrong: `pattern: "\\bwiden\\b"` → Right: `path: src/widen/` (no escaping, no
79
+ self-match).
80
+
81
+ ## Anchors, Severity, Size
82
+
83
+ - `path:` = repo-relative prefix (file or directory). `pattern:` =
84
+ case-insensitive regex over path + new content.
85
+ - Severity: `low | medium | high | critical`.
86
+ - Injection is capped at ~3 scars / ~700 chars each — write tight: 5–15 lines,
87
+ evidence cited inline.
88
+
89
+ ## Verify Before Finishing
90
+
91
+ - `scar lint` must pass.
92
+ - At least one `evidence` receipt (commit / pr / incident / note) — without it
93
+ the scar is challengeable on sight.
@@ -0,0 +1,25 @@
1
+ ---
2
+ # COPY THIS FILE — do not edit the template itself.
3
+ # New scars: write to .scars/candidates/<slug>.md with status: candidate.
4
+ # A human reviewer promotes to .scars/NNNN-<slug>.<type>.md with status: active.
5
+ id: 0 # assigned at promotion (next free NNNN)
6
+ type: deadend # deadend = tried+failed | fence = looks wrong, intentional | landmine = touching A breaks B
7
+ title: One line, searchable, says the constraint
8
+ severity: medium # low | medium | high | critical
9
+ confidence: 0.7 # 0..1 — how sure are we this still holds
10
+ created: 1970-01-01
11
+ authors: ["claude-code"] # add the human reviewer at promotion
12
+ anchors:
13
+ - path: src/module/ # file or directory this protects
14
+ - pattern: "regex" # optional: fires when matching code appears in ANY new/edited file
15
+ evidence:
16
+ - commit: abc1234 # at least one receipt: commit, pr, incident, or note
17
+ expires:
18
+ condition: "what change would make this scar obsolete"
19
+ review_after: 1971-01-01 # force a freshness look even if condition never triggers
20
+ status: template # candidate | active | challenged | archived (template = never parsed)
21
+ ---
22
+
23
+ Body: 5-15 lines of prose. What was tried/observed, why it failed or why the
24
+ weirdness is intentional, and what a future editor must do instead. Write it
25
+ for someone (human or agent) with zero context. Cite the evidence inline.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "scar-cli"
3
- version = "0.4.0"
3
+ version = "0.6.0"
4
4
  description = "SCAR — version control for negative knowledge (deadends, fences, landmines)"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"
@@ -64,3 +64,10 @@ def config(target: str) -> str:
64
64
  if target not in CONFIGS:
65
65
  raise ValueError(f"unknown target '{target}' (expected: {', '.join(TARGETS)})")
66
66
  return CONFIGS[target]
67
+
68
+
69
+ def skill() -> str:
70
+ """Return the full scar-authoring SKILL.md body (packaged, runtime-neutral)."""
71
+ from importlib.resources import files
72
+ resource = files("scar").joinpath("skills/scar-authoring/SKILL.md")
73
+ return resource.read_text(encoding="utf-8")