scar-cli 0.3.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.
- {scar_cli-0.3.0 → scar_cli-0.5.0}/.scars/0001-git-grep-ere-pitfalls.landmine.md +3 -5
- {scar_cli-0.3.0 → scar_cli-0.5.0}/.scars/0002-agent-direct-hook-install.deadend.md +4 -4
- {scar_cli-0.3.0 → scar_cli-0.5.0}/.scars/0005-history-rewrite-orphans-commit-evidence.landmine.md +13 -8
- scar_cli-0.5.0/.scars/0006-yaml-pattern-anchor-over-escaping.landmine.md +41 -0
- scar_cli-0.5.0/.scars/candidates/fp-log.txt +3 -0
- scar_cli-0.5.0/CHANGELOG.md +62 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/PKG-INFO +10 -1
- {scar_cli-0.3.0 → scar_cli-0.5.0}/README.md +9 -0
- scar_cli-0.5.0/ROADMAP.md +48 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/SCAR-FORMAT.md +11 -0
- scar_cli-0.5.0/experiments/harvest/PROTOCOL.md +76 -0
- scar_cli-0.5.0/hook/scar-hooks.py +15 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/pyproject.toml +1 -1
- scar_cli-0.5.0/src/scar/cli.py +602 -0
- scar_cli-0.5.0/src/scar/evidence.py +86 -0
- scar_cli-0.5.0/src/scar/harvest.py +351 -0
- scar_cli-0.3.0/hook/scar-hooks.py → scar_cli-0.5.0/src/scar/installer.py +33 -46
- {scar_cli-0.3.0 → scar_cli-0.5.0}/src/scar/match.py +20 -7
- scar_cli-0.5.0/src/scar/orphan.py +233 -0
- scar_cli-0.5.0/tests/test_cli.py +651 -0
- scar_cli-0.5.0/tests/test_evidence.py +134 -0
- scar_cli-0.5.0/tests/test_harvest.py +298 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/tests/test_installer.py +38 -0
- scar_cli-0.5.0/tests/test_orphan.py +398 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/uv.lock +1 -1
- scar_cli-0.3.0/.scars/candidates/fp-log.txt +0 -1
- scar_cli-0.3.0/CHANGELOG.md +0 -27
- scar_cli-0.3.0/ROADMAP.md +0 -46
- scar_cli-0.3.0/src/scar/cli.py +0 -274
- scar_cli-0.3.0/src/scar/harvest.py +0 -111
- scar_cli-0.3.0/tests/test_cli.py +0 -151
- scar_cli-0.3.0/tests/test_harvest.py +0 -63
- {scar_cli-0.3.0 → scar_cli-0.5.0}/.github/workflows/ci.yml +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/.github/workflows/pr-validation.yml +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/.github/workflows/release.yml +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/.gitignore +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/.scars/0003-installer-binds-to-active-venv-scar.landmine.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/.scars/0004-promote-roundtrip-drops-expires-evidence.landmine.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/.scars/README.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/.scars/template.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/AGENTS.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/CONTRIBUTING.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/IDEA.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/LICENSE +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/SPEC.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/STRESS-TEST.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/anchor-survival/PROTOCOL.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/anchor-survival/RESULTS.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/anchor-survival/long_replay.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/anchor-survival/replay.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/auto-authorship/FINDINGS.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/auto-authorship/PROTOCOL.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/fence-honor/.gitignore +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/fence-honor/PROTOCOL.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/fence-honor/RESULTS.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/fence-honor/fixture/.scars/0001-vendor-retry-window.fence.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/fence-honor/fixture/.scars/0002-evicting-session-store.deadend.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/fence-honor/fixture/.scars/0003-export-column-order.landmine.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/fence-honor/fixture/README.md +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/fence-honor/fixture/payments/retry.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/fence-honor/fixture/reports/export.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/fence-honor/fixture/services/sessions.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/fence-honor/grade.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/experiments/harvest/harvest.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/src/scar/__init__.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/src/scar/agent.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/src/scar/hooks.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/src/scar/lint.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/src/scar/mcp.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/src/scar/model.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/src/scar/render.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/src/scar/store.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/tests/test_hooks.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/tests/test_lifecycle.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/tests/test_lint.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/tests/test_match.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/tests/test_mcp.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/tests/test_model.py +0 -0
- {scar_cli-0.3.0 → scar_cli-0.5.0}/tests/test_store.py +0 -0
|
@@ -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:
|
|
12
|
-
- pattern: "git.{0,20}grep
|
|
11
|
+
- path: src/scar/harvest.py
|
|
12
|
+
- pattern: "git.{0,20}grep"
|
|
13
13
|
evidence:
|
|
14
|
-
-
|
|
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
|
-
-
|
|
11
|
+
- path: src/scar/installer.py
|
|
12
12
|
evidence:
|
|
13
|
-
-
|
|
14
|
-
-
|
|
15
|
-
- note: "
|
|
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
|
|
{scar_cli-0.3.0 → scar_cli-0.5.0}/.scars/0005-history-rewrite-orphans-commit-evidence.landmine.md
RENAMED
|
@@ -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
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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,3 @@
|
|
|
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
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [0.5.0](https://github.com/Daily-Nerd/Scar/compare/v0.4.0...v0.5.0) (2026-06-13)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* **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))
|
|
9
|
+
* **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))
|
|
10
|
+
* **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))
|
|
11
|
+
* **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))
|
|
12
|
+
* **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))
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
### Bug Fixes
|
|
16
|
+
|
|
17
|
+
* **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))
|
|
18
|
+
* **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))
|
|
19
|
+
* **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))
|
|
20
|
+
|
|
21
|
+
## [0.4.0](https://github.com/Daily-Nerd/Scar/compare/v0.3.0...v0.4.0) (2026-06-12)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Features
|
|
25
|
+
|
|
26
|
+
* **format:** reserve optional receipt_id field ([#29](https://github.com/Daily-Nerd/Scar/issues/29)) ([47ce933](https://github.com/Daily-Nerd/Scar/commit/47ce933cde02fa1155d0474e98101804cb7b1a80))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Bug Fixes
|
|
30
|
+
|
|
31
|
+
* **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)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
### Documentation
|
|
35
|
+
|
|
36
|
+
* **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))
|
|
37
|
+
|
|
38
|
+
## [0.3.0](https://github.com/Daily-Nerd/Scar/compare/v0.2.0...v0.3.0) (2026-06-12)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
### Features
|
|
42
|
+
|
|
43
|
+
* **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))
|
|
44
|
+
|
|
45
|
+
## [0.2.0](https://github.com/Daily-Nerd/Scar/compare/v0.1.1...v0.2.0) (2026-06-12)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
### Features
|
|
49
|
+
|
|
50
|
+
* **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)
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
### Documentation
|
|
54
|
+
|
|
55
|
+
* **readme:** scar challenge is planned, not shipped — point to lifecycle issue ([8c6b021](https://github.com/Daily-Nerd/Scar/commit/8c6b021c95299cf40bf6c2d978a0421bb9705cb6))
|
|
56
|
+
|
|
57
|
+
## [0.1.1](https://github.com/Daily-Nerd/Scar/compare/v0.1.0...v0.1.1) (2026-06-12)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
### Bug Fixes
|
|
61
|
+
|
|
62
|
+
* **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.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: SCAR — version control for negative knowledge (deadends, fences, landmines)
|
|
5
5
|
License: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -84,6 +84,15 @@ Wiring the Claude Code hook (auto-injects scars before any agent edit):
|
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
86
|
scar hook install
|
|
87
|
+
scar hook status
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Hooks are advisory and are installed only by this explicit user command. To
|
|
91
|
+
stop all automatic injection and drafting while keeping the repository's
|
|
92
|
+
`.scars/` records:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
scar hook uninstall
|
|
87
96
|
```
|
|
88
97
|
|
|
89
98
|
Wiring MCP-capable agents:
|
|
@@ -75,6 +75,15 @@ Wiring the Claude Code hook (auto-injects scars before any agent edit):
|
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
77
|
scar hook install
|
|
78
|
+
scar hook status
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Hooks are advisory and are installed only by this explicit user command. To
|
|
82
|
+
stop all automatic injection and drafting while keeping the repository's
|
|
83
|
+
`.scars/` records:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
scar hook uninstall
|
|
78
87
|
```
|
|
79
88
|
|
|
80
89
|
Wiring MCP-capable agents:
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# SCAR — Roadmap
|
|
2
|
+
|
|
3
|
+
Restructured after the adversarial review (see [STRESS-TEST.md](STRESS-TEST.md)): **validation before construction**. Every phase had a kill gate decided in advance. Phase 0 and Phase 1 are complete; Phase 2 is in progress. Strategy note (2026-06-11): SCAR is published as **OSS-as-gift** — personal infrastructure shared openly, no product promises. Product work only happens if organic stranger traction appears; that decision retired gate 0.5.
|
|
4
|
+
|
|
5
|
+
## Phase 0 — Kill-gate experiments ✅ complete
|
|
6
|
+
|
|
7
|
+
| # | Experiment | Gate (pass/kill) | Result |
|
|
8
|
+
|---|-----------|------------------|--------|
|
|
9
|
+
| 0.1 | Prototype `scar harvest` (reverts, add-then-remove deps, reopened issues, comment archaeology); run on 5 real aging repos | ≥1 "I'd forgotten that" reaction per repo from someone who knows it; harvest precision subjectively >50% | ✅ **PASSED 2026-06-09** on a 7-year-old personal infra repo — owner had forgotten a service-placement deadend the harvest resurfaced; 12/12 curated candidates correct. Raw precision 13% → ranking layer remains a requirement (Phase 2). |
|
|
10
|
+
| 0.2 | Prototype anchors (tree-sitter symbols + content fingerprints); replay real historical refactor commits against them | ≥80% anchor survival across rename + file-split refactors | ✅ **PASSED 2026-06-09** — 94.8% single-commit, 88.0% at 200-commit zero-maintenance horizon; naive path+line baseline 0.0% ([results](experiments/anchor-survival/RESULTS.md)) |
|
|
11
|
+
| 0.3 | **Fence honor test**: hand-write fences on a real repo, wire a PreToolUse hook, A/B agent sessions hook-on vs hook-off on tasks that tempt fence-bulldozing | Hook measurably reduces fence violations without degrading task completion | ✅ **PASSED 2026-06-09** — 6/6 control violations vs 0/6 treatment ([results](experiments/fence-honor/RESULTS.md)) |
|
|
12
|
+
| 0.4 | Auto-authorship trial: 2 weeks of normal agent-assisted work with the stop-hook drafting `deadend` candidates | ≥5 human-kept scars; false-positive rate <15% (skeptic's bar, adopted) | ✅ **PASSED 2026-06-11**, closed day 3 of 14 — 13 keepable agent-authored scars across 3 repos, 0% rejected. One agent-authored scar caught a real parser bug and fired on the exact edit that fixed it. Drafter *trigger* precision was tuned separately (revert-language-only) after 3 of 6 firings proved false. |
|
|
13
|
+
| 0.5 | Survey 50 Claude Code / Cursor users re: Copilot Memory | Meaningful segment says no + wants repo-resident, reviewable knowledge | ⛔ **RETIRED 2026-06-11** — the OSS-as-gift decision removed the product hypothesis this gate validated. If stranger traction ever reopens the product question, this survey reopens with it. |
|
|
14
|
+
| 0.6 | Implement Lore trailers on the same repo as 0.3; compare agent behavior vs scar injection | SCAR injection outperforms history-walk over trailers on latency and compliance | ⏸ **Deprioritized** — no longer gating anything; optional research. |
|
|
15
|
+
|
|
16
|
+
## Phase 1 — Format + CLI (OSS) ✅ shipped
|
|
17
|
+
|
|
18
|
+
Public at [github.com/Daily-Nerd/Scar](https://github.com/Daily-Nerd/Scar), `scar-cli` on PyPI. Honest deltas from the original plan:
|
|
19
|
+
|
|
20
|
+
- ✅ `SCAR-FORMAT.md` v0.1 published; one parser/serializer (`model.py`), one renderer (`render.py`)
|
|
21
|
+
- ✅ CLI: `init, lint, status, promote, check, why, challenge, archive, harvest, hook, mcp, agent, inject` — lifecycle commands (`challenge`/`archive`, expiry review surfacing) shipped beyond the original plan
|
|
22
|
+
- ✅ Claude Code plugin: PreToolUse injection + stop-hook candidate drafting, both field-validated (gates 0.3, 0.4)
|
|
23
|
+
- ⚠️ **Python, not Go/Rust** — zero-dependency stdlib hits the goal the compiled binary was chasing (~20ms hook startup, trivial install via `uv tool install scar-cli`); a rewrite is not planned unless profiling says otherwise
|
|
24
|
+
- ❌ No `add` command — copy `template.md` + `scar promote` covers authoring; revisit only on user friction
|
|
25
|
+
- ❌ Lore trailer ingestion in `harvest` — moved to Phase 2, optional
|
|
26
|
+
- ✅ Dogfooding: 6 repos, including this one (the repo's own scars caught its own release-process bug)
|
|
27
|
+
|
|
28
|
+
## Phase 2 — Ecosystem 🔄 in progress
|
|
29
|
+
|
|
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
|
+
- ✅ 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 (`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
|
+
- ⬜ 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 (#43) — scar #5's expiry condition, now enforced; advisory in `lint`, skipped on shallow clones
|
|
37
|
+
|
|
38
|
+
## Phase 3 — The org graph ⏸ parked by design
|
|
39
|
+
|
|
40
|
+
The commercial hypothesis (cross-repo aggregation, recurrence analytics, policy, managed harvest) is **parked under the OSS-as-gift decision**, not killed: it reopens only on organic adoption signal — external repos with active scars, inbound interest from strangers. "SCAR remains a free standard" was declared in advance as an acceptable ending, and it is the current operating assumption.
|
|
41
|
+
|
|
42
|
+
## Non-negotiable principles carried from the stress test
|
|
43
|
+
|
|
44
|
+
1. Advisory by default, forever. Blocking is opt-in, per-scar-severity, in CI only.
|
|
45
|
+
2. Max 3 scars / ~120 words each injected per edit. The fatigue budget is a format-level guarantee, not a tuning knob (enforced in `render.py`).
|
|
46
|
+
3. Rot must be loud. No scar ever disappears silently; orphaning is a visible state. *(Lifecycle transitions enforce this for human decisions; orphan detection — the code-drift half — is Phase 2's next milestone.)*
|
|
47
|
+
4. Assume zero ongoing human maintenance; design for graceful visible decay.
|
|
48
|
+
5. The format stays open and vendor-neutral even if a company forms. Platform absorption of the format = success, not failure.
|
|
@@ -47,6 +47,17 @@ is deliberate: consumers in hook hot-paths parse with zero dependencies.
|
|
|
47
47
|
| `expires.condition` | recommended | quoted string | what change obsoletes this scar |
|
|
48
48
|
| `expires.review_after` | recommended | ISO date | forces periodic freshness contact |
|
|
49
49
|
| `status` | yes | `candidate` \| `active` \| `challenged` \| `archived` \| `orphaned` \| `template` | lifecycle §5 |
|
|
50
|
+
| `receipt_id` | reserved | string ref | **reserved — not yet parsed.** Optional pointer to a cryptographic provenance receipt; see note below. |
|
|
51
|
+
|
|
52
|
+
`receipt_id` is a forward-compatibility reservation, not a live field. Scar's
|
|
53
|
+
trust model is social by design (git history, `authors`, evidence-by-reference);
|
|
54
|
+
that is sufficient within one repo or org. It does **not** carry across orgs that
|
|
55
|
+
share no git history — the future cross-org / org-graph layer where "this dead end
|
|
56
|
+
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))
|
|
58
|
+
bound to an authorship (`scar_draft`) or promotion event. No tool emits, requires,
|
|
59
|
+
or validates it today, and the line-wise parser ignores it like any unknown key —
|
|
60
|
+
so existing scars are unaffected. It will not become required in v0.x.
|
|
50
61
|
|
|
51
62
|
Body: prose after the frontmatter, 5–15 lines. What happened, why, what a
|
|
52
63
|
future editor must do instead — written for a reader with zero context.
|
|
@@ -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,15 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Backward-compatible wrapper for the packaged hook lifecycle commands."""
|
|
3
|
+
|
|
4
|
+
import sys
|
|
5
|
+
|
|
6
|
+
from scar.installer import find_scar, install, status, uninstall
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
if __name__ == "__main__":
|
|
10
|
+
args = sys.argv[1:]
|
|
11
|
+
dry = "--dry-run" in args
|
|
12
|
+
cmd = next((a for a in args if not a.startswith("-")), "status")
|
|
13
|
+
sys.exit({"install": lambda: install(dry=dry),
|
|
14
|
+
"uninstall": lambda: uninstall(dry=dry),
|
|
15
|
+
"status": status}.get(cmd, status)())
|