@stackwright-pro/mcp 0.2.0-alpha.20 → 0.2.0-alpha.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.
package/dist/server.js CHANGED
@@ -1135,7 +1135,6 @@ function setupPackages(opts) {
1135
1135
 
1136
1136
  // src/tools/questions.ts
1137
1137
  var import_promises = require("fs/promises");
1138
- var import_node_fs = require("fs");
1139
1138
  var import_node_path = require("path");
1140
1139
  var import_zod8 = require("zod");
1141
1140
 
@@ -1408,12 +1407,12 @@ function registerQuestionTools(server2) {
1408
1407
  content: [
1409
1408
  {
1410
1409
  type: "text",
1411
- text: `Phase "${phase}" has no questions to present. Do NOT call ask_user_question. Call stackwright_pro_save_phase_answers({ phase: "${phase}", rawAnswers: [] }) directly, then proceed to the execution step for this phase.`
1412
- },
1413
- {
1414
- type: "text",
1415
- // Empty array — second block always present so the foreman's two-block contract holds
1416
- text: JSON.stringify([])
1410
+ text: JSON.stringify({
1411
+ phase,
1412
+ skipped: true,
1413
+ reason: "No questions to present (all filtered by dependsOn conditions)",
1414
+ answers: []
1415
+ })
1417
1416
  }
1418
1417
  ],
1419
1418
  isError: false
@@ -1434,144 +1433,6 @@ function registerQuestionTools(server2) {
1434
1433
  };
1435
1434
  }
1436
1435
  );
1437
- server2.tool(
1438
- "stackwright_pro_get_next_question",
1439
- "Returns the next unanswered question for a phase as a plain JSON object \u2014 one question at a time. Returns { done: true } when all questions are answered or the phase has no questions. Call this in a loop: present the question in plain chat, get user reply, call record_answer, repeat.",
1440
- { phase: import_zod8.z.string().describe('Phase name e.g. "designer", "api", "auth"') },
1441
- async ({ phase }) => {
1442
- const SAFE_PHASE = /^[a-z][a-z0-9-]{0,30}$/;
1443
- if (!SAFE_PHASE.test(phase)) {
1444
- return {
1445
- content: [
1446
- {
1447
- type: "text",
1448
- text: JSON.stringify({ error: `Invalid phase name: "${phase.slice(0, 50)}"` })
1449
- }
1450
- ],
1451
- isError: true
1452
- };
1453
- }
1454
- const cwd = process.cwd();
1455
- const questionsPath = (0, import_node_path.join)(cwd, ".stackwright", "questions", `${phase}.json`);
1456
- let questions = [];
1457
- try {
1458
- const raw = await (0, import_promises.readFile)(questionsPath, "utf-8");
1459
- const parsed = JSON.parse(raw);
1460
- questions = parsed.questions ?? [];
1461
- } catch {
1462
- return { content: [{ type: "text", text: JSON.stringify({ done: true }) }] };
1463
- }
1464
- if (questions.length === 0) {
1465
- return { content: [{ type: "text", text: JSON.stringify({ done: true }) }] };
1466
- }
1467
- const answersPath = (0, import_node_path.join)(cwd, ".stackwright", "answers", `${phase}.json`);
1468
- let answeredIds = /* @__PURE__ */ new Set();
1469
- try {
1470
- const raw = await (0, import_promises.readFile)(answersPath, "utf-8");
1471
- const parsed = JSON.parse(raw);
1472
- answeredIds = new Set(Object.keys(parsed.answers ?? {}));
1473
- } catch {
1474
- }
1475
- const next = questions.find((q) => !answeredIds.has(q.id));
1476
- if (!next) {
1477
- return { content: [{ type: "text", text: JSON.stringify({ done: true }) }] };
1478
- }
1479
- return {
1480
- content: [
1481
- {
1482
- type: "text",
1483
- text: JSON.stringify({
1484
- done: false,
1485
- questionId: next.id,
1486
- question: next.question,
1487
- type: next.type,
1488
- options: next.options ?? null,
1489
- help: next.help ?? null,
1490
- index: questions.indexOf(next) + 1,
1491
- total: questions.length
1492
- })
1493
- }
1494
- ]
1495
- };
1496
- }
1497
- );
1498
- server2.tool(
1499
- "stackwright_pro_record_answer",
1500
- "Records a single answer to a phase question. Appends to .stackwright/answers/{phase}.json incrementally. Idempotent \u2014 calling twice for the same questionId overwrites. Call after receiving each user reply in the conversational question loop.",
1501
- {
1502
- phase: import_zod8.z.string().describe('Phase name e.g. "designer"'),
1503
- questionId: import_zod8.z.string().describe('The question ID from get_next_question, e.g. "designer-1"'),
1504
- answer: import_zod8.z.string().describe("The user's free-text answer")
1505
- },
1506
- async ({ phase, questionId, answer }) => {
1507
- const SAFE_PHASE = /^[a-z][a-z0-9-]{0,30}$/;
1508
- if (!SAFE_PHASE.test(phase)) {
1509
- return {
1510
- content: [
1511
- { type: "text", text: JSON.stringify({ error: "Invalid phase name" }) }
1512
- ],
1513
- isError: true
1514
- };
1515
- }
1516
- if (!/^[a-z0-9][a-z0-9-]{0,63}$/i.test(questionId)) {
1517
- return {
1518
- content: [
1519
- { type: "text", text: JSON.stringify({ error: "Invalid questionId" }) }
1520
- ],
1521
- isError: true
1522
- };
1523
- }
1524
- const safeAnswer = answer.slice(0, 2e3);
1525
- const cwd = process.cwd();
1526
- const answersDir = (0, import_node_path.join)(cwd, ".stackwright", "answers");
1527
- const answersPath = (0, import_node_path.join)(answersDir, `${phase}.json`);
1528
- if ((0, import_node_fs.existsSync)(answersDir) && (0, import_node_fs.lstatSync)(answersDir).isSymbolicLink()) {
1529
- return {
1530
- content: [
1531
- {
1532
- type: "text",
1533
- text: JSON.stringify({ error: "answers dir is a symlink \u2014 refusing to write" })
1534
- }
1535
- ],
1536
- isError: true
1537
- };
1538
- }
1539
- (0, import_node_fs.mkdirSync)(answersDir, { recursive: true });
1540
- let existing = {
1541
- version: "1.0",
1542
- phase,
1543
- answers: {}
1544
- };
1545
- try {
1546
- if ((0, import_node_fs.existsSync)(answersPath)) {
1547
- if ((0, import_node_fs.lstatSync)(answersPath).isSymbolicLink()) {
1548
- return {
1549
- content: [
1550
- {
1551
- type: "text",
1552
- text: JSON.stringify({ error: "answers file is a symlink \u2014 refusing to write" })
1553
- }
1554
- ],
1555
- isError: true
1556
- };
1557
- }
1558
- const raw = await (0, import_promises.readFile)(answersPath, "utf-8");
1559
- existing = JSON.parse(raw);
1560
- }
1561
- } catch {
1562
- }
1563
- existing.answers[questionId] = safeAnswer;
1564
- existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1565
- const tmp = `${answersPath}.tmp`;
1566
- await (0, import_promises.writeFile)(tmp, JSON.stringify(existing, null, 2), "utf-8");
1567
- (0, import_node_fs.renameSync)(tmp, answersPath);
1568
- return {
1569
- content: [
1570
- { type: "text", text: JSON.stringify({ recorded: true, phase, questionId }) }
1571
- ]
1572
- };
1573
- }
1574
- );
1575
1436
  }
1576
1437
 
1577
1438
  // src/tools/orchestration.ts
@@ -1743,57 +1604,7 @@ function handleGetOtterName(input) {
1743
1604
  isError: false
1744
1605
  };
1745
1606
  }
1746
- function handleSaveBuildContext(input) {
1747
- const cwd = input._cwd ?? process.cwd();
1748
- const dir = (0, import_path3.join)(cwd, ".stackwright");
1749
- const filePath = (0, import_path3.join)(dir, "build-context.json");
1750
- try {
1751
- (0, import_fs3.mkdirSync)(dir, { recursive: true });
1752
- if ((0, import_fs3.existsSync)(filePath)) {
1753
- const stat = (0, import_fs3.lstatSync)(filePath);
1754
- if (stat.isSymbolicLink()) {
1755
- return {
1756
- text: JSON.stringify({
1757
- success: false,
1758
- error: `Refusing to write to symlink: ${filePath}`
1759
- }),
1760
- isError: true
1761
- };
1762
- }
1763
- }
1764
- const payload = {
1765
- version: "1.0",
1766
- savedAt: (/* @__PURE__ */ new Date()).toISOString(),
1767
- buildContext: input.buildContext
1768
- };
1769
- (0, import_fs3.writeFileSync)(filePath, JSON.stringify(payload, null, 2) + "\n");
1770
- return {
1771
- text: JSON.stringify({ success: true, path: filePath }),
1772
- isError: false
1773
- };
1774
- } catch (err) {
1775
- const message = err instanceof Error ? err.message : String(err);
1776
- return {
1777
- text: JSON.stringify({ success: false, error: message }),
1778
- isError: true
1779
- };
1780
- }
1781
- }
1782
1607
  function registerOrchestrationTools(server2) {
1783
- server2.tool(
1784
- "stackwright_pro_save_build_context",
1785
- `Save the user's initial build description to .stackwright/build-context.json. Call this once at startup after the user answers the opening "what are you building" question. The saved context is automatically prepended to specialist prompts by stackwright_pro_build_specialist_prompt.`,
1786
- {
1787
- buildContext: import_zod9.z.string().describe("Free-text description of what the user wants to build")
1788
- },
1789
- async ({ buildContext }) => {
1790
- const { text, isError } = handleSaveBuildContext({ buildContext });
1791
- return {
1792
- content: [{ type: "text", text }],
1793
- isError
1794
- };
1795
- }
1796
- );
1797
1608
  server2.tool(
1798
1609
  "stackwright_pro_parse_otter_response",
1799
1610
  "Parse and validate a specialist otter's QUESTION_COLLECTION_MODE JSON response. Handles JSON extraction from LLM responses (strips markdown, fixes single quotes, trailing commas). Detects the phase from the otter name. Use this immediately after invoke_agent() to get a validated manifest phase object.",
@@ -2072,62 +1883,28 @@ function handleSetPipelineState(input) {
2072
1883
  return { text: JSON.stringify({ error: true, message: String(err) }), isError: true };
2073
1884
  }
2074
1885
  }
2075
- function handleCheckExecutionReady(_cwd, phase) {
1886
+ function handleCheckExecutionReady(_cwd) {
2076
1887
  const cwd = _cwd ?? process.cwd();
2077
- if (phase) {
2078
- if (!isValidPhase(phase)) {
2079
- return {
2080
- text: JSON.stringify({
2081
- error: true,
2082
- message: `Invalid phase: ${phase}. Valid phases are: ${PHASE_ORDER.join(", ")}`
2083
- }),
2084
- isError: true
2085
- };
2086
- }
2087
- const answerFile = (0, import_path4.join)(cwd, ".stackwright", "answers", `${phase}.json`);
2088
- if (!(0, import_fs4.existsSync)(answerFile)) {
2089
- return {
2090
- text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
2091
- isError: false
2092
- };
2093
- }
2094
- try {
2095
- const raw = safeReadSync(answerFile);
2096
- const parsed = JSON.parse(raw);
2097
- if (typeof parsed["version"] !== "string" || typeof parsed["phase"] !== "string" || typeof parsed["answers"] !== "object" || parsed["answers"] === null) {
2098
- return {
2099
- text: JSON.stringify({ ready: false, phase, reason: "Answer file is malformed" }),
2100
- isError: false
2101
- };
2102
- }
2103
- return { text: JSON.stringify({ ready: true, phase }), isError: false };
2104
- } catch {
2105
- return {
2106
- text: JSON.stringify({ ready: false, phase, reason: "Answer file could not be parsed" }),
2107
- isError: false
2108
- };
2109
- }
2110
- }
2111
1888
  try {
2112
1889
  const answersDir = (0, import_path4.join)(cwd, ".stackwright", "answers");
2113
1890
  const answeredPhases = [];
2114
1891
  const missingPhases = [];
2115
- for (const phase2 of PHASE_ORDER) {
2116
- const answerFile = (0, import_path4.join)(answersDir, `${phase2}.json`);
1892
+ for (const phase of PHASE_ORDER) {
1893
+ const answerFile = (0, import_path4.join)(answersDir, `${phase}.json`);
2117
1894
  if ((0, import_fs4.existsSync)(answerFile)) {
2118
1895
  try {
2119
1896
  const raw = safeReadSync(answerFile);
2120
1897
  const parsed = JSON.parse(raw);
2121
1898
  if (typeof parsed["version"] !== "string" || typeof parsed["phase"] !== "string" || typeof parsed["answers"] !== "object" || parsed["answers"] === null) {
2122
- missingPhases.push(phase2);
1899
+ missingPhases.push(phase);
2123
1900
  continue;
2124
1901
  }
2125
- answeredPhases.push(phase2);
1902
+ answeredPhases.push(phase);
2126
1903
  } catch {
2127
- missingPhases.push(phase2);
1904
+ missingPhases.push(phase);
2128
1905
  }
2129
1906
  } else {
2130
- missingPhases.push(phase2);
1907
+ missingPhases.push(phase);
2131
1908
  }
2132
1909
  }
2133
1910
  return {
@@ -2237,17 +2014,6 @@ function handleBuildSpecialistPrompt(input) {
2237
2014
  if ((0, import_fs4.existsSync)(answersPath)) {
2238
2015
  answers = JSON.parse(safeReadSync(answersPath));
2239
2016
  }
2240
- let buildContextText = "";
2241
- const buildContextPath = (0, import_path4.join)(cwd, ".stackwright", "build-context.json");
2242
- if ((0, import_fs4.existsSync)(buildContextPath)) {
2243
- try {
2244
- const bcRaw = JSON.parse(safeReadSync(buildContextPath));
2245
- if (typeof bcRaw.buildContext === "string" && bcRaw.buildContext.trim().length > 0) {
2246
- buildContextText = bcRaw.buildContext.trim();
2247
- }
2248
- } catch {
2249
- }
2250
- }
2251
2017
  const deps = PHASE_DEPENDENCIES[phase];
2252
2018
  const artifactSections = [];
2253
2019
  const missingDependencies = [];
@@ -2258,14 +2024,13 @@ function handleBuildSpecialistPrompt(input) {
2258
2024
  const content = JSON.parse(safeReadSync(artifactPath));
2259
2025
  const expectedOtter = PHASE_TO_OTTER2[dep];
2260
2026
  const artifactOtter = content["generatedBy"];
2261
- const normalizedOtter = artifactOtter?.replace(/-[a-f0-9]{6}$/, "");
2262
2027
  if (!artifactOtter) {
2263
2028
  missingDependencies.push(dep);
2264
2029
  artifactSections.push(
2265
2030
  `[${artifactFile}]:
2266
2031
  (integrity check failed: missing generatedBy field)`
2267
2032
  );
2268
- } else if (normalizedOtter !== expectedOtter) {
2033
+ } else if (artifactOtter !== expectedOtter) {
2269
2034
  missingDependencies.push(dep);
2270
2035
  artifactSections.push(
2271
2036
  `[${artifactFile}]:
@@ -2281,11 +2046,7 @@ ${JSON.stringify(content, null, 2)}`);
2281
2046
  (not yet available)`);
2282
2047
  }
2283
2048
  }
2284
- const parts = [];
2285
- if (buildContextText) {
2286
- parts.push("BUILD_CONTEXT:", buildContextText, "");
2287
- }
2288
- parts.push("ANSWERS:", JSON.stringify(answers, null, 2));
2049
+ const parts = ["ANSWERS:", JSON.stringify(answers, null, 2)];
2289
2050
  if (artifactSections.length > 0) {
2290
2051
  parts.push("", "UPSTREAM ARTIFACTS:", "", ...artifactSections);
2291
2052
  }
@@ -2444,11 +2205,9 @@ function registerPipelineTools(server2) {
2444
2205
  );
2445
2206
  server2.tool(
2446
2207
  "stackwright_pro_check_execution_ready",
2447
- `Check all phases have answer files in .stackwright/answers/. If phase is provided, check only that phase. ${DESC}`,
2448
- {
2449
- phase: import_zod10.z.string().optional().describe("If provided, check only this phase's readiness. Omit to check all phases.")
2450
- },
2451
- async ({ phase }) => res(handleCheckExecutionReady(void 0, phase))
2208
+ `Check all phases have answer files in .stackwright/answers/. ${DESC}`,
2209
+ {},
2210
+ async () => res(handleCheckExecutionReady())
2452
2211
  );
2453
2212
  server2.tool(
2454
2213
  "stackwright_pro_list_artifacts",
@@ -2458,49 +2217,12 @@ function registerPipelineTools(server2) {
2458
2217
  );
2459
2218
  server2.tool(
2460
2219
  "stackwright_pro_write_phase_questions",
2461
- `Parse otter question-collection response \u2192 .stackwright/questions/{phase}.json. Specialists may also call this directly with a parsed questions array. ${DESC}`,
2220
+ `Parse otter question-collection response \u2192 .stackwright/questions/{phase}.json. ${DESC}`,
2462
2221
  {
2463
- phase: import_zod10.z.string().optional().describe('Phase name, e.g. "designer" (required for direct write)'),
2464
- responseText: import_zod10.z.string().optional().describe("Raw LLM response from QUESTION_COLLECTION_MODE"),
2465
- questions: jsonCoerce(import_zod10.z.array(import_zod10.z.any()).optional()).describe(
2466
- "Questions array for direct specialist write"
2467
- )
2222
+ phase: import_zod10.z.string().describe('Phase name, e.g. "designer"'),
2223
+ responseText: import_zod10.z.string().describe("Raw LLM response from QUESTION_COLLECTION_MODE")
2468
2224
  },
2469
- async ({ phase, responseText, questions }) => {
2470
- if (phase && questions && Array.isArray(questions)) {
2471
- const SAFE_PHASE = /^[a-z][a-z0-9-]{0,30}$/;
2472
- if (!SAFE_PHASE.test(phase)) {
2473
- return {
2474
- content: [
2475
- { type: "text", text: JSON.stringify({ error: `Invalid phase name` }) }
2476
- ],
2477
- isError: true
2478
- };
2479
- }
2480
- const questionsDir = (0, import_path4.join)(process.cwd(), ".stackwright", "questions");
2481
- (0, import_fs4.mkdirSync)(questionsDir, { recursive: true });
2482
- const outPath = (0, import_path4.join)(questionsDir, `${phase}.json`);
2483
- (0, import_fs4.writeFileSync)(
2484
- outPath,
2485
- JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
2486
- );
2487
- return {
2488
- content: [
2489
- {
2490
- type: "text",
2491
- text: JSON.stringify({
2492
- written: true,
2493
- phase,
2494
- count: questions.length
2495
- })
2496
- }
2497
- ]
2498
- };
2499
- }
2500
- return res(
2501
- handleWritePhaseQuestions({ phase: phase ?? "", responseText: responseText ?? "" })
2502
- );
2503
- }
2225
+ async ({ phase, responseText }) => res(handleWritePhaseQuestions({ phase, responseText }))
2504
2226
  );
2505
2227
  server2.tool(
2506
2228
  "stackwright_pro_build_specialist_prompt",
@@ -2755,9 +2477,7 @@ function registerSafeWriteTools(server2) {
2755
2477
  callerOtter: import_zod11.z.string().describe('The otter agent name requesting the write, e.g. "stackwright-pro-page-otter"'),
2756
2478
  filePath: import_zod11.z.string().describe('Relative path from project root, e.g. "pages/dashboard/content.yml"'),
2757
2479
  content: import_zod11.z.string().describe("File content to write"),
2758
- createDirectories: boolCoerce(import_zod11.z.boolean().optional().default(true)).describe(
2759
- "Create parent directories if they don't exist. Default: true"
2760
- )
2480
+ createDirectories: import_zod11.z.boolean().optional().describe("Create parent directories if they don't exist. Default: true")
2761
2481
  },
2762
2482
  async ({ callerOtter, filePath, content, createDirectories }) => {
2763
2483
  const result = handleSafeWrite({
@@ -3171,39 +2891,39 @@ var import_path7 = require("path");
3171
2891
  var _checksums = /* @__PURE__ */ new Map([
3172
2892
  [
3173
2893
  "stackwright-pro-api-otter.json",
3174
- "0ac26d85a5ad35b072a58965e1d5e090dd5c5f16dc14e68c452c3e99fcbb5510"
2894
+ "f1cc9edf2dd1df3ebcea1d0ab33d17a358faaf8aa97ee232cd7994042f2eac0d"
3175
2895
  ],
3176
2896
  [
3177
2897
  "stackwright-pro-auth-otter.json",
3178
- "d789b71f196659d5745ebfca87a7bda60a1bb63cfeccd17b4a273ac1e29bb08d"
2898
+ "a19e06c503209a8a35fe321d30448623545b36b48c47a6ec064d13406ad1f725"
3179
2899
  ],
3180
2900
  [
3181
2901
  "stackwright-pro-dashboard-otter.json",
3182
- "600e8597429c353e5b886f316731be84a86cd8b93617bf046e3cbf390b31a431"
2902
+ "b3cb3d7554f2e9eed3b57d5e0e3bf85d6ba5b4db5d3af5514391cf0575fcc001"
3183
2903
  ],
3184
2904
  [
3185
2905
  "stackwright-pro-data-otter.json",
3186
- "b2946e3da3b53282c122d150e6db86b0cb89d2edba2a94a7666b26d27051be96"
2906
+ "bfacb87ae82867472a75982215554336a105a658d6cd3dd2c8b819fa1e11d7ac"
3187
2907
  ],
3188
2908
  [
3189
2909
  "stackwright-pro-designer-otter.json",
3190
- "f4dbff5149051c77be1645de5ee12c0bd7d590c687a0b2d86737b915a5a6d5f0"
2910
+ "c58fa7c7ead9e6398074e1c7ce3f31a8ef4eb3679f5fa18cc03cae3a87878c88"
3191
2911
  ],
3192
2912
  [
3193
2913
  "stackwright-pro-foreman-otter.json",
3194
- "7464523d7288374dc6efa5c213c825ec0616e00cf4f739d8039eb41df812cbc5"
2914
+ "f52264c1f6297b72f3da6d92d41b6d63356db776242caeab25571e6a8df628e4"
3195
2915
  ],
3196
2916
  [
3197
2917
  "stackwright-pro-page-otter.json",
3198
- "12aca7b666b3c85c1d96c700a2da7f209604cb75d0f064995e052711ddafd657"
2918
+ "65bec3a3a0dda6b7591bba2de9399f1e3a4fb99cfe1075342f4f4be98d917b67"
3199
2919
  ],
3200
2920
  [
3201
2921
  "stackwright-pro-theme-otter.json",
3202
- "a303ec6c045420f2c916583e3f6efcda469e9610fedfc84a508ed8a8a75866bc"
2922
+ "1f182326f1acd3d4091a38c7012085cbb4945893e95be4ca3de72318ad092767"
3203
2923
  ],
3204
2924
  [
3205
2925
  "stackwright-pro-workflow-otter.json",
3206
- "16da6c109d0b5ee60d0a14e009dbeab02a7bbac3b0947795769da053565b9821"
2926
+ "0eec9d6a731678cf547c2a7b0b6fc338ca143c35501365a1e4e5dd2779dd5510"
3207
2927
  ]
3208
2928
  ]);
3209
2929
  Object.freeze(_checksums);
@@ -3809,7 +3529,7 @@ var package_default = {
3809
3529
  "test:coverage": "vitest run --coverage"
3810
3530
  },
3811
3531
  name: "@stackwright-pro/mcp",
3812
- version: "0.2.0-alpha.20",
3532
+ version: "0.2.0-alpha.13",
3813
3533
  description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
3814
3534
  license: "PROPRIETARY",
3815
3535
  main: "./dist/server.js",