mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-21 02:26:31 +03:00
Fix truncated surface-brief slug collisions (#774)
* Fix truncated slug collisions Prepared with AI assistance under maintainer-authorized automation. * Preserve legacy long-slug reads Prepared with AI assistance under maintainer-authorized automation. * Harden legacy slug compatibility Require target metadata before reading collision-prone legacy brief and critique paths. Add regressions for two long targets with the same pre-hash suffix.\n\nPrepared with AI assistance. * Keep explicit access to legacy critiques Allow identity-less pre-hash snapshots to be read by their exact legacy slug while keeping path and URL fallback identity-gated. Document the compatibility boundary and extend collision coverage.\n\nPrepared with AI assistance.
This commit is contained in:
@@ -483,7 +483,7 @@ The build (`scripts/lib/utils.js` `replaceScriptProviderMarker`) rewrites exactl
|
||||
- non-string / empty after trim -> `null`.
|
||||
- URL (`/^https?:\/\//i`): `new URL(...)`; invalid -> `null`; slug = `kebab(hostname + pathname)` (port, query, hash dropped; hostname is lowercased by URL).
|
||||
- Else path: abs = absolute or `path.resolve(cwd, trimmed)`; rel = `path.relative(cwd, abs)`; if rel starts with `..` or is absolute -> rel = basename(abs); if rel is `''` or `'.'` -> `null`; slug = `kebab(rel)`.
|
||||
`kebab(v)`: lowercase; replace runs of `/`, `\`, `.` (`/[/\\.]+/g`) with `-`; replace `/[^a-z0-9-]+/g` with `-`; collapse `/-+/g` -> `-`; strip leading/trailing `-`; empty -> `null`; if length > 50 keep the LAST 50 chars then strip one leading `-`.
|
||||
`kebab(v)`: lowercase; replace runs of `/`, `\`, `.` (`/[/\\.]+/g`) with `-`; replace `/[^a-z0-9-]+/g` with `-`; collapse `/-+/g` -> `-`; strip leading/trailing `-`; empty -> `null`. Values up to 50 characters remain unchanged. Longer normalized values keep their last 41 characters (stripping one leading `-`) and append `-` plus the first 8 lowercase hex characters of the SHA-256 digest of the full normalized value.
|
||||
Examples: `site/pages/index.astro` -> `site-pages-index-astro`; `http://localhost:3000/pricing` -> `localhost-pricing`; `https://Impeccable.Style/docs/audit/` -> `impeccable-style-docs-audit`.
|
||||
|
||||
#### `.impeccable/` path resolution (`lib/impeccable-paths.mjs`)
|
||||
@@ -710,7 +710,7 @@ Tier 2 (`staleness-deep.mjs`, doctor only):
|
||||
|
||||
- **Invoked from**: `reference/new-work.md`: `node {{scripts_path}}/surface-brief.mjs read <primary-target>` and `... write <primary-target> <body-file> [related-target ...]`; `context.mjs` SURFACE_CONTEXT_AVAILABLE names `read <path>`. `reference/live.md` says live must not shell out to it.
|
||||
- **CLI args**: positional `<command> [target] [bodyFile] [related...]`; commands `path`, `list`, `read`, `write`. projectRoot = `resolveProjectRoot(cwd, target ? {targetPath: target} : {})` (so the target itself steers monorepo resolution).
|
||||
- **Outputs**: `path`: cwd-relative brief path + `\n` (error `surface brief path requires a concrete target` when unslugable). `list`: `JSON.stringify([{slug, path (projectRoot-relative posix), primaryTarget, relatedTargets}], null, 2)` + `\n`. `read`: on resolution prints the brief's full text verbatim (no added newline), exit 0; else if candidates exist prints their summaries JSON to **stderr**, and exits 2 either way. `write`: requires target and bodyFile (else error `usage: surface-brief.mjs write <primary-target> <body-file>`); writes the brief (format above) and prints cwd-relative path + `\n`. Unknown command -> error `usage: surface-brief.mjs <path|list|read|write> [target] [body-file] [related-target ...]`. All thrown errors -> stderr `<message>\n`, exit 1.
|
||||
- **Outputs**: `path`: cwd-relative brief path + `\n` (error `surface brief path requires a concrete target` when unslugable). `list`: `JSON.stringify([{slug, path (projectRoot-relative posix), primaryTarget, relatedTargets}], null, 2)` + `\n`. `read`: on resolution prints the brief's full text verbatim (no added newline), exit 0; direct target resolution probes both the current hashed long slug and the previous suffix-only long slug, but accepts the collision-prone legacy path only when the brief's target metadata matches the requested target; else if candidates exist prints their summaries JSON to **stderr**, and exits 2 either way. `write`: requires target and bodyFile (else error `usage: surface-brief.mjs write <primary-target> <body-file>`); writes the brief (format above) and prints cwd-relative path + `\n`. Unknown command -> error `usage: surface-brief.mjs <path|list|read|write> [target] [body-file] [related-target ...]`. All thrown errors -> stderr `<message>\n`, exit 1.
|
||||
- **Tests**: `tests/surface-brief.test.mjs` (library level: slug path `.impeccable/surfaces/src-pages-index-astro.md`, related-target resolution, only-brief/ambiguous, overwrite semantics, route normalization, `route.md` root); `tests/context.test.mjs` brief loading via context.
|
||||
|
||||
---
|
||||
@@ -721,7 +721,7 @@ Tier 2 (`staleness-deep.mjs`, doctor only):
|
||||
- **CLI args**: `slug <target>`; `write <slug-or-target> <body-file>`; `latest <slug-or-target>`; `trend <slug-or-target> [limit=5]`. `coerceSlug`: value matching `/^[a-z0-9-]+$/` used as-is, else `slugFromTarget(value)` (cwd = process.cwd()).
|
||||
- **Env vars**: `IMPECCABLE_CRITIQUE_META` (JSON object for frontmatter on `write`; parse failure ignored).
|
||||
- **Storage**: dir `getCritiqueDir(cwd)` = `<projectRoot>/.impeccable/critique/`. Filename `<stamp>__<slug>.md`, stamp = ISO UTC with `:` and `.` -> `-` and the `-mmmZ` fraction removed: `2026-05-12T18-30-00Z`. File content: `---\n<key>: <value>\n...---\n<body.trim()>\n`, keys = `{...meta, timestamp, slug}` (meta first, then computed override); null/undefined skipped; string values containing `:` or `#` are JSON-quoted. Snapshot filename regex `/^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}Z__.+\.md$/`; sorted lexicographically (= chronologically). Frontmatter read: `"..."` values JSON-parsed, `/^-?\d+$/` -> Number, else string.
|
||||
- **Outputs**: `slug`: slug + `\n` exit 0, or stderr `no stable slug for input\n` exit 1. `write`: missing args -> stderr `usage: write <slug-or-target> <body-file>\n` exit 1; else absolute path written + `\n`. `latest`: none -> exit 2 (no output); else prints file body verbatim. `trend`: `JSON.stringify(rows, null, 2)` + `\n`, rows = frontmatter objects of the last N matching files oldest->newest (`[]` if none). Unknown -> stderr `usage: critique-storage.mjs <slug|write|latest|trend> [args]\n` exit 1.
|
||||
- **Outputs**: `slug`: slug + `\n` exit 0, or stderr `no stable slug for input\n` exit 1. `write`: missing args -> stderr `usage: write <slug-or-target> <body-file>\n` exit 1; else absolute path written + `\n`. `latest`: none -> exit 2 (no output); else prints file body verbatim. For explicit path/URL targets, `latest`, `trend`, and `close` probe both the current hashed long slug and the previous suffix-only long slug, but accept a legacy snapshot only when its recorded target identity matches. Identity-less pre-upgrade snapshots remain available by passing their exact legacy slug, which avoids guessing a path/URL owner for a collision-prone key. `trend`: `JSON.stringify(rows, null, 2)` + `\n`, rows = frontmatter objects of the last N matching files oldest->newest (`[]` if none). Unknown -> stderr `usage: critique-storage.mjs <slug|write|latest|trend> [args]\n` exit 1.
|
||||
- **Gotchas**: `latest`/`trend` match by suffix `__<slug>.md`; `readLatestSnapshotAcrossTargets` uses suffix `.md`. Main-module guard compares realpaths so symlinked invocation works.
|
||||
- **Tests**: `tests/critique-storage.test.mjs` (slug rules, stamp format, round-trip, newest selection, meta cannot override timestamp/slug, quoting `:`/`#`, CLI slug/exit codes/symlink/latest exit 2, trend ordering).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user