@remnic/core 1.0.3 → 1.1.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.
Files changed (148) hide show
  1. package/dist/abort-error.d.ts +32 -0
  2. package/dist/abort-error.js +11 -0
  3. package/dist/access-cli.js +41 -40
  4. package/dist/access-cli.js.map +1 -1
  5. package/dist/access-http.d.ts +3 -2
  6. package/dist/access-http.js +11 -11
  7. package/dist/access-mcp.d.ts +3 -2
  8. package/dist/access-mcp.js +7 -7
  9. package/dist/access-schema.d.ts +16 -16
  10. package/dist/access-service-HmO1Trrx.d.ts +732 -0
  11. package/dist/access-service.d.ts +11 -658
  12. package/dist/access-service.js +9 -9
  13. package/dist/bootstrap.d.ts +1 -1
  14. package/dist/briefing.js +6 -6
  15. package/dist/calibration.js +3 -3
  16. package/dist/causal-consolidation.js +10 -10
  17. package/dist/{chunk-PMB3WGDL.js → chunk-37UIFYWO.js} +64 -3
  18. package/dist/chunk-37UIFYWO.js.map +1 -0
  19. package/dist/{chunk-X4WESCKA.js → chunk-3QHL5ABG.js} +5 -5
  20. package/dist/{chunk-2VFW5K5U.js → chunk-3SV6CQHO.js} +10 -8
  21. package/dist/chunk-3SV6CQHO.js.map +1 -0
  22. package/dist/{chunk-U2IQTSBY.js → chunk-3WHVNEN7.js} +1 -1
  23. package/dist/chunk-3WHVNEN7.js.map +1 -0
  24. package/dist/{chunk-QKAH5B6E.js → chunk-44ICJRF3.js} +97 -9
  25. package/dist/chunk-44ICJRF3.js.map +1 -0
  26. package/dist/{chunk-S4LX5EBI.js → chunk-47UU5PU2.js} +48 -9
  27. package/dist/chunk-47UU5PU2.js.map +1 -0
  28. package/dist/{chunk-ECKDIK5F.js → chunk-4LACOVZX.js} +2 -2
  29. package/dist/{chunk-AYPYCLR7.js → chunk-6LX5ORAS.js} +2 -2
  30. package/dist/{chunk-GJQPH5G3.js → chunk-6UJ47TVX.js} +2 -2
  31. package/dist/{chunk-KWP7T3DP.js → chunk-7ECD5ATE.js} +2 -2
  32. package/dist/{chunk-74JR4N5J.js → chunk-7WQ6SLIE.js} +2 -2
  33. package/dist/{chunk-7PA4OZEU.js → chunk-BLKTA7MM.js} +6 -14
  34. package/dist/chunk-BLKTA7MM.js.map +1 -0
  35. package/dist/{chunk-XMGSSBFX.js → chunk-DEPL3635.js} +172 -100
  36. package/dist/chunk-DEPL3635.js.map +1 -0
  37. package/dist/{chunk-JROGC36Y.js → chunk-DHHP2Z4X.js} +2 -2
  38. package/dist/{chunk-BKQJBXXX.js → chunk-GGD5W7TB.js} +2 -2
  39. package/dist/chunk-GGD5W7TB.js.map +1 -0
  40. package/dist/{chunk-POMSFKTB.js → chunk-GV6NLQ4X.js} +14 -14
  41. package/dist/{chunk-AAI7JARD.js → chunk-HMDCOMYU.js} +8 -11
  42. package/dist/chunk-HMDCOMYU.js.map +1 -0
  43. package/dist/{chunk-POBPGDWI.js → chunk-ITRLGI2T.js} +2 -2
  44. package/dist/{chunk-MYQWXITD.js → chunk-JIU55F3X.js} +2 -2
  45. package/dist/{chunk-UPMD5XND.js → chunk-JL2PU6AI.js} +16 -5
  46. package/dist/chunk-JL2PU6AI.js.map +1 -0
  47. package/dist/{chunk-OJFGVJS6.js → chunk-MBJHSA7F.js} +65 -9
  48. package/dist/chunk-MBJHSA7F.js.map +1 -0
  49. package/dist/{chunk-V7XCAHIB.js → chunk-MVTHXUBX.js} +46 -23
  50. package/dist/chunk-MVTHXUBX.js.map +1 -0
  51. package/dist/{chunk-BTY5RRRF.js → chunk-N42IWANG.js} +5 -5
  52. package/dist/{chunk-NSB3WSYS.js → chunk-NQEVYWX6.js} +74 -3
  53. package/dist/chunk-NQEVYWX6.js.map +1 -0
  54. package/dist/chunk-OIT5QGG4.js +80 -0
  55. package/dist/chunk-OIT5QGG4.js.map +1 -0
  56. package/dist/chunk-PVGDJXVK.js +21 -0
  57. package/dist/chunk-PVGDJXVK.js.map +1 -0
  58. package/dist/{chunk-RCICHSHL.js → chunk-SYUK3VLY.js} +2 -2
  59. package/dist/{chunk-YFYL2SIJ.js → chunk-WBSAYXVI.js} +127 -39
  60. package/dist/chunk-WBSAYXVI.js.map +1 -0
  61. package/dist/{chunk-KEG4GNGI.js → chunk-XZ2TIKGC.js} +38 -8
  62. package/dist/chunk-XZ2TIKGC.js.map +1 -0
  63. package/dist/chunk-Y4FHOFJ2.js +140 -0
  64. package/dist/chunk-Y4FHOFJ2.js.map +1 -0
  65. package/dist/chunk-YNB73F22.js +137 -0
  66. package/dist/chunk-YNB73F22.js.map +1 -0
  67. package/dist/{chunk-HITJFT7E.js → chunk-ZVBB3T7V.js} +10 -5
  68. package/dist/chunk-ZVBB3T7V.js.map +1 -0
  69. package/dist/{cli-DwIBnp2g.d.ts → cli-BneVIEvh.d.ts} +2 -2
  70. package/dist/cli.d.ts +4 -3
  71. package/dist/cli.js +25 -24
  72. package/dist/config.js +1 -1
  73. package/dist/contradiction-review-WIUBAR52.js +21 -0
  74. package/dist/contradiction-review-WIUBAR52.js.map +1 -0
  75. package/dist/contradiction-scan-GR33PONM.js +376 -0
  76. package/dist/contradiction-scan-GR33PONM.js.map +1 -0
  77. package/dist/direct-answer-wiring.d.ts +77 -0
  78. package/dist/direct-answer-wiring.js +75 -0
  79. package/dist/direct-answer-wiring.js.map +1 -0
  80. package/dist/direct-answer.d.ts +106 -0
  81. package/dist/direct-answer.js +10 -0
  82. package/dist/direct-answer.js.map +1 -0
  83. package/dist/{engine-X7X3AAG3.js → engine-5TIQBYZR.js} +7 -7
  84. package/dist/engine-5TIQBYZR.js.map +1 -0
  85. package/dist/entity-retrieval.js +6 -6
  86. package/dist/explicit-capture.d.ts +1 -1
  87. package/dist/extraction.js +6 -6
  88. package/dist/fallback-llm.d.ts +11 -2
  89. package/dist/fallback-llm.js +3 -3
  90. package/dist/harmonic-retrieval.js +2 -1
  91. package/dist/index.d.ts +10 -124
  92. package/dist/index.js +74 -137
  93. package/dist/index.js.map +1 -1
  94. package/dist/intent.js +1 -1
  95. package/dist/local-llm.d.ts +10 -3
  96. package/dist/local-llm.js +1 -1
  97. package/dist/operator-toolkit.js +12 -11
  98. package/dist/{orchestrator-B9kwlCep.d.ts → orchestrator-DRYA6_lW.d.ts} +21 -2
  99. package/dist/orchestrator.d.ts +1 -1
  100. package/dist/orchestrator.js +36 -35
  101. package/dist/qmd.js +2 -1
  102. package/dist/recall-state.d.ts +28 -1
  103. package/dist/recall-state.js +1 -1
  104. package/dist/resolution-QBTDHTG7.js +100 -0
  105. package/dist/resolution-QBTDHTG7.js.map +1 -0
  106. package/dist/resolve-provider-secret.d.ts +24 -1
  107. package/dist/resolve-provider-secret.js +3 -1
  108. package/dist/resume-bundles.js +3 -3
  109. package/dist/schemas.d.ts +14 -14
  110. package/dist/semantic-consolidation.js +6 -6
  111. package/dist/semantic-rule-promotion.js +6 -6
  112. package/dist/semantic-rule-verifier.js +6 -6
  113. package/dist/storage.js +5 -5
  114. package/dist/summarizer.js +5 -5
  115. package/dist/types-DJhqDJUV.d.ts +50 -0
  116. package/dist/types.d.ts +34 -2
  117. package/dist/types.js +1 -1
  118. package/dist/verified-recall.js +6 -6
  119. package/package.json +1 -1
  120. package/dist/chunk-2VFW5K5U.js.map +0 -1
  121. package/dist/chunk-7PA4OZEU.js.map +0 -1
  122. package/dist/chunk-AAI7JARD.js.map +0 -1
  123. package/dist/chunk-BKQJBXXX.js.map +0 -1
  124. package/dist/chunk-HITJFT7E.js.map +0 -1
  125. package/dist/chunk-KEG4GNGI.js.map +0 -1
  126. package/dist/chunk-NSB3WSYS.js.map +0 -1
  127. package/dist/chunk-OJFGVJS6.js.map +0 -1
  128. package/dist/chunk-PMB3WGDL.js.map +0 -1
  129. package/dist/chunk-QKAH5B6E.js.map +0 -1
  130. package/dist/chunk-S4LX5EBI.js.map +0 -1
  131. package/dist/chunk-U2IQTSBY.js.map +0 -1
  132. package/dist/chunk-UPMD5XND.js.map +0 -1
  133. package/dist/chunk-V7XCAHIB.js.map +0 -1
  134. package/dist/chunk-XMGSSBFX.js.map +0 -1
  135. package/dist/chunk-YFYL2SIJ.js.map +0 -1
  136. /package/dist/{engine-X7X3AAG3.js.map → abort-error.js.map} +0 -0
  137. /package/dist/{chunk-X4WESCKA.js.map → chunk-3QHL5ABG.js.map} +0 -0
  138. /package/dist/{chunk-ECKDIK5F.js.map → chunk-4LACOVZX.js.map} +0 -0
  139. /package/dist/{chunk-AYPYCLR7.js.map → chunk-6LX5ORAS.js.map} +0 -0
  140. /package/dist/{chunk-GJQPH5G3.js.map → chunk-6UJ47TVX.js.map} +0 -0
  141. /package/dist/{chunk-KWP7T3DP.js.map → chunk-7ECD5ATE.js.map} +0 -0
  142. /package/dist/{chunk-74JR4N5J.js.map → chunk-7WQ6SLIE.js.map} +0 -0
  143. /package/dist/{chunk-JROGC36Y.js.map → chunk-DHHP2Z4X.js.map} +0 -0
  144. /package/dist/{chunk-POMSFKTB.js.map → chunk-GV6NLQ4X.js.map} +0 -0
  145. /package/dist/{chunk-POBPGDWI.js.map → chunk-ITRLGI2T.js.map} +0 -0
  146. /package/dist/{chunk-MYQWXITD.js.map → chunk-JIU55F3X.js.map} +0 -0
  147. /package/dist/{chunk-BTY5RRRF.js.map → chunk-N42IWANG.js.map} +0 -0
  148. /package/dist/{chunk-RCICHSHL.js.map → chunk-SYUK3VLY.js.map} +0 -0
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/binary-lifecycle/types.ts","../src/binary-lifecycle/backend.ts","../src/binary-lifecycle/scanner.ts","../src/binary-lifecycle/manifest.ts","../src/binary-lifecycle/pipeline.ts","../src/projection/index.ts","../src/utils/category-dir.ts","../src/onboarding/index.ts","../src/curation/index.ts","../src/dedup/index.ts","../src/review/index.ts","../src/sync/index.ts","../src/connectors/index.ts","../src/connectors/codex-marketplace.ts","../src/spaces/index.ts","../src/memory-extension/shared-instructions.ts","../src/memory-extension/codex-publisher.ts","../src/memory-extension/claude-code-publisher.ts","../src/memory-extension/hermes-publisher.ts","../src/memory-extension/index.ts","../src/taxonomy/default-taxonomy.ts","../src/taxonomy/resolver.ts","../src/taxonomy/resolver-doc-generator.ts","../src/taxonomy/taxonomy-loader.ts","../src/enrichment/types.ts","../src/enrichment/provider-registry.ts","../src/enrichment/web-search-provider.ts","../src/enrichment/pipeline.ts","../src/enrichment/audit.ts"],"sourcesContent":["/**\n * Binary file lifecycle management types.\n *\n * Defines the configuration, manifest, and record structures for the\n * three-stage binary lifecycle pipeline: mirror, redirect, clean.\n */\n\nexport interface BinaryLifecycleConfig {\n /** Master toggle. Default: false. */\n enabled: boolean;\n /** Days after mirror before local copy is eligible for cleanup. Default: 7. */\n gracePeriodDays: number;\n /** Files larger than this are skipped during scan. Default: 50 MB. */\n maxBinarySizeBytes: number;\n /** Glob patterns for binary file types to manage. */\n scanPatterns: string[];\n /** Backend configuration for binary storage. */\n backend: BinaryStorageBackendConfig;\n}\n\nexport interface BinaryStorageBackendConfig {\n /** Backend type. \"filesystem\" copies to a local directory. \"none\" is a no-op (dry-run/testing). */\n type: \"filesystem\" | \"s3\" | \"none\";\n /** Destination directory for the filesystem backend. */\n basePath?: string;\n /** S3 bucket name (future). */\n s3Bucket?: string;\n /** S3 region (future). */\n s3Region?: string;\n /** S3 key prefix (future). */\n s3Prefix?: string;\n}\n\nexport type BinaryAssetStatus =\n | \"pending\"\n | \"mirrored\"\n | \"redirected\"\n | \"cleaned\"\n | \"error\";\n\nexport interface BinaryAssetRecord {\n /** Relative path from memoryDir to the original file. */\n originalPath: string;\n /** Path (or URL) in the backend storage. */\n mirroredPath: string;\n /** SHA-256 hex digest of file content. */\n contentHash: string;\n /** File size in bytes. */\n sizeBytes: number;\n /** MIME type (e.g. \"image/png\"). */\n mimeType: string;\n /** ISO 8601 timestamp when the file was mirrored. */\n mirroredAt: string;\n /** ISO 8601 timestamp when markdown references were rewritten. */\n redirectedAt?: string;\n /** ISO 8601 timestamp when the local copy was deleted. */\n cleanedAt?: string;\n /** Current lifecycle status. */\n status: BinaryAssetStatus;\n}\n\nexport interface BinaryLifecycleManifest {\n version: 1;\n assets: BinaryAssetRecord[];\n lastScanAt?: string;\n}\n\nexport interface PipelineResult {\n scanned: number;\n mirrored: number;\n redirected: number;\n cleaned: number;\n errors: string[];\n dryRun: boolean;\n}\n\nexport const DEFAULT_SCAN_PATTERNS = [\n \"*.png\",\n \"*.jpg\",\n \"*.jpeg\",\n \"*.gif\",\n \"*.pdf\",\n \"*.mp3\",\n \"*.mp4\",\n \"*.wav\",\n];\n\nexport const DEFAULT_MAX_BINARY_SIZE_BYTES = 50 * 1024 * 1024; // 50 MB\nexport const DEFAULT_GRACE_PERIOD_DAYS = 7;\n","/**\n * Binary storage backend interface and implementations.\n *\n * Backends handle the actual persistence of binary files to an external\n * location. The pipeline calls upload/exists/delete through this interface\n * so swapping storage providers requires no pipeline changes.\n */\n\nimport fs from \"node:fs\";\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { BinaryStorageBackendConfig } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Interface\n// ---------------------------------------------------------------------------\n\nexport interface BinaryStorageBackend {\n /** Discriminator for the backend type. */\n readonly type: string;\n /**\n * Upload a local file to the backend.\n * @returns The backend path or URL where the file was stored.\n */\n upload(localPath: string, remotePath: string): Promise<string>;\n /** Check whether a remote path already exists in the backend. */\n exists(remotePath: string): Promise<boolean>;\n /** Delete a file from the backend. */\n delete(remotePath: string): Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// Filesystem backend\n// ---------------------------------------------------------------------------\n\nexport class FilesystemBackend implements BinaryStorageBackend {\n readonly type = \"filesystem\";\n private readonly basePath: string;\n\n constructor(basePath: string) {\n if (!basePath || basePath.trim().length === 0) {\n throw new Error(\"FilesystemBackend requires a non-empty basePath\");\n }\n this.basePath = basePath;\n }\n\n async upload(localPath: string, remotePath: string): Promise<string> {\n const dest = path.join(this.basePath, remotePath);\n const destDir = path.dirname(dest);\n await fsp.mkdir(destDir, { recursive: true });\n await fsp.copyFile(localPath, dest);\n return dest;\n }\n\n async exists(remotePath: string): Promise<boolean> {\n const dest = path.join(this.basePath, remotePath);\n try {\n await fsp.access(dest, fs.constants.F_OK);\n return true;\n } catch {\n return false;\n }\n }\n\n async delete(remotePath: string): Promise<void> {\n const dest = path.join(this.basePath, remotePath);\n try {\n await fsp.unlink(dest);\n } catch (err: unknown) {\n // Ignore ENOENT (already deleted); rethrow everything else.\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// None backend (no-op, for dry-run / testing)\n// ---------------------------------------------------------------------------\n\nexport class NoneBackend implements BinaryStorageBackend {\n readonly type = \"none\";\n\n async upload(_localPath: string, remotePath: string): Promise<string> {\n return remotePath;\n }\n\n async exists(_remotePath: string): Promise<boolean> {\n return false;\n }\n\n async delete(_remotePath: string): Promise<void> {\n // intentional no-op\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\nexport function createBackend(cfg: BinaryStorageBackendConfig): BinaryStorageBackend {\n switch (cfg.type) {\n case \"filesystem\": {\n if (!cfg.basePath) {\n throw new Error(\n \"BinaryStorageBackendConfig.basePath is required when type is \\\"filesystem\\\"\",\n );\n }\n return new FilesystemBackend(cfg.basePath);\n }\n case \"s3\":\n throw new Error(\"S3 binary storage backend is not yet implemented\");\n case \"none\":\n return new NoneBackend();\n default:\n throw new Error(`Unknown binary storage backend type: ${String((cfg as { type: string }).type)}`);\n }\n}\n","/**\n * Binary file scanner.\n *\n * Recursively walks the memory directory, matches files against configured\n * glob patterns, skips files already tracked in the manifest, and respects\n * the max-size limit.\n */\n\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { BinaryLifecycleConfig, BinaryLifecycleManifest } from \"./types.js\";\n\n/**\n * Test whether a filename matches any of the provided glob patterns.\n * Supports simple `*.ext` patterns (the default scan patterns).\n * For more complex globs a proper library should be used; this covers\n * the 95% case without adding a dependency.\n */\nexport function matchesPatterns(filename: string, patterns: string[]): boolean {\n const lower = filename.toLowerCase();\n for (const pattern of patterns) {\n // Simple *.ext matching\n if (pattern.startsWith(\"*.\")) {\n const ext = pattern.slice(1).toLowerCase(); // e.g. \".png\"\n if (lower.endsWith(ext)) return true;\n } else if (lower === pattern.toLowerCase()) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Scan memoryDir recursively for binary files matching the configured patterns.\n * Returns relative paths (relative to memoryDir) for files not yet tracked.\n */\nexport async function scanForBinaries(\n memoryDir: string,\n config: BinaryLifecycleConfig,\n manifest: BinaryLifecycleManifest,\n): Promise<string[]> {\n const tracked = new Set(manifest.assets.map((a) => a.originalPath));\n const results: string[] = [];\n\n async function walk(dir: string): Promise<void> {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fsp.readdir(dir, { withFileTypes: true });\n } catch {\n return; // skip unreadable directories\n }\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n // Normalize to POSIX separators so redirect matching works on Windows\n // (markdown links always use forward slashes).\n const relativePath = path.relative(memoryDir, fullPath).split(path.sep).join(\"/\");\n\n if (entry.isDirectory()) {\n // Skip the manifest directory itself and hidden dirs starting with .\n if (entry.name === \".binary-lifecycle\") continue;\n await walk(fullPath);\n continue;\n }\n\n if (!entry.isFile()) continue;\n\n // Check pattern match\n if (!matchesPatterns(entry.name, config.scanPatterns)) continue;\n\n // Skip already tracked\n if (tracked.has(relativePath)) continue;\n\n // Check file size\n try {\n const stat = await fsp.stat(fullPath);\n if (stat.size > config.maxBinarySizeBytes) continue;\n if (stat.size === 0) continue; // skip empty files\n } catch {\n continue; // stat failure, skip\n }\n\n results.push(relativePath);\n }\n }\n\n await walk(memoryDir);\n return results;\n}\n","/**\n * Binary lifecycle manifest — read/write operations.\n *\n * The manifest lives at `${memoryDir}/.binary-lifecycle/manifest.json`.\n * Writes use the atomic temp-then-rename pattern (CLAUDE.md #54).\n */\n\nimport fs from \"node:fs\";\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\nimport type { BinaryLifecycleManifest } from \"./types.js\";\n\nconst MANIFEST_DIR = \".binary-lifecycle\";\nconst MANIFEST_FILE = \"manifest.json\";\n\nexport function manifestDir(memoryDir: string): string {\n return path.join(memoryDir, MANIFEST_DIR);\n}\n\nexport function manifestPath(memoryDir: string): string {\n return path.join(memoryDir, MANIFEST_DIR, MANIFEST_FILE);\n}\n\n/**\n * Read the manifest from disk. Returns a fresh empty manifest if the file\n * does not exist or contains invalid JSON (CLAUDE.md #18).\n */\nexport async function readManifest(memoryDir: string): Promise<BinaryLifecycleManifest> {\n const filePath = manifestPath(memoryDir);\n try {\n const raw = await fsp.readFile(filePath, \"utf-8\");\n const parsed: unknown = JSON.parse(raw);\n // CLAUDE.md #18: validate the parsed result is a non-null object.\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n return emptyManifest();\n }\n const obj = parsed as Record<string, unknown>;\n if (obj.version !== 1 || !Array.isArray(obj.assets)) {\n return emptyManifest();\n }\n return parsed as BinaryLifecycleManifest;\n } catch {\n return emptyManifest();\n }\n}\n\n/**\n * Write the manifest atomically: write to a temp file, then rename.\n * CLAUDE.md #54: never delete before write. Write temp first, rename atomically.\n */\nexport async function writeManifest(\n memoryDir: string,\n manifest: BinaryLifecycleManifest,\n): Promise<void> {\n const dir = manifestDir(memoryDir);\n await fsp.mkdir(dir, { recursive: true });\n const dest = manifestPath(memoryDir);\n const tmpSuffix = crypto.randomBytes(8).toString(\"hex\");\n const tmpPath = `${dest}.${tmpSuffix}.tmp`;\n // Sort keys for deterministic output (CLAUDE.md #38).\n const content = JSON.stringify(manifest, null, 2) + \"\\n\";\n await fsp.writeFile(tmpPath, content, \"utf-8\");\n try {\n await fsp.rename(tmpPath, dest);\n } catch (renameErr) {\n // Clean up temp on rename failure (cross-device edge case).\n try {\n await fsp.unlink(tmpPath);\n } catch {\n // ignore cleanup failure\n }\n throw renameErr;\n }\n}\n\nexport function emptyManifest(): BinaryLifecycleManifest {\n return { version: 1, assets: [] };\n}\n","/**\n * Binary lifecycle pipeline — mirror, redirect, clean.\n *\n * Three-stage pipeline:\n * 1. Mirror: upload binary to backend, record in manifest\n * 2. Redirect: scan markdown for inline refs, replace with redirect path\n * 3. Clean: after grace period, delete local copy\n */\n\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\nimport type {\n BinaryAssetRecord,\n BinaryLifecycleConfig,\n PipelineResult,\n} from \"./types.js\";\nimport type { BinaryStorageBackend } from \"./backend.js\";\nimport { readManifest, writeManifest } from \"./manifest.js\";\nimport { scanForBinaries } from \"./scanner.js\";\n\n/** Minimal logger interface so we don't depend on the full logger module. */\ninterface PipelineLogger {\n info(msg: string): void;\n warn(msg: string): void;\n error(msg: string): void;\n}\n\ninterface PipelineOptions {\n dryRun?: boolean;\n /** Force-clean all files past grace period, ignoring redirect status. */\n forceClean?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function hashFile(filePath: string): Promise<string> {\n const content = await fsp.readFile(filePath);\n return crypto.createHash(\"sha256\").update(content).digest(\"hex\");\n}\n\nfunction guessMimeType(ext: string): string {\n const map: Record<string, string> = {\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".pdf\": \"application/pdf\",\n \".mp3\": \"audio/mpeg\",\n \".mp4\": \"video/mp4\",\n \".wav\": \"audio/wav\",\n };\n return map[ext.toLowerCase()] ?? \"application/octet-stream\";\n}\n\n/**\n * Escape special regex characters in a string.\n * CLAUDE.md #46: always escapeRegex on user-derived parts.\n */\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline stages\n// ---------------------------------------------------------------------------\n\nasync function stageMirror(\n memoryDir: string,\n newPaths: string[],\n backend: BinaryStorageBackend,\n assets: BinaryAssetRecord[],\n log: PipelineLogger,\n dryRun: boolean,\n): Promise<{ mirrored: number; errors: string[] }> {\n let mirrored = 0;\n const errors: string[] = [];\n\n for (const relPath of newPaths) {\n const fullPath = path.join(memoryDir, relPath);\n try {\n const stat = await fsp.stat(fullPath);\n const contentHash = await hashFile(fullPath);\n const ext = path.extname(relPath);\n const mimeType = guessMimeType(ext);\n const remotePath = relPath;\n\n let backendLocation = remotePath;\n if (!dryRun) {\n backendLocation = await backend.upload(fullPath, remotePath);\n }\n\n const record: BinaryAssetRecord = {\n originalPath: relPath,\n mirroredPath: backendLocation,\n contentHash,\n sizeBytes: stat.size,\n mimeType,\n mirroredAt: new Date().toISOString(),\n status: \"mirrored\",\n };\n\n assets.push(record);\n mirrored++;\n log.info(`[binary-lifecycle] mirrored: ${relPath} (${stat.size} bytes)${dryRun ? \" [dry-run]\" : \"\"}`);\n } catch (err) {\n const msg = `mirror failed for ${relPath}: ${err instanceof Error ? err.message : String(err)}`;\n log.error(`[binary-lifecycle] ${msg}`);\n errors.push(msg);\n }\n }\n\n return { mirrored, errors };\n}\n\nasync function stageRedirect(\n memoryDir: string,\n assets: BinaryAssetRecord[],\n log: PipelineLogger,\n dryRun: boolean,\n): Promise<{ redirected: number; errors: string[] }> {\n let redirected = 0;\n const errors: string[] = [];\n\n // Only redirect assets that are mirrored but not yet redirected.\n const candidates = assets.filter((a) => a.status === \"mirrored\");\n if (candidates.length === 0) return { redirected, errors };\n\n // Find all markdown files in memoryDir (recursive).\n const mdFiles = await findMarkdownFiles(memoryDir);\n\n for (const asset of candidates) {\n let matchCount = 0;\n let writeFailCount = 0;\n for (const mdPath of mdFiles) {\n try {\n const content = await fsp.readFile(mdPath, \"utf-8\");\n\n // Build the match path relative to this markdown file's directory.\n // Markdown links like `![img](./image.png)` are file-relative, but\n // asset.originalPath is memory-root relative (e.g. `sub/image.png`).\n // Resolve the asset path relative to the markdown file's directory\n // so both forms match correctly.\n const mdDir = path.dirname(mdPath);\n const assetAbsolute = path.join(memoryDir, asset.originalPath);\n const relativeToMd = path.relative(mdDir, assetAbsolute);\n // Normalise to forward slashes for regex matching (markdown uses /).\n const relativeForward = relativeToMd.split(path.sep).join(\"/\");\n const escaped = escapeRegex(relativeForward);\n\n // Build a regex that matches markdown image/link references to the file.\n // Handles: ![alt](./path) , ![alt](path) , [text](./path)\n const pattern = new RegExp(\n `(!?\\\\[[^\\\\]]*\\\\]\\\\()(\\\\.\\\\/)?(${escaped})(\\\\))`,\n \"g\",\n );\n\n if (!pattern.test(content)) continue;\n matchCount++;\n\n if (!dryRun) {\n // Reset lastIndex after test().\n pattern.lastIndex = 0;\n const updated = content.replace(pattern, (_match, open, _dotSlash, _file, close) => {\n return `${open as string}${asset.mirroredPath}${close as string}`;\n });\n await fsp.writeFile(mdPath, updated, \"utf-8\");\n }\n } catch (err) {\n // Track write failures separately so we don't transition status\n // when some markdown rewrites failed (P1: block redirect on failure).\n writeFailCount++;\n const msg = `redirect scan failed for ${mdPath}: ${err instanceof Error ? err.message : String(err)}`;\n log.error(`[binary-lifecycle] ${msg}`);\n errors.push(msg);\n }\n }\n\n // Only transition to \"redirected\" when at least one reference was found\n // AND all matched files were rewritten successfully.\n if (matchCount > 0 && writeFailCount === 0) {\n asset.status = \"redirected\";\n asset.redirectedAt = new Date().toISOString();\n redirected++;\n log.info(`[binary-lifecycle] redirected: ${asset.originalPath}${dryRun ? \" [dry-run]\" : \"\"}`);\n } else if (matchCount > 0 && writeFailCount > 0) {\n // Some rewrites failed — set error status so the asset is not cleaned\n // prematurely. It can be retried on the next pipeline run.\n asset.status = \"error\";\n log.warn(\n `[binary-lifecycle] redirect partial failure for ${asset.originalPath}: ` +\n `${matchCount} match(es), ${writeFailCount} write failure(s) — status set to error`,\n );\n }\n }\n\n return { redirected, errors };\n}\n\nasync function stageClean(\n memoryDir: string,\n assets: BinaryAssetRecord[],\n gracePeriodDays: number,\n log: PipelineLogger,\n dryRun: boolean,\n forceClean: boolean,\n): Promise<{ cleaned: number; errors: string[] }> {\n let cleaned = 0;\n const errors: string[] = [];\n const now = Date.now();\n const graceMs = gracePeriodDays * 24 * 60 * 60 * 1000;\n\n // Clean only assets that have been redirected (markdown refs already rewritten).\n // Mirrored-only assets must NOT be cleaned — their markdown refs still point\n // to the local file, so deletion would break links.\n const candidates = assets.filter(\n (a) => a.status === \"redirected\",\n );\n\n for (const asset of candidates) {\n const mirroredMs = new Date(asset.mirroredAt).getTime();\n const ageMs = now - mirroredMs;\n\n if (!forceClean && ageMs < graceMs) {\n // Not yet past grace period.\n continue;\n }\n\n const fullPath = path.join(memoryDir, asset.originalPath);\n try {\n if (!dryRun) {\n await fsp.unlink(fullPath);\n }\n asset.status = \"cleaned\";\n asset.cleanedAt = new Date().toISOString();\n cleaned++;\n log.info(`[binary-lifecycle] cleaned: ${asset.originalPath}${dryRun ? \" [dry-run]\" : \"\"}`);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n // Already gone — mark as cleaned.\n asset.status = \"cleaned\";\n asset.cleanedAt = new Date().toISOString();\n cleaned++;\n } else {\n const msg = `clean failed for ${asset.originalPath}: ${err instanceof Error ? err.message : String(err)}`;\n log.error(`[binary-lifecycle] ${msg}`);\n errors.push(msg);\n }\n }\n }\n\n return { cleaned, errors };\n}\n\n// ---------------------------------------------------------------------------\n// Markdown file discovery\n// ---------------------------------------------------------------------------\n\nasync function findMarkdownFiles(dir: string): Promise<string[]> {\n const results: string[] = [];\n\n async function walk(current: string): Promise<void> {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fsp.readdir(current, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const full = path.join(current, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \".binary-lifecycle\") continue;\n await walk(full);\n } else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n results.push(full);\n }\n }\n }\n\n await walk(dir);\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Main pipeline entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Run the binary lifecycle pipeline: scan, mirror, redirect, clean.\n */\nexport async function runBinaryLifecyclePipeline(\n memoryDir: string,\n config: BinaryLifecycleConfig,\n backend: BinaryStorageBackend,\n log: PipelineLogger,\n opts?: PipelineOptions,\n): Promise<PipelineResult> {\n const dryRun = opts?.dryRun ?? false;\n const forceClean = opts?.forceClean ?? false;\n\n const manifest = await readManifest(memoryDir);\n\n // Stage 0: Scan\n const newPaths = await scanForBinaries(memoryDir, config, manifest);\n const scanned = newPaths.length;\n\n // Stage 1: Mirror\n const mirrorResult = await stageMirror(\n memoryDir,\n newPaths,\n backend,\n manifest.assets,\n log,\n dryRun,\n );\n\n // Stage 2: Redirect\n const redirectResult = await stageRedirect(memoryDir, manifest.assets, log, dryRun);\n\n // Stage 3: Clean\n const cleanResult = await stageClean(\n memoryDir,\n manifest.assets,\n config.gracePeriodDays,\n log,\n dryRun,\n forceClean,\n );\n\n // Persist manifest (unless dry-run).\n manifest.lastScanAt = new Date().toISOString();\n if (!dryRun) {\n await writeManifest(memoryDir, manifest);\n }\n\n const allErrors = [\n ...mirrorResult.errors,\n ...redirectResult.errors,\n ...cleanResult.errors,\n ];\n\n return {\n scanned,\n mirrored: mirrorResult.mirrored,\n redirected: redirectResult.redirected,\n cleaned: cleanResult.cleaned,\n errors: allErrors,\n dryRun,\n };\n}\n","/**\n * @remnic/core — Workspace Tree Projection\n *\n * Generates a human-readable `.engram/context-tree/` from canonical memory.\n * Each node is a `.md` file with rich metadata, * (provenance, trust, confidence, source anchors).\n * Manual edits are preserved in fenced blocks.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getCategoryDir, ALL_CATEGORY_KEYS } from \"../utils/category-dir.js\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface TreeNode {\n /** Relative path from context-tree root, e.g. \"entities/claude.md\" */\n path: string;\n /** Category from canonical memory */\n category: string;\n /** Human-readable title */\n title: string;\n /** File content (rendered markdown) */\n content: string;\n /** Source memory IDs that contributed to this node */\n sourceAnchors: string[];\n /** Confidence (0-1) */\n confidence: number;\n /** Trust zone classification */\n confidenceTier: string;\n /** When this node was generated */\n generatedAt: string;\n /** Provenance chain */\n provenance: ProvenanceEntry[];\n}\n\nexport interface ProvenanceEntry {\n memoryId: string;\n source: string;\n extracted: string;\n}\n\nexport interface GenerateOptions {\n /** Memory root directory (e.g. ~/.openclaw/workspace/memory/local) */\n memoryDir: string;\n /** Output directory (e.g. .engram/context-tree) */\n outputDir: string;\n /** Categories to include (default: all) */\n categories?: string[];\n /** Whether to include entity graph */\n includeEntities?: boolean;\n /** Whether to include orphaned questions */\n includeQuestions?: boolean;\n /** Max nodes per category (default: unlimited) */\n maxPerCategory?: number;\n /** Whether to watch for changes and regenerate incrementally */\n watch?: boolean;\n}\n\nexport interface GenerateResult {\n nodesGenerated: number;\n nodesSkipped: number;\n categories: Record<string, number>;\n durationMs: number;\n outputDir: string;\n}\n\n// ── Generation ──────────────────────────────────────────────────────────────\n\n/**\n * Generate a context tree from canonical memory.\n *\n * Reads memory `.md` files from the source directory, * and projects them into a clean, * human-readable tree structure at `outputDir`.\n */\nexport async function generateContextTree(options: GenerateOptions): Promise<GenerateResult> {\n const startTime = Date.now();\n const {\n memoryDir,\n outputDir,\n categories: filterCategories,\n includeEntities = true,\n includeQuestions = true,\n maxPerCategory = Infinity,\n } = options;\n\n let nodesGenerated = 0;\n let nodesSkipped = 0;\n const categoryCounts: Record<string, number> = {};\n\n // Ensure output directory exists\n fs.mkdirSync(outputDir, { recursive: true });\n\n // Process each category (exclude 'question' — handled by separate includeQuestions pass)\n const allCategories = filterCategories ?? ALL_CATEGORY_KEYS.filter((c) => c !== \"question\");\n\n for (const category of allCategories) {\n const categoryDir = getCategoryDir(memoryDir, category);\n if (!fs.existsSync(categoryDir)) continue;\n\n categoryCounts[category] = 0;\n const files = walkR(categoryDir);\n let count = 0;\n\n for (const filePath of files) {\n if (count >= maxPerCategory) {\n nodesSkipped++;\n continue;\n }\n\n const content = fs.readFileSync(filePath, \"utf8\");\n const fm = parseFrontmatter(content);\n if (!fm) {\n nodesSkipped++;\n continue;\n }\n\n const node = projectNode(filePath, category, fm, content);\n if (!node) {\n nodesSkipped++;\n continue;\n }\n\n // Write node to output\n const outputPath = path.join(outputDir, node.path);\n fs.mkdirSync(path.dirname(outputPath), { recursive: true });\n fs.writeFileSync(outputPath, node.content);\n\n nodesGenerated++;\n categoryCounts[category] = (categoryCounts[category] ?? 0) + 1;\n count++;\n }\n }\n\n // Process entities\n if (includeEntities) {\n const entitiesDir = path.join(memoryDir, \"entities\");\n if (fs.existsSync(entitiesDir)) {\n categoryCounts[\"entity\"] = 0;\n const entityFiles = walkR(entitiesDir);\n let count = 0;\n\n for (const filePath of entityFiles) {\n if (count >= maxPerCategory) {\n nodesSkipped++;\n continue;\n }\n\n const content = fs.readFileSync(filePath, \"utf8\");\n const fileName = path.basename(filePath, \".md\");\n const node = projectEntityNode(fileName, content);\n\n const outputPath = path.join(outputDir, \"entities\", `${fileName}.md`);\n fs.mkdirSync(path.dirname(outputPath), { recursive: true });\n fs.writeFileSync(outputPath, node.content);\n\n nodesGenerated++;\n categoryCounts[\"entity\"] = (categoryCounts[\"entity\"] ?? 0) + 1;\n count++;\n }\n }\n }\n\n // Process questions\n if (includeQuestions) {\n const questionsDir = path.join(memoryDir, \"questions\");\n if (fs.existsSync(questionsDir)) {\n categoryCounts[\"question\"] = 0;\n const qFiles = walkR(questionsDir);\n let count = 0;\n\n for (const filePath of qFiles) {\n if (count >= maxPerCategory) {\n nodesSkipped++;\n continue;\n }\n\n const content = fs.readFileSync(filePath, \"utf8\");\n const fm = parseFrontmatter(content);\n if (!fm) {\n nodesSkipped++;\n continue;\n }\n\n const node = projectNode(filePath, \"question\", fm, content);\n if (!node) {\n nodesSkipped++;\n continue;\n }\n\n const outputPath = path.join(outputDir, node.path);\n fs.mkdirSync(path.dirname(outputPath), { recursive: true });\n fs.writeFileSync(outputPath, node.content);\n\n nodesGenerated++;\n categoryCounts[\"question\"] = (categoryCounts[\"question\"] ?? 0) + 1;\n count++;\n }\n }\n }\n\n // Write index\n const index = generateIndex(categoryCounts, outputDir);\n fs.writeFileSync(path.join(outputDir, \"INDEX.md\"), index);\n\n return {\n nodesGenerated,\n nodesSkipped,\n categories: categoryCounts,\n durationMs: Date.now() - startTime,\n outputDir,\n };\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\n\nfunction walkR(dir: string): string[] {\n const results: string[] = [];\n function walk(directory: string): void {\n for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {\n const fullPath = path.join(directory, entry.name);\n if (entry.isDirectory()) {\n walk(fullPath);\n } else if (entry.name.endsWith(\".md\")) {\n results.push(fullPath);\n }\n }\n }\n walk(dir);\n return results;\n}\n\ninterface Frontmatter {\n id: string;\n category: string;\n created: string;\n updated: string;\n confidence: number;\n confidenceTier: string;\n tags: string[];\n source: string;\n entityRef?: string;\n lifecycleState?: string;\n}\n\nfunction parseFrontmatter(content: string): Frontmatter | null {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!match) return null;\n\n const fmText = match[1];\n const fm: Record<string, unknown> = {};\n for (const line of fmText.split(\"\\n\")) {\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n if (key === \"tags\") {\n try {\n fm[key] = JSON.parse(value) as string[];\n } catch {\n fm[key] = [] as string[];\n }\n } else if (key === \"confidence\") {\n const parsed = parseFloat(value);\n fm[key] = Number.isFinite(parsed) ? parsed : 0;\n } else {\n fm[key] = value;\n }\n }\n return fm as unknown as Frontmatter;\n}\n\nfunction extractBody(content: string): string {\n const match = content.match(/^---\\n[\\s\\S]*?\\n---\\n([\\s\\S]*)/);\n return match ? match[1].trim() : content.trim();\n}\n\nfunction projectNode(\n filePath: string,\n category: string,\n fm: Frontmatter,\n rawContent: string,\n): TreeNode | null {\n const body = extractBody(rawContent);\n const fileName = path.basename(filePath, \".md\");\n const dateDir = path.basename(path.dirname(filePath));\n\n // Build relative path: category/date/file or just category/file\n let relPath: string;\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(dateDir)) {\n relPath = path.join(category, dateDir, `${fileName}.md`);\n } else {\n relPath = path.join(category, `${fileName}.md`);\n }\n\n const generatedAt = new Date().toISOString();\n\n const md = `# ${fm.id}\n\n> **Category:** ${fm.category}\n> **Created:** ${fm.created}\n> **Updated:** ${fm.updated ?? fm.created}\n> **Confidence:** ${fm.confidence} (${fm.confidenceTier}${fm.lifecycleState ? `, ${fm.lifecycleState}` : \"\"})\n${fm.tags?.length ? `\\n> **Tags:** ${fm.tags.join(\", \")}` : \"\"}\n${fm.entityRef ? `\\n> **Entity:** ${fm.entityRef}` : \"\"}\n> **Source:** ${fm.source ?? \"unknown\"}\n> **Projected:** ${generatedAt}\n\n---\n\n${body}\n`;\n\n return {\n path: relPath,\n category,\n title: fm.id,\n content: md,\n sourceAnchors: [fm.id],\n confidence: fm.confidence ?? 0,\n confidenceTier: fm.confidenceTier ?? \"unknown\",\n generatedAt,\n provenance: [{\n memoryId: fm.id,\n source: fm.source ?? \"unknown\",\n extracted: fm.created,\n }],\n };\n}\n\nfunction projectEntityNode(fileName: string, content: string): TreeNode {\n const generatedAt = new Date().toISOString();\n\n const md = `> **Projected:** ${generatedAt}\n> **Source:** canonical\n\n---\n\n${content}\n`;\n\n return {\n path: path.join(\"entities\", `${fileName}.md`),\n category: \"entity\",\n title: fileName,\n content: md,\n sourceAnchors: [fileName],\n confidence: 1,\n confidenceTier: \"explicit\",\n generatedAt,\n provenance: [{\n memoryId: fileName,\n source: \"canonical\",\n extracted: generatedAt,\n }],\n };\n}\n\nfunction generateIndex(\n categoryCounts: Record<string, number>,\n outputDir: string,\n): string {\n const lines = [\n \"# Context Tree Index\",\n \"\",\n `Generated: ${new Date().toISOString()}`,\n \"\",\n \"## Summary\",\n \"\",\n `| Category | Count |`,\n `|----------|-------|`,\n ];\n\n let total = 0;\n for (const [cat, count] of Object.entries(categoryCounts).sort()) {\n lines.push(`| ${cat} | ${count} |`);\n total += count;\n }\n\n lines.push(\"\");\n lines.push(`**Total:** ${total} nodes`);\n lines.push(\"\");\n lines.push(\"## Structure\");\n lines.push(\"\");\n lines.push(\"```\");\n lines.push(\"context-tree/\");\n lines.push(\"├── entities/ # Entity knowledge graph\");\n lines.push(\"├── fact/ # Factual memories (date-partitioned)\");\n lines.push(\"├── correction/ # Correction memories\");\n lines.push(\"├── decision/ # Decisions\");\n lines.push(\"├── moment/ # Notable moments\");\n lines.push(\"├── preference/ # Preferences\");\n lines.push(\"├── principle/ # Principles\");\n lines.push(\"├── question/ # Open questions\");\n lines.push(\"└── INDEX.md # This file\");\n lines.push(\"```\");\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n","/**\n * @remnic/core — Category Directory Map\n *\n * Shared mapping of memory category names to directory names.\n * Single source of truth — import from here instead of copy-pasting.\n */\n\nimport path from \"node:path\";\n\nexport const CATEGORY_DIR_MAP: Record<string, string> = {\n correction: \"corrections\",\n question: \"questions\",\n preference: \"preferences\",\n decision: \"decisions\",\n moment: \"moments\",\n commitment: \"commitments\",\n principle: \"principles\",\n rule: \"rules\",\n skill: \"skills\",\n relationship: \"relationships\",\n procedure: \"procedures\",\n};\n\n/** All directory names derived from CATEGORY_DIR_MAP, plus \"facts\" (the default). */\nexport const ALL_CATEGORY_DIRS: string[] = [\n \"facts\",\n ...Object.values(CATEGORY_DIR_MAP),\n];\n\n/** All category keys (singular form) — used when iterating categories and calling getCategoryDir. */\nexport const ALL_CATEGORY_KEYS: string[] = [\n \"fact\",\n ...Object.keys(CATEGORY_DIR_MAP),\n];\n\n/**\n * Resolve a category name to its directory path under memoryDir.\n * Falls back to `facts/` for unknown categories.\n */\nexport function getCategoryDir(memoryDir: string, category: string): string {\n const dir = CATEGORY_DIR_MAP[category];\n return dir ? path.join(memoryDir, dir) : path.join(memoryDir, \"facts\");\n}\n","/**\n * @remnic/core — Onboarding\n *\n * Detects project language, shape, and documentation to produce\n * an onboarding plan for memory ingestion.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface OnboardOptions {\n /** Directory to scan (defaults to cwd) */\n directory: string;\n /** Max depth to walk (default: 6) */\n maxDepth?: number;\n /** Directories to skip */\n excludeDirs?: string[];\n}\n\nexport interface LanguageInfo {\n /** Language name (e.g. \"TypeScript\", \"Python\") */\n language: string;\n /** Confidence in detection (0-1) */\n confidence: number;\n /** Evidence (e.g. [\"package.json\", \"tsconfig.json\", \"*.ts files\"]) */\n evidence: string[];\n}\n\nexport interface DocFile {\n /** Absolute path */\n path: string;\n /** Relative path from project root */\n relativePath: string;\n /** Estimated type */\n kind: \"readme\" | \"changelog\" | \"contributing\" | \"license\" | \"config\" | \"docs\" | \"other\";\n /** File size in bytes */\n size: number;\n}\n\nexport type ProjectShape = \"app\" | \"library\" | \"monorepo\" | \"workspace\" | \"script\" | \"unknown\";\n\nexport interface OnboardResult {\n /** Project root */\n directory: string;\n /** Detected languages (sorted by confidence) */\n languages: LanguageInfo[];\n /** Detected project shape */\n shape: ProjectShape;\n /** Shape evidence */\n shapeEvidence: string[];\n /** Discovered documentation files */\n docs: DocFile[];\n /** Total files scanned */\n totalFiles: number;\n /** Duration in ms */\n durationMs: number;\n /** Suggested ingestion plan */\n plan: IngestionPlan;\n}\n\nexport interface IngestionPlan {\n /** Priority files to ingest first */\n priorityFiles: DocFile[];\n /** Estimated total files to ingest */\n estimatedFiles: number;\n /** Recommended categories */\n categories: string[];\n /** Suggested memory namespace */\n suggestedNamespace: string;\n}\n\n// ── Language detection rules ─────────────────────────────────────────────────\n\ninterface LanguageRule {\n language: string;\n extensions: string[];\n manifests: string[];\n configFiles: string[];\n}\n\nconst LANGUAGE_RULES: LanguageRule[] = [\n {\n language: \"TypeScript\",\n extensions: [\".ts\", \".tsx\"],\n manifests: [\"package.json\"],\n configFiles: [\"tsconfig.json\", \"tsup.config.ts\"],\n },\n {\n language: \"JavaScript\",\n extensions: [\".js\", \".jsx\", \".mjs\", \".cjs\"],\n manifests: [\"package.json\"],\n configFiles: [\".eslintrc\", \".prettierrc\"],\n },\n {\n language: \"Python\",\n extensions: [\".py\", \".pyi\"],\n manifests: [\"pyproject.toml\", \"setup.py\", \"setup.cfg\", \"requirements.txt\"],\n configFiles: [\"mypy.ini\", \".flake8\", \"tox.ini\"],\n },\n {\n language: \"Go\",\n extensions: [\".go\"],\n manifests: [\"go.mod\", \"go.sum\"],\n configFiles: [],\n },\n {\n language: \"Rust\",\n extensions: [\".rs\"],\n manifests: [\"Cargo.toml\"],\n configFiles: [],\n },\n {\n language: \"Ruby\",\n extensions: [\".rb\"],\n manifests: [\"Gemfile\", \"*.gemspec\"],\n configFiles: [\".rubocop.yml\"],\n },\n {\n language: \"PHP\",\n extensions: [\".php\"],\n manifests: [\"composer.json\"],\n configFiles: [\"phpcs.xml\"],\n },\n {\n language: \"Java\",\n extensions: [\".java\", \".kt\"],\n manifests: [\"pom.xml\", \"build.gradle\", \"build.gradle.kts\"],\n configFiles: [],\n },\n {\n language: \"Swift\",\n extensions: [\".swift\"],\n manifests: [\"Package.swift\", \"Podfile\"],\n configFiles: [],\n },\n {\n language: \"C#\",\n extensions: [\".cs\"],\n manifests: [\"*.csproj\", \"*.sln\"],\n configFiles: [],\n },\n {\n language: \"Shell\",\n extensions: [\".sh\", \".bash\", \".zsh\"],\n manifests: [],\n configFiles: [],\n },\n {\n language: \"Dart\",\n extensions: [\".dart\"],\n manifests: [\"pubspec.yaml\"],\n configFiles: [],\n },\n {\n language: \"Elixir\",\n extensions: [\".ex\", \".exs\"],\n manifests: [\"mix.exs\"],\n configFiles: [],\n },\n];\n\nconst DEFAULT_EXCLUDE = new Set([\n \"node_modules\",\n \".git\",\n \"vendor\",\n \"__pycache__\",\n \".venv\",\n \"venv\",\n \"dist\",\n \"build\",\n \".next\",\n \".nuxt\",\n \"target\",\n \"coverage\",\n \".engram\",\n]);\n\n// ── Main function ────────────────────────────────────────────────────────────\n\nexport function onboard(options: OnboardOptions): OnboardResult {\n const startTime = Date.now();\n const {\n directory,\n maxDepth = 6,\n excludeDirs = [],\n } = options;\n\n const exclude = new Set([...DEFAULT_EXCLUDE, ...excludeDirs]);\n\n // Collect all files\n const files = walkDir(directory, exclude, maxDepth);\n\n // Detect languages\n const languages = detectLanguages(files, directory);\n\n // Detect shape\n const { shape, evidence: shapeEvidence } = detectShape(files, directory);\n\n // Discover docs\n const docs = discoverDocs(files, directory);\n\n // Build plan\n const plan = buildPlan(languages, shape, docs, directory);\n\n return {\n directory,\n languages,\n shape,\n shapeEvidence,\n docs,\n totalFiles: files.length,\n durationMs: Date.now() - startTime,\n plan,\n };\n}\n\n// ── Walk directory ───────────────────────────────────────────────────────────\n\nfunction walkDir(\n root: string,\n exclude: Set<string>,\n maxDepth: number,\n): string[] {\n const results: string[] = [];\n\n function walk(dir: string, depth: number): void {\n if (depth > maxDepth) return;\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (exclude.has(entry.name)) continue;\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(fullPath, depth + 1);\n } else if (entry.isFile()) {\n results.push(fullPath);\n }\n }\n }\n\n walk(root, 0);\n return results;\n}\n\n// ── Language detection ───────────────────────────────────────────────────────\n\nfunction detectLanguages(files: string[], root: string): LanguageInfo[] {\n const results: LanguageInfo[] = [];\n\n // Count extensions\n const extCounts = new Map<string, number>();\n for (const f of files) {\n const ext = path.extname(f).toLowerCase();\n if (ext) extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);\n }\n\n // Check manifests at root level\n const rootFiles = new Set(\n files\n .filter((f) => path.dirname(f) === root)\n .map((f) => path.basename(f)),\n );\n\n for (const rule of LANGUAGE_RULES) {\n const evidence: string[] = [];\n let score = 0;\n\n // Extension matches\n let extMatch = 0;\n for (const ext of rule.extensions) {\n const count = extCounts.get(ext) ?? 0;\n if (count > 0) {\n extMatch += count;\n evidence.push(`${ext} files (${count})`);\n }\n }\n score += Math.min(extMatch * 0.05, 0.5);\n\n // Manifest matches\n for (const manifest of rule.manifests) {\n if (manifest.includes(\"*\")) {\n // Glob pattern — e.g. \"*.gemspec\" matches files ending with \".gemspec\"\n const suffix = manifest.replaceAll(/\\*/g, \"\");\n if ([...rootFiles].some((f) => f.endsWith(suffix))) {\n score += 0.2;\n evidence.push(manifest);\n }\n } else if (rootFiles.has(manifest)) {\n score += 0.2;\n evidence.push(manifest);\n }\n }\n\n // Config matches\n for (const cfg of rule.configFiles) {\n if (rootFiles.has(cfg)) {\n score += 0.1;\n evidence.push(cfg);\n }\n }\n\n if (score > 0) {\n results.push({\n language: rule.language,\n confidence: Math.min(score, 1),\n evidence,\n });\n }\n }\n\n return results.sort((a, b) => b.confidence - a.confidence);\n}\n\n// ── Shape detection ──────────────────────────────────────────────────────────\n\nfunction detectShape(\n files: string[],\n root: string,\n): { shape: ProjectShape; evidence: string[] } {\n const rootFiles = new Set(\n files\n .filter((f) => path.dirname(f) === root)\n .map((f) => path.basename(f)),\n );\n\n const rootDirs = new Set<string>();\n try {\n for (const entry of fs.readdirSync(root, { withFileTypes: true })) {\n if (entry.isDirectory()) rootDirs.add(entry.name);\n }\n } catch {\n // ignore\n }\n\n const evidence: string[] = [];\n\n // Monorepo: workspace packages/ or libs/ dirs + root package.json with workspaces\n if (rootFiles.has(\"package.json\")) {\n const pkg = readJsonSafe(path.join(root, \"package.json\"));\n if (pkg?.workspaces) {\n evidence.push(\"package.json has workspaces\");\n return { shape: \"monorepo\", evidence };\n }\n }\n\n if (rootDirs.has(\"packages\") || rootDirs.has(\"libs\")) {\n evidence.push(\"has packages/ or libs/ directory\");\n return { shape: \"monorepo\", evidence };\n }\n\n // Workspace: pnpm-workspace.yaml, Cargo workspace, go.work\n if (rootFiles.has(\"pnpm-workspace.yaml\") || rootFiles.has(\"go.work\")) {\n evidence.push(\"workspace manifest found\");\n return { shape: \"workspace\", evidence };\n }\n\n const cargoToml = readTomlWorkspace(path.join(root, \"Cargo.toml\"));\n if (cargoToml) {\n evidence.push(\"Cargo.toml has workspace\");\n return { shape: \"workspace\", evidence };\n }\n\n // Library: has lib/ or src/index.*, exports in package.json\n if (rootFiles.has(\"package.json\")) {\n const pkg = readJsonSafe(path.join(root, \"package.json\"));\n if (pkg?.exports || pkg?.main) {\n // If it also has \"bin\", it's more of an app\n if (pkg?.bin) {\n evidence.push(\"package.json has bin\");\n return { shape: \"app\", evidence };\n }\n evidence.push(\"package.json has exports/main\");\n return { shape: \"library\", evidence };\n }\n }\n\n // Check for app indicators\n if (\n rootFiles.has(\"Dockerfile\") ||\n rootFiles.has(\"docker-compose.yml\") ||\n rootFiles.has(\"docker-compose.yaml\") ||\n rootDirs.has(\"app\") ||\n rootDirs.has(\"src\") && rootDirs.has(\"public\")\n ) {\n evidence.push(\"app-like structure detected\");\n return { shape: \"app\", evidence };\n }\n\n // Script: few files, no package manifest\n if (files.length <= 5 && !rootFiles.has(\"package.json\") && !rootFiles.has(\"pyproject.toml\")) {\n evidence.push(\"few files, no manifest\");\n return { shape: \"script\", evidence };\n }\n\n return { shape: \"unknown\", evidence: [\"no strong shape signal\"] };\n}\n\n// ── Doc discovery ────────────────────────────────────────────────────────────\n\nfunction discoverDocs(files: string[], root: string): DocFile[] {\n const docs: DocFile[] = [];\n const docPatterns: Array<{ pattern: RegExp; kind: DocFile[\"kind\"] }> = [\n { pattern: /^readme(\\.\\w+)?$/i, kind: \"readme\" },\n { pattern: /^changelog(\\.\\w+)?$/i, kind: \"changelog\" },\n { pattern: /^changes(\\.\\w+)?$/i, kind: \"changelog\" },\n { pattern: /^contributing(\\.\\w+)?$/i, kind: \"contributing\" },\n { pattern: /^code[_-]of[_-]conduct(\\.\\w+)?$/i, kind: \"contributing\" },\n { pattern: /^license(\\.\\w+)?$/i, kind: \"license\" },\n { pattern: /^copying(\\.\\w+)?$/i, kind: \"license\" },\n { pattern: /^\\.env\\.example$/i, kind: \"config\" },\n { pattern: /^\\.editorconfig$/i, kind: \"config\" },\n ];\n\n for (const filePath of files) {\n const basename = path.basename(filePath).toLowerCase();\n const relPath = path.relative(root, filePath);\n let kind: DocFile[\"kind\"] | undefined;\n\n // Check against patterns\n for (const { pattern, kind: k } of docPatterns) {\n if (pattern.test(basename)) {\n kind = k;\n break;\n }\n }\n\n // Check docs/ directories\n if (!kind && isUnderDocsDir(relPath)) {\n kind = \"docs\";\n }\n\n // Check markdown files at root or in docs/\n if (!kind && (basename.endsWith(\".md\") || basename.endsWith(\".mdx\"))) {\n if (path.dirname(relPath) === \".\" || isUnderDocsDir(relPath)) {\n kind = \"docs\";\n }\n }\n\n if (kind) {\n let size = 0;\n try {\n size = fs.statSync(filePath).size;\n } catch {\n // ignore\n }\n docs.push({\n path: filePath,\n relativePath: relPath,\n kind,\n size,\n });\n }\n }\n\n return docs;\n}\n\nfunction isUnderDocsDir(relPath: string): boolean {\n const parts = relPath.split(path.sep);\n return parts[0] === \"docs\" || parts[0] === \"doc\" || parts[0] === \"documentation\";\n}\n\n// ── Plan generation ──────────────────────────────────────────────────────────\n\nfunction buildPlan(\n languages: LanguageInfo[],\n shape: ProjectShape,\n docs: DocFile[],\n _root: string,\n): IngestionPlan {\n // Priority: README, CONTRIBUTING, then other docs\n const priorityOrder: Record<DocFile[\"kind\"], number> = {\n readme: 0,\n contributing: 1,\n changelog: 2,\n license: 3,\n docs: 4,\n config: 5,\n other: 6,\n };\n\n const priorityFiles = [...docs]\n .filter((d) => d.size > 0)\n .sort((a, b) => priorityOrder[a.kind] - priorityOrder[b.kind]);\n\n // Recommended categories based on project shape\n const categories: string[] = [\"fact\", \"preference\", \"decision\", \"principle\"];\n if (shape === \"monorepo\" || shape === \"workspace\") {\n categories.push(\"entity\");\n }\n\n // Namespace suggestion from primary language\n const suggestedNamespace = languages.length > 0\n ? languages[0].language.toLowerCase()\n : \"project\";\n\n return {\n priorityFiles,\n estimatedFiles: docs.filter((d) => d.size > 0).length,\n categories,\n suggestedNamespace,\n };\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction readJsonSafe(filePath: string): Record<string, unknown> | null {\n try {\n return JSON.parse(fs.readFileSync(filePath, \"utf8\"));\n } catch {\n return null;\n }\n}\n\nfunction readTomlWorkspace(filePath: string): boolean {\n try {\n const content = fs.readFileSync(filePath, \"utf8\");\n return content.includes(\"[workspace]\");\n } catch {\n return false;\n }\n}\n","/**\n * @remnic/core — Curation\n *\n * Deliberate ingestion of files into memory with provenance tracking.\n * Supports statement-level extraction, dedup, and contradiction checks.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\nimport { getCategoryDir, ALL_CATEGORY_DIRS } from \"../utils/category-dir.js\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface CurateOptions {\n /** File or directory path to curate */\n targetPath: string;\n /** Memory root directory for writing */\n memoryDir: string;\n /** Source label (e.g. \"manual\", \"docs\", \"onboarding\") */\n source?: string;\n /** Category override (default: auto-detect) */\n category?: string;\n /** Confidence to assign (default: 0.9 for curated items) */\n confidence?: number;\n /** Entity reference to attach */\n entityRef?: string;\n /** Tags to add */\n tags?: string[];\n /** Whether to perform dedup check against existing memories */\n checkDuplicates?: boolean;\n /** Whether to detect contradictions */\n checkContradictions?: boolean;\n /** Whether to write files (default: true). False = dry run */\n write?: boolean;\n}\n\nexport interface CuratedStatement {\n /** Unique ID for this statement */\n id: string;\n /** The extracted statement text */\n content: string;\n /** Category */\n category: string;\n /** Confidence */\n confidence: number;\n /** Provenance info */\n provenance: StatementProvenance;\n /** Hash of content for dedup */\n contentHash: string;\n /** Tags */\n tags: string[];\n /** Entity reference */\n entityRef?: string;\n}\n\nexport interface StatementProvenance {\n /** Source file path */\n sourcePath: string;\n /** Relative path from project root */\n relativePath: string;\n /** Source label */\n source: string;\n /** Line number if extractable (0 = unknown) */\n lineNumber: number;\n /** Timestamp of ingestion */\n ingestedAt: string;\n /** Hash of the source file for diff tracking */\n sourceFileHash: string;\n}\n\nexport interface CurateResult {\n /** Statements extracted */\n statements: CuratedStatement[];\n /** Files processed */\n filesProcessed: number;\n /** Files skipped (empty, binary, etc.) */\n filesSkipped: number;\n /** Duplicate statements found (if checkDuplicates) */\n duplicates: DuplicateResult[];\n /** Contradictions found (if checkContradictions) */\n contradictions: ContradictionResult[];\n /** Memory files written */\n written: string[];\n /** Duration in ms */\n durationMs: number;\n}\n\nexport interface DuplicateResult {\n /** New statement */\n newStatement: CuratedStatement;\n /** Existing memory ID that matches */\n existingId: string;\n /** Similarity score (0-1) */\n similarity: number;\n /** Recommended action */\n action: \"skip\" | \"merge\" | \"keep\";\n}\n\nexport interface ContradictionResult {\n /** New statement */\n newStatement: CuratedStatement;\n /** Conflicting memory ID */\n conflictingId: string;\n /** The conflicting content */\n conflictingContent: string;\n /** Severity */\n severity: \"high\" | \"medium\" | \"low\";\n}\n\n// ── Main function ────────────────────────────────────────────────────────────\n\nexport async function curate(options: CurateOptions): Promise<CurateResult> {\n const startTime = Date.now();\n const {\n targetPath,\n memoryDir,\n source = \"curation\",\n category: categoryOverride,\n confidence = 0.9,\n entityRef,\n tags = [],\n checkDuplicates = true,\n checkContradictions = false,\n write = true,\n } = options;\n\n const statements: CuratedStatement[] = [];\n const written: string[] = [];\n const duplicates: DuplicateResult[] = [];\n const contradictions: ContradictionResult[] = [];\n let filesProcessed = 0;\n let filesSkipped = 0;\n\n // Determine targets\n const targets = resolveTargets(targetPath);\n\n // Load existing memories for dedup/contradiction checks\n const existingMemories = checkDuplicates || checkContradictions\n ? loadExistingMemories(memoryDir)\n : new Map();\n\n // Process each file\n for (const filePath of targets) {\n const content = readFileSafe(filePath);\n if (!content) {\n filesSkipped++;\n continue;\n }\n\n if (isBinary(content)) {\n filesSkipped++;\n continue;\n }\n\n filesProcessed++;\n\n const sourceFileHash = hashContent(content);\n const fileStatements = extractStatements(\n content,\n filePath,\n targetPath,\n source,\n sourceFileHash,\n categoryOverride,\n confidence,\n entityRef,\n tags,\n );\n\n for (const stmt of fileStatements) {\n // Dedup check\n if (checkDuplicates) {\n const dup = findDuplicate(stmt, existingMemories);\n if (dup) {\n duplicates.push(dup);\n if (dup.action === \"skip\") continue;\n }\n }\n\n // Contradiction check\n if (checkContradictions) {\n const contra = findContradiction(stmt, existingMemories);\n if (contra) {\n contradictions.push(contra);\n }\n }\n\n statements.push(stmt);\n\n // Write to memory\n if (write) {\n const writtenPath = writeStatement(stmt, memoryDir);\n if (writtenPath) {\n written.push(writtenPath);\n existingMemories.set(stmt.contentHash, {\n id: stmt.id,\n content: stmt.content,\n category: stmt.category,\n });\n }\n }\n }\n }\n\n return {\n statements,\n filesProcessed,\n filesSkipped,\n duplicates,\n contradictions,\n written,\n durationMs: Date.now() - startTime,\n };\n}\n\n// ── Target resolution ────────────────────────────────────────────────────────\n\nfunction resolveTargets(targetPath: string): string[] {\n const stat = fs.statSync(targetPath);\n if (stat.isFile()) return [targetPath];\n\n // Directory — walk for .md, .txt, .mdx\n const results: string[] = [];\n const extensions = new Set([\".md\", \".txt\", \".mdx\", \".rst\"]);\n\n function walk(dir: string): void {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name !== \"node_modules\" && entry.name !== \".git\") {\n walk(fullPath);\n }\n } else if (extensions.has(path.extname(entry.name).toLowerCase())) {\n results.push(fullPath);\n }\n }\n }\n\n walk(targetPath);\n return results;\n}\n\n// ── Statement extraction ─────────────────────────────────────────────────────\n\nfunction extractStatements(\n content: string,\n filePath: string,\n projectRoot: string,\n source: string,\n sourceFileHash: string,\n categoryOverride: string | undefined,\n confidence: number,\n entityRef: string | undefined,\n tags: string[],\n): CuratedStatement[] {\n const relativePath = path.relative(projectRoot, filePath);\n const statements: CuratedStatement[] = [];\n const now = new Date().toISOString();\n\n // Split content into paragraphs/lines and extract meaningful statements\n const paragraphs = content\n .split(/\\n{2,}/)\n .map((p) => p.trim())\n .filter((p) => p.length > 20 && p.length < 2000);\n\n // Also extract list items\n const listItems = content\n .split(\"\\n\")\n .filter((l) => /^\\s*[-*]\\s+/.test(l))\n .map((l) => l.replace(/^\\s*[-*]\\s+/, \"\").trim())\n .filter((l) => l.length > 10 && l.length < 500);\n\n const allItems = [...paragraphs, ...listItems];\n\n // Deduplicate within file\n const seen = new Set<string>();\n for (const item of allItems) {\n const hash = hashContent(item.toLowerCase());\n if (seen.has(hash)) continue;\n seen.add(hash);\n\n const id = generateId();\n const category = categoryOverride ?? detectCategory(item);\n\n statements.push({\n id,\n content: item,\n category,\n confidence,\n provenance: {\n sourcePath: filePath,\n relativePath,\n source,\n lineNumber: 0,\n ingestedAt: now,\n sourceFileHash,\n },\n contentHash: hash,\n tags: [...tags],\n entityRef,\n });\n }\n\n return statements;\n}\n\n// ── Category detection ───────────────────────────────────────────────────────\n\nfunction detectCategory(text: string): string {\n const lower = text.toLowerCase();\n\n if (/^(always|never|must|should|don't|avoid|ensure)/.test(lower)) return \"principle\";\n if (/^(we|team|project)\\s+(decided|chose|will|use)/.test(lower)) return \"decision\";\n if (/^(i|we)\\s+(prefer|like|want|hate|dislike)/.test(lower)) return \"preference\";\n if (/fix|bug|issue|broken|error/i.test(lower)) return \"correction\";\n if (/\\?.+$/.test(lower.trim())) return \"question\";\n\n return \"fact\";\n}\n\n// ── Dedup ────────────────────────────────────────────────────────────────────\n\ninterface ExistingMemory {\n id: string;\n content: string;\n category: string;\n}\n\nfunction findDuplicate(\n stmt: CuratedStatement,\n existing: Map<string, ExistingMemory>,\n): DuplicateResult | null {\n const stmtLower = stmt.content.toLowerCase();\n\n // Exact hash match\n const exactMatch = existing.get(stmt.contentHash);\n if (exactMatch) {\n return {\n newStatement: stmt,\n existingId: exactMatch.id,\n similarity: 1,\n action: \"skip\",\n };\n }\n\n // Fuzzy match — check substring containment\n for (const [_, mem] of existing) {\n const memLower = mem.content.toLowerCase();\n if (memLower.length > 50 && stmtLower.length > 50) {\n // Check if one contains the other\n if (memLower.includes(stmtLower.slice(0, 40)) || stmtLower.includes(memLower.slice(0, 40))) {\n return {\n newStatement: stmt,\n existingId: mem.id,\n similarity: 0.85,\n action: \"skip\",\n };\n }\n }\n }\n\n return null;\n}\n\n// ── Contradiction detection ──────────────────────────────────────────────────\n\nfunction findContradiction(\n stmt: CuratedStatement,\n existing: Map<string, ExistingMemory>,\n): ContradictionResult | null {\n const negationPatterns = [\n /\\b(not|don't|doesn't|isn't|aren't|won't|can't|never|no)\\b/i,\n ];\n\n const hasNegation = negationPatterns.some((p) => p.test(stmt.content));\n if (!hasNegation) return null;\n\n // Strip negation and look for positive version\n const stripped = stmt.content\n .toLowerCase()\n .replace(/\\b(not|don't|doesn't|isn't|aren't|won't|can't|never|no)\\b/gi, \"\")\n .trim();\n\n if (stripped.length < 20) return null;\n\n for (const [_, mem] of existing) {\n const memLower = mem.content.toLowerCase();\n // If an existing memory affirms what this statement negates\n if (memLower.includes(stripped.slice(0, Math.min(30, stripped.length)))) {\n return {\n newStatement: stmt,\n conflictingId: mem.id,\n conflictingContent: mem.content,\n severity: \"high\",\n };\n }\n }\n\n return null;\n}\n\n// ── Memory loading ───────────────────────────────────────────────────────────\n\nfunction loadExistingMemories(memoryDir: string): Map<string, ExistingMemory> {\n const result = new Map<string, ExistingMemory>();\n if (!fs.existsSync(memoryDir)) return result;\n\n // Walk all known category dirs for existing memories\n const dirs = ALL_CATEGORY_DIRS;\n for (const dir of dirs) {\n const fullDir = path.join(memoryDir, dir);\n if (!fs.existsSync(fullDir)) continue;\n\n walkFiles(fullDir, (filePath) => {\n const content = readFileSafe(filePath);\n if (!content) return;\n\n const fm = parseFrontmatter(content);\n const body = extractBody(content);\n if (!fm?.id || !body) return;\n\n const hash = hashContent(body.toLowerCase());\n result.set(hash, {\n id: fm.id,\n content: body,\n category: fm.category ?? dir.slice(0, -1),\n });\n });\n }\n\n return result;\n}\n\n// ── Writing ──────────────────────────────────────────────────────────────────\n\nfunction writeStatement(stmt: CuratedStatement, memoryDir: string): string | null {\n const now = new Date();\n const dateDir = now.toISOString().split(\"T\")[0];\n const categoryDir = getCategoryDir(memoryDir, stmt.category);\n\n const dir = path.join(categoryDir, dateDir);\n fs.mkdirSync(dir, { recursive: true });\n\n const fileName = `${stmt.category}-${Date.now()}-${stmt.id.slice(0, 8)}.md`;\n const filePath = path.join(dir, fileName);\n\n const frontmatter = [\n \"---\",\n `id: ${stmt.id}`,\n `category: ${stmt.category}`,\n `created: ${stmt.provenance.ingestedAt}`,\n `updated: ${stmt.provenance.ingestedAt}`,\n `confidence: ${stmt.confidence}`,\n `confidenceTier: ${tierFromConfidence(stmt.confidence)}`,\n `source: ${stmt.provenance.source}`,\n `tags: ${JSON.stringify(stmt.tags)}`,\n stmt.entityRef ? `entityRef: ${stmt.entityRef}` : null,\n `provenanceFile: ${stmt.provenance.relativePath}`,\n `provenanceHash: ${stmt.provenance.sourceFileHash}`,\n \"---\",\n ]\n .filter(Boolean)\n .join(\"\\n\");\n\n const body = `${frontmatter}\\n\\n${stmt.content}\\n`;\n\n try {\n fs.writeFileSync(filePath, body);\n return filePath;\n } catch {\n return null;\n }\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction generateId(): string {\n return crypto.randomUUID();\n}\n\nfunction hashContent(content: string): string {\n return crypto.createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16);\n}\n\nfunction tierFromConfidence(confidence: number): string {\n if (confidence >= 0.95) return \"explicit\";\n if (confidence >= 0.8) return \"high\";\n if (confidence >= 0.5) return \"medium\";\n return \"low\";\n}\n\nfunction readFileSafe(filePath: string): string | null {\n try {\n return fs.readFileSync(filePath, \"utf8\");\n } catch {\n return null;\n }\n}\n\nfunction isBinary(content: string): boolean {\n // Simple heuristic: if content has null bytes, it's binary\n for (let i = 0; i < Math.min(content.length, 8000); i++) {\n if (content.charCodeAt(i) === 0) return true;\n }\n return false;\n}\n\ninterface SimpleFrontmatter {\n id?: string;\n category?: string;\n [key: string]: unknown;\n}\n\nfunction parseFrontmatter(content: string): SimpleFrontmatter | null {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!match) return null;\n\n const fm: SimpleFrontmatter = {};\n for (const line of match[1].split(\"\\n\")) {\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n (fm as Record<string, unknown>)[key] = value;\n }\n return fm;\n}\n\nfunction extractBody(content: string): string {\n const match = content.match(/^---\\n[\\s\\S]*?\\n---\\n([\\s\\S]*)/);\n return match ? match[1].trim() : content.trim();\n}\n\nfunction walkFiles(dir: string, callback: (filePath: string) => void): void {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walkFiles(fullPath, callback);\n } else if (entry.name.endsWith(\".md\")) {\n callback(fullPath);\n }\n }\n}\n","/**\n * @remnic/core — Dedup & Contradiction Detection\n *\n * Statement-level deduplication and contradiction detection\n * against existing memories. Can be used standalone or via curation.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\nimport { ALL_CATEGORY_DIRS } from \"../utils/category-dir.js\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface MemoryEntry {\n /** Memory ID */\n id: string;\n /** Content text */\n content: string;\n /** Category */\n category: string;\n /** File path (if known) */\n filePath?: string;\n}\n\nexport interface DedupOptions {\n /** Memory root directory */\n memoryDir: string;\n /** Categories to scan (default: all) */\n categories?: string[];\n /** Similarity threshold for fuzzy matching (0-1, default: 0.85) */\n threshold?: number;\n /** Max memories to load (default: 10000) */\n maxLoad?: number;\n}\n\nexport interface DedupResult {\n /** Total memories scanned */\n scanned: number;\n /** Duplicate pairs found */\n duplicates: DuplicatePair[];\n /** Duration in ms */\n durationMs: number;\n}\n\nexport interface DuplicatePair {\n /** First memory */\n left: MemoryEntry;\n /** Second memory */\n right: MemoryEntry;\n /** Similarity score */\n similarity: number;\n /** Recommended action */\n action: \"merge\" | \"keep_left\" | \"keep_right\";\n}\n\nexport interface ContradictionOptions {\n /** Memory root directory */\n memoryDir: string;\n /** Categories to scan (default: all) */\n categories?: string[];\n /** Max memories to load (default: 10000) */\n maxLoad?: number;\n}\n\nexport interface ContradictionResult {\n /** Total memories scanned */\n scanned: number;\n /** Contradictions found */\n contradictions: ContradictionPair[];\n /** Duration in ms */\n durationMs: number;\n}\n\nexport interface ContradictionPair {\n /** First statement */\n left: MemoryEntry;\n /** Contradicting statement */\n right: MemoryEntry;\n /** Severity */\n severity: \"high\" | \"medium\" | \"low\";\n /** Reason */\n reason: string;\n}\n\n// ── Main functions ───────────────────────────────────────────────────────────\n\nexport function findDuplicates(options: DedupOptions): DedupResult {\n const startTime = Date.now();\n const { memoryDir, threshold = 0.85, maxLoad = 10000 } = options;\n\n const memories = loadMemories(memoryDir, options.categories, maxLoad);\n const duplicates: DuplicatePair[] = [];\n\n // Compare all pairs (O(n^2) but bounded by maxLoad)\n for (let i = 0; i < memories.length; i++) {\n for (let j = i + 1; j < memories.length; j++) {\n const sim = computeSimilarity(memories[i].content, memories[j].content);\n if (sim >= threshold) {\n duplicates.push({\n left: memories[i],\n right: memories[j],\n similarity: sim,\n action: sim >= 0.98 ? \"merge\" : sim >= 0.9 ? \"keep_right\" : \"keep_left\",\n });\n }\n }\n }\n\n return {\n scanned: memories.length,\n duplicates,\n durationMs: Date.now() - startTime,\n };\n}\n\nexport function findContradictions(options: ContradictionOptions): ContradictionResult {\n const startTime = Date.now();\n const { memoryDir, maxLoad = 10000 } = options;\n\n const memories = loadMemories(memoryDir, options.categories, maxLoad);\n const contradictions: ContradictionPair[] = [];\n\n for (let i = 0; i < memories.length; i++) {\n for (let j = i + 1; j < memories.length; j++) {\n const contra = detectContradiction(memories[i], memories[j]);\n if (contra) {\n contradictions.push(contra);\n }\n }\n }\n\n return {\n scanned: memories.length,\n contradictions,\n durationMs: Date.now() - startTime,\n };\n}\n\n// ── Similarity computation ───────────────────────────────────────────────────\n\nfunction computeSimilarity(a: string, b: string): number {\n // Normalize\n const normA = normalize(a);\n const normB = normalize(b);\n\n // Exact match\n if (normA === normB) return 1;\n\n // Hash-based exact match\n if (hashContent(normA) === hashContent(normB)) return 0.99;\n\n // Substring containment\n if (normA.length > 50 && normB.length > 50) {\n if (normA.includes(normB.slice(0, 40)) || normB.includes(normA.slice(0, 40))) {\n return 0.9;\n }\n }\n\n // Word overlap (Jaccard)\n const wordsA = new Set(normA.split(/\\s+/));\n const wordsB = new Set(normB.split(/\\s+/));\n const intersection = new Set([...wordsA].filter((w) => wordsB.has(w)));\n const union = new Set([...wordsA, ...wordsB]);\n\n if (union.size === 0) return 0;\n return intersection.size / union.size;\n}\n\nfunction normalize(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s']/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\n// ── Contradiction detection ──────────────────────────────────────────────────\n\nconst NEGATION_WORDS = new Set([\n \"not\", \"don't\", \"doesn't\", \"isn't\", \"aren't\", \"won't\", \"can't\",\n \"never\", \"no\", \"none\", \"neither\", \"nor\", \"nothing\", \"nowhere\",\n]);\n\nfunction detectContradiction(\n a: MemoryEntry,\n b: MemoryEntry,\n): ContradictionPair | null {\n const normA = normalize(a.content);\n const normB = normalize(b.content);\n\n // Check if one has negation and the other doesn't\n const aHasNegation = containsNegation(normA);\n const bHasNegation = containsNegation(normB);\n\n if (aHasNegation === bHasNegation) return null;\n\n // Strip negation and compare core content\n const strippedA = stripNegation(normA);\n const strippedB = stripNegation(normB);\n\n const sim = computeSimilarity(strippedA, strippedB);\n if (sim < 0.7) return null;\n\n // Check for opposite quantifiers\n const oppQuantifiers = [\n [\"always\", \"never\"],\n [\"all\", \"none\"],\n [\"every\", \"no\"],\n [\"must\", \"must not\"],\n [\"should\", \"should not\"],\n [\"can\", \"cannot\"],\n ];\n\n for (const [pos, neg] of oppQuantifiers) {\n if (\n (a.content.toLowerCase().includes(pos) && b.content.toLowerCase().includes(neg)) ||\n (a.content.toLowerCase().includes(neg) && b.content.toLowerCase().includes(pos))\n ) {\n return {\n left: a,\n right: b,\n severity: \"high\",\n reason: `Opposite quantifiers: \"${pos}\" vs \"${neg}\"`,\n };\n }\n }\n\n return {\n left: a,\n right: b,\n severity: sim >= 0.85 ? \"high\" : \"medium\",\n reason: \"Negated version of similar content\",\n };\n}\n\nfunction containsNegation(text: string): boolean {\n const words = text.split(/\\s+/);\n return words.some((w) => NEGATION_WORDS.has(w));\n}\n\nfunction stripNegation(text: string): string {\n return text\n .replace(/\\b(not|don't|doesn't|isn't|aren't|won't|can't|never|no|none|neither|nor|nothing|nowhere)\\b/gi, \"\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\n// ── Memory loading ───────────────────────────────────────────────────────────\n\nfunction loadMemories(\n memoryDir: string,\n categories?: string[],\n maxLoad = 10000,\n): MemoryEntry[] {\n const result: MemoryEntry[] = [];\n const allCategories = categories ?? ALL_CATEGORY_DIRS;\n\n for (const category of allCategories) {\n if (result.length >= maxLoad) break;\n\n const dir = path.join(memoryDir, category);\n if (!fs.existsSync(dir)) continue;\n\n walkMdFiles(dir, (filePath) => {\n if (result.length >= maxLoad) return;\n\n const content = readFileSafe(filePath);\n if (!content) return;\n\n const fm = parseFrontmatter(content);\n const body = extractBody(content);\n if (!fm?.id || !body) return;\n\n result.push({\n id: fm.id as string,\n content: body,\n category: (fm.category as string) ?? category.slice(0, -1),\n filePath,\n });\n });\n }\n\n return result;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction hashContent(content: string): string {\n return crypto.createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16);\n}\n\nfunction readFileSafe(filePath: string): string | null {\n try {\n return fs.readFileSync(filePath, \"utf8\");\n } catch {\n return null;\n }\n}\n\nfunction parseFrontmatter(content: string): Record<string, unknown> | null {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!match) return null;\n\n const fm: Record<string, unknown> = {};\n for (const line of match[1].split(\"\\n\")) {\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n fm[key] = value;\n }\n return fm;\n}\n\nfunction extractBody(content: string): string {\n const match = content.match(/^---\\n[\\s\\S]*?\\n---\\n([\\s\\S]*)/);\n return match ? match[1].trim() : content.trim();\n}\n\nfunction walkMdFiles(dir: string, callback: (filePath: string) => void): void {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walkMdFiles(fullPath, callback);\n } else if (entry.name.endsWith(\".md\")) {\n callback(fullPath);\n }\n }\n}\n","/**\n * @remnic/core — Review Inbox\n *\n * Manages low-confidence memories and suggestions pending review.\n * Integrates with the existing review-queue system.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getCategoryDir, ALL_CATEGORY_DIRS } from \"../utils/category-dir.js\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface ReviewItem {\n /** Memory ID */\n id: string;\n /** Content text */\n content: string;\n /** Category */\n category: string;\n /** Confidence score (0-1) */\n confidence: number;\n /** Confidence tier */\n confidenceTier: string;\n /** Source */\n source: string;\n /** File path if available */\n filePath?: string;\n /** Created date */\n created: string;\n /** Reason it's in review */\n reviewReason: \"low_confidence\" | \"suggestion\" | \"contradiction\" | \"duplicate\";\n /** Additional context */\n context?: string;\n}\n\nexport type ReviewAction = \"approve\" | \"dismiss\" | \"flag\";\n\nexport interface ReviewResult {\n /** Item acted upon */\n itemId: string;\n /** Action taken */\n action: ReviewAction;\n /** Updated file path (if modified) */\n updatedPath?: string;\n /** Status message */\n message: string;\n}\n\nexport interface ReviewListResult {\n /** Items pending review */\n items: ReviewItem[];\n /** Total count */\n total: number;\n /** Duration in ms */\n durationMs: number;\n}\n\nexport interface ReviewOptions {\n /** Memory root directory */\n memoryDir: string;\n /** Filter by reason */\n reason?: ReviewItem[\"reviewReason\"];\n /** Max items to return (default: 50) */\n limit?: number;\n /** Include items with confidence below this threshold (default: 0.7) */\n confidenceThreshold?: number;\n}\n\n// ── Main functions ───────────────────────────────────────────────────────────\n\n/**\n * List items pending review.\n */\nexport function listReviewItems(options: ReviewOptions): ReviewListResult {\n const startTime = Date.now();\n const {\n memoryDir,\n reason: filterReason,\n limit = 50,\n confidenceThreshold = 0.7,\n } = options;\n\n const items: ReviewItem[] = [];\n\n // Check suggestions directory\n const suggestionsDir = path.join(memoryDir, \"suggestions\");\n if (fs.existsSync(suggestionsDir)) {\n walkMd(suggestionsDir, (filePath, content) => {\n if (items.length >= limit) return;\n\n const fm = parseFrontmatter(content);\n const body = extractBody(content);\n if (!fm?.id) return;\n\n items.push({\n id: fm.id as string,\n content: body,\n category: (fm.category as string) ?? \"suggestion\",\n confidence: parseConfidence(fm.confidence, 0.5),\n confidenceTier: (fm.confidenceTier as string) ?? \"low\",\n source: (fm.source as string) ?? \"unknown\",\n filePath,\n created: (fm.created as string) ?? new Date().toISOString(),\n reviewReason: \"suggestion\",\n });\n });\n }\n\n // Check review directory\n const reviewDir = path.join(memoryDir, \"review\");\n if (fs.existsSync(reviewDir)) {\n walkMd(reviewDir, (filePath, content) => {\n if (items.length >= limit) return;\n\n const fm = parseFrontmatter(content);\n const body = extractBody(content);\n if (!fm?.id) return;\n\n items.push({\n id: fm.id as string,\n content: body,\n category: (fm.category as string) ?? \"review\",\n confidence: parseConfidence(fm.confidence, 0.5),\n confidenceTier: (fm.confidenceTier as string) ?? \"low\",\n source: (fm.source as string) ?? \"unknown\",\n filePath,\n created: (fm.created as string) ?? new Date().toISOString(),\n reviewReason: (fm.reviewReason as ReviewItem[\"reviewReason\"]) ?? \"low_confidence\",\n context: fm.context as string | undefined,\n });\n });\n }\n\n // Scan all categories for low-confidence items\n const categories = ALL_CATEGORY_DIRS;\n for (const category of categories) {\n if (items.length >= limit) break;\n\n const dir = path.join(memoryDir, category);\n if (!fs.existsSync(dir)) continue;\n\n walkMd(dir, (filePath, content) => {\n if (items.length >= limit) return;\n\n const fm = parseFrontmatter(content);\n const body = extractBody(content);\n if (!fm?.id) return;\n\n const confidence = parseConfidence(fm.confidence, 1);\n if (confidence >= confidenceThreshold) return;\n\n // Skip if already in items\n if (items.some((i) => i.id === fm.id)) return;\n\n items.push({\n id: fm.id as string,\n content: body,\n category: (fm.category as string) ?? category.slice(0, -1),\n confidence,\n confidenceTier: (fm.confidenceTier as string) ?? \"low\",\n source: (fm.source as string) ?? \"unknown\",\n filePath,\n created: (fm.created as string) ?? new Date().toISOString(),\n reviewReason: \"low_confidence\",\n });\n });\n }\n\n // Filter by reason\n const filtered = filterReason\n ? items.filter((i) => i.reviewReason === filterReason)\n : items;\n\n return {\n items: filtered.slice(0, limit),\n total: filtered.length,\n durationMs: Date.now() - startTime,\n };\n}\n\n/**\n * Perform a review action on an item.\n */\nexport function performReview(\n memoryDir: string,\n itemId: string,\n action: ReviewAction,\n): ReviewResult {\n switch (action) {\n case \"approve\":\n return approveItem(memoryDir, itemId);\n case \"dismiss\":\n return dismissItem(memoryDir, itemId);\n case \"flag\":\n return flagItem(memoryDir, itemId);\n }\n}\n\n// ── Actions ──────────────────────────────────────────────────────────────────\n\nfunction approveItem(memoryDir: string, itemId: string): ReviewResult {\n // Find the item in suggestions/ or review/\n const locations = [\"suggestions\", \"review\"];\n for (const loc of locations) {\n const dir = path.join(memoryDir, loc);\n if (!fs.existsSync(dir)) continue;\n\n const found = findFileById(dir, itemId);\n if (!found) continue;\n\n const content = fs.readFileSync(found, \"utf8\");\n const fm = parseFrontmatter(content);\n const body = extractBody(content);\n if (!fm) return { itemId, action: \"approve\", message: \"Could not parse frontmatter\" };\n\n // Promote to facts/ with boosted confidence\n const category = (fm.category as string) ?? \"fact\";\n const targetDir = getCategoryDir(memoryDir, category);\n const dateDir = new Date().toISOString().split(\"T\")[0];\n const outputPath = path.join(targetDir, dateDir, path.basename(found));\n\n // Update confidence in content\n const updatedContent = content\n .replace(/confidence: [\\d.]+/, \"confidence: 0.9\")\n .replace(/confidenceTier: \\w+/, \"confidenceTier: high\");\n\n fs.mkdirSync(path.dirname(outputPath), { recursive: true });\n fs.writeFileSync(outputPath, updatedContent);\n\n // Remove from review\n fs.unlinkSync(found);\n\n return {\n itemId,\n action: \"approve\",\n updatedPath: outputPath,\n message: `Promoted to ${category} with confidence 0.9`,\n };\n }\n\n return { itemId, action: \"approve\", message: \"Item not found\" };\n}\n\nfunction dismissItem(memoryDir: string, itemId: string): ReviewResult {\n const locations = [\"suggestions\", \"review\"];\n for (const loc of locations) {\n const dir = path.join(memoryDir, loc);\n if (!fs.existsSync(dir)) continue;\n\n const found = findFileById(dir, itemId);\n if (found) {\n fs.unlinkSync(found);\n return { itemId, action: \"dismiss\", message: \"Dismissed and removed\" };\n }\n }\n\n return { itemId, action: \"dismiss\", message: \"Item not found\" };\n}\n\nfunction flagItem(memoryDir: string, itemId: string): ReviewResult {\n const locations = [\"suggestions\", \"review\"];\n for (const loc of locations) {\n const dir = path.join(memoryDir, loc);\n if (!fs.existsSync(dir)) continue;\n\n const found = findFileById(dir, itemId);\n if (found) {\n // Add flagged marker to frontmatter\n const content = fs.readFileSync(found, \"utf8\");\n const fixed = content.replace(\n /^(---\\n)/,\n `---\\nflagged: true\\nflaggedAt: ${new Date().toISOString()}\\n`,\n );\n fs.writeFileSync(found, fixed);\n return { itemId, action: \"flag\", message: \"Flagged for further review\" };\n }\n }\n\n return { itemId, action: \"flag\", message: \"Item not found\" };\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction findFileById(dir: string, id: string): string | null {\n const files = walkMdPaths(dir);\n for (const filePath of files) {\n const content = readFileSafe(filePath);\n if (!content) continue;\n const fm = parseFrontmatter(content);\n if (fm?.id === id) return filePath;\n }\n return null;\n}\n\nfunction parseConfidence(value: unknown, fallback: number): number {\n if (typeof value === \"number\") return Number.isFinite(value) ? value : fallback;\n if (typeof value === \"string\") {\n const n = parseFloat(value);\n return Number.isFinite(n) ? n : fallback;\n }\n return fallback;\n}\n\nfunction readFileSafe(filePath: string): string | null {\n try {\n return fs.readFileSync(filePath, \"utf8\");\n } catch {\n return null;\n }\n}\n\nfunction parseFrontmatter(content: string): Record<string, unknown> | null {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!match) return null;\n\n const fm: Record<string, unknown> = {};\n for (const line of match[1].split(\"\\n\")) {\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n fm[key] = value;\n }\n return fm;\n}\n\nfunction extractBody(content: string): string {\n const match = content.match(/^---\\n[\\s\\S]*?\\n---\\n([\\s\\S]*)/);\n return match ? match[1].trim() : content.trim();\n}\n\nfunction walkMd(dir: string, callback: (filePath: string, content: string) => void): void {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walkMd(fullPath, callback);\n } else if (entry.name.endsWith(\".md\")) {\n const content = readFileSafe(fullPath);\n if (content) callback(fullPath, content);\n }\n }\n}\n\nfunction walkMdPaths(dir: string): string[] {\n const results: string[] = [];\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...walkMdPaths(fullPath));\n } else if (entry.name.endsWith(\".md\")) {\n results.push(fullPath);\n }\n }\n return results;\n}\n","/**\n * @remnic/core — Diff-Aware Sync\n *\n * Watches source files for changes and triggers re-ingestion\n * only for changed content. Uses file hashing to detect changes.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface SyncOptions {\n /** Source directory to watch */\n sourceDir: string;\n /** Memory root directory */\n memoryDir: string;\n /** State file path (stores hashes). Default: memoryDir/.sync-state.json */\n stateFile?: string;\n /** File extensions to watch (default: .md, .txt, .mdx) */\n extensions?: string[];\n /** Directories to exclude */\n excludeDirs?: string[];\n /** Whether to actually write changes (default: true) */\n dryRun?: boolean;\n}\n\nexport interface SyncResult {\n /** Files scanned */\n scanned: number;\n /** Files changed since last sync */\n changed: FileChange[];\n /** Files unchanged */\n unchanged: number;\n /** Files deleted since last sync */\n deleted: string[];\n /** Files newly added */\n added: string[];\n /** Duration in ms */\n durationMs: number;\n /** State file path */\n stateFile: string;\n}\n\nexport interface FileChange {\n /** Absolute file path */\n filePath: string;\n /** Relative path from source root */\n relativePath: string;\n /** Change type */\n type: \"added\" | \"modified\" | \"deleted\";\n /** Current content hash */\n currentHash: string;\n /** Previous content hash (if modified) */\n previousHash?: string;\n /** File size in bytes */\n size: number;\n}\n\nexport interface SyncState {\n /** Map of relative path → content hash */\n fileHashes: Record<string, string>;\n /** Last sync timestamp */\n lastSyncAt: string;\n /** Version of state format */\n version: number;\n}\n\n// ── Constants ────────────────────────────────────────────────────────────────\n\nconst DEFAULT_EXTENSIONS = new Set([\".md\", \".txt\", \".mdx\", \".rst\"]);\nconst DEFAULT_EXCLUDE = new Set([\n \"node_modules\",\n \".git\",\n \"dist\",\n \"build\",\n \".engram\",\n \"coverage\",\n]);\n\n// ── Main function ────────────────────────────────────────────────────────────\n\nexport function syncChanges(options: SyncOptions): SyncResult {\n const startTime = Date.now();\n const {\n sourceDir,\n memoryDir,\n extensions = [...DEFAULT_EXTENSIONS],\n excludeDirs = [],\n dryRun = false,\n } = options;\n\n const extSet = new Set(extensions);\n const excludeSet = new Set([...DEFAULT_EXCLUDE, ...excludeDirs]);\n const stateFilePath = options.stateFile ?? path.join(memoryDir, \".sync-state.json\");\n\n // Load previous state\n const prevState = loadState(stateFilePath);\n\n // Scan current files\n const currentFiles = scanFiles(sourceDir, extSet, excludeSet);\n\n // Compute diffs\n const changes = computeDiff(currentFiles, prevState.fileHashes, sourceDir);\n\n const added = changes.filter((c) => c.type === \"added\").map((c) => c.relativePath);\n const modified = changes.filter((c) => c.type === \"modified\");\n const deleted = changes\n .filter((c) => c.type === \"deleted\")\n .map((c) => c.relativePath);\n\n // Update state (even in dry run, we want to show what would change)\n if (!dryRun) {\n const newState: SyncState = {\n fileHashes: {},\n lastSyncAt: new Date().toISOString(),\n version: 1,\n };\n\n // Build new state from current files\n for (const [relPath, hash] of Object.entries(currentFiles)) {\n newState.fileHashes[relPath] = hash;\n }\n\n // Write state\n fs.mkdirSync(path.dirname(stateFilePath), { recursive: true });\n fs.writeFileSync(stateFilePath, JSON.stringify(newState, null, 2));\n }\n\n return {\n scanned: Object.keys(currentFiles).length,\n changed: changes,\n unchanged:\n Object.keys(currentFiles).length - changes.filter((c) => c.type !== \"deleted\").length,\n deleted,\n added,\n durationMs: Date.now() - startTime,\n stateFile: stateFilePath,\n };\n}\n\n/**\n * Watch for changes and call callback on file changes.\n * Returns a stop function.\n */\nexport function watchForChanges(\n options: SyncOptions,\n onChange: (changes: FileChange[]) => void,\n): { stop: () => void } {\n const { sourceDir, extensions, excludeDirs } = options;\n const extSet = new Set(extensions ?? DEFAULT_EXTENSIONS);\n const excludeSet = new Set([...DEFAULT_EXCLUDE, ...(excludeDirs ?? [])]);\n\n let lastHashes: Record<string, string> = {};\n\n // Initial scan\n const currentFiles = scanFiles(sourceDir, extSet, excludeSet);\n lastHashes = { ...currentFiles };\n\n // Poll interval (FSWatcher doesn't reliably work for all platforms)\n const interval = setInterval(() => {\n const nowFiles = scanFiles(sourceDir, extSet, excludeSet);\n const changes = computeDiff(nowFiles, lastHashes, sourceDir);\n\n if (changes.length > 0) {\n // Update hashes\n for (const change of changes) {\n if (change.type === \"deleted\") {\n delete lastHashes[change.relativePath];\n } else {\n lastHashes[change.relativePath] = change.currentHash;\n }\n }\n onChange(changes);\n }\n }, 5000);\n\n return {\n stop: () => clearInterval(interval),\n };\n}\n\n// ── File scanning ────────────────────────────────────────────────────────────\n\nfunction scanFiles(\n root: string,\n extensions: Set<string>,\n exclude: Set<string>,\n): Record<string, string> {\n const result: Record<string, string> = {};\n\n function walk(dir: string): void {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (exclude.has(entry.name)) continue;\n\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(fullPath);\n } else if (entry.isFile()) {\n const ext = path.extname(entry.name).toLowerCase();\n if (!extensions.has(ext)) continue;\n\n const relPath = path.relative(root, fullPath);\n try {\n const content = fs.readFileSync(fullPath, \"utf8\");\n result[relPath] = hashContent(content);\n } catch {\n // Can't read — skip\n }\n }\n }\n }\n\n walk(root);\n return result;\n}\n\n// ── Diff computation ─────────────────────────────────────────────────────────\n\nfunction computeDiff(\n current: Record<string, string>,\n previous: Record<string, string>,\n sourceDir: string,\n): FileChange[] {\n const changes: FileChange[] = [];\n\n // Find added and modified\n for (const [relPath, hash] of Object.entries(current)) {\n const fullPath = path.join(sourceDir, relPath);\n\n if (!(relPath in previous)) {\n // Added\n let size = 0;\n try {\n size = fs.statSync(fullPath).size;\n } catch {\n // ignore\n }\n changes.push({\n filePath: fullPath,\n relativePath: relPath,\n type: \"added\",\n currentHash: hash,\n size,\n });\n } else if (previous[relPath] !== hash) {\n // Modified\n let size = 0;\n try {\n size = fs.statSync(fullPath).size;\n } catch {\n // ignore\n }\n changes.push({\n filePath: fullPath,\n relativePath: relPath,\n type: \"modified\",\n currentHash: hash,\n previousHash: previous[relPath],\n size,\n });\n }\n }\n\n // Find deleted\n for (const relPath of Object.keys(previous)) {\n if (!(relPath in current)) {\n changes.push({\n filePath: path.join(sourceDir, relPath),\n relativePath: relPath,\n type: \"deleted\",\n currentHash: \"\",\n size: 0,\n });\n }\n }\n\n return changes;\n}\n\n// ── State management ─────────────────────────────────────────────────────────\n\nfunction loadState(stateFilePath: string): SyncState {\n try {\n const raw = fs.readFileSync(stateFilePath, \"utf8\");\n return JSON.parse(raw);\n } catch {\n return {\n fileHashes: {},\n lastSyncAt: new Date(0).toISOString(),\n version: 1,\n };\n }\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction hashContent(content: string): string {\n return crypto.createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16);\n}\n","/**\n * @remnic/core — Connector Manager\n *\n * Metadata-driven registry for host adapters (Codex CLI, Claude Code, Cursor, etc.).\n * Manages connector lifecycle: install, remove, configure, health.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { spawnSync } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { generateToken, revokeToken, buildTokenEntry, commitTokenEntry, loadTokenStore, saveTokenStore } from \"../tokens.js\";\nimport { coerceInstallExtension } from \"./coerce.js\";\n\n// Native memory artifact materialization for Codex CLI (#378). Surfaced here\n// so downstream callers can `import { materializeForNamespace } from \"@remnic/core/connectors\"`.\nexport {\n materializeForNamespace,\n ensureSentinel,\n describeMemoriesDir,\n renderMemorySummary,\n renderMemoryMd,\n renderRawMemories,\n renderRolloutSummary,\n validateMemoryMd,\n approximateTokenCount,\n truncateToTokenBudget,\n MATERIALIZE_VERSION,\n SENTINEL_FILE,\n TMP_DIR,\n type MaterializeOptions,\n type MaterializeResult,\n type RolloutSummaryInput,\n type MemoryMdValidation,\n} from \"./codex-materialize.js\";\nexport {\n runCodexMaterialize,\n type RunMaterializeOptions,\n} from \"./codex-materialize-runner.js\";\nexport {\n generateMarketplaceManifest,\n validateMarketplaceManifest,\n checkMarketplaceManifest,\n writeMarketplaceManifest,\n installFromMarketplace,\n MARKETPLACE_SCHEMA_VERSION,\n MARKETPLACE_MANIFEST_FILENAME,\n type MarketplaceManifest,\n type MarketplaceEntry,\n type MarketplaceConfig,\n type MarketplaceInstallType,\n type MarketplaceInstallResult,\n type MarketplaceValidation,\n type MarketplaceLogger,\n} from \"./codex-marketplace.js\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface ConnectorManifest {\n /** Unique connector ID (e.g. \"claude-code\", \"codex-cli\") */\n id: string;\n /** Human-readable name */\n name: string;\n /** Version */\n version: string;\n /** Description */\n description: string;\n /** Capabilities */\n capabilities: ConnectorCapability;\n /** Required config fields */\n configSchema?: Record<string, string>;\n /** Whether currently installed */\n installed?: boolean;\n /** Homepage URL */\n homepage?: string;\n /** Author */\n author?: string;\n /** Repository URL */\n repository?: string;\n /** Tags */\n tags?: string[];\n /**\n * Whether this connector requires a bearer token for daemon authentication.\n * When false (the default), installConnector will NOT generate or persist a\n * token entry in tokens.json — credentials are never materialized on disk for\n * connectors that use MCP, embedded, CLI, or SDK transports that don't need\n * token auth. Set to true only for HTTP connectors that actually authenticate\n * requests with a bearer token (e.g. hermes, replit, generic-mcp).\n */\n requiresToken?: boolean;\n}\n\nexport interface ConnectorCapability {\n /** Can observe conversations */\n observe: boolean;\n /** Can recall/query memories */\n recall: boolean;\n /** Can store memories */\n store: boolean;\n /** Can search */\n search: boolean;\n /** Can manage entities */\n entities: boolean;\n /** Supports real-time sync */\n realtimeSync: boolean;\n /** Supports batch operations */\n batch: boolean;\n /** Max memory budget in chars */\n maxBudgetChars?: number;\n /** Connection type */\n connectionType: \"mcp\" | \"http\" | \"cli\" | \"sdk\" | \"embedded\";\n}\n\nexport interface ConnectorInstance {\n /** Connector ID */\n connectorId: string;\n /** Resolved config */\n config: Record<string, unknown>;\n /** Status */\n status: \"installed\" | \"running\" | \"error\" | \"disabled\";\n /** Installed at timestamp */\n installedAt?: string;\n /** Error message if erro */\n error?: string;\n}\n\nexport interface ConnectorRegistry {\n /** Known connectors */\n connectors: ConnectorManifest[];\n /** Registry file path */\n registryPath: string;\n}\n\nexport interface InstallOptions {\n /** Connector ID to install */\n connectorId: string;\n /** Config values */\n config?: Record<string, unknown>;\n /** Memory directory */\n memoryDir?: string;\n /** Whether to force reinstall */\n force?: boolean;\n}\n\nexport interface InstallResult {\n /** Connector ID */\n connectorId: string;\n /** Status */\n status: \"installed\" | \"already_installed\" | \"config_required\" | \"error\";\n /** Config path */\n configPath?: string;\n /** Message */\n message: string;\n}\n\nexport interface RemoveResult {\n /** Connector ID */\n connectorId: string;\n /** Removed config path */\n configPath: string;\n /** Message */\n message: string;\n /** Status: \"removed\" on success, \"error\" if the removal failed partway, \"not_found\" if the connector was not installed, \"skipped\" if removal was aborted (e.g. malformed config). */\n status: \"removed\" | \"error\" | \"not_found\" | \"skipped\";\n /** Machine-readable skip reason (present when status === \"skipped\"). */\n reason?: string;\n}\n\nexport interface DoctorResult {\n /** Connector ID */\n connectorId: string;\n /** Checks */\n checks: DoctorCheck[];\n /** All healthy */\n healthy: boolean;\n}\n\nexport interface DoctorCheck {\n /** Check name */\n name: string;\n /** Passed */\n ok: boolean;\n /** Detail */\n detail: string;\n}\n\n// ── Helpers (Finding 4) ───────────────────────────────────────────────────\n\n// Re-export coerceInstallExtension so existing import sites\n// (`import { coerceInstallExtension } from \"./index.js\"`) keep working without\n// change. The binding comes from the top-level import above.\nexport { coerceInstallExtension };\n\n// ── Built-in connector definitions ─────────────────────────────────────────\n\nconst BUILTIN_CONNECTORS: ConnectorManifest[] = [\n {\n id: \"claude-code\",\n name: \"Claude Code\",\n version: \"1.0.0\",\n description: \"Anthropic's Claude Code CLI — direct memory access via MCP\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: true,\n entities: true,\n realtimeSync: true,\n batch: false,\n maxBudgetChars: 32000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n namespace: \"Optional namespace (default: 'default')\",\n },\n homepage: \"https://claude.ai/code\",\n author: \"Anthropic\",\n tags: [\"official\", \"ai\", \"claude\"],\n requiresToken: true,\n },\n {\n id: \"codex-cli\",\n name: \"Codex CLI\",\n version: \"1.0.0\",\n description: \"OpenAI Codex CLI — memory via MCP tool\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: false,\n entities: false,\n realtimeSync: false,\n batch: true,\n maxBudgetChars: 8000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n namespace: \"Optional namespace\",\n },\n homepage: \"https://openai.com/codex\",\n author: \"OpenAI\",\n tags: [\"official\", \"ai\", \"codex\"],\n requiresToken: true,\n },\n {\n id: \"cursor\",\n name: \"Cursor IDE\",\n version: \"1.0.0\",\n description: \"Cursor IDE — memory via config file + tool calls\",\n capabilities: {\n observe: false,\n recall: true,\n store: false,\n search: true,\n entities: false,\n realtimeSync: false,\n batch: false,\n maxBudgetChars: 32000,\n connectionType: \"embedded\",\n },\n configSchema: {\n memoryDir: \"Path to Remnic memory directory\",\n },\n homepage: \"https://cursor.com\",\n author: \"Cursor Inc.\",\n tags: [\"official\", \"ide\"],\n },\n {\n id: \"cline\",\n name: \"Cline\",\n version: \"1.0.0\",\n description: \"VS Code Cline extension — memory via MCP\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: false,\n entities: false,\n realtimeSync: false,\n batch: true,\n maxBudgetChars: 8000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n namespace: \"Optional namespace\",\n },\n homepage: \"https://github.com/cline/cline\",\n author: \"Cline\",\n tags: [\"community\", \"vscode\"],\n },\n {\n id: \"github-copilot\",\n name: \"GitHub Copilot\",\n version: \"1.0.0\",\n description: \"GitHub Copilot — memory via MCP server\",\n capabilities: {\n observe: false,\n recall: true,\n store: false,\n search: true,\n entities: false,\n realtimeSync: false,\n batch: false,\n maxBudgetChars: 16000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n },\n homepage: \"https://github.com/features/copilot\",\n author: \"GitHub\",\n tags: [\"official\", \"ai\", \"github\"],\n },\n {\n id: \"roo-code\",\n name: \"Roo Code\",\n version: \"1.0.0\",\n description: \"Roo Code — memory via MCP\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: false,\n entities: false,\n realtimeSync: false,\n batch: true,\n maxBudgetChars: 16000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n namespace: \"Optional namespace\",\n },\n homepage: \"https://roocode.com\",\n author: \"Roo Code\",\n tags: [\"community\", \"vscode\"],\n },\n {\n id: \"windsurf\",\n name: \"Windsurf\",\n version: \"1.0.0\",\n description: \"Windsurf IDE — memory via MCP\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: true,\n entities: false,\n realtimeSync: false,\n batch: false,\n maxBudgetChars: 32000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n },\n homepage: \"https://windsurf.com\",\n author: \"Codeium\",\n tags: [\"official\", \"ide\"],\n },\n {\n id: \"amp\",\n name: \"Amp\",\n version: \"1.0.0\",\n description: \"Amp coding agent — memory via MCP\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: true,\n entities: false,\n realtimeSync: false,\n batch: false,\n maxBudgetChars: 32000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n },\n homepage: \"https://ampcode.com\",\n author: \"Sourcegraph\",\n tags: [\"official\", \"ai\"],\n },\n {\n id: \"replit\",\n name: \"Replit Agent\",\n version: \"1.0.0\",\n description: \"Replit Agent — memory via HTTP API (reduced capabilities)\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: false,\n entities: false,\n realtimeSync: false,\n batch: false,\n maxBudgetChars: 8000,\n connectionType: \"http\",\n },\n configSchema: {\n apiUrl: \"URL of the Remnic HTTP API\",\n authToken: \"Bearer token for authentication\",\n },\n homepage: \"https://replit.com\",\n author: \"Replit\",\n tags: [\"official\", \"cloud\"],\n requiresToken: true,\n },\n {\n id: \"generic-mcp\",\n name: \"Generic MCP Client\",\n version: \"1.0.0\",\n description: \"Any MCP-compatible client — connect via standard MCP protocol\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: true,\n entities: true,\n realtimeSync: true,\n batch: true,\n maxBudgetChars: 64000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n namespace: \"Optional namespace\",\n authToken: \"Bearer token for authentication\",\n },\n homepage: \"https://github.com/joshuaswarren/remnic\",\n author: \"Remnic\",\n tags: [\"generic\", \"mcp\"],\n requiresToken: true,\n },\n {\n id: \"weclone\",\n name: \"WeClone Avatar\",\n version: \"1.0.0\",\n description:\n \"Memory-aware OpenAI-compatible proxy for deployed WeClone avatars — \" +\n \"injects Remnic recall into chat completions and buffers turns via observe\",\n capabilities: {\n observe: true,\n recall: true,\n store: false,\n search: false,\n entities: false,\n realtimeSync: false,\n batch: false,\n maxBudgetChars: 32000,\n connectionType: \"http\",\n },\n configSchema: {\n wecloneApiUrl:\n \"Base URL of the WeClone OpenAI-compatible API (e.g. http://localhost:8000/v1)\",\n proxyPort: \"Local port where the memory proxy will listen (default 8100)\",\n remnicDaemonUrl:\n \"URL of the Remnic daemon exposing /engram/v1/recall and /engram/v1/observe\",\n sessionStrategy:\n \"Per-caller session mapping strategy: 'caller-id' | 'single'\",\n wecloneModelName: \"Optional fine-tuned model name passed through to WeClone\",\n },\n homepage: \"https://github.com/xming521/weclone\",\n author: \"Remnic\",\n tags: [\"official\", \"ai\", \"weclone\", \"proxy\"],\n requiresToken: true,\n },\n {\n id: \"hermes\",\n name: \"Hermes Agent\",\n version: \"1.0.0\",\n description: \"Hermes Agent MemoryProvider — automatic recall/observe on every turn via Python plugin protocol\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: true,\n entities: false,\n realtimeSync: true,\n batch: false,\n maxBudgetChars: 32000,\n connectionType: \"http\",\n },\n configSchema: {\n host: \"Remnic daemon host (default: 127.0.0.1)\",\n port: \"Remnic daemon port (default: 4318)\",\n profile: \"Hermes profile name (default: default)\",\n },\n homepage: \"https://github.com/joshuaswarren/remnic/tree/main/packages/plugin-hermes\",\n author: \"Remnic\",\n tags: [\"official\", \"python\", \"hermes\"],\n requiresToken: true,\n },\n];\n\n// ── Registry management ───────────────────────────────────────────────────\n\nconst REGISTRY_DIR_NAME = \".engram-connectors\";\n\nexport function getRegistryPath(): string {\n const configDir = process.env.XDG_CONFIG_HOME\n ? path.join(process.env.XDG_CONFIG_HOME, \"engram\")\n : path.join(process.env.HOME ?? \"~\", \".config\", \"engram\");\n return path.join(configDir, REGISTRY_DIR_NAME, \"registry.json\");\n}\n\nexport function loadRegistry(): ConnectorRegistry {\n const regPath = getRegistryPath();\n\n if (!fs.existsSync(regPath)) {\n // First time — bootstrap with built-in connectors\n const registry: ConnectorRegistry = {\n connectors: BUILTIN_CONNECTORS,\n registryPath: regPath,\n };\n saveRegistry(registry);\n return registry;\n }\n\n const raw = fs.readFileSync(regPath, \"utf8\");\n try {\n const parsed = JSON.parse(raw);\n // Built-ins always take precedence over persisted entries with the same ID.\n // This ensures that upgraded manifests (e.g. newly-added requiresToken: true)\n // are never shadowed by stale registry.json entries from an older version.\n // Only connectors whose IDs are NOT in BUILTIN_CONNECTORS are preserved from\n // the persisted file — those are genuine user-added custom connectors.\n const builtinIds = new Set(BUILTIN_CONNECTORS.map((b) => b.id));\n const customOnly = (parsed.connectors ?? []).filter((c: ConnectorManifest) => !builtinIds.has(c.id));\n const merged = [...BUILTIN_CONNECTORS, ...customOnly];\n return {\n connectors: merged,\n registryPath: regPath,\n };\n } catch {\n const registry: ConnectorRegistry = {\n connectors: BUILTIN_CONNECTORS,\n registryPath: regPath,\n };\n saveRegistry(registry);\n return registry;\n }\n}\n\nexport function saveRegistry(registry: ConnectorRegistry): void {\n const regPath = registry.registryPath;\n fs.mkdirSync(path.dirname(regPath), { recursive: true });\n fs.writeFileSync(regPath, JSON.stringify({ connectors: registry.connectors }, null, 2));\n}\n\n// ── List connectors ────────────────────────────────────────────────────────\n\nexport function listConnectors(): {\n installed: ConnectorInstance[];\n available: ConnectorManifest[];\n} {\n const registry = loadRegistry();\n const connectorsDir = getConnectorsDir();\n const installedIds = new Set<string>();\n\n // Find installed connectors\n if (fs.existsSync(connectorsDir)) {\n for (const entry of fs.readdirSync(connectorsDir)) {\n if (entry.endsWith(\".json\")) {\n try {\n const config = JSON.parse(\n fs.readFileSync(path.join(connectorsDir, entry), \"utf8\"),\n );\n installedIds.add(config.connectorId as string);\n } catch {\n // ignore malformed configs\n }\n }\n }\n }\n\n // Mark installed vs available\n const available: ConnectorManifest[] = registry.connectors.map((manifest) => ({\n ...manifest,\n installed: installedIds.has(manifest.id),\n }));\n\n // Build installed list\n const installed: ConnectorInstance[] = [];\n for (const id of installedIds) {\n const configPath = path.join(connectorsDir, `${id}.json`);\n try {\n const raw = JSON.parse(fs.readFileSync(configPath, \"utf8\")) as Record<string, unknown>;\n // Codex P1 (PRRT_kwDORJXyws56U9U0): strip any legacy `token` field from\n // the returned config so that `remnic connectors list --json` never prints\n // a bearer token — tokens live only in tokens.json. This handles existing\n // on-disk connector.json files written by older Remnic versions without\n // rewriting user files.\n const { token: _redacted, ...config } = raw;\n installed.push({\n connectorId: id,\n config,\n status: \"installed\",\n installedAt: raw.installedAt as string | undefined,\n });\n } catch {\n // ignore\n }\n }\n\n return { installed, available };\n}\n\n// ── Get connector token ────────────────────────────────────────────────────\n// Codex P1 (PRRT_kwDORJXyws56U9U0): tokens are stored exclusively in\n// tokens.json. This helper is the canonical way to retrieve the bearer token\n// for a connector — connector.json never contains it.\n\nexport function getConnectorToken(connectorId: string): string | undefined {\n try {\n return loadTokenStore().tokens.find((t) => t.connector === connectorId)?.token;\n } catch {\n return undefined;\n }\n}\n\n// ── Install connector ───────────────────────────────────────────────────────\n\nexport function installConnector(options: InstallOptions): InstallResult {\n const registry = loadRegistry();\n const manifest = registry.connectors.find((c) => c.id === options.connectorId);\n\n if (!manifest) {\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Unknown connector: ${options.connectorId}`,\n };\n }\n\n // Check if already installed\n const existing = listConnectors().installed.find(\n (c) => c.connectorId === options.connectorId,\n );\n\n if (existing && !options.force) {\n return {\n connectorId: options.connectorId,\n status: \"already_installed\",\n message: \"Already installed. Use --force to reinstall.\",\n };\n }\n\n // Write config\n const configDir = getConnectorsDir();\n fs.mkdirSync(configDir, { recursive: true });\n\n const configPath = path.join(configDir, `${options.connectorId}.json`);\n\n // For the hermes connector, resolve profile/host/port with the following\n // precedence: saved-connector-JSON → explicit options.config → defaults.\n // Reading happens BEFORE we overwrite the connector JSON so that a\n // force-reinstall without re-supplied --config options preserves the\n // previously configured values and writes the new token to the correct\n // Hermes profile rather than resetting to \"default\"/127.0.0.1/4318.\n //\n // Issue C fix: sanitizer calls during options resolution are wrapped in\n // try-catch so that invalid user-supplied values (e.g. --config port=abc)\n // return a clean failed InstallResult instead of throwing.\n let hermesSavedProfile: string | undefined;\n let hermesSavedHost: string | undefined;\n let hermesSavedPort: number | undefined;\n // Resolved values (used both in resolvedConfig and in the YAML update below)\n let hermesResolvedProfile: string | undefined;\n let hermesResolvedHost: string | undefined;\n let hermesResolvedPort: number | undefined;\n if (options.connectorId === \"hermes\") {\n if (fs.existsSync(configPath)) {\n try {\n const prev = JSON.parse(fs.readFileSync(configPath, \"utf8\"));\n // Fix 2: coerce saved values through sanitizers so that CLI-written\n // string ports (\"5555\") are accepted just like number ports (5555).\n // Pass each through its sanitizer and fall back to undefined on error\n // so a corrupt saved value doesn't prevent install from defaulting.\n if (prev?.profile != null) {\n try {\n hermesSavedProfile = sanitizeHermesProfile(String(prev.profile));\n } catch {\n // Invalid saved profile — fall through to default\n }\n }\n if (prev?.host != null) {\n try {\n hermesSavedHost = sanitizeHermesHost(String(prev.host));\n } catch {\n // Invalid saved host — fall through to default\n }\n }\n if (prev?.port != null) {\n try {\n const coercedPort = Number(String(prev.port));\n hermesSavedPort = sanitizeHermesPort(coercedPort);\n } catch {\n // Invalid saved port — fall through to default\n }\n }\n } catch {\n // Could not read existing config — fall through to defaults\n }\n }\n // Use saved/default values here; user-supplied profile/host are validated\n // and applied in the sanitization block below (single point of validation).\n hermesResolvedProfile = hermesSavedProfile ?? \"default\";\n hermesResolvedHost = hermesSavedHost ?? \"127.0.0.1\";\n\n // Issue C: wrap sanitizeHermesPort (and profile/host) in try-catch so\n // that invalid user-supplied values return a clean error result.\n if (options.config?.port !== undefined) {\n try {\n hermesResolvedPort = sanitizeHermesPort(Number(String(options.config.port)));\n } catch (err) {\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Invalid Hermes config: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n if (hermesResolvedPort === undefined) {\n hermesResolvedPort = hermesSavedPort ?? 4318;\n }\n\n // Also validate user-supplied profile and host up-front (Issue C coverage).\n if (options.config?.profile !== undefined) {\n try {\n hermesResolvedProfile = sanitizeHermesProfile(String(options.config.profile));\n } catch (err) {\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Invalid Hermes config: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n if (options.config?.host !== undefined) {\n try {\n hermesResolvedHost = sanitizeHermesHost(String(options.config.host));\n } catch (err) {\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Invalid Hermes config: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n }\n\n // Generate a per-connector auth token so the daemon can authenticate\n // requests from this connector.\n //\n // Security gate (PRRT_kwDORJXyws56U9U0 round 6): tokens are ONLY generated\n // and persisted to tokens.json for connectors that actually require bearer-token\n // auth (manifest.requiresToken === true). MCP, CLI, embedded, and SDK connectors\n // never need a token entry on disk — generating one unconditionally materialized\n // credentials for connectors that have no auth requirement, a security regression.\n //\n // For hermes (Issue B fix): use buildTokenEntry() to generate a candidate\n // token WITHOUT immediately persisting it to tokens.json. We commit the\n // candidate to the store only AFTER upsertHermesConfig succeeds, so that\n // a failed or skipped config.yaml write never leaves the daemon with a\n // revoked token and no valid replacement written.\n //\n // For other connectors that requiresToken: generateToken() is idempotent —\n // it filters the old entry and writes a fresh one atomically, so force-reinstall\n // produces a new token automatically.\n //\n // Token write errors (e.g. read-only HOME with writable XDG_CONFIG_HOME)\n // are non-fatal: we degrade gracefully and proceed with the connector\n // config write rather than aborting the whole install.\n //\n // For non-Hermes connectors that requiresToken: snapshot the FULL token store\n // BEFORE generateToken() so that if the connector JSON write later fails, we\n // can restore the store to its pre-install state (UXJG fix — non-Hermes atomic\n // rollback). Using a full-store snapshot (not a single-entry snapshot) ensures\n // that a partial write of tokens.json during generateToken can be unwound\n // atomically, covering both fresh-install and force-reinstall cases uniformly.\n const nonHermesPriorTokenStore = (options.connectorId !== \"hermes\" && manifest.requiresToken)\n ? loadTokenStore()\n : null;\n\n let tokenEntry: ReturnType<typeof generateToken> | null = null;\n if (options.connectorId === \"hermes\") {\n // Build a candidate token; do NOT save yet (Issue B).\n try {\n tokenEntry = buildTokenEntry(options.connectorId);\n } catch {\n // Non-fatal: fall through with tokenEntry === null.\n }\n } else if (manifest.requiresToken) {\n // Only generate and persist a token entry for connectors that need token auth.\n try {\n tokenEntry = generateToken(options.connectorId);\n } catch {\n // Non-fatal: token store unavailable. Connector config will still be\n // written; user can run `remnic token generate <id>` to create the token.\n //\n // Roll back the snapshot so that a partial write of tokens.json during\n // generateToken (e.g. ENOSPC/EIO mid-write) does not leave other\n // connectors' auth state corrupted. Best-effort: if the restore itself\n // fails there is nothing more we can do here, but the error is swallowed\n // so install continues in the same degraded (tokenEntry === null) path\n // as before (PRRT_kwDORJXyws56UleN fix).\n if (nonHermesPriorTokenStore !== null) {\n try {\n saveTokenStore(nonHermesPriorTokenStore);\n } catch {\n // Best-effort: snapshot restore failed; caller sees degraded install.\n }\n }\n }\n }\n // else: connector does not require token auth — tokenEntry stays null and\n // tokens.json is never touched for this connector.\n\n // Thread 2 (PRRT_kwDORJXyws56VYwM): if the connector requires token auth but\n // generateToken threw (tokenEntry is still null), abort now instead of\n // continuing with a broken install that returns \"success\" without a valid token.\n if (options.connectorId !== \"hermes\" && manifest.requiresToken && tokenEntry === null) {\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message:\n `${manifest.name} install aborted: token generation failed. ` +\n `Run \\`remnic token generate ${options.connectorId}\\` to create the token, then reinstall.`,\n };\n }\n\n // Build config from schema defaults + user overrides.\n // Codex P1 (PRRT_kwDORJXyws56U9U0): tokens MUST NOT be written into\n // connector.json. The authoritative store is tokens.json (0o600). Writing the\n // token here created a second, unredacted copy that `remnic connectors list\n // --json` printed verbatim, leaking live bearer tokens into shell history, CI\n // logs, and telemetry. Callers needing the token for a specific connector\n // must use loadTokenStore() and find the entry by connectorId directly.\n //\n // For hermes, include the resolved profile/host/port so that future\n // force-reinstalls can read them back even if options.config is not supplied.\n //\n // Strip any stray `token` key the caller may have supplied via options.config\n // so it cannot be persisted to disk even on legacy call paths.\n const { token: _callerToken, ...safeUserConfig } = (options.config ?? {}) as Record<string, unknown>;\n const resolvedConfig: Record<string, unknown> = {\n connectorId: options.connectorId,\n installedAt: new Date().toISOString(),\n ...safeUserConfig,\n // For hermes, always overlay the sanitized/coerced resolved values so that\n // the connector JSON always has a numeric port and validated profile/host.\n // This also ensures options.config string values (from --config=port=5555)\n // are replaced with their sanitized numeric equivalents (Fix 2 root cause).\n ...(hermesResolvedProfile !== undefined ? {\n profile: hermesResolvedProfile,\n host: hermesResolvedHost,\n port: hermesResolvedPort,\n } : {}),\n };\n\n // ── Hermes atomic install flow ─────────────────────────────────────────────\n // The Hermes install sequence must be atomic: connector.json must only be\n // written if and only if both the YAML write AND the token-store commit\n // succeed. Partial failures must leave the prior state intact so the daemon\n // keeps working with the old token.\n //\n // Step order (all-or-nothing):\n // a. Generate token candidate (buildTokenEntry, no store write yet).\n // b. Validate profile (fail-fast).\n // c. Write config.yaml via upsertHermesConfig — if skipped (missing dir)\n // or throws, abort with status \"error\". Old token is NOT revoked.\n // d. Commit new token to tokens.json — if this throws, rollback the YAML\n // write (restore prior content or delete new file) and abort.\n // e. Write connector.json only after both (c) and (d) succeed.\n // f. Health check — gated on committed === true && tokenEntry != null.\n //\n // Non-Hermes connectors: connector.json is written immediately (no YAML\n // dependency) and the health check is not performed.\n\n if (options.connectorId === \"hermes\") {\n // hermesResolvedProfile/Host/Port were computed above using the correct\n // precedence (saved JSON → explicit options.config → defaults).\n const rawProfile = hermesResolvedProfile!;\n const hermesHost = hermesResolvedHost!;\n const hermesPort = hermesResolvedPort!;\n\n // (b) Validate profile name — fail-fast before touching any files.\n let hermesProfile: string;\n try {\n hermesProfile = sanitizeHermesProfile(rawProfile);\n } catch (err) {\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Hermes install aborted: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n\n // Token generation is required for an atomic Hermes install.\n if (!tokenEntry) {\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message:\n \"Hermes install aborted: token store unavailable. \" +\n \"Run `remnic token generate hermes` then reinstall to complete setup.\",\n };\n }\n\n // (c) Write config.yaml. If the profile dir does not exist (skipped) or\n // the write throws, abort WITHOUT committing the token or writing connector.json.\n let yamlResult: HermesConfigResult;\n try {\n yamlResult = upsertHermesConfig({\n profile: hermesProfile,\n host: hermesHost,\n port: hermesPort,\n token: tokenEntry.token,\n });\n } catch (err) {\n // upsertHermesConfig threw — old token preserved, connector.json unchanged.\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Hermes install aborted: config.yaml write failed — ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n\n if (!yamlResult.updated) {\n // Skipped (profile dir missing) — abort so connector.json is NOT written\n // with a token the daemon won't recognize (the profile doesn't exist).\n // Preserves any prior Hermes profile/connector.json untouched.\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Hermes install aborted: ${yamlResult.reason ?? \"config.yaml not written\"}. ` +\n `Create the Hermes profile directory first, then reinstall.`,\n };\n }\n\n // (d) Commit token to tokens.json. If this fails, roll back the YAML write\n // and abort — the old token must remain valid and connector.json must stay\n // unchanged so the daemon keeps working.\n //\n // IMPORTANT (UXJI/UXJT): Snapshot the FULL token store BEFORE calling\n // commitTokenEntry(). A single-entry approach (capturing the return value\n // of commitTokenEntry) is insufficient: if commitTokenEntry throws mid-write\n // (e.g. ENOSPC truncating tokens.json), the assignment never completes and\n // the rollback becomes a no-op, leaving tokens.json potentially corrupt.\n // The full-store snapshot, captured before the write attempt, is guaranteed\n // clean and can be written back atomically by saveTokenStore.\n const priorTokenStore = loadTokenStore();\n let committed = false;\n try {\n commitTokenEntry(tokenEntry);\n committed = true;\n } catch (commitErr) {\n // Roll back the token store: restore the full snapshot so a partial write\n // (e.g. ENOSPC truncating tokens.json mid-write) cannot leave the store\n // corrupt or missing the prior hermes entry.\n let tokensRolledBack = true;\n let tokensRollbackErrMsg = \"\";\n try {\n saveTokenStore(priorTokenStore);\n } catch (tokenRestoreErr) {\n tokensRolledBack = false;\n tokensRollbackErrMsg = tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);\n }\n // Roll back the YAML write: restore prior content (or delete newly-created file).\n let yamlRolledBack = true;\n let yamlRollbackErrMsg = \"\";\n try {\n if (yamlResult.priorContent === null) {\n // File was created new — remove it entirely.\n fs.unlinkSync(yamlResult.configPath);\n } else if (typeof yamlResult.priorContent === \"string\") {\n // File existed before — restore original content.\n writeSecretFileSync(yamlResult.configPath, yamlResult.priorContent);\n }\n } catch (yamlRestoreErr) {\n yamlRolledBack = false;\n yamlRollbackErrMsg = yamlRestoreErr instanceof Error ? yamlRestoreErr.message : String(yamlRestoreErr);\n }\n // Build an error message that accurately reflects which rollbacks succeeded.\n const commitErrMsg = commitErr instanceof Error ? commitErr.message : String(commitErr);\n let message: string;\n if (tokensRolledBack && yamlRolledBack) {\n message =\n `Hermes install failed during token commit — ` +\n `${commitErrMsg}. ` +\n `config.yaml and tokens.json restored to prior state. ` +\n `Resolve the tokens.json access issue, then reinstall.`;\n } else if (!yamlRolledBack && tokensRolledBack) {\n message =\n `Hermes install failed during token commit — ` +\n `${commitErrMsg}. ` +\n `tokens.json restored but config.yaml rollback ALSO failed ` +\n `(${yamlRollbackErrMsg}). ` +\n `Hermes daemon may be in an inconsistent state: config references a stale token. ` +\n `Manually inspect ~/.hermes/profiles/${hermesProfile}/config.yaml and reinstall.`;\n } else if (yamlRolledBack && !tokensRolledBack) {\n message =\n `Hermes install failed during token commit — ` +\n `${commitErrMsg}. ` +\n `config.yaml restored but tokens.json rollback ALSO failed ` +\n `(${tokensRollbackErrMsg}). ` +\n `Hermes daemon may be in an inconsistent state: tokens.json is corrupt or incomplete. ` +\n `Manually inspect ~/.remnic/tokens.json and reinstall.`;\n } else {\n message =\n `Hermes install failed during token commit — ` +\n `${commitErrMsg}. ` +\n `BOTH rollbacks failed: config.yaml rollback failed (${yamlRollbackErrMsg}); ` +\n `tokens.json rollback failed (${tokensRollbackErrMsg}). ` +\n `Hermes daemon is likely in an inconsistent state. ` +\n `Manually inspect ~/.hermes/profiles/${hermesProfile}/config.yaml ` +\n `and ~/.remnic/tokens.json, then reinstall.`;\n }\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message,\n };\n }\n\n // (e) Both YAML write and token commit succeeded — now attempt to write connector.json.\n // If this write fails (e.g. connectors dir is not writable), roll back Phase D (token\n // commit) and Phase C (YAML upsert) so no partial-install state is left behind.\n // We restore the full token store snapshot captured before Phase D so that\n // tokens.json is guaranteed consistent with the rolled-back config.yaml.\n try {\n writeSecretFileSync(configPath, JSON.stringify(resolvedConfig, null, 2));\n } catch (writeErr) {\n // Roll back Phase D: restore the full token store snapshot so tokens.json\n // is consistent with the rolled-back config.yaml.\n let tokenRollbackFailed = false;\n let tokenRollbackMsg = \"token store restored to pre-install snapshot\";\n try {\n saveTokenStore(priorTokenStore);\n } catch (tokenRestoreErr) {\n tokenRollbackFailed = true;\n tokenRollbackMsg = `token rollback failed: ${tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr)}`;\n }\n // Roll back Phase C: restore config.yaml to its prior content.\n let yamlRollbackMsg = \"config.yaml restored\";\n try {\n if (yamlResult.priorContent === null) {\n // File was created new — delete it. Track whether the unlink actually\n // succeeded so we report honestly rather than claiming removal when it\n // silently failed inside the inner catch.\n let unlinkSucceeded = false;\n let unlinkErr: unknown;\n try {\n fs.unlinkSync(yamlResult.configPath);\n unlinkSucceeded = true;\n } catch (err) {\n unlinkErr = err;\n }\n if (unlinkSucceeded) {\n yamlRollbackMsg = \"config.yaml removed (was newly created)\";\n } else {\n const unlinkMsg = unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr);\n yamlRollbackMsg = `config.yaml rollback failed: could not remove newly-created file — ${unlinkMsg}`;\n }\n } else if (typeof yamlResult.priorContent === \"string\") {\n writeSecretFileSync(yamlResult.configPath, yamlResult.priorContent);\n yamlRollbackMsg = \"config.yaml restored to prior content\";\n }\n } catch (yamlRollbackErr) {\n yamlRollbackMsg = `config.yaml rollback failed: ${yamlRollbackErr instanceof Error ? yamlRollbackErr.message : String(yamlRollbackErr)}`;\n }\n const urgentSuffix = tokenRollbackFailed\n ? ` tokens.json may be in an inconsistent state — manually restore hermes token with 'remnic token generate hermes'.`\n : \"\";\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message:\n `Hermes install aborted: connector config write failed — ` +\n `connector directory may not be writable. ` +\n `Rollback: ${tokenRollbackMsg}; ${yamlRollbackMsg}.` +\n `${urgentSuffix} Resolve the permission issue, then reinstall.`,\n };\n }\n\n const notes: string[] = [];\n notes.push(`Updated Hermes config: ${yamlResult.configPath}`);\n\n // Clean up the old profile's remnic: block if the profile changed.\n // Compare resolved config paths (not raw strings) so that case-insensitive\n // filesystems (macOS default) don't treat \"Research\" and \"research\" as\n // different profiles — resolving both would yield the same config.yaml,\n // and removing it would strip the block we just wrote (PRRT_kwDORJXyws56VQ76).\n let oldProfileResolvesToDifferentFile = false;\n if (hermesSavedProfile !== undefined) {\n try {\n oldProfileResolvesToDifferentFile =\n hermesConfigPath(hermesSavedProfile) !== hermesConfigPath(hermesProfile);\n } catch {\n // If either profile fails sanitization the comparison is moot; skip cleanup.\n oldProfileResolvesToDifferentFile = false;\n }\n }\n if (oldProfileResolvesToDifferentFile) {\n try {\n const oldCleanResult = removeHermesConfig({ profile: hermesSavedProfile! });\n if (oldCleanResult.updated) {\n notes.push(`Cleaned stale remnic: block from previous profile: ${oldCleanResult.configPath}`);\n }\n } catch {\n // Non-fatal: if we can't clean the old profile, log a note but don't fail.\n notes.push(`Note: could not clean stale remnic: block from previous profile \"${hermesSavedProfile}\"`);\n }\n }\n\n // (f) Health check — only when the token was actually committed to the store.\n // Without commitment, the daemon won't recognise the token → 401 → 6s sleep\n // → false-negative \"Daemon not reachable\". committed is always true here\n // (we returned early on failure above) but the explicit guard is kept for\n // clarity and future robustness.\n if (committed && tokenEntry) {\n const daemonOk = checkDaemonHealth(hermesHost, hermesPort, tokenEntry.token);\n if (daemonOk) {\n notes.push(\"Daemon health check: OK\");\n } else {\n notes.push(\n `Daemon not reachable at ${hermesHost}:${hermesPort} — start with: remnic daemon start`,\n );\n }\n }\n\n const suffix = notes.length > 0 ? `\\n ${notes.join(\"\\n \")}` : \"\";\n return {\n connectorId: options.connectorId,\n status: \"installed\",\n configPath,\n message: `Installed ${manifest.name} v${manifest.version}${suffix}`,\n };\n }\n\n // ── Non-Hermes connectors: write connector.json ───────────────────────────\n // Write with owner-only permissions because the JSON may embed the\n // connector bearer token. Matches the 0o600 hardening on\n // ~/.remnic/tokens.json so the token is never world-readable via this\n // secondary location.\n\n // Codex CLI: also drop the phase-2 memory extension unless the caller\n // explicitly opted out via `config.installExtension: false`.\n let extensionMessage = \"\";\n // Explicit structured flag for the config-write rollback gate. This MUST\n // stay decoupled from `extensionMessage` because that string embeds the\n // install path — substring-matching on \"skipped\" would misfire whenever\n // the codex home happens to contain the word \"skipped\".\n let extensionInstalled = false;\n // Holds the commit/rollback handle returned by installCodexMemoryExtension().\n // The backup of any prior extension is kept alive until commit() is called.\n let extensionHandle: { commit(): void; rollback(): void } | null = null;\n if (options.connectorId === \"codex-cli\") {\n // Finding 1: coerce string \"false\"/\"true\" from CLI config parsing to a real\n // boolean before the gate check, then persist the coerced value so it is\n // stored as a boolean in the config file.\n const coerced = coerceInstallExtension(resolvedConfig.installExtension);\n if (coerced !== undefined) {\n resolvedConfig.installExtension = coerced;\n }\n const shouldInstall = resolvedConfig.installExtension !== false;\n // Persist the effective installExtension boolean explicitly so that\n // removeConnector's provenance check (Finding 3) can match. When the caller\n // did not pass the key, the default is true — write it so later removal\n // knows Remnic owned the install.\n resolvedConfig.installExtension = shouldInstall;\n // Resolve the Codex home path NOW so we can persist the absolute path\n // into the saved config. This guarantees removeConnector can target the\n // exact same directory later even if $CODEX_HOME is unset or changed.\n const codexHomeOverride =\n typeof resolvedConfig.codexHome === \"string\" && resolvedConfig.codexHome.length > 0\n ? (resolvedConfig.codexHome as string)\n : null;\n const resolvedCodexHome = resolveCodexHome(codexHomeOverride);\n resolvedConfig.codexHome = resolvedCodexHome;\n\n if (shouldInstall) {\n try {\n const extensionSourceOverride =\n typeof resolvedConfig.extensionSourceDir === \"string\" &&\n resolvedConfig.extensionSourceDir.length > 0\n ? (resolvedConfig.extensionSourceDir as string)\n : null;\n const extResult = installCodexMemoryExtension({\n codexHome: resolvedCodexHome,\n sourceDir: extensionSourceOverride,\n });\n extensionMessage = ` (memory extension: ${extResult.remnicExtensionDir})`;\n extensionInstalled = true;\n extensionHandle = extResult;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : \"unknown error\";\n // Codex P2 (PRRT_kwDORJXyws56Ur_G): generateToken already rotated\n // tokens.json before reaching this point. The extension threw, so no\n // connector.json was written — roll back the token store to the\n // pre-install snapshot so tokens.json and the absent connector.json\n // stay consistent (no orphaned/active token without a matching config).\n //\n // Initialize to false: only set true once saveTokenStore() succeeds.\n // For connectors without requiresToken the rollback block is skipped\n // entirely, so the suffix must remain absent — not \"Token has been\n // rolled back.\" (which would be factually incorrect).\n let extensionErrTokenRolledBack = false;\n let extensionErrTokenRollbackMsg = \"\";\n if (tokenEntry !== null && nonHermesPriorTokenStore !== null) {\n try {\n saveTokenStore(nonHermesPriorTokenStore);\n extensionErrTokenRolledBack = true;\n } catch (tokenRestoreErr) {\n extensionErrTokenRolledBack = false;\n extensionErrTokenRollbackMsg =\n tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);\n }\n }\n // Only include a token-rollback suffix for connectors that have a token\n // to roll back. Non-token connectors (requiresToken !== true) never\n // generated a token entry, so no rollback occurred and the message must\n // not claim otherwise.\n const tokenRollbackSuffix = manifest.requiresToken\n ? extensionErrTokenRolledBack\n ? \" Token has been rolled back.\"\n : ` Token rollback FAILED (${extensionErrTokenRollbackMsg}) — tokens.json may contain an orphaned entry. ` +\n `Manually inspect ~/.remnic/tokens.json and reinstall.`\n : \"\";\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Memory extension install failed — ${errMsg}.${tokenRollbackSuffix} Resolve the issue, then reinstall.`,\n };\n }\n } else {\n extensionMessage = \" (memory extension: skipped via installExtension=false)\";\n }\n }\n\n // ── WeClone: write proxy config to ~/.remnic/connectors/weclone.json ─────\n //\n // The standalone `remnic-weclone-proxy` CLI (see packages/connector-weclone)\n // reads its config from ~/.remnic/connectors/weclone.json by default so the\n // proxy can start without depending on Remnic's XDG-scoped registry layout.\n // Compose and write that file here, BEFORE the registry connector.json is\n // written, so that a failure in either file's write path rolls back cleanly.\n //\n // Precedence for each field: user-supplied via --config → saved prior proxy\n // config (on --force) → manifest defaults. The generated bearer token (if\n // any) is persisted into remnicAuthToken so the proxy can authenticate with\n // the daemon without a second token lookup at runtime.\n let weCloneProxyHandleRollback: (() => void) | null = null;\n if (options.connectorId === \"weclone\") {\n try {\n // Force-reinstall (and any reinstall path) must keep using the exact\n // proxy config path that was persisted on the previous install. If we\n // re-derive from the current env each time, a user whose REMNIC_HOME /\n // ENGRAM_HOME changed between installs would end up with two proxy\n // config files — the old one stays with stale settings + a revoked\n // token, the new one gets the live token, and any running proxy still\n // reading the old file starts failing auth. Read the saved\n // `proxyConfigPath` from the existing registry config first, and only\n // fall back to env-derivation for genuine first-time installs.\n let proxyConfigPath: string | null = null;\n if (existing && fs.existsSync(configPath)) {\n try {\n const savedRegistryConfig = JSON.parse(fs.readFileSync(configPath, \"utf8\")) as Record<string, unknown>;\n if (\n typeof savedRegistryConfig.proxyConfigPath === \"string\" &&\n savedRegistryConfig.proxyConfigPath.length > 0\n ) {\n proxyConfigPath = savedRegistryConfig.proxyConfigPath;\n }\n } catch {\n // Saved registry config unreadable — fall through to env resolution.\n }\n }\n if (proxyConfigPath === null) {\n proxyConfigPath = resolveWeCloneProxyConfigPath();\n }\n const prior = readWeCloneProxyConfigIfExists(proxyConfigPath);\n const proxyConfig = buildWeCloneProxyConfig({\n userConfig: safeUserConfig,\n priorConfig: prior ? safeParseJson(prior) : null,\n authToken: tokenEntry?.token,\n });\n fs.mkdirSync(path.dirname(proxyConfigPath), { recursive: true });\n // Install the rollback closure BEFORE the write starts. `writeSecretFileSync`\n // opens the file in truncate mode, so a mid-write failure (ENOSPC, EPERM)\n // could leave `weclone.json` empty. Creating the rollback now guarantees\n // we can always restore prior content (or delete a newly-created file)\n // even if the write itself throws.\n weCloneProxyHandleRollback = () => {\n try {\n if (prior === null) {\n // File was created (or would have been created) by this install —\n // delete whatever is left behind, if anything.\n if (fs.existsSync(proxyConfigPath)) {\n fs.unlinkSync(proxyConfigPath);\n }\n } else {\n writeSecretFileSync(proxyConfigPath, prior);\n }\n } catch {\n // Best-effort rollback.\n }\n };\n try {\n writeSecretFileSync(\n proxyConfigPath,\n JSON.stringify(proxyConfig, null, 2),\n );\n } catch (writeErr) {\n // Truncate-and-write failed partway through — restore the file (or\n // remove the empty partial) and re-throw so the outer catch drives\n // the structured error response + token rollback.\n try {\n weCloneProxyHandleRollback();\n } catch {\n // Best-effort.\n }\n weCloneProxyHandleRollback = null;\n throw writeErr;\n }\n // Record the proxy-side config path on the registry JSON so operators\n // and `remnic connectors doctor weclone` can locate it later. Persist the\n // effective proxy port so `remnic connectors list` reflects the resolved\n // value rather than whatever (possibly missing) the user supplied.\n resolvedConfig.proxyConfigPath = proxyConfigPath;\n resolvedConfig.proxyPort = proxyConfig.proxyPort;\n resolvedConfig.wecloneApiUrl = proxyConfig.wecloneApiUrl;\n resolvedConfig.remnicDaemonUrl = proxyConfig.remnicDaemonUrl;\n resolvedConfig.sessionStrategy = proxyConfig.sessionStrategy;\n } catch (weCloneErr) {\n // Track token rollback success/failure explicitly so the error message\n // can truthfully report whether tokens.json was restored or is in a\n // potentially-inconsistent state. Mirrors the care taken in the\n // registry-config-write failure handler below.\n let tokenRolledBack = false;\n let tokenRollbackMsg = \"\";\n if (tokenEntry !== null && nonHermesPriorTokenStore !== null) {\n try {\n saveTokenStore(nonHermesPriorTokenStore);\n tokenRolledBack = true;\n } catch (tokenRestoreErr) {\n tokenRolledBack = false;\n tokenRollbackMsg =\n tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);\n }\n }\n const tokenSuffix = manifest.requiresToken && tokenEntry !== null\n ? tokenRolledBack\n ? \" Token has been rolled back.\"\n : ` Token rollback FAILED (${tokenRollbackMsg}) — tokens.json may contain an orphaned entry. ` +\n `Manually inspect ~/.remnic/tokens.json and reinstall.`\n : \"\";\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message:\n `WeClone install aborted: proxy config write failed — ` +\n `${weCloneErr instanceof Error ? weCloneErr.message : String(weCloneErr)}.` +\n `${tokenSuffix} Resolve the write permission issue on ~/.remnic/connectors/, then reinstall.`,\n };\n }\n }\n\n // Finding 5: strip internal/test-only keys that must never be persisted to\n // the config file. These keys are used at install time only (e.g. to inject\n // a synthetic extension source dir in tests) and have no meaning on disk.\n // Denylist — add any future test-only keys here with a comment.\n const INTERNAL_KEYS_DENYLIST = [\n \"extensionSourceDir\", // test-only override for the plugin-codex source path\n ];\n for (const key of INTERNAL_KEYS_DENYLIST) {\n delete resolvedConfig[key];\n }\n\n // Atomic rollback (UXJG / Codex P1): if the JSON write fails (e.g., permission\n // denied on XDG_CONFIG_HOME), generateToken() above already rotated the token in\n // tokens.json. Roll back via the full-store snapshot captured before generateToken\n // so tokens.json and the absent connector.json stay consistent — no stale token\n // lingers without a matching config file. Full-store restore (vs. single-entry\n // restore/revoke) handles partial writes atomically for both fresh-install and\n // force-reinstall paths uniformly.\n //\n // Also roll back any codex-cli memory extension if the config write fails so\n // that no dangling memories_extensions/remnic directory is left with no config\n // provenance for removeConnector to find and clean up later.\n try {\n writeSecretFileSync(configPath, JSON.stringify(resolvedConfig, null, 2));\n } catch (writeErr) {\n // Roll back non-hermes token store if needed. Track success so we can\n // report accurately — unconditionally claiming rollback succeeded when it\n // silently failed would leave operators unable to diagnose inconsistent state.\n //\n // Initialize to false: only set true once saveTokenStore() succeeds.\n // Non-token connectors skip this block entirely, so we must not emit a\n // \"Token has been rolled back.\" suffix for them.\n let configWriteTokenRolledBack = false;\n let configWriteTokenRollbackMsg = \"\";\n if (tokenEntry !== null && nonHermesPriorTokenStore !== null) {\n try {\n saveTokenStore(nonHermesPriorTokenStore);\n configWriteTokenRolledBack = true;\n } catch (tokenRestoreErr) {\n configWriteTokenRolledBack = false;\n configWriteTokenRollbackMsg =\n tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);\n }\n }\n // Roll back the codex-cli extension if it was installed.\n // Use extensionHandle.rollback() so that a pre-existing (possibly\n // customised) extension is restored from the backup kept by\n // installCodexMemoryExtension(), rather than unconditionally deleted.\n if (extensionInstalled && extensionHandle !== null) {\n try {\n extensionHandle.rollback();\n } catch {\n // Best-effort rollback: log but don't mask the original write error.\n console.warn(\n \"[remnic/connectors] installConnector: config write failed and extension rollback also failed — \" +\n \"manual cleanup of memories_extensions/remnic may be required.\",\n );\n }\n }\n // Roll back the WeClone proxy config if it was written.\n if (weCloneProxyHandleRollback !== null) {\n try {\n weCloneProxyHandleRollback();\n } catch {\n // Best-effort rollback.\n }\n }\n // Only include a token-rollback suffix for connectors that actually had a\n // token to roll back. Non-token connectors (requiresToken !== true) never\n // generated a token entry. For requiresToken connectors where generateToken\n // threw (tokenEntry === null), no token was written to tokens.json so no\n // rollback occurred — avoid a misleading \"Token rollback FAILED\" message\n // (Thread 1, PRRT_kwDORJXyws56VVnB).\n const configWriteTokenSuffix = manifest.requiresToken && tokenEntry !== null\n ? configWriteTokenRolledBack\n ? \" Token has been rolled back.\"\n : ` Token rollback FAILED (${configWriteTokenRollbackMsg}) — tokens.json may contain an orphaned entry. ` +\n `Manually inspect ~/.remnic/tokens.json and reinstall.`\n : \"\";\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message:\n `${manifest.name} install aborted: connector config write failed — ` +\n `${writeErr instanceof Error ? writeErr.message : String(writeErr)}.` +\n `${configWriteTokenSuffix} Resolve the write permission issue, then reinstall.`,\n };\n }\n\n // Config write succeeded — permanently drop the backup of the prior extension.\n if (extensionInstalled && extensionHandle !== null) {\n extensionHandle.commit();\n }\n\n return {\n connectorId: options.connectorId,\n status: \"installed\",\n configPath,\n message: `Installed ${manifest.name} v${manifest.version}${extensionMessage}`,\n };\n}\n\n// ── Remove connector ───────────────────────────────────────────────────────\n\nexport function removeConnector(connectorId: string): RemoveResult {\n const configDir = getConnectorsDir();\n const configPath = path.join(configDir, `${connectorId}.json`);\n\n // For codex-cli, read the saved config BEFORE touching anything so we have\n // both the persisted codexHome and the installExtension flag available for\n // later use in extension removal (Findings 1, 3, 4, 5).\n let codexHomeOverride: string | null = null;\n let savedInstallExtension: boolean | undefined = undefined;\n // Finding 1: track whether config parsing succeeded. If parsing throws, we\n // cannot trust any metadata and must fail closed (skip extension removal).\n let configParsed = false;\n if (connectorId === \"codex-cli\" && fs.existsSync(configPath)) {\n try {\n const parsed = JSON.parse(fs.readFileSync(configPath, \"utf8\")) as Record<string, unknown>;\n configParsed = true;\n if (typeof parsed.codexHome === \"string\" && parsed.codexHome.length > 0) {\n codexHomeOverride = parsed.codexHome;\n }\n // Finding 4: coerce saved installExtension so string \"false\" still works.\n const coerced = coerceInstallExtension(parsed.installExtension);\n if (coerced !== undefined) {\n savedInstallExtension = coerced;\n }\n } catch {\n // Finding 1: config is malformed — log debug and fail closed.\n // codexHomeOverride and savedInstallExtension remain unset; configParsed\n // stays false so extension removal is skipped below.\n console.debug(\n \"[remnic/connectors] removeConnector: codex-cli.json parse failed — skipping extension removal to avoid touching unverified paths\",\n );\n }\n }\n\n if (!fs.existsSync(configPath)) {\n // Best-effort: revoke any orphan token that may have survived a prior partial\n // cleanup (e.g. connector JSON deleted manually or XDG_CONFIG_HOME change).\n // This prevents a stale bearer token from remaining valid in tokens.json while\n // the connector appears \"not installed\" to the caller.\n // Config file is missing — we have no evidence that this installation ever\n // managed the extension directory, so it is unsafe to remove it (the user\n // may have self-managed it or installed with installExtension=false).\n // Skip removeCodexMemoryExtension entirely in this recovery path.\n let staleTokenRevoked = false;\n try {\n staleTokenRevoked = revokeToken(connectorId);\n } catch {\n // Best-effort: token store may be missing or read-only; do not mask the\n // not_found signal to the caller.\n }\n const message = staleTokenRevoked\n ? `${connectorId} is not installed. Removed stale token entry for ${connectorId}.`\n : \"Not installed\";\n return {\n connectorId,\n configPath,\n status: \"not_found\",\n message,\n };\n }\n\n // Read connector config before deleting it (needed for hermes profile lookup)\n let storedProfile = \"default\";\n if (connectorId === \"hermes\") {\n try {\n const stored = JSON.parse(fs.readFileSync(configPath, \"utf8\"));\n if (typeof stored?.profile === \"string\") storedProfile = stored.profile;\n } catch {\n // use default profile\n }\n }\n\n // For weclone, read the persisted proxy config path from the saved registry\n // config BEFORE deleting it. Using the persisted absolute path (rather than\n // recomputing from current REMNIC_HOME / ENGRAM_HOME / $HOME) guarantees\n // that a remove still targets the original file even if the environment\n // has changed between install and remove.\n //\n // Parse failure handling: if the registry config exists but is malformed,\n // we MUST abort the whole removal (mirror of the codex-cli provenance\n // gate). Silently falling back to an env-derived path would delete the\n // registry entry first and then miss the real proxy config if the\n // environment had since changed, orphaning the file (which may still hold\n // a live bearer token). Only install-time WRITES persist the path; if we\n // lost it on read, the only safe action is to stop and let the operator\n // fix the config or clean up manually.\n let weCloneProxyConfigPath: string | null = null;\n let weCloneRegistryParseFailed = false;\n if (connectorId === \"weclone\") {\n try {\n const stored = JSON.parse(fs.readFileSync(configPath, \"utf8\")) as Record<string, unknown>;\n if (typeof stored.proxyConfigPath === \"string\" && stored.proxyConfigPath.length > 0) {\n weCloneProxyConfigPath = stored.proxyConfigPath;\n }\n } catch {\n weCloneRegistryParseFailed = true;\n }\n // No persisted path AND parse succeeded means this is a legacy install\n // pre-dating proxyConfigPath provenance. Fall back to env resolution\n // only in that specific case so we still make a best-effort cleanup.\n if (weCloneProxyConfigPath === null && !weCloneRegistryParseFailed) {\n try {\n weCloneProxyConfigPath = resolveWeCloneProxyConfigPath();\n } catch {\n // Resolution failed (e.g. no HOME) — leave null; cleanup block skips.\n }\n }\n }\n if (connectorId === \"weclone\" && weCloneRegistryParseFailed) {\n console.warn(\n \"[remnic/connectors] removeConnector: weclone.json is malformed — \" +\n \"aborting removal to preserve provenance. Fix or delete \" +\n configPath +\n \" manually and retry.\",\n );\n return {\n connectorId,\n configPath,\n message:\n \"Removal aborted: weclone.json is malformed. Registry config left in place for inspection; \" +\n \"proxy config NOT removed.\",\n status: \"skipped\",\n reason: \"config-parse-failed\",\n };\n }\n\n // Finding 4: if the codex-cli config exists but failed to parse, abort the\n // entire removal. Leave both the config file AND the extension directory\n // untouched so the operator can inspect/fix the config file and retry.\n // Unlinking the config here would destroy the only provenance record and make\n // deterministic retry impossible.\n if (connectorId === \"codex-cli\" && fs.existsSync(configPath) && !configParsed) {\n console.warn(\n \"[remnic/connectors] removeConnector: codex-cli.json is malformed — \" +\n \"aborting removal to preserve provenance. Fix or delete \" +\n configPath +\n \" manually and retry.\",\n );\n return {\n connectorId,\n configPath,\n message: \"Removal aborted: codex-cli.json is malformed. Config file left in place for inspection.\",\n status: \"skipped\",\n reason: \"config-parse-failed\",\n };\n }\n\n // Finding 5: remove extension BEFORE deleting the config file. If extension\n // removal throws (e.g. EPERM/EBUSY), we re-throw WITHOUT deleting the config\n // so the user can retry — the config still has the persisted codexHome needed\n // to locate the extension directory.\n let extensionMessage = \"\";\n if (connectorId === \"codex-cli\") {\n // Finding 4: skip extension deletion when installExtension was explicitly disabled.\n if (savedInstallExtension === false) {\n extensionMessage = \" (memory extension: skipped — installExtension=false)\";\n // Finding 3: require EXPLICIT provenance (installExtension===true AND a saved\n // codexHome) before removing the extension. Legacy configs that pre-date this\n // feature have no installExtension key, so savedInstallExtension is undefined;\n // without provenance we cannot be sure Remnic ever owned the directory.\n } else if (savedInstallExtension !== true || codexHomeOverride === null) {\n extensionMessage = \" (memory extension: skipped — no install provenance in saved config)\";\n } else {\n const extResult = removeCodexMemoryExtension({ codexHome: codexHomeOverride });\n extensionMessage = extResult.removed\n ? ` (memory extension removed: ${extResult.remnicExtensionDir})`\n : \" (no memory extension present)\";\n }\n }\n\n // Delete the connector config file AFTER extension removal (Finding 5): if\n // extension removal throws, we do not reach here and the config is preserved.\n // Token revocation and YAML cleanup only happen after the file is gone so\n // that a failed unlink (e.g., read-only directory) does not leave a\n // token-less orphan install on disk.\n try {\n fs.unlinkSync(configPath);\n } catch (unlinkErr) {\n const sanitizedErr = unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr);\n return {\n connectorId,\n configPath,\n status: \"error\",\n message:\n `${connectorId} remove aborted: could not delete connector file (${sanitizedErr}). ` +\n `Token and any connector-specific state were not modified.`,\n };\n }\n\n // File removed — now safe to revoke the auth token.\n // Non-fatal: if the token store is read-only or missing, connector removal\n // should still succeed. Stale tokens will be rejected by the daemon when the\n // token file is later accessible.\n const notes: string[] = [];\n // Track revocation success so downstream error branches (e.g. weclone\n // proxy-delete failure) can accurately report whether the token was\n // cleaned up rather than hardcoding \"Token has been rolled back\".\n let tokenRevoked = true;\n try {\n revokeToken(connectorId);\n } catch (revokeErr) {\n // Surface the failure so callers know the token was not cleaned up.\n // The connector config has already been removed at this point.\n tokenRevoked = false;\n const revokeMsg = revokeErr instanceof Error ? revokeErr.message : String(revokeErr);\n notes.push(`Warning: token revocation failed — ${revokeMsg}. The token for ${connectorId} may still be present in tokens.json.`);\n }\n\n // WeClone-specific: remove the proxy config file at the path persisted in\n // the registry config (read above before the registry file was deleted).\n // Using the persisted absolute path — not a re-derivation from the current\n // environment — is load-bearing: if REMNIC_HOME / ENGRAM_HOME changes (or\n // is unset) between install and remove, recomputing here would leave the\n // original proxy config (with a live bearer token) on disk while reporting\n // success. If the file is present but unlink fails (e.g. EPERM), we MUST\n // surface an error status rather than pretending success — a later retry\n // via `remnic connectors remove weclone` would go down the `not_found`\n // path because the registry config was already unlinked, leaving the\n // proxy config orphaned (potentially with a still-valid token).\n let weCloneProxyDeleteFailed: string | null = null;\n if (connectorId === \"weclone\") {\n if (weCloneProxyConfigPath === null) {\n notes.push(\n \"WeClone proxy config cleanup skipped: no persisted path found in saved config \" +\n \"(likely a legacy install predating proxyConfigPath provenance).\",\n );\n } else {\n // Safety gate: validate the persisted path before unlinking. Because\n // `weCloneProxyConfigPath` is loaded from user-controlled JSON, a\n // malformed or tampered weclone.json could make `removeConnector` delete\n // an arbitrary file. Restrict deletion to paths that are:\n // 1. Absolute (relative paths are CWD-dependent and were never written\n // by the installer).\n // 2. End with the known suffix \"connectors/weclone.json\" — the only\n // filename the installer ever writes, regardless of base directory.\n // If either check fails, skip the unlink and surface an error so the\n // operator can clean up manually. Failing closed is safer than silently\n // deleting an unexpected path.\n const expectedSuffix = path.join(\"connectors\", \"weclone.json\");\n const isSafePath =\n path.isAbsolute(weCloneProxyConfigPath) &&\n weCloneProxyConfigPath.endsWith(expectedSuffix);\n if (!isSafePath) {\n weCloneProxyDeleteFailed =\n `Proxy config path ${JSON.stringify(weCloneProxyConfigPath)} failed safety validation ` +\n `(must be absolute and end with \"${expectedSuffix}\"). ` +\n `Refusing to delete — remove the file manually if it exists.`;\n } else {\n try {\n if (fs.existsSync(weCloneProxyConfigPath)) {\n fs.unlinkSync(weCloneProxyConfigPath);\n notes.push(`Removed WeClone proxy config: ${weCloneProxyConfigPath}`);\n }\n } catch (err) {\n // Hard failure: leaving the file behind with a live token is a\n // security issue. Capture the error so we return status:\"error\".\n weCloneProxyDeleteFailed = err instanceof Error ? err.message : String(err);\n }\n }\n }\n }\n if (weCloneProxyDeleteFailed !== null && weCloneProxyConfigPath !== null) {\n // Report the token-revocation status truthfully. If revocation already\n // failed above, claiming the token was \"cleaned up\" here would mislead\n // the operator into thinking the only action left is deleting the\n // orphan file — when in reality the bearer token is also still live.\n const tokenStatus = tokenRevoked\n ? \"the registry config was deleted and the token was revoked\"\n : \"the registry config was deleted but TOKEN REVOCATION ALSO FAILED — \" +\n \"inspect ~/.remnic/tokens.json and revoke manually\";\n return {\n connectorId,\n configPath,\n status: \"error\",\n message:\n `WeClone remove partially succeeded: ${tokenStatus}, ` +\n `but the proxy config at ${weCloneProxyConfigPath} could not be deleted ` +\n `(${weCloneProxyDeleteFailed}). Manually remove that file — it may still contain ` +\n `a Remnic daemon bearer token.`,\n };\n }\n\n // Hermes-specific: strip the remnic: block from config.yaml.\n // Only attempted after successful file removal so that config.yaml cleanup\n // is consistent with the connector JSON state.\n if (connectorId === \"hermes\") {\n try {\n const yamlResult = removeHermesConfig({ profile: storedProfile });\n if (yamlResult.updated) {\n notes.push(`Removed remnic: block from Hermes config: ${yamlResult.configPath}`);\n } else if (yamlResult.skipped) {\n notes.push(`Hermes config cleanup skipped: ${yamlResult.reason}`);\n }\n } catch (err) {\n notes.push(\n `Hermes config cleanup skipped: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n const suffix = notes.length > 0 ? `\\n ${notes.join(\"\\n \")}` : \"\";\n return {\n connectorId,\n configPath,\n status: \"removed\",\n message: `Removed${extensionMessage}${suffix}`,\n };\n}\n\n// ── Hermes config.yaml helpers ─────────────────────────────────────────────────\n\ninterface HermesConfigResult {\n updated: boolean;\n skipped: boolean;\n reason?: string;\n configPath: string;\n /**\n * The exact byte-for-byte content of the config.yaml that existed BEFORE\n * this upsert ran. `null` when the file did not exist (new file was created).\n * `undefined` when the write was skipped (priorContent is irrelevant).\n * Used by installConnector to roll back the YAML write if commitTokenEntry\n * subsequently throws.\n */\n priorContent?: string | null;\n}\n\n/**\n * Validate and sanitize a Hermes profile name.\n *\n * Profile names appear as a path segment under `~/.hermes/profiles/`, so we\n * must reject any value that could traverse outside that directory. Hermes\n * itself restricts profile names to filesystem-safe identifiers; we mirror\n * that convention and additionally require the resolved config path to stay\n * under the profiles root.\n *\n * Throws on invalid input rather than silently normalizing — the caller\n * should surface the error so the user can supply a valid profile.\n */\nfunction sanitizeHermesProfile(profile: string): string {\n if (typeof profile !== \"string\" || profile.length === 0) {\n throw new Error(\"Hermes profile name must be a non-empty string\");\n }\n // Disallow anything that isn't a plain profile identifier. We accept\n // letters, digits, hyphen, underscore, and dot — but reject leading dots\n // (hidden dirs) and any path separator or parent-dir reference.\n if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(profile)) {\n throw new Error(\n `Invalid Hermes profile name: ${JSON.stringify(profile)} — must match [A-Za-z0-9][A-Za-z0-9._-]*`,\n );\n }\n if (profile.includes(\"..\")) {\n throw new Error(`Invalid Hermes profile name: ${JSON.stringify(profile)} — must not contain \"..\"`);\n }\n return profile;\n}\n\nfunction hermesConfigPath(profile: string): string {\n const safeProfile = sanitizeHermesProfile(profile);\n // Use process.env.HOME consistent with getConnectorsDir() and tokens.ts\n // defaultTokensPath(); fall back to os.homedir() for robustness.\n const profilesRoot = path.resolve(process.env.HOME ?? os.homedir(), \".hermes\", \"profiles\");\n const cfgPath = path.resolve(profilesRoot, safeProfile, \"config.yaml\");\n // Defense in depth: ensure the resolved path is still under profilesRoot.\n const rel = path.relative(profilesRoot, cfgPath);\n if (rel.startsWith(\"..\") || path.isAbsolute(rel)) {\n throw new Error(\n `Invalid Hermes profile path: resolved outside ${profilesRoot}`,\n );\n }\n return cfgPath;\n}\n\n/**\n * Validate a Hermes host string before interpolating it into YAML.\n *\n * YAML-injection guard: connector config values come from raw CLI input\n * (`--config host=...`) or config-file JSON, both of which are untrusted.\n * Without validation, a value like `127.0.0.1\"\\n session_key: \"evil`\n * would emit additional YAML keys into the `remnic:` block and silently\n * override Hermes settings.\n *\n * Accepted forms:\n * - Plain IPv4: 127.0.0.1, 10.0.0.5\n * - Plain DNS hostname: localhost, foo.example.com\n * - Bracketed IPv6 literal: [::1], [2001:db8::1]\n *\n * Rejected forms:\n * - host:port combos: 127.0.0.1:4318 (colons not allowed outside brackets)\n * - Unbalanced brackets: [::1\n * - Any whitespace, quotes, or control characters\n *\n * Hermes builds its base URL as `http://{host}:{port}`, so supplying a\n * host that already embeds a port (e.g. \"127.0.0.1:4318\") would produce\n * the double-port URL \"http://127.0.0.1:4318:4318/...\" and fail at runtime\n * even though install reports success. We reject that form here.\n */\nfunction sanitizeHermesHost(host: string): string {\n if (typeof host !== \"string\" || host.length === 0) {\n throw new Error(\"Hermes host must be a non-empty string\");\n }\n if (host.length > 253) {\n throw new Error(`Hermes host too long (max 253 chars): ${JSON.stringify(host.slice(0, 32))}…`);\n }\n\n // Bracketed IPv6 literal: must start with \"[\", end with \"]\", and contain\n // only hex digits and colons inside the brackets.\n if (host.startsWith(\"[\")) {\n if (!host.endsWith(\"]\")) {\n throw new Error(\n `Invalid Hermes host: ${JSON.stringify(host)} — unbalanced brackets in IPv6 literal`,\n );\n }\n const inner = host.slice(1, -1);\n if (inner.length === 0 || !/^[0-9A-Fa-f:]+$/.test(inner)) {\n throw new Error(\n `Invalid Hermes host: ${JSON.stringify(host)} — bracketed IPv6 literal must contain only hex digits and colons`,\n );\n }\n return host;\n }\n\n // Unbracketed value: colons are not allowed (would indicate an embedded port\n // or an unbracketed IPv6 address, both of which must be rejected here).\n if (host.includes(\":\")) {\n throw new Error(\n `Invalid Hermes host: ${JSON.stringify(host)} — host must not include a port; supply the port separately with --config port=<n>`,\n );\n }\n\n // Plain IPv4 or DNS hostname: allow letters, digits, dots, and hyphens only.\n // No whitespace, quotes, or control characters.\n if (!/^[A-Za-z0-9._\\-]+$/.test(host)) {\n throw new Error(\n `Invalid Hermes host: ${JSON.stringify(host)} — must be a plain hostname or IP literal`,\n );\n }\n return host;\n}\n\n/**\n * Validate a Hermes port value. Accepts positive integers in [1, 65535].\n *\n * Rejects non-integer numeric strings (e.g. \"4318.9\") rather than silently\n * truncating them — a fractional port is almost certainly a typo and writing\n * the truncated value to config.yaml would be misleading.\n */\nfunction sanitizeHermesPort(port: number | string): number {\n const numeric = Number(port);\n // Reject NaN, Infinity, -Infinity, and any non-integer (e.g. 4318.9)\n if (!Number.isInteger(numeric)) {\n throw new Error(\n `Invalid Hermes port \"${port}\": must be a positive integer`,\n );\n }\n if (numeric < 1 || numeric > 65535) {\n throw new Error(`Invalid Hermes port: ${JSON.stringify(port)} — must be an integer in [1, 65535]`);\n }\n return numeric;\n}\n\n/**\n * Write a file with owner-only (0o600) permissions.\n *\n * Used for any file that may contain a bearer token. writeFileSync's `mode`\n * option only applies when the file is newly created, so we also chmod\n * afterwards to tighten permissions on pre-existing files. The chmod is\n * best-effort on platforms that don't support POSIX modes.\n */\nfunction writeSecretFileSync(filePath: string, data: string): void {\n fs.writeFileSync(filePath, data, { mode: 0o600 });\n try {\n fs.chmodSync(filePath, 0o600);\n } catch {\n /* best-effort on non-POSIX filesystems */\n }\n}\n\n/**\n * Upsert the `remnic:` block in a Hermes profile config.yaml.\n *\n * Rules:\n * - If the profile directory does not exist, skip with a warning (we do not\n * create arbitrary Hermes state).\n * - If config.yaml does not exist, create it with only the remnic: block.\n * - If config.yaml exists and already contains a `remnic:` block, update the\n * host/port/token lines in-place (line-based, preserves comments elsewhere).\n * - If config.yaml exists with no `remnic:` block, append one.\n * - Idempotent on repeated calls.\n */\nexport function upsertHermesConfig(opts: {\n profile: string;\n host: string;\n port: number;\n token: string;\n}): HermesConfigResult {\n const cfgPath = hermesConfigPath(opts.profile);\n const profileDir = path.dirname(cfgPath);\n\n // YAML-injection guard: validate scalar values before interpolating them\n // into the `remnic:` block. sanitizeHermesHost/Port throw on anything\n // that could break out of the scalar context.\n const safeHost = sanitizeHermesHost(opts.host);\n const safePort = sanitizeHermesPort(opts.port);\n // Token is generated by randomBytes + a fixed alphabetic prefix, so it's\n // already safe for YAML scalar interpolation. We still guard against an\n // unexpectedly malformed token reaching this function.\n if (!/^[A-Za-z0-9_]+$/.test(opts.token)) {\n throw new Error(\"Invalid Hermes token: contains non-alphanumeric characters\");\n }\n\n if (!fs.existsSync(profileDir)) {\n return {\n updated: false,\n skipped: true,\n reason: `Hermes profile directory not found: ${profileDir}`,\n configPath: cfgPath,\n };\n }\n\n const block = [\n \"remnic:\",\n ` host: \"${safeHost}\"`,\n ` port: ${safePort}`,\n ` token: \"${opts.token}\"`,\n ].join(\"\\n\");\n\n if (!fs.existsSync(cfgPath)) {\n // Create with just the remnic block. 0o600 because the file now holds\n // a bearer token — matching the permissions on ~/.remnic/tokens.json.\n writeSecretFileSync(cfgPath, block + \"\\n\");\n // priorContent: null signals \"file was created new\" — rollback means delete.\n return { updated: true, skipped: false, configPath: cfgPath, priorContent: null };\n }\n\n const raw = fs.readFileSync(cfgPath, \"utf8\");\n\n // Check whether there's an existing remnic: block\n const hasRemnicBlock = /^remnic:/m.test(raw);\n\n if (!hasRemnicBlock) {\n // Append the block (preserve existing content)\n const separator = raw.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n writeSecretFileSync(cfgPath, raw + separator + block + \"\\n\");\n // priorContent: raw preserves the original file so it can be restored on rollback.\n return { updated: true, skipped: false, configPath: cfgPath, priorContent: raw };\n }\n\n // Update the existing block. Strategy: replace the content of the remnic:\n // section by matching from `^remnic:` to the next top-level key or end-of-file.\n // We rewrite only the host/port/token sub-keys inside the block; other keys\n // under remnic: (e.g. session_key, timeout) are preserved.\n //\n // Trailing-newline handling: split(\"\\n\") on a file that ends with \"\\n\" produces\n // a final empty-string element. If that element is still inside the remnic block\n // when we hit it, it gets pushed to newLines via the else branch — placing a\n // blank line between existing sub-keys and any newly-appended missing sub-keys.\n // We strip the trailing empty element before the loop and re-add a single \"\\n\"\n // at write time, normalising the file to always end with exactly one newline.\n const splitLines = raw.split(\"\\n\");\n // Remove trailing empty element produced by a file that ends with \"\\n\"\n if (splitLines.length > 0 && splitLines[splitLines.length - 1] === \"\") {\n splitLines.pop();\n }\n const lines = splitLines;\n const newLines: string[] = [];\n let inRemnicBlock = false;\n let blockWritten = false;\n\n // Track which sub-keys we've emitted\n const written = { host: false, port: false, token: false };\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n\n if (/^remnic:/.test(line)) {\n inRemnicBlock = true;\n newLines.push(line);\n continue;\n }\n\n if (inRemnicBlock) {\n // A line that starts with a non-space character and is not empty signals\n // the start of the next top-level YAML key — we've left the remnic block.\n if (line.length > 0 && !/^\\s/.test(line)) {\n // Emit any un-written keys before closing the block. Uses the\n // already-validated safeHost/safePort values.\n if (!written.host) newLines.push(` host: \"${safeHost}\"`);\n if (!written.port) newLines.push(` port: ${safePort}`);\n if (!written.token) newLines.push(` token: \"${opts.token}\"`);\n blockWritten = true;\n inRemnicBlock = false;\n newLines.push(line);\n continue;\n }\n\n // Replace host/port/token lines; preserve other sub-keys\n if (/^\\s+host:/.test(line)) {\n newLines.push(` host: \"${safeHost}\"`);\n written.host = true;\n } else if (/^\\s+port:/.test(line)) {\n newLines.push(` port: ${safePort}`);\n written.port = true;\n } else if (/^\\s+token:/.test(line)) {\n newLines.push(` token: \"${opts.token}\"`);\n written.token = true;\n } else {\n newLines.push(line);\n }\n continue;\n }\n\n newLines.push(line);\n }\n\n if (inRemnicBlock && !blockWritten) {\n // File ended while still inside the remnic block\n if (!written.host) newLines.push(` host: \"${safeHost}\"`);\n if (!written.port) newLines.push(` port: ${safePort}`);\n if (!written.token) newLines.push(` token: \"${opts.token}\"`);\n }\n\n // Always write exactly one trailing newline, matching the create and append paths.\n writeSecretFileSync(cfgPath, newLines.join(\"\\n\") + \"\\n\");\n // priorContent: raw is the original file content for rollback if needed.\n return { updated: true, skipped: false, configPath: cfgPath, priorContent: raw };\n}\n\n/**\n * Remove the `remnic:` block from a Hermes profile config.yaml.\n * Idempotent — if the block is absent, returns skipped.\n */\nexport function removeHermesConfig(opts: { profile: string }): HermesConfigResult {\n const cfgPath = hermesConfigPath(opts.profile);\n\n if (!fs.existsSync(cfgPath)) {\n return {\n updated: false,\n skipped: true,\n reason: \"Hermes config.yaml not found\",\n configPath: cfgPath,\n };\n }\n\n const raw = fs.readFileSync(cfgPath, \"utf8\");\n if (!/^remnic:/m.test(raw)) {\n return {\n updated: false,\n skipped: true,\n reason: \"No remnic: block found in config.yaml\",\n configPath: cfgPath,\n };\n }\n\n // Strip the remnic: block and its indented children\n const lines = raw.split(\"\\n\");\n const newLines: string[] = [];\n let inRemnicBlock = false;\n\n for (const line of lines) {\n if (/^remnic:/.test(line)) {\n inRemnicBlock = true;\n continue;\n }\n if (inRemnicBlock) {\n if (line.length > 0 && !/^\\s/.test(line)) {\n inRemnicBlock = false;\n newLines.push(line);\n }\n // else: still in the block — skip the line\n continue;\n }\n newLines.push(line);\n }\n\n // Trim trailing blank lines left behind after the block removal\n while (newLines.length > 0 && newLines[newLines.length - 1]?.trim() === \"\") {\n newLines.pop();\n }\n\n // Use writeSecretFileSync to keep the file at 0o600 even after the token\n // has been removed. The file previously held a bearer token (so it was\n // written with 0o600 originally); preserving that mode prevents a window\n // where a rewrite with default umask temporarily widens permissions.\n writeSecretFileSync(cfgPath, newLines.length > 0 ? newLines.join(\"\\n\") + \"\\n\" : \"\");\n return { updated: true, skipped: false, configPath: cfgPath };\n}\n\n// ── Daemon health check (synchronous, non-fatal) ────────────────────────────\n\n/**\n * Probe exit-code contract (used by checkDaemonHealth):\n * 0 — HTTP 200 (healthy)\n * 2 — HTTP 401 (token cache miss: retry after TTL)\n * 1 — any other HTTP status or network error\n */\nconst HEALTH_EXIT_OK = 0;\nconst HEALTH_EXIT_UNAUTHORIZED = 2;\n\n/**\n * Ping /engram/v1/health synchronously.\n * Returns true if the daemon responds with HTTP 200, false otherwise.\n * Uses child_process.spawnSync to run a one-liner Node script so that the\n * existing synchronous installConnector() flow does not need to become async.\n *\n * Data (host, port, token) are passed via environment variables — NOT\n * interpolated into the script string — to prevent injection from\n * user-supplied config values.\n *\n * /engram/v1/health is protected by bearer auth in the access HTTP server,\n * so the caller must pass the connector token (or the configured server\n * token) or the probe will always return 401 and report the daemon as\n * unreachable even when it is running.\n *\n * 401 handling: the daemon caches valid tokens with a 5-second TTL\n * (getAllValidTokensCached). A freshly-rotated token may not appear in the\n * cache for up to 5 s after rotation. We tolerate a single 401 by sleeping\n * one cache TTL (6000 ms = 5 s TTL + 1 s buffer) and retrying exactly once.\n */\nfunction checkDaemonHealth(host: string, port: number, authToken?: string): boolean {\n try {\n // Validate port: must be an integer in [1, 65535].\n // This guards against user config supplying a non-numeric string.\n const safePort = Math.trunc(Number(port));\n if (!Number.isFinite(safePort) || safePort < 1 || safePort > 65535) {\n return false;\n }\n // Finding 7 fix: Node's http.get({ host }) expects an unbracketed IPv6\n // literal (e.g. \"::1\"), but sanitizeHermesHost permits bracketed form\n // \"[::1]\" (required for URL contexts). Strip the brackets here so that\n // http.get receives the bare address and doesn't fail to connect.\n // IPv4 and hostname strings are unaffected (no brackets to strip).\n const bareHost = host.startsWith(\"[\") && host.endsWith(\"]\")\n ? host.slice(1, -1)\n : host;\n\n // Data (host, port, token) are passed via env vars, never interpolated\n // into the script string, preventing any code-injection from malformed\n // config values.\n // Exit codes: 0 = 200 OK, 2 = 401 Unauthorized, 1 = other error.\n const script = [\n \"const http = require('http');\",\n \"const headers = {};\",\n \"if (process.env.REMNIC_HEALTH_TOKEN) {\",\n \" headers['authorization'] = 'Bearer ' + process.env.REMNIC_HEALTH_TOKEN;\",\n \"}\",\n \"const req = http.get({\",\n \" host: process.env.REMNIC_HEALTH_HOST,\",\n \" port: parseInt(process.env.REMNIC_HEALTH_PORT, 10),\",\n \" path: '/engram/v1/health',\",\n \" headers,\",\n \" timeout: 3000,\",\n \"}, (res) => { process.exit(res.statusCode === 200 ? 0 : res.statusCode === 401 ? 2 : 1); });\",\n \"req.on('error', () => process.exit(1));\",\n \"req.on('timeout', () => { req.destroy(); process.exit(1); });\",\n ].join(\"\\n\");\n const env: NodeJS.ProcessEnv = {\n ...process.env,\n REMNIC_HEALTH_HOST: bareHost,\n REMNIC_HEALTH_PORT: String(safePort),\n };\n if (authToken) {\n env.REMNIC_HEALTH_TOKEN = authToken;\n }\n const spawnOpts = { timeout: 4000, env };\n const result = spawnSync(process.execPath, [\"-e\", script], spawnOpts);\n\n if (result.status === HEALTH_EXIT_OK) {\n return true;\n }\n\n if (result.status === HEALTH_EXIT_UNAUTHORIZED) {\n // The daemon's token cache (5 s TTL) has not yet picked up the freshly\n // rotated token. Sleep one TTL + buffer and retry exactly once.\n console.error(\n \"[remnic/connectors] health probe got 401 — retrying after token cache TTL...\",\n );\n // Synchronous sleep via spawnSync (avoids making the caller async).\n spawnSync(process.execPath, [\"-e\", \"setTimeout(() => {}, 6000)\"], {\n timeout: 7000,\n env: {},\n });\n const retry = spawnSync(process.execPath, [\"-e\", script], spawnOpts);\n return retry.status === HEALTH_EXIT_OK;\n }\n\n return false;\n } catch {\n return false;\n }\n}\n\n// ── Doctor ────────────────────────────────────────────────────────────────────\n\nexport async function doctorConnector(connectorId: string): Promise<DoctorResult> {\n const installed = listConnectors().installed;\n const instance = installed.find((c) => c.connectorId === connectorId);\n\n if (!instance) {\n return {\n connectorId,\n checks: [{ name: \"Installed\", ok: false, detail: \"Not installed\" }],\n healthy: false,\n };\n }\n\n const configPath = path.join(getConnectorsDir(), `${connectorId}.json`);\n const checks: DoctorCheck[] = [];\n\n // Check config exists\n checks.push({\n name: \"Config file\",\n ok: fs.existsSync(configPath),\n detail: configPath,\n });\n\n // Check config is valid JSON\n try {\n const raw = fs.readFileSync(configPath, \"utf8\");\n JSON.parse(raw);\n checks.push({ name: \"Config valid\", ok: true, detail: \"OK\" });\n } catch (e) {\n checks.push({ name: \"Config valid\", ok: false, detail: String(e) });\n }\n\n // Check MCP server reachable (if applicable)\n const mcpUrl = instance.config.mcpServerUrl as string | undefined;\n if (mcpUrl) {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 3000);\n const response = await fetch(mcpUrl, { signal: controller.signal });\n clearTimeout(timeoutId);\n checks.push({ name: \"MCP server\", ok: response.ok, detail: mcpUrl });\n } catch (e) {\n checks.push({\n name: \"MCP server\",\n ok: false,\n detail: `Cannot reach ${mcpUrl}: ${e instanceof Error ? e.message : \"unknown\"}`,\n });\n }\n }\n\n // Check memory dir (if applicable)\n const memoryDir = instance.config.memoryDir as string | undefined;\n if (memoryDir) {\n if (fs.existsSync(memoryDir)) {\n checks.push({ name: \"Memory directory\", ok: true, detail: memoryDir });\n } else {\n checks.push({ name: \"Memory directory\", ok: false, detail: `Not found: ${memoryDir}` });\n }\n }\n\n const healthy = checks.every((c) => c.ok);\n return { connectorId, checks, healthy };\n}\n\n// ── Codex memory extension install ────────────────────────────────────────\n\n/**\n * Name of the Codex memories folder. Matches Codex's\n * `MEMORIES_SUBDIR = \"memories\"`.\n */\nconst CODEX_MEMORIES_SUBDIR = \"memories\";\n\n/**\n * Name of the Codex memory-extensions folder. Matches Codex's\n * `EXTENSIONS_SUBDIR = \"memories_extensions\"`.\n *\n * Codex computes the extensions root as a **sibling** of the memories dir via\n * Rust's `Path::with_file_name(\"memories_extensions\")` — so for the default\n * Codex home the layout is:\n *\n * ~/.codex/memories/\n * ~/.codex/memories_extensions/\n *\n * Extension files live **outside** of `memories/`, never inside it.\n */\nconst CODEX_EXTENSIONS_SUBDIR = \"memories_extensions\";\n\n/** Folder name Remnic installs its extension under. */\nconst REMNIC_EXTENSION_DIR_NAME = \"remnic\";\n\nexport interface CodexMemoryExtensionPaths {\n /** Resolved Codex home directory (e.g. `~/.codex`). */\n codexHome: string;\n /** Resolved Codex memories directory (`<codex_home>/memories`). */\n memoriesDir: string;\n /** Sibling extensions root (`<codex_home>/memories_extensions`). */\n extensionsRoot: string;\n /** The specific Remnic extension directory inside the extensions root. */\n remnicExtensionDir: string;\n}\n\nexport interface InstallCodexMemoryExtensionOptions {\n /** Optional override for `$CODEX_HOME`. Highest priority. */\n codexHome?: string | null;\n /** Optional override for the plugin-codex extension source directory. */\n sourceDir?: string | null;\n}\n\nexport interface InstallCodexMemoryExtensionResult extends CodexMemoryExtensionPaths {\n /** Absolute path to the installed `instructions.md`. */\n instructionsPath: string;\n /** Number of files copied. */\n filesCopied: number;\n /**\n * Commit the install: permanently remove the backup of the prior extension\n * (if one existed). Call this once the config write has succeeded.\n */\n commit(): void;\n /**\n * Roll back the install: restore the prior extension if one existed, or\n * remove the newly-installed directory for a fresh install. Call this when\n * a subsequent step (e.g. config write) has failed.\n */\n rollback(): void;\n}\n\nexport interface RemoveCodexMemoryExtensionOptions {\n codexHome?: string | null;\n}\n\nexport interface RemoveCodexMemoryExtensionResult extends CodexMemoryExtensionPaths {\n /** True if an existing `remnic` extension directory was removed. */\n removed: boolean;\n}\n\n/**\n * Resolve the Codex home directory. Precedence:\n * 1. explicit `override` argument (from config)\n * 2. `$CODEX_HOME` env var\n * 3. `~/.codex`\n */\nexport function resolveCodexHome(override?: string | null): string {\n if (override && typeof override === \"string\" && override.trim().length > 0) {\n return path.resolve(override.trim());\n }\n const envHome = process.env.CODEX_HOME;\n if (envHome && envHome.trim().length > 0) {\n return path.resolve(envHome.trim());\n }\n const home = process.env.HOME ?? process.env.USERPROFILE ?? \"~\";\n // Use path.resolve so the result is always absolute. When both HOME and\n // USERPROFILE are unset we fall back to the literal \"~\" sentinel and\n // path.resolve(\"~\", \".codex\") resolves it against cwd — not ideal, but\n // still an absolute path. A cleaner error can be added later if needed.\n return path.resolve(home, \".codex\");\n}\n\n/**\n * Compute the Codex memories + memory-extensions layout for a given Codex home.\n *\n * The extensions root is computed as a **sibling** of the memories dir by\n * taking `path.dirname(memoriesDir)` and joining `memories_extensions`. This\n * mirrors Rust's `with_file_name(\"memories_extensions\")` semantics used by\n * Codex's `memory_extensions_root()`. Do NOT place the extension inside\n * `<codex_home>/memories/`.\n */\nexport function resolveCodexMemoryExtensionPaths(\n codexHomeOverride?: string | null,\n): CodexMemoryExtensionPaths {\n const codexHome = resolveCodexHome(codexHomeOverride);\n const memoriesDir = path.join(codexHome, CODEX_MEMORIES_SUBDIR);\n // Sibling computation: with_file_name(EXTENSIONS_SUBDIR)\n const extensionsRoot = path.join(path.dirname(memoriesDir), CODEX_EXTENSIONS_SUBDIR);\n const remnicExtensionDir = path.join(extensionsRoot, REMNIC_EXTENSION_DIR_NAME);\n return { codexHome, memoriesDir, extensionsRoot, remnicExtensionDir };\n}\n\n/**\n * Locate the plugin-codex `memories_extensions/remnic/` source directory on\n * disk. Search order:\n * 1. explicit `override`\n * 2. resolve via `@remnic/plugin-codex` package (handles global npm installs)\n * 3. sibling `node_modules/@remnic/plugin-codex` relative to this module\n * 4. walk upward from this file's location (monorepo development)\n * 5. walk upward from `process.cwd()` (monorepo fallback)\n *\n * Returns the absolute path or throws a descriptive error listing all paths\n * searched when none exist.\n */\nexport function locatePluginCodexExtensionSource(override?: string | null): string {\n if (override && typeof override === \"string\" && override.trim().length > 0) {\n const resolved = path.resolve(override.trim());\n if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {\n return resolved;\n }\n throw new Error(`Codex extension source directory not found: ${resolved}`);\n }\n\n const EXTENSION_SUBPATH = path.join(\"memories_extensions\", \"remnic\");\n const WORKSPACE_RELATIVE_PATH = path.join(\n \"packages\",\n \"plugin-codex\",\n \"memories_extensions\",\n \"remnic\",\n );\n\n const searched: string[] = [];\n\n // Primary path: the bundled payload shipped with @remnic/core itself.\n // tsup copies src/connectors/codex/ → dist/connectors/codex/ (see tsup.config.ts\n // onSuccess hook). However, tsup bundles all source into dist/ as flat files\n // (dist/index.js, dist/chunk-*.js), so at runtime import.meta.url points to\n // dist/index.js or a dist/chunk-*.js — NOT dist/connectors/index.js.\n // Therefore we probe two sibling-relative candidates:\n // 1. moduleDir/codex — matches tsx/ts-node on src/connectors/index.ts\n // 2. moduleDir/connectors/codex — matches the tsup dist layout where this code\n // lands in dist/index.js or dist/chunk-*.js\n try {\n const moduleDir = path.dirname(fileURLToPath(import.meta.url));\n\n // Candidate 1: adjacent codex/ (tsx/ts-node from src/connectors/)\n const bundledCandidate = path.join(moduleDir, \"codex\");\n searched.push(bundledCandidate);\n if (fs.existsSync(bundledCandidate) && fs.statSync(bundledCandidate).isDirectory()) {\n return bundledCandidate;\n }\n\n // Candidate 2: dist/connectors/codex/ — the tsup output path.\n // When this module is bundled into dist/index.js or dist/chunk-*.js,\n // moduleDir is dist/ and tsup copies the payload to dist/connectors/codex/.\n const distConnectorsCandidate = path.join(moduleDir, \"connectors\", \"codex\");\n searched.push(distConnectorsCandidate);\n if (\n fs.existsSync(distConnectorsCandidate) &&\n fs.statSync(distConnectorsCandidate).isDirectory()\n ) {\n return distConnectorsCandidate;\n }\n } catch {\n // import.meta.url unavailable — not running as ESM, skip bundled path.\n }\n\n // Finding 2 — path 1: resolve via `@remnic/plugin-codex` package.json.\n // This covers global `npm install -g @remnic/remnic-core` or pnpm global installs\n // where the package lives under the global node_modules tree.\n try {\n const requireFromHere = createRequire(import.meta.url);\n const pluginPkgJsonPath = requireFromHere.resolve(\"@remnic/plugin-codex/package.json\");\n const pluginPkgRoot = path.dirname(pluginPkgJsonPath);\n const candidate = path.join(pluginPkgRoot, EXTENSION_SUBPATH);\n searched.push(candidate);\n if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {\n return candidate;\n }\n } catch {\n // @remnic/plugin-codex not installed — fall through to next strategy.\n }\n\n // Finding 2 — path 2: sibling node_modules under the module's own directory.\n // Handles cases like:\n // .../node_modules/@remnic/remnic-core/src/connectors/index.js\n // .../node_modules/@remnic/plugin-codex/memories_extensions/remnic\n try {\n const moduleDir = path.dirname(fileURLToPath(import.meta.url));\n let dir = moduleDir;\n for (let depth = 0; depth < 8; depth += 1) {\n const candidate = path.join(\n dir,\n \"node_modules\",\n \"@remnic\",\n \"plugin-codex\",\n EXTENSION_SUBPATH,\n );\n searched.push(candidate);\n if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {\n return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n } catch {\n // import.meta.url unavailable — not running as ESM.\n }\n\n // Finding 2 — path 3 & 4: walk upward from this file's location and from\n // process.cwd() looking for the monorepo layout (`packages/plugin-codex/…`).\n const anchors: string[] = [];\n try {\n anchors.push(path.dirname(fileURLToPath(import.meta.url)));\n } catch {\n // Not running under ESM with import.meta — skip.\n }\n anchors.push(process.cwd());\n\n for (const anchor of anchors) {\n let dir = anchor;\n for (let depth = 0; depth < 12; depth += 1) {\n const candidate = path.join(dir, WORKSPACE_RELATIVE_PATH);\n searched.push(candidate);\n if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {\n return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n }\n\n throw new Error(\n \"Could not locate the plugin-codex memories_extensions/remnic source directory.\\n\" +\n \"Paths searched:\\n\" +\n searched.map((p) => ` - ${p}`).join(\"\\n\") +\n \"\\nInstall @remnic/plugin-codex or pass sourceDir explicitly.\",\n );\n}\n\n/** Recursive synchronous directory copy. */\nfunction copyDirRecursiveSync(src: string, dest: string): number {\n let count = 0;\n fs.mkdirSync(dest, { recursive: true });\n const entries = fs.readdirSync(src, { withFileTypes: true });\n for (const entry of entries) {\n const from = path.join(src, entry.name);\n const to = path.join(dest, entry.name);\n if (entry.isDirectory()) {\n count += copyDirRecursiveSync(from, to);\n } else if (entry.isFile()) {\n fs.copyFileSync(from, to);\n count += 1;\n }\n // Skip symlinks, sockets, etc. — extension content is plain files.\n }\n return count;\n}\n\n/**\n * Install the Remnic memory extension into `<codex_home>/memories_extensions/remnic/`\n * atomically. The copy is written to a sibling `.remnic.tmp-<pid>-<ts>` directory\n * and then renamed into place, so a concurrent Codex phase-2 run never sees a\n * half-written extension.\n *\n * This function is **idempotent and scoped**: it only touches the `remnic`\n * subfolder inside `memories_extensions/`. Adjacent extensions (other\n * vendors) are never read, written, or removed.\n */\nexport function installCodexMemoryExtension(\n options: InstallCodexMemoryExtensionOptions = {},\n): InstallCodexMemoryExtensionResult {\n const paths = resolveCodexMemoryExtensionPaths(options.codexHome ?? null);\n const sourceDir = locatePluginCodexExtensionSource(options.sourceDir ?? null);\n\n fs.mkdirSync(paths.extensionsRoot, { recursive: true });\n\n // Clean any stale tmp from a previous crashed run by scanning the\n // extensions root for any `.remnic.tmp-*` prefixed entry. We must do this\n // BEFORE creating the new tmp directory. Per-entry errors are swallowed so\n // one bad entry doesn't abort cleanup of the rest.\n //\n // Finding 2: only remove tmp dirs that are provably stale (older than\n // STALE_TMP_THRESHOLD_MS). Dirs younger than the threshold belong to a\n // concurrent install that is still in progress; deleting them would corrupt\n // the other process's atomic rename.\n const tmpPrefix = `.${REMNIC_EXTENSION_DIR_NAME}.tmp-`;\n const STALE_TMP_THRESHOLD_MS = 10 * 60 * 1000; // 10 minutes\n const now = Date.now();\n try {\n const existingEntries = fs.readdirSync(paths.extensionsRoot);\n for (const entry of existingEntries) {\n if (!entry.startsWith(tmpPrefix)) continue;\n const stalePath = path.join(paths.extensionsRoot, entry);\n try {\n const stat = fs.statSync(stalePath);\n const ageMs = now - stat.mtimeMs;\n if (ageMs < STALE_TMP_THRESHOLD_MS) {\n // Too recent — leave it alone; another install is likely still running.\n continue;\n }\n fs.rmSync(stalePath, { recursive: true, force: true });\n } catch {\n // swallow — one bad entry should not abort the others\n }\n }\n } catch {\n // extensions root just-created / unreadable — nothing to clean\n }\n\n const tmpName = `${tmpPrefix}${process.pid}-${Date.now()}`;\n const tmpDir = path.join(paths.extensionsRoot, tmpName);\n\n let filesCopied = 0;\n let commitFn: () => void = () => { /* no-op: set below on success */ };\n let rollbackFn: () => void = () => { /* no-op: set below on success */ };\n try {\n filesCopied = copyDirRecursiveSync(sourceDir, tmpDir);\n\n // Atomic replace: rename old remnic/ to a timestamped backup, then rename\n // the tmp dir into place. If the second rename fails, restore from backup\n // so the old extension is never permanently lost.\n const backupDir = `${paths.remnicExtensionDir}.bak-${Date.now()}`;\n const hadExisting = fs.existsSync(paths.remnicExtensionDir);\n if (hadExisting) {\n fs.renameSync(paths.remnicExtensionDir, backupDir);\n }\n try {\n fs.renameSync(tmpDir, paths.remnicExtensionDir);\n } catch (renameErr) {\n // New rename failed — restore backup so the old extension survives.\n if (hadExisting) {\n try {\n fs.renameSync(backupDir, paths.remnicExtensionDir);\n } catch {\n // swallow — backup restore best-effort\n }\n }\n throw renameErr;\n }\n // The new extension is in place. We intentionally keep the backup alive\n // until the caller calls commit(). This gives the caller a chance to roll\n // back to the prior state if a subsequent operation (e.g. config write) fails.\n //\n // commit() — remove the backup (called on success)\n // rollback() — restore the prior extension from backup, or remove the newly\n // installed directory if this was a fresh install\n commitFn = (): void => {\n if (hadExisting) {\n try {\n fs.rmSync(backupDir, { recursive: true, force: true });\n } catch {\n // swallow — stale backup is harmless\n }\n }\n };\n rollbackFn = (): void => {\n if (hadExisting) {\n // Restore the prior extension from backup.\n try {\n // Remove the newly-installed dir first so rename can succeed.\n if (fs.existsSync(paths.remnicExtensionDir)) {\n fs.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });\n }\n fs.renameSync(backupDir, paths.remnicExtensionDir);\n } catch {\n // swallow — best-effort restore; backup remains on disk\n }\n } else {\n // Fresh install — just remove the directory we created.\n try {\n if (fs.existsSync(paths.remnicExtensionDir)) {\n fs.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });\n }\n } catch {\n // swallow\n }\n }\n };\n } catch (err) {\n // Best-effort cleanup so we never leave .tmp garbage behind.\n if (fs.existsSync(tmpDir)) {\n try {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n } catch {\n // swallow\n }\n }\n throw err;\n }\n\n const instructionsPath = path.join(paths.remnicExtensionDir, \"instructions.md\");\n\n return {\n ...paths,\n instructionsPath,\n filesCopied,\n commit: commitFn,\n rollback: rollbackFn,\n };\n}\n\n/**\n * Remove the Remnic memory extension. Only touches\n * `<codex_home>/memories_extensions/remnic/` — never adjacent extensions.\n */\nexport function removeCodexMemoryExtension(\n options: RemoveCodexMemoryExtensionOptions = {},\n): RemoveCodexMemoryExtensionResult {\n const paths = resolveCodexMemoryExtensionPaths(options.codexHome ?? null);\n let removed = false;\n if (fs.existsSync(paths.remnicExtensionDir)) {\n fs.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });\n removed = true;\n }\n return { ...paths, removed };\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction getConnectorsDir(): string {\n const configDir = process.env.XDG_CONFIG_HOME\n ? path.join(process.env.XDG_CONFIG_HOME, \"engram\")\n : path.join(process.env.HOME ?? \"~\", \".config\", \"engram\");\n return path.join(configDir, REGISTRY_DIR_NAME, \"connectors\");\n}\n\n// ── WeClone proxy config helpers ───────────────────────────────────────────\n//\n// The standalone `remnic-weclone-proxy` CLI reads its config from\n// ~/.remnic/connectors/weclone.json by default. `remnic connectors install\n// weclone` composes and persists that file so the proxy can start without\n// additional setup. The file is also tracked by the connector registry (at\n// getConnectorsDir()/weclone.json) so `remnic connectors list/remove/doctor`\n// work uniformly across all connectors.\n\nconst WECLONE_PROXY_CONFIG_DIRNAME = \".remnic\";\nconst WECLONE_PROXY_CONFIG_FILENAME = \"weclone.json\";\n\n/**\n * Resolve the path to ~/.remnic/connectors/weclone.json for the current user.\n * Honours REMNIC_HOME / ENGRAM_HOME env overrides so tests can point the\n * install at a temp dir without leaking into the real home directory.\n *\n * Always returns an absolute path via `path.resolve` so install-time and\n * run-time resolution agree even when the override is a relative path like\n * `tmp/remnic` (which would otherwise be interpreted against the caller's\n * current working directory). Must stay in lockstep with the proxy CLI's\n * `defaultConfigPath()` in @remnic/connector-weclone/src/cli.ts.\n *\n * `HOME=\"\"` edge case: `process.env.HOME ?? os.homedir()` would keep the\n * empty string (empty is not nullish), which `path.resolve(\"\", ...)` then\n * interprets as CWD. `os.homedir()` by contrast falls back to the OS\n * password database when HOME is empty, so the two code paths would\n * disagree. We therefore treat empty HOME as absent and delegate to\n * `os.homedir()` in both places — the same rule the proxy CLI follows.\n */\nexport function resolveWeCloneProxyConfigPath(): string {\n const override = process.env.REMNIC_HOME ?? process.env.ENGRAM_HOME;\n if (override && override.length > 0) {\n return path.resolve(override, \"connectors\", WECLONE_PROXY_CONFIG_FILENAME);\n }\n const envHome = process.env.HOME;\n const home = envHome && envHome.length > 0 ? envHome : os.homedir();\n return path.resolve(\n home,\n WECLONE_PROXY_CONFIG_DIRNAME,\n \"connectors\",\n WECLONE_PROXY_CONFIG_FILENAME,\n );\n}\n\n/**\n * Read the existing proxy config file, if any. Returns raw contents so the\n * caller can both parse it (for value precedence) and restore it verbatim on\n * rollback without touching byte-level formatting.\n */\nfunction readWeCloneProxyConfigIfExists(configPath: string): string | null {\n try {\n if (!fs.existsSync(configPath)) return null;\n return fs.readFileSync(configPath, \"utf8\");\n } catch {\n return null;\n }\n}\n\n/** Safely parse a JSON string into a record; returns null on error. */\nfunction safeParseJson(raw: string): Record<string, unknown> | null {\n try {\n const parsed = JSON.parse(raw);\n if (typeof parsed === \"object\" && parsed !== null && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n return null;\n } catch {\n return null;\n }\n}\n\ninterface WeCloneProxyConfig {\n wecloneApiUrl: string;\n wecloneModelName: string;\n proxyPort: number;\n remnicDaemonUrl: string;\n remnicAuthToken?: string;\n sessionStrategy: \"caller-id\" | \"single\";\n memoryInjection: {\n maxTokens: number;\n position: \"system-append\" | \"system-prepend\";\n template: string;\n };\n}\n\nconst WECLONE_DEFAULTS = {\n wecloneApiUrl: \"http://localhost:8000/v1\",\n wecloneModelName: \"weclone-avatar\",\n proxyPort: 8100,\n remnicDaemonUrl: \"http://localhost:4318\",\n sessionStrategy: \"single\" as const,\n memoryInjection: {\n maxTokens: 1500,\n position: \"system-append\" as const,\n template: \"[Memory Context]\\n{memories}\\n[End Memory Context]\",\n },\n};\n\n/**\n * Resolve a string field with precedence: userConfig → priorConfig → default.\n * Only non-empty strings are accepted from either source; invalid values fall\n * through so the user gets a working default rather than a broken install.\n */\nfunction resolveStringField(\n userConfig: Record<string, unknown>,\n priorConfig: Record<string, unknown> | null,\n key: string,\n fallback: string,\n): string {\n const fromUser = userConfig[key];\n if (typeof fromUser === \"string\" && fromUser.length > 0) return fromUser;\n if (priorConfig) {\n const fromPrior = priorConfig[key];\n if (typeof fromPrior === \"string\" && fromPrior.length > 0) return fromPrior;\n }\n return fallback;\n}\n\n/**\n * Coerce a config value to an integer port in [1, 65535]. Accepts number or\n * numeric string (parseConnectorConfig produces strings from `--config\n * proxyPort=8100`). Returns null if the value is missing or invalid so the\n * caller can fall through to the next precedence level.\n */\nfunction coercePort(value: unknown): number | null {\n if (typeof value === \"number\" && Number.isInteger(value) && value >= 1 && value <= 65535) {\n return value;\n }\n if (typeof value === \"string\" && value.length > 0) {\n const n = Number(value);\n if (Number.isInteger(n) && n >= 1 && n <= 65535) return n;\n }\n return null;\n}\n\nfunction resolvePort(\n userConfig: Record<string, unknown>,\n priorConfig: Record<string, unknown> | null,\n fallback: number,\n): number {\n const fromUser = coercePort(userConfig.proxyPort);\n if (fromUser !== null) return fromUser;\n if (priorConfig) {\n const fromPrior = coercePort(priorConfig.proxyPort);\n if (fromPrior !== null) return fromPrior;\n }\n return fallback;\n}\n\nfunction resolveSessionStrategy(\n userConfig: Record<string, unknown>,\n priorConfig: Record<string, unknown> | null,\n): \"caller-id\" | \"single\" {\n const valid = new Set([\"caller-id\", \"single\"]);\n const fromUser = userConfig.sessionStrategy;\n if (typeof fromUser === \"string\" && valid.has(fromUser)) {\n return fromUser as \"caller-id\" | \"single\";\n }\n if (priorConfig) {\n const fromPrior = priorConfig.sessionStrategy;\n if (typeof fromPrior === \"string\" && valid.has(fromPrior)) {\n return fromPrior as \"caller-id\" | \"single\";\n }\n }\n return WECLONE_DEFAULTS.sessionStrategy;\n}\n\n/**\n * Compose a WeCloneProxyConfig from user-supplied overrides and any prior\n * saved config, filling in defaults for every required field. The returned\n * shape is exactly what the proxy's parseConfig() expects.\n */\nexport function buildWeCloneProxyConfig(args: {\n userConfig: Record<string, unknown>;\n priorConfig: Record<string, unknown> | null;\n authToken?: string;\n}): WeCloneProxyConfig {\n const { userConfig, priorConfig, authToken } = args;\n\n const wecloneApiUrl = resolveStringField(\n userConfig,\n priorConfig,\n \"wecloneApiUrl\",\n WECLONE_DEFAULTS.wecloneApiUrl,\n );\n const wecloneModelName = resolveStringField(\n userConfig,\n priorConfig,\n \"wecloneModelName\",\n WECLONE_DEFAULTS.wecloneModelName,\n );\n const remnicDaemonUrl = resolveStringField(\n userConfig,\n priorConfig,\n \"remnicDaemonUrl\",\n WECLONE_DEFAULTS.remnicDaemonUrl,\n );\n const proxyPort = resolvePort(\n userConfig,\n priorConfig,\n WECLONE_DEFAULTS.proxyPort,\n );\n const sessionStrategy = resolveSessionStrategy(userConfig, priorConfig);\n\n // Memory injection: always start from defaults, then shallow-merge any\n // prior values, then user overrides. Individual field validation happens in\n // the proxy's parseConfig() at proxy startup — here we only assemble a\n // best-effort shape. A malformed user override would be rejected later with\n // a clean error message.\n //\n // `typeof [] === \"object\"` so a bare `typeof ... === \"object\" && ... !==\n // null` guard would let an array spread numeric-indexed properties into\n // the merged object, silently corrupting it. Explicitly reject arrays.\n const memoryInjection = {\n ...WECLONE_DEFAULTS.memoryInjection,\n ...(priorConfig &&\n typeof priorConfig.memoryInjection === \"object\" &&\n priorConfig.memoryInjection !== null &&\n !Array.isArray(priorConfig.memoryInjection)\n ? (priorConfig.memoryInjection as Record<string, unknown>)\n : {}),\n ...(typeof userConfig.memoryInjection === \"object\" &&\n userConfig.memoryInjection !== null &&\n !Array.isArray(userConfig.memoryInjection)\n ? (userConfig.memoryInjection as Record<string, unknown>)\n : {}),\n } as WeCloneProxyConfig[\"memoryInjection\"];\n\n const config: WeCloneProxyConfig = {\n wecloneApiUrl,\n wecloneModelName,\n proxyPort,\n remnicDaemonUrl,\n sessionStrategy,\n memoryInjection,\n };\n\n // Token precedence: freshly minted token → user-supplied → prior saved.\n // Never write a token if none is available — the proxy tolerates missing\n // tokens (it just won't send Authorization headers to the daemon).\n if (authToken && authToken.length > 0) {\n config.remnicAuthToken = authToken;\n } else if (typeof userConfig.remnicAuthToken === \"string\" && userConfig.remnicAuthToken.length > 0) {\n config.remnicAuthToken = userConfig.remnicAuthToken;\n } else if (priorConfig && typeof priorConfig.remnicAuthToken === \"string\" && priorConfig.remnicAuthToken.length > 0) {\n config.remnicAuthToken = priorConfig.remnicAuthToken;\n }\n\n return config;\n}\n","/**\n * codex-marketplace.ts — Codex marketplace installation support (#418)\n *\n * Provides types and functions for integrating Remnic with the Codex CLI\n * marketplace system (`codex marketplace add`). This module handles:\n *\n * - Generating a `marketplace.json` manifest describing Remnic as an\n * installable plugin in the Codex marketplace ecosystem.\n * - Validating marketplace manifest files against the expected schema.\n * - Writing manifests to disk atomically.\n * - Installing plugins from marketplace sources (GitHub, git, local, URL).\n *\n * Privacy\n * -------\n * This module does not persist any user content. It only reads package\n * metadata (name, version, description) and writes public marketplace\n * manifest files.\n */\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { log } from \"../logger.js\";\nimport type { PluginConfig } from \"../types.js\";\n\n// ── Types ─────────────────────────────────────────────────────────────────\n\n/** Source type for marketplace installation. */\nexport type MarketplaceInstallType = \"github\" | \"git\" | \"local\" | \"url\";\n\n/** A single plugin entry within a marketplace manifest. */\nexport interface MarketplaceEntry {\n /** Plugin name (e.g. \"remnic\"). */\n name: string;\n /** Semver version string. */\n version: string;\n /** Human-readable description. */\n description: string;\n /** Repository identifier (e.g. \"joshuaswarren/remnic\"). */\n repository: string;\n /** How this plugin should be installed. */\n installType: MarketplaceInstallType;\n /** Optional direct URL to the plugin manifest. */\n manifestUrl?: string;\n /** Optional entry point path within the repository. */\n entry?: string;\n /** Optional config schema reference. */\n configSchema?: string;\n}\n\n/** Top-level marketplace manifest. */\nexport interface MarketplaceManifest {\n /** Schema version. Must be 1. */\n version: 1;\n /** Marketplace name. */\n name: string;\n /** Human-readable description. */\n description: string;\n /** Available plugins. */\n plugins: MarketplaceEntry[];\n}\n\n/** Configuration for the marketplace subsystem. */\nexport interface MarketplaceConfig {\n /** Whether marketplace features are enabled. Default: true. */\n enabled: boolean;\n /** Local path where marketplace data is cached. */\n registryPath: string;\n /** Whether to auto-update marketplace data on install. Default: false. */\n autoUpdate: boolean;\n}\n\n/** Result of a marketplace install operation. */\nexport interface MarketplaceInstallResult {\n /** Whether the install succeeded. */\n ok: boolean;\n /** Human-readable message. */\n message: string;\n /** Source that was installed from. */\n source: string;\n /** Source type. */\n sourceType: MarketplaceInstallType;\n /** Plugins that were discovered. */\n pluginsFound: string[];\n /** Errors encountered (empty on success). */\n errors: string[];\n}\n\n/** Logger interface accepted by marketplace functions. */\nexport interface MarketplaceLogger {\n info: (msg: string) => void;\n warn: (msg: string) => void;\n debug?: (msg: string) => void;\n}\n\n// ── Constants ─────────────────────────────────────────────────────────────\n\n/** Current marketplace schema version. */\nexport const MARKETPLACE_SCHEMA_VERSION = 1 as const;\n\n/** Default marketplace manifest filename. */\nexport const MARKETPLACE_MANIFEST_FILENAME = \"marketplace.json\";\n\n/** Valid install types for validation. */\nconst VALID_INSTALL_TYPES = new Set<string>([\"github\", \"git\", \"local\", \"url\"]);\n\n// ── Generate ──────────────────────────────────────────────────────────────\n\n/**\n * Generate a marketplace manifest describing Remnic as an installable plugin.\n *\n * Reads version from the workspace root `package.json` at the resolved path,\n * or falls back to a default version string.\n */\nexport function generateMarketplaceManifest(\n options?: { packageVersion?: string },\n): MarketplaceManifest {\n const version = options?.packageVersion ?? readPackageVersion() ?? \"0.0.0\";\n\n return {\n version: MARKETPLACE_SCHEMA_VERSION,\n name: \"remnic\",\n description: \"Remnic: Local-first AI memory with semantic search and consolidation\",\n plugins: [\n {\n name: \"remnic\",\n version,\n description: \"Persistent memory plugin for Codex CLI\",\n repository: \"joshuaswarren/remnic\",\n installType: \"github\",\n entry: \"packages/plugin-codex\",\n configSchema: \"openclaw.plugin.json\",\n },\n ],\n };\n}\n\n// ── Validate ──────────────────────────────────────────────────────────────\n\n/** Validation result with structured errors. */\nexport interface MarketplaceValidation {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Validate that an unknown value conforms to the MarketplaceManifest schema.\n *\n * Returns a typed manifest on success. Throws on invalid input with a\n * descriptive error message listing all schema violations.\n */\nexport function validateMarketplaceManifest(manifest: unknown): MarketplaceManifest {\n const validation = checkMarketplaceManifest(manifest);\n if (!validation.valid) {\n throw new Error(\n `Invalid marketplace manifest: ${validation.errors.join(\"; \")}`,\n );\n }\n return manifest as MarketplaceManifest;\n}\n\n/**\n * Non-throwing validation. Returns a structured result with error details.\n */\nexport function checkMarketplaceManifest(manifest: unknown): MarketplaceValidation {\n const errors: string[] = [];\n\n if (typeof manifest !== \"object\" || manifest === null) {\n return { valid: false, errors: [\"manifest must be a non-null object\"] };\n }\n\n const obj = manifest as Record<string, unknown>;\n\n // version\n if (obj.version !== MARKETPLACE_SCHEMA_VERSION) {\n errors.push(`version must be ${MARKETPLACE_SCHEMA_VERSION}, got ${JSON.stringify(obj.version)}`);\n }\n\n // name\n if (typeof obj.name !== \"string\" || obj.name.trim().length === 0) {\n errors.push(\"name must be a non-empty string\");\n }\n\n // description\n if (typeof obj.description !== \"string\" || obj.description.trim().length === 0) {\n errors.push(\"description must be a non-empty string\");\n }\n\n // plugins\n if (!Array.isArray(obj.plugins)) {\n errors.push(\"plugins must be an array\");\n } else if (obj.plugins.length === 0) {\n errors.push(\"plugins must contain at least one entry\");\n } else {\n for (let i = 0; i < obj.plugins.length; i++) {\n const plugin = obj.plugins[i] as Record<string, unknown>;\n const prefix = `plugins[${i}]`;\n\n if (typeof plugin !== \"object\" || plugin === null) {\n errors.push(`${prefix} must be a non-null object`);\n continue;\n }\n\n if (typeof plugin.name !== \"string\" || plugin.name.trim().length === 0) {\n errors.push(`${prefix}.name must be a non-empty string`);\n }\n\n if (typeof plugin.version !== \"string\" || plugin.version.trim().length === 0) {\n errors.push(`${prefix}.version must be a non-empty string`);\n }\n\n if (typeof plugin.description !== \"string\" || plugin.description.trim().length === 0) {\n errors.push(`${prefix}.description must be a non-empty string`);\n }\n\n if (typeof plugin.repository !== \"string\" || plugin.repository.trim().length === 0) {\n errors.push(`${prefix}.repository must be a non-empty string`);\n }\n\n if (typeof plugin.installType !== \"string\" || !VALID_INSTALL_TYPES.has(plugin.installType)) {\n errors.push(\n `${prefix}.installType must be one of: ${[...VALID_INSTALL_TYPES].join(\", \")}; ` +\n `got ${JSON.stringify(plugin.installType)}`,\n );\n }\n\n // Optional fields — validate type when present (PR #427 post-merge fix).\n if (\"manifestUrl\" in plugin && plugin.manifestUrl !== undefined) {\n if (typeof plugin.manifestUrl !== \"string\" || (plugin.manifestUrl as string).trim().length === 0) {\n errors.push(`${prefix}.manifestUrl must be a non-empty string when provided`);\n }\n }\n if (\"entry\" in plugin && plugin.entry !== undefined) {\n if (typeof plugin.entry !== \"string\" || (plugin.entry as string).trim().length === 0) {\n errors.push(`${prefix}.entry must be a non-empty string when provided`);\n }\n }\n if (\"configSchema\" in plugin && plugin.configSchema !== undefined) {\n if (typeof plugin.configSchema !== \"string\" || (plugin.configSchema as string).trim().length === 0) {\n errors.push(`${prefix}.configSchema must be a non-empty string when provided`);\n }\n }\n }\n }\n\n return { valid: errors.length === 0, errors };\n}\n\n// ── Write ─────────────────────────────────────────────────────────────────\n\n/**\n * Write a marketplace manifest to disk atomically.\n *\n * Uses write-to-temp-then-rename to avoid partial writes (CLAUDE.md gotcha #54).\n */\nexport async function writeMarketplaceManifest(\n outputDir: string,\n manifest: MarketplaceManifest,\n): Promise<void> {\n // Validate before writing — never write garbage to disk.\n const validation = checkMarketplaceManifest(manifest);\n if (!validation.valid) {\n throw new Error(\n `Refusing to write invalid manifest: ${validation.errors.join(\"; \")}`,\n );\n }\n\n mkdirSync(outputDir, { recursive: true });\n\n const destPath = path.join(outputDir, MARKETPLACE_MANIFEST_FILENAME);\n const tmpPath = `${destPath}.tmp.${process.pid}`;\n const content = JSON.stringify(manifest, null, 2) + \"\\n\";\n\n writeFileSync(tmpPath, content);\n renameSync(tmpPath, destPath);\n}\n\n// ── Install ───────────────────────────────────────────────────────────────\n\n/**\n * Install from a marketplace source.\n *\n * Reads the marketplace.json from the given source, validates it, and\n * returns a result describing what was found.\n */\nexport async function installFromMarketplace(\n source: string,\n sourceType: MarketplaceInstallType,\n config: PluginConfig,\n logger?: MarketplaceLogger,\n): Promise<MarketplaceInstallResult> {\n const _log: MarketplaceLogger = logger ?? {\n info: (msg) => log.info(`[marketplace] ${msg}`),\n warn: (msg) => log.warn(`[marketplace] ${msg}`),\n debug: (msg) => log.debug(`[marketplace] ${msg}`),\n };\n\n if (!config.codexMarketplaceEnabled) {\n return {\n ok: false,\n message: \"Codex marketplace is disabled in config (codexMarketplaceEnabled: false)\",\n source,\n sourceType,\n pluginsFound: [],\n errors: [\"marketplace_disabled\"],\n };\n }\n\n try {\n const manifest = await resolveManifest(source, sourceType, _log);\n const pluginNames = manifest.plugins.map((p) => p.name);\n\n _log.info(`marketplace install: found ${pluginNames.length} plugin(s) from ${sourceType}://${source}`);\n\n return {\n ok: true,\n message: `Successfully resolved ${pluginNames.length} plugin(s) from marketplace: ${pluginNames.join(\", \")}`,\n source,\n sourceType,\n pluginsFound: pluginNames,\n errors: [],\n };\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n _log.warn(`marketplace install failed: ${errMsg}`);\n return {\n ok: false,\n message: `Failed to install from marketplace: ${errMsg}`,\n source,\n sourceType,\n pluginsFound: [],\n errors: [errMsg],\n };\n }\n}\n\n// ── Source resolution ─────────────────────────────────────────────────────\n\n/**\n * Resolve a marketplace manifest from the given source.\n */\nasync function resolveManifest(\n source: string,\n sourceType: MarketplaceInstallType,\n logger: MarketplaceLogger,\n): Promise<MarketplaceManifest> {\n switch (sourceType) {\n case \"local\":\n return resolveLocal(source, logger);\n case \"url\":\n return resolveUrl(source, logger);\n case \"github\":\n return resolveGithub(source, logger);\n case \"git\":\n return resolveGit(source, logger);\n default: {\n // Exhaustive check — CLAUDE.md gotcha #51: reject invalid input.\n const _: never = sourceType;\n throw new Error(`Invalid source type: ${String(_)}`);\n }\n }\n}\n\n/**\n * Read marketplace.json from a local directory.\n */\nfunction resolveLocal(\n dirPath: string,\n logger: MarketplaceLogger,\n): MarketplaceManifest {\n const manifestPath = path.join(dirPath, MARKETPLACE_MANIFEST_FILENAME);\n\n if (!existsSync(manifestPath)) {\n throw new Error(`marketplace.json not found at ${manifestPath}`);\n }\n\n logger.debug?.(`reading local marketplace manifest: ${manifestPath}`);\n\n const raw = readFileSync(manifestPath, \"utf-8\");\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`Invalid JSON in ${manifestPath}`);\n }\n\n // CLAUDE.md gotcha #18: validate parse result type\n if (typeof parsed !== \"object\" || parsed === null) {\n throw new Error(`marketplace.json at ${manifestPath} is not a valid object`);\n }\n\n return validateMarketplaceManifest(parsed);\n}\n\n/**\n * Fetch marketplace.json from a URL.\n */\nasync function resolveUrl(\n url: string,\n logger: MarketplaceLogger,\n): Promise<MarketplaceManifest> {\n logger.debug?.(`fetching marketplace manifest from URL: ${url}`);\n\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(url);\n } catch {\n throw new Error(`Invalid URL: ${url}`);\n }\n\n if (parsedUrl.protocol !== \"https:\" && parsedUrl.protocol !== \"http:\") {\n throw new Error(`Unsupported URL protocol: ${parsedUrl.protocol} (use https or http)`);\n }\n\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status} fetching ${url}`);\n }\n\n const body = await response.json() as unknown;\n return validateMarketplaceManifest(body);\n}\n\n/**\n * Resolve marketplace.json from a GitHub repository reference (owner/repo).\n *\n * Attempts to fetch the raw marketplace.json from the default branch.\n */\nasync function resolveGithub(\n repo: string,\n logger: MarketplaceLogger,\n): Promise<MarketplaceManifest> {\n // Validate format: must be owner/repo\n if (!/^[a-zA-Z0-9._-]+\\/[a-zA-Z0-9._-]+$/u.test(repo)) {\n throw new Error(`Invalid GitHub repo format: \"${repo}\" (expected owner/repo)`);\n }\n\n const rawUrl = `https://raw.githubusercontent.com/${repo}/HEAD/${MARKETPLACE_MANIFEST_FILENAME}`;\n logger.debug?.(`fetching marketplace manifest from GitHub: ${rawUrl}`);\n\n return resolveUrl(rawUrl, logger);\n}\n\n/**\n * Resolve marketplace.json from a git URL.\n *\n * For now this delegates to URL-based resolution by constructing a raw URL.\n * Full git clone support can be added later.\n */\nasync function resolveGit(\n gitUrl: string,\n logger: MarketplaceLogger,\n): Promise<MarketplaceManifest> {\n // For git URLs that look like GitHub HTTPS URLs, extract owner/repo and\n // delegate to the GitHub resolver.\n const ghMatch = gitUrl.match(\n /^(?:https?:\\/\\/)?github\\.com\\/([a-zA-Z0-9._-]+\\/[a-zA-Z0-9._-]+?)(?:\\.git)?$/u,\n );\n if (ghMatch?.[1]) {\n logger.debug?.(`git URL looks like GitHub — delegating to github resolver`);\n return resolveGithub(ghMatch[1], logger);\n }\n\n throw new Error(\n `Git URL resolution requires a GitHub-format URL for now. ` +\n `Got: ${gitUrl}. Use --type github or --type url instead.`,\n );\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────\n\n/**\n * Read the workspace root package.json version. Returns undefined if not found.\n */\nfunction readPackageVersion(): string | undefined {\n // Walk up from this file to find the workspace root package.json\n // This module lives at packages/remnic-core/src/connectors/codex-marketplace.ts\n // so workspace root is 4 levels up.\n const candidates = [\n path.resolve(import.meta.dirname ?? \".\", \"../../../..\"),\n path.resolve(import.meta.dirname ?? \".\", \"../../../../..\"),\n path.resolve(import.meta.dirname ?? \".\", \"..\"),\n ];\n\n for (const candidate of candidates) {\n const pkgPath = path.join(candidate, \"package.json\");\n try {\n if (!existsSync(pkgPath)) continue;\n const raw = readFileSync(pkgPath, \"utf-8\");\n const parsed = JSON.parse(raw);\n if (typeof parsed === \"object\" && parsed !== null && typeof parsed.version === \"string\") {\n return parsed.version;\n }\n } catch {\n // ignore\n }\n }\n\n return undefined;\n}\n","/**\n * @remnic/core — Spaces + Collaboration\n *\n * First-class memory spaces (personal, project, team) with merge/conflict\n * flows, promotion workflow, and audit trail.\n *\n * Each space is an isolated memory directory. Spaces can share memories\n * through push/pull and promotion workflows.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport type SpaceKind = \"personal\" | \"project\" | \"team\";\n\nexport interface Space {\n /** Unique space ID */\n id: string;\n /** Human-readable name */\n name: string;\n /** Space type */\n kind: SpaceKind;\n /** Description */\n description?: string;\n /** Memory directory path (absolute) */\n memoryDir: string;\n /** Created at */\n createdAt: string;\n /** Updated at */\n updatedAt: string;\n /** Owner */\n owner?: string;\n /** Members (for team spaces) */\n members?: string[];\n /** Parent space (for promotion) */\n parentSpaceId?: string;\n}\n\nexport interface SpaceManifest {\n /** Current active space ID */\n activeSpaceId: string;\n /** All spaces */\n spaces: Space[];\n /** Manifest version */\n version: number;\n /** Last updated */\n updatedAt?: string;\n}\n\nexport interface SpaceSwitchResult {\n previousSpaceId: string;\n currentSpaceId: string;\n message: string;\n}\n\nexport interface SpacePushResult {\n sourceSpaceId: string;\n targetSpaceId: string;\n memoriesPushed: number;\n conflicts: ConflictEntry[];\n durationMs: number;\n}\n\nexport interface SpacePullResult {\n sourceSpaceId: string;\n targetSpaceId: string;\n memoriesPulled: number;\n conflicts: ConflictEntry[];\n durationMs: number;\n}\n\nexport interface SpaceShareResult {\n spaceId: string;\n sharedWith: string[];\n message: string;\n}\n\nexport interface SpacePromoteResult {\n sourceSpaceId: string;\n targetSpaceId: string;\n memoriesPromoted: number;\n conflicts: ConflictEntry[];\n durationMs: number;\n}\n\nexport interface ConflictEntry {\n /** Memory ID */\n memoryId: string;\n /** Source file path */\n sourcePath: string;\n /** Target file path */\n targetPath: string;\n /** Conflict type */\n conflictType: \"content_mismatch\" | \"metadata_mismatch\" | \"both\";\n /** Source content hash */\n sourceHash: string;\n /** Target content hash */\n targetHash: string;\n}\n\nexport interface MergeResult {\n merged: number;\n conflicts: ConflictEntry[];\n skipped: number;\n durationMs: number;\n}\n\nexport interface AuditEntry {\n id: string;\n timestamp: string;\n action: string;\n sourceSpaceId: string;\n targetSpaceId?: string;\n actor?: string;\n details: string;\n memoryIds?: string[];\n}\n\n// ── Manifest management ─────────────────────────────────────────────────────\n\nconst MANIFEST_VERSION = 1;\n\nexport function getSpacesDir(baseDir?: string): string {\n const homeDir = baseDir ?? process.env.HOME ?? \"~\";\n return path.join(homeDir, \".config\", \"engram\", \"spaces\");\n}\n\nexport function getManifestPath(baseDir?: string): string {\n return path.join(getSpacesDir(baseDir), \"manifest.json\");\n}\n\nexport function loadManifest(baseDir?: string, memoryDirOverride?: string): SpaceManifest {\n const manifestPath = getManifestPath(baseDir);\n\n if (!fs.existsSync(manifestPath)) {\n // Bootstrap with a personal space\n const personalSpace = createPersonalSpace(baseDir, memoryDirOverride);\n const manifest: SpaceManifest = {\n activeSpaceId: personalSpace.id,\n spaces: [personalSpace],\n version: MANIFEST_VERSION,\n };\n saveManifest(manifest, baseDir);\n return manifest;\n }\n\n const raw = JSON.parse(fs.readFileSync(manifestPath, \"utf8\"));\n return raw as SpaceManifest;\n}\n\nexport function saveManifest(manifest: SpaceManifest, baseDir?: string): void {\n const manifestPath = getManifestPath(baseDir);\n fs.mkdirSync(path.dirname(manifestPath), { recursive: true });\n fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + \"\\n\");\n}\n\nfunction createPersonalSpace(baseDir?: string, memoryDirOverride?: string): Space {\n const homeDir = baseDir ?? process.env.HOME ?? \"~\";\n // Priority: override > env var > existing standalone dir > existing OpenClaw dir > new standalone dir\n const standalonePath = path.join(homeDir, \".engram\", \"memory\");\n const openclawPath = path.join(homeDir, \".openclaw\", \"workspace\", \"memory\", \"local\");\n const memoryDir = memoryDirOverride\n ?? process.env.ENGRAM_MEMORY_DIR\n ?? (fs.existsSync(standalonePath) ? standalonePath\n : fs.existsSync(openclawPath) ? openclawPath\n : standalonePath);\n const now = new Date().toISOString();\n\n return {\n id: \"personal\",\n name: \"Personal\",\n kind: \"personal\",\n description: \"Default personal memory space\",\n memoryDir,\n createdAt: now,\n updatedAt: now,\n owner: process.env.USER,\n };\n}\n\n// ── Space CRUD ──────────────────────────────────────────────────────────────\n\nexport function listSpaces(baseDir?: string): Space[] {\n const manifest = loadManifest(baseDir);\n return manifest.spaces;\n}\n\nexport function getActiveSpace(baseDir?: string): Space {\n const manifest = loadManifest(baseDir);\n const space = manifest.spaces.find((s) => s.id === manifest.activeSpaceId);\n if (!space) throw new Error(`Active space ${manifest.activeSpaceId} not found`);\n return space;\n}\n\nexport function createSpace(options: {\n name: string;\n kind: SpaceKind;\n description?: string;\n memoryDir?: string;\n parentSpaceId?: string;\n baseDir?: string;\n}): Space {\n const manifest = loadManifest(options.baseDir);\n const id = options.name.toLowerCase().replace(/[^a-z0-9-]/g, \"-\").replace(/-+/g, \"-\");\n\n if (manifest.spaces.some((s) => s.id === id)) {\n throw new Error(`Space \"${id}\" already exists`);\n }\n\n // Validate parent space exists\n if (options.parentSpaceId && !manifest.spaces.some((s) => s.id === options.parentSpaceId)) {\n throw new Error(`Parent space \"${options.parentSpaceId}\" not found`);\n }\n\n const now = new Date().toISOString();\n const memoryDir = options.memoryDir ?? path.join(\n getSpacesDir(options.baseDir),\n id,\n \"memory\",\n );\n\n const space: Space = {\n id,\n name: options.name,\n kind: options.kind,\n description: options.description,\n memoryDir,\n createdAt: now,\n updatedAt: now,\n owner: process.env.USER,\n parentSpaceId: options.parentSpaceId,\n };\n\n // Ensure memory directory exists\n fs.mkdirSync(memoryDir, { recursive: true });\n\n manifest.spaces.push(space);\n manifest.updatedAt = now;\n saveManifest(manifest, options.baseDir);\n\n // Audit\n appendAudit({\n action: \"space.create\",\n sourceSpaceId: id,\n details: `Created ${options.kind} space \"${options.name}\"`,\n }, options.baseDir);\n\n return space;\n}\n\nexport function deleteSpace(spaceId: string, baseDir?: string): void {\n const manifest = loadManifest(baseDir);\n\n if (spaceId === \"personal\") {\n throw new Error(\"Cannot delete the personal space\");\n }\n\n const idx = manifest.spaces.findIndex((s) => s.id === spaceId);\n if (idx === -1) throw new Error(`Space \"${spaceId}\" not found`);\n\n // If deleting active space, switch to personal\n if (manifest.activeSpaceId === spaceId) {\n manifest.activeSpaceId = \"personal\";\n }\n\n // Clear parentSpaceId references from children\n for (const space of manifest.spaces) {\n if (space.parentSpaceId === spaceId) {\n space.parentSpaceId = undefined;\n }\n }\n\n manifest.spaces.splice(idx, 1);\n saveManifest(manifest, baseDir);\n\n appendAudit({\n action: \"space.delete\",\n sourceSpaceId: spaceId,\n details: `Deleted space \"${spaceId}\"`,\n }, baseDir);\n}\n\n// ── Switch ───────────────────────────────────────────────────────────────────\n\nexport function switchSpace(spaceId: string, baseDir?: string): SpaceSwitchResult {\n const manifest = loadManifest(baseDir);\n const space = manifest.spaces.find((s) => s.id === spaceId);\n\n if (!space) throw new Error(`Space \"${spaceId}\" not found`);\n\n const previousId = manifest.activeSpaceId;\n manifest.activeSpaceId = spaceId;\n saveManifest(manifest, baseDir);\n\n appendAudit({\n action: \"space.switch\",\n sourceSpaceId: previousId,\n targetSpaceId: spaceId,\n details: `Switched from \"${previousId}\" to \"${spaceId}\"`,\n }, baseDir);\n\n return {\n previousSpaceId: previousId,\n currentSpaceId: spaceId,\n message: `Switched to \"${space.name}\"`,\n };\n}\n\n// ── Push / Pull ─────────────────────────────────────────────────────────────\n\nexport function pushToSpace(\n sourceSpaceId: string,\n targetSpaceId: string,\n options?: { memoryIds?: string[]; force?: boolean; baseDir?: string },\n): SpacePushResult {\n const startTime = Date.now();\n const manifest = loadManifest(options?.baseDir);\n\n const source = manifest.spaces.find((s) => s.id === sourceSpaceId);\n const target = manifest.spaces.find((s) => s.id === targetSpaceId);\n\n if (!source) throw new Error(`Source space \"${sourceSpaceId}\" not found`);\n if (!target) throw new Error(`Target space \"${targetSpaceId}\" not found`);\n\n const result = copyMemories(source.memoryDir, target.memoryDir, {\n filterIds: options?.memoryIds,\n force: options?.force,\n });\n\n appendAudit({\n action: \"space.push\",\n sourceSpaceId,\n targetSpaceId,\n details: `Pushed ${result.merged} memories, ${result.conflicts.length} conflicts`,\n }, options?.baseDir);\n\n return {\n sourceSpaceId,\n targetSpaceId,\n memoriesPushed: result.merged,\n conflicts: result.conflicts,\n durationMs: Date.now() - startTime,\n };\n}\n\nexport function pullFromSpace(\n sourceSpaceId: string,\n targetSpaceId: string,\n options?: { memoryIds?: string[]; force?: boolean; baseDir?: string },\n): SpacePullResult {\n const startTime = Date.now();\n const manifest = loadManifest(options?.baseDir);\n\n const source = manifest.spaces.find((s) => s.id === sourceSpaceId);\n const target = manifest.spaces.find((s) => s.id === targetSpaceId);\n\n if (!source) throw new Error(`Source space \"${sourceSpaceId}\" not found`);\n if (!target) throw new Error(`Target space \"${targetSpaceId}\" not found`);\n\n const result = copyMemories(source.memoryDir, target.memoryDir, {\n filterIds: options?.memoryIds,\n force: options?.force,\n });\n\n appendAudit({\n action: \"space.pull\",\n sourceSpaceId,\n targetSpaceId,\n details: `Pulled ${result.merged} memories, ${result.conflicts.length} conflicts`,\n }, options?.baseDir);\n\n return {\n sourceSpaceId,\n targetSpaceId,\n memoriesPulled: result.merged,\n conflicts: result.conflicts,\n durationMs: Date.now() - startTime,\n };\n}\n\n// ── Share ────────────────────────────────────────────────────────────────────\n\nexport function shareSpace(\n spaceId: string,\n members: string[],\n baseDir?: string,\n): SpaceShareResult {\n const manifest = loadManifest(baseDir);\n const space = manifest.spaces.find((s) => s.id === spaceId);\n\n if (!space) throw new Error(`Space \"${spaceId}\" not found`);\n if (space.kind === \"personal\") throw new Error(\"Cannot share personal space\");\n\n space.members = [...new Set([...(space.members ?? []), ...members])];\n space.updatedAt = new Date().toISOString();\n saveManifest(manifest, baseDir);\n\n appendAudit({\n action: \"space.share\",\n sourceSpaceId: spaceId,\n details: `Shared with: ${members.join(\", \")}`,\n }, baseDir);\n\n return {\n spaceId,\n sharedWith: members,\n message: `Shared \"${space.name}\" with ${members.length} member(s)`,\n };\n}\n\n// ── Promote ──────────────────────────────────────────────────────────────────\n\nexport function promoteSpace(\n sourceSpaceId: string,\n targetSpaceId: string,\n options?: { memoryIds?: string[]; force?: boolean; forceOverwrite?: boolean; baseDir?: string },\n): SpacePromoteResult {\n const startTime = Date.now();\n const manifest = loadManifest(options?.baseDir);\n\n const source = manifest.spaces.find((s) => s.id === sourceSpaceId);\n const target = manifest.spaces.find((s) => s.id === targetSpaceId);\n\n if (!source) throw new Error(`Source space \"${sourceSpaceId}\" not found`);\n if (!target) throw new Error(`Target space \"${targetSpaceId}\" not found`);\n\n // Promotion requires parent-child relationship or explicit force\n if (source.parentSpaceId !== targetSpaceId && target.parentSpaceId !== sourceSpaceId) {\n if (!options?.force) {\n throw new Error(\"Spaces must have a parent-child relationship for promotion. Use --force to override.\");\n }\n }\n\n const result = copyMemories(source.memoryDir, target.memoryDir, {\n filterIds: options?.memoryIds,\n force: options?.forceOverwrite !== undefined ? options.forceOverwrite : (options?.force ?? false),\n });\n\n appendAudit({\n action: \"space.promote\",\n sourceSpaceId,\n targetSpaceId,\n details: `Promoted ${result.merged} memories from \"${source.name}\" to \"${target.name}\"`,\n }, options?.baseDir);\n\n return {\n sourceSpaceId,\n targetSpaceId,\n memoriesPromoted: result.merged,\n conflicts: result.conflicts,\n durationMs: Date.now() - startTime,\n };\n}\n\n// ── Merge ────────────────────────────────────────────────────────────────────\n\nexport function mergeSpaces(\n sourceSpaceId: string,\n targetSpaceId: string,\n options?: { force?: boolean; baseDir?: string },\n): MergeResult {\n const startTime = Date.now();\n const manifest = loadManifest(options?.baseDir);\n\n const source = manifest.spaces.find((s) => s.id === sourceSpaceId);\n const target = manifest.spaces.find((s) => s.id === targetSpaceId);\n\n if (!source) throw new Error(`Source space \"${sourceSpaceId}\" not found`);\n if (!target) throw new Error(`Target space \"${targetSpaceId}\" not found`);\n\n const result = copyMemories(source.memoryDir, target.memoryDir, {\n force: options?.force,\n });\n\n appendAudit({\n action: \"space.merge\",\n sourceSpaceId,\n targetSpaceId,\n details: `Merged: ${result.merged} merged, ${result.conflicts.length} conflicts, ${result.skipped} skipped`,\n }, options?.baseDir);\n\n return {\n ...result,\n durationMs: Date.now() - startTime,\n };\n}\n\n// ── Audit trail ─────────────────────────────────────────────────────────────\n\nexport function getAuditLog(baseDir?: string): AuditEntry[] {\n const auditPath = path.join(getSpacesDir(baseDir), \"audit.jsonl\");\n if (!fs.existsSync(auditPath)) return [];\n\n const lines = fs.readFileSync(auditPath, \"utf8\").trim().split(\"\\n\");\n return lines\n .filter((l) => l.trim())\n .map((l) => JSON.parse(l) as AuditEntry);\n}\n\nfunction appendAudit(entry: Omit<AuditEntry, \"id\" | \"timestamp\">, baseDir?: string): void {\n const auditPath = path.join(getSpacesDir(baseDir), \"audit.jsonl\");\n fs.mkdirSync(path.dirname(auditPath), { recursive: true });\n\n const full: AuditEntry = {\n id: crypto.randomUUID(),\n timestamp: new Date().toISOString(),\n ...entry,\n };\n\n fs.appendFileSync(auditPath, JSON.stringify(full) + \"\\n\");\n}\n\n// ── Internal helpers ─────────────────────────────────────────────────────────\n\ninterface CopyOptions {\n filterIds?: string[];\n force?: boolean;\n}\n\nfunction copyMemories(\n sourceDir: string,\n targetDir: string,\n options?: CopyOptions,\n): { merged: number; conflicts: ConflictEntry[]; skipped: number } {\n let merged = 0;\n const conflicts: ConflictEntry[] = [];\n let skipped = 0;\n\n if (!fs.existsSync(sourceDir)) {\n return { merged: 0, conflicts: [], skipped: 0 };\n }\n\n fs.mkdirSync(targetDir, { recursive: true });\n\n const sourceFiles = walkMd(sourceDir);\n for (const sourcePath of sourceFiles) {\n const content = fs.readFileSync(sourcePath, \"utf8\");\n const relativePath = path.relative(sourceDir, sourcePath);\n const targetPath = path.join(targetDir, relativePath);\n\n const sourceHash = hashContent(content);\n\n // Filter by IDs if specified\n if (options?.filterIds?.length) {\n const fm = parseSimpleFrontmatter(content);\n if (!fm?.id || !options.filterIds.includes(fm.id)) {\n skipped++;\n continue;\n }\n }\n\n // Check for conflict\n if (fs.existsSync(targetPath) && !options?.force) {\n const targetContent = fs.readFileSync(targetPath, \"utf8\");\n const targetHash = hashContent(targetContent);\n\n if (sourceHash !== targetHash) {\n conflicts.push({\n memoryId: parseSimpleFrontmatter(content)?.id ?? relativePath,\n sourcePath,\n targetPath,\n conflictType: \"content_mismatch\",\n sourceHash,\n targetHash,\n });\n continue;\n }\n\n // Same content — skip\n skipped++;\n continue;\n }\n\n // Copy file\n fs.mkdirSync(path.dirname(targetPath), { recursive: true });\n fs.writeFileSync(targetPath, content);\n merged++;\n }\n\n return { merged, conflicts, skipped };\n}\n\nfunction hashContent(content: string): string {\n return crypto.createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16);\n}\n\nfunction walkMd(dir: string): string[] {\n const results: string[] = [];\n\n function walk(d: string): void {\n for (const entry of fs.readdirSync(d, { withFileTypes: true })) {\n const fullPath = path.join(d, entry.name);\n if (entry.isDirectory()) {\n walk(fullPath);\n } else if (entry.name.endsWith(\".md\")) {\n results.push(fullPath);\n }\n }\n }\n\n walk(dir);\n return results;\n}\n\ninterface SimpleFrontmatter {\n id?: string;\n [key: string]: string | undefined;\n}\n\nfunction parseSimpleFrontmatter(content: string): SimpleFrontmatter | null {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!match) return null;\n\n const fm: SimpleFrontmatter = {};\n for (const line of match[1].split(\"\\n\")) {\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n fm[key] = value;\n }\n return fm;\n}\n","/**\n * @remnic/core — Shared Instruction Blocks\n *\n * Reusable markdown fragments that every host-specific publisher can\n * compose into its instructions.md file. Keeping them here avoids\n * per-host copy-paste drift.\n */\n\n/**\n * Describes the Remnic memory types a host agent may encounter.\n */\nexport const REMNIC_SEMANTIC_OVERVIEW = `\\\n## Remnic Memory Types\n\nRemnic stores memories as plain Markdown files with YAML front-matter.\nEach memory has a **type** that describes its semantic role:\n\n| Type | Description |\n|------|-------------|\n| \\`fact\\` | An objective piece of knowledge the user confirmed or that was extracted from a session. |\n| \\`preference\\` | A stated or inferred user preference (e.g. coding style, tool choice). |\n| \\`decision\\` | An explicit decision or trade-off the user made. |\n| \\`entity\\` | A named thing the user cares about (project, service, person, API). |\n| \\`skill\\` | A reusable workflow or procedure documented for future sessions. |\n| \\`correction\\` | A fix or amendment to a previously stored memory. |\n| \\`question\\` | An open question or uncertainty flagged for future resolution. |\n| \\`observation\\` | A pattern noticed across sessions (e.g. \"user always runs tests before commits\"). |\n| \\`summary\\` | A condensed roll-up of recent sessions or a topic area. |\n\nWhen reading Remnic content, the front-matter \\`type\\` field tells you what\nkind of knowledge you are looking at and how much weight to give it.\n`;\n\n/**\n * Explains the oai-mem-citation block format hosts should use when\n * referencing Remnic-sourced content.\n */\nexport const REMNIC_CITATION_FORMAT = `\\\n## Citing Remnic Memories\n\nWhen a piece of your output draws on a Remnic file, cite it using the\nmemory citation block format so the user can trace the source:\n\n\\`\\`\\`\n<oai-mem-citation path=\"<path-relative-to-remnic-memory-base>\" />\n\\`\\`\\`\n\nThe path must be **relative to the Remnic memory base** (the directory\nnamed \\`memories/\\` under \\`<remnic-home>\\`), not absolute. Examples:\n\n- \\`<oai-mem-citation path=\"default/MEMORY.md\" />\\`\n- \\`<oai-mem-citation path=\"my-project/skills/deploy/SKILL.md\" />\\`\n- \\`<oai-mem-citation path=\"shared/memory_summary.md\" />\\`\n\nCite each distinct source once near the fact it supports. Do not invent\ncitations for files you have not actually read.\n`;\n\n/**\n * Table of MCP tools the Remnic daemon exposes. Hosts that can reach\n * the MCP server should prefer these over raw file reads.\n *\n * Tool names use the canonical `remnic.*` prefix. Legacy `engram.*`\n * aliases are also accepted by the server for backward compatibility.\n */\nexport const REMNIC_MCP_TOOL_INVENTORY = `\\\n## Remnic MCP Tools\n\nWhen the Remnic MCP server is reachable, the following tools are\navailable. Prefer MCP tools over direct file reads when the host\nsupports MCP connections.\n\n| Tool | Purpose |\n|------|---------|\n| \\`remnic.recall\\` | Retrieve contextually relevant memories for the current session. |\n| \\`remnic.recall_explain\\` | Like recall, but includes an explanation of why each memory was selected. |\n| \\`remnic.memory_store\\` | Persist a new memory (fact, preference, decision, etc.). |\n| \\`remnic.memory_get\\` | Fetch a specific memory by ID. |\n| \\`remnic.memory_search\\` | Full-text + semantic search across all memories. |\n| \\`remnic.memory_timeline\\` | Retrieve memories in chronological order within a time range. |\n| \\`remnic.observe\\` | Record an observation from the current conversation turn. |\n| \\`remnic.entity_get\\` | Look up a named entity and its relationships. |\n| \\`remnic.memory_entities_list\\` | List all known entities. |\n| \\`remnic.memory_profile\\` | Retrieve the user profile summary. |\n| \\`remnic.day_summary\\` | Generate a summary of memories from a specific day. |\n| \\`remnic.briefing\\` | Generate a structured briefing for an upcoming session. |\n| \\`remnic.memory_feedback\\` | Submit feedback on a recalled memory (useful, outdated, wrong). |\n| \\`remnic.memory_promote\\` | Promote a memory to a higher confidence tier. |\n| \\`remnic.context_checkpoint\\` | Save a conversation checkpoint for continuity. |\n| \\`remnic.suggestion_submit\\` | Submit a suggestion for a new memory to the review queue. |\n| \\`remnic.review_queue_list\\` | List pending suggestions in the review queue. |\n| \\`remnic.work_task\\` | Create or update a work task. |\n| \\`remnic.work_project\\` | Create or update a work project. |\n| \\`remnic.work_board\\` | View the work board. |\n\nLegacy \\`engram.*\\` prefixed names are accepted as aliases for all tools.\n`;\n\n/**\n * Decision rules for when a host agent should use MCP recall vs\n * reading Remnic files directly from disk.\n */\nexport const REMNIC_RECALL_DECISION_RULES = `\\\n## When to Use Recall vs Direct Read\n\n### Use \\`remnic.recall\\` (MCP) when:\n\n- The Remnic MCP server is reachable (the host has an active MCP\n connection to the Remnic daemon).\n- You want contextually relevant memories ranked by the recall planner\n (semantic search + reranking + importance scoring).\n- You need memories across multiple namespaces or topics.\n- The conversation benefits from Remnic's intent detection and\n adaptive recall depth.\n\n### Use direct file reads when:\n\n- You are in a sandboxed environment with no network or MCP access\n (e.g. Codex phase-2 consolidation).\n- You need a specific file you already know the path to.\n- The MCP server is unavailable or unhealthy.\n- You are operating on the raw memory files for maintenance or\n migration purposes.\n\n### General guidance:\n\n- Prefer MCP tools when available — they provide ranked, deduplicated,\n and context-aware results.\n- Fall back to file reads gracefully — never block on a failed MCP call\n when the data is also on disk.\n- Never write directly to the Remnic memory directory unless you are an\n authorized extraction or consolidation process.\n`;\n","/**\n * @remnic/core — Codex Memory Extension Publisher\n *\n * Writes Remnic instructions into ~/.codex/memories_extensions/remnic/\n * so the Codex agent can discover and use Remnic memories during its\n * consolidation phase.\n */\n\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nimport type {\n MemoryExtensionPublisher,\n PublishContext,\n PublishResult,\n PublisherCapabilities,\n} from \"./types.js\";\n\nimport {\n REMNIC_SEMANTIC_OVERVIEW,\n REMNIC_CITATION_FORMAT,\n REMNIC_MCP_TOOL_INVENTORY,\n REMNIC_RECALL_DECISION_RULES,\n} from \"./shared-instructions.js\";\n\n/** Folder name Remnic installs its extension under inside memories_extensions/. */\nconst REMNIC_EXTENSION_DIR_NAME = \"remnic\";\n\n/**\n * Codex-specific publisher that knows the Codex extension layout:\n * ~/.codex/memories_extensions/remnic/instructions.md\n */\nexport class CodexMemoryExtensionPublisher implements MemoryExtensionPublisher {\n readonly hostId = \"codex\";\n\n static readonly capabilities: PublisherCapabilities = {\n instructionsMd: true,\n skillsFolder: true,\n citationFormat: true,\n readPathTemplate: true,\n };\n\n async resolveExtensionRoot(\n env?: NodeJS.ProcessEnv,\n ): Promise<string> {\n const e = env ?? process.env;\n const codexHome =\n e.CODEX_HOME?.trim() || path.join(e.HOME ?? os.homedir(), \".codex\");\n return path.join(codexHome, \"memories_extensions\", REMNIC_EXTENSION_DIR_NAME);\n }\n\n async isHostAvailable(): Promise<boolean> {\n try {\n const home = process.env.CODEX_HOME?.trim() ||\n path.join(process.env.HOME ?? os.homedir(), \".codex\");\n return fs.existsSync(home);\n } catch {\n return false;\n }\n }\n\n async renderInstructions(ctx: PublishContext): Promise<string> {\n const memDir = ctx.config.memoryDir;\n const ns = ctx.config.namespace ?? \"default\";\n\n const sections: string[] = [\n `# Remnic Memory Extension for Codex\\n`,\n `This document tells you how to use Remnic as an authoritative local ` +\n `memory source. Remnic is a local-first, file-backed memory system. ` +\n `All Remnic content lives on disk as plain Markdown.\\n`,\n REMNIC_SEMANTIC_OVERVIEW,\n `## Where Remnic Content Lives\\n\\n` +\n `Memory base directory: \\`${memDir}\\`\\n\\n` +\n `Namespace: \\`${ns}\\`\\n\\n` +\n `Under the base directory, memories are organized by namespace:\\n\\n` +\n \"```\\n\" +\n `${memDir}/<namespace>/\\n` +\n ` MEMORY.md # compact top-of-mind memory\\n` +\n ` memory_summary.md # optional longer summary\\n` +\n ` skills/\\n` +\n ` <skill-name>/SKILL.md # reusable workflows\\n` +\n ` rollout_summaries/\\n` +\n ` *.md # per-session rollup notes\\n` +\n \"```\\n\",\n REMNIC_CITATION_FORMAT,\n REMNIC_MCP_TOOL_INVENTORY,\n REMNIC_RECALL_DECISION_RULES,\n `## Sandboxing Rules (Codex Phase-2)\\n\\n` +\n `When running inside the Codex phase-2 consolidation sandbox:\\n\\n` +\n `- **No network.** Do not attempt HTTP calls or MCP connections.\\n` +\n `- **No CLI invocation.** Do not shell out to \\`remnic\\` or \\`engram\\`.\\n` +\n `- **No MCP tool calls.** Use filesystem reads only.\\n` +\n `- **Local writes** are allowed only where Codex's sandbox policy permits.\\n` +\n `- **Respect missing files.** If a file does not exist, move on silently.\\n`,\n ];\n\n return sections.join(\"\\n\");\n }\n\n async publish(ctx: PublishContext): Promise<PublishResult> {\n const extensionRoot = await this.resolveExtensionRoot();\n const instructionsPath = path.join(extensionRoot, \"instructions.md\");\n const filesWritten: string[] = [];\n const skipped: string[] = [];\n\n ctx.log.info(`Publishing Codex memory extension to ${extensionRoot}`);\n\n // Ensure the extension root exists.\n fs.mkdirSync(extensionRoot, { recursive: true });\n\n // Render and write instructions.md using atomic write (temp + rename).\n // Per CLAUDE.md #54: never delete before write in file replace operations.\n const content = await this.renderInstructions(ctx);\n const tmpPath = `${instructionsPath}.tmp-${process.pid}-${Date.now()}`;\n\n try {\n fs.writeFileSync(tmpPath, content, \"utf-8\");\n fs.renameSync(tmpPath, instructionsPath);\n filesWritten.push(instructionsPath);\n ctx.log.info(`Wrote ${instructionsPath}`);\n } catch (err) {\n // Clean up temp file on failure.\n try {\n if (fs.existsSync(tmpPath)) {\n fs.unlinkSync(tmpPath);\n }\n } catch {\n // swallow cleanup error\n }\n throw err;\n }\n\n return {\n hostId: this.hostId,\n extensionRoot,\n filesWritten,\n skipped,\n };\n }\n\n async unpublish(): Promise<void> {\n const extensionRoot = await this.resolveExtensionRoot();\n if (fs.existsSync(extensionRoot)) {\n fs.rmSync(extensionRoot, { recursive: true, force: true });\n }\n }\n}\n","/**\n * @remnic/core — Claude Code Memory Extension Publisher (stub)\n *\n * Placeholder publisher for Claude Code. Claude Code does not yet\n * support a file-based memory extension directory, so all methods are\n * no-ops that return safe defaults.\n */\n\nimport type {\n MemoryExtensionPublisher,\n PublishContext,\n PublishResult,\n PublisherCapabilities,\n} from \"./types.js\";\n\nexport class ClaudeCodeMemoryExtensionPublisher implements MemoryExtensionPublisher {\n readonly hostId = \"claude-code\";\n\n static readonly capabilities: PublisherCapabilities = {\n instructionsMd: false,\n skillsFolder: false,\n citationFormat: false,\n readPathTemplate: false,\n };\n\n async resolveExtensionRoot(): Promise<string> {\n // Claude Code does not have an extension directory yet.\n return \"\";\n }\n\n async isHostAvailable(): Promise<boolean> {\n return false;\n }\n\n async renderInstructions(_ctx: PublishContext): Promise<string> {\n return \"\";\n }\n\n async publish(_ctx: PublishContext): Promise<PublishResult> {\n return {\n hostId: this.hostId,\n extensionRoot: \"\",\n filesWritten: [],\n skipped: [],\n };\n }\n\n async unpublish(): Promise<void> {\n // no-op\n }\n}\n","/**\n * @remnic/core — Hermes Memory Extension Publisher (stub)\n *\n * Placeholder publisher for Hermes. Hermes uses a daemon-based\n * transport and does not currently consume file-based memory\n * extensions, so all methods are no-ops.\n */\n\nimport type {\n MemoryExtensionPublisher,\n PublishContext,\n PublishResult,\n PublisherCapabilities,\n} from \"./types.js\";\n\nexport class HermesMemoryExtensionPublisher implements MemoryExtensionPublisher {\n readonly hostId = \"hermes\";\n\n static readonly capabilities: PublisherCapabilities = {\n instructionsMd: false,\n skillsFolder: false,\n citationFormat: false,\n readPathTemplate: false,\n };\n\n async resolveExtensionRoot(): Promise<string> {\n // Hermes does not have an extension directory.\n return \"\";\n }\n\n async isHostAvailable(): Promise<boolean> {\n return false;\n }\n\n async renderInstructions(_ctx: PublishContext): Promise<string> {\n return \"\";\n }\n\n async publish(_ctx: PublishContext): Promise<PublishResult> {\n return {\n hostId: this.hostId,\n extensionRoot: \"\",\n filesWritten: [],\n skipped: [],\n };\n }\n\n async unpublish(): Promise<void> {\n // no-op\n }\n}\n","/**\n * @remnic/core — Memory Extension Publisher Registry\n *\n * Generic registry that host adapters populate at startup via\n * `registerPublisher()`. The publisher *classes* live in core so\n * adapters can import them, but the wiring of host-specific\n * implementations into the registry happens in the host adapter\n * layer (e.g. @remnic/cli), not here. This keeps core free of\n * host-specific knowledge (CLAUDE.md gotcha #31).\n *\n * Usage (from a host adapter):\n * import { registerPublisher, CodexMemoryExtensionPublisher } from \"@remnic/core\";\n * registerPublisher(\"codex\", () => new CodexMemoryExtensionPublisher());\n */\n\nexport type {\n MemoryExtensionPublisher,\n PublishContext,\n PublishResult,\n PublisherCapabilities,\n} from \"./types.js\";\n\nexport {\n REMNIC_SEMANTIC_OVERVIEW,\n REMNIC_CITATION_FORMAT,\n REMNIC_MCP_TOOL_INVENTORY,\n REMNIC_RECALL_DECISION_RULES,\n} from \"./shared-instructions.js\";\n\nexport { CodexMemoryExtensionPublisher } from \"./codex-publisher.js\";\nexport { ClaudeCodeMemoryExtensionPublisher } from \"./claude-code-publisher.js\";\nexport { HermesMemoryExtensionPublisher } from \"./hermes-publisher.js\";\n\nimport type { MemoryExtensionPublisher } from \"./types.js\";\n\n/**\n * Factory registry keyed by host ID. Each value is a zero-argument\n * factory that returns a fresh publisher instance.\n *\n * Starts empty — host adapters populate it via registerPublisher().\n */\nexport const PUBLISHERS: Record<string, () => MemoryExtensionPublisher> = {};\n\n/**\n * Register a publisher factory for a given host ID.\n *\n * Host adapters call this at startup to wire their host-specific\n * publisher implementations into the registry. Calling with an\n * existing hostId replaces the previous factory.\n */\nexport function registerPublisher(\n hostId: string,\n factory: () => MemoryExtensionPublisher,\n): void {\n PUBLISHERS[hostId] = factory;\n}\n\n/**\n * Maps connector IDs to publisher host IDs.\n *\n * Most connector IDs match their publisher host ID exactly (e.g.\n * \"claude-code\" -> \"claude-code\", \"hermes\" -> \"hermes\").\n * This map only needs entries for connector IDs that differ from\n * their publisher host ID. Connectors without a publisher (e.g.\n * \"cursor\", \"cline\") are intentionally absent.\n */\nconst CONNECTOR_TO_HOST: Record<string, string> = {\n \"codex-cli\": \"codex\",\n};\n\n/**\n * Resolve a connector ID to its publisher host ID.\n *\n * Returns the explicit mapping if one exists, otherwise returns\n * the connector ID itself (identity mapping covers the common case\n * where connector ID === host ID).\n */\nexport function hostIdForConnector(connectorId: string): string {\n return CONNECTOR_TO_HOST[connectorId] ?? connectorId;\n}\n\n/**\n * Look up a publisher by host ID.\n * Returns undefined for unknown host IDs rather than throwing.\n */\nexport function publisherFor(hostId: string): MemoryExtensionPublisher | undefined {\n const factory = PUBLISHERS[hostId];\n return factory ? factory() : undefined;\n}\n\n/**\n * Look up a publisher by connector ID.\n *\n * Resolves the connector ID to its host ID first (e.g. \"codex-cli\" -> \"codex\"),\n * then looks up the publisher. Returns undefined if no publisher exists for\n * the resolved host ID.\n */\nexport function publisherForConnector(connectorId: string): MemoryExtensionPublisher | undefined {\n return publisherFor(hostIdForConnector(connectorId));\n}\n","/**\n * Default MECE taxonomy that maps every existing MemoryCategory value\n * to exactly one taxonomy category, ordered by priority.\n */\n\nimport type { Taxonomy } from \"./types.js\";\n\nexport const DEFAULT_TAXONOMY: Taxonomy = {\n version: 1,\n categories: [\n {\n id: \"corrections\",\n name: \"Corrections\",\n description: \"Corrections to previously stored information\",\n filingRules: [\"Any update that supersedes a prior fact\"],\n priority: 10,\n memoryCategories: [\"correction\"],\n },\n {\n id: \"principles\",\n name: \"Principles\",\n description: \"Rules, guidelines, and recurring patterns\",\n filingRules: [\"A guiding principle, rule, or skill\"],\n priority: 20,\n memoryCategories: [\"principle\", \"rule\", \"skill\"],\n },\n {\n id: \"procedures\",\n name: \"Procedures\",\n description: \"Ordered multi-step workflows the user repeats\",\n filingRules: [\"A repeatable sequence of steps or commands for a task\"],\n priority: 25,\n memoryCategories: [\"procedure\"],\n },\n {\n id: \"entities\",\n name: \"Entities\",\n description: \"People, organizations, places, projects\",\n filingRules: [\"Named entity with attributes\"],\n priority: 30,\n memoryCategories: [\"entity\", \"relationship\"],\n },\n {\n id: \"decisions\",\n name: \"Decisions\",\n description: \"Choices made and their rationale\",\n filingRules: [\"A decision or commitment with reasoning\"],\n priority: 35,\n memoryCategories: [\"decision\", \"commitment\"],\n },\n {\n id: \"preferences\",\n name: \"Preferences\",\n description: \"User likes, dislikes, and style choices\",\n filingRules: [\"Anything expressing a preference or taste\"],\n priority: 40,\n memoryCategories: [\"preference\"],\n },\n {\n id: \"facts\",\n name: \"Facts\",\n description: \"Objective statements about the world\",\n filingRules: [\"Any factual claim or piece of information\"],\n priority: 50,\n memoryCategories: [\"fact\"],\n },\n {\n id: \"moments\",\n name: \"Moments\",\n description: \"Significant events or experiences\",\n filingRules: [\"A specific event worth remembering\"],\n priority: 60,\n memoryCategories: [\"moment\"],\n },\n ],\n};\n","/**\n * Resolver decision tree for the MECE taxonomy.\n *\n * Given extracted content and its MemoryCategory, determines which\n * taxonomy category the knowledge should be filed under.\n */\n\nimport type { MemoryCategory } from \"../types.js\";\nimport type { ResolverDecision, Taxonomy, TaxonomyCategory } from \"./types.js\";\n\nconst DEFAULT_CATEGORY_ID = \"facts\";\n\n/**\n * Resolve a piece of content to a taxonomy category.\n *\n * Algorithm:\n * 1. Find all taxonomy categories whose `memoryCategories` include\n * the given `memoryCategory`.\n * 2. If exactly one match, return it with confidence 1.0.\n * 3. If multiple matches, pick the one with the lowest priority\n * number (highest precedence). Apply keyword heuristics from\n * filing rules as a secondary signal.\n * 4. If no match, fall back to the \"facts\" category (or first\n * category if \"facts\" is absent) with low confidence.\n * 5. Always populate `alternatives` with other plausible categories.\n */\nexport function resolveCategory(\n content: string,\n memoryCategory: MemoryCategory,\n taxonomy: Taxonomy,\n): ResolverDecision {\n const contentLower = content.toLowerCase();\n\n // Step 1: find matching categories\n const matches = taxonomy.categories.filter((cat) =>\n cat.memoryCategories.includes(memoryCategory),\n );\n\n if (matches.length === 0) {\n // No taxonomy category accepts this MemoryCategory — fall back\n const fallback =\n taxonomy.categories.find((c) => c.id === DEFAULT_CATEGORY_ID) ??\n taxonomy.categories[0];\n if (!fallback) {\n return {\n categoryId: DEFAULT_CATEGORY_ID,\n confidence: 0,\n reason: \"Taxonomy is empty; using default category\",\n alternatives: [],\n };\n }\n const alternatives = taxonomy.categories\n .filter((c) => c.id !== fallback.id)\n .map((c) => ({\n categoryId: c.id,\n reason: c.description,\n }));\n return {\n categoryId: fallback.id,\n confidence: 0.3,\n reason: `No taxonomy category maps to MemoryCategory \"${memoryCategory}\"; falling back to \"${fallback.name}\"`,\n alternatives,\n };\n }\n\n if (matches.length === 1) {\n const match = matches[0]!;\n const alternatives = taxonomy.categories\n .filter((c) => c.id !== match.id)\n .map((c) => ({\n categoryId: c.id,\n reason: c.description,\n }));\n return {\n categoryId: match.id,\n confidence: 1.0,\n reason: `Unique match: MemoryCategory \"${memoryCategory}\" maps to \"${match.name}\"`,\n alternatives,\n };\n }\n\n // Multiple matches — use filing rule keyword heuristics + priority\n const scored = matches.map((cat) => ({\n cat,\n keywordScore: computeKeywordScore(contentLower, cat),\n }));\n\n // Sort by keyword score descending, then priority ascending (lower wins)\n scored.sort((a, b) => {\n if (b.keywordScore !== a.keywordScore) return b.keywordScore - a.keywordScore;\n return a.cat.priority - b.cat.priority;\n });\n\n const best = scored[0]!;\n const runnerUp = scored[1];\n\n // Confidence is higher when keyword match clearly differentiates\n const confidence =\n best.keywordScore > 0 && (!runnerUp || best.keywordScore > runnerUp.keywordScore)\n ? 0.9\n : 0.7;\n\n const alternatives = taxonomy.categories\n .filter((c) => c.id !== best.cat.id)\n .map((c) => ({\n categoryId: c.id,\n reason: c.description,\n }));\n\n const reason =\n best.keywordScore > 0\n ? `Filing rules for \"${best.cat.name}\" matched content keywords (priority ${best.cat.priority})`\n : `Priority tie-break: \"${best.cat.name}\" has lowest priority number (${best.cat.priority})`;\n\n return {\n categoryId: best.cat.id,\n confidence,\n reason,\n alternatives,\n };\n}\n\n/**\n * Compute a simple keyword overlap score between content and\n * a category's filing rules + description.\n */\nfunction computeKeywordScore(contentLower: string, cat: TaxonomyCategory): number {\n let score = 0;\n const ruleText = [...cat.filingRules, cat.description]\n .join(\" \")\n .toLowerCase();\n\n // Extract meaningful words (3+ chars) from the rule text\n const keywords = ruleText\n .split(/[^a-z0-9]+/)\n .filter((w) => w.length >= 3);\n\n for (const kw of keywords) {\n if (contentLower.includes(kw)) {\n score += 1;\n }\n }\n return score;\n}\n","/**\n * Generates a markdown decision-tree document (RESOLVER.md) from a\n * taxonomy definition.\n *\n * The document walks a user through filing a new piece of knowledge\n * by checking each category in priority order (lowest number first).\n */\n\nimport type { Taxonomy } from \"./types.js\";\n\n/**\n * Produce a markdown decision tree for the given taxonomy.\n *\n * Categories are listed in priority order (lowest number = checked first).\n * Each step asks whether the knowledge fits the category and, if so,\n * instructs the reader to file it there.\n */\nexport function generateResolverDocument(taxonomy: Taxonomy): string {\n const sorted = [...taxonomy.categories].sort((a, b) => {\n if (a.priority !== b.priority) return a.priority - b.priority;\n return a.id.localeCompare(b.id);\n });\n\n const lines: string[] = [\n \"# Memory Filing Resolver\",\n \"\",\n \"Given a new piece of knowledge, follow this tree to determine where it belongs.\",\n \"\",\n ];\n\n let step = 1;\n for (const cat of sorted) {\n lines.push(`## Step ${step}: ${cat.description}?`);\n lines.push(\"\");\n for (const rule of cat.filingRules) {\n lines.push(`- ${rule}`);\n }\n lines.push(\"\");\n lines.push(\n `> YES: File under **${cat.id}/** (priority ${cat.priority})`,\n );\n lines.push(\"\");\n step++;\n }\n\n lines.push(\"## Tie-breaking\");\n lines.push(\"\");\n lines.push(\n \"If a fact could go in multiple categories, file under the one with the **lowest priority number**.\",\n );\n lines.push(\"\");\n lines.push(`---`);\n lines.push(`*Generated from taxonomy v${taxonomy.version}*`);\n lines.push(\"\");\n\n return lines.join(\"\\n\");\n}\n","/**\n * Loads and validates a user-customized taxonomy from disk, merging\n * with the built-in defaults.\n *\n * User taxonomies are stored at `<memoryDir>/.taxonomy/taxonomy.json`.\n */\n\nimport { readFile, mkdir, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { Taxonomy, TaxonomyCategory } from \"./types.js\";\nimport { DEFAULT_TAXONOMY } from \"./default-taxonomy.js\";\n\nconst TAXONOMY_DIR = \".taxonomy\";\nconst TAXONOMY_FILE = \"taxonomy.json\";\n\n/** Maximum allowed slug length */\nconst MAX_SLUG_LENGTH = 32;\n\n/** Regex for valid slug: lowercase letters, digits, hyphens */\nconst SLUG_RE = /^[a-z][a-z0-9-]*$/;\n\n/**\n * Validate a taxonomy category slug.\n * Throws if the slug is invalid.\n */\nexport function validateSlug(slug: string): void {\n if (slug.length === 0) {\n throw new Error(\"Taxonomy category ID must not be empty\");\n }\n if (slug.length > MAX_SLUG_LENGTH) {\n throw new Error(\n `Taxonomy category ID \"${slug}\" exceeds ${MAX_SLUG_LENGTH} characters`,\n );\n }\n if (!SLUG_RE.test(slug)) {\n throw new Error(\n `Taxonomy category ID \"${slug}\" is invalid: must be lowercase letters, digits, and hyphens, starting with a letter`,\n );\n }\n}\n\n/**\n * Validate an entire taxonomy for structural correctness.\n * Throws on the first error found.\n */\nexport function validateTaxonomy(taxonomy: Taxonomy): void {\n if (typeof taxonomy.version !== \"number\" || taxonomy.version < 1) {\n throw new Error(\"Taxonomy version must be a positive integer\");\n }\n if (!Array.isArray(taxonomy.categories)) {\n throw new Error(\"Taxonomy categories must be an array\");\n }\n\n const seenIds = new Set<string>();\n for (const cat of taxonomy.categories) {\n validateSlug(cat.id);\n if (seenIds.has(cat.id)) {\n throw new Error(`Duplicate taxonomy category ID: \"${cat.id}\"`);\n }\n seenIds.add(cat.id);\n\n if (typeof cat.name !== \"string\" || cat.name.trim().length === 0) {\n throw new Error(`Taxonomy category \"${cat.id}\" must have a non-empty name`);\n }\n if (typeof cat.description !== \"string\" || cat.description.trim().length === 0) {\n throw new Error(`Taxonomy category \"${cat.id}\" must have a non-empty description`);\n }\n if (!Array.isArray(cat.filingRules)) {\n throw new Error(`Taxonomy category \"${cat.id}\" filingRules must be an array`);\n }\n if (typeof cat.priority !== \"number\" || !Number.isFinite(cat.priority)) {\n throw new Error(`Taxonomy category \"${cat.id}\" must have a finite numeric priority`);\n }\n if (!Array.isArray(cat.memoryCategories)) {\n throw new Error(`Taxonomy category \"${cat.id}\" memoryCategories must be an array`);\n }\n if (cat.parentId !== undefined) {\n if (typeof cat.parentId !== \"string\") {\n throw new Error(`Taxonomy category \"${cat.id}\" parentId must be a string if set`);\n }\n }\n }\n\n // Validate parentId references\n for (const cat of taxonomy.categories) {\n if (cat.parentId !== undefined && !seenIds.has(cat.parentId)) {\n throw new Error(\n `Taxonomy category \"${cat.id}\" references unknown parentId \"${cat.parentId}\"`,\n );\n }\n }\n}\n\n/**\n * Load a taxonomy from the user's memory directory.\n *\n * If `<memoryDir>/.taxonomy/taxonomy.json` exists, loads it, validates\n * it, and merges with the defaults (user categories override defaults\n * by ID). If the file does not exist, returns the defaults.\n */\nexport async function loadTaxonomy(memoryDir: string): Promise<Taxonomy> {\n const taxonomyPath = path.join(memoryDir, TAXONOMY_DIR, TAXONOMY_FILE);\n let raw: string;\n try {\n raw = await readFile(taxonomyPath, \"utf-8\");\n } catch (err: unknown) {\n // Only fall back to defaults for missing file; rethrow permission / I/O errors\n if (err instanceof Error && (err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return structuredClone(DEFAULT_TAXONOMY);\n }\n throw err;\n }\n\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new Error(\"taxonomy.json must be a JSON object\");\n }\n\n const obj = parsed as Record<string, unknown>;\n const userVersion = typeof obj.version === \"number\" ? obj.version : DEFAULT_TAXONOMY.version;\n const userCategories = Array.isArray(obj.categories)\n ? (obj.categories as TaxonomyCategory[])\n : [];\n\n // Validate: reject duplicate IDs in user categories before merging.\n // Without this check, duplicates are silently collapsed with last-write-wins\n // semantics when inserted into the Map.\n const userIdCounts = new Map<string, number>();\n for (const cat of userCategories) {\n const id = typeof cat.id === \"string\" ? cat.id : String(cat.id);\n userIdCounts.set(id, (userIdCounts.get(id) ?? 0) + 1);\n }\n const duplicateIds = [...userIdCounts.entries()]\n .filter(([, count]) => count > 1)\n .map(([id]) => id);\n if (duplicateIds.length > 0) {\n throw new Error(\n `Duplicate category IDs in taxonomy.json: ${duplicateIds.map((id) => `\"${id}\"`).join(\", \")}`,\n );\n }\n\n // Merge: user categories override defaults by ID\n const mergedMap = new Map<string, TaxonomyCategory>();\n for (const cat of DEFAULT_TAXONOMY.categories) {\n mergedMap.set(cat.id, { ...cat });\n }\n for (const cat of userCategories) {\n mergedMap.set(cat.id, cat);\n }\n\n const merged: Taxonomy = {\n version: userVersion,\n categories: [...mergedMap.values()],\n };\n\n validateTaxonomy(merged);\n return merged;\n}\n\n/**\n * Save a taxonomy to the user's memory directory.\n */\nexport async function saveTaxonomy(\n memoryDir: string,\n taxonomy: Taxonomy,\n): Promise<void> {\n validateTaxonomy(taxonomy);\n const dir = path.join(memoryDir, TAXONOMY_DIR);\n await mkdir(dir, { recursive: true });\n const filePath = path.join(dir, TAXONOMY_FILE);\n await writeFile(filePath, JSON.stringify(taxonomy, null, 2) + \"\\n\", \"utf-8\");\n}\n\n/**\n * Get the taxonomy directory path for a given memory directory.\n */\nexport function getTaxonomyDir(memoryDir: string): string {\n return path.join(memoryDir, TAXONOMY_DIR);\n}\n\n/**\n * Get the taxonomy file path for a given memory directory.\n */\nexport function getTaxonomyFilePath(memoryDir: string): string {\n return path.join(memoryDir, TAXONOMY_DIR, TAXONOMY_FILE);\n}\n","/**\n * Enrichment pipeline types (issue #365).\n *\n * Defines the provider interface, candidate shape, pipeline config,\n * and result types for the external enrichment subsystem.\n */\n\nimport type { ImportanceLevel, MemoryCategory } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Provider config & interface\n// ---------------------------------------------------------------------------\n\nexport type EnrichmentCostTier = \"free\" | \"cheap\" | \"expensive\";\n\nexport interface EnrichmentProviderConfig {\n id: string;\n enabled: boolean;\n costTier: EnrichmentCostTier;\n rateLimit?: { maxPerMinute: number; maxPerDay: number };\n}\n\nexport interface EnrichmentCandidate {\n text: string;\n source: string;\n sourceUrl?: string;\n confidence: number;\n category: MemoryCategory;\n tags?: string[];\n}\n\nexport interface EnrichmentProvider {\n readonly id: string;\n readonly costTier: EnrichmentCostTier;\n enrich(entity: EntityEnrichmentInput): Promise<EnrichmentCandidate[]>;\n isAvailable(): Promise<boolean>;\n}\n\n// ---------------------------------------------------------------------------\n// Entity enrichment input\n// ---------------------------------------------------------------------------\n\nexport interface EntityEnrichmentInput {\n name: string;\n type: string;\n knownFacts: string[];\n importanceLevel: ImportanceLevel;\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline result\n// ---------------------------------------------------------------------------\n\nexport interface EnrichmentResult {\n entityName: string;\n provider: string;\n candidatesFound: number;\n candidatesAccepted: number;\n candidatesRejected: number;\n acceptedCandidates: EnrichmentCandidate[];\n elapsed: number;\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline config\n// ---------------------------------------------------------------------------\n\nexport interface EnrichmentPipelineConfig {\n enabled: boolean;\n providers: EnrichmentProviderConfig[];\n importanceThresholds: {\n critical: string[];\n high: string[];\n normal: string[];\n low: string[];\n };\n maxCandidatesPerEntity: number;\n autoEnrichOnCreate: boolean;\n scheduleIntervalMs: number;\n}\n\n/**\n * Build a default (disabled) pipeline config. Every consumer that needs a\n * config object should call this rather than duplicating the defaults.\n */\nexport function defaultEnrichmentPipelineConfig(): EnrichmentPipelineConfig {\n return {\n enabled: false,\n providers: [],\n importanceThresholds: {\n critical: [],\n high: [],\n normal: [],\n low: [],\n },\n maxCandidatesPerEntity: 20,\n autoEnrichOnCreate: false,\n scheduleIntervalMs: 3_600_000,\n };\n}\n","/**\n * Enrichment provider registry (issue #365).\n *\n * Central registry for enrichment providers. Providers register themselves\n * at startup; the pipeline queries the registry to determine which providers\n * to run for a given importance tier.\n */\n\nimport type { ImportanceLevel } from \"../types.js\";\nimport type {\n EnrichmentPipelineConfig,\n EnrichmentProvider,\n} from \"./types.js\";\n\nexport class EnrichmentProviderRegistry {\n private readonly providers = new Map<string, EnrichmentProvider>();\n\n /** Register a provider. Overwrites any existing provider with the same id. */\n register(provider: EnrichmentProvider): void {\n this.providers.set(provider.id, provider);\n }\n\n /** Look up a single provider by id. */\n get(id: string): EnrichmentProvider | undefined {\n return this.providers.get(id);\n }\n\n /**\n * Return all registered providers whose id appears in the config's\n * `providers` list with `enabled: true`.\n */\n listEnabled(config: EnrichmentPipelineConfig): EnrichmentProvider[] {\n const enabledIds = new Set(\n config.providers\n .filter((p) => p.enabled)\n .map((p) => p.id),\n );\n const result: EnrichmentProvider[] = [];\n for (const [id, provider] of this.providers.entries()) {\n if (enabledIds.has(id)) {\n result.push(provider);\n }\n }\n return result;\n }\n\n /**\n * Return providers that should run for a given importance level.\n * Providers are resolved from `config.importanceThresholds[level]` and\n * filtered to only those that are both registered and enabled.\n */\n getForImportance(\n level: ImportanceLevel,\n config: EnrichmentPipelineConfig,\n ): EnrichmentProvider[] {\n // \"trivial\" entities never get enrichment providers\n if (level === \"trivial\") return [];\n\n const thresholds = config.importanceThresholds;\n const providerIds: string[] =\n level === \"critical\"\n ? thresholds.critical\n : level === \"high\"\n ? thresholds.high\n : level === \"normal\"\n ? thresholds.normal\n : thresholds.low;\n\n const enabledIds = new Set(\n config.providers\n .filter((p) => p.enabled)\n .map((p) => p.id),\n );\n\n const result: EnrichmentProvider[] = [];\n for (const id of providerIds) {\n if (!enabledIds.has(id)) continue;\n const provider = this.providers.get(id);\n if (provider) {\n result.push(provider);\n }\n }\n return result;\n }\n}\n","/**\n * Web search enrichment provider stub (issue #365).\n *\n * A basic provider backed by web search. Since this is opt-in and we do not\n * want to hard-code an API key, the provider accepts an optional `searchFn`\n * injection point. When no search function is configured it returns empty\n * results, making it safe to register unconditionally.\n */\n\nimport type {\n EnrichmentCandidate,\n EnrichmentCostTier,\n EnrichmentProvider,\n EntityEnrichmentInput,\n} from \"./types.js\";\n\nexport type WebSearchFn = (query: string) => Promise<string[]>;\n\nexport interface WebSearchProviderOptions {\n /**\n * Injected search function. Each returned string is treated as a raw\n * snippet. When `undefined` the provider returns empty results.\n */\n searchFn?: WebSearchFn;\n}\n\nexport class WebSearchProvider implements EnrichmentProvider {\n readonly id = \"web-search\";\n readonly costTier: EnrichmentCostTier = \"cheap\";\n\n private readonly searchFn: WebSearchFn | undefined;\n\n constructor(options: WebSearchProviderOptions = {}) {\n this.searchFn = options.searchFn;\n }\n\n async isAvailable(): Promise<boolean> {\n return this.searchFn !== undefined;\n }\n\n async enrich(entity: EntityEnrichmentInput): Promise<EnrichmentCandidate[]> {\n if (!this.searchFn) return [];\n\n const query = `${entity.name} ${entity.type}`;\n let snippets: string[];\n try {\n snippets = await this.searchFn(query);\n } catch {\n return [];\n }\n\n return snippets\n .filter((s) => typeof s === \"string\" && s.trim().length > 0)\n .map((snippet) => ({\n text: snippet.trim(),\n source: this.id,\n sourceUrl: undefined,\n confidence: 0.5,\n category: \"fact\" as const,\n tags: [\"web-search\"],\n }));\n }\n}\n","/**\n * Enrichment pipeline orchestrator (issue #365).\n *\n * For each entity, determines the importance tier, resolves the providers\n * to run, executes them in sequence (respecting rate limits), tags\n * candidates, and caps at `maxCandidatesPerEntity`.\n *\n * Accepted candidates are returned in each `EnrichmentResult` via the\n * `acceptedCandidates` field so that callers can persist them.\n */\n\nimport type { LoggerBackend } from \"../logger.js\";\nimport type { EnrichmentProviderRegistry } from \"./provider-registry.js\";\nimport type {\n EnrichmentCandidate,\n EnrichmentPipelineConfig,\n EnrichmentProvider,\n EnrichmentResult,\n EntityEnrichmentInput,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Rate-limit tracking\n// ---------------------------------------------------------------------------\n\ninterface RateLimitBucket {\n minuteCount: number;\n minuteReset: number;\n dayCount: number;\n dayReset: number;\n}\n\nfunction isRateLimited(\n provider: EnrichmentProvider,\n config: EnrichmentPipelineConfig,\n buckets: Map<string, RateLimitBucket>,\n): boolean {\n const providerCfg = config.providers.find((p) => p.id === provider.id);\n if (!providerCfg?.rateLimit) return false;\n\n const now = Date.now();\n let bucket = buckets.get(provider.id);\n if (!bucket) {\n bucket = {\n minuteCount: 0,\n minuteReset: now + 60_000,\n dayCount: 0,\n dayReset: now + 86_400_000,\n };\n buckets.set(provider.id, bucket);\n }\n\n // Reset windows if expired\n if (now >= bucket.minuteReset) {\n bucket.minuteCount = 0;\n bucket.minuteReset = now + 60_000;\n }\n if (now >= bucket.dayReset) {\n bucket.dayCount = 0;\n bucket.dayReset = now + 86_400_000;\n }\n\n const { maxPerMinute, maxPerDay } = providerCfg.rateLimit;\n return bucket.minuteCount >= maxPerMinute || bucket.dayCount >= maxPerDay;\n}\n\nfunction recordCall(\n providerId: string,\n buckets: Map<string, RateLimitBucket>,\n): void {\n const bucket = buckets.get(providerId);\n if (bucket) {\n bucket.minuteCount += 1;\n bucket.dayCount += 1;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline\n// ---------------------------------------------------------------------------\n\nexport async function runEnrichmentPipeline(\n entities: EntityEnrichmentInput[],\n registry: EnrichmentProviderRegistry,\n config: EnrichmentPipelineConfig,\n log: LoggerBackend,\n): Promise<EnrichmentResult[]> {\n if (!config.enabled) return [];\n if (entities.length === 0) return [];\n\n const rateBuckets = new Map<string, RateLimitBucket>();\n const results: EnrichmentResult[] = [];\n\n for (const entity of entities) {\n const providers = registry.getForImportance(entity.importanceLevel, config);\n\n for (const provider of providers) {\n const start = Date.now();\n\n // Check availability\n let available: boolean;\n try {\n available = await provider.isAvailable();\n } catch {\n available = false;\n }\n\n if (!available) {\n log.debug?.(\n `enrichment: skipping provider ${provider.id} for ${entity.name} — unavailable`,\n );\n results.push({\n entityName: entity.name,\n provider: provider.id,\n candidatesFound: 0,\n candidatesAccepted: 0,\n candidatesRejected: 0,\n acceptedCandidates: [],\n elapsed: Date.now() - start,\n });\n continue;\n }\n\n // Check rate limit\n if (isRateLimited(provider, config, rateBuckets)) {\n log.debug?.(\n `enrichment: skipping provider ${provider.id} for ${entity.name} — rate limited`,\n );\n results.push({\n entityName: entity.name,\n provider: provider.id,\n candidatesFound: 0,\n candidatesAccepted: 0,\n candidatesRejected: 0,\n acceptedCandidates: [],\n elapsed: Date.now() - start,\n });\n continue;\n }\n\n // Run provider.\n // Count every attempt toward rate-limit buckets — including failures —\n // because the provider may have consumed external quota before throwing\n // (PR #425 review finding 2).\n let candidates: EnrichmentCandidate[];\n try {\n candidates = await provider.enrich(entity);\n } catch (err) {\n recordCall(provider.id, rateBuckets);\n log.error?.(\n `enrichment: provider ${provider.id} failed for ${entity.name}: ${err instanceof Error ? err.message : String(err)}`,\n );\n results.push({\n entityName: entity.name,\n provider: provider.id,\n candidatesFound: 0,\n candidatesAccepted: 0,\n candidatesRejected: 0,\n acceptedCandidates: [],\n elapsed: Date.now() - start,\n });\n continue;\n }\n recordCall(provider.id, rateBuckets);\n\n // Tag each candidate with provider id\n for (const candidate of candidates) {\n candidate.source = provider.id;\n }\n\n // Cap at maxCandidatesPerEntity.\n // 0 means \"accept none\"; undefined/negative means \"no cap\".\n const maxCandidates = config.maxCandidatesPerEntity;\n let accepted: EnrichmentCandidate[];\n if (maxCandidates === 0) {\n accepted = [];\n } else if (maxCandidates > 0 && candidates.length > maxCandidates) {\n accepted = candidates.slice(0, maxCandidates);\n } else {\n accepted = candidates;\n }\n const rejected = candidates.length - accepted.length;\n\n results.push({\n entityName: entity.name,\n provider: provider.id,\n candidatesFound: candidates.length,\n candidatesAccepted: accepted.length,\n candidatesRejected: rejected,\n acceptedCandidates: accepted,\n elapsed: Date.now() - start,\n });\n }\n }\n\n return results;\n}\n","/**\n * Enrichment audit trail (issue #365).\n *\n * Append-only JSONL log for every enrichment candidate that was evaluated.\n * Each entry records whether the candidate was accepted or rejected, the\n * provider that produced it, and an optional reason string.\n */\n\nimport { mkdir, readFile, appendFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface EnrichmentAuditEntry {\n timestamp: string;\n entityName: string;\n provider: string;\n candidateText: string;\n sourceUrl?: string;\n accepted: boolean;\n reason?: string;\n}\n\n// ---------------------------------------------------------------------------\n// File helpers\n// ---------------------------------------------------------------------------\n\nconst AUDIT_FILENAME = \"enrichment-audit.jsonl\";\n\nfunction auditFilePath(auditDir: string): string {\n return path.join(auditDir, AUDIT_FILENAME);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Append a single audit entry to the JSONL log. Creates the audit directory\n * and file if they do not exist.\n */\nexport async function appendAuditEntry(\n auditDir: string,\n entry: EnrichmentAuditEntry,\n): Promise<void> {\n await mkdir(auditDir, { recursive: true });\n const line = JSON.stringify(entry) + \"\\n\";\n await appendFile(auditFilePath(auditDir), line, \"utf-8\");\n}\n\n/**\n * Read the audit log and return entries, optionally filtered to entries at\n * or after `since` (ISO 8601 timestamp, half-open interval).\n */\nexport async function readAuditLog(\n auditDir: string,\n since?: string,\n): Promise<EnrichmentAuditEntry[]> {\n const filePath = auditFilePath(auditDir);\n if (!existsSync(filePath)) return [];\n\n const raw = await readFile(filePath, \"utf-8\");\n const entries: EnrichmentAuditEntry[] = [];\n\n for (const line of raw.split(\"\\n\")) {\n const trimmed = line.trim();\n if (trimmed.length === 0) continue;\n try {\n const parsed: unknown = JSON.parse(trimmed);\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n \"timestamp\" in parsed &&\n \"entityName\" in parsed\n ) {\n const entry = parsed as EnrichmentAuditEntry;\n if (since && entry.timestamp < since) continue;\n entries.push(entry);\n }\n } catch {\n // Skip malformed lines\n }\n }\n\n return entries;\n}\n"],"mappings":"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","names":["fsp","path","fsp","path","fsp","path","crypto","fsp","crypto","log","path","fs","path","path","fs","path","fs","path","fs","path","crypto","fs","path","parseFrontmatter","extractBody","crypto","fs","path","crypto","hashContent","path","fs","readFileSafe","parseFrontmatter","extractBody","crypto","fs","path","path","fs","parseFrontmatter","extractBody","readFileSafe","fs","path","crypto","DEFAULT_EXCLUDE","hashContent","fs","path","path","path","manifestPath","path","fs","fs","path","fs","path","crypto","manifestPath","walkMd","hashContent","fs","os","path","REMNIC_EXTENSION_DIR_NAME","path","os","fs","alternatives","path","path","log","mkdir","readFile","existsSync","path"]}
1
+ {"version":3,"sources":["../src/binary-lifecycle/types.ts","../src/binary-lifecycle/backend.ts","../src/binary-lifecycle/scanner.ts","../src/binary-lifecycle/manifest.ts","../src/binary-lifecycle/pipeline.ts","../src/projection/index.ts","../src/utils/category-dir.ts","../src/onboarding/index.ts","../src/curation/index.ts","../src/dedup/index.ts","../src/review/index.ts","../src/sync/index.ts","../src/connectors/index.ts","../src/connectors/codex-marketplace.ts","../src/spaces/index.ts","../src/memory-extension/shared-instructions.ts","../src/memory-extension/codex-publisher.ts","../src/memory-extension/claude-code-publisher.ts","../src/memory-extension/hermes-publisher.ts","../src/memory-extension/index.ts","../src/taxonomy/default-taxonomy.ts","../src/taxonomy/resolver-doc-generator.ts","../src/taxonomy/taxonomy-loader.ts","../src/enrichment/types.ts","../src/enrichment/provider-registry.ts","../src/enrichment/web-search-provider.ts","../src/enrichment/pipeline.ts","../src/enrichment/audit.ts"],"sourcesContent":["/**\n * Binary file lifecycle management types.\n *\n * Defines the configuration, manifest, and record structures for the\n * three-stage binary lifecycle pipeline: mirror, redirect, clean.\n */\n\nexport interface BinaryLifecycleConfig {\n /** Master toggle. Default: false. */\n enabled: boolean;\n /** Days after mirror before local copy is eligible for cleanup. Default: 7. */\n gracePeriodDays: number;\n /** Files larger than this are skipped during scan. Default: 50 MB. */\n maxBinarySizeBytes: number;\n /** Glob patterns for binary file types to manage. */\n scanPatterns: string[];\n /** Backend configuration for binary storage. */\n backend: BinaryStorageBackendConfig;\n}\n\nexport interface BinaryStorageBackendConfig {\n /** Backend type. \"filesystem\" copies to a local directory. \"none\" is a no-op (dry-run/testing). */\n type: \"filesystem\" | \"s3\" | \"none\";\n /** Destination directory for the filesystem backend. */\n basePath?: string;\n /** S3 bucket name (future). */\n s3Bucket?: string;\n /** S3 region (future). */\n s3Region?: string;\n /** S3 key prefix (future). */\n s3Prefix?: string;\n}\n\nexport type BinaryAssetStatus =\n | \"pending\"\n | \"mirrored\"\n | \"redirected\"\n | \"cleaned\"\n | \"error\";\n\nexport interface BinaryAssetRecord {\n /** Relative path from memoryDir to the original file. */\n originalPath: string;\n /** Path (or URL) in the backend storage. */\n mirroredPath: string;\n /** SHA-256 hex digest of file content. */\n contentHash: string;\n /** File size in bytes. */\n sizeBytes: number;\n /** MIME type (e.g. \"image/png\"). */\n mimeType: string;\n /** ISO 8601 timestamp when the file was mirrored. */\n mirroredAt: string;\n /** ISO 8601 timestamp when markdown references were rewritten. */\n redirectedAt?: string;\n /** ISO 8601 timestamp when the local copy was deleted. */\n cleanedAt?: string;\n /** Current lifecycle status. */\n status: BinaryAssetStatus;\n}\n\nexport interface BinaryLifecycleManifest {\n version: 1;\n assets: BinaryAssetRecord[];\n lastScanAt?: string;\n}\n\nexport interface PipelineResult {\n scanned: number;\n mirrored: number;\n redirected: number;\n cleaned: number;\n errors: string[];\n dryRun: boolean;\n}\n\nexport const DEFAULT_SCAN_PATTERNS = [\n \"*.png\",\n \"*.jpg\",\n \"*.jpeg\",\n \"*.gif\",\n \"*.pdf\",\n \"*.mp3\",\n \"*.mp4\",\n \"*.wav\",\n];\n\nexport const DEFAULT_MAX_BINARY_SIZE_BYTES = 50 * 1024 * 1024; // 50 MB\nexport const DEFAULT_GRACE_PERIOD_DAYS = 7;\n","/**\n * Binary storage backend interface and implementations.\n *\n * Backends handle the actual persistence of binary files to an external\n * location. The pipeline calls upload/exists/delete through this interface\n * so swapping storage providers requires no pipeline changes.\n */\n\nimport fs from \"node:fs\";\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { BinaryStorageBackendConfig } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Interface\n// ---------------------------------------------------------------------------\n\nexport interface BinaryStorageBackend {\n /** Discriminator for the backend type. */\n readonly type: string;\n /**\n * Upload a local file to the backend.\n * @returns The backend path or URL where the file was stored.\n */\n upload(localPath: string, remotePath: string): Promise<string>;\n /** Check whether a remote path already exists in the backend. */\n exists(remotePath: string): Promise<boolean>;\n /** Delete a file from the backend. */\n delete(remotePath: string): Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// Filesystem backend\n// ---------------------------------------------------------------------------\n\nexport class FilesystemBackend implements BinaryStorageBackend {\n readonly type = \"filesystem\";\n private readonly basePath: string;\n\n constructor(basePath: string) {\n if (!basePath || basePath.trim().length === 0) {\n throw new Error(\"FilesystemBackend requires a non-empty basePath\");\n }\n this.basePath = basePath;\n }\n\n async upload(localPath: string, remotePath: string): Promise<string> {\n const dest = path.join(this.basePath, remotePath);\n const destDir = path.dirname(dest);\n await fsp.mkdir(destDir, { recursive: true });\n await fsp.copyFile(localPath, dest);\n return dest;\n }\n\n async exists(remotePath: string): Promise<boolean> {\n const dest = path.join(this.basePath, remotePath);\n try {\n await fsp.access(dest, fs.constants.F_OK);\n return true;\n } catch {\n return false;\n }\n }\n\n async delete(remotePath: string): Promise<void> {\n const dest = path.join(this.basePath, remotePath);\n try {\n await fsp.unlink(dest);\n } catch (err: unknown) {\n // Ignore ENOENT (already deleted); rethrow everything else.\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// None backend (no-op, for dry-run / testing)\n// ---------------------------------------------------------------------------\n\nexport class NoneBackend implements BinaryStorageBackend {\n readonly type = \"none\";\n\n async upload(_localPath: string, remotePath: string): Promise<string> {\n return remotePath;\n }\n\n async exists(_remotePath: string): Promise<boolean> {\n return false;\n }\n\n async delete(_remotePath: string): Promise<void> {\n // intentional no-op\n }\n}\n\n// ---------------------------------------------------------------------------\n// Factory\n// ---------------------------------------------------------------------------\n\nexport function createBackend(cfg: BinaryStorageBackendConfig): BinaryStorageBackend {\n switch (cfg.type) {\n case \"filesystem\": {\n if (!cfg.basePath) {\n throw new Error(\n \"BinaryStorageBackendConfig.basePath is required when type is \\\"filesystem\\\"\",\n );\n }\n return new FilesystemBackend(cfg.basePath);\n }\n case \"s3\":\n throw new Error(\"S3 binary storage backend is not yet implemented\");\n case \"none\":\n return new NoneBackend();\n default:\n throw new Error(`Unknown binary storage backend type: ${String((cfg as { type: string }).type)}`);\n }\n}\n","/**\n * Binary file scanner.\n *\n * Recursively walks the memory directory, matches files against configured\n * glob patterns, skips files already tracked in the manifest, and respects\n * the max-size limit.\n */\n\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { BinaryLifecycleConfig, BinaryLifecycleManifest } from \"./types.js\";\n\n/**\n * Test whether a filename matches any of the provided glob patterns.\n * Supports simple `*.ext` patterns (the default scan patterns).\n * For more complex globs a proper library should be used; this covers\n * the 95% case without adding a dependency.\n */\nexport function matchesPatterns(filename: string, patterns: string[]): boolean {\n const lower = filename.toLowerCase();\n for (const pattern of patterns) {\n // Simple *.ext matching\n if (pattern.startsWith(\"*.\")) {\n const ext = pattern.slice(1).toLowerCase(); // e.g. \".png\"\n if (lower.endsWith(ext)) return true;\n } else if (lower === pattern.toLowerCase()) {\n return true;\n }\n }\n return false;\n}\n\n/**\n * Scan memoryDir recursively for binary files matching the configured patterns.\n * Returns relative paths (relative to memoryDir) for files not yet tracked.\n */\nexport async function scanForBinaries(\n memoryDir: string,\n config: BinaryLifecycleConfig,\n manifest: BinaryLifecycleManifest,\n): Promise<string[]> {\n const tracked = new Set(manifest.assets.map((a) => a.originalPath));\n const results: string[] = [];\n\n async function walk(dir: string): Promise<void> {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fsp.readdir(dir, { withFileTypes: true });\n } catch {\n return; // skip unreadable directories\n }\n\n for (const entry of entries) {\n const fullPath = path.join(dir, entry.name);\n // Normalize to POSIX separators so redirect matching works on Windows\n // (markdown links always use forward slashes).\n const relativePath = path.relative(memoryDir, fullPath).split(path.sep).join(\"/\");\n\n if (entry.isDirectory()) {\n // Skip the manifest directory itself and hidden dirs starting with .\n if (entry.name === \".binary-lifecycle\") continue;\n await walk(fullPath);\n continue;\n }\n\n if (!entry.isFile()) continue;\n\n // Check pattern match\n if (!matchesPatterns(entry.name, config.scanPatterns)) continue;\n\n // Skip already tracked\n if (tracked.has(relativePath)) continue;\n\n // Check file size\n try {\n const stat = await fsp.stat(fullPath);\n if (stat.size > config.maxBinarySizeBytes) continue;\n if (stat.size === 0) continue; // skip empty files\n } catch {\n continue; // stat failure, skip\n }\n\n results.push(relativePath);\n }\n }\n\n await walk(memoryDir);\n return results;\n}\n","/**\n * Binary lifecycle manifest — read/write operations.\n *\n * The manifest lives at `${memoryDir}/.binary-lifecycle/manifest.json`.\n * Writes use the atomic temp-then-rename pattern (CLAUDE.md #54).\n */\n\nimport fs from \"node:fs\";\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\nimport type { BinaryLifecycleManifest } from \"./types.js\";\n\nconst MANIFEST_DIR = \".binary-lifecycle\";\nconst MANIFEST_FILE = \"manifest.json\";\n\nexport function manifestDir(memoryDir: string): string {\n return path.join(memoryDir, MANIFEST_DIR);\n}\n\nexport function manifestPath(memoryDir: string): string {\n return path.join(memoryDir, MANIFEST_DIR, MANIFEST_FILE);\n}\n\n/**\n * Read the manifest from disk. Returns a fresh empty manifest if the file\n * does not exist or contains invalid JSON (CLAUDE.md #18).\n */\nexport async function readManifest(memoryDir: string): Promise<BinaryLifecycleManifest> {\n const filePath = manifestPath(memoryDir);\n try {\n const raw = await fsp.readFile(filePath, \"utf-8\");\n const parsed: unknown = JSON.parse(raw);\n // CLAUDE.md #18: validate the parsed result is a non-null object.\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n return emptyManifest();\n }\n const obj = parsed as Record<string, unknown>;\n if (obj.version !== 1 || !Array.isArray(obj.assets)) {\n return emptyManifest();\n }\n return parsed as BinaryLifecycleManifest;\n } catch {\n return emptyManifest();\n }\n}\n\n/**\n * Write the manifest atomically: write to a temp file, then rename.\n * CLAUDE.md #54: never delete before write. Write temp first, rename atomically.\n */\nexport async function writeManifest(\n memoryDir: string,\n manifest: BinaryLifecycleManifest,\n): Promise<void> {\n const dir = manifestDir(memoryDir);\n await fsp.mkdir(dir, { recursive: true });\n const dest = manifestPath(memoryDir);\n const tmpSuffix = crypto.randomBytes(8).toString(\"hex\");\n const tmpPath = `${dest}.${tmpSuffix}.tmp`;\n // Sort keys for deterministic output (CLAUDE.md #38).\n const content = JSON.stringify(manifest, null, 2) + \"\\n\";\n await fsp.writeFile(tmpPath, content, \"utf-8\");\n try {\n await fsp.rename(tmpPath, dest);\n } catch (renameErr) {\n // Clean up temp on rename failure (cross-device edge case).\n try {\n await fsp.unlink(tmpPath);\n } catch {\n // ignore cleanup failure\n }\n throw renameErr;\n }\n}\n\nexport function emptyManifest(): BinaryLifecycleManifest {\n return { version: 1, assets: [] };\n}\n","/**\n * Binary lifecycle pipeline — mirror, redirect, clean.\n *\n * Three-stage pipeline:\n * 1. Mirror: upload binary to backend, record in manifest\n * 2. Redirect: scan markdown for inline refs, replace with redirect path\n * 3. Clean: after grace period, delete local copy\n */\n\nimport fsp from \"node:fs/promises\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\nimport type {\n BinaryAssetRecord,\n BinaryLifecycleConfig,\n PipelineResult,\n} from \"./types.js\";\nimport type { BinaryStorageBackend } from \"./backend.js\";\nimport { readManifest, writeManifest } from \"./manifest.js\";\nimport { scanForBinaries } from \"./scanner.js\";\n\n/** Minimal logger interface so we don't depend on the full logger module. */\ninterface PipelineLogger {\n info(msg: string): void;\n warn(msg: string): void;\n error(msg: string): void;\n}\n\ninterface PipelineOptions {\n dryRun?: boolean;\n /** Force-clean all files past grace period, ignoring redirect status. */\n forceClean?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nasync function hashFile(filePath: string): Promise<string> {\n const content = await fsp.readFile(filePath);\n return crypto.createHash(\"sha256\").update(content).digest(\"hex\");\n}\n\nfunction guessMimeType(ext: string): string {\n const map: Record<string, string> = {\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".pdf\": \"application/pdf\",\n \".mp3\": \"audio/mpeg\",\n \".mp4\": \"video/mp4\",\n \".wav\": \"audio/wav\",\n };\n return map[ext.toLowerCase()] ?? \"application/octet-stream\";\n}\n\n/**\n * Escape special regex characters in a string.\n * CLAUDE.md #46: always escapeRegex on user-derived parts.\n */\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline stages\n// ---------------------------------------------------------------------------\n\nasync function stageMirror(\n memoryDir: string,\n newPaths: string[],\n backend: BinaryStorageBackend,\n assets: BinaryAssetRecord[],\n log: PipelineLogger,\n dryRun: boolean,\n): Promise<{ mirrored: number; errors: string[] }> {\n let mirrored = 0;\n const errors: string[] = [];\n\n for (const relPath of newPaths) {\n const fullPath = path.join(memoryDir, relPath);\n try {\n const stat = await fsp.stat(fullPath);\n const contentHash = await hashFile(fullPath);\n const ext = path.extname(relPath);\n const mimeType = guessMimeType(ext);\n const remotePath = relPath;\n\n let backendLocation = remotePath;\n if (!dryRun) {\n backendLocation = await backend.upload(fullPath, remotePath);\n }\n\n const record: BinaryAssetRecord = {\n originalPath: relPath,\n mirroredPath: backendLocation,\n contentHash,\n sizeBytes: stat.size,\n mimeType,\n mirroredAt: new Date().toISOString(),\n status: \"mirrored\",\n };\n\n assets.push(record);\n mirrored++;\n log.info(`[binary-lifecycle] mirrored: ${relPath} (${stat.size} bytes)${dryRun ? \" [dry-run]\" : \"\"}`);\n } catch (err) {\n const msg = `mirror failed for ${relPath}: ${err instanceof Error ? err.message : String(err)}`;\n log.error(`[binary-lifecycle] ${msg}`);\n errors.push(msg);\n }\n }\n\n return { mirrored, errors };\n}\n\nasync function stageRedirect(\n memoryDir: string,\n assets: BinaryAssetRecord[],\n log: PipelineLogger,\n dryRun: boolean,\n): Promise<{ redirected: number; errors: string[] }> {\n let redirected = 0;\n const errors: string[] = [];\n\n // Only redirect assets that are mirrored but not yet redirected.\n const candidates = assets.filter((a) => a.status === \"mirrored\");\n if (candidates.length === 0) return { redirected, errors };\n\n // Find all markdown files in memoryDir (recursive).\n const mdFiles = await findMarkdownFiles(memoryDir);\n\n for (const asset of candidates) {\n let matchCount = 0;\n let writeFailCount = 0;\n for (const mdPath of mdFiles) {\n try {\n const content = await fsp.readFile(mdPath, \"utf-8\");\n\n // Build the match path relative to this markdown file's directory.\n // Markdown links like `![img](./image.png)` are file-relative, but\n // asset.originalPath is memory-root relative (e.g. `sub/image.png`).\n // Resolve the asset path relative to the markdown file's directory\n // so both forms match correctly.\n const mdDir = path.dirname(mdPath);\n const assetAbsolute = path.join(memoryDir, asset.originalPath);\n const relativeToMd = path.relative(mdDir, assetAbsolute);\n // Normalise to forward slashes for regex matching (markdown uses /).\n const relativeForward = relativeToMd.split(path.sep).join(\"/\");\n const escaped = escapeRegex(relativeForward);\n\n // Build a regex that matches markdown image/link references to the file.\n // Handles: ![alt](./path) , ![alt](path) , [text](./path)\n const pattern = new RegExp(\n `(!?\\\\[[^\\\\]]*\\\\]\\\\()(\\\\.\\\\/)?(${escaped})(\\\\))`,\n \"g\",\n );\n\n if (!pattern.test(content)) continue;\n matchCount++;\n\n if (!dryRun) {\n // Reset lastIndex after test().\n pattern.lastIndex = 0;\n const updated = content.replace(pattern, (_match, open, _dotSlash, _file, close) => {\n return `${open as string}${asset.mirroredPath}${close as string}`;\n });\n await fsp.writeFile(mdPath, updated, \"utf-8\");\n }\n } catch (err) {\n // Track write failures separately so we don't transition status\n // when some markdown rewrites failed (P1: block redirect on failure).\n writeFailCount++;\n const msg = `redirect scan failed for ${mdPath}: ${err instanceof Error ? err.message : String(err)}`;\n log.error(`[binary-lifecycle] ${msg}`);\n errors.push(msg);\n }\n }\n\n // Only transition to \"redirected\" when at least one reference was found\n // AND all matched files were rewritten successfully.\n if (matchCount > 0 && writeFailCount === 0) {\n asset.status = \"redirected\";\n asset.redirectedAt = new Date().toISOString();\n redirected++;\n log.info(`[binary-lifecycle] redirected: ${asset.originalPath}${dryRun ? \" [dry-run]\" : \"\"}`);\n } else if (matchCount > 0 && writeFailCount > 0) {\n // Some rewrites failed — set error status so the asset is not cleaned\n // prematurely. It can be retried on the next pipeline run.\n asset.status = \"error\";\n log.warn(\n `[binary-lifecycle] redirect partial failure for ${asset.originalPath}: ` +\n `${matchCount} match(es), ${writeFailCount} write failure(s) — status set to error`,\n );\n }\n }\n\n return { redirected, errors };\n}\n\nasync function stageClean(\n memoryDir: string,\n assets: BinaryAssetRecord[],\n gracePeriodDays: number,\n log: PipelineLogger,\n dryRun: boolean,\n forceClean: boolean,\n): Promise<{ cleaned: number; errors: string[] }> {\n let cleaned = 0;\n const errors: string[] = [];\n const now = Date.now();\n const graceMs = gracePeriodDays * 24 * 60 * 60 * 1000;\n\n // Clean only assets that have been redirected (markdown refs already rewritten).\n // Mirrored-only assets must NOT be cleaned — their markdown refs still point\n // to the local file, so deletion would break links.\n const candidates = assets.filter(\n (a) => a.status === \"redirected\",\n );\n\n for (const asset of candidates) {\n const mirroredMs = new Date(asset.mirroredAt).getTime();\n const ageMs = now - mirroredMs;\n\n if (!forceClean && ageMs < graceMs) {\n // Not yet past grace period.\n continue;\n }\n\n const fullPath = path.join(memoryDir, asset.originalPath);\n try {\n if (!dryRun) {\n await fsp.unlink(fullPath);\n }\n asset.status = \"cleaned\";\n asset.cleanedAt = new Date().toISOString();\n cleaned++;\n log.info(`[binary-lifecycle] cleaned: ${asset.originalPath}${dryRun ? \" [dry-run]\" : \"\"}`);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code === \"ENOENT\") {\n // Already gone — mark as cleaned.\n asset.status = \"cleaned\";\n asset.cleanedAt = new Date().toISOString();\n cleaned++;\n } else {\n const msg = `clean failed for ${asset.originalPath}: ${err instanceof Error ? err.message : String(err)}`;\n log.error(`[binary-lifecycle] ${msg}`);\n errors.push(msg);\n }\n }\n }\n\n return { cleaned, errors };\n}\n\n// ---------------------------------------------------------------------------\n// Markdown file discovery\n// ---------------------------------------------------------------------------\n\nasync function findMarkdownFiles(dir: string): Promise<string[]> {\n const results: string[] = [];\n\n async function walk(current: string): Promise<void> {\n let entries: import(\"node:fs\").Dirent[];\n try {\n entries = await fsp.readdir(current, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n const full = path.join(current, entry.name);\n if (entry.isDirectory()) {\n if (entry.name === \".binary-lifecycle\") continue;\n await walk(full);\n } else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n results.push(full);\n }\n }\n }\n\n await walk(dir);\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Main pipeline entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Run the binary lifecycle pipeline: scan, mirror, redirect, clean.\n */\nexport async function runBinaryLifecyclePipeline(\n memoryDir: string,\n config: BinaryLifecycleConfig,\n backend: BinaryStorageBackend,\n log: PipelineLogger,\n opts?: PipelineOptions,\n): Promise<PipelineResult> {\n const dryRun = opts?.dryRun ?? false;\n const forceClean = opts?.forceClean ?? false;\n\n const manifest = await readManifest(memoryDir);\n\n // Stage 0: Scan\n const newPaths = await scanForBinaries(memoryDir, config, manifest);\n const scanned = newPaths.length;\n\n // Stage 1: Mirror\n const mirrorResult = await stageMirror(\n memoryDir,\n newPaths,\n backend,\n manifest.assets,\n log,\n dryRun,\n );\n\n // Stage 2: Redirect\n const redirectResult = await stageRedirect(memoryDir, manifest.assets, log, dryRun);\n\n // Stage 3: Clean\n const cleanResult = await stageClean(\n memoryDir,\n manifest.assets,\n config.gracePeriodDays,\n log,\n dryRun,\n forceClean,\n );\n\n // Persist manifest (unless dry-run).\n manifest.lastScanAt = new Date().toISOString();\n if (!dryRun) {\n await writeManifest(memoryDir, manifest);\n }\n\n const allErrors = [\n ...mirrorResult.errors,\n ...redirectResult.errors,\n ...cleanResult.errors,\n ];\n\n return {\n scanned,\n mirrored: mirrorResult.mirrored,\n redirected: redirectResult.redirected,\n cleaned: cleanResult.cleaned,\n errors: allErrors,\n dryRun,\n };\n}\n","/**\n * @remnic/core — Workspace Tree Projection\n *\n * Generates a human-readable `.engram/context-tree/` from canonical memory.\n * Each node is a `.md` file with rich metadata, * (provenance, trust, confidence, source anchors).\n * Manual edits are preserved in fenced blocks.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getCategoryDir, ALL_CATEGORY_KEYS } from \"../utils/category-dir.js\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface TreeNode {\n /** Relative path from context-tree root, e.g. \"entities/claude.md\" */\n path: string;\n /** Category from canonical memory */\n category: string;\n /** Human-readable title */\n title: string;\n /** File content (rendered markdown) */\n content: string;\n /** Source memory IDs that contributed to this node */\n sourceAnchors: string[];\n /** Confidence (0-1) */\n confidence: number;\n /** Trust zone classification */\n confidenceTier: string;\n /** When this node was generated */\n generatedAt: string;\n /** Provenance chain */\n provenance: ProvenanceEntry[];\n}\n\nexport interface ProvenanceEntry {\n memoryId: string;\n source: string;\n extracted: string;\n}\n\nexport interface GenerateOptions {\n /** Memory root directory (e.g. ~/.openclaw/workspace/memory/local) */\n memoryDir: string;\n /** Output directory (e.g. .engram/context-tree) */\n outputDir: string;\n /** Categories to include (default: all) */\n categories?: string[];\n /** Whether to include entity graph */\n includeEntities?: boolean;\n /** Whether to include orphaned questions */\n includeQuestions?: boolean;\n /** Max nodes per category (default: unlimited) */\n maxPerCategory?: number;\n /** Whether to watch for changes and regenerate incrementally */\n watch?: boolean;\n}\n\nexport interface GenerateResult {\n nodesGenerated: number;\n nodesSkipped: number;\n categories: Record<string, number>;\n durationMs: number;\n outputDir: string;\n}\n\n// ── Generation ──────────────────────────────────────────────────────────────\n\n/**\n * Generate a context tree from canonical memory.\n *\n * Reads memory `.md` files from the source directory, * and projects them into a clean, * human-readable tree structure at `outputDir`.\n */\nexport async function generateContextTree(options: GenerateOptions): Promise<GenerateResult> {\n const startTime = Date.now();\n const {\n memoryDir,\n outputDir,\n categories: filterCategories,\n includeEntities = true,\n includeQuestions = true,\n maxPerCategory = Infinity,\n } = options;\n\n let nodesGenerated = 0;\n let nodesSkipped = 0;\n const categoryCounts: Record<string, number> = {};\n\n // Ensure output directory exists\n fs.mkdirSync(outputDir, { recursive: true });\n\n // Process each category (exclude 'question' — handled by separate includeQuestions pass)\n const allCategories = filterCategories ?? ALL_CATEGORY_KEYS.filter((c) => c !== \"question\");\n\n for (const category of allCategories) {\n const categoryDir = getCategoryDir(memoryDir, category);\n if (!fs.existsSync(categoryDir)) continue;\n\n categoryCounts[category] = 0;\n const files = walkR(categoryDir);\n let count = 0;\n\n for (const filePath of files) {\n if (count >= maxPerCategory) {\n nodesSkipped++;\n continue;\n }\n\n const content = fs.readFileSync(filePath, \"utf8\");\n const fm = parseFrontmatter(content);\n if (!fm) {\n nodesSkipped++;\n continue;\n }\n\n const node = projectNode(filePath, category, fm, content);\n if (!node) {\n nodesSkipped++;\n continue;\n }\n\n // Write node to output\n const outputPath = path.join(outputDir, node.path);\n fs.mkdirSync(path.dirname(outputPath), { recursive: true });\n fs.writeFileSync(outputPath, node.content);\n\n nodesGenerated++;\n categoryCounts[category] = (categoryCounts[category] ?? 0) + 1;\n count++;\n }\n }\n\n // Process entities\n if (includeEntities) {\n const entitiesDir = path.join(memoryDir, \"entities\");\n if (fs.existsSync(entitiesDir)) {\n categoryCounts[\"entity\"] = 0;\n const entityFiles = walkR(entitiesDir);\n let count = 0;\n\n for (const filePath of entityFiles) {\n if (count >= maxPerCategory) {\n nodesSkipped++;\n continue;\n }\n\n const content = fs.readFileSync(filePath, \"utf8\");\n const fileName = path.basename(filePath, \".md\");\n const node = projectEntityNode(fileName, content);\n\n const outputPath = path.join(outputDir, \"entities\", `${fileName}.md`);\n fs.mkdirSync(path.dirname(outputPath), { recursive: true });\n fs.writeFileSync(outputPath, node.content);\n\n nodesGenerated++;\n categoryCounts[\"entity\"] = (categoryCounts[\"entity\"] ?? 0) + 1;\n count++;\n }\n }\n }\n\n // Process questions\n if (includeQuestions) {\n const questionsDir = path.join(memoryDir, \"questions\");\n if (fs.existsSync(questionsDir)) {\n categoryCounts[\"question\"] = 0;\n const qFiles = walkR(questionsDir);\n let count = 0;\n\n for (const filePath of qFiles) {\n if (count >= maxPerCategory) {\n nodesSkipped++;\n continue;\n }\n\n const content = fs.readFileSync(filePath, \"utf8\");\n const fm = parseFrontmatter(content);\n if (!fm) {\n nodesSkipped++;\n continue;\n }\n\n const node = projectNode(filePath, \"question\", fm, content);\n if (!node) {\n nodesSkipped++;\n continue;\n }\n\n const outputPath = path.join(outputDir, node.path);\n fs.mkdirSync(path.dirname(outputPath), { recursive: true });\n fs.writeFileSync(outputPath, node.content);\n\n nodesGenerated++;\n categoryCounts[\"question\"] = (categoryCounts[\"question\"] ?? 0) + 1;\n count++;\n }\n }\n }\n\n // Write index\n const index = generateIndex(categoryCounts, outputDir);\n fs.writeFileSync(path.join(outputDir, \"INDEX.md\"), index);\n\n return {\n nodesGenerated,\n nodesSkipped,\n categories: categoryCounts,\n durationMs: Date.now() - startTime,\n outputDir,\n };\n}\n\n// ── Helpers ──────────────────────────────────────────────────────────────────\n\n\nfunction walkR(dir: string): string[] {\n const results: string[] = [];\n function walk(directory: string): void {\n for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {\n const fullPath = path.join(directory, entry.name);\n if (entry.isDirectory()) {\n walk(fullPath);\n } else if (entry.name.endsWith(\".md\")) {\n results.push(fullPath);\n }\n }\n }\n walk(dir);\n return results;\n}\n\ninterface Frontmatter {\n id: string;\n category: string;\n created: string;\n updated: string;\n confidence: number;\n confidenceTier: string;\n tags: string[];\n source: string;\n entityRef?: string;\n lifecycleState?: string;\n}\n\nfunction parseFrontmatter(content: string): Frontmatter | null {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!match) return null;\n\n const fmText = match[1];\n const fm: Record<string, unknown> = {};\n for (const line of fmText.split(\"\\n\")) {\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n if (key === \"tags\") {\n try {\n fm[key] = JSON.parse(value) as string[];\n } catch {\n fm[key] = [] as string[];\n }\n } else if (key === \"confidence\") {\n const parsed = parseFloat(value);\n fm[key] = Number.isFinite(parsed) ? parsed : 0;\n } else {\n fm[key] = value;\n }\n }\n return fm as unknown as Frontmatter;\n}\n\nfunction extractBody(content: string): string {\n const match = content.match(/^---\\n[\\s\\S]*?\\n---\\n([\\s\\S]*)/);\n return match ? match[1].trim() : content.trim();\n}\n\nfunction projectNode(\n filePath: string,\n category: string,\n fm: Frontmatter,\n rawContent: string,\n): TreeNode | null {\n const body = extractBody(rawContent);\n const fileName = path.basename(filePath, \".md\");\n const dateDir = path.basename(path.dirname(filePath));\n\n // Build relative path: category/date/file or just category/file\n let relPath: string;\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(dateDir)) {\n relPath = path.join(category, dateDir, `${fileName}.md`);\n } else {\n relPath = path.join(category, `${fileName}.md`);\n }\n\n const generatedAt = new Date().toISOString();\n\n const md = `# ${fm.id}\n\n> **Category:** ${fm.category}\n> **Created:** ${fm.created}\n> **Updated:** ${fm.updated ?? fm.created}\n> **Confidence:** ${fm.confidence} (${fm.confidenceTier}${fm.lifecycleState ? `, ${fm.lifecycleState}` : \"\"})\n${fm.tags?.length ? `\\n> **Tags:** ${fm.tags.join(\", \")}` : \"\"}\n${fm.entityRef ? `\\n> **Entity:** ${fm.entityRef}` : \"\"}\n> **Source:** ${fm.source ?? \"unknown\"}\n> **Projected:** ${generatedAt}\n\n---\n\n${body}\n`;\n\n return {\n path: relPath,\n category,\n title: fm.id,\n content: md,\n sourceAnchors: [fm.id],\n confidence: fm.confidence ?? 0,\n confidenceTier: fm.confidenceTier ?? \"unknown\",\n generatedAt,\n provenance: [{\n memoryId: fm.id,\n source: fm.source ?? \"unknown\",\n extracted: fm.created,\n }],\n };\n}\n\nfunction projectEntityNode(fileName: string, content: string): TreeNode {\n const generatedAt = new Date().toISOString();\n\n const md = `> **Projected:** ${generatedAt}\n> **Source:** canonical\n\n---\n\n${content}\n`;\n\n return {\n path: path.join(\"entities\", `${fileName}.md`),\n category: \"entity\",\n title: fileName,\n content: md,\n sourceAnchors: [fileName],\n confidence: 1,\n confidenceTier: \"explicit\",\n generatedAt,\n provenance: [{\n memoryId: fileName,\n source: \"canonical\",\n extracted: generatedAt,\n }],\n };\n}\n\nfunction generateIndex(\n categoryCounts: Record<string, number>,\n outputDir: string,\n): string {\n const lines = [\n \"# Context Tree Index\",\n \"\",\n `Generated: ${new Date().toISOString()}`,\n \"\",\n \"## Summary\",\n \"\",\n `| Category | Count |`,\n `|----------|-------|`,\n ];\n\n let total = 0;\n for (const [cat, count] of Object.entries(categoryCounts).sort()) {\n lines.push(`| ${cat} | ${count} |`);\n total += count;\n }\n\n lines.push(\"\");\n lines.push(`**Total:** ${total} nodes`);\n lines.push(\"\");\n lines.push(\"## Structure\");\n lines.push(\"\");\n lines.push(\"```\");\n lines.push(\"context-tree/\");\n lines.push(\"├── entities/ # Entity knowledge graph\");\n lines.push(\"├── fact/ # Factual memories (date-partitioned)\");\n lines.push(\"├── correction/ # Correction memories\");\n lines.push(\"├── decision/ # Decisions\");\n lines.push(\"├── moment/ # Notable moments\");\n lines.push(\"├── preference/ # Preferences\");\n lines.push(\"├── principle/ # Principles\");\n lines.push(\"├── question/ # Open questions\");\n lines.push(\"└── INDEX.md # This file\");\n lines.push(\"```\");\n\n return lines.join(\"\\n\") + \"\\n\";\n}\n","/**\n * @remnic/core — Category Directory Map\n *\n * Shared mapping of memory category names to directory names.\n * Single source of truth — import from here instead of copy-pasting.\n */\n\nimport path from \"node:path\";\n\nexport const CATEGORY_DIR_MAP: Record<string, string> = {\n correction: \"corrections\",\n question: \"questions\",\n preference: \"preferences\",\n decision: \"decisions\",\n moment: \"moments\",\n commitment: \"commitments\",\n principle: \"principles\",\n rule: \"rules\",\n skill: \"skills\",\n relationship: \"relationships\",\n procedure: \"procedures\",\n};\n\n/** All directory names derived from CATEGORY_DIR_MAP, plus \"facts\" (the default). */\nexport const ALL_CATEGORY_DIRS: string[] = [\n \"facts\",\n ...Object.values(CATEGORY_DIR_MAP),\n];\n\n/** All category keys (singular form) — used when iterating categories and calling getCategoryDir. */\nexport const ALL_CATEGORY_KEYS: string[] = [\n \"fact\",\n ...Object.keys(CATEGORY_DIR_MAP),\n];\n\n/**\n * Resolve a category name to its directory path under memoryDir.\n * Falls back to `facts/` for unknown categories.\n */\nexport function getCategoryDir(memoryDir: string, category: string): string {\n const dir = CATEGORY_DIR_MAP[category];\n return dir ? path.join(memoryDir, dir) : path.join(memoryDir, \"facts\");\n}\n","/**\n * @remnic/core — Onboarding\n *\n * Detects project language, shape, and documentation to produce\n * an onboarding plan for memory ingestion.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface OnboardOptions {\n /** Directory to scan (defaults to cwd) */\n directory: string;\n /** Max depth to walk (default: 6) */\n maxDepth?: number;\n /** Directories to skip */\n excludeDirs?: string[];\n}\n\nexport interface LanguageInfo {\n /** Language name (e.g. \"TypeScript\", \"Python\") */\n language: string;\n /** Confidence in detection (0-1) */\n confidence: number;\n /** Evidence (e.g. [\"package.json\", \"tsconfig.json\", \"*.ts files\"]) */\n evidence: string[];\n}\n\nexport interface DocFile {\n /** Absolute path */\n path: string;\n /** Relative path from project root */\n relativePath: string;\n /** Estimated type */\n kind: \"readme\" | \"changelog\" | \"contributing\" | \"license\" | \"config\" | \"docs\" | \"other\";\n /** File size in bytes */\n size: number;\n}\n\nexport type ProjectShape = \"app\" | \"library\" | \"monorepo\" | \"workspace\" | \"script\" | \"unknown\";\n\nexport interface OnboardResult {\n /** Project root */\n directory: string;\n /** Detected languages (sorted by confidence) */\n languages: LanguageInfo[];\n /** Detected project shape */\n shape: ProjectShape;\n /** Shape evidence */\n shapeEvidence: string[];\n /** Discovered documentation files */\n docs: DocFile[];\n /** Total files scanned */\n totalFiles: number;\n /** Duration in ms */\n durationMs: number;\n /** Suggested ingestion plan */\n plan: IngestionPlan;\n}\n\nexport interface IngestionPlan {\n /** Priority files to ingest first */\n priorityFiles: DocFile[];\n /** Estimated total files to ingest */\n estimatedFiles: number;\n /** Recommended categories */\n categories: string[];\n /** Suggested memory namespace */\n suggestedNamespace: string;\n}\n\n// ── Language detection rules ─────────────────────────────────────────────────\n\ninterface LanguageRule {\n language: string;\n extensions: string[];\n manifests: string[];\n configFiles: string[];\n}\n\nconst LANGUAGE_RULES: LanguageRule[] = [\n {\n language: \"TypeScript\",\n extensions: [\".ts\", \".tsx\"],\n manifests: [\"package.json\"],\n configFiles: [\"tsconfig.json\", \"tsup.config.ts\"],\n },\n {\n language: \"JavaScript\",\n extensions: [\".js\", \".jsx\", \".mjs\", \".cjs\"],\n manifests: [\"package.json\"],\n configFiles: [\".eslintrc\", \".prettierrc\"],\n },\n {\n language: \"Python\",\n extensions: [\".py\", \".pyi\"],\n manifests: [\"pyproject.toml\", \"setup.py\", \"setup.cfg\", \"requirements.txt\"],\n configFiles: [\"mypy.ini\", \".flake8\", \"tox.ini\"],\n },\n {\n language: \"Go\",\n extensions: [\".go\"],\n manifests: [\"go.mod\", \"go.sum\"],\n configFiles: [],\n },\n {\n language: \"Rust\",\n extensions: [\".rs\"],\n manifests: [\"Cargo.toml\"],\n configFiles: [],\n },\n {\n language: \"Ruby\",\n extensions: [\".rb\"],\n manifests: [\"Gemfile\", \"*.gemspec\"],\n configFiles: [\".rubocop.yml\"],\n },\n {\n language: \"PHP\",\n extensions: [\".php\"],\n manifests: [\"composer.json\"],\n configFiles: [\"phpcs.xml\"],\n },\n {\n language: \"Java\",\n extensions: [\".java\", \".kt\"],\n manifests: [\"pom.xml\", \"build.gradle\", \"build.gradle.kts\"],\n configFiles: [],\n },\n {\n language: \"Swift\",\n extensions: [\".swift\"],\n manifests: [\"Package.swift\", \"Podfile\"],\n configFiles: [],\n },\n {\n language: \"C#\",\n extensions: [\".cs\"],\n manifests: [\"*.csproj\", \"*.sln\"],\n configFiles: [],\n },\n {\n language: \"Shell\",\n extensions: [\".sh\", \".bash\", \".zsh\"],\n manifests: [],\n configFiles: [],\n },\n {\n language: \"Dart\",\n extensions: [\".dart\"],\n manifests: [\"pubspec.yaml\"],\n configFiles: [],\n },\n {\n language: \"Elixir\",\n extensions: [\".ex\", \".exs\"],\n manifests: [\"mix.exs\"],\n configFiles: [],\n },\n];\n\nconst DEFAULT_EXCLUDE = new Set([\n \"node_modules\",\n \".git\",\n \"vendor\",\n \"__pycache__\",\n \".venv\",\n \"venv\",\n \"dist\",\n \"build\",\n \".next\",\n \".nuxt\",\n \"target\",\n \"coverage\",\n \".engram\",\n]);\n\n// ── Main function ────────────────────────────────────────────────────────────\n\nexport function onboard(options: OnboardOptions): OnboardResult {\n const startTime = Date.now();\n const {\n directory,\n maxDepth = 6,\n excludeDirs = [],\n } = options;\n\n const exclude = new Set([...DEFAULT_EXCLUDE, ...excludeDirs]);\n\n // Collect all files\n const files = walkDir(directory, exclude, maxDepth);\n\n // Detect languages\n const languages = detectLanguages(files, directory);\n\n // Detect shape\n const { shape, evidence: shapeEvidence } = detectShape(files, directory);\n\n // Discover docs\n const docs = discoverDocs(files, directory);\n\n // Build plan\n const plan = buildPlan(languages, shape, docs, directory);\n\n return {\n directory,\n languages,\n shape,\n shapeEvidence,\n docs,\n totalFiles: files.length,\n durationMs: Date.now() - startTime,\n plan,\n };\n}\n\n// ── Walk directory ───────────────────────────────────────────────────────────\n\nfunction walkDir(\n root: string,\n exclude: Set<string>,\n maxDepth: number,\n): string[] {\n const results: string[] = [];\n\n function walk(dir: string, depth: number): void {\n if (depth > maxDepth) return;\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (exclude.has(entry.name)) continue;\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(fullPath, depth + 1);\n } else if (entry.isFile()) {\n results.push(fullPath);\n }\n }\n }\n\n walk(root, 0);\n return results;\n}\n\n// ── Language detection ───────────────────────────────────────────────────────\n\nfunction detectLanguages(files: string[], root: string): LanguageInfo[] {\n const results: LanguageInfo[] = [];\n\n // Count extensions\n const extCounts = new Map<string, number>();\n for (const f of files) {\n const ext = path.extname(f).toLowerCase();\n if (ext) extCounts.set(ext, (extCounts.get(ext) ?? 0) + 1);\n }\n\n // Check manifests at root level\n const rootFiles = new Set(\n files\n .filter((f) => path.dirname(f) === root)\n .map((f) => path.basename(f)),\n );\n\n for (const rule of LANGUAGE_RULES) {\n const evidence: string[] = [];\n let score = 0;\n\n // Extension matches\n let extMatch = 0;\n for (const ext of rule.extensions) {\n const count = extCounts.get(ext) ?? 0;\n if (count > 0) {\n extMatch += count;\n evidence.push(`${ext} files (${count})`);\n }\n }\n score += Math.min(extMatch * 0.05, 0.5);\n\n // Manifest matches\n for (const manifest of rule.manifests) {\n if (manifest.includes(\"*\")) {\n // Glob pattern — e.g. \"*.gemspec\" matches files ending with \".gemspec\"\n const suffix = manifest.replaceAll(/\\*/g, \"\");\n if ([...rootFiles].some((f) => f.endsWith(suffix))) {\n score += 0.2;\n evidence.push(manifest);\n }\n } else if (rootFiles.has(manifest)) {\n score += 0.2;\n evidence.push(manifest);\n }\n }\n\n // Config matches\n for (const cfg of rule.configFiles) {\n if (rootFiles.has(cfg)) {\n score += 0.1;\n evidence.push(cfg);\n }\n }\n\n if (score > 0) {\n results.push({\n language: rule.language,\n confidence: Math.min(score, 1),\n evidence,\n });\n }\n }\n\n return results.sort((a, b) => b.confidence - a.confidence);\n}\n\n// ── Shape detection ──────────────────────────────────────────────────────────\n\nfunction detectShape(\n files: string[],\n root: string,\n): { shape: ProjectShape; evidence: string[] } {\n const rootFiles = new Set(\n files\n .filter((f) => path.dirname(f) === root)\n .map((f) => path.basename(f)),\n );\n\n const rootDirs = new Set<string>();\n try {\n for (const entry of fs.readdirSync(root, { withFileTypes: true })) {\n if (entry.isDirectory()) rootDirs.add(entry.name);\n }\n } catch {\n // ignore\n }\n\n const evidence: string[] = [];\n\n // Monorepo: workspace packages/ or libs/ dirs + root package.json with workspaces\n if (rootFiles.has(\"package.json\")) {\n const pkg = readJsonSafe(path.join(root, \"package.json\"));\n if (pkg?.workspaces) {\n evidence.push(\"package.json has workspaces\");\n return { shape: \"monorepo\", evidence };\n }\n }\n\n if (rootDirs.has(\"packages\") || rootDirs.has(\"libs\")) {\n evidence.push(\"has packages/ or libs/ directory\");\n return { shape: \"monorepo\", evidence };\n }\n\n // Workspace: pnpm-workspace.yaml, Cargo workspace, go.work\n if (rootFiles.has(\"pnpm-workspace.yaml\") || rootFiles.has(\"go.work\")) {\n evidence.push(\"workspace manifest found\");\n return { shape: \"workspace\", evidence };\n }\n\n const cargoToml = readTomlWorkspace(path.join(root, \"Cargo.toml\"));\n if (cargoToml) {\n evidence.push(\"Cargo.toml has workspace\");\n return { shape: \"workspace\", evidence };\n }\n\n // Library: has lib/ or src/index.*, exports in package.json\n if (rootFiles.has(\"package.json\")) {\n const pkg = readJsonSafe(path.join(root, \"package.json\"));\n if (pkg?.exports || pkg?.main) {\n // If it also has \"bin\", it's more of an app\n if (pkg?.bin) {\n evidence.push(\"package.json has bin\");\n return { shape: \"app\", evidence };\n }\n evidence.push(\"package.json has exports/main\");\n return { shape: \"library\", evidence };\n }\n }\n\n // Check for app indicators\n if (\n rootFiles.has(\"Dockerfile\") ||\n rootFiles.has(\"docker-compose.yml\") ||\n rootFiles.has(\"docker-compose.yaml\") ||\n rootDirs.has(\"app\") ||\n rootDirs.has(\"src\") && rootDirs.has(\"public\")\n ) {\n evidence.push(\"app-like structure detected\");\n return { shape: \"app\", evidence };\n }\n\n // Script: few files, no package manifest\n if (files.length <= 5 && !rootFiles.has(\"package.json\") && !rootFiles.has(\"pyproject.toml\")) {\n evidence.push(\"few files, no manifest\");\n return { shape: \"script\", evidence };\n }\n\n return { shape: \"unknown\", evidence: [\"no strong shape signal\"] };\n}\n\n// ── Doc discovery ────────────────────────────────────────────────────────────\n\nfunction discoverDocs(files: string[], root: string): DocFile[] {\n const docs: DocFile[] = [];\n const docPatterns: Array<{ pattern: RegExp; kind: DocFile[\"kind\"] }> = [\n { pattern: /^readme(\\.\\w+)?$/i, kind: \"readme\" },\n { pattern: /^changelog(\\.\\w+)?$/i, kind: \"changelog\" },\n { pattern: /^changes(\\.\\w+)?$/i, kind: \"changelog\" },\n { pattern: /^contributing(\\.\\w+)?$/i, kind: \"contributing\" },\n { pattern: /^code[_-]of[_-]conduct(\\.\\w+)?$/i, kind: \"contributing\" },\n { pattern: /^license(\\.\\w+)?$/i, kind: \"license\" },\n { pattern: /^copying(\\.\\w+)?$/i, kind: \"license\" },\n { pattern: /^\\.env\\.example$/i, kind: \"config\" },\n { pattern: /^\\.editorconfig$/i, kind: \"config\" },\n ];\n\n for (const filePath of files) {\n const basename = path.basename(filePath).toLowerCase();\n const relPath = path.relative(root, filePath);\n let kind: DocFile[\"kind\"] | undefined;\n\n // Check against patterns\n for (const { pattern, kind: k } of docPatterns) {\n if (pattern.test(basename)) {\n kind = k;\n break;\n }\n }\n\n // Check docs/ directories\n if (!kind && isUnderDocsDir(relPath)) {\n kind = \"docs\";\n }\n\n // Check markdown files at root or in docs/\n if (!kind && (basename.endsWith(\".md\") || basename.endsWith(\".mdx\"))) {\n if (path.dirname(relPath) === \".\" || isUnderDocsDir(relPath)) {\n kind = \"docs\";\n }\n }\n\n if (kind) {\n let size = 0;\n try {\n size = fs.statSync(filePath).size;\n } catch {\n // ignore\n }\n docs.push({\n path: filePath,\n relativePath: relPath,\n kind,\n size,\n });\n }\n }\n\n return docs;\n}\n\nfunction isUnderDocsDir(relPath: string): boolean {\n const parts = relPath.split(path.sep);\n return parts[0] === \"docs\" || parts[0] === \"doc\" || parts[0] === \"documentation\";\n}\n\n// ── Plan generation ──────────────────────────────────────────────────────────\n\nfunction buildPlan(\n languages: LanguageInfo[],\n shape: ProjectShape,\n docs: DocFile[],\n _root: string,\n): IngestionPlan {\n // Priority: README, CONTRIBUTING, then other docs\n const priorityOrder: Record<DocFile[\"kind\"], number> = {\n readme: 0,\n contributing: 1,\n changelog: 2,\n license: 3,\n docs: 4,\n config: 5,\n other: 6,\n };\n\n const priorityFiles = [...docs]\n .filter((d) => d.size > 0)\n .sort((a, b) => priorityOrder[a.kind] - priorityOrder[b.kind]);\n\n // Recommended categories based on project shape\n const categories: string[] = [\"fact\", \"preference\", \"decision\", \"principle\"];\n if (shape === \"monorepo\" || shape === \"workspace\") {\n categories.push(\"entity\");\n }\n\n // Namespace suggestion from primary language\n const suggestedNamespace = languages.length > 0\n ? languages[0].language.toLowerCase()\n : \"project\";\n\n return {\n priorityFiles,\n estimatedFiles: docs.filter((d) => d.size > 0).length,\n categories,\n suggestedNamespace,\n };\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction readJsonSafe(filePath: string): Record<string, unknown> | null {\n try {\n return JSON.parse(fs.readFileSync(filePath, \"utf8\"));\n } catch {\n return null;\n }\n}\n\nfunction readTomlWorkspace(filePath: string): boolean {\n try {\n const content = fs.readFileSync(filePath, \"utf8\");\n return content.includes(\"[workspace]\");\n } catch {\n return false;\n }\n}\n","/**\n * @remnic/core — Curation\n *\n * Deliberate ingestion of files into memory with provenance tracking.\n * Supports statement-level extraction, dedup, and contradiction checks.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\nimport { getCategoryDir, ALL_CATEGORY_DIRS } from \"../utils/category-dir.js\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface CurateOptions {\n /** File or directory path to curate */\n targetPath: string;\n /** Memory root directory for writing */\n memoryDir: string;\n /** Source label (e.g. \"manual\", \"docs\", \"onboarding\") */\n source?: string;\n /** Category override (default: auto-detect) */\n category?: string;\n /** Confidence to assign (default: 0.9 for curated items) */\n confidence?: number;\n /** Entity reference to attach */\n entityRef?: string;\n /** Tags to add */\n tags?: string[];\n /** Whether to perform dedup check against existing memories */\n checkDuplicates?: boolean;\n /** Whether to detect contradictions */\n checkContradictions?: boolean;\n /** Whether to write files (default: true). False = dry run */\n write?: boolean;\n}\n\nexport interface CuratedStatement {\n /** Unique ID for this statement */\n id: string;\n /** The extracted statement text */\n content: string;\n /** Category */\n category: string;\n /** Confidence */\n confidence: number;\n /** Provenance info */\n provenance: StatementProvenance;\n /** Hash of content for dedup */\n contentHash: string;\n /** Tags */\n tags: string[];\n /** Entity reference */\n entityRef?: string;\n}\n\nexport interface StatementProvenance {\n /** Source file path */\n sourcePath: string;\n /** Relative path from project root */\n relativePath: string;\n /** Source label */\n source: string;\n /** Line number if extractable (0 = unknown) */\n lineNumber: number;\n /** Timestamp of ingestion */\n ingestedAt: string;\n /** Hash of the source file for diff tracking */\n sourceFileHash: string;\n}\n\nexport interface CurateResult {\n /** Statements extracted */\n statements: CuratedStatement[];\n /** Files processed */\n filesProcessed: number;\n /** Files skipped (empty, binary, etc.) */\n filesSkipped: number;\n /** Duplicate statements found (if checkDuplicates) */\n duplicates: DuplicateResult[];\n /** Contradictions found (if checkContradictions) */\n contradictions: ContradictionResult[];\n /** Memory files written */\n written: string[];\n /** Duration in ms */\n durationMs: number;\n}\n\nexport interface DuplicateResult {\n /** New statement */\n newStatement: CuratedStatement;\n /** Existing memory ID that matches */\n existingId: string;\n /** Similarity score (0-1) */\n similarity: number;\n /** Recommended action */\n action: \"skip\" | \"merge\" | \"keep\";\n}\n\nexport interface ContradictionResult {\n /** New statement */\n newStatement: CuratedStatement;\n /** Conflicting memory ID */\n conflictingId: string;\n /** The conflicting content */\n conflictingContent: string;\n /** Severity */\n severity: \"high\" | \"medium\" | \"low\";\n}\n\n// ── Main function ────────────────────────────────────────────────────────────\n\nexport async function curate(options: CurateOptions): Promise<CurateResult> {\n const startTime = Date.now();\n const {\n targetPath,\n memoryDir,\n source = \"curation\",\n category: categoryOverride,\n confidence = 0.9,\n entityRef,\n tags = [],\n checkDuplicates = true,\n checkContradictions = false,\n write = true,\n } = options;\n\n const statements: CuratedStatement[] = [];\n const written: string[] = [];\n const duplicates: DuplicateResult[] = [];\n const contradictions: ContradictionResult[] = [];\n let filesProcessed = 0;\n let filesSkipped = 0;\n\n // Determine targets\n const targets = resolveTargets(targetPath);\n\n // Load existing memories for dedup/contradiction checks\n const existingMemories = checkDuplicates || checkContradictions\n ? loadExistingMemories(memoryDir)\n : new Map();\n\n // Process each file\n for (const filePath of targets) {\n const content = readFileSafe(filePath);\n if (!content) {\n filesSkipped++;\n continue;\n }\n\n if (isBinary(content)) {\n filesSkipped++;\n continue;\n }\n\n filesProcessed++;\n\n const sourceFileHash = hashContent(content);\n const fileStatements = extractStatements(\n content,\n filePath,\n targetPath,\n source,\n sourceFileHash,\n categoryOverride,\n confidence,\n entityRef,\n tags,\n );\n\n for (const stmt of fileStatements) {\n // Dedup check\n if (checkDuplicates) {\n const dup = findDuplicate(stmt, existingMemories);\n if (dup) {\n duplicates.push(dup);\n if (dup.action === \"skip\") continue;\n }\n }\n\n // Contradiction check\n if (checkContradictions) {\n const contra = findContradiction(stmt, existingMemories);\n if (contra) {\n contradictions.push(contra);\n }\n }\n\n statements.push(stmt);\n\n // Write to memory\n if (write) {\n const writtenPath = writeStatement(stmt, memoryDir);\n if (writtenPath) {\n written.push(writtenPath);\n existingMemories.set(stmt.contentHash, {\n id: stmt.id,\n content: stmt.content,\n category: stmt.category,\n });\n }\n }\n }\n }\n\n return {\n statements,\n filesProcessed,\n filesSkipped,\n duplicates,\n contradictions,\n written,\n durationMs: Date.now() - startTime,\n };\n}\n\n// ── Target resolution ────────────────────────────────────────────────────────\n\nfunction resolveTargets(targetPath: string): string[] {\n const stat = fs.statSync(targetPath);\n if (stat.isFile()) return [targetPath];\n\n // Directory — walk for .md, .txt, .mdx\n const results: string[] = [];\n const extensions = new Set([\".md\", \".txt\", \".mdx\", \".rst\"]);\n\n function walk(dir: string): void {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n if (entry.name !== \"node_modules\" && entry.name !== \".git\") {\n walk(fullPath);\n }\n } else if (extensions.has(path.extname(entry.name).toLowerCase())) {\n results.push(fullPath);\n }\n }\n }\n\n walk(targetPath);\n return results;\n}\n\n// ── Statement extraction ─────────────────────────────────────────────────────\n\nfunction extractStatements(\n content: string,\n filePath: string,\n projectRoot: string,\n source: string,\n sourceFileHash: string,\n categoryOverride: string | undefined,\n confidence: number,\n entityRef: string | undefined,\n tags: string[],\n): CuratedStatement[] {\n const relativePath = path.relative(projectRoot, filePath);\n const statements: CuratedStatement[] = [];\n const now = new Date().toISOString();\n\n // Split content into paragraphs/lines and extract meaningful statements\n const paragraphs = content\n .split(/\\n{2,}/)\n .map((p) => p.trim())\n .filter((p) => p.length > 20 && p.length < 2000);\n\n // Also extract list items\n const listItems = content\n .split(\"\\n\")\n .filter((l) => /^\\s*[-*]\\s+/.test(l))\n .map((l) => l.replace(/^\\s*[-*]\\s+/, \"\").trim())\n .filter((l) => l.length > 10 && l.length < 500);\n\n const allItems = [...paragraphs, ...listItems];\n\n // Deduplicate within file\n const seen = new Set<string>();\n for (const item of allItems) {\n const hash = hashContent(item.toLowerCase());\n if (seen.has(hash)) continue;\n seen.add(hash);\n\n const id = generateId();\n const category = categoryOverride ?? detectCategory(item);\n\n statements.push({\n id,\n content: item,\n category,\n confidence,\n provenance: {\n sourcePath: filePath,\n relativePath,\n source,\n lineNumber: 0,\n ingestedAt: now,\n sourceFileHash,\n },\n contentHash: hash,\n tags: [...tags],\n entityRef,\n });\n }\n\n return statements;\n}\n\n// ── Category detection ───────────────────────────────────────────────────────\n\nfunction detectCategory(text: string): string {\n const lower = text.toLowerCase();\n\n if (/^(always|never|must|should|don't|avoid|ensure)/.test(lower)) return \"principle\";\n if (/^(we|team|project)\\s+(decided|chose|will|use)/.test(lower)) return \"decision\";\n if (/^(i|we)\\s+(prefer|like|want|hate|dislike)/.test(lower)) return \"preference\";\n if (/fix|bug|issue|broken|error/i.test(lower)) return \"correction\";\n if (/\\?.+$/.test(lower.trim())) return \"question\";\n\n return \"fact\";\n}\n\n// ── Dedup ────────────────────────────────────────────────────────────────────\n\ninterface ExistingMemory {\n id: string;\n content: string;\n category: string;\n}\n\nfunction findDuplicate(\n stmt: CuratedStatement,\n existing: Map<string, ExistingMemory>,\n): DuplicateResult | null {\n const stmtLower = stmt.content.toLowerCase();\n\n // Exact hash match\n const exactMatch = existing.get(stmt.contentHash);\n if (exactMatch) {\n return {\n newStatement: stmt,\n existingId: exactMatch.id,\n similarity: 1,\n action: \"skip\",\n };\n }\n\n // Fuzzy match — check substring containment\n for (const [_, mem] of existing) {\n const memLower = mem.content.toLowerCase();\n if (memLower.length > 50 && stmtLower.length > 50) {\n // Check if one contains the other\n if (memLower.includes(stmtLower.slice(0, 40)) || stmtLower.includes(memLower.slice(0, 40))) {\n return {\n newStatement: stmt,\n existingId: mem.id,\n similarity: 0.85,\n action: \"skip\",\n };\n }\n }\n }\n\n return null;\n}\n\n// ── Contradiction detection ──────────────────────────────────────────────────\n\nfunction findContradiction(\n stmt: CuratedStatement,\n existing: Map<string, ExistingMemory>,\n): ContradictionResult | null {\n const negationPatterns = [\n /\\b(not|don't|doesn't|isn't|aren't|won't|can't|never|no)\\b/i,\n ];\n\n const hasNegation = negationPatterns.some((p) => p.test(stmt.content));\n if (!hasNegation) return null;\n\n // Strip negation and look for positive version\n const stripped = stmt.content\n .toLowerCase()\n .replace(/\\b(not|don't|doesn't|isn't|aren't|won't|can't|never|no)\\b/gi, \"\")\n .trim();\n\n if (stripped.length < 20) return null;\n\n for (const [_, mem] of existing) {\n const memLower = mem.content.toLowerCase();\n // If an existing memory affirms what this statement negates\n if (memLower.includes(stripped.slice(0, Math.min(30, stripped.length)))) {\n return {\n newStatement: stmt,\n conflictingId: mem.id,\n conflictingContent: mem.content,\n severity: \"high\",\n };\n }\n }\n\n return null;\n}\n\n// ── Memory loading ───────────────────────────────────────────────────────────\n\nfunction loadExistingMemories(memoryDir: string): Map<string, ExistingMemory> {\n const result = new Map<string, ExistingMemory>();\n if (!fs.existsSync(memoryDir)) return result;\n\n // Walk all known category dirs for existing memories\n const dirs = ALL_CATEGORY_DIRS;\n for (const dir of dirs) {\n const fullDir = path.join(memoryDir, dir);\n if (!fs.existsSync(fullDir)) continue;\n\n walkFiles(fullDir, (filePath) => {\n const content = readFileSafe(filePath);\n if (!content) return;\n\n const fm = parseFrontmatter(content);\n const body = extractBody(content);\n if (!fm?.id || !body) return;\n\n const hash = hashContent(body.toLowerCase());\n result.set(hash, {\n id: fm.id,\n content: body,\n category: fm.category ?? dir.slice(0, -1),\n });\n });\n }\n\n return result;\n}\n\n// ── Writing ──────────────────────────────────────────────────────────────────\n\nfunction writeStatement(stmt: CuratedStatement, memoryDir: string): string | null {\n const now = new Date();\n const dateDir = now.toISOString().split(\"T\")[0];\n const categoryDir = getCategoryDir(memoryDir, stmt.category);\n\n const dir = path.join(categoryDir, dateDir);\n fs.mkdirSync(dir, { recursive: true });\n\n const fileName = `${stmt.category}-${Date.now()}-${stmt.id.slice(0, 8)}.md`;\n const filePath = path.join(dir, fileName);\n\n const frontmatter = [\n \"---\",\n `id: ${stmt.id}`,\n `category: ${stmt.category}`,\n `created: ${stmt.provenance.ingestedAt}`,\n `updated: ${stmt.provenance.ingestedAt}`,\n `confidence: ${stmt.confidence}`,\n `confidenceTier: ${tierFromConfidence(stmt.confidence)}`,\n `source: ${stmt.provenance.source}`,\n `tags: ${JSON.stringify(stmt.tags)}`,\n stmt.entityRef ? `entityRef: ${stmt.entityRef}` : null,\n `provenanceFile: ${stmt.provenance.relativePath}`,\n `provenanceHash: ${stmt.provenance.sourceFileHash}`,\n \"---\",\n ]\n .filter(Boolean)\n .join(\"\\n\");\n\n const body = `${frontmatter}\\n\\n${stmt.content}\\n`;\n\n try {\n fs.writeFileSync(filePath, body);\n return filePath;\n } catch {\n return null;\n }\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction generateId(): string {\n return crypto.randomUUID();\n}\n\nfunction hashContent(content: string): string {\n return crypto.createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16);\n}\n\nfunction tierFromConfidence(confidence: number): string {\n if (confidence >= 0.95) return \"explicit\";\n if (confidence >= 0.8) return \"high\";\n if (confidence >= 0.5) return \"medium\";\n return \"low\";\n}\n\nfunction readFileSafe(filePath: string): string | null {\n try {\n return fs.readFileSync(filePath, \"utf8\");\n } catch {\n return null;\n }\n}\n\nfunction isBinary(content: string): boolean {\n // Simple heuristic: if content has null bytes, it's binary\n for (let i = 0; i < Math.min(content.length, 8000); i++) {\n if (content.charCodeAt(i) === 0) return true;\n }\n return false;\n}\n\ninterface SimpleFrontmatter {\n id?: string;\n category?: string;\n [key: string]: unknown;\n}\n\nfunction parseFrontmatter(content: string): SimpleFrontmatter | null {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!match) return null;\n\n const fm: SimpleFrontmatter = {};\n for (const line of match[1].split(\"\\n\")) {\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n (fm as Record<string, unknown>)[key] = value;\n }\n return fm;\n}\n\nfunction extractBody(content: string): string {\n const match = content.match(/^---\\n[\\s\\S]*?\\n---\\n([\\s\\S]*)/);\n return match ? match[1].trim() : content.trim();\n}\n\nfunction walkFiles(dir: string, callback: (filePath: string) => void): void {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walkFiles(fullPath, callback);\n } else if (entry.name.endsWith(\".md\")) {\n callback(fullPath);\n }\n }\n}\n","/**\n * @remnic/core — Dedup & Contradiction Detection\n *\n * Statement-level deduplication and contradiction detection\n * against existing memories. Can be used standalone or via curation.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\nimport { ALL_CATEGORY_DIRS } from \"../utils/category-dir.js\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface MemoryEntry {\n /** Memory ID */\n id: string;\n /** Content text */\n content: string;\n /** Category */\n category: string;\n /** File path (if known) */\n filePath?: string;\n}\n\nexport interface DedupOptions {\n /** Memory root directory */\n memoryDir: string;\n /** Categories to scan (default: all) */\n categories?: string[];\n /** Similarity threshold for fuzzy matching (0-1, default: 0.85) */\n threshold?: number;\n /** Max memories to load (default: 10000) */\n maxLoad?: number;\n}\n\nexport interface DedupResult {\n /** Total memories scanned */\n scanned: number;\n /** Duplicate pairs found */\n duplicates: DuplicatePair[];\n /** Duration in ms */\n durationMs: number;\n}\n\nexport interface DuplicatePair {\n /** First memory */\n left: MemoryEntry;\n /** Second memory */\n right: MemoryEntry;\n /** Similarity score */\n similarity: number;\n /** Recommended action */\n action: \"merge\" | \"keep_left\" | \"keep_right\";\n}\n\nexport interface ContradictionOptions {\n /** Memory root directory */\n memoryDir: string;\n /** Categories to scan (default: all) */\n categories?: string[];\n /** Max memories to load (default: 10000) */\n maxLoad?: number;\n}\n\nexport interface ContradictionResult {\n /** Total memories scanned */\n scanned: number;\n /** Contradictions found */\n contradictions: ContradictionPair[];\n /** Duration in ms */\n durationMs: number;\n}\n\nexport interface ContradictionPair {\n /** First statement */\n left: MemoryEntry;\n /** Contradicting statement */\n right: MemoryEntry;\n /** Severity */\n severity: \"high\" | \"medium\" | \"low\";\n /** Reason */\n reason: string;\n}\n\n// ── Main functions ───────────────────────────────────────────────────────────\n\nexport function findDuplicates(options: DedupOptions): DedupResult {\n const startTime = Date.now();\n const { memoryDir, threshold = 0.85, maxLoad = 10000 } = options;\n\n const memories = loadMemories(memoryDir, options.categories, maxLoad);\n const duplicates: DuplicatePair[] = [];\n\n // Compare all pairs (O(n^2) but bounded by maxLoad)\n for (let i = 0; i < memories.length; i++) {\n for (let j = i + 1; j < memories.length; j++) {\n const sim = computeSimilarity(memories[i].content, memories[j].content);\n if (sim >= threshold) {\n duplicates.push({\n left: memories[i],\n right: memories[j],\n similarity: sim,\n action: sim >= 0.98 ? \"merge\" : sim >= 0.9 ? \"keep_right\" : \"keep_left\",\n });\n }\n }\n }\n\n return {\n scanned: memories.length,\n duplicates,\n durationMs: Date.now() - startTime,\n };\n}\n\nexport function findContradictions(options: ContradictionOptions): ContradictionResult {\n const startTime = Date.now();\n const { memoryDir, maxLoad = 10000 } = options;\n\n const memories = loadMemories(memoryDir, options.categories, maxLoad);\n const contradictions: ContradictionPair[] = [];\n\n for (let i = 0; i < memories.length; i++) {\n for (let j = i + 1; j < memories.length; j++) {\n const contra = detectContradiction(memories[i], memories[j]);\n if (contra) {\n contradictions.push(contra);\n }\n }\n }\n\n return {\n scanned: memories.length,\n contradictions,\n durationMs: Date.now() - startTime,\n };\n}\n\n// ── Similarity computation ───────────────────────────────────────────────────\n\nfunction computeSimilarity(a: string, b: string): number {\n // Normalize\n const normA = normalize(a);\n const normB = normalize(b);\n\n // Exact match\n if (normA === normB) return 1;\n\n // Hash-based exact match\n if (hashContent(normA) === hashContent(normB)) return 0.99;\n\n // Substring containment\n if (normA.length > 50 && normB.length > 50) {\n if (normA.includes(normB.slice(0, 40)) || normB.includes(normA.slice(0, 40))) {\n return 0.9;\n }\n }\n\n // Word overlap (Jaccard)\n const wordsA = new Set(normA.split(/\\s+/));\n const wordsB = new Set(normB.split(/\\s+/));\n const intersection = new Set([...wordsA].filter((w) => wordsB.has(w)));\n const union = new Set([...wordsA, ...wordsB]);\n\n if (union.size === 0) return 0;\n return intersection.size / union.size;\n}\n\nfunction normalize(text: string): string {\n return text\n .toLowerCase()\n .replace(/[^\\w\\s']/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\n// ── Contradiction detection ──────────────────────────────────────────────────\n\nconst NEGATION_WORDS = new Set([\n \"not\", \"don't\", \"doesn't\", \"isn't\", \"aren't\", \"won't\", \"can't\",\n \"never\", \"no\", \"none\", \"neither\", \"nor\", \"nothing\", \"nowhere\",\n]);\n\nfunction detectContradiction(\n a: MemoryEntry,\n b: MemoryEntry,\n): ContradictionPair | null {\n const normA = normalize(a.content);\n const normB = normalize(b.content);\n\n // Check if one has negation and the other doesn't\n const aHasNegation = containsNegation(normA);\n const bHasNegation = containsNegation(normB);\n\n if (aHasNegation === bHasNegation) return null;\n\n // Strip negation and compare core content\n const strippedA = stripNegation(normA);\n const strippedB = stripNegation(normB);\n\n const sim = computeSimilarity(strippedA, strippedB);\n if (sim < 0.7) return null;\n\n // Check for opposite quantifiers\n const oppQuantifiers = [\n [\"always\", \"never\"],\n [\"all\", \"none\"],\n [\"every\", \"no\"],\n [\"must\", \"must not\"],\n [\"should\", \"should not\"],\n [\"can\", \"cannot\"],\n ];\n\n for (const [pos, neg] of oppQuantifiers) {\n if (\n (a.content.toLowerCase().includes(pos) && b.content.toLowerCase().includes(neg)) ||\n (a.content.toLowerCase().includes(neg) && b.content.toLowerCase().includes(pos))\n ) {\n return {\n left: a,\n right: b,\n severity: \"high\",\n reason: `Opposite quantifiers: \"${pos}\" vs \"${neg}\"`,\n };\n }\n }\n\n return {\n left: a,\n right: b,\n severity: sim >= 0.85 ? \"high\" : \"medium\",\n reason: \"Negated version of similar content\",\n };\n}\n\nfunction containsNegation(text: string): boolean {\n const words = text.split(/\\s+/);\n return words.some((w) => NEGATION_WORDS.has(w));\n}\n\nfunction stripNegation(text: string): string {\n return text\n .replace(/\\b(not|don't|doesn't|isn't|aren't|won't|can't|never|no|none|neither|nor|nothing|nowhere)\\b/gi, \"\")\n .replace(/\\s+/g, \" \")\n .trim();\n}\n\n// ── Memory loading ───────────────────────────────────────────────────────────\n\nfunction loadMemories(\n memoryDir: string,\n categories?: string[],\n maxLoad = 10000,\n): MemoryEntry[] {\n const result: MemoryEntry[] = [];\n const allCategories = categories ?? ALL_CATEGORY_DIRS;\n\n for (const category of allCategories) {\n if (result.length >= maxLoad) break;\n\n const dir = path.join(memoryDir, category);\n if (!fs.existsSync(dir)) continue;\n\n walkMdFiles(dir, (filePath) => {\n if (result.length >= maxLoad) return;\n\n const content = readFileSafe(filePath);\n if (!content) return;\n\n const fm = parseFrontmatter(content);\n const body = extractBody(content);\n if (!fm?.id || !body) return;\n\n result.push({\n id: fm.id as string,\n content: body,\n category: (fm.category as string) ?? category.slice(0, -1),\n filePath,\n });\n });\n }\n\n return result;\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction hashContent(content: string): string {\n return crypto.createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16);\n}\n\nfunction readFileSafe(filePath: string): string | null {\n try {\n return fs.readFileSync(filePath, \"utf8\");\n } catch {\n return null;\n }\n}\n\nfunction parseFrontmatter(content: string): Record<string, unknown> | null {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!match) return null;\n\n const fm: Record<string, unknown> = {};\n for (const line of match[1].split(\"\\n\")) {\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n fm[key] = value;\n }\n return fm;\n}\n\nfunction extractBody(content: string): string {\n const match = content.match(/^---\\n[\\s\\S]*?\\n---\\n([\\s\\S]*)/);\n return match ? match[1].trim() : content.trim();\n}\n\nfunction walkMdFiles(dir: string, callback: (filePath: string) => void): void {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walkMdFiles(fullPath, callback);\n } else if (entry.name.endsWith(\".md\")) {\n callback(fullPath);\n }\n }\n}\n","/**\n * @remnic/core — Review Inbox\n *\n * Manages low-confidence memories and suggestions pending review.\n * Integrates with the existing review-queue system.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { getCategoryDir, ALL_CATEGORY_DIRS } from \"../utils/category-dir.js\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface ReviewItem {\n /** Memory ID */\n id: string;\n /** Content text */\n content: string;\n /** Category */\n category: string;\n /** Confidence score (0-1) */\n confidence: number;\n /** Confidence tier */\n confidenceTier: string;\n /** Source */\n source: string;\n /** File path if available */\n filePath?: string;\n /** Created date */\n created: string;\n /** Reason it's in review */\n reviewReason: \"low_confidence\" | \"suggestion\" | \"contradiction\" | \"duplicate\";\n /** Additional context */\n context?: string;\n}\n\nexport type ReviewAction = \"approve\" | \"dismiss\" | \"flag\";\n\nexport interface ReviewResult {\n /** Item acted upon */\n itemId: string;\n /** Action taken */\n action: ReviewAction;\n /** Updated file path (if modified) */\n updatedPath?: string;\n /** Status message */\n message: string;\n}\n\nexport interface ReviewListResult {\n /** Items pending review */\n items: ReviewItem[];\n /** Total count */\n total: number;\n /** Duration in ms */\n durationMs: number;\n}\n\nexport interface ReviewOptions {\n /** Memory root directory */\n memoryDir: string;\n /** Filter by reason */\n reason?: ReviewItem[\"reviewReason\"];\n /** Max items to return (default: 50) */\n limit?: number;\n /** Include items with confidence below this threshold (default: 0.7) */\n confidenceThreshold?: number;\n}\n\n// ── Main functions ───────────────────────────────────────────────────────────\n\n/**\n * List items pending review.\n */\nexport function listReviewItems(options: ReviewOptions): ReviewListResult {\n const startTime = Date.now();\n const {\n memoryDir,\n reason: filterReason,\n limit = 50,\n confidenceThreshold = 0.7,\n } = options;\n\n const items: ReviewItem[] = [];\n\n // Check suggestions directory\n const suggestionsDir = path.join(memoryDir, \"suggestions\");\n if (fs.existsSync(suggestionsDir)) {\n walkMd(suggestionsDir, (filePath, content) => {\n if (items.length >= limit) return;\n\n const fm = parseFrontmatter(content);\n const body = extractBody(content);\n if (!fm?.id) return;\n\n items.push({\n id: fm.id as string,\n content: body,\n category: (fm.category as string) ?? \"suggestion\",\n confidence: parseConfidence(fm.confidence, 0.5),\n confidenceTier: (fm.confidenceTier as string) ?? \"low\",\n source: (fm.source as string) ?? \"unknown\",\n filePath,\n created: (fm.created as string) ?? new Date().toISOString(),\n reviewReason: \"suggestion\",\n });\n });\n }\n\n // Check review directory\n const reviewDir = path.join(memoryDir, \"review\");\n if (fs.existsSync(reviewDir)) {\n walkMd(reviewDir, (filePath, content) => {\n if (items.length >= limit) return;\n\n const fm = parseFrontmatter(content);\n const body = extractBody(content);\n if (!fm?.id) return;\n\n items.push({\n id: fm.id as string,\n content: body,\n category: (fm.category as string) ?? \"review\",\n confidence: parseConfidence(fm.confidence, 0.5),\n confidenceTier: (fm.confidenceTier as string) ?? \"low\",\n source: (fm.source as string) ?? \"unknown\",\n filePath,\n created: (fm.created as string) ?? new Date().toISOString(),\n reviewReason: (fm.reviewReason as ReviewItem[\"reviewReason\"]) ?? \"low_confidence\",\n context: fm.context as string | undefined,\n });\n });\n }\n\n // Scan all categories for low-confidence items\n const categories = ALL_CATEGORY_DIRS;\n for (const category of categories) {\n if (items.length >= limit) break;\n\n const dir = path.join(memoryDir, category);\n if (!fs.existsSync(dir)) continue;\n\n walkMd(dir, (filePath, content) => {\n if (items.length >= limit) return;\n\n const fm = parseFrontmatter(content);\n const body = extractBody(content);\n if (!fm?.id) return;\n\n const confidence = parseConfidence(fm.confidence, 1);\n if (confidence >= confidenceThreshold) return;\n\n // Skip if already in items\n if (items.some((i) => i.id === fm.id)) return;\n\n items.push({\n id: fm.id as string,\n content: body,\n category: (fm.category as string) ?? category.slice(0, -1),\n confidence,\n confidenceTier: (fm.confidenceTier as string) ?? \"low\",\n source: (fm.source as string) ?? \"unknown\",\n filePath,\n created: (fm.created as string) ?? new Date().toISOString(),\n reviewReason: \"low_confidence\",\n });\n });\n }\n\n // Filter by reason\n const filtered = filterReason\n ? items.filter((i) => i.reviewReason === filterReason)\n : items;\n\n return {\n items: filtered.slice(0, limit),\n total: filtered.length,\n durationMs: Date.now() - startTime,\n };\n}\n\n/**\n * Perform a review action on an item.\n */\nexport function performReview(\n memoryDir: string,\n itemId: string,\n action: ReviewAction,\n): ReviewResult {\n switch (action) {\n case \"approve\":\n return approveItem(memoryDir, itemId);\n case \"dismiss\":\n return dismissItem(memoryDir, itemId);\n case \"flag\":\n return flagItem(memoryDir, itemId);\n }\n}\n\n// ── Actions ──────────────────────────────────────────────────────────────────\n\nfunction approveItem(memoryDir: string, itemId: string): ReviewResult {\n // Find the item in suggestions/ or review/\n const locations = [\"suggestions\", \"review\"];\n for (const loc of locations) {\n const dir = path.join(memoryDir, loc);\n if (!fs.existsSync(dir)) continue;\n\n const found = findFileById(dir, itemId);\n if (!found) continue;\n\n const content = fs.readFileSync(found, \"utf8\");\n const fm = parseFrontmatter(content);\n const body = extractBody(content);\n if (!fm) return { itemId, action: \"approve\", message: \"Could not parse frontmatter\" };\n\n // Promote to facts/ with boosted confidence\n const category = (fm.category as string) ?? \"fact\";\n const targetDir = getCategoryDir(memoryDir, category);\n const dateDir = new Date().toISOString().split(\"T\")[0];\n const outputPath = path.join(targetDir, dateDir, path.basename(found));\n\n // Update confidence in content\n const updatedContent = content\n .replace(/confidence: [\\d.]+/, \"confidence: 0.9\")\n .replace(/confidenceTier: \\w+/, \"confidenceTier: high\");\n\n fs.mkdirSync(path.dirname(outputPath), { recursive: true });\n fs.writeFileSync(outputPath, updatedContent);\n\n // Remove from review\n fs.unlinkSync(found);\n\n return {\n itemId,\n action: \"approve\",\n updatedPath: outputPath,\n message: `Promoted to ${category} with confidence 0.9`,\n };\n }\n\n return { itemId, action: \"approve\", message: \"Item not found\" };\n}\n\nfunction dismissItem(memoryDir: string, itemId: string): ReviewResult {\n const locations = [\"suggestions\", \"review\"];\n for (const loc of locations) {\n const dir = path.join(memoryDir, loc);\n if (!fs.existsSync(dir)) continue;\n\n const found = findFileById(dir, itemId);\n if (found) {\n fs.unlinkSync(found);\n return { itemId, action: \"dismiss\", message: \"Dismissed and removed\" };\n }\n }\n\n return { itemId, action: \"dismiss\", message: \"Item not found\" };\n}\n\nfunction flagItem(memoryDir: string, itemId: string): ReviewResult {\n const locations = [\"suggestions\", \"review\"];\n for (const loc of locations) {\n const dir = path.join(memoryDir, loc);\n if (!fs.existsSync(dir)) continue;\n\n const found = findFileById(dir, itemId);\n if (found) {\n // Add flagged marker to frontmatter\n const content = fs.readFileSync(found, \"utf8\");\n const fixed = content.replace(\n /^(---\\n)/,\n `---\\nflagged: true\\nflaggedAt: ${new Date().toISOString()}\\n`,\n );\n fs.writeFileSync(found, fixed);\n return { itemId, action: \"flag\", message: \"Flagged for further review\" };\n }\n }\n\n return { itemId, action: \"flag\", message: \"Item not found\" };\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction findFileById(dir: string, id: string): string | null {\n const files = walkMdPaths(dir);\n for (const filePath of files) {\n const content = readFileSafe(filePath);\n if (!content) continue;\n const fm = parseFrontmatter(content);\n if (fm?.id === id) return filePath;\n }\n return null;\n}\n\nfunction parseConfidence(value: unknown, fallback: number): number {\n if (typeof value === \"number\") return Number.isFinite(value) ? value : fallback;\n if (typeof value === \"string\") {\n const n = parseFloat(value);\n return Number.isFinite(n) ? n : fallback;\n }\n return fallback;\n}\n\nfunction readFileSafe(filePath: string): string | null {\n try {\n return fs.readFileSync(filePath, \"utf8\");\n } catch {\n return null;\n }\n}\n\nfunction parseFrontmatter(content: string): Record<string, unknown> | null {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!match) return null;\n\n const fm: Record<string, unknown> = {};\n for (const line of match[1].split(\"\\n\")) {\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n fm[key] = value;\n }\n return fm;\n}\n\nfunction extractBody(content: string): string {\n const match = content.match(/^---\\n[\\s\\S]*?\\n---\\n([\\s\\S]*)/);\n return match ? match[1].trim() : content.trim();\n}\n\nfunction walkMd(dir: string, callback: (filePath: string, content: string) => void): void {\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walkMd(fullPath, callback);\n } else if (entry.name.endsWith(\".md\")) {\n const content = readFileSafe(fullPath);\n if (content) callback(fullPath, content);\n }\n }\n}\n\nfunction walkMdPaths(dir: string): string[] {\n const results: string[] = [];\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...walkMdPaths(fullPath));\n } else if (entry.name.endsWith(\".md\")) {\n results.push(fullPath);\n }\n }\n return results;\n}\n","/**\n * @remnic/core — Diff-Aware Sync\n *\n * Watches source files for changes and triggers re-ingestion\n * only for changed content. Uses file hashing to detect changes.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface SyncOptions {\n /** Source directory to watch */\n sourceDir: string;\n /** Memory root directory */\n memoryDir: string;\n /** State file path (stores hashes). Default: memoryDir/.sync-state.json */\n stateFile?: string;\n /** File extensions to watch (default: .md, .txt, .mdx) */\n extensions?: string[];\n /** Directories to exclude */\n excludeDirs?: string[];\n /** Whether to actually write changes (default: true) */\n dryRun?: boolean;\n}\n\nexport interface SyncResult {\n /** Files scanned */\n scanned: number;\n /** Files changed since last sync */\n changed: FileChange[];\n /** Files unchanged */\n unchanged: number;\n /** Files deleted since last sync */\n deleted: string[];\n /** Files newly added */\n added: string[];\n /** Duration in ms */\n durationMs: number;\n /** State file path */\n stateFile: string;\n}\n\nexport interface FileChange {\n /** Absolute file path */\n filePath: string;\n /** Relative path from source root */\n relativePath: string;\n /** Change type */\n type: \"added\" | \"modified\" | \"deleted\";\n /** Current content hash */\n currentHash: string;\n /** Previous content hash (if modified) */\n previousHash?: string;\n /** File size in bytes */\n size: number;\n}\n\nexport interface SyncState {\n /** Map of relative path → content hash */\n fileHashes: Record<string, string>;\n /** Last sync timestamp */\n lastSyncAt: string;\n /** Version of state format */\n version: number;\n}\n\n// ── Constants ────────────────────────────────────────────────────────────────\n\nconst DEFAULT_EXTENSIONS = new Set([\".md\", \".txt\", \".mdx\", \".rst\"]);\nconst DEFAULT_EXCLUDE = new Set([\n \"node_modules\",\n \".git\",\n \"dist\",\n \"build\",\n \".engram\",\n \"coverage\",\n]);\n\n// ── Main function ────────────────────────────────────────────────────────────\n\nexport function syncChanges(options: SyncOptions): SyncResult {\n const startTime = Date.now();\n const {\n sourceDir,\n memoryDir,\n extensions = [...DEFAULT_EXTENSIONS],\n excludeDirs = [],\n dryRun = false,\n } = options;\n\n const extSet = new Set(extensions);\n const excludeSet = new Set([...DEFAULT_EXCLUDE, ...excludeDirs]);\n const stateFilePath = options.stateFile ?? path.join(memoryDir, \".sync-state.json\");\n\n // Load previous state\n const prevState = loadState(stateFilePath);\n\n // Scan current files\n const currentFiles = scanFiles(sourceDir, extSet, excludeSet);\n\n // Compute diffs\n const changes = computeDiff(currentFiles, prevState.fileHashes, sourceDir);\n\n const added = changes.filter((c) => c.type === \"added\").map((c) => c.relativePath);\n const modified = changes.filter((c) => c.type === \"modified\");\n const deleted = changes\n .filter((c) => c.type === \"deleted\")\n .map((c) => c.relativePath);\n\n // Update state (even in dry run, we want to show what would change)\n if (!dryRun) {\n const newState: SyncState = {\n fileHashes: {},\n lastSyncAt: new Date().toISOString(),\n version: 1,\n };\n\n // Build new state from current files\n for (const [relPath, hash] of Object.entries(currentFiles)) {\n newState.fileHashes[relPath] = hash;\n }\n\n // Write state\n fs.mkdirSync(path.dirname(stateFilePath), { recursive: true });\n fs.writeFileSync(stateFilePath, JSON.stringify(newState, null, 2));\n }\n\n return {\n scanned: Object.keys(currentFiles).length,\n changed: changes,\n unchanged:\n Object.keys(currentFiles).length - changes.filter((c) => c.type !== \"deleted\").length,\n deleted,\n added,\n durationMs: Date.now() - startTime,\n stateFile: stateFilePath,\n };\n}\n\n/**\n * Watch for changes and call callback on file changes.\n * Returns a stop function.\n */\nexport function watchForChanges(\n options: SyncOptions,\n onChange: (changes: FileChange[]) => void,\n): { stop: () => void } {\n const { sourceDir, extensions, excludeDirs } = options;\n const extSet = new Set(extensions ?? DEFAULT_EXTENSIONS);\n const excludeSet = new Set([...DEFAULT_EXCLUDE, ...(excludeDirs ?? [])]);\n\n let lastHashes: Record<string, string> = {};\n\n // Initial scan\n const currentFiles = scanFiles(sourceDir, extSet, excludeSet);\n lastHashes = { ...currentFiles };\n\n // Poll interval (FSWatcher doesn't reliably work for all platforms)\n const interval = setInterval(() => {\n const nowFiles = scanFiles(sourceDir, extSet, excludeSet);\n const changes = computeDiff(nowFiles, lastHashes, sourceDir);\n\n if (changes.length > 0) {\n // Update hashes\n for (const change of changes) {\n if (change.type === \"deleted\") {\n delete lastHashes[change.relativePath];\n } else {\n lastHashes[change.relativePath] = change.currentHash;\n }\n }\n onChange(changes);\n }\n }, 5000);\n\n return {\n stop: () => clearInterval(interval),\n };\n}\n\n// ── File scanning ────────────────────────────────────────────────────────────\n\nfunction scanFiles(\n root: string,\n extensions: Set<string>,\n exclude: Set<string>,\n): Record<string, string> {\n const result: Record<string, string> = {};\n\n function walk(dir: string): void {\n let entries: fs.Dirent[];\n try {\n entries = fs.readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n\n for (const entry of entries) {\n if (exclude.has(entry.name)) continue;\n\n const fullPath = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n walk(fullPath);\n } else if (entry.isFile()) {\n const ext = path.extname(entry.name).toLowerCase();\n if (!extensions.has(ext)) continue;\n\n const relPath = path.relative(root, fullPath);\n try {\n const content = fs.readFileSync(fullPath, \"utf8\");\n result[relPath] = hashContent(content);\n } catch {\n // Can't read — skip\n }\n }\n }\n }\n\n walk(root);\n return result;\n}\n\n// ── Diff computation ─────────────────────────────────────────────────────────\n\nfunction computeDiff(\n current: Record<string, string>,\n previous: Record<string, string>,\n sourceDir: string,\n): FileChange[] {\n const changes: FileChange[] = [];\n\n // Find added and modified\n for (const [relPath, hash] of Object.entries(current)) {\n const fullPath = path.join(sourceDir, relPath);\n\n if (!(relPath in previous)) {\n // Added\n let size = 0;\n try {\n size = fs.statSync(fullPath).size;\n } catch {\n // ignore\n }\n changes.push({\n filePath: fullPath,\n relativePath: relPath,\n type: \"added\",\n currentHash: hash,\n size,\n });\n } else if (previous[relPath] !== hash) {\n // Modified\n let size = 0;\n try {\n size = fs.statSync(fullPath).size;\n } catch {\n // ignore\n }\n changes.push({\n filePath: fullPath,\n relativePath: relPath,\n type: \"modified\",\n currentHash: hash,\n previousHash: previous[relPath],\n size,\n });\n }\n }\n\n // Find deleted\n for (const relPath of Object.keys(previous)) {\n if (!(relPath in current)) {\n changes.push({\n filePath: path.join(sourceDir, relPath),\n relativePath: relPath,\n type: \"deleted\",\n currentHash: \"\",\n size: 0,\n });\n }\n }\n\n return changes;\n}\n\n// ── State management ─────────────────────────────────────────────────────────\n\nfunction loadState(stateFilePath: string): SyncState {\n try {\n const raw = fs.readFileSync(stateFilePath, \"utf8\");\n return JSON.parse(raw);\n } catch {\n return {\n fileHashes: {},\n lastSyncAt: new Date(0).toISOString(),\n version: 1,\n };\n }\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction hashContent(content: string): string {\n return crypto.createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16);\n}\n","/**\n * @remnic/core — Connector Manager\n *\n * Metadata-driven registry for host adapters (Codex CLI, Claude Code, Cursor, etc.).\n * Manages connector lifecycle: install, remove, configure, health.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport os from \"node:os\";\nimport { spawnSync } from \"node:child_process\";\nimport { createRequire } from \"node:module\";\nimport { fileURLToPath } from \"node:url\";\n\nimport { generateToken, revokeToken, buildTokenEntry, commitTokenEntry, loadTokenStore, saveTokenStore } from \"../tokens.js\";\nimport { coerceInstallExtension } from \"./coerce.js\";\n\n// Native memory artifact materialization for Codex CLI (#378). Surfaced here\n// so downstream callers can `import { materializeForNamespace } from \"@remnic/core/connectors\"`.\nexport {\n materializeForNamespace,\n ensureSentinel,\n describeMemoriesDir,\n renderMemorySummary,\n renderMemoryMd,\n renderRawMemories,\n renderRolloutSummary,\n validateMemoryMd,\n approximateTokenCount,\n truncateToTokenBudget,\n MATERIALIZE_VERSION,\n SENTINEL_FILE,\n TMP_DIR,\n type MaterializeOptions,\n type MaterializeResult,\n type RolloutSummaryInput,\n type MemoryMdValidation,\n} from \"./codex-materialize.js\";\nexport {\n runCodexMaterialize,\n type RunMaterializeOptions,\n} from \"./codex-materialize-runner.js\";\nexport {\n generateMarketplaceManifest,\n validateMarketplaceManifest,\n checkMarketplaceManifest,\n writeMarketplaceManifest,\n installFromMarketplace,\n MARKETPLACE_SCHEMA_VERSION,\n MARKETPLACE_MANIFEST_FILENAME,\n type MarketplaceManifest,\n type MarketplaceEntry,\n type MarketplaceConfig,\n type MarketplaceInstallType,\n type MarketplaceInstallResult,\n type MarketplaceValidation,\n type MarketplaceLogger,\n} from \"./codex-marketplace.js\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport interface ConnectorManifest {\n /** Unique connector ID (e.g. \"claude-code\", \"codex-cli\") */\n id: string;\n /** Human-readable name */\n name: string;\n /** Version */\n version: string;\n /** Description */\n description: string;\n /** Capabilities */\n capabilities: ConnectorCapability;\n /** Required config fields */\n configSchema?: Record<string, string>;\n /** Whether currently installed */\n installed?: boolean;\n /** Homepage URL */\n homepage?: string;\n /** Author */\n author?: string;\n /** Repository URL */\n repository?: string;\n /** Tags */\n tags?: string[];\n /**\n * Whether this connector requires a bearer token for daemon authentication.\n * When false (the default), installConnector will NOT generate or persist a\n * token entry in tokens.json — credentials are never materialized on disk for\n * connectors that use MCP, embedded, CLI, or SDK transports that don't need\n * token auth. Set to true only for HTTP connectors that actually authenticate\n * requests with a bearer token (e.g. hermes, replit, generic-mcp).\n */\n requiresToken?: boolean;\n}\n\nexport interface ConnectorCapability {\n /** Can observe conversations */\n observe: boolean;\n /** Can recall/query memories */\n recall: boolean;\n /** Can store memories */\n store: boolean;\n /** Can search */\n search: boolean;\n /** Can manage entities */\n entities: boolean;\n /** Supports real-time sync */\n realtimeSync: boolean;\n /** Supports batch operations */\n batch: boolean;\n /** Max memory budget in chars */\n maxBudgetChars?: number;\n /** Connection type */\n connectionType: \"mcp\" | \"http\" | \"cli\" | \"sdk\" | \"embedded\";\n}\n\nexport interface ConnectorInstance {\n /** Connector ID */\n connectorId: string;\n /** Resolved config */\n config: Record<string, unknown>;\n /** Status */\n status: \"installed\" | \"running\" | \"error\" | \"disabled\";\n /** Installed at timestamp */\n installedAt?: string;\n /** Error message if erro */\n error?: string;\n}\n\nexport interface ConnectorRegistry {\n /** Known connectors */\n connectors: ConnectorManifest[];\n /** Registry file path */\n registryPath: string;\n}\n\nexport interface InstallOptions {\n /** Connector ID to install */\n connectorId: string;\n /** Config values */\n config?: Record<string, unknown>;\n /** Memory directory */\n memoryDir?: string;\n /** Whether to force reinstall */\n force?: boolean;\n}\n\nexport interface InstallResult {\n /** Connector ID */\n connectorId: string;\n /** Status */\n status: \"installed\" | \"already_installed\" | \"config_required\" | \"error\";\n /** Config path */\n configPath?: string;\n /** Message */\n message: string;\n}\n\nexport interface RemoveResult {\n /** Connector ID */\n connectorId: string;\n /** Removed config path */\n configPath: string;\n /** Message */\n message: string;\n /** Status: \"removed\" on success, \"error\" if the removal failed partway, \"not_found\" if the connector was not installed, \"skipped\" if removal was aborted (e.g. malformed config). */\n status: \"removed\" | \"error\" | \"not_found\" | \"skipped\";\n /** Machine-readable skip reason (present when status === \"skipped\"). */\n reason?: string;\n}\n\nexport interface DoctorResult {\n /** Connector ID */\n connectorId: string;\n /** Checks */\n checks: DoctorCheck[];\n /** All healthy */\n healthy: boolean;\n}\n\nexport interface DoctorCheck {\n /** Check name */\n name: string;\n /** Passed */\n ok: boolean;\n /** Detail */\n detail: string;\n}\n\n// ── Helpers (Finding 4) ───────────────────────────────────────────────────\n\n// Re-export coerceInstallExtension so existing import sites\n// (`import { coerceInstallExtension } from \"./index.js\"`) keep working without\n// change. The binding comes from the top-level import above.\nexport { coerceInstallExtension };\n\n// ── Built-in connector definitions ─────────────────────────────────────────\n\nconst BUILTIN_CONNECTORS: ConnectorManifest[] = [\n {\n id: \"claude-code\",\n name: \"Claude Code\",\n version: \"1.0.0\",\n description: \"Anthropic's Claude Code CLI — direct memory access via MCP\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: true,\n entities: true,\n realtimeSync: true,\n batch: false,\n maxBudgetChars: 32000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n namespace: \"Optional namespace (default: 'default')\",\n },\n homepage: \"https://claude.ai/code\",\n author: \"Anthropic\",\n tags: [\"official\", \"ai\", \"claude\"],\n requiresToken: true,\n },\n {\n id: \"codex-cli\",\n name: \"Codex CLI\",\n version: \"1.0.0\",\n description: \"OpenAI Codex CLI — memory via MCP tool\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: false,\n entities: false,\n realtimeSync: false,\n batch: true,\n maxBudgetChars: 8000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n namespace: \"Optional namespace\",\n },\n homepage: \"https://openai.com/codex\",\n author: \"OpenAI\",\n tags: [\"official\", \"ai\", \"codex\"],\n requiresToken: true,\n },\n {\n id: \"cursor\",\n name: \"Cursor IDE\",\n version: \"1.0.0\",\n description: \"Cursor IDE — memory via config file + tool calls\",\n capabilities: {\n observe: false,\n recall: true,\n store: false,\n search: true,\n entities: false,\n realtimeSync: false,\n batch: false,\n maxBudgetChars: 32000,\n connectionType: \"embedded\",\n },\n configSchema: {\n memoryDir: \"Path to Remnic memory directory\",\n },\n homepage: \"https://cursor.com\",\n author: \"Cursor Inc.\",\n tags: [\"official\", \"ide\"],\n },\n {\n id: \"cline\",\n name: \"Cline\",\n version: \"1.0.0\",\n description: \"VS Code Cline extension — memory via MCP\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: false,\n entities: false,\n realtimeSync: false,\n batch: true,\n maxBudgetChars: 8000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n namespace: \"Optional namespace\",\n },\n homepage: \"https://github.com/cline/cline\",\n author: \"Cline\",\n tags: [\"community\", \"vscode\"],\n },\n {\n id: \"github-copilot\",\n name: \"GitHub Copilot\",\n version: \"1.0.0\",\n description: \"GitHub Copilot — memory via MCP server\",\n capabilities: {\n observe: false,\n recall: true,\n store: false,\n search: true,\n entities: false,\n realtimeSync: false,\n batch: false,\n maxBudgetChars: 16000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n },\n homepage: \"https://github.com/features/copilot\",\n author: \"GitHub\",\n tags: [\"official\", \"ai\", \"github\"],\n },\n {\n id: \"roo-code\",\n name: \"Roo Code\",\n version: \"1.0.0\",\n description: \"Roo Code — memory via MCP\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: false,\n entities: false,\n realtimeSync: false,\n batch: true,\n maxBudgetChars: 16000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n namespace: \"Optional namespace\",\n },\n homepage: \"https://roocode.com\",\n author: \"Roo Code\",\n tags: [\"community\", \"vscode\"],\n },\n {\n id: \"windsurf\",\n name: \"Windsurf\",\n version: \"1.0.0\",\n description: \"Windsurf IDE — memory via MCP\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: true,\n entities: false,\n realtimeSync: false,\n batch: false,\n maxBudgetChars: 32000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n },\n homepage: \"https://windsurf.com\",\n author: \"Codeium\",\n tags: [\"official\", \"ide\"],\n },\n {\n id: \"amp\",\n name: \"Amp\",\n version: \"1.0.0\",\n description: \"Amp coding agent — memory via MCP\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: true,\n entities: false,\n realtimeSync: false,\n batch: false,\n maxBudgetChars: 32000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n },\n homepage: \"https://ampcode.com\",\n author: \"Sourcegraph\",\n tags: [\"official\", \"ai\"],\n },\n {\n id: \"replit\",\n name: \"Replit Agent\",\n version: \"1.0.0\",\n description: \"Replit Agent — memory via HTTP API (reduced capabilities)\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: false,\n entities: false,\n realtimeSync: false,\n batch: false,\n maxBudgetChars: 8000,\n connectionType: \"http\",\n },\n configSchema: {\n apiUrl: \"URL of the Remnic HTTP API\",\n authToken: \"Bearer token for authentication\",\n },\n homepage: \"https://replit.com\",\n author: \"Replit\",\n tags: [\"official\", \"cloud\"],\n requiresToken: true,\n },\n {\n id: \"generic-mcp\",\n name: \"Generic MCP Client\",\n version: \"1.0.0\",\n description: \"Any MCP-compatible client — connect via standard MCP protocol\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: true,\n entities: true,\n realtimeSync: true,\n batch: true,\n maxBudgetChars: 64000,\n connectionType: \"mcp\",\n },\n configSchema: {\n mcpServerUrl: \"URL of the MCP Remnic server\",\n namespace: \"Optional namespace\",\n authToken: \"Bearer token for authentication\",\n },\n homepage: \"https://github.com/joshuaswarren/remnic\",\n author: \"Remnic\",\n tags: [\"generic\", \"mcp\"],\n requiresToken: true,\n },\n {\n id: \"weclone\",\n name: \"WeClone Avatar\",\n version: \"1.0.0\",\n description:\n \"Memory-aware OpenAI-compatible proxy for deployed WeClone avatars — \" +\n \"injects Remnic recall into chat completions and buffers turns via observe\",\n capabilities: {\n observe: true,\n recall: true,\n store: false,\n search: false,\n entities: false,\n realtimeSync: false,\n batch: false,\n maxBudgetChars: 32000,\n connectionType: \"http\",\n },\n configSchema: {\n wecloneApiUrl:\n \"Base URL of the WeClone OpenAI-compatible API (e.g. http://localhost:8000/v1)\",\n proxyPort: \"Local port where the memory proxy will listen (default 8100)\",\n remnicDaemonUrl:\n \"URL of the Remnic daemon exposing /engram/v1/recall and /engram/v1/observe\",\n sessionStrategy:\n \"Per-caller session mapping strategy: 'caller-id' | 'single'\",\n wecloneModelName: \"Optional fine-tuned model name passed through to WeClone\",\n },\n homepage: \"https://github.com/xming521/weclone\",\n author: \"Remnic\",\n tags: [\"official\", \"ai\", \"weclone\", \"proxy\"],\n requiresToken: true,\n },\n {\n id: \"hermes\",\n name: \"Hermes Agent\",\n version: \"1.0.0\",\n description: \"Hermes Agent MemoryProvider — automatic recall/observe on every turn via Python plugin protocol\",\n capabilities: {\n observe: true,\n recall: true,\n store: true,\n search: true,\n entities: false,\n realtimeSync: true,\n batch: false,\n maxBudgetChars: 32000,\n connectionType: \"http\",\n },\n configSchema: {\n host: \"Remnic daemon host (default: 127.0.0.1)\",\n port: \"Remnic daemon port (default: 4318)\",\n profile: \"Hermes profile name (default: default)\",\n },\n homepage: \"https://github.com/joshuaswarren/remnic/tree/main/packages/plugin-hermes\",\n author: \"Remnic\",\n tags: [\"official\", \"python\", \"hermes\"],\n requiresToken: true,\n },\n];\n\n// ── Registry management ───────────────────────────────────────────────────\n\nconst REGISTRY_DIR_NAME = \".engram-connectors\";\n\nexport function getRegistryPath(): string {\n const configDir = process.env.XDG_CONFIG_HOME\n ? path.join(process.env.XDG_CONFIG_HOME, \"engram\")\n : path.join(process.env.HOME ?? \"~\", \".config\", \"engram\");\n return path.join(configDir, REGISTRY_DIR_NAME, \"registry.json\");\n}\n\nexport function loadRegistry(): ConnectorRegistry {\n const regPath = getRegistryPath();\n\n if (!fs.existsSync(regPath)) {\n // First time — bootstrap with built-in connectors\n const registry: ConnectorRegistry = {\n connectors: BUILTIN_CONNECTORS,\n registryPath: regPath,\n };\n saveRegistry(registry);\n return registry;\n }\n\n const raw = fs.readFileSync(regPath, \"utf8\");\n try {\n const parsed = JSON.parse(raw);\n // Built-ins always take precedence over persisted entries with the same ID.\n // This ensures that upgraded manifests (e.g. newly-added requiresToken: true)\n // are never shadowed by stale registry.json entries from an older version.\n // Only connectors whose IDs are NOT in BUILTIN_CONNECTORS are preserved from\n // the persisted file — those are genuine user-added custom connectors.\n const builtinIds = new Set(BUILTIN_CONNECTORS.map((b) => b.id));\n const customOnly = (parsed.connectors ?? []).filter((c: ConnectorManifest) => !builtinIds.has(c.id));\n const merged = [...BUILTIN_CONNECTORS, ...customOnly];\n return {\n connectors: merged,\n registryPath: regPath,\n };\n } catch {\n const registry: ConnectorRegistry = {\n connectors: BUILTIN_CONNECTORS,\n registryPath: regPath,\n };\n saveRegistry(registry);\n return registry;\n }\n}\n\nexport function saveRegistry(registry: ConnectorRegistry): void {\n const regPath = registry.registryPath;\n fs.mkdirSync(path.dirname(regPath), { recursive: true });\n fs.writeFileSync(regPath, JSON.stringify({ connectors: registry.connectors }, null, 2));\n}\n\n// ── List connectors ────────────────────────────────────────────────────────\n\nexport function listConnectors(): {\n installed: ConnectorInstance[];\n available: ConnectorManifest[];\n} {\n const registry = loadRegistry();\n const connectorsDir = getConnectorsDir();\n const installedIds = new Set<string>();\n\n // Find installed connectors\n if (fs.existsSync(connectorsDir)) {\n for (const entry of fs.readdirSync(connectorsDir)) {\n if (entry.endsWith(\".json\")) {\n try {\n const config = JSON.parse(\n fs.readFileSync(path.join(connectorsDir, entry), \"utf8\"),\n );\n installedIds.add(config.connectorId as string);\n } catch {\n // ignore malformed configs\n }\n }\n }\n }\n\n // Mark installed vs available\n const available: ConnectorManifest[] = registry.connectors.map((manifest) => ({\n ...manifest,\n installed: installedIds.has(manifest.id),\n }));\n\n // Build installed list\n const installed: ConnectorInstance[] = [];\n for (const id of installedIds) {\n const configPath = path.join(connectorsDir, `${id}.json`);\n try {\n const raw = JSON.parse(fs.readFileSync(configPath, \"utf8\")) as Record<string, unknown>;\n // Codex P1 (PRRT_kwDORJXyws56U9U0): strip any legacy `token` field from\n // the returned config so that `remnic connectors list --json` never prints\n // a bearer token — tokens live only in tokens.json. This handles existing\n // on-disk connector.json files written by older Remnic versions without\n // rewriting user files.\n const { token: _redacted, ...config } = raw;\n installed.push({\n connectorId: id,\n config,\n status: \"installed\",\n installedAt: raw.installedAt as string | undefined,\n });\n } catch {\n // ignore\n }\n }\n\n return { installed, available };\n}\n\n// ── Get connector token ────────────────────────────────────────────────────\n// Codex P1 (PRRT_kwDORJXyws56U9U0): tokens are stored exclusively in\n// tokens.json. This helper is the canonical way to retrieve the bearer token\n// for a connector — connector.json never contains it.\n\nexport function getConnectorToken(connectorId: string): string | undefined {\n try {\n return loadTokenStore().tokens.find((t) => t.connector === connectorId)?.token;\n } catch {\n return undefined;\n }\n}\n\n// ── Install connector ───────────────────────────────────────────────────────\n\nexport function installConnector(options: InstallOptions): InstallResult {\n const registry = loadRegistry();\n const manifest = registry.connectors.find((c) => c.id === options.connectorId);\n\n if (!manifest) {\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Unknown connector: ${options.connectorId}`,\n };\n }\n\n // Check if already installed\n const existing = listConnectors().installed.find(\n (c) => c.connectorId === options.connectorId,\n );\n\n if (existing && !options.force) {\n return {\n connectorId: options.connectorId,\n status: \"already_installed\",\n message: \"Already installed. Use --force to reinstall.\",\n };\n }\n\n // Write config\n const configDir = getConnectorsDir();\n fs.mkdirSync(configDir, { recursive: true });\n\n const configPath = path.join(configDir, `${options.connectorId}.json`);\n\n // For the hermes connector, resolve profile/host/port with the following\n // precedence: saved-connector-JSON → explicit options.config → defaults.\n // Reading happens BEFORE we overwrite the connector JSON so that a\n // force-reinstall without re-supplied --config options preserves the\n // previously configured values and writes the new token to the correct\n // Hermes profile rather than resetting to \"default\"/127.0.0.1/4318.\n //\n // Issue C fix: sanitizer calls during options resolution are wrapped in\n // try-catch so that invalid user-supplied values (e.g. --config port=abc)\n // return a clean failed InstallResult instead of throwing.\n let hermesSavedProfile: string | undefined;\n let hermesSavedHost: string | undefined;\n let hermesSavedPort: number | undefined;\n // Resolved values (used both in resolvedConfig and in the YAML update below)\n let hermesResolvedProfile: string | undefined;\n let hermesResolvedHost: string | undefined;\n let hermesResolvedPort: number | undefined;\n if (options.connectorId === \"hermes\") {\n if (fs.existsSync(configPath)) {\n try {\n const prev = JSON.parse(fs.readFileSync(configPath, \"utf8\"));\n // Fix 2: coerce saved values through sanitizers so that CLI-written\n // string ports (\"5555\") are accepted just like number ports (5555).\n // Pass each through its sanitizer and fall back to undefined on error\n // so a corrupt saved value doesn't prevent install from defaulting.\n if (prev?.profile != null) {\n try {\n hermesSavedProfile = sanitizeHermesProfile(String(prev.profile));\n } catch {\n // Invalid saved profile — fall through to default\n }\n }\n if (prev?.host != null) {\n try {\n hermesSavedHost = sanitizeHermesHost(String(prev.host));\n } catch {\n // Invalid saved host — fall through to default\n }\n }\n if (prev?.port != null) {\n try {\n const coercedPort = Number(String(prev.port));\n hermesSavedPort = sanitizeHermesPort(coercedPort);\n } catch {\n // Invalid saved port — fall through to default\n }\n }\n } catch {\n // Could not read existing config — fall through to defaults\n }\n }\n // Use saved/default values here; user-supplied profile/host are validated\n // and applied in the sanitization block below (single point of validation).\n hermesResolvedProfile = hermesSavedProfile ?? \"default\";\n hermesResolvedHost = hermesSavedHost ?? \"127.0.0.1\";\n\n // Issue C: wrap sanitizeHermesPort (and profile/host) in try-catch so\n // that invalid user-supplied values return a clean error result.\n if (options.config?.port !== undefined) {\n try {\n hermesResolvedPort = sanitizeHermesPort(Number(String(options.config.port)));\n } catch (err) {\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Invalid Hermes config: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n if (hermesResolvedPort === undefined) {\n hermesResolvedPort = hermesSavedPort ?? 4318;\n }\n\n // Also validate user-supplied profile and host up-front (Issue C coverage).\n if (options.config?.profile !== undefined) {\n try {\n hermesResolvedProfile = sanitizeHermesProfile(String(options.config.profile));\n } catch (err) {\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Invalid Hermes config: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n if (options.config?.host !== undefined) {\n try {\n hermesResolvedHost = sanitizeHermesHost(String(options.config.host));\n } catch (err) {\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Invalid Hermes config: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n }\n }\n\n // Generate a per-connector auth token so the daemon can authenticate\n // requests from this connector.\n //\n // Security gate (PRRT_kwDORJXyws56U9U0 round 6): tokens are ONLY generated\n // and persisted to tokens.json for connectors that actually require bearer-token\n // auth (manifest.requiresToken === true). MCP, CLI, embedded, and SDK connectors\n // never need a token entry on disk — generating one unconditionally materialized\n // credentials for connectors that have no auth requirement, a security regression.\n //\n // For hermes (Issue B fix): use buildTokenEntry() to generate a candidate\n // token WITHOUT immediately persisting it to tokens.json. We commit the\n // candidate to the store only AFTER upsertHermesConfig succeeds, so that\n // a failed or skipped config.yaml write never leaves the daemon with a\n // revoked token and no valid replacement written.\n //\n // For other connectors that requiresToken: generateToken() is idempotent —\n // it filters the old entry and writes a fresh one atomically, so force-reinstall\n // produces a new token automatically.\n //\n // Token write errors (e.g. read-only HOME with writable XDG_CONFIG_HOME)\n // are non-fatal: we degrade gracefully and proceed with the connector\n // config write rather than aborting the whole install.\n //\n // For non-Hermes connectors that requiresToken: snapshot the FULL token store\n // BEFORE generateToken() so that if the connector JSON write later fails, we\n // can restore the store to its pre-install state (UXJG fix — non-Hermes atomic\n // rollback). Using a full-store snapshot (not a single-entry snapshot) ensures\n // that a partial write of tokens.json during generateToken can be unwound\n // atomically, covering both fresh-install and force-reinstall cases uniformly.\n const nonHermesPriorTokenStore = (options.connectorId !== \"hermes\" && manifest.requiresToken)\n ? loadTokenStore()\n : null;\n\n let tokenEntry: ReturnType<typeof generateToken> | null = null;\n if (options.connectorId === \"hermes\") {\n // Build a candidate token; do NOT save yet (Issue B).\n try {\n tokenEntry = buildTokenEntry(options.connectorId);\n } catch {\n // Non-fatal: fall through with tokenEntry === null.\n }\n } else if (manifest.requiresToken) {\n // Only generate and persist a token entry for connectors that need token auth.\n try {\n tokenEntry = generateToken(options.connectorId);\n } catch {\n // Non-fatal: token store unavailable. Connector config will still be\n // written; user can run `remnic token generate <id>` to create the token.\n //\n // Roll back the snapshot so that a partial write of tokens.json during\n // generateToken (e.g. ENOSPC/EIO mid-write) does not leave other\n // connectors' auth state corrupted. Best-effort: if the restore itself\n // fails there is nothing more we can do here, but the error is swallowed\n // so install continues in the same degraded (tokenEntry === null) path\n // as before (PRRT_kwDORJXyws56UleN fix).\n if (nonHermesPriorTokenStore !== null) {\n try {\n saveTokenStore(nonHermesPriorTokenStore);\n } catch {\n // Best-effort: snapshot restore failed; caller sees degraded install.\n }\n }\n }\n }\n // else: connector does not require token auth — tokenEntry stays null and\n // tokens.json is never touched for this connector.\n\n // Thread 2 (PRRT_kwDORJXyws56VYwM): if the connector requires token auth but\n // generateToken threw (tokenEntry is still null), abort now instead of\n // continuing with a broken install that returns \"success\" without a valid token.\n if (options.connectorId !== \"hermes\" && manifest.requiresToken && tokenEntry === null) {\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message:\n `${manifest.name} install aborted: token generation failed. ` +\n `Run \\`remnic token generate ${options.connectorId}\\` to create the token, then reinstall.`,\n };\n }\n\n // Build config from schema defaults + user overrides.\n // Codex P1 (PRRT_kwDORJXyws56U9U0): tokens MUST NOT be written into\n // connector.json. The authoritative store is tokens.json (0o600). Writing the\n // token here created a second, unredacted copy that `remnic connectors list\n // --json` printed verbatim, leaking live bearer tokens into shell history, CI\n // logs, and telemetry. Callers needing the token for a specific connector\n // must use loadTokenStore() and find the entry by connectorId directly.\n //\n // For hermes, include the resolved profile/host/port so that future\n // force-reinstalls can read them back even if options.config is not supplied.\n //\n // Strip any stray `token` key the caller may have supplied via options.config\n // so it cannot be persisted to disk even on legacy call paths.\n const { token: _callerToken, ...safeUserConfig } = (options.config ?? {}) as Record<string, unknown>;\n const resolvedConfig: Record<string, unknown> = {\n connectorId: options.connectorId,\n installedAt: new Date().toISOString(),\n ...safeUserConfig,\n // For hermes, always overlay the sanitized/coerced resolved values so that\n // the connector JSON always has a numeric port and validated profile/host.\n // This also ensures options.config string values (from --config=port=5555)\n // are replaced with their sanitized numeric equivalents (Fix 2 root cause).\n ...(hermesResolvedProfile !== undefined ? {\n profile: hermesResolvedProfile,\n host: hermesResolvedHost,\n port: hermesResolvedPort,\n } : {}),\n };\n\n // ── Hermes atomic install flow ─────────────────────────────────────────────\n // The Hermes install sequence must be atomic: connector.json must only be\n // written if and only if both the YAML write AND the token-store commit\n // succeed. Partial failures must leave the prior state intact so the daemon\n // keeps working with the old token.\n //\n // Step order (all-or-nothing):\n // a. Generate token candidate (buildTokenEntry, no store write yet).\n // b. Validate profile (fail-fast).\n // c. Write config.yaml via upsertHermesConfig — if skipped (missing dir)\n // or throws, abort with status \"error\". Old token is NOT revoked.\n // d. Commit new token to tokens.json — if this throws, rollback the YAML\n // write (restore prior content or delete new file) and abort.\n // e. Write connector.json only after both (c) and (d) succeed.\n // f. Health check — gated on committed === true && tokenEntry != null.\n //\n // Non-Hermes connectors: connector.json is written immediately (no YAML\n // dependency) and the health check is not performed.\n\n if (options.connectorId === \"hermes\") {\n // hermesResolvedProfile/Host/Port were computed above using the correct\n // precedence (saved JSON → explicit options.config → defaults).\n const rawProfile = hermesResolvedProfile!;\n const hermesHost = hermesResolvedHost!;\n const hermesPort = hermesResolvedPort!;\n\n // (b) Validate profile name — fail-fast before touching any files.\n let hermesProfile: string;\n try {\n hermesProfile = sanitizeHermesProfile(rawProfile);\n } catch (err) {\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Hermes install aborted: ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n\n // Token generation is required for an atomic Hermes install.\n if (!tokenEntry) {\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message:\n \"Hermes install aborted: token store unavailable. \" +\n \"Run `remnic token generate hermes` then reinstall to complete setup.\",\n };\n }\n\n // (c) Write config.yaml. If the profile dir does not exist (skipped) or\n // the write throws, abort WITHOUT committing the token or writing connector.json.\n let yamlResult: HermesConfigResult;\n try {\n yamlResult = upsertHermesConfig({\n profile: hermesProfile,\n host: hermesHost,\n port: hermesPort,\n token: tokenEntry.token,\n });\n } catch (err) {\n // upsertHermesConfig threw — old token preserved, connector.json unchanged.\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Hermes install aborted: config.yaml write failed — ${err instanceof Error ? err.message : String(err)}`,\n };\n }\n\n if (!yamlResult.updated) {\n // Skipped (profile dir missing) — abort so connector.json is NOT written\n // with a token the daemon won't recognize (the profile doesn't exist).\n // Preserves any prior Hermes profile/connector.json untouched.\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Hermes install aborted: ${yamlResult.reason ?? \"config.yaml not written\"}. ` +\n `Create the Hermes profile directory first, then reinstall.`,\n };\n }\n\n // (d) Commit token to tokens.json. If this fails, roll back the YAML write\n // and abort — the old token must remain valid and connector.json must stay\n // unchanged so the daemon keeps working.\n //\n // IMPORTANT (UXJI/UXJT): Snapshot the FULL token store BEFORE calling\n // commitTokenEntry(). A single-entry approach (capturing the return value\n // of commitTokenEntry) is insufficient: if commitTokenEntry throws mid-write\n // (e.g. ENOSPC truncating tokens.json), the assignment never completes and\n // the rollback becomes a no-op, leaving tokens.json potentially corrupt.\n // The full-store snapshot, captured before the write attempt, is guaranteed\n // clean and can be written back atomically by saveTokenStore.\n const priorTokenStore = loadTokenStore();\n let committed = false;\n try {\n commitTokenEntry(tokenEntry);\n committed = true;\n } catch (commitErr) {\n // Roll back the token store: restore the full snapshot so a partial write\n // (e.g. ENOSPC truncating tokens.json mid-write) cannot leave the store\n // corrupt or missing the prior hermes entry.\n let tokensRolledBack = true;\n let tokensRollbackErrMsg = \"\";\n try {\n saveTokenStore(priorTokenStore);\n } catch (tokenRestoreErr) {\n tokensRolledBack = false;\n tokensRollbackErrMsg = tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);\n }\n // Roll back the YAML write: restore prior content (or delete newly-created file).\n let yamlRolledBack = true;\n let yamlRollbackErrMsg = \"\";\n try {\n if (yamlResult.priorContent === null) {\n // File was created new — remove it entirely.\n fs.unlinkSync(yamlResult.configPath);\n } else if (typeof yamlResult.priorContent === \"string\") {\n // File existed before — restore original content.\n writeSecretFileSync(yamlResult.configPath, yamlResult.priorContent);\n }\n } catch (yamlRestoreErr) {\n yamlRolledBack = false;\n yamlRollbackErrMsg = yamlRestoreErr instanceof Error ? yamlRestoreErr.message : String(yamlRestoreErr);\n }\n // Build an error message that accurately reflects which rollbacks succeeded.\n const commitErrMsg = commitErr instanceof Error ? commitErr.message : String(commitErr);\n let message: string;\n if (tokensRolledBack && yamlRolledBack) {\n message =\n `Hermes install failed during token commit — ` +\n `${commitErrMsg}. ` +\n `config.yaml and tokens.json restored to prior state. ` +\n `Resolve the tokens.json access issue, then reinstall.`;\n } else if (!yamlRolledBack && tokensRolledBack) {\n message =\n `Hermes install failed during token commit — ` +\n `${commitErrMsg}. ` +\n `tokens.json restored but config.yaml rollback ALSO failed ` +\n `(${yamlRollbackErrMsg}). ` +\n `Hermes daemon may be in an inconsistent state: config references a stale token. ` +\n `Manually inspect ~/.hermes/profiles/${hermesProfile}/config.yaml and reinstall.`;\n } else if (yamlRolledBack && !tokensRolledBack) {\n message =\n `Hermes install failed during token commit — ` +\n `${commitErrMsg}. ` +\n `config.yaml restored but tokens.json rollback ALSO failed ` +\n `(${tokensRollbackErrMsg}). ` +\n `Hermes daemon may be in an inconsistent state: tokens.json is corrupt or incomplete. ` +\n `Manually inspect ~/.remnic/tokens.json and reinstall.`;\n } else {\n message =\n `Hermes install failed during token commit — ` +\n `${commitErrMsg}. ` +\n `BOTH rollbacks failed: config.yaml rollback failed (${yamlRollbackErrMsg}); ` +\n `tokens.json rollback failed (${tokensRollbackErrMsg}). ` +\n `Hermes daemon is likely in an inconsistent state. ` +\n `Manually inspect ~/.hermes/profiles/${hermesProfile}/config.yaml ` +\n `and ~/.remnic/tokens.json, then reinstall.`;\n }\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message,\n };\n }\n\n // (e) Both YAML write and token commit succeeded — now attempt to write connector.json.\n // If this write fails (e.g. connectors dir is not writable), roll back Phase D (token\n // commit) and Phase C (YAML upsert) so no partial-install state is left behind.\n // We restore the full token store snapshot captured before Phase D so that\n // tokens.json is guaranteed consistent with the rolled-back config.yaml.\n try {\n writeSecretFileSync(configPath, JSON.stringify(resolvedConfig, null, 2));\n } catch (writeErr) {\n // Roll back Phase D: restore the full token store snapshot so tokens.json\n // is consistent with the rolled-back config.yaml.\n let tokenRollbackFailed = false;\n let tokenRollbackMsg = \"token store restored to pre-install snapshot\";\n try {\n saveTokenStore(priorTokenStore);\n } catch (tokenRestoreErr) {\n tokenRollbackFailed = true;\n tokenRollbackMsg = `token rollback failed: ${tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr)}`;\n }\n // Roll back Phase C: restore config.yaml to its prior content.\n let yamlRollbackMsg = \"config.yaml restored\";\n try {\n if (yamlResult.priorContent === null) {\n // File was created new — delete it. Track whether the unlink actually\n // succeeded so we report honestly rather than claiming removal when it\n // silently failed inside the inner catch.\n let unlinkSucceeded = false;\n let unlinkErr: unknown;\n try {\n fs.unlinkSync(yamlResult.configPath);\n unlinkSucceeded = true;\n } catch (err) {\n unlinkErr = err;\n }\n if (unlinkSucceeded) {\n yamlRollbackMsg = \"config.yaml removed (was newly created)\";\n } else {\n const unlinkMsg = unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr);\n yamlRollbackMsg = `config.yaml rollback failed: could not remove newly-created file — ${unlinkMsg}`;\n }\n } else if (typeof yamlResult.priorContent === \"string\") {\n writeSecretFileSync(yamlResult.configPath, yamlResult.priorContent);\n yamlRollbackMsg = \"config.yaml restored to prior content\";\n }\n } catch (yamlRollbackErr) {\n yamlRollbackMsg = `config.yaml rollback failed: ${yamlRollbackErr instanceof Error ? yamlRollbackErr.message : String(yamlRollbackErr)}`;\n }\n const urgentSuffix = tokenRollbackFailed\n ? ` tokens.json may be in an inconsistent state — manually restore hermes token with 'remnic token generate hermes'.`\n : \"\";\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message:\n `Hermes install aborted: connector config write failed — ` +\n `connector directory may not be writable. ` +\n `Rollback: ${tokenRollbackMsg}; ${yamlRollbackMsg}.` +\n `${urgentSuffix} Resolve the permission issue, then reinstall.`,\n };\n }\n\n const notes: string[] = [];\n notes.push(`Updated Hermes config: ${yamlResult.configPath}`);\n\n // Clean up the old profile's remnic: block if the profile changed.\n // Compare resolved config paths (not raw strings) so that case-insensitive\n // filesystems (macOS default) don't treat \"Research\" and \"research\" as\n // different profiles — resolving both would yield the same config.yaml,\n // and removing it would strip the block we just wrote (PRRT_kwDORJXyws56VQ76).\n let oldProfileResolvesToDifferentFile = false;\n if (hermesSavedProfile !== undefined) {\n try {\n oldProfileResolvesToDifferentFile =\n hermesConfigPath(hermesSavedProfile) !== hermesConfigPath(hermesProfile);\n } catch {\n // If either profile fails sanitization the comparison is moot; skip cleanup.\n oldProfileResolvesToDifferentFile = false;\n }\n }\n if (oldProfileResolvesToDifferentFile) {\n try {\n const oldCleanResult = removeHermesConfig({ profile: hermesSavedProfile! });\n if (oldCleanResult.updated) {\n notes.push(`Cleaned stale remnic: block from previous profile: ${oldCleanResult.configPath}`);\n }\n } catch {\n // Non-fatal: if we can't clean the old profile, log a note but don't fail.\n notes.push(`Note: could not clean stale remnic: block from previous profile \"${hermesSavedProfile}\"`);\n }\n }\n\n // (f) Health check — only when the token was actually committed to the store.\n // Without commitment, the daemon won't recognise the token → 401 → 6s sleep\n // → false-negative \"Daemon not reachable\". committed is always true here\n // (we returned early on failure above) but the explicit guard is kept for\n // clarity and future robustness.\n if (committed && tokenEntry) {\n const daemonOk = checkDaemonHealth(hermesHost, hermesPort, tokenEntry.token);\n if (daemonOk) {\n notes.push(\"Daemon health check: OK\");\n } else {\n notes.push(\n `Daemon not reachable at ${hermesHost}:${hermesPort} — start with: remnic daemon start`,\n );\n }\n }\n\n const suffix = notes.length > 0 ? `\\n ${notes.join(\"\\n \")}` : \"\";\n return {\n connectorId: options.connectorId,\n status: \"installed\",\n configPath,\n message: `Installed ${manifest.name} v${manifest.version}${suffix}`,\n };\n }\n\n // ── Non-Hermes connectors: write connector.json ───────────────────────────\n // Write with owner-only permissions because the JSON may embed the\n // connector bearer token. Matches the 0o600 hardening on\n // ~/.remnic/tokens.json so the token is never world-readable via this\n // secondary location.\n\n // Codex CLI: also drop the phase-2 memory extension unless the caller\n // explicitly opted out via `config.installExtension: false`.\n let extensionMessage = \"\";\n // Explicit structured flag for the config-write rollback gate. This MUST\n // stay decoupled from `extensionMessage` because that string embeds the\n // install path — substring-matching on \"skipped\" would misfire whenever\n // the codex home happens to contain the word \"skipped\".\n let extensionInstalled = false;\n // Holds the commit/rollback handle returned by installCodexMemoryExtension().\n // The backup of any prior extension is kept alive until commit() is called.\n let extensionHandle: { commit(): void; rollback(): void } | null = null;\n if (options.connectorId === \"codex-cli\") {\n // Finding 1: coerce string \"false\"/\"true\" from CLI config parsing to a real\n // boolean before the gate check, then persist the coerced value so it is\n // stored as a boolean in the config file.\n const coerced = coerceInstallExtension(resolvedConfig.installExtension);\n if (coerced !== undefined) {\n resolvedConfig.installExtension = coerced;\n }\n const shouldInstall = resolvedConfig.installExtension !== false;\n // Persist the effective installExtension boolean explicitly so that\n // removeConnector's provenance check (Finding 3) can match. When the caller\n // did not pass the key, the default is true — write it so later removal\n // knows Remnic owned the install.\n resolvedConfig.installExtension = shouldInstall;\n // Resolve the Codex home path NOW so we can persist the absolute path\n // into the saved config. This guarantees removeConnector can target the\n // exact same directory later even if $CODEX_HOME is unset or changed.\n const codexHomeOverride =\n typeof resolvedConfig.codexHome === \"string\" && resolvedConfig.codexHome.length > 0\n ? (resolvedConfig.codexHome as string)\n : null;\n const resolvedCodexHome = resolveCodexHome(codexHomeOverride);\n resolvedConfig.codexHome = resolvedCodexHome;\n\n if (shouldInstall) {\n try {\n const extensionSourceOverride =\n typeof resolvedConfig.extensionSourceDir === \"string\" &&\n resolvedConfig.extensionSourceDir.length > 0\n ? (resolvedConfig.extensionSourceDir as string)\n : null;\n const extResult = installCodexMemoryExtension({\n codexHome: resolvedCodexHome,\n sourceDir: extensionSourceOverride,\n });\n extensionMessage = ` (memory extension: ${extResult.remnicExtensionDir})`;\n extensionInstalled = true;\n extensionHandle = extResult;\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : \"unknown error\";\n // Codex P2 (PRRT_kwDORJXyws56Ur_G): generateToken already rotated\n // tokens.json before reaching this point. The extension threw, so no\n // connector.json was written — roll back the token store to the\n // pre-install snapshot so tokens.json and the absent connector.json\n // stay consistent (no orphaned/active token without a matching config).\n //\n // Initialize to false: only set true once saveTokenStore() succeeds.\n // For connectors without requiresToken the rollback block is skipped\n // entirely, so the suffix must remain absent — not \"Token has been\n // rolled back.\" (which would be factually incorrect).\n let extensionErrTokenRolledBack = false;\n let extensionErrTokenRollbackMsg = \"\";\n if (tokenEntry !== null && nonHermesPriorTokenStore !== null) {\n try {\n saveTokenStore(nonHermesPriorTokenStore);\n extensionErrTokenRolledBack = true;\n } catch (tokenRestoreErr) {\n extensionErrTokenRolledBack = false;\n extensionErrTokenRollbackMsg =\n tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);\n }\n }\n // Only include a token-rollback suffix for connectors that have a token\n // to roll back. Non-token connectors (requiresToken !== true) never\n // generated a token entry, so no rollback occurred and the message must\n // not claim otherwise.\n const tokenRollbackSuffix = manifest.requiresToken\n ? extensionErrTokenRolledBack\n ? \" Token has been rolled back.\"\n : ` Token rollback FAILED (${extensionErrTokenRollbackMsg}) — tokens.json may contain an orphaned entry. ` +\n `Manually inspect ~/.remnic/tokens.json and reinstall.`\n : \"\";\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message: `Memory extension install failed — ${errMsg}.${tokenRollbackSuffix} Resolve the issue, then reinstall.`,\n };\n }\n } else {\n extensionMessage = \" (memory extension: skipped via installExtension=false)\";\n }\n }\n\n // ── WeClone: write proxy config to ~/.remnic/connectors/weclone.json ─────\n //\n // The standalone `remnic-weclone-proxy` CLI (see packages/connector-weclone)\n // reads its config from ~/.remnic/connectors/weclone.json by default so the\n // proxy can start without depending on Remnic's XDG-scoped registry layout.\n // Compose and write that file here, BEFORE the registry connector.json is\n // written, so that a failure in either file's write path rolls back cleanly.\n //\n // Precedence for each field: user-supplied via --config → saved prior proxy\n // config (on --force) → manifest defaults. The generated bearer token (if\n // any) is persisted into remnicAuthToken so the proxy can authenticate with\n // the daemon without a second token lookup at runtime.\n let weCloneProxyHandleRollback: (() => void) | null = null;\n if (options.connectorId === \"weclone\") {\n try {\n // Force-reinstall (and any reinstall path) must keep using the exact\n // proxy config path that was persisted on the previous install. If we\n // re-derive from the current env each time, a user whose REMNIC_HOME /\n // ENGRAM_HOME changed between installs would end up with two proxy\n // config files — the old one stays with stale settings + a revoked\n // token, the new one gets the live token, and any running proxy still\n // reading the old file starts failing auth. Read the saved\n // `proxyConfigPath` from the existing registry config first, and only\n // fall back to env-derivation for genuine first-time installs.\n let proxyConfigPath: string | null = null;\n if (existing && fs.existsSync(configPath)) {\n try {\n const savedRegistryConfig = JSON.parse(fs.readFileSync(configPath, \"utf8\")) as Record<string, unknown>;\n if (\n typeof savedRegistryConfig.proxyConfigPath === \"string\" &&\n savedRegistryConfig.proxyConfigPath.length > 0\n ) {\n proxyConfigPath = savedRegistryConfig.proxyConfigPath;\n }\n } catch {\n // Saved registry config unreadable — fall through to env resolution.\n }\n }\n if (proxyConfigPath === null) {\n proxyConfigPath = resolveWeCloneProxyConfigPath();\n }\n const prior = readWeCloneProxyConfigIfExists(proxyConfigPath);\n const proxyConfig = buildWeCloneProxyConfig({\n userConfig: safeUserConfig,\n priorConfig: prior ? safeParseJson(prior) : null,\n authToken: tokenEntry?.token,\n });\n fs.mkdirSync(path.dirname(proxyConfigPath), { recursive: true });\n // Install the rollback closure BEFORE the write starts. `writeSecretFileSync`\n // opens the file in truncate mode, so a mid-write failure (ENOSPC, EPERM)\n // could leave `weclone.json` empty. Creating the rollback now guarantees\n // we can always restore prior content (or delete a newly-created file)\n // even if the write itself throws.\n weCloneProxyHandleRollback = () => {\n try {\n if (prior === null) {\n // File was created (or would have been created) by this install —\n // delete whatever is left behind, if anything.\n if (fs.existsSync(proxyConfigPath)) {\n fs.unlinkSync(proxyConfigPath);\n }\n } else {\n writeSecretFileSync(proxyConfigPath, prior);\n }\n } catch {\n // Best-effort rollback.\n }\n };\n try {\n writeSecretFileSync(\n proxyConfigPath,\n JSON.stringify(proxyConfig, null, 2),\n );\n } catch (writeErr) {\n // Truncate-and-write failed partway through — restore the file (or\n // remove the empty partial) and re-throw so the outer catch drives\n // the structured error response + token rollback.\n try {\n weCloneProxyHandleRollback();\n } catch {\n // Best-effort.\n }\n weCloneProxyHandleRollback = null;\n throw writeErr;\n }\n // Record the proxy-side config path on the registry JSON so operators\n // and `remnic connectors doctor weclone` can locate it later. Persist the\n // effective proxy port so `remnic connectors list` reflects the resolved\n // value rather than whatever (possibly missing) the user supplied.\n resolvedConfig.proxyConfigPath = proxyConfigPath;\n resolvedConfig.proxyPort = proxyConfig.proxyPort;\n resolvedConfig.wecloneApiUrl = proxyConfig.wecloneApiUrl;\n resolvedConfig.remnicDaemonUrl = proxyConfig.remnicDaemonUrl;\n resolvedConfig.sessionStrategy = proxyConfig.sessionStrategy;\n } catch (weCloneErr) {\n // Track token rollback success/failure explicitly so the error message\n // can truthfully report whether tokens.json was restored or is in a\n // potentially-inconsistent state. Mirrors the care taken in the\n // registry-config-write failure handler below.\n let tokenRolledBack = false;\n let tokenRollbackMsg = \"\";\n if (tokenEntry !== null && nonHermesPriorTokenStore !== null) {\n try {\n saveTokenStore(nonHermesPriorTokenStore);\n tokenRolledBack = true;\n } catch (tokenRestoreErr) {\n tokenRolledBack = false;\n tokenRollbackMsg =\n tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);\n }\n }\n const tokenSuffix = manifest.requiresToken && tokenEntry !== null\n ? tokenRolledBack\n ? \" Token has been rolled back.\"\n : ` Token rollback FAILED (${tokenRollbackMsg}) — tokens.json may contain an orphaned entry. ` +\n `Manually inspect ~/.remnic/tokens.json and reinstall.`\n : \"\";\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message:\n `WeClone install aborted: proxy config write failed — ` +\n `${weCloneErr instanceof Error ? weCloneErr.message : String(weCloneErr)}.` +\n `${tokenSuffix} Resolve the write permission issue on ~/.remnic/connectors/, then reinstall.`,\n };\n }\n }\n\n // Finding 5: strip internal/test-only keys that must never be persisted to\n // the config file. These keys are used at install time only (e.g. to inject\n // a synthetic extension source dir in tests) and have no meaning on disk.\n // Denylist — add any future test-only keys here with a comment.\n const INTERNAL_KEYS_DENYLIST = [\n \"extensionSourceDir\", // test-only override for the plugin-codex source path\n ];\n for (const key of INTERNAL_KEYS_DENYLIST) {\n delete resolvedConfig[key];\n }\n\n // Atomic rollback (UXJG / Codex P1): if the JSON write fails (e.g., permission\n // denied on XDG_CONFIG_HOME), generateToken() above already rotated the token in\n // tokens.json. Roll back via the full-store snapshot captured before generateToken\n // so tokens.json and the absent connector.json stay consistent — no stale token\n // lingers without a matching config file. Full-store restore (vs. single-entry\n // restore/revoke) handles partial writes atomically for both fresh-install and\n // force-reinstall paths uniformly.\n //\n // Also roll back any codex-cli memory extension if the config write fails so\n // that no dangling memories_extensions/remnic directory is left with no config\n // provenance for removeConnector to find and clean up later.\n try {\n writeSecretFileSync(configPath, JSON.stringify(resolvedConfig, null, 2));\n } catch (writeErr) {\n // Roll back non-hermes token store if needed. Track success so we can\n // report accurately — unconditionally claiming rollback succeeded when it\n // silently failed would leave operators unable to diagnose inconsistent state.\n //\n // Initialize to false: only set true once saveTokenStore() succeeds.\n // Non-token connectors skip this block entirely, so we must not emit a\n // \"Token has been rolled back.\" suffix for them.\n let configWriteTokenRolledBack = false;\n let configWriteTokenRollbackMsg = \"\";\n if (tokenEntry !== null && nonHermesPriorTokenStore !== null) {\n try {\n saveTokenStore(nonHermesPriorTokenStore);\n configWriteTokenRolledBack = true;\n } catch (tokenRestoreErr) {\n configWriteTokenRolledBack = false;\n configWriteTokenRollbackMsg =\n tokenRestoreErr instanceof Error ? tokenRestoreErr.message : String(tokenRestoreErr);\n }\n }\n // Roll back the codex-cli extension if it was installed.\n // Use extensionHandle.rollback() so that a pre-existing (possibly\n // customised) extension is restored from the backup kept by\n // installCodexMemoryExtension(), rather than unconditionally deleted.\n if (extensionInstalled && extensionHandle !== null) {\n try {\n extensionHandle.rollback();\n } catch {\n // Best-effort rollback: log but don't mask the original write error.\n console.warn(\n \"[remnic/connectors] installConnector: config write failed and extension rollback also failed — \" +\n \"manual cleanup of memories_extensions/remnic may be required.\",\n );\n }\n }\n // Roll back the WeClone proxy config if it was written.\n if (weCloneProxyHandleRollback !== null) {\n try {\n weCloneProxyHandleRollback();\n } catch {\n // Best-effort rollback.\n }\n }\n // Only include a token-rollback suffix for connectors that actually had a\n // token to roll back. Non-token connectors (requiresToken !== true) never\n // generated a token entry. For requiresToken connectors where generateToken\n // threw (tokenEntry === null), no token was written to tokens.json so no\n // rollback occurred — avoid a misleading \"Token rollback FAILED\" message\n // (Thread 1, PRRT_kwDORJXyws56VVnB).\n const configWriteTokenSuffix = manifest.requiresToken && tokenEntry !== null\n ? configWriteTokenRolledBack\n ? \" Token has been rolled back.\"\n : ` Token rollback FAILED (${configWriteTokenRollbackMsg}) — tokens.json may contain an orphaned entry. ` +\n `Manually inspect ~/.remnic/tokens.json and reinstall.`\n : \"\";\n return {\n connectorId: options.connectorId,\n status: \"error\",\n message:\n `${manifest.name} install aborted: connector config write failed — ` +\n `${writeErr instanceof Error ? writeErr.message : String(writeErr)}.` +\n `${configWriteTokenSuffix} Resolve the write permission issue, then reinstall.`,\n };\n }\n\n // Config write succeeded — permanently drop the backup of the prior extension.\n if (extensionInstalled && extensionHandle !== null) {\n extensionHandle.commit();\n }\n\n return {\n connectorId: options.connectorId,\n status: \"installed\",\n configPath,\n message: `Installed ${manifest.name} v${manifest.version}${extensionMessage}`,\n };\n}\n\n// ── Remove connector ───────────────────────────────────────────────────────\n\nexport function removeConnector(connectorId: string): RemoveResult {\n const configDir = getConnectorsDir();\n const configPath = path.join(configDir, `${connectorId}.json`);\n\n // For codex-cli, read the saved config BEFORE touching anything so we have\n // both the persisted codexHome and the installExtension flag available for\n // later use in extension removal (Findings 1, 3, 4, 5).\n let codexHomeOverride: string | null = null;\n let savedInstallExtension: boolean | undefined = undefined;\n // Finding 1: track whether config parsing succeeded. If parsing throws, we\n // cannot trust any metadata and must fail closed (skip extension removal).\n let configParsed = false;\n if (connectorId === \"codex-cli\" && fs.existsSync(configPath)) {\n try {\n const parsed = JSON.parse(fs.readFileSync(configPath, \"utf8\")) as Record<string, unknown>;\n configParsed = true;\n if (typeof parsed.codexHome === \"string\" && parsed.codexHome.length > 0) {\n codexHomeOverride = parsed.codexHome;\n }\n // Finding 4: coerce saved installExtension so string \"false\" still works.\n const coerced = coerceInstallExtension(parsed.installExtension);\n if (coerced !== undefined) {\n savedInstallExtension = coerced;\n }\n } catch {\n // Finding 1: config is malformed — log debug and fail closed.\n // codexHomeOverride and savedInstallExtension remain unset; configParsed\n // stays false so extension removal is skipped below.\n console.debug(\n \"[remnic/connectors] removeConnector: codex-cli.json parse failed — skipping extension removal to avoid touching unverified paths\",\n );\n }\n }\n\n if (!fs.existsSync(configPath)) {\n // Best-effort: revoke any orphan token that may have survived a prior partial\n // cleanup (e.g. connector JSON deleted manually or XDG_CONFIG_HOME change).\n // This prevents a stale bearer token from remaining valid in tokens.json while\n // the connector appears \"not installed\" to the caller.\n // Config file is missing — we have no evidence that this installation ever\n // managed the extension directory, so it is unsafe to remove it (the user\n // may have self-managed it or installed with installExtension=false).\n // Skip removeCodexMemoryExtension entirely in this recovery path.\n let staleTokenRevoked = false;\n try {\n staleTokenRevoked = revokeToken(connectorId);\n } catch {\n // Best-effort: token store may be missing or read-only; do not mask the\n // not_found signal to the caller.\n }\n const message = staleTokenRevoked\n ? `${connectorId} is not installed. Removed stale token entry for ${connectorId}.`\n : \"Not installed\";\n return {\n connectorId,\n configPath,\n status: \"not_found\",\n message,\n };\n }\n\n // Read connector config before deleting it (needed for hermes profile lookup)\n let storedProfile = \"default\";\n if (connectorId === \"hermes\") {\n try {\n const stored = JSON.parse(fs.readFileSync(configPath, \"utf8\"));\n if (typeof stored?.profile === \"string\") storedProfile = stored.profile;\n } catch {\n // use default profile\n }\n }\n\n // For weclone, read the persisted proxy config path from the saved registry\n // config BEFORE deleting it. Using the persisted absolute path (rather than\n // recomputing from current REMNIC_HOME / ENGRAM_HOME / $HOME) guarantees\n // that a remove still targets the original file even if the environment\n // has changed between install and remove.\n //\n // Parse failure handling: if the registry config exists but is malformed,\n // we MUST abort the whole removal (mirror of the codex-cli provenance\n // gate). Silently falling back to an env-derived path would delete the\n // registry entry first and then miss the real proxy config if the\n // environment had since changed, orphaning the file (which may still hold\n // a live bearer token). Only install-time WRITES persist the path; if we\n // lost it on read, the only safe action is to stop and let the operator\n // fix the config or clean up manually.\n let weCloneProxyConfigPath: string | null = null;\n let weCloneRegistryParseFailed = false;\n if (connectorId === \"weclone\") {\n try {\n const stored = JSON.parse(fs.readFileSync(configPath, \"utf8\")) as Record<string, unknown>;\n if (typeof stored.proxyConfigPath === \"string\" && stored.proxyConfigPath.length > 0) {\n weCloneProxyConfigPath = stored.proxyConfigPath;\n }\n } catch {\n weCloneRegistryParseFailed = true;\n }\n // No persisted path AND parse succeeded means this is a legacy install\n // pre-dating proxyConfigPath provenance. Fall back to env resolution\n // only in that specific case so we still make a best-effort cleanup.\n if (weCloneProxyConfigPath === null && !weCloneRegistryParseFailed) {\n try {\n weCloneProxyConfigPath = resolveWeCloneProxyConfigPath();\n } catch {\n // Resolution failed (e.g. no HOME) — leave null; cleanup block skips.\n }\n }\n }\n if (connectorId === \"weclone\" && weCloneRegistryParseFailed) {\n console.warn(\n \"[remnic/connectors] removeConnector: weclone.json is malformed — \" +\n \"aborting removal to preserve provenance. Fix or delete \" +\n configPath +\n \" manually and retry.\",\n );\n return {\n connectorId,\n configPath,\n message:\n \"Removal aborted: weclone.json is malformed. Registry config left in place for inspection; \" +\n \"proxy config NOT removed.\",\n status: \"skipped\",\n reason: \"config-parse-failed\",\n };\n }\n\n // Finding 4: if the codex-cli config exists but failed to parse, abort the\n // entire removal. Leave both the config file AND the extension directory\n // untouched so the operator can inspect/fix the config file and retry.\n // Unlinking the config here would destroy the only provenance record and make\n // deterministic retry impossible.\n if (connectorId === \"codex-cli\" && fs.existsSync(configPath) && !configParsed) {\n console.warn(\n \"[remnic/connectors] removeConnector: codex-cli.json is malformed — \" +\n \"aborting removal to preserve provenance. Fix or delete \" +\n configPath +\n \" manually and retry.\",\n );\n return {\n connectorId,\n configPath,\n message: \"Removal aborted: codex-cli.json is malformed. Config file left in place for inspection.\",\n status: \"skipped\",\n reason: \"config-parse-failed\",\n };\n }\n\n // Finding 5: remove extension BEFORE deleting the config file. If extension\n // removal throws (e.g. EPERM/EBUSY), we re-throw WITHOUT deleting the config\n // so the user can retry — the config still has the persisted codexHome needed\n // to locate the extension directory.\n let extensionMessage = \"\";\n if (connectorId === \"codex-cli\") {\n // Finding 4: skip extension deletion when installExtension was explicitly disabled.\n if (savedInstallExtension === false) {\n extensionMessage = \" (memory extension: skipped — installExtension=false)\";\n // Finding 3: require EXPLICIT provenance (installExtension===true AND a saved\n // codexHome) before removing the extension. Legacy configs that pre-date this\n // feature have no installExtension key, so savedInstallExtension is undefined;\n // without provenance we cannot be sure Remnic ever owned the directory.\n } else if (savedInstallExtension !== true || codexHomeOverride === null) {\n extensionMessage = \" (memory extension: skipped — no install provenance in saved config)\";\n } else {\n const extResult = removeCodexMemoryExtension({ codexHome: codexHomeOverride });\n extensionMessage = extResult.removed\n ? ` (memory extension removed: ${extResult.remnicExtensionDir})`\n : \" (no memory extension present)\";\n }\n }\n\n // Delete the connector config file AFTER extension removal (Finding 5): if\n // extension removal throws, we do not reach here and the config is preserved.\n // Token revocation and YAML cleanup only happen after the file is gone so\n // that a failed unlink (e.g., read-only directory) does not leave a\n // token-less orphan install on disk.\n try {\n fs.unlinkSync(configPath);\n } catch (unlinkErr) {\n const sanitizedErr = unlinkErr instanceof Error ? unlinkErr.message : String(unlinkErr);\n return {\n connectorId,\n configPath,\n status: \"error\",\n message:\n `${connectorId} remove aborted: could not delete connector file (${sanitizedErr}). ` +\n `Token and any connector-specific state were not modified.`,\n };\n }\n\n // File removed — now safe to revoke the auth token.\n // Non-fatal: if the token store is read-only or missing, connector removal\n // should still succeed. Stale tokens will be rejected by the daemon when the\n // token file is later accessible.\n const notes: string[] = [];\n // Track revocation success so downstream error branches (e.g. weclone\n // proxy-delete failure) can accurately report whether the token was\n // cleaned up rather than hardcoding \"Token has been rolled back\".\n let tokenRevoked = true;\n try {\n revokeToken(connectorId);\n } catch (revokeErr) {\n // Surface the failure so callers know the token was not cleaned up.\n // The connector config has already been removed at this point.\n tokenRevoked = false;\n const revokeMsg = revokeErr instanceof Error ? revokeErr.message : String(revokeErr);\n notes.push(`Warning: token revocation failed — ${revokeMsg}. The token for ${connectorId} may still be present in tokens.json.`);\n }\n\n // WeClone-specific: remove the proxy config file at the path persisted in\n // the registry config (read above before the registry file was deleted).\n // Using the persisted absolute path — not a re-derivation from the current\n // environment — is load-bearing: if REMNIC_HOME / ENGRAM_HOME changes (or\n // is unset) between install and remove, recomputing here would leave the\n // original proxy config (with a live bearer token) on disk while reporting\n // success. If the file is present but unlink fails (e.g. EPERM), we MUST\n // surface an error status rather than pretending success — a later retry\n // via `remnic connectors remove weclone` would go down the `not_found`\n // path because the registry config was already unlinked, leaving the\n // proxy config orphaned (potentially with a still-valid token).\n let weCloneProxyDeleteFailed: string | null = null;\n if (connectorId === \"weclone\") {\n if (weCloneProxyConfigPath === null) {\n notes.push(\n \"WeClone proxy config cleanup skipped: no persisted path found in saved config \" +\n \"(likely a legacy install predating proxyConfigPath provenance).\",\n );\n } else {\n // Safety gate: validate the persisted path before unlinking. Because\n // `weCloneProxyConfigPath` is loaded from user-controlled JSON, a\n // malformed or tampered weclone.json could make `removeConnector` delete\n // an arbitrary file. Restrict deletion to paths that are:\n // 1. Absolute (relative paths are CWD-dependent and were never written\n // by the installer).\n // 2. End with the known suffix \"connectors/weclone.json\" — the only\n // filename the installer ever writes, regardless of base directory.\n // If either check fails, skip the unlink and surface an error so the\n // operator can clean up manually. Failing closed is safer than silently\n // deleting an unexpected path.\n const expectedSuffix = path.join(\"connectors\", \"weclone.json\");\n const isSafePath =\n path.isAbsolute(weCloneProxyConfigPath) &&\n weCloneProxyConfigPath.endsWith(expectedSuffix);\n if (!isSafePath) {\n weCloneProxyDeleteFailed =\n `Proxy config path ${JSON.stringify(weCloneProxyConfigPath)} failed safety validation ` +\n `(must be absolute and end with \"${expectedSuffix}\"). ` +\n `Refusing to delete — remove the file manually if it exists.`;\n } else {\n try {\n if (fs.existsSync(weCloneProxyConfigPath)) {\n fs.unlinkSync(weCloneProxyConfigPath);\n notes.push(`Removed WeClone proxy config: ${weCloneProxyConfigPath}`);\n }\n } catch (err) {\n // Hard failure: leaving the file behind with a live token is a\n // security issue. Capture the error so we return status:\"error\".\n weCloneProxyDeleteFailed = err instanceof Error ? err.message : String(err);\n }\n }\n }\n }\n if (weCloneProxyDeleteFailed !== null && weCloneProxyConfigPath !== null) {\n // Report the token-revocation status truthfully. If revocation already\n // failed above, claiming the token was \"cleaned up\" here would mislead\n // the operator into thinking the only action left is deleting the\n // orphan file — when in reality the bearer token is also still live.\n const tokenStatus = tokenRevoked\n ? \"the registry config was deleted and the token was revoked\"\n : \"the registry config was deleted but TOKEN REVOCATION ALSO FAILED — \" +\n \"inspect ~/.remnic/tokens.json and revoke manually\";\n return {\n connectorId,\n configPath,\n status: \"error\",\n message:\n `WeClone remove partially succeeded: ${tokenStatus}, ` +\n `but the proxy config at ${weCloneProxyConfigPath} could not be deleted ` +\n `(${weCloneProxyDeleteFailed}). Manually remove that file — it may still contain ` +\n `a Remnic daemon bearer token.`,\n };\n }\n\n // Hermes-specific: strip the remnic: block from config.yaml.\n // Only attempted after successful file removal so that config.yaml cleanup\n // is consistent with the connector JSON state.\n if (connectorId === \"hermes\") {\n try {\n const yamlResult = removeHermesConfig({ profile: storedProfile });\n if (yamlResult.updated) {\n notes.push(`Removed remnic: block from Hermes config: ${yamlResult.configPath}`);\n } else if (yamlResult.skipped) {\n notes.push(`Hermes config cleanup skipped: ${yamlResult.reason}`);\n }\n } catch (err) {\n notes.push(\n `Hermes config cleanup skipped: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n\n const suffix = notes.length > 0 ? `\\n ${notes.join(\"\\n \")}` : \"\";\n return {\n connectorId,\n configPath,\n status: \"removed\",\n message: `Removed${extensionMessage}${suffix}`,\n };\n}\n\n// ── Hermes config.yaml helpers ─────────────────────────────────────────────────\n\ninterface HermesConfigResult {\n updated: boolean;\n skipped: boolean;\n reason?: string;\n configPath: string;\n /**\n * The exact byte-for-byte content of the config.yaml that existed BEFORE\n * this upsert ran. `null` when the file did not exist (new file was created).\n * `undefined` when the write was skipped (priorContent is irrelevant).\n * Used by installConnector to roll back the YAML write if commitTokenEntry\n * subsequently throws.\n */\n priorContent?: string | null;\n}\n\n/**\n * Validate and sanitize a Hermes profile name.\n *\n * Profile names appear as a path segment under `~/.hermes/profiles/`, so we\n * must reject any value that could traverse outside that directory. Hermes\n * itself restricts profile names to filesystem-safe identifiers; we mirror\n * that convention and additionally require the resolved config path to stay\n * under the profiles root.\n *\n * Throws on invalid input rather than silently normalizing — the caller\n * should surface the error so the user can supply a valid profile.\n */\nfunction sanitizeHermesProfile(profile: string): string {\n if (typeof profile !== \"string\" || profile.length === 0) {\n throw new Error(\"Hermes profile name must be a non-empty string\");\n }\n // Disallow anything that isn't a plain profile identifier. We accept\n // letters, digits, hyphen, underscore, and dot — but reject leading dots\n // (hidden dirs) and any path separator or parent-dir reference.\n if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(profile)) {\n throw new Error(\n `Invalid Hermes profile name: ${JSON.stringify(profile)} — must match [A-Za-z0-9][A-Za-z0-9._-]*`,\n );\n }\n if (profile.includes(\"..\")) {\n throw new Error(`Invalid Hermes profile name: ${JSON.stringify(profile)} — must not contain \"..\"`);\n }\n return profile;\n}\n\nfunction hermesConfigPath(profile: string): string {\n const safeProfile = sanitizeHermesProfile(profile);\n // Use process.env.HOME consistent with getConnectorsDir() and tokens.ts\n // defaultTokensPath(); fall back to os.homedir() for robustness.\n const profilesRoot = path.resolve(process.env.HOME ?? os.homedir(), \".hermes\", \"profiles\");\n const cfgPath = path.resolve(profilesRoot, safeProfile, \"config.yaml\");\n // Defense in depth: ensure the resolved path is still under profilesRoot.\n const rel = path.relative(profilesRoot, cfgPath);\n if (rel.startsWith(\"..\") || path.isAbsolute(rel)) {\n throw new Error(\n `Invalid Hermes profile path: resolved outside ${profilesRoot}`,\n );\n }\n return cfgPath;\n}\n\n/**\n * Validate a Hermes host string before interpolating it into YAML.\n *\n * YAML-injection guard: connector config values come from raw CLI input\n * (`--config host=...`) or config-file JSON, both of which are untrusted.\n * Without validation, a value like `127.0.0.1\"\\n session_key: \"evil`\n * would emit additional YAML keys into the `remnic:` block and silently\n * override Hermes settings.\n *\n * Accepted forms:\n * - Plain IPv4: 127.0.0.1, 10.0.0.5\n * - Plain DNS hostname: localhost, foo.example.com\n * - Bracketed IPv6 literal: [::1], [2001:db8::1]\n *\n * Rejected forms:\n * - host:port combos: 127.0.0.1:4318 (colons not allowed outside brackets)\n * - Unbalanced brackets: [::1\n * - Any whitespace, quotes, or control characters\n *\n * Hermes builds its base URL as `http://{host}:{port}`, so supplying a\n * host that already embeds a port (e.g. \"127.0.0.1:4318\") would produce\n * the double-port URL \"http://127.0.0.1:4318:4318/...\" and fail at runtime\n * even though install reports success. We reject that form here.\n */\nfunction sanitizeHermesHost(host: string): string {\n if (typeof host !== \"string\" || host.length === 0) {\n throw new Error(\"Hermes host must be a non-empty string\");\n }\n if (host.length > 253) {\n throw new Error(`Hermes host too long (max 253 chars): ${JSON.stringify(host.slice(0, 32))}…`);\n }\n\n // Bracketed IPv6 literal: must start with \"[\", end with \"]\", and contain\n // only hex digits and colons inside the brackets.\n if (host.startsWith(\"[\")) {\n if (!host.endsWith(\"]\")) {\n throw new Error(\n `Invalid Hermes host: ${JSON.stringify(host)} — unbalanced brackets in IPv6 literal`,\n );\n }\n const inner = host.slice(1, -1);\n if (inner.length === 0 || !/^[0-9A-Fa-f:]+$/.test(inner)) {\n throw new Error(\n `Invalid Hermes host: ${JSON.stringify(host)} — bracketed IPv6 literal must contain only hex digits and colons`,\n );\n }\n return host;\n }\n\n // Unbracketed value: colons are not allowed (would indicate an embedded port\n // or an unbracketed IPv6 address, both of which must be rejected here).\n if (host.includes(\":\")) {\n throw new Error(\n `Invalid Hermes host: ${JSON.stringify(host)} — host must not include a port; supply the port separately with --config port=<n>`,\n );\n }\n\n // Plain IPv4 or DNS hostname: allow letters, digits, dots, and hyphens only.\n // No whitespace, quotes, or control characters.\n if (!/^[A-Za-z0-9._\\-]+$/.test(host)) {\n throw new Error(\n `Invalid Hermes host: ${JSON.stringify(host)} — must be a plain hostname or IP literal`,\n );\n }\n return host;\n}\n\n/**\n * Validate a Hermes port value. Accepts positive integers in [1, 65535].\n *\n * Rejects non-integer numeric strings (e.g. \"4318.9\") rather than silently\n * truncating them — a fractional port is almost certainly a typo and writing\n * the truncated value to config.yaml would be misleading.\n */\nfunction sanitizeHermesPort(port: number | string): number {\n const numeric = Number(port);\n // Reject NaN, Infinity, -Infinity, and any non-integer (e.g. 4318.9)\n if (!Number.isInteger(numeric)) {\n throw new Error(\n `Invalid Hermes port \"${port}\": must be a positive integer`,\n );\n }\n if (numeric < 1 || numeric > 65535) {\n throw new Error(`Invalid Hermes port: ${JSON.stringify(port)} — must be an integer in [1, 65535]`);\n }\n return numeric;\n}\n\n/**\n * Write a file with owner-only (0o600) permissions.\n *\n * Used for any file that may contain a bearer token. writeFileSync's `mode`\n * option only applies when the file is newly created, so we also chmod\n * afterwards to tighten permissions on pre-existing files. The chmod is\n * best-effort on platforms that don't support POSIX modes.\n */\nfunction writeSecretFileSync(filePath: string, data: string): void {\n fs.writeFileSync(filePath, data, { mode: 0o600 });\n try {\n fs.chmodSync(filePath, 0o600);\n } catch {\n /* best-effort on non-POSIX filesystems */\n }\n}\n\n/**\n * Upsert the `remnic:` block in a Hermes profile config.yaml.\n *\n * Rules:\n * - If the profile directory does not exist, skip with a warning (we do not\n * create arbitrary Hermes state).\n * - If config.yaml does not exist, create it with only the remnic: block.\n * - If config.yaml exists and already contains a `remnic:` block, update the\n * host/port/token lines in-place (line-based, preserves comments elsewhere).\n * - If config.yaml exists with no `remnic:` block, append one.\n * - Idempotent on repeated calls.\n */\nexport function upsertHermesConfig(opts: {\n profile: string;\n host: string;\n port: number;\n token: string;\n}): HermesConfigResult {\n const cfgPath = hermesConfigPath(opts.profile);\n const profileDir = path.dirname(cfgPath);\n\n // YAML-injection guard: validate scalar values before interpolating them\n // into the `remnic:` block. sanitizeHermesHost/Port throw on anything\n // that could break out of the scalar context.\n const safeHost = sanitizeHermesHost(opts.host);\n const safePort = sanitizeHermesPort(opts.port);\n // Token is generated by randomBytes + a fixed alphabetic prefix, so it's\n // already safe for YAML scalar interpolation. We still guard against an\n // unexpectedly malformed token reaching this function.\n if (!/^[A-Za-z0-9_]+$/.test(opts.token)) {\n throw new Error(\"Invalid Hermes token: contains non-alphanumeric characters\");\n }\n\n if (!fs.existsSync(profileDir)) {\n return {\n updated: false,\n skipped: true,\n reason: `Hermes profile directory not found: ${profileDir}`,\n configPath: cfgPath,\n };\n }\n\n const block = [\n \"remnic:\",\n ` host: \"${safeHost}\"`,\n ` port: ${safePort}`,\n ` token: \"${opts.token}\"`,\n ].join(\"\\n\");\n\n if (!fs.existsSync(cfgPath)) {\n // Create with just the remnic block. 0o600 because the file now holds\n // a bearer token — matching the permissions on ~/.remnic/tokens.json.\n writeSecretFileSync(cfgPath, block + \"\\n\");\n // priorContent: null signals \"file was created new\" — rollback means delete.\n return { updated: true, skipped: false, configPath: cfgPath, priorContent: null };\n }\n\n const raw = fs.readFileSync(cfgPath, \"utf8\");\n\n // Check whether there's an existing remnic: block\n const hasRemnicBlock = /^remnic:/m.test(raw);\n\n if (!hasRemnicBlock) {\n // Append the block (preserve existing content)\n const separator = raw.endsWith(\"\\n\") ? \"\\n\" : \"\\n\\n\";\n writeSecretFileSync(cfgPath, raw + separator + block + \"\\n\");\n // priorContent: raw preserves the original file so it can be restored on rollback.\n return { updated: true, skipped: false, configPath: cfgPath, priorContent: raw };\n }\n\n // Update the existing block. Strategy: replace the content of the remnic:\n // section by matching from `^remnic:` to the next top-level key or end-of-file.\n // We rewrite only the host/port/token sub-keys inside the block; other keys\n // under remnic: (e.g. session_key, timeout) are preserved.\n //\n // Trailing-newline handling: split(\"\\n\") on a file that ends with \"\\n\" produces\n // a final empty-string element. If that element is still inside the remnic block\n // when we hit it, it gets pushed to newLines via the else branch — placing a\n // blank line between existing sub-keys and any newly-appended missing sub-keys.\n // We strip the trailing empty element before the loop and re-add a single \"\\n\"\n // at write time, normalising the file to always end with exactly one newline.\n const splitLines = raw.split(\"\\n\");\n // Remove trailing empty element produced by a file that ends with \"\\n\"\n if (splitLines.length > 0 && splitLines[splitLines.length - 1] === \"\") {\n splitLines.pop();\n }\n const lines = splitLines;\n const newLines: string[] = [];\n let inRemnicBlock = false;\n let blockWritten = false;\n\n // Track which sub-keys we've emitted\n const written = { host: false, port: false, token: false };\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n\n if (/^remnic:/.test(line)) {\n inRemnicBlock = true;\n newLines.push(line);\n continue;\n }\n\n if (inRemnicBlock) {\n // A line that starts with a non-space character and is not empty signals\n // the start of the next top-level YAML key — we've left the remnic block.\n if (line.length > 0 && !/^\\s/.test(line)) {\n // Emit any un-written keys before closing the block. Uses the\n // already-validated safeHost/safePort values.\n if (!written.host) newLines.push(` host: \"${safeHost}\"`);\n if (!written.port) newLines.push(` port: ${safePort}`);\n if (!written.token) newLines.push(` token: \"${opts.token}\"`);\n blockWritten = true;\n inRemnicBlock = false;\n newLines.push(line);\n continue;\n }\n\n // Replace host/port/token lines; preserve other sub-keys\n if (/^\\s+host:/.test(line)) {\n newLines.push(` host: \"${safeHost}\"`);\n written.host = true;\n } else if (/^\\s+port:/.test(line)) {\n newLines.push(` port: ${safePort}`);\n written.port = true;\n } else if (/^\\s+token:/.test(line)) {\n newLines.push(` token: \"${opts.token}\"`);\n written.token = true;\n } else {\n newLines.push(line);\n }\n continue;\n }\n\n newLines.push(line);\n }\n\n if (inRemnicBlock && !blockWritten) {\n // File ended while still inside the remnic block\n if (!written.host) newLines.push(` host: \"${safeHost}\"`);\n if (!written.port) newLines.push(` port: ${safePort}`);\n if (!written.token) newLines.push(` token: \"${opts.token}\"`);\n }\n\n // Always write exactly one trailing newline, matching the create and append paths.\n writeSecretFileSync(cfgPath, newLines.join(\"\\n\") + \"\\n\");\n // priorContent: raw is the original file content for rollback if needed.\n return { updated: true, skipped: false, configPath: cfgPath, priorContent: raw };\n}\n\n/**\n * Remove the `remnic:` block from a Hermes profile config.yaml.\n * Idempotent — if the block is absent, returns skipped.\n */\nexport function removeHermesConfig(opts: { profile: string }): HermesConfigResult {\n const cfgPath = hermesConfigPath(opts.profile);\n\n if (!fs.existsSync(cfgPath)) {\n return {\n updated: false,\n skipped: true,\n reason: \"Hermes config.yaml not found\",\n configPath: cfgPath,\n };\n }\n\n const raw = fs.readFileSync(cfgPath, \"utf8\");\n if (!/^remnic:/m.test(raw)) {\n return {\n updated: false,\n skipped: true,\n reason: \"No remnic: block found in config.yaml\",\n configPath: cfgPath,\n };\n }\n\n // Strip the remnic: block and its indented children\n const lines = raw.split(\"\\n\");\n const newLines: string[] = [];\n let inRemnicBlock = false;\n\n for (const line of lines) {\n if (/^remnic:/.test(line)) {\n inRemnicBlock = true;\n continue;\n }\n if (inRemnicBlock) {\n if (line.length > 0 && !/^\\s/.test(line)) {\n inRemnicBlock = false;\n newLines.push(line);\n }\n // else: still in the block — skip the line\n continue;\n }\n newLines.push(line);\n }\n\n // Trim trailing blank lines left behind after the block removal\n while (newLines.length > 0 && newLines[newLines.length - 1]?.trim() === \"\") {\n newLines.pop();\n }\n\n // Use writeSecretFileSync to keep the file at 0o600 even after the token\n // has been removed. The file previously held a bearer token (so it was\n // written with 0o600 originally); preserving that mode prevents a window\n // where a rewrite with default umask temporarily widens permissions.\n writeSecretFileSync(cfgPath, newLines.length > 0 ? newLines.join(\"\\n\") + \"\\n\" : \"\");\n return { updated: true, skipped: false, configPath: cfgPath };\n}\n\n// ── Daemon health check (synchronous, non-fatal) ────────────────────────────\n\n/**\n * Probe exit-code contract (used by checkDaemonHealth):\n * 0 — HTTP 200 (healthy)\n * 2 — HTTP 401 (token cache miss: retry after TTL)\n * 1 — any other HTTP status or network error\n */\nconst HEALTH_EXIT_OK = 0;\nconst HEALTH_EXIT_UNAUTHORIZED = 2;\n\n/**\n * Ping /engram/v1/health synchronously.\n * Returns true if the daemon responds with HTTP 200, false otherwise.\n * Uses child_process.spawnSync to run a one-liner Node script so that the\n * existing synchronous installConnector() flow does not need to become async.\n *\n * Data (host, port, token) are passed via environment variables — NOT\n * interpolated into the script string — to prevent injection from\n * user-supplied config values.\n *\n * /engram/v1/health is protected by bearer auth in the access HTTP server,\n * so the caller must pass the connector token (or the configured server\n * token) or the probe will always return 401 and report the daemon as\n * unreachable even when it is running.\n *\n * 401 handling: the daemon caches valid tokens with a 5-second TTL\n * (getAllValidTokensCached). A freshly-rotated token may not appear in the\n * cache for up to 5 s after rotation. We tolerate a single 401 by sleeping\n * one cache TTL (6000 ms = 5 s TTL + 1 s buffer) and retrying exactly once.\n */\nfunction checkDaemonHealth(host: string, port: number, authToken?: string): boolean {\n try {\n // Validate port: must be an integer in [1, 65535].\n // This guards against user config supplying a non-numeric string.\n const safePort = Math.trunc(Number(port));\n if (!Number.isFinite(safePort) || safePort < 1 || safePort > 65535) {\n return false;\n }\n // Finding 7 fix: Node's http.get({ host }) expects an unbracketed IPv6\n // literal (e.g. \"::1\"), but sanitizeHermesHost permits bracketed form\n // \"[::1]\" (required for URL contexts). Strip the brackets here so that\n // http.get receives the bare address and doesn't fail to connect.\n // IPv4 and hostname strings are unaffected (no brackets to strip).\n const bareHost = host.startsWith(\"[\") && host.endsWith(\"]\")\n ? host.slice(1, -1)\n : host;\n\n // Data (host, port, token) are passed via env vars, never interpolated\n // into the script string, preventing any code-injection from malformed\n // config values.\n // Exit codes: 0 = 200 OK, 2 = 401 Unauthorized, 1 = other error.\n const script = [\n \"const http = require('http');\",\n \"const headers = {};\",\n \"if (process.env.REMNIC_HEALTH_TOKEN) {\",\n \" headers['authorization'] = 'Bearer ' + process.env.REMNIC_HEALTH_TOKEN;\",\n \"}\",\n \"const req = http.get({\",\n \" host: process.env.REMNIC_HEALTH_HOST,\",\n \" port: parseInt(process.env.REMNIC_HEALTH_PORT, 10),\",\n \" path: '/engram/v1/health',\",\n \" headers,\",\n \" timeout: 3000,\",\n \"}, (res) => { process.exit(res.statusCode === 200 ? 0 : res.statusCode === 401 ? 2 : 1); });\",\n \"req.on('error', () => process.exit(1));\",\n \"req.on('timeout', () => { req.destroy(); process.exit(1); });\",\n ].join(\"\\n\");\n const env: NodeJS.ProcessEnv = {\n ...process.env,\n REMNIC_HEALTH_HOST: bareHost,\n REMNIC_HEALTH_PORT: String(safePort),\n };\n if (authToken) {\n env.REMNIC_HEALTH_TOKEN = authToken;\n }\n const spawnOpts = { timeout: 4000, env };\n const result = spawnSync(process.execPath, [\"-e\", script], spawnOpts);\n\n if (result.status === HEALTH_EXIT_OK) {\n return true;\n }\n\n if (result.status === HEALTH_EXIT_UNAUTHORIZED) {\n // The daemon's token cache (5 s TTL) has not yet picked up the freshly\n // rotated token. Sleep one TTL + buffer and retry exactly once.\n console.error(\n \"[remnic/connectors] health probe got 401 — retrying after token cache TTL...\",\n );\n // Synchronous sleep via spawnSync (avoids making the caller async).\n spawnSync(process.execPath, [\"-e\", \"setTimeout(() => {}, 6000)\"], {\n timeout: 7000,\n env: {},\n });\n const retry = spawnSync(process.execPath, [\"-e\", script], spawnOpts);\n return retry.status === HEALTH_EXIT_OK;\n }\n\n return false;\n } catch {\n return false;\n }\n}\n\n// ── Doctor ────────────────────────────────────────────────────────────────────\n\nexport async function doctorConnector(connectorId: string): Promise<DoctorResult> {\n const installed = listConnectors().installed;\n const instance = installed.find((c) => c.connectorId === connectorId);\n\n if (!instance) {\n return {\n connectorId,\n checks: [{ name: \"Installed\", ok: false, detail: \"Not installed\" }],\n healthy: false,\n };\n }\n\n const configPath = path.join(getConnectorsDir(), `${connectorId}.json`);\n const checks: DoctorCheck[] = [];\n\n // Check config exists\n checks.push({\n name: \"Config file\",\n ok: fs.existsSync(configPath),\n detail: configPath,\n });\n\n // Check config is valid JSON\n try {\n const raw = fs.readFileSync(configPath, \"utf8\");\n JSON.parse(raw);\n checks.push({ name: \"Config valid\", ok: true, detail: \"OK\" });\n } catch (e) {\n checks.push({ name: \"Config valid\", ok: false, detail: String(e) });\n }\n\n // Check MCP server reachable (if applicable)\n const mcpUrl = instance.config.mcpServerUrl as string | undefined;\n if (mcpUrl) {\n try {\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), 3000);\n const response = await fetch(mcpUrl, { signal: controller.signal });\n clearTimeout(timeoutId);\n checks.push({ name: \"MCP server\", ok: response.ok, detail: mcpUrl });\n } catch (e) {\n checks.push({\n name: \"MCP server\",\n ok: false,\n detail: `Cannot reach ${mcpUrl}: ${e instanceof Error ? e.message : \"unknown\"}`,\n });\n }\n }\n\n // Check memory dir (if applicable)\n const memoryDir = instance.config.memoryDir as string | undefined;\n if (memoryDir) {\n if (fs.existsSync(memoryDir)) {\n checks.push({ name: \"Memory directory\", ok: true, detail: memoryDir });\n } else {\n checks.push({ name: \"Memory directory\", ok: false, detail: `Not found: ${memoryDir}` });\n }\n }\n\n const healthy = checks.every((c) => c.ok);\n return { connectorId, checks, healthy };\n}\n\n// ── Codex memory extension install ────────────────────────────────────────\n\n/**\n * Name of the Codex memories folder. Matches Codex's\n * `MEMORIES_SUBDIR = \"memories\"`.\n */\nconst CODEX_MEMORIES_SUBDIR = \"memories\";\n\n/**\n * Name of the Codex memory-extensions folder. Matches Codex's\n * `EXTENSIONS_SUBDIR = \"memories_extensions\"`.\n *\n * Codex computes the extensions root as a **sibling** of the memories dir via\n * Rust's `Path::with_file_name(\"memories_extensions\")` — so for the default\n * Codex home the layout is:\n *\n * ~/.codex/memories/\n * ~/.codex/memories_extensions/\n *\n * Extension files live **outside** of `memories/`, never inside it.\n */\nconst CODEX_EXTENSIONS_SUBDIR = \"memories_extensions\";\n\n/** Folder name Remnic installs its extension under. */\nconst REMNIC_EXTENSION_DIR_NAME = \"remnic\";\n\nexport interface CodexMemoryExtensionPaths {\n /** Resolved Codex home directory (e.g. `~/.codex`). */\n codexHome: string;\n /** Resolved Codex memories directory (`<codex_home>/memories`). */\n memoriesDir: string;\n /** Sibling extensions root (`<codex_home>/memories_extensions`). */\n extensionsRoot: string;\n /** The specific Remnic extension directory inside the extensions root. */\n remnicExtensionDir: string;\n}\n\nexport interface InstallCodexMemoryExtensionOptions {\n /** Optional override for `$CODEX_HOME`. Highest priority. */\n codexHome?: string | null;\n /** Optional override for the plugin-codex extension source directory. */\n sourceDir?: string | null;\n}\n\nexport interface InstallCodexMemoryExtensionResult extends CodexMemoryExtensionPaths {\n /** Absolute path to the installed `instructions.md`. */\n instructionsPath: string;\n /** Number of files copied. */\n filesCopied: number;\n /**\n * Commit the install: permanently remove the backup of the prior extension\n * (if one existed). Call this once the config write has succeeded.\n */\n commit(): void;\n /**\n * Roll back the install: restore the prior extension if one existed, or\n * remove the newly-installed directory for a fresh install. Call this when\n * a subsequent step (e.g. config write) has failed.\n */\n rollback(): void;\n}\n\nexport interface RemoveCodexMemoryExtensionOptions {\n codexHome?: string | null;\n}\n\nexport interface RemoveCodexMemoryExtensionResult extends CodexMemoryExtensionPaths {\n /** True if an existing `remnic` extension directory was removed. */\n removed: boolean;\n}\n\n/**\n * Resolve the Codex home directory. Precedence:\n * 1. explicit `override` argument (from config)\n * 2. `$CODEX_HOME` env var\n * 3. `~/.codex`\n */\nexport function resolveCodexHome(override?: string | null): string {\n if (override && typeof override === \"string\" && override.trim().length > 0) {\n return path.resolve(override.trim());\n }\n const envHome = process.env.CODEX_HOME;\n if (envHome && envHome.trim().length > 0) {\n return path.resolve(envHome.trim());\n }\n const home = process.env.HOME ?? process.env.USERPROFILE ?? \"~\";\n // Use path.resolve so the result is always absolute. When both HOME and\n // USERPROFILE are unset we fall back to the literal \"~\" sentinel and\n // path.resolve(\"~\", \".codex\") resolves it against cwd — not ideal, but\n // still an absolute path. A cleaner error can be added later if needed.\n return path.resolve(home, \".codex\");\n}\n\n/**\n * Compute the Codex memories + memory-extensions layout for a given Codex home.\n *\n * The extensions root is computed as a **sibling** of the memories dir by\n * taking `path.dirname(memoriesDir)` and joining `memories_extensions`. This\n * mirrors Rust's `with_file_name(\"memories_extensions\")` semantics used by\n * Codex's `memory_extensions_root()`. Do NOT place the extension inside\n * `<codex_home>/memories/`.\n */\nexport function resolveCodexMemoryExtensionPaths(\n codexHomeOverride?: string | null,\n): CodexMemoryExtensionPaths {\n const codexHome = resolveCodexHome(codexHomeOverride);\n const memoriesDir = path.join(codexHome, CODEX_MEMORIES_SUBDIR);\n // Sibling computation: with_file_name(EXTENSIONS_SUBDIR)\n const extensionsRoot = path.join(path.dirname(memoriesDir), CODEX_EXTENSIONS_SUBDIR);\n const remnicExtensionDir = path.join(extensionsRoot, REMNIC_EXTENSION_DIR_NAME);\n return { codexHome, memoriesDir, extensionsRoot, remnicExtensionDir };\n}\n\n/**\n * Locate the plugin-codex `memories_extensions/remnic/` source directory on\n * disk. Search order:\n * 1. explicit `override`\n * 2. resolve via `@remnic/plugin-codex` package (handles global npm installs)\n * 3. sibling `node_modules/@remnic/plugin-codex` relative to this module\n * 4. walk upward from this file's location (monorepo development)\n * 5. walk upward from `process.cwd()` (monorepo fallback)\n *\n * Returns the absolute path or throws a descriptive error listing all paths\n * searched when none exist.\n */\nexport function locatePluginCodexExtensionSource(override?: string | null): string {\n if (override && typeof override === \"string\" && override.trim().length > 0) {\n const resolved = path.resolve(override.trim());\n if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {\n return resolved;\n }\n throw new Error(`Codex extension source directory not found: ${resolved}`);\n }\n\n const EXTENSION_SUBPATH = path.join(\"memories_extensions\", \"remnic\");\n const WORKSPACE_RELATIVE_PATH = path.join(\n \"packages\",\n \"plugin-codex\",\n \"memories_extensions\",\n \"remnic\",\n );\n\n const searched: string[] = [];\n\n // Primary path: the bundled payload shipped with @remnic/core itself.\n // tsup copies src/connectors/codex/ → dist/connectors/codex/ (see tsup.config.ts\n // onSuccess hook). However, tsup bundles all source into dist/ as flat files\n // (dist/index.js, dist/chunk-*.js), so at runtime import.meta.url points to\n // dist/index.js or a dist/chunk-*.js — NOT dist/connectors/index.js.\n // Therefore we probe two sibling-relative candidates:\n // 1. moduleDir/codex — matches tsx/ts-node on src/connectors/index.ts\n // 2. moduleDir/connectors/codex — matches the tsup dist layout where this code\n // lands in dist/index.js or dist/chunk-*.js\n try {\n const moduleDir = path.dirname(fileURLToPath(import.meta.url));\n\n // Candidate 1: adjacent codex/ (tsx/ts-node from src/connectors/)\n const bundledCandidate = path.join(moduleDir, \"codex\");\n searched.push(bundledCandidate);\n if (fs.existsSync(bundledCandidate) && fs.statSync(bundledCandidate).isDirectory()) {\n return bundledCandidate;\n }\n\n // Candidate 2: dist/connectors/codex/ — the tsup output path.\n // When this module is bundled into dist/index.js or dist/chunk-*.js,\n // moduleDir is dist/ and tsup copies the payload to dist/connectors/codex/.\n const distConnectorsCandidate = path.join(moduleDir, \"connectors\", \"codex\");\n searched.push(distConnectorsCandidate);\n if (\n fs.existsSync(distConnectorsCandidate) &&\n fs.statSync(distConnectorsCandidate).isDirectory()\n ) {\n return distConnectorsCandidate;\n }\n } catch {\n // import.meta.url unavailable — not running as ESM, skip bundled path.\n }\n\n // Finding 2 — path 1: resolve via `@remnic/plugin-codex` package.json.\n // This covers global `npm install -g @remnic/remnic-core` or pnpm global installs\n // where the package lives under the global node_modules tree.\n try {\n const requireFromHere = createRequire(import.meta.url);\n const pluginPkgJsonPath = requireFromHere.resolve(\"@remnic/plugin-codex/package.json\");\n const pluginPkgRoot = path.dirname(pluginPkgJsonPath);\n const candidate = path.join(pluginPkgRoot, EXTENSION_SUBPATH);\n searched.push(candidate);\n if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {\n return candidate;\n }\n } catch {\n // @remnic/plugin-codex not installed — fall through to next strategy.\n }\n\n // Finding 2 — path 2: sibling node_modules under the module's own directory.\n // Handles cases like:\n // .../node_modules/@remnic/remnic-core/src/connectors/index.js\n // .../node_modules/@remnic/plugin-codex/memories_extensions/remnic\n try {\n const moduleDir = path.dirname(fileURLToPath(import.meta.url));\n let dir = moduleDir;\n for (let depth = 0; depth < 8; depth += 1) {\n const candidate = path.join(\n dir,\n \"node_modules\",\n \"@remnic\",\n \"plugin-codex\",\n EXTENSION_SUBPATH,\n );\n searched.push(candidate);\n if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {\n return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n } catch {\n // import.meta.url unavailable — not running as ESM.\n }\n\n // Finding 2 — path 3 & 4: walk upward from this file's location and from\n // process.cwd() looking for the monorepo layout (`packages/plugin-codex/…`).\n const anchors: string[] = [];\n try {\n anchors.push(path.dirname(fileURLToPath(import.meta.url)));\n } catch {\n // Not running under ESM with import.meta — skip.\n }\n anchors.push(process.cwd());\n\n for (const anchor of anchors) {\n let dir = anchor;\n for (let depth = 0; depth < 12; depth += 1) {\n const candidate = path.join(dir, WORKSPACE_RELATIVE_PATH);\n searched.push(candidate);\n if (fs.existsSync(candidate) && fs.statSync(candidate).isDirectory()) {\n return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n }\n\n throw new Error(\n \"Could not locate the plugin-codex memories_extensions/remnic source directory.\\n\" +\n \"Paths searched:\\n\" +\n searched.map((p) => ` - ${p}`).join(\"\\n\") +\n \"\\nInstall @remnic/plugin-codex or pass sourceDir explicitly.\",\n );\n}\n\n/** Recursive synchronous directory copy. */\nfunction copyDirRecursiveSync(src: string, dest: string): number {\n let count = 0;\n fs.mkdirSync(dest, { recursive: true });\n const entries = fs.readdirSync(src, { withFileTypes: true });\n for (const entry of entries) {\n const from = path.join(src, entry.name);\n const to = path.join(dest, entry.name);\n if (entry.isDirectory()) {\n count += copyDirRecursiveSync(from, to);\n } else if (entry.isFile()) {\n fs.copyFileSync(from, to);\n count += 1;\n }\n // Skip symlinks, sockets, etc. — extension content is plain files.\n }\n return count;\n}\n\n/**\n * Install the Remnic memory extension into `<codex_home>/memories_extensions/remnic/`\n * atomically. The copy is written to a sibling `.remnic.tmp-<pid>-<ts>` directory\n * and then renamed into place, so a concurrent Codex phase-2 run never sees a\n * half-written extension.\n *\n * This function is **idempotent and scoped**: it only touches the `remnic`\n * subfolder inside `memories_extensions/`. Adjacent extensions (other\n * vendors) are never read, written, or removed.\n */\nexport function installCodexMemoryExtension(\n options: InstallCodexMemoryExtensionOptions = {},\n): InstallCodexMemoryExtensionResult {\n const paths = resolveCodexMemoryExtensionPaths(options.codexHome ?? null);\n const sourceDir = locatePluginCodexExtensionSource(options.sourceDir ?? null);\n\n fs.mkdirSync(paths.extensionsRoot, { recursive: true });\n\n // Clean any stale tmp from a previous crashed run by scanning the\n // extensions root for any `.remnic.tmp-*` prefixed entry. We must do this\n // BEFORE creating the new tmp directory. Per-entry errors are swallowed so\n // one bad entry doesn't abort cleanup of the rest.\n //\n // Finding 2: only remove tmp dirs that are provably stale (older than\n // STALE_TMP_THRESHOLD_MS). Dirs younger than the threshold belong to a\n // concurrent install that is still in progress; deleting them would corrupt\n // the other process's atomic rename.\n const tmpPrefix = `.${REMNIC_EXTENSION_DIR_NAME}.tmp-`;\n const STALE_TMP_THRESHOLD_MS = 10 * 60 * 1000; // 10 minutes\n const now = Date.now();\n try {\n const existingEntries = fs.readdirSync(paths.extensionsRoot);\n for (const entry of existingEntries) {\n if (!entry.startsWith(tmpPrefix)) continue;\n const stalePath = path.join(paths.extensionsRoot, entry);\n try {\n const stat = fs.statSync(stalePath);\n const ageMs = now - stat.mtimeMs;\n if (ageMs < STALE_TMP_THRESHOLD_MS) {\n // Too recent — leave it alone; another install is likely still running.\n continue;\n }\n fs.rmSync(stalePath, { recursive: true, force: true });\n } catch {\n // swallow — one bad entry should not abort the others\n }\n }\n } catch {\n // extensions root just-created / unreadable — nothing to clean\n }\n\n const tmpName = `${tmpPrefix}${process.pid}-${Date.now()}`;\n const tmpDir = path.join(paths.extensionsRoot, tmpName);\n\n let filesCopied = 0;\n let commitFn: () => void = () => { /* no-op: set below on success */ };\n let rollbackFn: () => void = () => { /* no-op: set below on success */ };\n try {\n filesCopied = copyDirRecursiveSync(sourceDir, tmpDir);\n\n // Atomic replace: rename old remnic/ to a timestamped backup, then rename\n // the tmp dir into place. If the second rename fails, restore from backup\n // so the old extension is never permanently lost.\n const backupDir = `${paths.remnicExtensionDir}.bak-${Date.now()}`;\n const hadExisting = fs.existsSync(paths.remnicExtensionDir);\n if (hadExisting) {\n fs.renameSync(paths.remnicExtensionDir, backupDir);\n }\n try {\n fs.renameSync(tmpDir, paths.remnicExtensionDir);\n } catch (renameErr) {\n // New rename failed — restore backup so the old extension survives.\n if (hadExisting) {\n try {\n fs.renameSync(backupDir, paths.remnicExtensionDir);\n } catch {\n // swallow — backup restore best-effort\n }\n }\n throw renameErr;\n }\n // The new extension is in place. We intentionally keep the backup alive\n // until the caller calls commit(). This gives the caller a chance to roll\n // back to the prior state if a subsequent operation (e.g. config write) fails.\n //\n // commit() — remove the backup (called on success)\n // rollback() — restore the prior extension from backup, or remove the newly\n // installed directory if this was a fresh install\n commitFn = (): void => {\n if (hadExisting) {\n try {\n fs.rmSync(backupDir, { recursive: true, force: true });\n } catch {\n // swallow — stale backup is harmless\n }\n }\n };\n rollbackFn = (): void => {\n if (hadExisting) {\n // Restore the prior extension from backup.\n try {\n // Remove the newly-installed dir first so rename can succeed.\n if (fs.existsSync(paths.remnicExtensionDir)) {\n fs.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });\n }\n fs.renameSync(backupDir, paths.remnicExtensionDir);\n } catch {\n // swallow — best-effort restore; backup remains on disk\n }\n } else {\n // Fresh install — just remove the directory we created.\n try {\n if (fs.existsSync(paths.remnicExtensionDir)) {\n fs.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });\n }\n } catch {\n // swallow\n }\n }\n };\n } catch (err) {\n // Best-effort cleanup so we never leave .tmp garbage behind.\n if (fs.existsSync(tmpDir)) {\n try {\n fs.rmSync(tmpDir, { recursive: true, force: true });\n } catch {\n // swallow\n }\n }\n throw err;\n }\n\n const instructionsPath = path.join(paths.remnicExtensionDir, \"instructions.md\");\n\n return {\n ...paths,\n instructionsPath,\n filesCopied,\n commit: commitFn,\n rollback: rollbackFn,\n };\n}\n\n/**\n * Remove the Remnic memory extension. Only touches\n * `<codex_home>/memories_extensions/remnic/` — never adjacent extensions.\n */\nexport function removeCodexMemoryExtension(\n options: RemoveCodexMemoryExtensionOptions = {},\n): RemoveCodexMemoryExtensionResult {\n const paths = resolveCodexMemoryExtensionPaths(options.codexHome ?? null);\n let removed = false;\n if (fs.existsSync(paths.remnicExtensionDir)) {\n fs.rmSync(paths.remnicExtensionDir, { recursive: true, force: true });\n removed = true;\n }\n return { ...paths, removed };\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction getConnectorsDir(): string {\n const configDir = process.env.XDG_CONFIG_HOME\n ? path.join(process.env.XDG_CONFIG_HOME, \"engram\")\n : path.join(process.env.HOME ?? \"~\", \".config\", \"engram\");\n return path.join(configDir, REGISTRY_DIR_NAME, \"connectors\");\n}\n\n// ── WeClone proxy config helpers ───────────────────────────────────────────\n//\n// The standalone `remnic-weclone-proxy` CLI reads its config from\n// ~/.remnic/connectors/weclone.json by default. `remnic connectors install\n// weclone` composes and persists that file so the proxy can start without\n// additional setup. The file is also tracked by the connector registry (at\n// getConnectorsDir()/weclone.json) so `remnic connectors list/remove/doctor`\n// work uniformly across all connectors.\n\nconst WECLONE_PROXY_CONFIG_DIRNAME = \".remnic\";\nconst WECLONE_PROXY_CONFIG_FILENAME = \"weclone.json\";\n\n/**\n * Resolve the path to ~/.remnic/connectors/weclone.json for the current user.\n * Honours REMNIC_HOME / ENGRAM_HOME env overrides so tests can point the\n * install at a temp dir without leaking into the real home directory.\n *\n * Always returns an absolute path via `path.resolve` so install-time and\n * run-time resolution agree even when the override is a relative path like\n * `tmp/remnic` (which would otherwise be interpreted against the caller's\n * current working directory). Must stay in lockstep with the proxy CLI's\n * `defaultConfigPath()` in @remnic/connector-weclone/src/cli.ts.\n *\n * `HOME=\"\"` edge case: `process.env.HOME ?? os.homedir()` would keep the\n * empty string (empty is not nullish), which `path.resolve(\"\", ...)` then\n * interprets as CWD. `os.homedir()` by contrast falls back to the OS\n * password database when HOME is empty, so the two code paths would\n * disagree. We therefore treat empty HOME as absent and delegate to\n * `os.homedir()` in both places — the same rule the proxy CLI follows.\n */\nexport function resolveWeCloneProxyConfigPath(): string {\n const override = process.env.REMNIC_HOME ?? process.env.ENGRAM_HOME;\n if (override && override.length > 0) {\n return path.resolve(override, \"connectors\", WECLONE_PROXY_CONFIG_FILENAME);\n }\n const envHome = process.env.HOME;\n const home = envHome && envHome.length > 0 ? envHome : os.homedir();\n return path.resolve(\n home,\n WECLONE_PROXY_CONFIG_DIRNAME,\n \"connectors\",\n WECLONE_PROXY_CONFIG_FILENAME,\n );\n}\n\n/**\n * Read the existing proxy config file, if any. Returns raw contents so the\n * caller can both parse it (for value precedence) and restore it verbatim on\n * rollback without touching byte-level formatting.\n */\nfunction readWeCloneProxyConfigIfExists(configPath: string): string | null {\n try {\n if (!fs.existsSync(configPath)) return null;\n return fs.readFileSync(configPath, \"utf8\");\n } catch {\n return null;\n }\n}\n\n/** Safely parse a JSON string into a record; returns null on error. */\nfunction safeParseJson(raw: string): Record<string, unknown> | null {\n try {\n const parsed = JSON.parse(raw);\n if (typeof parsed === \"object\" && parsed !== null && !Array.isArray(parsed)) {\n return parsed as Record<string, unknown>;\n }\n return null;\n } catch {\n return null;\n }\n}\n\ninterface WeCloneProxyConfig {\n wecloneApiUrl: string;\n wecloneModelName: string;\n proxyPort: number;\n remnicDaemonUrl: string;\n remnicAuthToken?: string;\n sessionStrategy: \"caller-id\" | \"single\";\n memoryInjection: {\n maxTokens: number;\n position: \"system-append\" | \"system-prepend\";\n template: string;\n };\n}\n\nconst WECLONE_DEFAULTS = {\n wecloneApiUrl: \"http://localhost:8000/v1\",\n wecloneModelName: \"weclone-avatar\",\n proxyPort: 8100,\n remnicDaemonUrl: \"http://localhost:4318\",\n sessionStrategy: \"single\" as const,\n memoryInjection: {\n maxTokens: 1500,\n position: \"system-append\" as const,\n template: \"[Memory Context]\\n{memories}\\n[End Memory Context]\",\n },\n};\n\n/**\n * Resolve a string field with precedence: userConfig → priorConfig → default.\n * Only non-empty strings are accepted from either source; invalid values fall\n * through so the user gets a working default rather than a broken install.\n */\nfunction resolveStringField(\n userConfig: Record<string, unknown>,\n priorConfig: Record<string, unknown> | null,\n key: string,\n fallback: string,\n): string {\n const fromUser = userConfig[key];\n if (typeof fromUser === \"string\" && fromUser.length > 0) return fromUser;\n if (priorConfig) {\n const fromPrior = priorConfig[key];\n if (typeof fromPrior === \"string\" && fromPrior.length > 0) return fromPrior;\n }\n return fallback;\n}\n\n/**\n * Coerce a config value to an integer port in [1, 65535]. Accepts number or\n * numeric string (parseConnectorConfig produces strings from `--config\n * proxyPort=8100`). Returns null if the value is missing or invalid so the\n * caller can fall through to the next precedence level.\n */\nfunction coercePort(value: unknown): number | null {\n if (typeof value === \"number\" && Number.isInteger(value) && value >= 1 && value <= 65535) {\n return value;\n }\n if (typeof value === \"string\" && value.length > 0) {\n const n = Number(value);\n if (Number.isInteger(n) && n >= 1 && n <= 65535) return n;\n }\n return null;\n}\n\nfunction resolvePort(\n userConfig: Record<string, unknown>,\n priorConfig: Record<string, unknown> | null,\n fallback: number,\n): number {\n const fromUser = coercePort(userConfig.proxyPort);\n if (fromUser !== null) return fromUser;\n if (priorConfig) {\n const fromPrior = coercePort(priorConfig.proxyPort);\n if (fromPrior !== null) return fromPrior;\n }\n return fallback;\n}\n\nfunction resolveSessionStrategy(\n userConfig: Record<string, unknown>,\n priorConfig: Record<string, unknown> | null,\n): \"caller-id\" | \"single\" {\n const valid = new Set([\"caller-id\", \"single\"]);\n const fromUser = userConfig.sessionStrategy;\n if (typeof fromUser === \"string\" && valid.has(fromUser)) {\n return fromUser as \"caller-id\" | \"single\";\n }\n if (priorConfig) {\n const fromPrior = priorConfig.sessionStrategy;\n if (typeof fromPrior === \"string\" && valid.has(fromPrior)) {\n return fromPrior as \"caller-id\" | \"single\";\n }\n }\n return WECLONE_DEFAULTS.sessionStrategy;\n}\n\n/**\n * Compose a WeCloneProxyConfig from user-supplied overrides and any prior\n * saved config, filling in defaults for every required field. The returned\n * shape is exactly what the proxy's parseConfig() expects.\n */\nexport function buildWeCloneProxyConfig(args: {\n userConfig: Record<string, unknown>;\n priorConfig: Record<string, unknown> | null;\n authToken?: string;\n}): WeCloneProxyConfig {\n const { userConfig, priorConfig, authToken } = args;\n\n const wecloneApiUrl = resolveStringField(\n userConfig,\n priorConfig,\n \"wecloneApiUrl\",\n WECLONE_DEFAULTS.wecloneApiUrl,\n );\n const wecloneModelName = resolveStringField(\n userConfig,\n priorConfig,\n \"wecloneModelName\",\n WECLONE_DEFAULTS.wecloneModelName,\n );\n const remnicDaemonUrl = resolveStringField(\n userConfig,\n priorConfig,\n \"remnicDaemonUrl\",\n WECLONE_DEFAULTS.remnicDaemonUrl,\n );\n const proxyPort = resolvePort(\n userConfig,\n priorConfig,\n WECLONE_DEFAULTS.proxyPort,\n );\n const sessionStrategy = resolveSessionStrategy(userConfig, priorConfig);\n\n // Memory injection: always start from defaults, then shallow-merge any\n // prior values, then user overrides. Individual field validation happens in\n // the proxy's parseConfig() at proxy startup — here we only assemble a\n // best-effort shape. A malformed user override would be rejected later with\n // a clean error message.\n //\n // `typeof [] === \"object\"` so a bare `typeof ... === \"object\" && ... !==\n // null` guard would let an array spread numeric-indexed properties into\n // the merged object, silently corrupting it. Explicitly reject arrays.\n const memoryInjection = {\n ...WECLONE_DEFAULTS.memoryInjection,\n ...(priorConfig &&\n typeof priorConfig.memoryInjection === \"object\" &&\n priorConfig.memoryInjection !== null &&\n !Array.isArray(priorConfig.memoryInjection)\n ? (priorConfig.memoryInjection as Record<string, unknown>)\n : {}),\n ...(typeof userConfig.memoryInjection === \"object\" &&\n userConfig.memoryInjection !== null &&\n !Array.isArray(userConfig.memoryInjection)\n ? (userConfig.memoryInjection as Record<string, unknown>)\n : {}),\n } as WeCloneProxyConfig[\"memoryInjection\"];\n\n const config: WeCloneProxyConfig = {\n wecloneApiUrl,\n wecloneModelName,\n proxyPort,\n remnicDaemonUrl,\n sessionStrategy,\n memoryInjection,\n };\n\n // Token precedence: freshly minted token → user-supplied → prior saved.\n // Never write a token if none is available — the proxy tolerates missing\n // tokens (it just won't send Authorization headers to the daemon).\n if (authToken && authToken.length > 0) {\n config.remnicAuthToken = authToken;\n } else if (typeof userConfig.remnicAuthToken === \"string\" && userConfig.remnicAuthToken.length > 0) {\n config.remnicAuthToken = userConfig.remnicAuthToken;\n } else if (priorConfig && typeof priorConfig.remnicAuthToken === \"string\" && priorConfig.remnicAuthToken.length > 0) {\n config.remnicAuthToken = priorConfig.remnicAuthToken;\n }\n\n return config;\n}\n","/**\n * codex-marketplace.ts — Codex marketplace installation support (#418)\n *\n * Provides types and functions for integrating Remnic with the Codex CLI\n * marketplace system (`codex marketplace add`). This module handles:\n *\n * - Generating a `marketplace.json` manifest describing Remnic as an\n * installable plugin in the Codex marketplace ecosystem.\n * - Validating marketplace manifest files against the expected schema.\n * - Writing manifests to disk atomically.\n * - Installing plugins from marketplace sources (GitHub, git, local, URL).\n *\n * Privacy\n * -------\n * This module does not persist any user content. It only reads package\n * metadata (name, version, description) and writes public marketplace\n * manifest files.\n */\n\nimport { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport path from \"node:path\";\n\nimport { log } from \"../logger.js\";\nimport type { PluginConfig } from \"../types.js\";\n\n// ── Types ─────────────────────────────────────────────────────────────────\n\n/** Source type for marketplace installation. */\nexport type MarketplaceInstallType = \"github\" | \"git\" | \"local\" | \"url\";\n\n/** A single plugin entry within a marketplace manifest. */\nexport interface MarketplaceEntry {\n /** Plugin name (e.g. \"remnic\"). */\n name: string;\n /** Semver version string. */\n version: string;\n /** Human-readable description. */\n description: string;\n /** Repository identifier (e.g. \"joshuaswarren/remnic\"). */\n repository: string;\n /** How this plugin should be installed. */\n installType: MarketplaceInstallType;\n /** Optional direct URL to the plugin manifest. */\n manifestUrl?: string;\n /** Optional entry point path within the repository. */\n entry?: string;\n /** Optional config schema reference. */\n configSchema?: string;\n}\n\n/** Top-level marketplace manifest. */\nexport interface MarketplaceManifest {\n /** Schema version. Must be 1. */\n version: 1;\n /** Marketplace name. */\n name: string;\n /** Human-readable description. */\n description: string;\n /** Available plugins. */\n plugins: MarketplaceEntry[];\n}\n\n/** Configuration for the marketplace subsystem. */\nexport interface MarketplaceConfig {\n /** Whether marketplace features are enabled. Default: true. */\n enabled: boolean;\n /** Local path where marketplace data is cached. */\n registryPath: string;\n /** Whether to auto-update marketplace data on install. Default: false. */\n autoUpdate: boolean;\n}\n\n/** Result of a marketplace install operation. */\nexport interface MarketplaceInstallResult {\n /** Whether the install succeeded. */\n ok: boolean;\n /** Human-readable message. */\n message: string;\n /** Source that was installed from. */\n source: string;\n /** Source type. */\n sourceType: MarketplaceInstallType;\n /** Plugins that were discovered. */\n pluginsFound: string[];\n /** Errors encountered (empty on success). */\n errors: string[];\n}\n\n/** Logger interface accepted by marketplace functions. */\nexport interface MarketplaceLogger {\n info: (msg: string) => void;\n warn: (msg: string) => void;\n debug?: (msg: string) => void;\n}\n\n// ── Constants ─────────────────────────────────────────────────────────────\n\n/** Current marketplace schema version. */\nexport const MARKETPLACE_SCHEMA_VERSION = 1 as const;\n\n/** Default marketplace manifest filename. */\nexport const MARKETPLACE_MANIFEST_FILENAME = \"marketplace.json\";\n\n/** Valid install types for validation. */\nconst VALID_INSTALL_TYPES = new Set<string>([\"github\", \"git\", \"local\", \"url\"]);\n\n// ── Generate ──────────────────────────────────────────────────────────────\n\n/**\n * Generate a marketplace manifest describing Remnic as an installable plugin.\n *\n * Reads version from the workspace root `package.json` at the resolved path,\n * or falls back to a default version string.\n */\nexport function generateMarketplaceManifest(\n options?: { packageVersion?: string },\n): MarketplaceManifest {\n const version = options?.packageVersion ?? readPackageVersion() ?? \"0.0.0\";\n\n return {\n version: MARKETPLACE_SCHEMA_VERSION,\n name: \"remnic\",\n description: \"Remnic: Local-first AI memory with semantic search and consolidation\",\n plugins: [\n {\n name: \"remnic\",\n version,\n description: \"Persistent memory plugin for Codex CLI\",\n repository: \"joshuaswarren/remnic\",\n installType: \"github\",\n entry: \"packages/plugin-codex\",\n configSchema: \"openclaw.plugin.json\",\n },\n ],\n };\n}\n\n// ── Validate ──────────────────────────────────────────────────────────────\n\n/** Validation result with structured errors. */\nexport interface MarketplaceValidation {\n valid: boolean;\n errors: string[];\n}\n\n/**\n * Validate that an unknown value conforms to the MarketplaceManifest schema.\n *\n * Returns a typed manifest on success. Throws on invalid input with a\n * descriptive error message listing all schema violations.\n */\nexport function validateMarketplaceManifest(manifest: unknown): MarketplaceManifest {\n const validation = checkMarketplaceManifest(manifest);\n if (!validation.valid) {\n throw new Error(\n `Invalid marketplace manifest: ${validation.errors.join(\"; \")}`,\n );\n }\n return manifest as MarketplaceManifest;\n}\n\n/**\n * Non-throwing validation. Returns a structured result with error details.\n */\nexport function checkMarketplaceManifest(manifest: unknown): MarketplaceValidation {\n const errors: string[] = [];\n\n if (typeof manifest !== \"object\" || manifest === null) {\n return { valid: false, errors: [\"manifest must be a non-null object\"] };\n }\n\n const obj = manifest as Record<string, unknown>;\n\n // version\n if (obj.version !== MARKETPLACE_SCHEMA_VERSION) {\n errors.push(`version must be ${MARKETPLACE_SCHEMA_VERSION}, got ${JSON.stringify(obj.version)}`);\n }\n\n // name\n if (typeof obj.name !== \"string\" || obj.name.trim().length === 0) {\n errors.push(\"name must be a non-empty string\");\n }\n\n // description\n if (typeof obj.description !== \"string\" || obj.description.trim().length === 0) {\n errors.push(\"description must be a non-empty string\");\n }\n\n // plugins\n if (!Array.isArray(obj.plugins)) {\n errors.push(\"plugins must be an array\");\n } else if (obj.plugins.length === 0) {\n errors.push(\"plugins must contain at least one entry\");\n } else {\n for (let i = 0; i < obj.plugins.length; i++) {\n const plugin = obj.plugins[i] as Record<string, unknown>;\n const prefix = `plugins[${i}]`;\n\n if (typeof plugin !== \"object\" || plugin === null) {\n errors.push(`${prefix} must be a non-null object`);\n continue;\n }\n\n if (typeof plugin.name !== \"string\" || plugin.name.trim().length === 0) {\n errors.push(`${prefix}.name must be a non-empty string`);\n }\n\n if (typeof plugin.version !== \"string\" || plugin.version.trim().length === 0) {\n errors.push(`${prefix}.version must be a non-empty string`);\n }\n\n if (typeof plugin.description !== \"string\" || plugin.description.trim().length === 0) {\n errors.push(`${prefix}.description must be a non-empty string`);\n }\n\n if (typeof plugin.repository !== \"string\" || plugin.repository.trim().length === 0) {\n errors.push(`${prefix}.repository must be a non-empty string`);\n }\n\n if (typeof plugin.installType !== \"string\" || !VALID_INSTALL_TYPES.has(plugin.installType)) {\n errors.push(\n `${prefix}.installType must be one of: ${[...VALID_INSTALL_TYPES].join(\", \")}; ` +\n `got ${JSON.stringify(plugin.installType)}`,\n );\n }\n\n // Optional fields — validate type when present (PR #427 post-merge fix).\n if (\"manifestUrl\" in plugin && plugin.manifestUrl !== undefined) {\n if (typeof plugin.manifestUrl !== \"string\" || (plugin.manifestUrl as string).trim().length === 0) {\n errors.push(`${prefix}.manifestUrl must be a non-empty string when provided`);\n }\n }\n if (\"entry\" in plugin && plugin.entry !== undefined) {\n if (typeof plugin.entry !== \"string\" || (plugin.entry as string).trim().length === 0) {\n errors.push(`${prefix}.entry must be a non-empty string when provided`);\n }\n }\n if (\"configSchema\" in plugin && plugin.configSchema !== undefined) {\n if (typeof plugin.configSchema !== \"string\" || (plugin.configSchema as string).trim().length === 0) {\n errors.push(`${prefix}.configSchema must be a non-empty string when provided`);\n }\n }\n }\n }\n\n return { valid: errors.length === 0, errors };\n}\n\n// ── Write ─────────────────────────────────────────────────────────────────\n\n/**\n * Write a marketplace manifest to disk atomically.\n *\n * Uses write-to-temp-then-rename to avoid partial writes (CLAUDE.md gotcha #54).\n */\nexport async function writeMarketplaceManifest(\n outputDir: string,\n manifest: MarketplaceManifest,\n): Promise<void> {\n // Validate before writing — never write garbage to disk.\n const validation = checkMarketplaceManifest(manifest);\n if (!validation.valid) {\n throw new Error(\n `Refusing to write invalid manifest: ${validation.errors.join(\"; \")}`,\n );\n }\n\n mkdirSync(outputDir, { recursive: true });\n\n const destPath = path.join(outputDir, MARKETPLACE_MANIFEST_FILENAME);\n const tmpPath = `${destPath}.tmp.${process.pid}`;\n const content = JSON.stringify(manifest, null, 2) + \"\\n\";\n\n writeFileSync(tmpPath, content);\n renameSync(tmpPath, destPath);\n}\n\n// ── Install ───────────────────────────────────────────────────────────────\n\n/**\n * Install from a marketplace source.\n *\n * Reads the marketplace.json from the given source, validates it, and\n * returns a result describing what was found.\n */\nexport async function installFromMarketplace(\n source: string,\n sourceType: MarketplaceInstallType,\n config: PluginConfig,\n logger?: MarketplaceLogger,\n): Promise<MarketplaceInstallResult> {\n const _log: MarketplaceLogger = logger ?? {\n info: (msg) => log.info(`[marketplace] ${msg}`),\n warn: (msg) => log.warn(`[marketplace] ${msg}`),\n debug: (msg) => log.debug(`[marketplace] ${msg}`),\n };\n\n if (!config.codexMarketplaceEnabled) {\n return {\n ok: false,\n message: \"Codex marketplace is disabled in config (codexMarketplaceEnabled: false)\",\n source,\n sourceType,\n pluginsFound: [],\n errors: [\"marketplace_disabled\"],\n };\n }\n\n try {\n const manifest = await resolveManifest(source, sourceType, _log);\n const pluginNames = manifest.plugins.map((p) => p.name);\n\n _log.info(`marketplace install: found ${pluginNames.length} plugin(s) from ${sourceType}://${source}`);\n\n return {\n ok: true,\n message: `Successfully resolved ${pluginNames.length} plugin(s) from marketplace: ${pluginNames.join(\", \")}`,\n source,\n sourceType,\n pluginsFound: pluginNames,\n errors: [],\n };\n } catch (err) {\n const errMsg = err instanceof Error ? err.message : String(err);\n _log.warn(`marketplace install failed: ${errMsg}`);\n return {\n ok: false,\n message: `Failed to install from marketplace: ${errMsg}`,\n source,\n sourceType,\n pluginsFound: [],\n errors: [errMsg],\n };\n }\n}\n\n// ── Source resolution ─────────────────────────────────────────────────────\n\n/**\n * Resolve a marketplace manifest from the given source.\n */\nasync function resolveManifest(\n source: string,\n sourceType: MarketplaceInstallType,\n logger: MarketplaceLogger,\n): Promise<MarketplaceManifest> {\n switch (sourceType) {\n case \"local\":\n return resolveLocal(source, logger);\n case \"url\":\n return resolveUrl(source, logger);\n case \"github\":\n return resolveGithub(source, logger);\n case \"git\":\n return resolveGit(source, logger);\n default: {\n // Exhaustive check — CLAUDE.md gotcha #51: reject invalid input.\n const _: never = sourceType;\n throw new Error(`Invalid source type: ${String(_)}`);\n }\n }\n}\n\n/**\n * Read marketplace.json from a local directory.\n */\nfunction resolveLocal(\n dirPath: string,\n logger: MarketplaceLogger,\n): MarketplaceManifest {\n const manifestPath = path.join(dirPath, MARKETPLACE_MANIFEST_FILENAME);\n\n if (!existsSync(manifestPath)) {\n throw new Error(`marketplace.json not found at ${manifestPath}`);\n }\n\n logger.debug?.(`reading local marketplace manifest: ${manifestPath}`);\n\n const raw = readFileSync(manifestPath, \"utf-8\");\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n throw new Error(`Invalid JSON in ${manifestPath}`);\n }\n\n // CLAUDE.md gotcha #18: validate parse result type\n if (typeof parsed !== \"object\" || parsed === null) {\n throw new Error(`marketplace.json at ${manifestPath} is not a valid object`);\n }\n\n return validateMarketplaceManifest(parsed);\n}\n\n/**\n * Fetch marketplace.json from a URL.\n */\nasync function resolveUrl(\n url: string,\n logger: MarketplaceLogger,\n): Promise<MarketplaceManifest> {\n logger.debug?.(`fetching marketplace manifest from URL: ${url}`);\n\n let parsedUrl: URL;\n try {\n parsedUrl = new URL(url);\n } catch {\n throw new Error(`Invalid URL: ${url}`);\n }\n\n if (parsedUrl.protocol !== \"https:\" && parsedUrl.protocol !== \"http:\") {\n throw new Error(`Unsupported URL protocol: ${parsedUrl.protocol} (use https or http)`);\n }\n\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`HTTP ${response.status} fetching ${url}`);\n }\n\n const body = await response.json() as unknown;\n return validateMarketplaceManifest(body);\n}\n\n/**\n * Resolve marketplace.json from a GitHub repository reference (owner/repo).\n *\n * Attempts to fetch the raw marketplace.json from the default branch.\n */\nasync function resolveGithub(\n repo: string,\n logger: MarketplaceLogger,\n): Promise<MarketplaceManifest> {\n // Validate format: must be owner/repo\n if (!/^[a-zA-Z0-9._-]+\\/[a-zA-Z0-9._-]+$/u.test(repo)) {\n throw new Error(`Invalid GitHub repo format: \"${repo}\" (expected owner/repo)`);\n }\n\n const rawUrl = `https://raw.githubusercontent.com/${repo}/HEAD/${MARKETPLACE_MANIFEST_FILENAME}`;\n logger.debug?.(`fetching marketplace manifest from GitHub: ${rawUrl}`);\n\n return resolveUrl(rawUrl, logger);\n}\n\n/**\n * Resolve marketplace.json from a git URL.\n *\n * For now this delegates to URL-based resolution by constructing a raw URL.\n * Full git clone support can be added later.\n */\nasync function resolveGit(\n gitUrl: string,\n logger: MarketplaceLogger,\n): Promise<MarketplaceManifest> {\n // For git URLs that look like GitHub HTTPS URLs, extract owner/repo and\n // delegate to the GitHub resolver.\n const ghMatch = gitUrl.match(\n /^(?:https?:\\/\\/)?github\\.com\\/([a-zA-Z0-9._-]+\\/[a-zA-Z0-9._-]+?)(?:\\.git)?$/u,\n );\n if (ghMatch?.[1]) {\n logger.debug?.(`git URL looks like GitHub — delegating to github resolver`);\n return resolveGithub(ghMatch[1], logger);\n }\n\n throw new Error(\n `Git URL resolution requires a GitHub-format URL for now. ` +\n `Got: ${gitUrl}. Use --type github or --type url instead.`,\n );\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────\n\n/**\n * Read the workspace root package.json version. Returns undefined if not found.\n */\nfunction readPackageVersion(): string | undefined {\n // Walk up from this file to find the workspace root package.json\n // This module lives at packages/remnic-core/src/connectors/codex-marketplace.ts\n // so workspace root is 4 levels up.\n const candidates = [\n path.resolve(import.meta.dirname ?? \".\", \"../../../..\"),\n path.resolve(import.meta.dirname ?? \".\", \"../../../../..\"),\n path.resolve(import.meta.dirname ?? \".\", \"..\"),\n ];\n\n for (const candidate of candidates) {\n const pkgPath = path.join(candidate, \"package.json\");\n try {\n if (!existsSync(pkgPath)) continue;\n const raw = readFileSync(pkgPath, \"utf-8\");\n const parsed = JSON.parse(raw);\n if (typeof parsed === \"object\" && parsed !== null && typeof parsed.version === \"string\") {\n return parsed.version;\n }\n } catch {\n // ignore\n }\n }\n\n return undefined;\n}\n","/**\n * @remnic/core — Spaces + Collaboration\n *\n * First-class memory spaces (personal, project, team) with merge/conflict\n * flows, promotion workflow, and audit trail.\n *\n * Each space is an isolated memory directory. Spaces can share memories\n * through push/pull and promotion workflows.\n */\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport crypto from \"node:crypto\";\n\n// ── Types ────────────────────────────────────────────────────────────────────\n\nexport type SpaceKind = \"personal\" | \"project\" | \"team\";\n\nexport interface Space {\n /** Unique space ID */\n id: string;\n /** Human-readable name */\n name: string;\n /** Space type */\n kind: SpaceKind;\n /** Description */\n description?: string;\n /** Memory directory path (absolute) */\n memoryDir: string;\n /** Created at */\n createdAt: string;\n /** Updated at */\n updatedAt: string;\n /** Owner */\n owner?: string;\n /** Members (for team spaces) */\n members?: string[];\n /** Parent space (for promotion) */\n parentSpaceId?: string;\n}\n\nexport interface SpaceManifest {\n /** Current active space ID */\n activeSpaceId: string;\n /** All spaces */\n spaces: Space[];\n /** Manifest version */\n version: number;\n /** Last updated */\n updatedAt?: string;\n}\n\nexport interface SpaceSwitchResult {\n previousSpaceId: string;\n currentSpaceId: string;\n message: string;\n}\n\nexport interface SpacePushResult {\n sourceSpaceId: string;\n targetSpaceId: string;\n memoriesPushed: number;\n conflicts: ConflictEntry[];\n durationMs: number;\n}\n\nexport interface SpacePullResult {\n sourceSpaceId: string;\n targetSpaceId: string;\n memoriesPulled: number;\n conflicts: ConflictEntry[];\n durationMs: number;\n}\n\nexport interface SpaceShareResult {\n spaceId: string;\n sharedWith: string[];\n message: string;\n}\n\nexport interface SpacePromoteResult {\n sourceSpaceId: string;\n targetSpaceId: string;\n memoriesPromoted: number;\n conflicts: ConflictEntry[];\n durationMs: number;\n}\n\nexport interface ConflictEntry {\n /** Memory ID */\n memoryId: string;\n /** Source file path */\n sourcePath: string;\n /** Target file path */\n targetPath: string;\n /** Conflict type */\n conflictType: \"content_mismatch\" | \"metadata_mismatch\" | \"both\";\n /** Source content hash */\n sourceHash: string;\n /** Target content hash */\n targetHash: string;\n}\n\nexport interface MergeResult {\n merged: number;\n conflicts: ConflictEntry[];\n skipped: number;\n durationMs: number;\n}\n\nexport interface AuditEntry {\n id: string;\n timestamp: string;\n action: string;\n sourceSpaceId: string;\n targetSpaceId?: string;\n actor?: string;\n details: string;\n memoryIds?: string[];\n}\n\n// ── Manifest management ─────────────────────────────────────────────────────\n\nconst MANIFEST_VERSION = 1;\n\nexport function getSpacesDir(baseDir?: string): string {\n const homeDir = baseDir ?? process.env.HOME ?? \"~\";\n return path.join(homeDir, \".config\", \"engram\", \"spaces\");\n}\n\nexport function getManifestPath(baseDir?: string): string {\n return path.join(getSpacesDir(baseDir), \"manifest.json\");\n}\n\nexport function loadManifest(baseDir?: string, memoryDirOverride?: string): SpaceManifest {\n const manifestPath = getManifestPath(baseDir);\n\n if (!fs.existsSync(manifestPath)) {\n // Bootstrap with a personal space\n const personalSpace = createPersonalSpace(baseDir, memoryDirOverride);\n const manifest: SpaceManifest = {\n activeSpaceId: personalSpace.id,\n spaces: [personalSpace],\n version: MANIFEST_VERSION,\n };\n saveManifest(manifest, baseDir);\n return manifest;\n }\n\n const raw = JSON.parse(fs.readFileSync(manifestPath, \"utf8\"));\n return raw as SpaceManifest;\n}\n\nexport function saveManifest(manifest: SpaceManifest, baseDir?: string): void {\n const manifestPath = getManifestPath(baseDir);\n fs.mkdirSync(path.dirname(manifestPath), { recursive: true });\n fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + \"\\n\");\n}\n\nfunction createPersonalSpace(baseDir?: string, memoryDirOverride?: string): Space {\n const homeDir = baseDir ?? process.env.HOME ?? \"~\";\n // Priority: override > env var > existing standalone dir > existing OpenClaw dir > new standalone dir\n const standalonePath = path.join(homeDir, \".engram\", \"memory\");\n const openclawPath = path.join(homeDir, \".openclaw\", \"workspace\", \"memory\", \"local\");\n const memoryDir = memoryDirOverride\n ?? process.env.ENGRAM_MEMORY_DIR\n ?? (fs.existsSync(standalonePath) ? standalonePath\n : fs.existsSync(openclawPath) ? openclawPath\n : standalonePath);\n const now = new Date().toISOString();\n\n return {\n id: \"personal\",\n name: \"Personal\",\n kind: \"personal\",\n description: \"Default personal memory space\",\n memoryDir,\n createdAt: now,\n updatedAt: now,\n owner: process.env.USER,\n };\n}\n\n// ── Space CRUD ──────────────────────────────────────────────────────────────\n\nexport function listSpaces(baseDir?: string): Space[] {\n const manifest = loadManifest(baseDir);\n return manifest.spaces;\n}\n\nexport function getActiveSpace(baseDir?: string): Space {\n const manifest = loadManifest(baseDir);\n const space = manifest.spaces.find((s) => s.id === manifest.activeSpaceId);\n if (!space) throw new Error(`Active space ${manifest.activeSpaceId} not found`);\n return space;\n}\n\nexport function createSpace(options: {\n name: string;\n kind: SpaceKind;\n description?: string;\n memoryDir?: string;\n parentSpaceId?: string;\n baseDir?: string;\n}): Space {\n const manifest = loadManifest(options.baseDir);\n const id = options.name.toLowerCase().replace(/[^a-z0-9-]/g, \"-\").replace(/-+/g, \"-\");\n\n if (manifest.spaces.some((s) => s.id === id)) {\n throw new Error(`Space \"${id}\" already exists`);\n }\n\n // Validate parent space exists\n if (options.parentSpaceId && !manifest.spaces.some((s) => s.id === options.parentSpaceId)) {\n throw new Error(`Parent space \"${options.parentSpaceId}\" not found`);\n }\n\n const now = new Date().toISOString();\n const memoryDir = options.memoryDir ?? path.join(\n getSpacesDir(options.baseDir),\n id,\n \"memory\",\n );\n\n const space: Space = {\n id,\n name: options.name,\n kind: options.kind,\n description: options.description,\n memoryDir,\n createdAt: now,\n updatedAt: now,\n owner: process.env.USER,\n parentSpaceId: options.parentSpaceId,\n };\n\n // Ensure memory directory exists\n fs.mkdirSync(memoryDir, { recursive: true });\n\n manifest.spaces.push(space);\n manifest.updatedAt = now;\n saveManifest(manifest, options.baseDir);\n\n // Audit\n appendAudit({\n action: \"space.create\",\n sourceSpaceId: id,\n details: `Created ${options.kind} space \"${options.name}\"`,\n }, options.baseDir);\n\n return space;\n}\n\nexport function deleteSpace(spaceId: string, baseDir?: string): void {\n const manifest = loadManifest(baseDir);\n\n if (spaceId === \"personal\") {\n throw new Error(\"Cannot delete the personal space\");\n }\n\n const idx = manifest.spaces.findIndex((s) => s.id === spaceId);\n if (idx === -1) throw new Error(`Space \"${spaceId}\" not found`);\n\n // If deleting active space, switch to personal\n if (manifest.activeSpaceId === spaceId) {\n manifest.activeSpaceId = \"personal\";\n }\n\n // Clear parentSpaceId references from children\n for (const space of manifest.spaces) {\n if (space.parentSpaceId === spaceId) {\n space.parentSpaceId = undefined;\n }\n }\n\n manifest.spaces.splice(idx, 1);\n saveManifest(manifest, baseDir);\n\n appendAudit({\n action: \"space.delete\",\n sourceSpaceId: spaceId,\n details: `Deleted space \"${spaceId}\"`,\n }, baseDir);\n}\n\n// ── Switch ───────────────────────────────────────────────────────────────────\n\nexport function switchSpace(spaceId: string, baseDir?: string): SpaceSwitchResult {\n const manifest = loadManifest(baseDir);\n const space = manifest.spaces.find((s) => s.id === spaceId);\n\n if (!space) throw new Error(`Space \"${spaceId}\" not found`);\n\n const previousId = manifest.activeSpaceId;\n manifest.activeSpaceId = spaceId;\n saveManifest(manifest, baseDir);\n\n appendAudit({\n action: \"space.switch\",\n sourceSpaceId: previousId,\n targetSpaceId: spaceId,\n details: `Switched from \"${previousId}\" to \"${spaceId}\"`,\n }, baseDir);\n\n return {\n previousSpaceId: previousId,\n currentSpaceId: spaceId,\n message: `Switched to \"${space.name}\"`,\n };\n}\n\n// ── Push / Pull ─────────────────────────────────────────────────────────────\n\nexport function pushToSpace(\n sourceSpaceId: string,\n targetSpaceId: string,\n options?: { memoryIds?: string[]; force?: boolean; baseDir?: string },\n): SpacePushResult {\n const startTime = Date.now();\n const manifest = loadManifest(options?.baseDir);\n\n const source = manifest.spaces.find((s) => s.id === sourceSpaceId);\n const target = manifest.spaces.find((s) => s.id === targetSpaceId);\n\n if (!source) throw new Error(`Source space \"${sourceSpaceId}\" not found`);\n if (!target) throw new Error(`Target space \"${targetSpaceId}\" not found`);\n\n const result = copyMemories(source.memoryDir, target.memoryDir, {\n filterIds: options?.memoryIds,\n force: options?.force,\n });\n\n appendAudit({\n action: \"space.push\",\n sourceSpaceId,\n targetSpaceId,\n details: `Pushed ${result.merged} memories, ${result.conflicts.length} conflicts`,\n }, options?.baseDir);\n\n return {\n sourceSpaceId,\n targetSpaceId,\n memoriesPushed: result.merged,\n conflicts: result.conflicts,\n durationMs: Date.now() - startTime,\n };\n}\n\nexport function pullFromSpace(\n sourceSpaceId: string,\n targetSpaceId: string,\n options?: { memoryIds?: string[]; force?: boolean; baseDir?: string },\n): SpacePullResult {\n const startTime = Date.now();\n const manifest = loadManifest(options?.baseDir);\n\n const source = manifest.spaces.find((s) => s.id === sourceSpaceId);\n const target = manifest.spaces.find((s) => s.id === targetSpaceId);\n\n if (!source) throw new Error(`Source space \"${sourceSpaceId}\" not found`);\n if (!target) throw new Error(`Target space \"${targetSpaceId}\" not found`);\n\n const result = copyMemories(source.memoryDir, target.memoryDir, {\n filterIds: options?.memoryIds,\n force: options?.force,\n });\n\n appendAudit({\n action: \"space.pull\",\n sourceSpaceId,\n targetSpaceId,\n details: `Pulled ${result.merged} memories, ${result.conflicts.length} conflicts`,\n }, options?.baseDir);\n\n return {\n sourceSpaceId,\n targetSpaceId,\n memoriesPulled: result.merged,\n conflicts: result.conflicts,\n durationMs: Date.now() - startTime,\n };\n}\n\n// ── Share ────────────────────────────────────────────────────────────────────\n\nexport function shareSpace(\n spaceId: string,\n members: string[],\n baseDir?: string,\n): SpaceShareResult {\n const manifest = loadManifest(baseDir);\n const space = manifest.spaces.find((s) => s.id === spaceId);\n\n if (!space) throw new Error(`Space \"${spaceId}\" not found`);\n if (space.kind === \"personal\") throw new Error(\"Cannot share personal space\");\n\n space.members = [...new Set([...(space.members ?? []), ...members])];\n space.updatedAt = new Date().toISOString();\n saveManifest(manifest, baseDir);\n\n appendAudit({\n action: \"space.share\",\n sourceSpaceId: spaceId,\n details: `Shared with: ${members.join(\", \")}`,\n }, baseDir);\n\n return {\n spaceId,\n sharedWith: members,\n message: `Shared \"${space.name}\" with ${members.length} member(s)`,\n };\n}\n\n// ── Promote ──────────────────────────────────────────────────────────────────\n\nexport function promoteSpace(\n sourceSpaceId: string,\n targetSpaceId: string,\n options?: { memoryIds?: string[]; force?: boolean; forceOverwrite?: boolean; baseDir?: string },\n): SpacePromoteResult {\n const startTime = Date.now();\n const manifest = loadManifest(options?.baseDir);\n\n const source = manifest.spaces.find((s) => s.id === sourceSpaceId);\n const target = manifest.spaces.find((s) => s.id === targetSpaceId);\n\n if (!source) throw new Error(`Source space \"${sourceSpaceId}\" not found`);\n if (!target) throw new Error(`Target space \"${targetSpaceId}\" not found`);\n\n // Promotion requires parent-child relationship or explicit force\n if (source.parentSpaceId !== targetSpaceId && target.parentSpaceId !== sourceSpaceId) {\n if (!options?.force) {\n throw new Error(\"Spaces must have a parent-child relationship for promotion. Use --force to override.\");\n }\n }\n\n const result = copyMemories(source.memoryDir, target.memoryDir, {\n filterIds: options?.memoryIds,\n force: options?.forceOverwrite !== undefined ? options.forceOverwrite : (options?.force ?? false),\n });\n\n appendAudit({\n action: \"space.promote\",\n sourceSpaceId,\n targetSpaceId,\n details: `Promoted ${result.merged} memories from \"${source.name}\" to \"${target.name}\"`,\n }, options?.baseDir);\n\n return {\n sourceSpaceId,\n targetSpaceId,\n memoriesPromoted: result.merged,\n conflicts: result.conflicts,\n durationMs: Date.now() - startTime,\n };\n}\n\n// ── Merge ────────────────────────────────────────────────────────────────────\n\nexport function mergeSpaces(\n sourceSpaceId: string,\n targetSpaceId: string,\n options?: { force?: boolean; baseDir?: string },\n): MergeResult {\n const startTime = Date.now();\n const manifest = loadManifest(options?.baseDir);\n\n const source = manifest.spaces.find((s) => s.id === sourceSpaceId);\n const target = manifest.spaces.find((s) => s.id === targetSpaceId);\n\n if (!source) throw new Error(`Source space \"${sourceSpaceId}\" not found`);\n if (!target) throw new Error(`Target space \"${targetSpaceId}\" not found`);\n\n const result = copyMemories(source.memoryDir, target.memoryDir, {\n force: options?.force,\n });\n\n appendAudit({\n action: \"space.merge\",\n sourceSpaceId,\n targetSpaceId,\n details: `Merged: ${result.merged} merged, ${result.conflicts.length} conflicts, ${result.skipped} skipped`,\n }, options?.baseDir);\n\n return {\n ...result,\n durationMs: Date.now() - startTime,\n };\n}\n\n// ── Audit trail ─────────────────────────────────────────────────────────────\n\nexport function getAuditLog(baseDir?: string): AuditEntry[] {\n const auditPath = path.join(getSpacesDir(baseDir), \"audit.jsonl\");\n if (!fs.existsSync(auditPath)) return [];\n\n const lines = fs.readFileSync(auditPath, \"utf8\").trim().split(\"\\n\");\n return lines\n .filter((l) => l.trim())\n .map((l) => JSON.parse(l) as AuditEntry);\n}\n\nfunction appendAudit(entry: Omit<AuditEntry, \"id\" | \"timestamp\">, baseDir?: string): void {\n const auditPath = path.join(getSpacesDir(baseDir), \"audit.jsonl\");\n fs.mkdirSync(path.dirname(auditPath), { recursive: true });\n\n const full: AuditEntry = {\n id: crypto.randomUUID(),\n timestamp: new Date().toISOString(),\n ...entry,\n };\n\n fs.appendFileSync(auditPath, JSON.stringify(full) + \"\\n\");\n}\n\n// ── Internal helpers ─────────────────────────────────────────────────────────\n\ninterface CopyOptions {\n filterIds?: string[];\n force?: boolean;\n}\n\nfunction copyMemories(\n sourceDir: string,\n targetDir: string,\n options?: CopyOptions,\n): { merged: number; conflicts: ConflictEntry[]; skipped: number } {\n let merged = 0;\n const conflicts: ConflictEntry[] = [];\n let skipped = 0;\n\n if (!fs.existsSync(sourceDir)) {\n return { merged: 0, conflicts: [], skipped: 0 };\n }\n\n fs.mkdirSync(targetDir, { recursive: true });\n\n const sourceFiles = walkMd(sourceDir);\n for (const sourcePath of sourceFiles) {\n const content = fs.readFileSync(sourcePath, \"utf8\");\n const relativePath = path.relative(sourceDir, sourcePath);\n const targetPath = path.join(targetDir, relativePath);\n\n const sourceHash = hashContent(content);\n\n // Filter by IDs if specified\n if (options?.filterIds?.length) {\n const fm = parseSimpleFrontmatter(content);\n if (!fm?.id || !options.filterIds.includes(fm.id)) {\n skipped++;\n continue;\n }\n }\n\n // Check for conflict\n if (fs.existsSync(targetPath) && !options?.force) {\n const targetContent = fs.readFileSync(targetPath, \"utf8\");\n const targetHash = hashContent(targetContent);\n\n if (sourceHash !== targetHash) {\n conflicts.push({\n memoryId: parseSimpleFrontmatter(content)?.id ?? relativePath,\n sourcePath,\n targetPath,\n conflictType: \"content_mismatch\",\n sourceHash,\n targetHash,\n });\n continue;\n }\n\n // Same content — skip\n skipped++;\n continue;\n }\n\n // Copy file\n fs.mkdirSync(path.dirname(targetPath), { recursive: true });\n fs.writeFileSync(targetPath, content);\n merged++;\n }\n\n return { merged, conflicts, skipped };\n}\n\nfunction hashContent(content: string): string {\n return crypto.createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16);\n}\n\nfunction walkMd(dir: string): string[] {\n const results: string[] = [];\n\n function walk(d: string): void {\n for (const entry of fs.readdirSync(d, { withFileTypes: true })) {\n const fullPath = path.join(d, entry.name);\n if (entry.isDirectory()) {\n walk(fullPath);\n } else if (entry.name.endsWith(\".md\")) {\n results.push(fullPath);\n }\n }\n }\n\n walk(dir);\n return results;\n}\n\ninterface SimpleFrontmatter {\n id?: string;\n [key: string]: string | undefined;\n}\n\nfunction parseSimpleFrontmatter(content: string): SimpleFrontmatter | null {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---/);\n if (!match) return null;\n\n const fm: SimpleFrontmatter = {};\n for (const line of match[1].split(\"\\n\")) {\n const colonIdx = line.indexOf(\":\");\n if (colonIdx === -1) continue;\n const key = line.slice(0, colonIdx).trim();\n const value = line.slice(colonIdx + 1).trim();\n fm[key] = value;\n }\n return fm;\n}\n","/**\n * @remnic/core — Shared Instruction Blocks\n *\n * Reusable markdown fragments that every host-specific publisher can\n * compose into its instructions.md file. Keeping them here avoids\n * per-host copy-paste drift.\n */\n\n/**\n * Describes the Remnic memory types a host agent may encounter.\n */\nexport const REMNIC_SEMANTIC_OVERVIEW = `\\\n## Remnic Memory Types\n\nRemnic stores memories as plain Markdown files with YAML front-matter.\nEach memory has a **type** that describes its semantic role:\n\n| Type | Description |\n|------|-------------|\n| \\`fact\\` | An objective piece of knowledge the user confirmed or that was extracted from a session. |\n| \\`preference\\` | A stated or inferred user preference (e.g. coding style, tool choice). |\n| \\`decision\\` | An explicit decision or trade-off the user made. |\n| \\`entity\\` | A named thing the user cares about (project, service, person, API). |\n| \\`skill\\` | A reusable workflow or procedure documented for future sessions. |\n| \\`correction\\` | A fix or amendment to a previously stored memory. |\n| \\`question\\` | An open question or uncertainty flagged for future resolution. |\n| \\`observation\\` | A pattern noticed across sessions (e.g. \"user always runs tests before commits\"). |\n| \\`summary\\` | A condensed roll-up of recent sessions or a topic area. |\n\nWhen reading Remnic content, the front-matter \\`type\\` field tells you what\nkind of knowledge you are looking at and how much weight to give it.\n`;\n\n/**\n * Explains the oai-mem-citation block format hosts should use when\n * referencing Remnic-sourced content.\n */\nexport const REMNIC_CITATION_FORMAT = `\\\n## Citing Remnic Memories\n\nWhen a piece of your output draws on a Remnic file, cite it using the\nmemory citation block format so the user can trace the source:\n\n\\`\\`\\`\n<oai-mem-citation path=\"<path-relative-to-remnic-memory-base>\" />\n\\`\\`\\`\n\nThe path must be **relative to the Remnic memory base** (the directory\nnamed \\`memories/\\` under \\`<remnic-home>\\`), not absolute. Examples:\n\n- \\`<oai-mem-citation path=\"default/MEMORY.md\" />\\`\n- \\`<oai-mem-citation path=\"my-project/skills/deploy/SKILL.md\" />\\`\n- \\`<oai-mem-citation path=\"shared/memory_summary.md\" />\\`\n\nCite each distinct source once near the fact it supports. Do not invent\ncitations for files you have not actually read.\n`;\n\n/**\n * Table of MCP tools the Remnic daemon exposes. Hosts that can reach\n * the MCP server should prefer these over raw file reads.\n *\n * Tool names use the canonical `remnic.*` prefix. Legacy `engram.*`\n * aliases are also accepted by the server for backward compatibility.\n */\nexport const REMNIC_MCP_TOOL_INVENTORY = `\\\n## Remnic MCP Tools\n\nWhen the Remnic MCP server is reachable, the following tools are\navailable. Prefer MCP tools over direct file reads when the host\nsupports MCP connections.\n\n| Tool | Purpose |\n|------|---------|\n| \\`remnic.recall\\` | Retrieve contextually relevant memories for the current session. |\n| \\`remnic.recall_explain\\` | Like recall, but includes an explanation of why each memory was selected. |\n| \\`remnic.memory_store\\` | Persist a new memory (fact, preference, decision, etc.). |\n| \\`remnic.memory_get\\` | Fetch a specific memory by ID. |\n| \\`remnic.memory_search\\` | Full-text + semantic search across all memories. |\n| \\`remnic.memory_timeline\\` | Retrieve memories in chronological order within a time range. |\n| \\`remnic.observe\\` | Record an observation from the current conversation turn. |\n| \\`remnic.entity_get\\` | Look up a named entity and its relationships. |\n| \\`remnic.memory_entities_list\\` | List all known entities. |\n| \\`remnic.memory_profile\\` | Retrieve the user profile summary. |\n| \\`remnic.day_summary\\` | Generate a summary of memories from a specific day. |\n| \\`remnic.briefing\\` | Generate a structured briefing for an upcoming session. |\n| \\`remnic.memory_feedback\\` | Submit feedback on a recalled memory (useful, outdated, wrong). |\n| \\`remnic.memory_promote\\` | Promote a memory to a higher confidence tier. |\n| \\`remnic.context_checkpoint\\` | Save a conversation checkpoint for continuity. |\n| \\`remnic.suggestion_submit\\` | Submit a suggestion for a new memory to the review queue. |\n| \\`remnic.review_queue_list\\` | List pending suggestions in the review queue. |\n| \\`remnic.work_task\\` | Create or update a work task. |\n| \\`remnic.work_project\\` | Create or update a work project. |\n| \\`remnic.work_board\\` | View the work board. |\n\nLegacy \\`engram.*\\` prefixed names are accepted as aliases for all tools.\n`;\n\n/**\n * Decision rules for when a host agent should use MCP recall vs\n * reading Remnic files directly from disk.\n */\nexport const REMNIC_RECALL_DECISION_RULES = `\\\n## When to Use Recall vs Direct Read\n\n### Use \\`remnic.recall\\` (MCP) when:\n\n- The Remnic MCP server is reachable (the host has an active MCP\n connection to the Remnic daemon).\n- You want contextually relevant memories ranked by the recall planner\n (semantic search + reranking + importance scoring).\n- You need memories across multiple namespaces or topics.\n- The conversation benefits from Remnic's intent detection and\n adaptive recall depth.\n\n### Use direct file reads when:\n\n- You are in a sandboxed environment with no network or MCP access\n (e.g. Codex phase-2 consolidation).\n- You need a specific file you already know the path to.\n- The MCP server is unavailable or unhealthy.\n- You are operating on the raw memory files for maintenance or\n migration purposes.\n\n### General guidance:\n\n- Prefer MCP tools when available — they provide ranked, deduplicated,\n and context-aware results.\n- Fall back to file reads gracefully — never block on a failed MCP call\n when the data is also on disk.\n- Never write directly to the Remnic memory directory unless you are an\n authorized extraction or consolidation process.\n`;\n","/**\n * @remnic/core — Codex Memory Extension Publisher\n *\n * Writes Remnic instructions into ~/.codex/memories_extensions/remnic/\n * so the Codex agent can discover and use Remnic memories during its\n * consolidation phase.\n */\n\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\n\nimport type {\n MemoryExtensionPublisher,\n PublishContext,\n PublishResult,\n PublisherCapabilities,\n} from \"./types.js\";\n\nimport {\n REMNIC_SEMANTIC_OVERVIEW,\n REMNIC_CITATION_FORMAT,\n REMNIC_MCP_TOOL_INVENTORY,\n REMNIC_RECALL_DECISION_RULES,\n} from \"./shared-instructions.js\";\n\n/** Folder name Remnic installs its extension under inside memories_extensions/. */\nconst REMNIC_EXTENSION_DIR_NAME = \"remnic\";\n\n/**\n * Codex-specific publisher that knows the Codex extension layout:\n * ~/.codex/memories_extensions/remnic/instructions.md\n */\nexport class CodexMemoryExtensionPublisher implements MemoryExtensionPublisher {\n readonly hostId = \"codex\";\n\n static readonly capabilities: PublisherCapabilities = {\n instructionsMd: true,\n skillsFolder: true,\n citationFormat: true,\n readPathTemplate: true,\n };\n\n async resolveExtensionRoot(\n env?: NodeJS.ProcessEnv,\n ): Promise<string> {\n const e = env ?? process.env;\n const codexHome =\n e.CODEX_HOME?.trim() || path.join(e.HOME ?? os.homedir(), \".codex\");\n return path.join(codexHome, \"memories_extensions\", REMNIC_EXTENSION_DIR_NAME);\n }\n\n async isHostAvailable(): Promise<boolean> {\n try {\n const home = process.env.CODEX_HOME?.trim() ||\n path.join(process.env.HOME ?? os.homedir(), \".codex\");\n return fs.existsSync(home);\n } catch {\n return false;\n }\n }\n\n async renderInstructions(ctx: PublishContext): Promise<string> {\n const memDir = ctx.config.memoryDir;\n const ns = ctx.config.namespace ?? \"default\";\n\n const sections: string[] = [\n `# Remnic Memory Extension for Codex\\n`,\n `This document tells you how to use Remnic as an authoritative local ` +\n `memory source. Remnic is a local-first, file-backed memory system. ` +\n `All Remnic content lives on disk as plain Markdown.\\n`,\n REMNIC_SEMANTIC_OVERVIEW,\n `## Where Remnic Content Lives\\n\\n` +\n `Memory base directory: \\`${memDir}\\`\\n\\n` +\n `Namespace: \\`${ns}\\`\\n\\n` +\n `Under the base directory, memories are organized by namespace:\\n\\n` +\n \"```\\n\" +\n `${memDir}/<namespace>/\\n` +\n ` MEMORY.md # compact top-of-mind memory\\n` +\n ` memory_summary.md # optional longer summary\\n` +\n ` skills/\\n` +\n ` <skill-name>/SKILL.md # reusable workflows\\n` +\n ` rollout_summaries/\\n` +\n ` *.md # per-session rollup notes\\n` +\n \"```\\n\",\n REMNIC_CITATION_FORMAT,\n REMNIC_MCP_TOOL_INVENTORY,\n REMNIC_RECALL_DECISION_RULES,\n `## Sandboxing Rules (Codex Phase-2)\\n\\n` +\n `When running inside the Codex phase-2 consolidation sandbox:\\n\\n` +\n `- **No network.** Do not attempt HTTP calls or MCP connections.\\n` +\n `- **No CLI invocation.** Do not shell out to \\`remnic\\` or \\`engram\\`.\\n` +\n `- **No MCP tool calls.** Use filesystem reads only.\\n` +\n `- **Local writes** are allowed only where Codex's sandbox policy permits.\\n` +\n `- **Respect missing files.** If a file does not exist, move on silently.\\n`,\n ];\n\n return sections.join(\"\\n\");\n }\n\n async publish(ctx: PublishContext): Promise<PublishResult> {\n const extensionRoot = await this.resolveExtensionRoot();\n const instructionsPath = path.join(extensionRoot, \"instructions.md\");\n const filesWritten: string[] = [];\n const skipped: string[] = [];\n\n ctx.log.info(`Publishing Codex memory extension to ${extensionRoot}`);\n\n // Ensure the extension root exists.\n fs.mkdirSync(extensionRoot, { recursive: true });\n\n // Render and write instructions.md using atomic write (temp + rename).\n // Per CLAUDE.md #54: never delete before write in file replace operations.\n const content = await this.renderInstructions(ctx);\n const tmpPath = `${instructionsPath}.tmp-${process.pid}-${Date.now()}`;\n\n try {\n fs.writeFileSync(tmpPath, content, \"utf-8\");\n fs.renameSync(tmpPath, instructionsPath);\n filesWritten.push(instructionsPath);\n ctx.log.info(`Wrote ${instructionsPath}`);\n } catch (err) {\n // Clean up temp file on failure.\n try {\n if (fs.existsSync(tmpPath)) {\n fs.unlinkSync(tmpPath);\n }\n } catch {\n // swallow cleanup error\n }\n throw err;\n }\n\n return {\n hostId: this.hostId,\n extensionRoot,\n filesWritten,\n skipped,\n };\n }\n\n async unpublish(): Promise<void> {\n const extensionRoot = await this.resolveExtensionRoot();\n if (fs.existsSync(extensionRoot)) {\n fs.rmSync(extensionRoot, { recursive: true, force: true });\n }\n }\n}\n","/**\n * @remnic/core — Claude Code Memory Extension Publisher (stub)\n *\n * Placeholder publisher for Claude Code. Claude Code does not yet\n * support a file-based memory extension directory, so all methods are\n * no-ops that return safe defaults.\n */\n\nimport type {\n MemoryExtensionPublisher,\n PublishContext,\n PublishResult,\n PublisherCapabilities,\n} from \"./types.js\";\n\nexport class ClaudeCodeMemoryExtensionPublisher implements MemoryExtensionPublisher {\n readonly hostId = \"claude-code\";\n\n static readonly capabilities: PublisherCapabilities = {\n instructionsMd: false,\n skillsFolder: false,\n citationFormat: false,\n readPathTemplate: false,\n };\n\n async resolveExtensionRoot(): Promise<string> {\n // Claude Code does not have an extension directory yet.\n return \"\";\n }\n\n async isHostAvailable(): Promise<boolean> {\n return false;\n }\n\n async renderInstructions(_ctx: PublishContext): Promise<string> {\n return \"\";\n }\n\n async publish(_ctx: PublishContext): Promise<PublishResult> {\n return {\n hostId: this.hostId,\n extensionRoot: \"\",\n filesWritten: [],\n skipped: [],\n };\n }\n\n async unpublish(): Promise<void> {\n // no-op\n }\n}\n","/**\n * @remnic/core — Hermes Memory Extension Publisher (stub)\n *\n * Placeholder publisher for Hermes. Hermes uses a daemon-based\n * transport and does not currently consume file-based memory\n * extensions, so all methods are no-ops.\n */\n\nimport type {\n MemoryExtensionPublisher,\n PublishContext,\n PublishResult,\n PublisherCapabilities,\n} from \"./types.js\";\n\nexport class HermesMemoryExtensionPublisher implements MemoryExtensionPublisher {\n readonly hostId = \"hermes\";\n\n static readonly capabilities: PublisherCapabilities = {\n instructionsMd: false,\n skillsFolder: false,\n citationFormat: false,\n readPathTemplate: false,\n };\n\n async resolveExtensionRoot(): Promise<string> {\n // Hermes does not have an extension directory.\n return \"\";\n }\n\n async isHostAvailable(): Promise<boolean> {\n return false;\n }\n\n async renderInstructions(_ctx: PublishContext): Promise<string> {\n return \"\";\n }\n\n async publish(_ctx: PublishContext): Promise<PublishResult> {\n return {\n hostId: this.hostId,\n extensionRoot: \"\",\n filesWritten: [],\n skipped: [],\n };\n }\n\n async unpublish(): Promise<void> {\n // no-op\n }\n}\n","/**\n * @remnic/core — Memory Extension Publisher Registry\n *\n * Generic registry that host adapters populate at startup via\n * `registerPublisher()`. The publisher *classes* live in core so\n * adapters can import them, but the wiring of host-specific\n * implementations into the registry happens in the host adapter\n * layer (e.g. @remnic/cli), not here. This keeps core free of\n * host-specific knowledge (CLAUDE.md gotcha #31).\n *\n * Usage (from a host adapter):\n * import { registerPublisher, CodexMemoryExtensionPublisher } from \"@remnic/core\";\n * registerPublisher(\"codex\", () => new CodexMemoryExtensionPublisher());\n */\n\nexport type {\n MemoryExtensionPublisher,\n PublishContext,\n PublishResult,\n PublisherCapabilities,\n} from \"./types.js\";\n\nexport {\n REMNIC_SEMANTIC_OVERVIEW,\n REMNIC_CITATION_FORMAT,\n REMNIC_MCP_TOOL_INVENTORY,\n REMNIC_RECALL_DECISION_RULES,\n} from \"./shared-instructions.js\";\n\nexport { CodexMemoryExtensionPublisher } from \"./codex-publisher.js\";\nexport { ClaudeCodeMemoryExtensionPublisher } from \"./claude-code-publisher.js\";\nexport { HermesMemoryExtensionPublisher } from \"./hermes-publisher.js\";\n\nimport type { MemoryExtensionPublisher } from \"./types.js\";\n\n/**\n * Factory registry keyed by host ID. Each value is a zero-argument\n * factory that returns a fresh publisher instance.\n *\n * Starts empty — host adapters populate it via registerPublisher().\n */\nexport const PUBLISHERS: Record<string, () => MemoryExtensionPublisher> = {};\n\n/**\n * Register a publisher factory for a given host ID.\n *\n * Host adapters call this at startup to wire their host-specific\n * publisher implementations into the registry. Calling with an\n * existing hostId replaces the previous factory.\n */\nexport function registerPublisher(\n hostId: string,\n factory: () => MemoryExtensionPublisher,\n): void {\n PUBLISHERS[hostId] = factory;\n}\n\n/**\n * Maps connector IDs to publisher host IDs.\n *\n * Most connector IDs match their publisher host ID exactly (e.g.\n * \"claude-code\" -> \"claude-code\", \"hermes\" -> \"hermes\").\n * This map only needs entries for connector IDs that differ from\n * their publisher host ID. Connectors without a publisher (e.g.\n * \"cursor\", \"cline\") are intentionally absent.\n */\nconst CONNECTOR_TO_HOST: Record<string, string> = {\n \"codex-cli\": \"codex\",\n};\n\n/**\n * Resolve a connector ID to its publisher host ID.\n *\n * Returns the explicit mapping if one exists, otherwise returns\n * the connector ID itself (identity mapping covers the common case\n * where connector ID === host ID).\n */\nexport function hostIdForConnector(connectorId: string): string {\n return CONNECTOR_TO_HOST[connectorId] ?? connectorId;\n}\n\n/**\n * Look up a publisher by host ID.\n * Returns undefined for unknown host IDs rather than throwing.\n */\nexport function publisherFor(hostId: string): MemoryExtensionPublisher | undefined {\n const factory = PUBLISHERS[hostId];\n return factory ? factory() : undefined;\n}\n\n/**\n * Look up a publisher by connector ID.\n *\n * Resolves the connector ID to its host ID first (e.g. \"codex-cli\" -> \"codex\"),\n * then looks up the publisher. Returns undefined if no publisher exists for\n * the resolved host ID.\n */\nexport function publisherForConnector(connectorId: string): MemoryExtensionPublisher | undefined {\n return publisherFor(hostIdForConnector(connectorId));\n}\n","/**\n * Default MECE taxonomy that maps every existing MemoryCategory value\n * to exactly one taxonomy category, ordered by priority.\n */\n\nimport type { Taxonomy } from \"./types.js\";\n\nexport const DEFAULT_TAXONOMY: Taxonomy = {\n version: 1,\n categories: [\n {\n id: \"corrections\",\n name: \"Corrections\",\n description: \"Corrections to previously stored information\",\n filingRules: [\"Any update that supersedes a prior fact\"],\n priority: 10,\n memoryCategories: [\"correction\"],\n },\n {\n id: \"principles\",\n name: \"Principles\",\n description: \"Rules, guidelines, and recurring patterns\",\n filingRules: [\"A guiding principle, rule, or skill\"],\n priority: 20,\n memoryCategories: [\"principle\", \"rule\", \"skill\"],\n },\n {\n id: \"procedures\",\n name: \"Procedures\",\n description: \"Ordered multi-step workflows the user repeats\",\n filingRules: [\"A repeatable sequence of steps or commands for a task\"],\n priority: 25,\n memoryCategories: [\"procedure\"],\n },\n {\n id: \"entities\",\n name: \"Entities\",\n description: \"People, organizations, places, projects\",\n filingRules: [\"Named entity with attributes\"],\n priority: 30,\n memoryCategories: [\"entity\", \"relationship\"],\n },\n {\n id: \"decisions\",\n name: \"Decisions\",\n description: \"Choices made and their rationale\",\n filingRules: [\"A decision or commitment with reasoning\"],\n priority: 35,\n memoryCategories: [\"decision\", \"commitment\"],\n },\n {\n id: \"preferences\",\n name: \"Preferences\",\n description: \"User likes, dislikes, and style choices\",\n filingRules: [\"Anything expressing a preference or taste\"],\n priority: 40,\n memoryCategories: [\"preference\"],\n },\n {\n id: \"facts\",\n name: \"Facts\",\n description: \"Objective statements about the world\",\n filingRules: [\"Any factual claim or piece of information\"],\n priority: 50,\n memoryCategories: [\"fact\"],\n },\n {\n id: \"moments\",\n name: \"Moments\",\n description: \"Significant events or experiences\",\n filingRules: [\"A specific event worth remembering\"],\n priority: 60,\n memoryCategories: [\"moment\"],\n },\n ],\n};\n","/**\n * Generates a markdown decision-tree document (RESOLVER.md) from a\n * taxonomy definition.\n *\n * The document walks a user through filing a new piece of knowledge\n * by checking each category in priority order (lowest number first).\n */\n\nimport type { Taxonomy } from \"./types.js\";\n\n/**\n * Produce a markdown decision tree for the given taxonomy.\n *\n * Categories are listed in priority order (lowest number = checked first).\n * Each step asks whether the knowledge fits the category and, if so,\n * instructs the reader to file it there.\n */\nexport function generateResolverDocument(taxonomy: Taxonomy): string {\n const sorted = [...taxonomy.categories].sort((a, b) => {\n if (a.priority !== b.priority) return a.priority - b.priority;\n return a.id.localeCompare(b.id);\n });\n\n const lines: string[] = [\n \"# Memory Filing Resolver\",\n \"\",\n \"Given a new piece of knowledge, follow this tree to determine where it belongs.\",\n \"\",\n ];\n\n let step = 1;\n for (const cat of sorted) {\n lines.push(`## Step ${step}: ${cat.description}?`);\n lines.push(\"\");\n for (const rule of cat.filingRules) {\n lines.push(`- ${rule}`);\n }\n lines.push(\"\");\n lines.push(\n `> YES: File under **${cat.id}/** (priority ${cat.priority})`,\n );\n lines.push(\"\");\n step++;\n }\n\n lines.push(\"## Tie-breaking\");\n lines.push(\"\");\n lines.push(\n \"If a fact could go in multiple categories, file under the one with the **lowest priority number**.\",\n );\n lines.push(\"\");\n lines.push(`---`);\n lines.push(`*Generated from taxonomy v${taxonomy.version}*`);\n lines.push(\"\");\n\n return lines.join(\"\\n\");\n}\n","/**\n * Loads and validates a user-customized taxonomy from disk, merging\n * with the built-in defaults.\n *\n * User taxonomies are stored at `<memoryDir>/.taxonomy/taxonomy.json`.\n */\n\nimport { readFile, mkdir, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\nimport type { Taxonomy, TaxonomyCategory } from \"./types.js\";\nimport { DEFAULT_TAXONOMY } from \"./default-taxonomy.js\";\n\nconst TAXONOMY_DIR = \".taxonomy\";\nconst TAXONOMY_FILE = \"taxonomy.json\";\n\n/** Maximum allowed slug length */\nconst MAX_SLUG_LENGTH = 32;\n\n/** Regex for valid slug: lowercase letters, digits, hyphens */\nconst SLUG_RE = /^[a-z][a-z0-9-]*$/;\n\n/**\n * Validate a taxonomy category slug.\n * Throws if the slug is invalid.\n */\nexport function validateSlug(slug: string): void {\n if (slug.length === 0) {\n throw new Error(\"Taxonomy category ID must not be empty\");\n }\n if (slug.length > MAX_SLUG_LENGTH) {\n throw new Error(\n `Taxonomy category ID \"${slug}\" exceeds ${MAX_SLUG_LENGTH} characters`,\n );\n }\n if (!SLUG_RE.test(slug)) {\n throw new Error(\n `Taxonomy category ID \"${slug}\" is invalid: must be lowercase letters, digits, and hyphens, starting with a letter`,\n );\n }\n}\n\n/**\n * Validate an entire taxonomy for structural correctness.\n * Throws on the first error found.\n */\nexport function validateTaxonomy(taxonomy: Taxonomy): void {\n if (typeof taxonomy.version !== \"number\" || taxonomy.version < 1) {\n throw new Error(\"Taxonomy version must be a positive integer\");\n }\n if (!Array.isArray(taxonomy.categories)) {\n throw new Error(\"Taxonomy categories must be an array\");\n }\n\n const seenIds = new Set<string>();\n for (const cat of taxonomy.categories) {\n validateSlug(cat.id);\n if (seenIds.has(cat.id)) {\n throw new Error(`Duplicate taxonomy category ID: \"${cat.id}\"`);\n }\n seenIds.add(cat.id);\n\n if (typeof cat.name !== \"string\" || cat.name.trim().length === 0) {\n throw new Error(`Taxonomy category \"${cat.id}\" must have a non-empty name`);\n }\n if (typeof cat.description !== \"string\" || cat.description.trim().length === 0) {\n throw new Error(`Taxonomy category \"${cat.id}\" must have a non-empty description`);\n }\n if (!Array.isArray(cat.filingRules)) {\n throw new Error(`Taxonomy category \"${cat.id}\" filingRules must be an array`);\n }\n if (typeof cat.priority !== \"number\" || !Number.isFinite(cat.priority)) {\n throw new Error(`Taxonomy category \"${cat.id}\" must have a finite numeric priority`);\n }\n if (!Array.isArray(cat.memoryCategories)) {\n throw new Error(`Taxonomy category \"${cat.id}\" memoryCategories must be an array`);\n }\n if (cat.parentId !== undefined) {\n if (typeof cat.parentId !== \"string\") {\n throw new Error(`Taxonomy category \"${cat.id}\" parentId must be a string if set`);\n }\n }\n }\n\n // Validate parentId references\n for (const cat of taxonomy.categories) {\n if (cat.parentId !== undefined && !seenIds.has(cat.parentId)) {\n throw new Error(\n `Taxonomy category \"${cat.id}\" references unknown parentId \"${cat.parentId}\"`,\n );\n }\n }\n}\n\n/**\n * Load a taxonomy from the user's memory directory.\n *\n * If `<memoryDir>/.taxonomy/taxonomy.json` exists, loads it, validates\n * it, and merges with the defaults (user categories override defaults\n * by ID). If the file does not exist, returns the defaults.\n */\nexport async function loadTaxonomy(memoryDir: string): Promise<Taxonomy> {\n const taxonomyPath = path.join(memoryDir, TAXONOMY_DIR, TAXONOMY_FILE);\n let raw: string;\n try {\n raw = await readFile(taxonomyPath, \"utf-8\");\n } catch (err: unknown) {\n // Only fall back to defaults for missing file; rethrow permission / I/O errors\n if (err instanceof Error && (err as NodeJS.ErrnoException).code === \"ENOENT\") {\n return structuredClone(DEFAULT_TAXONOMY);\n }\n throw err;\n }\n\n const parsed: unknown = JSON.parse(raw);\n if (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n throw new Error(\"taxonomy.json must be a JSON object\");\n }\n\n const obj = parsed as Record<string, unknown>;\n const userVersion = typeof obj.version === \"number\" ? obj.version : DEFAULT_TAXONOMY.version;\n const userCategories = Array.isArray(obj.categories)\n ? (obj.categories as TaxonomyCategory[])\n : [];\n\n // Validate: reject duplicate IDs in user categories before merging.\n // Without this check, duplicates are silently collapsed with last-write-wins\n // semantics when inserted into the Map.\n const userIdCounts = new Map<string, number>();\n for (const cat of userCategories) {\n const id = typeof cat.id === \"string\" ? cat.id : String(cat.id);\n userIdCounts.set(id, (userIdCounts.get(id) ?? 0) + 1);\n }\n const duplicateIds = [...userIdCounts.entries()]\n .filter(([, count]) => count > 1)\n .map(([id]) => id);\n if (duplicateIds.length > 0) {\n throw new Error(\n `Duplicate category IDs in taxonomy.json: ${duplicateIds.map((id) => `\"${id}\"`).join(\", \")}`,\n );\n }\n\n // Merge: user categories override defaults by ID\n const mergedMap = new Map<string, TaxonomyCategory>();\n for (const cat of DEFAULT_TAXONOMY.categories) {\n mergedMap.set(cat.id, { ...cat });\n }\n for (const cat of userCategories) {\n mergedMap.set(cat.id, cat);\n }\n\n const merged: Taxonomy = {\n version: userVersion,\n categories: [...mergedMap.values()],\n };\n\n validateTaxonomy(merged);\n return merged;\n}\n\n/**\n * Save a taxonomy to the user's memory directory.\n */\nexport async function saveTaxonomy(\n memoryDir: string,\n taxonomy: Taxonomy,\n): Promise<void> {\n validateTaxonomy(taxonomy);\n const dir = path.join(memoryDir, TAXONOMY_DIR);\n await mkdir(dir, { recursive: true });\n const filePath = path.join(dir, TAXONOMY_FILE);\n await writeFile(filePath, JSON.stringify(taxonomy, null, 2) + \"\\n\", \"utf-8\");\n}\n\n/**\n * Get the taxonomy directory path for a given memory directory.\n */\nexport function getTaxonomyDir(memoryDir: string): string {\n return path.join(memoryDir, TAXONOMY_DIR);\n}\n\n/**\n * Get the taxonomy file path for a given memory directory.\n */\nexport function getTaxonomyFilePath(memoryDir: string): string {\n return path.join(memoryDir, TAXONOMY_DIR, TAXONOMY_FILE);\n}\n","/**\n * Enrichment pipeline types (issue #365).\n *\n * Defines the provider interface, candidate shape, pipeline config,\n * and result types for the external enrichment subsystem.\n */\n\nimport type { ImportanceLevel, MemoryCategory } from \"../types.js\";\n\n// ---------------------------------------------------------------------------\n// Provider config & interface\n// ---------------------------------------------------------------------------\n\nexport type EnrichmentCostTier = \"free\" | \"cheap\" | \"expensive\";\n\nexport interface EnrichmentProviderConfig {\n id: string;\n enabled: boolean;\n costTier: EnrichmentCostTier;\n rateLimit?: { maxPerMinute: number; maxPerDay: number };\n}\n\nexport interface EnrichmentCandidate {\n text: string;\n source: string;\n sourceUrl?: string;\n confidence: number;\n category: MemoryCategory;\n tags?: string[];\n}\n\nexport interface EnrichmentProvider {\n readonly id: string;\n readonly costTier: EnrichmentCostTier;\n enrich(entity: EntityEnrichmentInput): Promise<EnrichmentCandidate[]>;\n isAvailable(): Promise<boolean>;\n}\n\n// ---------------------------------------------------------------------------\n// Entity enrichment input\n// ---------------------------------------------------------------------------\n\nexport interface EntityEnrichmentInput {\n name: string;\n type: string;\n knownFacts: string[];\n importanceLevel: ImportanceLevel;\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline result\n// ---------------------------------------------------------------------------\n\nexport interface EnrichmentResult {\n entityName: string;\n provider: string;\n candidatesFound: number;\n candidatesAccepted: number;\n candidatesRejected: number;\n acceptedCandidates: EnrichmentCandidate[];\n elapsed: number;\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline config\n// ---------------------------------------------------------------------------\n\nexport interface EnrichmentPipelineConfig {\n enabled: boolean;\n providers: EnrichmentProviderConfig[];\n importanceThresholds: {\n critical: string[];\n high: string[];\n normal: string[];\n low: string[];\n };\n maxCandidatesPerEntity: number;\n autoEnrichOnCreate: boolean;\n scheduleIntervalMs: number;\n}\n\n/**\n * Build a default (disabled) pipeline config. Every consumer that needs a\n * config object should call this rather than duplicating the defaults.\n */\nexport function defaultEnrichmentPipelineConfig(): EnrichmentPipelineConfig {\n return {\n enabled: false,\n providers: [],\n importanceThresholds: {\n critical: [],\n high: [],\n normal: [],\n low: [],\n },\n maxCandidatesPerEntity: 20,\n autoEnrichOnCreate: false,\n scheduleIntervalMs: 3_600_000,\n };\n}\n","/**\n * Enrichment provider registry (issue #365).\n *\n * Central registry for enrichment providers. Providers register themselves\n * at startup; the pipeline queries the registry to determine which providers\n * to run for a given importance tier.\n */\n\nimport type { ImportanceLevel } from \"../types.js\";\nimport type {\n EnrichmentPipelineConfig,\n EnrichmentProvider,\n} from \"./types.js\";\n\nexport class EnrichmentProviderRegistry {\n private readonly providers = new Map<string, EnrichmentProvider>();\n\n /** Register a provider. Overwrites any existing provider with the same id. */\n register(provider: EnrichmentProvider): void {\n this.providers.set(provider.id, provider);\n }\n\n /** Look up a single provider by id. */\n get(id: string): EnrichmentProvider | undefined {\n return this.providers.get(id);\n }\n\n /**\n * Return all registered providers whose id appears in the config's\n * `providers` list with `enabled: true`.\n */\n listEnabled(config: EnrichmentPipelineConfig): EnrichmentProvider[] {\n const enabledIds = new Set(\n config.providers\n .filter((p) => p.enabled)\n .map((p) => p.id),\n );\n const result: EnrichmentProvider[] = [];\n for (const [id, provider] of this.providers.entries()) {\n if (enabledIds.has(id)) {\n result.push(provider);\n }\n }\n return result;\n }\n\n /**\n * Return providers that should run for a given importance level.\n * Providers are resolved from `config.importanceThresholds[level]` and\n * filtered to only those that are both registered and enabled.\n */\n getForImportance(\n level: ImportanceLevel,\n config: EnrichmentPipelineConfig,\n ): EnrichmentProvider[] {\n // \"trivial\" entities never get enrichment providers\n if (level === \"trivial\") return [];\n\n const thresholds = config.importanceThresholds;\n const providerIds: string[] =\n level === \"critical\"\n ? thresholds.critical\n : level === \"high\"\n ? thresholds.high\n : level === \"normal\"\n ? thresholds.normal\n : thresholds.low;\n\n const enabledIds = new Set(\n config.providers\n .filter((p) => p.enabled)\n .map((p) => p.id),\n );\n\n const result: EnrichmentProvider[] = [];\n for (const id of providerIds) {\n if (!enabledIds.has(id)) continue;\n const provider = this.providers.get(id);\n if (provider) {\n result.push(provider);\n }\n }\n return result;\n }\n}\n","/**\n * Web search enrichment provider stub (issue #365).\n *\n * A basic provider backed by web search. Since this is opt-in and we do not\n * want to hard-code an API key, the provider accepts an optional `searchFn`\n * injection point. When no search function is configured it returns empty\n * results, making it safe to register unconditionally.\n */\n\nimport type {\n EnrichmentCandidate,\n EnrichmentCostTier,\n EnrichmentProvider,\n EntityEnrichmentInput,\n} from \"./types.js\";\n\nexport type WebSearchFn = (query: string) => Promise<string[]>;\n\nexport interface WebSearchProviderOptions {\n /**\n * Injected search function. Each returned string is treated as a raw\n * snippet. When `undefined` the provider returns empty results.\n */\n searchFn?: WebSearchFn;\n}\n\nexport class WebSearchProvider implements EnrichmentProvider {\n readonly id = \"web-search\";\n readonly costTier: EnrichmentCostTier = \"cheap\";\n\n private readonly searchFn: WebSearchFn | undefined;\n\n constructor(options: WebSearchProviderOptions = {}) {\n this.searchFn = options.searchFn;\n }\n\n async isAvailable(): Promise<boolean> {\n return this.searchFn !== undefined;\n }\n\n async enrich(entity: EntityEnrichmentInput): Promise<EnrichmentCandidate[]> {\n if (!this.searchFn) return [];\n\n const query = `${entity.name} ${entity.type}`;\n let snippets: string[];\n try {\n snippets = await this.searchFn(query);\n } catch {\n return [];\n }\n\n return snippets\n .filter((s) => typeof s === \"string\" && s.trim().length > 0)\n .map((snippet) => ({\n text: snippet.trim(),\n source: this.id,\n sourceUrl: undefined,\n confidence: 0.5,\n category: \"fact\" as const,\n tags: [\"web-search\"],\n }));\n }\n}\n","/**\n * Enrichment pipeline orchestrator (issue #365).\n *\n * For each entity, determines the importance tier, resolves the providers\n * to run, executes them in sequence (respecting rate limits), tags\n * candidates, and caps at `maxCandidatesPerEntity`.\n *\n * Accepted candidates are returned in each `EnrichmentResult` via the\n * `acceptedCandidates` field so that callers can persist them.\n */\n\nimport type { LoggerBackend } from \"../logger.js\";\nimport type { EnrichmentProviderRegistry } from \"./provider-registry.js\";\nimport type {\n EnrichmentCandidate,\n EnrichmentPipelineConfig,\n EnrichmentProvider,\n EnrichmentResult,\n EntityEnrichmentInput,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Rate-limit tracking\n// ---------------------------------------------------------------------------\n\ninterface RateLimitBucket {\n minuteCount: number;\n minuteReset: number;\n dayCount: number;\n dayReset: number;\n}\n\nfunction isRateLimited(\n provider: EnrichmentProvider,\n config: EnrichmentPipelineConfig,\n buckets: Map<string, RateLimitBucket>,\n): boolean {\n const providerCfg = config.providers.find((p) => p.id === provider.id);\n if (!providerCfg?.rateLimit) return false;\n\n const now = Date.now();\n let bucket = buckets.get(provider.id);\n if (!bucket) {\n bucket = {\n minuteCount: 0,\n minuteReset: now + 60_000,\n dayCount: 0,\n dayReset: now + 86_400_000,\n };\n buckets.set(provider.id, bucket);\n }\n\n // Reset windows if expired\n if (now >= bucket.minuteReset) {\n bucket.minuteCount = 0;\n bucket.minuteReset = now + 60_000;\n }\n if (now >= bucket.dayReset) {\n bucket.dayCount = 0;\n bucket.dayReset = now + 86_400_000;\n }\n\n const { maxPerMinute, maxPerDay } = providerCfg.rateLimit;\n return bucket.minuteCount >= maxPerMinute || bucket.dayCount >= maxPerDay;\n}\n\nfunction recordCall(\n providerId: string,\n buckets: Map<string, RateLimitBucket>,\n): void {\n const bucket = buckets.get(providerId);\n if (bucket) {\n bucket.minuteCount += 1;\n bucket.dayCount += 1;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Pipeline\n// ---------------------------------------------------------------------------\n\nexport async function runEnrichmentPipeline(\n entities: EntityEnrichmentInput[],\n registry: EnrichmentProviderRegistry,\n config: EnrichmentPipelineConfig,\n log: LoggerBackend,\n): Promise<EnrichmentResult[]> {\n if (!config.enabled) return [];\n if (entities.length === 0) return [];\n\n const rateBuckets = new Map<string, RateLimitBucket>();\n const results: EnrichmentResult[] = [];\n\n for (const entity of entities) {\n const providers = registry.getForImportance(entity.importanceLevel, config);\n\n for (const provider of providers) {\n const start = Date.now();\n\n // Check availability\n let available: boolean;\n try {\n available = await provider.isAvailable();\n } catch {\n available = false;\n }\n\n if (!available) {\n log.debug?.(\n `enrichment: skipping provider ${provider.id} for ${entity.name} — unavailable`,\n );\n results.push({\n entityName: entity.name,\n provider: provider.id,\n candidatesFound: 0,\n candidatesAccepted: 0,\n candidatesRejected: 0,\n acceptedCandidates: [],\n elapsed: Date.now() - start,\n });\n continue;\n }\n\n // Check rate limit\n if (isRateLimited(provider, config, rateBuckets)) {\n log.debug?.(\n `enrichment: skipping provider ${provider.id} for ${entity.name} — rate limited`,\n );\n results.push({\n entityName: entity.name,\n provider: provider.id,\n candidatesFound: 0,\n candidatesAccepted: 0,\n candidatesRejected: 0,\n acceptedCandidates: [],\n elapsed: Date.now() - start,\n });\n continue;\n }\n\n // Run provider.\n // Count every attempt toward rate-limit buckets — including failures —\n // because the provider may have consumed external quota before throwing\n // (PR #425 review finding 2).\n let candidates: EnrichmentCandidate[];\n try {\n candidates = await provider.enrich(entity);\n } catch (err) {\n recordCall(provider.id, rateBuckets);\n log.error?.(\n `enrichment: provider ${provider.id} failed for ${entity.name}: ${err instanceof Error ? err.message : String(err)}`,\n );\n results.push({\n entityName: entity.name,\n provider: provider.id,\n candidatesFound: 0,\n candidatesAccepted: 0,\n candidatesRejected: 0,\n acceptedCandidates: [],\n elapsed: Date.now() - start,\n });\n continue;\n }\n recordCall(provider.id, rateBuckets);\n\n // Tag each candidate with provider id\n for (const candidate of candidates) {\n candidate.source = provider.id;\n }\n\n // Cap at maxCandidatesPerEntity.\n // 0 means \"accept none\"; undefined/negative means \"no cap\".\n const maxCandidates = config.maxCandidatesPerEntity;\n let accepted: EnrichmentCandidate[];\n if (maxCandidates === 0) {\n accepted = [];\n } else if (maxCandidates > 0 && candidates.length > maxCandidates) {\n accepted = candidates.slice(0, maxCandidates);\n } else {\n accepted = candidates;\n }\n const rejected = candidates.length - accepted.length;\n\n results.push({\n entityName: entity.name,\n provider: provider.id,\n candidatesFound: candidates.length,\n candidatesAccepted: accepted.length,\n candidatesRejected: rejected,\n acceptedCandidates: accepted,\n elapsed: Date.now() - start,\n });\n }\n }\n\n return results;\n}\n","/**\n * Enrichment audit trail (issue #365).\n *\n * Append-only JSONL log for every enrichment candidate that was evaluated.\n * Each entry records whether the candidate was accepted or rejected, the\n * provider that produced it, and an optional reason string.\n */\n\nimport { mkdir, readFile, appendFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport path from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface EnrichmentAuditEntry {\n timestamp: string;\n entityName: string;\n provider: string;\n candidateText: string;\n sourceUrl?: string;\n accepted: boolean;\n reason?: string;\n}\n\n// ---------------------------------------------------------------------------\n// File helpers\n// ---------------------------------------------------------------------------\n\nconst AUDIT_FILENAME = \"enrichment-audit.jsonl\";\n\nfunction auditFilePath(auditDir: string): string {\n return path.join(auditDir, AUDIT_FILENAME);\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Append a single audit entry to the JSONL log. Creates the audit directory\n * and file if they do not exist.\n */\nexport async function appendAuditEntry(\n auditDir: string,\n entry: EnrichmentAuditEntry,\n): Promise<void> {\n await mkdir(auditDir, { recursive: true });\n const line = JSON.stringify(entry) + \"\\n\";\n await appendFile(auditFilePath(auditDir), line, \"utf-8\");\n}\n\n/**\n * Read the audit log and return entries, optionally filtered to entries at\n * or after `since` (ISO 8601 timestamp, half-open interval).\n */\nexport async function readAuditLog(\n auditDir: string,\n since?: string,\n): Promise<EnrichmentAuditEntry[]> {\n const filePath = auditFilePath(auditDir);\n if (!existsSync(filePath)) return [];\n\n const raw = await readFile(filePath, \"utf-8\");\n const entries: EnrichmentAuditEntry[] = [];\n\n for (const line of raw.split(\"\\n\")) {\n const trimmed = line.trim();\n if (trimmed.length === 0) continue;\n try {\n const parsed: unknown = JSON.parse(trimmed);\n if (\n typeof parsed === \"object\" &&\n parsed !== null &&\n \"timestamp\" in parsed &&\n \"entityName\" in parsed\n ) {\n const entry = parsed as EnrichmentAuditEntry;\n if (since && entry.timestamp < since) continue;\n entries.push(entry);\n }\n } catch {\n // Skip malformed lines\n }\n }\n\n return entries;\n}\n"],"mappings":"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","names":["fsp","path","fsp","path","fsp","path","crypto","fsp","crypto","log","path","fs","path","path","fs","path","fs","path","fs","path","crypto","fs","path","parseFrontmatter","extractBody","crypto","fs","path","crypto","hashContent","path","fs","readFileSafe","parseFrontmatter","extractBody","crypto","fs","path","path","fs","parseFrontmatter","extractBody","readFileSafe","fs","path","crypto","DEFAULT_EXCLUDE","hashContent","fs","path","path","path","manifestPath","path","fs","fs","path","fs","path","crypto","manifestPath","walkMd","hashContent","fs","os","path","REMNIC_EXTENSION_DIR_NAME","path","os","fs","path","path","log","mkdir","readFile","existsSync","path"]}