@adhdev/mesh-shared 0.9.82-rc.348 → 0.9.82-rc.349
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 +14 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +13 -0
- package/dist/index.mjs.map +1 -1
- package/dist/json.d.ts +9 -0
- package/package.json +1 -1
- package/src/json.ts +21 -0
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
normalizeGitStatus: () => normalizeGitStatus,
|
|
27
27
|
normalizeMeshNodeId: () => normalizeMeshNodeId,
|
|
28
28
|
normalizeMeshSessionRecord: () => normalizeMeshSessionRecord,
|
|
29
|
+
parseJsonRecord: () => parseJsonRecord,
|
|
29
30
|
pickBestTransitGitStatus: () => pickBestTransitGitStatus,
|
|
30
31
|
readBoolean: () => readBoolean,
|
|
31
32
|
readGitSubmodules: () => readGitSubmodules,
|
|
@@ -66,6 +67,18 @@ function readBoolean(...values) {
|
|
|
66
67
|
function readStringArray(value) {
|
|
67
68
|
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
|
|
68
69
|
}
|
|
70
|
+
function parseJsonRecord(value) {
|
|
71
|
+
if (!value) return {};
|
|
72
|
+
if (typeof value === "object") return readRecord(value);
|
|
73
|
+
if (typeof value !== "string") return {};
|
|
74
|
+
const trimmed = value.trim();
|
|
75
|
+
if (!trimmed) return {};
|
|
76
|
+
try {
|
|
77
|
+
return readRecord(JSON.parse(trimmed));
|
|
78
|
+
} catch {
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
}
|
|
69
82
|
function joinRepoPath(root, relativePath) {
|
|
70
83
|
const normalizedRoot = typeof root === "string" ? root.trim().replace(/[\\/]+$/, "") : "";
|
|
71
84
|
const normalizedPath = typeof relativePath === "string" ? relativePath.trim() : "";
|
|
@@ -302,6 +315,7 @@ function summarizeGitShape(status) {
|
|
|
302
315
|
normalizeGitStatus,
|
|
303
316
|
normalizeMeshNodeId,
|
|
304
317
|
normalizeMeshSessionRecord,
|
|
318
|
+
parseJsonRecord,
|
|
305
319
|
pickBestTransitGitStatus,
|
|
306
320
|
readBoolean,
|
|
307
321
|
readGitSubmodules,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/json.ts","../src/git-normalize.ts","../src/session-normalize.ts","../src/node-normalize.ts","../src/git-summarize.ts"],"sourcesContent":["/**\n * @adhdev/mesh-shared — pure, dependency-free mesh/git status normalizers shared\n * by daemon-core (standalone / local IPC) and web-core (cloud / P2P transit).\n *\n * This package exists to kill a recurring bug class: the cloud and standalone\n * transports each used to carry their own hand-synced copy of these normalizers,\n * and they drifted (cloud would strip/reshape fields, the web filter would drop\n * entries the standalone path kept). Both cores now import this single source of\n * truth. It MUST stay a pure leaf — types + pure functions on plain JS objects,\n * no Node/DOM APIs, no git exec, no transport, and an empty dependency set.\n */\n\nexport * from './json'\nexport * from './types'\nexport * from './git-normalize'\nexport * from './session-normalize'\nexport * from './node-normalize'\nexport * from './git-summarize'\n","/**\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 mesh-node identity normalizer shared by daemon-core (standalone /\n * local IPC) and web-core (cloud / P2P transit) — the node-id counterpart of\n * session-normalize.ts.\n *\n * A mesh node record can carry its stable identifier under THREE different field\n * names depending on the serialization path it travelled, all with the same\n * value:\n * - `id` — config canonical form (mesh registry / persisted config)\n * - `nodeId` — runtime/wire camelCase form (inline-cache de-serialization,\n * mesh_status output via readStringValue(node.nodeId, node.id))\n * - `node_id` — SQLite DB column form leaked onto the object\n *\n * Comparing only `node.id` (or a 2-way `id ?? nodeId` that omits `node_id`)\n * against a target id silently drops nodes that arrived in another form — e.g. an\n * inline-cached worktree node, leaving a target-routed task permanently pending\n * with a misleading `no_node_satisfies_required_tags` skip. This module is the\n * single 3-way source of truth so every comparison site absorbs all three forms.\n */\n\nimport { readString } from './json'\nimport type { MeshNodeIdentified } from './types'\n\n/**\n * Read a mesh node's stable identifier, absorbing any of the three\n * serialization forms (`id` / `nodeId` / `node_id`). Returns undefined when the\n * record carries no usable id in any form.\n */\nexport function normalizeMeshNodeId(node: MeshNodeIdentified | null | undefined): string | undefined {\n const record = (node && typeof node === 'object' ? node : {}) as MeshNodeIdentified\n return readString(record.id, record.nodeId, record.node_id)\n}\n\n/**\n * Whether a mesh node record matches the given candidate id, comparing the\n * node's normalized id (any form) against the candidate. False when either side\n * is empty — never matches an absent id against an absent candidate.\n */\nexport function meshNodeIdMatches(\n node: MeshNodeIdentified | null | undefined,\n candidateId: string | null | undefined,\n): boolean {\n if (!candidateId) return false\n const trimmed = candidateId.trim()\n if (!trimmed) return false\n return normalizeMeshNodeId(node) === trimmed\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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/json.ts","../src/git-normalize.ts","../src/session-normalize.ts","../src/node-normalize.ts","../src/git-summarize.ts"],"sourcesContent":["/**\n * @adhdev/mesh-shared — pure, dependency-free mesh/git status normalizers shared\n * by daemon-core (standalone / local IPC) and web-core (cloud / P2P transit).\n *\n * This package exists to kill a recurring bug class: the cloud and standalone\n * transports each used to carry their own hand-synced copy of these normalizers,\n * and they drifted (cloud would strip/reshape fields, the web filter would drop\n * entries the standalone path kept). Both cores now import this single source of\n * truth. It MUST stay a pure leaf — types + pure functions on plain JS objects,\n * no Node/DOM APIs, no git exec, no transport, and an empty dependency set.\n */\n\nexport * from './json'\nexport * from './types'\nexport * from './git-normalize'\nexport * from './session-normalize'\nexport * from './node-normalize'\nexport * from './git-summarize'\n","/**\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 * Coerce a value that is either an already-parsed plain object or a JSON string\n * into a plain object record. Arrays, primitives, parse failures and empty\n * strings all collapse to {} — callers treat the result as a best-effort record.\n *\n * This is the single source of truth for the `parseJsonRecord`/`parseJsonObject`\n * coercion that cloud mesh normalizers previously hand-redefined per file.\n */\nexport function parseJsonRecord(value: unknown): JsonRecord {\n if (!value) return {}\n if (typeof value === 'object') return readRecord(value)\n if (typeof value !== 'string') return {}\n const trimmed = value.trim()\n if (!trimmed) return {}\n try {\n return readRecord(JSON.parse(trimmed))\n } catch {\n return {}\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 mesh-node identity normalizer shared by daemon-core (standalone /\n * local IPC) and web-core (cloud / P2P transit) — the node-id counterpart of\n * session-normalize.ts.\n *\n * A mesh node record can carry its stable identifier under THREE different field\n * names depending on the serialization path it travelled, all with the same\n * value:\n * - `id` — config canonical form (mesh registry / persisted config)\n * - `nodeId` — runtime/wire camelCase form (inline-cache de-serialization,\n * mesh_status output via readStringValue(node.nodeId, node.id))\n * - `node_id` — SQLite DB column form leaked onto the object\n *\n * Comparing only `node.id` (or a 2-way `id ?? nodeId` that omits `node_id`)\n * against a target id silently drops nodes that arrived in another form — e.g. an\n * inline-cached worktree node, leaving a target-routed task permanently pending\n * with a misleading `no_node_satisfies_required_tags` skip. This module is the\n * single 3-way source of truth so every comparison site absorbs all three forms.\n */\n\nimport { readString } from './json'\nimport type { MeshNodeIdentified } from './types'\n\n/**\n * Read a mesh node's stable identifier, absorbing any of the three\n * serialization forms (`id` / `nodeId` / `node_id`). Returns undefined when the\n * record carries no usable id in any form.\n */\nexport function normalizeMeshNodeId(node: MeshNodeIdentified | null | undefined): string | undefined {\n const record = (node && typeof node === 'object' ? node : {}) as MeshNodeIdentified\n return readString(record.id, record.nodeId, record.node_id)\n}\n\n/**\n * Whether a mesh node record matches the given candidate id, comparing the\n * node's normalized id (any form) against the candidate. False when either side\n * is empty — never matches an absent id against an absent candidate.\n */\nexport function meshNodeIdMatches(\n node: MeshNodeIdentified | null | undefined,\n candidateId: string | null | undefined,\n): boolean {\n if (!candidateId) return false\n const trimmed = candidateId.trim()\n if (!trimmed) return false\n return normalizeMeshNodeId(node) === trimmed\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/index.mjs
CHANGED
|
@@ -25,6 +25,18 @@ function readBoolean(...values) {
|
|
|
25
25
|
function readStringArray(value) {
|
|
26
26
|
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
|
|
27
27
|
}
|
|
28
|
+
function parseJsonRecord(value) {
|
|
29
|
+
if (!value) return {};
|
|
30
|
+
if (typeof value === "object") return readRecord(value);
|
|
31
|
+
if (typeof value !== "string") return {};
|
|
32
|
+
const trimmed = value.trim();
|
|
33
|
+
if (!trimmed) return {};
|
|
34
|
+
try {
|
|
35
|
+
return readRecord(JSON.parse(trimmed));
|
|
36
|
+
} catch {
|
|
37
|
+
return {};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
28
40
|
function joinRepoPath(root, relativePath) {
|
|
29
41
|
const normalizedRoot = typeof root === "string" ? root.trim().replace(/[\\/]+$/, "") : "";
|
|
30
42
|
const normalizedPath = typeof relativePath === "string" ? relativePath.trim() : "";
|
|
@@ -260,6 +272,7 @@ export {
|
|
|
260
272
|
normalizeGitStatus,
|
|
261
273
|
normalizeMeshNodeId,
|
|
262
274
|
normalizeMeshSessionRecord,
|
|
275
|
+
parseJsonRecord,
|
|
263
276
|
pickBestTransitGitStatus,
|
|
264
277
|
readBoolean,
|
|
265
278
|
readGitSubmodules,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/json.ts","../src/git-normalize.ts","../src/session-normalize.ts","../src/node-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 mesh-node identity normalizer shared by daemon-core (standalone /\n * local IPC) and web-core (cloud / P2P transit) — the node-id counterpart of\n * session-normalize.ts.\n *\n * A mesh node record can carry its stable identifier under THREE different field\n * names depending on the serialization path it travelled, all with the same\n * value:\n * - `id` — config canonical form (mesh registry / persisted config)\n * - `nodeId` — runtime/wire camelCase form (inline-cache de-serialization,\n * mesh_status output via readStringValue(node.nodeId, node.id))\n * - `node_id` — SQLite DB column form leaked onto the object\n *\n * Comparing only `node.id` (or a 2-way `id ?? nodeId` that omits `node_id`)\n * against a target id silently drops nodes that arrived in another form — e.g. an\n * inline-cached worktree node, leaving a target-routed task permanently pending\n * with a misleading `no_node_satisfies_required_tags` skip. This module is the\n * single 3-way source of truth so every comparison site absorbs all three forms.\n */\n\nimport { readString } from './json'\nimport type { MeshNodeIdentified } from './types'\n\n/**\n * Read a mesh node's stable identifier, absorbing any of the three\n * serialization forms (`id` / `nodeId` / `node_id`). Returns undefined when the\n * record carries no usable id in any form.\n */\nexport function normalizeMeshNodeId(node: MeshNodeIdentified | null | undefined): string | undefined {\n const record = (node && typeof node === 'object' ? node : {}) as MeshNodeIdentified\n return readString(record.id, record.nodeId, record.node_id)\n}\n\n/**\n * Whether a mesh node record matches the given candidate id, comparing the\n * node's normalized id (any form) against the candidate. False when either side\n * is empty — never matches an absent id against an absent candidate.\n */\nexport function meshNodeIdMatches(\n node: MeshNodeIdentified | null | undefined,\n candidateId: string | null | undefined,\n): boolean {\n if (!candidateId) return false\n const trimmed = candidateId.trim()\n if (!trimmed) return false\n return normalizeMeshNodeId(node) === trimmed\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":[]}
|
|
1
|
+
{"version":3,"sources":["../src/json.ts","../src/git-normalize.ts","../src/session-normalize.ts","../src/node-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 * Coerce a value that is either an already-parsed plain object or a JSON string\n * into a plain object record. Arrays, primitives, parse failures and empty\n * strings all collapse to {} — callers treat the result as a best-effort record.\n *\n * This is the single source of truth for the `parseJsonRecord`/`parseJsonObject`\n * coercion that cloud mesh normalizers previously hand-redefined per file.\n */\nexport function parseJsonRecord(value: unknown): JsonRecord {\n if (!value) return {}\n if (typeof value === 'object') return readRecord(value)\n if (typeof value !== 'string') return {}\n const trimmed = value.trim()\n if (!trimmed) return {}\n try {\n return readRecord(JSON.parse(trimmed))\n } catch {\n return {}\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 mesh-node identity normalizer shared by daemon-core (standalone /\n * local IPC) and web-core (cloud / P2P transit) — the node-id counterpart of\n * session-normalize.ts.\n *\n * A mesh node record can carry its stable identifier under THREE different field\n * names depending on the serialization path it travelled, all with the same\n * value:\n * - `id` — config canonical form (mesh registry / persisted config)\n * - `nodeId` — runtime/wire camelCase form (inline-cache de-serialization,\n * mesh_status output via readStringValue(node.nodeId, node.id))\n * - `node_id` — SQLite DB column form leaked onto the object\n *\n * Comparing only `node.id` (or a 2-way `id ?? nodeId` that omits `node_id`)\n * against a target id silently drops nodes that arrived in another form — e.g. an\n * inline-cached worktree node, leaving a target-routed task permanently pending\n * with a misleading `no_node_satisfies_required_tags` skip. This module is the\n * single 3-way source of truth so every comparison site absorbs all three forms.\n */\n\nimport { readString } from './json'\nimport type { MeshNodeIdentified } from './types'\n\n/**\n * Read a mesh node's stable identifier, absorbing any of the three\n * serialization forms (`id` / `nodeId` / `node_id`). Returns undefined when the\n * record carries no usable id in any form.\n */\nexport function normalizeMeshNodeId(node: MeshNodeIdentified | null | undefined): string | undefined {\n const record = (node && typeof node === 'object' ? node : {}) as MeshNodeIdentified\n return readString(record.id, record.nodeId, record.node_id)\n}\n\n/**\n * Whether a mesh node record matches the given candidate id, comparing the\n * node's normalized id (any form) against the candidate. False when either side\n * is empty — never matches an absent id against an absent candidate.\n */\nexport function meshNodeIdMatches(\n node: MeshNodeIdentified | null | undefined,\n candidateId: string | null | undefined,\n): boolean {\n if (!candidateId) return false\n const trimmed = candidateId.trim()\n if (!trimmed) return false\n return normalizeMeshNodeId(node) === trimmed\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
CHANGED
|
@@ -13,6 +13,15 @@ export declare function readString(...values: unknown[]): string | undefined;
|
|
|
13
13
|
export declare function readNumber(...values: unknown[]): number | undefined;
|
|
14
14
|
export declare function readBoolean(...values: unknown[]): boolean | undefined;
|
|
15
15
|
export declare function readStringArray(value: unknown): string[];
|
|
16
|
+
/**
|
|
17
|
+
* Coerce a value that is either an already-parsed plain object or a JSON string
|
|
18
|
+
* into a plain object record. Arrays, primitives, parse failures and empty
|
|
19
|
+
* strings all collapse to {} — callers treat the result as a best-effort record.
|
|
20
|
+
*
|
|
21
|
+
* This is the single source of truth for the `parseJsonRecord`/`parseJsonObject`
|
|
22
|
+
* coercion that cloud mesh normalizers previously hand-redefined per file.
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseJsonRecord(value: unknown): JsonRecord;
|
|
16
25
|
/**
|
|
17
26
|
* Join a (possibly absent) repo root with a relative submodule path. Returns the
|
|
18
27
|
* path unchanged when it is already absolute, and undefined when nothing usable
|
package/package.json
CHANGED
package/src/json.ts
CHANGED
|
@@ -43,6 +43,27 @@ export function readStringArray(value: unknown): string[] {
|
|
|
43
43
|
: []
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Coerce a value that is either an already-parsed plain object or a JSON string
|
|
48
|
+
* into a plain object record. Arrays, primitives, parse failures and empty
|
|
49
|
+
* strings all collapse to {} — callers treat the result as a best-effort record.
|
|
50
|
+
*
|
|
51
|
+
* This is the single source of truth for the `parseJsonRecord`/`parseJsonObject`
|
|
52
|
+
* coercion that cloud mesh normalizers previously hand-redefined per file.
|
|
53
|
+
*/
|
|
54
|
+
export function parseJsonRecord(value: unknown): JsonRecord {
|
|
55
|
+
if (!value) return {}
|
|
56
|
+
if (typeof value === 'object') return readRecord(value)
|
|
57
|
+
if (typeof value !== 'string') return {}
|
|
58
|
+
const trimmed = value.trim()
|
|
59
|
+
if (!trimmed) return {}
|
|
60
|
+
try {
|
|
61
|
+
return readRecord(JSON.parse(trimmed))
|
|
62
|
+
} catch {
|
|
63
|
+
return {}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
46
67
|
/**
|
|
47
68
|
* Join a (possibly absent) repo root with a relative submodule path. Returns the
|
|
48
69
|
* path unchanged when it is already absolute, and undefined when nothing usable
|