@stackwright-pro/mcp 0.2.0-alpha.1 → 0.2.0-alpha.100

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/integrity.ts"],"sourcesContent":["/**\n * Otter Integrity Verification\n * ============================\n * Protects the Pro Otter Raft from disk-based prompt injection / jailbreak attacks.\n *\n * TypeScript port of python/src/stackwright_pro/raft/integrity.py — this lets\n * the MCP package verify otter files without a Python dependency.\n *\n * Certificate-pinned canonical checksums — hardcoded in the MCP package.\n *\n * These are NOT read from disk. An attacker who modifies otter JSON files\n * in @stackwright-pro/otters cannot also modify these constants without\n * compromising the separately-published @stackwright-pro/mcp package.\n *\n * To update: node scripts/sync-mcp-checksums.cjs\n * (reads from packages/otters/src/checksums.json, writes this file)\n */\nimport { createHash, timingSafeEqual } from 'crypto';\nimport { readFileSync, readdirSync, lstatSync } from 'fs';\nimport { join, basename } from 'path';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\n// ---------------------------------------------------------------------------\n// Certificate-pinned canonical checksums — frozen Map, immutable by design.\n// DO NOT read these from disk — that would defeat the entire purpose.\n// Object.freeze prevents property mutation at runtime; ReadonlyMap prevents\n// .set() / .delete() at compile time (belt-and-suspenders).\n// ---------------------------------------------------------------------------\n\nconst _checksums = new Map<string, string>([\n [\n 'stackwright-pro-api-otter.json',\n '175cca39127beea84ed8f1b264b8cd5cf58733db050d32328c039cca0cf0b981',\n ],\n [\n 'stackwright-pro-auth-otter.json',\n '1a21d9f23e250f23291124307e5a2a32af5a716566319a3290372dae3d02e637',\n ],\n [\n 'stackwright-pro-dashboard-otter.json',\n '6918b51d1007c69e635f252b20b82774fa7c942fa1d2399769c14f8d1df4df70',\n ],\n [\n 'stackwright-pro-data-otter.json',\n '1ad3ed99bbe7b550f654c679a8c0ea3363b2c52031042cd177c6e5f9e1c50a21',\n ],\n [\n 'stackwright-pro-designer-otter.json',\n '69a6c09fbb54f971c48eae7fd52faa63cf79be2923e435aca9ff802e8d541d0f',\n ],\n [\n 'stackwright-pro-domain-expert-otter.json',\n '14d77cade020f44d1b5a2b406e2599501f3466ee90ca4248500a441d419bc59c',\n ],\n [\n 'stackwright-pro-foreman-otter.json',\n '9191e176241c15f3d77180e1b843cc3a517b708737b5bd3cda7db62f2d6675d1',\n ],\n [\n 'stackwright-pro-form-wizard-otter.json',\n '7f6f94192f26face57dc8b2e22e0a49d23ceeeb356ca4ebde05492f1b25e3c47',\n ],\n [\n 'stackwright-pro-geo-otter.json',\n 'fc3d18e02a6147d95d3dd9093ce74c0e8fffaecc2f9ecdbd19c163a80129d264',\n ],\n [\n 'stackwright-pro-page-otter.json',\n '879e8ff4aa645bac2c63dc962cad1a549876983c257aec756e95293c3b22ce7e',\n ],\n [\n 'stackwright-pro-polish-otter.json',\n '9d0c53651b60507607efc0d9a0f9d166bf5ae8571f1645682e0b151d42c276ab',\n ],\n [\n 'stackwright-pro-qa-otter.json',\n '8e6007e18687b6b023f2c40f5517937a857da3f31e4e423cc308493ed601793c',\n ],\n [\n 'stackwright-pro-scaffold-otter.json',\n '5756ea77178334684b4df1d9d3395bca62cedb4991ab07dc4cf61aa7e540ea14',\n ],\n [\n 'stackwright-pro-theme-otter.json',\n 'b0eccc1945e30b80a0f9a3209c5428e8e32cd09063bd9b2ef7ecb891343774f2',\n ],\n [\n 'stackwright-services-otter.json',\n '8997bdd9a6fd3e6e02563578a1649480bc21acf919534ae41f73e70ade7cf500',\n ],\n]);\nObject.freeze(_checksums);\nconst CANONICAL_CHECKSUMS: ReadonlyMap<string, string> = _checksums;\n\n// ---------------------------------------------------------------------------\n// Import-time format validation — malformed constants are a packaging bug,\n// not a runtime surprise. Fail fast, fail loud.\n// ---------------------------------------------------------------------------\n\nconst SHA256_HEX_RE = /^[0-9a-f]{64}$/;\n\nfor (const [name, digest] of CANONICAL_CHECKSUMS) {\n if (!SHA256_HEX_RE.test(digest)) {\n throw new Error(\n `Malformed SHA-256 in CANONICAL_CHECKSUMS for \"${name}\": ` +\n `expected 64 hex chars, got ${digest.length}: \"${digest}\"`\n );\n }\n}\n\n// Exported for test assertions — derive counts from here, not hardcoded numbers.\nexport const CANONICAL_OTTERS: readonly string[] = Object.freeze([...CANONICAL_CHECKSUMS.keys()]);\n\n// 1 MB — generous headroom for agent definitions; anything larger is suspicious.\nconst MAX_OTTER_BYTES = 1 * 1024 * 1024;\n\n// ---------------------------------------------------------------------------\n// Core functions (exported for direct testing — no MCP server needed)\n// ---------------------------------------------------------------------------\n\n/** Compute the hex-encoded SHA-256 digest of raw bytes. Pure, no I/O. */\nexport function computeSha256(data: Buffer): string {\n return createHash('sha256').update(data).digest('hex');\n}\n\n/** Constant-time comparison of two hex digest strings. */\nfunction safeEqual(a: string, b: string): boolean {\n if (a.length !== b.length) return false;\n return timingSafeEqual(Buffer.from(a, 'utf8'), Buffer.from(b, 'utf8'));\n}\n\n// ---------------------------------------------------------------------------\n// Single-file verification\n// ---------------------------------------------------------------------------\n\nexport interface VerifyOtterFileResult {\n verified: boolean;\n filename: string;\n error?: string;\n}\n\n/**\n * Read a single otter JSON file, check its size, compute its SHA-256,\n * and constant-time compare against the canonical checksum.\n *\n * Single read → hash → decode. No TOCTOU window.\n */\nexport function verifyOtterFile(filePath: string): VerifyOtterFileResult {\n const filename = basename(filePath);\n\n // Fast-fail on unknown filenames before any I/O\n const expected = CANONICAL_CHECKSUMS.get(filename);\n if (expected === undefined) {\n return { verified: false, filename, error: `Unknown otter file: not in canonical set` };\n }\n\n // Symlink guard — refuse to follow symlinks (prevents symlink-based swaps)\n let stat: ReturnType<typeof lstatSync>;\n try {\n stat = lstatSync(filePath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { verified: false, filename, error: `Cannot stat file: ${msg}` };\n }\n\n if (stat.isSymbolicLink()) {\n return { verified: false, filename, error: 'Refusing to verify symlink' };\n }\n\n // Stat-based size pre-check — don't materialise oversized payloads\n const size = stat.size;\n\n if (size > MAX_OTTER_BYTES) {\n return {\n verified: false,\n filename,\n error: `File exceeds size limit (${MAX_OTTER_BYTES.toLocaleString()} bytes, got ${size.toLocaleString()})`,\n };\n }\n\n // Single read — used for hashing and UTF-8 validation (zero TOCTOU window)\n let raw: Buffer;\n try {\n raw = readFileSync(filePath);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return { verified: false, filename, error: `Cannot read file: ${msg}` };\n }\n\n // Belt-and-suspenders: re-check length after read in case of a race\n if (raw.length > MAX_OTTER_BYTES) {\n return {\n verified: false,\n filename,\n error: `File exceeds size limit after read (${MAX_OTTER_BYTES.toLocaleString()} bytes, got ${raw.length.toLocaleString()})`,\n };\n }\n\n // Hash the raw bytes\n const actual = computeSha256(raw);\n\n // Constant-time comparison prevents timing-oracle attacks\n if (!safeEqual(actual, expected)) {\n return {\n verified: false,\n filename,\n error: `SHA-256 mismatch: expected ${expected.substring(0, 8)}…, got ${actual.substring(0, 8)}…`,\n };\n }\n\n // UTF-8 validation — binary injection guard\n try {\n const decoder = new TextDecoder('utf-8', { fatal: true });\n decoder.decode(raw);\n } catch {\n return {\n verified: false,\n filename,\n error: 'File is not valid UTF-8 — may be corrupted or contain binary injection',\n };\n }\n\n return { verified: true, filename };\n}\n\n// ---------------------------------------------------------------------------\n// Directory-level verification\n// ---------------------------------------------------------------------------\n\nexport interface VerifyAllOttersResult {\n verified: string[];\n failed: Array<{ filename: string; error: string }>;\n unknown: string[];\n}\n\n/**\n * Scan a directory for `*-otter.json` files, verify each one against\n * canonical checksums. Returns lists of verified, failed, and unknown files.\n */\nexport function verifyAllOtters(otterDir: string): VerifyAllOttersResult {\n // ---------------------------------------------------------------------------\n // Path traversal guard — reject any input containing \"..\" sequences before\n // any I/O. An attacker controlling this parameter could otherwise scan\n // directories outside the expected otter install locations.\n // ---------------------------------------------------------------------------\n if (/(?:^|[/\\\\])\\.\\.(?:[/\\\\]|$)/.test(otterDir) || otterDir.includes('..')) {\n return {\n verified: [],\n failed: [\n {\n filename: '<directory>',\n error: `Security: path traversal sequence detected in otter directory parameter`,\n },\n ],\n unknown: [],\n };\n }\n\n const verified: string[] = [];\n const failed: Array<{ filename: string; error: string }> = [];\n const unknown: string[] = [];\n\n let entries: string[];\n try {\n entries = readdirSync(otterDir);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n return {\n verified: [],\n failed: [{ filename: '<directory>', error: `Cannot read directory: ${msg}` }],\n unknown: [],\n };\n }\n\n const otterFiles = entries.filter((f) => f.endsWith('-otter.json'));\n\n for (const filename of otterFiles) {\n const filePath = join(otterDir, filename);\n\n // Skip symlinks at the directory-scan level too\n try {\n if (lstatSync(filePath).isSymbolicLink()) {\n failed.push({ filename, error: 'Skipped: symlink' });\n continue;\n }\n } catch {\n // verifyOtterFile will handle stat errors\n }\n\n const result = verifyOtterFile(filePath);\n\n if (result.verified) {\n verified.push(result.filename);\n } else if (result.error?.startsWith('Unknown otter file')) {\n unknown.push(result.filename);\n } else {\n failed.push({ filename: result.filename, error: result.error ?? 'Unknown error' });\n }\n }\n\n // Check for missing canonical files — ones we expect but didn't find on disk\n for (const canonicalName of CANONICAL_CHECKSUMS.keys()) {\n if (!otterFiles.includes(canonicalName)) {\n failed.push({ filename: canonicalName, error: 'Missing from directory' });\n }\n }\n\n return { verified, failed, unknown };\n}\n\n// ---------------------------------------------------------------------------\n// Otter directory resolution\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_SEARCH_PATHS = ['node_modules/@stackwright-pro/otters/src/', 'packages/otters/src/'];\n\nfunction resolveOtterDir(): string | null {\n const cwd = process.cwd();\n for (const relative of DEFAULT_SEARCH_PATHS) {\n const candidate = join(cwd, relative);\n try {\n lstatSync(candidate);\n return candidate;\n } catch {\n // Not found, try next\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Audit stream — SOC2 / FedRAMP / DoD ATO compliance\n// ---------------------------------------------------------------------------\n\n/**\n * Structured audit event parameters for an integrity failure.\n * Kept as a plain interface so callers can pass partial results without\n * constructing the full VerifyAllOttersResult.\n */\nexport interface IntegrityAuditEvent {\n otterDir: string;\n failed: Array<{ filename: string; error: string }>;\n unknown: string[];\n}\n\n/**\n * Emit a structured INTEGRITY_FAIL audit event to stderr.\n *\n * Writes a single line to process.stderr in the format:\n * INTEGRITY_FAIL {\"level\":\"AUDIT\",\"event\":\"INTEGRITY_FAIL\",...}\n *\n * The line prefix \"INTEGRITY_FAIL\" (without JSON) allows log shippers\n * (FluentBit, syslog, CloudWatch Logs, Splunk) to match and route the\n * event to a dedicated audit stream using a simple string filter, even\n * before attempting JSON parsing.\n *\n * Exported for unit testing. Do not call directly in production code —\n * use registerIntegrityTools() which calls this automatically.\n */\nexport function emitIntegrityAuditEvent(params: IntegrityAuditEvent): void {\n const record = JSON.stringify({\n level: 'AUDIT',\n event: 'INTEGRITY_FAIL',\n timestamp: new Date().toISOString(),\n source: 'stackwright_pro_verify_otter_integrity',\n otterDir: params.otterDir,\n failedCount: params.failed.length,\n unknownCount: params.unknown.length,\n failures: params.failed,\n unknown: params.unknown,\n });\n process.stderr.write(`INTEGRITY_FAIL ${record}\\n`);\n}\n\n// ---------------------------------------------------------------------------\n// MCP tool registration\n// ---------------------------------------------------------------------------\n\nexport function registerIntegrityTools(server: McpServer): void {\n server.tool(\n 'stackwright_pro_verify_otter_integrity',\n 'Verify SHA-256 integrity of all Pro otter agent definitions. Call this at startup before discovering otters. Auto-discovers the otter directory from known paths. Returns verified/failed/unknown lists.',\n {},\n async () => {\n const resolved = resolveOtterDir();\n\n if (!resolved) {\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n error: true,\n message:\n 'Could not locate otter directory. Searched: ' + DEFAULT_SEARCH_PATHS.join(', '),\n }),\n },\n ],\n isError: true,\n };\n }\n\n const result = verifyAllOtters(resolved);\n\n const allGood = result.failed.length === 0 && result.unknown.length === 0;\n\n // Emit to dedicated audit stream for SOC2/FedRAMP/DoD ATO compliance\n if (!allGood) {\n emitIntegrityAuditEvent({\n otterDir: resolved,\n failed: result.failed,\n unknown: result.unknown,\n });\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: JSON.stringify({\n otterDir: resolved,\n totalCanonical: CANONICAL_CHECKSUMS.size,\n verifiedCount: result.verified.length,\n failedCount: result.failed.length,\n unknownCount: result.unknown.length,\n verified: result.verified,\n failed: result.failed,\n unknown: result.unknown,\n ...(allGood\n ? {}\n : {\n error:\n 'INTEGRITY CHECK FAILED: SHA-256 mismatch detected in otter agent definitions. Do not proceed — otter files may have been tampered with.',\n }),\n }),\n },\n ],\n isError: !allGood,\n };\n }\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Pipeline State & Artifact Management Tools — "Sinks Not Pipes"
3
+ *
4
+ * The filesystem IS the state machine. Every tool reads/writes to `.stackwright/`.
5
+ * Specialists produce artifacts → validated here. Retry logic lives HERE.
6
+ */
7
+
8
+ /**
9
+ * Canonical phase execution order — must remain a valid topological sort of
10
+ * the otter pipeline declarations. Validated at server startup by server.ts.
11
+ *
12
+ * Wave decomposition (derived from loadPipelineGraph()):
13
+ * Wave 1: designer, api (no deps)
14
+ * Wave 2: theme, auth, data (designer or api only)
15
+ * Wave 3: scaffold, workflow, geo (theme / theme+api / data)
16
+ * Wave 4: services (api + workflow)
17
+ * Wave 5: pages, dashboard (all above)
18
+ * Wave 6: polish (pages + dashboard + workflow + auth)
19
+ * Wave 7: qa (depends on polish + transitively everything)
20
+ *
21
+ * Key change from the old hardcoded PHASE_DEPENDENCIES:
22
+ * auth: was wave 4 (after pages/dashboard/workflow/geo); now wave 2 (needs designer only)
23
+ * workflow: was wave 1 (no deps); now wave 3 (needs theme + api)
24
+ */
25
+ declare const PHASE_ORDER: readonly ["designer", "theme", "scaffold", "api", "data", "auth", "geo", "workflow", "services", "pages", "dashboard", "polish", "qa"];
26
+ type Phase = (typeof PHASE_ORDER)[number];
27
+ /** Maps phase → expected artifact filename */
28
+ declare const PHASE_ARTIFACT: Record<Phase, string>;
29
+
30
+ export { PHASE_ARTIFACT, PHASE_ORDER, type Phase };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Pipeline State & Artifact Management Tools — "Sinks Not Pipes"
3
+ *
4
+ * The filesystem IS the state machine. Every tool reads/writes to `.stackwright/`.
5
+ * Specialists produce artifacts → validated here. Retry logic lives HERE.
6
+ */
7
+
8
+ /**
9
+ * Canonical phase execution order — must remain a valid topological sort of
10
+ * the otter pipeline declarations. Validated at server startup by server.ts.
11
+ *
12
+ * Wave decomposition (derived from loadPipelineGraph()):
13
+ * Wave 1: designer, api (no deps)
14
+ * Wave 2: theme, auth, data (designer or api only)
15
+ * Wave 3: scaffold, workflow, geo (theme / theme+api / data)
16
+ * Wave 4: services (api + workflow)
17
+ * Wave 5: pages, dashboard (all above)
18
+ * Wave 6: polish (pages + dashboard + workflow + auth)
19
+ * Wave 7: qa (depends on polish + transitively everything)
20
+ *
21
+ * Key change from the old hardcoded PHASE_DEPENDENCIES:
22
+ * auth: was wave 4 (after pages/dashboard/workflow/geo); now wave 2 (needs designer only)
23
+ * workflow: was wave 1 (no deps); now wave 3 (needs theme + api)
24
+ */
25
+ declare const PHASE_ORDER: readonly ["designer", "theme", "scaffold", "api", "data", "auth", "geo", "workflow", "services", "pages", "dashboard", "polish", "qa"];
26
+ type Phase = (typeof PHASE_ORDER)[number];
27
+ /** Maps phase → expected artifact filename */
28
+ declare const PHASE_ARTIFACT: Record<Phase, string>;
29
+
30
+ export { PHASE_ARTIFACT, PHASE_ORDER, type Phase };
@@ -0,0 +1,446 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/pipeline-constants.ts
21
+ var pipeline_constants_exports = {};
22
+ __export(pipeline_constants_exports, {
23
+ PHASE_ARTIFACT: () => PHASE_ARTIFACT,
24
+ PHASE_ORDER: () => PHASE_ORDER
25
+ });
26
+ module.exports = __toCommonJS(pipeline_constants_exports);
27
+
28
+ // src/tools/pipeline.ts
29
+ var import_zod5 = require("zod");
30
+ var import_proper_lockfile = require("proper-lockfile");
31
+
32
+ // src/coerce.ts
33
+ var import_zod = require("zod");
34
+
35
+ // src/artifact-signing.ts
36
+ var import_zod2 = require("zod");
37
+
38
+ // src/tools/pipeline.ts
39
+ var import_types3 = require("@stackwright-pro/types");
40
+
41
+ // src/tools/get-schema.ts
42
+ var import_zod4 = require("zod");
43
+ var import_types2 = require("@stackwright-pro/types");
44
+
45
+ // src/tools/validate-yaml-fragment.ts
46
+ var import_zod3 = require("zod");
47
+ var import_js_yaml = require("js-yaml");
48
+ var import_types = require("@stackwright-pro/types");
49
+ var NavigationLinkSchema = import_zod3.z.lazy(
50
+ () => import_zod3.z.object({
51
+ label: import_zod3.z.string().min(1),
52
+ href: import_zod3.z.string().min(1),
53
+ children: import_zod3.z.array(NavigationLinkSchema).optional()
54
+ })
55
+ );
56
+ var NavigationSectionSchema = import_zod3.z.object({
57
+ section: import_zod3.z.string().min(1),
58
+ items: import_zod3.z.array(NavigationLinkSchema)
59
+ });
60
+ var NavigationItemSchema = import_zod3.z.union([
61
+ NavigationLinkSchema,
62
+ NavigationSectionSchema
63
+ ]);
64
+
65
+ // src/tools/get-schema.ts
66
+ var NavigationLinkSchema2 = import_zod4.z.lazy(
67
+ () => import_zod4.z.object({
68
+ label: import_zod4.z.string().min(1),
69
+ href: import_zod4.z.string().min(1),
70
+ children: import_zod4.z.array(NavigationLinkSchema2).optional()
71
+ })
72
+ );
73
+ var NavigationSectionSchema2 = import_zod4.z.object({
74
+ section: import_zod4.z.string().min(1),
75
+ items: import_zod4.z.array(NavigationLinkSchema2)
76
+ });
77
+ var NavigationItemSchema2 = import_zod4.z.union([
78
+ NavigationLinkSchema2,
79
+ NavigationSectionSchema2
80
+ ]);
81
+
82
+ // src/tools/pipeline.ts
83
+ var import_telemetry = require("@stackwright-pro/telemetry");
84
+ var PHASE_ORDER = [
85
+ "designer",
86
+ "theme",
87
+ "scaffold",
88
+ // generates app/ directory from config
89
+ "api",
90
+ "data",
91
+ "auth",
92
+ // moved earlier — only depends on design-language.json (designer)
93
+ "geo",
94
+ "workflow",
95
+ "services",
96
+ "pages",
97
+ "dashboard",
98
+ "polish",
99
+ "qa"
100
+ ];
101
+ var PHASE_ARTIFACT = {
102
+ designer: "design-language.json",
103
+ theme: "theme-tokens.json",
104
+ scaffold: "scaffold-manifest.json",
105
+ api: "api-config.json",
106
+ auth: "auth-config.json",
107
+ data: "data-config.json",
108
+ pages: "pages-manifest.json",
109
+ dashboard: "dashboard-manifest.json",
110
+ workflow: "workflow-config.json",
111
+ services: "services-config.json",
112
+ polish: "polish-manifest.json",
113
+ geo: "geo-manifest.json",
114
+ qa: "qa-findings.json"
115
+ };
116
+ var PHASE_ARTIFACT_SCHEMA = {
117
+ designer: JSON.stringify(
118
+ {
119
+ version: "1.0",
120
+ generatedBy: "stackwright-pro-designer-otter",
121
+ application: {
122
+ type: "<operational|data-explorer|admin|logistics|general>",
123
+ environment: "<workstation|field|control-room|mixed>",
124
+ density: "<compact|balanced|spacious>",
125
+ accessibility: "<wcag-aa|section-508|none>",
126
+ colorScheme: "<light|dark|both>"
127
+ },
128
+ designLanguage: {
129
+ rationale: "<design rationale>",
130
+ spacingScale: { base: 8, scale: [0, 4, 8, 16, 24, 32, 48, 64] },
131
+ colorSemantics: { primary: "#1a365d", accent: "#e53e3e" },
132
+ typography: {
133
+ dataFont: "Inter",
134
+ headingFont: "Inter",
135
+ monoFont: "monospace",
136
+ dataSizePx: 12,
137
+ bodySizePx: 14
138
+ },
139
+ contrastRatio: "4.5",
140
+ borderRadius: "4",
141
+ shadowElevation: "standard"
142
+ },
143
+ themeTokenSeeds: {
144
+ light: {
145
+ background: "#ffffff",
146
+ foreground: "#1a1a1a",
147
+ primary: "#1a365d",
148
+ surface: "#f7f7f7",
149
+ border: "#e2e8f0"
150
+ },
151
+ dark: {
152
+ background: "#1a1a1a",
153
+ foreground: "#ffffff",
154
+ primary: "#90cdf4",
155
+ surface: "#2d2d2d",
156
+ border: "#4a5568"
157
+ }
158
+ },
159
+ conformsTo: null,
160
+ operationalNotes: []
161
+ },
162
+ null,
163
+ 2
164
+ ),
165
+ theme: JSON.stringify(
166
+ {
167
+ version: "1.0",
168
+ generatedBy: "stackwright-pro-theme-otter",
169
+ componentLibrary: "shadcn",
170
+ colorScheme: "<light|dark|both>",
171
+ tokens: {
172
+ colors: { "primary-500": "#1a365d", background: "#ffffff" },
173
+ spacing: { "spacing-1": "8px", "spacing-2": "16px" },
174
+ typography: { "font-data": "Inter", "text-sm": "12px" },
175
+ shape: { "radius-sm": "4px", "radius-md": "8px" },
176
+ shadows: { "shadow-sm": "0 1px 2px rgba(0,0,0,0.08)" }
177
+ },
178
+ cssVariables: {
179
+ "--background": "0 0% 100%",
180
+ "--foreground": "222.2 84% 4.9%",
181
+ "--primary": "222.2 47.4% 11.2%",
182
+ "--primary-foreground": "210 40% 98%",
183
+ "--surface": "210 40% 98%",
184
+ "--border": "214.3 31.8% 91.4%"
185
+ },
186
+ dark: { "--background": "222.2 84% 4.9%", "--foreground": "210 40% 98%" }
187
+ },
188
+ null,
189
+ 2
190
+ ),
191
+ scaffold: JSON.stringify(
192
+ {
193
+ version: "1.0",
194
+ generatedBy: "stackwright-pro-scaffold-otter",
195
+ // REQUIRED: appRouterFiles is a required key — NOT 'files' (see swp-k3mb)
196
+ appRouterFiles: [
197
+ "app/layout.tsx",
198
+ "app/_components/page-client.tsx",
199
+ "app/page.tsx",
200
+ "app/[...slug]/page.tsx",
201
+ "app/_components/providers.tsx",
202
+ "app/not-found.tsx"
203
+ ],
204
+ title: "<title from stackwright.yml>",
205
+ hasCollectionEndpoints: false,
206
+ hasAuthConfig: true
207
+ },
208
+ null,
209
+ 2
210
+ ),
211
+ api: JSON.stringify(
212
+ {
213
+ version: "1.0",
214
+ generatedBy: "stackwright-pro-api-otter",
215
+ entities: [
216
+ {
217
+ name: "Shipment",
218
+ endpoint: "/shipments",
219
+ method: "GET",
220
+ revalidate: 60,
221
+ mutationType: null
222
+ }
223
+ ],
224
+ skipped: [
225
+ {
226
+ spec: "ais-message-models.yaml",
227
+ format: "asyncapi",
228
+ reason: "AsyncAPI spec \u2014 WebSocket/event integration required (no REST endpoints)"
229
+ }
230
+ ],
231
+ warnings: [
232
+ "Spec contains external $ref URLs \u2014 recommend bundle: true in stackwright.yml integration config"
233
+ ],
234
+ auth: { type: "bearer", header: "Authorization", envVar: "API_TOKEN" },
235
+ baseUrl: "https://api.example.mil/v2",
236
+ specPath: "./specs/api.yaml"
237
+ },
238
+ null,
239
+ 2
240
+ ),
241
+ data: JSON.stringify(
242
+ {
243
+ version: "1.0",
244
+ generatedBy: "stackwright-pro-data-otter",
245
+ strategy: "<pulse-fast|isr-fast|isr-standard|isr-slow>",
246
+ pulseMode: false,
247
+ collections: [{ name: "equipment", revalidate: 60, pulse: false }],
248
+ endpoints: { included: ["/equipment/**"], excluded: ["/admin/**"] },
249
+ requiredPackages: { dependencies: {}, devPackages: {} }
250
+ },
251
+ null,
252
+ 2
253
+ ),
254
+ geo: JSON.stringify(
255
+ {
256
+ version: "1.0",
257
+ generatedBy: "stackwright-pro-geo-otter",
258
+ geoCollections: [
259
+ {
260
+ collection: "vessels",
261
+ latField: "latitude",
262
+ lngField: "longitude",
263
+ labelField: "vesselName",
264
+ colorField: "navigationStatus"
265
+ }
266
+ ],
267
+ pages: [
268
+ {
269
+ slug: "fleet-tracker",
270
+ type: "<tracker|zone|route|combined>",
271
+ collections: ["vessels"],
272
+ hasLayers: false
273
+ }
274
+ ]
275
+ },
276
+ null,
277
+ 2
278
+ ),
279
+ workflow: JSON.stringify(
280
+ {
281
+ version: "1.0",
282
+ generatedBy: "stackwright-pro-form-wizard-otter",
283
+ // Root key is `workflow` — NOT `workflowConfig` (see swp-k7cl)
284
+ workflow: {
285
+ id: "procurement-approval",
286
+ route: "/procurement",
287
+ files: ["workflows/procurement-approval.yml"],
288
+ serviceDependencies: ["service:workflow-state"],
289
+ warnings: []
290
+ }
291
+ },
292
+ null,
293
+ 2
294
+ ),
295
+ services: JSON.stringify(
296
+ {
297
+ version: "1.0",
298
+ generatedBy: "stackwright-services-otter",
299
+ flows: [
300
+ {
301
+ name: "at-risk-patients",
302
+ trigger: "<http|event|schedule|queue>",
303
+ steps: 3,
304
+ outputSpec: "at-risk-patients.openapi.json"
305
+ }
306
+ ],
307
+ workflows: [
308
+ {
309
+ name: "evacuation-coordination",
310
+ states: 4,
311
+ transitions: 5
312
+ }
313
+ ],
314
+ openApiSpecs: ["at-risk-patients.openapi.json"],
315
+ capabilitiesUsed: ["service.call", "collection.join", "collection.filter"]
316
+ },
317
+ null,
318
+ 2
319
+ ),
320
+ pages: JSON.stringify(
321
+ {
322
+ version: "1.0",
323
+ generatedBy: "stackwright-pro-page-otter",
324
+ pages: [
325
+ {
326
+ slug: "catalog",
327
+ type: "collection_listing",
328
+ collection: "products",
329
+ themeApplied: true,
330
+ authRequired: false
331
+ },
332
+ {
333
+ slug: "admin",
334
+ type: "protected",
335
+ collection: null,
336
+ themeApplied: true,
337
+ authRequired: true
338
+ }
339
+ ]
340
+ },
341
+ null,
342
+ 2
343
+ ),
344
+ dashboard: JSON.stringify(
345
+ {
346
+ version: "1.0",
347
+ generatedBy: "stackwright-pro-dashboard-otter",
348
+ pages: [
349
+ {
350
+ slug: "dashboard",
351
+ layout: "<grid|table|mixed>",
352
+ collections: ["equipment", "supplies"],
353
+ mode: "<ISR|Pulse>"
354
+ }
355
+ ]
356
+ },
357
+ null,
358
+ 2
359
+ ),
360
+ // type: 'pki' = CAC/DoD certificate auth | 'oidc' = enterprise SSO
361
+ // For dev-only mock auth: use type: 'oidc' with devOnly: true (Zod strips devOnly — it's a convention only)
362
+ auth: JSON.stringify(
363
+ {
364
+ version: "1.0",
365
+ generatedBy: "stackwright-pro-auth-otter",
366
+ authConfig: {
367
+ type: "<pki|oidc>",
368
+ // OIDC-only fields (omit for pki):
369
+ provider: "<azure_ad|okta|cognito|auth0|authentik|keycloak|custom>",
370
+ discoveryUrl: "<IdP OIDC discovery URL>",
371
+ clientId: "<OIDC client ID>",
372
+ clientSecret: "<OIDC client secret>",
373
+ rbacRoles: ["ADMIN", "ANALYST"],
374
+ rbacDefaultRole: "ANALYST",
375
+ protectedRoutes: ["/dashboard/:path*", "/procurement/:path*"],
376
+ auditEnabled: true,
377
+ auditRetentionDays: 90
378
+ },
379
+ // devScripts is OPTIONAL — only present when devOnly: true was used
380
+ // Polish otter and foreman MUST check devScripts.written before referencing scripts
381
+ devScripts: {
382
+ written: true,
383
+ scripts: { "dev:admin": "MOCK_USER=admin next dev" }
384
+ }
385
+ },
386
+ null,
387
+ 2
388
+ ),
389
+ polish: JSON.stringify(
390
+ {
391
+ version: "1.0",
392
+ generatedBy: "stackwright-pro-polish-otter",
393
+ landingPage: { slug: "", rewritten: true },
394
+ navigation: { itemCount: 5, items: ["/dashboard", "/equipment", "/workflows"] },
395
+ gettingStarted: "<rewritten|redirected|not-found>",
396
+ scaffoldCleanup: {
397
+ deleted: ["content/posts/getting-started.yaml"],
398
+ rewritten: ["app/not-found.tsx"],
399
+ skipped: []
400
+ }
401
+ },
402
+ null,
403
+ 2
404
+ ),
405
+ qa: JSON.stringify(
406
+ {
407
+ version: "1.0",
408
+ generatedBy: "stackwright-pro-qa-otter",
409
+ // skipped: true path — when dev server is unreachable at audit time
410
+ // skipped: false path — full audit completed
411
+ skipped: false,
412
+ skipReason: null,
413
+ wcagLevel: "<AA|AAA>",
414
+ summary: {
415
+ routesAudited: 3,
416
+ serious: 0,
417
+ moderate: 1,
418
+ minor: 0
419
+ },
420
+ findings: [
421
+ {
422
+ id: "qa-001",
423
+ route: "/dashboard",
424
+ severity: "<serious|moderate|minor>",
425
+ category: "<visual|a11y|design-contract|runtime>",
426
+ finding: "Human-readable description of what was observed",
427
+ evidence: {
428
+ screenshot: ".stackwright/qa/screenshots/dashboard-light.png",
429
+ consoleErrors: [],
430
+ axeViolations: []
431
+ },
432
+ suggested_otters: ["stackwright-pro-theme-otter"],
433
+ suggested_fix: "Specific, concrete next step the repair otter should take"
434
+ }
435
+ ]
436
+ },
437
+ null,
438
+ 2
439
+ )
440
+ };
441
+ // Annotate the CommonJS export names for ESM import in node:
442
+ 0 && (module.exports = {
443
+ PHASE_ARTIFACT,
444
+ PHASE_ORDER
445
+ });
446
+ //# sourceMappingURL=pipeline-constants.js.map