@unified-product-graph/mcp-server 0.8.4 → 0.8.6

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/CHANGELOG.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  All notable changes to `@unified-product-graph/mcp-server` are documented in this file. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
4
4
 
5
+ ## [0.8.5] - 2026-06-02
6
+
7
+ Field-report fast-follow (tester report on 0.8.4).
8
+
9
+ ### Fixed
10
+ - `skill_audit` no longer false-reports skills as out of sync on npm/npx installs. It resolved the canonical source from `process.cwd()/packages/upg-mcp-server/skills` — a monorepo-only path absent in a user's project — so every deployed skill came back unverifiable. It now resolves the skills bundled in the installed package (relative to the module), and treats a symlink to a byte-identical bundle, or a matching copy, as healthy: content match, not deployment method, is the signal.
11
+
12
+ ### Changed
13
+ - The `prioritise` `type_mismatch` hint now points to the framework_exercise escape hatch (`apply_framework` / `upg apply`, then prioritise with `exercise_id`), so scoring a non-target entity type is discoverable.
14
+
5
15
  ## [0.8.4] - 2026-06-02
6
16
 
7
17
  Framework exercises, with the 0.8.3 launch fix folded in.
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { UPGFileStore } from "@unified-product-graph/sdk";
10
10
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
11
11
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
12
  import fs2 from "fs";
13
- import { fileURLToPath } from "url";
13
+ import { fileURLToPath as fileURLToPath2 } from "url";
14
14
  import * as path5 from "path";
15
15
  import {
16
16
  CallToolRequestSchema,
@@ -12334,6 +12334,16 @@ var UPG_FRAMEWORKS = [
12334
12334
  "label": "Items to score",
12335
12335
  "entityTypeId": "feature",
12336
12336
  "description": "Features, opportunities, or solutions being evaluated"
12337
+ },
12338
+ {
12339
+ "label": "Opportunities to score",
12340
+ "entityTypeId": "opportunity",
12341
+ "description": "Opportunities scored on the same RICE Scoring inputs as features."
12342
+ },
12343
+ {
12344
+ "label": "Needs to score",
12345
+ "entityTypeId": "need",
12346
+ "description": "Needs scored on the same RICE Scoring inputs as features."
12337
12347
  }
12338
12348
  ],
12339
12349
  "data": {
@@ -12341,6 +12351,14 @@ var UPG_FRAMEWORKS = [
12341
12351
  {
12342
12352
  "type": "feature",
12343
12353
  "role": "scored_item"
12354
+ },
12355
+ {
12356
+ "type": "opportunity",
12357
+ "role": "scored_item"
12358
+ },
12359
+ {
12360
+ "type": "need",
12361
+ "role": "scored_item"
12344
12362
  }
12345
12363
  ],
12346
12364
  "required_properties": {
@@ -12381,6 +12399,82 @@ var UPG_FRAMEWORKS = [
12381
12399
  "label": "Effort",
12382
12400
  "description": "How much work is required to build and ship this, on the effort scale?"
12383
12401
  }
12402
+ ],
12403
+ "opportunity": [
12404
+ {
12405
+ "property": "reach",
12406
+ "type": "assessment",
12407
+ "scale_id": "reach_5",
12408
+ "required": true,
12409
+ "scope": "framework",
12410
+ "label": "Reach",
12411
+ "description": "How many users will this impact per quarter?"
12412
+ },
12413
+ {
12414
+ "property": "impact",
12415
+ "type": "assessment",
12416
+ "scale_id": "impact_5",
12417
+ "required": true,
12418
+ "scope": "framework",
12419
+ "label": "Impact",
12420
+ "description": "How much will this impact each user, on the impact scale?"
12421
+ },
12422
+ {
12423
+ "property": "confidence",
12424
+ "type": "assessment",
12425
+ "scale_id": "confidence_5",
12426
+ "required": true,
12427
+ "scope": "framework",
12428
+ "label": "Confidence",
12429
+ "description": "How confident are you in the reach, impact, and effort estimates?"
12430
+ },
12431
+ {
12432
+ "property": "effort",
12433
+ "type": "assessment",
12434
+ "scale_id": "effort_5",
12435
+ "required": true,
12436
+ "scope": "framework",
12437
+ "label": "Effort",
12438
+ "description": "How much work is required to build and ship this, on the effort scale?"
12439
+ }
12440
+ ],
12441
+ "need": [
12442
+ {
12443
+ "property": "reach",
12444
+ "type": "assessment",
12445
+ "scale_id": "reach_5",
12446
+ "required": true,
12447
+ "scope": "framework",
12448
+ "label": "Reach",
12449
+ "description": "How many users will this impact per quarter?"
12450
+ },
12451
+ {
12452
+ "property": "impact",
12453
+ "type": "assessment",
12454
+ "scale_id": "impact_5",
12455
+ "required": true,
12456
+ "scope": "framework",
12457
+ "label": "Impact",
12458
+ "description": "How much will this impact each user, on the impact scale?"
12459
+ },
12460
+ {
12461
+ "property": "confidence",
12462
+ "type": "assessment",
12463
+ "scale_id": "confidence_5",
12464
+ "required": true,
12465
+ "scope": "framework",
12466
+ "label": "Confidence",
12467
+ "description": "How confident are you in the reach, impact, and effort estimates?"
12468
+ },
12469
+ {
12470
+ "property": "effort",
12471
+ "type": "assessment",
12472
+ "scale_id": "effort_5",
12473
+ "required": true,
12474
+ "scope": "framework",
12475
+ "label": "Effort",
12476
+ "description": "How much work is required to build and ship this, on the effort scale?"
12477
+ }
12384
12478
  ]
12385
12479
  },
12386
12480
  "computed_properties": [
@@ -12390,6 +12484,20 @@ var UPG_FRAMEWORKS = [
12390
12484
  "entity_type": "feature",
12391
12485
  "label": "RICE Score",
12392
12486
  "format": "number"
12487
+ },
12488
+ {
12489
+ "property": "rice_score",
12490
+ "expression": "(reach * impact * confidence) / effort",
12491
+ "entity_type": "opportunity",
12492
+ "label": "RICE Score",
12493
+ "format": "number"
12494
+ },
12495
+ {
12496
+ "property": "rice_score",
12497
+ "expression": "(reach * impact * confidence) / effort",
12498
+ "entity_type": "need",
12499
+ "label": "RICE Score",
12500
+ "format": "number"
12393
12501
  }
12394
12502
  ]
12395
12503
  },
@@ -14773,6 +14881,16 @@ var UPG_FRAMEWORKS = [
14773
14881
  "label": "Ease",
14774
14882
  "entityTypeId": "feature",
14775
14883
  "description": "How easy is this to implement?"
14884
+ },
14885
+ {
14886
+ "label": "Opportunities to score",
14887
+ "entityTypeId": "opportunity",
14888
+ "description": "Opportunities scored on the same ICE Scoring inputs as features."
14889
+ },
14890
+ {
14891
+ "label": "Needs to score",
14892
+ "entityTypeId": "need",
14893
+ "description": "Needs scored on the same ICE Scoring inputs as features."
14776
14894
  }
14777
14895
  ],
14778
14896
  "data": {
@@ -14788,6 +14906,14 @@ var UPG_FRAMEWORKS = [
14788
14906
  {
14789
14907
  "type": "assumption",
14790
14908
  "role": "item"
14909
+ },
14910
+ {
14911
+ "type": "opportunity",
14912
+ "role": "scored_item"
14913
+ },
14914
+ {
14915
+ "type": "need",
14916
+ "role": "scored_item"
14791
14917
  }
14792
14918
  ],
14793
14919
  "required_properties": {
@@ -14816,6 +14942,58 @@ var UPG_FRAMEWORKS = [
14816
14942
  "label": "Ease",
14817
14943
  "description": "Ease of implementation (1-10)"
14818
14944
  }
14945
+ ],
14946
+ "opportunity": [
14947
+ {
14948
+ "property": "impact",
14949
+ "type": "number",
14950
+ "required": true,
14951
+ "scope": "framework",
14952
+ "label": "Impact",
14953
+ "description": "Expected impact on the target metric (1-10)"
14954
+ },
14955
+ {
14956
+ "property": "confidence",
14957
+ "type": "number",
14958
+ "required": true,
14959
+ "scope": "framework",
14960
+ "label": "Confidence",
14961
+ "description": "Confidence in the impact estimate (1-10)"
14962
+ },
14963
+ {
14964
+ "property": "ease",
14965
+ "type": "number",
14966
+ "required": true,
14967
+ "scope": "framework",
14968
+ "label": "Ease",
14969
+ "description": "Ease of implementation (1-10)"
14970
+ }
14971
+ ],
14972
+ "need": [
14973
+ {
14974
+ "property": "impact",
14975
+ "type": "number",
14976
+ "required": true,
14977
+ "scope": "framework",
14978
+ "label": "Impact",
14979
+ "description": "Expected impact on the target metric (1-10)"
14980
+ },
14981
+ {
14982
+ "property": "confidence",
14983
+ "type": "number",
14984
+ "required": true,
14985
+ "scope": "framework",
14986
+ "label": "Confidence",
14987
+ "description": "Confidence in the impact estimate (1-10)"
14988
+ },
14989
+ {
14990
+ "property": "ease",
14991
+ "type": "number",
14992
+ "required": true,
14993
+ "scope": "framework",
14994
+ "label": "Ease",
14995
+ "description": "Ease of implementation (1-10)"
14996
+ }
14819
14997
  ]
14820
14998
  },
14821
14999
  "computed_properties": [
@@ -14825,6 +15003,20 @@ var UPG_FRAMEWORKS = [
14825
15003
  "entity_type": "feature",
14826
15004
  "label": "ICE Score",
14827
15005
  "format": "number"
15006
+ },
15007
+ {
15008
+ "property": "ice_score",
15009
+ "expression": "impact * confidence * ease",
15010
+ "entity_type": "opportunity",
15011
+ "label": "ICE Score",
15012
+ "format": "number"
15013
+ },
15014
+ {
15015
+ "property": "ice_score",
15016
+ "expression": "impact * confidence * ease",
15017
+ "entity_type": "need",
15018
+ "label": "ICE Score",
15019
+ "format": "number"
14828
15020
  }
14829
15021
  ]
14830
15022
  },
@@ -14928,6 +15120,11 @@ var UPG_FRAMEWORKS = [
14928
15120
  "label": "Job Size",
14929
15121
  "entityTypeId": "metric",
14930
15122
  "description": "Estimated effort (story points, t-shirt size, or person-weeks)"
15123
+ },
15124
+ {
15125
+ "label": "Opportunities to score",
15126
+ "entityTypeId": "opportunity",
15127
+ "description": "Opportunities scored on the same WSJF (Weighted Shortest Job First) inputs as features."
14931
15128
  }
14932
15129
  ],
14933
15130
  "data": {
@@ -14939,6 +15136,10 @@ var UPG_FRAMEWORKS = [
14939
15136
  {
14940
15137
  "type": "metric",
14941
15138
  "role": "item"
15139
+ },
15140
+ {
15141
+ "type": "opportunity",
15142
+ "role": "scored_item"
14942
15143
  }
14943
15144
  ],
14944
15145
  "required_properties": {
@@ -14975,6 +15176,40 @@ var UPG_FRAMEWORKS = [
14975
15176
  "label": "Job Size",
14976
15177
  "description": "Estimated effort (story points, t-shirt size, or person-weeks)"
14977
15178
  }
15179
+ ],
15180
+ "opportunity": [
15181
+ {
15182
+ "property": "user_value",
15183
+ "type": "number",
15184
+ "required": true,
15185
+ "scope": "framework",
15186
+ "label": "User/Business Value",
15187
+ "description": "Relative value to users and the business if delivered"
15188
+ },
15189
+ {
15190
+ "property": "time_criticality",
15191
+ "type": "number",
15192
+ "required": true,
15193
+ "scope": "framework",
15194
+ "label": "Time Criticality",
15195
+ "description": "How much value decays if delivery is delayed (deadlines, competition, seasonal windows)"
15196
+ },
15197
+ {
15198
+ "property": "risk_reduction",
15199
+ "type": "number",
15200
+ "required": true,
15201
+ "scope": "framework",
15202
+ "label": "Risk Reduction / Opportunity Enablement",
15203
+ "description": "Value from reducing risk or enabling future opportunities"
15204
+ },
15205
+ {
15206
+ "property": "job_size",
15207
+ "type": "number",
15208
+ "required": true,
15209
+ "scope": "framework",
15210
+ "label": "Job Size",
15211
+ "description": "Estimated effort (story points, t-shirt size, or person-weeks)"
15212
+ }
14978
15213
  ]
14979
15214
  },
14980
15215
  "computed_properties": [
@@ -14984,6 +15219,13 @@ var UPG_FRAMEWORKS = [
14984
15219
  "entity_type": "feature",
14985
15220
  "label": "WSJF Score",
14986
15221
  "format": "number"
15222
+ },
15223
+ {
15224
+ "property": "wsjf_score",
15225
+ "expression": "(user_value + time_criticality + risk_reduction) / job_size",
15226
+ "entity_type": "opportunity",
15227
+ "label": "WSJF Score",
15228
+ "format": "number"
14987
15229
  }
14988
15230
  ]
14989
15231
  },
@@ -15091,6 +15333,11 @@ var UPG_FRAMEWORKS = [
15091
15333
  "label": "Risk Reduction",
15092
15334
  "entityTypeId": "risk",
15093
15335
  "description": "What risk does this mitigate?"
15336
+ },
15337
+ {
15338
+ "label": "Opportunities to score",
15339
+ "entityTypeId": "opportunity",
15340
+ "description": "Opportunities scored on the same Cost of Delay inputs as features."
15094
15341
  }
15095
15342
  ],
15096
15343
  "data": {
@@ -15110,6 +15357,10 @@ var UPG_FRAMEWORKS = [
15110
15357
  {
15111
15358
  "type": "risk",
15112
15359
  "role": "item"
15360
+ },
15361
+ {
15362
+ "type": "opportunity",
15363
+ "role": "scored_item"
15113
15364
  }
15114
15365
  ],
15115
15366
  "required_properties": {
@@ -15130,6 +15381,24 @@ var UPG_FRAMEWORKS = [
15130
15381
  "label": "Job Size",
15131
15382
  "description": "Weeks of development effort"
15132
15383
  }
15384
+ ],
15385
+ "opportunity": [
15386
+ {
15387
+ "property": "cost_of_delay",
15388
+ "type": "number",
15389
+ "required": true,
15390
+ "scope": "framework",
15391
+ "label": "Cost of Delay",
15392
+ "description": "Weekly revenue impact of not shipping"
15393
+ },
15394
+ {
15395
+ "property": "job_size",
15396
+ "type": "number",
15397
+ "required": true,
15398
+ "scope": "framework",
15399
+ "label": "Job Size",
15400
+ "description": "Weeks of development effort"
15401
+ }
15133
15402
  ]
15134
15403
  },
15135
15404
  "computed_properties": [
@@ -15139,6 +15408,13 @@ var UPG_FRAMEWORKS = [
15139
15408
  "entity_type": "feature",
15140
15409
  "label": "WSJF Score",
15141
15410
  "format": "number"
15411
+ },
15412
+ {
15413
+ "property": "wsjf_score",
15414
+ "expression": "cost_of_delay / job_size",
15415
+ "entity_type": "opportunity",
15416
+ "label": "WSJF Score",
15417
+ "format": "number"
15142
15418
  }
15143
15419
  ]
15144
15420
  },
@@ -23728,7 +24004,7 @@ function serializePortfolioWithHeader(doc, opts) {
23728
24004
  header.integrity = { algorithm: INTEGRITY_ALGORITHM, body: computeBodyChecksum(doc) };
23729
24005
  return JSON.stringify({ $upg: header, ...body }, null, 2) + "\n";
23730
24006
  }
23731
- var UPG_VERSION = "0.8.4";
24007
+ var UPG_VERSION = "0.8.6";
23732
24008
  var MARKDOWN_FORMAT_VERSION = "0.1";
23733
24009
  var UPG_TYPES = getTypes();
23734
24010
  var UPG_TYPES_SET = new Set(UPG_TYPES);
@@ -27778,7 +28054,8 @@ var pushToCloud = async (args, ctx) => {
27778
28054
 
27779
28055
  // src/tools/skills.ts
27780
28056
  import { existsSync as existsSync2, lstatSync, readlinkSync, readFileSync as readFileSync2, readdirSync as readdirSync2, realpathSync } from "fs";
27781
- import { join as join5, resolve as resolve2 } from "path";
28057
+ import { join as join5, resolve as resolve2, dirname as dirname3 } from "path";
28058
+ import { fileURLToPath } from "url";
27782
28059
  function repoRoot() {
27783
28060
  return process.cwd();
27784
28061
  }
@@ -27789,8 +28066,40 @@ function canonicalisePath(p) {
27789
28066
  return p;
27790
28067
  }
27791
28068
  }
28069
+ function isSkillsDir(candidate) {
28070
+ try {
28071
+ if (!existsSync2(candidate)) return false;
28072
+ return readdirSync2(candidate, { withFileTypes: true }).some(
28073
+ (e) => (e.isDirectory() || e.isSymbolicLink()) && existsSync2(join5(candidate, e.name, "SKILL.md"))
28074
+ );
28075
+ } catch {
28076
+ return false;
28077
+ }
28078
+ }
28079
+ function resolveBundledSkillsDir() {
28080
+ let md;
28081
+ try {
28082
+ md = dirname3(fileURLToPath(import.meta.url));
28083
+ } catch {
28084
+ md = process.cwd();
28085
+ }
28086
+ for (const c of [resolve2(md, "..", "skills"), resolve2(md, "..", "..", "skills"), resolve2(md, "skills")]) {
28087
+ if (isSkillsDir(c)) return c;
28088
+ }
28089
+ let dir = md;
28090
+ for (let i = 0; i < 12; i++) {
28091
+ const mono = join5(dir, "packages", "upg-mcp-server", "skills");
28092
+ if (isSkillsDir(mono)) return mono;
28093
+ const parent = dirname3(dir);
28094
+ if (parent === dir) break;
28095
+ dir = parent;
28096
+ }
28097
+ return null;
28098
+ }
27792
28099
  function sourceSkillsDir() {
27793
- return resolve2(repoRoot(), "packages/upg-mcp-server/skills");
28100
+ const cwdPath = resolve2(repoRoot(), "packages/upg-mcp-server/skills");
28101
+ if (existsSync2(cwdPath)) return cwdPath;
28102
+ return resolveBundledSkillsDir() ?? cwdPath;
27794
28103
  }
27795
28104
  function deployedSkillsDir() {
27796
28105
  return resolve2(repoRoot(), ".claude/skills");
@@ -27833,6 +28142,21 @@ function auditOne(name) {
27833
28142
  const deployedExists = existsSync2(deployedPath);
27834
28143
  if (!sourceExists) issues.push("Canonical source SKILL.md is missing");
27835
28144
  if (!deployedExists) issues.push("Deployed SKILL.md is missing; run ./scripts/link-skills.sh");
28145
+ let inSync = false;
28146
+ let deployedFrontmatter = null;
28147
+ let deployedFirstHeading = null;
28148
+ if (deployedExists) {
28149
+ const deployedBody = readFileSync2(deployedPath, "utf8");
28150
+ deployedFrontmatter = parseFrontmatter(deployedBody);
28151
+ deployedFirstHeading = firstHeading(deployedBody);
28152
+ if (sourceExists) {
28153
+ const sourceBody = readFileSync2(sourcePath, "utf8");
28154
+ inSync = deployedBody === sourceBody;
28155
+ if (!inSync) {
28156
+ issues.push("Deployed SKILL.md differs from canonical source; symlink is stale or broken");
28157
+ }
28158
+ }
28159
+ }
27836
28160
  let isSymlink = false;
27837
28161
  let symlinkTarget = null;
27838
28162
  if (existsSync2(deployedDir)) {
@@ -27840,32 +28164,17 @@ function auditOne(name) {
27840
28164
  isSymlink = stat.isSymbolicLink();
27841
28165
  if (isSymlink) {
27842
28166
  symlinkTarget = readlinkSync(deployedDir);
27843
- const targetReal = canonicalisePath(symlinkTarget);
27844
- const expectedReal = canonicalisePath(sourceDir);
27845
- if (targetReal !== expectedReal) {
27846
- issues.push(`Symlink points to ${symlinkTarget}, expected ${sourceDir}`);
28167
+ if (!inSync && sourceExists) {
28168
+ const targetReal = canonicalisePath(symlinkTarget);
28169
+ const expectedReal = canonicalisePath(sourceDir);
28170
+ if (targetReal !== expectedReal) {
28171
+ issues.push(`Symlink points to ${symlinkTarget}, expected ${sourceDir}`);
28172
+ }
27847
28173
  }
27848
- } else if (deployedExists) {
28174
+ } else if (deployedExists && !inSync) {
27849
28175
  issues.push("Deployed entry is a real directory, not a symlink; stale copy will not pick up source updates; run ./scripts/link-skills.sh");
27850
28176
  }
27851
28177
  }
27852
- let inSync = false;
27853
- let deployedFrontmatter = null;
27854
- let deployedFirstHeading = null;
27855
- if (deployedExists && sourceExists) {
27856
- const deployedBody = readFileSync2(deployedPath, "utf8");
27857
- const sourceBody = readFileSync2(sourcePath, "utf8");
27858
- inSync = deployedBody === sourceBody;
27859
- deployedFrontmatter = parseFrontmatter(deployedBody);
27860
- deployedFirstHeading = firstHeading(deployedBody);
27861
- if (!inSync) {
27862
- issues.push("Deployed SKILL.md differs from canonical source; symlink is stale or broken");
27863
- }
27864
- } else if (deployedExists) {
27865
- const deployedBody = readFileSync2(deployedPath, "utf8");
27866
- deployedFrontmatter = parseFrontmatter(deployedBody);
27867
- deployedFirstHeading = firstHeading(deployedBody);
27868
- }
27869
28178
  return {
27870
28179
  name,
27871
28180
  deployed_path: deployedPath,
@@ -29417,7 +29726,7 @@ var SERVER_INSTRUCTIONS = [
29417
29726
  ].join("\n");
29418
29727
  function resolvePackageVersion() {
29419
29728
  try {
29420
- const here = path5.dirname(fileURLToPath(import.meta.url));
29729
+ const here = path5.dirname(fileURLToPath2(import.meta.url));
29421
29730
  const pkgPath = path5.resolve(here, "..", "package.json");
29422
29731
  const raw = fs2.readFileSync(pkgPath, "utf-8");
29423
29732
  const pkg = JSON.parse(raw);
@@ -29475,7 +29784,7 @@ function createServer(store) {
29475
29784
 
29476
29785
  // src/index.ts
29477
29786
  import { nanoid } from "nanoid";
29478
- import { fileURLToPath as fileURLToPath2 } from "url";
29787
+ import { fileURLToPath as fileURLToPath3 } from "url";
29479
29788
  import { realpathSync as realpathSync2 } from "fs";
29480
29789
  async function discoverUPGFile(explicitFile) {
29481
29790
  if (explicitFile) return path6.resolve(explicitFile);
@@ -29649,7 +29958,7 @@ ${lines.join("\n")}
29649
29958
  function isEntrypoint() {
29650
29959
  if (!process.argv[1]) return false;
29651
29960
  try {
29652
- return realpathSync2(process.argv[1]) === realpathSync2(fileURLToPath2(import.meta.url));
29961
+ return realpathSync2(process.argv[1]) === realpathSync2(fileURLToPath3(import.meta.url));
29653
29962
  } catch {
29654
29963
  return false;
29655
29964
  }