product-spec-mcp 0.3.19 → 0.3.21

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.
Files changed (3) hide show
  1. package/README.md +10 -5
  2. package/dist/index.cjs +101 -17
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -19,11 +19,6 @@
19
19
 
20
20
  **不确定用哪个工具?** 先用 `product_spec_assist`,它会自动识别场景并调用合适的工具。
21
21
 
22
- ## 维护索引
23
-
24
- - 更新 MCP 本体前,先看 [product-spec-mcp 更新经验索引](docs/product-spec-mcp-update-lessons.md)。
25
- - WorkBuddy 接入、运行环境和副本同步问题仍记录在 [WorkBuddy 坑点记录](docs/workbuddy-mcp-pitfalls.md)。
26
-
27
22
  ## Features
28
23
 
29
24
  This MCP Server provides 7 tools for product development workflow:
@@ -130,6 +125,16 @@ Add to `~/.config/opencode/opencode.json`:
130
125
 
131
126
  > Note: opencode uses the `mcp` key. `mcpServers` is a Claude-style config key and will fail schema validation in current opencode versions.
132
127
 
128
+ ## FAQ
129
+
130
+ ### Where are maintainer notes?
131
+
132
+ If you plan to modify this MCP server itself, read the maintainer notes first:
133
+
134
+ - [product-spec-mcp update lessons](https://github.com/georgelue0321-vibe/product-spec-mcp/blob/main/docs/product-spec-mcp-update-lessons.md)
135
+
136
+ Client-specific integration notes are intentionally kept out of the main user flow. They live under `docs/` in the GitHub repository for maintainers who need them.
137
+
133
138
  ## Tools Documentation
134
139
 
135
140
  ### product_spec_assist (推荐入口)
package/dist/index.cjs CHANGED
@@ -7,7 +7,11 @@ var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
9
  var __commonJS = (cb, mod) => function __require() {
10
- return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
10
+ try {
11
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
12
+ } catch (e) {
13
+ throw mod = 0, e;
14
+ }
11
15
  };
12
16
  var __export = (target, all) => {
13
17
  for (var name in all)
@@ -6936,7 +6940,6 @@ function $constructor(name, initializer3, params) {
6936
6940
  Object.defineProperty(_, "name", { value: name });
6937
6941
  return _;
6938
6942
  }
6939
- var $brand = Symbol("zod_brand");
6940
6943
  var $ZodAsyncError = class extends Error {
6941
6944
  constructor() {
6942
6945
  super(`Encountered Promise during synchronous parse. Use .parseAsync() instead.`);
@@ -9440,8 +9443,6 @@ function en_default() {
9440
9443
  }
9441
9444
 
9442
9445
  // node_modules/zod/v4/core/registries.js
9443
- var $output = Symbol("ZodOutput");
9444
- var $input = Symbol("ZodInput");
9445
9446
  var $ZodRegistry = class {
9446
9447
  constructor() {
9447
9448
  this._map = /* @__PURE__ */ new Map();
@@ -16838,7 +16839,7 @@ ZodNaN.create = (params) => {
16838
16839
  ...processCreateParams(params)
16839
16840
  });
16840
16841
  };
16841
- var BRAND = Symbol("zod_brand");
16842
+ var BRAND = /* @__PURE__ */ Symbol("zod_brand");
16842
16843
  var ZodBranded = class extends ZodType2 {
16843
16844
  _parse(input) {
16844
16845
  const { ctx } = this._processInputParams(input);
@@ -17247,7 +17248,7 @@ function isTerminal(status) {
17247
17248
  }
17248
17249
 
17249
17250
  // node_modules/zod-to-json-schema/dist/esm/Options.js
17250
- var ignoreOverride = Symbol("Let zodToJsonSchema decide on which parser to use");
17251
+ var ignoreOverride = /* @__PURE__ */ Symbol("Let zodToJsonSchema decide on which parser to use");
17251
17252
  var defaultOptions = {
17252
17253
  name: void 0,
17253
17254
  $refStrategy: "root",
@@ -20223,7 +20224,7 @@ var Server = class extends Protocol {
20223
20224
  };
20224
20225
 
20225
20226
  // node_modules/@modelcontextprotocol/sdk/dist/esm/server/completable.js
20226
- var COMPLETABLE_SYMBOL = Symbol.for("mcp.completable");
20227
+ var COMPLETABLE_SYMBOL = /* @__PURE__ */ Symbol.for("mcp.completable");
20227
20228
  function isCompletable(schema) {
20228
20229
  return !!schema && typeof schema === "object" && COMPLETABLE_SYMBOL in schema;
20229
20230
  }
@@ -23537,6 +23538,79 @@ var SpecCompileOutputSchema = external_exports.object({
23537
23538
  })
23538
23539
  });
23539
23540
 
23541
+ // src/core/localToolSignals.ts
23542
+ function buildLocalToolSignalProfile(text) {
23543
+ const recordObject = extractRecordObject(text);
23544
+ const fieldLabels = buildFieldLabels(text, recordObject);
23545
+ const featureHints = buildFeatureHints(text, recordObject);
23546
+ const acceptanceItems = buildAcceptanceItems(text, recordObject, fieldLabels);
23547
+ return {
23548
+ recordObject,
23549
+ fieldExample: fieldLabels.join("\u3001"),
23550
+ featureHints,
23551
+ acceptanceItems
23552
+ };
23553
+ }
23554
+ function extractRecordObject(text) {
23555
+ if (/药品|药箱|药/.test(text)) return "\u836F\u54C1";
23556
+ const patterns = [
23557
+ /(?:做一个|做个|开发一个|创建一个|想做一个|想做个)([^,。,.;;]{1,16}?)(?:管理工具|提醒工具|记录工具|清单|小工具|页面|网页|HTML)/i,
23558
+ /记录(?:家里有哪些)?([^,。,.;;、和]{1,12})/
23559
+ ];
23560
+ for (const pattern of patterns) {
23561
+ const match = text.match(pattern);
23562
+ const value = cleanObjectLabel(match?.[1]);
23563
+ if (value) return value;
23564
+ }
23565
+ return "\u8BB0\u5F55";
23566
+ }
23567
+ function cleanObjectLabel(value) {
23568
+ if (!value) return "";
23569
+ return value.replace(/^(家庭|个人|家里|我的|一个|一款)/, "").replace(/(管理|提醒|记录|清单|工具|页面|网页|HTML)$/i, "").trim();
23570
+ }
23571
+ function buildFieldLabels(text, recordObject) {
23572
+ const labels = [`${recordObject}\u540D`];
23573
+ addIf(labels, "\u6570\u91CF/\u5E93\u5B58", /数量|库存|余量|剩余|补货/.test(text));
23574
+ addIf(labels, "\u6709\u6548\u671F/\u5230\u671F\u65E5", /有效期|过期|临期|到期|截止|保质期|续费|提醒/.test(text));
23575
+ addIf(labels, "\u5206\u7C7B", /分类|类别|标签|类型/.test(text));
23576
+ addIf(labels, "\u5B58\u653E\u4F4D\u7F6E", /位置|存放|放在|地点|地址/.test(text));
23577
+ addIf(labels, "\u72B6\u6001", /状态|进度|已完成|未完成|正常|异常/.test(text));
23578
+ addIf(labels, "\u91D1\u989D/\u4EF7\u683C", /金额|价格|费用|预算|保费/.test(text));
23579
+ addIf(labels, "\u94FE\u63A5", /链接|网址|URL/i.test(text));
23580
+ labels.push("\u5907\u6CE8");
23581
+ return Array.from(new Set(labels));
23582
+ }
23583
+ function buildFeatureHints(text, recordObject) {
23584
+ const hints = [];
23585
+ if (/记录|管理|保存|清单|列表/.test(text)) hints.push(`${recordObject}\u8BB0\u5F55\u7BA1\u7406`);
23586
+ if (/新增|添加|编辑|删除|增删改查|CRUD/i.test(text)) hints.push("\u65B0\u589E/\u7F16\u8F91/\u5220\u9664");
23587
+ if (/搜索|筛选|分类|标签|查询/.test(text)) hints.push("\u641C\u7D22/\u7B5B\u9009/\u5206\u7C7B");
23588
+ if (/提醒|到期|过期|临期|快过期|截止|倒计时|保质期|续费/.test(text)) hints.push("\u5230\u671F/\u8FC7\u671F\u63D0\u9192");
23589
+ if (/数量|库存|余量|剩余|补货/.test(text)) hints.push("\u6570\u91CF/\u5E93\u5B58\u7BA1\u7406");
23590
+ if (/高级|好看|美观|视觉|界面|页面|UI|ui|响应式/.test(text)) hints.push("\u9AD8\u7EA7\u754C\u9762\u4E0E\u54CD\u5E94\u5F0F\u5E03\u5C40");
23591
+ return Array.from(new Set(hints));
23592
+ }
23593
+ function buildAcceptanceItems(text, recordObject, fieldLabels) {
23594
+ const items = [];
23595
+ if (/记录|管理|保存|清单|列表/.test(text)) {
23596
+ items.push(`${recordObject}\u8BB0\u5F55\u80FD\u4FDD\u5B58${fieldLabels.join("\u3001")}`);
23597
+ }
23598
+ if (/提醒|到期|过期|临期|快过期|截止|保质期|续费/.test(text)) {
23599
+ items.push("\u63D0\u9192\u5217\u8868\u80FD\u6309\u65E5\u671F\u6392\u5E8F\uFF0C\u5DF2\u8FC7\u671F\u3001\u5373\u5C06\u5230\u671F\u548C\u6B63\u5E38\u72B6\u6001\u6E05\u6670");
23600
+ items.push("\u7F16\u8F91\u6216\u5220\u9664\u8BB0\u5F55\u540E\uFF0C\u63D0\u9192\u5217\u8868\u540C\u6B65\u66F4\u65B0");
23601
+ }
23602
+ if (/数量|库存|余量|剩余|补货/.test(text)) {
23603
+ items.push("\u6570\u91CF\u6216\u5E93\u5B58\u53D8\u5316\u540E\uFF0C\u5217\u8868\u3001\u8BE6\u60C5\u548C\u63D0\u9192\u72B6\u6001\u540C\u6B65\u66F4\u65B0");
23604
+ }
23605
+ if (/高级|好看|美观|视觉|界面|页面|UI|ui|响应式/.test(text)) {
23606
+ items.push("\u9875\u9762\u89C6\u89C9\u98CE\u683C\u4E00\u81F4\uFF0C\u684C\u9762\u7AEF\u548C\u79FB\u52A8\u7AEF\u90FD\u4E0D\u80FD\u51FA\u73B0\u6587\u5B57\u6EA2\u51FA\u6216\u63A7\u4EF6\u91CD\u53E0");
23607
+ }
23608
+ return items;
23609
+ }
23610
+ function addIf(items, label, condition) {
23611
+ if (condition) items.push(label);
23612
+ }
23613
+
23540
23614
  // src/core/promptBuilder.ts
23541
23615
  function buildSpec(rawIdea, context, readiness) {
23542
23616
  const assumptions = [];
@@ -23582,7 +23656,8 @@ function buildSpec(rawIdea, context, readiness) {
23582
23656
  assumptions.push("\u76EE\u6807\u7528\u6237\uFF1A\u9ED8\u8BA4\u4E3A\u4E2A\u4EBA\u7528\u6237");
23583
23657
  }
23584
23658
  const platform = normalizedContext.platform || extractPlatform(rawIdea) || "web";
23585
- const coreFeatures = hasStructuredAnswers ? buildGenericCoreFeatures(normalizedContext, extractFeatures(rawIdea)) : toArray(normalizedContext.core_features, extractFeatures(rawIdea));
23659
+ const extractedFeatures = personalLocalTool ? buildLocalFirstExtractedFeatures(rawIdea) : extractFeatures(rawIdea);
23660
+ const coreFeatures = hasStructuredAnswers ? buildGenericCoreFeatures(normalizedContext, extractedFeatures) : toArray(normalizedContext.core_features, extractedFeatures);
23586
23661
  const isGenericFeatures = coreFeatures.length === 1 && coreFeatures[0] === "\u6838\u5FC3\u529F\u80FD";
23587
23662
  if (isGenericFeatures) {
23588
23663
  assumptions.push("\u6838\u5FC3\u529F\u80FD\uFF1A\u672A\u8BC6\u522B\u5230\u5177\u4F53\u529F\u80FD\uFF0C\u9700\u8981\u7528\u6237\u8865\u5145");
@@ -24745,6 +24820,12 @@ function extractFeatures(text) {
24745
24820
  }
24746
24821
  return features.length > 0 ? features : ["\u6838\u5FC3\u529F\u80FD"];
24747
24822
  }
24823
+ function buildLocalFirstExtractedFeatures(text) {
24824
+ const genericFeatures = extractFeatures(text).filter((feature) => feature !== "\u6838\u5FC3\u529F\u80FD");
24825
+ const signalProfile = buildLocalToolSignalProfile(text);
24826
+ const features = [...genericFeatures, ...signalProfile.featureHints];
24827
+ return features.length > 0 ? Array.from(new Set(features)) : ["\u6838\u5FC3\u529F\u80FD"];
24828
+ }
24748
24829
  function isNegatedKeyword(text, keyword) {
24749
24830
  const escaped = keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24750
24831
  return new RegExp(`(\u4E0D\u63A5|\u4E0D\u505A|\u4E0D\u7528|\u65E0\u9700|\u4E0D\u9700\u8981|\u6682\u4E0D|\u5148\u4E0D|\u4E0D\u8981|\u6CA1\u6709).{0,8}${escaped}`, "i").test(text);
@@ -26649,7 +26730,7 @@ function buildLocalFirstAcceptanceCategories(technicalProfile, productType, feat
26649
26730
  }
26650
26731
  function buildGenericToolAcceptanceItems(productType, features) {
26651
26732
  const text = buildFeatureText(productType, features);
26652
- const items = [];
26733
+ const items = [...buildLocalToolSignalProfile(text).acceptanceItems];
26653
26734
  if (/地图|景点|酒店|美食|点位|坐标|路线/.test(text)) {
26654
26735
  items.push("\u5730\u56FE provider\u3001\u5750\u6807\u6765\u6E90\u548C API Key \u4F7F\u7528\u65B9\u5F0F\u5DF2\u660E\u786E\uFF1BKey \u4E0D\u5E94\u786C\u7F16\u7801\u5230\u516C\u5F00\u4ED3\u5E93");
26655
26736
  items.push("\u7528\u6237\u4FDD\u5B58\u7684\u7F8E\u98DF\u3001\u9152\u5E97\u3001\u666F\u70B9\u80FD\u6309\u7C7B\u578B\u7B5B\u9009\u5E76\u6B63\u786E\u663E\u793A\u5728\u5730\u56FE\u6216\u5217\u8868\u4E2D");
@@ -27222,7 +27303,7 @@ function buildProductQuickQuestions(message, knownContext) {
27222
27303
  const domain = classifyProductDomain(contextText, knownContext || {}).domain;
27223
27304
  const technicalProfile = buildTechnicalProfile(message, knownContext || {});
27224
27305
  if (domain === "generic" && isLocalFirstProfile(technicalProfile)) {
27225
- return withQuestionExamples(buildLocalFirstQuickQuestions(technicalProfile));
27306
+ return withQuestionExamples(buildLocalFirstQuickQuestions(technicalProfile, message));
27226
27307
  }
27227
27308
  if (domain === "crm") {
27228
27309
  return withQuestionExamples(buildCrmQuickQuestions());
@@ -27466,13 +27547,16 @@ function buildProductQuickQuestions(message, knownContext) {
27466
27547
  }
27467
27548
  ]);
27468
27549
  }
27469
- function buildLocalFirstQuickQuestions(technicalProfile) {
27550
+ function buildLocalFirstQuickQuestions(technicalProfile, message = "") {
27470
27551
  const storageDefault = technicalProfile.shape === "static_json_data_page" ? "static_json" : technicalProfile.shape === "local_json_import_export" ? "local_file" : "local_storage";
27552
+ const signalProfile = buildLocalToolSignalProfile(message);
27553
+ const recordObject = signalProfile.recordObject;
27554
+ const fieldExample = signalProfile.fieldExample;
27471
27555
  const questions = [
27472
27556
  {
27473
27557
  id: "record_object",
27474
- question: "\u4F60\u60F3\u8BB0\u5F55\u6216\u5C55\u793A\u4EC0\u4E48\u4E1C\u897F\uFF1F",
27475
- example: "\u6BD4\u5982\u98DF\u6750\u3001\u836F\u54C1\u3001\u6E38\u620F\u3001\u88C5\u5907\u3001\u4FDD\u5355\uFF0C\u6216\u666F\u70B9\u3001\u9152\u5E97\u3001\u7F8E\u98DF\u3002",
27558
+ question: `\u4F60\u60F3\u8BB0\u5F55\u6216\u5C55\u793A\u4EC0\u4E48\u4E1C\u897F\uFF1F`,
27559
+ example: recordObject === "\u8BB0\u5F55" ? "\u6BD4\u5982\u98DF\u6750\u3001\u836F\u54C1\u3001\u6E38\u620F\u3001\u88C5\u5907\u3001\u4FDD\u5355\uFF0C\u6216\u666F\u70B9\u3001\u9152\u5E97\u3001\u7F8E\u98DF\u3002" : `\u6BD4\u5982\u5148\u56F4\u7ED5\u201C${recordObject}\u201D\u505A\u4E2A\u4EBA\u8BB0\u5F55/\u6E05\u5355\u3002`,
27476
27560
  whyImportant: "\u5148\u786E\u8BA4\u6838\u5FC3\u5BF9\u8C61\uFF0C\u907F\u514D\u88AB\u6269\u6210\u540E\u53F0\u7CFB\u7EDF\u3002",
27477
27561
  priority: "P0",
27478
27562
  defaultValue: "personal_records",
@@ -27485,8 +27569,8 @@ function buildLocalFirstQuickQuestions(technicalProfile) {
27485
27569
  },
27486
27570
  {
27487
27571
  id: "record_items",
27488
- question: "\u6BCF\u6761\u8BB0\u5F55\u8981\u4FDD\u5B58\u54EA\u4E9B\u4FE1\u606F\uFF1F",
27489
- example: "\u6BD4\u5982\u540D\u79F0\u3001\u6570\u91CF\u3001\u65E5\u671F\u3001\u72B6\u6001\u3001\u5206\u7C7B\u3001\u5907\u6CE8\u3002",
27572
+ question: `\u6BCF\u6761${recordObject === "\u8BB0\u5F55" ? "\u8BB0\u5F55" : recordObject}\u8BB0\u5F55\u8981\u4FDD\u5B58\u54EA\u4E9B\u4FE1\u606F\uFF1F`,
27573
+ example: `\u6BD4\u5982${fieldExample}\u3002`,
27490
27574
  whyImportant: "\u51B3\u5B9A\u9875\u9762\u8868\u5355\u3001\u5217\u8868\u5217\u540D\u548C\u672C\u5730 JSON \u6570\u636E\u7ED3\u6784\u3002",
27491
27575
  priority: "P0",
27492
27576
  defaultValue: "name_date_status_note",
@@ -27514,7 +27598,7 @@ function buildLocalFirstQuickQuestions(technicalProfile) {
27514
27598
  {
27515
27599
  id: "operations",
27516
27600
  question: "\u9700\u8981\u54EA\u4E9B\u64CD\u4F5C\uFF1F",
27517
- example: "\u6BD4\u5982\u65B0\u589E\u3001\u7F16\u8F91\u3001\u5220\u9664\u3001\u641C\u7D22\u3001\u7B5B\u9009\u3001\u5206\u7C7B\u3002",
27601
+ example: signalProfile.featureHints.length > 0 ? `\u6BD4\u5982${signalProfile.featureHints.join("\u3001")}\u3002` : "\u6BD4\u5982\u65B0\u589E\u3001\u7F16\u8F91\u3001\u5220\u9664\u3001\u641C\u7D22\u3001\u7B5B\u9009\u3001\u5206\u7C7B\u3002",
27518
27602
  whyImportant: "\u51B3\u5B9A\u7B2C\u4E00\u7248\u529F\u80FD\u8303\u56F4\u548C\u9A8C\u6536\u6807\u51C6\u3002",
27519
27603
  priority: "P0",
27520
27604
  defaultValue: "crud_search_filter",
@@ -28302,7 +28386,7 @@ function registerProductSpecAssist(server) {
28302
28386
  function createServer() {
28303
28387
  const server = new McpServer({
28304
28388
  name: "product-spec-mcp",
28305
- version: "0.3.19"
28389
+ version: "0.3.21"
28306
28390
  });
28307
28391
  registerSpecInterrogate(server);
28308
28392
  registerSpecCompile(server);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "product-spec-mcp",
3
- "version": "0.3.19",
3
+ "version": "0.3.21",
4
4
  "description": "MCP Server for product specification - requirement interrogation, architecture decision, UI translation, debug guidance, and acceptance generation",
5
5
  "type": "commonjs",
6
6
  "main": "dist/index.cjs",