@adhdev/mesh-shared 0.9.82-rc.267

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 ADDED
@@ -0,0 +1,260 @@
1
+ // src/json.ts
2
+ function readRecord(value) {
3
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
4
+ }
5
+ function readString(...values) {
6
+ for (const value of values) {
7
+ if (typeof value !== "string") continue;
8
+ const trimmed = value.trim();
9
+ if (trimmed) return trimmed;
10
+ }
11
+ return void 0;
12
+ }
13
+ function readNumber(...values) {
14
+ for (const value of values) {
15
+ if (typeof value === "number" && Number.isFinite(value)) return value;
16
+ }
17
+ return void 0;
18
+ }
19
+ function readBoolean(...values) {
20
+ for (const value of values) {
21
+ if (typeof value === "boolean") return value;
22
+ }
23
+ return void 0;
24
+ }
25
+ function readStringArray(value) {
26
+ return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
27
+ }
28
+ function joinRepoPath(root, relativePath) {
29
+ const normalizedRoot = typeof root === "string" ? root.trim().replace(/[\\/]+$/, "") : "";
30
+ const normalizedPath = typeof relativePath === "string" ? relativePath.trim() : "";
31
+ if (!normalizedPath) return void 0;
32
+ if (/^(?:[A-Za-z]:[\\/]|\/)/.test(normalizedPath)) return normalizedPath;
33
+ if (!normalizedRoot) return void 0;
34
+ return `${normalizedRoot}/${normalizedPath.replace(/^[\\/]+/, "")}`;
35
+ }
36
+
37
+ // src/git-normalize.ts
38
+ function scoreGitUpstreamFreshness(status) {
39
+ switch (status) {
40
+ case "fresh":
41
+ return 30;
42
+ case "no_upstream":
43
+ return 4;
44
+ case "unchecked":
45
+ case void 0:
46
+ return 0;
47
+ case "stale":
48
+ return -10;
49
+ case "unavailable":
50
+ return -15;
51
+ default:
52
+ return 0;
53
+ }
54
+ }
55
+ function readGitSubmodules(value, parentRepoRoot) {
56
+ if (!Array.isArray(value)) return void 0;
57
+ const submodules = value.map((entry) => {
58
+ const submodule = readRecord(entry);
59
+ const path = readString(submodule.path);
60
+ const commit = readString(submodule.commit);
61
+ const repoPath = readString(submodule.repoPath, submodule.repo_root) ?? joinRepoPath(parentRepoRoot, path);
62
+ if (!path || !commit) return null;
63
+ const result = {
64
+ path,
65
+ commit,
66
+ dirty: readBoolean(submodule.dirty) ?? false,
67
+ outOfSync: readBoolean(submodule.outOfSync, submodule.out_of_sync) ?? false,
68
+ lastCheckedAt: readNumber(submodule.lastCheckedAt, submodule.last_checked_at) ?? Date.now()
69
+ };
70
+ if (repoPath) result.repoPath = repoPath;
71
+ const error = readString(submodule.error);
72
+ if (error) result.error = error;
73
+ return result;
74
+ }).filter((entry) => entry !== null);
75
+ return submodules.length > 0 ? submodules : void 0;
76
+ }
77
+ function hasGitStatusEvidence(status) {
78
+ return readBoolean(status.isGitRepo) !== void 0 || Boolean(readString(status.branch, status.upstream, status.upstreamStatus, status.upstream_status, status.headCommit)) || Boolean(readString(status.repoRoot, status.repo_root, status.workspace)) || readNumber(
79
+ status.ahead,
80
+ status.behind,
81
+ status.staged,
82
+ status.modified,
83
+ status.untracked,
84
+ status.deleted,
85
+ status.renamed,
86
+ status.lastCheckedAt,
87
+ status.last_checked_at
88
+ ) !== void 0 || Array.isArray(status.submodules) && status.submodules.length > 0;
89
+ }
90
+ function normalizeGitStatus(status, node, options) {
91
+ const explicitIsGitRepo = readBoolean(status.isGitRepo);
92
+ if (!Object.keys(status).length || !hasGitStatusEvidence(status)) return void 0;
93
+ const isGitRepo = explicitIsGitRepo ?? true;
94
+ const conflictFiles = Array.isArray(status.conflictFiles) ? status.conflictFiles.filter((entry) => typeof entry === "string") : [];
95
+ const conflictCount = readNumber(status.conflicts) ?? conflictFiles.length;
96
+ const hasConflicts = readBoolean(status.hasConflicts) ?? conflictCount > 0;
97
+ const repoRoot = readString(status.repoRoot, status.repo_root, node.repoRoot, node.repo_root, status.workspace, node.workspace) || void 0;
98
+ const submodules = readGitSubmodules(status.submodules, repoRoot);
99
+ const upstreamStatus = readString(status.upstreamStatus, status.upstream_status);
100
+ const upstreamFetchedAt = readNumber(status.upstreamFetchedAt, status.upstream_fetched_at);
101
+ const upstreamFetchError = readString(status.upstreamFetchError, status.upstream_fetch_error);
102
+ const error = readString(status.error);
103
+ const staged = readNumber(status.staged) ?? 0;
104
+ const modified = readNumber(status.modified) ?? 0;
105
+ const untracked = readNumber(status.untracked) ?? 0;
106
+ const deleted = readNumber(status.deleted) ?? 0;
107
+ const renamed = readNumber(status.renamed) ?? 0;
108
+ return {
109
+ workspace: readString(status.workspace, node.workspace) || "",
110
+ repoRoot: repoRoot ?? null,
111
+ isGitRepo,
112
+ branch: readString(status.branch) ?? null,
113
+ headCommit: readString(status.headCommit) ?? null,
114
+ headMessage: readString(status.headMessage) ?? null,
115
+ upstream: readString(status.upstream) ?? null,
116
+ upstreamStatus: upstreamStatus ?? "unchecked",
117
+ ...upstreamFetchedAt !== void 0 ? { upstreamFetchedAt } : {},
118
+ ...upstreamFetchError ? { upstreamFetchError } : {},
119
+ ahead: readNumber(status.ahead) ?? 0,
120
+ behind: readNumber(status.behind) ?? 0,
121
+ staged,
122
+ modified,
123
+ untracked,
124
+ deleted,
125
+ renamed,
126
+ dirty: readBoolean(status.dirty, status.isDirty, status.is_dirty) ?? (staged + modified + untracked + deleted + renamed > 0 || hasConflicts),
127
+ hasConflicts,
128
+ conflictFiles,
129
+ stashCount: readNumber(status.stashCount, status.stash_count) ?? 0,
130
+ lastCheckedAt: options?.lastCheckedAt ?? readNumber(status.lastCheckedAt, status.last_checked_at) ?? Date.now(),
131
+ ...submodules ? { submodules } : {},
132
+ ...error ? { error } : {}
133
+ };
134
+ }
135
+ function scoreGitStatusCandidate(git) {
136
+ if (!git) return Number.NEGATIVE_INFINITY;
137
+ let score = 0;
138
+ if (git.isGitRepo === true) score += 50;
139
+ if (git.isGitRepo === false) score -= 10;
140
+ if (git.branch) score += 20;
141
+ if (git.headCommit) score += 20;
142
+ if (git.upstream) score += 10;
143
+ score += scoreGitUpstreamFreshness(git.upstreamStatus);
144
+ if (typeof git.ahead === "number") score += 2;
145
+ if (typeof git.behind === "number") score += 2;
146
+ if (Array.isArray(git.submodules) && git.submodules.length > 0) score += 4 + git.submodules.length;
147
+ if (git.error) score -= 20;
148
+ return score;
149
+ }
150
+ function pickBestTransitGitStatus(node, options) {
151
+ const rawGit = readRecord(node.lastGit ?? node.last_git);
152
+ const gitResult = readRecord(rawGit.result);
153
+ const directStatus = readRecord(rawGit.status);
154
+ const nestedStatus = readRecord(gitResult.status);
155
+ const rawProbe = readRecord(node.lastProbe ?? node.last_probe);
156
+ const probeGit = readRecord(rawProbe.git);
157
+ const probeGitResult = readRecord(probeGit.result);
158
+ const probeDirectStatus = readRecord(probeGit.status);
159
+ const probeNestedStatus = readRecord(probeGitResult.status);
160
+ const lastCheckedAt = options?.lastCheckedAt;
161
+ let best = null;
162
+ for (const status of [directStatus, nestedStatus, probeDirectStatus, probeNestedStatus]) {
163
+ const normalized = normalizeGitStatus(status, node, { lastCheckedAt: lastCheckedAt ?? Date.now() });
164
+ if (!normalized) continue;
165
+ const score = scoreGitStatusCandidate(normalized);
166
+ if (!best || score > best.score) best = { git: normalized, score };
167
+ }
168
+ return best?.git;
169
+ }
170
+
171
+ // src/session-normalize.ts
172
+ function deriveSyntheticSessionId(record) {
173
+ const parts = [
174
+ readString(record.workspace),
175
+ readString(record.providerType, record.provider),
176
+ readString(record.role),
177
+ readString(record.state, record.status),
178
+ readString(record.title),
179
+ readString(record.createdAt, record.created_at),
180
+ readString(record.startedAt, record.started_at)
181
+ ].filter((part) => Boolean(part));
182
+ if (parts.length === 0) return void 0;
183
+ return `synthetic:${parts.join("|")}`;
184
+ }
185
+ function normalizeMeshSessionRecord(entry) {
186
+ const record = readRecord(entry);
187
+ const sessionId = readString(record.sessionId, record.session_id, record.id) ?? deriveSyntheticSessionId(record);
188
+ if (!sessionId) return null;
189
+ return {
190
+ sessionId,
191
+ ...readString(record.providerType, record.provider) ? { providerType: readString(record.providerType, record.provider) } : {},
192
+ ...readString(record.state, record.status) ? { state: readString(record.state, record.status) } : {},
193
+ ...readString(record.chatStatus, record.chat_status) ? { chatStatus: readString(record.chatStatus, record.chat_status) } : {},
194
+ ...readString(record.lifecycle) ? { lifecycle: readString(record.lifecycle) } : {},
195
+ ...readString(record.surfaceKind, record.surface_kind) ? { surfaceKind: readString(record.surfaceKind, record.surface_kind) } : {},
196
+ ...readString(record.recoveryState, record.recovery_state) ? { recoveryState: readString(record.recoveryState, record.recovery_state) } : {},
197
+ ...readString(record.workspace) ? { workspace: readString(record.workspace) } : {},
198
+ ...readString(record.title) ? { title: readString(record.title) } : {},
199
+ ...readString(record.role) ? { role: readString(record.role) } : {},
200
+ ...readBoolean(record.isSelfCoordinator, record.is_self_coordinator) !== void 0 ? { isSelfCoordinator: readBoolean(record.isSelfCoordinator, record.is_self_coordinator) } : {},
201
+ ...readString(record.statusNote, record.status_note) ? { statusNote: readString(record.statusNote, record.status_note) } : {},
202
+ ...readString(record.createdAt, record.created_at) ? { createdAt: readString(record.createdAt, record.created_at) } : {},
203
+ ...readString(record.startedAt, record.started_at) ? { startedAt: readString(record.startedAt, record.started_at) } : {},
204
+ ...readString(record.lastActivityAt, record.last_activity_at) ? { lastActivityAt: readString(record.lastActivityAt, record.last_activity_at) } : {},
205
+ ...readBoolean(record.isCached, record.is_cached) !== void 0 ? { isCached: readBoolean(record.isCached, record.is_cached) } : {}
206
+ };
207
+ }
208
+
209
+ // src/git-summarize.ts
210
+ function summarizeGitShape(status) {
211
+ const record = readRecord(status);
212
+ if (!Object.keys(record).length) return null;
213
+ const submodules = Array.isArray(record.submodules) ? record.submodules.map((entry) => {
214
+ const sub = readRecord(entry);
215
+ return {
216
+ path: readString(sub.path) ?? null,
217
+ commit: readString(sub.commit)?.slice(0, 12) ?? null,
218
+ dirty: readBoolean(sub.dirty) ?? false,
219
+ outOfSync: readBoolean(sub.outOfSync, sub.out_of_sync) ?? false
220
+ };
221
+ }) : [];
222
+ return {
223
+ isGitRepo: readBoolean(record.isGitRepo),
224
+ workspace: readString(record.workspace) ?? null,
225
+ repoRoot: readString(record.repoRoot, record.repo_root) ?? null,
226
+ branch: readString(record.branch) ?? null,
227
+ upstream: readString(record.upstream) ?? null,
228
+ upstreamStatus: readString(record.upstreamStatus, record.upstream_status) ?? null,
229
+ headCommit: readString(record.headCommit, record.head_commit)?.slice(0, 12) ?? null,
230
+ ahead: readNumber(record.ahead) ?? null,
231
+ behind: readNumber(record.behind) ?? null,
232
+ dirtyCounts: {
233
+ staged: readNumber(record.staged) ?? 0,
234
+ modified: readNumber(record.modified) ?? 0,
235
+ untracked: readNumber(record.untracked) ?? 0,
236
+ deleted: readNumber(record.deleted) ?? 0,
237
+ renamed: readNumber(record.renamed) ?? 0
238
+ },
239
+ lastCheckedAt: readNumber(record.lastCheckedAt, record.last_checked_at) ?? null,
240
+ submoduleCount: submodules.length,
241
+ submodules
242
+ };
243
+ }
244
+ export {
245
+ hasGitStatusEvidence,
246
+ joinRepoPath,
247
+ normalizeGitStatus,
248
+ normalizeMeshSessionRecord,
249
+ pickBestTransitGitStatus,
250
+ readBoolean,
251
+ readGitSubmodules,
252
+ readNumber,
253
+ readRecord,
254
+ readString,
255
+ readStringArray,
256
+ scoreGitStatusCandidate,
257
+ scoreGitUpstreamFreshness,
258
+ summarizeGitShape
259
+ };
260
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/json.ts","../src/git-normalize.ts","../src/session-normalize.ts","../src/git-summarize.ts"],"sourcesContent":["/**\n * Pure JSON-record reading primitives shared by the cloud (web-core / P2P transit)\n * and standalone (daemon-core / local IPC) mesh normalizers.\n *\n * These operate only on plain JS values — no Node/DOM APIs, no transport, no git\n * exec — so both cores can import them without violating the core↔core dependency\n * ban. They are the single source of truth for the field-coercion rules that the\n * two transports previously hand-synced (and drifted on).\n */\n\nexport type JsonRecord = Record<string, unknown>\n\nexport function readRecord(value: unknown): JsonRecord {\n return value && typeof value === 'object' && !Array.isArray(value) ? value as JsonRecord : {}\n}\n\nexport function readString(...values: unknown[]): string | undefined {\n for (const value of values) {\n if (typeof value !== 'string') continue\n const trimmed = value.trim()\n if (trimmed) return trimmed\n }\n return undefined\n}\n\nexport function readNumber(...values: unknown[]): number | undefined {\n for (const value of values) {\n if (typeof value === 'number' && Number.isFinite(value)) return value\n }\n return undefined\n}\n\nexport function readBoolean(...values: unknown[]): boolean | undefined {\n for (const value of values) {\n if (typeof value === 'boolean') return value\n }\n return undefined\n}\n\nexport function readStringArray(value: unknown): string[] {\n return Array.isArray(value)\n ? value.filter((entry): entry is string => typeof entry === 'string' && entry.trim().length > 0)\n : []\n}\n\n/**\n * Join a (possibly absent) repo root with a relative submodule path. Returns the\n * path unchanged when it is already absolute, and undefined when nothing usable\n * can be derived — callers must treat the result as optional.\n */\nexport function joinRepoPath(root: string | undefined, relativePath: string | undefined): string | undefined {\n const normalizedRoot = typeof root === 'string' ? root.trim().replace(/[\\\\/]+$/, '') : ''\n const normalizedPath = typeof relativePath === 'string' ? relativePath.trim() : ''\n if (!normalizedPath) return undefined\n if (/^(?:[A-Za-z]:[\\\\/]|\\/)/.test(normalizedPath)) return normalizedPath\n if (!normalizedRoot) return undefined\n return `${normalizedRoot}/${normalizedPath.replace(/^[\\\\/]+/, '')}`\n}\n","/**\n * Canonical git-status normalizers shared by the cloud (web-core) and standalone\n * (daemon-core router) mesh paths. Previously each transport hand-maintained its\n * own copy and they drifted (e.g. submodule drop rules, evidence checks); this is\n * the one implementation both import.\n */\n\nimport { joinRepoPath, readBoolean, readNumber, readRecord, readString, type JsonRecord } from './json'\nimport type { GitRepoStatus, GitSubmoduleStatus, GitUpstreamFreshness } from './types'\n\nexport function scoreGitUpstreamFreshness(status: GitUpstreamFreshness | undefined): number {\n switch (status) {\n case 'fresh':\n return 30\n case 'no_upstream':\n return 4\n case 'unchecked':\n case undefined:\n return 0\n case 'stale':\n return -10\n case 'unavailable':\n return -15\n default:\n return 0\n }\n}\n\nexport function readGitSubmodules(value: unknown, parentRepoRoot?: string): GitSubmoduleStatus[] | undefined {\n if (!Array.isArray(value)) return undefined\n const submodules = value\n .map(entry => {\n const submodule = readRecord(entry)\n const path = readString(submodule.path)\n const commit = readString(submodule.commit)\n const repoPath = readString(submodule.repoPath, submodule.repo_root)\n ?? joinRepoPath(parentRepoRoot, path)\n // repoPath is only used for the submodule node's display workspace, which is\n // allowed to be empty. The cloud P2P transit path can deliver submodule entries\n // without repoPath (and a per-node git object without a derivable repoRoot), so\n // dropping on missing repoPath would silently strip every submodule graph node.\n // Keep any submodule that carries both path and commit.\n if (!path || !commit) return null\n const result: GitSubmoduleStatus = {\n path,\n commit,\n dirty: readBoolean(submodule.dirty) ?? false,\n outOfSync: readBoolean(submodule.outOfSync, submodule.out_of_sync) ?? false,\n lastCheckedAt: readNumber(submodule.lastCheckedAt, submodule.last_checked_at) ?? Date.now(),\n }\n if (repoPath) result.repoPath = repoPath\n const error = readString(submodule.error)\n if (error) result.error = error\n return result\n })\n .filter((entry): entry is GitSubmoduleStatus => entry !== null)\n return submodules.length > 0 ? submodules : undefined\n}\n\nexport function hasGitStatusEvidence(status: JsonRecord): boolean {\n // BUG FIX: a transit-reshaped git status that carries only a repoRoot/workspace\n // (e.g. cloud P2P stripped the branch/upstream/counters but kept the path) must\n // NOT be dropped — otherwise the node loses its git object and any submodules\n // hanging off it. Treat a present repoRoot/repo_root/workspace as evidence too.\n return readBoolean(status.isGitRepo) !== undefined\n || Boolean(readString(status.branch, status.upstream, status.upstreamStatus, status.upstream_status, status.headCommit))\n || Boolean(readString(status.repoRoot, status.repo_root, status.workspace))\n || readNumber(\n status.ahead,\n status.behind,\n status.staged,\n status.modified,\n status.untracked,\n status.deleted,\n status.renamed,\n status.lastCheckedAt,\n status.last_checked_at,\n ) !== undefined\n || (Array.isArray(status.submodules) && status.submodules.length > 0)\n}\n\nexport function normalizeGitStatus(\n status: JsonRecord,\n node: JsonRecord,\n options?: { lastCheckedAt?: number },\n): GitRepoStatus | undefined {\n const explicitIsGitRepo = readBoolean(status.isGitRepo)\n if (!Object.keys(status).length || !hasGitStatusEvidence(status)) return undefined\n const isGitRepo = explicitIsGitRepo ?? true\n const conflictFiles = Array.isArray(status.conflictFiles)\n ? status.conflictFiles.filter((entry): entry is string => typeof entry === 'string')\n : []\n const conflictCount = readNumber(status.conflicts) ?? conflictFiles.length\n const hasConflicts = readBoolean(status.hasConflicts) ?? conflictCount > 0\n // node.workspace is in the fallback chain so a transit node carrying its path only\n // on the node (not the inner git object) still yields a parentRepoRoot for submodules.\n const repoRoot = readString(status.repoRoot, status.repo_root, node.repoRoot, node.repo_root, status.workspace, node.workspace) || undefined\n const submodules = readGitSubmodules(status.submodules, repoRoot)\n const upstreamStatus = readString(status.upstreamStatus, status.upstream_status)\n const upstreamFetchedAt = readNumber(status.upstreamFetchedAt, status.upstream_fetched_at)\n const upstreamFetchError = readString(status.upstreamFetchError, status.upstream_fetch_error)\n const error = readString(status.error)\n const staged = readNumber(status.staged) ?? 0\n const modified = readNumber(status.modified) ?? 0\n const untracked = readNumber(status.untracked) ?? 0\n const deleted = readNumber(status.deleted) ?? 0\n const renamed = readNumber(status.renamed) ?? 0\n return {\n workspace: readString(status.workspace, node.workspace) || '',\n repoRoot: repoRoot ?? null,\n isGitRepo,\n branch: readString(status.branch) ?? null,\n headCommit: readString(status.headCommit) ?? null,\n headMessage: readString(status.headMessage) ?? null,\n upstream: readString(status.upstream) ?? null,\n upstreamStatus: (upstreamStatus as GitUpstreamFreshness) ?? 'unchecked',\n ...(upstreamFetchedAt !== undefined ? { upstreamFetchedAt } : {}),\n ...(upstreamFetchError ? { upstreamFetchError } : {}),\n ahead: readNumber(status.ahead) ?? 0,\n behind: readNumber(status.behind) ?? 0,\n staged,\n modified,\n untracked,\n deleted,\n renamed,\n dirty: readBoolean(status.dirty, status.isDirty, status.is_dirty) ?? (staged + modified + untracked + deleted + renamed > 0 || hasConflicts),\n hasConflicts,\n conflictFiles,\n stashCount: readNumber(status.stashCount, status.stash_count) ?? 0,\n lastCheckedAt: options?.lastCheckedAt ?? readNumber(status.lastCheckedAt, status.last_checked_at) ?? Date.now(),\n ...(submodules ? { submodules } : {}),\n ...(error ? { error } : {}),\n }\n}\n\nexport function scoreGitStatusCandidate(git: GitRepoStatus | undefined): number {\n if (!git) return Number.NEGATIVE_INFINITY\n let score = 0\n if (git.isGitRepo === true) score += 50\n if (git.isGitRepo === false) score -= 10\n if (git.branch) score += 20\n if (git.headCommit) score += 20\n if (git.upstream) score += 10\n score += scoreGitUpstreamFreshness(git.upstreamStatus)\n if (typeof git.ahead === 'number') score += 2\n if (typeof git.behind === 'number') score += 2\n if (Array.isArray(git.submodules) && git.submodules.length > 0) score += 4 + git.submodules.length\n if (git.error) score -= 20\n return score\n}\n\n/**\n * Pick the best git status out of the four transit envelope slots a mesh node can\n * carry: lastGit.status, lastGit.result.status, lastProbe.git.status,\n * lastProbe.git.result.status. Returns undefined when none carry git evidence.\n */\nexport function pickBestTransitGitStatus(node: JsonRecord, options?: { lastCheckedAt?: number }): GitRepoStatus | undefined {\n const rawGit = readRecord(node.lastGit ?? node.last_git)\n const gitResult = readRecord(rawGit.result)\n const directStatus = readRecord(rawGit.status)\n const nestedStatus = readRecord(gitResult.status)\n const rawProbe = readRecord(node.lastProbe ?? node.last_probe)\n const probeGit = readRecord(rawProbe.git)\n const probeGitResult = readRecord(probeGit.result)\n const probeDirectStatus = readRecord(probeGit.status)\n const probeNestedStatus = readRecord(probeGitResult.status)\n const lastCheckedAt = options?.lastCheckedAt\n let best: { git: GitRepoStatus; score: number } | null = null\n for (const status of [directStatus, nestedStatus, probeDirectStatus, probeNestedStatus]) {\n const normalized = normalizeGitStatus(status, node, { lastCheckedAt: lastCheckedAt ?? Date.now() })\n if (!normalized) continue\n const score = scoreGitStatusCandidate(normalized)\n if (!best || score > best.score) best = { git: normalized, score }\n }\n return best?.git\n}\n","/**\n * Canonical mesh-session record normalizer shared by the cloud (web-core) mesh\n * paths. Parses an already-transit-shaped session record into a typed\n * RepoMeshSessionStatus.\n */\n\nimport { readBoolean, readRecord, readString } from './json'\nimport type { RepoMeshSessionStatus } from './types'\n\n/**\n * Build a deterministic synthetic session id from a record that has no explicit\n * id. Two transit refreshes of the same logical session produce the same id so\n * downstream dedupe stays stable across refreshes (a random id would create a new\n * node every poll). Derived from the stable identifying fields available on the\n * record; prefixed \"synthetic:\" so callers can tell it apart from a real id.\n */\nfunction deriveSyntheticSessionId(record: ReturnType<typeof readRecord>): string | undefined {\n const parts = [\n readString(record.workspace),\n readString(record.providerType, record.provider),\n readString(record.role),\n readString(record.state, record.status),\n readString(record.title),\n readString(record.createdAt, record.created_at),\n readString(record.startedAt, record.started_at),\n ].filter((part): part is string => Boolean(part))\n if (parts.length === 0) return undefined\n return `synthetic:${parts.join('|')}`\n}\n\nexport function normalizeMeshSessionRecord(entry: unknown): RepoMeshSessionStatus | null {\n const record = readRecord(entry)\n // BUG FIX: cloud transit can reshape/strip the explicit id field. Fall back\n // through sessionId → session_id → id, then to a DETERMINISTIC synthetic id\n // derived from record content so the session survives the round trip instead\n // of being dropped (and so dedupe stays stable across refreshes). Return null\n // ONLY when the record carries no identifying fields at all.\n const sessionId = readString(record.sessionId, record.session_id, record.id)\n ?? deriveSyntheticSessionId(record)\n if (!sessionId) return null\n return {\n sessionId,\n ...(readString(record.providerType, record.provider) ? { providerType: readString(record.providerType, record.provider) } : {}),\n ...(readString(record.state, record.status) ? { state: readString(record.state, record.status) } : {}),\n ...(readString(record.chatStatus, record.chat_status) ? { chatStatus: readString(record.chatStatus, record.chat_status) } : {}),\n ...(readString(record.lifecycle) ? { lifecycle: readString(record.lifecycle) as RepoMeshSessionStatus['lifecycle'] } : {}),\n ...(readString(record.surfaceKind, record.surface_kind) ? { surfaceKind: readString(record.surfaceKind, record.surface_kind) as RepoMeshSessionStatus['surfaceKind'] } : {}),\n ...(readString(record.recoveryState, record.recovery_state) ? { recoveryState: readString(record.recoveryState, record.recovery_state) } : {}),\n ...(readString(record.workspace) ? { workspace: readString(record.workspace) } : {}),\n ...(readString(record.title) ? { title: readString(record.title) } : {}),\n ...(readString(record.role) ? { role: readString(record.role) } : {}),\n ...(readBoolean(record.isSelfCoordinator, record.is_self_coordinator) !== undefined ? { isSelfCoordinator: readBoolean(record.isSelfCoordinator, record.is_self_coordinator) } : {}),\n ...(readString(record.statusNote, record.status_note) ? { statusNote: readString(record.statusNote, record.status_note) } : {}),\n ...(readString(record.createdAt, record.created_at) ? { createdAt: readString(record.createdAt, record.created_at) } : {}),\n ...(readString(record.startedAt, record.started_at) ? { startedAt: readString(record.startedAt, record.started_at) } : {}),\n ...(readString(record.lastActivityAt, record.last_activity_at) ? { lastActivityAt: readString(record.lastActivityAt, record.last_activity_at) } : {}),\n ...(readBoolean(record.isCached, record.is_cached) !== undefined ? { isCached: readBoolean(record.isCached, record.is_cached) } : {}),\n }\n}\n","/**\n * Canonical compact git-shape summarizer used by debug/log surfaces on both the\n * cloud (daemon-cloud mesh command summarizer) and standalone (daemon-core router\n * RepoMeshStatusDebug) paths. Produces a small, log-safe projection of a git\n * status record — commit SHAs are truncated to 12 chars.\n *\n * Transport-specific envelope unwrapping (result / result.status / top-level)\n * stays in the caller; this takes an already-unwrapped status record.\n */\n\nimport { readBoolean, readNumber, readRecord, readString } from './json'\n\nexport function summarizeGitShape(status: unknown): Record<string, unknown> | null {\n const record = readRecord(status)\n if (!Object.keys(record).length) return null\n const submodules = Array.isArray(record.submodules)\n ? record.submodules.map((entry: unknown) => {\n const sub = readRecord(entry)\n return {\n path: readString(sub.path) ?? null,\n commit: readString(sub.commit)?.slice(0, 12) ?? null,\n dirty: readBoolean(sub.dirty) ?? false,\n outOfSync: readBoolean(sub.outOfSync, sub.out_of_sync) ?? false,\n }\n })\n : []\n return {\n isGitRepo: readBoolean(record.isGitRepo),\n workspace: readString(record.workspace) ?? null,\n repoRoot: readString(record.repoRoot, record.repo_root) ?? null,\n branch: readString(record.branch) ?? null,\n upstream: readString(record.upstream) ?? null,\n upstreamStatus: readString(record.upstreamStatus, record.upstream_status) ?? null,\n headCommit: readString(record.headCommit, record.head_commit)?.slice(0, 12) ?? null,\n ahead: readNumber(record.ahead) ?? null,\n behind: readNumber(record.behind) ?? null,\n dirtyCounts: {\n staged: readNumber(record.staged) ?? 0,\n modified: readNumber(record.modified) ?? 0,\n untracked: readNumber(record.untracked) ?? 0,\n deleted: readNumber(record.deleted) ?? 0,\n renamed: readNumber(record.renamed) ?? 0,\n },\n lastCheckedAt: readNumber(record.lastCheckedAt, record.last_checked_at) ?? null,\n submoduleCount: submodules.length,\n submodules,\n }\n}\n"],"mappings":"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","names":[]}
package/dist/json.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Pure JSON-record reading primitives shared by the cloud (web-core / P2P transit)
3
+ * and standalone (daemon-core / local IPC) mesh normalizers.
4
+ *
5
+ * These operate only on plain JS values — no Node/DOM APIs, no transport, no git
6
+ * exec — so both cores can import them without violating the core↔core dependency
7
+ * ban. They are the single source of truth for the field-coercion rules that the
8
+ * two transports previously hand-synced (and drifted on).
9
+ */
10
+ export type JsonRecord = Record<string, unknown>;
11
+ export declare function readRecord(value: unknown): JsonRecord;
12
+ export declare function readString(...values: unknown[]): string | undefined;
13
+ export declare function readNumber(...values: unknown[]): number | undefined;
14
+ export declare function readBoolean(...values: unknown[]): boolean | undefined;
15
+ export declare function readStringArray(value: unknown): string[];
16
+ /**
17
+ * Join a (possibly absent) repo root with a relative submodule path. Returns the
18
+ * path unchanged when it is already absolute, and undefined when nothing usable
19
+ * can be derived — callers must treat the result as optional.
20
+ */
21
+ export declare function joinRepoPath(root: string | undefined, relativePath: string | undefined): string | undefined;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Canonical mesh-session record normalizer shared by the cloud (web-core) mesh
3
+ * paths. Parses an already-transit-shaped session record into a typed
4
+ * RepoMeshSessionStatus.
5
+ */
6
+ import type { RepoMeshSessionStatus } from './types';
7
+ export declare function normalizeMeshSessionRecord(entry: unknown): RepoMeshSessionStatus | null;
@@ -0,0 +1,110 @@
1
+ /**
2
+ * Canonical pure-shape mesh/git status types.
3
+ *
4
+ * These are the single source of truth for the data shapes that cross the
5
+ * cloud (P2P/transit) and standalone (local IPC) boundaries. daemon-core and
6
+ * web-core re-export these so neither side can drift its own copy. Pure type
7
+ * declarations only — no runtime, no dependency on either core.
8
+ */
9
+ export type GitUpstreamFreshness = 'fresh' | 'unchecked' | 'stale' | 'no_upstream' | 'unavailable';
10
+ export type GitFailureReason = 'not_git_repo' | 'git_not_installed' | 'timeout' | 'path_outside_repo' | 'dirty_index_required' | 'conflict' | 'invalid_args' | 'nothing_to_commit' | 'git_command_failed';
11
+ export interface GitRepoIdentity {
12
+ workspace: string;
13
+ repoRoot: string | null;
14
+ isGitRepo: boolean;
15
+ }
16
+ export interface GitSubmoduleStatus {
17
+ /** Submodule path relative to repo root */
18
+ path: string;
19
+ /** Current commit SHA the submodule is at */
20
+ commit: string;
21
+ /**
22
+ * Path to the submodule repo (absolute). Optional: cloud P2P transit can
23
+ * deliver submodule entries without a derivable repo path, and graph
24
+ * rendering only uses this for a display field that is allowed to be empty.
25
+ */
26
+ repoPath?: string;
27
+ /** Whether the submodule has uncommitted changes */
28
+ dirty: boolean;
29
+ /** Whether the submodule commit differs from what the parent repo expects */
30
+ outOfSync: boolean;
31
+ /** Last checked timestamp */
32
+ lastCheckedAt: number;
33
+ /** Error message if submodule status could not be read */
34
+ error?: string;
35
+ }
36
+ export interface DaemonBuildBehind {
37
+ /** Full build commit baked into the running daemon. */
38
+ buildCommit: string;
39
+ /** Short build commit. */
40
+ buildCommitShort: string;
41
+ /** HEAD commit the build commit is behind (repo or submodule). */
42
+ head: string;
43
+ /** Where the comparison matched: 'root' or the submodule path. */
44
+ scope: string;
45
+ /**
46
+ * Whether any package changed between buildCommit..HEAD affects the daemon
47
+ * runtime (daemon-core, standalone, session-host, terminal-mux, ghostty,
48
+ * mcp-server). When false, only web/render packages changed — the daemon does
49
+ * NOT need a rebuild/restart; only the web deploy is pending. Conservative:
50
+ * when the changed-package set can't be determined it defaults to true.
51
+ */
52
+ isDaemonAffecting: boolean;
53
+ /** Distinct package names changed between buildCommit..HEAD (best-effort). */
54
+ affectedPackages?: string[];
55
+ warning: string;
56
+ }
57
+ export interface GitRepoStatus extends GitRepoIdentity {
58
+ branch: string | null;
59
+ headCommit: string | null;
60
+ headMessage: string | null;
61
+ upstream: string | null;
62
+ /** Whether ahead/behind was verified against a freshly fetched upstream ref. */
63
+ upstreamStatus: GitUpstreamFreshness;
64
+ /** Timestamp for the fetch that refreshed upstream refs when upstreamStatus === 'fresh'. */
65
+ upstreamFetchedAt?: number;
66
+ /** Error from the last refresh attempt when upstreamStatus === 'stale'. */
67
+ upstreamFetchError?: string;
68
+ ahead: number;
69
+ behind: number;
70
+ staged: number;
71
+ modified: number;
72
+ untracked: number;
73
+ deleted: number;
74
+ renamed: number;
75
+ /** Aggregate dirty flag including root worktree changes, conflicts, stash, and submodule drift. */
76
+ dirty: boolean;
77
+ hasConflicts: boolean;
78
+ conflictFiles: string[];
79
+ stashCount: number;
80
+ lastCheckedAt: number;
81
+ /** Submodule statuses when auto-discover is enabled */
82
+ submodules?: GitSubmoduleStatus[];
83
+ /**
84
+ * Set when the running daemon's build commit is a STRICT ancestor of this
85
+ * repo's HEAD (or a submodule HEAD) — i.e. the live daemon predates committed
86
+ * code in this workspace and is awaiting a deploy/restart to catch up.
87
+ * Omitted entirely when no staleness is provable.
88
+ */
89
+ daemonBuildBehind?: DaemonBuildBehind;
90
+ error?: string;
91
+ reason?: GitFailureReason;
92
+ }
93
+ export interface RepoMeshSessionStatus {
94
+ sessionId: string;
95
+ providerType?: string;
96
+ state?: string;
97
+ chatStatus?: string;
98
+ lifecycle?: 'starting' | 'running' | 'stopping' | 'stopped' | 'failed' | 'interrupted';
99
+ surfaceKind?: 'live_runtime' | 'recovery_snapshot' | 'inactive_record';
100
+ recoveryState?: string | null;
101
+ workspace?: string | null;
102
+ title?: string | null;
103
+ role?: string | null;
104
+ isSelfCoordinator?: boolean;
105
+ statusNote?: string | null;
106
+ createdAt?: string | null;
107
+ startedAt?: string | null;
108
+ lastActivityAt?: string | null;
109
+ isCached?: boolean;
110
+ }
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@adhdev/mesh-shared",
3
+ "version": "0.9.82-rc.267",
4
+ "description": "ADHDev mesh-shared — pure mesh/git status normalizers shared by daemon-core and web-core",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./dist/index.d.ts",
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "default": "./dist/index.js"
13
+ }
14
+ },
15
+ "scripts": {
16
+ "build": "tsup && npm run build:types",
17
+ "build:types": "tsc -p tsconfig.json --emitDeclarationOnly --declaration --declarationMap false",
18
+ "dev": "tsup --watch --no-clean",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest",
21
+ "typecheck": "tsc --noEmit -p tsconfig.json"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "src"
26
+ ],
27
+ "keywords": [
28
+ "adhdev",
29
+ "mesh",
30
+ "git-status",
31
+ "normalizer"
32
+ ],
33
+ "author": "vilmire",
34
+ "license": "AGPL-3.0-or-later",
35
+ "dependencies": {},
36
+ "devDependencies": {
37
+ "@types/node": "^22.0.0",
38
+ "tsup": "^8.2.0",
39
+ "typescript": "^5.5.0",
40
+ "vitest": "^4.1.3"
41
+ },
42
+ "repository": {
43
+ "type": "git",
44
+ "url": "https://github.com/vilmire/adhdev.git",
45
+ "directory": "packages/mesh-shared"
46
+ },
47
+ "homepage": "https://github.com/vilmire/adhdev#readme"
48
+ }