@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.mjs CHANGED
@@ -1110,8 +1110,7 @@ function setupPackages(opts) {
1110
1110
  }
1111
1111
 
1112
1112
  // src/tools/questions.ts
1113
- import { readFile, writeFile } from "fs/promises";
1114
- import { existsSync as existsSync2, lstatSync as lstatSync2, mkdirSync, renameSync } from "fs";
1113
+ import { readFile } from "fs/promises";
1115
1114
  import { join } from "path";
1116
1115
  import { z as z8 } from "zod";
1117
1116
 
@@ -1384,12 +1383,12 @@ function registerQuestionTools(server2) {
1384
1383
  content: [
1385
1384
  {
1386
1385
  type: "text",
1387
- 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.`
1388
- },
1389
- {
1390
- type: "text",
1391
- // Empty array — second block always present so the foreman's two-block contract holds
1392
- text: JSON.stringify([])
1386
+ text: JSON.stringify({
1387
+ phase,
1388
+ skipped: true,
1389
+ reason: "No questions to present (all filtered by dependsOn conditions)",
1390
+ answers: []
1391
+ })
1393
1392
  }
1394
1393
  ],
1395
1394
  isError: false
@@ -1410,149 +1409,11 @@ function registerQuestionTools(server2) {
1410
1409
  };
1411
1410
  }
1412
1411
  );
1413
- server2.tool(
1414
- "stackwright_pro_get_next_question",
1415
- "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.",
1416
- { phase: z8.string().describe('Phase name e.g. "designer", "api", "auth"') },
1417
- async ({ phase }) => {
1418
- const SAFE_PHASE = /^[a-z][a-z0-9-]{0,30}$/;
1419
- if (!SAFE_PHASE.test(phase)) {
1420
- return {
1421
- content: [
1422
- {
1423
- type: "text",
1424
- text: JSON.stringify({ error: `Invalid phase name: "${phase.slice(0, 50)}"` })
1425
- }
1426
- ],
1427
- isError: true
1428
- };
1429
- }
1430
- const cwd = process.cwd();
1431
- const questionsPath = join(cwd, ".stackwright", "questions", `${phase}.json`);
1432
- let questions = [];
1433
- try {
1434
- const raw = await readFile(questionsPath, "utf-8");
1435
- const parsed = JSON.parse(raw);
1436
- questions = parsed.questions ?? [];
1437
- } catch {
1438
- return { content: [{ type: "text", text: JSON.stringify({ done: true }) }] };
1439
- }
1440
- if (questions.length === 0) {
1441
- return { content: [{ type: "text", text: JSON.stringify({ done: true }) }] };
1442
- }
1443
- const answersPath = join(cwd, ".stackwright", "answers", `${phase}.json`);
1444
- let answeredIds = /* @__PURE__ */ new Set();
1445
- try {
1446
- const raw = await readFile(answersPath, "utf-8");
1447
- const parsed = JSON.parse(raw);
1448
- answeredIds = new Set(Object.keys(parsed.answers ?? {}));
1449
- } catch {
1450
- }
1451
- const next = questions.find((q) => !answeredIds.has(q.id));
1452
- if (!next) {
1453
- return { content: [{ type: "text", text: JSON.stringify({ done: true }) }] };
1454
- }
1455
- return {
1456
- content: [
1457
- {
1458
- type: "text",
1459
- text: JSON.stringify({
1460
- done: false,
1461
- questionId: next.id,
1462
- question: next.question,
1463
- type: next.type,
1464
- options: next.options ?? null,
1465
- help: next.help ?? null,
1466
- index: questions.indexOf(next) + 1,
1467
- total: questions.length
1468
- })
1469
- }
1470
- ]
1471
- };
1472
- }
1473
- );
1474
- server2.tool(
1475
- "stackwright_pro_record_answer",
1476
- "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.",
1477
- {
1478
- phase: z8.string().describe('Phase name e.g. "designer"'),
1479
- questionId: z8.string().describe('The question ID from get_next_question, e.g. "designer-1"'),
1480
- answer: z8.string().describe("The user's free-text answer")
1481
- },
1482
- async ({ phase, questionId, answer }) => {
1483
- const SAFE_PHASE = /^[a-z][a-z0-9-]{0,30}$/;
1484
- if (!SAFE_PHASE.test(phase)) {
1485
- return {
1486
- content: [
1487
- { type: "text", text: JSON.stringify({ error: "Invalid phase name" }) }
1488
- ],
1489
- isError: true
1490
- };
1491
- }
1492
- if (!/^[a-z0-9][a-z0-9-]{0,63}$/i.test(questionId)) {
1493
- return {
1494
- content: [
1495
- { type: "text", text: JSON.stringify({ error: "Invalid questionId" }) }
1496
- ],
1497
- isError: true
1498
- };
1499
- }
1500
- const safeAnswer = answer.slice(0, 2e3);
1501
- const cwd = process.cwd();
1502
- const answersDir = join(cwd, ".stackwright", "answers");
1503
- const answersPath = join(answersDir, `${phase}.json`);
1504
- if (existsSync2(answersDir) && lstatSync2(answersDir).isSymbolicLink()) {
1505
- return {
1506
- content: [
1507
- {
1508
- type: "text",
1509
- text: JSON.stringify({ error: "answers dir is a symlink \u2014 refusing to write" })
1510
- }
1511
- ],
1512
- isError: true
1513
- };
1514
- }
1515
- mkdirSync(answersDir, { recursive: true });
1516
- let existing = {
1517
- version: "1.0",
1518
- phase,
1519
- answers: {}
1520
- };
1521
- try {
1522
- if (existsSync2(answersPath)) {
1523
- if (lstatSync2(answersPath).isSymbolicLink()) {
1524
- return {
1525
- content: [
1526
- {
1527
- type: "text",
1528
- text: JSON.stringify({ error: "answers file is a symlink \u2014 refusing to write" })
1529
- }
1530
- ],
1531
- isError: true
1532
- };
1533
- }
1534
- const raw = await readFile(answersPath, "utf-8");
1535
- existing = JSON.parse(raw);
1536
- }
1537
- } catch {
1538
- }
1539
- existing.answers[questionId] = safeAnswer;
1540
- existing.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1541
- const tmp = `${answersPath}.tmp`;
1542
- await writeFile(tmp, JSON.stringify(existing, null, 2), "utf-8");
1543
- renameSync(tmp, answersPath);
1544
- return {
1545
- content: [
1546
- { type: "text", text: JSON.stringify({ recorded: true, phase, questionId }) }
1547
- ]
1548
- };
1549
- }
1550
- );
1551
1412
  }
1552
1413
 
1553
1414
  // src/tools/orchestration.ts
1554
1415
  import { z as z9 } from "zod";
1555
- import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2, lstatSync as lstatSync3 } from "fs";
1416
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync2, mkdirSync, lstatSync as lstatSync2 } from "fs";
1556
1417
  import { join as join2 } from "path";
1557
1418
  var OTTER_NAME_TO_PHASE = [
1558
1419
  ["designer", "designer"],
@@ -1608,9 +1469,9 @@ function handleSaveManifest(input) {
1608
1469
  const dir = join2(cwd, ".stackwright");
1609
1470
  const filePath = join2(dir, "question-manifest.json");
1610
1471
  try {
1611
- mkdirSync2(dir, { recursive: true });
1612
- if (existsSync3(filePath)) {
1613
- const stat = lstatSync3(filePath);
1472
+ mkdirSync(dir, { recursive: true });
1473
+ if (existsSync2(filePath)) {
1474
+ const stat = lstatSync2(filePath);
1614
1475
  if (stat.isSymbolicLink()) {
1615
1476
  const message = `Refusing to write to symlink: ${filePath}`;
1616
1477
  return {
@@ -1642,7 +1503,7 @@ function handleSavePhaseAnswers(input) {
1642
1503
  const dir = join2(cwd, ".stackwright", "answers");
1643
1504
  const filePath = join2(dir, `${input.phase}.json`);
1644
1505
  try {
1645
- mkdirSync2(dir, { recursive: true });
1506
+ mkdirSync(dir, { recursive: true });
1646
1507
  let answers;
1647
1508
  if (input.questions && input.questions.length > 0) {
1648
1509
  answers = answersToManifestFormat(input.rawAnswers, input.questions);
@@ -1657,8 +1518,8 @@ function handleSavePhaseAnswers(input) {
1657
1518
  completedAt: (/* @__PURE__ */ new Date()).toISOString(),
1658
1519
  answers
1659
1520
  };
1660
- if (existsSync3(filePath)) {
1661
- const stat = lstatSync3(filePath);
1521
+ if (existsSync2(filePath)) {
1522
+ const stat = lstatSync2(filePath);
1662
1523
  if (stat.isSymbolicLink()) {
1663
1524
  const message = `Refusing to write to symlink: ${filePath}`;
1664
1525
  return {
@@ -1687,7 +1548,7 @@ function handleSavePhaseAnswers(input) {
1687
1548
  function handleReadPhaseAnswers(input) {
1688
1549
  const cwd = input._cwd ?? process.cwd();
1689
1550
  const filePath = join2(cwd, ".stackwright", "answers", `${input.phase}.json`);
1690
- if (!existsSync3(filePath)) {
1551
+ if (!existsSync2(filePath)) {
1691
1552
  return {
1692
1553
  text: JSON.stringify({ missing: true, phase: input.phase }),
1693
1554
  isError: false
@@ -1719,57 +1580,7 @@ function handleGetOtterName(input) {
1719
1580
  isError: false
1720
1581
  };
1721
1582
  }
1722
- function handleSaveBuildContext(input) {
1723
- const cwd = input._cwd ?? process.cwd();
1724
- const dir = join2(cwd, ".stackwright");
1725
- const filePath = join2(dir, "build-context.json");
1726
- try {
1727
- mkdirSync2(dir, { recursive: true });
1728
- if (existsSync3(filePath)) {
1729
- const stat = lstatSync3(filePath);
1730
- if (stat.isSymbolicLink()) {
1731
- return {
1732
- text: JSON.stringify({
1733
- success: false,
1734
- error: `Refusing to write to symlink: ${filePath}`
1735
- }),
1736
- isError: true
1737
- };
1738
- }
1739
- }
1740
- const payload = {
1741
- version: "1.0",
1742
- savedAt: (/* @__PURE__ */ new Date()).toISOString(),
1743
- buildContext: input.buildContext
1744
- };
1745
- writeFileSync2(filePath, JSON.stringify(payload, null, 2) + "\n");
1746
- return {
1747
- text: JSON.stringify({ success: true, path: filePath }),
1748
- isError: false
1749
- };
1750
- } catch (err) {
1751
- const message = err instanceof Error ? err.message : String(err);
1752
- return {
1753
- text: JSON.stringify({ success: false, error: message }),
1754
- isError: true
1755
- };
1756
- }
1757
- }
1758
1583
  function registerOrchestrationTools(server2) {
1759
- server2.tool(
1760
- "stackwright_pro_save_build_context",
1761
- `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.`,
1762
- {
1763
- buildContext: z9.string().describe("Free-text description of what the user wants to build")
1764
- },
1765
- async ({ buildContext }) => {
1766
- const { text, isError } = handleSaveBuildContext({ buildContext });
1767
- return {
1768
- content: [{ type: "text", text }],
1769
- isError
1770
- };
1771
- }
1772
- );
1773
1584
  server2.tool(
1774
1585
  "stackwright_pro_parse_otter_response",
1775
1586
  "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.",
@@ -1875,7 +1686,7 @@ function registerOrchestrationTools(server2) {
1875
1686
 
1876
1687
  // src/tools/pipeline.ts
1877
1688
  import { z as z10 } from "zod";
1878
- import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3, lstatSync as lstatSync4 } from "fs";
1689
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2, lstatSync as lstatSync3 } from "fs";
1879
1690
  import { join as join3 } from "path";
1880
1691
  var PHASE_ORDER = [
1881
1692
  "designer",
@@ -1949,7 +1760,7 @@ function statePath(cwd) {
1949
1760
  }
1950
1761
  function readState(cwd) {
1951
1762
  const p = statePath(cwd);
1952
- if (!existsSync4(p)) return createDefaultState();
1763
+ if (!existsSync3(p)) return createDefaultState();
1953
1764
  const raw = JSON.parse(safeReadSync(p));
1954
1765
  if (typeof raw !== "object" || raw === null || raw.version !== "1.0") {
1955
1766
  return createDefaultState();
@@ -1957,8 +1768,8 @@ function readState(cwd) {
1957
1768
  return raw;
1958
1769
  }
1959
1770
  function safeWriteSync(filePath, content) {
1960
- if (existsSync4(filePath)) {
1961
- const stat = lstatSync4(filePath);
1771
+ if (existsSync3(filePath)) {
1772
+ const stat = lstatSync3(filePath);
1962
1773
  if (stat.isSymbolicLink()) {
1963
1774
  throw new Error(`Refusing to write to symlink: ${filePath}`);
1964
1775
  }
@@ -1966,8 +1777,8 @@ function safeWriteSync(filePath, content) {
1966
1777
  writeFileSync3(filePath, content);
1967
1778
  }
1968
1779
  function safeReadSync(filePath) {
1969
- if (existsSync4(filePath)) {
1970
- const stat = lstatSync4(filePath);
1780
+ if (existsSync3(filePath)) {
1781
+ const stat = lstatSync3(filePath);
1971
1782
  if (stat.isSymbolicLink()) {
1972
1783
  throw new Error(`Refusing to read symlink: ${filePath}`);
1973
1784
  }
@@ -1976,7 +1787,7 @@ function safeReadSync(filePath) {
1976
1787
  }
1977
1788
  function writeState(cwd, state) {
1978
1789
  const dir = join3(cwd, ".stackwright");
1979
- mkdirSync3(dir, { recursive: true });
1790
+ mkdirSync2(dir, { recursive: true });
1980
1791
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
1981
1792
  safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
1982
1793
  }
@@ -2048,62 +1859,28 @@ function handleSetPipelineState(input) {
2048
1859
  return { text: JSON.stringify({ error: true, message: String(err) }), isError: true };
2049
1860
  }
2050
1861
  }
2051
- function handleCheckExecutionReady(_cwd, phase) {
1862
+ function handleCheckExecutionReady(_cwd) {
2052
1863
  const cwd = _cwd ?? process.cwd();
2053
- if (phase) {
2054
- if (!isValidPhase(phase)) {
2055
- return {
2056
- text: JSON.stringify({
2057
- error: true,
2058
- message: `Invalid phase: ${phase}. Valid phases are: ${PHASE_ORDER.join(", ")}`
2059
- }),
2060
- isError: true
2061
- };
2062
- }
2063
- const answerFile = join3(cwd, ".stackwright", "answers", `${phase}.json`);
2064
- if (!existsSync4(answerFile)) {
2065
- return {
2066
- text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
2067
- isError: false
2068
- };
2069
- }
2070
- try {
2071
- const raw = safeReadSync(answerFile);
2072
- const parsed = JSON.parse(raw);
2073
- if (typeof parsed["version"] !== "string" || typeof parsed["phase"] !== "string" || typeof parsed["answers"] !== "object" || parsed["answers"] === null) {
2074
- return {
2075
- text: JSON.stringify({ ready: false, phase, reason: "Answer file is malformed" }),
2076
- isError: false
2077
- };
2078
- }
2079
- return { text: JSON.stringify({ ready: true, phase }), isError: false };
2080
- } catch {
2081
- return {
2082
- text: JSON.stringify({ ready: false, phase, reason: "Answer file could not be parsed" }),
2083
- isError: false
2084
- };
2085
- }
2086
- }
2087
1864
  try {
2088
1865
  const answersDir = join3(cwd, ".stackwright", "answers");
2089
1866
  const answeredPhases = [];
2090
1867
  const missingPhases = [];
2091
- for (const phase2 of PHASE_ORDER) {
2092
- const answerFile = join3(answersDir, `${phase2}.json`);
2093
- if (existsSync4(answerFile)) {
1868
+ for (const phase of PHASE_ORDER) {
1869
+ const answerFile = join3(answersDir, `${phase}.json`);
1870
+ if (existsSync3(answerFile)) {
2094
1871
  try {
2095
1872
  const raw = safeReadSync(answerFile);
2096
1873
  const parsed = JSON.parse(raw);
2097
1874
  if (typeof parsed["version"] !== "string" || typeof parsed["phase"] !== "string" || typeof parsed["answers"] !== "object" || parsed["answers"] === null) {
2098
- missingPhases.push(phase2);
1875
+ missingPhases.push(phase);
2099
1876
  continue;
2100
1877
  }
2101
- answeredPhases.push(phase2);
1878
+ answeredPhases.push(phase);
2102
1879
  } catch {
2103
- missingPhases.push(phase2);
1880
+ missingPhases.push(phase);
2104
1881
  }
2105
1882
  } else {
2106
- missingPhases.push(phase2);
1883
+ missingPhases.push(phase);
2107
1884
  }
2108
1885
  }
2109
1886
  return {
@@ -2128,7 +1905,7 @@ function handleListArtifacts(_cwd) {
2128
1905
  for (const phase of PHASE_ORDER) {
2129
1906
  const expectedFile = PHASE_ARTIFACT[phase];
2130
1907
  const fullPath = join3(artifactsDir, expectedFile);
2131
- const exists = existsSync4(fullPath);
1908
+ const exists = existsSync3(fullPath);
2132
1909
  if (exists) completedCount++;
2133
1910
  artifacts.push({ phase, expectedFile, exists, path: fullPath });
2134
1911
  }
@@ -2167,7 +1944,7 @@ function handleWritePhaseQuestions(input) {
2167
1944
  } catch {
2168
1945
  }
2169
1946
  const questionsDir = join3(cwd, ".stackwright", "questions");
2170
- mkdirSync3(questionsDir, { recursive: true });
1947
+ mkdirSync2(questionsDir, { recursive: true });
2171
1948
  const filePath = join3(questionsDir, `${phase}.json`);
2172
1949
  const payload = {
2173
1950
  version: "1.0",
@@ -2210,38 +1987,26 @@ function handleBuildSpecialistPrompt(input) {
2210
1987
  try {
2211
1988
  const answersPath = join3(cwd, ".stackwright", "answers", `${phase}.json`);
2212
1989
  let answers = {};
2213
- if (existsSync4(answersPath)) {
1990
+ if (existsSync3(answersPath)) {
2214
1991
  answers = JSON.parse(safeReadSync(answersPath));
2215
1992
  }
2216
- let buildContextText = "";
2217
- const buildContextPath = join3(cwd, ".stackwright", "build-context.json");
2218
- if (existsSync4(buildContextPath)) {
2219
- try {
2220
- const bcRaw = JSON.parse(safeReadSync(buildContextPath));
2221
- if (typeof bcRaw.buildContext === "string" && bcRaw.buildContext.trim().length > 0) {
2222
- buildContextText = bcRaw.buildContext.trim();
2223
- }
2224
- } catch {
2225
- }
2226
- }
2227
1993
  const deps = PHASE_DEPENDENCIES[phase];
2228
1994
  const artifactSections = [];
2229
1995
  const missingDependencies = [];
2230
1996
  for (const dep of deps) {
2231
1997
  const artifactFile = PHASE_ARTIFACT[dep];
2232
1998
  const artifactPath = join3(cwd, ".stackwright", "artifacts", artifactFile);
2233
- if (existsSync4(artifactPath)) {
1999
+ if (existsSync3(artifactPath)) {
2234
2000
  const content = JSON.parse(safeReadSync(artifactPath));
2235
2001
  const expectedOtter = PHASE_TO_OTTER2[dep];
2236
2002
  const artifactOtter = content["generatedBy"];
2237
- const normalizedOtter = artifactOtter?.replace(/-[a-f0-9]{6}$/, "");
2238
2003
  if (!artifactOtter) {
2239
2004
  missingDependencies.push(dep);
2240
2005
  artifactSections.push(
2241
2006
  `[${artifactFile}]:
2242
2007
  (integrity check failed: missing generatedBy field)`
2243
2008
  );
2244
- } else if (normalizedOtter !== expectedOtter) {
2009
+ } else if (artifactOtter !== expectedOtter) {
2245
2010
  missingDependencies.push(dep);
2246
2011
  artifactSections.push(
2247
2012
  `[${artifactFile}]:
@@ -2257,11 +2022,7 @@ ${JSON.stringify(content, null, 2)}`);
2257
2022
  (not yet available)`);
2258
2023
  }
2259
2024
  }
2260
- const parts = [];
2261
- if (buildContextText) {
2262
- parts.push("BUILD_CONTEXT:", buildContextText, "");
2263
- }
2264
- parts.push("ANSWERS:", JSON.stringify(answers, null, 2));
2025
+ const parts = ["ANSWERS:", JSON.stringify(answers, null, 2)];
2265
2026
  if (artifactSections.length > 0) {
2266
2027
  parts.push("", "UPSTREAM ARTIFACTS:", "", ...artifactSections);
2267
2028
  }
@@ -2360,7 +2121,7 @@ function handleValidateArtifact(input) {
2360
2121
  }
2361
2122
  try {
2362
2123
  const artifactsDir = join3(cwd, ".stackwright", "artifacts");
2363
- mkdirSync3(artifactsDir, { recursive: true });
2124
+ mkdirSync2(artifactsDir, { recursive: true });
2364
2125
  const artifactFile = PHASE_ARTIFACT[phase];
2365
2126
  const artifactPath = join3(artifactsDir, artifactFile);
2366
2127
  safeWriteSync(artifactPath, JSON.stringify(artifact, null, 2) + "\n");
@@ -2420,11 +2181,9 @@ function registerPipelineTools(server2) {
2420
2181
  );
2421
2182
  server2.tool(
2422
2183
  "stackwright_pro_check_execution_ready",
2423
- `Check all phases have answer files in .stackwright/answers/. If phase is provided, check only that phase. ${DESC}`,
2424
- {
2425
- phase: z10.string().optional().describe("If provided, check only this phase's readiness. Omit to check all phases.")
2426
- },
2427
- async ({ phase }) => res(handleCheckExecutionReady(void 0, phase))
2184
+ `Check all phases have answer files in .stackwright/answers/. ${DESC}`,
2185
+ {},
2186
+ async () => res(handleCheckExecutionReady())
2428
2187
  );
2429
2188
  server2.tool(
2430
2189
  "stackwright_pro_list_artifacts",
@@ -2434,49 +2193,12 @@ function registerPipelineTools(server2) {
2434
2193
  );
2435
2194
  server2.tool(
2436
2195
  "stackwright_pro_write_phase_questions",
2437
- `Parse otter question-collection response \u2192 .stackwright/questions/{phase}.json. Specialists may also call this directly with a parsed questions array. ${DESC}`,
2196
+ `Parse otter question-collection response \u2192 .stackwright/questions/{phase}.json. ${DESC}`,
2438
2197
  {
2439
- phase: z10.string().optional().describe('Phase name, e.g. "designer" (required for direct write)'),
2440
- responseText: z10.string().optional().describe("Raw LLM response from QUESTION_COLLECTION_MODE"),
2441
- questions: jsonCoerce(z10.array(z10.any()).optional()).describe(
2442
- "Questions array for direct specialist write"
2443
- )
2198
+ phase: z10.string().describe('Phase name, e.g. "designer"'),
2199
+ responseText: z10.string().describe("Raw LLM response from QUESTION_COLLECTION_MODE")
2444
2200
  },
2445
- async ({ phase, responseText, questions }) => {
2446
- if (phase && questions && Array.isArray(questions)) {
2447
- const SAFE_PHASE = /^[a-z][a-z0-9-]{0,30}$/;
2448
- if (!SAFE_PHASE.test(phase)) {
2449
- return {
2450
- content: [
2451
- { type: "text", text: JSON.stringify({ error: `Invalid phase name` }) }
2452
- ],
2453
- isError: true
2454
- };
2455
- }
2456
- const questionsDir = join3(process.cwd(), ".stackwright", "questions");
2457
- mkdirSync3(questionsDir, { recursive: true });
2458
- const outPath = join3(questionsDir, `${phase}.json`);
2459
- writeFileSync3(
2460
- outPath,
2461
- JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
2462
- );
2463
- return {
2464
- content: [
2465
- {
2466
- type: "text",
2467
- text: JSON.stringify({
2468
- written: true,
2469
- phase,
2470
- count: questions.length
2471
- })
2472
- }
2473
- ]
2474
- };
2475
- }
2476
- return res(
2477
- handleWritePhaseQuestions({ phase: phase ?? "", responseText: responseText ?? "" })
2478
- );
2479
- }
2201
+ async ({ phase, responseText }) => res(handleWritePhaseQuestions({ phase, responseText }))
2480
2202
  );
2481
2203
  server2.tool(
2482
2204
  "stackwright_pro_build_specialist_prompt",
@@ -2497,7 +2219,7 @@ function registerPipelineTools(server2) {
2497
2219
 
2498
2220
  // src/tools/safe-write.ts
2499
2221
  import { z as z11 } from "zod";
2500
- import { writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync4, lstatSync as lstatSync5 } from "fs";
2222
+ import { writeFileSync as writeFileSync4, existsSync as existsSync4, mkdirSync as mkdirSync3, lstatSync as lstatSync4 } from "fs";
2501
2223
  import { normalize, isAbsolute, dirname, join as join4 } from "path";
2502
2224
  var OTTER_WRITE_ALLOWLISTS = {
2503
2225
  "stackwright-pro-designer-otter": [
@@ -2671,9 +2393,9 @@ function handleSafeWrite(input) {
2671
2393
  }
2672
2394
  const normalized = normalize(filePath);
2673
2395
  const fullPath = join4(cwd, normalized);
2674
- if (existsSync5(fullPath)) {
2396
+ if (existsSync4(fullPath)) {
2675
2397
  try {
2676
- const stat = lstatSync5(fullPath);
2398
+ const stat = lstatSync4(fullPath);
2677
2399
  if (stat.isSymbolicLink()) {
2678
2400
  const result = {
2679
2401
  success: false,
@@ -2700,7 +2422,7 @@ function handleSafeWrite(input) {
2700
2422
  }
2701
2423
  try {
2702
2424
  if (createDirectories) {
2703
- mkdirSync4(dirname(fullPath), { recursive: true });
2425
+ mkdirSync3(dirname(fullPath), { recursive: true });
2704
2426
  }
2705
2427
  writeFileSync4(fullPath, content, { encoding: "utf-8" });
2706
2428
  const result = {
@@ -2731,9 +2453,7 @@ function registerSafeWriteTools(server2) {
2731
2453
  callerOtter: z11.string().describe('The otter agent name requesting the write, e.g. "stackwright-pro-page-otter"'),
2732
2454
  filePath: z11.string().describe('Relative path from project root, e.g. "pages/dashboard/content.yml"'),
2733
2455
  content: z11.string().describe("File content to write"),
2734
- createDirectories: boolCoerce(z11.boolean().optional().default(true)).describe(
2735
- "Create parent directories if they don't exist. Default: true"
2736
- )
2456
+ createDirectories: z11.boolean().optional().describe("Create parent directories if they don't exist. Default: true")
2737
2457
  },
2738
2458
  async ({ callerOtter, filePath, content, createDirectories }) => {
2739
2459
  const result = handleSafeWrite({
@@ -2749,7 +2469,7 @@ function registerSafeWriteTools(server2) {
2749
2469
 
2750
2470
  // src/tools/auth.ts
2751
2471
  import { z as z12 } from "zod";
2752
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync6 } from "fs";
2472
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync5 } from "fs";
2753
2473
  import { join as join5 } from "path";
2754
2474
  function buildHierarchy(roles) {
2755
2475
  const h = {};
@@ -3029,7 +2749,7 @@ async function configureAuthHandler(params, cwd) {
3029
2749
  try {
3030
2750
  const envBlock = generateEnvBlock(method, params);
3031
2751
  const envPath = join5(cwd, ".env.example");
3032
- if (existsSync6(envPath)) {
2752
+ if (existsSync5(envPath)) {
3033
2753
  const existing = readFileSync4(envPath, "utf8");
3034
2754
  writeFileSync5(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
3035
2755
  } else {
@@ -3060,7 +2780,7 @@ async function configureAuthHandler(params, cwd) {
3060
2780
  protectedRoutes
3061
2781
  );
3062
2782
  const ymlPath = join5(cwd, "stackwright.yml");
3063
- if (!existsSync6(ymlPath)) {
2783
+ if (!existsSync5(ymlPath)) {
3064
2784
  writeFileSync5(ymlPath, authYaml, "utf8");
3065
2785
  } else {
3066
2786
  const existing = readFileSync4(ymlPath, "utf8");
@@ -3142,44 +2862,44 @@ function registerAuthTools(server2) {
3142
2862
 
3143
2863
  // src/integrity.ts
3144
2864
  import { createHash as createHash2, timingSafeEqual } from "crypto";
3145
- import { readFileSync as readFileSync5, readdirSync, lstatSync as lstatSync6 } from "fs";
2865
+ import { readFileSync as readFileSync5, readdirSync, lstatSync as lstatSync5 } from "fs";
3146
2866
  import { join as join6, basename } from "path";
3147
2867
  var _checksums = /* @__PURE__ */ new Map([
3148
2868
  [
3149
2869
  "stackwright-pro-api-otter.json",
3150
- "0ac26d85a5ad35b072a58965e1d5e090dd5c5f16dc14e68c452c3e99fcbb5510"
2870
+ "f1cc9edf2dd1df3ebcea1d0ab33d17a358faaf8aa97ee232cd7994042f2eac0d"
3151
2871
  ],
3152
2872
  [
3153
2873
  "stackwright-pro-auth-otter.json",
3154
- "d789b71f196659d5745ebfca87a7bda60a1bb63cfeccd17b4a273ac1e29bb08d"
2874
+ "a19e06c503209a8a35fe321d30448623545b36b48c47a6ec064d13406ad1f725"
3155
2875
  ],
3156
2876
  [
3157
2877
  "stackwright-pro-dashboard-otter.json",
3158
- "600e8597429c353e5b886f316731be84a86cd8b93617bf046e3cbf390b31a431"
2878
+ "b3cb3d7554f2e9eed3b57d5e0e3bf85d6ba5b4db5d3af5514391cf0575fcc001"
3159
2879
  ],
3160
2880
  [
3161
2881
  "stackwright-pro-data-otter.json",
3162
- "b2946e3da3b53282c122d150e6db86b0cb89d2edba2a94a7666b26d27051be96"
2882
+ "bfacb87ae82867472a75982215554336a105a658d6cd3dd2c8b819fa1e11d7ac"
3163
2883
  ],
3164
2884
  [
3165
2885
  "stackwright-pro-designer-otter.json",
3166
- "f4dbff5149051c77be1645de5ee12c0bd7d590c687a0b2d86737b915a5a6d5f0"
2886
+ "c58fa7c7ead9e6398074e1c7ce3f31a8ef4eb3679f5fa18cc03cae3a87878c88"
3167
2887
  ],
3168
2888
  [
3169
2889
  "stackwright-pro-foreman-otter.json",
3170
- "7464523d7288374dc6efa5c213c825ec0616e00cf4f739d8039eb41df812cbc5"
2890
+ "f52264c1f6297b72f3da6d92d41b6d63356db776242caeab25571e6a8df628e4"
3171
2891
  ],
3172
2892
  [
3173
2893
  "stackwright-pro-page-otter.json",
3174
- "12aca7b666b3c85c1d96c700a2da7f209604cb75d0f064995e052711ddafd657"
2894
+ "65bec3a3a0dda6b7591bba2de9399f1e3a4fb99cfe1075342f4f4be98d917b67"
3175
2895
  ],
3176
2896
  [
3177
2897
  "stackwright-pro-theme-otter.json",
3178
- "a303ec6c045420f2c916583e3f6efcda469e9610fedfc84a508ed8a8a75866bc"
2898
+ "1f182326f1acd3d4091a38c7012085cbb4945893e95be4ca3de72318ad092767"
3179
2899
  ],
3180
2900
  [
3181
2901
  "stackwright-pro-workflow-otter.json",
3182
- "16da6c109d0b5ee60d0a14e009dbeab02a7bbac3b0947795769da053565b9821"
2902
+ "0eec9d6a731678cf547c2a7b0b6fc338ca143c35501365a1e4e5dd2779dd5510"
3183
2903
  ]
3184
2904
  ]);
3185
2905
  Object.freeze(_checksums);
@@ -3208,7 +2928,7 @@ function verifyOtterFile(filePath) {
3208
2928
  }
3209
2929
  let stat;
3210
2930
  try {
3211
- stat = lstatSync6(filePath);
2931
+ stat = lstatSync5(filePath);
3212
2932
  } catch (err) {
3213
2933
  const msg = err instanceof Error ? err.message : String(err);
3214
2934
  return { verified: false, filename, error: `Cannot stat file: ${msg}` };
@@ -3277,7 +2997,7 @@ function verifyAllOtters(otterDir) {
3277
2997
  for (const filename of otterFiles) {
3278
2998
  const filePath = join6(otterDir, filename);
3279
2999
  try {
3280
- if (lstatSync6(filePath).isSymbolicLink()) {
3000
+ if (lstatSync5(filePath).isSymbolicLink()) {
3281
3001
  failed.push({ filename, error: "Skipped: symlink" });
3282
3002
  continue;
3283
3003
  }
@@ -3305,7 +3025,7 @@ function resolveOtterDir() {
3305
3025
  for (const relative of DEFAULT_SEARCH_PATHS) {
3306
3026
  const candidate = join6(cwd, relative);
3307
3027
  try {
3308
- lstatSync6(candidate);
3028
+ lstatSync5(candidate);
3309
3029
  return candidate;
3310
3030
  } catch {
3311
3031
  }
@@ -3360,7 +3080,7 @@ function registerIntegrityTools(server2) {
3360
3080
 
3361
3081
  // src/tools/domain.ts
3362
3082
  import { z as z13 } from "zod";
3363
- import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
3083
+ import { readFileSync as readFileSync6, existsSync as existsSync6 } from "fs";
3364
3084
  import { join as join7 } from "path";
3365
3085
  function handleListCollections(input) {
3366
3086
  const cwd = input._cwd ?? process.cwd();
@@ -3383,7 +3103,7 @@ function handleListCollections(input) {
3383
3103
  }
3384
3104
  ];
3385
3105
  for (const { path: path3, source, parse } of sources) {
3386
- if (!existsSync7(path3)) continue;
3106
+ if (!existsSync6(path3)) continue;
3387
3107
  try {
3388
3108
  const collections = parse(readFileSync6(path3, "utf8"));
3389
3109
  return {
@@ -3537,7 +3257,7 @@ function handleValidateWorkflow(input) {
3537
3257
  raw = input.workflow;
3538
3258
  } else {
3539
3259
  const artifactPath = join7(cwd, ".stackwright", "artifacts", "workflow-config.json");
3540
- if (!existsSync7(artifactPath)) {
3260
+ if (!existsSync6(artifactPath)) {
3541
3261
  return fail([
3542
3262
  {
3543
3263
  code: "NO_WORKFLOW",
@@ -3785,7 +3505,7 @@ var package_default = {
3785
3505
  "test:coverage": "vitest run --coverage"
3786
3506
  },
3787
3507
  name: "@stackwright-pro/mcp",
3788
- version: "0.2.0-alpha.20",
3508
+ version: "0.2.0-alpha.13",
3789
3509
  description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
3790
3510
  license: "PROPRIETARY",
3791
3511
  main: "./dist/server.js",