@stackwright-pro/mcp 0.2.0-alpha.61 → 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
@@ -3317,7 +3326,7 @@ function registerPipelineTools(server2) {
3317
3326
 
3318
3327
  // src/tools/safe-write.ts
3319
3328
  import { z as z12 } from "zod";
3320
- 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";
3321
3330
  import { normalize, isAbsolute, dirname, join as join5 } from "path";
3322
3331
  var OTTER_WRITE_ALLOWLISTS = {
3323
3332
  "stackwright-pro-designer-otter": [
@@ -3339,6 +3348,16 @@ var OTTER_WRITE_ALLOWLISTS = {
3339
3348
  prefix: ".env",
3340
3349
  suffix: "",
3341
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)"
3342
3361
  }
3343
3362
  ],
3344
3363
  "stackwright-pro-data-otter": [
@@ -3396,7 +3415,9 @@ var PROTECTED_PATH_PREFIXES = [
3396
3415
  ".stackwright/artifacts/signatures.json",
3397
3416
  // artifact signature manifest
3398
3417
  ".stackwright/questions/",
3399
- ".stackwright/answers/"
3418
+ ".stackwright/answers/",
3419
+ ".stackwright/page-registry.json"
3420
+ // page ownership — managed by safe_write internally
3400
3421
  ];
3401
3422
  var MAX_SAFE_WRITE_BYTES_JSON = 512 * 1024;
3402
3423
  var MAX_SAFE_WRITE_BYTES_YAML = 256 * 1024;
@@ -3410,6 +3431,58 @@ function getMaxBytesForPath(filePath) {
3410
3431
  return { limit: MAX_SAFE_WRITE_BYTES_ENV, label: "env" };
3411
3432
  return { limit: MAX_SAFE_WRITE_BYTES_DEFAULT, label: "default" };
3412
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
+ }
3413
3486
  function checkPathAllowed(callerOtter, filePath) {
3414
3487
  const normalized = normalize(filePath);
3415
3488
  if (normalized.includes("..")) {
@@ -3451,6 +3524,16 @@ function checkPathAllowed(callerOtter, filePath) {
3451
3524
  continue;
3452
3525
  }
3453
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
+ }
3454
3537
  return { allowed: true, rule: rule.description };
3455
3538
  }
3456
3539
  }
@@ -3576,11 +3659,38 @@ function handleSafeWrite(input) {
3576
3659
  };
3577
3660
  return { text: JSON.stringify(result), isError: true };
3578
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
+ }
3579
3676
  try {
3580
3677
  if (createDirectories) {
3581
3678
  mkdirSync5(dirname(fullPath), { recursive: true });
3582
3679
  }
3583
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
+ }
3584
3694
  const result = {
3585
3695
  success: true,
3586
3696
  path: normalized,
@@ -3627,7 +3737,7 @@ function registerSafeWriteTools(server2) {
3627
3737
 
3628
3738
  // src/tools/auth.ts
3629
3739
  import { z as z13 } from "zod";
3630
- 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";
3631
3741
  import { join as join6 } from "path";
3632
3742
  function buildHierarchy(roles) {
3633
3743
  const h = {};
@@ -3847,10 +3957,232 @@ ${routeLines}
3847
3957
  ${auditSection}
3848
3958
  `;
3849
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
+ }
3850
4180
  async function configureAuthHandler(params, cwd) {
3851
4181
  const {
3852
4182
  method,
3853
4183
  provider,
4184
+ devOnly = false,
4185
+ mockUsers,
3854
4186
  rbacRoles = ["SUPER_ADMIN", "ADMIN", "ANALYST"],
3855
4187
  auditEnabled = true,
3856
4188
  auditRetentionDays = 90,
@@ -3880,7 +4212,16 @@ async function configureAuthHandler(params, cwd) {
3880
4212
  }
3881
4213
  const filesWritten = [];
3882
4214
  try {
3883
- 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(
3884
4225
  method,
3885
4226
  params,
3886
4227
  roles,
@@ -3904,30 +4245,87 @@ async function configureAuthHandler(params, cwd) {
3904
4245
  isError: true
3905
4246
  };
3906
4247
  }
3907
- try {
3908
- const envBlock = generateEnvBlock(method, params);
3909
- const envPath = join6(cwd, ".env.example");
3910
- if (existsSync7(envPath)) {
3911
- const existing = readFileSync5(envPath, "utf8");
3912
- writeFileSync6(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
3913
- } else {
3914
- 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
+ };
3915
4316
  }
3916
- filesWritten.push(".env.example");
3917
- } catch (err) {
3918
- const msg = err instanceof Error ? err.message : String(err);
3919
- return {
3920
- content: [
3921
- {
3922
- type: "text",
3923
- text: JSON.stringify({ success: false, error: `Failed writing .env.example: ${msg}` })
3924
- }
3925
- ],
3926
- isError: true
3927
- };
3928
4317
  }
3929
4318
  try {
3930
- 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(
3931
4329
  method,
3932
4330
  params,
3933
4331
  roles,
@@ -3941,7 +4339,7 @@ async function configureAuthHandler(params, cwd) {
3941
4339
  if (!existsSync7(ymlPath)) {
3942
4340
  writeFileSync6(ymlPath, authYaml, "utf8");
3943
4341
  } else {
3944
- const existing = readFileSync5(ymlPath, "utf8");
4342
+ const existing = readFileSync6(ymlPath, "utf8");
3945
4343
  writeFileSync6(ymlPath, upsertAuthBlock(existing, authYaml), "utf8");
3946
4344
  }
3947
4345
  filesWritten.push("stackwright.yml");
@@ -4009,7 +4407,9 @@ function registerAuthTools(server2) {
4009
4407
  // Routes
4010
4408
  protectedRoutes: jsonCoerce(z13.array(z13.string()).optional()),
4011
4409
  // Injection for tests
4012
- _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())
4013
4413
  },
4014
4414
  async (params) => {
4015
4415
  const cwd = params._cwd ?? process.cwd();
@@ -4020,7 +4420,7 @@ function registerAuthTools(server2) {
4020
4420
 
4021
4421
  // src/integrity.ts
4022
4422
  import { createHash as createHash4, timingSafeEqual as timingSafeEqual2 } from "crypto";
4023
- 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";
4024
4424
  import { join as join7, basename } from "path";
4025
4425
  var _checksums = /* @__PURE__ */ new Map([
4026
4426
  [
@@ -4029,15 +4429,15 @@ var _checksums = /* @__PURE__ */ new Map([
4029
4429
  ],
4030
4430
  [
4031
4431
  "stackwright-pro-auth-otter.json",
4032
- "8a6ee02cfe7fede3ca708d05b8b46824eb71f60c7f474b6edf9599da77f779b2"
4432
+ "4bf6beba7150d08c74c5f6fbbeb20e988aba52a2029ff2892615e71f6ab12ed1"
4033
4433
  ],
4034
4434
  [
4035
4435
  "stackwright-pro-dashboard-otter.json",
4036
- "f5a83b74ad7c44edc6f39b45a568fa122d82aa4788f741ce14614da56d4e29a4"
4436
+ "9c319d311801730e8dc9bc142eebb8fc5a7f48da48fa0b8d8c3b7431652447be"
4037
4437
  ],
4038
4438
  [
4039
4439
  "stackwright-pro-data-otter.json",
4040
- "c406e1c775bcb1f2b038b40a92d9bd23172b40d774fc0fa50bad4c9714f53445"
4440
+ "4d9369277685a4acc484116920c9622ad8a1838012a493fcfe42a6ae5abe53cf"
4041
4441
  ],
4042
4442
  [
4043
4443
  "stackwright-pro-designer-otter.json",
@@ -4045,7 +4445,7 @@ var _checksums = /* @__PURE__ */ new Map([
4045
4445
  ],
4046
4446
  [
4047
4447
  "stackwright-pro-domain-expert-otter.json",
4048
- "bfe5c167d73fef3f2ef280fff56dcb552073c218e1394a43ecf983a03169ed55"
4448
+ "6055a2efc78f54a8393f628839e2a2563bf0c6de3ad32de00c82779a53381efd"
4049
4449
  ],
4050
4450
  [
4051
4451
  "stackwright-pro-foreman-otter.json",
@@ -4053,15 +4453,15 @@ var _checksums = /* @__PURE__ */ new Map([
4053
4453
  ],
4054
4454
  [
4055
4455
  "stackwright-pro-geo-otter.json",
4056
- "6eb7ecf97254dbd79c09ad24348bf16001423cce9585c14bef81afd67b7b901b"
4456
+ "9e09aaf2bb10197c6d1c05d0fd5f5f9380acc0cb697a410fcae839ffba648561"
4057
4457
  ],
4058
4458
  [
4059
4459
  "stackwright-pro-page-otter.json",
4060
- "9a5672f0758c81539337d86955e2892cd412547b4f111c2aa098eed1e62d7626"
4460
+ "532bb7e9a25a5c832edd1ff1ea0886dd4453905d86e6f9331eb957ae5e121833"
4061
4461
  ],
4062
4462
  [
4063
4463
  "stackwright-pro-polish-otter.json",
4064
- "d31116995fdb417798af6056efd03bb1c71e0891371aba1774d283c03c9d77e8"
4464
+ "8f284d4d6a204137cd786824fc584d5bddac1bc757204769b99ca5412cf2cea2"
4065
4465
  ],
4066
4466
  [
4067
4467
  "stackwright-pro-theme-otter.json",
@@ -4120,7 +4520,7 @@ function verifyOtterFile(filePath) {
4120
4520
  }
4121
4521
  let raw;
4122
4522
  try {
4123
- raw = readFileSync6(filePath);
4523
+ raw = readFileSync7(filePath);
4124
4524
  } catch (err) {
4125
4525
  const msg = err instanceof Error ? err.message : String(err);
4126
4526
  return { verified: false, filename, error: `Cannot read file: ${msg}` };
@@ -4290,7 +4690,7 @@ function registerIntegrityTools(server2) {
4290
4690
 
4291
4691
  // src/tools/domain.ts
4292
4692
  import { z as z14 } from "zod";
4293
- import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
4693
+ import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
4294
4694
  import { join as join8 } from "path";
4295
4695
  function handleListCollections(input) {
4296
4696
  const cwd = input._cwd ?? process.cwd();
@@ -4315,7 +4715,7 @@ function handleListCollections(input) {
4315
4715
  for (const { path: path3, source, parse } of sources) {
4316
4716
  if (!existsSync8(path3)) continue;
4317
4717
  try {
4318
- const collections = parse(readFileSync7(path3, "utf8"));
4718
+ const collections = parse(readFileSync8(path3, "utf8"));
4319
4719
  return {
4320
4720
  text: JSON.stringify({ collections, source, collectionCount: collections.length }),
4321
4721
  isError: false
@@ -4476,7 +4876,7 @@ function handleValidateWorkflow(input) {
4476
4876
  ]);
4477
4877
  }
4478
4878
  try {
4479
- raw = JSON.parse(readFileSync7(artifactPath, "utf8"));
4879
+ raw = JSON.parse(readFileSync8(artifactPath, "utf8"));
4480
4880
  } catch (err) {
4481
4881
  return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
4482
4882
  }