@stackwright-pro/mcp 0.2.0-alpha.40 → 0.2.0-alpha.49

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
@@ -1402,6 +1402,25 @@ function registerQuestionTools(server2) {
1402
1402
  }
1403
1403
  }
1404
1404
  const adapted = adaptQuestions(resolvedQuestions, answers ?? {});
1405
+ const labelSchema = import_zod8.z.string().max(50, "Value should have at most 50 characters");
1406
+ for (const q of adapted) {
1407
+ for (const opt of q.options) {
1408
+ const check = labelSchema.safeParse(opt.label);
1409
+ if (!check.success) {
1410
+ return {
1411
+ content: [
1412
+ {
1413
+ type: "text",
1414
+ text: JSON.stringify({
1415
+ error: `Option label for phase "${phase}" exceeds 50 characters: ${check.error.issues[0]?.message ?? "label too long"}. Truncate option labels to \u226450 characters before calling this tool.`
1416
+ })
1417
+ }
1418
+ ],
1419
+ isError: true
1420
+ };
1421
+ }
1422
+ }
1423
+ }
1405
1424
  if (adapted.length === 0) {
1406
1425
  return {
1407
1426
  content: [
@@ -1897,9 +1916,334 @@ function registerOrchestrationTools(server2) {
1897
1916
  }
1898
1917
 
1899
1918
  // src/tools/pipeline.ts
1900
- var import_zod10 = require("zod");
1919
+ var import_zod11 = require("zod");
1920
+ var import_fs5 = require("fs");
1921
+ var import_path5 = require("path");
1922
+ var import_crypto3 = require("crypto");
1923
+
1924
+ // src/artifact-signing.ts
1925
+ var import_crypto2 = require("crypto");
1901
1926
  var import_fs4 = require("fs");
1902
1927
  var import_path4 = require("path");
1928
+ var import_zod10 = require("zod");
1929
+ var ALGORITHM = "ECDSA-P384-SHA384";
1930
+ var KEY_FILE = "pipeline-keys.json";
1931
+ var KEY_DIR = ".stackwright";
1932
+ var SIGNATURE_MANIFEST = "signatures.json";
1933
+ var ARTIFACTS_DIR = ".stackwright/artifacts";
1934
+ function rejectSymlink(filePath, context) {
1935
+ if (!(0, import_fs4.existsSync)(filePath)) return;
1936
+ const stat = (0, import_fs4.lstatSync)(filePath);
1937
+ if (stat.isSymbolicLink()) {
1938
+ throw new Error(`Security: refusing to follow symlink at ${context}: ${filePath}`);
1939
+ }
1940
+ }
1941
+ function computeSha384(data) {
1942
+ return (0, import_crypto2.createHash)("sha384").update(data).digest("hex");
1943
+ }
1944
+ function safeDigestEqual(a, b) {
1945
+ if (a.length !== b.length) return false;
1946
+ return (0, import_crypto2.timingSafeEqual)(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
1947
+ }
1948
+ function emptyManifest() {
1949
+ return {
1950
+ version: "1.0",
1951
+ algorithm: ALGORITHM,
1952
+ signatures: {}
1953
+ };
1954
+ }
1955
+ function initPipelineKeys(cwd) {
1956
+ const keyDir = (0, import_path4.join)(cwd, KEY_DIR);
1957
+ const keyPath = (0, import_path4.join)(keyDir, KEY_FILE);
1958
+ rejectSymlink(keyPath, "pipeline-keys.json");
1959
+ (0, import_fs4.mkdirSync)(keyDir, { recursive: true });
1960
+ const { publicKey, privateKey } = (0, import_crypto2.generateKeyPairSync)("ec", {
1961
+ namedCurve: "P-384"
1962
+ });
1963
+ const publicKeyPem = publicKey.export({ type: "spki", format: "pem" });
1964
+ const privateKeyPem = privateKey.export({ type: "pkcs8", format: "pem" });
1965
+ const fingerprint = (0, import_crypto2.createHash)("sha256").update(publicKeyPem).digest("hex");
1966
+ const keyFile = {
1967
+ version: "1.0",
1968
+ algorithm: ALGORITHM,
1969
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1970
+ publicKeyPem,
1971
+ privateKeyPem
1972
+ };
1973
+ (0, import_fs4.writeFileSync)(keyPath, JSON.stringify(keyFile, null, 2), { encoding: "utf-8" });
1974
+ return { publicKeyPem, fingerprint };
1975
+ }
1976
+ function loadPipelineKeys(cwd) {
1977
+ const keyPath = (0, import_path4.join)(cwd, KEY_DIR, KEY_FILE);
1978
+ rejectSymlink(keyPath, "pipeline-keys.json");
1979
+ if (!(0, import_fs4.existsSync)(keyPath)) {
1980
+ throw new Error("Pipeline keys not found \u2014 call initPipelineKeys() first");
1981
+ }
1982
+ let raw;
1983
+ try {
1984
+ raw = (0, import_fs4.readFileSync)(keyPath, "utf-8");
1985
+ } catch (err) {
1986
+ const msg = err instanceof Error ? err.message : String(err);
1987
+ throw new Error(`Cannot read pipeline keys: ${msg}`, { cause: err });
1988
+ }
1989
+ let parsed;
1990
+ try {
1991
+ parsed = JSON.parse(raw);
1992
+ } catch {
1993
+ throw new Error("Pipeline keys file is not valid JSON");
1994
+ }
1995
+ if (typeof parsed.publicKeyPem !== "string" || !parsed.publicKeyPem.includes("-----BEGIN PUBLIC KEY-----")) {
1996
+ throw new Error("Invalid public key PEM in pipeline keys file");
1997
+ }
1998
+ if (typeof parsed.privateKeyPem !== "string" || !parsed.privateKeyPem.includes("-----BEGIN")) {
1999
+ throw new Error("Invalid private key PEM in pipeline keys file");
2000
+ }
2001
+ const publicKey = (0, import_crypto2.createPublicKey)(parsed.publicKeyPem);
2002
+ const privateKey = (0, import_crypto2.createPrivateKey)(parsed.privateKeyPem);
2003
+ return { privateKey, publicKey };
2004
+ }
2005
+ function signArtifact(artifactBytes, privateKey) {
2006
+ const digest = computeSha384(artifactBytes);
2007
+ const sig = (0, import_crypto2.sign)("SHA384", artifactBytes, privateKey);
2008
+ return {
2009
+ digest,
2010
+ signature: sig.toString("base64"),
2011
+ algorithm: ALGORITHM,
2012
+ signedAt: (/* @__PURE__ */ new Date()).toISOString()
2013
+ };
2014
+ }
2015
+ function verifyArtifact(artifactBytes, signature, publicKey) {
2016
+ const sigValid = (0, import_crypto2.verify)(
2017
+ "SHA384",
2018
+ artifactBytes,
2019
+ publicKey,
2020
+ Buffer.from(signature.signature, "base64")
2021
+ );
2022
+ if (!sigValid) return false;
2023
+ const actualDigest = computeSha384(artifactBytes);
2024
+ return safeDigestEqual(actualDigest, signature.digest);
2025
+ }
2026
+ function loadSignatureManifest(cwd) {
2027
+ const manifestPath = (0, import_path4.join)(cwd, ARTIFACTS_DIR, SIGNATURE_MANIFEST);
2028
+ rejectSymlink(manifestPath, "signatures.json");
2029
+ if (!(0, import_fs4.existsSync)(manifestPath)) return emptyManifest();
2030
+ let raw;
2031
+ try {
2032
+ raw = (0, import_fs4.readFileSync)(manifestPath, "utf-8");
2033
+ } catch (err) {
2034
+ const msg = err instanceof Error ? err.message : String(err);
2035
+ throw new Error(`Cannot read signature manifest: ${msg}`, { cause: err });
2036
+ }
2037
+ try {
2038
+ const parsed = JSON.parse(raw);
2039
+ if (parsed.version !== "1.0" || typeof parsed.signatures !== "object") {
2040
+ throw new Error("Malformed signature manifest: invalid version or missing signatures object");
2041
+ }
2042
+ return parsed;
2043
+ } catch (err) {
2044
+ if (err instanceof Error && err.message.startsWith("Malformed")) throw err;
2045
+ throw new Error("Signature manifest is not valid JSON", { cause: err });
2046
+ }
2047
+ }
2048
+ function saveArtifactSignature(cwd, artifactFilename, sig, signerOtter) {
2049
+ const artifactsDir = (0, import_path4.join)(cwd, ARTIFACTS_DIR);
2050
+ const manifestPath = (0, import_path4.join)(artifactsDir, SIGNATURE_MANIFEST);
2051
+ rejectSymlink(manifestPath, "signatures.json (save)");
2052
+ (0, import_fs4.mkdirSync)(artifactsDir, { recursive: true });
2053
+ const manifest = loadSignatureManifest(cwd);
2054
+ manifest.signatures[artifactFilename] = {
2055
+ ...sig,
2056
+ signedBy: signerOtter
2057
+ };
2058
+ (0, import_fs4.writeFileSync)(manifestPath, JSON.stringify(manifest, null, 2), { encoding: "utf-8" });
2059
+ }
2060
+ function getArtifactSignature(cwd, artifactFilename) {
2061
+ const manifest = loadSignatureManifest(cwd);
2062
+ const entry = manifest.signatures[artifactFilename];
2063
+ if (!entry) return null;
2064
+ return {
2065
+ digest: entry.digest,
2066
+ signature: entry.signature,
2067
+ algorithm: entry.algorithm,
2068
+ signedAt: entry.signedAt
2069
+ };
2070
+ }
2071
+ function emitSignatureAuditEvent(params) {
2072
+ const record = JSON.stringify({
2073
+ level: "AUDIT",
2074
+ event: "ARTIFACT_SIGNATURE_FAIL",
2075
+ timestamp: params.timestamp,
2076
+ source: params.source,
2077
+ artifactFilename: params.artifactFilename,
2078
+ expectedDigest: params.expectedDigest,
2079
+ actualDigest: params.actualDigest,
2080
+ phase: params.phase
2081
+ });
2082
+ process.stderr.write(`ARTIFACT_SIGNATURE_FAIL ${record}
2083
+ `);
2084
+ }
2085
+ function registerArtifactSigningTools(server2) {
2086
+ server2.tool(
2087
+ "stackwright_pro_verify_artifact_signatures",
2088
+ "Verify ECDSA P-384 signatures for all pipeline artifacts in .stackwright/artifacts/. Auto-discovers keys from .stackwright/pipeline-keys.json. Returns per-artifact verification status.",
2089
+ {
2090
+ cwd: import_zod10.z.string().optional().describe("Project root directory. Defaults to process.cwd().")
2091
+ },
2092
+ async ({ cwd: cwdParam }) => {
2093
+ const cwd = cwdParam ?? process.cwd();
2094
+ let publicKey;
2095
+ try {
2096
+ const keys = loadPipelineKeys(cwd);
2097
+ publicKey = keys.publicKey;
2098
+ } catch (err) {
2099
+ const msg = err instanceof Error ? err.message : String(err);
2100
+ return {
2101
+ content: [
2102
+ {
2103
+ type: "text",
2104
+ text: JSON.stringify({
2105
+ error: true,
2106
+ message: `Cannot load pipeline keys: ${msg}`
2107
+ })
2108
+ }
2109
+ ],
2110
+ isError: true
2111
+ };
2112
+ }
2113
+ let manifest;
2114
+ try {
2115
+ manifest = loadSignatureManifest(cwd);
2116
+ } catch (err) {
2117
+ const msg = err instanceof Error ? err.message : String(err);
2118
+ return {
2119
+ content: [
2120
+ {
2121
+ type: "text",
2122
+ text: JSON.stringify({
2123
+ error: true,
2124
+ message: `Cannot load signature manifest: ${msg}`
2125
+ })
2126
+ }
2127
+ ],
2128
+ isError: true
2129
+ };
2130
+ }
2131
+ const artifactsPath = (0, import_path4.join)(cwd, ARTIFACTS_DIR);
2132
+ let artifactFiles = [];
2133
+ try {
2134
+ if ((0, import_fs4.existsSync)(artifactsPath)) {
2135
+ artifactFiles = (0, import_fs4.readdirSync)(artifactsPath).filter(
2136
+ (f) => f.endsWith(".json") && f !== SIGNATURE_MANIFEST
2137
+ );
2138
+ }
2139
+ } catch {
2140
+ }
2141
+ const results = [];
2142
+ let hasFailure = false;
2143
+ for (const filename of artifactFiles) {
2144
+ const filePath = (0, import_path4.join)(artifactsPath, filename);
2145
+ try {
2146
+ rejectSymlink(filePath, `artifact ${filename}`);
2147
+ } catch {
2148
+ results.push({
2149
+ filename,
2150
+ verified: false,
2151
+ error: "Refusing to verify symlink"
2152
+ });
2153
+ hasFailure = true;
2154
+ continue;
2155
+ }
2156
+ let artifactBytes;
2157
+ try {
2158
+ artifactBytes = (0, import_fs4.readFileSync)(filePath);
2159
+ } catch (err) {
2160
+ const msg = err instanceof Error ? err.message : String(err);
2161
+ results.push({
2162
+ filename,
2163
+ verified: false,
2164
+ error: `Cannot read artifact: ${msg}`
2165
+ });
2166
+ hasFailure = true;
2167
+ continue;
2168
+ }
2169
+ const entry = manifest.signatures[filename];
2170
+ if (!entry) {
2171
+ results.push({
2172
+ filename,
2173
+ verified: false,
2174
+ error: "No signature found in manifest"
2175
+ });
2176
+ hasFailure = true;
2177
+ continue;
2178
+ }
2179
+ const sig = {
2180
+ digest: entry.digest,
2181
+ signature: entry.signature,
2182
+ algorithm: entry.algorithm,
2183
+ signedAt: entry.signedAt
2184
+ };
2185
+ const verified = verifyArtifact(artifactBytes, sig, publicKey);
2186
+ if (!verified) {
2187
+ const actualDigest = computeSha384(artifactBytes);
2188
+ emitSignatureAuditEvent({
2189
+ artifactFilename: filename,
2190
+ expectedDigest: sig.digest,
2191
+ actualDigest,
2192
+ phase: entry.signedBy ?? "unknown",
2193
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2194
+ source: "stackwright_pro_verify_artifact_signatures"
2195
+ });
2196
+ results.push({
2197
+ filename,
2198
+ verified: false,
2199
+ error: `Signature verification failed \u2014 artifact may have been tampered with`,
2200
+ signedBy: entry.signedBy,
2201
+ signedAt: entry.signedAt
2202
+ });
2203
+ hasFailure = true;
2204
+ } else {
2205
+ results.push({
2206
+ filename,
2207
+ verified: true,
2208
+ signedBy: entry.signedBy,
2209
+ signedAt: entry.signedAt
2210
+ });
2211
+ }
2212
+ }
2213
+ for (const manifestFilename of Object.keys(manifest.signatures)) {
2214
+ if (!artifactFiles.includes(manifestFilename)) {
2215
+ results.push({
2216
+ filename: manifestFilename,
2217
+ verified: false,
2218
+ error: "Artifact referenced in manifest but missing from disk"
2219
+ });
2220
+ hasFailure = true;
2221
+ }
2222
+ }
2223
+ const verifiedCount = results.filter((r) => r.verified).length;
2224
+ const failedCount = results.filter((r) => !r.verified).length;
2225
+ return {
2226
+ content: [
2227
+ {
2228
+ type: "text",
2229
+ text: JSON.stringify({
2230
+ totalArtifacts: artifactFiles.length,
2231
+ verifiedCount,
2232
+ failedCount,
2233
+ results,
2234
+ ...hasFailure ? {
2235
+ error: "SIGNATURE VERIFICATION FAILED: One or more artifact signatures are invalid. Do not proceed \u2014 artifacts may have been tampered with."
2236
+ } : {}
2237
+ })
2238
+ }
2239
+ ],
2240
+ isError: hasFailure
2241
+ };
2242
+ }
2243
+ );
2244
+ }
2245
+
2246
+ // src/tools/pipeline.ts
1903
2247
  var import_types = require("@stackwright-pro/types");
1904
2248
  var PHASE_ORDER = [
1905
2249
  "designer",
@@ -1975,11 +2319,11 @@ function createDefaultState() {
1975
2319
  };
1976
2320
  }
1977
2321
  function statePath(cwd) {
1978
- return (0, import_path4.join)(cwd, ".stackwright", "pipeline-state.json");
2322
+ return (0, import_path5.join)(cwd, ".stackwright", "pipeline-state.json");
1979
2323
  }
1980
2324
  function readState(cwd) {
1981
2325
  const p = statePath(cwd);
1982
- if (!(0, import_fs4.existsSync)(p)) return createDefaultState();
2326
+ if (!(0, import_fs5.existsSync)(p)) return createDefaultState();
1983
2327
  const raw = JSON.parse(safeReadSync(p));
1984
2328
  if (typeof raw !== "object" || raw === null || raw.version !== "1.0") {
1985
2329
  return createDefaultState();
@@ -1987,26 +2331,26 @@ function readState(cwd) {
1987
2331
  return raw;
1988
2332
  }
1989
2333
  function safeWriteSync(filePath, content) {
1990
- if ((0, import_fs4.existsSync)(filePath)) {
1991
- const stat = (0, import_fs4.lstatSync)(filePath);
2334
+ if ((0, import_fs5.existsSync)(filePath)) {
2335
+ const stat = (0, import_fs5.lstatSync)(filePath);
1992
2336
  if (stat.isSymbolicLink()) {
1993
2337
  throw new Error(`Refusing to write to symlink: ${filePath}`);
1994
2338
  }
1995
2339
  }
1996
- (0, import_fs4.writeFileSync)(filePath, content);
2340
+ (0, import_fs5.writeFileSync)(filePath, content);
1997
2341
  }
1998
2342
  function safeReadSync(filePath) {
1999
- if ((0, import_fs4.existsSync)(filePath)) {
2000
- const stat = (0, import_fs4.lstatSync)(filePath);
2343
+ if ((0, import_fs5.existsSync)(filePath)) {
2344
+ const stat = (0, import_fs5.lstatSync)(filePath);
2001
2345
  if (stat.isSymbolicLink()) {
2002
2346
  throw new Error(`Refusing to read symlink: ${filePath}`);
2003
2347
  }
2004
2348
  }
2005
- return (0, import_fs4.readFileSync)(filePath, "utf-8");
2349
+ return (0, import_fs5.readFileSync)(filePath, "utf-8");
2006
2350
  }
2007
2351
  function writeState(cwd, state) {
2008
- const dir = (0, import_path4.join)(cwd, ".stackwright");
2009
- (0, import_fs4.mkdirSync)(dir, { recursive: true });
2352
+ const dir = (0, import_path5.join)(cwd, ".stackwright");
2353
+ (0, import_fs5.mkdirSync)(dir, { recursive: true });
2010
2354
  state.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
2011
2355
  safeWriteSync(statePath(cwd), JSON.stringify(state, null, 2) + "\n");
2012
2356
  }
@@ -2027,6 +2371,15 @@ function handleGetPipelineState(_cwd) {
2027
2371
  const cwd = _cwd ?? process.cwd();
2028
2372
  try {
2029
2373
  const state = readState(cwd);
2374
+ const keyPath = (0, import_path5.join)(cwd, ".stackwright", "pipeline-keys.json");
2375
+ if (!(0, import_fs5.existsSync)(keyPath)) {
2376
+ try {
2377
+ const { fingerprint } = initPipelineKeys(cwd);
2378
+ state["signingKeyFingerprint"] = fingerprint;
2379
+ writeState(cwd, state);
2380
+ } catch {
2381
+ }
2382
+ }
2030
2383
  return { text: JSON.stringify(state), isError: false };
2031
2384
  } catch (err) {
2032
2385
  return { text: JSON.stringify({ error: true, message: String(err) }), isError: true };
@@ -2090,8 +2443,8 @@ function handleCheckExecutionReady(_cwd, phase) {
2090
2443
  isError: true
2091
2444
  };
2092
2445
  }
2093
- const answerFile = (0, import_path4.join)(cwd, ".stackwright", "answers", `${phase}.json`);
2094
- if (!(0, import_fs4.existsSync)(answerFile)) {
2446
+ const answerFile = (0, import_path5.join)(cwd, ".stackwright", "answers", `${phase}.json`);
2447
+ if (!(0, import_fs5.existsSync)(answerFile)) {
2095
2448
  return {
2096
2449
  text: JSON.stringify({ ready: false, phase, reason: "Answer file not found" }),
2097
2450
  isError: false
@@ -2115,12 +2468,12 @@ function handleCheckExecutionReady(_cwd, phase) {
2115
2468
  }
2116
2469
  }
2117
2470
  try {
2118
- const answersDir = (0, import_path4.join)(cwd, ".stackwright", "answers");
2471
+ const answersDir = (0, import_path5.join)(cwd, ".stackwright", "answers");
2119
2472
  const answeredPhases = [];
2120
2473
  const missingPhases = [];
2121
2474
  for (const phase2 of PHASE_ORDER) {
2122
- const answerFile = (0, import_path4.join)(answersDir, `${phase2}.json`);
2123
- if ((0, import_fs4.existsSync)(answerFile)) {
2475
+ const answerFile = (0, import_path5.join)(answersDir, `${phase2}.json`);
2476
+ if ((0, import_fs5.existsSync)(answerFile)) {
2124
2477
  try {
2125
2478
  const raw = safeReadSync(answerFile);
2126
2479
  const parsed = JSON.parse(raw);
@@ -2152,15 +2505,35 @@ function handleCheckExecutionReady(_cwd, phase) {
2152
2505
  function handleListArtifacts(_cwd) {
2153
2506
  const cwd = _cwd ?? process.cwd();
2154
2507
  try {
2155
- const artifactsDir = (0, import_path4.join)(cwd, ".stackwright", "artifacts");
2508
+ const artifactsDir = (0, import_path5.join)(cwd, ".stackwright", "artifacts");
2509
+ let manifest = null;
2510
+ try {
2511
+ manifest = loadSignatureManifest(cwd);
2512
+ } catch {
2513
+ }
2156
2514
  const artifacts = [];
2157
2515
  let completedCount = 0;
2158
2516
  for (const phase of PHASE_ORDER) {
2159
2517
  const expectedFile = PHASE_ARTIFACT[phase];
2160
- const fullPath = (0, import_path4.join)(artifactsDir, expectedFile);
2161
- const exists = (0, import_fs4.existsSync)(fullPath);
2518
+ const fullPath = (0, import_path5.join)(artifactsDir, expectedFile);
2519
+ const exists = (0, import_fs5.existsSync)(fullPath);
2162
2520
  if (exists) completedCount++;
2163
- artifacts.push({ phase, expectedFile, exists, path: fullPath });
2521
+ let signed = false;
2522
+ let signatureValid = null;
2523
+ if (exists && manifest) {
2524
+ const entry = manifest.signatures[expectedFile];
2525
+ if (entry) {
2526
+ signed = true;
2527
+ try {
2528
+ const rawBytes = Buffer.from(safeReadSync(fullPath), "utf-8");
2529
+ const { publicKey } = loadPipelineKeys(cwd);
2530
+ signatureValid = verifyArtifact(rawBytes, entry, publicKey);
2531
+ } catch {
2532
+ signatureValid = null;
2533
+ }
2534
+ }
2535
+ }
2536
+ artifacts.push({ phase, expectedFile, exists, path: fullPath, signed, signatureValid });
2164
2537
  }
2165
2538
  return {
2166
2539
  text: JSON.stringify({ artifacts, completedCount, totalCount: PHASE_ORDER.length }),
@@ -2196,9 +2569,9 @@ function handleWritePhaseQuestions(input) {
2196
2569
  }
2197
2570
  } catch {
2198
2571
  }
2199
- const questionsDir = (0, import_path4.join)(cwd, ".stackwright", "questions");
2200
- (0, import_fs4.mkdirSync)(questionsDir, { recursive: true });
2201
- const filePath = (0, import_path4.join)(questionsDir, `${phase}.json`);
2572
+ const questionsDir = (0, import_path5.join)(cwd, ".stackwright", "questions");
2573
+ (0, import_fs5.mkdirSync)(questionsDir, { recursive: true });
2574
+ const filePath = (0, import_path5.join)(questionsDir, `${phase}.json`);
2202
2575
  const payload = {
2203
2576
  version: "1.0",
2204
2577
  phase,
@@ -2238,14 +2611,14 @@ function handleBuildSpecialistPrompt(input) {
2238
2611
  };
2239
2612
  }
2240
2613
  try {
2241
- const answersPath = (0, import_path4.join)(cwd, ".stackwright", "answers", `${phase}.json`);
2614
+ const answersPath = (0, import_path5.join)(cwd, ".stackwright", "answers", `${phase}.json`);
2242
2615
  let answers = {};
2243
- if ((0, import_fs4.existsSync)(answersPath)) {
2616
+ if ((0, import_fs5.existsSync)(answersPath)) {
2244
2617
  answers = JSON.parse(safeReadSync(answersPath));
2245
2618
  }
2246
2619
  let buildContextText = "";
2247
- const buildContextPath = (0, import_path4.join)(cwd, ".stackwright", "build-context.json");
2248
- if ((0, import_fs4.existsSync)(buildContextPath)) {
2620
+ const buildContextPath = (0, import_path5.join)(cwd, ".stackwright", "build-context.json");
2621
+ if ((0, import_fs5.existsSync)(buildContextPath)) {
2249
2622
  try {
2250
2623
  const bcRaw = JSON.parse(safeReadSync(buildContextPath));
2251
2624
  if (typeof bcRaw.buildContext === "string" && bcRaw.buildContext.trim().length > 0) {
@@ -2259,9 +2632,39 @@ function handleBuildSpecialistPrompt(input) {
2259
2632
  const missingDependencies = [];
2260
2633
  for (const dep of deps) {
2261
2634
  const artifactFile = PHASE_ARTIFACT[dep];
2262
- const artifactPath = (0, import_path4.join)(cwd, ".stackwright", "artifacts", artifactFile);
2263
- if ((0, import_fs4.existsSync)(artifactPath)) {
2264
- const content = JSON.parse(safeReadSync(artifactPath));
2635
+ const artifactPath = (0, import_path5.join)(cwd, ".stackwright", "artifacts", artifactFile);
2636
+ if ((0, import_fs5.existsSync)(artifactPath)) {
2637
+ const rawContent = safeReadSync(artifactPath);
2638
+ const rawBytes = Buffer.from(rawContent, "utf-8");
2639
+ const content = JSON.parse(rawContent);
2640
+ let signatureVerified = false;
2641
+ let signatureAvailable = false;
2642
+ try {
2643
+ const sig = getArtifactSignature(cwd, artifactFile);
2644
+ if (sig) {
2645
+ signatureAvailable = true;
2646
+ const { publicKey } = loadPipelineKeys(cwd);
2647
+ signatureVerified = verifyArtifact(rawBytes, sig, publicKey);
2648
+ if (!signatureVerified) {
2649
+ const actualDigest = (0, import_crypto3.createHash)("sha384").update(rawBytes).digest("hex");
2650
+ emitSignatureAuditEvent({
2651
+ artifactFilename: artifactFile,
2652
+ expectedDigest: sig.digest,
2653
+ actualDigest,
2654
+ phase: dep,
2655
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2656
+ source: "stackwright_pro_build_specialist_prompt"
2657
+ });
2658
+ missingDependencies.push(dep);
2659
+ artifactSections.push(
2660
+ `[${artifactFile}]:
2661
+ (integrity check failed: ECDSA-P384 signature verification failed \u2014 artifact may have been tampered with)`
2662
+ );
2663
+ continue;
2664
+ }
2665
+ }
2666
+ } catch {
2667
+ }
2265
2668
  const expectedOtter = PHASE_TO_OTTER2[dep];
2266
2669
  const artifactOtter = content["generatedBy"];
2267
2670
  const normalizedOtter = artifactOtter?.replace(/-[a-f0-9]{6}$/, "");
@@ -2278,8 +2681,11 @@ function handleBuildSpecialistPrompt(input) {
2278
2681
  (integrity check failed: artifact claims generatedBy="${artifactOtter}" but expected="${expectedOtter}")`
2279
2682
  );
2280
2683
  } else {
2281
- artifactSections.push(`[${artifactFile}]:
2282
- ${JSON.stringify(content, null, 2)}`);
2684
+ const sigStatus = signatureAvailable ? signatureVerified ? " [signature verified]" : " [signature check skipped]" : " [unsigned]";
2685
+ artifactSections.push(
2686
+ `[${artifactFile}]${sigStatus}:
2687
+ ${JSON.stringify(content, null, 2)}`
2688
+ );
2283
2689
  }
2284
2690
  } else {
2285
2691
  missingDependencies.push(dep);
@@ -2295,6 +2701,9 @@ ${JSON.stringify(content, null, 2)}`);
2295
2701
  if (artifactSections.length > 0) {
2296
2702
  parts.push("", "UPSTREAM ARTIFACTS:", "", ...artifactSections);
2297
2703
  }
2704
+ const artifactSchema = PHASE_ARTIFACT_SCHEMA[phase];
2705
+ parts.push("", "REQUIRED_ARTIFACT_SCHEMA:");
2706
+ parts.push(artifactSchema);
2298
2707
  parts.push("", "Execute using these answers and the upstream artifacts provided.");
2299
2708
  const prompt = parts.join("\n");
2300
2709
  const dependenciesSatisfied = missingDependencies.length === 0;
@@ -2336,6 +2745,187 @@ var PHASE_REQUIRED_KEYS = {
2336
2745
  dashboard: ["version", "generatedBy"],
2337
2746
  workflow: ["version", "generatedBy"]
2338
2747
  };
2748
+ var PHASE_ARTIFACT_SCHEMA = {
2749
+ designer: JSON.stringify(
2750
+ {
2751
+ version: "1.0",
2752
+ generatedBy: "stackwright-pro-designer-otter",
2753
+ application: {
2754
+ type: "<operational|data-explorer|admin|logistics|general>",
2755
+ environment: "<workstation|field|control-room|mixed>",
2756
+ density: "<compact|balanced|spacious>",
2757
+ accessibility: "<wcag-aa|section-508|none>",
2758
+ colorScheme: "<light|dark|both>"
2759
+ },
2760
+ designLanguage: {
2761
+ rationale: "<design rationale>",
2762
+ spacingScale: { base: 8, scale: [0, 4, 8, 16, 24, 32, 48, 64] },
2763
+ colorSemantics: { primary: "#1a365d", accent: "#e53e3e" },
2764
+ typography: {
2765
+ dataFont: "Inter",
2766
+ headingFont: "Inter",
2767
+ monoFont: "monospace",
2768
+ dataSizePx: 12,
2769
+ bodySizePx: 14
2770
+ },
2771
+ contrastRatio: "4.5",
2772
+ borderRadius: "4",
2773
+ shadowElevation: "standard"
2774
+ },
2775
+ themeTokenSeeds: {
2776
+ light: {
2777
+ background: "#ffffff",
2778
+ foreground: "#1a1a1a",
2779
+ primary: "#1a365d",
2780
+ surface: "#f7f7f7",
2781
+ border: "#e2e8f0"
2782
+ },
2783
+ dark: {
2784
+ background: "#1a1a1a",
2785
+ foreground: "#ffffff",
2786
+ primary: "#90cdf4",
2787
+ surface: "#2d2d2d",
2788
+ border: "#4a5568"
2789
+ }
2790
+ },
2791
+ conformsTo: null,
2792
+ operationalNotes: []
2793
+ },
2794
+ null,
2795
+ 2
2796
+ ),
2797
+ theme: JSON.stringify(
2798
+ {
2799
+ version: "1.0",
2800
+ generatedBy: "stackwright-pro-theme-otter",
2801
+ componentLibrary: "shadcn",
2802
+ colorScheme: "<light|dark|both>",
2803
+ tokens: {
2804
+ colors: { "primary-500": "#1a365d", background: "#ffffff" },
2805
+ spacing: { "spacing-1": "8px", "spacing-2": "16px" },
2806
+ typography: { "font-data": "Inter", "text-sm": "12px" },
2807
+ shape: { "radius-sm": "4px", "radius-md": "8px" },
2808
+ shadows: { "shadow-sm": "0 1px 2px rgba(0,0,0,0.08)" }
2809
+ },
2810
+ cssVariables: {
2811
+ "--background": "0 0% 100%",
2812
+ "--foreground": "222.2 84% 4.9%",
2813
+ "--primary": "222.2 47.4% 11.2%",
2814
+ "--primary-foreground": "210 40% 98%",
2815
+ "--surface": "210 40% 98%",
2816
+ "--border": "214.3 31.8% 91.4%"
2817
+ },
2818
+ dark: { "--background": "222.2 84% 4.9%", "--foreground": "210 40% 98%" }
2819
+ },
2820
+ null,
2821
+ 2
2822
+ ),
2823
+ api: JSON.stringify(
2824
+ {
2825
+ version: "1.0",
2826
+ generatedBy: "stackwright-pro-api-otter",
2827
+ entities: [
2828
+ {
2829
+ name: "Shipment",
2830
+ endpoint: "/shipments",
2831
+ method: "GET",
2832
+ revalidate: 60,
2833
+ mutationType: null
2834
+ }
2835
+ ],
2836
+ auth: { type: "bearer", header: "Authorization", envVar: "API_TOKEN" },
2837
+ baseUrl: "https://api.example.mil/v2",
2838
+ specPath: "./specs/api.yaml"
2839
+ },
2840
+ null,
2841
+ 2
2842
+ ),
2843
+ data: JSON.stringify(
2844
+ {
2845
+ version: "1.0",
2846
+ generatedBy: "stackwright-pro-data-otter",
2847
+ strategy: "<pulse-fast|isr-fast|isr-standard|isr-slow>",
2848
+ pulseMode: false,
2849
+ collections: [{ name: "equipment", revalidate: 60, pulse: false }],
2850
+ endpoints: { included: ["/equipment/**"], excluded: ["/admin/**"] },
2851
+ requiredPackages: { dependencies: {}, devPackages: {} }
2852
+ },
2853
+ null,
2854
+ 2
2855
+ ),
2856
+ workflow: JSON.stringify(
2857
+ {
2858
+ version: "1.0",
2859
+ generatedBy: "stackwright-pro-workflow-otter",
2860
+ workflowConfig: {
2861
+ id: "procurement-approval",
2862
+ route: "/procurement",
2863
+ files: ["workflows/procurement-approval.yml"],
2864
+ serviceDependencies: ["service:workflow-state"],
2865
+ warnings: []
2866
+ }
2867
+ },
2868
+ null,
2869
+ 2
2870
+ ),
2871
+ pages: JSON.stringify(
2872
+ {
2873
+ version: "1.0",
2874
+ generatedBy: "stackwright-pro-page-otter",
2875
+ pages: [
2876
+ {
2877
+ slug: "catalog",
2878
+ type: "collection_listing",
2879
+ collection: "products",
2880
+ themeApplied: true,
2881
+ authRequired: false
2882
+ },
2883
+ {
2884
+ slug: "admin",
2885
+ type: "protected",
2886
+ collection: null,
2887
+ themeApplied: true,
2888
+ authRequired: true
2889
+ }
2890
+ ]
2891
+ },
2892
+ null,
2893
+ 2
2894
+ ),
2895
+ dashboard: JSON.stringify(
2896
+ {
2897
+ version: "1.0",
2898
+ generatedBy: "stackwright-pro-dashboard-otter",
2899
+ pages: [
2900
+ {
2901
+ slug: "dashboard",
2902
+ layout: "<grid|table|mixed>",
2903
+ collections: ["equipment", "supplies"],
2904
+ mode: "<ISR|Pulse>"
2905
+ }
2906
+ ]
2907
+ },
2908
+ null,
2909
+ 2
2910
+ ),
2911
+ auth: JSON.stringify(
2912
+ {
2913
+ version: "1.0",
2914
+ generatedBy: "stackwright-pro-auth-otter",
2915
+ authConfig: {
2916
+ method: "<cac|oidc|oauth2|none>",
2917
+ provider: "<azure-ad|okta|ping|cognito \u2014 if OIDC, else null>",
2918
+ rbacRoles: ["ADMIN", "ANALYST"],
2919
+ rbacDefaultRole: "ANALYST",
2920
+ protectedRoutes: ["/dashboard/:path*", "/procurement/:path*"],
2921
+ auditEnabled: true,
2922
+ auditRetentionDays: 90
2923
+ }
2924
+ },
2925
+ null,
2926
+ 2
2927
+ )
2928
+ };
2339
2929
  function handleValidateArtifact(input) {
2340
2930
  const cwd = input._cwd ?? process.cwd();
2341
2931
  const { phase, responseText, artifact: directArtifact } = input;
@@ -2429,11 +3019,22 @@ function handleValidateArtifact(input) {
2429
3019
  }
2430
3020
  }
2431
3021
  try {
2432
- const artifactsDir = (0, import_path4.join)(cwd, ".stackwright", "artifacts");
2433
- (0, import_fs4.mkdirSync)(artifactsDir, { recursive: true });
3022
+ const artifactsDir = (0, import_path5.join)(cwd, ".stackwright", "artifacts");
3023
+ (0, import_fs5.mkdirSync)(artifactsDir, { recursive: true });
2434
3024
  const artifactFile = PHASE_ARTIFACT[phase];
2435
- const artifactPath = (0, import_path4.join)(artifactsDir, artifactFile);
2436
- safeWriteSync(artifactPath, JSON.stringify(artifact, null, 2) + "\n");
3025
+ const artifactPath = (0, import_path5.join)(artifactsDir, artifactFile);
3026
+ const serialized = JSON.stringify(artifact, null, 2) + "\n";
3027
+ const artifactBytes = Buffer.from(serialized, "utf-8");
3028
+ safeWriteSync(artifactPath, serialized);
3029
+ let signed = false;
3030
+ try {
3031
+ const { privateKey } = loadPipelineKeys(cwd);
3032
+ const sig = signArtifact(artifactBytes, privateKey);
3033
+ const signerOtter = PHASE_TO_OTTER2[phase];
3034
+ saveArtifactSignature(cwd, artifactFile, sig, signerOtter);
3035
+ signed = true;
3036
+ } catch {
3037
+ }
2437
3038
  const state = readState(cwd);
2438
3039
  if (!state.phases[phase]) state.phases[phase] = defaultPhaseStatus();
2439
3040
  const ps = state.phases[phase];
@@ -2444,7 +3045,7 @@ function handleValidateArtifact(input) {
2444
3045
  valid: true,
2445
3046
  phase,
2446
3047
  artifactPath,
2447
- summary: `Wrote ${artifactFile} (keys: ${topKeys}${Object.keys(artifact).length > 5 ? ", ..." : ""})`
3048
+ summary: `Wrote ${artifactFile} (keys: ${topKeys}${Object.keys(artifact).length > 5 ? ", ..." : ""})${signed ? " [signed]" : ""}`
2448
3049
  };
2449
3050
  return { text: JSON.stringify(result), isError: false };
2450
3051
  } catch (err) {
@@ -2468,13 +3069,13 @@ function registerPipelineTools(server2) {
2468
3069
  "stackwright_pro_set_pipeline_state",
2469
3070
  `Atomic read\u2192modify\u2192write pipeline state. ${DESC}`,
2470
3071
  {
2471
- phase: import_zod10.z.string().optional().describe('Phase to update, e.g. "designer"'),
2472
- field: import_zod10.z.enum(["questionsCollected", "answered", "executed", "artifactWritten"]).optional().describe("Boolean field to set"),
2473
- value: boolCoerce(import_zod10.z.boolean().optional()).describe(
3072
+ phase: import_zod11.z.string().optional().describe('Phase to update, e.g. "designer"'),
3073
+ field: import_zod11.z.enum(["questionsCollected", "answered", "executed", "artifactWritten"]).optional().describe("Boolean field to set"),
3074
+ value: boolCoerce(import_zod11.z.boolean().optional()).describe(
2474
3075
  'Value for the field \u2014 must be a JSON boolean (true/false), NOT the string "true"/"false"'
2475
3076
  ),
2476
- status: import_zod10.z.enum(["setup", "questions", "execution", "done"]).optional().describe("Top-level status override"),
2477
- incrementRetry: boolCoerce(import_zod10.z.boolean().optional()).describe(
3077
+ status: import_zod11.z.enum(["setup", "questions", "execution", "done"]).optional().describe("Top-level status override"),
3078
+ incrementRetry: boolCoerce(import_zod11.z.boolean().optional()).describe(
2478
3079
  "Bump retryCount by 1 \u2014 must be a JSON boolean"
2479
3080
  )
2480
3081
  },
@@ -2492,7 +3093,7 @@ function registerPipelineTools(server2) {
2492
3093
  "stackwright_pro_check_execution_ready",
2493
3094
  `Check all phases have answer files in .stackwright/answers/. If phase is provided, check only that phase. ${DESC}`,
2494
3095
  {
2495
- phase: import_zod10.z.string().optional().describe("If provided, check only this phase's readiness. Omit to check all phases.")
3096
+ phase: import_zod11.z.string().optional().describe("If provided, check only this phase's readiness. Omit to check all phases.")
2496
3097
  },
2497
3098
  async ({ phase }) => res(handleCheckExecutionReady(void 0, phase))
2498
3099
  );
@@ -2506,9 +3107,9 @@ function registerPipelineTools(server2) {
2506
3107
  "stackwright_pro_write_phase_questions",
2507
3108
  `Parse otter question-collection response \u2192 .stackwright/questions/{phase}.json. Specialists may also call this directly with a parsed questions array. ${DESC}`,
2508
3109
  {
2509
- phase: import_zod10.z.string().optional().describe('Phase name, e.g. "designer" (required for direct write)'),
2510
- responseText: import_zod10.z.string().optional().describe("Raw LLM response from QUESTION_COLLECTION_MODE"),
2511
- questions: jsonCoerce(import_zod10.z.array(import_zod10.z.any()).optional()).describe(
3110
+ phase: import_zod11.z.string().optional().describe('Phase name, e.g. "designer" (required for direct write)'),
3111
+ responseText: import_zod11.z.string().optional().describe("Raw LLM response from QUESTION_COLLECTION_MODE"),
3112
+ questions: jsonCoerce(import_zod11.z.array(import_zod11.z.any()).optional()).describe(
2512
3113
  "Questions array for direct specialist write"
2513
3114
  )
2514
3115
  },
@@ -2523,10 +3124,10 @@ function registerPipelineTools(server2) {
2523
3124
  isError: true
2524
3125
  };
2525
3126
  }
2526
- const questionsDir = (0, import_path4.join)(process.cwd(), ".stackwright", "questions");
2527
- (0, import_fs4.mkdirSync)(questionsDir, { recursive: true });
2528
- const outPath = (0, import_path4.join)(questionsDir, `${phase}.json`);
2529
- (0, import_fs4.writeFileSync)(
3127
+ const questionsDir = (0, import_path5.join)(process.cwd(), ".stackwright", "questions");
3128
+ (0, import_fs5.mkdirSync)(questionsDir, { recursive: true });
3129
+ const outPath = (0, import_path5.join)(questionsDir, `${phase}.json`);
3130
+ (0, import_fs5.writeFileSync)(
2530
3131
  outPath,
2531
3132
  JSON.stringify({ phase, questions, writtenAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)
2532
3133
  );
@@ -2551,16 +3152,16 @@ function registerPipelineTools(server2) {
2551
3152
  server2.tool(
2552
3153
  "stackwright_pro_build_specialist_prompt",
2553
3154
  `Assemble execution prompt from answers + upstream artifacts. Foreman passes verbatim. ${DESC}`,
2554
- { phase: import_zod10.z.string().describe('Phase to build prompt for, e.g. "pages"') },
3155
+ { phase: import_zod11.z.string().describe('Phase to build prompt for, e.g. "pages"') },
2555
3156
  async ({ phase }) => res(handleBuildSpecialistPrompt({ phase }))
2556
3157
  );
2557
3158
  server2.tool(
2558
3159
  "stackwright_pro_validate_artifact",
2559
3160
  `Validate and write artifact to .stackwright/artifacts/. Returns retryPrompt on failure. ${DESC}`,
2560
3161
  {
2561
- phase: import_zod10.z.string().describe('Phase that produced this artifact, e.g. "designer"'),
2562
- responseText: import_zod10.z.string().optional().describe("Raw response text from the specialist otter (Foreman-mediated path, legacy)"),
2563
- artifact: import_zod10.z.record(import_zod10.z.unknown()).optional().describe(
3162
+ phase: import_zod11.z.string().describe('Phase that produced this artifact, e.g. "designer"'),
3163
+ responseText: import_zod11.z.string().optional().describe("Raw response text from the specialist otter (Foreman-mediated path, legacy)"),
3164
+ artifact: jsonCoerce(import_zod11.z.record(import_zod11.z.string(), import_zod11.z.unknown()).optional()).describe(
2564
3165
  "Artifact object to validate and write directly (specialist direct path \u2014 skips off-script detection and JSON parsing)"
2565
3166
  )
2566
3167
  },
@@ -2576,9 +3177,9 @@ function registerPipelineTools(server2) {
2576
3177
  }
2577
3178
 
2578
3179
  // src/tools/safe-write.ts
2579
- var import_zod11 = require("zod");
2580
- var import_fs5 = require("fs");
2581
- var import_path5 = require("path");
3180
+ var import_zod12 = require("zod");
3181
+ var import_fs6 = require("fs");
3182
+ var import_path6 = require("path");
2582
3183
  var OTTER_WRITE_ALLOWLISTS = {
2583
3184
  "stackwright-pro-designer-otter": [
2584
3185
  { prefix: ".stackwright/artifacts/", suffix: ".json", description: "Design language artifact" }
@@ -2621,15 +3222,31 @@ var OTTER_WRITE_ALLOWLISTS = {
2621
3222
  };
2622
3223
  var PROTECTED_PATH_PREFIXES = [
2623
3224
  ".stackwright/pipeline-state.json",
3225
+ ".stackwright/pipeline-keys.json",
3226
+ // ephemeral signing keys
3227
+ ".stackwright/artifacts/signatures.json",
3228
+ // artifact signature manifest
2624
3229
  ".stackwright/questions/",
2625
3230
  ".stackwright/answers/"
2626
3231
  ];
3232
+ var MAX_SAFE_WRITE_BYTES_JSON = 512 * 1024;
3233
+ var MAX_SAFE_WRITE_BYTES_YAML = 256 * 1024;
3234
+ var MAX_SAFE_WRITE_BYTES_ENV = 4 * 1024;
3235
+ var MAX_SAFE_WRITE_BYTES_DEFAULT = 256 * 1024;
3236
+ function getMaxBytesForPath(filePath) {
3237
+ if (filePath.endsWith(".json")) return { limit: MAX_SAFE_WRITE_BYTES_JSON, label: "JSON" };
3238
+ if (filePath.endsWith(".yml") || filePath.endsWith(".yaml"))
3239
+ return { limit: MAX_SAFE_WRITE_BYTES_YAML, label: "YAML" };
3240
+ if (filePath === ".env" || /^\.env\.[a-zA-Z0-9]+/.test(filePath))
3241
+ return { limit: MAX_SAFE_WRITE_BYTES_ENV, label: "env" };
3242
+ return { limit: MAX_SAFE_WRITE_BYTES_DEFAULT, label: "default" };
3243
+ }
2627
3244
  function checkPathAllowed(callerOtter, filePath) {
2628
- const normalized = (0, import_path5.normalize)(filePath);
3245
+ const normalized = (0, import_path6.normalize)(filePath);
2629
3246
  if (normalized.includes("..")) {
2630
3247
  return { allowed: false, error: 'Path traversal detected: ".." segments are not allowed' };
2631
3248
  }
2632
- if ((0, import_path5.isAbsolute)(normalized)) {
3249
+ if ((0, import_path6.isAbsolute)(normalized)) {
2633
3250
  return {
2634
3251
  allowed: false,
2635
3252
  error: "Absolute paths are not allowed \u2014 use paths relative to project root"
@@ -2749,11 +3366,23 @@ function handleSafeWrite(input) {
2749
3366
  };
2750
3367
  return { text: JSON.stringify(result), isError: true };
2751
3368
  }
2752
- const normalized = (0, import_path5.normalize)(filePath);
2753
- const fullPath = (0, import_path5.join)(cwd, normalized);
2754
- if ((0, import_fs5.existsSync)(fullPath)) {
3369
+ const contentBytes = Buffer.byteLength(content, "utf-8");
3370
+ const { limit: maxBytes, label: fileTypeLabel } = getMaxBytesForPath(filePath);
3371
+ if (contentBytes > maxBytes) {
3372
+ const result = {
3373
+ success: false,
3374
+ error: `Content size ${contentBytes} bytes exceeds ${fileTypeLabel} limit of ${maxBytes} bytes (${maxBytes / 1024} KB)`,
3375
+ callerOtter,
3376
+ attemptedPath: filePath,
3377
+ allowedPaths: []
3378
+ };
3379
+ return { text: JSON.stringify(result), isError: true };
3380
+ }
3381
+ const normalized = (0, import_path6.normalize)(filePath);
3382
+ const fullPath = (0, import_path6.join)(cwd, normalized);
3383
+ if ((0, import_fs6.existsSync)(fullPath)) {
2755
3384
  try {
2756
- const stat = (0, import_fs5.lstatSync)(fullPath);
3385
+ const stat = (0, import_fs6.lstatSync)(fullPath);
2757
3386
  if (stat.isSymbolicLink()) {
2758
3387
  const result = {
2759
3388
  success: false,
@@ -2780,9 +3409,9 @@ function handleSafeWrite(input) {
2780
3409
  }
2781
3410
  try {
2782
3411
  if (createDirectories) {
2783
- (0, import_fs5.mkdirSync)((0, import_path5.dirname)(fullPath), { recursive: true });
3412
+ (0, import_fs6.mkdirSync)((0, import_path6.dirname)(fullPath), { recursive: true });
2784
3413
  }
2785
- (0, import_fs5.writeFileSync)(fullPath, content, { encoding: "utf-8" });
3414
+ (0, import_fs6.writeFileSync)(fullPath, content, { encoding: "utf-8" });
2786
3415
  const result = {
2787
3416
  success: true,
2788
3417
  path: normalized,
@@ -2808,10 +3437,10 @@ function registerSafeWriteTools(server2) {
2808
3437
  "stackwright_pro_safe_write",
2809
3438
  DESC,
2810
3439
  {
2811
- callerOtter: import_zod11.z.string().describe('The otter agent name requesting the write, e.g. "stackwright-pro-page-otter"'),
2812
- filePath: import_zod11.z.string().describe('Relative path from project root, e.g. "pages/dashboard/content.yml"'),
2813
- content: import_zod11.z.string().describe("File content to write"),
2814
- createDirectories: boolCoerce(import_zod11.z.boolean().optional().default(true)).describe(
3440
+ callerOtter: import_zod12.z.string().describe('The otter agent name requesting the write, e.g. "stackwright-pro-page-otter"'),
3441
+ filePath: import_zod12.z.string().describe('Relative path from project root, e.g. "pages/dashboard/content.yml"'),
3442
+ content: import_zod12.z.string().describe("File content to write"),
3443
+ createDirectories: boolCoerce(import_zod12.z.boolean().optional().default(true)).describe(
2815
3444
  "Create parent directories if they don't exist. Default: true"
2816
3445
  )
2817
3446
  },
@@ -2828,9 +3457,9 @@ function registerSafeWriteTools(server2) {
2828
3457
  }
2829
3458
 
2830
3459
  // src/tools/auth.ts
2831
- var import_zod12 = require("zod");
2832
- var import_fs6 = require("fs");
2833
- var import_path6 = require("path");
3460
+ var import_zod13 = require("zod");
3461
+ var import_fs7 = require("fs");
3462
+ var import_path7 = require("path");
2834
3463
  function buildHierarchy(roles) {
2835
3464
  const h = {};
2836
3465
  for (let i = 0; i < roles.length - 1; i++) {
@@ -3092,7 +3721,7 @@ async function configureAuthHandler(params, cwd) {
3092
3721
  auditRetentionDays,
3093
3722
  protectedRoutes
3094
3723
  );
3095
- (0, import_fs6.writeFileSync)((0, import_path6.join)(cwd, "middleware.ts"), middlewareContent, "utf8");
3724
+ (0, import_fs7.writeFileSync)((0, import_path7.join)(cwd, "middleware.ts"), middlewareContent, "utf8");
3096
3725
  filesWritten.push("middleware.ts");
3097
3726
  } catch (err) {
3098
3727
  const msg = err instanceof Error ? err.message : String(err);
@@ -3108,12 +3737,12 @@ async function configureAuthHandler(params, cwd) {
3108
3737
  }
3109
3738
  try {
3110
3739
  const envBlock = generateEnvBlock(method, params);
3111
- const envPath = (0, import_path6.join)(cwd, ".env.example");
3112
- if ((0, import_fs6.existsSync)(envPath)) {
3113
- const existing = (0, import_fs6.readFileSync)(envPath, "utf8");
3114
- (0, import_fs6.writeFileSync)(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
3740
+ const envPath = (0, import_path7.join)(cwd, ".env.example");
3741
+ if ((0, import_fs7.existsSync)(envPath)) {
3742
+ const existing = (0, import_fs7.readFileSync)(envPath, "utf8");
3743
+ (0, import_fs7.writeFileSync)(envPath, existing.trimEnd() + "\n\n" + envBlock, "utf8");
3115
3744
  } else {
3116
- (0, import_fs6.writeFileSync)(envPath, envBlock, "utf8");
3745
+ (0, import_fs7.writeFileSync)(envPath, envBlock, "utf8");
3117
3746
  }
3118
3747
  filesWritten.push(".env.example");
3119
3748
  } catch (err) {
@@ -3139,12 +3768,12 @@ async function configureAuthHandler(params, cwd) {
3139
3768
  auditRetentionDays,
3140
3769
  protectedRoutes
3141
3770
  );
3142
- const ymlPath = (0, import_path6.join)(cwd, "stackwright.yml");
3143
- if (!(0, import_fs6.existsSync)(ymlPath)) {
3144
- (0, import_fs6.writeFileSync)(ymlPath, authYaml, "utf8");
3771
+ const ymlPath = (0, import_path7.join)(cwd, "stackwright.yml");
3772
+ if (!(0, import_fs7.existsSync)(ymlPath)) {
3773
+ (0, import_fs7.writeFileSync)(ymlPath, authYaml, "utf8");
3145
3774
  } else {
3146
- const existing = (0, import_fs6.readFileSync)(ymlPath, "utf8");
3147
- (0, import_fs6.writeFileSync)(ymlPath, upsertAuthBlock(existing, authYaml), "utf8");
3775
+ const existing = (0, import_fs7.readFileSync)(ymlPath, "utf8");
3776
+ (0, import_fs7.writeFileSync)(ymlPath, upsertAuthBlock(existing, authYaml), "utf8");
3148
3777
  }
3149
3778
  filesWritten.push("stackwright.yml");
3150
3779
  } catch (err) {
@@ -3183,35 +3812,35 @@ function registerAuthTools(server2) {
3183
3812
  "stackwright_pro_configure_auth",
3184
3813
  "Generate authentication middleware and configuration for a Next.js Stackwright application. Writes `middleware.ts` from a secure template, appends/updates the `auth:` section in `stackwright.yml`, and generates `.env.example` with required environment variables. \u26A0\uFE0F For CAC/PKI: generated `middleware.ts` carries a SECURITY REVIEW REQUIRED comment \u2014 certificate chain validation must be verified by a DoD security officer before production deployment. This is the ONLY approved path to generating `middleware.ts`. Never write TypeScript auth files directly.",
3185
3814
  {
3186
- method: import_zod12.z.enum(["cac", "oidc", "oauth2", "none"]),
3187
- provider: import_zod12.z.enum(["azure-ad", "okta", "ping", "cognito", "custom"]).optional(),
3815
+ method: import_zod13.z.enum(["cac", "oidc", "oauth2", "none"]),
3816
+ provider: import_zod13.z.enum(["azure-ad", "okta", "ping", "cognito", "custom"]).optional(),
3188
3817
  // CAC
3189
- cacCaBundle: import_zod12.z.string().optional(),
3190
- cacEdipiLookup: import_zod12.z.string().optional(),
3191
- cacOcspEndpoint: import_zod12.z.string().optional(),
3192
- cacCertHeader: import_zod12.z.string().optional(),
3818
+ cacCaBundle: import_zod13.z.string().optional(),
3819
+ cacEdipiLookup: import_zod13.z.string().optional(),
3820
+ cacOcspEndpoint: import_zod13.z.string().optional(),
3821
+ cacCertHeader: import_zod13.z.string().optional(),
3193
3822
  // OIDC
3194
- oidcDiscoveryUrl: import_zod12.z.string().optional(),
3195
- oidcClientId: import_zod12.z.string().optional(),
3196
- oidcClientSecret: import_zod12.z.string().optional(),
3197
- oidcScopes: import_zod12.z.string().optional(),
3198
- oidcRoleClaim: import_zod12.z.string().optional(),
3823
+ oidcDiscoveryUrl: import_zod13.z.string().optional(),
3824
+ oidcClientId: import_zod13.z.string().optional(),
3825
+ oidcClientSecret: import_zod13.z.string().optional(),
3826
+ oidcScopes: import_zod13.z.string().optional(),
3827
+ oidcRoleClaim: import_zod13.z.string().optional(),
3199
3828
  // OAuth2
3200
- oauth2AuthUrl: import_zod12.z.string().optional(),
3201
- oauth2TokenUrl: import_zod12.z.string().optional(),
3202
- oauth2ClientId: import_zod12.z.string().optional(),
3203
- oauth2ClientSecret: import_zod12.z.string().optional(),
3204
- oauth2Scopes: import_zod12.z.string().optional(),
3829
+ oauth2AuthUrl: import_zod13.z.string().optional(),
3830
+ oauth2TokenUrl: import_zod13.z.string().optional(),
3831
+ oauth2ClientId: import_zod13.z.string().optional(),
3832
+ oauth2ClientSecret: import_zod13.z.string().optional(),
3833
+ oauth2Scopes: import_zod13.z.string().optional(),
3205
3834
  // RBAC
3206
- rbacRoles: jsonCoerce(import_zod12.z.array(import_zod12.z.string()).optional()),
3207
- rbacDefaultRole: import_zod12.z.string().optional(),
3835
+ rbacRoles: jsonCoerce(import_zod13.z.array(import_zod13.z.string()).optional()),
3836
+ rbacDefaultRole: import_zod13.z.string().optional(),
3208
3837
  // Audit
3209
- auditEnabled: boolCoerce(import_zod12.z.boolean().optional()),
3210
- auditRetentionDays: numCoerce(import_zod12.z.number().int().positive().optional()),
3838
+ auditEnabled: boolCoerce(import_zod13.z.boolean().optional()),
3839
+ auditRetentionDays: numCoerce(import_zod13.z.number().int().positive().optional()),
3211
3840
  // Routes
3212
- protectedRoutes: jsonCoerce(import_zod12.z.array(import_zod12.z.string()).optional()),
3841
+ protectedRoutes: jsonCoerce(import_zod13.z.array(import_zod13.z.string()).optional()),
3213
3842
  // Injection for tests
3214
- _cwd: import_zod12.z.string().optional()
3843
+ _cwd: import_zod13.z.string().optional()
3215
3844
  },
3216
3845
  async (params) => {
3217
3846
  const cwd = params._cwd ?? process.cwd();
@@ -3221,13 +3850,13 @@ function registerAuthTools(server2) {
3221
3850
  }
3222
3851
 
3223
3852
  // src/integrity.ts
3224
- var import_crypto2 = require("crypto");
3225
- var import_fs7 = require("fs");
3226
- var import_path7 = require("path");
3853
+ var import_crypto4 = require("crypto");
3854
+ var import_fs8 = require("fs");
3855
+ var import_path8 = require("path");
3227
3856
  var _checksums = /* @__PURE__ */ new Map([
3228
3857
  [
3229
3858
  "stackwright-pro-api-otter.json",
3230
- "1fd28747ff43121533d40d6446f2d2670d6247afb04e3025cbbcb9ace0e7d1e2"
3859
+ "ed667124af3f025e090c0e65d0a86f0ef08fea06c0029b8fd0edf6df33df9f9c"
3231
3860
  ],
3232
3861
  [
3233
3862
  "stackwright-pro-auth-otter.json",
@@ -3235,7 +3864,7 @@ var _checksums = /* @__PURE__ */ new Map([
3235
3864
  ],
3236
3865
  [
3237
3866
  "stackwright-pro-dashboard-otter.json",
3238
- "a9e50f26e8b2b687910685f15104b4e76a74ad2e1e5a6021237e1eeb1cbde2ae"
3867
+ "5e930b4092b9002e3c1a413b36418e49c865199af12a546890ccf7f9e56a5593"
3239
3868
  ],
3240
3869
  [
3241
3870
  "stackwright-pro-data-otter.json",
@@ -3243,11 +3872,11 @@ var _checksums = /* @__PURE__ */ new Map([
3243
3872
  ],
3244
3873
  [
3245
3874
  "stackwright-pro-designer-otter.json",
3246
- "41c5b6b9f1f0f6eb0851e473f9d7d6ebd6a7e00dafd5cdeb8a8b12b0b756e245"
3875
+ "e80d4e7bab87d8647debadb238a58aac498ec5074ff25b21abd3b13ff778bf71"
3247
3876
  ],
3248
3877
  [
3249
3878
  "stackwright-pro-foreman-otter.json",
3250
- "7c8af9ce5b157ad3030f0255218a6ea923df18a36fe44db9bd5f04897434fc05"
3879
+ "02dd2485562361f2f3cfd998981349020d7599dcd2d969bb022f9f6d537f3517"
3251
3880
  ],
3252
3881
  [
3253
3882
  "stackwright-pro-page-otter.json",
@@ -3255,11 +3884,11 @@ var _checksums = /* @__PURE__ */ new Map([
3255
3884
  ],
3256
3885
  [
3257
3886
  "stackwright-pro-theme-otter.json",
3258
- "3a37d4bd696f142c4a4278ef653984fca4b776caa610182c2cb82f6732ef9b62"
3887
+ "d3a15871b71a466c12c4711fe37cbb018c768cb99eff15c40dbbc7061d4e966b"
3259
3888
  ],
3260
3889
  [
3261
3890
  "stackwright-pro-workflow-otter.json",
3262
- "fa2bae06e0f9e6b844008adc933d24b6a210708c0812ce068fc43733ee98b98e"
3891
+ "2ce1bcbb5c45dbb214499ea08c7175f8b743b51b8cb2ad539faf7df11edcf88a"
3263
3892
  ]
3264
3893
  ]);
3265
3894
  Object.freeze(_checksums);
@@ -3274,21 +3903,21 @@ for (const [name, digest] of CANONICAL_CHECKSUMS) {
3274
3903
  }
3275
3904
  var MAX_OTTER_BYTES = 1 * 1024 * 1024;
3276
3905
  function computeSha256(data) {
3277
- return (0, import_crypto2.createHash)("sha256").update(data).digest("hex");
3906
+ return (0, import_crypto4.createHash)("sha256").update(data).digest("hex");
3278
3907
  }
3279
3908
  function safeEqual(a, b) {
3280
3909
  if (a.length !== b.length) return false;
3281
- return (0, import_crypto2.timingSafeEqual)(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
3910
+ return (0, import_crypto4.timingSafeEqual)(Buffer.from(a, "utf8"), Buffer.from(b, "utf8"));
3282
3911
  }
3283
3912
  function verifyOtterFile(filePath) {
3284
- const filename = (0, import_path7.basename)(filePath);
3913
+ const filename = (0, import_path8.basename)(filePath);
3285
3914
  const expected = CANONICAL_CHECKSUMS.get(filename);
3286
3915
  if (expected === void 0) {
3287
3916
  return { verified: false, filename, error: `Unknown otter file: not in canonical set` };
3288
3917
  }
3289
3918
  let stat;
3290
3919
  try {
3291
- stat = (0, import_fs7.lstatSync)(filePath);
3920
+ stat = (0, import_fs8.lstatSync)(filePath);
3292
3921
  } catch (err) {
3293
3922
  const msg = err instanceof Error ? err.message : String(err);
3294
3923
  return { verified: false, filename, error: `Cannot stat file: ${msg}` };
@@ -3306,7 +3935,7 @@ function verifyOtterFile(filePath) {
3306
3935
  }
3307
3936
  let raw;
3308
3937
  try {
3309
- raw = (0, import_fs7.readFileSync)(filePath);
3938
+ raw = (0, import_fs8.readFileSync)(filePath);
3310
3939
  } catch (err) {
3311
3940
  const msg = err instanceof Error ? err.message : String(err);
3312
3941
  return { verified: false, filename, error: `Cannot read file: ${msg}` };
@@ -3339,12 +3968,24 @@ function verifyOtterFile(filePath) {
3339
3968
  return { verified: true, filename };
3340
3969
  }
3341
3970
  function verifyAllOtters(otterDir) {
3971
+ if (/(?:^|[/\\])\.\.(?:[/\\]|$)/.test(otterDir) || otterDir.includes("..")) {
3972
+ return {
3973
+ verified: [],
3974
+ failed: [
3975
+ {
3976
+ filename: "<directory>",
3977
+ error: `Security: path traversal sequence detected in otter directory parameter`
3978
+ }
3979
+ ],
3980
+ unknown: []
3981
+ };
3982
+ }
3342
3983
  const verified = [];
3343
3984
  const failed = [];
3344
3985
  const unknown = [];
3345
3986
  let entries;
3346
3987
  try {
3347
- entries = (0, import_fs7.readdirSync)(otterDir);
3988
+ entries = (0, import_fs8.readdirSync)(otterDir);
3348
3989
  } catch (err) {
3349
3990
  const msg = err instanceof Error ? err.message : String(err);
3350
3991
  return {
@@ -3355,9 +3996,9 @@ function verifyAllOtters(otterDir) {
3355
3996
  }
3356
3997
  const otterFiles = entries.filter((f) => f.endsWith("-otter.json"));
3357
3998
  for (const filename of otterFiles) {
3358
- const filePath = (0, import_path7.join)(otterDir, filename);
3999
+ const filePath = (0, import_path8.join)(otterDir, filename);
3359
4000
  try {
3360
- if ((0, import_fs7.lstatSync)(filePath).isSymbolicLink()) {
4001
+ if ((0, import_fs8.lstatSync)(filePath).isSymbolicLink()) {
3361
4002
  failed.push({ filename, error: "Skipped: symlink" });
3362
4003
  continue;
3363
4004
  }
@@ -3383,15 +4024,30 @@ var DEFAULT_SEARCH_PATHS = ["node_modules/@stackwright-pro/otters/src/", "packag
3383
4024
  function resolveOtterDir() {
3384
4025
  const cwd = process.cwd();
3385
4026
  for (const relative of DEFAULT_SEARCH_PATHS) {
3386
- const candidate = (0, import_path7.join)(cwd, relative);
4027
+ const candidate = (0, import_path8.join)(cwd, relative);
3387
4028
  try {
3388
- (0, import_fs7.lstatSync)(candidate);
4029
+ (0, import_fs8.lstatSync)(candidate);
3389
4030
  return candidate;
3390
4031
  } catch {
3391
4032
  }
3392
4033
  }
3393
4034
  return null;
3394
4035
  }
4036
+ function emitIntegrityAuditEvent(params) {
4037
+ const record = JSON.stringify({
4038
+ level: "AUDIT",
4039
+ event: "INTEGRITY_FAIL",
4040
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
4041
+ source: "stackwright_pro_verify_otter_integrity",
4042
+ otterDir: params.otterDir,
4043
+ failedCount: params.failed.length,
4044
+ unknownCount: params.unknown.length,
4045
+ failures: params.failed,
4046
+ unknown: params.unknown
4047
+ });
4048
+ process.stderr.write(`INTEGRITY_FAIL ${record}
4049
+ `);
4050
+ }
3395
4051
  function registerIntegrityTools(server2) {
3396
4052
  server2.tool(
3397
4053
  "stackwright_pro_verify_otter_integrity",
@@ -3415,6 +4071,13 @@ function registerIntegrityTools(server2) {
3415
4071
  }
3416
4072
  const result = verifyAllOtters(resolved);
3417
4073
  const allGood = result.failed.length === 0 && result.unknown.length === 0;
4074
+ if (!allGood) {
4075
+ emitIntegrityAuditEvent({
4076
+ otterDir: resolved,
4077
+ failed: result.failed,
4078
+ unknown: result.unknown
4079
+ });
4080
+ }
3418
4081
  return {
3419
4082
  content: [
3420
4083
  {
@@ -3428,25 +4091,27 @@ function registerIntegrityTools(server2) {
3428
4091
  verified: result.verified,
3429
4092
  failed: result.failed,
3430
4093
  unknown: result.unknown,
3431
- warning: result.failed.length > 0 ? "SHA-256 mismatches detected (non-blocking). PKI-signed manifest support coming soon." : void 0
4094
+ ...allGood ? {} : {
4095
+ error: "INTEGRITY CHECK FAILED: SHA-256 mismatch detected in otter agent definitions. Do not proceed \u2014 otter files may have been tampered with."
4096
+ }
3432
4097
  })
3433
4098
  }
3434
4099
  ],
3435
- isError: false
4100
+ isError: !allGood
3436
4101
  };
3437
4102
  }
3438
4103
  );
3439
4104
  }
3440
4105
 
3441
4106
  // src/tools/domain.ts
3442
- var import_zod13 = require("zod");
3443
- var import_fs8 = require("fs");
3444
- var import_path8 = require("path");
4107
+ var import_zod14 = require("zod");
4108
+ var import_fs9 = require("fs");
4109
+ var import_path9 = require("path");
3445
4110
  function handleListCollections(input) {
3446
4111
  const cwd = input._cwd ?? process.cwd();
3447
4112
  const sources = [
3448
4113
  {
3449
- path: (0, import_path8.join)(cwd, ".stackwright", "artifacts", "data-config.json"),
4114
+ path: (0, import_path9.join)(cwd, ".stackwright", "artifacts", "data-config.json"),
3450
4115
  source: "data-config.json",
3451
4116
  parse: (raw) => {
3452
4117
  const parsed = JSON.parse(raw);
@@ -3457,15 +4122,15 @@ function handleListCollections(input) {
3457
4122
  }
3458
4123
  },
3459
4124
  {
3460
- path: (0, import_path8.join)(cwd, "stackwright.yml"),
4125
+ path: (0, import_path9.join)(cwd, "stackwright.yml"),
3461
4126
  source: "stackwright.yml",
3462
4127
  parse: extractCollectionsFromYaml
3463
4128
  }
3464
4129
  ];
3465
4130
  for (const { path: path3, source, parse } of sources) {
3466
- if (!(0, import_fs8.existsSync)(path3)) continue;
4131
+ if (!(0, import_fs9.existsSync)(path3)) continue;
3467
4132
  try {
3468
- const collections = parse((0, import_fs8.readFileSync)(path3, "utf8"));
4133
+ const collections = parse((0, import_fs9.readFileSync)(path3, "utf8"));
3469
4134
  return {
3470
4135
  text: JSON.stringify({ collections, source, collectionCount: collections.length }),
3471
4136
  isError: false
@@ -3616,8 +4281,8 @@ function handleValidateWorkflow(input) {
3616
4281
  if (input.workflow && Object.keys(input.workflow).length > 0) {
3617
4282
  raw = input.workflow;
3618
4283
  } else {
3619
- const artifactPath = (0, import_path8.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
3620
- if (!(0, import_fs8.existsSync)(artifactPath)) {
4284
+ const artifactPath = (0, import_path9.join)(cwd, ".stackwright", "artifacts", "workflow-config.json");
4285
+ if (!(0, import_fs9.existsSync)(artifactPath)) {
3621
4286
  return fail([
3622
4287
  {
3623
4288
  code: "NO_WORKFLOW",
@@ -3626,7 +4291,7 @@ function handleValidateWorkflow(input) {
3626
4291
  ]);
3627
4292
  }
3628
4293
  try {
3629
- raw = JSON.parse((0, import_fs8.readFileSync)(artifactPath, "utf8"));
4294
+ raw = JSON.parse((0, import_fs9.readFileSync)(artifactPath, "utf8"));
3630
4295
  } catch (err) {
3631
4296
  return fail([{ code: "INVALID_JSON", message: `Failed to parse workflow artifact: ${err}` }]);
3632
4297
  }
@@ -3825,7 +4490,7 @@ function registerDomainTools(server2) {
3825
4490
  "stackwright_pro_resolve_data_strategy",
3826
4491
  "Look up the data freshness strategy configuration from the user's answer. Returns mechanism, revalidation seconds, required packages, and the exact MCP tool call to make. Replaces the strategy table in the data-otter prompt.",
3827
4492
  {
3828
- strategy: import_zod13.z.string().describe(
4493
+ strategy: import_zod14.z.string().describe(
3829
4494
  'The data-1 answer value: "pulse-fast", "isr-fast", "isr-standard", or "isr-slow"'
3830
4495
  )
3831
4496
  },
@@ -3835,7 +4500,7 @@ function registerDomainTools(server2) {
3835
4500
  "stackwright_pro_validate_workflow",
3836
4501
  "Validate a workflow definition against the Stackwright workflow schema. Checks step ID uniqueness, transition targets, terminal state existence, and service references. Call this after the workflow otter produces output.",
3837
4502
  {
3838
- workflow: jsonCoerce(import_zod13.z.record(import_zod13.z.string(), import_zod13.z.unknown()).optional()).describe(
4503
+ workflow: jsonCoerce(import_zod14.z.record(import_zod14.z.string(), import_zod14.z.unknown()).optional()).describe(
3839
4504
  "Parsed workflow object. If omitted, reads from .stackwright/artifacts/workflow-config.json"
3840
4505
  )
3841
4506
  },
@@ -3844,7 +4509,7 @@ function registerDomainTools(server2) {
3844
4509
  }
3845
4510
 
3846
4511
  // src/tools/type-schemas.ts
3847
- var import_zod14 = require("zod");
4512
+ var import_zod15 = require("zod");
3848
4513
  function buildTypeSchemaSummary() {
3849
4514
  return {
3850
4515
  version: "1.0",
@@ -3921,7 +4586,7 @@ function registerTypeSchemasTool(server2) {
3921
4586
  "stackwright_pro_get_type_schemas",
3922
4587
  "Returns a structured summary of all canonical @stackwright-pro/types schemas, organized by domain. Use this to determine which otter owns a given schema and what artifact key to expect.",
3923
4588
  {
3924
- format: import_zod14.z.enum(["full", "domains-only"]).optional().default("full").describe("full = complete summary with all fields; domains-only = just domain names")
4589
+ format: import_zod15.z.enum(["full", "domains-only"]).optional().default("full").describe("full = complete summary with all fields; domains-only = just domain names")
3925
4590
  },
3926
4591
  async ({ format }) => {
3927
4592
  const summary = buildTypeSchemaSummary();
@@ -3939,13 +4604,13 @@ var package_default = {
3939
4604
  "@stackwright-pro/types": "workspace:*",
3940
4605
  "@modelcontextprotocol/sdk": "^1.10.0",
3941
4606
  "@stackwright-pro/cli-data-explorer": "workspace:*",
3942
- zod: "^4.3.6"
4607
+ zod: "^4.4.3"
3943
4608
  },
3944
4609
  devDependencies: {
3945
- "@types/node": "^24.1.0",
3946
- tsup: "^8.5.0",
3947
- typescript: "^5.8.3",
3948
- vitest: "^4.0.18"
4610
+ "@types/node": "catalog:",
4611
+ tsup: "catalog:",
4612
+ typescript: "catalog:",
4613
+ vitest: "catalog:"
3949
4614
  },
3950
4615
  scripts: {
3951
4616
  prepublishOnly: "node scripts/verify-integrity-sync.js",
@@ -3956,9 +4621,9 @@ var package_default = {
3956
4621
  "test:coverage": "vitest run --coverage"
3957
4622
  },
3958
4623
  name: "@stackwright-pro/mcp",
3959
- version: "0.2.0-alpha.32",
4624
+ version: "0.2.0-alpha.48",
3960
4625
  description: "MCP tools for Stackwright Pro - Data Explorer, Security, ISR, and Dashboard generation",
3961
- license: "PROPRIETARY",
4626
+ license: "SEE LICENSE IN LICENSE",
3962
4627
  main: "./dist/server.js",
3963
4628
  bin: {
3964
4629
  "stackwright-pro-mcp": "./dist/server.js"
@@ -4007,6 +4672,7 @@ registerPipelineTools(server);
4007
4672
  registerSafeWriteTools(server);
4008
4673
  registerAuthTools(server);
4009
4674
  registerIntegrityTools(server);
4675
+ registerArtifactSigningTools(server);
4010
4676
  registerDomainTools(server);
4011
4677
  registerTypeSchemasTool(server);
4012
4678
  async function main() {