emdash-emit 0.1.14 → 0.1.16
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.
- package/CHANGELOG.md +96 -1
- package/README.md +259 -188
- package/dist/bin/emit.d.ts.map +1 -1
- package/dist/bin/emit.js +2 -0
- package/dist/bin/emit.js.map +1 -1
- package/dist/cli/commands/doctor.d.ts.map +1 -1
- package/dist/cli/commands/doctor.js +6 -0
- package/dist/cli/commands/doctor.js.map +1 -1
- package/dist/cli/commands/media.d.ts +4 -0
- package/dist/cli/commands/media.d.ts.map +1 -0
- package/dist/cli/commands/media.js +128 -0
- package/dist/cli/commands/media.js.map +1 -0
- package/dist/cli/commands/pull.d.ts +32 -0
- package/dist/cli/commands/pull.d.ts.map +1 -1
- package/dist/cli/commands/pull.js +136 -9
- package/dist/cli/commands/pull.js.map +1 -1
- package/dist/cli/commands/push.d.ts.map +1 -1
- package/dist/cli/commands/push.js +18 -0
- package/dist/cli/commands/push.js.map +1 -1
- package/dist/cli/commands/status.d.ts.map +1 -1
- package/dist/cli/commands/status.js +16 -5
- package/dist/cli/commands/status.js.map +1 -1
- package/dist/client/media.d.ts.map +1 -1
- package/dist/client/media.js +16 -5
- package/dist/client/media.js.map +1 -1
- package/dist/db/cache.d.ts +19 -4
- package/dist/db/cache.d.ts.map +1 -1
- package/dist/db/cache.js +127 -16
- package/dist/db/cache.js.map +1 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +7 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tools/sync-doctor.d.ts.map +1 -1
- package/dist/mcp/tools/sync-doctor.js +12 -0
- package/dist/mcp/tools/sync-doctor.js.map +1 -1
- package/dist/mcp/tools/sync-media-search.d.ts +4 -0
- package/dist/mcp/tools/sync-media-search.d.ts.map +1 -0
- package/dist/mcp/tools/sync-media-search.js +102 -0
- package/dist/mcp/tools/sync-media-search.js.map +1 -0
- package/dist/mcp/tools/sync-pull.d.ts.map +1 -1
- package/dist/mcp/tools/sync-pull.js +85 -13
- package/dist/mcp/tools/sync-pull.js.map +1 -1
- package/dist/mcp/tools/sync-push.d.ts.map +1 -1
- package/dist/mcp/tools/sync-push.js +5 -0
- package/dist/mcp/tools/sync-push.js.map +1 -1
- package/dist/mcp/tools/sync-status.d.ts.map +1 -1
- package/dist/mcp/tools/sync-status.js +6 -2
- package/dist/mcp/tools/sync-status.js.map +1 -1
- package/dist/sync/delete.d.ts +2 -1
- package/dist/sync/delete.d.ts.map +1 -1
- package/dist/sync/delete.js +6 -19
- package/dist/sync/delete.js.map +1 -1
- package/dist/sync/diff.d.ts.map +1 -1
- package/dist/sync/diff.js +18 -2
- package/dist/sync/diff.js.map +1 -1
- package/dist/sync/doctor.d.ts +27 -1
- package/dist/sync/doctor.d.ts.map +1 -1
- package/dist/sync/doctor.js +309 -26
- package/dist/sync/doctor.js.map +1 -1
- package/dist/sync/guards.d.ts +45 -0
- package/dist/sync/guards.d.ts.map +1 -0
- package/dist/sync/guards.js +236 -0
- package/dist/sync/guards.js.map +1 -0
- package/dist/sync/media/body_scan.d.ts +15 -0
- package/dist/sync/media/body_scan.d.ts.map +1 -0
- package/dist/sync/media/body_scan.js +132 -0
- package/dist/sync/media/body_scan.js.map +1 -0
- package/dist/sync/media/companions.d.ts +32 -0
- package/dist/sync/media/companions.d.ts.map +1 -0
- package/dist/sync/media/companions.js +260 -0
- package/dist/sync/media/companions.js.map +1 -0
- package/dist/sync/media/index.d.ts +9 -0
- package/dist/sync/media/index.d.ts.map +1 -0
- package/dist/sync/media/index.js +9 -0
- package/dist/sync/media/index.js.map +1 -0
- package/dist/sync/media/mirror_item.d.ts +27 -0
- package/dist/sync/media/mirror_item.d.ts.map +1 -0
- package/dist/sync/media/mirror_item.js +207 -0
- package/dist/sync/media/mirror_item.js.map +1 -0
- package/dist/sync/media/naming_mirror.d.ts +36 -0
- package/dist/sync/media/naming_mirror.d.ts.map +1 -0
- package/dist/sync/media/naming_mirror.js +143 -0
- package/dist/sync/media/naming_mirror.js.map +1 -0
- package/dist/sync/media/pull_article.d.ts +37 -0
- package/dist/sync/media/pull_article.d.ts.map +1 -0
- package/dist/sync/media/pull_article.js +273 -0
- package/dist/sync/media/pull_article.js.map +1 -0
- package/dist/sync/media/pull_library.d.ts +12 -0
- package/dist/sync/media/pull_library.d.ts.map +1 -0
- package/dist/sync/media/pull_library.js +255 -0
- package/dist/sync/media/pull_library.js.map +1 -0
- package/dist/sync/media/remote_index.d.ts +49 -0
- package/dist/sync/media/remote_index.d.ts.map +1 -0
- package/dist/sync/media/remote_index.js +241 -0
- package/dist/sync/media/remote_index.js.map +1 -0
- package/dist/sync/media/types.d.ts +237 -0
- package/dist/sync/media/types.d.ts.map +1 -0
- package/dist/sync/media/types.js +11 -0
- package/dist/sync/media/types.js.map +1 -0
- package/dist/sync/media.d.ts +1 -74
- package/dist/sync/media.d.ts.map +1 -1
- package/dist/sync/media.js +1 -592
- package/dist/sync/media.js.map +1 -1
- package/dist/sync/orchestrator.d.ts +23 -0
- package/dist/sync/orchestrator.d.ts.map +1 -1
- package/dist/sync/orchestrator.js +85 -1
- package/dist/sync/orchestrator.js.map +1 -1
- package/dist/sync/preflight.d.ts +1 -1
- package/dist/sync/preflight.d.ts.map +1 -1
- package/dist/sync/preflight.js +129 -17
- package/dist/sync/preflight.js.map +1 -1
- package/dist/sync/pull.d.ts +19 -0
- package/dist/sync/pull.d.ts.map +1 -1
- package/dist/sync/pull.js +169 -15
- package/dist/sync/pull.js.map +1 -1
- package/dist/sync/push.d.ts +7 -0
- package/dist/sync/push.d.ts.map +1 -1
- package/dist/sync/push.js +37 -10
- package/dist/sync/push.js.map +1 -1
- package/dist/sync/status.d.ts +4 -0
- package/dist/sync/status.d.ts.map +1 -1
- package/dist/sync/status.js +68 -4
- package/dist/sync/status.js.map +1 -1
- package/dist/utils/config.d.ts +25 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +28 -2
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/frontmatter.d.ts +1 -1
- package/dist/utils/frontmatter.d.ts.map +1 -1
- package/dist/utils/frontmatter.js +15 -4
- package/dist/utils/frontmatter.js.map +1 -1
- package/dist/utils/media-body-refs.d.ts +81 -0
- package/dist/utils/media-body-refs.d.ts.map +1 -0
- package/dist/utils/media-body-refs.js +240 -0
- package/dist/utils/media-body-refs.js.map +1 -0
- package/dist/utils/media-refs.d.ts +44 -0
- package/dist/utils/media-refs.d.ts.map +1 -0
- package/dist/utils/media-refs.js +101 -0
- package/dist/utils/media-refs.js.map +1 -0
- package/dist/utils/naming.d.ts +31 -0
- package/dist/utils/naming.d.ts.map +1 -1
- package/dist/utils/naming.js +58 -0
- package/dist/utils/naming.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,9 +7,92 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.1.16] - 2026-11-12
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **Media Search Index (`emit media-search` / `sync_media_search`)**:
|
|
15
|
+
- Search pre-uploaded remote EmDash media assets across filename, alt text, caption, storageKey, and local mirror stems without downloading bulk binary files (conserving bandwidth, disk space, and LLM context).
|
|
16
|
+
- Client-side pagination walk: Does not rely on the remote server's `q` parameter (which only filters filenames), enabling multi-lingual search over `alt` and `caption` metadata.
|
|
17
|
+
- Epistemic completeness reporting: Reports `scannedCount` and `walkComplete: boolean`. When stopped early at `--max-scan`, emits `[WALK_INCOMPLETE]` and refuses to assert that an asset is absent, preventing AI hallucinations and rogue duplicate INSERTs.
|
|
18
|
+
- Local mirror decoration: Resolves `mirrored: boolean`, `localPath`, and companion metadata against schema v5 `media_state` and companion YAMLs (`media/*.yaml`) without schema migrations.
|
|
19
|
+
- Keyword hints (`--suggest-keywords`): Suggests top 10 local tag slugs (sorted by frequency count) to help discover assets in migrated corpora with opaque ULID filenames.
|
|
20
|
+
|
|
21
|
+
- **Targeted StorageKey Mirroring (`--storage-key`)**:
|
|
22
|
+
- `emit pull-media --storage-key <key>` and MCP `sync_pull_media({ storageKey })`: Allows AI agents and users to mirror an individual asset discovered via search without pulling the full library or an entire article.
|
|
23
|
+
- Directly reuses walked remote records via `syncSingleMediaItem()` without issuing duplicate `GET /_emdash/api/media/{id}` requests.
|
|
24
|
+
- Enforces mutual exclusivity with `--article` and `--all` at the CLI level.
|
|
25
|
+
|
|
26
|
+
### Changed
|
|
27
|
+
|
|
28
|
+
- **Guard G5 / G11 Suggestion Guidance**:
|
|
29
|
+
- `LOCAL_IMAGE_DETECTED` and `MISSING_LOCAL_IMAGE_FILE` suggestions now recommend looking up mirrored assets via `emit media-search` / `sync_media_search` and replacing local paths with canonical `/_emdash/api/media/file/<storageKey>` URLs, emphasizing that metadata matching resolves only confirmed local mirrors.
|
|
30
|
+
|
|
31
|
+
## [0.1.15] - 2026-11-12
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **Article-Relative Local Media References (`localMediaRefs`, default-ON)**:
|
|
36
|
+
- 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.
|
|
37
|
+
- 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.
|
|
38
|
+
- 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).
|
|
39
|
+
- `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.
|
|
40
|
+
|
|
41
|
+
- **Canonical Sliding Window Size (`postsSyncLimit: 30`)**:
|
|
42
|
+
- 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.
|
|
43
|
+
|
|
44
|
+
- **Fast-Path Frontmatter Self-Healing (H1 / P1)**:
|
|
45
|
+
- `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.
|
|
46
|
+
- 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).
|
|
47
|
+
- 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).
|
|
48
|
+
- **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.
|
|
49
|
+
- **Status & Doctor Diagnostic Alignment (H2 / P2)**:
|
|
50
|
+
- `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)`.
|
|
51
|
+
- `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.
|
|
52
|
+
- **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).
|
|
53
|
+
|
|
54
|
+
- **Cache Schema v5 Migration (`media_state_v5`)**:
|
|
55
|
+
- 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.
|
|
56
|
+
- 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`.
|
|
57
|
+
- 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.
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
|
|
61
|
+
- **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.
|
|
62
|
+
- **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.
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
|
|
66
|
+
- **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.
|
|
67
|
+
- 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.
|
|
68
|
+
- `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.
|
|
69
|
+
- 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.
|
|
70
|
+
- **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.
|
|
71
|
+
- **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.
|
|
72
|
+
- `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`).
|
|
73
|
+
- 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.
|
|
74
|
+
- 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.
|
|
75
|
+
- `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.
|
|
76
|
+
- 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.
|
|
77
|
+
- **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:
|
|
78
|
+
- 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.
|
|
79
|
+
- 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.
|
|
80
|
+
- `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`.
|
|
81
|
+
|
|
82
|
+
### Removed
|
|
83
|
+
|
|
84
|
+
- **Legacy Synthesized Media Fallback (`media/legacy_*` companions)**:
|
|
85
|
+
- 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.
|
|
86
|
+
- `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.
|
|
87
|
+
- `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`.
|
|
88
|
+
- `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.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
10
92
|
## [0.1.14] - 2026-11-10
|
|
11
93
|
|
|
12
94
|
### Added
|
|
95
|
+
|
|
13
96
|
- **Taxonomy Strategy Enforcement (R1 / P1)**:
|
|
14
97
|
- 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
98
|
- 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 +124,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
41
124
|
## [0.1.13] - 2026-11-09
|
|
42
125
|
|
|
43
126
|
### Added
|
|
127
|
+
|
|
44
128
|
- **Pre-PUT Taxonomy Divergence Audit (R1 / EMIT-001)**:
|
|
45
129
|
- Audits remote taxonomy assignments (`GET /_emdash/api/content/:collection/:id/terms/{category,tag}`) prior to PUT when article defines taxonomies.
|
|
46
130
|
- Detects out-of-band remote taxonomy modifications independent of entry `_rev` and routes directly into 3-way merge / guards.
|
|
@@ -62,6 +146,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
62
146
|
- Added `profile` and `workspaceRoot` to MCP heartbeat recording state.
|
|
63
147
|
|
|
64
148
|
### Fixed
|
|
149
|
+
|
|
65
150
|
- **Cache Schema Mismatch Presentation (R4 / EMIT-006)**:
|
|
66
151
|
- 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
152
|
- **Deterministic Filename Resolution (R7 / EMIT-005)**:
|
|
@@ -70,6 +155,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
70
155
|
- Treated missing or malformed term lists as `unknown` (`null`) instead of `none` (`[]`), avoiding false divergence detection on unsupported or mock endpoints.
|
|
71
156
|
|
|
72
157
|
### Notes & Migration
|
|
158
|
+
|
|
73
159
|
- **Schema v2 Self-Healing for Existing Workspaces**:
|
|
74
160
|
- 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
161
|
|
|
@@ -78,6 +164,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
78
164
|
## [0.1.12] - 2026-11-09
|
|
79
165
|
|
|
80
166
|
### Added
|
|
167
|
+
|
|
81
168
|
- **Phase 0: SQLite Schema Migration & WAL Safety**:
|
|
82
169
|
- Implemented schema versioning via `PRAGMA user_version` (`CURRENT_CACHE_SCHEMA_VERSION = 1`).
|
|
83
170
|
- Added safe migration with `PRAGMA wal_checkpoint(TRUNCATE)` before backup and 3-generation backup retention (`.bak-0.1.11-<timestamp>`).
|
|
@@ -96,6 +183,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
96
183
|
- Maintained complete backward compatibility in `emit status` by omitting the `Flags:` line when all flags are 0.
|
|
97
184
|
|
|
98
185
|
### Fixed
|
|
186
|
+
|
|
99
187
|
- **Phase 1: Revision Normalization & Heal Symmetry**:
|
|
100
188
|
- Fixed false-positive unpushed edits quarantine caused by Base64 token vs numeric revision formats (`normalizeRev()`).
|
|
101
189
|
- Kept raw opaque tokens strictly unchanged for server `_rev` PUT transmission and storage.
|
|
@@ -112,6 +200,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
112
200
|
## [0.1.11] - 2026-11-09
|
|
113
201
|
|
|
114
202
|
### Fixed
|
|
203
|
+
|
|
115
204
|
- **Doctor Media References False-Positive (D1)**:
|
|
116
205
|
- Resolved `/_emdash/api/media/file/<storageKey>` references against indexed `media/*.yaml` companion documents and local media files.
|
|
117
206
|
- Fixes false-positive warnings where valid remote canonical image references were falsely reported as missing from disk.
|
|
@@ -135,6 +224,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
135
224
|
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
225
|
|
|
137
226
|
### Added
|
|
227
|
+
|
|
138
228
|
- **Phase 1a & 1b: Non-Destructive Frontmatter & Inline Taxonomy Comments**
|
|
139
229
|
- **Non-destructive frontmatter healing**: Unquoted colons, invalid YAML indentations, and encoding quirks are healed non-destructively while strictly preserving local body edits.
|
|
140
230
|
- **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 +260,14 @@ Version 0.1.10 represents a comprehensive modernization of Emit into an AI-first
|
|
|
170
260
|
- **Complete Documentation Harmonization**: Full alignment across `README.md`, `EMIT_MCP_INSTRUCTIONS`, and CLI command help.
|
|
171
261
|
|
|
172
262
|
### Changed
|
|
263
|
+
|
|
173
264
|
- **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
265
|
- 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
266
|
- Hermetic test isolation: tests stub `fetch` and isolate temporary `HOME` configurations to prevent ambient leakage from developer profiles.
|
|
176
267
|
|
|
177
268
|
### Known Limitations
|
|
178
|
-
|
|
269
|
+
|
|
270
|
+
- **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
271
|
- **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
272
|
|
|
181
273
|
---
|
|
@@ -183,12 +275,14 @@ Version 0.1.10 represents a comprehensive modernization of Emit into an AI-first
|
|
|
183
275
|
## [0.1.9] - 2026-11-04
|
|
184
276
|
|
|
185
277
|
### Added
|
|
278
|
+
|
|
186
279
|
- **Sliding Window Staging Desk Model**: Bounded synchronizations defaulting to latest 100 articles (`postsSyncLimit`) per status (`all`, `draft`, `published`).
|
|
187
280
|
- **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
281
|
- **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
282
|
- **Distributed Mutex Lock**: Distributed 7-minute lease protocol via `POST /_emdash/api/content/{collection}/{id}/lock` to prevent multi-agent write collisions.
|
|
190
283
|
|
|
191
284
|
### Changed
|
|
285
|
+
|
|
192
286
|
- Refactored `emit pull` to automatically sync taxonomies (`categories/` and `tags/`) by default alongside articles.
|
|
193
287
|
|
|
194
288
|
---
|
|
@@ -196,6 +290,7 @@ Version 0.1.10 represents a comprehensive modernization of Emit into an AI-first
|
|
|
196
290
|
## [0.1.8] - 2026-10-31
|
|
197
291
|
|
|
198
292
|
### Added
|
|
293
|
+
|
|
199
294
|
- Initial release of `emdash-emit`.
|
|
200
295
|
- Local-first synchronization engine for Cloudflare Workers + D1 powered EmDash CMS.
|
|
201
296
|
- Multi-site profile management (`emit profile add`, `list`, `use`, `remove`).
|