@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/integrity.js +9 -9
- package/dist/integrity.js.map +1 -1
- package/dist/integrity.mjs +9 -9
- package/dist/integrity.mjs.map +1 -1
- package/dist/server.js +33 -313
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +69 -349
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
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
|
|
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:
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
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
|
|
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
|
-
|
|
1612
|
-
if (
|
|
1613
|
-
const stat =
|
|
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
|
-
|
|
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 (
|
|
1661
|
-
const stat =
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 (
|
|
1961
|
-
const stat =
|
|
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 (
|
|
1970
|
-
const stat =
|
|
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
|
-
|
|
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
|
|
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
|
|
2092
|
-
const answerFile = join3(answersDir, `${
|
|
2093
|
-
if (
|
|
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(
|
|
1875
|
+
missingPhases.push(phase);
|
|
2099
1876
|
continue;
|
|
2100
1877
|
}
|
|
2101
|
-
answeredPhases.push(
|
|
1878
|
+
answeredPhases.push(phase);
|
|
2102
1879
|
} catch {
|
|
2103
|
-
missingPhases.push(
|
|
1880
|
+
missingPhases.push(phase);
|
|
2104
1881
|
}
|
|
2105
1882
|
} else {
|
|
2106
|
-
missingPhases.push(
|
|
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 =
|
|
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
|
-
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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/.
|
|
2424
|
-
{
|
|
2425
|
-
|
|
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.
|
|
2196
|
+
`Parse otter question-collection response \u2192 .stackwright/questions/{phase}.json. ${DESC}`,
|
|
2438
2197
|
{
|
|
2439
|
-
phase: z10.string().
|
|
2440
|
-
responseText: z10.string().
|
|
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
|
|
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
|
|
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 (
|
|
2396
|
+
if (existsSync4(fullPath)) {
|
|
2675
2397
|
try {
|
|
2676
|
-
const stat =
|
|
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
|
-
|
|
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:
|
|
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
|
|
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 (
|
|
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 (!
|
|
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
|
|
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
|
-
"
|
|
2870
|
+
"f1cc9edf2dd1df3ebcea1d0ab33d17a358faaf8aa97ee232cd7994042f2eac0d"
|
|
3151
2871
|
],
|
|
3152
2872
|
[
|
|
3153
2873
|
"stackwright-pro-auth-otter.json",
|
|
3154
|
-
"
|
|
2874
|
+
"a19e06c503209a8a35fe321d30448623545b36b48c47a6ec064d13406ad1f725"
|
|
3155
2875
|
],
|
|
3156
2876
|
[
|
|
3157
2877
|
"stackwright-pro-dashboard-otter.json",
|
|
3158
|
-
"
|
|
2878
|
+
"b3cb3d7554f2e9eed3b57d5e0e3bf85d6ba5b4db5d3af5514391cf0575fcc001"
|
|
3159
2879
|
],
|
|
3160
2880
|
[
|
|
3161
2881
|
"stackwright-pro-data-otter.json",
|
|
3162
|
-
"
|
|
2882
|
+
"bfacb87ae82867472a75982215554336a105a658d6cd3dd2c8b819fa1e11d7ac"
|
|
3163
2883
|
],
|
|
3164
2884
|
[
|
|
3165
2885
|
"stackwright-pro-designer-otter.json",
|
|
3166
|
-
"
|
|
2886
|
+
"c58fa7c7ead9e6398074e1c7ce3f31a8ef4eb3679f5fa18cc03cae3a87878c88"
|
|
3167
2887
|
],
|
|
3168
2888
|
[
|
|
3169
2889
|
"stackwright-pro-foreman-otter.json",
|
|
3170
|
-
"
|
|
2890
|
+
"f52264c1f6297b72f3da6d92d41b6d63356db776242caeab25571e6a8df628e4"
|
|
3171
2891
|
],
|
|
3172
2892
|
[
|
|
3173
2893
|
"stackwright-pro-page-otter.json",
|
|
3174
|
-
"
|
|
2894
|
+
"65bec3a3a0dda6b7591bba2de9399f1e3a4fb99cfe1075342f4f4be98d917b67"
|
|
3175
2895
|
],
|
|
3176
2896
|
[
|
|
3177
2897
|
"stackwright-pro-theme-otter.json",
|
|
3178
|
-
"
|
|
2898
|
+
"1f182326f1acd3d4091a38c7012085cbb4945893e95be4ca3de72318ad092767"
|
|
3179
2899
|
],
|
|
3180
2900
|
[
|
|
3181
2901
|
"stackwright-pro-workflow-otter.json",
|
|
3182
|
-
"
|
|
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 =
|
|
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 (
|
|
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
|
-
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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.
|
|
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",
|