@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.
@@ -0,0 +1,678 @@
1
+ import { U as UPGFileStore, I as IntegrityReport, a as UPGPortfolioStore } from './logic-DriXyFKi.js';
2
+ export { A as AdjacentEdge, b as AlternateAnchor, c as AnchorHint, B as BUSINESS_AREA_META, d as BusinessArea, C as ChangeEntry, e as CrossEdgeMigrationResult, D as DESCRIPTION_SOFT_LIMIT, f as DanglingEdgeClass, g as DanglingEdgeRecord, h as DanglingEdgeReport, E as ENTITY_CLASSIFICATION, i as EdgeAlias, j as EdgeInferenceFail, k as EdgeInferenceOk, l as EdgeInferenceResult, m as EdgeInferenceSuggestion, n as EdgePairValidationFail, o as EdgePairValidationOk, p as EdgePairValidationResult, q as EvalResult, r as InferEdgeTypeError, s as InspectResult, t as InspectViolation, L as LengthCheckResult, M as MergeResult, u as MissingEntityRow, P as PROPERTY_TREE_SOFT_BYTES, v as PROPERTY_TREE_SOFT_DEPTH, w as PlanResult, x as PortfolioLoadResult, y as PrioritiseExecutionResult, z as PrioritiseFallbackResult, F as PrioritiseRankedRow, G as PropertyTypeCheckResult, H as PropertyTypeViolation, Q as QuarantinedEntity, R as ReflectPrompt, J as ReflectPromptKind, K as ReflectResult, S as SchemaDriftSummary, T as TIER_CLUSTER_META, N as TIER_DESCRIPTIONS, O as TITLE_SOFT_LIMIT, V as Tier, W as TierCluster, X as TraceResult, Y as TraceTrailRow, Z as ValidateGraphBody, _ as buildAdjacentEdges, $ as buildAlternateAnchors, a0 as buildAnchorHint, a1 as buildResolverHints, a2 as checkLengthCaps, a3 as checkPropertyTypes, a4 as classifyDanglingEdges, a5 as collectAntiPatternInputs, a6 as computeSchemaDriftSummary, a7 as edgeId, a8 as evaluateExpression, a9 as executeInspect, aa as executePlan, ab as executePrioritise, ac as executeReflect, ad as executeTrace, ae as getClassification, af as getEntitiesForBusinessArea, ag as getEntitiesForCluster, ah as getEntitiesForTier, ai as getEntitiesForTierAndArea, aj as getEntitiesUpToTier, ak as getTierEntityCount, al as getTierForStage, am as inferEdgeType, an as inferEdgeTypeWithTier, ao as isRelevantForStage, ap as nodeId, aq as productId, ar as renderDanglingReport, as as renderDriftSummary, at as renderPropertyTypeWarning, au as validateClassificationCoverage, av as validateEdgeTypePair } from './logic-DriXyFKi.js';
3
+ import { UPGEdge, UPGProductStage, UPGBaseNode, UPGEdgeType, UPGPortfolio, UPGProductArea, UPGOrganization, UPGPortfolioDocument } from '@unified-product-graph/core';
4
+ export { EntityTypeResolution, UPGCrossEdge, UPGPortfolioDocument, UPG_ANTI_PATTERNS, UPG_REGIONS, UnknownEntityTypeError, resolveEntityType } from '@unified-product-graph/core';
5
+
6
+ /**
7
+ * Shared tool logic. Pure functions that operate on a UPGFileStore.
8
+ *
9
+ * Used by both the MCP server (server.ts) and the CLI (@unified-product-graph/mcp).
10
+ * Extract here, import everywhere.
11
+ */
12
+
13
+ /**
14
+ * Validate a status value against the lifecycle phases for an entity type.
15
+ * Returns a warning string if the status is invalid, or undefined if valid / no lifecycle.
16
+ */
17
+ declare function validateStatusAgainstLifecycle(entityType: string, status: string): string | undefined;
18
+ /**
19
+ * Returns the initial_phase for an entity type, or undefined if the type has no lifecycle.
20
+ */
21
+ declare function getDefaultStatus(entityType: string): string | undefined;
22
+ /**
23
+ * Backfill `slug` on a freshly-built node before it's added to the store.
24
+ * Picks the auto-generated slug from `title`, resolved against
25
+ * every existing slug + alias of the same `type` in the same product.
26
+ *
27
+ * No-op if the node already carries an explicit slug.
28
+ */
29
+ declare function autoFillSlug(node: UPGBaseNode, store: UPGFileStore): void;
30
+ declare function normalizeTags(tags: unknown): string[] | undefined;
31
+ declare const BUSINESS_AREAS: Record<string, {
32
+ emoji: string;
33
+ types: string[];
34
+ }>;
35
+ declare const STAGE_COVERAGE_TARGETS: Record<UPGProductStage, string[]>;
36
+ /**
37
+ * Resolve the canonical UPGProductStage for digest coverage. Returns
38
+ * `'concept'` as the default when stage is missing or unrecognised — concept
39
+ * is the narrowest expectation surface, so we under-grade rather than
40
+ * over-grade. Legacy `"idea"` and friends route through `coerceProductStage`
41
+ * (which lives in `@unified-product-graph/core`); the inline `"idea" →
42
+ * "concept"` fallback below is defensive in case the core helper is ever
43
+ * unavailable.
44
+ */
45
+ declare function resolveCoverageStage(rawStage: unknown): UPGProductStage;
46
+ declare const LIFECYCLE_PHASES: Record<string, string[]>;
47
+ declare const CHAINS: readonly [{
48
+ readonly name: "persona → job";
49
+ readonly from: "persona";
50
+ readonly to: "job";
51
+ readonly edgePattern: "job";
52
+ }, {
53
+ readonly name: "job → need";
54
+ readonly from: "job";
55
+ readonly to: "need";
56
+ readonly edgePattern: "need";
57
+ }, {
58
+ readonly name: "opportunity → solution";
59
+ readonly from: "opportunity";
60
+ readonly to: "solution";
61
+ readonly edgePattern: "solution";
62
+ }, {
63
+ readonly name: "solution → hypothesis";
64
+ readonly from: "solution";
65
+ readonly to: "hypothesis";
66
+ readonly edgePattern: "hypothesis";
67
+ }, {
68
+ readonly name: "hypothesis → experiment_plan";
69
+ readonly from: "hypothesis";
70
+ readonly to: "experiment_plan";
71
+ readonly edgePattern: "experiment_plan";
72
+ }, {
73
+ readonly name: "experiment_run → learning";
74
+ readonly from: "experiment_run";
75
+ readonly to: "learning";
76
+ readonly edgePattern: "learning";
77
+ }, {
78
+ readonly name: "objective → key_result";
79
+ readonly from: "objective";
80
+ readonly to: "key_result";
81
+ readonly edgePattern: "key_result";
82
+ }, {
83
+ readonly name: "feature → story_statement";
84
+ readonly from: "feature";
85
+ readonly to: "story_statement";
86
+ readonly edgePattern: "story_statement";
87
+ }];
88
+ /** Get sort priority for a type (lower = higher in tree). Unknown types sort last. */
89
+ declare function typeSortPriority(type: string): number;
90
+ /** Sort nodes by type priority, then alphabetically by title within same type */
91
+ declare function sortByType(nodes: UPGBaseNode[]): UPGBaseNode[];
92
+ interface CoverageRegion {
93
+ covered: number;
94
+ total: number;
95
+ /**
96
+ * NEW (Finding 9 / UPG-512): True when this region is on the product
97
+ * stage's expected-coverage list. Regions where this is `false` are
98
+ * surfaced for awareness but excluded from `stage_summary.overall_pct`.
99
+ */
100
+ counted_toward_stage: boolean;
101
+ types_present: string[];
102
+ types_missing: string[];
103
+ }
104
+ interface CoverageStageSummary {
105
+ stage: UPGProductStage;
106
+ /** Number of regions counted toward this stage's completeness. */
107
+ regions_counted: number;
108
+ /** Counted regions that are fully covered (covered === total). */
109
+ regions_complete: number;
110
+ /** Counted regions with partial coverage (0 < covered < total). */
111
+ regions_partial: number;
112
+ /** Whole-number percentage 0-100, averaged across counted regions only. */
113
+ overall_pct: number;
114
+ }
115
+ interface GraphDigest {
116
+ product: {
117
+ title: string;
118
+ stage: string;
119
+ };
120
+ counts: {
121
+ total_nodes: number;
122
+ total_edges: number;
123
+ by_type: Record<string, number>;
124
+ };
125
+ health: {
126
+ orphan_count: number;
127
+ orphan_rate: number;
128
+ connectivity: number;
129
+ validation_rate: number;
130
+ user_coverage: number;
131
+ };
132
+ chains: Record<string, number>;
133
+ /**
134
+ * Per-region coverage map. Keys are `BUSINESS_AREAS` ids (e.g.
135
+ * `identity`, `understanding`). The new stage-aware aggregate is at
136
+ * `coverage.stage_summary` (typed loosely here so `Record<string,
137
+ * CoverageRegion>` index lookups stay correct for callers).
138
+ *
139
+ * NOTE: callers iterating `Object.entries(coverage)` should skip the
140
+ * `stage_summary` key — it carries a different shape.
141
+ */
142
+ coverage: Record<string, CoverageRegion> & {
143
+ stage_summary?: CoverageStageSummary;
144
+ };
145
+ lifecycle: Record<string, number>;
146
+ }
147
+ declare function computeGraphDigest(store: UPGFileStore): GraphDigest;
148
+ interface SearchResult {
149
+ node: UPGBaseNode;
150
+ score: number;
151
+ match_field: string;
152
+ }
153
+ declare function searchNodes(store: UPGFileStore, query: string, options?: {
154
+ type?: string;
155
+ fields?: string[];
156
+ limit?: number;
157
+ }): SearchResult[];
158
+ declare function computeHealthScore(digest: GraphDigest): number;
159
+ declare function getOrphans(store: UPGFileStore): UPGBaseNode[];
160
+ interface ListNodesOptions {
161
+ type?: string;
162
+ status?: string;
163
+ parentId?: string;
164
+ tags?: string[];
165
+ includeEdges?: boolean;
166
+ countOnly?: boolean;
167
+ limit?: number;
168
+ offset?: number;
169
+ }
170
+ interface ListNodesResult {
171
+ nodes: Array<Record<string, unknown>>;
172
+ total: number;
173
+ }
174
+ declare function listNodes(store: UPGFileStore, options?: ListNodesOptions): ListNodesResult;
175
+ interface GetNodeResult {
176
+ node: UPGBaseNode;
177
+ edges_out: Array<Record<string, unknown>>;
178
+ edges_in: Array<Record<string, unknown>>;
179
+ }
180
+ declare function getNode(store: UPGFileStore, args: {
181
+ node_id: string;
182
+ compact_edges?: boolean;
183
+ }): GetNodeResult | null;
184
+ interface GetNodesResult {
185
+ nodes: Array<GetNodeResult>;
186
+ total: number;
187
+ not_found?: string[];
188
+ }
189
+ declare function getNodes(store: UPGFileStore, args: {
190
+ ids: string[];
191
+ compact_edges?: boolean;
192
+ }): GetNodesResult;
193
+ interface CreateNodeArgs {
194
+ type: string;
195
+ title: string;
196
+ description?: string;
197
+ tags?: unknown;
198
+ status?: string;
199
+ properties?: Record<string, unknown>;
200
+ parent_id?: string;
201
+ }
202
+ interface CreateNodeResult {
203
+ node: UPGBaseNode;
204
+ edge: UPGEdge | null;
205
+ warning?: string;
206
+ }
207
+ declare function createNode(store: UPGFileStore, args: CreateNodeArgs): CreateNodeResult;
208
+ interface CreateEdgeArgs {
209
+ source_id: string;
210
+ target_id?: string;
211
+ target_title?: string;
212
+ target_type?: string;
213
+ type?: string;
214
+ }
215
+ type CreateEdgeResult = {
216
+ edge: UPGEdge;
217
+ warning?: string;
218
+ } | {
219
+ error: string;
220
+ /**
221
+ * Source/target types when the failure is a "no canonical edge"
222
+ * resolver miss — surfaced so the MCP handler can attach
223
+ * `anchor_hint` / `alternate_anchors` / `adjacent_edges` enrichment
224
+ * blocks (UPG-505 + UPG-515).
225
+ */
226
+ no_canonical_edge_for?: {
227
+ source_type: string;
228
+ target_type: string;
229
+ };
230
+ };
231
+ declare function createEdge(store: UPGFileStore, args: CreateEdgeArgs): CreateEdgeResult;
232
+ interface DeleteNodeResult {
233
+ deleted_node_id: string;
234
+ deleted_node_title: string;
235
+ deleted_edge_ids: string[];
236
+ }
237
+ declare function deleteNode(store: UPGFileStore, args: {
238
+ node_id: string;
239
+ }): DeleteNodeResult;
240
+ interface DeleteEdgeResult {
241
+ deleted_edge_id: string;
242
+ }
243
+ declare function deleteEdge(store: UPGFileStore, args: {
244
+ edge_id: string;
245
+ }): DeleteEdgeResult;
246
+ interface MoveNodeArgs {
247
+ node_id: string;
248
+ new_parent_id: string;
249
+ /** Override the inferred edge type. Must be a key in UPG_EDGE_CATALOG. */
250
+ new_edge_type?: string;
251
+ /**
252
+ * Disambiguate when the node has more than one hierarchy edge. Caller
253
+ * specifies which existing parent edge to delete; otherwise the move is
254
+ * rejected with the candidate edge ids.
255
+ */
256
+ old_edge_id?: string;
257
+ }
258
+ type MoveNodeResult = {
259
+ moved: true;
260
+ node_id: string;
261
+ new_edge: UPGEdge;
262
+ removed_edge_id: string | null;
263
+ /** Removed edge object — exposed for caller-driven rollback (e.g. batch). */
264
+ removed_edge?: UPGEdge;
265
+ warning?: string;
266
+ } | {
267
+ moved: false;
268
+ error: string;
269
+ };
270
+ declare function moveNode(store: UPGFileStore, args: MoveNodeArgs): MoveNodeResult;
271
+ interface BatchMoveNodesResult {
272
+ moves: Array<{
273
+ node_id: string;
274
+ new_edge: UPGEdge;
275
+ removed_edge_id: string | null;
276
+ }>;
277
+ count: number;
278
+ warnings?: string[];
279
+ }
280
+ type BatchMoveNodesOutcome = {
281
+ ok: true;
282
+ result: BatchMoveNodesResult;
283
+ } | {
284
+ ok: false;
285
+ error: string;
286
+ failed_at_index: number | null;
287
+ };
288
+ /**
289
+ * Apply a batch of moves atomically. Validates every move against the catalog
290
+ * BEFORE any mutation; on the first failure the batch is rejected with no
291
+ * changes to the graph. If a mutation fails mid-application (highly unusual
292
+ * given upfront validation), already-applied moves are rolled back.
293
+ */
294
+ declare function batchMoveNodes(store: UPGFileStore, moves: MoveNodeArgs[]): BatchMoveNodesOutcome;
295
+ interface BatchNodeInput {
296
+ type: string;
297
+ title: string;
298
+ description?: string;
299
+ status?: string;
300
+ tags?: unknown;
301
+ properties?: Record<string, unknown>;
302
+ parent_id?: string;
303
+ parent_ref?: string;
304
+ }
305
+ interface BatchEdgeInput {
306
+ from_ref: string;
307
+ to_ref: string;
308
+ type?: string;
309
+ }
310
+ interface BatchCreateArgs {
311
+ nodes: BatchNodeInput[];
312
+ edges?: BatchEdgeInput[];
313
+ }
314
+ interface BatchCreateOk {
315
+ ok: true;
316
+ created: Array<{
317
+ id: string;
318
+ type: string;
319
+ title: string;
320
+ status?: string;
321
+ }>;
322
+ edges: UPGEdge[];
323
+ explicit_edges?: UPGEdge[];
324
+ count: number;
325
+ warnings?: string[];
326
+ }
327
+ interface BatchCreateFail {
328
+ ok: false;
329
+ error: string;
330
+ }
331
+ type BatchCreateResult = BatchCreateOk | BatchCreateFail;
332
+ /**
333
+ * Atomic batch creation — nodes plus optional explicit edges in a single
334
+ * all-or-nothing transaction.
335
+ *
336
+ * Validation pass walks every node and every edge against the canonical
337
+ * schema BEFORE any mutation. If anything fails, no nodes and no edges land.
338
+ *
339
+ * Apply pass creates nodes (with parent_ref / parent_id auto-edges from
340
+ * inference; failed inference = warning, never fabrication),
341
+ * then creates the explicit edges. If ANY apply step throws, rolls back
342
+ * every already-applied node + edge so the graph is bit-for-bit identical
343
+ * to the pre-call state.
344
+ */
345
+ declare function batchCreateNodes(store: UPGFileStore, args: BatchCreateArgs): BatchCreateResult;
346
+ interface MigrateNodeTypeArgs {
347
+ node_id: string;
348
+ new_type: string;
349
+ }
350
+ type MigrateNodeTypeResult = {
351
+ migrated: true;
352
+ node_id: string;
353
+ from_type: string;
354
+ to_type: string;
355
+ edges_rewritten: Array<{
356
+ id: string;
357
+ from: string;
358
+ to: string;
359
+ }>;
360
+ warning?: string;
361
+ } | {
362
+ migrated: false;
363
+ error: string;
364
+ suggestions?: string[];
365
+ };
366
+ /**
367
+ * Atomically change a single node's entity type and rewrite every incident
368
+ * edge to its new canonical edge type derived from the catalog.
369
+ *
370
+ * Distinct from `migrate_type` (which rewrites EVERY node of a given type
371
+ * across the whole graph by string substitution). This single-node variant
372
+ * re-infers each affected edge from (source.type, target.type) via
373
+ * `inferEdgeTypeWithTier`, so it preserves correctness when the graph mixes
374
+ * canonical and deprecated types.
375
+ *
376
+ * If any incident edge cannot be re-inferred, the entire migration is
377
+ * rejected and the graph is left unchanged. Rollback restores both the
378
+ * node's original type and any edges already rewritten.
379
+ */
380
+ declare function migrateNodeType(store: UPGFileStore, args: MigrateNodeTypeArgs): MigrateNodeTypeResult;
381
+
382
+ interface UPGClientOptions {
383
+ /** Path to the .upg file on disk. */
384
+ file: string;
385
+ /**
386
+ * Skip auto-load on first operation (default: false).
387
+ * When true, call `await client.load()` manually before any operation.
388
+ */
389
+ lazy?: boolean;
390
+ }
391
+ interface NodeListOptions extends ListNodesOptions {
392
+ }
393
+ interface EdgeListOptions {
394
+ source?: string;
395
+ target?: string;
396
+ type?: UPGEdgeType;
397
+ }
398
+ interface HealthResult {
399
+ score: number;
400
+ digest: GraphDigest;
401
+ }
402
+ interface SearchOptions {
403
+ /** Maximum number of results (default: 20). */
404
+ limit?: number;
405
+ /** Restrict to a single entity type. */
406
+ type?: string;
407
+ }
408
+ declare class UPGClient {
409
+ private readonly options;
410
+ private store;
411
+ private loadPromise;
412
+ /** Node operations namespace. */
413
+ readonly nodes: NodesAPI;
414
+ /** Edge operations namespace. */
415
+ readonly edges: EdgesAPI;
416
+ constructor(options: UPGClientOptions);
417
+ /**
418
+ * Load the .upg file. Called automatically on first operation unless
419
+ * `{ lazy: true }` was set. Safe to call multiple times — repeated calls
420
+ * are coalesced. If load fails (transient I/O, parse error, etc.) the
421
+ * promise is rejected AND the cached promise is cleared, so the next
422
+ * call retries from scratch rather than re-throwing the stale error.
423
+ */
424
+ load(): Promise<void>;
425
+ /** Internal: get the loaded store, loading on demand. */
426
+ getStore(): Promise<UPGFileStore>;
427
+ /** Persist pending changes to disk. Called automatically after mutations. */
428
+ flush(): Promise<void>;
429
+ /** Compute a health score (0–100) plus the underlying graph digest. */
430
+ health(): Promise<HealthResult>;
431
+ /** Search nodes by free-text query. */
432
+ search(query: string, options?: SearchOptions): Promise<SearchResult[]>;
433
+ /**
434
+ * Verify integrity of the loaded graph. Returns the integrity report from
435
+ * the last load + any in-memory mutation checks. `null` indicates no
436
+ * integrity issues were detected.
437
+ */
438
+ verify(): Promise<IntegrityReport | null>;
439
+ /**
440
+ * Diff against a previous version. Not yet implemented — tracked in
441
+ * UPG-541 follow-up. Will return a structured changeset between the
442
+ * current graph and the named ref (git revision or snapshot id).
443
+ */
444
+ diff(_ref: string): Promise<never>;
445
+ /** Release file watchers and free resources. */
446
+ close(): Promise<void>;
447
+ }
448
+ declare class NodesAPI {
449
+ private readonly client;
450
+ constructor(client: UPGClient);
451
+ /**
452
+ * Create a node. The `type` is validated against the UPG entity catalog —
453
+ * deprecated aliases are accepted with a warning, genuinely unknown types
454
+ * throw `UnknownEntityTypeError`.
455
+ */
456
+ create(args: CreateNodeArgs): Promise<CreateNodeResult>;
457
+ /** List nodes, optionally filtered by type / status / tag. */
458
+ list(options?: NodeListOptions): Promise<ListNodesResult>;
459
+ /** Get a single node by id. Returns `undefined` if not found. */
460
+ get(id: string): Promise<UPGBaseNode | undefined>;
461
+ /** Update a node by id. Returns the updated node. */
462
+ update(id: string, patch: Partial<UPGBaseNode>): Promise<UPGBaseNode>;
463
+ /** Delete a node and all incident edges. */
464
+ delete(id: string): Promise<DeleteNodeResult>;
465
+ }
466
+ declare class EdgesAPI {
467
+ private readonly client;
468
+ constructor(client: UPGClient);
469
+ /**
470
+ * Connect two nodes with an edge. Edge type is inferred from source +
471
+ * target entity types if not provided.
472
+ */
473
+ connect(sourceId: string, targetId: string, opts?: Partial<CreateEdgeArgs>): Promise<CreateEdgeResult>;
474
+ /** List edges, optionally filtered by source / target / type. */
475
+ list(options?: EdgeListOptions): Promise<UPGEdge[]>;
476
+ /** Delete an edge by id. */
477
+ delete(id: string): Promise<DeleteEdgeResult>;
478
+ }
479
+
480
+ /**
481
+ * Workspace bootstrap. Holds the core logic for `init_workspace`.
482
+ *
483
+ * Extracted from server.ts so the bug-prone fs choreography can be unit-tested
484
+ * against real tmp directories without booting the MCP transport.
485
+ *
486
+ * Two bugs this module guards against:
487
+ * 1. `readdir` returning the `.upg` workspace directory itself as an entry
488
+ * that "ends with .upg". The old code tried to rename `.upg → .upg/.upg`
489
+ * and crashed with EINVAL.
490
+ * 2. A user who already organised their `.upg` files inside `.upg/` re-running
491
+ * `init_workspace` from the project root. The old code found nothing at
492
+ * root and registered an empty product list. The new code discovers
493
+ * pre-existing siblings and registers them non-destructively.
494
+ */
495
+
496
+ interface WorkspaceProduct {
497
+ file: string;
498
+ title: string;
499
+ }
500
+ interface InitWorkspaceArgs {
501
+ cwd: string;
502
+ store: UPGFileStore;
503
+ moveExisting?: boolean;
504
+ }
505
+ interface InitWorkspaceResult {
506
+ workspace_path: string;
507
+ default_product: string;
508
+ products: WorkspaceProduct[];
509
+ current_product: {
510
+ title: string;
511
+ entities: number;
512
+ };
513
+ }
514
+ declare class WorkspaceAlreadyExistsError extends Error {
515
+ constructor();
516
+ }
517
+ declare class WorkspaceNotInitialisedError extends Error {
518
+ constructor();
519
+ }
520
+ declare class InvalidProductNameError extends Error {
521
+ constructor(reason: string);
522
+ }
523
+ /**
524
+ * Thrown when a `create_product` / product-stage write attempts to
525
+ * persist a non-canonical UPGProductStage value. Surfaces the canonical set
526
+ * + any documented coercion target in the error message so the caller can
527
+ * fix the input.
528
+ */
529
+ declare class InvalidProductStageError extends Error {
530
+ constructor(message: string);
531
+ }
532
+ declare function initWorkspace({ cwd, store, moveExisting, }: InitWorkspaceArgs): Promise<InitWorkspaceResult>;
533
+ interface CreateProductArgs {
534
+ cwd: string;
535
+ store: UPGFileStore;
536
+ name: string;
537
+ slug?: string;
538
+ description?: string;
539
+ stage?: UPGProductStage;
540
+ /**
541
+ * Optional portfolio node id in the CURRENT loaded store. When provided,
542
+ * a `product` node + `portfolio_contains_product` edge are created in the
543
+ * current store to express the hierarchy (portfolios live in a single .upg).
544
+ */
545
+ portfolio_id?: string;
546
+ }
547
+ interface CreateProductResult {
548
+ id: string;
549
+ file: string;
550
+ slug: string;
551
+ title: string;
552
+ workspace_path: string;
553
+ portfolio_attached: boolean;
554
+ }
555
+ declare function createProduct(args: CreateProductArgs): Promise<CreateProductResult>;
556
+
557
+ /**
558
+ * Portfolio routing helpers — UPG-526.
559
+ *
560
+ * Portfolio-scoped entity types (`portfolio`, `organization`, `product_area`)
561
+ * belong in `.upg/portfolio.upg` rather than the active product's `nodes[]`.
562
+ * These helpers centralise:
563
+ * - resolving the portfolio path for the current workspace
564
+ * - loading-or-initialising the portfolio store
565
+ * - appending entities to the right portfolio array (or setting the
566
+ * singleton `organization`)
567
+ * - reading portfolio-scoped entities back out in a shape compatible with
568
+ * `list_portfolios` / `list_product_areas`
569
+ *
570
+ * The set of portfolio-scoped types is intentionally narrow and documented as
571
+ * `PORTFOLIO_SCOPED_TYPES`. Adding a new type means:
572
+ * 1. extending `UPGPortfolioDocument` in `@unified-product-graph/core`
573
+ * 2. extending this set
574
+ * 3. extending the write-routing switch in `writePortfolioScopedNode`
575
+ */
576
+
577
+ /** Entity types that live in `.upg/portfolio.upg` instead of a product graph. */
578
+ declare const PORTFOLIO_SCOPED_TYPES: ReadonlySet<string>;
579
+ /** Default portfolio filename within a `.upg/` workspace. */
580
+ declare const PORTFOLIO_FILENAME = "portfolio.upg";
581
+ /** True when the entity type belongs in the portfolio document. */
582
+ declare function isPortfolioScopedType(type: string): boolean;
583
+ /**
584
+ * Resolve the portfolio file path for the current workspace.
585
+ * Returns null if the cwd has no `.upg/` directory.
586
+ */
587
+ declare function resolvePortfolioPath(cwd: string): string | null;
588
+ /**
589
+ * Resolve the portfolio file path, creating the `.upg/` directory if it does
590
+ * not exist. Used by write paths that need to mint a portfolio document on
591
+ * demand (e.g. first portfolio entity in a workspace that has product files but
592
+ * never had a portfolio doc).
593
+ */
594
+ declare function resolveOrCreatePortfolioPath(cwd: string): string;
595
+ /**
596
+ * Open (or initialise) the portfolio store at the given path. Caller is
597
+ * responsible for `flush()`-ing the store when the mutation is committed.
598
+ */
599
+ declare function openPortfolioStore(portfolioPath: string): Promise<UPGPortfolioStore>;
600
+ interface WritePortfolioNodeArgs {
601
+ /** Entity type — must satisfy `isPortfolioScopedType`. */
602
+ type: string;
603
+ /** Title for the new entity (or the organisation). */
604
+ title: string;
605
+ description?: string;
606
+ /** Free-form properties; subset is hoisted onto the typed shape per type. */
607
+ properties?: Record<string, unknown>;
608
+ /** When type === 'organization', allow overwriting an existing org. */
609
+ overwrite_organization?: boolean;
610
+ }
611
+ interface WritePortfolioNodeResult {
612
+ /** Persisted entity shape (the typed record actually written). */
613
+ entity: UPGPortfolio | UPGProductArea | UPGOrganization;
614
+ /** Where in the portfolio document the entity was written. */
615
+ written_to: 'portfolios' | 'product_areas' | 'organization';
616
+ /** Absolute portfolio file path. */
617
+ portfolio_file: string;
618
+ /** Optional warning (e.g. organization overwrite happened). */
619
+ warning?: string;
620
+ }
621
+ declare class PortfolioRoutingError extends Error {
622
+ constructor(message: string);
623
+ }
624
+ /**
625
+ * Append (or set, for `organization`) a portfolio-scoped entity into the
626
+ * portfolio document. Creates `.upg/portfolio.upg` on demand.
627
+ *
628
+ * For `organization`: refuses to overwrite an existing org unless
629
+ * `overwrite_organization: true` is supplied. The auto-generated org from
630
+ * `loadOrInit` is treated as a placeholder (org id starting with `org_` and
631
+ * title "Portfolio") and may be replaced silently.
632
+ */
633
+ declare function writePortfolioScopedNode(cwd: string, args: WritePortfolioNodeArgs): Promise<WritePortfolioNodeResult>;
634
+ /**
635
+ * Open the portfolio store if one exists. Returns null when there is no
636
+ * workspace and no portfolio document on disk — callers use this to render an
637
+ * empty list instead of erroring.
638
+ */
639
+ declare function openPortfolioStoreIfExists(cwd: string): Promise<UPGPortfolioStore | null>;
640
+ /**
641
+ * A lightweight product reference recorded on the portfolio document. The full
642
+ * UPG spec (`UPGPortfolioDocument.products`) types this slot as
643
+ * `UPGProduct & { nodes; edges }`, but the MCP model keeps each product in its
644
+ * own `.upg` file. The reference shape below carries just enough to look the
645
+ * product up later (id + file_path) plus a denormalised title for human-
646
+ * readable listings.
647
+ *
648
+ * If/when the spec adds an explicit `UPGProductReference` type, this shape
649
+ * should migrate to it. The fields are intentionally additive — every field
650
+ * other than `id` is optional so the record stays forward-compatible.
651
+ */
652
+ interface PortfolioProductReference {
653
+ id: string;
654
+ /** Workspace-relative path to the product's `.upg` file, when known. */
655
+ file_path?: string;
656
+ /** Display title; denormalised from the product's own `product.title`. */
657
+ title?: string;
658
+ }
659
+ /**
660
+ * Ensure that a product is registered on `portfolio.upg.products[]`. No-op when
661
+ * an entry with the same `id` already exists. Does NOT flush — caller flushes
662
+ * once after registering both source and target products in a single pass.
663
+ *
664
+ * @returns true when a new entry was appended, false when already present.
665
+ */
666
+ declare function registerProductOnPortfolio(doc: UPGPortfolioDocument, ref: PortfolioProductReference): boolean;
667
+ /**
668
+ * Best-effort lookup of a product's `.upg` file and title given its product
669
+ * id. Walks the workspace `.upg/` directory looking for a file whose
670
+ * `product.id` matches. Returns null when not found or when the workspace
671
+ * lookup fails.
672
+ */
673
+ declare function findProductFileById(cwd: string, productId: string): {
674
+ file_path: string;
675
+ title: string;
676
+ } | null;
677
+
678
+ export { BUSINESS_AREAS, type BatchCreateArgs, type BatchCreateFail, type BatchCreateOk, type BatchCreateResult, type BatchEdgeInput, type BatchMoveNodesOutcome, type BatchMoveNodesResult, type BatchNodeInput, CHAINS, type CoverageRegion, type CoverageStageSummary, type CreateEdgeArgs, type CreateEdgeResult, type CreateNodeArgs, type CreateNodeResult, type CreateProductArgs, type CreateProductResult, type DeleteEdgeResult, type DeleteNodeResult, type EdgeListOptions, type GetNodeResult, type GetNodesResult, type GraphDigest, type HealthResult, type InitWorkspaceArgs, type InitWorkspaceResult, IntegrityReport, InvalidProductNameError, InvalidProductStageError, LIFECYCLE_PHASES, type ListNodesOptions, type ListNodesResult, type MigrateNodeTypeArgs, type MigrateNodeTypeResult, type MoveNodeArgs, type MoveNodeResult, type NodeListOptions, PORTFOLIO_FILENAME, PORTFOLIO_SCOPED_TYPES, type PortfolioProductReference, PortfolioRoutingError, STAGE_COVERAGE_TARGETS, type SearchOptions, type SearchResult, UPGClient, type UPGClientOptions, UPGFileStore, UPGPortfolioStore, WorkspaceAlreadyExistsError, WorkspaceNotInitialisedError, type WorkspaceProduct, type WritePortfolioNodeArgs, type WritePortfolioNodeResult, autoFillSlug, batchCreateNodes, batchMoveNodes, computeGraphDigest, computeHealthScore, createEdge, createNode, createProduct, deleteEdge, deleteNode, findProductFileById, getDefaultStatus, getNode, getNodes, getOrphans, initWorkspace, isPortfolioScopedType, listNodes, migrateNodeType, moveNode, normalizeTags, openPortfolioStore, openPortfolioStoreIfExists, registerProductOnPortfolio, resolveCoverageStage, resolveOrCreatePortfolioPath, resolvePortfolioPath, searchNodes, sortByType, typeSortPriority, validateStatusAgainstLifecycle, writePortfolioScopedNode };