@stackwright-pro/mcp 0.2.0-alpha.17 → 0.2.0-alpha.19
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 +8 -8
- package/dist/integrity.js.map +1 -1
- package/dist/integrity.mjs +8 -8
- package/dist/integrity.mjs.map +1 -1
- package/dist/server.js +189 -13
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +230 -54
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/server.mjs
CHANGED
|
@@ -1110,7 +1110,8 @@ function setupPackages(opts) {
|
|
|
1110
1110
|
}
|
|
1111
1111
|
|
|
1112
1112
|
// src/tools/questions.ts
|
|
1113
|
-
import { readFile } from "fs/promises";
|
|
1113
|
+
import { readFile, writeFile } from "fs/promises";
|
|
1114
|
+
import { existsSync as existsSync2, lstatSync as lstatSync2, mkdirSync, renameSync } from "fs";
|
|
1114
1115
|
import { join } from "path";
|
|
1115
1116
|
import { z as z8 } from "zod";
|
|
1116
1117
|
|
|
@@ -1409,11 +1410,149 @@ function registerQuestionTools(server2) {
|
|
|
1409
1410
|
};
|
|
1410
1411
|
}
|
|
1411
1412
|
);
|
|
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
|
+
);
|
|
1412
1551
|
}
|
|
1413
1552
|
|
|
1414
1553
|
// src/tools/orchestration.ts
|
|
1415
1554
|
import { z as z9 } from "zod";
|
|
1416
|
-
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as
|
|
1555
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync as mkdirSync2, lstatSync as lstatSync3 } from "fs";
|
|
1417
1556
|
import { join as join2 } from "path";
|
|
1418
1557
|
var OTTER_NAME_TO_PHASE = [
|
|
1419
1558
|
["designer", "designer"],
|
|
@@ -1469,9 +1608,9 @@ function handleSaveManifest(input) {
|
|
|
1469
1608
|
const dir = join2(cwd, ".stackwright");
|
|
1470
1609
|
const filePath = join2(dir, "question-manifest.json");
|
|
1471
1610
|
try {
|
|
1472
|
-
|
|
1473
|
-
if (
|
|
1474
|
-
const stat =
|
|
1611
|
+
mkdirSync2(dir, { recursive: true });
|
|
1612
|
+
if (existsSync3(filePath)) {
|
|
1613
|
+
const stat = lstatSync3(filePath);
|
|
1475
1614
|
if (stat.isSymbolicLink()) {
|
|
1476
1615
|
const message = `Refusing to write to symlink: ${filePath}`;
|
|
1477
1616
|
return {
|
|
@@ -1503,7 +1642,7 @@ function handleSavePhaseAnswers(input) {
|
|
|
1503
1642
|
const dir = join2(cwd, ".stackwright", "answers");
|
|
1504
1643
|
const filePath = join2(dir, `${input.phase}.json`);
|
|
1505
1644
|
try {
|
|
1506
|
-
|
|
1645
|
+
mkdirSync2(dir, { recursive: true });
|
|
1507
1646
|
let answers;
|
|
1508
1647
|
if (input.questions && input.questions.length > 0) {
|
|
1509
1648
|
answers = answersToManifestFormat(input.rawAnswers, input.questions);
|
|
@@ -1518,8 +1657,8 @@ function handleSavePhaseAnswers(input) {
|
|
|
1518
1657
|
completedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1519
1658
|
answers
|
|
1520
1659
|
};
|
|
1521
|
-
if (
|
|
1522
|
-
const stat =
|
|
1660
|
+
if (existsSync3(filePath)) {
|
|
1661
|
+
const stat = lstatSync3(filePath);
|
|
1523
1662
|
if (stat.isSymbolicLink()) {
|
|
1524
1663
|
const message = `Refusing to write to symlink: ${filePath}`;
|
|
1525
1664
|
return {
|
|
@@ -1548,7 +1687,7 @@ function handleSavePhaseAnswers(input) {
|
|
|
1548
1687
|
function handleReadPhaseAnswers(input) {
|
|
1549
1688
|
const cwd = input._cwd ?? process.cwd();
|
|
1550
1689
|
const filePath = join2(cwd, ".stackwright", "answers", `${input.phase}.json`);
|
|
1551
|
-
if (!
|
|
1690
|
+
if (!existsSync3(filePath)) {
|
|
1552
1691
|
return {
|
|
1553
1692
|
text: JSON.stringify({ missing: true, phase: input.phase }),
|
|
1554
1693
|
isError: false
|
|
@@ -1585,9 +1724,9 @@ function handleSaveBuildContext(input) {
|
|
|
1585
1724
|
const dir = join2(cwd, ".stackwright");
|
|
1586
1725
|
const filePath = join2(dir, "build-context.json");
|
|
1587
1726
|
try {
|
|
1588
|
-
|
|
1589
|
-
if (
|
|
1590
|
-
const stat =
|
|
1727
|
+
mkdirSync2(dir, { recursive: true });
|
|
1728
|
+
if (existsSync3(filePath)) {
|
|
1729
|
+
const stat = lstatSync3(filePath);
|
|
1591
1730
|
if (stat.isSymbolicLink()) {
|
|
1592
1731
|
return {
|
|
1593
1732
|
text: JSON.stringify({
|
|
@@ -1736,7 +1875,7 @@ function registerOrchestrationTools(server2) {
|
|
|
1736
1875
|
|
|
1737
1876
|
// src/tools/pipeline.ts
|
|
1738
1877
|
import { z as z10 } from "zod";
|
|
1739
|
-
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as
|
|
1878
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3, existsSync as existsSync4, mkdirSync as mkdirSync3, lstatSync as lstatSync4 } from "fs";
|
|
1740
1879
|
import { join as join3 } from "path";
|
|
1741
1880
|
var PHASE_ORDER = [
|
|
1742
1881
|
"designer",
|
|
@@ -1810,7 +1949,7 @@ function statePath(cwd) {
|
|
|
1810
1949
|
}
|
|
1811
1950
|
function readState(cwd) {
|
|
1812
1951
|
const p = statePath(cwd);
|
|
1813
|
-
if (!
|
|
1952
|
+
if (!existsSync4(p)) return createDefaultState();
|
|
1814
1953
|
const raw = JSON.parse(safeReadSync(p));
|
|
1815
1954
|
if (typeof raw !== "object" || raw === null || raw.version !== "1.0") {
|
|
1816
1955
|
return createDefaultState();
|
|
@@ -1818,8 +1957,8 @@ function readState(cwd) {
|
|
|
1818
1957
|
return raw;
|
|
1819
1958
|
}
|
|
1820
1959
|
function safeWriteSync(filePath, content) {
|
|
1821
|
-
if (
|
|
1822
|
-
const stat =
|
|
1960
|
+
if (existsSync4(filePath)) {
|
|
1961
|
+
const stat = lstatSync4(filePath);
|
|
1823
1962
|
if (stat.isSymbolicLink()) {
|
|
1824
1963
|
throw new Error(`Refusing to write to symlink: ${filePath}`);
|
|
1825
1964
|
}
|
|
@@ -1827,8 +1966,8 @@ function safeWriteSync(filePath, content) {
|
|
|
1827
1966
|
writeFileSync3(filePath, content);
|
|
1828
1967
|
}
|
|
1829
1968
|
function safeReadSync(filePath) {
|
|
1830
|
-
if (
|
|
1831
|
-
const stat =
|
|
1969
|
+
if (existsSync4(filePath)) {
|
|
1970
|
+
const stat = lstatSync4(filePath);
|
|
1832
1971
|
if (stat.isSymbolicLink()) {
|
|
1833
1972
|
throw new Error(`Refusing to read symlink: ${filePath}`);
|
|
1834
1973
|
}
|
|
@@ -1837,7 +1976,7 @@ function safeReadSync(filePath) {
|
|
|
1837
1976
|
}
|
|
1838
1977
|
function writeState(cwd, state) {
|
|
1839
1978
|
const dir = join3(cwd, ".stackwright");
|
|
1840
|
-
|
|
1979
|
+
mkdirSync3(dir, { recursive: true });
|
|
1841
1980
|
state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1842
1981
|
safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
|
|
1843
1982
|
}
|
|
@@ -1922,7 +2061,7 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
1922
2061
|
};
|
|
1923
2062
|
}
|
|
1924
2063
|
const answerFile = join3(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
1925
|
-
if (!
|
|
2064
|
+
if (!existsSync4(answerFile)) {
|
|
1926
2065
|
return {
|
|
1927
2066
|
text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
|
|
1928
2067
|
isError: false
|
|
@@ -1951,7 +2090,7 @@ function handleCheckExecutionReady(_cwd, phase) {
|
|
|
1951
2090
|
const missingPhases = [];
|
|
1952
2091
|
for (const phase2 of PHASE_ORDER) {
|
|
1953
2092
|
const answerFile = join3(answersDir, `${phase2}.json`);
|
|
1954
|
-
if (
|
|
2093
|
+
if (existsSync4(answerFile)) {
|
|
1955
2094
|
try {
|
|
1956
2095
|
const raw = safeReadSync(answerFile);
|
|
1957
2096
|
const parsed = JSON.parse(raw);
|
|
@@ -1989,7 +2128,7 @@ function handleListArtifacts(_cwd) {
|
|
|
1989
2128
|
for (const phase of PHASE_ORDER) {
|
|
1990
2129
|
const expectedFile = PHASE_ARTIFACT[phase];
|
|
1991
2130
|
const fullPath = join3(artifactsDir, expectedFile);
|
|
1992
|
-
const exists =
|
|
2131
|
+
const exists = existsSync4(fullPath);
|
|
1993
2132
|
if (exists) completedCount++;
|
|
1994
2133
|
artifacts.push({ phase, expectedFile, exists, path: fullPath });
|
|
1995
2134
|
}
|
|
@@ -2028,7 +2167,7 @@ function handleWritePhaseQuestions(input) {
|
|
|
2028
2167
|
} catch {
|
|
2029
2168
|
}
|
|
2030
2169
|
const questionsDir = join3(cwd, ".stackwright", "questions");
|
|
2031
|
-
|
|
2170
|
+
mkdirSync3(questionsDir, { recursive: true });
|
|
2032
2171
|
const filePath = join3(questionsDir, `${phase}.json`);
|
|
2033
2172
|
const payload = {
|
|
2034
2173
|
version: "1.0",
|
|
@@ -2071,12 +2210,12 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
2071
2210
|
try {
|
|
2072
2211
|
const answersPath = join3(cwd, ".stackwright", "answers", `${phase}.json`);
|
|
2073
2212
|
let answers = {};
|
|
2074
|
-
if (
|
|
2213
|
+
if (existsSync4(answersPath)) {
|
|
2075
2214
|
answers = JSON.parse(safeReadSync(answersPath));
|
|
2076
2215
|
}
|
|
2077
2216
|
let buildContextText = "";
|
|
2078
2217
|
const buildContextPath = join3(cwd, ".stackwright", "build-context.json");
|
|
2079
|
-
if (
|
|
2218
|
+
if (existsSync4(buildContextPath)) {
|
|
2080
2219
|
try {
|
|
2081
2220
|
const bcRaw = JSON.parse(safeReadSync(buildContextPath));
|
|
2082
2221
|
if (typeof bcRaw.buildContext === "string" && bcRaw.buildContext.trim().length > 0) {
|
|
@@ -2091,7 +2230,7 @@ function handleBuildSpecialistPrompt(input) {
|
|
|
2091
2230
|
for (const dep of deps) {
|
|
2092
2231
|
const artifactFile = PHASE_ARTIFACT[dep];
|
|
2093
2232
|
const artifactPath = join3(cwd, ".stackwright", "artifacts", artifactFile);
|
|
2094
|
-
if (
|
|
2233
|
+
if (existsSync4(artifactPath)) {
|
|
2095
2234
|
const content = JSON.parse(safeReadSync(artifactPath));
|
|
2096
2235
|
const expectedOtter = PHASE_TO_OTTER2[dep];
|
|
2097
2236
|
const artifactOtter = content["generatedBy"];
|
|
@@ -2220,7 +2359,7 @@ function handleValidateArtifact(input) {
|
|
|
2220
2359
|
}
|
|
2221
2360
|
try {
|
|
2222
2361
|
const artifactsDir = join3(cwd, ".stackwright", "artifacts");
|
|
2223
|
-
|
|
2362
|
+
mkdirSync3(artifactsDir, { recursive: true });
|
|
2224
2363
|
const artifactFile = PHASE_ARTIFACT[phase];
|
|
2225
2364
|
const artifactPath = join3(artifactsDir, artifactFile);
|
|
2226
2365
|
safeWriteSync(artifactPath, JSON.stringify(artifact, null, 2) + "\n");
|
|
@@ -2294,12 +2433,49 @@ function registerPipelineTools(server2) {
|
|
|
2294
2433
|
);
|
|
2295
2434
|
server2.tool(
|
|
2296
2435
|
"stackwright_pro_write_phase_questions",
|
|
2297
|
-
`Parse otter question-collection response \u2192 .stackwright/questions/{phase}.json. ${DESC}`,
|
|
2436
|
+
`Parse otter question-collection response \u2192 .stackwright/questions/{phase}.json. Specialists may also call this directly with a parsed questions array. ${DESC}`,
|
|
2298
2437
|
{
|
|
2299
|
-
phase: z10.string().describe('Phase name, e.g. "designer"'),
|
|
2300
|
-
responseText: z10.string().describe("Raw LLM response from QUESTION_COLLECTION_MODE")
|
|
2438
|
+
phase: z10.string().optional().describe('Phase name, e.g. "designer" (required for direct write)'),
|
|
2439
|
+
responseText: z10.string().optional().describe("Raw LLM response from QUESTION_COLLECTION_MODE"),
|
|
2440
|
+
questions: jsonCoerce(z10.array(z10.any()).optional()).describe(
|
|
2441
|
+
"Questions array for direct specialist write"
|
|
2442
|
+
)
|
|
2301
2443
|
},
|
|
2302
|
-
async ({ phase, responseText }) =>
|
|
2444
|
+
async ({ phase, responseText, questions }) => {
|
|
2445
|
+
if (phase && questions && Array.isArray(questions)) {
|
|
2446
|
+
const SAFE_PHASE = /^[a-z][a-z0-9-]{0,30}$/;
|
|
2447
|
+
if (!SAFE_PHASE.test(phase)) {
|
|
2448
|
+
return {
|
|
2449
|
+
content: [
|
|
2450
|
+
{ type: "text", text: JSON.stringify({ error: `Invalid phase name` }) }
|
|
2451
|
+
],
|
|
2452
|
+
isError: true
|
|
2453
|
+
};
|
|
2454
|
+
}
|
|
2455
|
+
const questionsDir = join3(process.cwd(), ".stackwright", "questions");
|
|
2456
|
+
mkdirSync3(questionsDir, { recursive: true });
|
|
2457
|
+
const outPath = join3(questionsDir, `${phase}.json`);
|
|
2458
|
+
writeFileSync3(
|
|
2459
|
+
outPath,
|
|
2460
|
+
JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
|
|
2461
|
+
);
|
|
2462
|
+
return {
|
|
2463
|
+
content: [
|
|
2464
|
+
{
|
|
2465
|
+
type: "text",
|
|
2466
|
+
text: JSON.stringify({
|
|
2467
|
+
written: true,
|
|
2468
|
+
phase,
|
|
2469
|
+
count: questions.length
|
|
2470
|
+
})
|
|
2471
|
+
}
|
|
2472
|
+
]
|
|
2473
|
+
};
|
|
2474
|
+
}
|
|
2475
|
+
return res(
|
|
2476
|
+
handleWritePhaseQuestions({ phase: phase ?? "", responseText: responseText ?? "" })
|
|
2477
|
+
);
|
|
2478
|
+
}
|
|
2303
2479
|
);
|
|
2304
2480
|
server2.tool(
|
|
2305
2481
|
"stackwright_pro_build_specialist_prompt",
|
|
@@ -2320,7 +2496,7 @@ function registerPipelineTools(server2) {
|
|
|
2320
2496
|
|
|
2321
2497
|
// src/tools/safe-write.ts
|
|
2322
2498
|
import { z as z11 } from "zod";
|
|
2323
|
-
import { writeFileSync as writeFileSync4, existsSync as
|
|
2499
|
+
import { writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync4, lstatSync as lstatSync5 } from "fs";
|
|
2324
2500
|
import { normalize, isAbsolute, dirname, join as join4 } from "path";
|
|
2325
2501
|
var OTTER_WRITE_ALLOWLISTS = {
|
|
2326
2502
|
"stackwright-pro-designer-otter": [
|
|
@@ -2494,9 +2670,9 @@ function handleSafeWrite(input) {
|
|
|
2494
2670
|
}
|
|
2495
2671
|
const normalized = normalize(filePath);
|
|
2496
2672
|
const fullPath = join4(cwd, normalized);
|
|
2497
|
-
if (
|
|
2673
|
+
if (existsSync5(fullPath)) {
|
|
2498
2674
|
try {
|
|
2499
|
-
const stat =
|
|
2675
|
+
const stat = lstatSync5(fullPath);
|
|
2500
2676
|
if (stat.isSymbolicLink()) {
|
|
2501
2677
|
const result = {
|
|
2502
2678
|
success: false,
|
|
@@ -2523,7 +2699,7 @@ function handleSafeWrite(input) {
|
|
|
2523
2699
|
}
|
|
2524
2700
|
try {
|
|
2525
2701
|
if (createDirectories) {
|
|
2526
|
-
|
|
2702
|
+
mkdirSync4(dirname(fullPath), { recursive: true });
|
|
2527
2703
|
}
|
|
2528
2704
|
writeFileSync4(fullPath, content, { encoding: "utf-8" });
|
|
2529
2705
|
const result = {
|
|
@@ -2570,7 +2746,7 @@ function registerSafeWriteTools(server2) {
|
|
|
2570
2746
|
|
|
2571
2747
|
// src/tools/auth.ts
|
|
2572
2748
|
import { z as z12 } from "zod";
|
|
2573
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as
|
|
2749
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync5, existsSync as existsSync6 } from "fs";
|
|
2574
2750
|
import { join as join5 } from "path";
|
|
2575
2751
|
function buildHierarchy(roles) {
|
|
2576
2752
|
const h = {};
|
|
@@ -2850,7 +3026,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
2850
3026
|
try {
|
|
2851
3027
|
const envBlock = generateEnvBlock(method, params);
|
|
2852
3028
|
const envPath = join5(cwd, ".env.example");
|
|
2853
|
-
if (
|
|
3029
|
+
if (existsSync6(envPath)) {
|
|
2854
3030
|
const existing = readFileSync4(envPath, "utf8");
|
|
2855
3031
|
writeFileSync5(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
|
|
2856
3032
|
} else {
|
|
@@ -2881,7 +3057,7 @@ async function configureAuthHandler(params, cwd) {
|
|
|
2881
3057
|
protectedRoutes
|
|
2882
3058
|
);
|
|
2883
3059
|
const ymlPath = join5(cwd, "stackwright.yml");
|
|
2884
|
-
if (!
|
|
3060
|
+
if (!existsSync6(ymlPath)) {
|
|
2885
3061
|
writeFileSync5(ymlPath, authYaml, "utf8");
|
|
2886
3062
|
} else {
|
|
2887
3063
|
const existing = readFileSync4(ymlPath, "utf8");
|
|
@@ -2963,36 +3139,36 @@ function registerAuthTools(server2) {
|
|
|
2963
3139
|
|
|
2964
3140
|
// src/integrity.ts
|
|
2965
3141
|
import { createHash as createHash2, timingSafeEqual } from "crypto";
|
|
2966
|
-
import { readFileSync as readFileSync5, readdirSync, lstatSync as
|
|
3142
|
+
import { readFileSync as readFileSync5, readdirSync, lstatSync as lstatSync6 } from "fs";
|
|
2967
3143
|
import { join as join6, basename } from "path";
|
|
2968
3144
|
var _checksums = /* @__PURE__ */ new Map([
|
|
2969
3145
|
[
|
|
2970
3146
|
"stackwright-pro-api-otter.json",
|
|
2971
|
-
"
|
|
3147
|
+
"0ac26d85a5ad35b072a58965e1d5e090dd5c5f16dc14e68c452c3e99fcbb5510"
|
|
2972
3148
|
],
|
|
2973
3149
|
[
|
|
2974
3150
|
"stackwright-pro-auth-otter.json",
|
|
2975
|
-
"
|
|
3151
|
+
"d789b71f196659d5745ebfca87a7bda60a1bb63cfeccd17b4a273ac1e29bb08d"
|
|
2976
3152
|
],
|
|
2977
3153
|
[
|
|
2978
3154
|
"stackwright-pro-dashboard-otter.json",
|
|
2979
|
-
"
|
|
3155
|
+
"600e8597429c353e5b886f316731be84a86cd8b93617bf046e3cbf390b31a431"
|
|
2980
3156
|
],
|
|
2981
3157
|
[
|
|
2982
3158
|
"stackwright-pro-data-otter.json",
|
|
2983
|
-
"
|
|
3159
|
+
"b2946e3da3b53282c122d150e6db86b0cb89d2edba2a94a7666b26d27051be96"
|
|
2984
3160
|
],
|
|
2985
3161
|
[
|
|
2986
3162
|
"stackwright-pro-designer-otter.json",
|
|
2987
|
-
"
|
|
3163
|
+
"f4dbff5149051c77be1645de5ee12c0bd7d590c687a0b2d86737b915a5a6d5f0"
|
|
2988
3164
|
],
|
|
2989
3165
|
[
|
|
2990
3166
|
"stackwright-pro-foreman-otter.json",
|
|
2991
|
-
"
|
|
3167
|
+
"7464523d7288374dc6efa5c213c825ec0616e00cf4f739d8039eb41df812cbc5"
|
|
2992
3168
|
],
|
|
2993
3169
|
[
|
|
2994
3170
|
"stackwright-pro-page-otter.json",
|
|
2995
|
-
"
|
|
3171
|
+
"d75a71afa489478a6874abfbaa05baa46dcb2c16e4a5108f50f8187c9f67da60"
|
|
2996
3172
|
],
|
|
2997
3173
|
[
|
|
2998
3174
|
"stackwright-pro-theme-otter.json",
|
|
@@ -3000,7 +3176,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
3000
3176
|
],
|
|
3001
3177
|
[
|
|
3002
3178
|
"stackwright-pro-workflow-otter.json",
|
|
3003
|
-
"
|
|
3179
|
+
"16da6c109d0b5ee60d0a14e009dbeab02a7bbac3b0947795769da053565b9821"
|
|
3004
3180
|
]
|
|
3005
3181
|
]);
|
|
3006
3182
|
Object.freeze(_checksums);
|
|
@@ -3029,7 +3205,7 @@ function verifyOtterFile(filePath) {
|
|
|
3029
3205
|
}
|
|
3030
3206
|
let stat;
|
|
3031
3207
|
try {
|
|
3032
|
-
stat =
|
|
3208
|
+
stat = lstatSync6(filePath);
|
|
3033
3209
|
} catch (err) {
|
|
3034
3210
|
const msg = err instanceof Error ? err.message : String(err);
|
|
3035
3211
|
return { verified: false, filename, error: `Cannot stat file: ${msg}` };
|
|
@@ -3098,7 +3274,7 @@ function verifyAllOtters(otterDir) {
|
|
|
3098
3274
|
for (const filename of otterFiles) {
|
|
3099
3275
|
const filePath = join6(otterDir, filename);
|
|
3100
3276
|
try {
|
|
3101
|
-
if (
|
|
3277
|
+
if (lstatSync6(filePath).isSymbolicLink()) {
|
|
3102
3278
|
failed.push({ filename, error: "Skipped: symlink" });
|
|
3103
3279
|
continue;
|
|
3104
3280
|
}
|
|
@@ -3126,7 +3302,7 @@ function resolveOtterDir() {
|
|
|
3126
3302
|
for (const relative of DEFAULT_SEARCH_PATHS) {
|
|
3127
3303
|
const candidate = join6(cwd, relative);
|
|
3128
3304
|
try {
|
|
3129
|
-
|
|
3305
|
+
lstatSync6(candidate);
|
|
3130
3306
|
return candidate;
|
|
3131
3307
|
} catch {
|
|
3132
3308
|
}
|
|
@@ -3181,7 +3357,7 @@ function registerIntegrityTools(server2) {
|
|
|
3181
3357
|
|
|
3182
3358
|
// src/tools/domain.ts
|
|
3183
3359
|
import { z as z13 } from "zod";
|
|
3184
|
-
import { readFileSync as readFileSync6, existsSync as
|
|
3360
|
+
import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
|
|
3185
3361
|
import { join as join7 } from "path";
|
|
3186
3362
|
function handleListCollections(input) {
|
|
3187
3363
|
const cwd = input._cwd ?? process.cwd();
|
|
@@ -3204,7 +3380,7 @@ function handleListCollections(input) {
|
|
|
3204
3380
|
}
|
|
3205
3381
|
];
|
|
3206
3382
|
for (const { path: path3, source, parse } of sources) {
|
|
3207
|
-
if (!
|
|
3383
|
+
if (!existsSync7(path3)) continue;
|
|
3208
3384
|
try {
|
|
3209
3385
|
const collections = parse(readFileSync6(path3, "utf8"));
|
|
3210
3386
|
return {
|
|
@@ -3358,7 +3534,7 @@ function handleValidateWorkflow(input) {
|
|
|
3358
3534
|
raw = input.workflow;
|
|
3359
3535
|
} else {
|
|
3360
3536
|
const artifactPath = join7(cwd, ".stackwright", "artifacts", "workflow-config.json");
|
|
3361
|
-
if (!
|
|
3537
|
+
if (!existsSync7(artifactPath)) {
|
|
3362
3538
|
return fail([
|
|
3363
3539
|
{
|
|
3364
3540
|
code: "NO_WORKFLOW",
|
|
@@ -3606,7 +3782,7 @@ var package_default = {
|
|
|
3606
3782
|
"test:coverage": "vitest run --coverage"
|
|
3607
3783
|
},
|
|
3608
3784
|
name: "@stackwright-pro/mcp",
|
|
3609
|
-
version: "0.2.0-alpha.
|
|
3785
|
+
version: "0.2.0-alpha.19",
|
|
3610
3786
|
description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
|
|
3611
3787
|
license: "PROPRIETARY",
|
|
3612
3788
|
main: "./dist/server.js",
|