emdash-emit 0.1.14 → 0.1.15

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 (133) hide show
  1. package/CHANGELOG.md +77 -1
  2. package/README.md +235 -187
  3. package/dist/cli/commands/doctor.d.ts.map +1 -1
  4. package/dist/cli/commands/doctor.js +6 -0
  5. package/dist/cli/commands/doctor.js.map +1 -1
  6. package/dist/cli/commands/pull.d.ts +32 -0
  7. package/dist/cli/commands/pull.d.ts.map +1 -1
  8. package/dist/cli/commands/pull.js +108 -9
  9. package/dist/cli/commands/pull.js.map +1 -1
  10. package/dist/cli/commands/push.d.ts.map +1 -1
  11. package/dist/cli/commands/push.js +18 -0
  12. package/dist/cli/commands/push.js.map +1 -1
  13. package/dist/cli/commands/status.d.ts.map +1 -1
  14. package/dist/cli/commands/status.js +15 -5
  15. package/dist/cli/commands/status.js.map +1 -1
  16. package/dist/client/media.d.ts.map +1 -1
  17. package/dist/client/media.js +16 -5
  18. package/dist/client/media.js.map +1 -1
  19. package/dist/db/cache.d.ts +19 -4
  20. package/dist/db/cache.d.ts.map +1 -1
  21. package/dist/db/cache.js +127 -16
  22. package/dist/db/cache.js.map +1 -1
  23. package/dist/mcp/server.d.ts.map +1 -1
  24. package/dist/mcp/server.js +5 -3
  25. package/dist/mcp/server.js.map +1 -1
  26. package/dist/mcp/tools/sync-doctor.d.ts.map +1 -1
  27. package/dist/mcp/tools/sync-doctor.js +12 -0
  28. package/dist/mcp/tools/sync-doctor.js.map +1 -1
  29. package/dist/mcp/tools/sync-pull.d.ts.map +1 -1
  30. package/dist/mcp/tools/sync-pull.js +49 -12
  31. package/dist/mcp/tools/sync-pull.js.map +1 -1
  32. package/dist/mcp/tools/sync-push.d.ts.map +1 -1
  33. package/dist/mcp/tools/sync-push.js +5 -0
  34. package/dist/mcp/tools/sync-push.js.map +1 -1
  35. package/dist/mcp/tools/sync-status.d.ts.map +1 -1
  36. package/dist/mcp/tools/sync-status.js +6 -2
  37. package/dist/mcp/tools/sync-status.js.map +1 -1
  38. package/dist/sync/delete.d.ts +2 -1
  39. package/dist/sync/delete.d.ts.map +1 -1
  40. package/dist/sync/delete.js +6 -19
  41. package/dist/sync/delete.js.map +1 -1
  42. package/dist/sync/diff.d.ts.map +1 -1
  43. package/dist/sync/diff.js +18 -2
  44. package/dist/sync/diff.js.map +1 -1
  45. package/dist/sync/doctor.d.ts +27 -1
  46. package/dist/sync/doctor.d.ts.map +1 -1
  47. package/dist/sync/doctor.js +309 -26
  48. package/dist/sync/doctor.js.map +1 -1
  49. package/dist/sync/guards.d.ts +45 -0
  50. package/dist/sync/guards.d.ts.map +1 -0
  51. package/dist/sync/guards.js +235 -0
  52. package/dist/sync/guards.js.map +1 -0
  53. package/dist/sync/media/body_scan.d.ts +15 -0
  54. package/dist/sync/media/body_scan.d.ts.map +1 -0
  55. package/dist/sync/media/body_scan.js +132 -0
  56. package/dist/sync/media/body_scan.js.map +1 -0
  57. package/dist/sync/media/companions.d.ts +20 -0
  58. package/dist/sync/media/companions.d.ts.map +1 -0
  59. package/dist/sync/media/companions.js +111 -0
  60. package/dist/sync/media/companions.js.map +1 -0
  61. package/dist/sync/media/index.d.ts +9 -0
  62. package/dist/sync/media/index.d.ts.map +1 -0
  63. package/dist/sync/media/index.js +9 -0
  64. package/dist/sync/media/index.js.map +1 -0
  65. package/dist/sync/media/mirror_item.d.ts +27 -0
  66. package/dist/sync/media/mirror_item.d.ts.map +1 -0
  67. package/dist/sync/media/mirror_item.js +207 -0
  68. package/dist/sync/media/mirror_item.js.map +1 -0
  69. package/dist/sync/media/naming_mirror.d.ts +36 -0
  70. package/dist/sync/media/naming_mirror.d.ts.map +1 -0
  71. package/dist/sync/media/naming_mirror.js +143 -0
  72. package/dist/sync/media/naming_mirror.js.map +1 -0
  73. package/dist/sync/media/pull_article.d.ts +37 -0
  74. package/dist/sync/media/pull_article.d.ts.map +1 -0
  75. package/dist/sync/media/pull_article.js +273 -0
  76. package/dist/sync/media/pull_article.js.map +1 -0
  77. package/dist/sync/media/pull_library.d.ts +12 -0
  78. package/dist/sync/media/pull_library.d.ts.map +1 -0
  79. package/dist/sync/media/pull_library.js +207 -0
  80. package/dist/sync/media/pull_library.js.map +1 -0
  81. package/dist/sync/media/remote_index.d.ts +18 -0
  82. package/dist/sync/media/remote_index.d.ts.map +1 -0
  83. package/dist/sync/media/remote_index.js +94 -0
  84. package/dist/sync/media/remote_index.js.map +1 -0
  85. package/dist/sync/media/types.d.ts +191 -0
  86. package/dist/sync/media/types.d.ts.map +1 -0
  87. package/dist/sync/media/types.js +11 -0
  88. package/dist/sync/media/types.js.map +1 -0
  89. package/dist/sync/media.d.ts +1 -74
  90. package/dist/sync/media.d.ts.map +1 -1
  91. package/dist/sync/media.js +1 -592
  92. package/dist/sync/media.js.map +1 -1
  93. package/dist/sync/orchestrator.d.ts +23 -0
  94. package/dist/sync/orchestrator.d.ts.map +1 -1
  95. package/dist/sync/orchestrator.js +85 -1
  96. package/dist/sync/orchestrator.js.map +1 -1
  97. package/dist/sync/preflight.d.ts +1 -1
  98. package/dist/sync/preflight.d.ts.map +1 -1
  99. package/dist/sync/preflight.js +127 -15
  100. package/dist/sync/preflight.js.map +1 -1
  101. package/dist/sync/pull.d.ts +19 -0
  102. package/dist/sync/pull.d.ts.map +1 -1
  103. package/dist/sync/pull.js +169 -15
  104. package/dist/sync/pull.js.map +1 -1
  105. package/dist/sync/push.d.ts +7 -0
  106. package/dist/sync/push.d.ts.map +1 -1
  107. package/dist/sync/push.js +37 -10
  108. package/dist/sync/push.js.map +1 -1
  109. package/dist/sync/status.d.ts +4 -0
  110. package/dist/sync/status.d.ts.map +1 -1
  111. package/dist/sync/status.js +68 -4
  112. package/dist/sync/status.js.map +1 -1
  113. package/dist/utils/config.d.ts +25 -0
  114. package/dist/utils/config.d.ts.map +1 -1
  115. package/dist/utils/config.js +28 -2
  116. package/dist/utils/config.js.map +1 -1
  117. package/dist/utils/frontmatter.d.ts +1 -1
  118. package/dist/utils/frontmatter.d.ts.map +1 -1
  119. package/dist/utils/frontmatter.js +15 -4
  120. package/dist/utils/frontmatter.js.map +1 -1
  121. package/dist/utils/media-body-refs.d.ts +81 -0
  122. package/dist/utils/media-body-refs.d.ts.map +1 -0
  123. package/dist/utils/media-body-refs.js +240 -0
  124. package/dist/utils/media-body-refs.js.map +1 -0
  125. package/dist/utils/media-refs.d.ts +44 -0
  126. package/dist/utils/media-refs.d.ts.map +1 -0
  127. package/dist/utils/media-refs.js +101 -0
  128. package/dist/utils/media-refs.js.map +1 -0
  129. package/dist/utils/naming.d.ts +31 -0
  130. package/dist/utils/naming.d.ts.map +1 -1
  131. package/dist/utils/naming.js +58 -0
  132. package/dist/utils/naming.js.map +1 -1
  133. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -7,9 +7,73 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [Unreleased]
11
+
12
+ ## [0.1.15] - 2026-11-12
13
+
14
+ ### Added
15
+
16
+ - **Article-Relative Local Media References (`localMediaRefs`, default-ON)**:
17
+ - Workspace setting `.emit/config.json → localMediaRefs` (default `true`). Mirrored body images are written to disk as article-relative `../../media/<storageKey>` references so Markdown preview and local tooling resolve them offline from the article's own directory, instead of the canonical `/_emdash/api/media/file/<storageKey>` URL.
18
+ - Round-trip symmetry is preserved: the **baseline snapshot and hash stay canonical**, so the 3-way merge, `sync_state.local_hash`, and the remote payload all keep the canonical URL, while only the file on disk carries the local spelling. Push localizes _after_ preflight canonicalization, so an article that spells images locally still pushes the exact canonical body.
19
+ - CLI flags `emit pull --local-media-refs` / `emit push --local-media-refs` enable the setting and persist it; `--no-local-media-refs` clears a remembered preference. MCP `sync_pull` / `sync_push` accept a `localMediaRefs` argument that overrides the stored setting, matching CLI precedence (explicit argument > remembered config).
20
+ - `emit diff` renders the baseline in whichever spelling the disk already carries, so an untouched local-ref article never reports a phantom media rewrite and only a real edit shows up.
21
+
22
+ - **Canonical Sliding Window Size (`postsSyncLimit: 30`)**:
23
+ - The default sliding window limit is canonicalized to `30` (fetching up to 30 `published` + up to 30 `draft` articles in parallel, for a maximum working set of 60 files on the staging desk), bounding local filesystem footprint and aligning with Cloudflare Workers / D1 execution limits. Single-article fetch (`emit pull <id>`) remains fully available for out-of-window posts.
24
+
25
+ - **Fast-Path Frontmatter Self-Healing (H1 / P1)**:
26
+ - `tryFastPathOrHeal` in `src/sync/pull.ts` now validates `parseMarkdown` even when the disk hash matches the cached baseline. Legacy files whose unquoted percent-encoded taxonomy slugs never parse were previously skipped forever, so `emit status` reported them invalid while `emit pull` did nothing.
27
+ - Such files are repaired locally by `lenientRepairMarkdownFrontmatter` and re-tracked against their own repaired hash; the `base_snapshot` is left untouched, values are never altered, and no per-article or taxonomy fetch is issued — batch pulls of large legacy corpora stay fast (verified: only list/bylines/openapi requests for healed articles).
28
+ - Only when local lenient repair fails does the pull fall through to the full remote re-serialization path (which preserves unpushed bodies and quarantines divergence as before).
29
+ - **Lenient Repair Indentation Correctness**: `lenientRepairMarkdownFrontmatter` re-indents sequence items relative to their own parent taxonomy key (`sequenceParentIndent`) instead of hardcoding 4 spaces, so legacy 2-space sequences under a 2-space `categories:` key — the actual production corruption shape — are repaired correctly. Repaired candidates are additionally gated on gray-matter producing a plain object before schema validation.
30
+ - **Status & Doctor Diagnostic Alignment (H2 / P2)**:
31
+ - `getWorkspaceStatus` adds `healable` per invalid item and `summary.healableInvalid`, classifying exactly the set `emit doctor` can recover. `emit status` now shows `🟠 healable` vs `🔴 invalid` and reports `N invalid (M healable)`.
32
+ - `emit doctor` reports leniently repairable frontmatter as a `warning` with an explicit `emit pull` instruction instead of silently passing, so `status`, `doctor`, and `pull` never disagree.
33
+ - **Accurate Repair Hints (H3 / P3)**: CLI `emit status` and MCP `sync_status` distinguish healable from unrecoverable invalid files (`healableInvalid` / `unrecoverableInvalid` in the MCP payload) and describe the actual repair mechanism (local syntax-only re-quoting, no article fetch for cached unchanged revisions).
34
+
35
+ - **Cache Schema v5 Migration (`media_state_v5`)**:
36
+ - Upgraded SQLite cache schema to version 5. Replaced `local_hash` PRIMARY KEY in `media_state` with `storage_key` PRIMARY KEY to model the immutable public address space accurately.
37
+ - Separated `local_content_hash` (local SHA-256 binary digest) and `remote_record_ref` (`media-record:<id>`), resolving the historical collision asymmetry. Added index `idx_media_state_content_hash`.
38
+ - Added dedicated methods `getMediaStateByContentHash` and `deleteMediaStateByContentHash` for upload recovery. Preserved the upload confirmation invariant where old temporary storageKey entries are cleanly deleted before committing confirmed double-ULID keys.
39
+
40
+ ### Added
41
+
42
+ - **Two Media ID Spaces Specification (`docs/media-id-spaces.md`)**: a standalone reference documenting EmDash's two unrelated media ULIDs — the D1 `media` record `id` (used by `GET /_emdash/api/media/{id}` and every write endpoint) and the R2 object `storageKey` (served by `/_emdash/api/media/file/{storageKey}`) — with an identifier/endpoint matrix, a live production example proving they never match for the same asset, why a 401/404 from either route proves only a failed lookup, how migrated corpora enter a workspace (newest-first `mediaSyncLimit` window plus empty migrated featured images), and the `verifiedStorageKeys` → `orphanStorageKeys` resolution order.
43
+ - **Discovery Points for Humans and Agents**: the specification is reachable from every surface that touches media — README (§ Directory Structure & Data Specifications, Key Features, frontmatter schema note, `emit pull-media`, and a new FAQ entry for images that do not render in a local preview), AGENTS.md (§1 Reference Documents plus the workspace tree and Guard section), MCP `instructions` (the two-ULID rule and a media-window hint on `[MEDIA]`), `emit guide`, and the `emit doctor` "Media References" warning, which now states that a missing local mirror is not a broken reference. Tests assert the pointer survives in MCP instructions, `emit guide` output, and the doctor message.
44
+
45
+ ### Changed
46
+
47
+ - **Media Mirror Coverage — Local `url:` Recovery and a Complete Library Walk**: the audit that proved all 4,008 records exist also showed why Emit could not see them: only 6 of the 33 migrated companions carried a `storageKey:` field, and `pullMedia` stopped at `mediaSyncLimit` (300), so 3,708 assets were unreachable and 27 healthy companions produced false warnings.
48
+ - New exported `storageKeyFromMediaMeta(doc)` reads a companion's storageKey from `storageKey:` when present and otherwise recovers it from the canonical `url:` (`/_emdash/api/media/file/<storageKey>`, absolute or root-relative). A `url` that is not a file route names no key, and an explicit field always wins. `collectLocalMediaMirrorCandidates` and the `emit doctor` media index both use it, so a migrated companion verifies its own body reference instead of warning forever. `legacy_*` residue stays excluded.
49
+ - `pullMedia` reports coverage: `PullMediaResult.remoteTotalCount` carries the server's `totalCount`, and CLI/MCP append `remote library holds N record(s), of which this run walked M`. Under `--all` the walk is exhaustive, so it doubles as a storageKey census — `referencedUnmirroredStorageKeys` lists body keys that neither a local companion nor any remote record names, surfaced as `[UNREFERENCED_REMOTE_MEDIA]` and worded as a lookup result rather than a missing record. Local mirrors seed the active-key set, so `--prune` never deletes an asset this run's window simply did not reach.
50
+ - New exported `collectRemoteStorageKeys()` walks the library cursor-by-cursor (100 per page) into a `RemoteStorageKeyIndex`, and `findUnmirroredStorageKeys()` reports against it. A walk that stopped early is marked `complete: false` and yields nothing, because a partial walk proves nothing about the rest of the library.
51
+ - **Live-server correction (verified against aroooy.net)**: the deployed EmDash list endpoint returns only `{ items, nextCursor }` — no `totalCount` at all — so `RemoteStorageKeyIndex` adds `scannedCount`, and a _complete_ walk becomes the library figure that coverage lines report (`remoteTotalCount` falls back to it; an incomplete walk reports nothing rather than a partial page count). A complete `emit pull-media --all` run states its own library size instead of printing the previous `remote library holds 0 record(s)`, and `emit doctor --check-media-library` reads "The remote library walk reached N media record(s)" when the server supplies no total.
52
+ - **Article-targeted media reporting no longer calls a storageKey a failure**: on aroooy.net `emit pull-media --article <id>` printed `4 failed` for an article whose four images were all mirrored correctly, because the ULIDs collected from body URLs are R2 storageKeys and the canonical id lookup cannot match them by design.
53
+ - `pullArticleMedia` now counts those in a new `PullMediaResult.unresolvedIds`, and `failed` counts only references that failed the id lookup **and** every storageKey check (i.e. genuine `orphanStorageKeys`).
54
+ - New exported `formatArticleMediaSummary` renders CLI output as `(0 downloaded, 0 skipped/up-to-date, 4 verified/mirrored). (4 body ULID(s) are R2 storageKeys, not media record ids, so no id lookup was expected to match)`; MCP `sync_pull_media` gained the same `verified/mirrored` count and explanation. The reassuring note is suppressed whenever something genuinely is unresolved.
55
+ - README (`emit pull-media`), `docs/media-id-spaces.md`, and `PullMediaResult` docstrings now state that a migrated body's ULIDs are expected to resolve to nothing.
56
+ - `emit doctor --check-media-library` (MCP `sync_doctor checkMediaLibrary: true`) states the remote record count against the number of assets this workspace mirrors, so a missing-file warning reads as coverage. Plain `emit doctor` stays offline.
57
+ - Guidance now routes through Emit first: `[UNRESOLVED_MEDIA_ASSET]`, the doctor suggestion, README, MCP instructions, `emit guide`, and `docs/media-id-spaces.md` all route to `emit doctor --check-media-library` (metadata-only walk) first and reserve `emit pull-media --all` for deliberate full offline backups, with server-side verification only for what a complete walk could not match. Maintenance-only guardrails are enforced across CLI help, MCP schema descriptions, and `emit guide` outputs.
58
+ - **Media Resolution Stays Epistemic (production investigation follow-up)**: a live D1 audit of the migrated corpus proved that all 4,008 media records exist and every WordPress `storage_key` is registered, so the earlier "the D1 record is missing" framing was wrong — the 401/404 symptoms come from addressing `GET /_emdash/api/media/{id}` (D1 primary key) with an R2 `storageKey`, and from calling the authenticated admin API without a bearer token. Emit must therefore never conclude that a record is absent, and its advice must never invite a duplicate registration:
59
+ - CLI warning renamed to `⚠️ [UNRESOLVED_MEDIA_ASSET]`, and both its wording and `emit doctor --check-media-urls`'s suggestion now state that a 404 proves only a failed lookup and instruct verification by storageKey (`wrangler d1 SELECT` on the media table, or the admin UI) before registering anything. The old "Register the missing media records" instruction is gone.
60
+ - New exported `collectLocalMediaMirrorCandidates(mediaDir)` indexes every `media/*.yaml` companion by both its record id and its recorded `storageKey`, skipping `legacy_*` residue so fabricated metadata can never serve as verification evidence.
61
+ - `PullMediaResult` adds `verifiedStorageKeys`: a body key is confirmed when Emit resolved a record under that ULID (even if the record reports a different storageKey) or when a local mirror companion names it, so such a key is never escalated as missing. Only keys with no evidence at all stay in `orphanStorageKeys`.
62
+
63
+ ### Removed
64
+
65
+ - **Legacy Synthesized Media Fallback (`media/legacy_*` companions)**:
66
+ - Deleted `syncSynthesizedMediaItem` and every direct file-route fallback branch in `pullArticleMedia`: Emit no longer downloads a `/_emdash/api/media/file/<storageKey>` asset that no media record resolves to, never invents a media record for it, and writes nothing into `media/`. Registering records belongs to EmDash (admin UI) or Wrangler — Emit mirrors records and does not repair server data. This also eliminates the self-destruct bug where a synthesized pair was unlinked during cleanup because its `legacy_` base name never matched the referenced id.
67
+ - `pullArticleMedia` now collects every body storageKey via the new exported `collectArticleStorageKeys` (markdown images, HTML `img` tags, opaque fences, and legacy container lines) and reports the keys no media record could mirror as `orphanStorageKeys`, which CLI `emit pull-media --article` prints as a warning and MCP `sync_pull_media` returns both as an unresolved-keys list and the same warning. See **Changed** above for how that warning is worded now.
68
+ - `emit doctor` no longer recognizes `legacy_*` stems in its known-storageKey set, so leftover residue is reported as a missing reference instead of silently satisfying it; with `--check-media-urls` a key the origin still serves is downgraded to an informational "served by the file route but matched by no local mirror or record lookup" note, and the check's suggestion always points at storageKey verification through the EmDash admin UI or wrangler rather than at `pull-media`.
69
+ - `emit pull-media --prune` explicitly skips `legacy_*` files (they match no media id) so existing residue is never deleted implicitly; it must be removed by hand after the remote record is registered. Media metadata lookup also refuses to read a `legacy_*.yaml` as a real record's metadata.
70
+
71
+ ---
72
+
10
73
  ## [0.1.14] - 2026-11-10
11
74
 
12
75
  ### Added
76
+
13
77
  - **Taxonomy Strategy Enforcement (R1 / P1)**:
14
78
  - Made `mergeTaxonomies` in `src/sync/merge.ts` the single authoritative decision point for 3-way merge resolution, strictly honoring `--resolve-taxonomy local|remote` (CLI and MCP).
15
79
  - Extended strategy adoption to Case 2 (remote deleted vs local modified) and Case 4 (both modified), recording `strategyAdoptions` in merge warnings and returning discarded terms.
@@ -41,6 +105,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
41
105
  ## [0.1.13] - 2026-11-09
42
106
 
43
107
  ### Added
108
+
44
109
  - **Pre-PUT Taxonomy Divergence Audit (R1 / EMIT-001)**:
45
110
  - Audits remote taxonomy assignments (`GET /_emdash/api/content/:collection/:id/terms/{category,tag}`) prior to PUT when article defines taxonomies.
46
111
  - Detects out-of-band remote taxonomy modifications independent of entry `_rev` and routes directly into 3-way merge / guards.
@@ -62,6 +127,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
62
127
  - Added `profile` and `workspaceRoot` to MCP heartbeat recording state.
63
128
 
64
129
  ### Fixed
130
+
65
131
  - **Cache Schema Mismatch Presentation (R4 / EMIT-006)**:
66
132
  - Wrapped `CacheDatabase` initialization in CLI context resolver so `[CACHE_SCHEMA_MISMATCH]` exits with exit code 1 and a clean message instead of printing raw Node stack traces.
67
133
  - **Deterministic Filename Resolution (R7 / EMIT-005)**:
@@ -70,6 +136,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
70
136
  - Treated missing or malformed term lists as `unknown` (`null`) instead of `none` (`[]`), avoiding false divergence detection on unsupported or mock endpoints.
71
137
 
72
138
  ### Notes & Migration
139
+
73
140
  - **Schema v2 Self-Healing for Existing Workspaces**:
74
141
  - Articles synchronized prior to v0.1.12 will have `remote_taxonomy_hash` set to `NULL` in the SQLite cache upon upgrading to schema v2. The remote taxonomy hash is automatically calculated and saved on the article's first subsequent `emit push` or `emit pull`, after which out-of-band taxonomy divergence auditing is fully active.
75
142
 
@@ -78,6 +145,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
78
145
  ## [0.1.12] - 2026-11-09
79
146
 
80
147
  ### Added
148
+
81
149
  - **Phase 0: SQLite Schema Migration & WAL Safety**:
82
150
  - Implemented schema versioning via `PRAGMA user_version` (`CURRENT_CACHE_SCHEMA_VERSION = 1`).
83
151
  - Added safe migration with `PRAGMA wal_checkpoint(TRUNCATE)` before backup and 3-generation backup retention (`.bak-0.1.11-<timestamp>`).
@@ -96,6 +164,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
96
164
  - Maintained complete backward compatibility in `emit status` by omitting the `Flags:` line when all flags are 0.
97
165
 
98
166
  ### Fixed
167
+
99
168
  - **Phase 1: Revision Normalization & Heal Symmetry**:
100
169
  - Fixed false-positive unpushed edits quarantine caused by Base64 token vs numeric revision formats (`normalizeRev()`).
101
170
  - Kept raw opaque tokens strictly unchanged for server `_rev` PUT transmission and storage.
@@ -112,6 +181,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
112
181
  ## [0.1.11] - 2026-11-09
113
182
 
114
183
  ### Fixed
184
+
115
185
  - **Doctor Media References False-Positive (D1)**:
116
186
  - Resolved `/_emdash/api/media/file/<storageKey>` references against indexed `media/*.yaml` companion documents and local media files.
117
187
  - Fixes false-positive warnings where valid remote canonical image references were falsely reported as missing from disk.
@@ -135,6 +205,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
135
205
  Version 0.1.10 represents a comprehensive modernization of Emit into an AI-first, zero-data-loss content authoring environment. It eliminates phantom diffs, hardens deletion and conflict safety, enhances workspace diagnostics, and introduces targeted media syncing.
136
206
 
137
207
  ### Added
208
+
138
209
  - **Phase 1a & 1b: Non-Destructive Frontmatter & Inline Taxonomy Comments**
139
210
  - **Non-destructive frontmatter healing**: Unquoted colons, invalid YAML indentations, and encoding quirks are healed non-destructively while strictly preserving local body edits.
140
211
  - **Inline Taxonomy Comments (`# <Name>`)**: Frontmatter arrays for categories and tags now support trailing inline comments (e.g. `- tech # 技術`, `- 01M6... # Cloudflare`) so human and AI authors see names alongside IDs.
@@ -170,12 +241,14 @@ Version 0.1.10 represents a comprehensive modernization of Emit into an AI-first
170
241
  - **Complete Documentation Harmonization**: Full alignment across `README.md`, `EMIT_MCP_INSTRUCTIONS`, and CLI command help.
171
242
 
172
243
  ### Changed
244
+
173
245
  - **README Authoring Desk & Division of Labor clarification**: Non-Goals are now stated as deliberate design decisions rather than missing capabilities — `postsSyncLimit` can be raised to effectively sync the full article set, the sliding window bounds what stays on disk (not what is reachable via `emit pull <id>`), and a new "Division of Labor with the Official EmDash MCP" section documents that bulk media library management and site-wide operations are intentionally delegated to the official EmDash MCP / Admin Dashboard. A matching FAQ entry ("comprehensive piece referencing my entire back-catalog") was added, and the `--all` note now honestly reflects that `emit pull-media --all` exists while articles deliberately omit it.
174
246
  - Standardized CLI failure outputs across `emit new`, `emit delete`, and `emit conflicts` to print clean single-line actionable errors with suggestions and exit code 1, rather than dumping raw runtime stack traces.
175
247
  - Hermetic test isolation: tests stub `fetch` and isolate temporary `HOME` configurations to prevent ambient leakage from developer profiles.
176
248
 
177
249
  ### Known Limitations
178
- - **Orphaned media records are not reported by `emit doctor`**: If a draft media upload succeeded but the subsequent article push failed, Emit keeps the record in `media_state` with `status = 'failed'` for compensation on the next run. A record that never gets retried remains visible only in the local cache and may linger as a `pending` entry in the remote media library; `emit doctor` currently checks referenced-media *disk existence* but does not yet report these failed/pending `media_state` entries (planned for a future release). Run `emit pull-media` to re-verify library state in the meantime.
250
+
251
+ - **Orphaned media records are not reported by `emit doctor`**: If a draft media upload succeeded but the subsequent article push failed, Emit keeps the record in `media_state` with `status = 'failed'` for compensation on the next run. A record that never gets retried remains visible only in the local cache and may linger as a `pending` entry in the remote media library; `emit doctor` currently checks referenced-media _disk existence_ but does not yet report these failed/pending `media_state` entries (planned for a future release). Run `emit pull-media` to re-verify library state in the meantime.
179
252
  - **Article-targeted media pull (`--article`) scans remote Portable Text content with a ULID regex fallback** when no local Markdown body is available, which may collect ULIDs appearing outside body image references; precise AST-based extraction is planned for a future release.
180
253
 
181
254
  ---
@@ -183,12 +256,14 @@ Version 0.1.10 represents a comprehensive modernization of Emit into an AI-first
183
256
  ## [0.1.9] - 2026-11-04
184
257
 
185
258
  ### Added
259
+
186
260
  - **Sliding Window Staging Desk Model**: Bounded synchronizations defaulting to latest 100 articles (`postsSyncLimit`) per status (`all`, `draft`, `published`).
187
261
  - **Offline Draft Media Upload Pipeline**: Standalone image references in `media/drafts/*` are automatically uploaded to EmDash R2 storage and rewritten to storageKey-based URLs during `emit push`.
188
262
  - **Preflight Guards G1–G12**: Enforces strict invariants before writing to remote CMS (ID mismatch check, unintended publish guard, merge conflict marker detection, title autotrim, opaque block verification, etc.).
189
263
  - **Distributed Mutex Lock**: Distributed 7-minute lease protocol via `POST /_emdash/api/content/{collection}/{id}/lock` to prevent multi-agent write collisions.
190
264
 
191
265
  ### Changed
266
+
192
267
  - Refactored `emit pull` to automatically sync taxonomies (`categories/` and `tags/`) by default alongside articles.
193
268
 
194
269
  ---
@@ -196,6 +271,7 @@ Version 0.1.10 represents a comprehensive modernization of Emit into an AI-first
196
271
  ## [0.1.8] - 2026-10-31
197
272
 
198
273
  ### Added
274
+
199
275
  - Initial release of `emdash-emit`.
200
276
  - Local-first synchronization engine for Cloudflare Workers + D1 powered EmDash CMS.
201
277
  - Multi-site profile management (`emit profile add`, `list`, `use`, `remove`).