@skill-map/cli 0.26.1 → 0.28.0
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/dist/cli/tutorial/sm-master.md +373 -186
- package/dist/cli/tutorial/sm-tutorial.md +264 -270
- package/dist/cli.js +4289 -4316
- package/dist/cli.js.map +1 -1
- package/dist/conformance/index.js.map +1 -1
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/kernel/index.d.ts +1 -10
- package/dist/kernel/index.js +4 -4
- package/dist/kernel/index.js.map +1 -1
- package/dist/migrations/001_initial.sql +7 -4
- package/dist/ui/chunk-BGDH7CDV.js +1 -0
- package/dist/ui/chunk-XCUOAV77.js +123 -0
- package/dist/ui/index.html +2 -2
- package/dist/ui/{main-CYRAD3L6.js → main-TZL26MZU.js} +2 -2
- package/dist/ui/skill-map-mark-matrix.svg +8 -0
- package/dist/ui/{styles-EGXMA46P.css → styles-IKG3B6AM.css} +1 -1
- package/migrations/001_initial.sql +7 -4
- package/package.json +2 -2
- package/dist/ui/chunk-Q64EKSUB.js +0 -123
- package/dist/ui/chunk-UVILXZ5L.js +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../conformance/index.ts","../../kernel/util/format-error.ts","../../kernel/util/skill-map-paths.ts","../../core/paths/db-path.ts","../../kernel/util/tx.ts","../../conformance/i18n/runner.texts.ts"],"sourcesContent":["/**\n * Contract runner, executes the conformance cases shipped with\n * `@skill-map/spec` against an installed binary and emits a pass/fail result\n * per case.\n *\n * Implements the six assertion types from `spec/schemas/conformance-case.schema.json`.\n * Provisions a clean tmp scope per case, optionally pre-populated with the\n * referenced fixture corpus.\n *\n * Step 0b scope: single-case dispatch. Suite-level runner + reporter land\n * alongside Step 2 extensions.\n */\n\nimport { spawnSync } from 'node:child_process';\nimport { cpSync, existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { isAbsolute, join, relative, resolve } from 'node:path';\n\nimport { formatErrorMessage } from '../kernel/util/format-error.js';\nimport { KERNEL_SKILL_MAP_DIR } from '../kernel/util/skill-map-paths.js';\nimport { tx } from '../kernel/util/tx.js';\nimport { CONFORMANCE_RUNNER_TEXTS } from './i18n/runner.texts.js';\n\nexport type IAssertionResult =\n | { ok: true; type: string }\n | { ok: false; type: string; reason: string };\n\nexport interface IRunCaseResult {\n caseId: string;\n passed: boolean;\n exitCode: number;\n stdout: string;\n stderr: string;\n assertions: IAssertionResult[];\n}\n\nexport interface IRunCaseOptions {\n /** Absolute path to the binary wrapper (e.g. `bin/sm.js`). */\n binary: string;\n /** Absolute path to the `@skill-map/spec` root. */\n specRoot: string;\n /** Absolute path to the case JSON under `<conformance-root>/cases/`. */\n casePath: string;\n /**\n * Absolute path to the `<conformance-root>/fixtures/` directory backing\n * this case (or the parent conformance suite).\n *\n * Phase 5 / A.13 introduced per-Provider conformance directories that\n * live outside the spec tree (Claude-specific cases moved to\n * `src/extensions/providers/claude/conformance/`). Cases reference\n * fixtures by directory name; the runner resolves them under\n * `fixturesRoot` so the spec-agnostic kernel-empty-boot case and the\n * Claude `basic-scan` / `rename-high` / `orphan-detection` cases can\n * coexist without colliding fixture namespaces. Defaults to\n * `<specRoot>/conformance/fixtures` for the legacy spec layout.\n */\n fixturesRoot?: string;\n /** Extra env vars passed to the child. */\n env?: NodeJS.ProcessEnv;\n}\n\ninterface IConformanceCase {\n id: string;\n description: string;\n fixture?: string;\n setup?: {\n disableAllProviders?: boolean;\n disableAllExtractors?: boolean;\n disableAllAnalyzers?: boolean;\n priorScans?: Array<{ fixture: string; flags?: string[] }>;\n };\n invoke: {\n verb: string;\n sub?: string;\n args?: string[];\n flags?: string[];\n };\n assertions: IAssertion[];\n}\n\n/**\n * Build the env-var bag a case's `setup.disableAll*` toggles inject into\n * every child invocation (priorScans + the main `invoke`). The CLI's scan\n * composer (`composeScanExtensions`) reads these vars and drops every\n * extension of the matching kind from the in-scan pipeline.\n */\nfunction disableEnv(setup: IConformanceCase['setup']): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = {};\n if (setup?.disableAllProviders) env['SKILL_MAP_DISABLE_ALL_PROVIDERS'] = '1';\n if (setup?.disableAllExtractors) env['SKILL_MAP_DISABLE_ALL_EXTRACTORS'] = '1';\n if (setup?.disableAllAnalyzers) env['SKILL_MAP_DISABLE_ALL_ANALYZERS'] = '1';\n return env;\n}\n\nexport type IAssertion =\n | { type: 'exit-code'; value: number }\n | {\n type: 'json-path';\n path: string;\n equals?: unknown;\n greaterThan?: number;\n lessThan?: number;\n matches?: string;\n }\n | { type: 'file-exists'; path: string }\n | { type: 'file-contains-verbatim'; path: string; fixture: string }\n | { type: 'file-matches-schema'; path: string; schema: string }\n | { type: 'stderr-matches'; pattern: string };\n\n// Conformance runner orchestrates: case parse, setup steps, scope\n// provision, sm invocation, assert dispatch over the closed assertion\n// type union. Each step is one cyclomatic point; splitting hides the\n// pipeline. Per `context/lint.md` category 1 (CLI orchestrators).\n// eslint-disable-next-line complexity\nexport function runConformanceCase(options: IRunCaseOptions): IRunCaseResult {\n const raw = readFileSync(options.casePath, 'utf8');\n const c: IConformanceCase = JSON.parse(raw);\n\n const fixturesRoot = options.fixturesRoot ?? join(options.specRoot, 'conformance', 'fixtures');\n\n // Defence in depth (audit L5): the conformance case id is JSON-author-\n // controlled. Replace anything that isn't a safe filesystem char and\n // cap the length so an over-long id (or one carrying path separators\n // / control bytes) can't escape `tmpdir()` or grow the prefix beyond\n // a reasonable bound.\n const safeId = c.id.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 32);\n const scope = mkdtempSync(join(tmpdir(), `sm-conformance-${safeId}-`));\n const setupEnv = disableEnv(c.setup);\n try {\n // 1. Replay every `setup.priorScans` step into the scope DB before\n // the main invoke runs. Returns the failure result early if any\n // step exits non-zero.\n const priorFailure = runPriorScansSetup(c, options, scope, fixturesRoot, setupEnv);\n if (priorFailure) return priorFailure;\n\n // 2. Copy the main fixture (replacing prior fixture content but\n // preserving the DB), then run the case's `invoke`.\n if (c.fixture) {\n replaceFixture(scope, fixturesRoot, c.fixture);\n }\n\n const argv = [c.invoke.verb];\n if (c.invoke.sub) argv.push(c.invoke.sub);\n if (c.invoke.args) argv.push(...c.invoke.args);\n if (c.invoke.flags) argv.push(...c.invoke.flags);\n\n const child = spawnSync(process.execPath, [options.binary, ...argv], {\n cwd: scope,\n env: { ...process.env, ...options.env, ...setupEnv },\n encoding: 'utf8',\n });\n\n const stdout = child.stdout ?? '';\n const stderr = child.stderr ?? '';\n const exitCode = child.status ?? 0;\n\n const assertions = c.assertions.map((a) =>\n evaluateAssertion(a, {\n exitCode,\n stdout,\n stderr,\n scope,\n specRoot: options.specRoot,\n fixturesRoot,\n }),\n );\n const passed = assertions.every((a) => a.ok);\n\n return { caseId: c.id, passed, exitCode, stdout, stderr, assertions };\n } finally {\n rmSync(scope, { recursive: true, force: true });\n }\n}\n\n/**\n * Phase 1 of `runConformanceCase`, replay every `setup.priorScans`\n * step in order. Each step replaces every non-`.skill-map/` directory\n * with the named fixture, then runs `sm scan` so the snapshot persists\n * into the scope DB. The scope DB survives across steps (we never\n * delete `.skill-map/`).\n *\n * Returns `null` on success (caller continues) or a `IRunCaseResult`\n * with a single `priorScan` failure assertion (caller returns it\n * unchanged).\n */\n// Per-step replay: replace fixture, spawn `sm scan`, check exit. The\n// failure-result construction is verbose because it carries every\n// stream the caller reports back.\n// eslint-disable-next-line complexity\nfunction runPriorScansSetup(\n c: IConformanceCase,\n options: IRunCaseOptions,\n scope: string,\n fixturesRoot: string,\n setupEnv: NodeJS.ProcessEnv,\n): IRunCaseResult | null {\n for (const step of c.setup?.priorScans ?? []) {\n replaceFixture(scope, fixturesRoot, step.fixture);\n const stepArgv = ['scan', ...(step.flags ?? [])];\n const stepChild = spawnSync(process.execPath, [options.binary, ...stepArgv], {\n cwd: scope,\n env: { ...process.env, ...options.env, ...setupEnv },\n encoding: 'utf8',\n });\n if ((stepChild.status ?? 0) !== 0) {\n return {\n caseId: c.id,\n passed: false,\n exitCode: stepChild.status ?? 0,\n stdout: stepChild.stdout ?? '',\n stderr: stepChild.stderr ?? '',\n assertions: [\n {\n ok: false,\n type: 'priorScan',\n reason: tx(CONFORMANCE_RUNNER_TEXTS.priorScanFailed, {\n fixture: step.fixture,\n exit: stepChild.status ?? 0,\n stderr: stepChild.stderr ?? '',\n }),\n },\n ],\n };\n }\n }\n return null;\n}\n\n/**\n * Replace every top-level entry in `scope` EXCEPT `.skill-map/` (which\n * holds the kernel DB and persists across staging steps), then copy\n * the fixture's contents on top. Used by `priorScans` and the main\n * fixture phase to swap Provider content while keeping the DB stable.\n *\n * `fixturesRoot` is the absolute path to the `fixtures/` directory of\n * the conformance suite hosting the case (spec-owned for kernel cases,\n * Provider-owned for Provider cases, see `IRunCaseOptions.fixturesRoot`).\n */\nfunction replaceFixture(scope: string, fixturesRoot: string, fixture: string): void {\n assertContained(fixturesRoot, fixture, 'fixture');\n for (const entry of readdirSync(scope)) {\n if (entry === KERNEL_SKILL_MAP_DIR) continue;\n rmSync(join(scope, entry), { recursive: true, force: true });\n }\n const src = join(fixturesRoot, fixture);\n cpSync(src, scope, { recursive: true });\n}\n\n/**\n * Reject case-supplied path strings that escape the directory tree they\n * are anchored to. A hostile case JSON would otherwise be able to copy\n * arbitrary filesystem content into the tmp scope (`fixture: \"../..\"`)\n * or read files outside the conformance sandbox via `file-exists` /\n * `file-contains-verbatim` assertions.\n */\nfunction assertContained(root: string, rel: string, label: string): void {\n if (isAbsolute(rel)) {\n throw new Error(\n tx(CONFORMANCE_RUNNER_TEXTS.pathMustBeRelative, { label, path: rel, anchor: root }),\n );\n }\n const abs = resolve(root, rel);\n const r = relative(root, abs);\n if (r.startsWith('..') || isAbsolute(r)) {\n throw new Error(\n tx(CONFORMANCE_RUNNER_TEXTS.pathEscapesAnchor, { label, path: rel, anchor: root }),\n );\n }\n}\n\ninterface IAssertionContext {\n exitCode: number;\n stdout: string;\n stderr: string;\n scope: string;\n specRoot: string;\n fixturesRoot: string;\n}\n\n// Switch over assertion types (`exit-code` / `stdout-matches` /\n// `file-exists` / `file-contains-verbatim` / `file-matches-schema` /\n// `stderr-matches` / `json-path`) with one branch per type. Splitting\n// per type would scatter the discriminated-union dispatch.\n// eslint-disable-next-line complexity\nfunction evaluateAssertion(a: IAssertion, ctx: IAssertionContext): IAssertionResult {\n switch (a.type) {\n case 'exit-code':\n return ctx.exitCode === a.value\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.expectedExitCode, {\n expected: a.value,\n actual: ctx.exitCode,\n }),\n };\n case 'json-path':\n return evaluateJsonPath(a, ctx);\n case 'file-exists': {\n try {\n assertContained(ctx.scope, a.path, 'file-exists');\n } catch (err) {\n return { ok: false, type: a.type, reason: formatErrorMessage(err) };\n }\n const abs = resolve(ctx.scope, a.path);\n return existsSync(abs)\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.fileNotFound, { path: a.path }),\n };\n }\n case 'file-contains-verbatim': {\n try {\n assertContained(ctx.fixturesRoot, a.fixture, 'file-contains-verbatim/fixture');\n assertContained(ctx.scope, a.path, 'file-contains-verbatim/path');\n } catch (err) {\n return { ok: false, type: a.type, reason: formatErrorMessage(err) };\n }\n const fixturePath = join(ctx.fixturesRoot, a.fixture);\n const targetPath = resolve(ctx.scope, a.path);\n if (!existsSync(targetPath)) {\n return {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.targetNotFound, { path: a.path }),\n };\n }\n const needle = readFileSync(fixturePath);\n const haystack = readFileSync(targetPath);\n return haystack.includes(needle)\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.targetMissingFixture, { fixture: a.fixture }),\n };\n }\n case 'file-matches-schema':\n return {\n ok: false,\n type: a.type,\n reason: CONFORMANCE_RUNNER_TEXTS.fileMatchesSchemaUnimplemented,\n };\n case 'stderr-matches': {\n const re = new RegExp(a.pattern);\n return re.test(ctx.stderr)\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.stderrDidNotMatch, { pattern: a.pattern }),\n };\n }\n }\n}\n\n/**\n * Minimal JSONPath evaluator, supports only the subset used by the stub\n * conformance suite: `$.foo`, `$.foo.bar`, `$.foo.length`, `$[0]`.\n * The full RFC 9535 implementation lands with Step 2.\n */\nfunction evaluateJsonPath(\n a: Extract<IAssertion, { type: 'json-path' }>,\n ctx: IAssertionContext,\n): IAssertionResult {\n let doc: unknown;\n try {\n doc = JSON.parse(ctx.stdout);\n } catch (err) {\n return {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.stdoutNotJson, { message: formatErrorMessage(err) }),\n };\n }\n\n const segments = parsePath(a.path);\n if (!segments) {\n return {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.unsupportedJsonPath, { path: a.path }),\n };\n }\n\n const walked = traverseJsonPath(doc, segments, a.path);\n if (!walked.ok) return { ok: false, type: a.type, reason: walked.reason };\n\n return applyJsonPathComparator(a, walked.value);\n}\n\n/**\n * Walk a parsed JSONPath segment list against a JSON document. Returns\n * the resolved value or a structured failure (caller maps to\n * `IAssertionResult`). Pure, no IO, no shared state.\n */\nfunction traverseJsonPath(\n doc: unknown,\n segments: Array<string | number>,\n path: string,\n): { ok: true; value: unknown } | { ok: false; reason: string } {\n let current: unknown = doc;\n for (const seg of segments) {\n if (typeof seg === 'number') {\n if (!Array.isArray(current)) {\n return { ok: false, reason: tx(CONFORMANCE_RUNNER_TEXTS.expectedArrayAtPath, { path }) };\n }\n current = current[seg];\n } else if (seg === 'length' && Array.isArray(current)) {\n current = current.length;\n } else if (typeof current === 'object' && current !== null) {\n current = (current as Record<string, unknown>)[seg];\n } else {\n return {\n ok: false,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.cannotTraverseSegment, {\n type: typeof current,\n segment: String(seg),\n }),\n };\n }\n }\n return { ok: true, value: current };\n}\n\n/**\n * Apply the comparator clause (`equals` / `greaterThan` / `lessThan` /\n * `matches`) of a `json-path` assertion against the value resolved at\n * the requested path. Returns the final `IAssertionResult` directly.\n *\n * Complexity from the four parallel comparator branches; splitting into\n * one helper per comparator would be ceremony.\n */\n// eslint-disable-next-line complexity\nfunction applyJsonPathComparator(\n a: Extract<IAssertion, { type: 'json-path' }>,\n current: unknown,\n): IAssertionResult {\n if ('equals' in a && a.equals !== undefined) {\n return deepEqual(current, a.equals)\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.jsonPathEqualsMismatch, {\n path: a.path,\n actual: JSON.stringify(current),\n expected: JSON.stringify(a.equals),\n }),\n };\n }\n if ('greaterThan' in a && typeof a.greaterThan === 'number') {\n return typeof current === 'number' && current > a.greaterThan\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.jsonPathNotGreaterThan, {\n path: a.path,\n value: a.greaterThan,\n }),\n };\n }\n if ('lessThan' in a && typeof a.lessThan === 'number') {\n return typeof current === 'number' && current < a.lessThan\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.jsonPathNotLessThan, {\n path: a.path,\n value: a.lessThan,\n }),\n };\n }\n if ('matches' in a && typeof a.matches === 'string') {\n const re = new RegExp(a.matches);\n return typeof current === 'string' && re.test(current)\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.jsonPathDidNotMatch, {\n path: a.path,\n pattern: a.matches,\n }),\n };\n }\n return { ok: false, type: a.type, reason: CONFORMANCE_RUNNER_TEXTS.jsonPathNoComparator };\n}\n\nfunction parsePath(path: string): Array<string | number> | null {\n if (!path.startsWith('$')) return null;\n const tail = path.slice(1);\n const segments: Array<string | number> = [];\n const re = /\\.([a-zA-Z_][a-zA-Z0-9_-]*)|\\[(\\d+)\\]/g;\n let lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = re.exec(tail)) !== null) {\n if (match.index !== lastIndex) return null;\n if (match[1] !== undefined) segments.push(match[1]);\n else if (match[2] !== undefined) segments.push(Number.parseInt(match[2], 10));\n lastIndex = re.lastIndex;\n }\n if (lastIndex !== tail.length) return null;\n return segments;\n}\n\n// Structural equality over arbitrary JSON values: primitive / null /\n// array / object branches plus per-branch length / key-set checks.\n// The branching IS the type table. Per `context/lint.md` category 7\n// (recursive type-discriminator walkers).\n// eslint-disable-next-line complexity\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (a && b && typeof a === 'object' && typeof b === 'object') {\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n const ak = Object.keys(a as object);\n const bk = Object.keys(b as object);\n if (ak.length !== bk.length) return false;\n for (const k of ak) {\n if (\n !deepEqual(\n (a as Record<string, unknown>)[k],\n (b as Record<string, unknown>)[k],\n )\n )\n {return false;}\n }\n return true;\n }\n return false;\n}\n\n/** Verifies the spec root looks sane (contains `index.json`). */\nexport function assertSpecRoot(specRoot: string): void {\n const indexPath = join(specRoot, 'index.json');\n if (!existsSync(indexPath) || !statSync(indexPath).isFile()) {\n throw new Error(tx(CONFORMANCE_RUNNER_TEXTS.specRootMissingIndex, { specRoot }));\n }\n}\n","/**\n * Kernel-accessible counterpart of `cli/util/error-reporter.ts`'s\n * `formatErrorMessage`. The CLI helper now re-exports from here so the\n * historic CLI import path keeps working while kernel + BFF callers can\n * consume it directly without crossing the layering boundary.\n *\n * Kept deliberately tiny, same shape as the original CLI helper. The\n * surface grows (e.g. a `--verbose` stack mode, JSON envelope) only\n * when a concrete need surfaces.\n */\n\n/**\n * Compact error → string conversion.\n *\n * - `Error` → `err.message` verbatim. Callers wrap with their own\n * verb-specific context line via `tx(*_TEXTS.x, { message })` so\n * error catalogues stay greppable.\n * - Anything else → `String(value)`. Catches the rare throw-a-string\n * / throw-an-object path without exploding on `null`\n * (`String(null)` = `'null'`).\n */\nexport function formatErrorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n","/**\n * Kernel-side helpers that compose the layered-config file paths from\n * the canonical `SKILL_MAP_DIR` literal.\n *\n * `SKILL_MAP_DIR` is exported once from `core/paths/db-path.ts` and\n * re-exported here as `KERNEL_SKILL_MAP_DIR` so kernel-side callers\n * keep their historic name without the literal living in two files\n * (audit m3, one literal home, no `grep \"'\\.skill-map'\"` sweep\n * invariant to maintain across kernel + CLI).\n */\n\nimport { join } from 'node:path';\n\nimport { SKILL_MAP_DIR } from '../../core/paths/db-path.js';\n\n/**\n * Per-scope directory the kernel + CLI both store state under (DB file,\n * settings, plugins, etc.). Re-exported from `core/paths/db-path.ts`\n * the single canonical source for the literal.\n */\nexport const KERNEL_SKILL_MAP_DIR = SKILL_MAP_DIR;\n\nconst SETTINGS_FILENAME = 'settings.json';\nconst LOCAL_SETTINGS_FILENAME = 'settings.local.json';\n\n/**\n * `<scopeRoot>/.skill-map/settings.json`, the canonical layered-config\n * file. Used by `kernel/config/loader.ts` to compose its user / project\n * walk.\n */\nexport function kernelSettingsPath(scopeRoot: string): string {\n return join(scopeRoot, KERNEL_SKILL_MAP_DIR, SETTINGS_FILENAME);\n}\n\n/**\n * `<scopeRoot>/.skill-map/settings.local.json`, the local-overrides\n * companion to `settings.json`. Used by the same loader walk.\n */\nexport function kernelLocalSettingsPath(scopeRoot: string): string {\n return join(scopeRoot, KERNEL_SKILL_MAP_DIR, LOCAL_SETTINGS_FILENAME);\n}\n","/**\n * Pure path helpers for the on-disk skill-map scope layout. Moved out\n * of `cli/util/db-path.ts` so the BFF (`src/server/`) can consume them\n * without reaching into the CLI layer. The CLI-only siblings\n * (`assertDbExists`, `requireDbOrExit`, they take a stderr stream and\n * an `ExitCode`) stay in `cli/util/db-path.ts` and re-export the\n * primitives from here.\n *\n * Spec global flags (per `spec/cli-contract.md` §Global flags):\n * -g / --global operate on `~/.skill-map/` instead of `./.skill-map/`\n * --db <path> escape hatch for explicit DB file\n */\n\nimport { join, resolve } from 'node:path';\n\nimport type { IRuntimeContext } from '../runtime/runtime-context.js';\n\n/**\n * Per-scope directory the CLI stores its state under (DB file, settings,\n * plugins, etc.). Same name in project (`<cwd>/.skill-map/`) and global\n * (`~/.skill-map/`) scopes; the difference is the parent. Exported so\n * write-side scaffolding (`sm init`) and other helpers can reuse the\n * convention without duplicating the literal.\n */\nexport const SKILL_MAP_DIR = '.skill-map';\n\nconst DB_FILENAME = 'skill-map.db';\nconst JOBS_DIRNAME = 'jobs';\nconst PLUGINS_DIRNAME = 'plugins';\nconst SETTINGS_FILENAME = 'settings.json';\nconst LOCAL_SETTINGS_FILENAME = 'settings.local.json';\nconst IGNORE_FILENAME = '.skillmapignore';\n\n/**\n * Single source of truth for the relative DB path inside a scope\n * directory (`.skill-map/skill-map.db`). Same string in project and\n * global scope; the difference is the parent directory the helper\n * resolves against.\n */\nconst DEFAULT_DB_REL = `${SKILL_MAP_DIR}/${DB_FILENAME}`;\n\n/**\n * Entries `sm init` appends to the project `.gitignore`. Centralised\n * here (instead of the verb file) so the literals live alongside their\n * filename constants and the verb consumes them as a frozen list.\n */\nexport const GITIGNORE_ENTRIES: readonly string[] = [\n `${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME}`,\n `${SKILL_MAP_DIR}/${DB_FILENAME}`,\n];\n\n/**\n * Inputs for `resolveDbPath`. Extends `IRuntimeContext` so the helper\n * never reads `process.cwd()` / `homedir()` directly, every caller\n * threads the runtime context (mandatory) alongside the spec flags.\n * Pattern: `resolveDbPath({ global, db, ...defaultRuntimeContext() })`.\n */\nexport interface IDbLocationOptions extends IRuntimeContext {\n global: boolean;\n db: string | undefined;\n}\n\n/**\n * Resolve the DB file path from command-line options.\n *\n * Precedence: explicit `--db <path>` > `-g/--global` (~/.skill-map/) >\n * project default (cwd/.skill-map/).\n *\n * Always returns an absolute path. Does NOT verify existence, pair with\n * `assertDbExists` for read-side verbs.\n */\nexport function resolveDbPath(options: IDbLocationOptions): string {\n if (options.db) return resolve(options.db);\n if (options.global) return join(options.homedir, DEFAULT_DB_REL);\n return resolve(options.cwd, DEFAULT_DB_REL);\n}\n\n/**\n * Default project DB path (`<cwd>/.skill-map/skill-map.db`). Same effect\n * as `resolveDbPath({ global: false, db: undefined, ...ctx })`; this\n * helper is the cheaper and more explicit route for call sites that have\n * no `--global` / `--db` flags to honour (`sm scan`, `sm refresh`,\n * `sm watch`).\n */\nexport function defaultProjectDbPath(ctx: IRuntimeContext): string {\n return resolve(ctx.cwd, DEFAULT_DB_REL);\n}\n\n/**\n * Default project jobs directory (`<cwd>/.skill-map/jobs`). Used by the\n * `sm job prune` orphan-files pass and any other call site that needs\n * the project-scoped jobs spool.\n */\nexport function defaultProjectJobsDir(ctx: IRuntimeContext): string {\n return resolve(ctx.cwd, SKILL_MAP_DIR, JOBS_DIRNAME);\n}\n\n/**\n * Default project plugins directory (`<cwd>/.skill-map/plugins`).\n * Project + user plugin discovery composes this with the user-scoped\n * `<homedir>/.skill-map/plugins` peer.\n */\nexport function defaultProjectPluginsDir(ctx: IRuntimeContext): string {\n return resolve(ctx.cwd, SKILL_MAP_DIR, PLUGINS_DIRNAME);\n}\n\n/**\n * Default user (global) plugins directory (`<homedir>/.skill-map/plugins`).\n * Used alongside `defaultProjectPluginsDir` when discovery walks both\n * scopes.\n */\nexport function defaultUserPluginsDir(ctx: IRuntimeContext): string {\n return join(ctx.homedir, SKILL_MAP_DIR, PLUGINS_DIRNAME);\n}\n\n/**\n * Default DB path under an arbitrary scope root\n * (`<scopeRoot>/.skill-map/skill-map.db`). Companion to\n * `defaultProjectDbPath` for callers that already resolved the scope\n * root themselves (today: `sm init`, which switches between\n * `cwd`/`homedir` based on `--global`).\n */\nexport function defaultDbPath(scopeRoot: string): string {\n return join(scopeRoot, SKILL_MAP_DIR, DB_FILENAME);\n}\n\n/**\n * Default settings file (`<scopeRoot>/.skill-map/settings.json`).\n */\nexport function defaultSettingsPath(scopeRoot: string): string {\n return join(scopeRoot, SKILL_MAP_DIR, SETTINGS_FILENAME);\n}\n\n/**\n * Default local-overrides settings file\n * (`<scopeRoot>/.skill-map/settings.local.json`).\n */\nexport function defaultLocalSettingsPath(scopeRoot: string): string {\n return join(scopeRoot, SKILL_MAP_DIR, LOCAL_SETTINGS_FILENAME);\n}\n\n/**\n * Default `.skillmapignore` file path\n * (`<scopeRoot>/.skillmapignore`). Sits at the scope root, NOT inside\n * `.skill-map/`, `sm scan` reads it from the same level as `package.json`\n * etc. so authors can keep ignore rules visible in the project tree.\n */\nexport function defaultIgnoreFilePath(scopeRoot: string): string {\n return join(scopeRoot, IGNORE_FILENAME);\n}\n","/**\n * `tx(template, vars)`, string interpolation for the project's text\n * tables (`*.texts.ts` files under `kernel/i18n/` and `cli/i18n/`).\n *\n * Templates use the `{{name}}` placeholder shape (Mustache / Handlebars\n * / Transloco compatible) so the same string tables drop into a real\n * i18n library on the day this project migrates.\n *\n * Contract:\n * - Every `{{name}}` token in `template` MUST have a matching key in\n * `vars`. A missing key throws, silent fallback would hide a\n * forgotten arg in a production build, which is the worst kind of\n * bug to chase down.\n * - Values can be `string | number`. `null` / `undefined` keys are\n * rejected; the caller is expected to coerce upstream (e.g. format\n * a missing path as `'(unknown)'` before passing).\n * - Whitespace inside the braces is tolerated (`{{ name }}`); the\n * parser strips it. This keeps long templates readable when wrapped\n * across multiple TS lines via `+`.\n * - Literal `{{` is not currently supported, no real text needs it.\n * Add escaping the day a template needs to render Handlebars-style\n * content.\n *\n * Plural / conditional logic does NOT live in the template. The caller\n * picks the correct template (e.g. `entries_singular` vs\n * `entries_plural`) or composes the variable value upstream and passes\n * the finished string. Keeping templates flat is the price for staying\n * Transloco-ready.\n */\n\nconst TOKEN_RE = /\\{\\{\\s*([A-Za-z][A-Za-z0-9_]*)\\s*\\}\\}/g;\n\nexport function tx(\n template: string,\n vars: Record<string, string | number> = {},\n): string {\n return template.replace(TOKEN_RE, (_match, name: string) => {\n if (!Object.prototype.hasOwnProperty.call(vars, name)) {\n throw new Error(\n `tx: missing variable \"${name}\" for template \"${template.slice(0, 80)}${template.length > 80 ? '…' : ''}\"`,\n );\n }\n const value = vars[name];\n if (value === null || value === undefined) {\n throw new Error(\n `tx: variable \"${name}\" is null/undefined for template \"${template.slice(0, 80)}${template.length > 80 ? '…' : ''}\"`,\n );\n }\n return String(value);\n });\n}\n","/**\n * Strings emitted by the conformance runner (`conformance/index.ts`).\n * Same `tx(template, vars)` convention as every other `*.texts.ts` peer.\n *\n * Reasons surface in `IAssertionResult.reason`, visible to anyone\n * reading the runner output (CI logs, `sm conformance run --json`).\n * Keeping them in the catalog unblocks a future Transloco migration and\n * keeps the wording in one place.\n */\n\nexport const CONFORMANCE_RUNNER_TEXTS = {\n priorScanFailed:\n 'setup.priorScans step `{{fixture}}` failed with exit {{exit}}: {{stderr}}',\n\n pathMustBeRelative:\n 'conformance: {{label}} path \"{{path}}\" must be relative to its anchor ({{anchor}})',\n\n pathEscapesAnchor:\n 'conformance: {{label}} path \"{{path}}\" escapes its anchor ({{anchor}})',\n\n expectedExitCode:\n 'expected exit {{expected}}, got {{actual}}',\n\n fileNotFound:\n 'file not found: {{path}}',\n\n targetNotFound:\n 'target not found: {{path}}',\n\n targetMissingFixture:\n 'target does not contain fixture {{fixture}} verbatim',\n\n fileMatchesSchemaUnimplemented:\n 'file-matches-schema not yet implemented (requires ajv; lands with Step 2)',\n\n stderrDidNotMatch:\n 'stderr did not match /{{pattern}}/',\n\n stdoutNotJson:\n 'stdout is not valid JSON: {{message}}',\n\n unsupportedJsonPath:\n 'unsupported jsonpath: {{path}}',\n\n expectedArrayAtPath:\n 'expected array at {{path}}',\n\n cannotTraverseSegment:\n \"cannot traverse {{type}} at segment '{{segment}}'\",\n\n jsonPathEqualsMismatch:\n '{{path}} = {{actual}}, expected {{expected}}',\n\n jsonPathNotGreaterThan:\n '{{path}} not > {{value}}',\n\n jsonPathNotLessThan:\n '{{path}} not < {{value}}',\n\n jsonPathDidNotMatch:\n '{{path}} did not match /{{pattern}}/',\n\n jsonPathNoComparator:\n 'no comparator on json-path assertion',\n\n specRootMissingIndex:\n 'spec root missing index.json at {{specRoot}}',\n} as const;\n"],"mappings":";AAaA,SAAS,iBAAiB;AAC1B,SAAS,QAAQ,YAAY,aAAa,aAAa,cAAc,QAAQ,gBAAgB;AAC7F,SAAS,cAAc;AACvB,SAAS,YAAY,QAAAA,OAAM,UAAU,WAAAC,gBAAe;;;ACK7C,SAAS,mBAAmB,KAAsB;AACvD,SAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;;;ACZA,SAAS,QAAAC,aAAY;;;ACErB,SAAS,MAAM,eAAe;AAWvB,IAAM,gBAAgB;AAE7B,IAAM,cAAc;AAIpB,IAAM,0BAA0B;AAShC,IAAM,iBAAiB,GAAG,aAAa,IAAI,WAAW;AAO/C,IAAM,oBAAuC;AAAA,EAClD,GAAG,aAAa,IAAI,uBAAuB;AAAA,EAC3C,GAAG,aAAa,IAAI,WAAW;AACjC;;;AD7BO,IAAM,uBAAuB;;;AEUpC,IAAM,WAAW;AAEV,SAAS,GACd,UACA,OAAwC,CAAC,GACjC;AACR,SAAO,SAAS,QAAQ,UAAU,CAAC,QAAQ,SAAiB;AAC1D,QAAI,CAAC,OAAO,UAAU,eAAe,KAAK,MAAM,IAAI,GAAG;AACrD,YAAM,IAAI;AAAA,QACR,yBAAyB,IAAI,mBAAmB,SAAS,MAAM,GAAG,EAAE,CAAC,GAAG,SAAS,SAAS,KAAK,WAAM,EAAE;AAAA,MACzG;AAAA,IACF;AACA,UAAM,QAAQ,KAAK,IAAI;AACvB,QAAI,UAAU,QAAQ,UAAU,QAAW;AACzC,YAAM,IAAI;AAAA,QACR,iBAAiB,IAAI,qCAAqC,SAAS,MAAM,GAAG,EAAE,CAAC,GAAG,SAAS,SAAS,KAAK,WAAM,EAAE;AAAA,MACnH;AAAA,IACF;AACA,WAAO,OAAO,KAAK;AAAA,EACrB,CAAC;AACH;;;ACxCO,IAAM,2BAA2B;AAAA,EACtC,iBACE;AAAA,EAEF,oBACE;AAAA,EAEF,mBACE;AAAA,EAEF,kBACE;AAAA,EAEF,cACE;AAAA,EAEF,gBACE;AAAA,EAEF,sBACE;AAAA,EAEF,gCACE;AAAA,EAEF,mBACE;AAAA,EAEF,eACE;AAAA,EAEF,qBACE;AAAA,EAEF,qBACE;AAAA,EAEF,uBACE;AAAA,EAEF,wBACE;AAAA,EAEF,wBACE;AAAA,EAEF,qBACE;AAAA,EAEF,qBACE;AAAA,EAEF,sBACE;AAAA,EAEF,sBACE;AACJ;;;ALmBA,SAAS,WAAW,OAAqD;AACvE,QAAM,MAAyB,CAAC;AAChC,MAAI,OAAO,oBAAqB,KAAI,iCAAiC,IAAI;AACzE,MAAI,OAAO,qBAAsB,KAAI,kCAAkC,IAAI;AAC3E,MAAI,OAAO,oBAAqB,KAAI,iCAAiC,IAAI;AACzE,SAAO;AACT;AAsBO,SAAS,mBAAmB,SAA0C;AAC3E,QAAM,MAAM,aAAa,QAAQ,UAAU,MAAM;AACjD,QAAM,IAAsB,KAAK,MAAM,GAAG;AAE1C,QAAM,eAAe,QAAQ,gBAAgBC,MAAK,QAAQ,UAAU,eAAe,UAAU;AAO7F,QAAM,SAAS,EAAE,GAAG,QAAQ,mBAAmB,GAAG,EAAE,MAAM,GAAG,EAAE;AAC/D,QAAM,QAAQ,YAAYA,MAAK,OAAO,GAAG,kBAAkB,MAAM,GAAG,CAAC;AACrE,QAAM,WAAW,WAAW,EAAE,KAAK;AACnC,MAAI;AAIF,UAAM,eAAe,mBAAmB,GAAG,SAAS,OAAO,cAAc,QAAQ;AACjF,QAAI,aAAc,QAAO;AAIzB,QAAI,EAAE,SAAS;AACb,qBAAe,OAAO,cAAc,EAAE,OAAO;AAAA,IAC/C;AAEA,UAAM,OAAO,CAAC,EAAE,OAAO,IAAI;AAC3B,QAAI,EAAE,OAAO,IAAK,MAAK,KAAK,EAAE,OAAO,GAAG;AACxC,QAAI,EAAE,OAAO,KAAM,MAAK,KAAK,GAAG,EAAE,OAAO,IAAI;AAC7C,QAAI,EAAE,OAAO,MAAO,MAAK,KAAK,GAAG,EAAE,OAAO,KAAK;AAE/C,UAAM,QAAQ,UAAU,QAAQ,UAAU,CAAC,QAAQ,QAAQ,GAAG,IAAI,GAAG;AAAA,MACnE,KAAK;AAAA,MACL,KAAK,EAAE,GAAG,QAAQ,KAAK,GAAG,QAAQ,KAAK,GAAG,SAAS;AAAA,MACnD,UAAU;AAAA,IACZ,CAAC;AAED,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,SAAS,MAAM,UAAU;AAC/B,UAAM,WAAW,MAAM,UAAU;AAEjC,UAAM,aAAa,EAAE,WAAW;AAAA,MAAI,CAAC,MACnC,kBAAkB,GAAG;AAAA,QACnB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,UAAU,QAAQ;AAAA,QAClB;AAAA,MACF,CAAC;AAAA,IACH;AACA,UAAM,SAAS,WAAW,MAAM,CAAC,MAAM,EAAE,EAAE;AAE3C,WAAO,EAAE,QAAQ,EAAE,IAAI,QAAQ,UAAU,QAAQ,QAAQ,WAAW;AAAA,EACtE,UAAE;AACA,WAAO,OAAO,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAChD;AACF;AAiBA,SAAS,mBACP,GACA,SACA,OACA,cACA,UACuB;AACvB,aAAW,QAAQ,EAAE,OAAO,cAAc,CAAC,GAAG;AAC5C,mBAAe,OAAO,cAAc,KAAK,OAAO;AAChD,UAAM,WAAW,CAAC,QAAQ,GAAI,KAAK,SAAS,CAAC,CAAE;AAC/C,UAAM,YAAY,UAAU,QAAQ,UAAU,CAAC,QAAQ,QAAQ,GAAG,QAAQ,GAAG;AAAA,MAC3E,KAAK;AAAA,MACL,KAAK,EAAE,GAAG,QAAQ,KAAK,GAAG,QAAQ,KAAK,GAAG,SAAS;AAAA,MACnD,UAAU;AAAA,IACZ,CAAC;AACD,SAAK,UAAU,UAAU,OAAO,GAAG;AACjC,aAAO;AAAA,QACL,QAAQ,EAAE;AAAA,QACV,QAAQ;AAAA,QACR,UAAU,UAAU,UAAU;AAAA,QAC9B,QAAQ,UAAU,UAAU;AAAA,QAC5B,QAAQ,UAAU,UAAU;AAAA,QAC5B,YAAY;AAAA,UACV;AAAA,YACE,IAAI;AAAA,YACJ,MAAM;AAAA,YACN,QAAQ,GAAG,yBAAyB,iBAAiB;AAAA,cACnD,SAAS,KAAK;AAAA,cACd,MAAM,UAAU,UAAU;AAAA,cAC1B,QAAQ,UAAU,UAAU;AAAA,YAC9B,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAYA,SAAS,eAAe,OAAe,cAAsB,SAAuB;AAClF,kBAAgB,cAAc,SAAS,SAAS;AAChD,aAAW,SAAS,YAAY,KAAK,GAAG;AACtC,QAAI,UAAU,qBAAsB;AACpC,WAAOA,MAAK,OAAO,KAAK,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAC7D;AACA,QAAM,MAAMA,MAAK,cAAc,OAAO;AACtC,SAAO,KAAK,OAAO,EAAE,WAAW,KAAK,CAAC;AACxC;AASA,SAAS,gBAAgB,MAAc,KAAa,OAAqB;AACvE,MAAI,WAAW,GAAG,GAAG;AACnB,UAAM,IAAI;AAAA,MACR,GAAG,yBAAyB,oBAAoB,EAAE,OAAO,MAAM,KAAK,QAAQ,KAAK,CAAC;AAAA,IACpF;AAAA,EACF;AACA,QAAM,MAAMC,SAAQ,MAAM,GAAG;AAC7B,QAAM,IAAI,SAAS,MAAM,GAAG;AAC5B,MAAI,EAAE,WAAW,IAAI,KAAK,WAAW,CAAC,GAAG;AACvC,UAAM,IAAI;AAAA,MACR,GAAG,yBAAyB,mBAAmB,EAAE,OAAO,MAAM,KAAK,QAAQ,KAAK,CAAC;AAAA,IACnF;AAAA,EACF;AACF;AAgBA,SAAS,kBAAkB,GAAe,KAA0C;AAClF,UAAQ,EAAE,MAAM;AAAA,IACd,KAAK;AACH,aAAO,IAAI,aAAa,EAAE,QACtB,EAAE,IAAI,MAAM,MAAM,EAAE,KAAK,IACzB;AAAA,QACE,IAAI;AAAA,QACJ,MAAM,EAAE;AAAA,QACR,QAAQ,GAAG,yBAAyB,kBAAkB;AAAA,UACpD,UAAU,EAAE;AAAA,UACZ,QAAQ,IAAI;AAAA,QACd,CAAC;AAAA,MACH;AAAA,IACN,KAAK;AACH,aAAO,iBAAiB,GAAG,GAAG;AAAA,IAChC,KAAK,eAAe;AAClB,UAAI;AACF,wBAAgB,IAAI,OAAO,EAAE,MAAM,aAAa;AAAA,MAClD,SAAS,KAAK;AACZ,eAAO,EAAE,IAAI,OAAO,MAAM,EAAE,MAAM,QAAQ,mBAAmB,GAAG,EAAE;AAAA,MACpE;AACA,YAAM,MAAMA,SAAQ,IAAI,OAAO,EAAE,IAAI;AACrC,aAAO,WAAW,GAAG,IACjB,EAAE,IAAI,MAAM,MAAM,EAAE,KAAK,IACzB;AAAA,QACE,IAAI;AAAA,QACJ,MAAM,EAAE;AAAA,QACR,QAAQ,GAAG,yBAAyB,cAAc,EAAE,MAAM,EAAE,KAAK,CAAC;AAAA,MACpE;AAAA,IACN;AAAA,IACA,KAAK,0BAA0B;AAC7B,UAAI;AACF,wBAAgB,IAAI,cAAc,EAAE,SAAS,gCAAgC;AAC7E,wBAAgB,IAAI,OAAO,EAAE,MAAM,6BAA6B;AAAA,MAClE,SAAS,KAAK;AACZ,eAAO,EAAE,IAAI,OAAO,MAAM,EAAE,MAAM,QAAQ,mBAAmB,GAAG,EAAE;AAAA,MACpE;AACA,YAAM,cAAcD,MAAK,IAAI,cAAc,EAAE,OAAO;AACpD,YAAM,aAAaC,SAAQ,IAAI,OAAO,EAAE,IAAI;AAC5C,UAAI,CAAC,WAAW,UAAU,GAAG;AAC3B,eAAO;AAAA,UACL,IAAI;AAAA,UACJ,MAAM,EAAE;AAAA,UACR,QAAQ,GAAG,yBAAyB,gBAAgB,EAAE,MAAM,EAAE,KAAK,CAAC;AAAA,QACtE;AAAA,MACF;AACA,YAAM,SAAS,aAAa,WAAW;AACvC,YAAM,WAAW,aAAa,UAAU;AACxC,aAAO,SAAS,SAAS,MAAM,IAC3B,EAAE,IAAI,MAAM,MAAM,EAAE,KAAK,IACzB;AAAA,QACE,IAAI;AAAA,QACJ,MAAM,EAAE;AAAA,QACR,QAAQ,GAAG,yBAAyB,sBAAsB,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA,MAClF;AAAA,IACN;AAAA,IACA,KAAK;AACH,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,MAAM,EAAE;AAAA,QACR,QAAQ,yBAAyB;AAAA,MACnC;AAAA,IACF,KAAK,kBAAkB;AACrB,YAAM,KAAK,IAAI,OAAO,EAAE,OAAO;AAC/B,aAAO,GAAG,KAAK,IAAI,MAAM,IACrB,EAAE,IAAI,MAAM,MAAM,EAAE,KAAK,IACzB;AAAA,QACE,IAAI;AAAA,QACJ,MAAM,EAAE;AAAA,QACR,QAAQ,GAAG,yBAAyB,mBAAmB,EAAE,SAAS,EAAE,QAAQ,CAAC;AAAA,MAC/E;AAAA,IACN;AAAA,EACF;AACF;AAOA,SAAS,iBACP,GACA,KACkB;AAClB,MAAI;AACJ,MAAI;AACF,UAAM,KAAK,MAAM,IAAI,MAAM;AAAA,EAC7B,SAAS,KAAK;AACZ,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,MAAM,EAAE;AAAA,MACR,QAAQ,GAAG,yBAAyB,eAAe,EAAE,SAAS,mBAAmB,GAAG,EAAE,CAAC;AAAA,IACzF;AAAA,EACF;AAEA,QAAM,WAAW,UAAU,EAAE,IAAI;AACjC,MAAI,CAAC,UAAU;AACb,WAAO;AAAA,MACL,IAAI;AAAA,MACJ,MAAM,EAAE;AAAA,MACR,QAAQ,GAAG,yBAAyB,qBAAqB,EAAE,MAAM,EAAE,KAAK,CAAC;AAAA,IAC3E;AAAA,EACF;AAEA,QAAM,SAAS,iBAAiB,KAAK,UAAU,EAAE,IAAI;AACrD,MAAI,CAAC,OAAO,GAAI,QAAO,EAAE,IAAI,OAAO,MAAM,EAAE,MAAM,QAAQ,OAAO,OAAO;AAExE,SAAO,wBAAwB,GAAG,OAAO,KAAK;AAChD;AAOA,SAAS,iBACP,KACA,UACA,MAC8D;AAC9D,MAAI,UAAmB;AACvB,aAAW,OAAO,UAAU;AAC1B,QAAI,OAAO,QAAQ,UAAU;AAC3B,UAAI,CAAC,MAAM,QAAQ,OAAO,GAAG;AAC3B,eAAO,EAAE,IAAI,OAAO,QAAQ,GAAG,yBAAyB,qBAAqB,EAAE,KAAK,CAAC,EAAE;AAAA,MACzF;AACA,gBAAU,QAAQ,GAAG;AAAA,IACvB,WAAW,QAAQ,YAAY,MAAM,QAAQ,OAAO,GAAG;AACrD,gBAAU,QAAQ;AAAA,IACpB,WAAW,OAAO,YAAY,YAAY,YAAY,MAAM;AAC1D,gBAAW,QAAoC,GAAG;AAAA,IACpD,OAAO;AACL,aAAO;AAAA,QACL,IAAI;AAAA,QACJ,QAAQ,GAAG,yBAAyB,uBAAuB;AAAA,UACzD,MAAM,OAAO;AAAA,UACb,SAAS,OAAO,GAAG;AAAA,QACrB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AACA,SAAO,EAAE,IAAI,MAAM,OAAO,QAAQ;AACpC;AAWA,SAAS,wBACP,GACA,SACkB;AAClB,MAAI,YAAY,KAAK,EAAE,WAAW,QAAW;AAC3C,WAAO,UAAU,SAAS,EAAE,MAAM,IAC9B,EAAE,IAAI,MAAM,MAAM,EAAE,KAAK,IACzB;AAAA,MACE,IAAI;AAAA,MACJ,MAAM,EAAE;AAAA,MACR,QAAQ,GAAG,yBAAyB,wBAAwB;AAAA,QAC1D,MAAM,EAAE;AAAA,QACR,QAAQ,KAAK,UAAU,OAAO;AAAA,QAC9B,UAAU,KAAK,UAAU,EAAE,MAAM;AAAA,MACnC,CAAC;AAAA,IACH;AAAA,EACN;AACA,MAAI,iBAAiB,KAAK,OAAO,EAAE,gBAAgB,UAAU;AAC3D,WAAO,OAAO,YAAY,YAAY,UAAU,EAAE,cAC9C,EAAE,IAAI,MAAM,MAAM,EAAE,KAAK,IACzB;AAAA,MACE,IAAI;AAAA,MACJ,MAAM,EAAE;AAAA,MACR,QAAQ,GAAG,yBAAyB,wBAAwB;AAAA,QAC1D,MAAM,EAAE;AAAA,QACR,OAAO,EAAE;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACN;AACA,MAAI,cAAc,KAAK,OAAO,EAAE,aAAa,UAAU;AACrD,WAAO,OAAO,YAAY,YAAY,UAAU,EAAE,WAC9C,EAAE,IAAI,MAAM,MAAM,EAAE,KAAK,IACzB;AAAA,MACE,IAAI;AAAA,MACJ,MAAM,EAAE;AAAA,MACR,QAAQ,GAAG,yBAAyB,qBAAqB;AAAA,QACvD,MAAM,EAAE;AAAA,QACR,OAAO,EAAE;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACN;AACA,MAAI,aAAa,KAAK,OAAO,EAAE,YAAY,UAAU;AACnD,UAAM,KAAK,IAAI,OAAO,EAAE,OAAO;AAC/B,WAAO,OAAO,YAAY,YAAY,GAAG,KAAK,OAAO,IACjD,EAAE,IAAI,MAAM,MAAM,EAAE,KAAK,IACzB;AAAA,MACE,IAAI;AAAA,MACJ,MAAM,EAAE;AAAA,MACR,QAAQ,GAAG,yBAAyB,qBAAqB;AAAA,QACvD,MAAM,EAAE;AAAA,QACR,SAAS,EAAE;AAAA,MACb,CAAC;AAAA,IACH;AAAA,EACN;AACA,SAAO,EAAE,IAAI,OAAO,MAAM,EAAE,MAAM,QAAQ,yBAAyB,qBAAqB;AAC1F;AAEA,SAAS,UAAU,MAA6C;AAC9D,MAAI,CAAC,KAAK,WAAW,GAAG,EAAG,QAAO;AAClC,QAAM,OAAO,KAAK,MAAM,CAAC;AACzB,QAAM,WAAmC,CAAC;AAC1C,QAAM,KAAK;AACX,MAAI,YAAY;AAChB,MAAI;AACJ,UAAQ,QAAQ,GAAG,KAAK,IAAI,OAAO,MAAM;AACvC,QAAI,MAAM,UAAU,UAAW,QAAO;AACtC,QAAI,MAAM,CAAC,MAAM,OAAW,UAAS,KAAK,MAAM,CAAC,CAAC;AAAA,aACzC,MAAM,CAAC,MAAM,OAAW,UAAS,KAAK,OAAO,SAAS,MAAM,CAAC,GAAG,EAAE,CAAC;AAC5E,gBAAY,GAAG;AAAA,EACjB;AACA,MAAI,cAAc,KAAK,OAAQ,QAAO;AACtC,SAAO;AACT;AAOA,SAAS,UAAU,GAAY,GAAqB;AAClD,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,OAAO,MAAM,OAAO,EAAG,QAAO;AAClC,MAAI,KAAK,KAAK,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU;AAC5D,QAAI,MAAM,QAAQ,CAAC,MAAM,MAAM,QAAQ,CAAC,EAAG,QAAO;AAClD,UAAM,KAAK,OAAO,KAAK,CAAW;AAClC,UAAM,KAAK,OAAO,KAAK,CAAW;AAClC,QAAI,GAAG,WAAW,GAAG,OAAQ,QAAO;AACpC,eAAW,KAAK,IAAI;AAClB,UACE,CAAC;AAAA,QACE,EAA8B,CAAC;AAAA,QAC/B,EAA8B,CAAC;AAAA,MAClC,GAEA;AAAC,eAAO;AAAA,MAAM;AAAA,IAClB;AACA,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGO,SAAS,eAAe,UAAwB;AACrD,QAAM,YAAYD,MAAK,UAAU,YAAY;AAC7C,MAAI,CAAC,WAAW,SAAS,KAAK,CAAC,SAAS,SAAS,EAAE,OAAO,GAAG;AAC3D,UAAM,IAAI,MAAM,GAAG,yBAAyB,sBAAsB,EAAE,SAAS,CAAC,CAAC;AAAA,EACjF;AACF;","names":["join","resolve","join","join","resolve"]}
|
|
1
|
+
{"version":3,"sources":["../../conformance/index.ts","../../kernel/util/format-error.ts","../../kernel/util/skill-map-paths.ts","../../core/paths/db-path.ts","../../kernel/util/tx.ts","../../conformance/i18n/runner.texts.ts"],"sourcesContent":["/**\n * Contract runner, executes the conformance cases shipped with\n * `@skill-map/spec` against an installed binary and emits a pass/fail result\n * per case.\n *\n * Implements the six assertion types from `spec/schemas/conformance-case.schema.json`.\n * Provisions a clean tmp scope per case, optionally pre-populated with the\n * referenced fixture corpus.\n *\n * Step 0b scope: single-case dispatch. Suite-level runner + reporter land\n * alongside Step 2 extensions.\n */\n\nimport { spawnSync } from 'node:child_process';\nimport { cpSync, existsSync, mkdtempSync, readdirSync, readFileSync, rmSync, statSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { isAbsolute, join, relative, resolve } from 'node:path';\n\nimport { formatErrorMessage } from '../kernel/util/format-error.js';\nimport { KERNEL_SKILL_MAP_DIR } from '../kernel/util/skill-map-paths.js';\nimport { tx } from '../kernel/util/tx.js';\nimport { CONFORMANCE_RUNNER_TEXTS } from './i18n/runner.texts.js';\n\nexport type IAssertionResult =\n | { ok: true; type: string }\n | { ok: false; type: string; reason: string };\n\nexport interface IRunCaseResult {\n caseId: string;\n passed: boolean;\n exitCode: number;\n stdout: string;\n stderr: string;\n assertions: IAssertionResult[];\n}\n\nexport interface IRunCaseOptions {\n /** Absolute path to the binary wrapper (e.g. `bin/sm.js`). */\n binary: string;\n /** Absolute path to the `@skill-map/spec` root. */\n specRoot: string;\n /** Absolute path to the case JSON under `<conformance-root>/cases/`. */\n casePath: string;\n /**\n * Absolute path to the `<conformance-root>/fixtures/` directory backing\n * this case (or the parent conformance suite).\n *\n * Phase 5 / A.13 introduced per-Provider conformance directories that\n * live outside the spec tree (Claude-specific cases moved to\n * `src/extensions/providers/claude/conformance/`). Cases reference\n * fixtures by directory name; the runner resolves them under\n * `fixturesRoot` so the spec-agnostic kernel-empty-boot case and the\n * Claude `basic-scan` / `rename-high` / `orphan-detection` cases can\n * coexist without colliding fixture namespaces. Defaults to\n * `<specRoot>/conformance/fixtures` for the legacy spec layout.\n */\n fixturesRoot?: string;\n /** Extra env vars passed to the child. */\n env?: NodeJS.ProcessEnv;\n}\n\ninterface IConformanceCase {\n id: string;\n description: string;\n fixture?: string;\n setup?: {\n disableAllProviders?: boolean;\n disableAllExtractors?: boolean;\n disableAllAnalyzers?: boolean;\n priorScans?: Array<{ fixture: string; flags?: string[] }>;\n };\n invoke: {\n verb: string;\n sub?: string;\n args?: string[];\n flags?: string[];\n };\n assertions: IAssertion[];\n}\n\n/**\n * Build the env-var bag a case's `setup.disableAll*` toggles inject into\n * every child invocation (priorScans + the main `invoke`). The CLI's scan\n * composer (`composeScanExtensions`) reads these vars and drops every\n * extension of the matching kind from the in-scan pipeline.\n */\nfunction disableEnv(setup: IConformanceCase['setup']): NodeJS.ProcessEnv {\n const env: NodeJS.ProcessEnv = {};\n if (setup?.disableAllProviders) env['SKILL_MAP_DISABLE_ALL_PROVIDERS'] = '1';\n if (setup?.disableAllExtractors) env['SKILL_MAP_DISABLE_ALL_EXTRACTORS'] = '1';\n if (setup?.disableAllAnalyzers) env['SKILL_MAP_DISABLE_ALL_ANALYZERS'] = '1';\n return env;\n}\n\nexport type IAssertion =\n | { type: 'exit-code'; value: number }\n | {\n type: 'json-path';\n path: string;\n equals?: unknown;\n greaterThan?: number;\n lessThan?: number;\n matches?: string;\n }\n | { type: 'file-exists'; path: string }\n | { type: 'file-contains-verbatim'; path: string; fixture: string }\n | { type: 'file-matches-schema'; path: string; schema: string }\n | { type: 'stderr-matches'; pattern: string };\n\n// Conformance runner orchestrates: case parse, setup steps, scope\n// provision, sm invocation, assert dispatch over the closed assertion\n// type union. Each step is one cyclomatic point; splitting hides the\n// pipeline. Per `context/lint.md` category 1 (CLI orchestrators).\n// eslint-disable-next-line complexity\nexport function runConformanceCase(options: IRunCaseOptions): IRunCaseResult {\n const raw = readFileSync(options.casePath, 'utf8');\n const c: IConformanceCase = JSON.parse(raw);\n\n const fixturesRoot = options.fixturesRoot ?? join(options.specRoot, 'conformance', 'fixtures');\n\n // Defence in depth (audit L5): the conformance case id is JSON-author-\n // controlled. Replace anything that isn't a safe filesystem char and\n // cap the length so an over-long id (or one carrying path separators\n // / control bytes) can't escape `tmpdir()` or grow the prefix beyond\n // a reasonable bound.\n const safeId = c.id.replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 32);\n const scope = mkdtempSync(join(tmpdir(), `sm-conformance-${safeId}-`));\n const setupEnv = disableEnv(c.setup);\n try {\n // 1. Replay every `setup.priorScans` step into the scope DB before\n // the main invoke runs. Returns the failure result early if any\n // step exits non-zero.\n const priorFailure = runPriorScansSetup(c, options, scope, fixturesRoot, setupEnv);\n if (priorFailure) return priorFailure;\n\n // 2. Copy the main fixture (replacing prior fixture content but\n // preserving the DB), then run the case's `invoke`.\n if (c.fixture) {\n replaceFixture(scope, fixturesRoot, c.fixture);\n }\n\n const argv = [c.invoke.verb];\n if (c.invoke.sub) argv.push(c.invoke.sub);\n if (c.invoke.args) argv.push(...c.invoke.args);\n if (c.invoke.flags) argv.push(...c.invoke.flags);\n\n const child = spawnSync(process.execPath, [options.binary, ...argv], {\n cwd: scope,\n env: { ...process.env, ...options.env, ...setupEnv },\n encoding: 'utf8',\n });\n\n const stdout = child.stdout ?? '';\n const stderr = child.stderr ?? '';\n const exitCode = child.status ?? 0;\n\n const assertions = c.assertions.map((a) =>\n evaluateAssertion(a, {\n exitCode,\n stdout,\n stderr,\n scope,\n specRoot: options.specRoot,\n fixturesRoot,\n }),\n );\n const passed = assertions.every((a) => a.ok);\n\n return { caseId: c.id, passed, exitCode, stdout, stderr, assertions };\n } finally {\n rmSync(scope, { recursive: true, force: true });\n }\n}\n\n/**\n * Phase 1 of `runConformanceCase`, replay every `setup.priorScans`\n * step in order. Each step replaces every non-`.skill-map/` directory\n * with the named fixture, then runs `sm scan` so the snapshot persists\n * into the scope DB. The scope DB survives across steps (we never\n * delete `.skill-map/`).\n *\n * Returns `null` on success (caller continues) or a `IRunCaseResult`\n * with a single `priorScan` failure assertion (caller returns it\n * unchanged).\n */\n// Per-step replay: replace fixture, spawn `sm scan`, check exit. The\n// failure-result construction is verbose because it carries every\n// stream the caller reports back.\n// eslint-disable-next-line complexity\nfunction runPriorScansSetup(\n c: IConformanceCase,\n options: IRunCaseOptions,\n scope: string,\n fixturesRoot: string,\n setupEnv: NodeJS.ProcessEnv,\n): IRunCaseResult | null {\n for (const step of c.setup?.priorScans ?? []) {\n replaceFixture(scope, fixturesRoot, step.fixture);\n const stepArgv = ['scan', ...(step.flags ?? [])];\n const stepChild = spawnSync(process.execPath, [options.binary, ...stepArgv], {\n cwd: scope,\n env: { ...process.env, ...options.env, ...setupEnv },\n encoding: 'utf8',\n });\n if ((stepChild.status ?? 0) !== 0) {\n return {\n caseId: c.id,\n passed: false,\n exitCode: stepChild.status ?? 0,\n stdout: stepChild.stdout ?? '',\n stderr: stepChild.stderr ?? '',\n assertions: [\n {\n ok: false,\n type: 'priorScan',\n reason: tx(CONFORMANCE_RUNNER_TEXTS.priorScanFailed, {\n fixture: step.fixture,\n exit: stepChild.status ?? 0,\n stderr: stepChild.stderr ?? '',\n }),\n },\n ],\n };\n }\n }\n return null;\n}\n\n/**\n * Replace every top-level entry in `scope` EXCEPT `.skill-map/` (which\n * holds the kernel DB and persists across staging steps), then copy\n * the fixture's contents on top. Used by `priorScans` and the main\n * fixture phase to swap Provider content while keeping the DB stable.\n *\n * `fixturesRoot` is the absolute path to the `fixtures/` directory of\n * the conformance suite hosting the case (spec-owned for kernel cases,\n * Provider-owned for Provider cases, see `IRunCaseOptions.fixturesRoot`).\n */\nfunction replaceFixture(scope: string, fixturesRoot: string, fixture: string): void {\n assertContained(fixturesRoot, fixture, 'fixture');\n for (const entry of readdirSync(scope)) {\n if (entry === KERNEL_SKILL_MAP_DIR) continue;\n rmSync(join(scope, entry), { recursive: true, force: true });\n }\n const src = join(fixturesRoot, fixture);\n cpSync(src, scope, { recursive: true });\n}\n\n/**\n * Reject case-supplied path strings that escape the directory tree they\n * are anchored to. A hostile case JSON would otherwise be able to copy\n * arbitrary filesystem content into the tmp scope (`fixture: \"../..\"`)\n * or read files outside the conformance sandbox via `file-exists` /\n * `file-contains-verbatim` assertions.\n */\nfunction assertContained(root: string, rel: string, label: string): void {\n if (isAbsolute(rel)) {\n throw new Error(\n tx(CONFORMANCE_RUNNER_TEXTS.pathMustBeRelative, { label, path: rel, anchor: root }),\n );\n }\n const abs = resolve(root, rel);\n const r = relative(root, abs);\n if (r.startsWith('..') || isAbsolute(r)) {\n throw new Error(\n tx(CONFORMANCE_RUNNER_TEXTS.pathEscapesAnchor, { label, path: rel, anchor: root }),\n );\n }\n}\n\ninterface IAssertionContext {\n exitCode: number;\n stdout: string;\n stderr: string;\n scope: string;\n specRoot: string;\n fixturesRoot: string;\n}\n\n// Switch over assertion types (`exit-code` / `stdout-matches` /\n// `file-exists` / `file-contains-verbatim` / `file-matches-schema` /\n// `stderr-matches` / `json-path`) with one branch per type. Splitting\n// per type would scatter the discriminated-union dispatch.\n// eslint-disable-next-line complexity\nfunction evaluateAssertion(a: IAssertion, ctx: IAssertionContext): IAssertionResult {\n switch (a.type) {\n case 'exit-code':\n return ctx.exitCode === a.value\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.expectedExitCode, {\n expected: a.value,\n actual: ctx.exitCode,\n }),\n };\n case 'json-path':\n return evaluateJsonPath(a, ctx);\n case 'file-exists': {\n try {\n assertContained(ctx.scope, a.path, 'file-exists');\n } catch (err) {\n return { ok: false, type: a.type, reason: formatErrorMessage(err) };\n }\n const abs = resolve(ctx.scope, a.path);\n return existsSync(abs)\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.fileNotFound, { path: a.path }),\n };\n }\n case 'file-contains-verbatim': {\n try {\n assertContained(ctx.fixturesRoot, a.fixture, 'file-contains-verbatim/fixture');\n assertContained(ctx.scope, a.path, 'file-contains-verbatim/path');\n } catch (err) {\n return { ok: false, type: a.type, reason: formatErrorMessage(err) };\n }\n const fixturePath = join(ctx.fixturesRoot, a.fixture);\n const targetPath = resolve(ctx.scope, a.path);\n if (!existsSync(targetPath)) {\n return {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.targetNotFound, { path: a.path }),\n };\n }\n const needle = readFileSync(fixturePath);\n const haystack = readFileSync(targetPath);\n return haystack.includes(needle)\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.targetMissingFixture, { fixture: a.fixture }),\n };\n }\n case 'file-matches-schema':\n return {\n ok: false,\n type: a.type,\n reason: CONFORMANCE_RUNNER_TEXTS.fileMatchesSchemaUnimplemented,\n };\n case 'stderr-matches': {\n const re = new RegExp(a.pattern);\n return re.test(ctx.stderr)\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.stderrDidNotMatch, { pattern: a.pattern }),\n };\n }\n }\n}\n\n/**\n * Minimal JSONPath evaluator, supports only the subset used by the stub\n * conformance suite: `$.foo`, `$.foo.bar`, `$.foo.length`, `$[0]`.\n * The full RFC 9535 implementation lands with Step 2.\n */\nfunction evaluateJsonPath(\n a: Extract<IAssertion, { type: 'json-path' }>,\n ctx: IAssertionContext,\n): IAssertionResult {\n let doc: unknown;\n try {\n doc = JSON.parse(ctx.stdout);\n } catch (err) {\n return {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.stdoutNotJson, { message: formatErrorMessage(err) }),\n };\n }\n\n const segments = parsePath(a.path);\n if (!segments) {\n return {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.unsupportedJsonPath, { path: a.path }),\n };\n }\n\n const walked = traverseJsonPath(doc, segments, a.path);\n if (!walked.ok) return { ok: false, type: a.type, reason: walked.reason };\n\n return applyJsonPathComparator(a, walked.value);\n}\n\n/**\n * Walk a parsed JSONPath segment list against a JSON document. Returns\n * the resolved value or a structured failure (caller maps to\n * `IAssertionResult`). Pure, no IO, no shared state.\n */\nfunction traverseJsonPath(\n doc: unknown,\n segments: Array<string | number>,\n path: string,\n): { ok: true; value: unknown } | { ok: false; reason: string } {\n let current: unknown = doc;\n for (const seg of segments) {\n if (typeof seg === 'number') {\n if (!Array.isArray(current)) {\n return { ok: false, reason: tx(CONFORMANCE_RUNNER_TEXTS.expectedArrayAtPath, { path }) };\n }\n current = current[seg];\n } else if (seg === 'length' && Array.isArray(current)) {\n current = current.length;\n } else if (typeof current === 'object' && current !== null) {\n current = (current as Record<string, unknown>)[seg];\n } else {\n return {\n ok: false,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.cannotTraverseSegment, {\n type: typeof current,\n segment: String(seg),\n }),\n };\n }\n }\n return { ok: true, value: current };\n}\n\n/**\n * Apply the comparator clause (`equals` / `greaterThan` / `lessThan` /\n * `matches`) of a `json-path` assertion against the value resolved at\n * the requested path. Returns the final `IAssertionResult` directly.\n *\n * Complexity from the four parallel comparator branches; splitting into\n * one helper per comparator would be ceremony.\n */\n// eslint-disable-next-line complexity\nfunction applyJsonPathComparator(\n a: Extract<IAssertion, { type: 'json-path' }>,\n current: unknown,\n): IAssertionResult {\n if ('equals' in a && a.equals !== undefined) {\n return deepEqual(current, a.equals)\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.jsonPathEqualsMismatch, {\n path: a.path,\n actual: JSON.stringify(current),\n expected: JSON.stringify(a.equals),\n }),\n };\n }\n if ('greaterThan' in a && typeof a.greaterThan === 'number') {\n return typeof current === 'number' && current > a.greaterThan\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.jsonPathNotGreaterThan, {\n path: a.path,\n value: a.greaterThan,\n }),\n };\n }\n if ('lessThan' in a && typeof a.lessThan === 'number') {\n return typeof current === 'number' && current < a.lessThan\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.jsonPathNotLessThan, {\n path: a.path,\n value: a.lessThan,\n }),\n };\n }\n if ('matches' in a && typeof a.matches === 'string') {\n const re = new RegExp(a.matches);\n return typeof current === 'string' && re.test(current)\n ? { ok: true, type: a.type }\n : {\n ok: false,\n type: a.type,\n reason: tx(CONFORMANCE_RUNNER_TEXTS.jsonPathDidNotMatch, {\n path: a.path,\n pattern: a.matches,\n }),\n };\n }\n return { ok: false, type: a.type, reason: CONFORMANCE_RUNNER_TEXTS.jsonPathNoComparator };\n}\n\nfunction parsePath(path: string): Array<string | number> | null {\n if (!path.startsWith('$')) return null;\n const tail = path.slice(1);\n const segments: Array<string | number> = [];\n const re = /\\.([a-zA-Z_][a-zA-Z0-9_-]*)|\\[(\\d+)\\]/g;\n let lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = re.exec(tail)) !== null) {\n if (match.index !== lastIndex) return null;\n if (match[1] !== undefined) segments.push(match[1]);\n else if (match[2] !== undefined) segments.push(Number.parseInt(match[2], 10));\n lastIndex = re.lastIndex;\n }\n if (lastIndex !== tail.length) return null;\n return segments;\n}\n\n// Structural equality over arbitrary JSON values: primitive / null /\n// array / object branches plus per-branch length / key-set checks.\n// The branching IS the type table. Per `context/lint.md` category 7\n// (recursive type-discriminator walkers).\n// eslint-disable-next-line complexity\nfunction deepEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== typeof b) return false;\n if (a && b && typeof a === 'object' && typeof b === 'object') {\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n const ak = Object.keys(a as object);\n const bk = Object.keys(b as object);\n if (ak.length !== bk.length) return false;\n for (const k of ak) {\n if (\n !deepEqual(\n (a as Record<string, unknown>)[k],\n (b as Record<string, unknown>)[k],\n )\n )\n {return false;}\n }\n return true;\n }\n return false;\n}\n\n/** Verifies the spec root looks sane (contains `index.json`). */\nexport function assertSpecRoot(specRoot: string): void {\n const indexPath = join(specRoot, 'index.json');\n if (!existsSync(indexPath) || !statSync(indexPath).isFile()) {\n throw new Error(tx(CONFORMANCE_RUNNER_TEXTS.specRootMissingIndex, { specRoot }));\n }\n}\n","/**\n * Kernel-accessible counterpart of `cli/util/error-reporter.ts`'s\n * `formatErrorMessage`. The CLI helper now re-exports from here so the\n * historic CLI import path keeps working while kernel + BFF callers can\n * consume it directly without crossing the layering boundary.\n *\n * Kept deliberately tiny, same shape as the original CLI helper. The\n * surface grows (e.g. a `--verbose` stack mode, JSON envelope) only\n * when a concrete need surfaces.\n */\n\n/**\n * Compact error → string conversion.\n *\n * - `Error` → `err.message` verbatim. Callers wrap with their own\n * verb-specific context line via `tx(*_TEXTS.x, { message })` so\n * error catalogues stay greppable.\n * - Anything else → `String(value)`. Catches the rare throw-a-string\n * / throw-an-object path without exploding on `null`\n * (`String(null)` = `'null'`).\n */\nexport function formatErrorMessage(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n","/**\n * Kernel-side helpers that compose the layered-config file paths from\n * the canonical `SKILL_MAP_DIR` literal.\n *\n * `SKILL_MAP_DIR` is exported once from `core/paths/db-path.ts` and\n * re-exported here as `KERNEL_SKILL_MAP_DIR` so kernel-side callers\n * keep their historic name without the literal living in two files\n * (audit m3, one literal home, no `grep \"'\\.skill-map'\"` sweep\n * invariant to maintain across kernel + CLI).\n */\n\nimport { join } from 'node:path';\n\nimport { SKILL_MAP_DIR } from '../../core/paths/db-path.js';\n\n/**\n * Per-scope directory the kernel + CLI both store state under (DB file,\n * settings, plugins, etc.). Re-exported from `core/paths/db-path.ts`\n * the single canonical source for the literal.\n */\nexport const KERNEL_SKILL_MAP_DIR = SKILL_MAP_DIR;\n\nconst SETTINGS_FILENAME = 'settings.json';\nconst LOCAL_SETTINGS_FILENAME = 'settings.local.json';\n\n/**\n * `<scopeRoot>/.skill-map/settings.json`, the canonical layered-config\n * file. Used by `kernel/config/loader.ts` to compose its user / project\n * walk.\n */\nexport function kernelSettingsPath(scopeRoot: string): string {\n return join(scopeRoot, KERNEL_SKILL_MAP_DIR, SETTINGS_FILENAME);\n}\n\n/**\n * `<scopeRoot>/.skill-map/settings.local.json`, the local-overrides\n * companion to `settings.json`. Used by the same loader walk.\n */\nexport function kernelLocalSettingsPath(scopeRoot: string): string {\n return join(scopeRoot, KERNEL_SKILL_MAP_DIR, LOCAL_SETTINGS_FILENAME);\n}\n","/**\n * Pure path helpers for the on-disk skill-map scope layout. Moved out\n * of `cli/util/db-path.ts` so the BFF (`src/server/`) can consume them\n * without reaching into the CLI layer. The CLI-only siblings\n * (`assertDbExists`, `requireDbOrExit`, they take a stderr stream and\n * an `ExitCode`) stay in `cli/util/db-path.ts` and re-export the\n * primitives from here.\n *\n * Scope is always project-local: every helper resolves under\n * `<cwd>/.skill-map/`. There is no `-g/--global` flag and no implicit\n * `$HOME` read. The single documented exception is the per-user\n * settings file (`~/.skill-map/settings.json`), which lives in\n * `cli/util/user-settings-store.ts` and does NOT use any helper from\n * this module. See `spec/cli-contract.md` §Scope is always\n * project-local.\n *\n * `--db <path>` remains as an explicit escape hatch (mirrored on\n * `SmCommand` and threaded through `resolveDbPath` below).\n */\n\nimport { join, resolve } from 'node:path';\n\nimport type { IRuntimeContext } from '../runtime/runtime-context.js';\n\n/**\n * Per-scope directory the CLI stores its state under (DB file, settings,\n * plugins, etc.). Resolved against the project cwd\n * (`<cwd>/.skill-map/`). Exported so write-side scaffolding (`sm init`)\n * and other helpers can reuse the convention without duplicating the\n * literal.\n */\nexport const SKILL_MAP_DIR = '.skill-map';\n\nconst DB_FILENAME = 'skill-map.db';\nconst JOBS_DIRNAME = 'jobs';\nconst PLUGINS_DIRNAME = 'plugins';\nconst SETTINGS_FILENAME = 'settings.json';\nconst LOCAL_SETTINGS_FILENAME = 'settings.local.json';\nconst IGNORE_FILENAME = '.skillmapignore';\n\n/**\n * Single source of truth for the relative DB path inside the project\n * scope directory (`.skill-map/skill-map.db`).\n */\nconst DEFAULT_DB_REL = `${SKILL_MAP_DIR}/${DB_FILENAME}`;\n\n/**\n * Entries `sm init` appends to the project `.gitignore`. Centralised\n * here (instead of the verb file) so the literals live alongside their\n * filename constants and the verb consumes them as a frozen list.\n */\nexport const GITIGNORE_ENTRIES: readonly string[] = [\n `${SKILL_MAP_DIR}/${LOCAL_SETTINGS_FILENAME}`,\n `${SKILL_MAP_DIR}/${DB_FILENAME}`,\n];\n\n/**\n * Inputs for `resolveDbPath`. Extends `IRuntimeContext` so the helper\n * never reads `process.cwd()` directly, every caller threads the\n * runtime context (mandatory) alongside the `--db` override.\n * Pattern: `resolveDbPath({ db, ...defaultRuntimeContext() })`.\n */\nexport interface IDbLocationOptions extends IRuntimeContext {\n db: string | undefined;\n}\n\n/**\n * Resolve the DB file path from command-line options.\n *\n * Precedence: explicit `--db <path>` > project default\n * (`<cwd>/.skill-map/skill-map.db`).\n *\n * Always returns an absolute path. Does NOT verify existence, pair\n * with `assertDbExists` for read-side verbs.\n */\nexport function resolveDbPath(options: IDbLocationOptions): string {\n if (options.db) return resolve(options.db);\n return resolve(options.cwd, DEFAULT_DB_REL);\n}\n\n/**\n * Default project DB path (`<cwd>/.skill-map/skill-map.db`). Same\n * effect as `resolveDbPath({ db: undefined, ...ctx })`; this helper\n * is the cheaper and more explicit route for call sites that have no\n * `--db` flag to honour (`sm scan`, `sm refresh`, `sm watch`).\n */\nexport function defaultProjectDbPath(ctx: IRuntimeContext): string {\n return resolve(ctx.cwd, DEFAULT_DB_REL);\n}\n\n/**\n * Default project jobs directory (`<cwd>/.skill-map/jobs`). Used by the\n * `sm job prune` orphan-files pass and any other call site that needs\n * the project-scoped jobs spool.\n */\nexport function defaultProjectJobsDir(ctx: IRuntimeContext): string {\n return resolve(ctx.cwd, SKILL_MAP_DIR, JOBS_DIRNAME);\n}\n\n/**\n * Default project plugins directory (`<cwd>/.skill-map/plugins`). Sole\n * discovery root for drop-in plugins; the `--plugin-dir <path>`\n * override (CLI verbs under `sm plugins …`) replaces it when the user\n * wants to point at a sibling tree.\n */\nexport function defaultProjectPluginsDir(ctx: IRuntimeContext): string {\n return resolve(ctx.cwd, SKILL_MAP_DIR, PLUGINS_DIRNAME);\n}\n\n/**\n * Default DB path under an arbitrary scope root\n * (`<scopeRoot>/.skill-map/skill-map.db`). Companion to\n * `defaultProjectDbPath` for callers that already resolved the scope\n * root themselves (today: `sm init`).\n */\nexport function defaultDbPath(scopeRoot: string): string {\n return join(scopeRoot, SKILL_MAP_DIR, DB_FILENAME);\n}\n\n/**\n * Default settings file (`<scopeRoot>/.skill-map/settings.json`).\n */\nexport function defaultSettingsPath(scopeRoot: string): string {\n return join(scopeRoot, SKILL_MAP_DIR, SETTINGS_FILENAME);\n}\n\n/**\n * Default local-overrides settings file\n * (`<scopeRoot>/.skill-map/settings.local.json`).\n */\nexport function defaultLocalSettingsPath(scopeRoot: string): string {\n return join(scopeRoot, SKILL_MAP_DIR, LOCAL_SETTINGS_FILENAME);\n}\n\n/**\n * Default `.skillmapignore` file path\n * (`<scopeRoot>/.skillmapignore`). Sits at the scope root, NOT inside\n * `.skill-map/`, `sm scan` reads it from the same level as `package.json`\n * etc. so authors can keep ignore rules visible in the project tree.\n */\nexport function defaultIgnoreFilePath(scopeRoot: string): string {\n return join(scopeRoot, IGNORE_FILENAME);\n}\n","/**\n * `tx(template, vars)`, string interpolation for the project's text\n * tables (`*.texts.ts` files under `kernel/i18n/` and `cli/i18n/`).\n *\n * Templates use the `{{name}}` placeholder shape (Mustache / Handlebars\n * / Transloco compatible) so the same string tables drop into a real\n * i18n library on the day this project migrates.\n *\n * Contract:\n * - Every `{{name}}` token in `template` MUST have a matching key in\n * `vars`. A missing key throws, silent fallback would hide a\n * forgotten arg in a production build, which is the worst kind of\n * bug to chase down.\n * - Values can be `string | number`. `null` / `undefined` keys are\n * rejected; the caller is expected to coerce upstream (e.g. format\n * a missing path as `'(unknown)'` before passing).\n * - Whitespace inside the braces is tolerated (`{{ name }}`); the\n * parser strips it. This keeps long templates readable when wrapped\n * across multiple TS lines via `+`.\n * - Literal `{{` is not currently supported, no real text needs it.\n * Add escaping the day a template needs to render Handlebars-style\n * content.\n *\n * Plural / conditional logic does NOT live in the template. The caller\n * picks the correct template (e.g. `entries_singular` vs\n * `entries_plural`) or composes the variable value upstream and passes\n * the finished string. Keeping templates flat is the price for staying\n * Transloco-ready.\n */\n\nconst TOKEN_RE = /\\{\\{\\s*([A-Za-z][A-Za-z0-9_]*)\\s*\\}\\}/g;\n\nexport function tx(\n template: string,\n vars: Record<string, string | number> = {},\n): string {\n return template.replace(TOKEN_RE, (_match, name: string) => {\n if (!Object.prototype.hasOwnProperty.call(vars, name)) {\n throw new Error(\n `tx: missing variable \"${name}\" for template \"${template.slice(0, 80)}${template.length > 80 ? '…' : ''}\"`,\n );\n }\n const value = vars[name];\n if (value === null || value === undefined) {\n throw new Error(\n `tx: variable \"${name}\" is null/undefined for template \"${template.slice(0, 80)}${template.length > 80 ? '…' : ''}\"`,\n );\n }\n return String(value);\n });\n}\n","/**\n * Strings emitted by the conformance runner (`conformance/index.ts`).\n * Same `tx(template, vars)` convention as every other `*.texts.ts` peer.\n *\n * Reasons surface in `IAssertionResult.reason`, visible to anyone\n * reading the runner output (CI logs, `sm conformance run --json`).\n * Keeping them in the catalog unblocks a future Transloco migration and\n * keeps the wording in one place.\n */\n\nexport const CONFORMANCE_RUNNER_TEXTS = {\n priorScanFailed:\n 'setup.priorScans step `{{fixture}}` failed with exit {{exit}}: {{stderr}}',\n\n pathMustBeRelative:\n 'conformance: {{label}} path \"{{path}}\" must be relative to its anchor ({{anchor}})',\n\n pathEscapesAnchor:\n 'conformance: {{label}} path \"{{path}}\" escapes its anchor ({{anchor}})',\n\n expectedExitCode:\n 'expected exit {{expected}}, got {{actual}}',\n\n fileNotFound:\n 'file not found: {{path}}',\n\n targetNotFound:\n 'target not found: {{path}}',\n\n targetMissingFixture:\n 'target does not contain fixture {{fixture}} verbatim',\n\n fileMatchesSchemaUnimplemented:\n 'file-matches-schema not yet implemented (requires ajv; lands with Step 2)',\n\n stderrDidNotMatch:\n 'stderr did not match /{{pattern}}/',\n\n stdoutNotJson:\n 'stdout is not valid JSON: {{message}}',\n\n unsupportedJsonPath:\n 'unsupported jsonpath: {{path}}',\n\n expectedArrayAtPath:\n 'expected array at {{path}}',\n\n cannotTraverseSegment:\n \"cannot traverse {{type}} at segment '{{segment}}'\",\n\n jsonPathEqualsMismatch:\n '{{path}} = {{actual}}, expected {{expected}}',\n\n jsonPathNotGreaterThan:\n '{{path}} not > {{value}}',\n\n jsonPathNotLessThan:\n '{{path}} not < {{value}}',\n\n jsonPathDidNotMatch:\n '{{path}} did not match /{{pattern}}/',\n\n jsonPathNoComparator:\n 'no comparator on json-path assertion',\n\n specRootMissingIndex:\n 'spec root missing index.json at {{specRoot}}',\n} as const;\n"],"mappings":"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","names":["join","resolve","join","join","resolve"]}
|
package/dist/index.js
CHANGED
|
@@ -100,7 +100,7 @@ import cl100k_base from "js-tiktoken/ranks/cl100k_base";
|
|
|
100
100
|
// package.json
|
|
101
101
|
var package_default = {
|
|
102
102
|
name: "@skill-map/cli",
|
|
103
|
-
version: "0.
|
|
103
|
+
version: "0.28.0",
|
|
104
104
|
description: "skill-map reference implementation \u2014 kernel + CLI + adapters.",
|
|
105
105
|
license: "MIT",
|
|
106
106
|
type: "module",
|
|
@@ -388,6 +388,7 @@ var SCHEMA_FILES = {
|
|
|
388
388
|
"scan-result": "schemas/scan-result.schema.json",
|
|
389
389
|
"execution-record": "schemas/execution-record.schema.json",
|
|
390
390
|
"project-config": "schemas/project-config.schema.json",
|
|
391
|
+
"user-settings": "schemas/user-settings.schema.json",
|
|
391
392
|
"plugins-registry": "schemas/plugins-registry.schema.json",
|
|
392
393
|
job: "schemas/job.schema.json",
|
|
393
394
|
"report-base": "schemas/report-base.schema.json",
|
|
@@ -1707,7 +1708,8 @@ import { randomBytes } from "crypto";
|
|
|
1707
1708
|
import { dirname as dirname4 } from "path";
|
|
1708
1709
|
|
|
1709
1710
|
// core/config/helper.ts
|
|
1710
|
-
import {
|
|
1711
|
+
import { homedir as osHomedir } from "os";
|
|
1712
|
+
import { isAbsolute as isAbsolute2, join as join6, resolve as resolve8 } from "path";
|
|
1711
1713
|
|
|
1712
1714
|
// kernel/config/loader.ts
|
|
1713
1715
|
import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
|
|
@@ -2289,7 +2291,6 @@ function buildScanSetup(options) {
|
|
|
2289
2291
|
priorIndex,
|
|
2290
2292
|
priorExtractorRuns: options.priorExtractorRuns,
|
|
2291
2293
|
providerFrontmatter,
|
|
2292
|
-
scope: options.scope ?? "project",
|
|
2293
2294
|
strict: options.strict === true,
|
|
2294
2295
|
enableCache: options.enableCache === true
|
|
2295
2296
|
};
|
|
@@ -2332,7 +2333,6 @@ function buildScanReturn(walked, issues, renameOps, stats, options, setup) {
|
|
|
2332
2333
|
result: {
|
|
2333
2334
|
schemaVersion: 1,
|
|
2334
2335
|
scannedAt: setup.scannedAt,
|
|
2335
|
-
scope: setup.scope,
|
|
2336
2336
|
roots: options.roots,
|
|
2337
2337
|
providers: setup.exts.providers.map((a) => a.id),
|
|
2338
2338
|
scannedBy: SCANNED_BY,
|