@unified-product-graph/sdk 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +43 -0
- package/dist/chunk-ARDXTSGG.js +20904 -0
- package/dist/chunk-ARDXTSGG.js.map +1 -0
- package/dist/index.d.ts +678 -0
- package/dist/index.js +2732 -0
- package/dist/index.js.map +1 -0
- package/dist/logic-DriXyFKi.d.ts +1480 -0
- package/dist/logic.d.ts +2 -0
- package/dist/logic.js +95 -0
- package/dist/logic.js.map +1 -0
- package/package.json +60 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store.ts","../src/lib/tools.ts","../src/client.ts","../src/lib/workspace.ts","../src/lib/portfolio-routing.ts"],"sourcesContent":["import * as fs from 'node:fs/promises'\nimport * as path from 'node:path'\nimport { createHash } from 'node:crypto'\nimport type { FSWatcher } from 'chokidar'\nimport {\n validateUPGDocument,\n type UPGDocument,\n type UPGBaseNode,\n type UPGEdge,\n type UPGIntegrity,\n type UPGPortfolioDocument,\n type UPGCrossEdge,\n UPG_CROSS_EDGE_TYPES,\n} from '@unified-product-graph/core'\nimport { UPG_TYPES, rotateSlug, migrateEdge, migrateNodeProperties, UPG_VERSION, type UPGPropertyMigrationChange } from '@unified-product-graph/core'\nimport { coerceProductStage } from '@unified-product-graph/core'\nimport type { UPGEdgeType, UPGEntityType, UPGCrossEdgeType } from '@unified-product-graph/core'\nimport {\n classifyDanglingEdges,\n renderDanglingReport,\n type DanglingEdgeReport,\n type DanglingEdgeClass,\n} from './lib/dangling-edges.js'\nimport {\n computeSchemaDriftSummary,\n renderDriftSummary,\n type SchemaDriftSummary,\n} from './lib/schema-drift.js'\n\nexport interface QuarantinedEntity {\n id: string\n type: string\n title: string\n reason: string\n}\n\nexport interface IntegrityReport {\n /** Whether the file was modified outside the MCP server */\n tampered: boolean\n /** Entities that failed schema validation after external modification */\n quarantined: QuarantinedEntity[]\n /** Edges removed because they reference quarantined or missing nodes */\n orphanedEdges: number\n}\n\nexport interface ChangeEntry {\n action: 'create' | 'update' | 'delete'\n entity: 'node' | 'edge'\n id: string\n type: string\n title?: string\n timestamp: string\n}\n\nexport interface MergeResult {\n merged: boolean\n nodesAdded: number\n edgesAdded: number\n nodesFromDisk: number\n edgesFromDisk: number\n conflicts: Array<{ nodeId: string; field: string; ours: unknown; theirs: unknown }>\n}\n\nexport class UPGFileStore {\n private doc!: UPGDocument\n private filePath!: string\n private dirty = false\n private saveTimer: ReturnType<typeof setTimeout> | null = null\n private selfWriteInProgress = false\n private watcher: FSWatcher | null = null\n\n // Indexes for O(1) lookups\n private nodeMap = new Map<string, UPGBaseNode>()\n private edgeMap = new Map<string, UPGEdge>()\n private edgesByNode = new Map<string, Set<string>>() // nodeId → Set<edgeId>\n\n // Session change log\n private changeLog: ChangeEntry[] = []\n\n // Content hash for cache-aware responses\n private contentHash = ''\n\n // ── Concurrent write protection ─────────────────────────────────────────\n // Baseline = the raw file hash at the time we loaded or last saved.\n // If disk hash != baseline when we try to save, another process modified the file.\n private baselineFileHash = ''\n // Snapshot of node/edge IDs at baseline — used for three-way merge\n private baselineNodeIds = new Set<string>()\n private baselineEdgeIds = new Set<string>()\n // Last merge result (available to the server for reporting)\n private lastMergeResult: MergeResult | null = null\n\n // ── Integrity protection ───────────────────────────────────────────────\n // Last integrity check result (available to the server for reporting)\n private lastIntegrityReport: IntegrityReport | null = null\n // Set of known UPG entity types for schema validation\n private knownTypes = new Set(UPG_TYPES)\n // ── Dangling-edge tracking ───────────────────────────────────\n private lastDanglingReport: DanglingEdgeReport | null = null\n private lastDriftSummary: SchemaDriftSummary | null = null\n\n // ── Load / Save ──────────────────────────────────────────────────────────\n\n /** Hash raw file bytes — used for baseline comparison (NOT the same as contentHash) */\n private hashRawContent(raw: string): string {\n return createHash('sha256').update(raw).digest('hex').slice(0, 32)\n }\n\n /** Compute integrity checksum over nodes + edges content (deterministic) */\n private computeIntegrityChecksum(): string {\n // Sort nodes and edges by ID for deterministic hash regardless of order\n const sortedNodes = [...this.doc.nodes].sort((a, b) => a.id.localeCompare(b.id))\n const sortedEdges = [...this.doc.edges].sort((a, b) => a.id.localeCompare(b.id))\n const content = JSON.stringify({ nodes: sortedNodes, edges: sortedEdges })\n return createHash('sha256').update(content).digest('hex').slice(0, 32)\n }\n\n /** Stamp the document with current integrity checksum */\n private stampIntegrity(): void {\n this.doc._integrity = {\n checksum: this.computeIntegrityChecksum(),\n verified_at: new Date().toISOString(),\n verified_by: 'upg-mcp-local',\n }\n }\n\n /** Verify integrity and quarantine invalid entities if file was modified externally */\n private verifyIntegrity(): IntegrityReport {\n const report: IntegrityReport = { tampered: false, quarantined: [], orphanedEdges: 0 }\n\n // Check if integrity stamp exists\n if (!this.doc._integrity) {\n // First time — no stamp yet. Stamp it now, no quarantine needed.\n this.stampIntegrity()\n return report\n }\n\n // Verify checksum\n const currentChecksum = this.computeIntegrityChecksum()\n if (currentChecksum === this.doc._integrity.checksum) {\n // File was not modified externally — all good\n return report\n }\n\n // File was modified outside the MCP server\n report.tampered = true\n\n // Run entity-level validation: check each node against known types\n const validNodeIds = new Set<string>()\n const quarantinedIds = new Set<string>()\n\n this.doc.nodes = this.doc.nodes.filter((node) => {\n // Must have id, type, title\n if (!node.id || !node.type || !node.title) {\n report.quarantined.push({\n id: node.id || 'unknown',\n type: node.type || 'unknown',\n title: node.title || 'untitled',\n reason: 'Missing required field (id, type, or title)',\n })\n quarantinedIds.add(node.id || 'unknown')\n return false\n }\n\n // Type must be known\n if (!this.knownTypes.has(node.type) && node.type !== 'product' && node.type !== 'document') {\n report.quarantined.push({\n id: node.id,\n type: node.type,\n title: node.title,\n reason: `Unknown entity type: \"${node.type}\"`,\n })\n quarantinedIds.add(node.id)\n return false\n }\n\n validNodeIds.add(node.id)\n return true\n })\n\n // Remove edges referencing quarantined or missing nodes\n const beforeEdgeCount = this.doc.edges.length\n this.doc.edges = this.doc.edges.filter((edge) => {\n if (!edge.id || !edge.source || !edge.target || !edge.type) return false\n return validNodeIds.has(edge.source) && validNodeIds.has(edge.target)\n })\n report.orphanedEdges = beforeEdgeCount - this.doc.edges.length\n\n // Re-stamp with clean checksum after quarantine\n if (report.quarantined.length > 0 || report.orphanedEdges > 0) {\n this.stampIntegrity()\n this.dirty = true\n } else {\n // Tampered but all entities valid — re-stamp to accept the changes\n this.stampIntegrity()\n this.dirty = true\n }\n\n return report\n }\n\n getIntegrityReport(): IntegrityReport | null {\n return this.lastIntegrityReport\n }\n\n /** Snapshot current node/edge IDs as baseline for three-way merge */\n private snapshotBaseline(): void {\n this.baselineNodeIds = new Set(this.doc.nodes.map((n) => n.id))\n this.baselineEdgeIds = new Set(this.doc.edges.map((e) => e.id))\n }\n\n getLastMergeResult(): MergeResult | null {\n return this.lastMergeResult\n }\n\n async load(filePath: string): Promise<void> {\n this.filePath = path.resolve(filePath)\n const raw = await fs.readFile(this.filePath, 'utf-8')\n const parsed = JSON.parse(raw)\n\n const result = validateUPGDocument(parsed)\n if (!result.valid) {\n const msgs = result.errors\n .map((e) => ` ${e.path}: ${e.message}`)\n .join('\\n')\n throw new Error(`Invalid UPG document:\\n${msgs}`)\n }\n\n this.doc = parsed as UPGDocument\n\n // Soft-coerce non-canonical product.stage values in-memory so\n // existing graphs (e.g. ones with `stage: \"idea\"` or `\"discovery\"`)\n // keep loading. Strict validation happens on the WRITE path\n // (`create_product` / future `update_product`); reads stay permissive.\n // The on-disk file is NOT mutated here — the canonical value is used\n // only for in-memory consumers (digest, lifecycle benchmarks, copy\n // surfaces). To persist the migration, run `migrate_properties` /\n // operator equivalent. Mirrors the v0.2.13 properties.stage → status\n // lift but applied at the load boundary so production graphs work\n // without forcing an explicit migration sweep.\n if (this.doc.product?.stage !== undefined) {\n const coercion = coerceProductStage(this.doc.product.stage)\n if (coercion.wasCoerced && coercion.canonical) {\n process.stderr.write(\n `[product-stage] Product stage ${JSON.stringify(coercion.originalValue)} is not a canonical UPGProductStage. ` +\n `Coerced in-memory to ${JSON.stringify(coercion.canonical)}. ` +\n `Run \\`migrate_properties\\` (or update the file) to persist the canonical value. ` +\n `File: ${this.filePath}\\n`,\n )\n // Mutate the in-memory shape only — leaves the on-disk JSON\n // unchanged so the original value survives until explicit migration.\n this.doc = {\n ...this.doc,\n product: { ...this.doc.product, stage: coercion.canonical },\n }\n } else if (coercion.wasUnknown) {\n process.stderr.write(\n `[product-stage] Product stage ${JSON.stringify(coercion.originalValue)} is not a canonical UPGProductStage and has no documented coercion target. ` +\n `Treating as missing in lifecycle calculations. ` +\n `Set a canonical value via \\`update_node\\` to clear this warning. ` +\n `File: ${this.filePath}\\n`,\n )\n }\n }\n\n // Verify integrity — detect external modifications and quarantine invalid entities\n this.lastIntegrityReport = this.verifyIntegrity()\n\n this.rebuildIndexes()\n this.computeHash()\n this.baselineFileHash = this.hashRawContent(raw)\n this.snapshotBaseline()\n\n // Classify any dangling edges and surface a structured report on\n // stderr instead of the bare \"n dangling edges\" line. We do NOT auto-drop\n // — the agent or operator runs `repair_dangling_edges` for that.\n this.lastDanglingReport = classifyDanglingEdges(\n this.doc.edges,\n new Set(this.doc.nodes.map((n) => n.id)),\n )\n const rendered = renderDanglingReport(this.lastDanglingReport, this.filePath)\n if (rendered) process.stderr.write(rendered + '\\n')\n\n // Schema-drift summary on load. Counts only — full per-node\n // breakdown lives in `validate_graph`. Silent when zero drift.\n this.lastDriftSummary = computeSchemaDriftSummary(this.doc)\n const driftRendered = renderDriftSummary(this.lastDriftSummary, this.filePath)\n if (driftRendered) process.stderr.write(driftRendered + '\\n')\n\n await this.startWatching()\n }\n\n /**\n * Snapshot of the dangling-edge classification computed at load time.\n * Returns null until `load()` has run.\n */\n getDanglingReport(): DanglingEdgeReport | null {\n return this.lastDanglingReport\n }\n\n /**\n * Snapshot of the schema-drift summary computed at load time.\n * Counts only — full per-node breakdown is `validate_graph`.\n * Returns null until `load()` has run.\n */\n getDriftSummary(): SchemaDriftSummary | null {\n return this.lastDriftSummary\n }\n\n /**\n * Drop edges matching the given dangling classes from the document. Used by\n * the `repair_dangling_edges` tool with `dry_run: false`. Caller is\n * responsible for choosing classes — this method does not protect\n * `expected` cross-product edges by default; pass an empty array to no-op.\n */\n dropDanglingEdges(classes: ReadonlyArray<DanglingEdgeClass>): { dropped: number; remaining: DanglingEdgeReport } {\n if (classes.length === 0) {\n return { dropped: 0, remaining: this.lastDanglingReport ?? { total: 0, by_class: { expected: 0, suspect: 0, corrupt: 0 }, edges: [] } }\n }\n const report = classifyDanglingEdges(\n this.doc.edges,\n new Set(this.doc.nodes.map((n) => n.id)),\n )\n const targetClasses = new Set(classes)\n const dropIds = new Set(\n report.edges.filter((e) => targetClasses.has(e.class)).map((e) => e.id),\n )\n if (dropIds.size === 0) {\n return { dropped: 0, remaining: report }\n }\n\n this.doc.edges = this.doc.edges.filter((e) => !dropIds.has(e.id))\n this.rebuildIndexes()\n this.dirty = true\n this.computeHash()\n\n this.lastDanglingReport = classifyDanglingEdges(\n this.doc.edges,\n new Set(this.doc.nodes.map((n) => n.id)),\n )\n return { dropped: dropIds.size, remaining: this.lastDanglingReport }\n }\n\n async save(): Promise<void> {\n if (!this.dirty) return\n\n // ── Layer 1: Read-before-write dirty check ────────────────────────────\n // Re-read the file from disk and check if another process modified it\n // since we last loaded or saved.\n let diskRaw: string\n try {\n diskRaw = await fs.readFile(this.filePath, 'utf-8')\n } catch {\n // File doesn't exist (deleted externally?) — safe to write\n diskRaw = ''\n }\n\n const diskHash = diskRaw ? this.hashRawContent(diskRaw) : ''\n\n if (diskHash && diskHash !== this.baselineFileHash) {\n // ── Layer 2: Another process modified the file — attempt merge ──────\n this.lastMergeResult = await this.mergeWithDisk(diskRaw)\n\n if (this.lastMergeResult.conflicts.length > 0) {\n // True conflicts — same node modified differently by both sessions.\n // Refuse to write. The agent must handle this.\n const conflictDesc = this.lastMergeResult.conflicts\n .map((c) => ` Node ${c.nodeId}: field \"${c.field}\" — ours: ${JSON.stringify(c.ours)}, theirs: ${JSON.stringify(c.theirs)}`)\n .join('\\n')\n throw new Error(\n `CONFLICT: The .upg file was modified by another session.\\n` +\n ` Nodes added by other session: ${this.lastMergeResult.nodesFromDisk}\\n` +\n ` Edges added by other session: ${this.lastMergeResult.edgesFromDisk}\\n` +\n ` Conflicts (same node modified differently):\\n${conflictDesc}\\n\\n` +\n `Auto-merge failed. Run the save again after resolving conflicts, or reload the file.`\n )\n }\n\n // No conflicts — merge succeeded. Rebuild indexes for the merged doc.\n this.rebuildIndexes()\n }\n\n // ── Write to disk ─────────────────────────────────────────────────────\n this.doc.exported_at = new Date().toISOString()\n if (!this.doc.source.tool) {\n this.doc.source.tool = 'upg-mcp-local'\n }\n\n // Stamp integrity checksum before serializing\n this.stampIntegrity()\n\n const output = JSON.stringify(this.doc, null, 2) + '\\n'\n const tmpPath = this.filePath + '.tmp'\n\n this.selfWriteInProgress = true\n try {\n await fs.writeFile(tmpPath, output, 'utf-8')\n await fs.rename(tmpPath, this.filePath)\n this.dirty = false\n this.computeHash()\n this.baselineFileHash = this.hashRawContent(output)\n this.snapshotBaseline()\n } finally {\n setTimeout(() => {\n this.selfWriteInProgress = false\n }, 150)\n }\n }\n\n // ── Three-Way Merge ───────────────────────────────────────────────────────\n //\n // Three states:\n // baseline = what was on disk when we loaded (or last saved)\n // disk = what's on disk now (another session wrote this)\n // ours = our in-memory state\n //\n // Strategy:\n // - Nodes/edges in disk but not in baseline → added by other session → keep\n // - Nodes/edges in ours but not in baseline → added by this session → keep\n // - Nodes/edges in baseline but not in disk → deleted by other session → accept deletion\n // - Nodes/edges in baseline but not in ours → deleted by this session → accept deletion\n // - Nodes in both disk and ours with different content → CONFLICT\n //\n private async mergeWithDisk(diskRaw: string): Promise<MergeResult> {\n let diskDoc: UPGDocument\n try {\n const parsed = JSON.parse(diskRaw)\n if (!validateUPGDocument(parsed).valid) {\n // Disk has invalid JSON — can't merge, our version wins\n return { merged: true, nodesAdded: 0, edgesAdded: 0, nodesFromDisk: 0, edgesFromDisk: 0, conflicts: [] }\n }\n diskDoc = parsed as UPGDocument\n } catch {\n return { merged: true, nodesAdded: 0, edgesAdded: 0, nodesFromDisk: 0, edgesFromDisk: 0, conflicts: [] }\n }\n\n const diskNodeMap = new Map(diskDoc.nodes.map((n) => [n.id, n]))\n const diskEdgeMap = new Map(diskDoc.edges.map((e) => [e.id, e]))\n const ourNodeMap = this.nodeMap\n const ourEdgeMap = this.edgeMap\n\n const conflicts: MergeResult['conflicts'] = []\n let nodesFromDisk = 0\n let edgesFromDisk = 0\n\n // Find nodes added by the other session (in disk, not in baseline)\n for (const [id, diskNode] of diskNodeMap) {\n if (!this.baselineNodeIds.has(id)) {\n // New node from disk — add to our doc if we don't already have it\n if (!ourNodeMap.has(id)) {\n this.doc.nodes.push(diskNode)\n nodesFromDisk++\n }\n } else if (ourNodeMap.has(id)) {\n // Node exists in all three states — check for conflicting modifications\n const ourNode = ourNodeMap.get(id)!\n // Only flag conflict if BOTH sessions modified it (neither matches baseline)\n // We compare title and status as the most likely conflict fields\n const ourModified = ourNode.title !== diskNode.title || ourNode.status !== diskNode.status\n if (ourModified) {\n // Check if disk version is different from ours\n if (ourNode.title !== diskNode.title) {\n conflicts.push({ nodeId: id, field: 'title', ours: ourNode.title, theirs: diskNode.title })\n }\n if (ourNode.status !== diskNode.status) {\n conflicts.push({ nodeId: id, field: 'status', ours: ourNode.status, theirs: diskNode.status })\n }\n }\n }\n }\n\n // Find edges added by the other session\n for (const [id, diskEdge] of diskEdgeMap) {\n if (!this.baselineEdgeIds.has(id)) {\n if (!ourEdgeMap.has(id)) {\n // Verify both endpoints exist in our merged node set before adding\n const sourceExists = ourNodeMap.has(diskEdge.source) || diskNodeMap.has(diskEdge.source)\n const targetExists = ourNodeMap.has(diskEdge.target) || diskNodeMap.has(diskEdge.target)\n if (sourceExists && targetExists) {\n this.doc.edges.push(diskEdge)\n edgesFromDisk++\n }\n }\n }\n }\n\n // Accept deletions by the other session:\n // Nodes in our baseline that are NOT on disk → other session deleted them\n for (const id of this.baselineNodeIds) {\n if (!diskNodeMap.has(id) && ourNodeMap.has(id)) {\n // Only accept deletion if WE didn't modify the node\n const ourNode = ourNodeMap.get(id)!\n // Simple heuristic: if we have changes logged for this node, keep ours\n const weModified = this.changeLog.some(\n (c) => c.id === id && c.entity === 'node' && c.action === 'update',\n )\n if (!weModified) {\n this.doc.nodes = this.doc.nodes.filter((n) => n.id !== id)\n // Also remove edges connected to this node\n this.doc.edges = this.doc.edges.filter(\n (e) => e.source !== id && e.target !== id,\n )\n }\n }\n }\n\n return {\n merged: conflicts.length === 0,\n nodesAdded: nodesFromDisk,\n edgesAdded: edgesFromDisk,\n nodesFromDisk,\n edgesFromDisk,\n conflicts,\n }\n }\n\n async flush(): Promise<void> {\n if (this.saveTimer) {\n clearTimeout(this.saveTimer)\n this.saveTimer = null\n }\n await this.save()\n }\n\n /**\n * Mark the store as dirty so the next `flush()` or `save()` writes to disk.\n * Use this when an external caller mutates the document object directly (e.g.\n * the cross-edge migration which modifies `doc.edges` in-place via\n * `UPGPortfolioStore.migrateCrossEdgesFromDoc`).\n */\n markDirty(): void {\n this.dirty = true\n }\n\n private scheduleSave(): void {\n this.dirty = true\n if (this.saveTimer) clearTimeout(this.saveTimer)\n this.saveTimer = setTimeout(() => this.save(), 300)\n }\n\n // ── File Watching ────────────────────────────────────────────────────────\n\n private async startWatching(): Promise<void> {\n if (this.watcher) return\n // Lazy-load chokidar so the pure `@unified-product-graph/sdk/logic`\n // entry never pulls file-watching into consumers that don't read files\n // (e.g. the Postgres-backed cloud server).\n const { watch } = await import('chokidar')\n this.watcher = watch(this.filePath, {\n persistent: false,\n awaitWriteFinish: { stabilityThreshold: 200, pollInterval: 50 },\n })\n this.watcher.on('change', async () => {\n if (this.selfWriteInProgress) return\n try {\n const raw = await fs.readFile(this.filePath, 'utf-8')\n const parsed = JSON.parse(raw)\n if (!validateUPGDocument(parsed).valid) return\n\n if (this.dirty) {\n // We have unsaved changes AND the file changed externally.\n // Attempt to merge instead of silently discarding our work.\n const result = await this.mergeWithDisk(raw)\n this.lastMergeResult = result\n if (result.conflicts.length === 0 && (result.nodesFromDisk > 0 || result.edgesFromDisk > 0)) {\n // Merge succeeded — rebuild indexes, keep our dirty flag so we save the merged state\n this.rebuildIndexes()\n this.computeHash()\n this.baselineFileHash = this.hashRawContent(raw)\n // Don't clear dirty — we still need to save our changes + the merged entities\n }\n // If conflicts, keep our state as-is — the next save() will detect and report\n } else {\n // No unsaved changes — safe to reload from disk\n this.doc = parsed as UPGDocument\n this.rebuildIndexes()\n this.computeHash()\n this.baselineFileHash = this.hashRawContent(raw)\n this.snapshotBaseline()\n this.dirty = false\n }\n } catch {\n // External write produced invalid JSON — ignore, keep current state\n }\n })\n }\n\n stopWatching(): void {\n this.watcher?.close()\n this.watcher = null\n }\n\n // ── Index Management ─────────────────────────────────────────────────────\n\n private rebuildIndexes(): void {\n this.nodeMap.clear()\n this.edgeMap.clear()\n this.edgesByNode.clear()\n\n for (const node of this.doc.nodes) {\n this.nodeMap.set(node.id, node)\n }\n for (const edge of this.doc.edges) {\n this.edgeMap.set(edge.id, edge)\n this.indexEdgeForNode(edge)\n }\n }\n\n private indexEdgeForNode(edge: UPGEdge): void {\n for (const nodeId of [edge.source, edge.target]) {\n let set = this.edgesByNode.get(nodeId)\n if (!set) {\n set = new Set()\n this.edgesByNode.set(nodeId, set)\n }\n set.add(edge.id)\n }\n }\n\n private unindexEdgeForNode(edge: UPGEdge): void {\n this.edgesByNode.get(edge.source)?.delete(edge.id)\n this.edgesByNode.get(edge.target)?.delete(edge.id)\n }\n\n // ── Hash ─────────────────────────────────────────────────────────────────\n\n private computeHash(): void {\n const content = JSON.stringify({\n nodes: this.doc.nodes.length,\n edges: this.doc.edges.length,\n nodeIds: this.doc.nodes.map((n) => n.id).sort(),\n edgeIds: this.doc.edges.map((e) => e.id).sort(),\n lastMod: this.doc.exported_at,\n })\n this.contentHash = createHash('sha256').update(content).digest('hex').slice(0, 16)\n }\n\n getContentHash(): string {\n return this.contentHash\n }\n\n // ── Reads ────────────────────────────────────────────────────────────────\n\n getFilePath(): string {\n return this.filePath\n }\n\n getDocument(): UPGDocument {\n return this.doc\n }\n\n getProduct() {\n return this.doc.product\n }\n\n getNode(id: string): UPGBaseNode | undefined {\n return this.nodeMap.get(id)\n }\n\n getEdge(id: string): UPGEdge | undefined {\n return this.edgeMap.get(id)\n }\n\n getAllNodes(): UPGBaseNode[] {\n return this.doc.nodes\n }\n\n getAllEdges(): UPGEdge[] {\n return this.doc.edges\n }\n\n getEdgesForNode(nodeId: string): UPGEdge[] {\n const edgeIds = this.edgesByNode.get(nodeId)\n if (!edgeIds) return []\n return [...edgeIds]\n .map((id) => this.edgeMap.get(id)!)\n .filter(Boolean)\n }\n\n // ── Change Log ──────────────────────────────────────────────────────────\n\n private logChange(\n action: ChangeEntry['action'],\n entity: ChangeEntry['entity'],\n id: string,\n type: string,\n title?: string,\n ): void {\n this.changeLog.push({\n action,\n entity,\n id,\n type,\n title,\n timestamp: new Date().toISOString(),\n })\n }\n\n getChanges(since?: string): ChangeEntry[] {\n if (!since) return [...this.changeLog]\n return this.changeLog.filter((c) => c.timestamp >= since)\n }\n\n // ── Writes ───────────────────────────────────────────────────────────────\n\n addNode(node: UPGBaseNode): void {\n this.doc.nodes.push(node)\n this.nodeMap.set(node.id, node)\n this.logChange('create', 'node', node.id, node.type, node.title)\n this.scheduleSave()\n }\n\n updateNode(id: string, patch: Partial<UPGBaseNode>): UPGBaseNode {\n const node = this.nodeMap.get(id)\n if (!node) throw new Error(`Node not found: ${id}`)\n\n // Shallow merge top-level fields\n if (patch.type !== undefined) node.type = patch.type as UPGEntityType\n if (patch.title !== undefined) node.title = patch.title\n if (patch.description !== undefined) node.description = patch.description\n if (patch.tags !== undefined) node.tags = patch.tags\n if (patch.status !== undefined) node.status = patch.status\n\n // Slug change → rotate the old slug into aliases[]. Aliases\n // patched directly by the caller win — they replace the field outright.\n if (patch.slug !== undefined && patch.slug !== node.slug) {\n rotateSlug(node, patch.slug)\n }\n if (patch.aliases !== undefined) node.aliases = patch.aliases\n\n // Deep merge properties\n if (patch.properties) {\n node.properties = { ...(node.properties ?? {}), ...patch.properties }\n }\n\n this.logChange('update', 'node', node.id, node.type, node.title)\n this.scheduleSave()\n return node\n }\n\n removeNode(id: string): { node: UPGBaseNode; removedEdgeIds: string[] } {\n const node = this.nodeMap.get(id)\n if (!node) throw new Error(`Node not found: ${id}`)\n\n // Copy edge IDs before mutation — unindexEdgeForNode modifies the source Set\n const edgeIds = new Set(this.edgesByNode.get(id) ?? [])\n const removedEdgeIds: string[] = []\n for (const edgeId of edgeIds) {\n const edge = this.edgeMap.get(edgeId)\n if (edge) {\n this.unindexEdgeForNode(edge)\n this.edgeMap.delete(edgeId)\n removedEdgeIds.push(edgeId)\n }\n }\n this.doc.edges = this.doc.edges.filter((e) => !edgeIds.has(e.id))\n this.edgesByNode.delete(id)\n\n // Remove the node\n this.doc.nodes = this.doc.nodes.filter((n) => n.id !== id)\n this.nodeMap.delete(id)\n\n this.logChange('delete', 'node', node.id, node.type, node.title)\n for (const eid of removedEdgeIds) {\n this.logChange('delete', 'edge', eid, 'cascade', undefined)\n }\n this.scheduleSave()\n return { node, removedEdgeIds }\n }\n\n addEdge(edge: UPGEdge, skipValidation = false): void {\n if (!skipValidation) {\n if (!this.nodeMap.has(edge.source))\n throw new Error(`Source node not found: ${edge.source}`)\n if (!this.nodeMap.has(edge.target))\n throw new Error(`Target node not found: ${edge.target}`)\n }\n\n this.doc.edges.push(edge)\n this.edgeMap.set(edge.id, edge)\n this.indexEdgeForNode(edge)\n this.logChange('create', 'edge', edge.id, edge.type, undefined)\n this.scheduleSave()\n }\n\n removeEdge(id: string): UPGEdge {\n const edge = this.edgeMap.get(id)\n if (!edge) throw new Error(`Edge not found: ${id}`)\n\n this.unindexEdgeForNode(edge)\n this.edgeMap.delete(id)\n this.doc.edges = this.doc.edges.filter((e) => e.id !== id)\n\n this.logChange('delete', 'edge', edge.id, edge.type, undefined)\n this.scheduleSave()\n return edge\n }\n\n migrateType(\n fromType: string,\n toType: string,\n defaults?: Record<string, unknown>,\n ): {\n migratedNodes: number\n edgeRenames: Array<{ id: string; from: string; to: string; flipped: boolean }>\n edgeDrops: Array<{ id: string; from: string }>\n } {\n let migratedNodes = 0\n\n // Migrate nodes first — endpoint guards in UPG_EDGE_MIGRATIONS check\n // post-migration node types per the runtime migration contract.\n for (const node of this.doc.nodes) {\n if (node.type === fromType) {\n node.type = toType as UPGEntityType\n // Merge defaults — existing values take precedence\n if (defaults && Object.keys(defaults).length > 0) {\n node.properties = { ...defaults, ...(node.properties ?? {}) }\n }\n migratedNodes++\n }\n }\n\n // Edge migration: catalog-aware via UPG_EDGE_MIGRATIONS,\n // replacing the legacy substring substitution. Renames retarget edges,\n // drops remove them, flips swap source/target. Edges whose type has no\n // corresponding rule are left alone — caller can detect unmapped legacy\n // keys by comparing edge types against UPG_EDGE_CATALOG keys post-call.\n const edgeResult = this.applyEdgeMigrations('0.0.0', UPG_VERSION)\n\n this.scheduleSave()\n return {\n migratedNodes,\n edgeRenames: edgeResult.renamed,\n edgeDrops: edgeResult.dropped,\n }\n }\n\n /**\n * Exact-match rename of every edge whose `type === from` to `to`. Optionally\n * flips `source`/`target` for each affected edge. The catalog is intentionally\n * NOT consulted here — this is the low-level primitive backing\n * `rename_edge_type`. Catalog awareness lives in the wrappers\n * tracked separately.\n *\n * Returns the IDs of every edge that was actually mutated. The internal\n * `edgesByNode` index is keyed by node id, so a flip does not require\n * re-indexing — both endpoints are already tracked for the same edge id.\n */\n renameEdgeType(\n from: string,\n to: string,\n flip = false,\n ): { renamed: number; ids: string[] } {\n const ids: string[] = []\n for (const edge of this.doc.edges) {\n if (edge.type !== from) continue\n edge.type = to as UPGEdgeType\n if (flip) {\n const oldSource = edge.source\n edge.source = edge.target\n edge.target = oldSource\n }\n this.logChange('update', 'edge', edge.id, edge.type, undefined)\n ids.push(edge.id)\n }\n if (ids.length > 0) this.scheduleSave()\n return { renamed: ids.length, ids }\n }\n\n /**\n * Apply every applicable rule from `UPG_EDGE_MIGRATIONS` (the v0.2.4\n * canonical edge registry) to the loaded graph.\n * Renames retarget the edge type; flipped renames swap source/target;\n * dropped edges are removed entirely. Endpoint guards in the migration\n * rules check post-migration node types — so callers should run any\n * needed `migrateType` / `applySplit` pass on nodes BEFORE calling this.\n *\n * Wave 3 of the MCP edge-primitives cascade.\n */\n applyEdgeMigrations(\n fromVersion: string,\n toVersion: string,\n ): {\n renamed: Array<{ id: string; from: string; to: string; flipped: boolean }>\n dropped: Array<{ id: string; from: string }>\n } {\n const renamed: Array<{ id: string; from: string; to: string; flipped: boolean }> = []\n const dropped: Array<{ id: string; from: string }> = []\n // Snapshot edge IDs first — drops mutate the underlying array mid-walk.\n const edgeIds = this.doc.edges.map((e) => e.id)\n for (const id of edgeIds) {\n const edge = this.edgeMap.get(id)\n if (!edge) continue\n const sourceNode = this.nodeMap.get(edge.source)\n const targetNode = this.nodeMap.get(edge.target)\n const result = migrateEdge(edge, fromVersion, toVersion, {\n sourceType: sourceNode?.type,\n targetType: targetNode?.type,\n })\n if (result === null) {\n dropped.push({ id, from: edge.type })\n this.removeEdge(id)\n continue\n }\n if (result === edge) continue\n const oldType = edge.type\n const flipped = result.source !== edge.source\n edge.type = result.type as UPGEdgeType\n if (flipped) {\n edge.source = result.source as string\n edge.target = result.target as string\n }\n this.logChange('update', 'edge', edge.id, edge.type, undefined)\n renamed.push({ id: edge.id, from: oldType, to: edge.type, flipped })\n }\n if (renamed.length > 0 || dropped.length > 0) this.scheduleSave()\n return { renamed, dropped }\n }\n\n applyPropertyMigrations(\n fromVersion: string,\n toVersion: string,\n ): {\n top_level_renames: Array<{ id: string; from: string; to: string; value_changed: boolean }>\n lifted_properties: Array<{ id: string; from_property: string; to: string; value_changed: boolean }>\n dropped_props: Array<{ id: string; key: string }>\n dropped_self_referential: Array<{ id: string; field: string }>\n } {\n const top_level_renames: Array<{ id: string; from: string; to: string; value_changed: boolean }> = []\n const lifted_properties: Array<{ id: string; from_property: string; to: string; value_changed: boolean }> = []\n const dropped_props: Array<{ id: string; key: string }> = []\n const dropped_self_referential: Array<{ id: string; field: string }> = []\n let mutatedAny = false\n\n for (let i = 0; i < this.doc.nodes.length; i++) {\n const original = this.doc.nodes[i]\n const { node: migrated, changes } = migrateNodeProperties(\n original as unknown as Record<string, unknown> & { id?: string; type: string; properties?: Record<string, unknown> },\n fromVersion,\n toVersion,\n )\n if (changes.length === 0) continue\n for (const change of changes as UPGPropertyMigrationChange[]) {\n switch (change.kind) {\n case 'dropped': dropped_props.push({ id: original.id, key: change.key }); break\n case 'renamed_top_level': top_level_renames.push({ id: original.id, from: change.from, to: change.to, value_changed: change.value_changed }); break\n case 'lifted_to_top_level': lifted_properties.push({ id: original.id, from_property: change.from_property, to: change.to, value_changed: change.value_changed }); break\n case 'self_ref_dropped': dropped_self_referential.push({ id: original.id, field: change.field }); break\n }\n }\n const migratedNode = migrated as unknown as UPGBaseNode\n this.doc.nodes[i] = migratedNode\n this.nodeMap.set(migratedNode.id, migratedNode)\n this.logChange('update', 'node', migratedNode.id, migratedNode.type, undefined)\n mutatedAny = true\n }\n\n if (mutatedAny) this.scheduleSave()\n return { top_level_renames, lifted_properties, dropped_props, dropped_self_referential }\n }\n}\n\n// ─── UPGPortfolioStore ──────────────────────────────────────────────\n//\n// Manages a single `.portfolio.upg` file that holds the portfolio document —\n// the canonical home for cross-product edges. A portfolio document lives\n// alongside product `.upg` files in `.upg/` (e.g. `.upg/portfolio.upg`).\n//\n// Design choice: sibling class rather than extending UPGFileStore.\n// Rationale: portfolio documents have fundamentally different structure\n// (multiple products + cross_edges vs single-product nodes/edges). Merging\n// the two shapes into one class would require extensive conditional logic.\n// A dedicated class keeps each concern clean and independently testable.\n//\n// The store keeps the portfolio doc in memory and flushes on mutation.\n// It does NOT watch the file — portfolio files are expected to be written\n// only by this server, so watcher overhead is unnecessary.\n\nexport interface PortfolioLoadResult {\n /** Number of cross-product edges in the portfolio */\n cross_edge_count: number\n /** Number of products listed in the portfolio */\n product_count: number\n /** Path to the loaded portfolio file */\n file_path: string\n}\n\nexport interface CrossEdgeMigrationResult {\n /** Cross-edges successfully migrated to portfolio format */\n migrated: Array<{\n id: string\n source: string\n target: string\n type: string\n source_product_id: string\n }>\n /** IDs of edges that could not be migrated (missing product context) */\n skipped: Array<{ id: string; reason: string }>\n /** Whether this was a dry run (no writes performed) */\n dry_run: boolean\n}\n\nexport class UPGPortfolioStore {\n private doc: UPGPortfolioDocument | null = null\n private filePath: string | null = null\n private dirty = false\n private saveTimer: ReturnType<typeof setTimeout> | null = null\n\n // ── Load / Save ─────────────────────────────────────────────────────────────\n\n /**\n * Load an existing portfolio document from disk, or initialise an empty one\n * at the given path if it does not exist.\n *\n * @param filePath Absolute path to the `.portfolio.upg` file.\n * @param orgTitle Organisation title for newly created portfolios.\n */\n async loadOrInit(filePath: string, orgTitle = 'Portfolio'): Promise<PortfolioLoadResult> {\n this.filePath = path.resolve(filePath)\n\n let raw: string\n try {\n raw = await fs.readFile(this.filePath, 'utf-8')\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err\n // File does not exist — create a minimal valid portfolio document\n this.doc = this.makeEmptyPortfolio(orgTitle)\n this.dirty = true\n await this.flush()\n return {\n cross_edge_count: 0,\n product_count: 0,\n file_path: this.filePath,\n }\n }\n\n const parsed = JSON.parse(raw) as UPGPortfolioDocument\n if (parsed.type !== 'portfolio') {\n throw new Error(\n `Expected a portfolio document (type: \"portfolio\") at ${this.filePath}, ` +\n `but found type: \"${(parsed as { type?: string }).type ?? 'unknown'}\"`,\n )\n }\n this.doc = parsed\n return {\n cross_edge_count: this.doc.cross_edges.length,\n product_count: this.doc.products.length,\n file_path: this.filePath,\n }\n }\n\n /** Create a minimal valid UPGPortfolioDocument. */\n private makeEmptyPortfolio(orgTitle: string): UPGPortfolioDocument {\n return {\n upg_version: UPG_VERSION,\n type: 'portfolio',\n exported_at: new Date().toISOString(),\n source: { tool: 'upg-mcp-local' },\n organization: {\n id: `org_${createHash('sha256').update(orgTitle).digest('hex').slice(0, 8)}`,\n title: orgTitle,\n },\n product_areas: [],\n portfolios: [],\n products: [],\n cross_edges: [],\n }\n }\n\n /** Return the resolved portfolio file path, or null if not loaded. */\n getFilePath(): string | null {\n return this.filePath\n }\n\n /** Return the loaded portfolio document, or null if not loaded. */\n getDocument(): UPGPortfolioDocument | null {\n return this.doc\n }\n\n /** True when a portfolio document is loaded and ready. */\n isLoaded(): boolean {\n return this.doc !== null && this.filePath !== null\n }\n\n /** Flush pending writes to disk. No-op if not dirty. */\n async flush(): Promise<void> {\n if (this.saveTimer) {\n clearTimeout(this.saveTimer)\n this.saveTimer = null\n }\n await this.writeToDisk()\n }\n\n /**\n * Mark the portfolio store as dirty so the next `flush()` writes to disk.\n * Use this when an external caller mutates the document object directly via\n * `getDocument()` rather than going through `addCrossEdge` /\n * `removeCrossEdge`. Mirrors `UPGFileStore.markDirty()`.\n */\n markDirty(): void {\n this.dirty = true\n }\n\n private scheduleSave(): void {\n this.dirty = true\n if (this.saveTimer) clearTimeout(this.saveTimer)\n this.saveTimer = setTimeout(() => void this.writeToDisk(), 300)\n }\n\n private async writeToDisk(): Promise<void> {\n if (!this.dirty || !this.doc || !this.filePath) return\n this.doc.exported_at = new Date().toISOString()\n const output = JSON.stringify(this.doc, null, 2) + '\\n'\n const tmpPath = this.filePath + '.tmp'\n await fs.writeFile(tmpPath, output, 'utf-8')\n await fs.rename(tmpPath, this.filePath)\n this.dirty = false\n }\n\n // ── Cross-edge writes ────────────────────────────────────────────────────────\n\n /**\n * Add a cross-product edge to the portfolio document. Both `source` and\n * `target` must be qualified IDs (`{product_id}/{node_id}`).\n */\n addCrossEdge(edge: UPGCrossEdge): void {\n if (!this.doc) throw new Error('Portfolio document not loaded. Call loadOrInit() first.')\n if (!edge.id) throw new Error('Cross-edge must have an id')\n if (!edge.source.includes('/')) {\n throw new Error(\n `Cross-edge source must be a qualified ID ({product_id}/{node_id}), got: \"${edge.source}\"`,\n )\n }\n if (!edge.target.includes('/')) {\n throw new Error(\n `Cross-edge target must be a qualified ID ({product_id}/{node_id}), got: \"${edge.target}\"`,\n )\n }\n if (!(UPG_CROSS_EDGE_TYPES as readonly string[]).includes(edge.type)) {\n throw new Error(\n `Invalid cross-product edge type: \"${edge.type}\". ` +\n `Valid types: ${UPG_CROSS_EDGE_TYPES.join(', ')}`,\n )\n }\n this.doc.cross_edges.push(edge)\n this.scheduleSave()\n }\n\n /**\n * Remove a cross-product edge by ID.\n * @returns The removed edge, or null if not found.\n */\n removeCrossEdge(edgeId: string): UPGCrossEdge | null {\n if (!this.doc) throw new Error('Portfolio document not loaded.')\n const idx = this.doc.cross_edges.findIndex((e) => e.id === edgeId)\n if (idx === -1) return null\n const [removed] = this.doc.cross_edges.splice(idx, 1)\n this.scheduleSave()\n return removed\n }\n\n /** Return all cross-product edges. */\n getAllCrossEdges(): UPGCrossEdge[] {\n return this.doc?.cross_edges ?? []\n }\n\n /** Find a cross-product edge by ID. */\n getCrossEdge(id: string): UPGCrossEdge | undefined {\n return this.doc?.cross_edges.find((e) => e.id === id)\n }\n\n // ── Migration: inline → portfolio ─────────────────────────\n //\n // Scans `sourceDoc` for edges whose type is in UPG_CROSS_EDGE_TYPES, converts\n // them to UPGCrossEdge objects with qualified IDs, and either:\n // - dry_run: true → reports what would change without writing anything\n // - dry_run: false → writes them to this portfolio document AND removes them\n // from sourceDoc.edges (sourceDoc must be saved separately)\n //\n // `sourceProductId` is the product ID that owns the sourceDoc. When the target\n // node is NOT in sourceDoc, the caller must supply `targetProductId`; without\n // it, those edges are skipped (reported in `skipped`).\n\n /**\n * Migrate inline cross-product edges from a product document into this\n * portfolio document.\n *\n * **Does not flush** — caller is responsible for calling `.flush()` after\n * inspecting the result and, for non-dry-run, also saving `sourceDoc`.\n */\n migrateCrossEdgesFromDoc(\n sourceDoc: UPGDocument,\n sourceProductId: string,\n targetProductId: string | null,\n dryRun: boolean,\n ): CrossEdgeMigrationResult {\n if (!this.doc && !dryRun) {\n throw new Error('Portfolio document not loaded. Call loadOrInit() first.')\n }\n\n const crossEdgeTypeSet = new Set<string>(UPG_CROSS_EDGE_TYPES)\n const sourceNodeIds = new Set(sourceDoc.nodes.map((n) => n.id))\n\n const migrated: CrossEdgeMigrationResult['migrated'] = []\n const skipped: CrossEdgeMigrationResult['skipped'] = []\n const edgeIdsToRemove: string[] = []\n\n for (const edge of sourceDoc.edges) {\n if (!crossEdgeTypeSet.has(edge.type)) continue\n\n // source is always in the sourceDoc (it's a product node there)\n const qualifiedSource = `${sourceProductId}/${edge.source}`\n\n // target: if in sourceDoc, use same product; else use provided targetProductId\n let qualifiedTarget: string\n if (sourceNodeIds.has(edge.target)) {\n // target is also in the same product — unusual but structurally valid\n qualifiedTarget = `${sourceProductId}/${edge.target}`\n } else if (targetProductId) {\n qualifiedTarget = `${targetProductId}/${edge.target}`\n } else {\n skipped.push({\n id: edge.id,\n reason:\n `Target node \"${edge.target}\" is not in the source product and no ` +\n `targetProductId was provided — cannot determine qualified target ID`,\n })\n continue\n }\n\n migrated.push({\n id: edge.id,\n source: qualifiedSource,\n target: qualifiedTarget,\n type: edge.type,\n source_product_id: sourceProductId,\n })\n edgeIdsToRemove.push(edge.id)\n }\n\n if (!dryRun && migrated.length > 0) {\n if (!this.doc) throw new Error('Portfolio document not loaded.')\n\n // Write migrated edges to the portfolio\n for (const m of migrated) {\n const crossEdge: UPGCrossEdge = {\n id: m.id,\n source: m.source,\n target: m.target,\n type: m.type as UPGCrossEdgeType,\n source_product_id: sourceProductId,\n ...(m.target.split('/')[0] !== sourceProductId\n ? { target_product_id: m.target.split('/')[0] }\n : {}),\n }\n this.doc.cross_edges.push(crossEdge)\n }\n this.dirty = true\n\n // Remove migrated edges from the source document in-place\n const removeSet = new Set(edgeIdsToRemove)\n sourceDoc.edges = sourceDoc.edges.filter((e) => !removeSet.has(e.id))\n }\n\n return { migrated, skipped, dry_run: dryRun }\n }\n}\n\n// Re-export types so callers can import from store without going to @unified-product-graph/core\nexport type { UPGPortfolioDocument, UPGCrossEdge }\n","/**\n * Shared tool logic. Pure functions that operate on a UPGFileStore.\n *\n * Used by both the MCP server (server.ts) and the CLI (@unified-product-graph/mcp).\n * Extract here, import everywhere.\n */\n\nimport type { UPGFileStore } from '../store.js'\nimport type { UPGBaseNode, UPGEdge, UPGEdgeType, UPGEntityType, UPGProductStage } from '@unified-product-graph/core'\nimport {\n coerceProductStage,\n collectSlugsForType,\n generateSlug,\n getLifecycleForType,\n getReplacementType,\n resolveSlugCollision,\n UPG_EDGE_CATALOG,\n} from '@unified-product-graph/core'\n\n/**\n * Resolve a (possibly deprecated) entity type to its canonical replacement.\n * Returns the input unchanged if it is already canonical or unknown.\n */\nfunction canonicalType(name: string): string {\n return getReplacementType(name) ?? name\n}\n\n// ── Entity type validation ────────────────────────────────────────\n//\n// `resolveEntityType` + `UnknownEntityTypeError` live in\n// `@unified-product-graph/core` so cloud + local + downstream HTTP consumers\n// all run the same alias path (`get_entity_schema('jtbd') → job`) AND share a\n// single `UnknownEntityTypeError` class — an `instanceof` thrown by the SDK\n// stays true when caught in a server. We import + re-export here so existing\n// consumers (tools/nodes.ts, tools/schema.ts, __tests__/tools.test.ts) keep\n// their import path AND the symbols stay locally bound for use further down\n// the file (createNode + migrateNodeType).\nimport {\n resolveEntityType,\n UnknownEntityTypeError,\n type EntityTypeResolution,\n} from '@unified-product-graph/core'\nexport { resolveEntityType, UnknownEntityTypeError, type EntityTypeResolution }\nimport { nodeId, edgeId } from './id.js'\nimport { inferEdgeType, inferEdgeTypeWithTier } from './edge-inference.js'\nimport { validateEdgeTypePair } from './edge-pair-validator.js'\n\n// ── Lifecycle helpers ─────────────────────────────────────────────────────────\n\n/**\n * Validate a status value against the lifecycle phases for an entity type.\n * Returns a warning string if the status is invalid, or undefined if valid / no lifecycle.\n */\nexport function validateStatusAgainstLifecycle(\n entityType: string,\n status: string,\n): string | undefined {\n const lifecycle = getLifecycleForType(entityType)\n if (!lifecycle) return undefined\n const validPhases = lifecycle.phases.map((p) => p.id)\n if (!validPhases.includes(status)) {\n return `Status \"${status}\" is not a valid phase for type \"${entityType}\". Valid phases: [${validPhases.join(', ')}]`\n }\n return undefined\n}\n\n/**\n * Returns the initial_phase for an entity type, or undefined if the type has no lifecycle.\n */\nexport function getDefaultStatus(entityType: string): string | undefined {\n const lifecycle = getLifecycleForType(entityType)\n return lifecycle?.initial_phase\n}\n\n// ── Tag normalisation (shared) ──────────────────────────────────────────────\n\n/**\n * Backfill `slug` on a freshly-built node before it's added to the store.\n * Picks the auto-generated slug from `title`, resolved against\n * every existing slug + alias of the same `type` in the same product.\n *\n * No-op if the node already carries an explicit slug.\n */\nexport function autoFillSlug(node: UPGBaseNode, store: UPGFileStore): void {\n if (node.slug) return\n if (!node.title) return\n const existing = collectSlugsForType(store.getAllNodes(), node.type)\n const base = generateSlug(node.title)\n node.slug = resolveSlugCollision(base, existing)\n}\n\nexport function normalizeTags(tags: unknown): string[] | undefined {\n if (!tags) return undefined\n if (Array.isArray(tags)) return tags\n if (typeof tags === 'string') {\n try {\n const parsed = JSON.parse(tags)\n if (Array.isArray(parsed)) return parsed\n } catch { /* not valid JSON */ }\n return [tags] // treat as single-tag string\n }\n return undefined\n}\n\n// ── Business area → entity type mapping ──────────────────────────────────────\n\nexport const BUSINESS_AREAS: Record<string, { emoji: string; types: string[] }> = {\n identity: { emoji: '🎯', types: ['product', 'vision', 'mission'] },\n understanding: { emoji: '👤', types: ['persona', 'job', 'need', 'research_study', 'insight'] },\n // Surface canonical post-split types. `hypothesis` is canonical\n // (re-promoted in v0.4.0 from hypothesis_claim). `hypothesis_evidence`\n // is deprecated — use `evidence` + hypothesis_has_evidence edge instead.\n // `experiment` remains canonical alongside `experiment_plan` / `experiment_run`.\n discovery: { emoji: '💡', types: ['opportunity', 'solution', 'competitor', 'hypothesis', 'experiment_plan', 'experiment_run', 'learning'] },\n // `validation` overlaps `discovery` semantically but tracks the stage's\n // characteristic artefacts (hypothesis tested, evidence captured, experiment\n // run-throughs). Surfaced as its own region so STAGE_COVERAGE_TARGETS can\n // gate on it at `validation` stage without re-graveling `discovery`.\n validation: { emoji: '🧪', types: ['hypothesis', 'experiment_plan', 'experiment_run', 'evidence', 'learning'] },\n reaching: { emoji: '📣', types: ['ideal_customer_profile', 'positioning', 'messaging', 'acquisition_channel', 'content_strategy'] },\n converting: { emoji: '💰', types: ['value_proposition', 'pricing_tier', 'funnel', 'funnel_step'] },\n // (since v0.4.0) story_task collapsed into task; building area uses task for story work.\n building: { emoji: '📦', types: ['feature', 'story_statement', 'epic', 'release', 'user_journey', 'user_flow'] },\n sustaining: { emoji: '🏦', types: ['business_model', 'revenue_stream', 'cost_structure', 'unit_economics', 'pricing_strategy'] },\n learning: { emoji: '📊', types: ['outcome', 'metric', 'objective', 'key_result', 'retrospective'] },\n // Operations is a maintenance-stage concern — incidents, postmortems, error\n // budgets only become coverage-relevant once a product is in `maintenance`.\n // Surfaced informationally at earlier stages.\n operations: { emoji: '🚨', types: ['incident', 'postmortem', 'error_budget'] },\n}\n\n// ── Stage-aware coverage targets ──────────────────────────────────────────────\n//\n// Per Finding 9 in `2026-05-20-spec-as-observed-v2.md`. The eight (plus\n// `operations`) business-area regions in `BUSINESS_AREAS` are the spec's\n// implicit completeness model, but a concept-stage product should not be\n// graded against a launched product's checklist. Each `UPGProductStage` maps\n// to the list of region keys that are *counted toward completeness*. Regions\n// outside this list are still surfaced in `coverage` (with\n// `counted_toward_stage: false`) so the caller can see them informationally\n// without dragging the headline `overall_pct` down.\n//\n// Stages widen the counted set as a product matures:\n// concept → validation → build → beta → launch → growth → mature →\n// maintenance (adds Operations) → sunset (narrowest, only Identity +\n// Learning — the retrospective).\n//\n// Keys MUST exist in `BUSINESS_AREAS` above. The type assertion at the\n// bottom of this module enforces this at compile time.\nexport const STAGE_COVERAGE_TARGETS: Record<UPGProductStage, string[]> = {\n concept: ['identity', 'understanding', 'discovery'],\n validation: ['identity', 'understanding', 'discovery', 'validation'],\n build: ['identity', 'understanding', 'discovery', 'validation', 'building'],\n beta: ['identity', 'understanding', 'discovery', 'validation', 'building', 'reaching', 'converting'],\n launch: ['identity', 'understanding', 'discovery', 'validation', 'building', 'reaching', 'converting', 'sustaining'],\n growth: ['identity', 'understanding', 'discovery', 'validation', 'building', 'reaching', 'converting', 'sustaining', 'learning'],\n mature: ['identity', 'understanding', 'discovery', 'validation', 'building', 'reaching', 'converting', 'sustaining', 'learning'],\n maintenance: ['identity', 'understanding', 'discovery', 'validation', 'building', 'reaching', 'converting', 'sustaining', 'learning', 'operations'],\n // Sunset products are winding down — Identity stays (the product still has\n // a name + vision) and Learning becomes the priority (capture the\n // retrospective + reasons for sunsetting). Everything else is informational.\n sunset: ['identity', 'learning'],\n}\n\n/**\n * Resolve the canonical UPGProductStage for digest coverage. Returns\n * `'concept'` as the default when stage is missing or unrecognised — concept\n * is the narrowest expectation surface, so we under-grade rather than\n * over-grade. Legacy `\"idea\"` and friends route through `coerceProductStage`\n * (which lives in `@unified-product-graph/core`); the inline `\"idea\" →\n * \"concept\"` fallback below is defensive in case the core helper is ever\n * unavailable.\n */\nexport function resolveCoverageStage(rawStage: unknown): UPGProductStage {\n if (typeof rawStage === 'string') {\n const coerced = coerceProductStage(rawStage)\n if (coerced.canonical) return coerced.canonical\n // Defensive: if `coerceProductStage` ever stops returning canonical for\n // the most common legacy alias, keep the digest sane.\n if (rawStage.toLowerCase() === 'idea') return 'concept'\n }\n return 'concept'\n}\n\nexport const LIFECYCLE_PHASES: Record<string, string[]> = {\n strategy: ['product', 'outcome', 'metric', 'objective', 'key_result', 'vision', 'mission', 'strategic_theme', 'initiative'],\n users: ['persona', 'job', 'need', 'desired_outcome', 'job_step'],\n discovery: ['opportunity', 'solution', 'research_study', 'insight', 'competitor'],\n // (since v0.4.0) hypothesis is canonical; hypothesis_claim/hypothesis_evidence\n // are deprecated aliases. evidence replaces hypothesis_evidence in new graphs.\n validation: ['hypothesis', 'experiment_plan', 'experiment_run', 'learning', 'evidence', 'experiment', 'hypothesis_claim', 'hypothesis_evidence'],\n // (since v0.4.0) story_task collapsed into task (deprecated alias).\n execution: ['feature', 'epic', 'story_statement', 'release', 'task', 'bug', 'user_story', 'story_task'],\n}\n\n// ── Chain definitions ──────────────────────────────────────────────────────\n\n// Chain definitions reference canonical types. `hypothesis` is canonical\n// (v0.4.0); legacy `hypothesis_claim` nodes fold into canonical via\n// `canonicalType()`. `experiment` nodes also resolve correctly.\nexport const CHAINS = [\n { name: 'persona → job', from: 'persona', to: 'job', edgePattern: 'job' },\n { name: 'job → need', from: 'job', to: 'need', edgePattern: 'need' },\n { name: 'opportunity → solution', from: 'opportunity', to: 'solution', edgePattern: 'solution' },\n { name: 'solution → hypothesis', from: 'solution', to: 'hypothesis', edgePattern: 'hypothesis' },\n { name: 'hypothesis → experiment_plan', from: 'hypothesis', to: 'experiment_plan', edgePattern: 'experiment_plan' },\n { name: 'experiment_run → learning', from: 'experiment_run', to: 'learning', edgePattern: 'learning' },\n { name: 'objective → key_result', from: 'objective', to: 'key_result', edgePattern: 'key_result' },\n // (v0.2.7 split 2) features specify story_statements (the design\n // artefact / promise). story_tasks are the delivery work, linked from\n // story_statement via story_task_implements_story_statement.\n { name: 'feature → story_statement', from: 'feature', to: 'story_statement', edgePattern: 'story_statement' },\n] as const\n\n// ── Type sort order (for tree rendering — group children by type) ────────────\n// Follows the natural product thinking hierarchy: who → why → what → how → measure\nconst TYPE_SORT_ORDER: string[] = [\n // Identity\n 'product', 'vision', 'mission',\n // Users\n 'persona', 'job', 'job_step', 'need', 'desired_outcome',\n // Discovery\n 'outcome', 'opportunity', 'solution', 'research_study', 'insight',\n // Validation\n 'hypothesis', 'experiment', 'learning', 'evidence',\n // Competition\n 'competitor', 'competitor_feature',\n // Strategy\n 'strategic_theme', 'initiative', 'objective', 'key_result', 'metric',\n // Reaching\n 'ideal_customer_profile', 'market_segment', 'positioning', 'messaging', 'acquisition_channel', 'content_strategy',\n // Converting\n 'value_proposition', 'pricing_tier', 'pricing_strategy', 'funnel', 'funnel_step',\n // Building\n 'feature', 'feature_area', 'epic', 'user_story', 'release', 'user_journey', 'user_flow', 'screen', 'screen_state',\n // Architecture\n 'bounded_context', 'service', 'api_endpoint', 'database_schema', 'architecture_decision',\n // Sustaining\n 'business_model', 'revenue_stream', 'cost_structure', 'unit_economics',\n // Learning\n 'retrospective',\n]\n\n/** Get sort priority for a type (lower = higher in tree). Unknown types sort last. */\nexport function typeSortPriority(type: string): number {\n const idx = TYPE_SORT_ORDER.indexOf(type)\n return idx >= 0 ? idx : 999\n}\n\n/** Sort nodes by type priority, then alphabetically by title within same type */\nexport function sortByType(nodes: UPGBaseNode[]): UPGBaseNode[] {\n return [...nodes].sort((a, b) => {\n const priorityDiff = typeSortPriority(a.type) - typeSortPriority(b.type)\n if (priorityDiff !== 0) return priorityDiff\n return a.title.localeCompare(b.title)\n })\n}\n\n// ── Graph Digest ──────────────────────────────────────────────────────────\n\nexport interface CoverageRegion {\n covered: number\n total: number\n /**\n * NEW (Finding 9 / UPG-512): True when this region is on the product\n * stage's expected-coverage list. Regions where this is `false` are\n * surfaced for awareness but excluded from `stage_summary.overall_pct`.\n */\n counted_toward_stage: boolean\n types_present: string[]\n types_missing: string[]\n}\n\nexport interface CoverageStageSummary {\n stage: UPGProductStage\n /** Number of regions counted toward this stage's completeness. */\n regions_counted: number\n /** Counted regions that are fully covered (covered === total). */\n regions_complete: number\n /** Counted regions with partial coverage (0 < covered < total). */\n regions_partial: number\n /** Whole-number percentage 0-100, averaged across counted regions only. */\n overall_pct: number\n}\n\nexport interface GraphDigest {\n product: { title: string; stage: string }\n counts: { total_nodes: number; total_edges: number; by_type: Record<string, number> }\n health: {\n orphan_count: number\n orphan_rate: number\n connectivity: number\n validation_rate: number\n user_coverage: number\n }\n chains: Record<string, number>\n /**\n * Per-region coverage map. Keys are `BUSINESS_AREAS` ids (e.g.\n * `identity`, `understanding`). The new stage-aware aggregate is at\n * `coverage.stage_summary` (typed loosely here so `Record<string,\n * CoverageRegion>` index lookups stay correct for callers).\n *\n * NOTE: callers iterating `Object.entries(coverage)` should skip the\n * `stage_summary` key — it carries a different shape.\n */\n coverage: Record<string, CoverageRegion> & { stage_summary?: CoverageStageSummary }\n lifecycle: Record<string, number>\n}\n\nexport function computeGraphDigest(store: UPGFileStore): GraphDigest {\n const nodes = store.getAllNodes()\n const edges = store.getAllEdges()\n const product = store.getProduct()\n\n // Counts by type (raw — preserves the type as stored on the node)\n const byType: Record<string, number> = {}\n for (const n of nodes) byType[n.type] = (byType[n.type] ?? 0) + 1\n\n // Counts by canonical type — legacy-typed nodes (e.g. `jtbd`) fold into\n // their canonical replacement (`job`). Source of truth for chain coverage.\n const byCanonicalType: Record<string, number> = {}\n for (const n of nodes) {\n const c = canonicalType(n.type)\n byCanonicalType[c] = (byCanonicalType[c] ?? 0) + 1\n }\n\n // Orphan detection\n const connectedNodes = new Set<string>()\n for (const e of edges) {\n connectedNodes.add(e.source)\n connectedNodes.add(e.target)\n }\n const orphanCount = nodes.filter((n) => !connectedNodes.has(n.id)).length\n\n // Health metrics — read from canonical counts so legacy types still flow through.\n // (since v0.4.0) `hypothesis` is canonical (reverted from hypothesis_claim).\n // Legacy hypothesis_claim nodes fold into canonical via canonicalType() above.\n // We aggregate plan + run for the experiment count so the headline\n // \"validation_rate\" reading matches what users count as \"experiments\".\n const hypothesisCount = byCanonicalType['hypothesis'] ?? 0\n const experimentCount =\n (byCanonicalType['experiment_plan'] ?? 0) +\n (byCanonicalType['experiment_run'] ?? 0) +\n // `experiment` is still a canonical type for back-compat reads; count it too.\n (byCanonicalType['experiment'] ?? 0)\n const personaCount = byCanonicalType['persona'] ?? 0\n\n // Chain completeness. Both parentType and edgePattern are matched against\n // canonical names — a `jtbd` node connected via a `persona_pursues_jtbd`\n // edge still counts as a `persona → job` chain, because both sides are\n // resolved to their canonical forms before comparison.\n const chainStats = (parentType: string, edgePattern: string) => {\n let withChild = 0\n const parents = nodes.filter((n) => canonicalType(n.type) === parentType)\n for (const p of parents) {\n const pEdges = store.getEdgesForNode(p.id)\n const matches = pEdges.some(\n (e) =>\n e.source === p.id &&\n (e.type.includes(edgePattern) ||\n e.type.includes(canonicalType(edgePattern)) ||\n // Match deprecated edge fragments by resolving each segment to canonical.\n e.type.split('_').some((seg) => canonicalType(seg) === edgePattern)),\n )\n if (matches) withChild++\n }\n return { with_child: withChild, total: parents.length }\n }\n\n /**\n * Like `chainStats` but ALSO counts parents covered via a registered\n * canonical bridge. Each bridge spec describes a 2-hop path where the\n * parent has an incoming edge from a bridge node, and the bridge node\n * has an outgoing edge to the child type. UPG-508 (2026-05-20).\n */\n const chainStatsWithBridge = (\n parentType: string,\n edgePattern: string,\n bridges: Array<{\n incoming_from: string\n incoming_edge_substring: string\n bridge_outgoing_edge_substring: string\n bridge_to_type: string\n }>,\n ) => {\n let withChild = 0\n const parents = nodes.filter((n) => canonicalType(n.type) === parentType)\n for (const p of parents) {\n const pEdges = store.getEdgesForNode(p.id)\n const directMatch = pEdges.some(\n (e) =>\n e.source === p.id &&\n (e.type.includes(edgePattern) ||\n e.type.includes(canonicalType(edgePattern)) ||\n e.type.split('_').some((seg) => canonicalType(seg) === edgePattern)),\n )\n if (directMatch) {\n withChild++\n continue\n }\n let bridgeMatch = false\n for (const bridge of bridges) {\n const incoming = pEdges.filter(\n (e) =>\n e.target === p.id &&\n e.type.includes(bridge.incoming_edge_substring),\n )\n for (const inE of incoming) {\n const bridgeNode = store.getNode(inE.source)\n if (!bridgeNode || canonicalType(bridgeNode.type) !== bridge.incoming_from) continue\n const bridgeOut = store.getEdgesForNode(bridgeNode.id)\n const reachesChild = bridgeOut.some((be) => {\n if (be.source !== bridgeNode.id) return false\n if (!be.type.includes(bridge.bridge_outgoing_edge_substring)) return false\n const childNode = store.getNode(be.target)\n return childNode != null && canonicalType(childNode.type) === bridge.bridge_to_type\n })\n if (reachesChild) {\n bridgeMatch = true\n break\n }\n }\n if (bridgeMatch) break\n }\n if (bridgeMatch) withChild++\n }\n return { with_child: withChild, total: parents.length }\n }\n\n const personaJob = chainStats('persona', 'job')\n // UPG-508 (2026-05-20): jobs and needs are often connected only via\n // personas — `job ← persona_pursues_job ← persona → persona_experiences_need\n // → need`. Count direct edges OR the persona-bridge path.\n const jobNeed = chainStatsWithBridge(\n 'job',\n 'need',\n [\n {\n incoming_from: 'persona',\n incoming_edge_substring: 'persona_pursues_job',\n bridge_outgoing_edge_substring: 'persona_experiences_need',\n bridge_to_type: 'need',\n },\n ],\n )\n const oppSolution = chainStats('opportunity', 'solution')\n // Hypothesis is canonical (v0.4.0). Legacy hypothesis_claim nodes\n // still match — `chainStats` runs source types through canonicalType().\n const hypExperiment = chainStats('hypothesis', 'experiment_plan')\n const expLearning = chainStats('experiment_run', 'learning')\n\n // Business area coverage — fold deprecated types into canonical via the spec\n const typeSet = new Set<string>()\n for (const t of Object.keys(byType)) {\n typeSet.add(t)\n typeSet.add(canonicalType(t))\n }\n // Stage-aware filtering (Finding 9 / UPG-512). Resolve the product's stage\n // through the legacy-alias coercion path first, then read the list of\n // regions counted toward completeness for that stage. Per-region\n // `types_present` / `types_missing` are populated for ALL regions\n // regardless of stage — only `counted_toward_stage` and the\n // `stage_summary` aggregate distinguish counted from informational.\n const rawStage = product.stage ?? (nodes.find((n) => n.type === 'product')?.properties as Record<string, unknown> | undefined)?.stage\n const resolvedStage = resolveCoverageStage(rawStage)\n const countedRegions = new Set(STAGE_COVERAGE_TARGETS[resolvedStage] ?? [])\n\n const coverage: GraphDigest['coverage'] = {}\n const countedRegionStats: Array<{ covered: number; total: number }> = []\n for (const [area, def] of Object.entries(BUSINESS_AREAS)) {\n const present = def.types.filter((t) => typeSet.has(t))\n const missing = def.types.filter((t) => !typeSet.has(t))\n const isCounted = countedRegions.has(area)\n const region: CoverageRegion = {\n covered: present.length,\n total: def.types.length,\n counted_toward_stage: isCounted,\n types_present: present,\n types_missing: missing,\n }\n coverage[area] = region\n if (isCounted) countedRegionStats.push({ covered: region.covered, total: region.total })\n }\n\n // Stage summary — overall_pct is the mean per-region coverage across only\n // the counted regions. We compute per-region pct first (so a region with\n // 1/3 contributes 33, not 1) then average, so adding a tiny new region\n // doesn't tank the headline.\n const regionsComplete = countedRegionStats.filter((s) => s.total > 0 && s.covered === s.total).length\n const regionsPartial = countedRegionStats.filter((s) => s.covered > 0 && s.covered < s.total).length\n const perRegionPcts = countedRegionStats.map((s) => (s.total === 0 ? 100 : (s.covered / s.total) * 100))\n const overallPct = perRegionPcts.length === 0\n ? 0\n : Math.round(perRegionPcts.reduce((sum, pct) => sum + pct, 0) / perRegionPcts.length)\n coverage.stage_summary = {\n stage: resolvedStage,\n regions_counted: countedRegionStats.length,\n regions_complete: regionsComplete,\n regions_partial: regionsPartial,\n overall_pct: overallPct,\n }\n\n // Lifecycle balance — count via canonical types so `hypothesis`\n // (legacy) and `hypothesis_claim` (canonical) don't double-count when both\n // appear in LIFECYCLE_PHASES.validation. We dedupe the canonical set per\n // phase and read from `byCanonicalType` so legacy nodes are surfaced\n // exactly once under the canonical bucket.\n const lifecycle: Record<string, number> = {}\n for (const [phase, types] of Object.entries(LIFECYCLE_PHASES)) {\n const canonicalSet = new Set(types.map((t) => canonicalType(t)))\n lifecycle[phase] = [...canonicalSet].reduce(\n (sum, t) => sum + (byCanonicalType[t] ?? 0),\n 0,\n )\n }\n\n return {\n product: {\n title: product.title,\n stage: (product.stage\n ?? (nodes.find((n) => n.type === 'product')?.properties as Record<string, unknown> | undefined)?.stage as string | undefined\n ?? 'unknown'),\n },\n counts: { total_nodes: nodes.length, total_edges: edges.length, by_type: byType },\n health: {\n orphan_count: orphanCount,\n orphan_rate: nodes.length > 0 ? Math.round((orphanCount / nodes.length) * 100) / 100 : 0,\n connectivity: nodes.length > 0 ? Math.round(((nodes.length - orphanCount) / nodes.length) * 100) / 100 : 0,\n validation_rate: hypothesisCount > 0 ? Math.round((experimentCount / hypothesisCount) * 100) / 100 : 0,\n user_coverage: personaCount > 0 ? Math.round((personaJob.with_child / personaCount) * 100) / 100 : 0,\n },\n chains: {\n persona_with_job: personaJob.with_child, persona_total: personaJob.total,\n job_with_need: jobNeed.with_child, job_total: jobNeed.total,\n opportunity_with_solution: oppSolution.with_child, opportunity_total: oppSolution.total,\n hypothesis_untested: hypothesisCount - hypExperiment.with_child, hypothesis_total: hypothesisCount,\n experiment_with_learning: expLearning.with_child, experiment_total: experimentCount,\n },\n coverage,\n lifecycle,\n }\n}\n\n// ── Search ──────────────────────────────────────────────────────────────────\n\nexport interface SearchResult {\n node: UPGBaseNode\n score: number\n match_field: string\n}\n\nexport function searchNodes(\n store: UPGFileStore,\n query: string,\n options?: { type?: string; fields?: string[]; limit?: number }\n): SearchResult[] {\n const q = query.toLowerCase()\n const searchFields = new Set(options?.fields ?? ['title', 'description'])\n const limit = Math.min(options?.limit ?? 20, 100)\n\n let nodes = store.getAllNodes()\n if (options?.type) nodes = nodes.filter((n) => n.type === options.type)\n\n return nodes\n .map((n) => {\n let bestScore = 0\n let matchField = ''\n\n if (searchFields.has('title') && n.title.toLowerCase().includes(q)) {\n bestScore = 3; matchField = 'title'\n }\n if (searchFields.has('tags') && normalizeTags(n.tags)?.some((t: string) => t.toLowerCase().includes(q))) {\n if (2 > bestScore) { bestScore = 2; matchField = 'tags' }\n }\n if (searchFields.has('description') && n.description?.toLowerCase().includes(q)) {\n if (1 > bestScore) { bestScore = 1; matchField = 'description' }\n }\n if (searchFields.has('properties') && n.properties) {\n const propsStr = JSON.stringify(n.properties).toLowerCase()\n if (propsStr.includes(q)) {\n if (1 > bestScore) { bestScore = 1; matchField = 'properties' }\n }\n }\n\n if (bestScore === 0) return null\n return { node: n, score: bestScore, match_field: matchField }\n })\n .filter((s): s is SearchResult => s !== null)\n .sort((a, b) => b.score - a.score)\n .slice(0, limit)\n}\n\n// ── Health Score (0-100) ──────────────────────────────────────────────────\n\nexport function computeHealthScore(digest: GraphDigest): number {\n const orphanRate = digest.health.orphan_rate\n const orphanScore = Math.max(0, 100 - orphanRate * 200)\n // Filter out the special `stage_summary` key — it's an aggregate, not a\n // per-region row. Iterating Object.entries(coverage) directly would have\n // counted it as a (zero-coverage) region.\n const regions = Object.entries(digest.coverage)\n .filter(([key]) => key !== 'stage_summary')\n .map(([, value]) => value as CoverageRegion)\n const domainsCovered = regions.filter((c) => c.covered > 0).length\n const domainScore = (domainsCovered / Object.keys(BUSINESS_AREAS).length) * 100\n\n // Chain completeness\n let chainsComplete = 0\n const chainPairs = [\n [digest.chains.persona_with_job, digest.chains.persona_total],\n [digest.chains.job_with_need, digest.chains.job_total],\n [digest.chains.opportunity_with_solution, digest.chains.opportunity_total],\n [digest.chains.experiment_with_learning, digest.chains.experiment_total],\n ]\n for (const [connected, total] of chainPairs) {\n if (total > 0 && connected === total) chainsComplete++\n }\n const chainScore = chainPairs.length > 0 ? (chainsComplete / chainPairs.length) * 100 : 100\n\n const validationScore = digest.health.validation_rate * 100\n\n return Math.round(\n orphanScore * 0.25 +\n domainScore * 0.25 +\n chainScore * 0.30 +\n validationScore * 0.20\n )\n}\n\n// ── Orphan detection ──────────────────────────────────────────────────────\n\nexport function getOrphans(store: UPGFileStore): UPGBaseNode[] {\n const connectedNodes = new Set<string>()\n for (const e of store.getAllEdges()) {\n connectedNodes.add(e.source)\n connectedNodes.add(e.target)\n }\n return store.getAllNodes().filter((n) => !connectedNodes.has(n.id))\n}\n\n// ── List with filters ──────────────────────────────────────────────────────\n\nexport interface ListNodesOptions {\n type?: string\n status?: string\n parentId?: string\n tags?: string[]\n includeEdges?: boolean\n countOnly?: boolean\n limit?: number\n offset?: number\n}\n\nexport interface ListNodesResult {\n nodes: Array<Record<string, unknown>>\n total: number\n}\n\nexport function listNodes(\n store: UPGFileStore,\n options?: ListNodesOptions\n): ListNodesResult {\n let nodes = store.getAllNodes()\n\n if (options?.type) nodes = nodes.filter((n) => n.type === options.type)\n if (options?.status) nodes = nodes.filter((n) => n.status === options.status)\n if (options?.tags && options.tags.length > 0) {\n const filterTags = options.tags\n nodes = nodes.filter((n) => normalizeTags(n.tags)?.some((t: string) => filterTags.includes(t)))\n }\n if (options?.parentId) {\n const parentEdges = store.getEdgesForNode(options.parentId)\n const childIds = new Set(\n parentEdges.filter((e) => e.source === options.parentId).map((e) => e.target)\n )\n nodes = nodes.filter((n) => childIds.has(n.id))\n }\n\n const total = nodes.length\n const offset = options?.offset ?? 0\n const limit = Math.min(options?.limit ?? 50, 200)\n\n const page = nodes.slice(offset, offset + limit).map((n) => {\n const entry: Record<string, unknown> = {\n id: n.id,\n type: n.type,\n title: n.title,\n status: n.status,\n tags: n.tags,\n }\n if (options?.includeEdges) {\n entry.edges = store.getEdgesForNode(n.id).map((e) => ({\n id: e.id,\n type: e.type,\n source: e.source,\n target: e.target,\n }))\n }\n return entry\n })\n\n return { nodes: page, total }\n}\n\n// ── Get single node with edges ──────────────────────────────────────────────\n\nexport interface GetNodeResult {\n node: UPGBaseNode\n edges_out: Array<Record<string, unknown>>\n edges_in: Array<Record<string, unknown>>\n}\n\nexport function getNode(\n store: UPGFileStore,\n args: { node_id: string; compact_edges?: boolean }\n): GetNodeResult | null {\n const node = store.getNode(args.node_id)\n if (!node) return null\n\n const compact = args.compact_edges ?? false\n const edges = store.getEdgesForNode(args.node_id)\n\n const edgesOut = edges\n .filter((e) => e.source === args.node_id)\n .map((e) =>\n compact\n ? { id: e.id, type: e.type, source: e.source, target: e.target }\n : { ...e, target_title: store.getNode(e.target)?.title ?? '(unknown)' },\n )\n\n const edgesIn = edges\n .filter((e) => e.target === args.node_id)\n .map((e) =>\n compact\n ? { id: e.id, type: e.type, source: e.source, target: e.target }\n : { ...e, source_title: store.getNode(e.source)?.title ?? '(unknown)' },\n )\n\n return { node, edges_out: edgesOut, edges_in: edgesIn }\n}\n\n// ── Get multiple nodes with edges (batch) ────────────────────────────────────\n\nexport interface GetNodesResult {\n nodes: Array<GetNodeResult>\n total: number\n not_found?: string[]\n}\n\nexport function getNodes(\n store: UPGFileStore,\n args: { ids: string[]; compact_edges?: boolean }\n): GetNodesResult {\n const compact = args.compact_edges ?? false\n const results: GetNodeResult[] = []\n const notFound: string[] = []\n\n for (const id of args.ids) {\n const result = getNode(store, { node_id: id, compact_edges: compact })\n if (!result) {\n notFound.push(id)\n continue\n }\n results.push(result)\n }\n\n const response: GetNodesResult = { nodes: results, total: results.length }\n if (notFound.length > 0) response.not_found = notFound\n return response\n}\n\n// ── Create node ──────────────────────────────────────────────────────────────\n\nexport interface CreateNodeArgs {\n type: string\n title: string\n description?: string\n tags?: unknown\n status?: string\n properties?: Record<string, unknown>\n parent_id?: string\n}\n\nexport interface CreateNodeResult {\n node: UPGBaseNode\n edge: UPGEdge | null\n warning?: string\n}\n\nexport function createNode(\n store: UPGFileStore,\n args: CreateNodeArgs\n): CreateNodeResult {\n // Validate the entity type up front. Aliases (deprecated → canonical)\n // are accepted with a warning; genuinely unknown types throw\n // `UnknownEntityTypeError` (with near-miss suggestions) so the caller cannot\n // accidentally write an orphan node that no edge constraint will accept.\n const resolved = resolveEntityType(args.type)\n const canonicalNodeType = resolved.canonical\n const aliasWarning = resolved.alias\n ? `Type '${resolved.alias.from}' aliased to canonical '${resolved.alias.to}'. Update your caller to use '${resolved.alias.to}' directly.`\n : undefined\n\n const newNode: UPGBaseNode = {\n id: nodeId(),\n type: canonicalNodeType as UPGEntityType,\n title: args.title,\n }\n if (args.description) newNode.description = args.description\n if (args.tags) newNode.tags = normalizeTags(args.tags) ?? []\n if (args.properties) newNode.properties = args.properties\n autoFillSlug(newNode, store)\n\n // Lifecycle-aware status handling\n let warning: string | undefined = aliasWarning\n if (args.status) {\n newNode.status = args.status\n const statusWarning = validateStatusAgainstLifecycle(canonicalNodeType, args.status)\n if (statusWarning) warning = warning ? `${warning} | ${statusWarning}` : statusWarning\n } else {\n // Auto-default to initial_phase if the type has a lifecycle\n const defaultStatus = getDefaultStatus(canonicalNodeType)\n if (defaultStatus) newNode.status = defaultStatus\n }\n\n store.addNode(newNode)\n\n let edge: UPGEdge | null = null\n if (args.parent_id) {\n const parent = store.getNode(args.parent_id)\n if (!parent) {\n return {\n node: newNode,\n edge: null,\n warning: (warning ? warning + ' | ' : '') + `Parent node ${args.parent_id} not found. Node created without edge.`,\n }\n }\n const inference = inferEdgeTypeWithTier(parent.type, canonicalNodeType)\n if (!inference.ok) {\n // Do NOT fabricate the parent edge. Node still lands; caller\n // is told the edge couldn't be canonicalised so they can pick an\n // explicit edge type with create_edge.\n const suggestion = inference.suggestions.length > 0\n ? ` Suggestions: ${inference.suggestions.map((s) => `${s.source_type} → ${s.target_type} (${s.edge_type})`).join('; ')}.`\n : ''\n return {\n node: newNode,\n edge: null,\n warning:\n (warning ? warning + ' | ' : '') +\n `Parent edge not created — no canonical edge for ${parent.type} → ${canonicalNodeType}.${suggestion}`,\n }\n }\n edge = {\n id: edgeId(),\n source: args.parent_id,\n target: newNode.id,\n type: inference.edgeType,\n }\n store.addEdge(edge)\n }\n\n return warning ? { node: newNode, edge, warning } : { node: newNode, edge: edge as UPGEdge | null }\n}\n\n// ── Create edge ──────────────────────────────────────────────────────────────\n\nexport interface CreateEdgeArgs {\n source_id: string\n target_id?: string\n target_title?: string\n target_type?: string\n type?: string\n}\n\nexport type CreateEdgeResult =\n | { edge: UPGEdge; warning?: string }\n | {\n error: string\n /**\n * Source/target types when the failure is a \"no canonical edge\"\n * resolver miss — surfaced so the MCP handler can attach\n * `anchor_hint` / `alternate_anchors` / `adjacent_edges` enrichment\n * blocks (UPG-505 + UPG-515).\n */\n no_canonical_edge_for?: { source_type: string; target_type: string }\n }\n\nexport function createEdge(\n store: UPGFileStore,\n args: CreateEdgeArgs\n): CreateEdgeResult {\n // Resolve target: by ID or by title+type\n let targetId = args.target_id\n\n if (!targetId && !args.target_title) {\n return { error: 'Provide either target_id or target_title (with target_type)' }\n }\n\n if (!targetId && args.target_title) {\n if (!args.target_type) {\n return { error: 'target_type is required when using target_title' }\n }\n const candidates = store\n .getAllNodes()\n .filter(\n (n) =>\n n.type === args.target_type &&\n n.title.toLowerCase() === args.target_title!.toLowerCase(),\n )\n if (candidates.length === 0) {\n return { error: `No ${args.target_type} found with title \"${args.target_title}\"` }\n }\n if (candidates.length > 1) {\n return {\n error: `Ambiguous: ${candidates.length} nodes match \"${args.target_title}\" (type: ${args.target_type}). Use target_id instead. IDs: ${candidates.map((c) => c.id).join(', ')}`,\n }\n }\n targetId = candidates[0].id\n }\n\n const source = store.getNode(args.source_id)\n const target = store.getNode(targetId!)\n if (!source) return { error: `Source not found: ${args.source_id}` }\n if (!target) return { error: `Target not found: ${targetId}` }\n\n // Refuse graph-topology self-loops up-front. No canonical UPG edge type is\n // currently self-referential; an opt-in flag can be added later if one\n // becomes needed. Tracked as audit finding F2 (2026-05-20).\n if (args.source_id === targetId) {\n return {\n error:\n `Self-loop refused: source and target resolve to the same node \"${args.source_id}\". ` +\n `No canonical UPG edge type is self-referential. ` +\n `If you genuinely need a self-referential edge, file a spec proposal first.`,\n }\n }\n\n let edgeType: UPGEdgeType\n let edgeWarning: string | undefined\n\n if (args.type) {\n // User-supplied edge type — verify against the catalog's source/target\n // pair when the type is canonical. Non-canonical types fall through\n // (they're still surfaced by validate_graph as edge_drift). Tracked as\n // audit finding F1 (2026-05-20).\n const pairCheck = validateEdgeTypePair(args.type, source.type as string, target.type as string)\n if (!pairCheck.valid) {\n return { error: pairCheck.reason }\n }\n edgeType = args.type as UPGEdgeType\n } else {\n const inference = inferEdgeTypeWithTier(source.type, target.type)\n if (!inference.ok) {\n // Refuse to fabricate an edge type when the user did not\n // pass one. They must either pass an explicit `type` from the catalog\n // or fix the source/target types.\n const suggestion = inference.suggestions.length > 0\n ? ` Try one of: ${inference.suggestions.map((s) => `${s.source_type} → ${s.target_type} (${s.edge_type})`).join('; ')}.`\n : ''\n return {\n error: `No canonical edge type for ${source.type} → ${target.type}.${suggestion} Pass an explicit \\`type\\` if you need a non-catalog edge.`,\n no_canonical_edge_for: {\n source_type: source.type as string,\n target_type: target.type as string,\n },\n }\n }\n edgeType = inference.edgeType\n if (inference.aliased) {\n const parts = inference.aliased.map((a) => `${a.from} → ${a.to}`).join(', ')\n edgeWarning = `Edge inferred from canonical (${parts}).`\n }\n }\n\n const edge: UPGEdge = {\n id: edgeId(),\n source: args.source_id,\n target: targetId!,\n type: edgeType,\n }\n\n store.addEdge(edge)\n const response: { edge: UPGEdge; warning?: string } = { edge }\n if (edgeWarning) response.warning = edgeWarning\n return response\n}\n\n// ── Delete node ──────────────────────────────────────────────────────────────\n\nexport interface DeleteNodeResult {\n deleted_node_id: string\n deleted_node_title: string\n deleted_edge_ids: string[]\n}\n\nexport function deleteNode(\n store: UPGFileStore,\n args: { node_id: string }\n): DeleteNodeResult {\n const { node, removedEdgeIds } = store.removeNode(args.node_id)\n return {\n deleted_node_id: node.id,\n deleted_node_title: node.title,\n deleted_edge_ids: removedEdgeIds,\n }\n}\n\n// ── Delete edge ──────────────────────────────────────────────────────────────\n\nexport interface DeleteEdgeResult {\n deleted_edge_id: string\n}\n\nexport function deleteEdge(\n store: UPGFileStore,\n args: { edge_id: string }\n): DeleteEdgeResult {\n const edge = store.removeEdge(args.edge_id)\n return { deleted_edge_id: edge.id }\n}\n\n// ── Move node (atomic re-parent) ──────────────────────────────────\n\nexport interface MoveNodeArgs {\n node_id: string\n new_parent_id: string\n /** Override the inferred edge type. Must be a key in UPG_EDGE_CATALOG. */\n new_edge_type?: string\n /**\n * Disambiguate when the node has more than one hierarchy edge. Caller\n * specifies which existing parent edge to delete; otherwise the move is\n * rejected with the candidate edge ids.\n */\n old_edge_id?: string\n}\n\nexport type MoveNodeResult =\n | {\n moved: true\n node_id: string\n new_edge: UPGEdge\n removed_edge_id: string | null\n /** Removed edge object — exposed for caller-driven rollback (e.g. batch). */\n removed_edge?: UPGEdge\n warning?: string\n }\n | { moved: false; error: string }\n\n/**\n * Find the existing parent edge(s) for a node — edges where the node is\n * the target and the edge has classification 'hierarchy' in the catalog.\n * Non-canonical edges (not in UPG_EDGE_CATALOG) are skipped.\n */\nfunction findParentEdges(store: UPGFileStore, nodeId: string): UPGEdge[] {\n const incoming = store.getEdgesForNode(nodeId).filter((e) => e.target === nodeId)\n return incoming.filter((e) => {\n const def = UPG_EDGE_CATALOG[e.type]\n return def?.classification === 'hierarchy'\n })\n}\n\nexport function moveNode(store: UPGFileStore, args: MoveNodeArgs): MoveNodeResult {\n const node = store.getNode(args.node_id)\n if (!node) return { moved: false, error: `Node not found: ${args.node_id}` }\n\n const newParent = store.getNode(args.new_parent_id)\n if (!newParent) {\n return { moved: false, error: `New parent not found: ${args.new_parent_id}` }\n }\n\n if (args.node_id === args.new_parent_id) {\n return { moved: false, error: 'Cannot move a node onto itself.' }\n }\n\n // Identify the parent edge to remove (if any).\n let oldEdge: UPGEdge | null = null\n if (args.old_edge_id) {\n const explicit = store.getEdge(args.old_edge_id)\n if (!explicit) {\n return { moved: false, error: `old_edge_id not found: ${args.old_edge_id}` }\n }\n if (explicit.target !== args.node_id) {\n return {\n moved: false,\n error: `old_edge_id ${args.old_edge_id} does not target node ${args.node_id}.`,\n }\n }\n oldEdge = explicit\n } else {\n const parents = findParentEdges(store, args.node_id)\n if (parents.length > 1) {\n return {\n moved: false,\n error: `Node has ${parents.length} hierarchy edges; pass old_edge_id to disambiguate. Candidates: ${parents\n .map((e) => `${e.id} (${e.type})`)\n .join(', ')}`,\n }\n }\n oldEdge = parents[0] ?? null\n }\n\n // Resolve the new edge type. Explicit override > catalog inference.\n let newEdgeType: UPGEdgeType\n let aliasWarning: string | undefined\n if (args.new_edge_type) {\n if (!UPG_EDGE_CATALOG[args.new_edge_type as UPGEdgeType]) {\n return {\n moved: false,\n error: `new_edge_type \"${args.new_edge_type}\" is not in UPG_EDGE_CATALOG.`,\n }\n }\n newEdgeType = args.new_edge_type as UPGEdgeType\n } else {\n const inference = inferEdgeTypeWithTier(newParent.type, node.type)\n if (!inference.ok) {\n const suggestion = inference.suggestions.length > 0\n ? ` Suggestions: ${inference.suggestions.map((s) => `${s.source_type} → ${s.target_type} (${s.edge_type})`).join('; ')}.`\n : ''\n return {\n moved: false,\n error: `No canonical edge for ${newParent.type} → ${node.type}.${suggestion} Pass an explicit new_edge_type.`,\n }\n }\n newEdgeType = inference.edgeType\n if (inference.aliased) {\n const parts = inference.aliased.map((a) => `${a.from} → ${a.to}`).join(', ')\n aliasWarning = `Edge inferred from canonical (${parts}).`\n }\n }\n\n // Validate the resolved edge against catalog source/target constraints.\n const def = UPG_EDGE_CATALOG[newEdgeType]\n if (def.source_type !== newParent.type) {\n return {\n moved: false,\n error: `Edge \"${newEdgeType}\" requires source type \"${def.source_type}\", got \"${newParent.type}\".`,\n }\n }\n if (def.target_type !== node.type) {\n return {\n moved: false,\n error: `Edge \"${newEdgeType}\" requires target type \"${def.target_type}\", got \"${node.type}\".`,\n }\n }\n\n // Build the new edge upfront. If addEdge fails for any reason, we'll\n // rollback by re-adding the old edge.\n const newEdge: UPGEdge = {\n id: edgeId(),\n source: args.new_parent_id,\n target: args.node_id,\n type: newEdgeType,\n }\n\n // Atomic swap. removeEdge → addEdge in a single synchronous block. If\n // addEdge throws (target/source missing — should not happen given checks\n // above), rollback by restoring the old edge.\n if (oldEdge) {\n store.removeEdge(oldEdge.id)\n }\n try {\n store.addEdge(newEdge)\n } catch (err) {\n if (oldEdge) {\n // Restore — skipValidation because the old edge was already in the\n // graph and valid before we touched it.\n store.addEdge(oldEdge, true)\n }\n return {\n moved: false,\n error: `Failed to add new edge: ${(err as Error).message}. Graph rolled back.`,\n }\n }\n\n return {\n moved: true,\n node_id: args.node_id,\n new_edge: newEdge,\n removed_edge_id: oldEdge?.id ?? null,\n ...(oldEdge ? { removed_edge: oldEdge } : {}),\n ...(aliasWarning ? { warning: aliasWarning } : {}),\n }\n}\n\n// ── batch_move_nodes — atomic, all-or-nothing (cap 50) ──────────────────────\n\nexport interface BatchMoveNodesResult {\n moves: Array<{ node_id: string; new_edge: UPGEdge; removed_edge_id: string | null }>\n count: number\n warnings?: string[]\n}\n\nexport type BatchMoveNodesOutcome =\n | { ok: true; result: BatchMoveNodesResult }\n | { ok: false; error: string; failed_at_index: number | null }\n\n/**\n * Apply a batch of moves atomically. Validates every move against the catalog\n * BEFORE any mutation; on the first failure the batch is rejected with no\n * changes to the graph. If a mutation fails mid-application (highly unusual\n * given upfront validation), already-applied moves are rolled back.\n */\nexport function batchMoveNodes(\n store: UPGFileStore,\n moves: MoveNodeArgs[],\n): BatchMoveNodesOutcome {\n if (moves.length === 0) return { ok: false, error: 'moves array is empty', failed_at_index: null }\n if (moves.length > 50) return { ok: false, error: 'Maximum 50 moves per batch', failed_at_index: null }\n\n // ── Validation pass ─────────────────────────────────────────────────────\n // We dry-run each move's resolution against the CURRENT graph. This catches\n // (a) missing nodes, (b) unresolved edge inference, (c) catalog\n // constraint violations. We don't catch chained moves where move N depends\n // on the state after move N-1; that would require simulating the graph.\n // For now: reject those by requiring caller to pass new_edge_type per move\n // (fully spec'd) or to split into sequential calls. Surface that limit in\n // the docstring.\n for (let i = 0; i < moves.length; i++) {\n const m = moves[i]\n const node = store.getNode(m.node_id)\n if (!node) return { ok: false, error: `Move at index ${i}: node not found: ${m.node_id}`, failed_at_index: i }\n const parent = store.getNode(m.new_parent_id)\n if (!parent) return { ok: false, error: `Move at index ${i}: new parent not found: ${m.new_parent_id}`, failed_at_index: i }\n if (m.node_id === m.new_parent_id) {\n return { ok: false, error: `Move at index ${i}: cannot move a node onto itself.`, failed_at_index: i }\n }\n if (m.new_edge_type && !UPG_EDGE_CATALOG[m.new_edge_type as UPGEdgeType]) {\n return { ok: false, error: `Move at index ${i}: new_edge_type \"${m.new_edge_type}\" is not in UPG_EDGE_CATALOG.`, failed_at_index: i }\n }\n if (!m.new_edge_type) {\n const inference = inferEdgeTypeWithTier(parent.type, node.type)\n if (!inference.ok) {\n return { ok: false, error: `Move at index ${i}: no canonical edge for ${parent.type} → ${node.type}. Pass an explicit new_edge_type.`, failed_at_index: i }\n }\n }\n }\n\n // ── Apply pass with rollback ────────────────────────────────────────────\n type Applied = { newEdge: UPGEdge; oldEdge: UPGEdge | null }\n const applied: Applied[] = []\n for (let i = 0; i < moves.length; i++) {\n const result = moveNode(store, moves[i])\n if (!result.moved) {\n // Roll back already-applied moves in reverse order — restore the\n // exact old edge objects captured by moveNode so the graph is bit-\n // for-bit identical to where it started.\n for (let j = applied.length - 1; j >= 0; j--) {\n const a = applied[j]\n try {\n store.removeEdge(a.newEdge.id)\n } catch { /* edge already gone */ }\n if (a.oldEdge) {\n try {\n store.addEdge(a.oldEdge, true)\n } catch { /* duplicate id — graph diverged, surface but continue */ }\n }\n }\n return { ok: false, error: `Move at index ${i}: ${result.error}`, failed_at_index: i }\n }\n applied.push({\n newEdge: result.new_edge,\n oldEdge: result.removed_edge ?? null,\n })\n }\n\n return {\n ok: true,\n result: {\n moves: applied.map((a, i) => ({\n node_id: moves[i].node_id,\n new_edge: a.newEdge,\n removed_edge_id: a.oldEdge?.id ?? null,\n })),\n count: moves.length,\n },\n }\n}\n\n// ── batch_create_nodes (with optional atomic edges) ─────────────────────────\n\nexport interface BatchNodeInput {\n type: string\n title: string\n description?: string\n status?: string\n tags?: unknown\n properties?: Record<string, unknown>\n parent_id?: string\n parent_ref?: string\n}\n\nexport interface BatchEdgeInput {\n from_ref: string\n to_ref: string\n type?: string\n}\n\nexport interface BatchCreateArgs {\n nodes: BatchNodeInput[]\n edges?: BatchEdgeInput[]\n}\n\nexport interface BatchCreateOk {\n ok: true\n created: Array<{ id: string; type: string; title: string; status?: string }>\n edges: UPGEdge[]\n explicit_edges?: UPGEdge[]\n count: number\n warnings?: string[]\n}\n\nexport interface BatchCreateFail {\n ok: false\n error: string\n}\n\nexport type BatchCreateResult = BatchCreateOk | BatchCreateFail\n\n/**\n * Atomic batch creation — nodes plus optional explicit edges in a single\n * all-or-nothing transaction.\n *\n * Validation pass walks every node and every edge against the canonical\n * schema BEFORE any mutation. If anything fails, no nodes and no edges land.\n *\n * Apply pass creates nodes (with parent_ref / parent_id auto-edges from\n * inference; failed inference = warning, never fabrication),\n * then creates the explicit edges. If ANY apply step throws, rolls back\n * every already-applied node + edge so the graph is bit-for-bit identical\n * to the pre-call state.\n */\nexport function batchCreateNodes(\n store: UPGFileStore,\n args: BatchCreateArgs,\n): BatchCreateResult {\n const { nodes, edges: explicitEdges = [] } = args\n if (!Array.isArray(nodes)) return { ok: false, error: 'Missing required parameter: nodes (array)' }\n if (nodes.length === 0) return { ok: false, error: 'nodes array is empty' }\n if (nodes.length > 50) return { ok: false, error: 'Maximum 50 nodes per batch' }\n if (nodes.length + explicitEdges.length > 50) {\n return { ok: false, error: `Maximum 50 items per batch (got ${nodes.length} nodes + ${explicitEdges.length} edges)` }\n }\n\n // ── Validation pass ─────────────────────────────────────────────────────\n const resolvedTypes: string[] = []\n const aliasWarnings: string[] = []\n for (let i = 0; i < nodes.length; i++) {\n const n = nodes[i]\n if (!n.type) return { ok: false, error: `Node at index ${i}: missing required field \"type\"` }\n if (!n.title) return { ok: false, error: `Node at index ${i}: missing required field \"title\"` }\n try {\n const resolved = resolveEntityType(n.type)\n resolvedTypes.push(resolved.canonical)\n if (resolved.alias) {\n aliasWarnings.push(\n `Node at index ${i}: type '${resolved.alias.from}' aliased to canonical '${resolved.alias.to}'.`,\n )\n }\n } catch (err) {\n if (err instanceof UnknownEntityTypeError) {\n return { ok: false, error: `Node at index ${i}: ${err.message}` }\n }\n throw err\n }\n if (n.parent_ref !== undefined) {\n const match = n.parent_ref.match(/^\\$(\\d+)$/)\n if (!match) return { ok: false, error: `Node at index ${i}: invalid parent_ref \"${n.parent_ref}\" — use \"$0\", \"$1\", etc.` }\n const refIndex = parseInt(match[1], 10)\n if (refIndex >= i) return { ok: false, error: `Node at index ${i}: parent_ref \"${n.parent_ref}\" must reference an earlier index (0–${i - 1})` }\n }\n if (n.parent_id !== undefined && !store.getNode(n.parent_id)) {\n return { ok: false, error: `Node at index ${i}: parent_id \"${n.parent_id}\" not found in graph` }\n }\n }\n\n type ResolvedEdgeRef =\n | { kind: 'ref'; index: number }\n | { kind: 'id'; id: string }\n type ValidatedEdge = {\n from: ResolvedEdgeRef\n to: ResolvedEdgeRef\n typeOverride?: UPGEdgeType\n }\n const validatedEdges: ValidatedEdge[] = []\n\n const resolveEdgeRef = (raw: unknown, label: string, edgeIndex: number): ResolvedEdgeRef | { error: string } => {\n if (typeof raw !== 'string' || raw.length === 0) {\n return { error: `Edge at index ${edgeIndex}: missing or invalid \"${label}\"` }\n }\n const refMatch = raw.match(/^\\$(\\d+)$/)\n if (refMatch) {\n const idx = parseInt(refMatch[1], 10)\n if (idx >= nodes.length) {\n return { error: `Edge at index ${edgeIndex}: ${label} \"${raw}\" out of range — only ${nodes.length} nodes in this batch.` }\n }\n return { kind: 'ref', index: idx }\n }\n if (!store.getNode(raw)) {\n return { error: `Edge at index ${edgeIndex}: ${label} \"${raw}\" not found in graph (and is not a $N ref into this batch).` }\n }\n return { kind: 'id', id: raw }\n }\n const refSourceType = (ref: ResolvedEdgeRef): string =>\n ref.kind === 'ref' ? resolvedTypes[ref.index] : store.getNode(ref.id)!.type\n\n for (let i = 0; i < explicitEdges.length; i++) {\n const e = explicitEdges[i]\n const fromResolved = resolveEdgeRef(e.from_ref, 'from_ref', i)\n if ('error' in fromResolved) return { ok: false, error: fromResolved.error }\n const toResolved = resolveEdgeRef(e.to_ref, 'to_ref', i)\n if ('error' in toResolved) return { ok: false, error: toResolved.error }\n\n // Self-loop refusal — both sides resolve to the same ref OR the same\n // pre-existing node id. No canonical UPG edge type is self-referential.\n // F2 (2026-05-20).\n const sameRef =\n fromResolved.kind === 'ref' && toResolved.kind === 'ref' && fromResolved.index === toResolved.index\n const sameId =\n fromResolved.kind === 'id' && toResolved.kind === 'id' && fromResolved.id === toResolved.id\n if (sameRef || sameId) {\n return {\n ok: false,\n error:\n `Edge at index ${i}: self-loop refused — source and target resolve to the same node. ` +\n `No canonical UPG edge type is self-referential.`,\n }\n }\n\n let typeOverride: UPGEdgeType | undefined\n if (e.type !== undefined) {\n if (!UPG_EDGE_CATALOG[e.type as UPGEdgeType]) {\n return { ok: false, error: `Edge at index ${i}: type \"${e.type}\" not in UPG_EDGE_CATALOG.` }\n }\n // Catalog pair validation against the resolved source/target types.\n // F1 (2026-05-20).\n const sourceType = refSourceType(fromResolved)\n const targetType = refSourceType(toResolved)\n const pairCheck = validateEdgeTypePair(e.type, sourceType, targetType)\n if (!pairCheck.valid) {\n return { ok: false, error: `Edge at index ${i}: ${pairCheck.reason}` }\n }\n typeOverride = e.type as UPGEdgeType\n } else {\n const sourceType = refSourceType(fromResolved)\n const targetType = refSourceType(toResolved)\n const inference = inferEdgeTypeWithTier(sourceType, targetType)\n if (!inference.ok) {\n const suggestion = inference.suggestions.length > 0\n ? ` Suggestions: ${inference.suggestions.map((s) => `${s.source_type} → ${s.target_type} (${s.edge_type})`).join('; ')}.`\n : ''\n return { ok: false, error: `Edge at index ${i}: no canonical edge for ${sourceType} → ${targetType}.${suggestion} Pass an explicit \\`type\\` to override.` }\n }\n }\n validatedEdges.push({ from: fromResolved, to: toResolved, typeOverride })\n }\n\n // ── Apply pass with full rollback ───────────────────────────────────────\n const createdNodes: Array<{ id: string; type: string; title: string; status?: string }> = []\n const createdNodeRefs: UPGBaseNode[] = []\n const createdParentEdges: UPGEdge[] = []\n const explicitCreated: UPGEdge[] = []\n const warnings: string[] = [...aliasWarnings]\n\n const rollbackAll = () => {\n for (const e of explicitCreated.slice().reverse()) {\n try { store.removeEdge(e.id) } catch { /* gone */ }\n }\n for (const e of createdParentEdges.slice().reverse()) {\n try { store.removeEdge(e.id) } catch { /* gone */ }\n }\n for (const n of createdNodeRefs.slice().reverse()) {\n try { store.removeNode(n.id) } catch { /* gone */ }\n }\n }\n\n try {\n for (let i = 0; i < nodes.length; i++) {\n const n = nodes[i]\n const newNode: UPGBaseNode = {\n id: nodeId(),\n type: resolvedTypes[i] as UPGEntityType,\n title: n.title,\n }\n if (n.description) newNode.description = n.description\n if (n.tags) newNode.tags = normalizeTags(n.tags) ?? []\n if (n.properties) newNode.properties = n.properties\n\n if (n.status) {\n newNode.status = n.status\n const sw = validateStatusAgainstLifecycle(newNode.type, n.status)\n if (sw) warnings.push(`Node \"${n.title}\": ${sw}`)\n } else {\n const ds = getDefaultStatus(newNode.type)\n if (ds) newNode.status = ds\n }\n\n autoFillSlug(newNode, store)\n store.addNode(newNode)\n createdNodes.push({ id: newNode.id, type: newNode.type, title: newNode.title, status: newNode.status })\n createdNodeRefs.push(newNode)\n\n let parentId = n.parent_id\n if (n.parent_ref !== undefined) {\n const refIndex = parseInt(n.parent_ref.slice(1), 10)\n parentId = createdNodes[refIndex].id\n }\n if (parentId) {\n const parent = store.getNode(parentId)\n if (parent) {\n const inference = inferEdgeTypeWithTier(parent.type, newNode.type)\n if (inference.ok) {\n const edge: UPGEdge = { id: edgeId(), source: parentId, target: newNode.id, type: inference.edgeType }\n store.addEdge(edge)\n createdParentEdges.push(edge)\n } else {\n const suggestion = inference.suggestions.length > 0\n ? ` Suggestions: ${inference.suggestions.map((s) => `${s.source_type} → ${s.target_type} (${s.edge_type})`).join('; ')}.`\n : ''\n warnings.push(\n `Node \"${newNode.title}\": parent edge not created — no canonical edge for ${parent.type} → ${newNode.type}.${suggestion}`,\n )\n }\n }\n }\n }\n\n for (const v of validatedEdges) {\n const sourceId = v.from.kind === 'ref' ? createdNodes[v.from.index].id : v.from.id\n const targetId = v.to.kind === 'ref' ? createdNodes[v.to.index].id : v.to.id\n let edgeType: UPGEdgeType\n if (v.typeOverride) {\n edgeType = v.typeOverride\n } else {\n const source = store.getNode(sourceId)!\n const target = store.getNode(targetId)!\n const inference = inferEdgeTypeWithTier(source.type, target.type)\n if (!inference.ok) {\n throw new Error(`Edge inference unexpectedly failed for ${source.type} → ${target.type} (post-validation).`)\n }\n edgeType = inference.edgeType\n }\n const newEdge: UPGEdge = { id: edgeId(), source: sourceId, target: targetId, type: edgeType }\n store.addEdge(newEdge)\n explicitCreated.push(newEdge)\n }\n } catch (err) {\n rollbackAll()\n return {\n ok: false,\n error: `Atomic batch failed during apply: ${(err as Error).message}. All nodes and edges rolled back.`,\n }\n }\n\n // UPG-504: orphan warning. When the caller batched ≥2 nodes but produced\n // zero edges of any kind (no parent_ref auto-edges, no explicit edges),\n // surface a loud warning. Authors who don't read the warnings field stay\n // backward-compatible; authors who do read it get a teaching moment instead\n // of a silent orphan graph.\n if (\n createdNodes.length >= 2 &&\n createdParentEdges.length === 0 &&\n explicitCreated.length === 0\n ) {\n warnings.push(\n `Created ${createdNodes.length} nodes with no edges — they are orphans. ` +\n `Use the edges[] array in this call to link them. ` +\n `See get_entity_schema(<type>) for canonical edges per type.`,\n )\n }\n\n const result: BatchCreateOk = {\n ok: true,\n created: createdNodes,\n edges: createdParentEdges,\n count: createdNodes.length,\n }\n if (explicitCreated.length > 0) result.explicit_edges = explicitCreated\n if (warnings.length > 0) result.warnings = warnings\n return result\n}\n\n// ── migrate_node_type — single-node type change ───────────────────\n\nexport interface MigrateNodeTypeArgs {\n node_id: string\n new_type: string\n}\n\nexport type MigrateNodeTypeResult =\n | {\n migrated: true\n node_id: string\n from_type: string\n to_type: string\n edges_rewritten: Array<{ id: string; from: string; to: string }>\n warning?: string\n }\n | { migrated: false; error: string; suggestions?: string[] }\n\n/**\n * Atomically change a single node's entity type and rewrite every incident\n * edge to its new canonical edge type derived from the catalog.\n *\n * Distinct from `migrate_type` (which rewrites EVERY node of a given type\n * across the whole graph by string substitution). This single-node variant\n * re-infers each affected edge from (source.type, target.type) via\n * `inferEdgeTypeWithTier`, so it preserves correctness when the graph mixes\n * canonical and deprecated types.\n *\n * If any incident edge cannot be re-inferred, the entire migration is\n * rejected and the graph is left unchanged. Rollback restores both the\n * node's original type and any edges already rewritten.\n */\nexport function migrateNodeType(\n store: UPGFileStore,\n args: MigrateNodeTypeArgs,\n): MigrateNodeTypeResult {\n const node = store.getNode(args.node_id)\n if (!node) return { migrated: false, error: `Node not found: ${args.node_id}` }\n\n // Resolve new type — alias-aware. Unknown types throw, caught here\n // and translated to a structured failure so the caller doesn't have to.\n let resolved: EntityTypeResolution\n try {\n resolved = resolveEntityType(args.new_type)\n } catch (err) {\n if (err instanceof UnknownEntityTypeError) {\n return { migrated: false, error: err.message, suggestions: err.suggestions }\n }\n throw err\n }\n\n const oldType = node.type\n const newType = resolved.canonical\n const aliasWarning = resolved.alias\n ? `Type '${resolved.alias.from}' aliased to canonical '${resolved.alias.to}'.`\n : undefined\n if (oldType === newType) {\n return {\n migrated: true,\n node_id: args.node_id,\n from_type: oldType,\n to_type: newType,\n edges_rewritten: [],\n ...(aliasWarning ? { warning: aliasWarning } : {}),\n }\n }\n\n // Plan all edge rewrites BEFORE mutation. Each incident edge gets a new\n // canonical type derived from (source.type, target.type) under the new\n // type assignment. If any inference fails the migration is rejected.\n const incident = store.getEdgesForNode(args.node_id)\n type Plan = { oldEdge: UPGEdge; newType: UPGEdgeType }\n const plans: Plan[] = []\n for (const e of incident) {\n if (e.type === args.new_type) continue\n const sourceType = e.source === args.node_id ? newType : (store.getNode(e.source)?.type ?? '')\n const targetType = e.target === args.node_id ? newType : (store.getNode(e.target)?.type ?? '')\n if (!sourceType || !targetType) {\n return {\n migrated: false,\n error: `Edge ${e.id} references a missing node — fix graph integrity before migrating.`,\n }\n }\n const inference = inferEdgeTypeWithTier(sourceType, targetType)\n if (!inference.ok) {\n const suggestion = inference.suggestions.length > 0\n ? ` Suggestions: ${inference.suggestions.map((s) => `${s.source_type} → ${s.target_type} (${s.edge_type})`).join('; ')}.`\n : ''\n return {\n migrated: false,\n error: `Cannot re-infer edge ${e.id} (${e.type}) for ${sourceType} → ${targetType}.${suggestion} Delete the edge or pick an explicit edge type via update_node + create_edge.`,\n }\n }\n if (inference.edgeType !== e.type) {\n plans.push({ oldEdge: e, newType: inference.edgeType })\n }\n }\n\n // ── Apply pass with rollback ──────────────────────────────────────────\n // Mutating in place: remove old edges, change node type, add new edges.\n // If any addEdge throws, undo everything and surface the original error.\n const removed: UPGEdge[] = []\n const added: UPGEdge[] = []\n try {\n for (const p of plans) {\n const old = store.removeEdge(p.oldEdge.id)\n removed.push(old)\n }\n store.updateNode(args.node_id, { type: newType as UPGEntityType })\n for (const p of plans) {\n const newEdge: UPGEdge = {\n id: edgeId(),\n source: p.oldEdge.source,\n target: p.oldEdge.target,\n type: p.newType,\n }\n store.addEdge(newEdge)\n added.push(newEdge)\n }\n } catch (err) {\n // Rollback in reverse order.\n for (const a of added.slice().reverse()) {\n try { store.removeEdge(a.id) } catch { /* edge already gone */ }\n }\n try {\n store.updateNode(args.node_id, { type: oldType as UPGEntityType })\n } catch { /* node already gone — graph diverged, surface the original */ }\n for (const r of removed.slice().reverse()) {\n try { store.addEdge(r, true) } catch { /* duplicate id — surface the original */ }\n }\n return {\n migrated: false,\n error: `Migration failed mid-apply: ${(err as Error).message}. Graph rolled back.`,\n }\n }\n\n const edgesRewritten = plans.map((p, i) => ({\n id: added[i].id,\n from: p.oldEdge.type,\n to: p.newType,\n }))\n\n return {\n migrated: true,\n node_id: args.node_id,\n from_type: oldType,\n to_type: newType,\n edges_rewritten: edgesRewritten,\n ...(aliasWarning ? { warning: aliasWarning } : {}),\n }\n}\n","/**\n * UPGClient — high-level, namespaced facade over UPGFileStore.\n *\n * This is the recommended entry point for application code that wants to\n * read or write a `.upg` file. Advanced consumers can drop down to the\n * lower-level primitives (`UPGFileStore`, `createNode`, `createEdge`, etc.)\n * which are also exported from `@unified-product-graph/sdk`.\n *\n * The client is async throughout because file I/O on a `.upg` file is\n * inherently async. Mutations are persisted to disk via `flush()` after\n * each call, so callers never need to manage save state explicitly.\n *\n * ```ts\n * import { UPGClient } from '@unified-product-graph/sdk'\n *\n * const upg = new UPGClient({ file: './product.upg' })\n *\n * // Nodes\n * await upg.nodes.create({ type: 'feature', title: 'Dark mode' })\n * await upg.nodes.list({ type: 'feature' })\n * await upg.nodes.get('node-id')\n * await upg.nodes.update('node-id', { status: 'active' })\n * await upg.nodes.delete('node-id')\n *\n * // Edges\n * await upg.edges.connect('src-id', 'tgt-id')\n * await upg.edges.list({ source: 'node-id' })\n *\n * // Graph-level\n * await upg.health()\n * await upg.search('dark mode')\n * await upg.verify()\n * ```\n */\n\nimport type { UPGBaseNode, UPGEdge, UPGEdgeType } from '@unified-product-graph/core'\nimport { UPGFileStore } from './store.js'\nimport {\n createNode as createNodeOp,\n createEdge as createEdgeOp,\n deleteNode as deleteNodeOp,\n deleteEdge as deleteEdgeOp,\n listNodes as listNodesOp,\n getNode as getNodeOp,\n computeGraphDigest,\n computeHealthScore,\n searchNodes,\n type CreateNodeArgs,\n type CreateEdgeArgs,\n type ListNodesOptions,\n type GraphDigest,\n type SearchResult,\n} from './lib/tools.js'\n\nexport interface UPGClientOptions {\n /** Path to the .upg file on disk. */\n file: string\n /**\n * Skip auto-load on first operation (default: false).\n * When true, call `await client.load()` manually before any operation.\n */\n lazy?: boolean\n}\n\nexport interface NodeListOptions extends ListNodesOptions {}\n\nexport interface EdgeListOptions {\n source?: string\n target?: string\n type?: UPGEdgeType\n}\n\nexport interface HealthResult {\n score: number\n digest: GraphDigest\n}\n\nexport interface SearchOptions {\n /** Maximum number of results (default: 20). */\n limit?: number\n /** Restrict to a single entity type. */\n type?: string\n}\n\nexport class UPGClient {\n private readonly options: UPGClientOptions\n private store: UPGFileStore | null = null\n private loadPromise: Promise<void> | null = null\n\n /** Node operations namespace. */\n readonly nodes: NodesAPI\n /** Edge operations namespace. */\n readonly edges: EdgesAPI\n\n constructor(options: UPGClientOptions) {\n this.options = options\n this.nodes = new NodesAPI(this)\n this.edges = new EdgesAPI(this)\n }\n\n /**\n * Load the .upg file. Called automatically on first operation unless\n * `{ lazy: true }` was set. Safe to call multiple times — repeated calls\n * are coalesced. If load fails (transient I/O, parse error, etc.) the\n * promise is rejected AND the cached promise is cleared, so the next\n * call retries from scratch rather than re-throwing the stale error.\n */\n async load(): Promise<void> {\n if (this.store && !this.loadPromise) return\n if (this.loadPromise) return this.loadPromise\n this.loadPromise = (async () => {\n try {\n const store = new UPGFileStore()\n await store.load(this.options.file)\n this.store = store\n } catch (err) {\n // Clear the cached promise so the next call retries instead of\n // returning a permanently-rejected promise. The error still\n // propagates to *this* caller.\n this.loadPromise = null\n throw err\n }\n })()\n return this.loadPromise\n }\n\n /** Internal: get the loaded store, loading on demand. */\n async getStore(): Promise<UPGFileStore> {\n if (!this.store) await this.load()\n if (!this.store) throw new Error('UPGClient: store failed to load')\n return this.store\n }\n\n /** Persist pending changes to disk. Called automatically after mutations. */\n async flush(): Promise<void> {\n const store = await this.getStore()\n await store.flush()\n }\n\n /** Compute a health score (0–100) plus the underlying graph digest. */\n async health(): Promise<HealthResult> {\n const store = await this.getStore()\n const digest = computeGraphDigest(store)\n return { score: computeHealthScore(digest), digest }\n }\n\n /** Search nodes by free-text query. */\n async search(query: string, options: SearchOptions = {}): Promise<SearchResult[]> {\n const store = await this.getStore()\n return searchNodes(store, query, {\n limit: options.limit ?? 20,\n ...(options.type ? { type: options.type } : {}),\n })\n }\n\n /**\n * Verify integrity of the loaded graph. Returns the integrity report from\n * the last load + any in-memory mutation checks. `null` indicates no\n * integrity issues were detected.\n */\n async verify() {\n const store = await this.getStore()\n return store.getIntegrityReport()\n }\n\n /**\n * Diff against a previous version. Not yet implemented — tracked in\n * UPG-541 follow-up. Will return a structured changeset between the\n * current graph and the named ref (git revision or snapshot id).\n */\n async diff(_ref: string): Promise<never> {\n throw new Error(\n 'UPGClient.diff() is not yet implemented. Use the CLI (`upg diff <ref>`) for now.',\n )\n }\n\n /** Release file watchers and free resources. */\n async close(): Promise<void> {\n if (!this.store) return\n await this.store.flush()\n this.store = null\n this.loadPromise = null\n }\n}\n\nclass NodesAPI {\n constructor(private readonly client: UPGClient) {}\n\n /**\n * Create a node. The `type` is validated against the UPG entity catalog —\n * deprecated aliases are accepted with a warning, genuinely unknown types\n * throw `UnknownEntityTypeError`.\n */\n async create(args: CreateNodeArgs) {\n const store = await this.client.getStore()\n const result = createNodeOp(store, args)\n await store.flush()\n return result\n }\n\n /** List nodes, optionally filtered by type / status / tag. */\n async list(options: NodeListOptions = {}) {\n const store = await this.client.getStore()\n return listNodesOp(store, options)\n }\n\n /** Get a single node by id. Returns `undefined` if not found. */\n async get(id: string): Promise<UPGBaseNode | undefined> {\n const store = await this.client.getStore()\n const result = getNodeOp(store, { node_id: id })\n return result?.node\n }\n\n /** Update a node by id. Returns the updated node. */\n async update(id: string, patch: Partial<UPGBaseNode>): Promise<UPGBaseNode> {\n const store = await this.client.getStore()\n const updated = store.updateNode(id, patch)\n await store.flush()\n return updated\n }\n\n /** Delete a node and all incident edges. */\n async delete(id: string) {\n const store = await this.client.getStore()\n const result = deleteNodeOp(store, { node_id: id })\n await store.flush()\n return result\n }\n}\n\nclass EdgesAPI {\n constructor(private readonly client: UPGClient) {}\n\n /**\n * Connect two nodes with an edge. Edge type is inferred from source +\n * target entity types if not provided.\n */\n async connect(sourceId: string, targetId: string, opts: Partial<CreateEdgeArgs> = {}) {\n const store = await this.client.getStore()\n const args: CreateEdgeArgs = {\n source_id: sourceId,\n target_id: targetId,\n ...opts,\n }\n const result = createEdgeOp(store, args)\n await store.flush()\n return result\n }\n\n /** List edges, optionally filtered by source / target / type. */\n async list(options: EdgeListOptions = {}): Promise<UPGEdge[]> {\n const store = await this.client.getStore()\n let edges = store.getAllEdges()\n if (options.source) edges = edges.filter((e) => e.source === options.source)\n if (options.target) edges = edges.filter((e) => e.target === options.target)\n if (options.type) edges = edges.filter((e) => e.type === options.type)\n return edges\n }\n\n /** Delete an edge by id. */\n async delete(id: string) {\n const store = await this.client.getStore()\n const result = deleteEdgeOp(store, { edge_id: id })\n await store.flush()\n return result\n }\n}\n","/**\n * Workspace bootstrap. Holds the core logic for `init_workspace`.\n *\n * Extracted from server.ts so the bug-prone fs choreography can be unit-tested\n * against real tmp directories without booting the MCP transport.\n *\n * Two bugs this module guards against:\n * 1. `readdir` returning the `.upg` workspace directory itself as an entry\n * that \"ends with .upg\". The old code tried to rename `.upg → .upg/.upg`\n * and crashed with EINVAL.\n * 2. A user who already organised their `.upg` files inside `.upg/` re-running\n * `init_workspace` from the project root. The old code found nothing at\n * root and registered an empty product list. The new code discovers\n * pre-existing siblings and registers them non-destructively.\n */\n\nimport * as fsp from 'node:fs/promises'\nimport * as path from 'node:path'\nimport { createHash } from 'node:crypto'\nimport type { UPGFileStore } from '../store.js'\nimport type { UPGDocument, UPGProductStage } from '@unified-product-graph/core'\nimport {\n generateSlug,\n resolveSlugCollision,\n UPG_VERSION,\n validateProductStageStrict,\n} from '@unified-product-graph/core'\nimport { edgeId, productId } from './id.js'\n\nexport interface WorkspaceProduct {\n file: string\n title: string\n}\n\nexport interface InitWorkspaceArgs {\n cwd: string\n store: UPGFileStore\n moveExisting?: boolean\n}\n\nexport interface InitWorkspaceResult {\n workspace_path: string\n default_product: string\n products: WorkspaceProduct[]\n current_product: { title: string; entities: number }\n}\n\nexport class WorkspaceAlreadyExistsError extends Error {\n constructor() {\n super('Workspace already exists. Use get_workspace_info to see current state.')\n this.name = 'WorkspaceAlreadyExistsError'\n }\n}\n\nexport class WorkspaceNotInitialisedError extends Error {\n constructor() {\n super(\n 'Workspace not initialised. Run `init_workspace` first to enable multi-product management.',\n )\n this.name = 'WorkspaceNotInitialisedError'\n }\n}\n\nexport class InvalidProductNameError extends Error {\n constructor(reason: string) {\n super(`Invalid product name: ${reason}`)\n this.name = 'InvalidProductNameError'\n }\n}\n\n/**\n * Thrown when a `create_product` / product-stage write attempts to\n * persist a non-canonical UPGProductStage value. Surfaces the canonical set\n * + any documented coercion target in the error message so the caller can\n * fix the input.\n */\nexport class InvalidProductStageError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'InvalidProductStageError'\n }\n}\n\nasync function readProductTitle(filePath: string, fallback: string): Promise<string> {\n try {\n const raw = await fsp.readFile(filePath, 'utf-8')\n const doc = JSON.parse(raw)\n if (doc.product?.title && typeof doc.product.title === 'string') {\n return doc.product.title\n }\n } catch {\n // Malformed file or unreadable — fall back to filename\n }\n return fallback\n}\n\nexport async function initWorkspace({\n cwd,\n store,\n moveExisting = true,\n}: InitWorkspaceArgs): Promise<InitWorkspaceResult> {\n const resolvedCwd = path.resolve(cwd)\n const upgDir = path.resolve(resolvedCwd, '.upg')\n\n // Bail if the workspace is already initialised\n try {\n await fsp.access(path.join(upgDir, 'workspace.json'))\n throw new WorkspaceAlreadyExistsError()\n } catch (err) {\n if (err instanceof WorkspaceAlreadyExistsError) throw err\n // ENOENT — good, no workspace yet\n }\n\n await fsp.mkdir(upgDir, { recursive: true })\n\n // Root-level .upg files that may need moving. Use withFileTypes to filter\n // out the `.upg` directory entry itself — `'.upg'.endsWith('.upg')` would\n // otherwise match it and trigger a rename of the directory into itself.\n const rootEntries = await fsp.readdir(resolvedCwd, { withFileTypes: true })\n const rootUpgFiles = rootEntries\n .filter((e) => e.isFile() && e.name.endsWith('.upg'))\n .map((e) => e.name)\n .sort()\n\n // Pre-existing .upg files already living inside .upg/ — they are products\n // even if the user never moved them. Register without re-moving.\n let preExistingFiles: string[] = []\n try {\n const upgDirEntries = await fsp.readdir(upgDir, { withFileTypes: true })\n preExistingFiles = upgDirEntries\n .filter((e) => e.isFile() && e.name.endsWith('.upg'))\n .map((e) => e.name)\n .sort()\n } catch {\n // Directory was just created or unreadable — treat as empty\n }\n\n const products: WorkspaceProduct[] = []\n const seen = new Set<string>()\n\n for (const file of preExistingFiles) {\n const filePath = path.join(upgDir, file)\n const title = await readProductTitle(filePath, path.basename(file, '.upg'))\n products.push({ file, title })\n seen.add(file)\n }\n\n if (moveExisting) {\n for (const file of rootUpgFiles) {\n const srcPath = path.resolve(resolvedCwd, file)\n const destPath = path.resolve(upgDir, file)\n\n // Already inside the workspace dir (covers symlink edge cases where\n // resolvedCwd and upgDir share a prefix in unexpected ways).\n if (srcPath === destPath || path.dirname(srcPath) === upgDir) {\n if (!seen.has(file)) {\n const title = await readProductTitle(srcPath, path.basename(file, '.upg'))\n products.push({ file, title })\n seen.add(file)\n }\n continue\n }\n\n const title = await readProductTitle(srcPath, path.basename(file, '.upg'))\n\n // If the destination already exists (user dropped the same file in\n // both root and .upg/), keep the existing one and leave the root copy\n // alone — non-destructive by default.\n let destExists = false\n try {\n await fsp.access(destPath)\n destExists = true\n } catch {\n // dest absent — safe to move\n }\n\n if (!destExists) {\n await fsp.rename(srcPath, destPath)\n }\n if (!seen.has(file)) {\n products.push({ file, title })\n seen.add(file)\n }\n }\n }\n\n // If still nothing registered, seed with the currently-loaded store file.\n if (products.length === 0) {\n const currentFile = store.getFilePath()\n const basename = path.basename(currentFile)\n const destPath = path.resolve(upgDir, basename)\n try {\n await fsp.access(destPath)\n } catch {\n await fsp.copyFile(currentFile, destPath)\n }\n const product = store.getProduct()\n products.push({ file: basename, title: product.title })\n }\n\n const defaultProduct = products[0].file\n\n await fsp.writeFile(\n path.join(upgDir, 'workspace.json'),\n JSON.stringify({ version: '1.0', default_product: defaultProduct, products }, null, 2) + '\\n',\n 'utf-8',\n )\n\n // Reload store with the workspace's default product\n const newFilePath = path.join(upgDir, defaultProduct)\n await store.flush()\n store.stopWatching()\n await store.load(newFilePath)\n\n const product = store.getProduct()\n return {\n workspace_path: '.upg/',\n default_product: defaultProduct,\n products,\n current_product: { title: product.title, entities: store.getAllNodes().length },\n }\n}\n\n// ─── create_product ────────────────────────────────────────────────\n\nexport interface CreateProductArgs {\n cwd: string\n store: UPGFileStore\n name: string\n slug?: string\n description?: string\n stage?: UPGProductStage\n /**\n * Optional portfolio node id in the CURRENT loaded store. When provided,\n * a `product` node + `portfolio_contains_product` edge are created in the\n * current store to express the hierarchy (portfolios live in a single .upg).\n */\n portfolio_id?: string\n}\n\nexport interface CreateProductResult {\n id: string\n file: string\n slug: string\n title: string\n workspace_path: string\n portfolio_attached: boolean\n}\n\n/** Compute the integrity checksum a freshly written .upg file would carry. */\nfunction computeIntegrityChecksum(doc: UPGDocument): string {\n const sortedNodes = [...doc.nodes].sort((a, b) => a.id.localeCompare(b.id))\n const sortedEdges = [...doc.edges].sort((a, b) => a.id.localeCompare(b.id))\n const content = JSON.stringify({ nodes: sortedNodes, edges: sortedEdges })\n return createHash('sha256').update(content).digest('hex').slice(0, 32)\n}\n\nexport async function createProduct(args: CreateProductArgs): Promise<CreateProductResult> {\n const { cwd, store, name, slug: slugArg, description, stage, portfolio_id } = args\n const upgDir = path.resolve(cwd, '.upg')\n\n // Workspace mode required — single-file mode has no place to put the new sibling.\n try {\n await fsp.access(path.join(upgDir, 'workspace.json'))\n } catch {\n throw new WorkspaceNotInitialisedError()\n }\n\n // Validate name. Trim whitespace; reject empty / non-string.\n if (typeof name !== 'string' || name.trim().length === 0) {\n throw new InvalidProductNameError('name must be a non-empty string')\n }\n const trimmedName = name.trim()\n\n // Strict validation on the write path. The TypeScript signature\n // (`stage?: UPGProductStage`) is erased at the MCP boundary because args\n // arrive as plain JSON, so we must check at runtime. Reject non-canonical\n // values with a helpful error pointing to the canonical set + any\n // documented coercion target. Reads still soft-coerce (UPGFileStore.load).\n if (stage !== undefined) {\n const stageError = validateProductStageStrict(stage)\n if (stageError !== null) {\n throw new InvalidProductStageError(stageError)\n }\n }\n\n // Slug: prefer explicit, otherwise derive from name. Resolve collision against\n // existing workspace files (each .upg basename without extension is a slug).\n const upgDirEntries = await fsp.readdir(upgDir, { withFileTypes: true })\n const existingSlugs = new Set(\n upgDirEntries\n .filter((e) => e.isFile() && e.name.endsWith('.upg'))\n .map((e) => path.basename(e.name, '.upg')),\n )\n const baseSlug = slugArg && slugArg.trim().length > 0 ? generateSlug(slugArg) : generateSlug(trimmedName)\n const slug = resolveSlugCollision(baseSlug, existingSlugs)\n const filename = `${slug}.upg`\n const destPath = path.join(upgDir, filename)\n\n // Mint product ID via the canonical generator so it matches every other\n // server-minted ID prefix.\n const newProductId = productId()\n\n // Build a minimal valid UPGDocument. validateUPGDocument() at load-time will\n // confirm shape; integrity is stamped here so the new file is immediately\n // tamper-detectable.\n const newDoc: UPGDocument = {\n upg_version: UPG_VERSION,\n exported_at: new Date().toISOString(),\n source: { tool: 'upg-mcp-local' },\n product: {\n id: newProductId,\n title: trimmedName,\n ...(description ? { description } : {}),\n ...(stage ? { stage } : {}),\n },\n nodes: [],\n edges: [],\n }\n newDoc._integrity = {\n checksum: computeIntegrityChecksum(newDoc),\n verified_at: new Date().toISOString(),\n verified_by: 'upg-mcp-local',\n }\n\n // Refuse to clobber — should be impossible given collision resolution above\n // but guard anyway in case of a race against an external writer.\n try {\n await fsp.access(destPath)\n throw new Error(`File already exists at ${destPath} — slug resolution failed`)\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'ENOENT') throw err\n }\n\n await fsp.writeFile(destPath, JSON.stringify(newDoc, null, 2) + '\\n', 'utf-8')\n\n // Append to workspace.json's products list\n const workspacePath = path.join(upgDir, 'workspace.json')\n const workspaceRaw = await fsp.readFile(workspacePath, 'utf-8')\n const workspace = JSON.parse(workspaceRaw) as {\n version: string\n default_product: string\n products: WorkspaceProduct[]\n }\n workspace.products = [\n ...workspace.products,\n { file: filename, title: trimmedName },\n ]\n await fsp.writeFile(\n workspacePath,\n JSON.stringify(workspace, null, 2) + '\\n',\n 'utf-8',\n )\n\n // Optional portfolio attachment in the CURRENT store. The portfolio lives in\n // a single .upg file; portfolio_contains_product is an in-graph hierarchy\n // edge, not a cross-product one.\n let portfolioAttached = false\n if (portfolio_id) {\n const portfolio = store.getNode(portfolio_id)\n if (portfolio && portfolio.type === 'portfolio') {\n // Mirror the new product as a node in the current store so the edge can\n // attach to a real target. The node id matches the new product's id.\n store.addNode({\n id: newProductId,\n type: 'product',\n title: trimmedName,\n ...(description ? { description } : {}),\n })\n store.addEdge({\n id: edgeId(),\n source: portfolio_id,\n target: newProductId,\n type: 'portfolio_contains_product',\n })\n portfolioAttached = true\n }\n }\n\n return {\n id: newProductId,\n file: filename,\n slug,\n title: trimmedName,\n workspace_path: '.upg/',\n portfolio_attached: portfolioAttached,\n }\n}\n","/**\n * Portfolio routing helpers — UPG-526.\n *\n * Portfolio-scoped entity types (`portfolio`, `organization`, `product_area`)\n * belong in `.upg/portfolio.upg` rather than the active product's `nodes[]`.\n * These helpers centralise:\n * - resolving the portfolio path for the current workspace\n * - loading-or-initialising the portfolio store\n * - appending entities to the right portfolio array (or setting the\n * singleton `organization`)\n * - reading portfolio-scoped entities back out in a shape compatible with\n * `list_portfolios` / `list_product_areas`\n *\n * The set of portfolio-scoped types is intentionally narrow and documented as\n * `PORTFOLIO_SCOPED_TYPES`. Adding a new type means:\n * 1. extending `UPGPortfolioDocument` in `@unified-product-graph/core`\n * 2. extending this set\n * 3. extending the write-routing switch in `writePortfolioScopedNode`\n */\n\nimport * as fs from 'node:fs'\nimport * as path from 'node:path'\nimport { createHash } from 'node:crypto'\nimport { nodeId } from './id.js'\nimport { UPGPortfolioStore } from '../store.js'\nimport type {\n UPGPortfolioDocument,\n UPGPortfolio,\n UPGProductArea,\n UPGOrganization,\n} from '@unified-product-graph/core'\n\n/** Entity types that live in `.upg/portfolio.upg` instead of a product graph. */\nexport const PORTFOLIO_SCOPED_TYPES: ReadonlySet<string> = new Set([\n 'portfolio',\n 'organization',\n 'product_area',\n])\n\n/** Default portfolio filename within a `.upg/` workspace. */\nexport const PORTFOLIO_FILENAME = 'portfolio.upg'\n\n/** True when the entity type belongs in the portfolio document. */\nexport function isPortfolioScopedType(type: string): boolean {\n return PORTFOLIO_SCOPED_TYPES.has(type)\n}\n\n/**\n * Resolve the portfolio file path for the current workspace.\n * Returns null if the cwd has no `.upg/` directory.\n */\nexport function resolvePortfolioPath(cwd: string): string | null {\n const upgDir = path.join(cwd, '.upg')\n if (!fs.existsSync(upgDir)) return null\n return path.join(upgDir, PORTFOLIO_FILENAME)\n}\n\n/**\n * Resolve the portfolio file path, creating the `.upg/` directory if it does\n * not exist. Used by write paths that need to mint a portfolio document on\n * demand (e.g. first portfolio entity in a workspace that has product files but\n * never had a portfolio doc).\n */\nexport function resolveOrCreatePortfolioPath(cwd: string): string {\n const upgDir = path.join(cwd, '.upg')\n if (!fs.existsSync(upgDir)) {\n fs.mkdirSync(upgDir, { recursive: true })\n }\n return path.join(upgDir, PORTFOLIO_FILENAME)\n}\n\n/**\n * Open (or initialise) the portfolio store at the given path. Caller is\n * responsible for `flush()`-ing the store when the mutation is committed.\n */\nexport async function openPortfolioStore(\n portfolioPath: string,\n): Promise<UPGPortfolioStore> {\n const store = new UPGPortfolioStore()\n await store.loadOrInit(portfolioPath)\n return store\n}\n\n// ── Write routing ────────────────────────────────────────────────────────────\n\nexport interface WritePortfolioNodeArgs {\n /** Entity type — must satisfy `isPortfolioScopedType`. */\n type: string\n /** Title for the new entity (or the organisation). */\n title: string\n description?: string\n /** Free-form properties; subset is hoisted onto the typed shape per type. */\n properties?: Record<string, unknown>\n /** When type === 'organization', allow overwriting an existing org. */\n overwrite_organization?: boolean\n}\n\nexport interface WritePortfolioNodeResult {\n /** Persisted entity shape (the typed record actually written). */\n entity: UPGPortfolio | UPGProductArea | UPGOrganization\n /** Where in the portfolio document the entity was written. */\n written_to: 'portfolios' | 'product_areas' | 'organization'\n /** Absolute portfolio file path. */\n portfolio_file: string\n /** Optional warning (e.g. organization overwrite happened). */\n warning?: string\n}\n\nexport class PortfolioRoutingError extends Error {\n constructor(message: string) {\n super(message)\n this.name = 'PortfolioRoutingError'\n }\n}\n\n/**\n * Append (or set, for `organization`) a portfolio-scoped entity into the\n * portfolio document. Creates `.upg/portfolio.upg` on demand.\n *\n * For `organization`: refuses to overwrite an existing org unless\n * `overwrite_organization: true` is supplied. The auto-generated org from\n * `loadOrInit` is treated as a placeholder (org id starting with `org_` and\n * title \"Portfolio\") and may be replaced silently.\n */\nexport async function writePortfolioScopedNode(\n cwd: string,\n args: WritePortfolioNodeArgs,\n): Promise<WritePortfolioNodeResult> {\n if (!isPortfolioScopedType(args.type)) {\n throw new PortfolioRoutingError(\n `writePortfolioScopedNode called with non-portfolio type \"${args.type}\". ` +\n `Valid types: ${[...PORTFOLIO_SCOPED_TYPES].join(', ')}.`,\n )\n }\n\n const portfolioPath = resolveOrCreatePortfolioPath(cwd)\n const store = await openPortfolioStore(portfolioPath)\n const doc = store.getDocument()\n if (!doc) {\n throw new PortfolioRoutingError(\n `Failed to initialise portfolio document at ${portfolioPath}`,\n )\n }\n\n let result: WritePortfolioNodeResult\n switch (args.type) {\n case 'portfolio':\n result = appendPortfolio(doc, args, portfolioPath)\n break\n case 'product_area':\n result = appendProductArea(doc, args, portfolioPath)\n break\n case 'organization':\n result = setOrganization(doc, args, portfolioPath)\n break\n default:\n // Unreachable thanks to the guard above, but keeps the type checker\n // honest if PORTFOLIO_SCOPED_TYPES grows out of sync.\n throw new PortfolioRoutingError(`Unhandled portfolio-scoped type: ${args.type}`)\n }\n\n // Mark dirty (the typed mutations above bypass the store's scheduleSave) and\n // flush synchronously so the caller can return a stable response shape.\n // `setDirty` is not exposed on UPGPortfolioStore — we work around by hand-\n // writing through the store's existing API. The simplest cross-cutting hook\n // is `addCrossEdge`-style: it sets `dirty = true` internally. Since we\n // mutated `doc` in place via getDocument(), call a no-op flush primitive.\n //\n // Implementation choice: expose a `markDirty()` on UPGPortfolioStore so\n // callers can opt-in to a flush without going through a typed mutation\n // method. See `store.ts`.\n store.markDirty()\n await store.flush()\n\n return result\n}\n\nfunction appendPortfolio(\n doc: UPGPortfolioDocument,\n args: WritePortfolioNodeArgs,\n portfolioPath: string,\n): WritePortfolioNodeResult {\n const props = args.properties ?? {}\n const entity: UPGPortfolio = {\n id: nodeId(),\n title: args.title,\n }\n if (args.description) entity.description = args.description\n if (typeof props.parent_portfolio_id === 'string' || props.parent_portfolio_id === null) {\n entity.parent_portfolio_id = props.parent_portfolio_id as string | null\n }\n if (\n props.hierarchy_model === 'flat' ||\n props.hierarchy_model === 'nested' ||\n props.hierarchy_model === 'matrix'\n ) {\n entity.hierarchy_model = props.hierarchy_model\n }\n if (Array.isArray(props.products)) {\n entity.products = props.products.filter((p): p is string => typeof p === 'string')\n }\n doc.portfolios.push(entity)\n return { entity, written_to: 'portfolios', portfolio_file: portfolioPath }\n}\n\nfunction appendProductArea(\n doc: UPGPortfolioDocument,\n args: WritePortfolioNodeArgs,\n portfolioPath: string,\n): WritePortfolioNodeResult {\n const props = args.properties ?? {}\n const entity: UPGProductArea = {\n id: nodeId(),\n title: args.title,\n }\n if (args.description) entity.description = args.description\n if (typeof props.parent_area_id === 'string' || props.parent_area_id === null) {\n entity.parent_area_id = props.parent_area_id as string | null\n }\n if (\n props.strategic_priority === 'critical' ||\n props.strategic_priority === 'high' ||\n props.strategic_priority === 'medium' ||\n props.strategic_priority === 'low'\n ) {\n entity.strategic_priority = props.strategic_priority\n }\n if (Array.isArray(props.products)) {\n entity.products = props.products.filter((p): p is string => typeof p === 'string')\n }\n doc.product_areas.push(entity)\n return { entity, written_to: 'product_areas', portfolio_file: portfolioPath }\n}\n\nfunction setOrganization(\n doc: UPGPortfolioDocument,\n args: WritePortfolioNodeArgs,\n portfolioPath: string,\n): WritePortfolioNodeResult {\n const props = args.properties ?? {}\n const existing = doc.organization\n const isPlaceholder = isPlaceholderOrganization(existing)\n\n if (existing && !isPlaceholder && !args.overwrite_organization) {\n throw new PortfolioRoutingError(\n `Portfolio already has an organization (id: \"${existing.id}\", title: ` +\n `\"${existing.title}\"). A portfolio holds exactly one organization. ` +\n `Pass overwrite_organization: true to replace it (e.g. ` +\n `create_node({type: \"organization\", title: \"...\", overwrite_organization: true})), ` +\n `or call update_node on the existing organization id.`,\n )\n }\n\n const entity: UPGOrganization = {\n id: `org_${createHash('sha256').update(args.title).digest('hex').slice(0, 8)}`,\n title: args.title,\n }\n if (args.description) entity.description = args.description\n if (typeof props.logo_url === 'string') entity.logo_url = props.logo_url\n if (typeof props.industry === 'string') entity.industry = props.industry\n\n const warning = !isPlaceholder && args.overwrite_organization\n ? `Replaced existing organization \"${existing.title}\" (id: ${existing.id}) with \"${entity.title}\" (id: ${entity.id}).`\n : undefined\n\n doc.organization = entity\n return {\n entity,\n written_to: 'organization',\n portfolio_file: portfolioPath,\n ...(warning ? { warning } : {}),\n }\n}\n\n/**\n * The portfolio store auto-creates a placeholder organization on `loadOrInit`\n * (title \"Portfolio\", id derived from that title). Treat those records as\n * blank slate — they were never authored by a caller.\n */\nfunction isPlaceholderOrganization(\n org: UPGOrganization | undefined | null,\n): boolean {\n if (!org) return true\n if (org.title !== 'Portfolio') return false\n const placeholderId = `org_${createHash('sha256').update('Portfolio').digest('hex').slice(0, 8)}`\n return org.id === placeholderId\n}\n\n// ── Read routing ─────────────────────────────────────────────────────────────\n\n/**\n * Open the portfolio store if one exists. Returns null when there is no\n * workspace and no portfolio document on disk — callers use this to render an\n * empty list instead of erroring.\n */\nexport async function openPortfolioStoreIfExists(\n cwd: string,\n): Promise<UPGPortfolioStore | null> {\n const portfolioPath = resolvePortfolioPath(cwd)\n if (!portfolioPath) return null\n if (!fs.existsSync(portfolioPath)) return null\n const store = new UPGPortfolioStore()\n await store.loadOrInit(portfolioPath)\n return store\n}\n\n// ── Cross-product edge product registration ──────────────────────────────────\n\n/**\n * A lightweight product reference recorded on the portfolio document. The full\n * UPG spec (`UPGPortfolioDocument.products`) types this slot as\n * `UPGProduct & { nodes; edges }`, but the MCP model keeps each product in its\n * own `.upg` file. The reference shape below carries just enough to look the\n * product up later (id + file_path) plus a denormalised title for human-\n * readable listings.\n *\n * If/when the spec adds an explicit `UPGProductReference` type, this shape\n * should migrate to it. The fields are intentionally additive — every field\n * other than `id` is optional so the record stays forward-compatible.\n */\nexport interface PortfolioProductReference {\n id: string\n /** Workspace-relative path to the product's `.upg` file, when known. */\n file_path?: string\n /** Display title; denormalised from the product's own `product.title`. */\n title?: string\n}\n\n/**\n * Ensure that a product is registered on `portfolio.upg.products[]`. No-op when\n * an entry with the same `id` already exists. Does NOT flush — caller flushes\n * once after registering both source and target products in a single pass.\n *\n * @returns true when a new entry was appended, false when already present.\n */\nexport function registerProductOnPortfolio(\n doc: UPGPortfolioDocument,\n ref: PortfolioProductReference,\n): boolean {\n if (!ref.id) return false\n // The spec types `products` as `Array<UPGProduct & { nodes; edges }>` but at\n // the MCP layer the products[] slot is used as a lightweight reference index\n // (id + file_path + title). Cast through `unknown` to avoid TS friction at\n // the boundary; runtime shape is preserved.\n const products = doc.products as unknown as PortfolioProductReference[]\n if (products.some((p) => p.id === ref.id)) return false\n const entry: PortfolioProductReference = { id: ref.id }\n if (ref.file_path) entry.file_path = ref.file_path\n if (ref.title) entry.title = ref.title\n products.push(entry)\n return true\n}\n\n/**\n * Best-effort lookup of a product's `.upg` file and title given its product\n * id. Walks the workspace `.upg/` directory looking for a file whose\n * `product.id` matches. Returns null when not found or when the workspace\n * lookup fails.\n */\nexport function findProductFileById(\n cwd: string,\n productId: string,\n): { file_path: string; title: string } | null {\n const upgDir = path.join(cwd, '.upg')\n if (!fs.existsSync(upgDir)) return null\n let entries: fs.Dirent[]\n try {\n entries = fs.readdirSync(upgDir, { withFileTypes: true })\n } catch {\n return null\n }\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith('.upg')) continue\n // Skip the portfolio file itself.\n if (entry.name === PORTFOLIO_FILENAME) continue\n const filePath = path.join(upgDir, entry.name)\n try {\n const raw = fs.readFileSync(filePath, 'utf-8')\n const doc = JSON.parse(raw) as { product?: { id?: string; title?: string } }\n if (doc.product?.id === productId) {\n return {\n file_path: path.relative(cwd, filePath),\n title: doc.product.title ?? entry.name,\n }\n }\n } catch {\n // skip unreadable/malformed file\n }\n }\n return null\n}\n"],"mappings":"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","names":["nodeId","edgeId","nodeId","path","createHash","title","basename","product","createHash","fs","path","createHash","createHash","productId"]}
|