@stackwright-pro/mcp 0.2.0-alpha.111 → 0.2.0-alpha.113

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
@@ -580,7 +580,7 @@ function registerDashboardTools(server2) {
580
580
  yamlLines.push(` header: Status`);
581
581
  yamlLines.push(` type: badge`);
582
582
  }
583
- const yaml3 = yamlLines.join("\n");
583
+ const yaml4 = yamlLines.join("\n");
584
584
  return {
585
585
  content: [
586
586
  {
@@ -590,7 +590,7 @@ function registerDashboardTools(server2) {
590
590
  Layout: ${layout}
591
591
 
592
592
  \`\`\`yaml
593
- ${yaml3}
593
+ ${yaml4}
594
594
  \`\`\`
595
595
 
596
596
  \u{1F4A1} Add this to pages/dashboard/content.yml and validate with stackwright_validate_pages.`
@@ -671,7 +671,7 @@ ${yaml3}
671
671
  yamlLines.push(" action: navigate");
672
672
  yamlLines.push(` href: "/${entity}"`);
673
673
  yamlLines.push(" style: secondary");
674
- const yaml3 = yamlLines.join("\n");
674
+ const yaml4 = yamlLines.join("\n");
675
675
  return {
676
676
  content: [
677
677
  {
@@ -679,7 +679,7 @@ ${yaml3}
679
679
  text: `\u{1F4C4} Generated Detail Page for: ${entity}
680
680
 
681
681
  \`\`\`yaml
682
- ${yaml3}
682
+ ${yaml4}
683
683
  \`\`\`
684
684
 
685
685
  \u{1F4A1} Add this to pages/${entity}/[${slugField}]/content.yml and validate.`
@@ -2834,21 +2834,40 @@ function readAllManifestPages(artifactsDir) {
2834
2834
  return allPages;
2835
2835
  }
2836
2836
  for (const entry of entries) {
2837
- if (!entry.endsWith("-manifest.json")) continue;
2838
- const filePath = (0, import_path6.join)(artifactsDir, entry);
2839
- try {
2840
- const raw = JSON.parse((0, import_fs6.readFileSync)(filePath, "utf-8"));
2841
- if (!Array.isArray(raw.pages)) continue;
2842
- for (const page of raw.pages) {
2843
- if (typeof page.slug !== "string" || !page.slug) continue;
2837
+ if (entry.endsWith("-manifest.json")) {
2838
+ const filePath = (0, import_path6.join)(artifactsDir, entry);
2839
+ try {
2840
+ const raw = JSON.parse((0, import_fs6.readFileSync)(filePath, "utf-8"));
2841
+ if (!Array.isArray(raw.pages)) continue;
2842
+ for (const page of raw.pages) {
2843
+ if (typeof page.slug !== "string" || !page.slug) continue;
2844
+ allPages.push({
2845
+ slug: page.slug,
2846
+ authRequired: page.authRequired,
2847
+ requiredRoles: page.requiredRoles,
2848
+ source: entry
2849
+ });
2850
+ }
2851
+ } catch {
2852
+ }
2853
+ continue;
2854
+ }
2855
+ if (entry === "workflow-config.json" || entry.startsWith("workflow-config-")) {
2856
+ const filePath = (0, import_path6.join)(artifactsDir, entry);
2857
+ try {
2858
+ const raw = JSON.parse((0, import_fs6.readFileSync)(filePath, "utf-8"));
2859
+ const route = raw.workflow?.route;
2860
+ if (typeof route !== "string" || !route) continue;
2861
+ const slug = route.startsWith("/") ? route.slice(1) : route;
2844
2862
  allPages.push({
2845
- slug: page.slug,
2846
- authRequired: page.authRequired,
2847
- requiredRoles: page.requiredRoles,
2863
+ slug,
2864
+ authRequired: true,
2865
+ requiredRoles: void 0,
2848
2866
  source: entry
2849
2867
  });
2868
+ } catch {
2850
2869
  }
2851
- } catch {
2870
+ continue;
2852
2871
  }
2853
2872
  }
2854
2873
  return allPages;
@@ -2856,7 +2875,7 @@ function readAllManifestPages(artifactsDir) {
2856
2875
  function registerReadAuthManifestsTool(server2) {
2857
2876
  server2.tool(
2858
2877
  "stackwright_pro_read_auth_manifests",
2859
- "Read ALL *-manifest.json files from the pipeline artifacts directory (pages-manifest.json, dashboard-manifest.json, geo-manifest.json, etc.) and deterministically derive protectedRoutes and uncoveredSlugs. Uses the both-forms invariant (swp-n7kk): every authRequired route registers BOTH /{slug} (exact) AND /{slug}/:path* (sub-path). Call this in Step 1 of auth configuration instead of manually reading each manifest file. Fixes the Bead C issue where dashboard-manifest.json and geo-manifest.json were silently missed.",
2878
+ "Read ALL route-producing artifacts from the pipeline artifacts directory: *-manifest.json files (pages, dashboard, geo, form-wizard) AND workflow-config.json / workflow-config-*.json (different shape: workflow.route). Deterministically derives protectedRoutes and uncoveredSlugs. Uses the both-forms invariant (swp-n7kk): every authRequired route registers BOTH /{slug} (exact) AND /{slug}/:path* (sub-path). Workflow routes appear in uncoveredSlugs (no requiredRoles declared) for auth-reconcile to resolve via cross-cutting rules from auth-config.json. Extended by swp-hng0 to cover all four manifest types (was: pages+dashboard only).",
2860
2879
  {
2861
2880
  rbacRoles: import_zod11.z.array(import_zod11.z.string()).describe(
2862
2881
  'RBAC role hierarchy in descending privilege order, e.g. ["ESF8_COORDINATOR", "HOSPITAL_EM", "OBSERVER"]. Used to compute the lowestRole for each authRequired page.'
@@ -2990,7 +3009,7 @@ function enrichErrors(issues, synonyms) {
2990
3009
  });
2991
3010
  }
2992
3011
  function handleValidateYamlFragment(input) {
2993
- const { schemaName, yaml: yaml3 } = input;
3012
+ const { schemaName, yaml: yaml4 } = input;
2994
3013
  if (!SUPPORTED_SCHEMA_NAMES.includes(schemaName)) {
2995
3014
  return {
2996
3015
  valid: false,
@@ -3000,7 +3019,7 @@ function handleValidateYamlFragment(input) {
3000
3019
  const name = schemaName;
3001
3020
  let parsed;
3002
3021
  try {
3003
- parsed = (0, import_js_yaml2.load)(yaml3);
3022
+ parsed = (0, import_js_yaml2.load)(yaml4);
3004
3023
  } catch (err) {
3005
3024
  return {
3006
3025
  valid: false,
@@ -3026,8 +3045,8 @@ function registerValidateYamlFragmentTool(server2) {
3026
3045
  ),
3027
3046
  yaml: import_zod12.z.string().describe("YAML string to validate (can also be JSON \u2014 js-yaml parses both)")
3028
3047
  },
3029
- async ({ schemaName, yaml: yaml3 }) => {
3030
- const result = handleValidateYamlFragment({ schemaName, yaml: yaml3 });
3048
+ async ({ schemaName, yaml: yaml4 }) => {
3049
+ const result = handleValidateYamlFragment({ schemaName, yaml: yaml4 });
3031
3050
  return {
3032
3051
  content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
3033
3052
  };
@@ -3240,6 +3259,9 @@ var MANIFEST_NAME_TO_PHASE = {
3240
3259
  "stackwright-pro-dashboard-otter": "dashboard",
3241
3260
  "stackwright-pro-form-wizard-otter": "workflow",
3242
3261
  "stackwright-pro-geo-otter": "geo",
3262
+ // auth-reconcile runs AFTER pages+dashboard+geo+workflow (wave 6)
3263
+ // Reads auth-config.json + all four manifests; emits auth-reconcile-config.json + stackwright.auth.yml
3264
+ "stackwright-pro-auth-reconcile-otter": "auth-reconcile",
3243
3265
  "stackwright-pro-polish-otter": "polish",
3244
3266
  "stackwright-services-otter": "services",
3245
3267
  "stackwright-pro-qa-otter": "qa"
@@ -3455,13 +3477,19 @@ var PHASE_ORDER = [
3455
3477
  "api",
3456
3478
  "data",
3457
3479
  "auth",
3458
- // moved earlier only depends on design-language.json (designer)
3480
+ // wave 2 — moved earlier, only depends on design-language.json (designer)
3459
3481
  "geo",
3460
3482
  "workflow",
3461
3483
  "services",
3462
3484
  "pages",
3463
3485
  "dashboard",
3486
+ // auth-reconcile: wave 6 — post-manifest reconciliation
3487
+ // Reads auth-config.json + pages/dashboard/geo/workflow manifests;
3488
+ // emits auth-reconcile-config.json + stackwright.auth.yml (Gate 4 single source of truth)
3489
+ "auth-reconcile",
3490
+ // wave 7: polish — depends on auth-reconcile (replaces auth-config.json direct dep)
3464
3491
  "polish",
3492
+ // wave 8: qa — terminal QA gate, depends on polish + transitively everything
3465
3493
  "qa"
3466
3494
  ];
3467
3495
  var PHASE_ARTIFACT = {
@@ -3475,6 +3503,7 @@ var PHASE_ARTIFACT = {
3475
3503
  dashboard: "dashboard-manifest.json",
3476
3504
  workflow: "workflow-config.json",
3477
3505
  services: "services-config.json",
3506
+ "auth-reconcile": "auth-reconcile-config.json",
3478
3507
  polish: "polish-manifest.json",
3479
3508
  geo: "geo-manifest.json",
3480
3509
  qa: "qa-findings.json"
@@ -3489,16 +3518,36 @@ var PHASE_TO_OTTER2 = {
3489
3518
  pages: "stackwright-pro-page-otter",
3490
3519
  dashboard: "stackwright-pro-dashboard-otter",
3491
3520
  workflow: "stackwright-pro-form-wizard-otter",
3521
+ "auth-reconcile": "stackwright-pro-auth-reconcile-otter",
3492
3522
  services: "stackwright-services-otter",
3493
3523
  polish: "stackwright-pro-polish-otter",
3494
3524
  geo: "stackwright-pro-geo-otter",
3495
3525
  qa: "stackwright-pro-qa-otter"
3496
3526
  };
3527
+ var AUTO_EXECUTE_PHASES = /* @__PURE__ */ new Set(["auth-reconcile"]);
3528
+ var PhaseStatusSchema = import_zod14.z.object({
3529
+ status: import_zod14.z.enum(["pending", "running", "completed", "skipped", "failed"]).default("pending"),
3530
+ questionsCollected: import_zod14.z.boolean().default(false),
3531
+ answered: import_zod14.z.boolean().default(false),
3532
+ executed: import_zod14.z.boolean().default(false),
3533
+ artifactWritten: import_zod14.z.boolean().default(false),
3534
+ retryCount: import_zod14.z.number().default(0),
3535
+ inFlight: import_zod14.z.boolean().default(false)
3536
+ });
3537
+ var PipelineStateSchema = import_zod14.z.object({
3538
+ version: import_zod14.z.literal("1.0"),
3539
+ currentPhase: import_zod14.z.string(),
3540
+ status: import_zod14.z.enum(["setup", "questions", "execution", "done"]),
3541
+ phases: import_zod14.z.record(import_zod14.z.string(), PhaseStatusSchema),
3542
+ startedAt: import_zod14.z.string(),
3543
+ updatedAt: import_zod14.z.string()
3544
+ });
3497
3545
  function isValidPhase2(phase) {
3498
3546
  return PHASE_ORDER.includes(phase);
3499
3547
  }
3500
3548
  function defaultPhaseStatus() {
3501
3549
  return {
3550
+ status: "pending",
3502
3551
  questionsCollected: false,
3503
3552
  answered: false,
3504
3553
  executed: false,
@@ -3529,10 +3578,9 @@ function readState(cwd) {
3529
3578
  const p = statePath(cwd);
3530
3579
  if (!(0, import_fs8.existsSync)(p)) return createDefaultState();
3531
3580
  const raw = JSON.parse(safeReadSync(p));
3532
- if (typeof raw !== "object" || raw === null || raw.version !== "1.0") {
3533
- return createDefaultState();
3534
- }
3535
- return raw;
3581
+ const result = PipelineStateSchema.safeParse(raw);
3582
+ if (!result.success) return createDefaultState();
3583
+ return result.data;
3536
3584
  }
3537
3585
  function safeWriteSync(filePath, content) {
3538
3586
  if ((0, import_fs8.existsSync)(filePath)) {
@@ -3621,13 +3669,16 @@ function handleSetPipelineState(input) {
3621
3669
  isError: true
3622
3670
  };
3623
3671
  }
3624
- const VALID_FIELDS = [
3625
- "questionsCollected",
3626
- "answered",
3627
- "executed",
3628
- "artifactWritten",
3629
- "inFlight"
3630
- ];
3672
+ if (input.field === "artifactWritten") {
3673
+ return {
3674
+ text: JSON.stringify({
3675
+ error: true,
3676
+ message: "Cannot set 'artifactWritten' directly. Use stackwright_pro_validate_artifact instead \u2014 it verifies the file exists on disk before flagging."
3677
+ }),
3678
+ isError: true
3679
+ };
3680
+ }
3681
+ const VALID_FIELDS = ["questionsCollected", "answered", "executed", "inFlight"];
3631
3682
  if (input.field && !VALID_FIELDS.includes(input.field)) {
3632
3683
  return {
3633
3684
  text: JSON.stringify({
@@ -3648,6 +3699,15 @@ function handleSetPipelineState(input) {
3648
3699
  isError: true
3649
3700
  };
3650
3701
  }
3702
+ if (update.field === "artifactWritten") {
3703
+ return {
3704
+ text: JSON.stringify({
3705
+ error: true,
3706
+ message: "Cannot set 'artifactWritten' directly. Use stackwright_pro_validate_artifact instead \u2014 it verifies the file exists on disk before flagging."
3707
+ }),
3708
+ isError: true
3709
+ };
3710
+ }
3651
3711
  if (!VALID_FIELDS.includes(update.field)) {
3652
3712
  return {
3653
3713
  text: JSON.stringify({
@@ -3673,6 +3733,9 @@ function handleSetPipelineState(input) {
3673
3733
  if (input.field && input.value !== void 0) {
3674
3734
  phaseState[input.field] = input.value;
3675
3735
  }
3736
+ if (input.phaseStatus !== void 0) {
3737
+ phaseState.status = input.phaseStatus;
3738
+ }
3676
3739
  if (input.incrementRetry) {
3677
3740
  phaseState.retryCount += 1;
3678
3741
  }
@@ -3699,16 +3762,6 @@ function handleSetPipelineState(input) {
3699
3762
  },
3700
3763
  { cwd }
3701
3764
  );
3702
- if (input.field === "artifactWritten" && input.value === true && input.phase) {
3703
- (0, import_telemetry.emit)({ type: "phase_complete", phase: input.phase }, { cwd });
3704
- }
3705
- if (input.updates) {
3706
- for (const update of input.updates) {
3707
- if (update.field === "artifactWritten" && update.value === true) {
3708
- (0, import_telemetry.emit)({ type: "phase_complete", phase: update.phase }, { cwd });
3709
- }
3710
- }
3711
- }
3712
3765
  } catch {
3713
3766
  }
3714
3767
  return { text: JSON.stringify(state), isError: false };
@@ -3728,6 +3781,9 @@ function handleCheckExecutionReady(_cwd, phase) {
3728
3781
  isError: true
3729
3782
  };
3730
3783
  }
3784
+ if (AUTO_EXECUTE_PHASES.has(phase)) {
3785
+ return { text: JSON.stringify({ ready: true, phase, autoExecute: true }), isError: false };
3786
+ }
3731
3787
  const answerFile = (0, import_path8.join)(cwd, ".stackwright", "answers", `${phase}.json`);
3732
3788
  if (!(0, import_fs8.existsSync)(answerFile)) {
3733
3789
  return {
@@ -3757,6 +3813,10 @@ function handleCheckExecutionReady(_cwd, phase) {
3757
3813
  const answeredPhases = [];
3758
3814
  const missingPhases = [];
3759
3815
  for (const phase2 of PHASE_ORDER) {
3816
+ if (AUTO_EXECUTE_PHASES.has(phase2)) {
3817
+ answeredPhases.push(phase2);
3818
+ continue;
3819
+ }
3760
3820
  const answerFile = (0, import_path8.join)(answersDir, `${phase2}.json`);
3761
3821
  if ((0, import_fs8.existsSync)(answerFile)) {
3762
3822
  try {
@@ -4085,6 +4145,8 @@ var PHASE_REQUIRED_KEYS = {
4085
4145
  dashboard: ["version", "generatedBy"],
4086
4146
  workflow: ["version", "generatedBy"],
4087
4147
  services: ["version", "generatedBy", "flows"],
4148
+ // auth-reconcile: must have version, generatedBy, and merged protectedRoutes
4149
+ "auth-reconcile": ["version", "generatedBy", "protectedRoutes"],
4088
4150
  polish: ["version", "generatedBy"],
4089
4151
  geo: ["version", "generatedBy", "geoCollections"],
4090
4152
  // qa: skipped=true path only needs version+generatedBy+skipped;
@@ -4364,6 +4426,28 @@ var PHASE_ARTIFACT_SCHEMA = {
4364
4426
  null,
4365
4427
  2
4366
4428
  ),
4429
+ "auth-reconcile": JSON.stringify(
4430
+ {
4431
+ version: "1.0",
4432
+ generatedBy: "stackwright-pro-auth-reconcile-otter",
4433
+ authType: "<oidc|saml|cac|oauth2>",
4434
+ rbacRoles: ["<HIGHEST_ROLE>", "<MID_ROLE>", "<LOWEST_ROLE>"],
4435
+ rbacDefaultRole: "<LOWEST_ROLE>",
4436
+ protectedRoutes: [
4437
+ { pattern: "/dashboard", requiredRole: "<role>" },
4438
+ { pattern: "/dashboard/:path*", requiredRole: "<role>" }
4439
+ ],
4440
+ uncoveredSlugs: [],
4441
+ manifestsRead: ["pages-manifest.json", "dashboard-manifest.json", "geo-manifest.json"],
4442
+ summary: {
4443
+ totalRoutes: 2,
4444
+ manifestDerived: 2,
4445
+ crossCutting: 0
4446
+ }
4447
+ },
4448
+ null,
4449
+ 2
4450
+ ),
4367
4451
  polish: JSON.stringify(
4368
4452
  {
4369
4453
  version: "1.0",
@@ -4787,6 +4871,7 @@ function _validateArtifactInner(input) {
4787
4871
  if (!state.phases[phase]) state.phases[phase] = defaultPhaseStatus();
4788
4872
  const ps = state.phases[phase];
4789
4873
  ps.artifactWritten = true;
4874
+ ps.status = "completed";
4790
4875
  });
4791
4876
  const topKeys = Object.keys(artifact).slice(0, 5).join(", ");
4792
4877
  const result = {
@@ -4814,6 +4899,7 @@ function handleValidateArtifact(input) {
4814
4899
  const parsed = JSON.parse(result.text);
4815
4900
  if (parsed.valid === true && parsed.artifactPath) {
4816
4901
  (0, import_telemetry.emit)({ type: "file_write", path: parsed.artifactPath, phase }, { cwd });
4902
+ (0, import_telemetry.emit)({ type: "phase_complete", phase }, { cwd });
4817
4903
  }
4818
4904
  (0, import_telemetry.emit)(
4819
4905
  {
@@ -4914,12 +5000,19 @@ function registerPipelineTools(server2) {
4914
5000
  `Atomic read\u2192modify\u2192write pipeline state. ${DESC}`,
4915
5001
  {
4916
5002
  phase: import_zod14.z.string().optional().describe('Phase to update, e.g. "designer"'),
4917
- field: import_zod14.z.enum(["questionsCollected", "answered", "executed", "artifactWritten", "inFlight"]).optional().describe("Boolean field to set"),
5003
+ field: import_zod14.z.enum(["questionsCollected", "answered", "executed", "inFlight"]).optional().describe(
5004
+ "Boolean field to set. NOTE: artifactWritten is intentionally absent \u2014 use stackwright_pro_validate_artifact instead (it verifies the file exists on disk before flagging)."
5005
+ ),
4918
5006
  // inFlight: set true BEFORE specialist invoke (dataflow mode, swp-ioc7); false after.
4919
5007
  value: boolCoerce(import_zod14.z.boolean().optional()).describe(
4920
5008
  'Value for the field \u2014 must be a JSON boolean (true/false), NOT the string "true"/"false"'
4921
5009
  ),
4922
- status: import_zod14.z.enum(["setup", "questions", "execution", "done"]).optional().describe("Top-level status override"),
5010
+ // phaseStatus: per-phase execution enum DISTINCT from top-level `status` (pipeline flow).
5011
+ // Named `phaseStatus` to avoid ambiguity. Sets PhaseStatus.status on the given phase.
5012
+ phaseStatus: import_zod14.z.enum(["pending", "running", "completed", "skipped", "failed"]).optional().describe(
5013
+ "Per-phase execution status (pending/running/completed/skipped/failed). Distinct from top-level `status` (pipeline flow). Use phaseStatus: 'skipped' when a specialist otter is unregistered."
5014
+ ),
5015
+ status: import_zod14.z.enum(["setup", "questions", "execution", "done"]).optional().describe("Top-level pipeline flow status override \u2014 distinct from phaseStatus"),
4923
5016
  incrementRetry: boolCoerce(import_zod14.z.boolean().optional()).describe(
4924
5017
  "Bump retryCount by 1 \u2014 must be a JSON boolean"
4925
5018
  ),
@@ -4931,8 +5024,8 @@ function registerPipelineTools(server2) {
4931
5024
  "questionsCollected",
4932
5025
  "answered",
4933
5026
  "executed",
4934
- "artifactWritten",
4935
5027
  "inFlight"
5028
+ // artifactWritten intentionally absent — use validate_artifact (swp-og9c)
4936
5029
  ]),
4937
5030
  value: import_zod14.z.boolean()
4938
5031
  })
@@ -4944,6 +5037,7 @@ function registerPipelineTools(server2) {
4944
5037
  ...args.phase != null ? { phase: args.phase } : {},
4945
5038
  ...args.field != null ? { field: args.field } : {},
4946
5039
  ...args.value != null ? { value: args.value } : {},
5040
+ ...args.phaseStatus != null ? { phaseStatus: args.phaseStatus } : {},
4947
5041
  ...args.status != null ? { status: args.status } : {},
4948
5042
  ...args.incrementRetry != null ? { incrementRetry: args.incrementRetry } : {},
4949
5043
  ...args.updates != null ? { updates: args.updates } : {}
@@ -5105,6 +5199,20 @@ var OTTER_WRITE_ALLOWLISTS = {
5105
5199
  description: "Stackwright config (themeName + customTheme writeback)"
5106
5200
  }
5107
5201
  ],
5202
+ // swp-hng0: auth-reconcile otter — reads all manifests, re-emits auth-reconcile-config.json
5203
+ // AND overwrites stackwright.auth.yml (Gate 4: single source of truth after reconciliation)
5204
+ "stackwright-pro-auth-reconcile-otter": [
5205
+ {
5206
+ prefix: ".stackwright/artifacts/",
5207
+ suffix: ".json",
5208
+ description: "Auth reconcile artifact (auth-reconcile-config.json)"
5209
+ },
5210
+ {
5211
+ prefix: "stackwright.auth.",
5212
+ suffix: ".yml",
5213
+ description: "Canonical auth sidecar (stackwright.auth.yml \u2014 Gate 4 rewrite)"
5214
+ }
5215
+ ],
5108
5216
  "stackwright-pro-auth-otter": [
5109
5217
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Auth config artifact" },
5110
5218
  {
@@ -6528,7 +6636,11 @@ var _checksums = /* @__PURE__ */ new Map([
6528
6636
  ],
6529
6637
  [
6530
6638
  "stackwright-pro-auth-otter.json",
6531
- "eee511fa214203ce88d51996c6b1b483b6ad3a0ae474cc7be5d380fdbbc43bb1"
6639
+ "c9302132ac4180c70dd452814eb4aa4fb9a1d6a9683f6e01ffce509a6001c6cf"
6640
+ ],
6641
+ [
6642
+ "stackwright-pro-auth-reconcile-otter.json",
6643
+ "7ffd67d51568c7b2a1eb2ea0ac2b2a8041eb9ddd9c1eb5b95744e55ba049b385"
6532
6644
  ],
6533
6645
  [
6534
6646
  "stackwright-pro-dashboard-otter.json",
@@ -6536,7 +6648,7 @@ var _checksums = /* @__PURE__ */ new Map([
6536
6648
  ],
6537
6649
  [
6538
6650
  "stackwright-pro-data-otter.json",
6539
- "be4e3d6f530ae2d6b79d6412e8d2d6defb3caffddedf5be4f928976a72206074"
6651
+ "7d1de846488d84eebf8798aef4d75f4f4d250c1ae070ce1200ead3bb8a545eaf"
6540
6652
  ],
6541
6653
  [
6542
6654
  "stackwright-pro-designer-otter.json",
@@ -6548,7 +6660,7 @@ var _checksums = /* @__PURE__ */ new Map([
6548
6660
  ],
6549
6661
  [
6550
6662
  "stackwright-pro-foreman-otter.json",
6551
- "88c4101154d44a7c5470ded13562402a39323ee570ed3a90a73b8d85d7fd2817"
6663
+ "65aa3273274b2b1859d432c3d1166c4a73971861036bf18c35acd8a5357fcf26"
6552
6664
  ],
6553
6665
  [
6554
6666
  "stackwright-pro-form-wizard-otter.json",
@@ -6564,7 +6676,7 @@ var _checksums = /* @__PURE__ */ new Map([
6564
6676
  ],
6565
6677
  [
6566
6678
  "stackwright-pro-polish-otter.json",
6567
- "4b8223d89af7f92b9051fee7bb29fc4b1ee2a5a9ab87e8e67a6d8be6122fa029"
6679
+ "3ae1252a60727ebca67e33a807d5061cafb88de52f3b98a9d4641f9db725a6ed"
6568
6680
  ],
6569
6681
  [
6570
6682
  "stackwright-pro-qa-otter.json",
@@ -8469,16 +8581,185 @@ function registerListSpecsTool(server2) {
8469
8581
  );
8470
8582
  }
8471
8583
 
8472
- // src/tools/consolidate-integrations.ts
8584
+ // src/tools/describe-endpoint.ts
8473
8585
  var import_zod25 = require("zod");
8474
8586
  var import_fs18 = require("fs");
8475
- var import_proper_lockfile2 = require("proper-lockfile");
8476
8587
  var import_path18 = require("path");
8477
8588
  var import_js_yaml5 = __toESM(require("js-yaml"));
8589
+ function resolveRef(root, ref) {
8590
+ if (!ref.startsWith("#/")) {
8591
+ throw new Error(`[describe_endpoint] External $refs not supported: "${ref}"`);
8592
+ }
8593
+ const parts = ref.slice(2).split("/");
8594
+ let current = root;
8595
+ for (const part of parts) {
8596
+ const key = part.replace(/~1/g, "/").replace(/~0/g, "~");
8597
+ if (current == null || typeof current !== "object") {
8598
+ throw new Error(
8599
+ `[describe_endpoint] $ref "${ref}" cannot be resolved \u2014 segment "${key}" not found`
8600
+ );
8601
+ }
8602
+ current = current[key];
8603
+ }
8604
+ if (current == null || typeof current !== "object") {
8605
+ throw new Error(`[describe_endpoint] $ref "${ref}" resolved to a non-object value`);
8606
+ }
8607
+ return current;
8608
+ }
8609
+ function deref(root, obj) {
8610
+ if (obj == null || typeof obj !== "object") return {};
8611
+ const o = obj;
8612
+ if (typeof o["$ref"] === "string") {
8613
+ const resolved = resolveRef(root, o["$ref"]);
8614
+ return deref(root, resolved);
8615
+ }
8616
+ return o;
8617
+ }
8618
+ function loadSpec(specPath) {
8619
+ const raw = (0, import_fs18.readFileSync)(specPath, "utf-8");
8620
+ const ext = (0, import_path18.extname)(specPath).toLowerCase();
8621
+ if (ext === ".json") {
8622
+ return JSON.parse(raw);
8623
+ }
8624
+ const parsed = import_js_yaml5.default.load(raw);
8625
+ if (parsed == null || typeof parsed !== "object") {
8626
+ throw new Error(`[describe_endpoint] Spec file "${specPath}" did not parse to an object`);
8627
+ }
8628
+ return parsed;
8629
+ }
8630
+ function extractParams(root, pathItem, operation) {
8631
+ const pathLevelRaw = Array.isArray(pathItem["parameters"]) ? pathItem["parameters"] : [];
8632
+ const opLevelRaw = Array.isArray(operation["parameters"]) ? operation["parameters"] : [];
8633
+ const merged = /* @__PURE__ */ new Map();
8634
+ for (const raw of [...pathLevelRaw, ...opLevelRaw]) {
8635
+ const param = deref(root, raw);
8636
+ const name = String(param["name"] ?? "");
8637
+ const inVal = String(param["in"] ?? "");
8638
+ if (name && inVal) {
8639
+ merged.set(`${name}:${inVal}`, param);
8640
+ }
8641
+ }
8642
+ const pathParams = [];
8643
+ const queryParams = [];
8644
+ for (const param of merged.values()) {
8645
+ const inVal = String(param["in"] ?? "");
8646
+ const name = String(param["name"] ?? "");
8647
+ const schema = deref(root, param["schema"] ?? {});
8648
+ if (inVal === "path") {
8649
+ pathParams.push({ name, in: "path", required: true, schema });
8650
+ } else if (inVal === "query") {
8651
+ const required = param["required"] === true;
8652
+ queryParams.push({ name, in: "query", required, schema });
8653
+ }
8654
+ }
8655
+ return { pathParams, queryParams };
8656
+ }
8657
+ function extractSecurity(root, globalSecurity, operationSecurity) {
8658
+ const securityRequirements = operationSecurity !== void 0 ? operationSecurity : globalSecurity;
8659
+ if (securityRequirements.length === 0) return [];
8660
+ const schemes = root["components"]?.["securitySchemes"];
8661
+ if (!schemes || typeof schemes !== "object") return [];
8662
+ const result = [];
8663
+ const seen = /* @__PURE__ */ new Set();
8664
+ for (const req of securityRequirements) {
8665
+ if (!req || typeof req !== "object") continue;
8666
+ for (const schemeName of Object.keys(req)) {
8667
+ if (seen.has(schemeName)) continue;
8668
+ seen.add(schemeName);
8669
+ const schemeRaw = schemes[schemeName];
8670
+ if (!schemeRaw) continue;
8671
+ const scheme = deref(root, schemeRaw);
8672
+ const type = String(scheme["type"] ?? "unknown");
8673
+ if (type === "apiKey") {
8674
+ const inVal = String(scheme["in"] ?? "header");
8675
+ const paramName = String(scheme["name"] ?? schemeName);
8676
+ result.push({ name: schemeName, type, in: inVal, paramName });
8677
+ } else if (type === "http") {
8678
+ const httpScheme = String(scheme["scheme"] ?? "bearer").toLowerCase();
8679
+ result.push({ name: schemeName, type, in: "bearer", paramName: "Authorization" });
8680
+ void httpScheme;
8681
+ } else if (type === "oauth2") {
8682
+ result.push({ name: schemeName, type, in: "bearer", paramName: "Authorization" });
8683
+ } else if (type === "openIdConnect") {
8684
+ result.push({ name: schemeName, type, in: "bearer", paramName: "Authorization" });
8685
+ } else {
8686
+ result.push({ name: schemeName, type, in: "unknown", paramName: schemeName });
8687
+ }
8688
+ }
8689
+ }
8690
+ return result;
8691
+ }
8692
+ function handleDescribeEndpoint(input) {
8693
+ const { specPath, path: path5, method } = input;
8694
+ const normalizedMethod = method.toLowerCase();
8695
+ const root = loadSpec(specPath);
8696
+ const globalSecurity = Array.isArray(root["security"]) ? root["security"] : [];
8697
+ const paths = root["paths"];
8698
+ if (!paths) {
8699
+ return { pathParams: [], queryParams: [], security: [] };
8700
+ }
8701
+ const pathItem = deref(root, paths[path5]);
8702
+ if (!pathItem || Object.keys(pathItem).length === 0) {
8703
+ throw new Error(`[describe_endpoint] Path "${path5}" not found in spec "${specPath}"`);
8704
+ }
8705
+ const operation = deref(root, pathItem[normalizedMethod]);
8706
+ if (!operation || Object.keys(operation).length === 0) {
8707
+ throw new Error(
8708
+ `[describe_endpoint] Method "${method.toUpperCase()}" not found at path "${path5}" in spec "${specPath}"`
8709
+ );
8710
+ }
8711
+ const { pathParams, queryParams } = extractParams(root, pathItem, operation);
8712
+ const operationSecurity = Array.isArray(operation["security"]) ? operation["security"] : void 0;
8713
+ const security = extractSecurity(root, globalSecurity, operationSecurity);
8714
+ return { pathParams, queryParams, security };
8715
+ }
8716
+ function registerDescribeEndpointTool(server2) {
8717
+ server2.tool(
8718
+ "stackwright_pro_describe_endpoint",
8719
+ [
8720
+ "Inspect an OpenAPI 3.0/3.1 spec and return resolved parameter and security details for a",
8721
+ "specific operation. Deterministic \u2014 no LLM. Handles $ref resolution within the spec document.",
8722
+ "",
8723
+ "Returns:",
8724
+ " pathParams \u2014 path parameters [{name, in, required, schema}]",
8725
+ " queryParams \u2014 query parameters [{name, in, required, schema}]",
8726
+ " security \u2014 applicable security schemes [{name, type, in, paramName}]",
8727
+ "",
8728
+ "Used by the data-otter to populate params: entries in stackwright.collections.yml",
8729
+ "from required query params and resolved auth query params (swp-8r8i + swp-1l6l)."
8730
+ ].join("\n"),
8731
+ {
8732
+ specPath: import_zod25.z.string().describe("Absolute path to the OpenAPI spec file (.yaml, .yml, or .json)"),
8733
+ path: import_zod25.z.string().describe('The API path to inspect (e.g. "/stations/{stationId}/observations")'),
8734
+ method: import_zod25.z.string().describe('HTTP method (case-insensitive: "get", "post", "put", etc.)')
8735
+ },
8736
+ async ({ specPath, path: path5, method }) => {
8737
+ try {
8738
+ const result = handleDescribeEndpoint({ specPath, path: path5, method });
8739
+ return {
8740
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }]
8741
+ };
8742
+ } catch (err) {
8743
+ const message = err instanceof Error ? err.message : String(err);
8744
+ return {
8745
+ content: [{ type: "text", text: JSON.stringify({ error: message }) }],
8746
+ isError: true
8747
+ };
8748
+ }
8749
+ }
8750
+ );
8751
+ }
8752
+
8753
+ // src/tools/consolidate-integrations.ts
8754
+ var import_zod26 = require("zod");
8755
+ var import_fs19 = require("fs");
8756
+ var import_proper_lockfile2 = require("proper-lockfile");
8757
+ var import_path19 = require("path");
8758
+ var import_js_yaml6 = __toESM(require("js-yaml"));
8478
8759
  var BASE_MOCK_PORT = 4011;
8479
8760
  var STRIP_SUFFIXES2 = ["-openapi", "-asyncapi", "-api", "-spec"];
8480
8761
  function deriveNameFromFilename(filename) {
8481
- const base = (0, import_path18.basename)(filename, ".json").replace(/^api-config-/, "");
8762
+ const base = (0, import_path19.basename)(filename, ".json").replace(/^api-config-/, "");
8482
8763
  for (const suffix of STRIP_SUFFIXES2) {
8483
8764
  if (base.endsWith(suffix)) return base.slice(0, -suffix.length);
8484
8765
  }
@@ -8486,13 +8767,13 @@ function deriveNameFromFilename(filename) {
8486
8767
  }
8487
8768
  function handleConsolidateIntegrations(input) {
8488
8769
  const { projectRoot } = input;
8489
- const artifactsDir = (0, import_path18.join)(projectRoot, ".stackwright", "artifacts");
8490
- if (!(0, import_fs18.existsSync)(artifactsDir)) {
8770
+ const artifactsDir = (0, import_path19.join)(projectRoot, ".stackwright", "artifacts");
8771
+ if (!(0, import_fs19.existsSync)(artifactsDir)) {
8491
8772
  return { written: false, integrationCount: 0, reason: "no per-spec artifacts found" };
8492
8773
  }
8493
8774
  let entries;
8494
8775
  try {
8495
- entries = (0, import_fs18.readdirSync)(artifactsDir).filter(
8776
+ entries = (0, import_fs19.readdirSync)(artifactsDir).filter(
8496
8777
  (f) => f.startsWith("api-config-") && f.endsWith(".json") && f !== "api-config.json"
8497
8778
  );
8498
8779
  } catch {
@@ -8507,7 +8788,7 @@ function handleConsolidateIntegrations(input) {
8507
8788
  entries.forEach((filename, index) => {
8508
8789
  let artifact = {};
8509
8790
  try {
8510
- const raw = (0, import_fs18.readFileSync)((0, import_path18.join)(artifactsDir, filename), "utf-8");
8791
+ const raw = (0, import_fs19.readFileSync)((0, import_path19.join)(artifactsDir, filename), "utf-8");
8511
8792
  artifact = JSON.parse(raw);
8512
8793
  } catch {
8513
8794
  }
@@ -8540,20 +8821,20 @@ function handleConsolidateIntegrations(input) {
8540
8821
  });
8541
8822
  const integrations = Array.from(integrationsByName.values());
8542
8823
  const dedupedCount = entries.length - skippedIntegrationsList.length - integrations.length;
8543
- const integrationsYmlPath = (0, import_path18.join)(projectRoot, "stackwright.integrations.yml");
8824
+ const integrationsYmlPath = (0, import_path19.join)(projectRoot, "stackwright.integrations.yml");
8544
8825
  let existing = {};
8545
- if ((0, import_fs18.existsSync)(integrationsYmlPath)) {
8826
+ if ((0, import_fs19.existsSync)(integrationsYmlPath)) {
8546
8827
  try {
8547
- const raw = (0, import_fs18.readFileSync)(integrationsYmlPath, "utf-8");
8548
- existing = import_js_yaml5.default.load(raw) ?? {};
8828
+ const raw = (0, import_fs19.readFileSync)(integrationsYmlPath, "utf-8");
8829
+ existing = import_js_yaml6.default.load(raw) ?? {};
8549
8830
  } catch {
8550
8831
  existing = {};
8551
8832
  }
8552
8833
  }
8553
8834
  const merged = { ...existing, integrations };
8554
- (0, import_fs18.mkdirSync)((0, import_path18.join)(projectRoot), { recursive: true });
8555
- if (!(0, import_fs18.existsSync)(integrationsYmlPath)) {
8556
- (0, import_fs18.writeFileSync)(integrationsYmlPath, "", "utf-8");
8835
+ (0, import_fs19.mkdirSync)((0, import_path19.join)(projectRoot), { recursive: true });
8836
+ if (!(0, import_fs19.existsSync)(integrationsYmlPath)) {
8837
+ (0, import_fs19.writeFileSync)(integrationsYmlPath, "", "utf-8");
8557
8838
  }
8558
8839
  const release = (0, import_proper_lockfile2.lockSync)(integrationsYmlPath, {
8559
8840
  realpath: false,
@@ -8562,8 +8843,8 @@ function handleConsolidateIntegrations(input) {
8562
8843
  try {
8563
8844
  const header = `# stackwright.integrations.yml \u2014 Auto-generated by stackwright_pro_consolidate_integrations
8564
8845
  `;
8565
- const body = import_js_yaml5.default.dump(merged, { lineWidth: 120, noRefs: true });
8566
- (0, import_fs18.writeFileSync)(integrationsYmlPath, header + body, "utf-8");
8846
+ const body = import_js_yaml6.default.dump(merged, { lineWidth: 120, noRefs: true });
8847
+ (0, import_fs19.writeFileSync)(integrationsYmlPath, header + body, "utf-8");
8567
8848
  } finally {
8568
8849
  release();
8569
8850
  }
@@ -8591,7 +8872,7 @@ function registerConsolidateIntegrationsTool(server2) {
8591
8872
  "no per-spec artifacts are found."
8592
8873
  ].join(" "),
8593
8874
  {
8594
- projectRoot: import_zod25.z.string().describe("Absolute path to the project root directory")
8875
+ projectRoot: import_zod26.z.string().describe("Absolute path to the project root directory")
8595
8876
  },
8596
8877
  async ({ projectRoot }) => {
8597
8878
  try {
@@ -8649,7 +8930,7 @@ var package_default = {
8649
8930
  "test:coverage": "vitest run --coverage"
8650
8931
  },
8651
8932
  name: "@stackwright-pro/mcp",
8652
- version: "0.2.0-alpha.111",
8933
+ version: "0.2.0-alpha.113",
8653
8934
  description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
8654
8935
  license: "SEE LICENSE IN LICENSE",
8655
8936
  main: "./dist/server.js",
@@ -8719,6 +9000,7 @@ registerCompileTools(server);
8719
9000
  registerStripLegacyIntegrationsTool(server);
8720
9001
  registerEmitterTools(server);
8721
9002
  registerListSpecsTool(server);
9003
+ registerDescribeEndpointTool(server);
8722
9004
  registerConsolidateIntegrationsTool(server);
8723
9005
  (0, import_register.registerContentTypeTools)(server);
8724
9006
  (0, import_register.registerPageTools)(server);