@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/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 (!/^[a-z0-9][a-z0-9-]{0,63}$/i.test(questionId)) {
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
- answers = answersToManifestFormat(input.rawAnswers, input.questions);
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(import_zod9.z.array(import_zod9.z.any()).optional()).describe(
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 isValidPhase(phase) {
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 && !isValidPhase(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 (!isValidPhase(phase)) {
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 (!isValidPhase(phase)) {
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 (!isValidPhase(phase)) {
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 (!isValidPhase(phase)) {
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
- "4bf6beba7150d08c74c5f6fbbeb20e988aba52a2029ff2892615e71f6ab12ed1"
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
- "6055a2efc78f54a8393f628839e2a2563bf0c6de3ad32de00c82779a53381efd"
4612
+ "41e3a5838f05f6a51ed272860dd2d5b1df1f20bfc847eb8f39be109b89738e99"
4457
4613
  ],
4458
4614
  [
4459
4615
  "stackwright-pro-foreman-otter.json",
4460
- "ab38ef53b95ec610a38b2866d78a135cbec16d257a9b35d7e46e2fee2d4de235"
4616
+ "5f18e37ee3f2064c62a2d01dfc541285f27e5762d0b1011e2a37a48af96c74c8"
4461
4617
  ],
4462
4618
  [
4463
4619
  "stackwright-pro-geo-otter.json",
4464
- "9e09aaf2bb10197c6d1c05d0fd5f5f9380acc0cb697a410fcae839ffba648561"
4620
+ "ff1191af8108cc70c09afe6a99b008dcb46f71e20f884da1ee424e431868a1b6"
4465
4621
  ],
4466
4622
  [
4467
4623
  "stackwright-pro-page-otter.json",
4468
- "532bb7e9a25a5c832edd1ff1ea0886dd4453905d86e6f9331eb957ae5e121833"
4624
+ "1dcdf866be76d18f3aa2a9e56b8bf57a3d43477b43c410eb6d1feac0e7683658"
4469
4625
  ],
4470
4626
  [
4471
4627
  "stackwright-pro-polish-otter.json",
4472
- "8f284d4d6a204137cd786824fc584d5bddac1bc757204769b99ca5412cf2cea2"
4628
+ "72a52901b4781c9f0be97024ba6e19415cdfbd92369d9274aa3abe7efd208bc8"
4473
4629
  ],
4474
4630
  [
4475
4631
  "stackwright-pro-theme-otter.json",
4476
- "08bb04009fdfb8743b10ac4d503cbaddaf8d7c804ba9b606aaed9cc516fd8e93"
4632
+ "e8530a1146abc57eb31d2f036ba057d5eae7d4a2d9dca00e415d1c352ef4c5b5"
4477
4633
  ],
4478
4634
  [
4479
4635
  "stackwright-pro-workflow-otter.json",
4480
- "c90d6773b2287aa9a640c2715ca0e75f44c13e99fddcfb89ced36603f38930ce"
4636
+ "e02c1a5f492dd6b11f68e293d92f5f661dbb22e57570dcf133a9d0ffc7a8be7d"
4481
4637
  ],
4482
4638
  [
4483
4639
  "stackwright-services-otter.json",
4484
- "4893a596d187110124f78336ee91184a51b3c8d980c455382fe481adb9b487b5"
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.61",
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 {