@jim80net/memex-core 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -174,6 +174,36 @@ boost: 0.05
174
174
 
175
175
  Consumers typically extend `MemexCoreConfig` with platform-specific fields (hooks config, sync config, sleep schedule, etc.) and handle file loading themselves.
176
176
 
177
+ ## Sync
178
+
179
+ The `sync` module provides Git-based cross-device sync via `syncPull` and `syncCommitAndPush`. Both accept a `SyncConfig` object.
180
+
181
+ ### Case-insensitive project IDs (default)
182
+
183
+ Project IDs are lowercased by default across all three resolution paths
184
+ (manual mappings, git remote URLs, and encoded `_local/` path fallbacks).
185
+ A clone of `git@github.com:Jim80Net/Repo.git` and `git@github.com:jim80net/repo.git`
186
+ now map to the same canonical id: `github.com/jim80net/repo`.
187
+
188
+ To preserve the original case, set `caseSensitive: true` in your sync config:
189
+
190
+ ```typescript
191
+ const syncConfig: SyncConfig = {
192
+ enabled: true,
193
+ repo: "git@github.com:me/memex-sync.git",
194
+ autoPull: true,
195
+ autoCommitPush: true,
196
+ projectMappings: {},
197
+ caseSensitive: true, // preserve case as-is
198
+ };
199
+ ```
200
+
201
+ On first sync after upgrading from a version that wrote mixed-case directories,
202
+ `syncPull` will run a one-shot migration that renames legacy paths to lowercase
203
+ and writes a `.memex-sync/version.json` marker so the scan only runs once. The
204
+ migration is safe across devices (only runs against post-pull state), idempotent,
205
+ and handles case-insensitive filesystems (macOS APFS, Windows NTFS) correctly.
206
+
177
207
  ## Development
178
208
 
179
209
  ```bash
package/dist/cache.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
3
3
  import { dirname } from "node:path";
4
- const CACHE_VERSION = 2;
4
+ const CACHE_VERSION = 3;
5
5
  export async function loadCache(cachePath, embeddingModel) {
6
6
  const empty = { version: CACHE_VERSION, embeddingModel, skills: {} };
7
7
  try {
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Run a git command inside a directory. 30s timeout.
3
+ */
4
+ export declare function git(args: string[], cwd: string): Promise<{
5
+ stdout: string;
6
+ stderr: string;
7
+ }>;
8
+ export declare function isGitRepo(dir: string): Promise<boolean>;
9
+ export declare function hasRemote(dir: string): Promise<boolean>;
10
+ export declare function hasCommits(dir: string): Promise<boolean>;
11
+ export declare function getDefaultBranch(dir: string): Promise<string>;
12
+ //# sourceMappingURL=git-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-helpers.d.ts","sourceRoot":"","sources":["../src/git-helpers.ts"],"names":[],"mappings":"AAKA;;GAEG;AACH,wBAAsB,GAAG,CACvB,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,EAAE,MAAM,GACV,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,CAE7C;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7D;AAED,wBAAsB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO7D;AAED,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAO9D;AAED,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAgBnE"}
@@ -0,0 +1,58 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execFileAsync = promisify(execFile);
4
+ /**
5
+ * Run a git command inside a directory. 30s timeout.
6
+ */
7
+ export async function git(args, cwd) {
8
+ return execFileAsync("git", args, { cwd, timeout: 30_000 });
9
+ }
10
+ export async function isGitRepo(dir) {
11
+ try {
12
+ await git(["rev-parse", "--git-dir"], dir);
13
+ return true;
14
+ }
15
+ catch {
16
+ return false;
17
+ }
18
+ }
19
+ export async function hasRemote(dir) {
20
+ try {
21
+ const { stdout } = await git(["remote"], dir);
22
+ return stdout.trim().length > 0;
23
+ }
24
+ catch {
25
+ return false;
26
+ }
27
+ }
28
+ export async function hasCommits(dir) {
29
+ try {
30
+ await git(["rev-parse", "HEAD"], dir);
31
+ return true;
32
+ }
33
+ catch {
34
+ return false;
35
+ }
36
+ }
37
+ export async function getDefaultBranch(dir) {
38
+ try {
39
+ const { stdout } = await git(["symbolic-ref", "refs/remotes/origin/HEAD"], dir);
40
+ const ref = stdout.trim();
41
+ const branch = ref.replace(/^refs\/remotes\/origin\//, "");
42
+ if (branch)
43
+ return branch;
44
+ }
45
+ catch {
46
+ try {
47
+ const { stdout } = await git(["ls-remote", "--symref", "origin", "HEAD"], dir);
48
+ const match = stdout.match(/ref:\s+refs\/heads\/(\S+)/);
49
+ if (match)
50
+ return match[1];
51
+ }
52
+ catch {
53
+ // Fall through to default
54
+ }
55
+ }
56
+ return "main";
57
+ }
58
+ //# sourceMappingURL=git-helpers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-helpers.js","sourceRoot":"","sources":["../src/git-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,MAAM,aAAa,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;AAE1C;;GAEG;AACH,MAAM,CAAC,KAAK,UAAU,GAAG,CACvB,IAAc,EACd,GAAW;IAEX,OAAO,aAAa,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;AAC9D,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW;IACzC,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,WAAW,CAAC,EAAE,GAAG,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,GAAW;IACzC,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,CAAC,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAC;QAC9C,OAAO,MAAM,CAAC,IAAI,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,GAAW;IAC1C,IAAI,CAAC;QACH,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;QACtC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,GAAW;IAChD,IAAI,CAAC;QACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,CAAC,cAAc,EAAE,0BAA0B,CAAC,EAAE,GAAG,CAAC,CAAC;QAChF,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,EAAE,CAAC;QAC1B,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,0BAA0B,EAAE,EAAE,CAAC,CAAC;QAC3D,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;IAC5B,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,EAAE,GAAG,CAAC,CAAC;YAC/E,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,2BAA2B,CAAC,CAAC;YACxD,IAAI,KAAK;gBAAE,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,0BAA0B;QAC5B,CAAC;IACH,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
package/dist/index.d.ts CHANGED
@@ -3,12 +3,16 @@ export * from "./config.js";
3
3
  export * from "./embeddings.js";
4
4
  export * from "./file-lock.js";
5
5
  export * from "./path-encoder.js";
6
+ export type { HarnessKind, PortableLocationWarn, ResolvedPortableLocation, ScanRoot, ScanRootContext, ScanRootRegistry, ScanRootSpec, } from "./portable-location.js";
7
+ export { buildScanRoots, decodeFragment, decodePortableLocation, decodePortableLocationResolved, encodeFragment, encodePortableLocation, escapePortableText, HANDLE_PREFIX, resolvePortableLocation, resolvePortableLocationResolved, splitPortableHandle, stableUnclassifiedKey, unescapePortableText, } from "./portable-location.js";
6
8
  export * from "./project-mapping.js";
7
9
  export * from "./project-registry.js";
8
10
  export * from "./session.js";
9
- export type { ScanDirs } from "./skill-index.js";
11
+ export type { ScanDirs, SkillIndexOptions } from "./skill-index.js";
10
12
  export { parseFrontmatter, parseMemoryFile, SkillIndex } from "./skill-index.js";
11
13
  export { autoResolveMarkdownConflict, getSyncScanDirs, initSyncRepo, syncCommitAndPush, syncPull, } from "./sync.js";
14
+ export type { MigrationResult } from "./sync-migration.js";
15
+ export { migrateProjectIdsToLowercase, readSyncRepoVersion, runSyncMigrations, writeSyncRepoVersion, } from "./sync-migration.js";
12
16
  export * from "./telemetry.js";
13
17
  export * from "./traces.js";
14
18
  export * from "./types.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,YAAY,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EACL,2BAA2B,EAC3B,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,QAAQ,GACT,MAAM,WAAW,CAAC;AACnB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,YAAY,EACV,WAAW,EACX,oBAAoB,EACpB,wBAAwB,EACxB,QAAQ,EACR,eAAe,EACf,gBAAgB,EAChB,YAAY,GACb,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,cAAc,EACd,cAAc,EACd,sBAAsB,EACtB,8BAA8B,EAC9B,cAAc,EACd,sBAAsB,EACtB,kBAAkB,EAClB,aAAa,EACb,uBAAuB,EACvB,+BAA+B,EAC/B,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAC7B,YAAY,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACpE,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EACL,2BAA2B,EAC3B,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,QAAQ,GACT,MAAM,WAAW,CAAC;AACnB,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EACL,4BAA4B,EAC5B,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
package/dist/index.js CHANGED
@@ -3,11 +3,13 @@ export * from "./config.js";
3
3
  export * from "./embeddings.js";
4
4
  export * from "./file-lock.js";
5
5
  export * from "./path-encoder.js";
6
+ export { buildScanRoots, decodeFragment, decodePortableLocation, decodePortableLocationResolved, encodeFragment, encodePortableLocation, escapePortableText, HANDLE_PREFIX, resolvePortableLocation, resolvePortableLocationResolved, splitPortableHandle, stableUnclassifiedKey, unescapePortableText, } from "./portable-location.js";
6
7
  export * from "./project-mapping.js";
7
8
  export * from "./project-registry.js";
8
9
  export * from "./session.js";
9
10
  export { parseFrontmatter, parseMemoryFile, SkillIndex } from "./skill-index.js";
10
11
  export { autoResolveMarkdownConflict, getSyncScanDirs, initSyncRepo, syncCommitAndPush, syncPull, } from "./sync.js";
12
+ export { migrateProjectIdsToLowercase, readSyncRepoVersion, runSyncMigrations, writeSyncRepoVersion, } from "./sync-migration.js";
11
13
  export * from "./telemetry.js";
12
14
  export * from "./traces.js";
13
15
  export * from "./types.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAE7B,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EACL,2BAA2B,EAC3B,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,QAAQ,GACT,MAAM,WAAW,CAAC;AACnB,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAUlC,OAAO,EACL,cAAc,EACd,cAAc,EACd,sBAAsB,EACtB,8BAA8B,EAC9B,cAAc,EACd,sBAAsB,EACtB,kBAAkB,EAClB,aAAa,EACb,uBAAuB,EACvB,+BAA+B,EAC/B,mBAAmB,EACnB,qBAAqB,EACrB,oBAAoB,GACrB,MAAM,wBAAwB,CAAC;AAChC,cAAc,sBAAsB,CAAC;AACrC,cAAc,uBAAuB,CAAC;AACtC,cAAc,cAAc,CAAC;AAE7B,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AACjF,OAAO,EACL,2BAA2B,EAC3B,eAAe,EACf,YAAY,EACZ,iBAAiB,EACjB,QAAQ,GACT,MAAM,WAAW,CAAC;AAEnB,OAAO,EACL,4BAA4B,EAC5B,mBAAmB,EACnB,iBAAiB,EACjB,oBAAoB,GACrB,MAAM,qBAAqB,CAAC;AAC7B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC"}
@@ -0,0 +1,84 @@
1
+ import type { ScanDirs } from "./skill-index.js";
2
+ /** A scan root with a stable, host-free label for portable handles. */
3
+ export type ScanRoot = {
4
+ key: string;
5
+ rootPath: string;
6
+ };
7
+ export type ScanRootRegistry = ScanRoot[];
8
+ export type ScanRootSpec = ScanDirs;
9
+ export type HarnessKind = "grok" | "claude" | "codex" | "hermes";
10
+ export type ScanRootContext = {
11
+ cwd: string;
12
+ syncRepoDir?: string;
13
+ syncEnabled?: boolean;
14
+ globalSkillsDirs: string[];
15
+ globalRulesDirs: string[];
16
+ projectSkillsDir: string;
17
+ projectRulesDir: string;
18
+ harness: HarnessKind;
19
+ };
20
+ export declare const HANDLE_PREFIX = "memex://";
21
+ export type PortableLocationWarn = (message: string) => void;
22
+ /** Filesystem path + optional memory section name (fragment decoded once). */
23
+ export type ResolvedPortableLocation = {
24
+ filePath: string;
25
+ sectionName?: string;
26
+ };
27
+ /**
28
+ * Portable handles are opaque location tokens — not general URIs.
29
+ * Form: memex://{rootKey}/{posix-rel}[#{fragment}]
30
+ */
31
+ /** Injective escape for rel segments and fragments (% first, then #). */
32
+ export declare function escapePortableText(text: string): string;
33
+ /** Reverse of escapePortableText (# last on decode, then %). */
34
+ export declare function unescapePortableText(text: string): string;
35
+ /** @alias escapePortableText */
36
+ export declare const encodeFragment: typeof escapePortableText;
37
+ /** @alias unescapePortableText */
38
+ export declare const decodeFragment: typeof unescapePortableText;
39
+ /**
40
+ * Split at the last literal '#' — escaped #s are %23 and do not split.
41
+ * Fragment is unescaped exactly once.
42
+ */
43
+ export declare function splitPortableHandle(handle: string): {
44
+ body: string;
45
+ fragment?: string;
46
+ };
47
+ /** Stable fallback rootKey from normalized absolute dir path (§4.4 path-hash option). */
48
+ export declare function stableUnclassifiedKey(kind: "skill" | "rule" | "memory", dir: string): string;
49
+ /**
50
+ * Build labeled scan roots from harness context and scan directory spec.
51
+ * Same semantic root MUST use the same rootKey on every host (normative catalog).
52
+ */
53
+ export declare function buildScanRoots(ctx: ScanRootContext, spec: ScanRootSpec): ScanRootRegistry;
54
+ /**
55
+ * Absolute path (+ optional #fragment) → portable handle.
56
+ * Fail-open: returns null when mapping is impossible (caller should skip-with-warning).
57
+ */
58
+ export declare function encodePortableLocation(registry: ScanRootRegistry, absolute: string, warn?: PortableLocationWarn, fragment?: string): string | null;
59
+ /**
60
+ * Portable handle → absolute file path + optional section name.
61
+ * Fragment is split and %23-unescaped exactly once — never rejoin with '#'.
62
+ * Fail-closed on traversal escape.
63
+ */
64
+ export declare function decodePortableLocationResolved(registry: ScanRootRegistry, handle: string): ResolvedPortableLocation;
65
+ /**
66
+ * Portable handle → absolute path string (legacy join form).
67
+ * Prefer decodePortableLocationResolved when a fragment may be present.
68
+ */
69
+ export declare function decodePortableLocation(registry: ScanRootRegistry, handle: string): string;
70
+ /**
71
+ * Resolve portable handle or legacy absolute path for filesystem I/O.
72
+ * Phase 2: agent-facing read tools MUST pass `allowAbsolute: false` so untrusted
73
+ * absolute paths cannot bypass containment (memex-grok#19 closes only then).
74
+ */
75
+ export declare function resolvePortableLocationResolved(registry: ScanRootRegistry, input: string, options?: {
76
+ warn?: PortableLocationWarn;
77
+ allowAbsolute?: boolean;
78
+ }): ResolvedPortableLocation;
79
+ /** @deprecated Prefer resolvePortableLocationResolved to avoid fragment double-decode. */
80
+ export declare function resolvePortableLocation(registry: ScanRootRegistry, input: string, options?: {
81
+ warn?: PortableLocationWarn;
82
+ allowAbsolute?: boolean;
83
+ }): string;
84
+ //# sourceMappingURL=portable-location.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portable-location.d.ts","sourceRoot":"","sources":["../src/portable-location.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,248 @@
1
+ import { createHash } from "node:crypto";
2
+ import { join, relative, resolve, sep } from "node:path";
3
+ export const HANDLE_PREFIX = "memex://";
4
+ /**
5
+ * Portable handles are opaque location tokens — not general URIs.
6
+ * Form: memex://{rootKey}/{posix-rel}[#{fragment}]
7
+ */
8
+ /** Injective escape for rel segments and fragments (% first, then #). */
9
+ export function escapePortableText(text) {
10
+ return text.replace(/%/g, "%25").replace(/#/g, "%23");
11
+ }
12
+ /** Reverse of escapePortableText (# last on decode, then %). */
13
+ export function unescapePortableText(text) {
14
+ return text.replace(/%23/g, "#").replace(/%25/g, "%");
15
+ }
16
+ /** @alias escapePortableText */
17
+ export const encodeFragment = escapePortableText;
18
+ /** @alias unescapePortableText */
19
+ export const decodeFragment = unescapePortableText;
20
+ /**
21
+ * Split at the last literal '#' — escaped #s are %23 and do not split.
22
+ * Fragment is unescaped exactly once.
23
+ */
24
+ export function splitPortableHandle(handle) {
25
+ const lastHash = handle.lastIndexOf("#");
26
+ if (lastHash === -1)
27
+ return { body: handle };
28
+ return {
29
+ body: handle.slice(0, lastHash),
30
+ fragment: unescapePortableText(handle.slice(lastHash + 1)),
31
+ };
32
+ }
33
+ function encodeRelPath(rel) {
34
+ return normalizeRel(rel).split("/").map(escapePortableText).join("/");
35
+ }
36
+ function decodeRelPath(rel) {
37
+ return rel.split("/").map(unescapePortableText).join("/");
38
+ }
39
+ function normalizeRel(rel) {
40
+ return rel.split(sep).join("/");
41
+ }
42
+ function isAbsolutePath(p) {
43
+ return p.startsWith("/") || /^[A-Za-z]:[\\/]/.test(p);
44
+ }
45
+ function hasUnsafeRelSegments(rel) {
46
+ const segments = rel.split("/");
47
+ return segments.some((s) => s === ".." || s === "");
48
+ }
49
+ function isPathContained(rootPath, absolute) {
50
+ const root = resolve(rootPath);
51
+ const normalized = resolve(absolute);
52
+ return normalized === root || normalized.startsWith(root + sep);
53
+ }
54
+ function sortRegistry(roots) {
55
+ return [...roots].sort((a, b) => b.rootPath.length - a.rootPath.length);
56
+ }
57
+ function matchRoot(registry, filePath) {
58
+ const normalized = resolve(filePath);
59
+ return registry.find((r) => isPathContained(r.rootPath, normalized));
60
+ }
61
+ function pathHasSegment(resolvedPath, segment) {
62
+ return resolve(resolvedPath).split(sep).includes(segment);
63
+ }
64
+ /** Stable fallback rootKey from normalized absolute dir path (§4.4 path-hash option). */
65
+ export function stableUnclassifiedKey(kind, dir) {
66
+ const hash = createHash("sha256").update(resolve(dir)).digest("hex").slice(0, 8);
67
+ return `${kind}-unclassified-${hash}`;
68
+ }
69
+ /**
70
+ * Build labeled scan roots from harness context and scan directory spec.
71
+ * Same semantic root MUST use the same rootKey on every host (normative catalog).
72
+ */
73
+ export function buildScanRoots(ctx, spec) {
74
+ const harness = ctx.harness;
75
+ const roots = [];
76
+ const seen = new Set();
77
+ const add = (key, dir) => {
78
+ const rootPath = resolve(dir);
79
+ if (seen.has(rootPath))
80
+ return;
81
+ seen.add(rootPath);
82
+ roots.push({ key, rootPath });
83
+ };
84
+ const globalSkillKeys = new Map();
85
+ for (const dir of ctx.globalSkillsDirs) {
86
+ const resolved = resolve(dir);
87
+ if (pathHasSegment(resolved, ".grok")) {
88
+ globalSkillKeys.set(resolved, "grok-global");
89
+ }
90
+ else if (pathHasSegment(resolved, ".claude")) {
91
+ globalSkillKeys.set(resolved, "claude-global");
92
+ }
93
+ else if (pathHasSegment(resolved, ".codex")) {
94
+ globalSkillKeys.set(resolved, "codex-global");
95
+ }
96
+ else if (pathHasSegment(resolved, ".hermes")) {
97
+ globalSkillKeys.set(resolved, "hermes-global");
98
+ }
99
+ }
100
+ const globalRuleKeys = new Map();
101
+ for (const dir of ctx.globalRulesDirs) {
102
+ const resolved = resolve(dir);
103
+ if (pathHasSegment(resolved, ".grok")) {
104
+ globalRuleKeys.set(resolved, "grok-rules-global");
105
+ }
106
+ else if (pathHasSegment(resolved, ".claude")) {
107
+ globalRuleKeys.set(resolved, "claude-rules-global");
108
+ }
109
+ else if (pathHasSegment(resolved, ".codex")) {
110
+ globalRuleKeys.set(resolved, "codex-rules-global");
111
+ }
112
+ else if (pathHasSegment(resolved, ".hermes")) {
113
+ globalRuleKeys.set(resolved, "hermes-rules-global");
114
+ }
115
+ }
116
+ const projectSkillsResolved = resolve(ctx.projectSkillsDir);
117
+ const projectRulesResolved = resolve(ctx.projectRulesDir);
118
+ const syncSkillsDir = ctx.syncEnabled && ctx.syncRepoDir ? resolve(join(ctx.syncRepoDir, "skills")) : undefined;
119
+ const syncRulesDir = ctx.syncEnabled && ctx.syncRepoDir ? resolve(join(ctx.syncRepoDir, "rules")) : undefined;
120
+ const unclassifiedSkillDirs = [];
121
+ for (const dir of spec.skillDirs) {
122
+ const resolved = resolve(dir);
123
+ const catalogKey = globalSkillKeys.get(resolved);
124
+ if (catalogKey) {
125
+ add(catalogKey, dir);
126
+ }
127
+ else if (resolved === projectSkillsResolved) {
128
+ add(`${harness}-project`, dir);
129
+ }
130
+ else if (syncSkillsDir && resolved === syncSkillsDir) {
131
+ add("sync-skills", dir);
132
+ }
133
+ else {
134
+ unclassifiedSkillDirs.push(dir);
135
+ }
136
+ }
137
+ for (const dir of unclassifiedSkillDirs) {
138
+ add(stableUnclassifiedKey("skill", dir), dir);
139
+ }
140
+ const unclassifiedRuleDirs = [];
141
+ for (const dir of spec.ruleDirs) {
142
+ const resolved = resolve(dir);
143
+ const catalogKey = globalRuleKeys.get(resolved);
144
+ if (catalogKey) {
145
+ add(catalogKey, dir);
146
+ }
147
+ else if (resolved === projectRulesResolved) {
148
+ add(`${harness}-rules-project`, dir);
149
+ }
150
+ else if (syncRulesDir && resolved === syncRulesDir) {
151
+ add("sync-rules", dir);
152
+ }
153
+ else {
154
+ unclassifiedRuleDirs.push(dir);
155
+ }
156
+ }
157
+ for (const dir of unclassifiedRuleDirs) {
158
+ add(stableUnclassifiedKey("rule", dir), dir);
159
+ }
160
+ for (const dir of spec.memoryDirs) {
161
+ add(stableUnclassifiedKey("memory", dir), dir);
162
+ }
163
+ return sortRegistry(roots);
164
+ }
165
+ /**
166
+ * Absolute path (+ optional #fragment) → portable handle.
167
+ * Fail-open: returns null when mapping is impossible (caller should skip-with-warning).
168
+ */
169
+ export function encodePortableLocation(registry, absolute, warn, fragment) {
170
+ const body = resolve(absolute);
171
+ const root = matchRoot(registry, body);
172
+ if (!root) {
173
+ warn?.(`skipped indexing ${absolute} — no portable handle (outside registered scan roots)`);
174
+ return null;
175
+ }
176
+ const rel = normalizeRel(relative(root.rootPath, body));
177
+ if (hasUnsafeRelSegments(rel)) {
178
+ warn?.(`skipped indexing ${absolute} — no portable handle (unsafe relative path: ${rel})`);
179
+ return null;
180
+ }
181
+ const handle = `${HANDLE_PREFIX}${root.key}/${encodeRelPath(rel)}`;
182
+ if (fragment === undefined)
183
+ return handle;
184
+ return `${handle}#${escapePortableText(fragment)}`;
185
+ }
186
+ /**
187
+ * Portable handle → absolute file path + optional section name.
188
+ * Fragment is split and %23-unescaped exactly once — never rejoin with '#'.
189
+ * Fail-closed on traversal escape.
190
+ */
191
+ export function decodePortableLocationResolved(registry, handle) {
192
+ if (!handle.startsWith(HANDLE_PREFIX)) {
193
+ throw new Error(`invalid portable location handle: ${handle}`);
194
+ }
195
+ const { body, fragment: sectionName } = splitPortableHandle(handle);
196
+ const pathBody = body.slice(HANDLE_PREFIX.length);
197
+ const slash = pathBody.indexOf("/");
198
+ if (slash === -1) {
199
+ throw new Error(`invalid portable location handle (missing path segment): ${handle}`);
200
+ }
201
+ const key = pathBody.slice(0, slash);
202
+ const rel = decodeRelPath(pathBody.slice(slash + 1));
203
+ if (hasUnsafeRelSegments(rel)) {
204
+ throw new Error(`portable location handle escapes scan root: ${handle}`);
205
+ }
206
+ const root = registry.find((r) => r.key === key);
207
+ if (!root) {
208
+ throw new Error(`unknown portable location root '${key}'`);
209
+ }
210
+ const filePath = resolve(root.rootPath, rel);
211
+ if (!isPathContained(root.rootPath, filePath)) {
212
+ throw new Error(`portable location handle escapes scan root: ${handle}`);
213
+ }
214
+ return sectionName !== undefined ? { filePath, sectionName } : { filePath };
215
+ }
216
+ /**
217
+ * Portable handle → absolute path string (legacy join form).
218
+ * Prefer decodePortableLocationResolved when a fragment may be present.
219
+ */
220
+ export function decodePortableLocation(registry, handle) {
221
+ const { filePath, sectionName } = decodePortableLocationResolved(registry, handle);
222
+ return sectionName !== undefined ? `${filePath}#${sectionName}` : filePath;
223
+ }
224
+ /**
225
+ * Resolve portable handle or legacy absolute path for filesystem I/O.
226
+ * Phase 2: agent-facing read tools MUST pass `allowAbsolute: false` so untrusted
227
+ * absolute paths cannot bypass containment (memex-grok#19 closes only then).
228
+ */
229
+ export function resolvePortableLocationResolved(registry, input, options) {
230
+ const trimmed = input.trim();
231
+ if (!trimmed)
232
+ return { filePath: trimmed };
233
+ if (trimmed.startsWith(HANDLE_PREFIX)) {
234
+ return decodePortableLocationResolved(registry, trimmed);
235
+ }
236
+ if (options?.allowAbsolute !== false && isAbsolutePath(trimmed)) {
237
+ options?.warn?.(`deprecated: readSkillContent received absolute path; use portable memex:// handles`);
238
+ const { body, fragment } = splitPortableHandle(trimmed);
239
+ return fragment !== undefined ? { filePath: body, sectionName: fragment } : { filePath: body };
240
+ }
241
+ throw new Error(`unrecognized location: ${input}`);
242
+ }
243
+ /** @deprecated Prefer resolvePortableLocationResolved to avoid fragment double-decode. */
244
+ export function resolvePortableLocation(registry, input, options) {
245
+ const { filePath, sectionName } = resolvePortableLocationResolved(registry, input, options);
246
+ return sectionName !== undefined ? `${filePath}#${sectionName}` : filePath;
247
+ }
248
+ //# sourceMappingURL=portable-location.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"portable-location.js","sourceRoot":"","sources":["../src/portable-location.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AA0BzD,MAAM,CAAC,MAAM,aAAa,GAAG,UAAU,CAAC;AAUxC;;;GAGG;AACH,yEAAyE;AACzE,MAAM,UAAU,kBAAkB,CAAC,IAAY;IAC7C,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;AACxD,CAAC;AAED,gEAAgE;AAChE,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACxD,CAAC;AAED,gCAAgC;AAChC,MAAM,CAAC,MAAM,cAAc,GAAG,kBAAkB,CAAC;AAEjD,kCAAkC;AAClC,MAAM,CAAC,MAAM,cAAc,GAAG,oBAAoB,CAAC;AAEnD;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAc;IAChD,MAAM,QAAQ,GAAG,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACzC,IAAI,QAAQ,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC7C,OAAO;QACL,IAAI,EAAE,MAAM,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC;QAC/B,QAAQ,EAAE,oBAAoB,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,GAAG,CAAC,CAAC,CAAC;KAC3D,CAAC;AACJ,CAAC;AAED,SAAS,aAAa,CAAC,GAAW;IAChC,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AACxE,CAAC;AAED,SAAS,aAAa,CAAC,GAAW;IAChC,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,oBAAoB,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,YAAY,CAAC,GAAW;IAC/B,OAAO,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClC,CAAC;AAED,SAAS,cAAc,CAAC,CAAS;IAC/B,OAAO,CAAC,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,iBAAiB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACxD,CAAC;AAED,SAAS,oBAAoB,CAAC,GAAW;IACvC,MAAM,QAAQ,GAAG,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IAChC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;AACtD,CAAC;AAED,SAAS,eAAe,CAAC,QAAgB,EAAE,QAAgB;IACzD,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/B,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrC,OAAO,UAAU,KAAK,IAAI,IAAI,UAAU,CAAC,UAAU,CAAC,IAAI,GAAG,GAAG,CAAC,CAAC;AAClE,CAAC;AAED,SAAS,YAAY,CAAC,KAAiB;IACrC,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC1E,CAAC;AAED,SAAS,SAAS,CAAC,QAA0B,EAAE,QAAgB;IAC7D,MAAM,UAAU,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IACrC,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC,CAAC;AACvE,CAAC;AAED,SAAS,cAAc,CAAC,YAAoB,EAAE,OAAe;IAC3D,OAAO,OAAO,CAAC,YAAY,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC;AAC5D,CAAC;AAED,yFAAyF;AACzF,MAAM,UAAU,qBAAqB,CAAC,IAAiC,EAAE,GAAW;IAClF,MAAM,IAAI,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;IACjF,OAAO,GAAG,IAAI,iBAAiB,IAAI,EAAE,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,cAAc,CAAC,GAAoB,EAAE,IAAkB;IACrE,MAAM,OAAO,GAAG,GAAG,CAAC,OAAO,CAAC;IAC5B,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAE/B,MAAM,GAAG,GAAG,CAAC,GAAW,EAAE,GAAW,EAAE,EAAE;QACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO;QAC/B,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACnB,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAE,CAAC,CAAC;IAChC,CAAC,CAAC;IAEF,MAAM,eAAe,GAAG,IAAI,GAAG,EAAkB,CAAC;IAClD,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACvC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC;YACtC,eAAe,CAAC,GAAG,CAAC,QAAQ,EAAE,aAAa,CAAC,CAAC;QAC/C,CAAC;aAAM,IAAI,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC;YAC/C,eAAe,CAAC,GAAG,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;QACjD,CAAC;aAAM,IAAI,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;YAC9C,eAAe,CAAC,GAAG,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC;QAChD,CAAC;aAAM,IAAI,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC;YAC/C,eAAe,CAAC,GAAG,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IAED,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAC;IACjD,KAAK,MAAM,GAAG,IAAI,GAAG,CAAC,eAAe,EAAE,CAAC;QACtC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,IAAI,cAAc,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,CAAC;YACtC,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,mBAAmB,CAAC,CAAC;QACpD,CAAC;aAAM,IAAI,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC;YAC/C,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,qBAAqB,CAAC,CAAC;QACtD,CAAC;aAAM,IAAI,cAAc,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;YAC9C,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,oBAAoB,CAAC,CAAC;QACrD,CAAC;aAAM,IAAI,cAAc,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC;YAC/C,cAAc,CAAC,GAAG,CAAC,QAAQ,EAAE,qBAAqB,CAAC,CAAC;QACtD,CAAC;IACH,CAAC;IAED,MAAM,qBAAqB,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAC5D,MAAM,oBAAoB,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IAC1D,MAAM,aAAa,GACjB,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC5F,MAAM,YAAY,GAChB,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAE3F,MAAM,qBAAqB,GAAa,EAAE,CAAC;IAC3C,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,UAAU,GAAG,eAAe,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QACjD,IAAI,UAAU,EAAE,CAAC;YACf,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACvB,CAAC;aAAM,IAAI,QAAQ,KAAK,qBAAqB,EAAE,CAAC;YAC9C,GAAG,CAAC,GAAG,OAAO,UAAU,EAAE,GAAG,CAAC,CAAC;QACjC,CAAC;aAAM,IAAI,aAAa,IAAI,QAAQ,KAAK,aAAa,EAAE,CAAC;YACvD,GAAG,CAAC,aAAa,EAAE,GAAG,CAAC,CAAC;QAC1B,CAAC;aAAM,CAAC;YACN,qBAAqB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,qBAAqB,EAAE,CAAC;QACxC,GAAG,CAAC,qBAAqB,CAAC,OAAO,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,oBAAoB,GAAa,EAAE,CAAC;IAC1C,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;QAC9B,MAAM,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAChD,IAAI,UAAU,EAAE,CAAC;YACf,GAAG,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;QACvB,CAAC;aAAM,IAAI,QAAQ,KAAK,oBAAoB,EAAE,CAAC;YAC7C,GAAG,CAAC,GAAG,OAAO,gBAAgB,EAAE,GAAG,CAAC,CAAC;QACvC,CAAC;aAAM,IAAI,YAAY,IAAI,QAAQ,KAAK,YAAY,EAAE,CAAC;YACrD,GAAG,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;QACzB,CAAC;aAAM,CAAC;YACN,oBAAoB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjC,CAAC;IACH,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,oBAAoB,EAAE,CAAC;QACvC,GAAG,CAAC,qBAAqB,CAAC,MAAM,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;IAC/C,CAAC;IAED,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;QAClC,GAAG,CAAC,qBAAqB,CAAC,QAAQ,EAAE,GAAG,CAAC,EAAE,GAAG,CAAC,CAAC;IACjD,CAAC;IAED,OAAO,YAAY,CAAC,KAAK,CAAC,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CACpC,QAA0B,EAC1B,QAAgB,EAChB,IAA2B,EAC3B,QAAiB;IAEjB,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC/B,MAAM,IAAI,GAAG,SAAS,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;IACvC,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,IAAI,EAAE,CAAC,oBAAoB,QAAQ,uDAAuD,CAAC,CAAC;QAC5F,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,GAAG,GAAG,YAAY,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC;IACxD,IAAI,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,IAAI,EAAE,CAAC,oBAAoB,QAAQ,gDAAgD,GAAG,GAAG,CAAC,CAAC;QAC3F,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GAAG,GAAG,aAAa,GAAG,IAAI,CAAC,GAAG,IAAI,aAAa,CAAC,GAAG,CAAC,EAAE,CAAC;IACnE,IAAI,QAAQ,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC;IAC1C,OAAO,GAAG,MAAM,IAAI,kBAAkB,CAAC,QAAQ,CAAC,EAAE,CAAC;AACrD,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,8BAA8B,CAC5C,QAA0B,EAC1B,MAAc;IAEd,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CAAC,qCAAqC,MAAM,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAC;IACpE,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,4DAA4D,MAAM,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,MAAM,GAAG,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;IACrC,MAAM,GAAG,GAAG,aAAa,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC;IACrD,IAAI,oBAAoB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,KAAK,CAAC,+CAA+C,MAAM,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,KAAK,GAAG,CAAC,CAAC;IACjD,IAAI,CAAC,IAAI,EAAE,CAAC;QACV,MAAM,IAAI,KAAK,CAAC,mCAAmC,GAAG,GAAG,CAAC,CAAC;IAC7D,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC7C,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,+CAA+C,MAAM,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,OAAO,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC;AAC9E,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,sBAAsB,CAAC,QAA0B,EAAE,MAAc;IAC/E,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,8BAA8B,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACnF,OAAO,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,QAAQ,IAAI,WAAW,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC7E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,+BAA+B,CAC7C,QAA0B,EAC1B,KAAa,EACb,OAAkE;IAElE,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO;QAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC;IAE3C,IAAI,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;QACtC,OAAO,8BAA8B,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC3D,CAAC;IAED,IAAI,OAAO,EAAE,aAAa,KAAK,KAAK,IAAI,cAAc,CAAC,OAAO,CAAC,EAAE,CAAC;QAChE,OAAO,EAAE,IAAI,EAAE,CACb,oFAAoF,CACrF,CAAC;QACF,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,mBAAmB,CAAC,OAAO,CAAC,CAAC;QACxD,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,WAAW,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IACjG,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,0BAA0B,KAAK,EAAE,CAAC,CAAC;AACrD,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,uBAAuB,CACrC,QAA0B,EAC1B,KAAa,EACb,OAAkE;IAElE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,GAAG,+BAA+B,CAAC,QAAQ,EAAE,KAAK,EAAE,OAAO,CAAC,CAAC;IAC5F,OAAO,WAAW,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,QAAQ,IAAI,WAAW,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC;AAC7E,CAAC"}
@@ -3,11 +3,17 @@ import type { SyncConfig } from "./types.js";
3
3
  * Normalize a git remote URL to a canonical path segment.
4
4
  * Handles SSH, HTTPS, and .git suffix variations.
5
5
  *
6
+ * By default the result is lowercased so that clones of the same repo with
7
+ * different casing (`GitHub.com:Jim80Net/Repo` vs `github.com:jim80net/repo`)
8
+ * collapse onto a single canonical project id. Pass `caseSensitive = true`
9
+ * to preserve the original case.
10
+ *
6
11
  * Examples:
7
12
  * git@github.com:jim80net/repo.git → github.com/jim80net/repo
13
+ * git@GitHub.com:Jim80Net/Repo.git → github.com/jim80net/repo
8
14
  * https://github.com/jim80net/repo.git → github.com/jim80net/repo
9
15
  */
10
- export declare function normalizeGitUrl(url: string): string;
16
+ export declare function normalizeGitUrl(url: string, caseSensitive?: boolean): string;
11
17
  /**
12
18
  * Resolve the canonical project identifier for a given cwd.
13
19
  *
@@ -15,11 +21,22 @@ export declare function normalizeGitUrl(url: string): string;
15
21
  * 1. Manual mapping from config (explicit override)
16
22
  * 2. Git remote URL → normalized to host/owner/repo
17
23
  * 3. Encoded cwd path → stored under _local/
24
+ *
25
+ * All three paths are lowercased by default. Set `syncConfig.caseSensitive`
26
+ * to `true` to preserve the original casing.
18
27
  */
19
28
  export declare function resolveProjectId(cwd: string, syncConfig: SyncConfig): Promise<string>;
20
29
  /**
21
30
  * Find all project memory directories in the sync repo that match the current cwd.
22
- * Multiple matches are possible (e.g., same project stored under both URL and encoded path).
31
+ *
32
+ * Returns:
33
+ * - The canonical (lowercase) memory dir if it exists.
34
+ * - The `_local/<encoded>` fallback if it exists and differs from the canonical.
35
+ * - Any legacy mixed-case directory whose lowercase form equals the canonical id
36
+ * (rollout window before a post-upgrade sync has migrated the repo).
37
+ *
38
+ * Multiple matches are expected during the upgrade window; callers should merge
39
+ * their contents.
23
40
  */
24
41
  export declare function findMatchingProjectMemoryDirs(cwd: string, syncRepoPath: string, syncConfig: SyncConfig): Promise<string[]>;
25
42
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"project-mapping.d.ts","sourceRoot":"","sources":["../src/project-mapping.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAmBnD;AAkBD;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAc3F;AAED;;;GAGG;AACH,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,MAAM,EAAE,CAAC,CAyBnB;AAED;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,MAAM,CAAC,CAGjB"}
1
+ {"version":3,"file":"project-mapping.d.ts","sourceRoot":"","sources":["../src/project-mapping.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAI7C;;;;;;;;;;;;;GAaG;AACH,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,aAAa,UAAQ,GAAG,MAAM,CAqB1E;AAkBD;;;;;;;;;;GAUG;AACH,wBAAsB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,GAAG,OAAO,CAAC,MAAM,CAAC,CAiB3F;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,6BAA6B,CACjD,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,MAAM,EAAE,CAAC,CA+BnB;AAmDD;;GAEG;AACH,wBAAsB,uBAAuB,CAC3C,GAAG,EAAE,MAAM,EACX,YAAY,EAAE,MAAM,EACpB,UAAU,EAAE,UAAU,GACrB,OAAO,CAAC,MAAM,CAAC,CAGjB"}