@stackwright-pro/mcp 0.2.0-alpha.60 → 0.2.0-alpha.66

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.mjs CHANGED
@@ -1678,6 +1678,14 @@ function handleSavePhaseAnswers(input) {
1678
1678
  let answers;
1679
1679
  if (input.questions && input.questions.length > 0) {
1680
1680
  answers = answersToManifestFormat(input.rawAnswers, input.questions);
1681
+ if (Object.keys(answers).length === 0 && input.rawAnswers.length > 0) {
1682
+ answers = Object.fromEntries(
1683
+ input.rawAnswers.map((a) => [
1684
+ a.question_header,
1685
+ a.selected_options.length > 1 ? a.selected_options : a.selected_options[0] ?? ""
1686
+ ])
1687
+ );
1688
+ }
1681
1689
  } else {
1682
1690
  answers = Object.fromEntries(
1683
1691
  input.rawAnswers.map((a) => [a.question_header, a.selected_options[0] ?? ""])
@@ -2277,12 +2285,13 @@ var PHASE_DEPENDENCIES = {
2277
2285
  // workflow states as trigger sources. Produces OpenAPI specs consumed
2278
2286
  // by pages and dashboard for typed data wiring.
2279
2287
  services: ["api", "workflow"],
2280
- // pages/dashboard: now also depend on services for typed endpoint wiring
2281
- // via the OpenAPI specs that services produces.
2282
- // 'api' is still transitive through 'data'; auth removed page-otter has
2283
- // graceful fallback and reads role names from workflow artifacts instead
2284
- pages: ["designer", "theme", "data", "services"],
2285
- dashboard: ["designer", "theme", "data", "services"],
2288
+ // pages/dashboard: depend on services for typed endpoint wiring (OpenAPI
2289
+ // specs) and on geo so the specialist prompt includes geo-manifest.json
2290
+ // page/dashboard otters see which page slugs are already claimed and
2291
+ // won't attempt to overwrite them (swp-73c). 'api' is still transitive
2292
+ // through 'data'; auth removed — page-otter has graceful fallback.
2293
+ pages: ["designer", "theme", "data", "services", "geo"],
2294
+ dashboard: ["designer", "theme", "data", "services", "geo"],
2286
2295
  // auth is the penultimate phase — runs after all content-producing phases
2287
2296
  // so it can read pages, dashboard, workflow, and geo artifacts for route
2288
2297
  // protection and RBAC wiring. Skipped upstream phases still satisfy deps
@@ -3024,13 +3033,19 @@ var PHASE_ARTIFACT_SCHEMA = {
3024
3033
  null,
3025
3034
  2
3026
3035
  ),
3036
+ // type: 'pki' = CAC/DoD certificate auth | 'oidc' = enterprise SSO
3037
+ // For dev-only mock auth: use type: 'oidc' with devOnly: true (Zod strips devOnly — it's a convention only)
3027
3038
  auth: JSON.stringify(
3028
3039
  {
3029
3040
  version: "1.0",
3030
3041
  generatedBy: "stackwright-pro-auth-otter",
3031
3042
  authConfig: {
3032
- method: "<cac|oidc|oauth2|none>",
3033
- provider: "<azure-ad|okta|ping|cognito \u2014 if OIDC, else null>",
3043
+ type: "<pki|oidc>",
3044
+ // OIDC-only fields (omit for pki):
3045
+ provider: "<azure_ad|okta|cognito|auth0|authentik|keycloak|custom>",
3046
+ discoveryUrl: "<IdP OIDC discovery URL>",
3047
+ clientId: "<OIDC client ID>",
3048
+ clientSecret: "<OIDC client secret>",
3034
3049
  rbacRoles: ["ADMIN", "ANALYST"],
3035
3050
  rbacDefaultRole: "ANALYST",
3036
3051
  protectedRoutes: ["/dashboard/:path*", "/procurement/:path*"],
@@ -3311,7 +3326,7 @@ function registerPipelineTools(server2) {
3311
3326
 
3312
3327
  // src/tools/safe-write.ts
3313
3328
  import { z as z12 } from "zod";
3314
- import { writeFileSync as writeFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync5, lstatSync as lstatSync6 } from "fs";
3329
+ import { writeFileSync as writeFileSync5, readFileSync as readFileSync5, existsSync as existsSync6, mkdirSync as mkdirSync5, lstatSync as lstatSync6 } from "fs";
3315
3330
  import { normalize, isAbsolute, dirname, join as join5 } from "path";
3316
3331
  var OTTER_WRITE_ALLOWLISTS = {
3317
3332
  "stackwright-pro-designer-otter": [
@@ -3333,6 +3348,16 @@ var OTTER_WRITE_ALLOWLISTS = {
3333
3348
  prefix: ".env",
3334
3349
  suffix: "",
3335
3350
  description: "Dotenv files (.env, .env.local, .env.production, etc.)"
3351
+ },
3352
+ {
3353
+ prefix: "lib/mock-auth",
3354
+ suffix: ".ts",
3355
+ description: "Mock auth module (DEV_ONLY_MODE role updates)"
3356
+ },
3357
+ {
3358
+ prefix: "package.json",
3359
+ suffix: ".json",
3360
+ description: "Project package.json (DEV_ONLY_MODE script cleanup)"
3336
3361
  }
3337
3362
  ],
3338
3363
  "stackwright-pro-data-otter": [
@@ -3371,6 +3396,16 @@ var OTTER_WRITE_ALLOWLISTS = {
3371
3396
  { prefix: "pages/", suffix: "/content.yml", description: "Landing page content" },
3372
3397
  { prefix: "pages/", suffix: "/content.yaml", description: "Landing page content" },
3373
3398
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Polish artifact" }
3399
+ ],
3400
+ "stackwright-services-otter": [
3401
+ { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Services config artifact" },
3402
+ { prefix: "services/", suffix: ".ts", description: "Service implementation files" },
3403
+ { prefix: "services/", suffix: ".yaml", description: "Service flow definitions" },
3404
+ { prefix: "services/", suffix: ".yml", description: "Service flow definitions" },
3405
+ { prefix: "lib/seeds/", suffix: ".ts", description: "Seed data files" },
3406
+ { prefix: "specs/", suffix: ".json", description: "Generated OpenAPI specs" },
3407
+ { prefix: "specs/", suffix: ".yaml", description: "Generated OpenAPI specs" },
3408
+ { prefix: "stackwright-generated/", suffix: ".json", description: "Services manifest" }
3374
3409
  ]
3375
3410
  };
3376
3411
  var PROTECTED_PATH_PREFIXES = [
@@ -3380,7 +3415,9 @@ var PROTECTED_PATH_PREFIXES = [
3380
3415
  ".stackwright/artifacts/signatures.json",
3381
3416
  // artifact signature manifest
3382
3417
  ".stackwright/questions/",
3383
- ".stackwright/answers/"
3418
+ ".stackwright/answers/",
3419
+ ".stackwright/page-registry.json"
3420
+ // page ownership — managed by safe_write internally
3384
3421
  ];
3385
3422
  var MAX_SAFE_WRITE_BYTES_JSON = 512 * 1024;
3386
3423
  var MAX_SAFE_WRITE_BYTES_YAML = 256 * 1024;
@@ -3394,6 +3431,58 @@ function getMaxBytesForPath(filePath) {
3394
3431
  return { limit: MAX_SAFE_WRITE_BYTES_ENV, label: "env" };
3395
3432
  return { limit: MAX_SAFE_WRITE_BYTES_DEFAULT, label: "default" };
3396
3433
  }
3434
+ var PAGE_REGISTRY_FILE = ".stackwright/page-registry.json";
3435
+ function extractPageSlug(filePath) {
3436
+ const match = normalize(filePath).match(/^pages\/(.+?)\/content\.ya?ml$/);
3437
+ return match?.[1] ?? null;
3438
+ }
3439
+ function extractContentTypes(yamlContent) {
3440
+ const typePattern = /^\s*-?\s*type:\s*(\S+)/gm;
3441
+ const types = [];
3442
+ let m;
3443
+ while ((m = typePattern.exec(yamlContent)) !== null) {
3444
+ const typeName = m[1];
3445
+ if (typeName && !types.includes(typeName)) {
3446
+ types.push(typeName);
3447
+ }
3448
+ }
3449
+ return types.length > 0 ? types : ["unknown"];
3450
+ }
3451
+ function readPageRegistry(cwd) {
3452
+ const regPath = join5(cwd, PAGE_REGISTRY_FILE);
3453
+ if (!existsSync6(regPath)) return {};
3454
+ try {
3455
+ const stat = lstatSync6(regPath);
3456
+ if (stat.isSymbolicLink()) return {};
3457
+ return JSON.parse(readFileSync5(regPath, "utf-8"));
3458
+ } catch {
3459
+ return {};
3460
+ }
3461
+ }
3462
+ function writePageRegistry(cwd, registry) {
3463
+ const dir = join5(cwd, ".stackwright");
3464
+ mkdirSync5(dir, { recursive: true });
3465
+ const regPath = join5(cwd, PAGE_REGISTRY_FILE);
3466
+ if (existsSync6(regPath)) {
3467
+ const stat = lstatSync6(regPath);
3468
+ if (stat.isSymbolicLink()) {
3469
+ throw new Error("Refusing to write page registry through symlink");
3470
+ }
3471
+ }
3472
+ writeFileSync5(regPath, JSON.stringify(registry, null, 2) + "\n", { encoding: "utf-8" });
3473
+ }
3474
+ function checkPageOwnership(cwd, slug, callerOtter) {
3475
+ const registry = readPageRegistry(cwd);
3476
+ const existing = registry[slug];
3477
+ if (!existing || existing.claimedBy === callerOtter) {
3478
+ return { allowed: true };
3479
+ }
3480
+ return {
3481
+ allowed: false,
3482
+ owner: existing.claimedBy,
3483
+ contentTypes: existing.contentTypes
3484
+ };
3485
+ }
3397
3486
  function checkPathAllowed(callerOtter, filePath) {
3398
3487
  const normalized = normalize(filePath);
3399
3488
  if (normalized.includes("..")) {
@@ -3435,6 +3524,16 @@ function checkPathAllowed(callerOtter, filePath) {
3435
3524
  continue;
3436
3525
  }
3437
3526
  }
3527
+ if (rule.prefix === "lib/mock-auth" && rule.suffix === ".ts") {
3528
+ if (normalized !== "lib/mock-auth.ts") {
3529
+ continue;
3530
+ }
3531
+ }
3532
+ if (rule.prefix === "package.json" && rule.suffix === ".json") {
3533
+ if (normalized !== "package.json") {
3534
+ continue;
3535
+ }
3536
+ }
3438
3537
  return { allowed: true, rule: rule.description };
3439
3538
  }
3440
3539
  }
@@ -3560,11 +3659,38 @@ function handleSafeWrite(input) {
3560
3659
  };
3561
3660
  return { text: JSON.stringify(result), isError: true };
3562
3661
  }
3662
+ const pageSlug = extractPageSlug(normalized);
3663
+ if (pageSlug) {
3664
+ const ownership = checkPageOwnership(cwd, pageSlug, callerOtter);
3665
+ if (!ownership.allowed) {
3666
+ const result = {
3667
+ success: false,
3668
+ error: `Page slug "${pageSlug}" is already claimed by ${ownership.owner} (content types: ${ownership.contentTypes.join(", ")}). Use a different slug or coordinate with the owning otter.`,
3669
+ callerOtter,
3670
+ attemptedPath: filePath,
3671
+ allowedPaths: []
3672
+ };
3673
+ return { text: JSON.stringify(result), isError: true };
3674
+ }
3675
+ }
3563
3676
  try {
3564
3677
  if (createDirectories) {
3565
3678
  mkdirSync5(dirname(fullPath), { recursive: true });
3566
3679
  }
3567
3680
  writeFileSync5(fullPath, content, { encoding: "utf-8" });
3681
+ if (pageSlug) {
3682
+ try {
3683
+ const registry = readPageRegistry(cwd);
3684
+ registry[pageSlug] = {
3685
+ slug: pageSlug,
3686
+ claimedBy: callerOtter,
3687
+ contentTypes: extractContentTypes(content),
3688
+ writtenAt: (/* @__PURE__ */ new Date()).toISOString()
3689
+ };
3690
+ writePageRegistry(cwd, registry);
3691
+ } catch {
3692
+ }
3693
+ }
3568
3694
  const result = {
3569
3695
  success: true,
3570
3696
  path: normalized,
@@ -3611,7 +3737,7 @@ function registerSafeWriteTools(server2) {
3611
3737
 
3612
3738
  // src/tools/auth.ts
3613
3739
  import { z as z13 } from "zod";
3614
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync6, existsSync as existsSync7 } from "fs";
3740
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync6, existsSync as existsSync7, mkdirSync as mkdirSync6 } from "fs";
3615
3741
  import { join as join6 } from "path";
3616
3742
  function buildHierarchy(roles) {
3617
3743
  const h = {};
@@ -3831,10 +3957,232 @@ ${routeLines}
3831
3957
  ${auditSection}
3832
3958
  `;
3833
3959
  }
3960
+ function generateDevOnlyMiddlewareContent(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
3961
+ const rbacBlock = ` rbac: {
3962
+ roles: ${JSON.stringify(roles)},
3963
+ defaultRole: '${defaultRole}',
3964
+ hierarchy: ${JSON.stringify(hierarchy, null, 4)},
3965
+ },`;
3966
+ const auditBlock = ` audit: {
3967
+ enabled: ${auditEnabled},
3968
+ retentionDays: ${auditRetentionDays},
3969
+ },`;
3970
+ const routesBlock = ` protectedRoutes: ${JSON.stringify(protectedRoutes)},`;
3971
+ const configBlock = `export const config = {
3972
+ matcher: ${JSON.stringify(protectedRoutes)},
3973
+ };`;
3974
+ const devHeader = [
3975
+ "// middleware.ts \u2014 generated by @stackwright-pro/auth-nextjs (dev-only mock)",
3976
+ "// DEV ONLY \u2014 uses mock authentication from lib/mock-auth.ts",
3977
+ "// Do NOT deploy to production.",
3978
+ "import { createProMiddleware } from '@stackwright-pro/auth-nextjs';",
3979
+ "import { mockAuthProvider } from './lib/mock-auth';"
3980
+ ].join("\n");
3981
+ if (method === "cac") {
3982
+ const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
3983
+ const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
3984
+ const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
3985
+ const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
3986
+ return `${devHeader}
3987
+
3988
+ export const middleware = createProMiddleware({
3989
+ method: 'cac',
3990
+ cac: {
3991
+ caBundle: '${caBundle}',
3992
+ edipiLookup: '${edipiLookup}',
3993
+ ocspEndpoint: '${ocspEndpoint}',
3994
+ certHeader: '${certHeader}',
3995
+ provider: mockAuthProvider,
3996
+ },
3997
+ ${rbacBlock}
3998
+ ${auditBlock}
3999
+ ${routesBlock}
4000
+ });
4001
+
4002
+ ${configBlock}
4003
+ `;
4004
+ }
4005
+ if (method === "oidc") {
4006
+ const scopes2 = params.oidcScopes ?? "openid profile email";
4007
+ const roleClaim = params.oidcRoleClaim ?? "roles";
4008
+ return `${devHeader}
4009
+
4010
+ export const middleware = createProMiddleware({
4011
+ method: 'oidc',
4012
+ oidc: {
4013
+ discoveryUrl: 'https://dev-mock-oidc/.well-known/openid-configuration',
4014
+ clientId: 'dev-mock-client',
4015
+ clientSecret: 'dev-mock-secret',
4016
+ scopes: '${scopes2}',
4017
+ roleClaim: '${roleClaim}',
4018
+ provider: mockAuthProvider,
4019
+ },
4020
+ ${rbacBlock}
4021
+ ${auditBlock}
4022
+ ${routesBlock}
4023
+ });
4024
+
4025
+ ${configBlock}
4026
+ `;
4027
+ }
4028
+ const scopes = params.oauth2Scopes ?? "read write";
4029
+ return `${devHeader}
4030
+
4031
+ export const middleware = createProMiddleware({
4032
+ method: 'oauth2',
4033
+ oauth2: {
4034
+ authorizationUrl: 'https://dev-mock-oauth2/authorize',
4035
+ tokenUrl: 'https://dev-mock-oauth2/token',
4036
+ clientId: 'dev-mock-client',
4037
+ clientSecret: 'dev-mock-secret',
4038
+ scopes: '${scopes}',
4039
+ provider: mockAuthProvider,
4040
+ },
4041
+ ${rbacBlock}
4042
+ ${auditBlock}
4043
+ ${routesBlock}
4044
+ });
4045
+
4046
+ ${configBlock}
4047
+ `;
4048
+ }
4049
+ function generateDevOnlyYamlBlock(method, params, roles, defaultRole, hierarchy, auditEnabled, auditRetentionDays, protectedRoutes) {
4050
+ const rbacSection = ` rbac:
4051
+ roles:
4052
+ ${rolesToYaml(roles, " ")}
4053
+ defaultRole: ${defaultRole}
4054
+ hierarchy:
4055
+ ${hierarchyToYaml(hierarchy, " ")}`;
4056
+ const auditSection = ` audit:
4057
+ enabled: ${auditEnabled}
4058
+ retentionDays: ${auditRetentionDays}`;
4059
+ const routeLines = protectedRoutes.map((r) => ` - pattern: ${r}
4060
+ requiredRole: ${defaultRole}`).join("\n");
4061
+ const providerLine = params.provider ? ` provider: ${params.provider}
4062
+ ` : "";
4063
+ if (method === "cac") {
4064
+ const caBundle = params.cacCaBundle ?? "./certs/dod-ca-bundle.pem";
4065
+ const edipiLookup = params.cacEdipiLookup ?? "./config/edipi-lookup.json";
4066
+ const ocspEndpoint = params.cacOcspEndpoint ?? "https://ocsp.disa.mil";
4067
+ const certHeader = params.cacCertHeader ?? "X-SSL-Client-Cert";
4068
+ return `auth:
4069
+ method: cac
4070
+ devOnly: true
4071
+ ${providerLine} middleware: ./middleware.ts
4072
+ cac:
4073
+ caBundle: ${caBundle}
4074
+ edipiLookup: ${edipiLookup}
4075
+ ocspEndpoint: ${ocspEndpoint}
4076
+ certHeader: ${certHeader}
4077
+ ${rbacSection}
4078
+ protectedRoutes:
4079
+ ${routeLines}
4080
+ ${auditSection}
4081
+ `;
4082
+ }
4083
+ if (method === "oidc") {
4084
+ const scopes2 = params.oidcScopes ?? "openid profile email";
4085
+ const roleClaim = params.oidcRoleClaim ?? "roles";
4086
+ return `auth:
4087
+ method: oidc
4088
+ devOnly: true
4089
+ ${providerLine} middleware: ./middleware.ts
4090
+ oidc:
4091
+ discoveryUrl: https://dev-mock-oidc/.well-known/openid-configuration
4092
+ clientId: dev-mock-client
4093
+ clientSecret: dev-mock-secret
4094
+ scopes: ${scopes2}
4095
+ roleClaim: ${roleClaim}
4096
+ ${rbacSection}
4097
+ protectedRoutes:
4098
+ ${routeLines}
4099
+ ${auditSection}
4100
+ `;
4101
+ }
4102
+ const scopes = params.oauth2Scopes ?? "read write";
4103
+ return `auth:
4104
+ method: oauth2
4105
+ devOnly: true
4106
+ ${providerLine} middleware: ./middleware.ts
4107
+ oauth2:
4108
+ authorizationUrl: https://dev-mock-oauth2/authorize
4109
+ tokenUrl: https://dev-mock-oauth2/token
4110
+ clientId: dev-mock-client
4111
+ clientSecret: dev-mock-secret
4112
+ scopes: ${scopes}
4113
+ ${rbacSection}
4114
+ protectedRoutes:
4115
+ ${routeLines}
4116
+ ${auditSection}
4117
+ `;
4118
+ }
4119
+ function deriveDevKey(roleName) {
4120
+ const segment = roleName.split("_")[0];
4121
+ return (segment ?? roleName).toLowerCase();
4122
+ }
4123
+ function generateMockAuthContent(roles, mockUsers) {
4124
+ const entries = [];
4125
+ const scriptLines = [];
4126
+ for (let i = 0; i < roles.length; i++) {
4127
+ const role = roles[i];
4128
+ const devKey = deriveDevKey(role);
4129
+ const persona = mockUsers?.[i];
4130
+ const name = persona?.name ?? `Dev ${role}`;
4131
+ const email = persona?.email ?? `dev-${devKey}@example.mil`;
4132
+ const edipi = String(i + 1).padStart(10, "0");
4133
+ entries.push(` ${devKey}: {
4134
+ id: 'mock-${devKey}-${String(i + 1).padStart(3, "0")}',
4135
+ name: '${name}',
4136
+ email: '${email}',
4137
+ roles: ['${role}'],
4138
+ edipi: '${edipi}',
4139
+ }`);
4140
+ scriptLines.push(` * pnpm dev:${devKey} \u2014 ${name}`);
4141
+ }
4142
+ return `/**
4143
+ * Mock authentication for development mode.
4144
+ *
4145
+ * DEV_ONLY_MODE \u2014 no real auth provider. Select a persona via MOCK_USER env var
4146
+ * or use the convenience scripts in package.json:
4147
+ ${scriptLines.join("\n")}
4148
+ */
4149
+
4150
+ export const MOCK_USERS = {
4151
+ ${entries.join(",\n")}
4152
+ } as const;
4153
+
4154
+ export type MockRole = keyof typeof MOCK_USERS;
4155
+
4156
+ export function getMockUser(role?: string) {
4157
+ const key = (role ?? process.env.MOCK_USER) as MockRole | undefined;
4158
+ if (!key) return null;
4159
+ return MOCK_USERS[key] ?? null;
4160
+ }
4161
+
4162
+ export function mockAuthProvider() {
4163
+ return getMockUser();
4164
+ }
4165
+ `;
4166
+ }
4167
+ function updatePackageJsonScripts(existingJson, roles, mockUsers) {
4168
+ const pkg = JSON.parse(existingJson);
4169
+ const scripts = pkg.scripts ?? {};
4170
+ delete scripts["dev:admin"];
4171
+ delete scripts["dev:analyst"];
4172
+ delete scripts["dev:viewer"];
4173
+ for (let i = 0; i < roles.length; i++) {
4174
+ const devKey = deriveDevKey(roles[i]);
4175
+ scripts[`dev:${devKey}`] = `MOCK_USER=${devKey} next dev`;
4176
+ }
4177
+ pkg.scripts = scripts;
4178
+ return JSON.stringify(pkg, null, 2) + "\n";
4179
+ }
3834
4180
  async function configureAuthHandler(params, cwd) {
3835
4181
  const {
3836
4182
  method,
3837
4183
  provider,
4184
+ devOnly = false,
4185
+ mockUsers,
3838
4186
  rbacRoles = ["SUPER_ADMIN", "ADMIN", "ANALYST"],
3839
4187
  auditEnabled = true,
3840
4188
  auditRetentionDays = 90,
@@ -3864,7 +4212,16 @@ async function configureAuthHandler(params, cwd) {
3864
4212
  }
3865
4213
  const filesWritten = [];
3866
4214
  try {
3867
- const middlewareContent = generateMiddlewareContent(
4215
+ const middlewareContent = devOnly ? generateDevOnlyMiddlewareContent(
4216
+ method,
4217
+ params,
4218
+ roles,
4219
+ defaultRole,
4220
+ hierarchy,
4221
+ auditEnabled,
4222
+ auditRetentionDays,
4223
+ protectedRoutes
4224
+ ) : generateMiddlewareContent(
3868
4225
  method,
3869
4226
  params,
3870
4227
  roles,
@@ -3888,30 +4245,87 @@ async function configureAuthHandler(params, cwd) {
3888
4245
  isError: true
3889
4246
  };
3890
4247
  }
3891
- try {
3892
- const envBlock = generateEnvBlock(method, params);
3893
- const envPath = join6(cwd, ".env.example");
3894
- if (existsSync7(envPath)) {
3895
- const existing = readFileSync5(envPath, "utf8");
3896
- writeFileSync6(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
3897
- } else {
3898
- writeFileSync6(envPath, envBlock, "utf8");
4248
+ if (!devOnly) {
4249
+ try {
4250
+ const envBlock = generateEnvBlock(method, params);
4251
+ const envPath = join6(cwd, ".env.example");
4252
+ if (existsSync7(envPath)) {
4253
+ const existing = readFileSync6(envPath, "utf8");
4254
+ writeFileSync6(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
4255
+ } else {
4256
+ writeFileSync6(envPath, envBlock, "utf8");
4257
+ }
4258
+ filesWritten.push(".env.example");
4259
+ } catch (err) {
4260
+ const msg = err instanceof Error ? err.message : String(err);
4261
+ return {
4262
+ content: [
4263
+ {
4264
+ type: "text",
4265
+ text: JSON.stringify({ success: false, error: `Failed writing .env.example: ${msg}` })
4266
+ }
4267
+ ],
4268
+ isError: true
4269
+ };
4270
+ }
4271
+ }
4272
+ if (devOnly) {
4273
+ try {
4274
+ const mockAuthDir = join6(cwd, "lib");
4275
+ mkdirSync6(mockAuthDir, { recursive: true });
4276
+ const mockAuthContent = generateMockAuthContent(roles, mockUsers);
4277
+ writeFileSync6(join6(mockAuthDir, "mock-auth.ts"), mockAuthContent, "utf8");
4278
+ filesWritten.push("lib/mock-auth.ts");
4279
+ } catch (err) {
4280
+ const msg = err instanceof Error ? err.message : String(err);
4281
+ return {
4282
+ content: [
4283
+ {
4284
+ type: "text",
4285
+ text: JSON.stringify({
4286
+ success: false,
4287
+ error: `Failed writing lib/mock-auth.ts: ${msg}`
4288
+ })
4289
+ }
4290
+ ],
4291
+ isError: true
4292
+ };
4293
+ }
4294
+ try {
4295
+ const pkgPath = join6(cwd, "package.json");
4296
+ if (existsSync7(pkgPath)) {
4297
+ const existingPkg = readFileSync6(pkgPath, "utf8");
4298
+ const updatedPkg = updatePackageJsonScripts(existingPkg, roles, mockUsers);
4299
+ writeFileSync6(pkgPath, updatedPkg, "utf8");
4300
+ filesWritten.push("package.json");
4301
+ }
4302
+ } catch (err) {
4303
+ const msg = err instanceof Error ? err.message : String(err);
4304
+ return {
4305
+ content: [
4306
+ {
4307
+ type: "text",
4308
+ text: JSON.stringify({
4309
+ success: false,
4310
+ error: `Failed updating package.json: ${msg}`
4311
+ })
4312
+ }
4313
+ ],
4314
+ isError: true
4315
+ };
3899
4316
  }
3900
- filesWritten.push(".env.example");
3901
- } catch (err) {
3902
- const msg = err instanceof Error ? err.message : String(err);
3903
- return {
3904
- content: [
3905
- {
3906
- type: "text",
3907
- text: JSON.stringify({ success: false, error: `Failed writing .env.example: ${msg}` })
3908
- }
3909
- ],
3910
- isError: true
3911
- };
3912
4317
  }
3913
4318
  try {
3914
- const authYaml = generateYamlBlock(
4319
+ const authYaml = devOnly ? generateDevOnlyYamlBlock(
4320
+ method,
4321
+ params,
4322
+ roles,
4323
+ defaultRole,
4324
+ hierarchy,
4325
+ auditEnabled,
4326
+ auditRetentionDays,
4327
+ protectedRoutes
4328
+ ) : generateYamlBlock(
3915
4329
  method,
3916
4330
  params,
3917
4331
  roles,
@@ -3925,7 +4339,7 @@ async function configureAuthHandler(params, cwd) {
3925
4339
  if (!existsSync7(ymlPath)) {
3926
4340
  writeFileSync6(ymlPath, authYaml, "utf8");
3927
4341
  } else {
3928
- const existing = readFileSync5(ymlPath, "utf8");
4342
+ const existing = readFileSync6(ymlPath, "utf8");
3929
4343
  writeFileSync6(ymlPath, upsertAuthBlock(existing, authYaml), "utf8");
3930
4344
  }
3931
4345
  filesWritten.push("stackwright.yml");
@@ -3993,7 +4407,9 @@ function registerAuthTools(server2) {
3993
4407
  // Routes
3994
4408
  protectedRoutes: jsonCoerce(z13.array(z13.string()).optional()),
3995
4409
  // Injection for tests
3996
- _cwd: z13.string().optional()
4410
+ _cwd: z13.string().optional(),
4411
+ devOnly: boolCoerce(z13.boolean().optional()),
4412
+ mockUsers: jsonCoerce(z13.array(z13.object({ name: z13.string(), email: z13.string() })).optional())
3997
4413
  },
3998
4414
  async (params) => {
3999
4415
  const cwd = params._cwd ?? process.cwd();
@@ -4004,7 +4420,7 @@ function registerAuthTools(server2) {
4004
4420
 
4005
4421
  // src/integrity.ts
4006
4422
  import { createHash as createHash4, timingSafeEqual as timingSafeEqual2 } from "crypto";
4007
- import { readFileSync as readFileSync6, readdirSync as readdirSync2, lstatSync as lstatSync7 } from "fs";
4423
+ import { readFileSync as readFileSync7, readdirSync as readdirSync2, lstatSync as lstatSync7 } from "fs";
4008
4424
  import { join as join7, basename } from "path";
4009
4425
  var _checksums = /* @__PURE__ */ new Map([
4010
4426
  [
@@ -4013,15 +4429,15 @@ var _checksums = /* @__PURE__ */ new Map([
4013
4429
  ],
4014
4430
  [
4015
4431
  "stackwright-pro-auth-otter.json",
4016
- "bf0e66e35d15ba818ba6ff1a007df34975565bacbb35cc0c80151fb1da13e573"
4432
+ "4bf6beba7150d08c74c5f6fbbeb20e988aba52a2029ff2892615e71f6ab12ed1"
4017
4433
  ],
4018
4434
  [
4019
4435
  "stackwright-pro-dashboard-otter.json",
4020
- "f5a83b74ad7c44edc6f39b45a568fa122d82aa4788f741ce14614da56d4e29a4"
4436
+ "9c319d311801730e8dc9bc142eebb8fc5a7f48da48fa0b8d8c3b7431652447be"
4021
4437
  ],
4022
4438
  [
4023
4439
  "stackwright-pro-data-otter.json",
4024
- "c406e1c775bcb1f2b038b40a92d9bd23172b40d774fc0fa50bad4c9714f53445"
4440
+ "4d9369277685a4acc484116920c9622ad8a1838012a493fcfe42a6ae5abe53cf"
4025
4441
  ],
4026
4442
  [
4027
4443
  "stackwright-pro-designer-otter.json",
@@ -4029,23 +4445,23 @@ var _checksums = /* @__PURE__ */ new Map([
4029
4445
  ],
4030
4446
  [
4031
4447
  "stackwright-pro-domain-expert-otter.json",
4032
- "bfe5c167d73fef3f2ef280fff56dcb552073c218e1394a43ecf983a03169ed55"
4448
+ "6055a2efc78f54a8393f628839e2a2563bf0c6de3ad32de00c82779a53381efd"
4033
4449
  ],
4034
4450
  [
4035
4451
  "stackwright-pro-foreman-otter.json",
4036
- "a3a4c6b3dde05d8bed213759b1b6644d345b3107b73624ff5654d30b98297649"
4452
+ "ab38ef53b95ec610a38b2866d78a135cbec16d257a9b35d7e46e2fee2d4de235"
4037
4453
  ],
4038
4454
  [
4039
4455
  "stackwright-pro-geo-otter.json",
4040
- "6eb7ecf97254dbd79c09ad24348bf16001423cce9585c14bef81afd67b7b901b"
4456
+ "9e09aaf2bb10197c6d1c05d0fd5f5f9380acc0cb697a410fcae839ffba648561"
4041
4457
  ],
4042
4458
  [
4043
4459
  "stackwright-pro-page-otter.json",
4044
- "9a5672f0758c81539337d86955e2892cd412547b4f111c2aa098eed1e62d7626"
4460
+ "532bb7e9a25a5c832edd1ff1ea0886dd4453905d86e6f9331eb957ae5e121833"
4045
4461
  ],
4046
4462
  [
4047
4463
  "stackwright-pro-polish-otter.json",
4048
- "d31116995fdb417798af6056efd03bb1c71e0891371aba1774d283c03c9d77e8"
4464
+ "8f284d4d6a204137cd786824fc584d5bddac1bc757204769b99ca5412cf2cea2"
4049
4465
  ],
4050
4466
  [
4051
4467
  "stackwright-pro-theme-otter.json",
@@ -4057,7 +4473,7 @@ var _checksums = /* @__PURE__ */ new Map([
4057
4473
  ],
4058
4474
  [
4059
4475
  "stackwright-services-otter.json",
4060
- "2a99df3e50415d027c0bc2a57f509882928bb1ae516e61dda667641ce1652ac3"
4476
+ "4893a596d187110124f78336ee91184a51b3c8d980c455382fe481adb9b487b5"
4061
4477
  ]
4062
4478
  ]);
4063
4479
  Object.freeze(_checksums);
@@ -4104,7 +4520,7 @@ function verifyOtterFile(filePath) {
4104
4520
  }
4105
4521
  let raw;
4106
4522
  try {
4107
- raw = readFileSync6(filePath);
4523
+ raw = readFileSync7(filePath);
4108
4524
  } catch (err) {
4109
4525
  const msg = err instanceof Error ? err.message : String(err);
4110
4526
  return { verified: false, filename, error: `Cannot read file: ${msg}` };
@@ -4274,7 +4690,7 @@ function registerIntegrityTools(server2) {
4274
4690
 
4275
4691
  // src/tools/domain.ts
4276
4692
  import { z as z14 } from "zod";
4277
- import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
4693
+ import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
4278
4694
  import { join as join8 } from "path";
4279
4695
  function handleListCollections(input) {
4280
4696
  const cwd = input._cwd ?? process.cwd();
@@ -4299,7 +4715,7 @@ function handleListCollections(input) {
4299
4715
  for (const { path: path3, source, parse } of sources) {
4300
4716
  if (!existsSync8(path3)) continue;
4301
4717
  try {
4302
- const collections = parse(readFileSync7(path3, "utf8"));
4718
+ const collections = parse(readFileSync8(path3, "utf8"));
4303
4719
  return {
4304
4720
  text: JSON.stringify({ collections, source, collectionCount: collections.length }),
4305
4721
  isError: false
@@ -4460,7 +4876,7 @@ function handleValidateWorkflow(input) {
4460
4876
  ]);
4461
4877
  }
4462
4878
  try {
4463
- raw = JSON.parse(readFileSync7(artifactPath, "utf8"));
4879
+ raw = JSON.parse(readFileSync8(artifactPath, "utf8"));
4464
4880
  } catch (err) {
4465
4881
  return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
4466
4882
  }
@@ -4790,7 +5206,7 @@ var package_default = {
4790
5206
  "test:coverage": "vitest run --coverage"
4791
5207
  },
4792
5208
  name: "@stackwright-pro/mcp",
4793
- version: "0.2.0-alpha.60",
5209
+ version: "0.2.0-alpha.61",
4794
5210
  description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
4795
5211
  license: "SEE LICENSE IN LICENSE",
4796
5212
  main: "./dist/server.js",
@@ -4846,6 +5262,15 @@ registerDomainTools(server);
4846
5262
  registerTypeSchemasTool(server);
4847
5263
  async function main() {
4848
5264
  const transport = new StdioServerTransport();
5265
+ try {
5266
+ const servicesRegisterPkg = "@stackwright-services/mcp/register";
5267
+ const mod = await import(servicesRegisterPkg);
5268
+ if (typeof mod.registerServicesTools === "function") {
5269
+ mod.registerServicesTools(server);
5270
+ console.error("Stackwright Services tools registered on pro MCP");
5271
+ }
5272
+ } catch {
5273
+ }
4849
5274
  await server.connect(transport);
4850
5275
  console.error("Stackwright Pro MCP server running on stdio");
4851
5276
  }