@stackwright-pro/mcp 0.2.0-alpha.66 → 0.2.0-alpha.68
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 +364 -31
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +373 -31
- package/dist/server.mjs.map +1 -1
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -1331,6 +1331,13 @@ function answersToManifestFormat(answers, questions) {
|
|
|
1331
1331
|
return result;
|
|
1332
1332
|
}
|
|
1333
1333
|
|
|
1334
|
+
// src/validation.ts
|
|
1335
|
+
var SAFE_PHASE = /^[a-z][a-z0-9-]{0,30}$/;
|
|
1336
|
+
var SAFE_QUESTION_ID = /^[a-z0-9][a-z0-9-]{0,63}$/i;
|
|
1337
|
+
function isValidPhase(phase) {
|
|
1338
|
+
return SAFE_PHASE.test(phase);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1334
1341
|
// src/tools/questions.ts
|
|
1335
1342
|
var ManifestQuestionSchema = import_zod8.z.object({
|
|
1336
1343
|
id: import_zod8.z.string(),
|
|
@@ -1365,7 +1372,6 @@ function registerQuestionTools(server2) {
|
|
|
1365
1372
|
},
|
|
1366
1373
|
async ({ phase, questions, answers }) => {
|
|
1367
1374
|
let resolvedQuestions;
|
|
1368
|
-
const SAFE_PHASE = /^[a-z][a-z0-9-]{0,30}$/;
|
|
1369
1375
|
if (!SAFE_PHASE.test(phase)) {
|
|
1370
1376
|
return {
|
|
1371
1377
|
content: [
|
|
@@ -1467,7 +1473,6 @@ function registerQuestionTools(server2) {
|
|
|
1467
1473
|
"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.",
|
|
1468
1474
|
{ phase: import_zod8.z.string().describe('Phase name e.g. "designer", "api", "auth"') },
|
|
1469
1475
|
async ({ phase }) => {
|
|
1470
|
-
const SAFE_PHASE = /^[a-z][a-z0-9-]{0,30}$/;
|
|
1471
1476
|
if (!SAFE_PHASE.test(phase)) {
|
|
1472
1477
|
return {
|
|
1473
1478
|
content: [
|
|
@@ -1532,7 +1537,6 @@ function registerQuestionTools(server2) {
|
|
|
1532
1537
|
answer: import_zod8.z.string().describe("The user's free-text answer")
|
|
1533
1538
|
},
|
|
1534
1539
|
async ({ phase, questionId, answer }) => {
|
|
1535
|
-
const SAFE_PHASE = /^[a-z][a-z0-9-]{0,30}$/;
|
|
1536
1540
|
if (!SAFE_PHASE.test(phase)) {
|
|
1537
1541
|
return {
|
|
1538
1542
|
content: [
|
|
@@ -1541,7 +1545,7 @@ function registerQuestionTools(server2) {
|
|
|
1541
1545
|
isError: true
|
|
1542
1546
|
};
|
|
1543
1547
|
}
|
|
1544
|
-
if (
|
|
1548
|
+
if (!SAFE_QUESTION_ID.test(questionId)) {
|
|
1545
1549
|
return {
|
|
1546
1550
|
content: [
|
|
1547
1551
|
{ type: "text", text: JSON.stringify({ error: "Invalid questionId" }) }
|
|
@@ -1694,14 +1698,61 @@ function handleSaveManifest(input) {
|
|
|
1694
1698
|
}
|
|
1695
1699
|
}
|
|
1696
1700
|
function handleSavePhaseAnswers(input) {
|
|
1701
|
+
if (!isValidPhase(input.phase)) {
|
|
1702
|
+
return {
|
|
1703
|
+
text: JSON.stringify({
|
|
1704
|
+
success: false,
|
|
1705
|
+
error: `Invalid phase name: "${input.phase.slice(0, 50)}". Must be lowercase alphanumeric with hyphens, max 31 chars.`
|
|
1706
|
+
}),
|
|
1707
|
+
isError: true
|
|
1708
|
+
};
|
|
1709
|
+
}
|
|
1710
|
+
for (let i = 0; i < input.rawAnswers.length; i++) {
|
|
1711
|
+
const a = input.rawAnswers[i];
|
|
1712
|
+
if (!a.question_header || a.question_header.trim().length === 0) {
|
|
1713
|
+
return {
|
|
1714
|
+
text: JSON.stringify({
|
|
1715
|
+
success: false,
|
|
1716
|
+
error: `rawAnswers[${i}].question_header is empty \u2014 every answer must have a non-empty question_header.`
|
|
1717
|
+
}),
|
|
1718
|
+
isError: true
|
|
1719
|
+
};
|
|
1720
|
+
}
|
|
1721
|
+
if (!a.selected_options || a.selected_options.length === 0) {
|
|
1722
|
+
return {
|
|
1723
|
+
text: JSON.stringify({
|
|
1724
|
+
success: false,
|
|
1725
|
+
error: `rawAnswers[${i}].selected_options is empty for question_header "${a.question_header}" \u2014 every answer must have at least one selected option.`
|
|
1726
|
+
}),
|
|
1727
|
+
isError: true
|
|
1728
|
+
};
|
|
1729
|
+
}
|
|
1730
|
+
}
|
|
1731
|
+
if (input.questions && input.questions.length > 0 && input.rawAnswers.length === 0) {
|
|
1732
|
+
return {
|
|
1733
|
+
text: JSON.stringify({
|
|
1734
|
+
success: false,
|
|
1735
|
+
error: `${input.questions.length} questions provided but rawAnswers is empty \u2014 this is a malformed payload. If the phase truly has no questions, omit the questions parameter.`
|
|
1736
|
+
}),
|
|
1737
|
+
isError: true
|
|
1738
|
+
};
|
|
1739
|
+
}
|
|
1697
1740
|
const cwd = input._cwd ?? process.cwd();
|
|
1698
1741
|
const dir = (0, import_path3.join)(cwd, ".stackwright", "answers");
|
|
1699
1742
|
const filePath = (0, import_path3.join)(dir, `${input.phase}.json`);
|
|
1700
1743
|
try {
|
|
1701
1744
|
(0, import_fs3.mkdirSync)(dir, { recursive: true });
|
|
1745
|
+
const warnings = [];
|
|
1702
1746
|
let answers;
|
|
1703
1747
|
if (input.questions && input.questions.length > 0) {
|
|
1704
|
-
|
|
1748
|
+
try {
|
|
1749
|
+
answers = answersToManifestFormat(input.rawAnswers, input.questions);
|
|
1750
|
+
} catch (mapErr) {
|
|
1751
|
+
answers = {};
|
|
1752
|
+
warnings.push(
|
|
1753
|
+
`Reverse-mapping threw (${mapErr instanceof Error ? mapErr.message : String(mapErr)}) \u2014 falling back to direct key mapping.`
|
|
1754
|
+
);
|
|
1755
|
+
}
|
|
1705
1756
|
if (Object.keys(answers).length === 0 && input.rawAnswers.length > 0) {
|
|
1706
1757
|
answers = Object.fromEntries(
|
|
1707
1758
|
input.rawAnswers.map((a) => [
|
|
@@ -1709,6 +1760,15 @@ function handleSavePhaseAnswers(input) {
|
|
|
1709
1760
|
a.selected_options.length > 1 ? a.selected_options : a.selected_options[0] ?? ""
|
|
1710
1761
|
])
|
|
1711
1762
|
);
|
|
1763
|
+
warnings.push(
|
|
1764
|
+
`Reverse-mapping produced empty answers despite ${input.rawAnswers.length} rawAnswers \u2014 used direct key mapping as fallback. Ensure question_header values match the manifest header format (e.g. "DESI-1", not descriptive keys).`
|
|
1765
|
+
);
|
|
1766
|
+
}
|
|
1767
|
+
const requiredCount = input.questions.filter((q) => q.required !== false).length;
|
|
1768
|
+
if (input.rawAnswers.length < requiredCount) {
|
|
1769
|
+
warnings.push(
|
|
1770
|
+
`Only ${input.rawAnswers.length} answers provided for ${requiredCount} required questions (${input.questions.length} total) \u2014 answers may be incomplete.`
|
|
1771
|
+
);
|
|
1712
1772
|
}
|
|
1713
1773
|
} else {
|
|
1714
1774
|
answers = Object.fromEntries(
|
|
@@ -1736,7 +1796,8 @@ function handleSavePhaseAnswers(input) {
|
|
|
1736
1796
|
text: JSON.stringify({
|
|
1737
1797
|
success: true,
|
|
1738
1798
|
path: filePath,
|
|
1739
|
-
answersCount: Object.keys(answers).length
|
|
1799
|
+
answersCount: Object.keys(answers).length,
|
|
1800
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
1740
1801
|
}),
|
|
1741
1802
|
isError: false
|
|
1742
1803
|
};
|
|
@@ -1883,15 +1944,31 @@ function registerOrchestrationTools(server2) {
|
|
|
1883
1944
|
rawAnswers: jsonCoerce(
|
|
1884
1945
|
import_zod9.z.array(
|
|
1885
1946
|
import_zod9.z.object({
|
|
1886
|
-
question_header: import_zod9.z.string(),
|
|
1887
|
-
selected_options: import_zod9.z.array(import_zod9.z.string()),
|
|
1947
|
+
question_header: import_zod9.z.string().min(1, "question_header must not be empty"),
|
|
1948
|
+
selected_options: import_zod9.z.array(import_zod9.z.string()).min(1, "selected_options must have at least one entry"),
|
|
1888
1949
|
other_text: import_zod9.z.string().nullable().optional()
|
|
1889
1950
|
})
|
|
1890
1951
|
)
|
|
1891
1952
|
).describe(
|
|
1892
1953
|
"Answers as returned by ask_user_question \u2014 pass the native array, not a JSON string"
|
|
1893
1954
|
),
|
|
1894
|
-
questions: jsonCoerce(
|
|
1955
|
+
questions: jsonCoerce(
|
|
1956
|
+
import_zod9.z.array(
|
|
1957
|
+
import_zod9.z.object({
|
|
1958
|
+
id: import_zod9.z.string(),
|
|
1959
|
+
question: import_zod9.z.string(),
|
|
1960
|
+
type: import_zod9.z.string(),
|
|
1961
|
+
options: import_zod9.z.array(import_zod9.z.object({ label: import_zod9.z.string(), value: import_zod9.z.string() })).optional(),
|
|
1962
|
+
required: import_zod9.z.boolean().optional(),
|
|
1963
|
+
default: import_zod9.z.union([import_zod9.z.string(), import_zod9.z.boolean(), import_zod9.z.array(import_zod9.z.string())]).optional(),
|
|
1964
|
+
help: import_zod9.z.string().optional(),
|
|
1965
|
+
dependsOn: import_zod9.z.object({
|
|
1966
|
+
questionId: import_zod9.z.string(),
|
|
1967
|
+
value: import_zod9.z.union([import_zod9.z.string(), import_zod9.z.array(import_zod9.z.string())])
|
|
1968
|
+
}).optional()
|
|
1969
|
+
}).passthrough()
|
|
1970
|
+
).optional()
|
|
1971
|
+
).describe(
|
|
1895
1972
|
"Original manifest questions for label\u2192value reverse-mapping \u2014 pass the native array, not a JSON string"
|
|
1896
1973
|
)
|
|
1897
1974
|
},
|
|
@@ -2335,7 +2412,7 @@ var PHASE_TO_OTTER2 = {
|
|
|
2335
2412
|
polish: "stackwright-pro-polish-otter",
|
|
2336
2413
|
geo: "stackwright-pro-geo-otter"
|
|
2337
2414
|
};
|
|
2338
|
-
function
|
|
2415
|
+
function isValidPhase2(phase) {
|
|
2339
2416
|
return PHASE_ORDER.includes(phase);
|
|
2340
2417
|
}
|
|
2341
2418
|
function defaultPhaseStatus() {
|
|
@@ -2431,7 +2508,7 @@ function handleGetPipelineState(_cwd) {
|
|
|
2431
2508
|
}
|
|
2432
2509
|
function handleSetPipelineState(input) {
|
|
2433
2510
|
const cwd = input._cwd ?? process.cwd();
|
|
2434
|
-
if (input.phase && !
|
|
2511
|
+
if (input.phase && !isValidPhase2(input.phase)) {
|
|
2435
2512
|
return {
|
|
2436
2513
|
text: JSON.stringify({
|
|
2437
2514
|
error: true,
|
|
@@ -2450,6 +2527,28 @@ function handleSetPipelineState(input) {
|
|
|
2450
2527
|
isError: true
|
|
2451
2528
|
};
|
|
2452
2529
|
}
|
|
2530
|
+
if (input.updates && input.updates.length > 0) {
|
|
2531
|
+
for (const update of input.updates) {
|
|
2532
|
+
if (!isValidPhase2(update.phase)) {
|
|
2533
|
+
return {
|
|
2534
|
+
text: JSON.stringify({
|
|
2535
|
+
error: true,
|
|
2536
|
+
message: `Invalid phase in batch update: ${update.phase}. Valid phases are: ${PHASE_ORDER.join(", ")}`
|
|
2537
|
+
}),
|
|
2538
|
+
isError: true
|
|
2539
|
+
};
|
|
2540
|
+
}
|
|
2541
|
+
if (!VALID_FIELDS.includes(update.field)) {
|
|
2542
|
+
return {
|
|
2543
|
+
text: JSON.stringify({
|
|
2544
|
+
error: true,
|
|
2545
|
+
message: `Invalid field in batch update: ${update.field}. Valid fields are: ${VALID_FIELDS.join(", ")}`
|
|
2546
|
+
}),
|
|
2547
|
+
isError: true
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
}
|
|
2551
|
+
}
|
|
2453
2552
|
try {
|
|
2454
2553
|
const state = readState(cwd);
|
|
2455
2554
|
if (input.status) {
|
|
@@ -2469,6 +2568,16 @@ function handleSetPipelineState(input) {
|
|
|
2469
2568
|
}
|
|
2470
2569
|
state.currentPhase = phase;
|
|
2471
2570
|
}
|
|
2571
|
+
if (input.updates && input.updates.length > 0) {
|
|
2572
|
+
for (const update of input.updates) {
|
|
2573
|
+
if (!state.phases[update.phase]) {
|
|
2574
|
+
state.phases[update.phase] = defaultPhaseStatus();
|
|
2575
|
+
}
|
|
2576
|
+
const batchPhaseState = state.phases[update.phase];
|
|
2577
|
+
batchPhaseState[update.field] = update.value;
|
|
2578
|
+
state.currentPhase = update.phase;
|
|
2579
|
+
}
|
|
2580
|
+
}
|
|
2472
2581
|
writeState(cwd, state);
|
|
2473
2582
|
return { text: JSON.stringify(state), isError: false };
|
|
2474
2583
|
} catch (err) {
|
|
@@ -2478,7 +2587,7 @@ function handleSetPipelineState(input) {
|
|
|
2478
2587
|
function handleCheckExecutionReady(_cwd, phase) {
|
|
2479
2588
|
const cwd = _cwd ?? process.cwd();
|
|
2480
2589
|
if (phase) {
|
|
2481
|
-
if (!
|
|
2590
|
+
if (!isValidPhase2(phase)) {
|
|
2482
2591
|
return {
|
|
2483
2592
|
text: JSON.stringify({
|
|
2484
2593
|
error: true,
|
|
@@ -2626,7 +2735,7 @@ function handleListArtifacts(_cwd) {
|
|
|
2626
2735
|
function handleWritePhaseQuestions(input) {
|
|
2627
2736
|
const cwd = input._cwd ?? process.cwd();
|
|
2628
2737
|
const { phase, responseText } = input;
|
|
2629
|
-
if (!
|
|
2738
|
+
if (!isValidPhase2(phase)) {
|
|
2630
2739
|
return {
|
|
2631
2740
|
text: JSON.stringify({ error: true, message: `Unknown phase: ${phase}` }),
|
|
2632
2741
|
isError: true
|
|
@@ -2684,7 +2793,7 @@ function handleWritePhaseQuestions(input) {
|
|
|
2684
2793
|
function handleBuildSpecialistPrompt(input) {
|
|
2685
2794
|
const cwd = input._cwd ?? process.cwd();
|
|
2686
2795
|
const { phase } = input;
|
|
2687
|
-
if (!
|
|
2796
|
+
if (!isValidPhase2(phase)) {
|
|
2688
2797
|
return {
|
|
2689
2798
|
text: JSON.stringify({ error: true, message: `Unknown phase: ${phase}` }),
|
|
2690
2799
|
isError: true
|
|
@@ -2777,6 +2886,18 @@ ${JSON.stringify(content, null, 2)}`
|
|
|
2777
2886
|
if (buildContextText) {
|
|
2778
2887
|
parts.push("BUILD_CONTEXT:", buildContextText, "");
|
|
2779
2888
|
}
|
|
2889
|
+
if (phase === "auth") {
|
|
2890
|
+
const initContextPath = (0, import_path5.join)(cwd, ".stackwright", "init-context.json");
|
|
2891
|
+
if ((0, import_fs5.existsSync)(initContextPath)) {
|
|
2892
|
+
try {
|
|
2893
|
+
const initContext = JSON.parse(safeReadSync(initContextPath));
|
|
2894
|
+
if (initContext.devOnly === true || initContext.nonInteractive === true) {
|
|
2895
|
+
answers["devOnly"] = true;
|
|
2896
|
+
}
|
|
2897
|
+
} catch {
|
|
2898
|
+
}
|
|
2899
|
+
}
|
|
2900
|
+
}
|
|
2780
2901
|
parts.push("ANSWERS:", JSON.stringify(answers, null, 2));
|
|
2781
2902
|
if (artifactSections.length > 0) {
|
|
2782
2903
|
parts.push("", "UPSTREAM ARTIFACTS:", "", ...artifactSections);
|
|
@@ -3059,6 +3180,12 @@ var PHASE_ARTIFACT_SCHEMA = {
|
|
|
3059
3180
|
protectedRoutes: ["/dashboard/:path*", "/procurement/:path*"],
|
|
3060
3181
|
auditEnabled: true,
|
|
3061
3182
|
auditRetentionDays: 90
|
|
3183
|
+
},
|
|
3184
|
+
// devScripts is OPTIONAL — only present when devOnly: true was used
|
|
3185
|
+
// Polish otter and foreman MUST check devScripts.written before referencing scripts
|
|
3186
|
+
devScripts: {
|
|
3187
|
+
written: true,
|
|
3188
|
+
scripts: { "dev:admin": "MOCK_USER=admin next dev" }
|
|
3062
3189
|
}
|
|
3063
3190
|
},
|
|
3064
3191
|
null,
|
|
@@ -3070,7 +3197,12 @@ var PHASE_ARTIFACT_SCHEMA = {
|
|
|
3070
3197
|
generatedBy: "stackwright-pro-polish-otter",
|
|
3071
3198
|
landingPage: { slug: "", rewritten: true },
|
|
3072
3199
|
navigation: { itemCount: 5, items: ["/dashboard", "/equipment", "/workflows"] },
|
|
3073
|
-
gettingStarted: "<rewritten|redirected|not-found>"
|
|
3200
|
+
gettingStarted: "<rewritten|redirected|not-found>",
|
|
3201
|
+
scaffoldCleanup: {
|
|
3202
|
+
deleted: ["content/posts/getting-started.yaml"],
|
|
3203
|
+
rewritten: ["app/not-found.tsx"],
|
|
3204
|
+
skipped: []
|
|
3205
|
+
}
|
|
3074
3206
|
},
|
|
3075
3207
|
null,
|
|
3076
3208
|
2
|
|
@@ -3079,7 +3211,7 @@ var PHASE_ARTIFACT_SCHEMA = {
|
|
|
3079
3211
|
function handleValidateArtifact(input) {
|
|
3080
3212
|
const cwd = input._cwd ?? process.cwd();
|
|
3081
3213
|
const { phase, responseText, artifact: directArtifact } = input;
|
|
3082
|
-
if (!
|
|
3214
|
+
if (!isValidPhase2(phase)) {
|
|
3083
3215
|
return {
|
|
3084
3216
|
text: JSON.stringify({ error: true, message: `Unknown phase: ${phase}` }),
|
|
3085
3217
|
isError: true
|
|
@@ -3227,7 +3359,16 @@ function registerPipelineTools(server2) {
|
|
|
3227
3359
|
status: import_zod11.z.enum(["setup", "questions", "execution", "done"]).optional().describe("Top-level status override"),
|
|
3228
3360
|
incrementRetry: boolCoerce(import_zod11.z.boolean().optional()).describe(
|
|
3229
3361
|
"Bump retryCount by 1 \u2014 must be a JSON boolean"
|
|
3230
|
-
)
|
|
3362
|
+
),
|
|
3363
|
+
updates: jsonCoerce(
|
|
3364
|
+
import_zod11.z.array(
|
|
3365
|
+
import_zod11.z.object({
|
|
3366
|
+
phase: import_zod11.z.string(),
|
|
3367
|
+
field: import_zod11.z.enum(["questionsCollected", "answered", "executed", "artifactWritten"]),
|
|
3368
|
+
value: import_zod11.z.boolean()
|
|
3369
|
+
})
|
|
3370
|
+
).optional()
|
|
3371
|
+
).describe("Batch updates \u2014 apply multiple field changes in one atomic read\u2192modify\u2192write")
|
|
3231
3372
|
},
|
|
3232
3373
|
async (args) => res(
|
|
3233
3374
|
handleSetPipelineState({
|
|
@@ -3235,7 +3376,8 @@ function registerPipelineTools(server2) {
|
|
|
3235
3376
|
...args.field != null ? { field: args.field } : {},
|
|
3236
3377
|
...args.value != null ? { value: args.value } : {},
|
|
3237
3378
|
...args.status != null ? { status: args.status } : {},
|
|
3238
|
-
...args.incrementRetry != null ? { incrementRetry: args.incrementRetry } : {}
|
|
3379
|
+
...args.incrementRetry != null ? { incrementRetry: args.incrementRetry } : {},
|
|
3380
|
+
...args.updates != null ? { updates: args.updates } : {}
|
|
3239
3381
|
})
|
|
3240
3382
|
)
|
|
3241
3383
|
);
|
|
@@ -3271,7 +3413,6 @@ function registerPipelineTools(server2) {
|
|
|
3271
3413
|
},
|
|
3272
3414
|
async ({ phase, responseText, questions }) => {
|
|
3273
3415
|
if (phase && questions && Array.isArray(questions)) {
|
|
3274
|
-
const SAFE_PHASE = /^[a-z][a-z0-9-]{0,30}$/;
|
|
3275
3416
|
if (!SAFE_PHASE.test(phase)) {
|
|
3276
3417
|
return {
|
|
3277
3418
|
content: [
|
|
@@ -3403,7 +3544,8 @@ var OTTER_WRITE_ALLOWLISTS = {
|
|
|
3403
3544
|
},
|
|
3404
3545
|
{ prefix: "pages/", suffix: "/content.yml", description: "Landing page content" },
|
|
3405
3546
|
{ prefix: "pages/", suffix: "/content.yaml", description: "Landing page content" },
|
|
3406
|
-
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Polish artifact" }
|
|
3547
|
+
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Polish artifact" },
|
|
3548
|
+
{ prefix: "README.md", suffix: "", description: "Project README rewrite" }
|
|
3407
3549
|
],
|
|
3408
3550
|
"stackwright-services-otter": [
|
|
3409
3551
|
{ prefix: ".stackwright/artifacts/", suffix: ".json", description: "Services config artifact" },
|
|
@@ -4376,7 +4518,21 @@ async function configureAuthHandler(params, cwd) {
|
|
|
4376
4518
|
rbacDefaultRole: defaultRole,
|
|
4377
4519
|
protectedRoutesCount: protectedRoutes.length,
|
|
4378
4520
|
filesWritten,
|
|
4379
|
-
securityWarning
|
|
4521
|
+
securityWarning,
|
|
4522
|
+
...devOnly ? {
|
|
4523
|
+
devScripts: {
|
|
4524
|
+
written: filesWritten.includes("package.json"),
|
|
4525
|
+
scripts: filesWritten.includes("package.json") ? Object.fromEntries(
|
|
4526
|
+
roles.map((r) => {
|
|
4527
|
+
const k = deriveDevKey(r);
|
|
4528
|
+
return [`dev:${k}`, `MOCK_USER=${k} next dev`];
|
|
4529
|
+
})
|
|
4530
|
+
) : {},
|
|
4531
|
+
...filesWritten.includes("package.json") ? {} : {
|
|
4532
|
+
warning: "package.json not found \u2014 dev scripts were not written. Run the auth phase again after scaffolding creates package.json."
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
} : {}
|
|
4380
4536
|
})
|
|
4381
4537
|
}
|
|
4382
4538
|
]
|
|
@@ -4437,7 +4593,7 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
4437
4593
|
],
|
|
4438
4594
|
[
|
|
4439
4595
|
"stackwright-pro-auth-otter.json",
|
|
4440
|
-
"
|
|
4596
|
+
"c3be215f05cc48ec51ed75541184b89a8ac123463b80fe02f2adb6c1b5163853"
|
|
4441
4597
|
],
|
|
4442
4598
|
[
|
|
4443
4599
|
"stackwright-pro-dashboard-otter.json",
|
|
@@ -4453,35 +4609,35 @@ var _checksums = /* @__PURE__ */ new Map([
|
|
|
4453
4609
|
],
|
|
4454
4610
|
[
|
|
4455
4611
|
"stackwright-pro-domain-expert-otter.json",
|
|
4456
|
-
"
|
|
4612
|
+
"41e3a5838f05f6a51ed272860dd2d5b1df1f20bfc847eb8f39be109b89738e99"
|
|
4457
4613
|
],
|
|
4458
4614
|
[
|
|
4459
4615
|
"stackwright-pro-foreman-otter.json",
|
|
4460
|
-
"
|
|
4616
|
+
"5f18e37ee3f2064c62a2d01dfc541285f27e5762d0b1011e2a37a48af96c74c8"
|
|
4461
4617
|
],
|
|
4462
4618
|
[
|
|
4463
4619
|
"stackwright-pro-geo-otter.json",
|
|
4464
|
-
"
|
|
4620
|
+
"ff1191af8108cc70c09afe6a99b008dcb46f71e20f884da1ee424e431868a1b6"
|
|
4465
4621
|
],
|
|
4466
4622
|
[
|
|
4467
4623
|
"stackwright-pro-page-otter.json",
|
|
4468
|
-
"
|
|
4624
|
+
"1dcdf866be76d18f3aa2a9e56b8bf57a3d43477b43c410eb6d1feac0e7683658"
|
|
4469
4625
|
],
|
|
4470
4626
|
[
|
|
4471
4627
|
"stackwright-pro-polish-otter.json",
|
|
4472
|
-
"
|
|
4628
|
+
"72a52901b4781c9f0be97024ba6e19415cdfbd92369d9274aa3abe7efd208bc8"
|
|
4473
4629
|
],
|
|
4474
4630
|
[
|
|
4475
4631
|
"stackwright-pro-theme-otter.json",
|
|
4476
|
-
"
|
|
4632
|
+
"e8530a1146abc57eb31d2f036ba057d5eae7d4a2d9dca00e415d1c352ef4c5b5"
|
|
4477
4633
|
],
|
|
4478
4634
|
[
|
|
4479
4635
|
"stackwright-pro-workflow-otter.json",
|
|
4480
|
-
"
|
|
4636
|
+
"e02c1a5f492dd6b11f68e293d92f5f661dbb22e57570dcf133a9d0ffc7a8be7d"
|
|
4481
4637
|
],
|
|
4482
4638
|
[
|
|
4483
4639
|
"stackwright-services-otter.json",
|
|
4484
|
-
"
|
|
4640
|
+
"b8a252d6a2c5090138899925d3ea95bf3339b6e49adc276a1f5f7304b4ae5134"
|
|
4485
4641
|
]
|
|
4486
4642
|
]);
|
|
4487
4643
|
Object.freeze(_checksums);
|
|
@@ -5191,6 +5347,182 @@ function registerTypeSchemasTool(server2) {
|
|
|
5191
5347
|
);
|
|
5192
5348
|
}
|
|
5193
5349
|
|
|
5350
|
+
// src/tools/scaffold-cleanup.ts
|
|
5351
|
+
var import_fs10 = require("fs");
|
|
5352
|
+
var import_path10 = require("path");
|
|
5353
|
+
var SCAFFOLD_FILES_TO_DELETE = [
|
|
5354
|
+
"content/posts/getting-started.yaml",
|
|
5355
|
+
"content/posts/hello-world.yaml"
|
|
5356
|
+
];
|
|
5357
|
+
var SCAFFOLD_DIRS_TO_PRUNE = [
|
|
5358
|
+
"content/posts"
|
|
5359
|
+
// Don't prune 'content' — user may have other content directories
|
|
5360
|
+
];
|
|
5361
|
+
var NOT_FOUND_PATH = "app/not-found.tsx";
|
|
5362
|
+
var FALLBACK_COLORS = {
|
|
5363
|
+
background: "#ffffff",
|
|
5364
|
+
foreground: "#171717",
|
|
5365
|
+
primary: "#525252",
|
|
5366
|
+
mutedForeground: "#737373"
|
|
5367
|
+
};
|
|
5368
|
+
function readThemeColors(cwd) {
|
|
5369
|
+
const tokenPath = (0, import_path10.join)(cwd, ".stackwright", "artifacts", "theme-tokens.json");
|
|
5370
|
+
if (!(0, import_fs10.existsSync)(tokenPath)) return { ...FALLBACK_COLORS };
|
|
5371
|
+
const stat = (0, import_fs10.lstatSync)(tokenPath);
|
|
5372
|
+
if (stat.isSymbolicLink()) return { ...FALLBACK_COLORS };
|
|
5373
|
+
try {
|
|
5374
|
+
const raw = JSON.parse((0, import_fs10.readFileSync)(tokenPath, "utf-8"));
|
|
5375
|
+
const css = raw?.cssVariables ?? {};
|
|
5376
|
+
const toHsl = (hslValue) => {
|
|
5377
|
+
if (!hslValue || typeof hslValue !== "string") return null;
|
|
5378
|
+
return `hsl(${hslValue})`;
|
|
5379
|
+
};
|
|
5380
|
+
return {
|
|
5381
|
+
background: toHsl(css["--background"]) ?? FALLBACK_COLORS.background,
|
|
5382
|
+
foreground: toHsl(css["--foreground"]) ?? FALLBACK_COLORS.foreground,
|
|
5383
|
+
primary: toHsl(css["--primary"]) ?? FALLBACK_COLORS.primary,
|
|
5384
|
+
mutedForeground: toHsl(css["--muted-foreground"]) ?? FALLBACK_COLORS.mutedForeground
|
|
5385
|
+
};
|
|
5386
|
+
} catch {
|
|
5387
|
+
return { ...FALLBACK_COLORS };
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
function generateNotFoundPage(colors) {
|
|
5391
|
+
return `export default function NotFound() {
|
|
5392
|
+
return (
|
|
5393
|
+
<div
|
|
5394
|
+
style={{
|
|
5395
|
+
display: 'flex',
|
|
5396
|
+
flexDirection: 'column',
|
|
5397
|
+
alignItems: 'center',
|
|
5398
|
+
justifyContent: 'center',
|
|
5399
|
+
minHeight: '100vh',
|
|
5400
|
+
backgroundColor: '${colors.background}',
|
|
5401
|
+
color: '${colors.foreground}',
|
|
5402
|
+
fontFamily: 'system-ui, -apple-system, sans-serif',
|
|
5403
|
+
padding: '2rem',
|
|
5404
|
+
textAlign: 'center',
|
|
5405
|
+
}}
|
|
5406
|
+
>
|
|
5407
|
+
<h1 style={{ fontSize: '4rem', fontWeight: 700, margin: 0, color: '${colors.primary}' }}>
|
|
5408
|
+
404
|
|
5409
|
+
</h1>
|
|
5410
|
+
<p style={{ fontSize: '1.25rem', color: '${colors.mutedForeground}', marginTop: '0.5rem' }}>
|
|
5411
|
+
Page not found
|
|
5412
|
+
</p>
|
|
5413
|
+
<a
|
|
5414
|
+
href="/"
|
|
5415
|
+
style={{
|
|
5416
|
+
marginTop: '2rem',
|
|
5417
|
+
padding: '0.75rem 1.5rem',
|
|
5418
|
+
backgroundColor: '${colors.primary}',
|
|
5419
|
+
color: '${colors.background}',
|
|
5420
|
+
borderRadius: '0.5rem',
|
|
5421
|
+
textDecoration: 'none',
|
|
5422
|
+
fontSize: '0.875rem',
|
|
5423
|
+
fontWeight: 500,
|
|
5424
|
+
}}
|
|
5425
|
+
>
|
|
5426
|
+
Go Home
|
|
5427
|
+
</a>
|
|
5428
|
+
</div>
|
|
5429
|
+
);
|
|
5430
|
+
}
|
|
5431
|
+
`;
|
|
5432
|
+
}
|
|
5433
|
+
function isSafePath(fullPath) {
|
|
5434
|
+
if (!(0, import_fs10.existsSync)(fullPath)) return true;
|
|
5435
|
+
const stat = (0, import_fs10.lstatSync)(fullPath);
|
|
5436
|
+
return !stat.isSymbolicLink();
|
|
5437
|
+
}
|
|
5438
|
+
function isDirEmpty(dirPath) {
|
|
5439
|
+
if (!(0, import_fs10.existsSync)(dirPath)) return false;
|
|
5440
|
+
const stat = (0, import_fs10.lstatSync)(dirPath);
|
|
5441
|
+
if (!stat.isDirectory()) return false;
|
|
5442
|
+
return (0, import_fs10.readdirSync)(dirPath).length === 0;
|
|
5443
|
+
}
|
|
5444
|
+
function handleCleanupScaffold(_cwd) {
|
|
5445
|
+
const cwd = _cwd ?? process.cwd();
|
|
5446
|
+
const result = {
|
|
5447
|
+
deleted: [],
|
|
5448
|
+
rewritten: [],
|
|
5449
|
+
skipped: [],
|
|
5450
|
+
errors: [],
|
|
5451
|
+
prunedDirs: []
|
|
5452
|
+
};
|
|
5453
|
+
for (const relPath of SCAFFOLD_FILES_TO_DELETE) {
|
|
5454
|
+
const fullPath = (0, import_path10.join)(cwd, relPath);
|
|
5455
|
+
if (!(0, import_fs10.existsSync)(fullPath)) {
|
|
5456
|
+
result.skipped.push(relPath);
|
|
5457
|
+
continue;
|
|
5458
|
+
}
|
|
5459
|
+
if (!isSafePath(fullPath)) {
|
|
5460
|
+
result.errors.push(`Refusing to delete symlink: ${relPath}`);
|
|
5461
|
+
continue;
|
|
5462
|
+
}
|
|
5463
|
+
try {
|
|
5464
|
+
(0, import_fs10.unlinkSync)(fullPath);
|
|
5465
|
+
result.deleted.push(relPath);
|
|
5466
|
+
} catch (err) {
|
|
5467
|
+
result.errors.push(`Failed to delete ${relPath}: ${String(err)}`);
|
|
5468
|
+
}
|
|
5469
|
+
}
|
|
5470
|
+
for (const relDir of SCAFFOLD_DIRS_TO_PRUNE) {
|
|
5471
|
+
const fullDir = (0, import_path10.join)(cwd, relDir);
|
|
5472
|
+
if (!(0, import_fs10.existsSync)(fullDir)) continue;
|
|
5473
|
+
if (!isSafePath(fullDir)) {
|
|
5474
|
+
result.errors.push(`Refusing to prune symlink directory: ${relDir}`);
|
|
5475
|
+
continue;
|
|
5476
|
+
}
|
|
5477
|
+
if (isDirEmpty(fullDir)) {
|
|
5478
|
+
try {
|
|
5479
|
+
(0, import_fs10.rmdirSync)(fullDir);
|
|
5480
|
+
result.prunedDirs.push(relDir);
|
|
5481
|
+
} catch (err) {
|
|
5482
|
+
result.errors.push(`Failed to prune directory ${relDir}: ${String(err)}`);
|
|
5483
|
+
}
|
|
5484
|
+
}
|
|
5485
|
+
}
|
|
5486
|
+
{
|
|
5487
|
+
const fullPath = (0, import_path10.join)(cwd, NOT_FOUND_PATH);
|
|
5488
|
+
if (!(0, import_fs10.existsSync)(fullPath)) {
|
|
5489
|
+
result.skipped.push(NOT_FOUND_PATH);
|
|
5490
|
+
} else if (!isSafePath(fullPath)) {
|
|
5491
|
+
result.errors.push(`Refusing to rewrite symlink: ${NOT_FOUND_PATH}`);
|
|
5492
|
+
} else {
|
|
5493
|
+
try {
|
|
5494
|
+
const colors = readThemeColors(cwd);
|
|
5495
|
+
const content = generateNotFoundPage(colors);
|
|
5496
|
+
const dir = (0, import_path10.dirname)(fullPath);
|
|
5497
|
+
(0, import_fs10.mkdirSync)(dir, { recursive: true });
|
|
5498
|
+
(0, import_fs10.writeFileSync)(fullPath, content, "utf-8");
|
|
5499
|
+
result.rewritten.push(NOT_FOUND_PATH);
|
|
5500
|
+
} catch (err) {
|
|
5501
|
+
result.errors.push(`Failed to rewrite ${NOT_FOUND_PATH}: ${String(err)}`);
|
|
5502
|
+
}
|
|
5503
|
+
}
|
|
5504
|
+
}
|
|
5505
|
+
return {
|
|
5506
|
+
text: JSON.stringify(result),
|
|
5507
|
+
isError: false
|
|
5508
|
+
};
|
|
5509
|
+
}
|
|
5510
|
+
function registerScaffoldCleanupTools(server2) {
|
|
5511
|
+
const DESC = "Deterministic scaffold remnant cleanup \u2014 removes stale OSS scaffold files after raft run.";
|
|
5512
|
+
server2.tool(
|
|
5513
|
+
"stackwright_pro_cleanup_scaffold",
|
|
5514
|
+
`Remove/rewrite stale scaffold files (blog posts, hardcoded not-found page) after the raft pipeline completes. ${DESC}`,
|
|
5515
|
+
{},
|
|
5516
|
+
async () => {
|
|
5517
|
+
const r = handleCleanupScaffold();
|
|
5518
|
+
return {
|
|
5519
|
+
content: [{ type: "text", text: r.text }],
|
|
5520
|
+
isError: r.isError
|
|
5521
|
+
};
|
|
5522
|
+
}
|
|
5523
|
+
);
|
|
5524
|
+
}
|
|
5525
|
+
|
|
5194
5526
|
// package.json
|
|
5195
5527
|
var package_default = {
|
|
5196
5528
|
dependencies: {
|
|
@@ -5214,7 +5546,7 @@ var package_default = {
|
|
|
5214
5546
|
"test:coverage": "vitest run --coverage"
|
|
5215
5547
|
},
|
|
5216
5548
|
name: "@stackwright-pro/mcp",
|
|
5217
|
-
version: "0.2.0-alpha.
|
|
5549
|
+
version: "0.2.0-alpha.68",
|
|
5218
5550
|
description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
|
|
5219
5551
|
license: "SEE LICENSE IN LICENSE",
|
|
5220
5552
|
main: "./dist/server.js",
|
|
@@ -5268,6 +5600,7 @@ registerIntegrityTools(server);
|
|
|
5268
5600
|
registerArtifactSigningTools(server);
|
|
5269
5601
|
registerDomainTools(server);
|
|
5270
5602
|
registerTypeSchemasTool(server);
|
|
5603
|
+
registerScaffoldCleanupTools(server);
|
|
5271
5604
|
async function main() {
|
|
5272
5605
|
const transport = new import_stdio.StdioServerTransport();
|
|
5273
5606
|
try {
|