browser-git-ops 0.0.4 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -21,23 +21,28 @@ var OpfsStorage = class OpfsStorage2 {
21
21
  ok = true;
22
22
  return ok;
23
23
  }
24
- /** 利用可能なサブディレクトリ名の候補を返す
25
- * @returns {Promise<string[]>} available root directories
26
- */
27
24
  /**
28
25
  * Return available root folder names for OPFS. This method is synchronous
29
26
  * to satisfy the StorageBackendConstructor contract; it returns a cached
30
27
  * hint if available and kicks off an async probe to populate the cache.
31
28
  * If no information is available synchronously an empty array is returned.
32
- * @returns {Promise<string[]>} available root directories
29
+ * @returns {Promise<string[]>} available root directories
33
30
  */
34
- static async availableRoots() {
31
+ static async availableRoots(namespace) {
35
32
  try {
36
33
  const root = await OpfsStorage2._getNavigatorStorageRoot();
37
34
  if (!root)
38
35
  return [];
39
- return await OpfsStorage2._collectDirectoryNames(root);
40
- } catch {
36
+ for await (const handle of root.values()) {
37
+ const name = OpfsStorage2._extractHandleName(handle);
38
+ if (name === namespace && OpfsStorage2._isDirectoryHandle(handle)) {
39
+ return await OpfsStorage2._collectDirectoryNames(handle);
40
+ }
41
+ }
42
+ return [];
43
+ } catch (error) {
44
+ if (typeof console !== "undefined" && console.debug)
45
+ console.debug("availableRoots probe failed", error);
41
46
  return [];
42
47
  }
43
48
  }
@@ -88,6 +93,7 @@ var OpfsStorage = class OpfsStorage2 {
88
93
  }
89
94
  rootDir = "apigit_storage";
90
95
  currentBranch = null;
96
+ namespace = "";
91
97
  /**
92
98
  * Calculate SHA-1 hex digest of given content.
93
99
  * @param content Input string
@@ -100,10 +106,21 @@ var OpfsStorage = class OpfsStorage2 {
100
106
  const hashArray = Array.from(new Uint8Array(hashBuffer));
101
107
  return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
102
108
  }
103
- /** コンストラクタ(OPFS は初期化不要)。`root` は OPFS ルート直下に作成するサブディレクトリ名です。 */
104
- constructor(root) {
105
- if (root)
106
- this.rootDir = root;
109
+ /**
110
+ * コンストラクタ(OPFS は初期化不要)。
111
+ * `namespace` は必須。挙動:
112
+ * - `new(namespace)` のみの場合は OPFS ルートとして `namespace` を使う(テストの期待値に合わせる)。
113
+ * - `new(namespace, root)` の場合は `namespace/root` を使う。
114
+ */
115
+ constructor(namespace, root) {
116
+ this.namespace = namespace || "";
117
+ if (root) {
118
+ this.rootDir = this.namespace ? `${this.namespace}/${root}` : root;
119
+ } else if (this.namespace) {
120
+ this.rootDir = this.namespace;
121
+ } else {
122
+ this.rootDir = this.rootDir;
123
+ }
107
124
  }
108
125
  /**
109
126
  * 初期化(OPFS はランタイム判定のみ)
@@ -131,7 +148,7 @@ var OpfsStorage = class OpfsStorage2 {
131
148
  }
132
149
  /**
133
150
  * Map logical segment to concrete prefix used on OPFS.
134
- * @returns {string} concrete prefix path for the given segment
151
+ * @returns {string} concrete prefix path for the given segment
135
152
  */
136
153
  _segmentToPrefix(segment) {
137
154
  if (segment === "workspace")
@@ -140,7 +157,6 @@ var OpfsStorage = class OpfsStorage2 {
140
157
  const branch = this.currentBranch || "main";
141
158
  return `.git/${branch}/${segName}`;
142
159
  }
143
- // legacy canUseOpfs removed; use static canUse() instead
144
160
  /**
145
161
  * Try to get OPFS root from navigator.storage.getDirectory().
146
162
  * @returns {Promise<any|null>}
@@ -154,7 +170,9 @@ var OpfsStorage = class OpfsStorage2 {
154
170
  const maybe = nav.storage.getDirectory();
155
171
  const d = await Promise.resolve(maybe);
156
172
  return d || null;
157
- } catch {
173
+ } catch (error) {
174
+ if (typeof console !== "undefined" && console.debug)
175
+ console.debug("_tryNavigatorStorage failed", error);
158
176
  return null;
159
177
  }
160
178
  }
@@ -169,7 +187,9 @@ var OpfsStorage = class OpfsStorage2 {
169
187
  }
170
188
  try {
171
189
  return await opfs.getDirectory();
172
- } catch {
190
+ } catch (error) {
191
+ if (typeof console !== "undefined" && console.debug)
192
+ console.debug("_tryOriginPrivateFileSystem failed", error);
173
193
  return null;
174
194
  }
175
195
  }
@@ -200,10 +220,6 @@ var OpfsStorage = class OpfsStorage2 {
200
220
  }
201
221
  return directory;
202
222
  }
203
- /**
204
- * index を読み出す
205
- * @returns {Promise<IndexFile|null>} 読み出した IndexFile、存在しなければ null
206
- */
207
223
  /**
208
224
  * Read index metadata file from OPFS.
209
225
  * @returns {Promise<string|null>}
@@ -220,7 +236,9 @@ var OpfsStorage = class OpfsStorage2 {
220
236
  const fh = await root.getFileHandle("index");
221
237
  const file = await fh.getFile();
222
238
  return await file.text();
223
- } catch {
239
+ } catch (error) {
240
+ if (typeof console !== "undefined" && console.debug)
241
+ console.debug("_readIndexMetadata failed", error);
224
242
  return null;
225
243
  }
226
244
  }
@@ -579,8 +597,8 @@ var OpfsStorage = class OpfsStorage2 {
579
597
  * @param root root directory handle
580
598
  * @param prefix prefix dir
581
599
  * @param filepath path relative to prefix
582
- * @returns {Promise<string|null>} file contents or null when not found
583
- */
600
+ * @returns {Promise<string|null>} file contents or null when not found
601
+ */
584
602
  async readFromPrefix(root, prefix, filepath) {
585
603
  try {
586
604
  const fullPath = this.rootDir ? `${this.rootDir}/${prefix}/${filepath}` : `${prefix}/${filepath}`;
@@ -664,7 +682,6 @@ var OpfsStorage = class OpfsStorage2 {
664
682
  await this._handleChildEntry(d, name, base, results);
665
683
  }
666
684
  }
667
- /** Handle a single child entry name: try file first, then directory. */
668
685
  /**
669
686
  * @returns {Promise<void>}
670
687
  */
@@ -718,7 +735,7 @@ var OpfsStorage = class OpfsStorage2 {
718
735
  * @param prefix プレフィックス(例: 'dir/sub')。省略時はルート
719
736
  * @param segment セグメント('workspace' 等)。省略時は 'workspace'
720
737
  * @param recursive サブディレクトリも含めるか。省略時は true
721
- * @returns {Promise<Array<{ path: string; info: string | null }>>}
738
+ * @returns {Promise<Array<{ path: string; info: string | null }>>}
722
739
  */
723
740
  async listFiles(prefix, segment, recursive = true) {
724
741
  const root = await this.getOpfsRoot();
@@ -762,12 +779,9 @@ var OpfsStorage = class OpfsStorage2 {
762
779
  */
763
780
  async _findStorageDirectory(navRoot) {
764
781
  try {
765
- for await (const handle of navRoot.values()) {
766
- const name = OpfsStorage2._extractHandleName(handle);
767
- if (name === this.rootDir && OpfsStorage2._isDirectoryHandle(handle))
768
- return handle;
769
- }
770
- return null;
782
+ const parts = this.rootDir.split("/").filter(Boolean);
783
+ const directoryHandle = await this.traverseDir(navRoot, parts);
784
+ return directoryHandle;
771
785
  } catch {
772
786
  return null;
773
787
  }
@@ -951,7 +965,6 @@ var AbstractGitAdapter = class {
951
965
  setLogger(logger) {
952
966
  this.logger = logger;
953
967
  }
954
- // Internal helpers to forward logs only when a logger is injected
955
968
  /**
956
969
  * Log debug messages when a logger is present.
957
970
  * @param _messages messages to log (unused when no logger)
@@ -1000,14 +1013,6 @@ var AbstractGitAdapter = class {
1000
1013
  }
1001
1014
  }
1002
1015
  }
1003
- /**
1004
- * Proxy to shared fetchWithRetry implementation
1005
- * @param input RequestInfo
1006
- * @param init RequestInit
1007
- * @param attempts retry attempts
1008
- * @param baseDelay base delay ms
1009
- * @returns Promise resolving to Response
1010
- */
1011
1016
  /**
1012
1017
  * Normalize different header-like shapes into a plain object.
1013
1018
  * @param headerLike headers in Headers, array, or plain object form
@@ -1116,14 +1121,6 @@ var AbstractGitAdapter = class {
1116
1121
  const jitter = Math.floor(Math.random() * base * 0.3);
1117
1122
  return base + jitter;
1118
1123
  }
1119
- /**
1120
- * Delegate to shared mapWithConcurrency implementation
1121
- * @template T,R
1122
- * @param items items to map
1123
- * @param mapper async mapper
1124
- * @param concurrency concurrency limit
1125
- * @returns Promise resolving to mapped results
1126
- */
1127
1124
  /**
1128
1125
  * Map items with limited concurrency by delegating to the shared helper.
1129
1126
  * @template T,R
@@ -1231,12 +1228,6 @@ var GitHubAdapter = class extends abstractAdapter_default {
1231
1228
  parents
1232
1229
  };
1233
1230
  }
1234
- /**
1235
- * コンテンツから sha1 を算出します。
1236
- * @param {string} content コンテンツ
1237
- * @returns {string} sha1 ハッシュ
1238
- */
1239
- // shaOf is inherited from AbstractGitAdapter
1240
1231
  /**
1241
1232
  * ブロブを作成またはキャッシュから取得します。
1242
1233
  * @param {any[]} changes 変更一覧(create/update を含む)
@@ -1251,11 +1242,6 @@ var GitHubAdapter = class extends abstractAdapter_default {
1251
1242
  map[r.path] = r.sha;
1252
1243
  return map;
1253
1244
  }
1254
- /**
1255
- * ブロブ作成用のヘルパー(createBlobs から抽出)
1256
- * @param {any} ch 変更エントリ
1257
- * @returns {Promise<{path:string,sha:string}>}
1258
- */
1259
1245
  /**
1260
1246
  * Create a blob for a change or return cached blobSha.
1261
1247
  * @param {any} ch change entry
@@ -1462,10 +1448,6 @@ var GitHubAdapter = class extends abstractAdapter_default {
1462
1448
  }
1463
1449
  throw new NonRetryableError("getRef: sha not found");
1464
1450
  }
1465
- /**
1466
- * Helper to fetch a ref URL and extract a SHA if present.
1467
- * Returns null when SHA not found.
1468
- */
1469
1451
  /**
1470
1452
  * Fetch a ref URL and extract a SHA if present.
1471
1453
  * @param {string} url API URL to fetch
@@ -1560,18 +1542,6 @@ var GitHubAdapter = class extends abstractAdapter_default {
1560
1542
  const enc = index.encoding || "utf-8";
1561
1543
  return { content: index.content, encoding: enc };
1562
1544
  }
1563
- /**
1564
- * Resolve a commit-ish (branch, tag, or SHA) to a commit SHA.
1565
- * Resolution order: branch -> tag -> commit endpoint -> treat as SHA
1566
- * Throws if resolution fails.
1567
- */
1568
- /**
1569
- * Resolve a commit-ish (branch, tag, or SHA) to a commit SHA.
1570
- * Resolution order: branch -> tag -> commit endpoint -> treat as SHA
1571
- * Throws if resolution fails.
1572
- * @param {string} reference commit-ish to resolve
1573
- * @returns {Promise<string>} resolved commit SHA
1574
- */
1575
1545
  /**
1576
1546
  * Resolve a commit-ish (branch, tag, or SHA) to a commit SHA.
1577
1547
  * Resolution order: branch -> tag -> commit endpoint -> treat as SHA
@@ -1611,11 +1581,6 @@ var GitHubAdapter = class extends abstractAdapter_default {
1611
1581
  }
1612
1582
  return null;
1613
1583
  }
1614
- /**
1615
- * Try to resolve reference as a branch and return its sha.
1616
- * @param {string} reference branch name
1617
- * @returns {Promise<string|null>} resolved sha or null
1618
- */
1619
1584
  /**
1620
1585
  * Try to resolve a branch name to a commit SHA.
1621
1586
  * @param {string} reference branch name
@@ -1625,11 +1590,6 @@ var GitHubAdapter = class extends abstractAdapter_default {
1625
1590
  const sha = await this.getRef(`heads/${reference}`);
1626
1591
  return sha || null;
1627
1592
  }
1628
- /**
1629
- * Try to resolve reference as a tag and return its sha.
1630
- * @param {string} reference tag name
1631
- * @returns {Promise<string|null>} resolved sha or null
1632
- */
1633
1593
  /**
1634
1594
  * Try to resolve a tag name to a commit SHA.
1635
1595
  * @param {string} reference tag name
@@ -1639,11 +1599,6 @@ var GitHubAdapter = class extends abstractAdapter_default {
1639
1599
  const sha = await this.getRef(`tags/${reference}`);
1640
1600
  return sha || null;
1641
1601
  }
1642
- /**
1643
- * Try to resolve reference using commits endpoint (could be SHA or other form).
1644
- * @param {string} reference commit-ish
1645
- * @returns {Promise<string|null>} resolved sha or null
1646
- */
1647
1602
  /**
1648
1603
  * Try to resolve via commits endpoint (may accept SHA or other forms).
1649
1604
  * @param {string} reference commit-ish
@@ -1659,11 +1614,6 @@ var GitHubAdapter = class extends abstractAdapter_default {
1659
1614
  }
1660
1615
  return null;
1661
1616
  }
1662
- /**
1663
- * Blob を取得して content を返す。取得失敗時は content=null を返す。
1664
- * @param {{sha:string,path:string}} f blob 情報
1665
- * @returns {Promise<{path:string,content:string|null}>}
1666
- */
1667
1617
  /**
1668
1618
  * Fetch a blob's content; return null content on failure.
1669
1619
  * @param {any} f blob metadata
@@ -1703,16 +1653,6 @@ var GitHubAdapter = class extends abstractAdapter_default {
1703
1653
  }
1704
1654
  return safe;
1705
1655
  }
1706
- /**
1707
- * Fetch a blob's content; return null content on failure.
1708
- * @param {any} f blob metadata
1709
- * @returns {Promise<{path:string,content:string|null}>}
1710
- */
1711
- /**
1712
- * リポジトリのスナップショットを取得します。
1713
- * @param {string} branch ブランチ名 (default: 'main')
1714
- * @returns {Promise<{headSha:string,shas:Record<string,string>,fetchContent:Function,snapshot:Record<string,string>}>}
1715
- */
1716
1656
  /**
1717
1657
  * Fetch repository snapshot: headSha, shas map and a fetchContent helper.
1718
1658
  * @param {string} branch branch name
@@ -1787,22 +1727,6 @@ var GitHubAdapter = class extends abstractAdapter_default {
1787
1727
  out[p] = content;
1788
1728
  return null;
1789
1729
  }
1790
- /**
1791
- * 指定パスのコンテンツを取得し、キャッシュと snapshot を更新します。
1792
- * @param {Map<string, any>} fileMap ファイルメタ情報マップ
1793
- * @param {Map<string, string>} contentCache キャッシュマップ
1794
- * @param {Record<string,string>} snapshot スナップショット出力マップ
1795
- * @param {string} p 取得対象パス
1796
- * @returns {Promise<string|null>} ファイル内容または null
1797
- */
1798
- /**
1799
- * Fetch single path content, update cache and snapshot.
1800
- * @param {Map<string, any>} fileMap file map
1801
- * @param {Map<string,string>} contentCache cache map
1802
- * @param {Record<string,string>} snapshot snapshot map
1803
- * @param {string} p path to fetch
1804
- * @returns {Promise<string|null>} content or null
1805
- */
1806
1730
  /**
1807
1731
  * Fetch single path content, update cache and snapshot.
1808
1732
  * @param {Map<string, any>} fileMap file map
@@ -1882,7 +1806,7 @@ var GitLabAdapter = class extends abstractAdapter_default {
1882
1806
  }
1883
1807
  /**
1884
1808
  * GitLab のページングヘッダを解析します(x-next-page / x-total-pages)。
1885
- * @returns {{nextPage?: number, lastPage?: number}} ページ番号情報
1809
+ * @returns {{nextPage?: number, lastPage?: number}} ページ番号情報
1886
1810
  */
1887
1811
  _parsePagingHeaders(resp) {
1888
1812
  const out = {};
@@ -1914,12 +1838,6 @@ var GitLabAdapter = class extends abstractAdapter_default {
1914
1838
  parents
1915
1839
  };
1916
1840
  }
1917
- /**
1918
- * コンテンツから sha1 を算出します。
1919
- * @param {string} content コンテンツ
1920
- * @returns {string} sha1 ハッシュ
1921
- */
1922
- // shaOf is inherited from AbstractGitAdapter
1923
1841
  /**
1924
1842
  * 変更一覧から blob sha のマップを作成します(疑似実装)。
1925
1843
  * @param {any[]} changes 変更一覧
@@ -1999,9 +1917,6 @@ var GitLabAdapter = class extends abstractAdapter_default {
1999
1917
  }
2000
1918
  return await this.postCommit(url, body);
2001
1919
  }
2002
- /**
2003
- * Retrieve project metadata (default branch, name, id) and cache it.
2004
- */
2005
1920
  /**
2006
1921
  * Retrieve project metadata (default branch, name, id) and cache it.
2007
1922
  * @returns {Promise<import('../virtualfs/types.ts').RepositoryMetadata>} repository metadata
@@ -2100,7 +2015,7 @@ var GitLabAdapter = class extends abstractAdapter_default {
2100
2015
  }
2101
2016
  /**
2102
2017
  * Convert change descriptors to GitLab API actions
2103
- * @returns {Array<any>} API-compatible actions array
2018
+ * @returns {Array<any>} API-compatible actions array
2104
2019
  */
2105
2020
  createActions(changes) {
2106
2021
  return changes.map((c) => {
@@ -2111,11 +2026,6 @@ var GitLabAdapter = class extends abstractAdapter_default {
2111
2026
  return { action: "update", file_path: c.path, content: c.content };
2112
2027
  });
2113
2028
  }
2114
- /**
2115
- * Verify remote branch head matches expected parent SHA.
2116
- * @throws Error if non-fast-forward
2117
- * @returns {Promise<void>}
2118
- */
2119
2029
  /**
2120
2030
  * Verify that remote branch head matches expected parent SHA.
2121
2031
  * Throws when non-fast-forward detected.
@@ -2150,10 +2060,6 @@ var GitLabAdapter = class extends abstractAdapter_default {
2150
2060
  }
2151
2061
  return index.id || index.commit || index;
2152
2062
  }
2153
- /**
2154
- * Post commit request and parse response
2155
- * @returns {Promise<any>}
2156
- */
2157
2063
  /**
2158
2064
  * Post commit request and return parsed commit response.
2159
2065
  * @param {string} url endpoint URL
@@ -2165,19 +2071,6 @@ var GitLabAdapter = class extends abstractAdapter_default {
2165
2071
  const text = await response.text().catch(() => "");
2166
2072
  return this.parseCommitResponse(text);
2167
2073
  }
2168
- /**
2169
- * fetch をリトライ付きで実行します。
2170
- * @param {string} url リクエスト URL
2171
- * @param {RequestInit} opts fetch オプション
2172
- * @param {number} [retries] 最大リトライ回数
2173
- * @returns {Promise<Response>} レスポンス
2174
- */
2175
- // fetchWithRetry is provided by AbstractGitAdapter
2176
- /**
2177
- * Wait helper for fetch retry backoff.
2178
- * @param attempt Attempt number
2179
- * @returns {Promise<void>} resolves after backoff
2180
- */
2181
2074
  /**
2182
2075
  * Wait helper for retry backoff.
2183
2076
  * @param {number} attempt attempt number
@@ -2187,28 +2080,6 @@ var GitLabAdapter = class extends abstractAdapter_default {
2187
2080
  const wait = this.backoffMs(attempt);
2188
2081
  await new Promise((r) => setTimeout(r, wait));
2189
2082
  }
2190
- /**
2191
- * ステータスが再試行対象か判定します。
2192
- * @param {number} status ステータスコード
2193
- * @returns {boolean}
2194
- */
2195
- // isRetryableStatus is provided by AbstractGitAdapter
2196
- /**
2197
- * バックオフ時間を計算します。
2198
- * @param {number} attempt 試行回数(1..)
2199
- * @returns {number} ミリ秒
2200
- */
2201
- // backoffMs provided by AbstractGitAdapter
2202
- // small concurrency mapper used for fetching files
2203
- /**
2204
- * 並列マッピングユーティリティ
2205
- * @template T, R
2206
- * @param {T[]} items 入力配列
2207
- * @param {(t:T)=>Promise<R>} mapper マッピング関数
2208
- * @param {number} concurrency 同時実行数
2209
- * @returns {Promise<R[]>}
2210
- */
2211
- // mapWithConcurrency provided by AbstractGitAdapter
2212
2083
  /**
2213
2084
  * Prepare JSON body for commit API call.
2214
2085
  * @returns {string} JSON body
@@ -2216,9 +2087,6 @@ var GitLabAdapter = class extends abstractAdapter_default {
2216
2087
  _prepareCommitBody(branch, message, actions) {
2217
2088
  return JSON.stringify({ branch, commit_message: message, actions });
2218
2089
  }
2219
- /**
2220
- * Optionally verify parent SHA; swallow non-422 errors.
2221
- */
2222
2090
  /**
2223
2091
  * Optionally verify parent SHA; rethrow errors after logging.
2224
2092
  * @param {string} expectedParentSha expected SHA
@@ -2237,7 +2105,7 @@ var GitLabAdapter = class extends abstractAdapter_default {
2237
2105
  /**
2238
2106
  * リポジトリのスナップショットを取得します。
2239
2107
  * @param {string} branch ブランチ名 (default: 'main')
2240
- * @returns {Promise<{headSha:string,shas:Record<string,string>,fetchContent:(paths:string[])=>Promise<Record<string,string>>}>}
2108
+ * @returns {Promise<{headSha:string,shas:Record<string,string>,fetchContent:(paths:string[])=>Promise<Record<string,string>>}>}
2241
2109
  */
2242
2110
  async fetchSnapshot(branch = "main", concurrency = 5) {
2243
2111
  const headSha = await this._determineHeadSha(branch);
@@ -2247,11 +2115,6 @@ var GitLabAdapter = class extends abstractAdapter_default {
2247
2115
  const fetchContent = (paths) => this._fetchContentFromFileSet(fileSet, cache, snapshot, paths, branch, concurrency);
2248
2116
  return { headSha, shas, fetchContent, snapshot };
2249
2117
  }
2250
- /**
2251
- * Determine the remote head SHA for a branch. Falls back to branch name on error.
2252
- * @param {string} branch Branch name
2253
- * @returns {Promise<string>} head SHA or branch
2254
- */
2255
2118
  /**
2256
2119
  * Determine the head SHA for a branch; fallback to branch name if unavailable.
2257
2120
  * @param {string} branch branch name
@@ -2274,11 +2137,6 @@ var GitLabAdapter = class extends abstractAdapter_default {
2274
2137
  }
2275
2138
  return branch;
2276
2139
  }
2277
- /**
2278
- * Fetch repository tree and build shas/fileSet.
2279
- * @param {string} branch Branch name
2280
- * @returns {Promise<{shas:Record<string,string>,fileSet:Set<string>}>}
2281
- */
2282
2140
  /**
2283
2141
  * Fetch repository tree and build shas map and fileSet.
2284
2142
  * @param {string} branch branch name
@@ -2290,10 +2148,6 @@ var GitLabAdapter = class extends abstractAdapter_default {
2290
2148
  const files = Array.isArray(treeJ) ? treeJ.filter((t) => t.type === "blob") : [];
2291
2149
  return this._buildShasAndFileSet(files);
2292
2150
  }
2293
- /**
2294
- * Helper to fetch files from the repository tree with caching and concurrency.
2295
- * @returns {Promise<Record<string,string>>}
2296
- */
2297
2151
  /**
2298
2152
  * Fetch contents for requested paths from a FileSet with caching.
2299
2153
  * @param {Set<string>} fileSet set of available files
@@ -2315,14 +2169,6 @@ var GitLabAdapter = class extends abstractAdapter_default {
2315
2169
  }, concurrency);
2316
2170
  return out;
2317
2171
  }
2318
- /**
2319
- * 指定パスのファイル内容を取得し、キャッシュと snapshot を更新します。
2320
- * @param {Map<string,string>} cache キャッシュマップ
2321
- * @param {Record<string,string>} snapshot スナップショットマップ
2322
- * @param {string} p ファイルパス
2323
- * @param {string} branch ブランチ名
2324
- * @returns {Promise<string|null>} ファイル内容または null
2325
- */
2326
2172
  /**
2327
2173
  * Fetch the content for a single file path, updating cache and snapshot.
2328
2174
  * @param {Map<string,string>} cache cache map
@@ -2345,12 +2191,6 @@ var GitLabAdapter = class extends abstractAdapter_default {
2345
2191
  }
2346
2192
  return null;
2347
2193
  }
2348
- /**
2349
- * ファイルの raw コンテンツを取得して返します。失敗時は null を返します。
2350
- * @param {string} path ファイルパス
2351
- * @param {string} branch ブランチ名
2352
- * @returns {Promise<string|null>} ファイル内容または null
2353
- */
2354
2194
  /**
2355
2195
  * Fetch raw file content from GitLab; return null on failure.
2356
2196
  * @param {string} path file path
@@ -2372,7 +2212,6 @@ var GitLabAdapter = class extends abstractAdapter_default {
2372
2212
  return null;
2373
2213
  }
2374
2214
  }
2375
- /** Build shas map and fileSet from tree entries */
2376
2215
  /**
2377
2216
  * Build shas map and fileSet from tree entries
2378
2217
  * @returns {{shas:Record<string,string>,fileSet:Set<string>}}
@@ -2389,18 +2228,6 @@ var GitLabAdapter = class extends abstractAdapter_default {
2389
2228
  }
2390
2229
  return { shas, fileSet };
2391
2230
  }
2392
- /**
2393
- * Resolve a commit-ish (branch name, tag name, or SHA) to a commit SHA.
2394
- * Resolution order: branch -> tag -> commits endpoint -> treat as SHA
2395
- * Throws if not resolvable.
2396
- */
2397
- /**
2398
- * Resolve a commit-ish (branch, tag, or SHA) to a commit SHA.
2399
- * Resolution order: branch -> tag -> commits endpoint -> treat as SHA
2400
- * Throws if not resolvable.
2401
- * @param {string} reference commit-ish to resolve
2402
- * @returns {Promise<string>} resolved commit SHA
2403
- */
2404
2231
  /**
2405
2232
  * Resolve a commit-ish (branch, tag, or SHA) to a commit SHA.
2406
2233
  * Resolution order: branch -> tag -> commits endpoint -> treat as SHA
@@ -3504,7 +3331,7 @@ var RemoteSynchronizer = class {
3504
3331
  /**
3505
3332
  * Read and parse the stored info entry for `path`.
3506
3333
  * Returns parsed object or null when missing/invalid.
3507
- * @returns {Promise<any|null>} parsed object or null
3334
+ * @returns {Promise<any|null>} parsed object or null
3508
3335
  */
3509
3336
  async _readInfoEntry(path) {
3510
3337
  try {
@@ -3527,7 +3354,7 @@ var RemoteSynchronizer = class {
3527
3354
  /**
3528
3355
  * Attempt to fetch base content using adapterInstance.getBlob.
3529
3356
  * Returns string when fetched, null when fetch attempted but failed, or undefined when adapter not supported.
3530
- * @returns {Promise<string|null|undefined>} fetched content, null, or undefined
3357
+ * @returns {Promise<string|null|undefined>} fetched content, null, or undefined
3531
3358
  */
3532
3359
  async _tryFetchBaseWithAdapter(adapterInstance, baseSha, path) {
3533
3360
  if (!adapterInstance || typeof adapterInstance.getBlob !== "function")
@@ -3563,7 +3390,7 @@ var RemoteSynchronizer = class {
3563
3390
  /**
3564
3391
  * Attempt to fetch raw file using adapterInstance._fetchFileRaw if available.
3565
3392
  * Returns string when fetched, null when attempted but failed, or undefined when adapter not supported.
3566
- * @returns {Promise<string|null|undefined>} fetched content, null, or undefined
3393
+ * @returns {Promise<string|null|undefined>} fetched content, null, or undefined
3567
3394
  */
3568
3395
  async _tryFetchRawFile(adapterInstance, path, ie) {
3569
3396
  if (!adapterInstance || typeof adapterInstance._fetchFileRaw !== "function")
@@ -3671,7 +3498,7 @@ var VirtualFS = class {
3671
3498
  if (options?.backend)
3672
3499
  this.backend = options.backend;
3673
3500
  else
3674
- this.backend = new OpfsStorage();
3501
+ this.backend = new OpfsStorage("default");
3675
3502
  if (options && options.logger)
3676
3503
  this.logger = options.logger;
3677
3504
  this.applier = new LocalChangeApplier(this.backend);
@@ -3711,22 +3538,6 @@ var VirtualFS = class {
3711
3538
  set lastCommitKey(k) {
3712
3539
  this.indexManager.setLastCommitKey(k);
3713
3540
  }
3714
- /**
3715
- * SHA-1 helper wrapper (delegates to ./hashUtils)
3716
- * @param {string} content - ハッシュ対象の文字列
3717
- * @returns {Promise<string>} SHA-1 ハッシュの16進表現
3718
- */
3719
- async shaOf(content) {
3720
- return await shaOf2(content);
3721
- }
3722
- /**
3723
- * SHA helper for Git blob formatting
3724
- * @param {string} content - blob コンテンツ
3725
- * @returns {Promise<string>} SHA-1 ハッシュの16進表現(git blob 用)
3726
- */
3727
- async shaOfGitBlob(content) {
3728
- return await shaOfGitBlob(content);
3729
- }
3730
3541
  /**
3731
3542
  * VirtualFS の初期化を行います(バックエンド初期化と index 読み込み)。
3732
3543
  * @returns {Promise<void>}
@@ -3866,6 +3677,312 @@ var VirtualFS = class {
3866
3677
  }
3867
3678
  return null;
3868
3679
  }
3680
+ /**
3681
+ * Helper: obtain backend listFilesRaw in a safe manner.
3682
+ * @returns {Promise<any[]>}
3683
+ */
3684
+ async _getBackendFilesRaw() {
3685
+ try {
3686
+ if (this.backend && typeof this.backend.listFilesRaw === "function") {
3687
+ return await this.backend.listFilesRaw();
3688
+ }
3689
+ } catch (error) {
3690
+ if (typeof console !== "undefined" && console.debug)
3691
+ console.debug("_getBackendFilesRaw failed", error);
3692
+ }
3693
+ return [];
3694
+ }
3695
+ /**
3696
+ * Helper: apply parsed info text into stats object when possible.
3697
+ * @param infoTxt raw info text
3698
+ * @param stats stats object to mutate
3699
+ * @returns {void}
3700
+ */
3701
+ _applyInfoTxtToStats(infoTxt, stats) {
3702
+ if (!infoTxt)
3703
+ return;
3704
+ try {
3705
+ const info = JSON.parse(infoTxt);
3706
+ if (typeof info.baseSha === "string")
3707
+ stats.gitBlobSha = info.baseSha;
3708
+ if (typeof info.updatedAt === "number")
3709
+ stats.mtime = new Date(info.updatedAt);
3710
+ if (typeof info.size === "number")
3711
+ stats.size = info.size;
3712
+ } catch (error) {
3713
+ if (typeof console !== "undefined" && console.debug)
3714
+ console.debug("parse info failed", error);
3715
+ }
3716
+ }
3717
+ /**
3718
+ * Find a matched backend entry for the given filepath.
3719
+ * @param filepath target filepath
3720
+ * @param filesRaw backend raw listing
3721
+ * @returns {any|null}
3722
+ */
3723
+ _findMatchedFile(filepath, filesRaw) {
3724
+ if (!Array.isArray(filesRaw))
3725
+ return null;
3726
+ return filesRaw.find((f) => {
3727
+ if (!f || !f.path)
3728
+ return false;
3729
+ return f.path === filepath || f.path.endsWith("/" + filepath) || f.path.endsWith("/" + filepath);
3730
+ });
3731
+ }
3732
+ /**
3733
+ * Create default stats object with consistent shape.
3734
+ * @param now current Date
3735
+ * @returns {any}
3736
+ */
3737
+ _createDefaultStats(now) {
3738
+ return {
3739
+ dev: 0,
3740
+ ino: 0,
3741
+ mode: 33188,
3742
+ nlink: 1,
3743
+ uid: 0,
3744
+ gid: 0,
3745
+ rdev: 0,
3746
+ size: 0,
3747
+ blksize: void 0,
3748
+ blocks: void 0,
3749
+ atime: now,
3750
+ mtime: now,
3751
+ ctime: now,
3752
+ birthtime: now,
3753
+ /** @returns {boolean} */
3754
+ isFile: () => true,
3755
+ /** @returns {boolean} */
3756
+ isDirectory: () => false
3757
+ };
3758
+ }
3759
+ /**
3760
+ * Populate stats.gitCommitSha from adapterMeta if available.
3761
+ * @param stats stats object to mutate
3762
+ * @returns {void}
3763
+ */
3764
+ _populateCommitShaFromMeta(stats) {
3765
+ if (!stats.gitCommitSha && this.adapterMeta && this.adapterMeta.opts && this.adapterMeta.opts.branch) {
3766
+ stats.gitCommitSha = this.adapterMeta.opts.branch;
3767
+ }
3768
+ }
3769
+ /**
3770
+ * Try to resolve commit SHA from an instantiated adapter when needed.
3771
+ * @param stats stats object to mutate
3772
+ * @returns {Promise<void>}
3773
+ */
3774
+ async _resolveCommitShaFromAdapter(stats) {
3775
+ const instAdapter = await this._safeGetAdapterInstance();
3776
+ if (!instAdapter || stats.gitCommitSha)
3777
+ return;
3778
+ if (typeof instAdapter.resolveRef !== "function")
3779
+ return;
3780
+ try {
3781
+ const branch = this.adapterMeta && this.adapterMeta.opts && this.adapterMeta.opts.branch || "main";
3782
+ const resolved = await instAdapter.resolveRef(branch);
3783
+ if (resolved)
3784
+ stats.gitCommitSha = resolved;
3785
+ } catch (error) {
3786
+ if (typeof console !== "undefined" && console.debug)
3787
+ console.debug("_resolveCommitShaFromAdapter resolveRef failed", error);
3788
+ }
3789
+ }
3790
+ /**
3791
+ * Safely get adapter instance, returning null on error.
3792
+ * @returns {Promise<any|null>}
3793
+ */
3794
+ async _safeGetAdapterInstance() {
3795
+ try {
3796
+ return await this.getAdapterInstance();
3797
+ } catch (error) {
3798
+ if (typeof console !== "undefined" && console.debug)
3799
+ console.debug("_safeGetAdapterInstance failed", error);
3800
+ return null;
3801
+ }
3802
+ }
3803
+ /**
3804
+ * Helper: populate stats.gitCommitSha using adapterMeta or adapter.resolveRef when available.
3805
+ * @param stats stats object to mutate
3806
+ * @returns {Promise<void>}
3807
+ */
3808
+ async _resolveAdapterCommitShaIfNeeded(stats) {
3809
+ this._populateCommitShaFromMeta(stats);
3810
+ if (!stats.gitCommitSha)
3811
+ await this._resolveCommitShaFromAdapter(stats);
3812
+ }
3813
+ /**
3814
+ * Determine whether a normalized path is an exact file entry in the provided entries.
3815
+ * @param normalizedDirectory normalized directory string
3816
+ * @param keys index keys array
3817
+ * @param entries index entries object
3818
+ * @returns {boolean}
3819
+ */
3820
+ _isExactFile(normalizedDirectory, keys, entries) {
3821
+ return keys.includes(normalizedDirectory) && entries[normalizedDirectory] && entries[normalizedDirectory].state !== "deleted";
3822
+ }
3823
+ /**
3824
+ * Collect immediate child names from index entries for given directory.
3825
+ * @param normalizedDirectory normalized directory string
3826
+ * @param entries index entries object
3827
+ * @returns {Set<string>} set of immediate child names
3828
+ */
3829
+ _collectNamesFromIndex(normalizedDirectory, entries) {
3830
+ const outNames = /* @__PURE__ */ new Set();
3831
+ const keys = Object.keys(entries || {});
3832
+ for (const k of keys) {
3833
+ const v = entries[k];
3834
+ if (v && v.state === "deleted")
3835
+ continue;
3836
+ if (normalizedDirectory === "." || normalizedDirectory === "") {
3837
+ this._collectNamesFromIndexRoot(k, outNames);
3838
+ continue;
3839
+ }
3840
+ this._processIndexKeyForDirectory(k, normalizedDirectory, outNames);
3841
+ }
3842
+ return outNames;
3843
+ }
3844
+ /**
3845
+ * Process a single index key for a non-root directory and add immediate child when applicable.
3846
+ * @param key index key
3847
+ * @param normalizedDirectory normalized directory string
3848
+ * @param outNames set to mutate
3849
+ * @returns {void}
3850
+ */
3851
+ _processIndexKeyForDirectory(key, normalizedDirectory, outNames) {
3852
+ if (key === normalizedDirectory)
3853
+ return;
3854
+ if (key.startsWith(normalizedDirectory + "/")) {
3855
+ const rest = key.slice(normalizedDirectory.length + 1);
3856
+ const first = rest.indexOf("/") === -1 ? rest : rest.slice(0, rest.indexOf("/"));
3857
+ outNames.add(first);
3858
+ }
3859
+ }
3860
+ /**
3861
+ * Safe wrapper for backend.listFiles returning [] on failure.
3862
+ * @param normalizedDirectory directory path
3863
+ * @returns {Promise<any[]>}
3864
+ */
3865
+ async _getBackendList(normalizedDirectory) {
3866
+ try {
3867
+ return await this.backend.listFiles(normalizedDirectory, void 0, false);
3868
+ } catch (error) {
3869
+ if (typeof console !== "undefined" && console.debug)
3870
+ console.debug("_getBackendList failed", error);
3871
+ return [];
3872
+ }
3873
+ }
3874
+ /**
3875
+ * Helper for collecting names when scanning root directory entries.
3876
+ * @param key index key
3877
+ * @param outNames set to mutate
3878
+ * @returns {void}
3879
+ */
3880
+ _collectNamesFromIndexRoot(key, outNames) {
3881
+ const first = key.indexOf("/") === -1 ? key : key.slice(0, key.indexOf("/"));
3882
+ outNames.add(first);
3883
+ }
3884
+ /**
3885
+ * Check whether normalizedDirectory corresponds to an exact file entry.
3886
+ * @param normalizedDirectory normalized directory string
3887
+ * @param entries index entries object
3888
+ * @returns {boolean}
3889
+ */
3890
+ _hasExactEntry(normalizedDirectory, entries) {
3891
+ const keys = Object.keys(entries || {});
3892
+ return this._isExactFile(normalizedDirectory, keys, entries);
3893
+ }
3894
+ /**
3895
+ * Consult backend.listFiles to collect immediate child names for given directory.
3896
+ * Best-effort: logs and returns empty set on failure.
3897
+ * @param normalizedDirectory normalized directory string
3898
+ * @returns {Promise<Set<string>>}
3899
+ */
3900
+ async _collectNamesFromBackend(normalizedDirectory) {
3901
+ const outNames = /* @__PURE__ */ new Set();
3902
+ if (!this._backendSupportsListFiles())
3903
+ return outNames;
3904
+ const backendList = await this._getBackendList(normalizedDirectory);
3905
+ if (!Array.isArray(backendList) || backendList.length === 0)
3906
+ return outNames;
3907
+ for (const it of backendList)
3908
+ this._processBackendEntry(it, normalizedDirectory, outNames);
3909
+ return outNames;
3910
+ }
3911
+ /**
3912
+ * Return true when backend supports listFiles
3913
+ * @returns {boolean}
3914
+ */
3915
+ _backendSupportsListFiles() {
3916
+ return !!(this.backend && typeof this.backend.listFiles === "function");
3917
+ }
3918
+ /**
3919
+ * Process a single backend listFiles entry and add immediate child name to outNames when applicable.
3920
+ * @param it backend entry
3921
+ * @param normalizedDirectory normalized directory string
3922
+ * @param outNames set to mutate
3923
+ * @returns {void}
3924
+ */
3925
+ _processBackendEntry(it, normalizedDirectory, outNames) {
3926
+ try {
3927
+ if (!it || !it.path)
3928
+ return;
3929
+ const p = it.path;
3930
+ if (p === normalizedDirectory)
3931
+ return;
3932
+ if (p.startsWith(normalizedDirectory + "/")) {
3933
+ const rest = p.slice(normalizedDirectory.length + 1);
3934
+ const first = rest.indexOf("/") === -1 ? rest : rest.slice(0, rest.indexOf("/"));
3935
+ outNames.add(first);
3936
+ }
3937
+ } catch (error) {
3938
+ if (typeof console !== "undefined" && console.debug)
3939
+ console.debug("_processBackendEntry failed", error);
3940
+ }
3941
+ }
3942
+ /**
3943
+ * Build Dirent-like lightweight objects for given names.
3944
+ * @param names array of names
3945
+ * @param keys array of index keys
3946
+ * @param entries index entries object
3947
+ * @param normalizedDirectory normalized directory string
3948
+ * @returns {Array<any>} array of Dirent-like objects
3949
+ */
3950
+ _buildDirentTypes(names, keys, entries, normalizedDirectory) {
3951
+ const out = [];
3952
+ for (const name of names) {
3953
+ const childPath = normalizedDirectory === "." ? name : `${normalizedDirectory}/${name}`;
3954
+ const { isFile, isDirectory } = this._determineChildType(childPath, keys, entries);
3955
+ const _isFileFunction = function() {
3956
+ return isFile && !isDirectory;
3957
+ };
3958
+ const _isDirectoryFunction = function() {
3959
+ return isDirectory;
3960
+ };
3961
+ out.push({ name, isFile: _isFileFunction, isDirectory: _isDirectoryFunction });
3962
+ }
3963
+ return out;
3964
+ }
3965
+ /**
3966
+ * Determine whether a childPath corresponds to a file, directory, or both.
3967
+ * @param childPath path of child
3968
+ * @param keys index keys
3969
+ * @param entries index entries
3970
+ * @returns {{isFile:boolean,isDirectory:boolean}}
3971
+ */
3972
+ _determineChildType(childPath, keys, entries) {
3973
+ let isDirectory = false;
3974
+ let isFile = false;
3975
+ for (const k of keys) {
3976
+ if (k === childPath && entries[k] && entries[k].state !== "deleted") {
3977
+ isFile = true;
3978
+ }
3979
+ if (k.startsWith(childPath + "/")) {
3980
+ isDirectory = true;
3981
+ break;
3982
+ }
3983
+ }
3984
+ return { isFile, isDirectory };
3985
+ }
3869
3986
  /**
3870
3987
  * Return persisted adapter metadata (if any).
3871
3988
  * @returns {any|null}
@@ -3883,15 +4000,6 @@ var VirtualFS = class {
3883
4000
  await this.localFileManager.writeFile(filepath, content);
3884
4001
  await this.loadIndex();
3885
4002
  }
3886
- /**
3887
- * ファイルを削除します(トゥームストーン作成を含む)。
3888
- * @param {string} filepath ファイルパス
3889
- * @returns {Promise<void>}
3890
- */
3891
- async deleteFile(filepath) {
3892
- await this.localFileManager.deleteFile(filepath);
3893
- await this.loadIndex();
3894
- }
3895
4003
  /**
3896
4004
  * rename を delete + create の合成で行うヘルパ
3897
4005
  * @param from 元パス
@@ -3902,7 +4010,7 @@ var VirtualFS = class {
3902
4010
  if (content === null)
3903
4011
  throw new Error("source not found");
3904
4012
  await this.writeFile(to, content);
3905
- await this.deleteFile(from);
4013
+ await this.unlink(from);
3906
4014
  }
3907
4015
  /**
3908
4016
  * ワークスペース/ベースからファイル内容を読み出します。
@@ -3919,7 +4027,9 @@ var VirtualFS = class {
3919
4027
  await this.remoteSynchronizer.fetchBaseIfMissing(filepath, adapter);
3920
4028
  content = await this.localFileManager.readFile(filepath);
3921
4029
  }
3922
- } catch {
4030
+ } catch (error) {
4031
+ if (typeof console !== "undefined" && console.debug)
4032
+ console.debug("readFile on-demand fetch failed", error);
3923
4033
  }
3924
4034
  return content;
3925
4035
  }
@@ -3931,6 +4041,151 @@ var VirtualFS = class {
3931
4041
  async readConflict(filepath) {
3932
4042
  return await this.conflictManager.readConflict(filepath);
3933
4043
  }
4044
+ /**
4045
+ * fs.stat 互換: 指定ファイルのメタ情報を返す
4046
+ * ワークスペース上の情報を優先し、未取得時は Git のメタ情報で補完する。
4047
+ * @returns {Promise<any>} stats オブジェクト
4048
+ */
4049
+ async stat(filepath) {
4050
+ if (!filepath || typeof filepath !== "string")
4051
+ throw new TypeError("filepath is required");
4052
+ const filesRaw = await this._getBackendFilesRaw();
4053
+ const matched = this._findMatchedFile(filepath, filesRaw);
4054
+ const now = /* @__PURE__ */ new Date();
4055
+ const stats = this._createDefaultStats(now);
4056
+ try {
4057
+ const infoTxt = await this.backend.readBlob(filepath, "info");
4058
+ this._applyInfoTxtToStats(infoTxt, stats);
4059
+ } catch (error) {
4060
+ if (typeof console !== "undefined" && console.debug)
4061
+ console.debug("stat readBlob failed", error);
4062
+ }
4063
+ if (matched) {
4064
+ stats.workspacePath = matched.path;
4065
+ }
4066
+ await this._resolveAdapterCommitShaIfNeeded(stats);
4067
+ return stats;
4068
+ }
4069
+ /**
4070
+ * fs.unlink 互換: ファイルを削除する
4071
+ */
4072
+ async unlink(filepath) {
4073
+ if (!filepath || typeof filepath !== "string")
4074
+ throw new TypeError("filepath is required");
4075
+ await this.localFileManager.deleteFile(filepath);
4076
+ await this.loadIndex();
4077
+ }
4078
+ /**
4079
+ * fs.mkdir 互換 (簡易実装): workspace 側にディレクトリ情報を書き込む
4080
+ */
4081
+ async mkdir(dirpath, _options) {
4082
+ if (!dirpath || typeof dirpath !== "string")
4083
+ throw new TypeError("dirpath is required");
4084
+ const info = { path: dirpath, state: "dir", createdAt: Date.now() };
4085
+ if (this.backend && typeof this.backend.writeBlob === "function") {
4086
+ await this.backend.writeBlob(dirpath, JSON.stringify(info), "info-workspace").catch((error) => {
4087
+ throw Object.assign(new Error("\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u4F5C\u6210\u5931\u6557"), { code: "EEXIST", cause: error });
4088
+ });
4089
+ }
4090
+ }
4091
+ /**
4092
+ * fs.rmdir 互換 (簡易実装)
4093
+ */
4094
+ async rmdir(dirpath, options) {
4095
+ if (!dirpath || typeof dirpath !== "string")
4096
+ throw new TypeError("dirpath is required");
4097
+ const children = await this._listChildrenOfDir(dirpath);
4098
+ if (children.length > 0 && !(options && options.recursive)) {
4099
+ const errorObject = new Error("Directory not empty");
4100
+ errorObject.code = "ENOTEMPTY";
4101
+ throw errorObject;
4102
+ }
4103
+ if (options && options.recursive)
4104
+ await this._deleteChildrenRecursive(children);
4105
+ }
4106
+ /**
4107
+ * Return list of child paths for given dirpath based on index entries.
4108
+ * @param dirpath directory path
4109
+ * @returns {Promise<string[]>}
4110
+ */
4111
+ async _listChildrenOfDir(dirpath) {
4112
+ const paths = await this.listPaths();
4113
+ return paths.filter((p) => p === dirpath || p.startsWith(dirpath + "/"));
4114
+ }
4115
+ /**
4116
+ * Delete array of children using localFileManager, logging failures per-child.
4117
+ * @param children array of paths
4118
+ * @returns {Promise<void>}
4119
+ */
4120
+ async _deleteChildrenRecursive(children) {
4121
+ for (const p of children) {
4122
+ try {
4123
+ await this.localFileManager.deleteFile(p);
4124
+ } catch (error) {
4125
+ if (typeof console !== "undefined" && console.debug)
4126
+ console.debug("rmdir recursive delete failed for", p, error);
4127
+ }
4128
+ }
4129
+ }
4130
+ /**
4131
+ * fs.readdir 互換 (簡易実装)
4132
+ * @returns {Promise<string[]|Array<any>>}
4133
+ */
4134
+ async readdir(dirpath, options) {
4135
+ if (!dirpath || typeof dirpath !== "string")
4136
+ throw new TypeError("dirpath is required");
4137
+ const index = await this.indexManager.getIndex();
4138
+ const entries = index && index.entries || {};
4139
+ const keys = Object.keys(entries);
4140
+ const names = await this._gatherDirectoryNames(dirpath, entries, keys);
4141
+ const maybeEmpty = this._returnIfNoNames(names, options);
4142
+ if (maybeEmpty !== null)
4143
+ return maybeEmpty;
4144
+ const normalizedDirectory = dirpath === "" ? "." : dirpath;
4145
+ if (options && options.withFileTypes)
4146
+ return this._buildDirentTypes(names, keys, entries, normalizedDirectory);
4147
+ return names;
4148
+ }
4149
+ /**
4150
+ * Return an empty array when names is empty according to options, else null to continue.
4151
+ * @param names array of names
4152
+ * @param options readdir options
4153
+ * @returns {Array<any>|null}
4154
+ */
4155
+ _returnIfNoNames(names, options) {
4156
+ if (!names || names.length === 0)
4157
+ return options && options.withFileTypes ? [] : [];
4158
+ return null;
4159
+ }
4160
+ /**
4161
+ * Gather immediate child names for a directory using index and backend as fallback.
4162
+ * Throws ENOTDIR when the path represents a file.
4163
+ * @param dirpath original directory path
4164
+ * @param entries index entries object
4165
+ * @param keys array of index keys
4166
+ * @returns {Promise<string[]>} immediate child names
4167
+ */
4168
+ async _gatherDirectoryNames(dirpath, entries, keys) {
4169
+ const normalizedDirectory = dirpath === "" ? "." : dirpath;
4170
+ const outNames = /* @__PURE__ */ new Set();
4171
+ const isExactFile = this._isExactFile(normalizedDirectory, keys, entries);
4172
+ const indexNames = this._collectNamesFromIndex(normalizedDirectory, entries);
4173
+ for (const n of indexNames)
4174
+ outNames.add(n);
4175
+ if (outNames.size === 0 && normalizedDirectory !== "." && !isExactFile) {
4176
+ const backendNames = await this._collectNamesFromBackend(normalizedDirectory);
4177
+ for (const n of backendNames)
4178
+ outNames.add(n);
4179
+ }
4180
+ if (isExactFile && outNames.size === 0) {
4181
+ const errorObject = new Error("\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3067\u306F\u3042\u308A\u307E\u305B\u3093");
4182
+ errorObject.code = "ENOTDIR";
4183
+ throw errorObject;
4184
+ }
4185
+ if (outNames.size === 0)
4186
+ return [];
4187
+ return Array.from(outNames);
4188
+ }
3934
4189
  /**
3935
4190
  * 指定パスのリモート衝突ファイル (.git-conflict/) を削除して
3936
4191
  * 競合を解消済とマークします。
@@ -3974,12 +4229,9 @@ var VirtualFS = class {
3974
4229
  const entries = index && index.entries || {};
3975
4230
  const out = [];
3976
4231
  for (const k of Object.keys(entries)) {
3977
- try {
3978
- const v = entries[k];
3979
- if (v && v.state === "deleted")
3980
- continue;
3981
- } catch {
3982
- }
4232
+ const v = entries[k];
4233
+ if (v && v.state === "deleted")
4234
+ continue;
3983
4235
  out.push(k);
3984
4236
  }
3985
4237
  return out;
@@ -4035,7 +4287,9 @@ var VirtualFS = class {
4035
4287
  if (input.parentSha && typeof adapter.getCommitTreeSha === "function") {
4036
4288
  try {
4037
4289
  baseTreeSha = await adapter.getCommitTreeSha(input.parentSha);
4038
- } catch {
4290
+ } catch (error) {
4291
+ if (typeof console !== "undefined" && console.debug)
4292
+ console.debug("getCommitTreeSha failed, continuing without baseTree", error);
4039
4293
  baseTreeSha = void 0;
4040
4294
  }
4041
4295
  }
@@ -4194,7 +4448,7 @@ apigit-commit-key:${input.commitKey}`;
4194
4448
  /**
4195
4449
  * Persist the requested branch into adapter metadata (best-effort).
4196
4450
  * @param {string} branch branch name to persist
4197
- * @returns {Promise<void>}
4451
+ * @returns {Promise<void>}
4198
4452
  */
4199
4453
  async _persistAdapterBranchMeta(branch, adapterInstance) {
4200
4454
  const meta = this.adapterMeta && this.adapterMeta.opts ? { ...this.adapterMeta } : await this.getAdapter();
@@ -4398,7 +4652,7 @@ apigit-commit-key:${input.commitKey}`;
4398
4652
  /**
4399
4653
  * Convenience to get default branch name from adapter repository metadata.
4400
4654
  * Returns null when adapter not available.
4401
- * @returns {Promise<string|null>}
4655
+ * @returns {Promise<string|null>}
4402
4656
  */
4403
4657
  async getDefaultBranch() {
4404
4658
  const instAdapter = await this.getAdapterInstance();
@@ -4438,11 +4692,6 @@ apigit-commit-key:${input.commitKey}`;
4438
4692
  console.debug("persist repository metadata aborted", error);
4439
4693
  }
4440
4694
  }
4441
- /**
4442
- * Normalize a resolved descriptor (string headSha or object) into a
4443
- * RemoteSnapshotDescriptor or null. Helper to reduce cognitive complexity.
4444
- * @returns {Promise<RemoteSnapshotDescriptor|null>} 正規化された descriptor または null
4445
- */
4446
4695
  /**
4447
4696
  * Try persisting repository metadata when available. Best-effort.
4448
4697
  * @param instAdapter adapter instance or null
@@ -4546,7 +4795,7 @@ apigit-commit-key:${input.commitKey}`;
4546
4795
  input.changes = await this.getChangeSet();
4547
4796
  }
4548
4797
  if (!input.commitKey) {
4549
- input.commitKey = await this.shaOf((input.parentSha || "") + JSON.stringify(input.changes));
4798
+ input.commitKey = await shaOf2((input.parentSha || "") + JSON.stringify(input.changes));
4550
4799
  }
4551
4800
  const instAdapter = await this.getAdapterInstance();
4552
4801
  if (!instAdapter) {
@@ -4575,7 +4824,7 @@ var virtualfs_default = VirtualFS;
4575
4824
  var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
4576
4825
  /**
4577
4826
  * 環境に IndexedDB が存在するかを同期検査します。
4578
- * @returns {boolean} 利用可能なら true
4827
+ * @returns {boolean} 利用可能なら true
4579
4828
  */
4580
4829
  static canUse() {
4581
4830
  try {
@@ -4587,6 +4836,8 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
4587
4836
  dbName;
4588
4837
  dbPromise;
4589
4838
  currentBranch = null;
4839
+ root;
4840
+ rootPrefix = "";
4590
4841
  static VAR_WORKSPACE_BASE = "workspace";
4591
4842
  // Historically this was a separate workspace-info store, but some test
4592
4843
  // fakes expect info entries to be available in 'git-info'. Alias the
@@ -4600,7 +4851,7 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
4600
4851
  /** 利用可能な DB 名の一覧を返す
4601
4852
  * @returns {string[]} available root names
4602
4853
  */
4603
- static async availableRoots() {
4854
+ static async availableRoots(namespace) {
4604
4855
  const g = globalThis;
4605
4856
  const idb = g.indexedDB;
4606
4857
  if (!idb)
@@ -4608,7 +4859,12 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
4608
4859
  if (typeof idb.databases !== "function")
4609
4860
  return [];
4610
4861
  try {
4611
- return await IndexedDatabaseStorage2._namesFromDatabases(idb);
4862
+ const names = await IndexedDatabaseStorage2._namesFromDatabases(idb);
4863
+ if (!namespace)
4864
+ return names;
4865
+ if (names.includes(namespace))
4866
+ return ["apigit_storage"];
4867
+ return [];
4612
4868
  } catch {
4613
4869
  return [];
4614
4870
  }
@@ -4628,8 +4884,10 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
4628
4884
  return Array.from(new Set(names));
4629
4885
  }
4630
4886
  /** コンストラクタ */
4631
- constructor(root) {
4632
- this.dbName = root ?? IndexedDatabaseStorage2.DEFAULT_DB_NAME;
4887
+ constructor(namespace, _root) {
4888
+ this.dbName = namespace || IndexedDatabaseStorage2.DEFAULT_DB_NAME;
4889
+ this.root = _root || void 0;
4890
+ this.rootPrefix = this.root ? `${this.root}_` : "";
4633
4891
  this.dbPromise = this.openDb();
4634
4892
  }
4635
4893
  /**
@@ -4661,27 +4919,26 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
4661
4919
  */
4662
4920
  /**
4663
4921
  * Handle DB upgrade event and create required object stores.
4664
- * @param ev Upgrade event
4922
+ * Creates the object stores used by this backend, names are resolved
4923
+ * through `_storeName` to include any configured `_root` prefix.
4924
+ * @param {Event} event - Upgrade event from `indexedDB.open`
4665
4925
  * @returns {void}
4666
4926
  */
4667
4927
  _handleUpgrade(event) {
4668
4928
  const database = event.target.result;
4669
- if (!database.objectStoreNames.contains(IndexedDatabaseStorage2.VAR_WORKSPACE_BASE))
4670
- database.createObjectStore(IndexedDatabaseStorage2.VAR_WORKSPACE_BASE);
4671
- if (!database.objectStoreNames.contains(IndexedDatabaseStorage2.VAR_WORKSPACE_INFO))
4672
- database.createObjectStore(IndexedDatabaseStorage2.VAR_WORKSPACE_INFO);
4673
- if (!database.objectStoreNames.contains(IndexedDatabaseStorage2.VAR_BASE))
4674
- database.createObjectStore(IndexedDatabaseStorage2.VAR_BASE);
4675
- if (!database.objectStoreNames.contains(IndexedDatabaseStorage2.VAR_CONFLICT))
4676
- database.createObjectStore(IndexedDatabaseStorage2.VAR_CONFLICT);
4677
- if (!database.objectStoreNames.contains(IndexedDatabaseStorage2.VAR_INFO))
4678
- database.createObjectStore(IndexedDatabaseStorage2.VAR_INFO);
4679
- if (!database.objectStoreNames.contains("index"))
4680
- database.createObjectStore("index");
4929
+ if (!database.objectStoreNames.contains(this._storeName(IndexedDatabaseStorage2.VAR_WORKSPACE_BASE)))
4930
+ database.createObjectStore(this._storeName(IndexedDatabaseStorage2.VAR_WORKSPACE_BASE));
4931
+ if (!database.objectStoreNames.contains(this._storeName(IndexedDatabaseStorage2.VAR_WORKSPACE_INFO)))
4932
+ database.createObjectStore(this._storeName(IndexedDatabaseStorage2.VAR_WORKSPACE_INFO));
4933
+ if (!database.objectStoreNames.contains(this._storeName(IndexedDatabaseStorage2.VAR_BASE)))
4934
+ database.createObjectStore(this._storeName(IndexedDatabaseStorage2.VAR_BASE));
4935
+ if (!database.objectStoreNames.contains(this._storeName(IndexedDatabaseStorage2.VAR_CONFLICT)))
4936
+ database.createObjectStore(this._storeName(IndexedDatabaseStorage2.VAR_CONFLICT));
4937
+ if (!database.objectStoreNames.contains(this._storeName(IndexedDatabaseStorage2.VAR_INFO)))
4938
+ database.createObjectStore(this._storeName(IndexedDatabaseStorage2.VAR_INFO));
4939
+ if (!database.objectStoreNames.contains(this._storeName("index")))
4940
+ database.createObjectStore(this._storeName("index"));
4681
4941
  }
4682
- /**
4683
- * 指定 DB に対する onversionchange ハンドラを生成します。
4684
- */
4685
4942
  /**
4686
4943
  * Create a handler to close DB on version change.
4687
4944
  * @param dbParam Target DB
@@ -4692,9 +4949,6 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
4692
4949
  databaseParameter.close();
4693
4950
  };
4694
4951
  }
4695
- /**
4696
- * DB open の成功ハンドラ
4697
- */
4698
4952
  /**
4699
4953
  * Called when DB open succeeds.
4700
4954
  * @param req IDB open request
@@ -4706,9 +4960,6 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
4706
4960
  database.onversionchange = this._makeVersionChangeHandler(database);
4707
4961
  resolve(database);
4708
4962
  }
4709
- /**
4710
- * DB open のエラーハンドラ
4711
- */
4712
4963
  /**
4713
4964
  * Called when DB open errors.
4714
4965
  * @param req IDB open request
@@ -4718,22 +4969,19 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
4718
4969
  _onOpenError(request, reject) {
4719
4970
  reject(request.error);
4720
4971
  }
4721
- /**
4722
- * トランザクションラッパー。cb 内の処理をトランザクションで実行し、
4723
- * 必要なら再試行します。
4724
- */
4725
4972
  /**
4726
4973
  * トランザクションラッパー。cb 内の処理をトランザクションで実行し、必要なら再試行します。
4727
4974
  * @returns {Promise<void>} トランザクション処理完了時に解決
4728
4975
  */
4729
4976
  async tx(storeName, mode, callback) {
4977
+ const physical = this._storeName(storeName);
4730
4978
  try {
4731
- return await this._performTxAttempt(storeName, mode, callback);
4979
+ return await this._performTxAttempt(physical, mode, callback);
4732
4980
  } catch (error) {
4733
4981
  const isInvalidState = error && (error.name === "InvalidStateError" || /closing/i.test(String(error.message || "")));
4734
4982
  if (isInvalidState) {
4735
4983
  this.dbPromise = this.openDb();
4736
- return await this._performTxAttempt(storeName, mode, callback);
4984
+ return await this._performTxAttempt(physical, mode, callback);
4737
4985
  }
4738
4986
  throw error;
4739
4987
  }
@@ -4830,10 +5078,6 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
4830
5078
  return orig.apply(target, arguments_);
4831
5079
  };
4832
5080
  }
4833
- /**
4834
- * Schedule a microtask to invoke tx.oncomplete in case fake IndexedDB
4835
- * implementations never fire it.
4836
- */
4837
5081
  /**
4838
5082
  * Schedule a microtask to invoke tx.oncomplete in case fake IndexedDB
4839
5083
  * implementations never fire it.
@@ -4849,7 +5093,6 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
4849
5093
  }
4850
5094
  }, 0);
4851
5095
  }
4852
- // legacy canUseOpfs removed; use static canUse() instead
4853
5096
  /**
4854
5097
  * index を読み出す
4855
5098
  * @returns {Promise<IndexFile|null>} 読み出した IndexFile、存在しなければ null
@@ -4881,10 +5124,6 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
4881
5124
  this.currentBranch = null;
4882
5125
  }
4883
5126
  }
4884
- /**
4885
- * Apply metadata to result. @returns void
4886
- * @returns {void}
4887
- */
4888
5127
  /**
4889
5128
  * Load workspace-local info entries into result.entries (workspace overrides branch-scoped)
4890
5129
  * @param result IndexFile being populated
@@ -4939,8 +5178,9 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
4939
5178
  async _readIndexMeta(database) {
4940
5179
  return await new Promise((resolve) => {
4941
5180
  try {
4942
- const tx = database.transaction("index", "readonly");
4943
- const store = tx.objectStore("index");
5181
+ const indexName = this._storeName("index");
5182
+ const tx = database.transaction(indexName, "readonly");
5183
+ const store = tx.objectStore(indexName);
4944
5184
  const request = store.get("index");
4945
5185
  request.onsuccess = () => {
4946
5186
  resolve(request.result ?? null);
@@ -5120,14 +5360,7 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
5120
5360
  return await this._getFromStore(storeName, key);
5121
5361
  }
5122
5362
  /**
5123
- * Read blob when a segment is provided. Handles info-workspace/info-git/info and other segments.
5124
- * @param segment Segment name
5125
- * @param filepath Path to read
5126
- * @param branch Current branch
5127
- * @returns {Promise<string|null>} blob content or null
5128
- */
5129
- /**
5130
- * blob を削除する
5363
+ * blob を削除する
5131
5364
  * @returns {Promise<void>} 削除完了時に解決
5132
5365
  */
5133
5366
  async deleteBlob(filepath, segment) {
@@ -5178,10 +5411,6 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
5178
5411
  }
5179
5412
  return toWrite;
5180
5413
  }
5181
- /**
5182
- * Gather entries that should be written to workspace-info: those that exist in workspace-base.
5183
- * @returns {Promise<Array<{k:string,v:any}>>}
5184
- */
5185
5414
  /**
5186
5415
  * Compute store name and key for a given segment and filepath.
5187
5416
  * @returns {{storeName:string,key:string}}
@@ -5191,10 +5420,6 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
5191
5420
  const key = seg === "workspace" ? filepath : seg === "conflictBlob" ? `${branch}::conflictBlob::${filepath}` : `${branch}::${filepath}`;
5192
5421
  return { storeName, key };
5193
5422
  }
5194
- /**
5195
- * Compute store name and key for a given segment and filepath.
5196
- * @returns {{storeName:string,key:string}}
5197
- */
5198
5423
  /**
5199
5424
  * Delete a filepath from all relevant stores for the given branch.
5200
5425
  * @returns {Promise<void>}
@@ -5207,10 +5432,6 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
5207
5432
  await this._deleteFromStore(IndexedDatabaseStorage2.VAR_INFO, filepath);
5208
5433
  await this._deleteFromStore(IndexedDatabaseStorage2.VAR_WORKSPACE_INFO, filepath);
5209
5434
  }
5210
- /**
5211
- * Delete a filepath from all relevant stores for the given branch.
5212
- * @returns {Promise<void>}
5213
- */
5214
5435
  /**
5215
5436
  * Read a value from a specific object store.
5216
5437
  * @param storeName Object store name
@@ -5221,8 +5442,9 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
5221
5442
  const database = await this.dbPromise;
5222
5443
  return new Promise((resolve) => {
5223
5444
  try {
5224
- const tx = database.transaction(storeName, "readonly");
5225
- const store = tx.objectStore(storeName);
5445
+ const physical = this._storeName(storeName);
5446
+ const tx = database.transaction(physical, "readonly");
5447
+ const store = tx.objectStore(physical);
5226
5448
  const request = store.get(filepath);
5227
5449
  request.onsuccess = () => {
5228
5450
  const result = request.result ?? null;
@@ -5252,8 +5474,9 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
5252
5474
  const database = await this.dbPromise;
5253
5475
  return new Promise((resolve) => {
5254
5476
  try {
5255
- const tx = database.transaction(storeName, "readonly");
5256
- const store = tx.objectStore(storeName);
5477
+ const physical = this._storeName(storeName);
5478
+ const tx = database.transaction(physical, "readonly");
5479
+ const store = tx.objectStore(physical);
5257
5480
  const keys = [];
5258
5481
  const request = store.openKeyCursor();
5259
5482
  request.onsuccess = this._makeCursorSuccessHandler(resolve, keys);
@@ -5288,8 +5511,13 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
5288
5511
  });
5289
5512
  }
5290
5513
  /**
5291
- * Create a cursor success handler bound to resolve and keys array.
5514
+ * Map a logical store name to its physical store name including root prefix.
5515
+ * @param name logical store identifier
5516
+ * @returns physical store name used in IndexedDB
5292
5517
  */
5518
+ _storeName(name) {
5519
+ return this.rootPrefix ? `${this.rootPrefix}${name}` : name;
5520
+ }
5293
5521
  /**
5294
5522
  * Create a cursor success handler bound to resolve and keys array.
5295
5523
  * @param resolve resolver
@@ -5330,9 +5558,6 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
5330
5558
  keys = this._filterKeys(keys, p, recursive);
5331
5559
  return await this._collectFiles(keys, seg);
5332
5560
  }
5333
- /**
5334
- * Raw listing that returns implementation-specific URIs and a normalized path.
5335
- */
5336
5561
  /**
5337
5562
  * Raw listing that returns implementation-specific URIs and a normalized path.
5338
5563
  * @param prefix optional prefix to filter
@@ -5363,9 +5588,6 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
5363
5588
  }
5364
5589
  return out;
5365
5590
  }
5366
- /**
5367
- * Helper: build entries from store keys with filtering and path normalization
5368
- */
5369
5591
  /**
5370
5592
  * Helper: build entries from store keys with filtering and path normalization
5371
5593
  * @returns {Promise<Array<{uri:string,path:string,info?:string|null}>>}
@@ -5442,10 +5664,6 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
5442
5664
  }
5443
5665
  return keys;
5444
5666
  }
5445
- /**
5446
- * Collect file info objects for keys array.
5447
- * @returns {Promise<Array<{path:string, info:string|null}>>}
5448
- */
5449
5667
  /**
5450
5668
  * Collect file info objects for keys array.
5451
5669
  * @param keys list of keys
@@ -5461,15 +5679,6 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
5461
5679
  }
5462
5680
  return out;
5463
5681
  }
5464
- /**
5465
- * Collect file info objects for keys array.
5466
- * @param keys key list
5467
- * @param _seg segment (unused)
5468
- * @returns {Promise<Array<{path:string, info:string|null}>>}
5469
- */
5470
- /**
5471
- * Resolve info value for a given key: prefer workspace-local, then branch-scoped.
5472
- */
5473
5682
  /**
5474
5683
  * Resolve info value for a given key: prefer workspace-local, then branch-scoped.
5475
5684
  * @param k key
@@ -5482,10 +5691,6 @@ var IndexedDatabaseStorage = class IndexedDatabaseStorage2 {
5482
5691
  info = await this._getFromStore(IndexedDatabaseStorage2.VAR_INFO, k);
5483
5692
  return info;
5484
5693
  }
5485
- /**
5486
- * Resolve info value for a given key: prefer workspace-local, then branch-scoped.
5487
- * @returns {Promise<string|null>}
5488
- */
5489
5694
  /**
5490
5695
  * Calculate SHA-1 hex digest of given content.
5491
5696
  * @param content Input string
@@ -5564,6 +5769,23 @@ function computeSha1Fallback(message) {
5564
5769
  for (let index = 0; index < bytes.length; index += 4) {
5565
5770
  words.push(bytes[index] << 24 | bytes[index + 1] << 16 | bytes[index + 2] << 8 | bytes[index + 3]);
5566
5771
  }
5772
+ let h0 = 1732584193;
5773
+ let h1 = 4023233417;
5774
+ let h2 = 2562383102;
5775
+ let h3 = 271733878;
5776
+ let h4 = 3285377520;
5777
+ const results = processWords(words);
5778
+ h0 = results[0];
5779
+ h1 = results[1];
5780
+ h2 = results[2];
5781
+ h3 = results[3];
5782
+ h4 = results[4];
5783
+ function toHex(n) {
5784
+ return n.toString(16).padStart(8, "0");
5785
+ }
5786
+ return (toHex(h0) + toHex(h1) + toHex(h2) + toHex(h3) + toHex(h4)).toLowerCase();
5787
+ }
5788
+ function processWords(words) {
5567
5789
  let h0 = 1732584193;
5568
5790
  let h1 = 4023233417;
5569
5791
  let h2 = 2562383102;
@@ -5577,37 +5799,40 @@ function computeSha1Fallback(message) {
5577
5799
  const temporary = w[t - 3] ^ w[t - 8] ^ w[t - 14] ^ w[t - 16];
5578
5800
  w[t] = (temporary << 1 | temporary >>> 31) >>> 0;
5579
5801
  }
5580
- let a = h0, b = h1, c = h2, d = h3, errorReg = h4;
5581
- for (let t = 0; t < 80; t++) {
5582
- let f, k;
5583
- if (t < 20) {
5584
- f = b & c | ~b & d;
5585
- k = 1518500249;
5586
- } else if (t < 40) {
5587
- f = b ^ c ^ d;
5588
- k = 1859775393;
5589
- } else if (t < 60) {
5590
- f = b & c | b & d | c & d;
5591
- k = 2400959708;
5592
- } else {
5593
- f = b ^ c ^ d;
5594
- k = 3395469782;
5595
- }
5596
- const temporaryValue = (a << 5 | a >>> 27) + f + errorReg + k + (w[t] >>> 0) >>> 0;
5597
- errorReg = d;
5598
- d = c;
5599
- c = (b << 30 | b >>> 2) >>> 0;
5600
- b = a;
5601
- a = temporaryValue;
5602
- }
5603
- h0 = h0 + a >>> 0;
5604
- h1 = h1 + b >>> 0;
5605
- h2 = h2 + c >>> 0;
5606
- h3 = h3 + d >>> 0;
5607
- h4 = h4 + errorReg >>> 0;
5608
- }
5609
- const toHex = (n) => n.toString(16).padStart(8, "0");
5610
- return (toHex(h0) + toHex(h1) + toHex(h2) + toHex(h3) + toHex(h4)).toLowerCase();
5802
+ const updated = processChunk(w, [h0, h1, h2, h3, h4]);
5803
+ h0 = updated[0];
5804
+ h1 = updated[1];
5805
+ h2 = updated[2];
5806
+ h3 = updated[3];
5807
+ h4 = updated[4];
5808
+ }
5809
+ return [h0, h1, h2, h3, h4];
5810
+ }
5811
+ function processChunk(w, h) {
5812
+ let a = h[0], b = h[1], c = h[2], d = h[3], registerE = h[4];
5813
+ for (let t = 0; t < 80; t++) {
5814
+ let f, k;
5815
+ if (t < 20) {
5816
+ f = b & c | ~b & d;
5817
+ k = 1518500249;
5818
+ } else if (t < 40) {
5819
+ f = b ^ c ^ d;
5820
+ k = 1859775393;
5821
+ } else if (t < 60) {
5822
+ f = b & c | b & d | c & d;
5823
+ k = 2400959708;
5824
+ } else {
5825
+ f = b ^ c ^ d;
5826
+ k = 3395469782;
5827
+ }
5828
+ const temporaryValue = (a << 5 | a >>> 27) + f + registerE + k + (w[t] >>> 0) >>> 0;
5829
+ registerE = d;
5830
+ d = c;
5831
+ c = (b << 30 | b >>> 2) >>> 0;
5832
+ b = a;
5833
+ a = temporaryValue;
5834
+ }
5835
+ return [h[0] + a >>> 0, h[1] + b >>> 0, h[2] + c >>> 0, h[3] + d >>> 0, h[4] + registerE >>> 0];
5611
5836
  }
5612
5837
  function parseExistingInfo(store, filepath) {
5613
5838
  const existingTxt = store.infoBlobs.has(filepath) ? store.infoBlobs.get(filepath) : null;
@@ -5655,31 +5880,36 @@ async function updateInfoForWrite(store, filepath, seg, content) {
5655
5880
  const now = Date.now();
5656
5881
  const existing = parseExistingInfo(store, filepath);
5657
5882
  if (seg === "info") {
5658
- try {
5659
- const parsed = JSON.parse(content);
5660
- store.infoBlobs.set(filepath, JSON.stringify(parsed));
5661
- return;
5662
- } catch {
5663
- store.infoBlobs.set(filepath, String(content));
5664
- return;
5665
- }
5883
+ await handleInfoSegment(store, filepath, content);
5884
+ return;
5666
5885
  }
5667
5886
  const sha = await shaOf3(content);
5668
5887
  let entry;
5669
- if (seg === "workspace") {
5888
+ if (seg === "workspace")
5670
5889
  entry = buildWorkspaceEntry(existing, filepath, sha, now);
5671
- } else if (seg === "base") {
5890
+ else if (seg === "base")
5672
5891
  entry = buildBaseEntry(existing, filepath, sha, now);
5673
- } else if (seg === "conflict") {
5892
+ else if (seg === "conflict")
5674
5893
  entry = buildConflictEntry(existing, filepath, now);
5675
- } else {
5894
+ else
5676
5895
  entry = { path: filepath, updatedAt: now };
5677
- }
5678
5896
  store.infoBlobs.set(filepath, JSON.stringify(entry));
5679
- } catch {
5897
+ } catch (error) {
5898
+ if (typeof console !== "undefined" && console.debug)
5899
+ console.debug("updateInfoForWrite failed", error);
5680
5900
  return;
5681
5901
  }
5682
5902
  }
5903
+ async function handleInfoSegment(store, filepath, content) {
5904
+ try {
5905
+ const parsed = JSON.parse(content);
5906
+ store.infoBlobs.set(filepath, JSON.stringify(parsed));
5907
+ } catch (error) {
5908
+ store.infoBlobs.set(filepath, String(content));
5909
+ if (typeof console !== "undefined" && console.debug)
5910
+ console.debug("handleInfoSegment: stored raw content due to parse error", error);
5911
+ }
5912
+ }
5683
5913
 
5684
5914
  // src/virtualfs/inmemoryStorage.ts
5685
5915
  var BRANCH_SEP = "::";
@@ -5703,17 +5933,26 @@ var InMemoryStorage = class InMemoryStorage2 {
5703
5933
  * 利用可能なルート名を返します。
5704
5934
  * @returns {string[]} ルート名の配列
5705
5935
  */
5706
- static availableRoots() {
5936
+ static availableRoots(namespace) {
5707
5937
  const keys = Array.from(InMemoryStorage2.stores.keys());
5708
- return keys.length ? keys : ["apigit_storage"];
5938
+ if (namespace) {
5939
+ const filtered = keys.filter((k) => k.startsWith(namespace + "/")).map((k) => k.slice((namespace + "/").length));
5940
+ return filtered.length ? filtered : ["apigit_storage"];
5941
+ }
5942
+ const roots = keys.map((k) => {
5943
+ const parts = k.split("/");
5944
+ return parts.length > 1 ? parts.slice(1).join("/") : parts[0];
5945
+ });
5946
+ const uniq = Array.from(new Set(roots));
5947
+ return uniq.length ? uniq : ["apigit_storage"];
5709
5948
  }
5710
- // legacy canUseOpfs removed; use static canUse() instead
5711
5949
  /**
5712
5950
  * コンストラクタ。互換性のためにディレクトリ名を受け取るが無視する。
5713
5951
  * @param directory 任意のディレクトリ文字列(使用しない)
5714
5952
  */
5715
- constructor(directory) {
5716
- this.rootKey = directory ?? `__inmem_${Math.random().toString(36).slice(2)}`;
5953
+ constructor(namespace, directory) {
5954
+ const directoryName = directory ?? `__inmem_${Math.random().toString(36).slice(2)}`;
5955
+ this.rootKey = namespace ? `${namespace}/${directoryName}` : directoryName;
5717
5956
  if (!InMemoryStorage2.stores.has(this.rootKey)) {
5718
5957
  InMemoryStorage2.stores.set(this.rootKey, {
5719
5958
  index: { head: "", entries: {} },
@@ -5755,7 +5994,7 @@ var InMemoryStorage = class InMemoryStorage2 {
5755
5994
  }
5756
5995
  /**
5757
5996
  * Load workspace-local info entries into result.entries (unprefixed keys)
5758
- * @returns {void}
5997
+ * @returns {void}
5759
5998
  */
5760
5999
  _loadInMemoryWorkspaceInfo(store, result, branch) {
5761
6000
  for (const [k, v] of store.infoBlobs.entries()) {
@@ -5768,7 +6007,7 @@ var InMemoryStorage = class InMemoryStorage2 {
5768
6007
  }
5769
6008
  /**
5770
6009
  * Load branch-scoped info entries into result.entries without overwriting workspace-local entries
5771
- * @returns {void}
6010
+ * @returns {void}
5772
6011
  */
5773
6012
  _loadInMemoryBranchInfo(store, result, branch) {
5774
6013
  for (const [k, v] of store.infoBlobs.entries()) {
@@ -5841,10 +6080,6 @@ var InMemoryStorage = class InMemoryStorage2 {
5841
6080
  const wrapped = seg === SEG_WORKSPACE || seg === "conflict" ? this._wrapStoreForInfoNoPrefix(store) : this._wrapStoreForInfoPrefix(store);
5842
6081
  await updateInfoForWrite(wrapped, filepath, seg, content);
5843
6082
  }
5844
- /**
5845
- * Handle workspace blob writes that should be based on existing git-scoped info.
5846
- * Returns true when the operation is handled and caller should return early.
5847
- */
5848
6083
  /**
5849
6084
  * Handle workspace blob writes that should be based on existing git-scoped info.
5850
6085
  * Returns true when the operation is handled and caller should return early.
@@ -6136,7 +6371,7 @@ var InMemoryStorage = class InMemoryStorage2 {
6136
6371
  * @param prefix プレフィックス(例: 'dir/sub')
6137
6372
  * @param segment セグメント('workspace' 等)。省略時は 'workspace'
6138
6373
  * @param recursive サブディレクトリも含めるか。省略時は true
6139
- * @returns {Promise<Array<{ path: string; info: string | null }>>}
6374
+ * @returns {Promise<Array<{ path: string; info: string | null }>>}
6140
6375
  */
6141
6376
  async listFiles(prefix, segment, recursive = true) {
6142
6377
  const seg = segment || SEG_WORKSPACE;
@@ -6155,9 +6390,6 @@ var InMemoryStorage = class InMemoryStorage2 {
6155
6390
  const keys = this._resolveKeysForList(allKeys, String(seg), p, recursive);
6156
6391
  return this._collectFilesInMemory(keys, store);
6157
6392
  }
6158
- /**
6159
- * Resolve and filter keys for `listFiles` into final key list.
6160
- */
6161
6393
  /**
6162
6394
  * Resolve and filter keys for `listFiles` into final key list.
6163
6395
  * @returns {string[]} filtered keys
@@ -6294,9 +6526,15 @@ var InMemoryStorage = class InMemoryStorage2 {
6294
6526
  static delete(rootName) {
6295
6527
  if (InMemoryStorage2.stores.has(rootName)) {
6296
6528
  InMemoryStorage2.stores.delete(rootName);
6297
- } else {
6298
- throw new Error(`InMemory root "${rootName}" not found`);
6529
+ return;
6530
+ }
6531
+ for (const key of Array.from(InMemoryStorage2.stores.keys())) {
6532
+ if (key === rootName || key.endsWith("/" + rootName)) {
6533
+ InMemoryStorage2.stores.delete(key);
6534
+ return;
6535
+ }
6299
6536
  }
6537
+ throw new Error(`InMemory root "${rootName}" not found`);
6300
6538
  }
6301
6539
  };
6302
6540
  var inmemoryStorage_default = InMemoryStorage;