@ennamjsc/agents-scaffold 1.1.0
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/index.js +674 -0
- package/dist/index.js.map +1 -0
- package/package.json +42 -0
- package/templates/_shared/.claude/agents/project-owner.md +19 -0
- package/templates/_shared/.claude/agents/reviewer.md +24 -0
- package/templates/_shared/.claude/agents/team-lead.md +20 -0
- package/templates/_shared/.claude/commands/boot.md +14 -0
- package/templates/_shared/.claude/commands/checkpoint.md +28 -0
- package/templates/_shared/.claude/commands/escalate.md +26 -0
- package/templates/_shared/.claude/commands/memory.md +12 -0
- package/templates/_shared/.claude/hooks/session-start.ps1 +3 -0
- package/templates/_shared/.claude/hooks/session-start.sh +4 -0
- package/templates/_shared/.claude/settings.json.hbs +17 -0
- package/templates/_shared/.gitignore.append +12 -0
- package/templates/_shared/.mcp.json.hbs +25 -0
- package/templates/_shared/.serena/checkpoint/.gitkeep +1 -0
- package/templates/_shared/.serena/memories/INDEX.md +13 -0
- package/templates/_shared/.serena/memories/backlog/.gitkeep +1 -0
- package/templates/_shared/.serena/memories/comms/active/.gitkeep +1 -0
- package/templates/_shared/.serena/memories/comms/resolved/.gitkeep +1 -0
- package/templates/_shared/.serena/memories/decisions/.gitkeep +1 -0
- package/templates/_shared/.serena/memories/services/.gitkeep +1 -0
- package/templates/_shared/AGENTS.md +75 -0
- package/templates/_shared/CLAUDE.md.partial.hbs +286 -0
- package/templates/_shared/docs/superpowers/plans/.gitkeep +1 -0
- package/templates/_shared/docs/superpowers/specs/.gitkeep +1 -0
- package/templates/flutter/.claude/agents/mobile-dev.md +21 -0
- package/templates/flutter/.mcp.json.partial.hbs +9 -0
- package/templates/flutter/CLAUDE.md.partial.hbs +26 -0
- package/templates/go/.claude/agents/backend-dev-go.md +22 -0
- package/templates/go/CLAUDE.md.partial.hbs +28 -0
- package/templates/local-root/CLAUDE.md.partial.hbs +50 -0
- package/templates/next/.claude/agents/web-dev.md +23 -0
- package/templates/next/.mcp.json.partial.hbs +13 -0
- package/templates/next/CLAUDE.md.partial.hbs +35 -0
- package/templates/python/.claude/agents/backend-dev-python.md +21 -0
- package/templates/python/CLAUDE.md.partial.hbs +27 -0
- package/templates/qa/.claude/agents/qa-tester.md +23 -0
- package/templates/qa/.claude/commands/qa-report.md +13 -0
- package/templates/qa/.claude/commands/qa-run.md +13 -0
- package/templates/qa/.mcp.json.partial.hbs +8 -0
- package/templates/qa/CLAUDE.md.partial.hbs +20 -0
- package/templates/qa/evidence/.gitkeep +1 -0
- package/templates/qa/qa/.gitkeep +1 -0
- package/templates/qa/test-cases/README.md +11 -0
- package/templates/qa/test-cases/TEMPLATE.md +25 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/profiles.ts","../src/enumerate.ts","../src/classify.ts","../src/conflict.ts","../src/plan.ts","../src/execute.ts","../src/render.ts","../src/merge/json.ts","../src/merge/marker.ts","../src/merge/lines.ts","../src/backup.ts","../src/ux.ts"],"sourcesContent":["import { cac } from 'cac';\nimport { readFile, access } from 'node:fs/promises';\nimport { fileURLToPath } from 'node:url';\nimport path from 'node:path';\nimport { select, isCancel, cancel } from '@clack/prompts';\nimport { getProfile, listProfiles } from './profiles.js';\nimport { enumerateFiles } from './enumerate.js';\nimport { scanConflicts } from './conflict.js';\nimport { buildPlan } from './plan.js';\nimport { executeOps } from './execute.js';\nimport { buildContext, renderFileEntry, renderJsonContent } from './render.js';\nimport { mergeMarker } from './merge/marker.js';\nimport { mergeJson } from './merge/json.js';\nimport { mergeLines } from './merge/lines.js';\nimport { printIntro, printPlan, confirmProceed, printNextSteps } from './ux.js';\nimport type { UserStrategy, OperationPlan } from './types.js';\n\nconst HERE = path.dirname(fileURLToPath(import.meta.url));\nconst PKG = JSON.parse(await readFile(path.join(HERE, '..', 'package.json'), 'utf8')) as { version: string };\n\nconst cli = cac('ennam-agents-scaffold');\n\ncli\n .command('[profile]', 'Install Claude Code config into the current directory')\n .option('--dry-run', 'Print the plan without writing anything')\n .option('--force', 'Overwrite all conflicts without prompting (alias for --merge-strategy=overwrite)')\n .option('--merge-strategy <s>', 'ask | skip | overwrite (default: ask)', { default: 'ask' })\n .option('--no-prompts', 'Fail on missing info instead of prompting (CI mode)')\n .option('--verbose', 'Verbose output')\n .action(async (profileArg: string | undefined, flags: Record<string, unknown>) => {\n printIntro(PKG.version);\n\n // cac normalises kebab-case flags: --dry-run → dryRun, --merge-strategy → mergeStrategy.\n // For --no-prompts, cac sets prompts: false (omit defaults to true).\n const interactive = flags.prompts !== false;\n\n let profileName = profileArg;\n if (!profileName) {\n if (!interactive) {\n console.error('Error: profile is required in --no-prompts mode');\n process.exit(2);\n }\n const choices = listProfiles().map(p => ({ value: p.name, label: `${p.name} — ${p.description}` }));\n const picked = await select({ message: 'Choose a profile:', options: choices });\n if (isCancel(picked)) { cancel('Aborted.'); process.exit(1); }\n profileName = picked as string;\n }\n\n let profile;\n try {\n profile = getProfile(profileName);\n } catch (err) {\n // Clean stderr message, no stack trace (Rule 12 — fail loud, but cleanly).\n console.error(`Error: ${(err as Error).message}`);\n process.exit(2);\n }\n const cwd = process.cwd();\n\n // Auto-detect: if there is no .git in cwd, the scaffold silently skips\n // git-only artifacts (today: .gitignore). No flag — works for every profile.\n let hasGit = true;\n try {\n await access(path.join(cwd, '.git'));\n } catch {\n hasGit = false;\n }\n\n const strategy: UserStrategy = (flags.force ? 'overwrite' : (flags.mergeStrategy as UserStrategy)) ?? 'ask';\n\n const entries = await enumerateFiles(profile);\n const ctx = buildContext({ profile: profileName, cwd, version: PKG.version });\n const byRel = new Map(entries.map(e => [e.relPath, e]));\n const provider = async (rel: string) => {\n const entry = byRel.get(rel);\n if (!entry) return null;\n if (entry.kind === 'append-marker') {\n // For marker-merge: \"identical\" means the existing file already contains the\n // expected marker block. Simulate the merge and compare to the full existing content.\n // Strip trailing \\n from block to match execute.ts behaviour (mergeMarker idempotency).\n const block = (await renderFileEntry(entry, ctx)).replace(/\\n$/, '');\n const abs = path.join(cwd, rel);\n let existing = '';\n try {\n existing = await readFile(abs, 'utf8');\n } catch {\n return null; // file absent — scanConflicts handles absent separately\n }\n return mergeMarker(existing, block);\n }\n if (entry.kind === 'json-merge') {\n // For json-merge: \"identical\" means the existing file already has the same merged result.\n // Compute mergeJson(userExisting, scaffoldCombined) to match execute.ts exactly.\n let existingObj: Record<string, unknown> = {};\n try {\n const existingText = await readFile(path.join(cwd, rel), 'utf8');\n if (existingText.trim().length > 0) {\n existingObj = JSON.parse(existingText) as Record<string, unknown>;\n }\n } catch {\n return null; // file absent — scanConflicts handles absent separately\n }\n const scaffoldObj = await renderJsonContent(entry, ctx);\n const merged = mergeJson(\n existingObj as Parameters<typeof mergeJson>[0],\n scaffoldObj as Parameters<typeof mergeJson>[0],\n );\n return JSON.stringify(merged, null, 2) + '\\n';\n }\n if (entry.kind === 'append-lines') {\n // For append-lines: \"identical\" means mergeLines(existing, incoming) === existing.\n // Compute mergeLines to match execute.ts exactly.\n let existing = '';\n try {\n existing = await readFile(path.join(cwd, rel), 'utf8');\n } catch {\n return null; // file absent — scanConflicts handles absent separately\n }\n const incoming = await renderFileEntry(entry, ctx);\n return mergeLines(existing, incoming);\n }\n return renderFileEntry(entry, ctx);\n };\n const conflicts = await scanConflicts(cwd, entries.map(e => e.relPath), provider);\n const ops = buildPlan({ entries, conflicts, strategy, hasGit });\n const plan: OperationPlan = { cwd, profile, ops, hasGit };\n\n printPlan(plan);\n\n if (flags.dryRun) {\n console.log('\\n(dry-run — no files written)');\n process.exit(0);\n }\n\n if (interactive) {\n const proceed = await confirmProceed();\n if (!proceed) process.exit(1);\n }\n\n const result = await executeOps({ cwd, ops, ctx, interactive });\n printNextSteps(profile, result, hasGit);\n });\n\ncli.help();\ncli.version(PKG.version);\ncli.parse();\n","import { fileURLToPath } from 'node:url';\nimport path from 'node:path';\nimport { existsSync } from 'node:fs';\nimport type { ProfileDef } from './types.js';\n\n// In dev (running from src or dist inside the monorepo), templates live at repo root.\n// In published package, templates live alongside dist/ inside the package.\nconst HERE = path.dirname(fileURLToPath(import.meta.url));\nconst CANDIDATE_PUBLISHED = path.join(HERE, '..', 'templates'); // dist/../templates\nconst CANDIDATE_MONOREPO = path.join(HERE, '..', '..', '..', 'templates'); // packages/cli/dist/../../../templates\nconst TEMPLATES = existsSync(CANDIDATE_PUBLISHED) ? CANDIDATE_PUBLISHED : CANDIDATE_MONOREPO;\n\nconst REGISTRY: Record<string, ProfileDef> = {\n next: {\n name: 'next',\n description: 'Next.js 16 App Router + React 19 + TS strict',\n templateDir: path.join(TEMPLATES, 'next'),\n extraMcp: ['chrome-devtools', 'figma'],\n },\n flutter: {\n name: 'flutter',\n description: 'Flutter 3.x + Dart + Riverpod/Bloc + dio',\n templateDir: path.join(TEMPLATES, 'flutter'),\n extraMcp: ['figma'],\n },\n python: {\n name: 'python',\n description: 'Python 3.12 + FastAPI + uv + ruff + pytest',\n templateDir: path.join(TEMPLATES, 'python'),\n extraMcp: [],\n },\n go: {\n name: 'go',\n description: 'Go 1.24 + stdlib net/http + pgx + slog',\n templateDir: path.join(TEMPLATES, 'go'),\n extraMcp: [],\n },\n qa: {\n name: 'qa',\n description: 'QA workflow — test cases, evidence, chrome-devtools',\n templateDir: path.join(TEMPLATES, 'qa'),\n extraMcp: ['chrome-devtools'],\n },\n 'local-root': {\n name: 'local-root',\n description: 'Orchestration root — polyrepo coordinator, reads sub-platform .serena/ memories',\n templateDir: path.join(TEMPLATES, 'local-root'),\n extraMcp: [],\n },\n};\n\nexport function getProfile(name: string): ProfileDef {\n const p = REGISTRY[name];\n if (!p) throw new Error(`Unknown profile: \"${name}\". Available: ${Object.keys(REGISTRY).join(', ')}`);\n return p;\n}\n\nexport function listProfiles(): ProfileDef[] {\n return Object.values(REGISTRY);\n}\n\nexport function getSharedDir(): string {\n return path.join(TEMPLATES, '_shared');\n}\n","import path from 'node:path';\r\nimport fg from 'fast-glob';\r\nimport type { ProfileDef, FileEntry } from './types.js';\r\nimport { getSharedDir } from './profiles.js';\r\nimport { classifyFile } from './classify.js';\r\n\r\n/**\r\n * Strip .hbs and .partial.hbs and .append suffixes to derive the on-disk relPath.\r\n * `CLAUDE.md.partial.hbs` → `CLAUDE.md`\r\n * `.gitignore.append` → `.gitignore`\r\n * `settings.json.hbs` → `settings.json`\r\n */\r\nfunction targetRelPath(srcRel: string): string {\r\n return srcRel\r\n .replace(/\\.partial\\.hbs$/, '')\r\n .replace(/\\.hbs$/, '')\r\n .replace(/\\.append$/, '');\r\n}\r\n\r\nasync function collect(dir: string): Promise<{ src: string; rel: string }[]> {\r\n const files = await fg('**/*', { cwd: dir, dot: true, onlyFiles: true });\r\n return files.map(rel => ({ src: path.join(dir, rel), rel }));\r\n}\r\n\r\nexport async function enumerateFiles(profile: ProfileDef): Promise<FileEntry[]> {\r\n const sharedDir = getSharedDir();\r\n const shared = await collect(sharedDir);\r\n const profileFiles = await collect(profile.templateDir);\r\n\r\n // Map keyed by target relPath. Profile entries override shared on collision.\r\n const map = new Map<string, FileEntry>();\r\n\r\n // Handle marker-merge sources separately so we can pair shared + profile partials.\r\n // For Plan 2, only CLAUDE.md uses marker pairing.\r\n const markerPairs = new Map<string, { sharedSrc?: string; profileSrc?: string }>();\r\n const collectMarker = (src: string, rel: string, isShared: boolean): boolean => {\r\n if (!rel.endsWith('.partial.hbs')) return false;\r\n const target = targetRelPath(rel);\r\n if (target !== 'CLAUDE.md') return false;\r\n const entry = markerPairs.get(target) ?? {};\r\n if (isShared) entry.sharedSrc = src;\r\n else entry.profileSrc = src;\r\n markerPairs.set(target, entry);\r\n return true;\r\n };\r\n\r\n for (const { src, rel } of shared) {\r\n if (collectMarker(src, rel, true)) continue;\r\n if (src.endsWith('.partial.hbs')) continue; // non-CLAUDE partial — handled in T11\r\n const target = targetRelPath(rel);\r\n map.set(target, {\r\n srcAbs: src,\r\n relPath: target,\r\n isTemplate: src.endsWith('.hbs'),\r\n kind: classifyFile(target),\r\n });\r\n }\r\n for (const { src, rel } of profileFiles) {\r\n if (collectMarker(src, rel, false)) continue;\r\n if (src.endsWith('.partial.hbs')) continue;\r\n const target = targetRelPath(rel);\r\n map.set(target, {\r\n srcAbs: src,\r\n relPath: target,\r\n isTemplate: src.endsWith('.hbs'),\r\n kind: classifyFile(target),\r\n });\r\n }\r\n\r\n // Emit one FileEntry per marker pair.\r\n for (const [target, pair] of markerPairs) {\r\n if (!pair.sharedSrc) continue; // no shared partial → no marker block to write\r\n map.set(target, {\r\n srcAbs: pair.sharedSrc,\r\n relPath: target,\r\n isTemplate: true,\r\n kind: 'append-marker',\r\n extraSrcAbs: pair.profileSrc,\r\n });\r\n }\r\n\r\n // For json-merge: shared `_shared/.mcp.json.hbs` is the base; profile `<profile>/.mcp.json.partial.hbs`\r\n // is the addition. Attach profile partial as extraSrcAbs on the shared entry.\r\n const profileMcpPartial = profileFiles.find(({ rel }) => rel === '.mcp.json.partial.hbs');\r\n if (profileMcpPartial) {\r\n const existing = map.get('.mcp.json');\r\n if (existing) {\r\n existing.extraSrcAbs = profileMcpPartial.src;\r\n }\r\n }\r\n\r\n return [...map.values()].sort((a, b) => a.relPath.localeCompare(b.relPath));\r\n}\r\n","import type { FileKind } from './types.js';\r\n\r\ninterface Rule {\r\n match: (rel: string) => boolean;\r\n kind: FileKind;\r\n}\r\n\r\n// Order matters: first match wins.\r\n// T11: .mcp.json and .claude/settings.json use json-merge (wired in T11 via execute.ts).\r\n// User wins on key conflicts; profile partial (.mcp.json.partial.hbs) is merged into shared\r\n// .mcp.json.hbs before the final write.\r\nconst RULES: Rule[] = [\r\n { match: r => r === 'AGENTS.md', kind: 'write-or-ask' },\r\n { match: r => r === 'CLAUDE.md', kind: 'append-marker' },\r\n { match: r => r === '.gitignore', kind: 'append-lines' },\r\n { match: r => r === '.mcp.json', kind: 'json-merge' },\r\n { match: r => r === '.claude/settings.json', kind: 'json-merge' },\r\n { match: r => r.startsWith('.claude/hooks/'), kind: 'write-or-ask' },\r\n { match: r => r.startsWith('.claude/commands/'), kind: 'skip-if-exists' },\r\n { match: r => r.startsWith('.claude/agents/'), kind: 'skip-if-exists' },\r\n { match: r => r.startsWith('.serena/'), kind: 'skip-if-exists' },\r\n { match: r => r.startsWith('docs/superpowers/'), kind: 'skip-if-exists' },\r\n];\r\n\r\nexport function classifyFile(relPath: string): FileKind {\r\n const norm = relPath.replace(/\\\\/g, '/');\r\n for (const r of RULES) if (r.match(norm)) return r.kind;\r\n return 'write-or-ask';\r\n}\r\n","import { readFile, access } from 'node:fs/promises';\nimport path from 'node:path';\nimport type { ConflictState, ConflictReport } from './types.js';\n\nexport type RenderedProvider = (relPath: string) => Promise<string | null>;\n\n/**\n * Scan cwd for each target relPath.\n * If `provider` is given, identical/differs is determined by comparing rendered content;\n * otherwise we only distinguish absent vs differs (content presence implies \"differs\" for safety).\n */\nexport async function scanConflicts(\n cwd: string,\n relPaths: string[],\n provider?: RenderedProvider,\n): Promise<ConflictReport> {\n const out: ConflictReport = new Map();\n for (const rel of relPaths) {\n const abs = path.join(cwd, rel);\n let exists = true;\n try {\n await access(abs);\n } catch {\n exists = false;\n }\n if (!exists) {\n out.set(rel, 'absent');\n continue;\n }\n if (!provider) {\n out.set(rel, 'differs');\n continue;\n }\n const incoming = await provider(rel);\n if (incoming === null) {\n out.set(rel, 'differs');\n continue;\n }\n const existing = await readFile(abs, 'utf8');\n out.set(rel, existing === incoming ? 'identical' : 'differs');\n }\n return out;\n}\n","import type { FileEntry, PlannedOp, UserStrategy, ConflictReport } from './types.js';\n\nexport interface BuildPlanInput {\n entries: FileEntry[];\n conflicts: ConflictReport;\n strategy: UserStrategy;\n hasGit: boolean;\n}\n\nexport function buildPlan(input: BuildPlanInput): PlannedOp[] {\n const ops: PlannedOp[] = [];\n for (const e of input.entries) {\n const state = input.conflicts.get(e.relPath) ?? 'absent';\n\n // No-repo guard: if the target dir has no .git, never touch .gitignore.\n // The user almost certainly doesn't want a .gitignore in a non-git directory.\n if (e.relPath === '.gitignore' && !input.hasGit) {\n ops.push({\n relPath: e.relPath,\n src: e,\n conflict: state,\n op: 'skip',\n reason: 'No .git detected — skipping .gitignore',\n needsPrompt: false,\n });\n continue;\n }\n\n // append-marker is handled the same regardless of conflict state (except identical → skip)\n if (e.kind === 'append-marker') {\n if (state === 'identical') {\n ops.push({ relPath: e.relPath, src: e, conflict: state, op: 'skip', reason: 'identical — skip', needsPrompt: false });\n } else {\n ops.push({ relPath: e.relPath, src: e, conflict: state, op: 'merge-marker', reason: state === 'absent' ? 'absent — write marker block' : 'differs — merge marker block', needsPrompt: false });\n }\n continue;\n }\n\n // json-merge is handled regardless of conflict state (except identical → skip).\n // All states use merge-json op so execute.ts always writes JSON.stringify output,\n // keeping provider and execute in sync for idempotency.\n if (e.kind === 'json-merge') {\n if (state === 'identical') {\n ops.push({ relPath: e.relPath, src: e, conflict: state, op: 'skip', reason: 'identical — skip', needsPrompt: false });\n } else {\n ops.push({ relPath: e.relPath, src: e, conflict: state, op: 'merge-json', reason: state === 'absent' ? 'absent — write json' : 'differs — deep-merge (user wins)', needsPrompt: false });\n }\n continue;\n }\n\n // append-lines is handled regardless of conflict state (except identical → skip).\n // mergeLines(existing, incoming) handles absent files (existing='') naturally.\n if (e.kind === 'append-lines') {\n if (state === 'identical') {\n ops.push({ relPath: e.relPath, src: e, conflict: state, op: 'skip', reason: 'identical — skip', needsPrompt: false });\n } else {\n ops.push({ relPath: e.relPath, src: e, conflict: state, op: 'merge-lines', reason: state === 'absent' ? 'absent — write lines' : 'differs — append missing lines (dedup)', needsPrompt: false });\n }\n continue;\n }\n\n if (state === 'absent') {\n ops.push({ relPath: e.relPath, src: e, conflict: state, op: 'write', reason: 'absent — write', needsPrompt: false });\n continue;\n }\n if (state === 'identical') {\n ops.push({ relPath: e.relPath, src: e, conflict: state, op: 'skip', reason: 'identical — skip', needsPrompt: false });\n continue;\n }\n // state === 'differs'\n if (e.kind === 'skip-if-exists') {\n ops.push({ relPath: e.relPath, src: e, conflict: state, op: 'skip', reason: 'skip-if-exists kind', needsPrompt: false });\n continue;\n }\n if (e.kind === 'mkdir-only') {\n ops.push({ relPath: e.relPath, src: e, conflict: state, op: 'mkdir', reason: 'mkdir-only kind', needsPrompt: false });\n continue;\n }\n // write-or-ask\n switch (input.strategy) {\n case 'overwrite':\n ops.push({ relPath: e.relPath, src: e, conflict: state, op: 'write', reason: 'differs — overwrite (--merge-strategy=overwrite)', needsPrompt: false });\n break;\n case 'skip':\n ops.push({ relPath: e.relPath, src: e, conflict: state, op: 'skip', reason: 'differs — keep existing (--merge-strategy=skip)', needsPrompt: false });\n break;\n case 'ask':\n default:\n ops.push({ relPath: e.relPath, src: e, conflict: state, op: 'write', reason: 'differs — will prompt at execute time', needsPrompt: true });\n break;\n }\n }\n return ops;\n}\n","import { readFile, writeFile, mkdir } from 'node:fs/promises';\nimport path from 'node:path';\nimport { confirm, isCancel, cancel } from '@clack/prompts';\nimport type { PlannedOp, RenderContext } from './types.js';\nimport { renderString, renderJsonContent } from './render.js';\nimport { mergeMarker } from './merge/marker.js';\nimport { mergeJson } from './merge/json.js';\nimport { mergeLines } from './merge/lines.js';\nimport { backupFile, newSessionId, rotateBackups } from './backup.js';\n\nexport interface ExecuteInput {\n cwd: string;\n ops: PlannedOp[];\n ctx: RenderContext;\n interactive: boolean; // false in CI/test; true for normal runs (enables ask prompt)\n}\n\nexport interface ExecuteResult {\n written: number;\n skipped: number;\n mkdirs: number;\n}\n\nasync function maybeRender(op: PlannedOp, ctx: RenderContext): Promise<string> {\n const raw = await readFile(op.src.srcAbs, 'utf8');\n if (!op.src.extraSrcAbs) {\n return op.src.isTemplate ? renderString(raw, ctx) : raw;\n }\n // Combine shared partial + profile partial via the `profileSection` Handlebars slot.\n const profileRaw = await readFile(op.src.extraSrcAbs, 'utf8');\n const profileRendered = renderString(profileRaw, ctx);\n const extendedCtx = { ...ctx, profileSection: profileRendered };\n return renderString(raw, extendedCtx as RenderContext);\n}\n\nasync function promptOverwrite(relPath: string): Promise<boolean> {\n const ans = await confirm({\n message: `File exists and differs: ${relPath}. Overwrite?`,\n initialValue: false,\n });\n if (isCancel(ans)) {\n cancel('Aborted by user.');\n process.exit(1);\n }\n return ans === true;\n}\n\nexport async function executeOps(input: ExecuteInput): Promise<ExecuteResult> {\n const result: ExecuteResult = { written: 0, skipped: 0, mkdirs: 0 };\n const session = newSessionId();\n\n for (const op of input.ops) {\n const target = path.join(input.cwd, op.relPath);\n\n if (op.op === 'skip') {\n result.skipped++;\n continue;\n }\n if (op.op === 'mkdir') {\n await mkdir(target, { recursive: true });\n result.mkdirs++;\n continue;\n }\n if (op.op === 'merge-marker') {\n await mkdir(path.dirname(target), { recursive: true });\n let existing = '';\n try {\n existing = await readFile(target, 'utf8');\n if (existing.length > 0) await backupFile(input.cwd, op.relPath, session);\n } catch {\n // file doesn't exist — fine\n }\n // Strip trailing newline from block before merging — mergeMarker is idempotent\n // only when block does not end with \\n (the engine appends its own trailing \\n).\n const block = (await maybeRender(op, input.ctx)).replace(/\\n$/, '');\n const merged = mergeMarker(existing, block);\n await writeFile(target, merged, 'utf8');\n result.written++;\n continue;\n }\n if (op.op === 'merge-json') {\n await mkdir(path.dirname(target), { recursive: true });\n let existingObj: Record<string, unknown> = {};\n let existed = false;\n try {\n const existingText = await readFile(target, 'utf8');\n if (existingText.trim().length > 0) {\n existingObj = JSON.parse(existingText) as Record<string, unknown>;\n existed = true;\n }\n } catch {\n // file doesn't exist — fine\n }\n if (existed) await backupFile(input.cwd, op.relPath, session);\n\n const scaffoldObj = await renderJsonContent(op.src, input.ctx);\n const merged = mergeJson(\n existingObj as Parameters<typeof mergeJson>[0],\n scaffoldObj as Parameters<typeof mergeJson>[0],\n );\n await writeFile(target, JSON.stringify(merged, null, 2) + '\\n', 'utf8');\n result.written++;\n continue;\n }\n if (op.op === 'merge-lines') {\n await mkdir(path.dirname(target), { recursive: true });\n let existing = '';\n try {\n existing = await readFile(target, 'utf8');\n if (existing.length > 0) await backupFile(input.cwd, op.relPath, session);\n } catch {\n // file doesn't exist — fine\n }\n const incoming = await maybeRender(op, input.ctx);\n const merged = mergeLines(existing, incoming);\n await writeFile(target, merged, 'utf8');\n result.written++;\n continue;\n }\n // op === 'write'\n if (op.needsPrompt) {\n if (input.interactive) {\n const yes = await promptOverwrite(op.relPath);\n if (!yes) {\n result.skipped++;\n continue;\n }\n } else {\n // Fail loud: ask-strategy + non-interactive would otherwise silently\n // overwrite a user-edited file. Force the caller to pick a strategy\n // (Rule 12 — fail loud, don't silently degrade).\n console.error(\n `Error: ${op.relPath} differs from scaffold and --merge-strategy=ask is not safe in non-interactive mode.\\n` +\n ` Re-run with --merge-strategy=overwrite (or --force) to replace, or --merge-strategy=skip to keep the existing file.`,\n );\n process.exit(2);\n }\n }\n await mkdir(path.dirname(target), { recursive: true });\n const content = await maybeRender(op, input.ctx);\n await writeFile(target, content, 'utf8');\n result.written++;\n }\n await rotateBackups(input.cwd, 3);\n return result;\n}\n","import path from 'node:path';\r\nimport { readFile } from 'node:fs/promises';\r\nimport Handlebars from 'handlebars';\r\nimport type { RenderContext, FileEntry } from './types.js';\r\nimport { mergeJson } from './merge/json.js';\r\n\r\nexport interface BuildContextOpts {\r\n profile: string;\r\n cwd: string;\r\n version: string;\r\n}\r\n\r\nexport function buildContext(opts: BuildContextOpts): RenderContext {\r\n const now = new Date();\r\n const date = `${now.getUTCFullYear()}-${String(now.getUTCMonth() + 1).padStart(2, '0')}-${String(now.getUTCDate()).padStart(2, '0')}`;\r\n return {\r\n scaffoldVersion: opts.version,\r\n profile: opts.profile,\r\n cwd: opts.cwd,\r\n projectName: path.basename(opts.cwd),\r\n year: now.getUTCFullYear(),\r\n date,\r\n isWindows: process.platform === 'win32',\r\n };\r\n}\r\n\r\n// Register custom helpers once at module load (Handlebars helpers are global, idempotent).\r\nHandlebars.registerHelper('json', (value: unknown) => {\r\n return JSON.stringify(value);\r\n});\r\n\r\nexport function renderString(template: string, ctx: RenderContext): string {\r\n return Handlebars.compile(template, { noEscape: true })(ctx);\r\n}\r\n\r\n/**\r\n * Render a FileEntry to the exact content the scaffold would write to disk.\r\n * Used by scanConflicts to detect already-installed files (identical → skip).\r\n * Handles extraSrcAbs (marker-merge) by combining shared + profile partials.\r\n */\r\nexport async function renderFileEntry(entry: FileEntry, ctx: RenderContext): Promise<string> {\r\n const raw = await readFile(entry.srcAbs, 'utf8');\r\n if (!entry.extraSrcAbs) {\r\n return entry.isTemplate ? renderString(raw, ctx) : raw;\r\n }\r\n // Combine shared partial + profile partial via the `profileSection` slot.\r\n const profileRaw = await readFile(entry.extraSrcAbs, 'utf8');\r\n const profileRendered = renderString(profileRaw, ctx);\r\n const extendedCtx = { ...ctx, profileSection: profileRendered };\r\n return renderString(raw, extendedCtx as RenderContext);\r\n}\r\n\r\n/**\r\n * Produce the scaffold-side combined JSON object for a json-merge entry.\r\n * If entry has extraSrcAbs (profile partial), profile wins on conflict.\r\n * mergeJson(first, second) keeps first's value on conflict, so:\r\n * mergeJson(profileObj, sharedObj) → profile wins.\r\n */\r\nexport async function renderJsonContent(entry: FileEntry, ctx: RenderContext): Promise<Record<string, unknown>> {\r\n const sharedRaw = await readFile(entry.srcAbs, 'utf8');\r\n const sharedObj = JSON.parse(renderString(sharedRaw, ctx)) as Record<string, unknown>;\r\n if (!entry.extraSrcAbs) return sharedObj;\r\n const profileRaw = await readFile(entry.extraSrcAbs, 'utf8');\r\n const profileObj = JSON.parse(renderString(profileRaw, ctx)) as Record<string, unknown>;\r\n // Profile wins on conflict (profile is more specific). mergeJson(first, second) keeps first's value.\r\n return mergeJson(\r\n profileObj as Parameters<typeof mergeJson>[0],\r\n sharedObj as Parameters<typeof mergeJson>[0],\r\n );\r\n}\r\n","type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };\r\ntype JsonObject = { [key: string]: JsonValue };\r\n\r\n/**\r\n * Deep merge two JSON-compatible objects with **user-wins** policy.\r\n *\r\n * Rules per key (when both `user` and `scaffold` have the key):\r\n * - User value `null` → keep user's null (explicit user choice)\r\n * - Both objects (not arrays) → recurse\r\n * - Otherwise → keep user value (arrays, scalars)\r\n *\r\n * Keys present only in `scaffold` are added.\r\n * Inputs are NOT mutated; the result is a fresh object.\r\n *\r\n * Note: This is NOT RFC 7396 — RFC 7396 has scaffold-wins semantics. We use user-wins\r\n * because the typical scaffold scenario is \"ship sensible defaults; respect any user customisation.\"\r\n */\r\nexport function mergeJson(user: JsonObject, scaffold: JsonObject): JsonObject {\r\n const out: JsonObject = { ...user };\r\n for (const key of Object.keys(scaffold)) {\r\n if (!(key in user)) {\r\n out[key] = deepClone(scaffold[key]!);\r\n continue;\r\n }\r\n const u = user[key];\r\n const s = scaffold[key];\r\n if (isPlainObject(u) && isPlainObject(s)) {\r\n out[key] = mergeJson(u, s);\r\n } else {\r\n out[key] = deepClone(u as JsonValue); // user wins (including null)\r\n }\r\n }\r\n return out;\r\n}\r\n\r\nfunction isPlainObject(v: unknown): v is JsonObject {\r\n return typeof v === 'object' && v !== null && !Array.isArray(v);\r\n}\r\n\r\nfunction deepClone<T extends JsonValue>(v: T): T {\r\n return JSON.parse(JSON.stringify(v));\r\n}\r\n","const BEGIN_RE = /<!--\\s*ennam-agents-scaffold:begin v[^\\s>]*\\s*-->/;\nconst BEGIN_RE_GLOBAL = /<!--\\s*ennam-agents-scaffold:begin v[^\\s>]*\\s*-->/g;\nconst END_MARKER = '<!-- ennam-agents-scaffold:end -->';\n\n/**\n * Insert or replace the scaffold-managed block in `existing`.\n *\n * - If existing has no begin marker → append block at end (with a blank line separator).\n * - If existing has a begin marker → find matching end marker, replace span.\n * - If a begin marker exists without an end marker → throw (malformed).\n * - If multiple begin markers exist → throw (refuses to silently leave stale blocks).\n *\n * Trailing newline normalization: result always ends with exactly one `\\n`.\n */\nexport function mergeMarker(existing: string, block: string): string {\n if (existing.length === 0) {\n return block.endsWith('\\n') ? block : block + '\\n';\n }\n\n // Fail loud on duplicate scaffold blocks. Otherwise we would silently update\n // only the first block and leave the second (stale) block in place (Rule 12).\n const allBegins = existing.match(BEGIN_RE_GLOBAL);\n if (allBegins && allBegins.length > 1) {\n throw new Error(\n 'Multiple ennam-agents-scaffold begin markers found; refusing to merge. ' +\n 'Please consolidate to a single managed block before re-running the scaffold.',\n );\n }\n\n const beginMatch = existing.match(BEGIN_RE);\n if (!beginMatch) {\n // No marker — append\n const sep = existing.endsWith('\\n') ? '\\n' : '\\n\\n';\n const trailing = block.endsWith('\\n') ? '' : '\\n';\n return existing + sep + block + trailing;\n }\n\n // Marker present — find matching end\n const beginStart = beginMatch.index!;\n const afterBegin = beginStart + beginMatch[0].length;\n const endIndex = existing.indexOf(END_MARKER, afterBegin);\n if (endIndex === -1) {\n throw new Error('Marker block malformed: begin marker found but end marker not found');\n }\n const afterEnd = endIndex + END_MARKER.length;\n\n const before = existing.slice(0, beginStart);\n const after = existing.slice(afterEnd);\n return before + block + after;\n}\n","/**\r\n * Append lines from `incoming` to `user` content, skipping any line whose trimmed\r\n * value already appears in `user`. Preserves order: user's lines first, then\r\n * incoming lines that are new.\r\n *\r\n * Used for `.gitignore` and similar line-oriented append-only files.\r\n */\r\nexport function mergeLines(user: string, incoming: string): string {\r\n const userLines = user.split('\\n');\r\n // Drop the artifact empty string at the end if user ends with \\n.\r\n if (userLines.length > 0 && userLines[userLines.length - 1] === '') {\r\n userLines.pop();\r\n }\r\n const userSet = new Set(userLines.map(l => l.trim()));\r\n\r\n const incomingLines = incoming.split('\\n');\r\n if (incomingLines.length > 0 && incomingLines[incomingLines.length - 1] === '') {\r\n incomingLines.pop();\r\n }\r\n\r\n const toAppend: string[] = [];\r\n for (const line of incomingLines) {\r\n if (userSet.has(line.trim())) continue;\r\n toAppend.push(line);\r\n userSet.add(line.trim());\r\n }\r\n\r\n const all = [...userLines, ...toAppend];\r\n return all.join('\\n') + '\\n';\r\n}\r\n","import { cp, mkdir, readdir, rm } from 'node:fs/promises';\r\nimport path from 'node:path';\r\n\r\nexport const BACKUP_DIR = '.ennam-scaffold-backup';\r\n\r\n/**\r\n * Copy `<cwd>/<relPath>` to `<cwd>/.ennam-scaffold-backup/<session>/<relPath>`.\r\n * Returns the absolute path of the backup copy.\r\n * If `session` is omitted, a fresh ISO-like timestamp is generated.\r\n * Caller is responsible for using one `session` per scaffold run so all\r\n * backups land in the same dir.\r\n */\r\nexport async function backupFile(cwd: string, relPath: string, session?: string): Promise<string> {\r\n const sessionDir = session ?? new Date().toISOString().replace(/[:.]/g, '-');\r\n const dest = path.join(cwd, BACKUP_DIR, sessionDir, relPath);\r\n await mkdir(path.dirname(dest), { recursive: true });\r\n await cp(path.join(cwd, relPath), dest);\r\n return dest;\r\n}\r\n\r\n/**\r\n * Remove all but the `keep` newest session directories under .ennam-scaffold-backup/.\r\n * Session dirs sort lexicographically — ISO timestamps do this correctly.\r\n * No-op if backup root does not exist.\r\n */\r\nexport async function rotateBackups(cwd: string, keep: number): Promise<void> {\r\n const root = path.join(cwd, BACKUP_DIR);\r\n let entries: string[];\r\n try {\r\n entries = await readdir(root);\r\n } catch {\r\n return; // backup dir doesn't exist yet — nothing to rotate\r\n }\r\n const sorted = entries.sort();\r\n const toRemove = sorted.slice(0, Math.max(0, sorted.length - keep));\r\n for (const name of toRemove) {\r\n await rm(path.join(root, name), { recursive: true, force: true });\r\n }\r\n}\r\n\r\n/** Generate the canonical session id used by execute.ts for the current run. */\r\nexport function newSessionId(): string {\r\n return new Date().toISOString().replace(/[:.]/g, '-');\r\n}\r\n","import pc from 'picocolors';\nimport { intro, outro, log, confirm, isCancel, cancel } from '@clack/prompts';\nimport type { OperationPlan, ProfileDef } from './types.js';\nimport type { ExecuteResult } from './execute.js';\n\nexport function printIntro(version: string): void {\n intro(pc.cyan(`Ennam Agents Scaffold v${version}`));\n}\n\nexport function printPlan(plan: OperationPlan): void {\n const lines: string[] = [];\n for (const op of plan.ops) {\n const marker = op.op === 'write' ? pc.green('+ write ') : op.op === 'mkdir' ? pc.blue('+ mkdir ') : op.op === 'merge-json' || op.op === 'merge-marker' || op.op === 'merge-lines' ? pc.yellow('~ merge ') : pc.gray(' skip ');\n lines.push(`${marker} ${op.relPath} ${pc.dim(`(${op.reason})`)}`);\n }\n log.step(`Plan (${plan.ops.length} ops):\\n ${lines.join('\\n ')}`);\n}\n\nexport async function confirmProceed(): Promise<boolean> {\n const yes = await confirm({ message: 'Proceed?', initialValue: true });\n if (isCancel(yes)) {\n cancel('Aborted.');\n return false;\n }\n return yes === true;\n}\n\nexport function printNextSteps(profile: ProfileDef, result: ExecuteResult, hasGit: boolean): void {\n const envVars = ['JIRA_URL', 'JIRA_TOKEN'];\n if (profile.extraMcp.includes('figma')) envVars.push('FIGMA_TOKEN');\n const steps: string[] = [];\n if (hasGit) {\n steps.push('Review changes: git diff');\n } else {\n steps.push('Inspect changes in your editor (no .git detected — run `git init` first if you want diff/version tracking)');\n }\n steps.push(`Set env vars in .env.local: ${envVars.join(', ')}`);\n steps.push('Start Claude Code: claude');\n steps.push('Inside Claude: run /boot');\n outro(\n pc.cyan(`Done.`) +\n `\\n Profile: ${pc.bold(profile.name)}` +\n `\\n Written: ${result.written} Skipped: ${result.skipped} Mkdir: ${result.mkdirs}`\n );\n console.log();\n steps.forEach((s, i) => console.log(` ${i + 1}. ${s}`));\n}\n"],"mappings":";;;AAAA,SAAS,WAAW;AACpB,SAAS,YAAAA,WAAU,UAAAC,eAAc;AACjC,SAAS,iBAAAC,sBAAqB;AAC9B,OAAOC,WAAU;AACjB,SAAS,QAAQ,YAAAC,WAAU,UAAAC,eAAc;;;ACJzC,SAAS,qBAAqB;AAC9B,OAAO,UAAU;AACjB,SAAS,kBAAkB;AAK3B,IAAM,OAAO,KAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AACxD,IAAM,sBAAsB,KAAK,KAAK,MAAM,MAAM,WAAW;AAC7D,IAAM,qBAAsB,KAAK,KAAK,MAAM,MAAM,MAAM,MAAM,WAAW;AACzE,IAAM,YAAY,WAAW,mBAAmB,IAAI,sBAAsB;AAE1E,IAAM,WAAuC;AAAA,EAC3C,MAAM;AAAA,IACJ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa,KAAK,KAAK,WAAW,MAAM;AAAA,IACxC,UAAU,CAAC,mBAAmB,OAAO;AAAA,EACvC;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa,KAAK,KAAK,WAAW,SAAS;AAAA,IAC3C,UAAU,CAAC,OAAO;AAAA,EACpB;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa,KAAK,KAAK,WAAW,QAAQ;AAAA,IAC1C,UAAU,CAAC;AAAA,EACb;AAAA,EACA,IAAI;AAAA,IACF,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa,KAAK,KAAK,WAAW,IAAI;AAAA,IACtC,UAAU,CAAC;AAAA,EACb;AAAA,EACA,IAAI;AAAA,IACF,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa,KAAK,KAAK,WAAW,IAAI;AAAA,IACtC,UAAU,CAAC,iBAAiB;AAAA,EAC9B;AAAA,EACA,cAAc;AAAA,IACZ,MAAM;AAAA,IACN,aAAa;AAAA,IACb,aAAa,KAAK,KAAK,WAAW,YAAY;AAAA,IAC9C,UAAU,CAAC;AAAA,EACb;AACF;AAEO,SAAS,WAAW,MAA0B;AACnD,QAAM,IAAI,SAAS,IAAI;AACvB,MAAI,CAAC,EAAG,OAAM,IAAI,MAAM,qBAAqB,IAAI,iBAAiB,OAAO,KAAK,QAAQ,EAAE,KAAK,IAAI,CAAC,EAAE;AACpG,SAAO;AACT;AAEO,SAAS,eAA6B;AAC3C,SAAO,OAAO,OAAO,QAAQ;AAC/B;AAEO,SAAS,eAAuB;AACrC,SAAO,KAAK,KAAK,WAAW,SAAS;AACvC;;;AC/DA,OAAOC,WAAU;AACjB,OAAO,QAAQ;;;ACUf,IAAM,QAAgB;AAAA,EACpB,EAAE,OAAO,OAAK,MAAM,aAA0C,MAAM,eAAe;AAAA,EACnF,EAAE,OAAO,OAAK,MAAM,aAA0C,MAAM,gBAAgB;AAAA,EACpF,EAAE,OAAO,OAAK,MAAM,cAA0C,MAAM,eAAe;AAAA,EACnF,EAAE,OAAO,OAAK,MAAM,aAA0C,MAAM,aAAa;AAAA,EACjF,EAAE,OAAO,OAAK,MAAM,yBAA0C,MAAM,aAAa;AAAA,EACjF,EAAE,OAAO,OAAK,EAAE,WAAW,gBAAgB,GAAmB,MAAM,eAAe;AAAA,EACnF,EAAE,OAAO,OAAK,EAAE,WAAW,mBAAmB,GAAgB,MAAM,iBAAiB;AAAA,EACrF,EAAE,OAAO,OAAK,EAAE,WAAW,iBAAiB,GAAkB,MAAM,iBAAiB;AAAA,EACrF,EAAE,OAAO,OAAK,EAAE,WAAW,UAAU,GAAyB,MAAM,iBAAiB;AAAA,EACrF,EAAE,OAAO,OAAK,EAAE,WAAW,mBAAmB,GAAgB,MAAM,iBAAiB;AACvF;AAEO,SAAS,aAAa,SAA2B;AACtD,QAAM,OAAO,QAAQ,QAAQ,OAAO,GAAG;AACvC,aAAW,KAAK,MAAO,KAAI,EAAE,MAAM,IAAI,EAAG,QAAO,EAAE;AACnD,SAAO;AACT;;;ADhBA,SAAS,cAAc,QAAwB;AAC7C,SAAO,OACJ,QAAQ,mBAAmB,EAAE,EAC7B,QAAQ,UAAU,EAAE,EACpB,QAAQ,aAAa,EAAE;AAC5B;AAEA,eAAe,QAAQ,KAAsD;AAC3E,QAAM,QAAQ,MAAM,GAAG,QAAQ,EAAE,KAAK,KAAK,KAAK,MAAM,WAAW,KAAK,CAAC;AACvE,SAAO,MAAM,IAAI,UAAQ,EAAE,KAAKC,MAAK,KAAK,KAAK,GAAG,GAAG,IAAI,EAAE;AAC7D;AAEA,eAAsB,eAAe,SAA2C;AAC9E,QAAM,YAAY,aAAa;AAC/B,QAAM,SAAS,MAAM,QAAQ,SAAS;AACtC,QAAM,eAAe,MAAM,QAAQ,QAAQ,WAAW;AAGtD,QAAM,MAAM,oBAAI,IAAuB;AAIvC,QAAM,cAAc,oBAAI,IAAyD;AACjF,QAAM,gBAAgB,CAAC,KAAa,KAAa,aAA+B;AAC9E,QAAI,CAAC,IAAI,SAAS,cAAc,EAAG,QAAO;AAC1C,UAAM,SAAS,cAAc,GAAG;AAChC,QAAI,WAAW,YAAa,QAAO;AACnC,UAAM,QAAQ,YAAY,IAAI,MAAM,KAAK,CAAC;AAC1C,QAAI,SAAU,OAAM,YAAY;AAAA,QAC3B,OAAM,aAAa;AACxB,gBAAY,IAAI,QAAQ,KAAK;AAC7B,WAAO;AAAA,EACT;AAEA,aAAW,EAAE,KAAK,IAAI,KAAK,QAAQ;AACjC,QAAI,cAAc,KAAK,KAAK,IAAI,EAAG;AACnC,QAAI,IAAI,SAAS,cAAc,EAAG;AAClC,UAAM,SAAS,cAAc,GAAG;AAChC,QAAI,IAAI,QAAQ;AAAA,MACd,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,YAAY,IAAI,SAAS,MAAM;AAAA,MAC/B,MAAM,aAAa,MAAM;AAAA,IAC3B,CAAC;AAAA,EACH;AACA,aAAW,EAAE,KAAK,IAAI,KAAK,cAAc;AACvC,QAAI,cAAc,KAAK,KAAK,KAAK,EAAG;AACpC,QAAI,IAAI,SAAS,cAAc,EAAG;AAClC,UAAM,SAAS,cAAc,GAAG;AAChC,QAAI,IAAI,QAAQ;AAAA,MACd,QAAQ;AAAA,MACR,SAAS;AAAA,MACT,YAAY,IAAI,SAAS,MAAM;AAAA,MAC/B,MAAM,aAAa,MAAM;AAAA,IAC3B,CAAC;AAAA,EACH;AAGA,aAAW,CAAC,QAAQ,IAAI,KAAK,aAAa;AACxC,QAAI,CAAC,KAAK,UAAW;AACrB,QAAI,IAAI,QAAQ;AAAA,MACd,QAAQ,KAAK;AAAA,MACb,SAAS;AAAA,MACT,YAAY;AAAA,MACZ,MAAM;AAAA,MACN,aAAa,KAAK;AAAA,IACpB,CAAC;AAAA,EACH;AAIA,QAAM,oBAAoB,aAAa,KAAK,CAAC,EAAE,IAAI,MAAM,QAAQ,uBAAuB;AACxF,MAAI,mBAAmB;AACrB,UAAM,WAAW,IAAI,IAAI,WAAW;AACpC,QAAI,UAAU;AACZ,eAAS,cAAc,kBAAkB;AAAA,IAC3C;AAAA,EACF;AAEA,SAAO,CAAC,GAAG,IAAI,OAAO,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,cAAc,EAAE,OAAO,CAAC;AAC5E;;;AE5FA,SAAS,UAAU,cAAc;AACjC,OAAOC,WAAU;AAUjB,eAAsB,cACpB,KACA,UACA,UACyB;AACzB,QAAM,MAAsB,oBAAI,IAAI;AACpC,aAAW,OAAO,UAAU;AAC1B,UAAM,MAAMA,MAAK,KAAK,KAAK,GAAG;AAC9B,QAAI,SAAS;AACb,QAAI;AACF,YAAM,OAAO,GAAG;AAAA,IAClB,QAAQ;AACN,eAAS;AAAA,IACX;AACA,QAAI,CAAC,QAAQ;AACX,UAAI,IAAI,KAAK,QAAQ;AACrB;AAAA,IACF;AACA,QAAI,CAAC,UAAU;AACb,UAAI,IAAI,KAAK,SAAS;AACtB;AAAA,IACF;AACA,UAAM,WAAW,MAAM,SAAS,GAAG;AACnC,QAAI,aAAa,MAAM;AACrB,UAAI,IAAI,KAAK,SAAS;AACtB;AAAA,IACF;AACA,UAAM,WAAW,MAAM,SAAS,KAAK,MAAM;AAC3C,QAAI,IAAI,KAAK,aAAa,WAAW,cAAc,SAAS;AAAA,EAC9D;AACA,SAAO;AACT;;;ACjCO,SAAS,UAAU,OAAoC;AAC5D,QAAM,MAAmB,CAAC;AAC1B,aAAW,KAAK,MAAM,SAAS;AAC7B,UAAM,QAAQ,MAAM,UAAU,IAAI,EAAE,OAAO,KAAK;AAIhD,QAAI,EAAE,YAAY,gBAAgB,CAAC,MAAM,QAAQ;AAC/C,UAAI,KAAK;AAAA,QACP,SAAS,EAAE;AAAA,QACX,KAAK;AAAA,QACL,UAAU;AAAA,QACV,IAAI;AAAA,QACJ,QAAQ;AAAA,QACR,aAAa;AAAA,MACf,CAAC;AACD;AAAA,IACF;AAGA,QAAI,EAAE,SAAS,iBAAiB;AAC9B,UAAI,UAAU,aAAa;AACzB,YAAI,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,GAAG,UAAU,OAAO,IAAI,QAAQ,QAAQ,yBAAoB,aAAa,MAAM,CAAC;AAAA,MACtH,OAAO;AACL,YAAI,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,GAAG,UAAU,OAAO,IAAI,gBAAgB,QAAQ,UAAU,WAAW,qCAAgC,qCAAgC,aAAa,MAAM,CAAC;AAAA,MAC/L;AACA;AAAA,IACF;AAKA,QAAI,EAAE,SAAS,cAAc;AAC3B,UAAI,UAAU,aAAa;AACzB,YAAI,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,GAAG,UAAU,OAAO,IAAI,QAAQ,QAAQ,yBAAoB,aAAa,MAAM,CAAC;AAAA,MACtH,OAAO;AACL,YAAI,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,GAAG,UAAU,OAAO,IAAI,cAAc,QAAQ,UAAU,WAAW,6BAAwB,yCAAoC,aAAa,MAAM,CAAC;AAAA,MACzL;AACA;AAAA,IACF;AAIA,QAAI,EAAE,SAAS,gBAAgB;AAC7B,UAAI,UAAU,aAAa;AACzB,YAAI,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,GAAG,UAAU,OAAO,IAAI,QAAQ,QAAQ,yBAAoB,aAAa,MAAM,CAAC;AAAA,MACtH,OAAO;AACL,YAAI,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,GAAG,UAAU,OAAO,IAAI,eAAe,QAAQ,UAAU,WAAW,8BAAyB,+CAA0C,aAAa,MAAM,CAAC;AAAA,MACjM;AACA;AAAA,IACF;AAEA,QAAI,UAAU,UAAU;AACtB,UAAI,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,GAAG,UAAU,OAAO,IAAI,SAAS,QAAQ,uBAAkB,aAAa,MAAM,CAAC;AACnH;AAAA,IACF;AACA,QAAI,UAAU,aAAa;AACzB,UAAI,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,GAAG,UAAU,OAAO,IAAI,QAAQ,QAAQ,yBAAoB,aAAa,MAAM,CAAC;AACpH;AAAA,IACF;AAEA,QAAI,EAAE,SAAS,kBAAkB;AAC/B,UAAI,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,GAAG,UAAU,OAAO,IAAI,QAAQ,QAAQ,uBAAuB,aAAa,MAAM,CAAC;AACvH;AAAA,IACF;AACA,QAAI,EAAE,SAAS,cAAc;AAC3B,UAAI,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,GAAG,UAAU,OAAO,IAAI,SAAS,QAAQ,mBAAmB,aAAa,MAAM,CAAC;AACpH;AAAA,IACF;AAEA,YAAQ,MAAM,UAAU;AAAA,MACtB,KAAK;AACH,YAAI,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,GAAG,UAAU,OAAO,IAAI,SAAS,QAAQ,yDAAoD,aAAa,MAAM,CAAC;AACrJ;AAAA,MACF,KAAK;AACH,YAAI,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,GAAG,UAAU,OAAO,IAAI,QAAQ,QAAQ,wDAAmD,aAAa,MAAM,CAAC;AACnJ;AAAA,MACF,KAAK;AAAA,MACL;AACE,YAAI,KAAK,EAAE,SAAS,EAAE,SAAS,KAAK,GAAG,UAAU,OAAO,IAAI,SAAS,QAAQ,8CAAyC,aAAa,KAAK,CAAC;AACzI;AAAA,IACJ;AAAA,EACF;AACA,SAAO;AACT;;;AC7FA,SAAS,YAAAC,WAAU,WAAW,SAAAC,cAAa;AAC3C,OAAOC,WAAU;AACjB,SAAS,SAAS,UAAU,cAAc;;;ACF1C,OAAOC,WAAU;AACjB,SAAS,YAAAC,iBAAgB;AACzB,OAAO,gBAAgB;;;ACehB,SAAS,UAAU,MAAkB,UAAkC;AAC5E,QAAM,MAAkB,EAAE,GAAG,KAAK;AAClC,aAAW,OAAO,OAAO,KAAK,QAAQ,GAAG;AACvC,QAAI,EAAE,OAAO,OAAO;AAClB,UAAI,GAAG,IAAI,UAAU,SAAS,GAAG,CAAE;AACnC;AAAA,IACF;AACA,UAAM,IAAI,KAAK,GAAG;AAClB,UAAM,IAAI,SAAS,GAAG;AACtB,QAAI,cAAc,CAAC,KAAK,cAAc,CAAC,GAAG;AACxC,UAAI,GAAG,IAAI,UAAU,GAAG,CAAC;AAAA,IAC3B,OAAO;AACL,UAAI,GAAG,IAAI,UAAU,CAAc;AAAA,IACrC;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,cAAc,GAA6B;AAClD,SAAO,OAAO,MAAM,YAAY,MAAM,QAAQ,CAAC,MAAM,QAAQ,CAAC;AAChE;AAEA,SAAS,UAA+B,GAAS;AAC/C,SAAO,KAAK,MAAM,KAAK,UAAU,CAAC,CAAC;AACrC;;;AD7BO,SAAS,aAAa,MAAuC;AAClE,QAAM,MAAM,oBAAI,KAAK;AACrB,QAAM,OAAO,GAAG,IAAI,eAAe,CAAC,IAAI,OAAO,IAAI,YAAY,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO,IAAI,WAAW,CAAC,EAAE,SAAS,GAAG,GAAG,CAAC;AACnI,SAAO;AAAA,IACL,iBAAiB,KAAK;AAAA,IACtB,SAAS,KAAK;AAAA,IACd,KAAK,KAAK;AAAA,IACV,aAAaC,MAAK,SAAS,KAAK,GAAG;AAAA,IACnC,MAAM,IAAI,eAAe;AAAA,IACzB;AAAA,IACA,WAAW,QAAQ,aAAa;AAAA,EAClC;AACF;AAGA,WAAW,eAAe,QAAQ,CAAC,UAAmB;AACpD,SAAO,KAAK,UAAU,KAAK;AAC7B,CAAC;AAEM,SAAS,aAAa,UAAkB,KAA4B;AACzE,SAAO,WAAW,QAAQ,UAAU,EAAE,UAAU,KAAK,CAAC,EAAE,GAAG;AAC7D;AAOA,eAAsB,gBAAgB,OAAkB,KAAqC;AAC3F,QAAM,MAAM,MAAMC,UAAS,MAAM,QAAQ,MAAM;AAC/C,MAAI,CAAC,MAAM,aAAa;AACtB,WAAO,MAAM,aAAa,aAAa,KAAK,GAAG,IAAI;AAAA,EACrD;AAEA,QAAM,aAAa,MAAMA,UAAS,MAAM,aAAa,MAAM;AAC3D,QAAM,kBAAkB,aAAa,YAAY,GAAG;AACpD,QAAM,cAAc,EAAE,GAAG,KAAK,gBAAgB,gBAAgB;AAC9D,SAAO,aAAa,KAAK,WAA4B;AACvD;AAQA,eAAsB,kBAAkB,OAAkB,KAAsD;AAC9G,QAAM,YAAY,MAAMA,UAAS,MAAM,QAAQ,MAAM;AACrD,QAAM,YAAY,KAAK,MAAM,aAAa,WAAW,GAAG,CAAC;AACzD,MAAI,CAAC,MAAM,YAAa,QAAO;AAC/B,QAAM,aAAa,MAAMA,UAAS,MAAM,aAAa,MAAM;AAC3D,QAAM,aAAa,KAAK,MAAM,aAAa,YAAY,GAAG,CAAC;AAE3D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,EACF;AACF;;;AErEA,IAAM,WAAW;AACjB,IAAM,kBAAkB;AACxB,IAAM,aAAa;AAYZ,SAAS,YAAY,UAAkB,OAAuB;AACnE,MAAI,SAAS,WAAW,GAAG;AACzB,WAAO,MAAM,SAAS,IAAI,IAAI,QAAQ,QAAQ;AAAA,EAChD;AAIA,QAAM,YAAY,SAAS,MAAM,eAAe;AAChD,MAAI,aAAa,UAAU,SAAS,GAAG;AACrC,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AAEA,QAAM,aAAa,SAAS,MAAM,QAAQ;AAC1C,MAAI,CAAC,YAAY;AAEf,UAAM,MAAM,SAAS,SAAS,IAAI,IAAI,OAAO;AAC7C,UAAM,WAAW,MAAM,SAAS,IAAI,IAAI,KAAK;AAC7C,WAAO,WAAW,MAAM,QAAQ;AAAA,EAClC;AAGA,QAAM,aAAa,WAAW;AAC9B,QAAM,aAAa,aAAa,WAAW,CAAC,EAAE;AAC9C,QAAM,WAAW,SAAS,QAAQ,YAAY,UAAU;AACxD,MAAI,aAAa,IAAI;AACnB,UAAM,IAAI,MAAM,qEAAqE;AAAA,EACvF;AACA,QAAM,WAAW,WAAW,WAAW;AAEvC,QAAM,SAAS,SAAS,MAAM,GAAG,UAAU;AAC3C,QAAM,QAAQ,SAAS,MAAM,QAAQ;AACrC,SAAO,SAAS,QAAQ;AAC1B;;;AC1CO,SAAS,WAAW,MAAc,UAA0B;AACjE,QAAM,YAAY,KAAK,MAAM,IAAI;AAEjC,MAAI,UAAU,SAAS,KAAK,UAAU,UAAU,SAAS,CAAC,MAAM,IAAI;AAClE,cAAU,IAAI;AAAA,EAChB;AACA,QAAM,UAAU,IAAI,IAAI,UAAU,IAAI,OAAK,EAAE,KAAK,CAAC,CAAC;AAEpD,QAAM,gBAAgB,SAAS,MAAM,IAAI;AACzC,MAAI,cAAc,SAAS,KAAK,cAAc,cAAc,SAAS,CAAC,MAAM,IAAI;AAC9E,kBAAc,IAAI;AAAA,EACpB;AAEA,QAAM,WAAqB,CAAC;AAC5B,aAAW,QAAQ,eAAe;AAChC,QAAI,QAAQ,IAAI,KAAK,KAAK,CAAC,EAAG;AAC9B,aAAS,KAAK,IAAI;AAClB,YAAQ,IAAI,KAAK,KAAK,CAAC;AAAA,EACzB;AAEA,QAAM,MAAM,CAAC,GAAG,WAAW,GAAG,QAAQ;AACtC,SAAO,IAAI,KAAK,IAAI,IAAI;AAC1B;;;AC7BA,SAAS,IAAI,OAAO,SAAS,UAAU;AACvC,OAAOC,WAAU;AAEV,IAAM,aAAa;AAS1B,eAAsB,WAAW,KAAa,SAAiB,SAAmC;AAChG,QAAM,aAAa,YAAW,oBAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,SAAS,GAAG;AAC3E,QAAM,OAAOA,MAAK,KAAK,KAAK,YAAY,YAAY,OAAO;AAC3D,QAAM,MAAMA,MAAK,QAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AACnD,QAAM,GAAGA,MAAK,KAAK,KAAK,OAAO,GAAG,IAAI;AACtC,SAAO;AACT;AAOA,eAAsB,cAAc,KAAa,MAA6B;AAC5E,QAAM,OAAOA,MAAK,KAAK,KAAK,UAAU;AACtC,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,QAAQ,IAAI;AAAA,EAC9B,QAAQ;AACN;AAAA,EACF;AACA,QAAM,SAAS,QAAQ,KAAK;AAC5B,QAAM,WAAW,OAAO,MAAM,GAAG,KAAK,IAAI,GAAG,OAAO,SAAS,IAAI,CAAC;AAClE,aAAW,QAAQ,UAAU;AAC3B,UAAM,GAAGA,MAAK,KAAK,MAAM,IAAI,GAAG,EAAE,WAAW,MAAM,OAAO,KAAK,CAAC;AAAA,EAClE;AACF;AAGO,SAAS,eAAuB;AACrC,UAAO,oBAAI,KAAK,GAAE,YAAY,EAAE,QAAQ,SAAS,GAAG;AACtD;;;ALpBA,eAAe,YAAY,IAAe,KAAqC;AAC7E,QAAM,MAAM,MAAMC,UAAS,GAAG,IAAI,QAAQ,MAAM;AAChD,MAAI,CAAC,GAAG,IAAI,aAAa;AACvB,WAAO,GAAG,IAAI,aAAa,aAAa,KAAK,GAAG,IAAI;AAAA,EACtD;AAEA,QAAM,aAAa,MAAMA,UAAS,GAAG,IAAI,aAAa,MAAM;AAC5D,QAAM,kBAAkB,aAAa,YAAY,GAAG;AACpD,QAAM,cAAc,EAAE,GAAG,KAAK,gBAAgB,gBAAgB;AAC9D,SAAO,aAAa,KAAK,WAA4B;AACvD;AAEA,eAAe,gBAAgB,SAAmC;AAChE,QAAM,MAAM,MAAM,QAAQ;AAAA,IACxB,SAAS,4BAA4B,OAAO;AAAA,IAC5C,cAAc;AAAA,EAChB,CAAC;AACD,MAAI,SAAS,GAAG,GAAG;AACjB,WAAO,kBAAkB;AACzB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,SAAO,QAAQ;AACjB;AAEA,eAAsB,WAAW,OAA6C;AAC5E,QAAM,SAAwB,EAAE,SAAS,GAAG,SAAS,GAAG,QAAQ,EAAE;AAClE,QAAM,UAAU,aAAa;AAE7B,aAAW,MAAM,MAAM,KAAK;AAC1B,UAAM,SAASC,MAAK,KAAK,MAAM,KAAK,GAAG,OAAO;AAE9C,QAAI,GAAG,OAAO,QAAQ;AACpB,aAAO;AACP;AAAA,IACF;AACA,QAAI,GAAG,OAAO,SAAS;AACrB,YAAMC,OAAM,QAAQ,EAAE,WAAW,KAAK,CAAC;AACvC,aAAO;AACP;AAAA,IACF;AACA,QAAI,GAAG,OAAO,gBAAgB;AAC5B,YAAMA,OAAMD,MAAK,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACrD,UAAI,WAAW;AACf,UAAI;AACF,mBAAW,MAAMD,UAAS,QAAQ,MAAM;AACxC,YAAI,SAAS,SAAS,EAAG,OAAM,WAAW,MAAM,KAAK,GAAG,SAAS,OAAO;AAAA,MAC1E,QAAQ;AAAA,MAER;AAGA,YAAM,SAAS,MAAM,YAAY,IAAI,MAAM,GAAG,GAAG,QAAQ,OAAO,EAAE;AAClE,YAAM,SAAS,YAAY,UAAU,KAAK;AAC1C,YAAM,UAAU,QAAQ,QAAQ,MAAM;AACtC,aAAO;AACP;AAAA,IACF;AACA,QAAI,GAAG,OAAO,cAAc;AAC1B,YAAME,OAAMD,MAAK,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACrD,UAAI,cAAuC,CAAC;AAC5C,UAAI,UAAU;AACd,UAAI;AACF,cAAM,eAAe,MAAMD,UAAS,QAAQ,MAAM;AAClD,YAAI,aAAa,KAAK,EAAE,SAAS,GAAG;AAClC,wBAAc,KAAK,MAAM,YAAY;AACrC,oBAAU;AAAA,QACZ;AAAA,MACF,QAAQ;AAAA,MAER;AACA,UAAI,QAAS,OAAM,WAAW,MAAM,KAAK,GAAG,SAAS,OAAO;AAE5D,YAAM,cAAc,MAAM,kBAAkB,GAAG,KAAK,MAAM,GAAG;AAC7D,YAAM,SAAS;AAAA,QACb;AAAA,QACA;AAAA,MACF;AACA,YAAM,UAAU,QAAQ,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI,MAAM,MAAM;AACtE,aAAO;AACP;AAAA,IACF;AACA,QAAI,GAAG,OAAO,eAAe;AAC3B,YAAME,OAAMD,MAAK,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACrD,UAAI,WAAW;AACf,UAAI;AACF,mBAAW,MAAMD,UAAS,QAAQ,MAAM;AACxC,YAAI,SAAS,SAAS,EAAG,OAAM,WAAW,MAAM,KAAK,GAAG,SAAS,OAAO;AAAA,MAC1E,QAAQ;AAAA,MAER;AACA,YAAM,WAAW,MAAM,YAAY,IAAI,MAAM,GAAG;AAChD,YAAM,SAAS,WAAW,UAAU,QAAQ;AAC5C,YAAM,UAAU,QAAQ,QAAQ,MAAM;AACtC,aAAO;AACP;AAAA,IACF;AAEA,QAAI,GAAG,aAAa;AAClB,UAAI,MAAM,aAAa;AACrB,cAAM,MAAM,MAAM,gBAAgB,GAAG,OAAO;AAC5C,YAAI,CAAC,KAAK;AACR,iBAAO;AACP;AAAA,QACF;AAAA,MACF,OAAO;AAIL,gBAAQ;AAAA,UACN,UAAU,GAAG,OAAO;AAAA;AAAA,QAEtB;AACA,gBAAQ,KAAK,CAAC;AAAA,MAChB;AAAA,IACF;AACA,UAAME,OAAMD,MAAK,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AACrD,UAAM,UAAU,MAAM,YAAY,IAAI,MAAM,GAAG;AAC/C,UAAM,UAAU,QAAQ,SAAS,MAAM;AACvC,WAAO;AAAA,EACT;AACA,QAAM,cAAc,MAAM,KAAK,CAAC;AAChC,SAAO;AACT;;;AMjJA,OAAO,QAAQ;AACf,SAAS,OAAO,OAAO,KAAK,WAAAE,UAAS,YAAAC,WAAU,UAAAC,eAAc;AAItD,SAAS,WAAW,SAAuB;AAChD,QAAM,GAAG,KAAK,0BAA0B,OAAO,EAAE,CAAC;AACpD;AAEO,SAAS,UAAU,MAA2B;AACnD,QAAM,QAAkB,CAAC;AACzB,aAAW,MAAM,KAAK,KAAK;AACzB,UAAM,SAAS,GAAG,OAAO,UAAU,GAAG,MAAM,UAAU,IAAI,GAAG,OAAO,UAAU,GAAG,KAAK,UAAU,IAAI,GAAG,OAAO,gBAAgB,GAAG,OAAO,kBAAkB,GAAG,OAAO,gBAAgB,GAAG,OAAO,UAAU,IAAI,GAAG,KAAK,UAAU;AAC9N,UAAM,KAAK,GAAG,MAAM,IAAI,GAAG,OAAO,KAAK,GAAG,IAAI,IAAI,GAAG,MAAM,GAAG,CAAC,EAAE;AAAA,EACnE;AACA,MAAI,KAAK,SAAS,KAAK,IAAI,MAAM;AAAA,IAAa,MAAM,KAAK,MAAM,CAAC,EAAE;AACpE;AAEA,eAAsB,iBAAmC;AACvD,QAAM,MAAM,MAAMF,SAAQ,EAAE,SAAS,YAAY,cAAc,KAAK,CAAC;AACrE,MAAIC,UAAS,GAAG,GAAG;AACjB,IAAAC,QAAO,UAAU;AACjB,WAAO;AAAA,EACT;AACA,SAAO,QAAQ;AACjB;AAEO,SAAS,eAAe,SAAqB,QAAuB,QAAuB;AAChG,QAAM,UAAU,CAAC,YAAY,YAAY;AACzC,MAAI,QAAQ,SAAS,SAAS,OAAO,EAAG,SAAQ,KAAK,aAAa;AAClE,QAAM,QAAkB,CAAC;AACzB,MAAI,QAAQ;AACV,UAAM,KAAK,0BAA0B;AAAA,EACvC,OAAO;AACL,UAAM,KAAK,iHAA4G;AAAA,EACzH;AACA,QAAM,KAAK,+BAA+B,QAAQ,KAAK,IAAI,CAAC,EAAE;AAC9D,QAAM,KAAK,2BAA2B;AACtC,QAAM,KAAK,0BAA0B;AACrC;AAAA,IACE,GAAG,KAAK,OAAO,IACf;AAAA,aAAgB,GAAG,KAAK,QAAQ,IAAI,CAAC;AAAA,aACrB,OAAO,OAAO,cAAc,OAAO,OAAO,YAAY,OAAO,MAAM;AAAA,EACrF;AACA,UAAQ,IAAI;AACZ,QAAM,QAAQ,CAAC,GAAG,MAAM,QAAQ,IAAI,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;AAC3D;;;AZ7BA,IAAMC,QAAOC,MAAK,QAAQC,eAAc,YAAY,GAAG,CAAC;AACxD,IAAM,MAAM,KAAK,MAAM,MAAMC,UAASF,MAAK,KAAKD,OAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpF,IAAM,MAAM,IAAI,uBAAuB;AAEvC,IACG,QAAQ,aAAa,uDAAuD,EAC5E,OAAO,aAAa,yCAAyC,EAC7D,OAAO,WAAW,kFAAkF,EACpG,OAAO,wBAAwB,yCAAyC,EAAE,SAAS,MAAM,CAAC,EAC1F,OAAO,gBAAgB,qDAAqD,EAC5E,OAAO,aAAa,gBAAgB,EACpC,OAAO,OAAO,YAAgC,UAAmC;AAChF,aAAW,IAAI,OAAO;AAItB,QAAM,cAAc,MAAM,YAAY;AAEtC,MAAI,cAAc;AAClB,MAAI,CAAC,aAAa;AAChB,QAAI,CAAC,aAAa;AAChB,cAAQ,MAAM,iDAAiD;AAC/D,cAAQ,KAAK,CAAC;AAAA,IAChB;AACA,UAAM,UAAU,aAAa,EAAE,IAAI,QAAM,EAAE,OAAO,EAAE,MAAM,OAAO,GAAG,EAAE,IAAI,WAAM,EAAE,WAAW,GAAG,EAAE;AAClG,UAAM,SAAS,MAAM,OAAO,EAAE,SAAS,qBAAqB,SAAS,QAAQ,CAAC;AAC9E,QAAII,UAAS,MAAM,GAAG;AAAE,MAAAC,QAAO,UAAU;AAAG,cAAQ,KAAK,CAAC;AAAA,IAAG;AAC7D,kBAAc;AAAA,EAChB;AAEA,MAAI;AACJ,MAAI;AACF,cAAU,WAAW,WAAW;AAAA,EAClC,SAAS,KAAK;AAEZ,YAAQ,MAAM,UAAW,IAAc,OAAO,EAAE;AAChD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,QAAM,MAAM,QAAQ,IAAI;AAIxB,MAAI,SAAS;AACb,MAAI;AACF,UAAMC,QAAOL,MAAK,KAAK,KAAK,MAAM,CAAC;AAAA,EACrC,QAAQ;AACN,aAAS;AAAA,EACX;AAEA,QAAM,YAA0B,MAAM,QAAQ,cAAe,MAAM,kBAAmC;AAEtG,QAAM,UAAU,MAAM,eAAe,OAAO;AAC5C,QAAM,MAAM,aAAa,EAAE,SAAS,aAAa,KAAK,SAAS,IAAI,QAAQ,CAAC;AAC5E,QAAM,QAAQ,IAAI,IAAI,QAAQ,IAAI,OAAK,CAAC,EAAE,SAAS,CAAC,CAAC,CAAC;AACtD,QAAM,WAAW,OAAO,QAAgB;AACtC,UAAM,QAAQ,MAAM,IAAI,GAAG;AAC3B,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,MAAM,SAAS,iBAAiB;AAIlC,YAAM,SAAS,MAAM,gBAAgB,OAAO,GAAG,GAAG,QAAQ,OAAO,EAAE;AACnE,YAAM,MAAMA,MAAK,KAAK,KAAK,GAAG;AAC9B,UAAI,WAAW;AACf,UAAI;AACF,mBAAW,MAAME,UAAS,KAAK,MAAM;AAAA,MACvC,QAAQ;AACN,eAAO;AAAA,MACT;AACA,aAAO,YAAY,UAAU,KAAK;AAAA,IACpC;AACA,QAAI,MAAM,SAAS,cAAc;AAG/B,UAAI,cAAuC,CAAC;AAC5C,UAAI;AACF,cAAM,eAAe,MAAMA,UAASF,MAAK,KAAK,KAAK,GAAG,GAAG,MAAM;AAC/D,YAAI,aAAa,KAAK,EAAE,SAAS,GAAG;AAClC,wBAAc,KAAK,MAAM,YAAY;AAAA,QACvC;AAAA,MACF,QAAQ;AACN,eAAO;AAAA,MACT;AACA,YAAM,cAAc,MAAM,kBAAkB,OAAO,GAAG;AACtD,YAAM,SAAS;AAAA,QACb;AAAA,QACA;AAAA,MACF;AACA,aAAO,KAAK,UAAU,QAAQ,MAAM,CAAC,IAAI;AAAA,IAC3C;AACA,QAAI,MAAM,SAAS,gBAAgB;AAGjC,UAAI,WAAW;AACf,UAAI;AACF,mBAAW,MAAME,UAASF,MAAK,KAAK,KAAK,GAAG,GAAG,MAAM;AAAA,MACvD,QAAQ;AACN,eAAO;AAAA,MACT;AACA,YAAM,WAAW,MAAM,gBAAgB,OAAO,GAAG;AACjD,aAAO,WAAW,UAAU,QAAQ;AAAA,IACtC;AACA,WAAO,gBAAgB,OAAO,GAAG;AAAA,EACnC;AACA,QAAM,YAAY,MAAM,cAAc,KAAK,QAAQ,IAAI,OAAK,EAAE,OAAO,GAAG,QAAQ;AAChF,QAAM,MAAM,UAAU,EAAE,SAAS,WAAW,UAAU,OAAO,CAAC;AAC9D,QAAM,OAAsB,EAAE,KAAK,SAAS,KAAK,OAAO;AAExD,YAAU,IAAI;AAEd,MAAI,MAAM,QAAQ;AAChB,YAAQ,IAAI,qCAAgC;AAC5C,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,aAAa;AACf,UAAM,UAAU,MAAM,eAAe;AACrC,QAAI,CAAC,QAAS,SAAQ,KAAK,CAAC;AAAA,EAC9B;AAEA,QAAM,SAAS,MAAM,WAAW,EAAE,KAAK,KAAK,KAAK,YAAY,CAAC;AAC9D,iBAAe,SAAS,QAAQ,MAAM;AACxC,CAAC;AAEH,IAAI,KAAK;AACT,IAAI,QAAQ,IAAI,OAAO;AACvB,IAAI,MAAM;","names":["readFile","access","fileURLToPath","path","isCancel","cancel","path","path","path","readFile","mkdir","path","path","readFile","path","readFile","path","readFile","path","mkdir","confirm","isCancel","cancel","HERE","path","fileURLToPath","readFile","isCancel","cancel","access"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ennamjsc/agents-scaffold",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Install Claude Code tooling into existing projects",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ennam-agents-scaffold": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"templates"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsup",
|
|
18
|
+
"dev": "tsup --watch",
|
|
19
|
+
"test": "vitest run --passWithNoTests",
|
|
20
|
+
"test:watch": "vitest",
|
|
21
|
+
"prepublishOnly": "npm run build"
|
|
22
|
+
},
|
|
23
|
+
"engines": {
|
|
24
|
+
"node": ">=20"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@types/node": "^25.9.1",
|
|
28
|
+
"execa": "^9.6.1",
|
|
29
|
+
"strip-ansi": "^7.2.0",
|
|
30
|
+
"tmp-promise": "^3.0.3",
|
|
31
|
+
"tsup": "^8.5.1",
|
|
32
|
+
"typescript": "^6.0.3",
|
|
33
|
+
"vitest": "^4.1.6"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@clack/prompts": "^1.4.0",
|
|
37
|
+
"cac": "^6.7.14",
|
|
38
|
+
"fast-glob": "^3.3.3",
|
|
39
|
+
"handlebars": "^4.7.9",
|
|
40
|
+
"picocolors": "^1.1.1"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: project-owner
|
|
3
|
+
description: High-level vision and scope keeper. Use for prioritization, scope decisions, roadmap calls. Does not write production code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are the project owner. Your responsibilities:
|
|
7
|
+
|
|
8
|
+
- Set and defend project goals.
|
|
9
|
+
- Decide scope inclusion/exclusion when ambiguity arises.
|
|
10
|
+
- Prioritize backlog items based on value vs effort.
|
|
11
|
+
- Surface trade-offs to humans for decision.
|
|
12
|
+
|
|
13
|
+
Rules:
|
|
14
|
+
- Follow @AGENTS.md (especially Rule 2 — Simplicity First).
|
|
15
|
+
- You do NOT write production code. If implementation is needed, dispatch team-lead or a dev agent.
|
|
16
|
+
- Before deciding, read `.serena/memories/decisions/` for context on prior choices.
|
|
17
|
+
- Record every scope/priority decision to `.serena/memories/decisions/<topic>.md`.
|
|
18
|
+
|
|
19
|
+
Output style: concise, declarative. Lead with the decision; reasoning below.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: reviewer
|
|
3
|
+
description: Code reviewer. Applies AGENTS.md rules + Superpowers code-review checklist. Surface issues; do not modify code.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are the code reviewer. Your responsibilities:
|
|
7
|
+
|
|
8
|
+
- Read the diff/PR for which you were dispatched.
|
|
9
|
+
- Check against @AGENTS.md (all 12 rules).
|
|
10
|
+
- Apply @superpowers:requesting-code-review checklist for the reviewee's perspective.
|
|
11
|
+
- Surface issues with severity: blocker / major / minor / nit.
|
|
12
|
+
- Do NOT modify code; only report.
|
|
13
|
+
|
|
14
|
+
Process:
|
|
15
|
+
1. Identify the scope of the change (files, intent).
|
|
16
|
+
2. Check Rule 2 (Simplicity): is this the minimum code? Are there speculative abstractions?
|
|
17
|
+
3. Check Rule 3 (Surgical): does the diff touch only what's needed?
|
|
18
|
+
4. Check Rule 9 (Tests verify intent): do tests assert WHY, not just WHAT?
|
|
19
|
+
5. Check Rule 12 (Fail loud): is there silent skipping or swallowed errors?
|
|
20
|
+
6. Output a structured report:
|
|
21
|
+
- **Blockers** — must fix before merge
|
|
22
|
+
- **Majors** — should fix; document if deferred
|
|
23
|
+
- **Minors / Nits** — author's discretion
|
|
24
|
+
- **Praise** — call out anything notably well done
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: team-lead
|
|
3
|
+
description: Task decomposition and dispatch coordinator. Splits work into independent units, dispatches to dev/qa agents, integrates results.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
You are the team lead. Your responsibilities:
|
|
7
|
+
|
|
8
|
+
- Take a feature/task and decompose into independent sub-tasks.
|
|
9
|
+
- Decide which agent runs each sub-task (mobile-dev, web-dev, backend-dev-*, qa-tester).
|
|
10
|
+
- Dispatch via `superpowers:dispatching-parallel-agents` when sub-tasks are independent.
|
|
11
|
+
- Integrate results; resolve conflicts between agents' outputs.
|
|
12
|
+
- Surface blockers to project-owner or human.
|
|
13
|
+
|
|
14
|
+
Rules:
|
|
15
|
+
- Follow @AGENTS.md.
|
|
16
|
+
- Always start with @superpowers:brainstorming if the task is creative.
|
|
17
|
+
- For multi-step work, use @superpowers:writing-plans before implementation.
|
|
18
|
+
- Never write production code yourself — dispatch.
|
|
19
|
+
- Record dispatch decisions in `.serena/memories/decisions/dispatch-<topic>.md`.
|
|
20
|
+
- Verify each sub-task with @superpowers:verification-before-completion before integrating.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run the Session Boot Protocol (read memories + latest checkpoint).
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Execute the Session Boot Protocol from CLAUDE.md:
|
|
6
|
+
|
|
7
|
+
1. Read `.serena/memories/INDEX.md`. List its sections.
|
|
8
|
+
2. Based on the user's task, read the relevant `services/<svc>.md`.
|
|
9
|
+
3. Check `.serena/memories/comms/active/` for messages addressed to your role.
|
|
10
|
+
4. Check `.serena/memories/backlog/` for pending items in your domain.
|
|
11
|
+
5. Read the latest checkpoint in `.serena/checkpoint/` for your role.
|
|
12
|
+
6. Summarize what you learned. Confirm understanding before touching code.
|
|
13
|
+
|
|
14
|
+
Do not read source code until steps 1-6 are complete.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Write a checkpoint file to .serena/checkpoint/.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Write a checkpoint to `.serena/checkpoint/<agent-name>-<YYYY-MM-DD>.md`. If today's file already exists, append a new session block (`## Session N — <time>`).
|
|
6
|
+
|
|
7
|
+
Required sections:
|
|
8
|
+
|
|
9
|
+
```markdown
|
|
10
|
+
# Checkpoint: <agent-name> — <date>
|
|
11
|
+
|
|
12
|
+
## What was done
|
|
13
|
+
- bullet list
|
|
14
|
+
|
|
15
|
+
## Files changed
|
|
16
|
+
- list
|
|
17
|
+
|
|
18
|
+
## Current state
|
|
19
|
+
- working / broken / partial
|
|
20
|
+
|
|
21
|
+
## Next steps
|
|
22
|
+
- ...
|
|
23
|
+
|
|
24
|
+
## Blockers / Risks
|
|
25
|
+
- ...
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Keep it under 50 lines. Write the file before responding "done".
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Surface a blocker or question to another agent / human via comms.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Create a file at `.serena/memories/comms/active/<you>-to-<them>-<topic>.md` with:
|
|
6
|
+
|
|
7
|
+
```markdown
|
|
8
|
+
# <you> → <them>: <topic>
|
|
9
|
+
|
|
10
|
+
**Date:** <YYYY-MM-DD>
|
|
11
|
+
**Status:** open
|
|
12
|
+
|
|
13
|
+
## Context
|
|
14
|
+
- ...
|
|
15
|
+
|
|
16
|
+
## Question / Blocker
|
|
17
|
+
- ...
|
|
18
|
+
|
|
19
|
+
## What I've tried
|
|
20
|
+
- ...
|
|
21
|
+
|
|
22
|
+
## What I need
|
|
23
|
+
- decision / approval / information / unblock action
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Use slug-case for `<topic>`. Confirm the file path after writing.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Show current memory state (INDEX, active comms, latest checkpoint).
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
Print a concise overview of `.serena/`:
|
|
6
|
+
|
|
7
|
+
1. Content of `.serena/memories/INDEX.md`
|
|
8
|
+
2. List filenames in `.serena/memories/comms/active/`
|
|
9
|
+
3. List filenames in `.serena/memories/backlog/`
|
|
10
|
+
4. Path and first 20 lines of the most recent file in `.serena/checkpoint/`
|
|
11
|
+
|
|
12
|
+
Do not read other files unless the user asks.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"outputStyle": "default",
|
|
3
|
+
"model": "claude-opus-4-7",
|
|
4
|
+
"hooks": {
|
|
5
|
+
"SessionStart": [
|
|
6
|
+
{ "command": ".claude/hooks/session-start.{{#if isWindows}}ps1{{else}}sh{{/if}}" }
|
|
7
|
+
]
|
|
8
|
+
},
|
|
9
|
+
"permissions": {
|
|
10
|
+
"additionalAllowList": [
|
|
11
|
+
"Bash(npm:*)",
|
|
12
|
+
"Bash(npx:*)",
|
|
13
|
+
"Bash(node:*)",
|
|
14
|
+
"Bash(git:*)"
|
|
15
|
+
]
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Claude Code local overrides
|
|
2
|
+
.claude/settings.local.json
|
|
3
|
+
.claude/.cache/
|
|
4
|
+
|
|
5
|
+
# Serena local cache
|
|
6
|
+
.serena/.cache/
|
|
7
|
+
|
|
8
|
+
# Backup folder from scaffold edit-mode (safe to delete after review)
|
|
9
|
+
.ennam-scaffold-backup/
|
|
10
|
+
|
|
11
|
+
# Checkpoints are committed by default. Uncomment to ignore:
|
|
12
|
+
# .serena/checkpoint/
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"mcpServers": {
|
|
3
|
+
"serena": {
|
|
4
|
+
"command": "uvx",
|
|
5
|
+
"args": [
|
|
6
|
+
"--from", "git+https://github.com/oraios/serena",
|
|
7
|
+
"serena", "start-mcp-server",
|
|
8
|
+
"--context", "ide-assistant",
|
|
9
|
+
"--project", {{json cwd}}
|
|
10
|
+
]
|
|
11
|
+
},
|
|
12
|
+
"context7": {
|
|
13
|
+
"command": "npx",
|
|
14
|
+
"args": ["-y", "@upstash/context7-mcp"]
|
|
15
|
+
},
|
|
16
|
+
"jira": {
|
|
17
|
+
"command": "npx",
|
|
18
|
+
"args": ["-y", "@ennam/jira-mcp"],
|
|
19
|
+
"env": {
|
|
20
|
+
"JIRA_URL": "${JIRA_URL}",
|
|
21
|
+
"JIRA_TOKEN": "${JIRA_TOKEN}"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Memory Index
|
|
2
|
+
|
|
3
|
+
## Services
|
|
4
|
+
(none yet — add `services/<svc>.md` and link here as services emerge)
|
|
5
|
+
|
|
6
|
+
## Decisions
|
|
7
|
+
(none yet — record durable choices in `decisions/<topic>.md` and link here)
|
|
8
|
+
|
|
9
|
+
## Active Comms
|
|
10
|
+
(empty)
|
|
11
|
+
|
|
12
|
+
## Backlog
|
|
13
|
+
(empty)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# AGENTS.md — Agent Behavioral Rules
|
|
2
|
+
|
|
3
|
+
These rules apply to every agent and every task in this project
|
|
4
|
+
unless explicitly overridden by the user.
|
|
5
|
+
Bias: caution over speed on non-trivial work.
|
|
6
|
+
Use judgment on trivial tasks.
|
|
7
|
+
|
|
8
|
+
## Rule 1 — Think Before Coding
|
|
9
|
+
State assumptions explicitly. If uncertain, ask rather than guess.
|
|
10
|
+
Present multiple interpretations when ambiguity exists.
|
|
11
|
+
Push back when a simpler approach exists.
|
|
12
|
+
Stop when confused. Name what's unclear.
|
|
13
|
+
|
|
14
|
+
## Rule 2 — Simplicity First
|
|
15
|
+
Minimum code that solves the problem. Nothing speculative.
|
|
16
|
+
No features beyond what was asked. No abstractions for single-use code.
|
|
17
|
+
Test: would a senior engineer say this is overcomplicated? If yes, simplify.
|
|
18
|
+
|
|
19
|
+
## Rule 3 — Surgical Changes
|
|
20
|
+
Touch only what you must. Clean up only your own mess.
|
|
21
|
+
Don't "improve" adjacent code, comments, or formatting.
|
|
22
|
+
Don't refactor what isn't broken. Match existing style.
|
|
23
|
+
|
|
24
|
+
## Rule 4 — Goal-Driven Execution
|
|
25
|
+
Define success criteria before starting. Loop until verified.
|
|
26
|
+
Don't blindly follow step lists. Define success and iterate toward it.
|
|
27
|
+
Strong success criteria let you loop independently.
|
|
28
|
+
|
|
29
|
+
## Rule 5 — Use the model only for judgment calls
|
|
30
|
+
Use AI for: classification, drafting, summarization, extraction.
|
|
31
|
+
Do NOT use AI for: routing, retries, deterministic transforms.
|
|
32
|
+
If code/tools can answer, use code/tools.
|
|
33
|
+
|
|
34
|
+
## Rule 6 — Context discipline
|
|
35
|
+
If approaching context limits, summarize progress and start fresh.
|
|
36
|
+
Surface the situation. Do not silently degrade output quality.
|
|
37
|
+
Write a checkpoint before resetting context.
|
|
38
|
+
|
|
39
|
+
## Rule 7 — Surface conflicts, don't average them
|
|
40
|
+
If two patterns contradict, pick one (more recent / more tested).
|
|
41
|
+
Explain why. Flag the other for cleanup.
|
|
42
|
+
Don't blend conflicting patterns into a hybrid.
|
|
43
|
+
|
|
44
|
+
## Rule 8 — Read before you write
|
|
45
|
+
Before adding code, read exports, immediate callers, shared utilities.
|
|
46
|
+
"Looks orthogonal" is dangerous.
|
|
47
|
+
If unsure why code is structured a certain way, ask or check git blame.
|
|
48
|
+
|
|
49
|
+
## Rule 9 — Tests verify intent, not just behavior
|
|
50
|
+
Tests must encode WHY behavior matters, not just WHAT it does.
|
|
51
|
+
A test that can't fail when business logic changes is wrong.
|
|
52
|
+
|
|
53
|
+
## Rule 10 — Checkpoint after every significant step
|
|
54
|
+
Summarize what was done, what's verified, what's left.
|
|
55
|
+
Don't continue from a state you can't describe back.
|
|
56
|
+
If you lose track, stop and restate.
|
|
57
|
+
|
|
58
|
+
## Rule 11 — Match the codebase's conventions, even if you disagree
|
|
59
|
+
Conformance > taste inside the codebase.
|
|
60
|
+
If you genuinely think a convention is harmful, surface it.
|
|
61
|
+
Don't fork silently.
|
|
62
|
+
|
|
63
|
+
## Rule 12 — Fail loud
|
|
64
|
+
"Completed" is wrong if anything was skipped silently.
|
|
65
|
+
"Tests pass" is wrong if any were skipped.
|
|
66
|
+
Default to surfacing uncertainty, not hiding it.
|
|
67
|
+
|
|
68
|
+
## Rule 13 — Trust code over LLM regurgitation
|
|
69
|
+
When code hands an LLM a list of facts (filenames, IDs, exact strings)
|
|
70
|
+
and expects them back in output, do NOT trust the LLM's reproduction.
|
|
71
|
+
Claude normalizes, prefixes, abbreviates, and reorders. Either override
|
|
72
|
+
the LLM's output with code-derived ground-truth before persisting, or
|
|
73
|
+
have the LLM reference items by index (doc1/doc2) that the code maps
|
|
74
|
+
back. Tests must mock the LLM returning altered strings — a mock that
|
|
75
|
+
echoes inputs faithfully cannot catch this class.
|