@mattstack/rt-client 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { createRequire } from "node:module";
2
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
+
1
4
  // src/transport.ts
2
5
  import { homedir } from "os";
3
6
  import { join } from "path";
@@ -39,6 +42,93 @@ function readMrsByBranch(repoName, branches, opts = {}) {
39
42
  function resolveForgeToken(repoName, forge, opts = {}) {
40
43
  return rtCommand("secrets:forge-token", { repoName, forge }, { sockPath: opts.sockPath, timeoutMs: 1e4 });
41
44
  }
45
+ function listRuns(repo, opts = {}) {
46
+ const payload = {};
47
+ if (repo !== undefined)
48
+ payload.repo = repo;
49
+ return rtCommand("runs:list", payload, { sockPath: opts.sockPath, timeoutMs: 1e4 });
50
+ }
51
+ function getRun(runId, repo, opts = {}) {
52
+ const payload = { runId };
53
+ if (repo !== undefined)
54
+ payload.repo = repo;
55
+ return rtCommand("runs:get", payload, { sockPath: opts.sockPath, timeoutMs: 1e4 });
56
+ }
57
+ function abandonRun(runId, repo, reason, opts = {}) {
58
+ const payload = { runId };
59
+ if (repo !== undefined)
60
+ payload.repo = repo;
61
+ if (reason !== undefined)
62
+ payload.reason = reason;
63
+ return rtCommand("runs:abandon", payload, { sockPath: opts.sockPath, timeoutMs: 1e4 });
64
+ }
65
+ function chatJoin(a, o = {}) {
66
+ const payload = { room: a.room, handle: a.handle };
67
+ if (a.wakeOn !== undefined)
68
+ payload.wakeOn = a.wakeOn;
69
+ if (a.cwd !== undefined)
70
+ payload.cwd = a.cwd;
71
+ if (a.pane !== undefined)
72
+ payload.pane = a.pane;
73
+ return rtCommand("chat:join", payload, { sockPath: o.sockPath, timeoutMs: 1e4 });
74
+ }
75
+ function chatLeave(a, o = {}) {
76
+ return rtCommand("chat:leave", { room: a.room, handle: a.handle }, { sockPath: o.sockPath, timeoutMs: 1e4 });
77
+ }
78
+ function chatPost(a, o = {}) {
79
+ return rtCommand("chat:post", { room: a.room, handle: a.handle, body: a.body }, { sockPath: o.sockPath, timeoutMs: 1e4 });
80
+ }
81
+ function chatRead(a, o = {}) {
82
+ const payload = { handle: a.handle };
83
+ if (a.room !== undefined)
84
+ payload.room = a.room;
85
+ if (a.limit !== undefined)
86
+ payload.limit = a.limit;
87
+ if (a.sinceMs !== undefined)
88
+ payload.sinceMs = a.sinceMs;
89
+ return rtCommand("chat:read", payload, { sockPath: o.sockPath, timeoutMs: 1e4 });
90
+ }
91
+ function chatRooms(a, o = {}) {
92
+ return rtCommand("chat:rooms", { handle: a.handle }, { sockPath: o.sockPath, timeoutMs: 1e4 });
93
+ }
94
+ function chatWho(a, o = {}) {
95
+ return rtCommand("chat:who", { room: a.room }, { sockPath: o.sockPath, timeoutMs: 1e4 });
96
+ }
97
+ function chatMark(a, o = {}) {
98
+ const payload = { handle: a.handle };
99
+ if (a.room !== undefined)
100
+ payload.room = a.room;
101
+ return rtCommand("chat:mark", payload, { sockPath: o.sockPath, timeoutMs: 1e4 });
102
+ }
103
+ function chatMessages(a, o = {}) {
104
+ const payload = { room: a.room };
105
+ if (a.before !== undefined)
106
+ payload.before = a.before;
107
+ if (a.limit !== undefined)
108
+ payload.limit = a.limit;
109
+ return rtCommand("chat:messages", payload, { sockPath: o.sockPath, timeoutMs: 1e4 });
110
+ }
111
+ function chatArm(a, o = {}) {
112
+ const payload = { handle: a.handle };
113
+ if (a.room !== undefined)
114
+ payload.room = a.room;
115
+ return rtCommand("chat:arm", payload, { sockPath: o.sockPath, timeoutMs: 1e4 });
116
+ }
117
+ function chatTouch(a, o = {}) {
118
+ return rtCommand("chat:touch", { handle: a.handle }, { sockPath: o.sockPath, timeoutMs: 1e4 });
119
+ }
120
+ function chatDisarm(a, o = {}) {
121
+ return rtCommand("chat:disarm", { handle: a.handle }, { sockPath: o.sockPath, timeoutMs: 1e4 });
122
+ }
123
+ function chatUnreadWaking(a, o = {}) {
124
+ const payload = { handle: a.handle };
125
+ if (a.room !== undefined)
126
+ payload.room = a.room;
127
+ return rtCommand("chat:unread-waking", payload, { sockPath: o.sockPath, timeoutMs: 1e4 });
128
+ }
129
+ function eventsHead(o = {}) {
130
+ return rtCommand("events:head", {}, { sockPath: o.sockPath, timeoutMs: 1e4 });
131
+ }
42
132
  // src/commands.ts
43
133
  var COMMAND_NAMES = [
44
134
  "project-mrs:read",
@@ -49,8 +139,22 @@ var COMMAND_NAMES = [
49
139
  "events:emit",
50
140
  "events:wait",
51
141
  "events:list",
142
+ "events:head",
52
143
  "runs:list",
53
- "runs:get"
144
+ "runs:get",
145
+ "runs:abandon",
146
+ "chat:join",
147
+ "chat:leave",
148
+ "chat:post",
149
+ "chat:read",
150
+ "chat:rooms",
151
+ "chat:who",
152
+ "chat:mark",
153
+ "chat:messages",
154
+ "chat:arm",
155
+ "chat:touch",
156
+ "chat:disarm",
157
+ "chat:unread-waking"
54
158
  ];
55
159
  // src/relay.ts
56
160
  var DEFAULT_WS_URL = "ws://127.0.0.1:9401/ws";
@@ -98,16 +202,48 @@ function subscribe(onEvent, opts = {}) {
98
202
  // src/repos.ts
99
203
  import { existsSync, readFileSync } from "fs";
100
204
  import { homedir as homedir2 } from "os";
101
- import { join as join2 } from "path";
205
+ import { dirname, join as join2 } from "path";
102
206
  function defaultReposJsonPath() {
103
207
  return join2(homedir2(), ".mattstack", "rt", "repos.json");
104
208
  }
105
- function repoNameForPath(repoPath, reposJsonPath) {
106
- const path = reposJsonPath ?? defaultReposJsonPath();
209
+ function loadBunSqliteDatabase() {
107
210
  try {
108
- if (!existsSync(path))
211
+ return __require("bun:sqlite").Database;
212
+ } catch {
213
+ return null;
214
+ }
215
+ }
216
+ function repoNameFromStateDb(repoPath, dbPath) {
217
+ if (!existsSync(dbPath))
218
+ return null;
219
+ const DatabaseCtor = loadBunSqliteDatabase();
220
+ if (!DatabaseCtor)
221
+ return null;
222
+ try {
223
+ const db = new DatabaseCtor(dbPath, { readonly: true });
224
+ try {
225
+ const rows = db.query("SELECT k, v FROM kv WHERE ns = 'repo-index';").all();
226
+ for (const row of rows) {
227
+ try {
228
+ if (JSON.parse(row.v) === repoPath)
229
+ return row.k;
230
+ } catch {
231
+ continue;
232
+ }
233
+ }
234
+ return null;
235
+ } finally {
236
+ db.close();
237
+ }
238
+ } catch {
239
+ return null;
240
+ }
241
+ }
242
+ function repoNameFromJson(repoPath, reposJsonPath) {
243
+ try {
244
+ if (!existsSync(reposJsonPath))
109
245
  return null;
110
- const raw = readFileSync(path, "utf8");
246
+ const raw = readFileSync(reposJsonPath, "utf8");
111
247
  const index = JSON.parse(raw);
112
248
  for (const [repoName, value] of Object.entries(index)) {
113
249
  if (value === repoPath)
@@ -118,6 +254,14 @@ function repoNameForPath(repoPath, reposJsonPath) {
118
254
  return null;
119
255
  }
120
256
  }
257
+ function repoNameForPath(repoPath, reposJsonPath) {
258
+ const jsonPath = reposJsonPath ?? defaultReposJsonPath();
259
+ const dbPath = join2(dirname(jsonPath), "state.db");
260
+ const fromDb = repoNameFromStateDb(repoPath, dbPath);
261
+ if (fromDb !== null)
262
+ return fromDb;
263
+ return repoNameFromJson(repoPath, jsonPath);
264
+ }
121
265
  // src/settings/resolve.ts
122
266
  import { homedir as homedir4 } from "os";
123
267
  import { join as join5 } from "path";
@@ -278,7 +422,7 @@ var REGISTRY = [
278
422
  merge: "deep",
279
423
  repoScoped: true,
280
424
  migrated: true,
281
- description: "Branch-naming templates. rt itself has no readers of this key yet the VS Code extension still reads repos/<repo>/branch-naming.json by repo name, which stays authoritative until the extension ports over. Setting this key stores a value nothing consumes."
425
+ description: "Branch-naming templates, repoScoped. Read by the VS Code extension (extensions/vscode/rt-context), which lazily imports the legacy repos/<repo>/branch-naming.json into this key on first read; rt itself has no CLI-side reader."
282
426
  },
283
427
  {
284
428
  key: "rt.variations",
@@ -338,9 +482,8 @@ var REGISTRY = [
338
482
  scopes: ALL_SCOPES,
339
483
  merge: "deep",
340
484
  repoScoped: true,
341
- migrated: false,
342
- legacyFile: "repos/<repo>/hooks.json",
343
- description: "User-defined lifecycle hooks rt runs around commands (pre/post command scripts)."
485
+ migrated: true,
486
+ description: "Per-repo git hook enable/disable state ({enabled, hooks: {<hookName>: boolean}}); ownership-latch port of repos/<repo>/hooks.json, store wins per field once it owns the key — including per-hook-name entries inside the nested hooks map, each defaulting to enabled when absent. The installed git-hook shim still greps repos/<repo>/hooks.json with zero process spawns (a hook fires on every git operation); that file is now a DERIVED CACHE this key writes through, kept current by commands/hooks.ts's regenerateHooksCache at every write seam."
344
487
  },
345
488
  {
346
489
  key: "mattstack.integrations",
@@ -363,6 +506,14 @@ var REGISTRY = [
363
506
  merge: "replace",
364
507
  description: "Absolute path to the installed mattstack.app bundle, written by the app at launch so rt stops hardcoding ~/Applications."
365
508
  },
509
+ {
510
+ key: "rt.integrations",
511
+ type: "object",
512
+ scopes: ["user"],
513
+ merge: "deep",
514
+ migrated: true,
515
+ description: "User-confirmed integration hosts (forgeHost, switchboardUrl), written only by an explicit `rt setup <id> connect --host` after that host validates a real credential. The one trusted source a credential is ever sent to — mattstack.integrations' team-declared host is shown to the user but never auto-used for a fetch."
516
+ },
366
517
  {
367
518
  key: "claude.marketplaces",
368
519
  type: "array",
@@ -551,6 +702,35 @@ var REGISTRY = [
551
702
  scopes: ["machine"],
552
703
  merge: "deep",
553
704
  description: "gitq checkout-board config: tracked repos, local port, and the herdr workspace it launches into."
705
+ },
706
+ {
707
+ key: "chat.handle",
708
+ type: "string",
709
+ scopes: ["user"],
710
+ merge: "replace",
711
+ description: "Explicit rt chat handle for this developer on this machine; overrides the derived <repo>-<dir> handle when set."
712
+ },
713
+ {
714
+ key: "chat.humanHandle",
715
+ type: "string",
716
+ scopes: ["user"],
717
+ default: "matt",
718
+ merge: "replace",
719
+ description: "The human's own chat handle, so agents can @-mention them by name."
720
+ },
721
+ {
722
+ key: "chat.push.provider",
723
+ type: "string",
724
+ scopes: ["user"],
725
+ merge: "replace",
726
+ description: "Push notification provider used to alert the human of chat mentions when away from a terminal."
727
+ },
728
+ {
729
+ key: "chat.push.target",
730
+ type: "string",
731
+ scopes: ["user"],
732
+ merge: "replace",
733
+ description: "Destination (topic/URL/token) the configured chat.push.provider sends to."
554
734
  }
555
735
  ];
556
736
 
@@ -992,7 +1172,7 @@ function explainSetting(key, opts = {}) {
992
1172
  import { existsSync as existsSync3, mkdirSync, readFileSync as readFileSync4, renameSync, unlinkSync, writeFileSync } from "fs";
993
1173
  import { applyEdits, modify, parseTree } from "jsonc-parser";
994
1174
  import { randomBytes } from "crypto";
995
- import { dirname } from "path";
1175
+ import { dirname as dirname2 } from "path";
996
1176
  var FORMAT = { tabSize: 2, insertSpaces: true, eol: `
997
1177
  ` };
998
1178
  function refuse(message) {
@@ -1101,7 +1281,7 @@ function writeIntoStore(storePath, jsonPath, value, createIfMissing) {
1101
1281
  if (!createIfMissing) {
1102
1282
  refuse(`store file ${storePath} does not exist`);
1103
1283
  }
1104
- mkdirSync(dirname(storePath), { recursive: true });
1284
+ mkdirSync(dirname2(storePath), { recursive: true });
1105
1285
  content = seedHeader();
1106
1286
  }
1107
1287
  const edits = modify(content, jsonPath, value, { formattingOptions: FORMAT });
@@ -1120,6 +1300,9 @@ function writeIntoStore(storePath, jsonPath, value, createIfMissing) {
1120
1300
  throw err;
1121
1301
  }
1122
1302
  }
1303
+ // src/settings/identity.ts
1304
+ import { existsSync as existsSync4, readFileSync as readFileSync5, realpathSync } from "fs";
1305
+
1123
1306
  // src/settings/exec.ts
1124
1307
  async function runCapture(argv, opts = {}) {
1125
1308
  const captureStderr = opts.stderr === "pipe";
@@ -1156,6 +1339,27 @@ async function runCapture(argv, opts = {}) {
1156
1339
  // src/settings/identity.ts
1157
1340
  var URL_RE = /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\/(?:[^@/]+@)?([^/]+)\/(.+)$/;
1158
1341
  var SCP_RE = /^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/;
1342
+ function serializeIdentity(id) {
1343
+ return `${id.kind}:${encodeURIComponent(id.id)}`;
1344
+ }
1345
+ function parseIdentity(wire) {
1346
+ const colon = wire.indexOf(":");
1347
+ if (colon === -1)
1348
+ return null;
1349
+ const kind = wire.slice(0, colon);
1350
+ if (kind !== "remote" && kind !== "path")
1351
+ return null;
1352
+ const encoded = wire.slice(colon + 1);
1353
+ let id;
1354
+ try {
1355
+ id = decodeURIComponent(encoded);
1356
+ } catch {
1357
+ return null;
1358
+ }
1359
+ if (encodeURIComponent(id) !== encoded)
1360
+ return null;
1361
+ return { kind, id };
1362
+ }
1159
1363
  function normalizeRemote(remote) {
1160
1364
  const trimmed = remote.trim();
1161
1365
  if (!trimmed)
@@ -1186,9 +1390,10 @@ function identityFromRemote(remote) {
1186
1390
  if (overrides !== null && typeof overrides === "object" && !Array.isArray(overrides)) {
1187
1391
  const hit = overrides[remote];
1188
1392
  if (typeof hit === "string")
1189
- return hit;
1393
+ return { kind: "remote", id: hit };
1190
1394
  }
1191
- return normalizeRemote(remote);
1395
+ const normalized = normalizeRemote(remote);
1396
+ return normalized === null ? null : { kind: "remote", id: normalized };
1192
1397
  }
1193
1398
  var memo = new Map;
1194
1399
  async function deriveRepoIdentity(repoPath) {
@@ -1197,43 +1402,95 @@ async function deriveRepoIdentity(repoPath) {
1197
1402
  return cached;
1198
1403
  const result = await (async () => {
1199
1404
  const spawned = await runCapture(["git", "-C", repoPath, "config", "--get", "remote.origin.url"]);
1200
- if (spawned.exitCode !== 0)
1201
- return null;
1202
- const remote = spawned.stdout.trim();
1203
- if (!remote)
1204
- return null;
1205
- return identityFromRemote(remote);
1405
+ if (spawned.exitCode === 0) {
1406
+ const remote = spawned.stdout.trim();
1407
+ const fromRemote = remote ? identityFromRemote(remote) : null;
1408
+ if (fromRemote)
1409
+ return fromRemote;
1410
+ }
1411
+ const listed = await runCapture(["git", "-C", repoPath, "worktree", "list", "--porcelain"]);
1412
+ const first = listed.exitCode === 0 ? /^worktree (.+)$/m.exec(listed.stdout)?.[1]?.trim() : undefined;
1413
+ let base;
1414
+ if (first) {
1415
+ const top = await runCapture(["git", "-C", first, "rev-parse", "--show-toplevel"]);
1416
+ if (top.exitCode === 0 && top.stdout.trim())
1417
+ base = top.stdout.trim();
1418
+ }
1419
+ if (!base) {
1420
+ const own = await runCapture(["git", "-C", repoPath, "rev-parse", "--show-toplevel"]);
1421
+ base = own.exitCode === 0 && own.stdout.trim() ? own.stdout.trim() : repoPath;
1422
+ }
1423
+ return { kind: "path", id: safeRealpath(base) };
1206
1424
  })();
1207
- if (result !== null)
1425
+ if (result.kind === "remote")
1208
1426
  memo.set(repoPath, Promise.resolve(result));
1209
1427
  return result;
1210
1428
  }
1211
1429
  function clearIdentityMemo() {
1212
1430
  memo.clear();
1213
1431
  }
1432
+ function safeRealpath(p) {
1433
+ try {
1434
+ return realpathSync(p);
1435
+ } catch {
1436
+ return p;
1437
+ }
1438
+ }
1439
+ async function resolveNameToIdentity(name, reposJsonPath) {
1440
+ if (!existsSync4(reposJsonPath))
1441
+ return null;
1442
+ try {
1443
+ const index = JSON.parse(readFileSync5(reposJsonPath, "utf8"));
1444
+ const path = index[name];
1445
+ if (typeof path !== "string")
1446
+ return null;
1447
+ return await deriveRepoIdentity(path);
1448
+ } catch {
1449
+ return null;
1450
+ }
1451
+ }
1214
1452
  export {
1215
1453
  validateValue,
1216
1454
  subscribe,
1217
1455
  setSetting,
1456
+ serializeIdentity,
1218
1457
  rtCommand,
1458
+ resolveNameToIdentity,
1219
1459
  resolveForgeToken,
1220
1460
  repoNameForPath,
1221
1461
  readStore,
1222
1462
  readProjectMRs,
1223
1463
  readMrsByBranch,
1224
1464
  readDiscussions,
1465
+ parseIdentity,
1225
1466
  normalizeRemote,
1226
1467
  listTeams,
1227
1468
  listSettings,
1469
+ listRuns,
1228
1470
  isMigrated,
1229
1471
  identityFromRemote,
1230
1472
  getSetting,
1473
+ getRun,
1231
1474
  getDef,
1232
1475
  explainSetting,
1233
1476
  expandVariables,
1477
+ eventsHead,
1234
1478
  deriveRepoIdentity,
1235
1479
  clearIdentityMemo,
1480
+ chatWho,
1481
+ chatUnreadWaking,
1482
+ chatTouch,
1483
+ chatRooms,
1484
+ chatRead,
1485
+ chatPost,
1486
+ chatMessages,
1487
+ chatMark,
1488
+ chatLeave,
1489
+ chatJoin,
1490
+ chatDisarm,
1491
+ chatArm,
1236
1492
  allDefs,
1493
+ abandonRun,
1237
1494
  SCOPE_ORDER,
1238
1495
  REGISTRY,
1239
1496
  DEFAULT_WS_URL,
package/dist/repos.d.ts CHANGED
@@ -1,7 +1,13 @@
1
1
  /**
2
2
  * Exact-match lookup: returns the repo name whose recorded path equals
3
- * `repoPath`, or null if the file is missing, corrupt, or has no match.
4
- * Never throws -- a resolution failure just means the caller falls back to
5
- * whatever it had before (an unqualified path, a prompt, etc).
3
+ * `repoPath`, or null if no source has a match. Never throws -- a
4
+ * resolution failure just means the caller falls back to whatever it had
5
+ * before (an unqualified path, a prompt, etc).
6
+ *
7
+ * Prefers state.db (authoritative, kept live by every rt process); falls
8
+ * back to the repos.json compat mirror when state.db is unreachable — a
9
+ * pre-upgrade rt install, a non-Bun consumer, or a state.db this process
10
+ * can't open. `reposJsonPath`, when passed, also relocates the state.db
11
+ * lookup: both files live side by side under the same rt data directory.
6
12
  */
7
13
  export declare function repoNameForPath(repoPath: string, reposJsonPath?: string): string | null;
@@ -1,14 +1,15 @@
1
1
  /**
2
- * Repo identity: the normalized-remote string that keys `repos.<identity>`
3
- * sections in every settings store (RT-47 spec, "Repo identity").
2
+ * Repo identity: the tagged value that keys `repos.<identity>` sections in
3
+ * every settings store.
4
4
  *
5
- * Identity is `host/path` (lowercase host, path case preserved), derived from
6
- * `remote.origin.url` — never a filesystem path, so it is checkout-location
7
- * independent: every worktree of a repo shares the same remote and therefore
8
- * the same identity. A remote that doesn't match a recognized host form
9
- * (bare local paths are the main case repos.json has two) normalizes to
10
- * null, meaning repo-scoped sections are unreachable for it and only global
11
- * scopes apply. That's an honest degrade, not a crash.
5
+ * A `remote`-kind identity is `host/path` (lowercase host, path case
6
+ * preserved), derived from `remote.origin.url` — checkout-location
7
+ * independent, since every worktree of a repo shares the same remote. When
8
+ * no usable remote exists, identity falls back to `path`-kind: the realpath
9
+ * of the *main* worktree, which is still shared across that repo's linked
10
+ * worktrees (see `deriveRepoIdentity`) even though it is filesystem-bound.
11
+ * `deriveRepoIdentity` therefore never returns null every repo has at
12
+ * least a path-kind identity.
12
13
  *
13
14
  * Three entry points:
14
15
  * - `normalizeRemote` is the pure string transform, no I/O.
@@ -24,6 +25,20 @@
24
25
  * through `identityFromRemote`, memoized per path so repeated callers in
25
26
  * one process don't re-spawn git.
26
27
  */
28
+ export type RepoIdentity = {
29
+ kind: "remote";
30
+ id: string;
31
+ } | {
32
+ kind: "path";
33
+ id: string;
34
+ };
35
+ /**
36
+ * The wire form crosses the daemon socket, sits in board config, and lands in
37
+ * console's `/runs/:repo/...` URL — all of which need one slash-free segment.
38
+ * `encodeURIComponent` guarantees that and is exactly reversible.
39
+ */
40
+ export declare function serializeIdentity(id: RepoIdentity): string;
41
+ export declare function parseIdentity(wire: string): RepoIdentity | null;
27
42
  /**
28
43
  * Pure normalization: `remote` → `host/path` (lowercase host, `.git` and
29
44
  * embedded credentials stripped) or null when the remote doesn't match a
@@ -36,21 +51,30 @@ export declare function normalizeRemote(remote: string): string | null;
36
51
  * Reads the machine store fresh each call (files are small; store reads are
37
52
  * not memoized anywhere in the resolver design).
38
53
  */
39
- export declare function identityFromRemote(remote: string): string | null;
54
+ export declare function identityFromRemote(remote: string): RepoIdentity | null;
40
55
  /**
41
56
  * Async derivation from a repo path: `git -C <repoPath> config --get
42
57
  * remote.origin.url`, then identityFromRemote (so overrides apply to
43
58
  * derivation too). Never a sync spawn — safe to call from daemon contexts.
59
+ * Never returns null: no usable remote falls back to a path-kind identity
60
+ * (the main worktree's realpath, via `git worktree list`, so every linked
61
+ * worktree of one repo still shares the same identity).
44
62
  *
45
- * Only a SUCCESSFUL derivation (non-null identity) is memoized, for the life
46
- * of the process; a remote change after that first success is NOT picked up
47
- * until clearIdentityMemo() — documented behavior, not a bug (see spec:
48
- * derivation is a one-time capture per process, not a live poll). A FAILED
49
- * derivation (no remote yet, git not initialized yet, etc.) is never cached
50
- * and is retried on every subsequent call — a caller racing repo
51
- * provisioning (mid-clone, daemon-startup) must not permanently lose
52
- * identity for a path just because it asked too early.
63
+ * Only a `remote`-kind result is memoized, for the life of the process; a
64
+ * remote change after that first success is NOT picked up until
65
+ * clearIdentityMemo() — documented behavior, not a bug (see spec: derivation
66
+ * is a one-time capture per process, not a live poll). A `path`-kind result
67
+ * is never cached and is retried on every subsequent call cheap to
68
+ * recompute, and a caller racing repo provisioning (mid-clone,
69
+ * daemon-startup, a remote added after the fact) must not permanently lose
70
+ * the chance to pick up a real remote just because it asked too early.
53
71
  */
54
- export declare function deriveRepoIdentity(repoPath: string): Promise<string | null>;
72
+ export declare function deriveRepoIdentity(repoPath: string): Promise<RepoIdentity>;
55
73
  /** Test-only: clear the derivation memo so a test can force re-derivation. */
56
74
  export declare function clearIdentityMemo(): void;
75
+ /**
76
+ * One-shot helper for rewriting board's existing name-valued config to
77
+ * host/path identities. NOT a runtime path — the daemon never calls it.
78
+ * Resolves a repo name to the identity of the path it points at in repos.json.
79
+ */
80
+ export declare function resolveNameToIdentity(name: string, reposJsonPath: string): Promise<RepoIdentity | null>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mattstack/rt-client",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {