opencode-skills-collection 4.0.42 → 4.0.43

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 (41) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +3 -1
  2. package/bundled-skills/docs/integrations/jetski-cortex.md +3 -3
  3. package/bundled-skills/docs/integrations/jetski-gemini-loader/README.md +1 -1
  4. package/bundled-skills/docs/maintainers/repo-growth-seo.md +1 -1
  5. package/bundled-skills/docs/maintainers/skills-update-guide.md +1 -1
  6. package/bundled-skills/docs/users/aas-core.md +1 -1
  7. package/bundled-skills/docs/users/bundles.md +1 -1
  8. package/bundled-skills/docs/users/claude-code-skills.md +1 -1
  9. package/bundled-skills/docs/users/gemini-cli-skills.md +1 -1
  10. package/bundled-skills/docs/users/kiro-integration.md +1 -1
  11. package/bundled-skills/docs/users/usage.md +3 -3
  12. package/bundled-skills/docs/users/visual-guide.md +4 -4
  13. package/bundled-skills/lore/SKILL.md +104 -298
  14. package/bundled-skills/lore/references/audit-template.md +21 -4
  15. package/bundled-skills/lore/references/compatibility.md +51 -121
  16. package/bundled-skills/lore/references/config.md +22 -23
  17. package/bundled-skills/lore/references/entry-format.md +33 -3
  18. package/bundled-skills/lore/references/history-command.md +98 -2
  19. package/bundled-skills/lore/references/platform-mirrors.md +45 -16
  20. package/bundled-skills/lore/references/stale-new-markers.md +10 -8
  21. package/bundled-skills/lore/references/summary-template.md +8 -1
  22. package/bundled-skills/lore/references/workflows.md +192 -0
  23. package/bundled-skills/lore/scripts/README.md +14 -13
  24. package/bundled-skills/lore/scripts/README.zh-CN.md +14 -13
  25. package/bundled-skills/lore/scripts/find_duplicates.py +14 -4
  26. package/bundled-skills/lore/scripts/find_stale.py +68 -16
  27. package/bundled-skills/lore/scripts/history.py +235 -23
  28. package/bundled-skills/lore/scripts/id_hash.py +7 -4
  29. package/bundled-skills/lore/scripts/list_entries.py +82 -13
  30. package/bundled-skills/poka-yoke/SKILL.md +172 -0
  31. package/bundled-skills/project-state-governor/SKILL.md +1 -1
  32. package/bundled-skills/project-state-governor/references/project-state-schema.md +1 -1
  33. package/bundled-skills/spec-driven-loop/SKILL.md +203 -0
  34. package/bundled-skills/spec-driven-loop/references/agent-and-judge-contracts.md +120 -0
  35. package/bundled-skills/spec-driven-loop/references/document-templates.md +184 -0
  36. package/package.json +1 -1
  37. package/skills_index.json +67 -1
  38. package/bundled-skills/lore/README.md +0 -386
  39. package/bundled-skills/lore/README.zh-CN.md +0 -386
  40. package/bundled-skills/lore/WORKFLOWS.md +0 -216
  41. package/bundled-skills/lore/WORKFLOWS.zh-CN.md +0 -216
@@ -4,18 +4,18 @@ This document defines how `lore` evolves without breaking existing user projects
4
4
 
5
5
  ## Three principles
6
6
 
7
- 1. **Add, never subtract.** New fields, scripts, sections, and reference docs always use new names. Removal happens only after the deprecation cycle (below).
7
+ 1. **Add, never subtract.** New fields, scripts, sections, and reference docs always use new names. Removal is a breaking change: the commit message must be prefixed `BREAKING:` and explain what users need to change.
8
8
  2. **Readers are forward-compatible.** An older skill reading a newer `.lore/` ignores unknown fields, unknown files, and unknown tags. It never errors on unfamiliar content.
9
- 3. **Writers are backward-compatible (during transition).** A newer skill detecting an older `.lore/` runs a migration step before writing. It never overwrites old data with new defaults.
9
+ 3. **Writers are backward-compatible (during transition).** A newer skill detecting an older `.lore/` reads the older shape, fills missing fields with defaults, and writes only what it intended to change. It never overwrites old data with new defaults.
10
10
 
11
11
  ## Layer-specific rules
12
12
 
13
13
  ### Layer 1: `.lore/.config.json` schema
14
14
 
15
15
  - `schema_version` is **required** (integer). See `references/config.md` for handling missing/newer/older values.
16
- - Adding a new field = bump `schema_version` to N+1; old fields stay; the field is added with its default value at first read.
17
- - Removing a field requires the deprecation cycle (one schema version's worth of warnings before hard removal).
18
- - Renaming a field: write the new field, copy the value, mark the old one with `_deprecated: "reason"`. The migration tool handles this in `migrate.py`.
16
+ - Adding a new optional field does not require a schema bump: old readers ignore unknown fields, new readers fill missing fields with defaults. See "Rule of thumb" under Examples below.
17
+ - Removing a field is a breaking change. The commit must be prefixed `BREAKING:` and name the field and the migration step the user must take.
18
+ - Renaming a field: keep both fields for one release, mark the old field as deprecated in the commit message, and remove in the next breaking release. The user edits their config by hand.
19
19
 
20
20
  ### Layer 2: Entry format
21
21
 
@@ -23,8 +23,8 @@ This document defines how `lore` evolves without breaking existing user projects
23
23
  - [ARCH-2026-07-10-a3f2] Entry text; reason. #added:2026-07-10 #verified:2026-07-15
24
24
  ```
25
25
 
26
- - IDs (`LAYER-DATE-HASH`) are stable as long as the entry text is unchanged. Editing an entry produces a new ID; old ID stays in history (via git) for `history` queries.
27
- - Tag set is a closed set today: `#added`, `#verified`, `#stale`, `#archived`. Adding a new tag is allowed; old skills' tag parsers (which match `(added|verified|stale|archived)`) silently ignore unknown tags.
26
+ - IDs (`LAYER-DATE-HASH`) are stable as long as the entry text is unchanged. Editing an entry produces a new ID; old ID stays in history (via git) for `history` queries. `sync`'s `[REFINED]` respects this: tags-only updates keep the ID, body rewrites create a new ID and link the old entry via `#superseded-by`.
27
+ - Tag set is a closed set today: `#added`, `#verified`, `#stale`, `#superseded-by`. Adding a new tag is allowed; old skills' tag parsers (which match `(added|verified|stale|superseded-by)`) silently ignore unknown tags. The previous `#archived` tag is no longer part of the vocabulary; old entries carrying it are treated as unknown tags (still parse, semantic meaning is lost — use `#superseded-by` going forward).
28
28
  - **Never make a tag required.** Required tags break every old entry in every old `.lore/`.
29
29
 
30
30
  ### Layer 3: `.lore/` directory structure
@@ -34,38 +34,40 @@ Current canonical layout:
34
34
  ```
35
35
  .lore/
36
36
  ├── SUMMARY.md
37
+ ├── .config.json
37
38
  ├── _global/
38
39
  ├── scopes/
40
+ ├── .archive/ (My notes backups before a user-requested wipe)
39
41
  ├── draft/ (init only — temporary)
40
- ├── audit/ (audit only)
41
- └── archive/ (referenced in spec; reserved for future)
42
+ └── audit/ (audit only)
42
43
  ```
43
44
 
44
45
  Rules:
45
-
46
46
  - Adding a new top-level directory (e.g., `rejected/` for rejected entries) is non-breaking.
47
47
  - Renaming an existing directory is breaking — every reference in `references/*.md`, every script, and every user's project breaks.
48
- - Removing a directory is breaking unless that directory was never actually written (e.g., removing `archive/` today is non-breaking because nothing writes there yet).
48
+ - Removing a directory is non-breaking if it was never actually written. The previous `archive/` directory fell into this category and has been removed from the layout. Note: when the user asks to wipe a mirror's My notes, lore archives the old content to `.lore/.archive/` first (see `references/platform-mirrors.md`); that directory is part of the layout above and is **not** an entries archive — it is a user-confirmed backup location only.
49
49
 
50
50
  ### Layer 4: Python scripts
51
51
 
52
- Current scripts: `id_hash.py`, `list_entries.py`, `find_stale.py`, `find_duplicates.py`, `history.py`, plus planned `migrate.py`.
52
+ Current scripts: `id_hash.py`, `list_entries.py`, `find_stale.py`, `find_duplicates.py`, `history.py`.
53
53
 
54
54
  Rules:
55
55
 
56
- - **Renaming is breaking.** All names are part of the public surface; they're referenced from `SKILL.md`, `references/*.md`, and downstream tooling. Don't rename; deprecate and add a new one if needed.
57
- - **Removing is breaking.** A deprecated script stays on disk with a clear "Deprecated: use `migrate.py` instead" header for one schema version.
56
+ - **Renaming is breaking.** All names are part of the public surface; they're referenced from `SKILL.md`, `references/*.md`, and downstream tooling. Don't rename; add a new one with a different name if needed.
57
+ - **Removing is breaking.** When a script is removed, the commit is prefixed `BREAKING:` and names the replacement. The removed file is deleted in the same commit.
58
58
  - **Adding a new script is non-breaking.** Reference it from `SKILL.md` reference index on introduction.
59
- - **Changing output format is breaking for `--json` consumers.** Add `--v2-output` or a new flag; old flag keeps old behavior forever.
59
+ - **Changing output format is breaking for `--json` consumers.** Add a new flag (e.g. `--v2-output`) rather than changing existing output; the old flag keeps old behavior forever.
60
60
 
61
61
  ### Layer 5: Platform mirror files
62
62
 
63
63
  Mirror files (`CLAUDE.md`, `.cursorrules`, `AGENTS.md`, etc.) follow this contract:
64
64
 
65
65
  ```markdown
66
+ <!-- LORE:START -->
66
67
  ## Lore (auto-managed)
67
68
 
68
69
  ... lore content ...
70
+ <!-- LORE:END -->
69
71
 
70
72
  ---
71
73
  ## My notes (free edit)
@@ -75,16 +77,18 @@ Mirror files (`CLAUDE.md`, `.cursorrules`, `AGENTS.md`, etc.) follow this contra
75
77
 
76
78
  Rules:
77
79
 
78
- - `## Lore (auto-managed)` is a **contract string**. Never rename; never remove. Mirror detection regexes depend on it.
80
+ - `<!-- LORE:START -->` and `<!-- LORE:END -->` are **contract strings** for new mirrors (post-v1). Never rename; never remove. They are the authoritative boundary the skill uses for detection.
81
+ - `## Lore (auto-managed)` is a **contract string**. Never rename; never remove. Mirror detection regexes depend on it as a secondary signal.
79
82
  - `## My notes (free edit)` is a **contract string**. Never rename; never remove. User-written content depends on it.
80
- - The content between `## Lore` and `---` is lore's domain; content after `---` is the user's. Respect the boundary on every regeneration.
81
- - Adding a new auto-managed section (e.g., `## Sync history (auto-managed)`) is allowed; insert before `---`. Old skills ignore it.
83
+ - The content between `<!-- LORE:START -->` and `<!-- LORE:END -->` is lore's domain; content after `<!-- LORE:END -->` and `---` is the user's. Respect the boundary on every regeneration.
84
+ - Pre-v1 mirrors without HTML comments are still detected and preserved via the `---` separator and `## My notes` header. The first `lore mirror` run on such a file offers the user an upgrade prompt (see `references/platform-mirrors.md` rule 5b).
85
+ - Adding a new auto-managed section (e.g., `## Sync history (auto-managed)`) is allowed; insert before `<!-- LORE:END -->`. Old skills ignore it.
82
86
  - Changing the index template body (e.g., adding a "Last mirror:" line) is non-breaking: content-based dedup means unchanged mirrors are not rewritten, so old mirrors stay valid.
83
87
  - **Backward-write safety**: if the existing mirror has no `## My notes (free edit)` section (e.g., a legacy single-section mirror from a pre-v1 project), the first `lore mirror` run must **append** an empty My notes section rather than overwriting the file.
84
88
 
85
89
  ### Layer 6: reference docs
86
90
 
87
- Current docs: `entry-format.md`, `summary-template.md`, `audit-template.md`, `monorepo-detection.md`, `stale-new-markers.md`, `platform-mirrors.md`, `config.md`, `history-command.md`, `compatibility.md` (this file).
91
+ Current docs: `workflows.md`, `entry-format.md`, `summary-template.md`, `audit-template.md`, `monorepo-detection.md`, `stale-new-markers.md`, `platform-mirrors.md`, `config.md`, `history-command.md`, `compatibility.md` (this file).
88
92
 
89
93
  Rules:
90
94
 
@@ -93,122 +97,47 @@ Rules:
93
97
  - **Removing a doc** is breaking. Mark it `<!-- DEPRECATED: see new-location.md -->` for one schema version, then move to `archive/` (in `references/`, not in `.lore/`).
94
98
  - **Adding a doc** is non-breaking. Add to `SKILL.md` reference index on introduction.
95
99
 
96
- ## Migration tool
97
-
98
- `scripts/migrate.py` does not exist in v1. It will be added on the first `schema_version` bump.
99
-
100
- **Triggers that will ship the first migration:**
101
-
102
- - A new required field is added to `.lore/.config.json`.
103
- - A field is renamed or its accepted values are tightened.
104
- - The entry ID algorithm changes.
105
-
106
- Until any of these happen, `schema_version: 1` is the only version and no migration is possible or needed.
107
-
108
- Until the script ships, `list_entries.py` emits a one-time warning to stderr if `.lore/.config.json` is missing the `schema_version` field. This nudges users to add the field manually before the first breaking change ships, so future upgrades can detect the version mismatch correctly.
109
-
110
- **Template — to be implemented when the first migration is needed:**
111
-
112
- ```python
113
- #!/usr/bin/env python3
114
- """Migrate .lore/.config.json from version N to N+1.
115
-
116
- Idempotent: running twice produces no further changes.
117
- Refuses to run if schema_version > EXPECTED_FROM.
118
- """
119
- import json, sys
120
- from pathlib import Path
121
-
122
- EXPECTED_FROM = 1 # versions this script can read
123
- TARGET = 2 # current schema version after migration
124
-
100
+ ## Migration
125
101
 
126
- def migrate(config: dict) -> tuple[dict, list[str]]:
127
- msgs = []
128
- # v1 → v2 transformations go here. Example:
129
- # if config.get("mirror_mode") == "summary":
130
- # config.pop("mirror_mode", None)
131
- # msgs.append('removed deprecated "mirror_mode": "summary"')
132
- config["schema_version"] = TARGET
133
- return config, msgs
102
+ There is no automatic migration tool in v1. Upgrades are `git pull` + read the commit history for any `BREAKING:` commits. The user edits their config by hand if a field was renamed or removed.
134
103
 
104
+ `list_entries.py` emits a one-time `[WARN]` to stderr if `.lore/.config.json` is missing the `schema_version` field. Add `"schema_version": 1` manually to silence it.
135
105
 
136
- def main() -> int:
137
- cfg_path = Path(".lore/.config.json")
138
- if not cfg_path.exists():
139
- print(f"error: {cfg_path} not found", file=sys.stderr)
140
- return 1
141
- cfg = json.loads(cfg_path.read_text(encoding="utf-8"))
142
- current = cfg.get("schema_version", 1)
143
- if current > EXPECTED_FROM:
144
- print(
145
- f"error: schema_version={current} is newer than this skill "
146
- f"can read (max: {EXPECTED_FROM}). Pull latest lore.",
147
- file=sys.stderr,
148
- )
149
- return 2
150
- if current < TARGET:
151
- print(f"migrating v{current} → v{TARGET}...")
152
- cfg, msgs = migrate(cfg)
153
- for m in msgs:
154
- print(f" - {m}")
155
- cfg_path.write_text(json.dumps(cfg, indent=2) + "\n", encoding="utf-8")
156
- print("done.")
157
- return 0
106
+ ## Deprecation
158
107
 
159
-
160
- if __name__ == "__main__":
161
- sys.exit(main())
162
- ```
163
-
164
- ## Deprecation workflow
165
-
166
- Any capability slated for removal follows a three-stage cycle. The minimum cycle is **two schema versions** (typically 6–12 months).
167
-
168
- | Stage | Schema version | Behavior |
169
- |---|---|---|
170
- | **Announce** | N | Add an entry to `references/deprecations.md` with the feature name, replacement, and removal target version. The skill prints a one-line notice when the feature is used. |
171
- | **Warn** | N+1 | The skill prints a louder warning (with remediation steps) and writes a `_deprecation_warnings_shown` array to `.lore/.config.json` so the warning doesn't repeat. |
172
- | **Remove** | N+2 | Hard delete. Old `.lore/` data is migrated by `migrate.py` to the new format. Users who skipped migrations will see explicit errors pointing at `migrate.py`. |
173
-
174
- Skipping a stage is allowed only for security fixes or unreleased features that were never shipped.
108
+ There is no deprecation registry in v1. A capability slated for removal ships in a commit prefixed `BREAKING:` that names the capability and the migration step. The previous release may emit a one-line `[WARN]` notice when the deprecated capability is used, but there is no automated reminder system.
175
109
 
176
110
  ## CI enforcement
177
111
 
178
- A compatibility CI job (planned for `.github/workflows/compat.yml`) verifies:
179
-
180
- 1. **New skill reads old `.lore/`**: checkout a fixture project from `fixtures/v0-project/`, run `list_entries.py`, `history.py`, `find_stale.py` on it. Pass = no exceptions, correct counts.
181
- 2. **Old skill reads new `.lore/`**: build a fixture with the latest schema, check out the previous release's scripts, run them. Pass = no exceptions on known fields; unknown fields silently ignored.
182
- 3. **No rename or delete in `scripts/` or `references/`**: PR diff against `scripts/` and `references/` filenames; any removed file = failure.
183
-
184
- At least one fixture project must be checked in at `fixtures/v0-project/` and re-pinned to a known old schema version after each major release.
112
+ There is no compatibility CI in v1. Verification is the author's responsibility before each release: run `list_entries.py`, `history.py`, `find_stale.py` against a `.lore/` populated by the author's own work, and confirm the output is sensible.
185
113
 
186
114
  ## Examples
187
115
 
188
116
  ### Compatible change (additive)
189
117
 
190
- Adding a new `compress_thresholds.max_entries_per_scope` field:
118
+ Adding a new optional field, for example `compress_thresholds.max_entries_per_scope` with default `100`:
191
119
 
192
- - Bump `schema_version` to 2.
193
- - `migrate.py` v1 v2: add the new field with default `100` if absent.
194
- - Old skill reads v2 config: sees only the fields it knows; ignores `max_entries_per_scope`.
195
- - New skill reads v1 config: detects `schema_version` mismatch, refuses to write until migration runs.
120
+ - Old configs continue to operate; the new field reads as the default.
121
+ - Old skill reading a new config: sees only the fields it knows; ignores the new field.
122
+ - New skill reading an old config: detects the missing field and uses the default.
123
+ - No upgrade notes needed; the change is invisible to existing users.
124
+
125
+ **Rule of thumb.** An additive optional change is non-breaking: ship it without bumping anything, no migration steps, no warnings.
196
126
 
197
127
  ### Incompatible change (avoid)
198
128
 
199
- Renaming `mirror_mode` to `render_mode`:
129
+ Renaming `mirror_mode` to `render_mode` in a single release:
200
130
 
201
- - Every existing `.lore/.config.json` would silently lose its `mirror_mode: "index"` setting on next migration (old field dropped, new field absent → defaults kick in).
202
- - Bad. Instead: add `render_mode` as the new canonical field; mark `mirror_mode` as `_deprecated`; keep both for one schema version; eventually remove `mirror_mode` via the deprecation cycle.
131
+ - Every existing `.lore/.config.json` would silently lose its `mirror_mode: "index"` setting (old field dropped, new field absent → defaults kick in).
132
+ - Bad. Instead: keep both fields for one release, mark `mirror_mode` as deprecated in the commit message, then remove `mirror_mode` in the next breaking release.
203
133
 
204
- ### Breaking change (deprecation cycle required)
134
+ ### Breaking change (commit message)
205
135
 
206
- Removing support for `mirror_mode: "full"`:
136
+ Removing support for a config value such as `mirror_mode: "full"`:
207
137
 
208
- - v1: ship `mirror_mode: "full"` as deprecated; full mode still works.
209
- - v2: print warning when `"full"` is set; suggest migrating to `"index"`.
210
- - v3: hard reject `"full"`; `migrate.py` auto-converts to `"index"`.
211
- - Each version's release notes link to the deprecation entry in `references/deprecations.md`.
138
+ - The release prints a warning when the value is set; suggests `"index"`.
139
+ - A future release hard-rejects `"full"`. Users edit their config by hand.
140
+ - The breaking commit message names the change and the manual edit.
212
141
 
213
142
  ## Decision checklist
214
143
 
@@ -216,13 +145,14 @@ Before merging any change to lore, answer these questions:
216
145
 
217
146
  1. Does this change add, modify, or remove anything in `.lore/`?
218
147
  2. Does this change add, modify, or remove any script in `scripts/`?
219
- 3. Does this change add, modify, or remove any contract string (`## Lore (auto-managed)`, `## My notes (free edit)`, etc.)?
148
+ 3. Does this change add, modify, or remove any contract string (`## Lore (auto-managed)`, `## My notes (free edit)`, `<!-- LORE:START -->`, `<!-- LORE:END -->`, etc.)?
220
149
  4. Does this change add, modify, or remove any reference doc filename?
221
150
  5. Does this change add, modify, or remove any entry tag?
222
151
 
223
- If any answer is "modify" or "remove", the change requires either:
224
- - A migration step in `migrate.py` (for schema changes)
225
- - A deprecation cycle (for removals)
226
- - An entry in `references/deprecations.md`
152
+ If any answer is "add" and the change is non-breaking (new optional field, new optional tag, new doc, new script), ship as-is with a regular commit prefix (`feat:`, `docs:`, `refactor:`).
153
+
154
+ If any answer is "modify" or "remove", the change is breaking and the commit must:
155
+ - Be prefixed `BREAKING:` instead of `feat:` / `refactor:`.
156
+ - Name what changed and what the user must do in the commit body.
227
157
 
228
- If all answers are "add" or "no", the change is non-breaking and can ship as a minor or patch release.
158
+ If all answers are "add" or "no", the change is non-breaking and ships as a regular commit.
@@ -19,7 +19,8 @@
19
19
  "sync_thresholds": {
20
20
  "min_lines_changed": 50,
21
21
  "min_directories_changed": 2
22
- }
22
+ },
23
+ "last_sync_sha": "f9ca271..." // set automatically by sync; null on first run
23
24
  }
24
25
  ```
25
26
 
@@ -29,10 +30,9 @@
29
30
 
30
31
  - **Missing** → treated as `schema_version: 1`. A `[WARN]` notice is printed to stderr by `list_entries.py`; add the field manually to silence it.
31
32
  - **Equal to skill's expected version** → use as-is.
32
- - **Lower than expected** → refuse to write; ask the user to run the migration script shipped with that future release.
33
- - **Higher than expected** → refuse to read with an error; the user's skill is older than their `.lore/`. They need to upgrade lore (pull latest from upstream) before continuing.
33
+ - **Higher than expected** → warn and continue: `list_entries.py` prints a `[WARN]` to stderr and proceeds with best-effort reads (readers are forward-compatible — see `references/compatibility.md`). The user's skill is older than their `.lore/`; recommend pulling the latest lore from upstream.
34
34
 
35
- For the full compatibility policy (migration tools, deprecation cycle, reader/writer contracts), see `references/compatibility.md`.
35
+ For the full compatibility policy, see `references/compatibility.md`.
36
36
 
37
37
  ## Field semantics
38
38
 
@@ -40,10 +40,10 @@ For the full compatibility policy (migration tools, deprecation cycle, reader/wr
40
40
 
41
41
  Default: `false`.
42
42
 
43
- Controls whether `compress` and `lore mirror` regenerate platform mirrors automatically after the canonical change is accepted.
43
+ Controls whether `compress` regenerates platform mirrors automatically after it writes `SUMMARY.md`. It does **not** gate the explicit `lore mirror` command `lore mirror` always regenerates (with content-based dedup) once `mirror_targets` is resolved.
44
44
 
45
- - `true` — regenerate mirrors automatically
46
- - `false` — ask per target before writing
45
+ - `true` — `compress` regenerates mirrors automatically
46
+ - `false` — `compress` asks per target before writing
47
47
 
48
48
  Note: this flag does **not** affect `sync`. By default `sync` does not touch mirrors at all (see `sync_updates_mirror`).
49
49
 
@@ -63,8 +63,8 @@ Default: `"medium"`.
63
63
  Controls how much confirmation `sync` requires for individual change types.
64
64
 
65
65
  - `"high"` — auto-apply everything, including `NEW` and `STALE`. Only `ALERT` blocks interrupt.
66
- - `"medium"` — auto-apply low-risk changes (de-duplicate hits, equivalent REFINEDs). `NEW`, `STALE`, and `ALERT` require confirmation.
67
- - `"low"` — every change requires confirmation, including de-duplicate hits and equivalent REFINEDs.
66
+ - `"medium"` — auto-apply low-risk changes (de-duplicate hits, tags-only REFINEDs). Body-changing REFINEDs, `NEW`, `STALE`, and `ALERT` require confirmation.
67
+ - `"low"` — every change requires confirmation, including de-duplicate hits and tags-only REFINEDs.
68
68
 
69
69
  ### `mirror_targets`
70
70
 
@@ -88,7 +88,7 @@ When auto-detection is in effect, `lore init` populates this field with the user
88
88
 
89
89
  Default: `"index"`.
90
90
 
91
- Only `"index"` is accepted. The mirror renders a small index structure pointing into `.lore/` (see `references/platform-mirrors.md` for the template and adaptive rendering rules). Per-session token cost stays flat (~500 B) regardless of project size.
91
+ Only `"index"` is accepted. The mirror renders a small index structure pointing into `.lore/` (see `references/platform-mirrors.md` for the template and adaptive rendering rules). Per-session token cost stays flat (~600 B worst case, including a one-line operational opening and per-scope descriptions) regardless of entry count.
92
92
 
93
93
  Any other value (e.g., the historical `"summary"` or `"full"`) is rejected at config-load time with an error. Remove the field, or set it to `"index"`.
94
94
 
@@ -104,22 +104,21 @@ Defaults: `{"min_lines_changed": 50, "min_directories_changed": 2}`.
104
104
 
105
105
  `sync` only proposes an update when at least one trigger threshold is met (see `SKILL.md` sync trigger threshold). Lowering these values means `sync` proposes updates more often.
106
106
 
107
- ## Editing the config
107
+ ### `last_sync_sha`
108
108
 
109
- Edit `.lore/.config.json` directly. After editing:
109
+ Default: absent (treated as `null`).
110
110
 
111
- - `sync` and `compress` re-read the config on every run; no restart needed.
112
- - Invalid JSON → fall back to defaults + warn the user.
113
- - After editing, verify with `python scripts/list_entries.py --config-check` (added in a future migration).
114
- - For schema version changes, see `references/compatibility.md`.
111
+ The git commit SHA from which the next `sync` will compute its delta. Written automatically by `sync` after every successful `.lore/*` update. Optional and additive — old configs without it keep working.
115
112
 
116
- ## Upgrade path
113
+ - **Absent or `null`** — `sync` falls back to working-tree diff alone (`git diff`, working tree vs. `HEAD`). Already-committed changes between two syncs are invisible.
114
+ - **Set to a reachable SHA** — `sync` uses `git diff <last_sync_sha>..HEAD` for committed changes plus working-tree diff for uncommitted changes. This is the recommended state and lets batched commits be captured by a single later sync.
115
+ - **Set to an unreachable SHA** (e.g., after `git rebase` or a force-push that orphaned the SHA) — `sync` prints a `[WARN]` to stderr and falls back to the working-tree diff alone. The next successful sync resets the baseline.
116
+ - **Empty repo** (no commits yet) — the field is `null`; only the working-tree diff applies.
117
117
 
118
- When a future lore release introduces the first `schema_version` bump:
118
+ ## Editing the config
119
119
 
120
- 1. That release ships `scripts/migrate.py` for the specific version bump.
121
- 2. The skill prompts: "Your `.lore/.config.json` is v1; lore now expects v2. Run `python scripts/migrate.py` to upgrade."
122
- 3. `migrate.py` is idempotent — running it twice is a no-op.
123
- 4. After migration, the file is updated in place; no manual editing needed.
120
+ Edit `.lore/.config.json` directly. After editing:
124
121
 
125
- In v1, no migration has shipped and `scripts/migrate.py` does not exist. Add `"schema_version": 1` manually to old configs to silence the warning.
122
+ - `sync` and `compress` re-read the config on every run; no restart needed.
123
+ - Invalid JSON → fall back to defaults + warn the user.
124
+ - For breaking-config changes, see `references/compatibility.md` and look for the `BREAKING:` commit in the project's git history.
@@ -27,14 +27,18 @@ The 4-char `xxxx` is the first 4 hex chars of `sha256(entry text)`. This makes I
27
27
 
28
28
  If two entries have identical content (hash collision, statistically rare), add a distinguishing word to one and recompute.
29
29
 
30
+ ### Updating an entry (REFINED)
31
+
32
+ Because the ID hashes the body, **any body change produces a new ID**. `sync`'s `[REFINED]` proposal follows this rule: tags-only updates (body unchanged) keep the ID; body rewrites create a new entry with a freshly hashed ID and link the old one via `#superseded-by:<new-id>` (see `references/stale-new-markers.md`).
33
+
30
34
  ## Tag specification
31
35
 
32
36
  | Tag | Meaning |
33
37
  |---|---|
34
38
  | `#added:YYYY-MM-DD` | When the entry was created |
35
39
  | `#verified:YYYY-MM-DD` | Last time a human or audit confirmed the entry is still true |
36
- | `#stale:YYYY-MM-DD` | Flagged by `sync` as superseded or contradicted; user decides keep/archive |
37
- | `#archived:YYYY-MM-DD` | Moved to `archive/` |
40
+ | `#stale:YYYY-MM-DD` | Flagged by `sync` as no longer accurate. Two cases: (a) the entry was superseded pair with `#superseded-by:<new-id>`; (b) deprecated with no successor — alone. |
41
+ | `#superseded-by:LAYER-YYYY-MM-DD-xxxx` | Points to the entry that replaces this one. When present, it implies staleness; the `#stale:<date>` tag is optional but encouraged for clarity. The `xxxx` is the 4-hex content hash of the replacement. |
38
42
 
39
43
  Multiple tags can co-exist on one entry (e.g. `#added:2026-01-15 #verified:2026-06-01`).
40
44
 
@@ -60,6 +64,32 @@ If a fact can't fit in ≤ 2 lines, split into multiple entries and cross-refere
60
64
 
61
65
  Instead of stuffing them into a single overly long bullet.
62
66
 
67
+ ## Superseded-by chain
68
+
69
+ When an entry is replaced by another (e.g. a tech-stack swap, a convention reversal), the old entry carries `#superseded-by:<new-id>` alongside `#stale:<date>`. This turns the replacement relationship from prose into data that scripts can walk.
70
+
71
+ Syntax: `#superseded-by:LAYER-YYYY-MM-DD-xxxx` — the replacement entry's full ID. The replacement entry itself carries no back-reference; its `#verified:DATE` and `#added:DATE` are sufficient.
72
+
73
+ Worked example — bcrypt replaces SHA-256 in `scopes/backend/DECISIONS.md`:
74
+
75
+ ```markdown
76
+ - [DEC-2026-07-10-ee31] SHA-256 + salt for password hashing; reason: no native dep, deterministic. #added:2026-07-10 #stale:2026-07-10 #superseded-by:DEC-2026-07-10-e45d
77
+ - [DEC-2026-07-10-e45d] Use bcrypt (rounds=12) for password hashing; reason: industry standard, built-in salt. #added:2026-07-10
78
+ ```
79
+
80
+ Consumers:
81
+
82
+ - `find_stale.py --json` — groups stale entries by their `replaced_by` target; flags chains where the target ID does not exist (broken chain).
83
+ - `history.py --follow-superseded <id>` — prints the entry plus every successor along the chain (newest first).
84
+ - `compress` — skips entries with `replaced_by` set when selecting the 3–5 entries per (scope, layer).
85
+ - `audit` — when reporting CONFLICT between two entries, surfaces the chain if both belong to one.
86
+
87
+ Constraints:
88
+
89
+ - The tag is **optional**. Old entries without it continue to work; old skills ignore it.
90
+ - **At most one `#superseded-by` tag per entry.** Successive replacements form a chain (A → B → C), never a fork: an entry is replaced by one successor at a time. If an entry carries more than one tag, `list_entries.py` warns and keeps the first.
91
+ - Cross-file references: the ID is sufficient because the LAYER prefix plus hash makes collisions across files vanishingly rare. If two files contain the same ID, prefer the one in the same scope as the entry being read.
92
+
63
93
  ## What counts as "atomic"
64
94
 
65
95
  A fact is atomic if it answers exactly one question:
@@ -67,4 +97,4 @@ A fact is atomic if it answers exactly one question:
67
97
  - "What is the frontend framework?" → `ARCH` entry about Next.js
68
98
  - "Why Next.js not Remix?" → `DEC` entry referencing the `ARCH` entry
69
99
 
70
- If your entry answers two questions, split it.
100
+ If your entry answers two questions, split it.
@@ -11,6 +11,7 @@ lore history <entry-id>
11
11
  lore history <file-path>
12
12
  lore history --scope=<name>
13
13
  lore history --since=<YYYY-MM-DD>
14
+ lore history --follow-superseded
14
15
  lore history --json
15
16
  ```
16
17
 
@@ -19,9 +20,36 @@ lore history --json
19
20
  | Form | Argument shape | Example | Behavior |
20
21
  |---|---|---|---|
21
22
  | Entry | `[A-Z]+-\d{4}-\d{2}-\d{2}-[a-f0-9]{4}` | `lore history DEC-2026-02-03-7c19` | Locate entry in `.lore/`, derive its `#added` date and code file, then `git log` since that date. |
22
- | File | contains `/` or starts with `.` | `lore history frontend/src/store/index.ts` | Run `git log --since=1970-01-01` on the given path. |
23
+ | File | contains `/` or starts with `.` | `lore history frontend/src/store/index.ts` | Run the full `git log` on the given path unless `--since` is provided. |
23
24
  | Scope | `--scope=<name>` only | `lore history --scope=frontend` | For each `*.md` in `.lore/scopes/<name>/`, run file form on the lore file path itself. |
24
25
 
26
+ ### `--follow-superseded`
27
+
28
+ When set on the entry form, prints not just the requested entry's git history, but the history of every entry in its `#superseded-by` chain (newest successor first). Stops when an entry has no `#superseded-by` tag or the chain reaches a non-existent ID. Output prepends a `## Chain` section listing each entry's ID and file path before the per-entry `git log` blocks.
29
+
30
+ Example:
31
+
32
+ ```
33
+ $ lore history --follow-superseded DEC-2026-07-10-ee31
34
+
35
+ # history: [DEC-2026-07-10-ee31] --follow-superseded
36
+
37
+ ## Chain
38
+ 1. [DEC-2026-07-10-ee31] (scopes/backend/DECISIONS.md) — SHA-256 + salt
39
+ → superseded-by → DEC-2026-07-10-e45d
40
+ 2. [DEC-2026-07-10-e45d] (scopes/backend/DECISIONS.md) — bcrypt (rounds=12)
41
+ → no successor
42
+
43
+ # history: [DEC-2026-07-10-ee31]
44
+
45
+ > Entry: scopes/backend/DECISIONS.md
46
+ > Since: 2026-07-10
47
+ > File: backend/app/auth.py
48
+ > Commits: 3 (showing all)
49
+
50
+ ...
51
+ ```
52
+
25
53
  ## Code-file resolution (entry form)
26
54
 
27
55
  Priority:
@@ -59,6 +87,7 @@ appears only when at least one commit is found.
59
87
  "code_file": "frontend/src/store/index.ts",
60
88
  "since": "2026-02-03",
61
89
  "since_source": "entry_added",
90
+ "chain": null,
62
91
  "commits": [
63
92
  {
64
93
  "hash": "...",
@@ -73,6 +102,8 @@ appears only when at least one commit is found.
73
102
  }
74
103
  ```
75
104
 
105
+ When `--follow-superseded` is set, `chain` is an array of `{entry_id, lore_file, code_file, since}` for each successor; otherwise `null`.
106
+
76
107
  ## Error handling
77
108
 
78
109
  | Condition | Exit code | Message |
@@ -103,4 +134,69 @@ appears only when at least one commit is found.
103
134
  commit history. `lore history` fills that gap: given a memory entry,
104
135
  it shows the commits that introduced or modified the underlying code,
105
136
  letting the agent answer "why does this decision exist?" with a pointer
106
- to the original commit instead of an LLM-generated guess.
137
+ to the original commit instead of an LLM-generated guess.
138
+
139
+ ## `--since` normalization (same-day commit safety)
140
+
141
+ `git log --since=YYYY-MM-DD` interpretation is version-dependent.
142
+ Older git versions parse a bare date as the user's local-timezone
143
+ midnight; newer versions parse it as UTC midnight. A commit made early
144
+ in the day can therefore be silently dropped when filtering by a
145
+ same-day `#added` tag.
146
+
147
+ To avoid this, `lore history` normalizes date-only inputs to an explicit
148
+ ISO-8601 timestamp before passing them to `git log`. The transformation
149
+ is `YYYY-MM-DD` → `YYYY-MM-DD T 00:00:00`, applied at the entry-form
150
+ extraction point and at the file-form `--since` argument. Strings that
151
+ already contain a time component (a `T` or a space) are passed through
152
+ unchanged.
153
+
154
+ This means the JSON output's `since` field will show e.g.
155
+ `"2026-07-13T00:00:00"` for a date-only `#added`, not `"2026-07-13"`.
156
+ The same-day commit you want to surface is now guaranteed to be in the
157
+ result, regardless of the host's git version or the user's timezone.
158
+
159
+ File and scope forms without an explicit `--since`, and entry forms
160
+ without an `#added` tag, omit Git's `--since` option so they truly scan
161
+ the full history. Their JSON metadata retains
162
+ `"1970-01-01T00:00:00"` as the backward-compatible default marker;
163
+ that marker is not passed to Git because the exact Unix epoch can be
164
+ treated as a sentinel and incorrectly return no commits.
165
+
166
+ ## Known issues
167
+
168
+ ### Cross-timezone `#added` interpretation (unfixed)
169
+
170
+ The normalization in the previous section only resolves the *git version*
171
+ ambiguity. A second, deeper ambiguity remains: `#added` is a date-only
172
+ string written by the **author** of the entry, but `git log --since=`
173
+ interprets it in the **runner's** local timezone.
174
+
175
+ **Scenario** (uncovered, not yet reproduced in a real project):
176
+
177
+ 1. Author in `+0800` commits at 2026-07-13 02:00 `+0800`
178
+ (= 2026-07-12 18:00 UTC) and adds an entry with
179
+ `#added: 2026-07-13` (their local date).
180
+ 2. Runner in `PST (UTC-8)` invokes `lore history DEC-…`.
181
+ 3. `git log --since=2026-07-13T00:00:00` is interpreted in the
182
+ runner's PST as 2026-07-13 00:00 PST (= 2026-07-13 08:00 UTC).
183
+ 4. The commit at 2026-07-12 18:00 UTC is **before** 2026-07-13 08:00
184
+ UTC, so the commit is **excluded** from history.
185
+ 5. From the runner's perspective, the commit looks "older than the
186
+ entry" even though both occurred on the same calendar day in the
187
+ author's timezone.
188
+
189
+ This is a real bug, but it is **not fixed** in this release because:
190
+
191
+ - No cross-timezone reproduction has been reported.
192
+ - Fixing it would require either (a) subtracting one day from
193
+ `#added` before passing to `git log`, which occasionally over-includes
194
+ the previous day, or (b) capturing the exact UTC time of `#added` in
195
+ the entry format, which is a breaking change to the entry schema.
196
+
197
+ **Workaround today**: pass `--since=<#added - 1 day>` explicitly when
198
+ the runner is in a timezone west of the author's.
199
+
200
+ **Trigger to fix**: any user report of "history missed a commit that I
201
+ know is related" with cross-timezone evidence. Until then, the field
202
+ remains a known issue rather than a known fix.