@noir-ai/create 1.2.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/manifest.ts","../src/migrations/index.ts","../src/migrations/runner.ts","../src/scaffold.ts","../src/scaffold-version.ts","../src/writers.ts","../src/stack-detect.ts","../src/template.ts","../src/template-loader.ts"],"sourcesContent":["import { join, relative } from 'node:path';\nimport {\n AGENTS_MD_FILENAME,\n type EmitContext,\n emitAgentsMd,\n type HostAdapter,\n type HostId,\n resolveAdapter,\n} from '@noir-ai/adapters';\nimport {\n CONTEXT_BLOCK,\n IGNORE_BLOCK,\n type ManagedBlock,\n managedBlock,\n NOIR_DIR,\n paths,\n RULES_BLOCK,\n} from '@noir-ai/core';\nimport type { WriteMode } from './writers.js';\n\n/**\n * Declarative scaffold manifest — the single source of truth for what\n * `init` / `create` / `sync` emit. Each entry is one artifact, tagged with its\n * write {@link WriteMode} so the orchestrator can dispatch without knowing\n * what's inside.\n *\n * FAITHFULNESS CONTRACT (S-T1 → S-T2 → S10): this table is a strict superset of\n * the artifacts `packages/cli/src/{init,sync}.ts` wrote pre-Slice-S. The cli\n * refactor (S-T2) replaced those ad-hoc writers with a call into `scaffold()`;\n * the byte-for-byte output MUST stay equivalent for first-run init. S10 makes\n * the manifest HOST-PARAMETRIC: {@link buildManifest} now returns host-agnostic\n * entries + a {@link buildHostArtifacts} call that materializes per-host files\n * (CLAUDE.md/GEMINI.md for claude/gemini; AGENTS.md + .cursor/.../opencode.json\n * for agents-md/cursor/opencode) via the resolved adapter. The claude default\n * `noir init` stays BYTE-IDENTICAL to v1.1 — fix-wave I1 REMOVED the additive\n * root `AGENTS.md` (it was double-importing `.noir/NOIR.md` + RULES.md via\n * CLAUDE.md's existing @-imports; claude's native surface is CLAUDE.md alone).\n *\n * Path-derivation: repo-relative POSIX strings that mirror\n * `@noir-ai/core/layout.ts` (`paths.*`). The test suite asserts\n * `join(root, entry.path) === paths.X(root)` for every entry layout knows\n * about, so a layout rename is caught here instead of silently drifting.\n */\n\n/** S10: `HostTag` is now the SAME `HostId` enum the adapter registry uses\n * (re-exported so existing imports keep working). Pre-S10 this was the\n * literal `'claude'`; widening to `HostId` lets one manifest serve every host\n * via the orchestrator's host filter + {@link buildHostArtifacts}. */\nexport type HostTag = HostId;\n\nexport interface ManifestEntry {\n /** Repo-relative POSIX path (forward slashes). Orchestrator joins with root. */\n path: string;\n mode: WriteMode;\n /** Host tag; entry is skipped when opts.host !== entry.host.\n * Undefined = host-agnostic (every host emits it). */\n host?: HostTag;\n /** Required for `managedBlock` mode: the named block to re-emit. */\n block?: ManagedBlock;\n /** Literal content (`regenerate`/`skipIfExists`) or literal region BODY\n * (`managedBlock` — the orchestrator wraps it with the block markers).\n * Mutually exclusive with {@link template}. */\n content?: string;\n /** Template name (resolved by `template-loader`) for content/body.\n * Mutually exclusive with {@link content}. */\n template?: string;\n /** One-line human description for `noir doctor` + logs. */\n description?: string;\n}\n\nexport type BuildManifestContext = {\n /** Absolute repo root. Added in S10 so {@link buildHostArtifacts} can resolve\n * absolute adapter paths (`adapter.mcpConfigPath({root})`, etc.) to the\n * manifest's repo-relative POSIX shape. */\n root: string;\n /** Canonical project id (already created/read by the orchestrator). */\n projectId: string;\n /** Target host. Drives {@link buildHostArtifacts} via `resolveAdapter(host)`. */\n host: HostTag;\n /** MCP transport the host should use to reach Noir. */\n transport: 'stdio' | 'streamable-http';\n /** Required when transport is `streamable-http`. */\n url?: string;\n};\n\n// --- named managed blocks ----------------------------------------------------\n\n/** Co-owned NOIR.md auto-brief region. Defined locally (not exported from\n * core) because core's keystone-K named instances cover only the three\n * regions core itself writes (context/rules/ignore); the brief is the\n * scaffold engine's own. Uses the SAME `managedBlock()` factory so marker\n * shape stays consistent with the rest of the family. */\nexport const BRIEF_BLOCK: ManagedBlock = managedBlock('brief', 'html');\n\n// --- repo-relative path constants (mirror @noir-ai/core/layout.ts) -----------\n// Inlined as string literals so the manifest has zero runtime dep on layout\n// for path strings; the test suite cross-checks against `paths.*`.\n\nconst P = {\n projectId: `${NOIR_DIR}/project.id`,\n config: `${NOIR_DIR}/config.yml`,\n noirMd: `${NOIR_DIR}/NOIR.md`,\n rulesMd: `${NOIR_DIR}/rules/RULES.md`,\n} as const;\n\n// Aliases for the parity test (kept here so a layout rename breaks the test\n// at the same site the literal lives, not in a far-off helper).\nexport const MANIFEST_PATH_PARITY: ReadonlyArray<\n [entryPath: string, layoutFn: (root: string) => string]\n> = [\n [P.projectId, paths.projectId],\n [P.config, paths.config],\n [P.noirMd, paths.noirMd],\n [P.rulesMd, paths.rulesMd],\n];\n\n/**\n * Build the manifest for a given ctx. Pure (no I/O). The orchestrator calls\n * this once per scaffold run; tests assert the shape is stable.\n *\n * S10 structure: the manifest is now `[...hostAgnosticEntries(ctx), ...hostSpecificEntries(ctx)]`\n * where the host-specific half comes from {@link buildHostArtifacts} (driven by\n * `resolveAdapter(ctx.host)`). The host-agnostic half is unchanged from v1.1\n * (canonical `.noir/` store + ignore files). Fix-wave I1: {@link buildHostArtifacts}\n * emits AGENTS.md ONLY for agents-md/cursor/opencode (claude/gemini use their\n * own CLAUDE.md/GEMINI.md — emitting AGENTS.md too would double-import .noir/).\n *\n * Mode-tagging rationale per artifact (see S-T1 report for the full table):\n * - `project.id` → skipIfExists. First init writes a fresh id; re-init MUST\n * NOT overwrite — that would orphan the indexed store DB named after it.\n * - `config.yml` → skipIfExists. User-owned; the seed is written once.\n * (The seed renders `host: {{host}}` so a `--host gemini` init persists the\n * chosen host for `noir sync` to read back.)\n * - `NOIR.md` → managedBlock (BRIEF_BLOCK). Auto-brief is co-owned.\n * - `RULES.md` → skipIfExists. User-owned working-contract seed.\n * - ignore files → managedBlock (IGNORE_BLOCK). Matches syncIgnores.\n * - host entries → SEE {@link buildHostArtifacts} (regenerate / managedBlock).\n */\nexport function buildManifest(ctx: BuildManifestContext): ManifestEntry[] {\n return [...hostAgnosticEntries(ctx), ...buildHostArtifacts(resolveAdapter(ctx.host), ctx)];\n}\n\n/** The host-agnostic canonical-store + ignore entries — identical bytes for\n * every host. Split out so {@link buildHostArtifacts} can be unit-tested in\n * isolation and so the doctor's host-artifacts check can reason about the\n * host-specific half alone. */\nfunction hostAgnosticEntries(ctx: BuildManifestContext): ManifestEntry[] {\n return [\n {\n path: P.projectId,\n mode: 'skipIfExists',\n content: `${ctx.projectId}\\n`,\n description: 'canonical project id (store DB is named after it)',\n },\n {\n path: P.config,\n mode: 'skipIfExists',\n template: 'config.yml.tmpl',\n description: 'user config seed (host + mode)',\n },\n {\n path: P.noirMd,\n mode: 'managedBlock',\n block: BRIEF_BLOCK,\n template: 'noir.md.tmpl',\n description: 'NOIR.md auto-brief (project id pointer)',\n },\n {\n path: P.rulesMd,\n mode: 'skipIfExists',\n template: 'rules-seed.md.tmpl',\n description: 'AI working-rules seed',\n },\n\n // --- ignore files (host-agnostic; co-owned via IGNORE_BLOCK) ------------\n {\n path: '.gitignore',\n mode: 'managedBlock',\n block: IGNORE_BLOCK,\n template: 'gitignore.tmpl',\n description: '.gitignore noir managed block',\n },\n {\n path: '.dockerignore',\n mode: 'managedBlock',\n block: IGNORE_BLOCK,\n template: 'dockerignore.tmpl',\n description: '.dockerignore noir managed block',\n },\n {\n path: '.npmignore',\n mode: 'managedBlock',\n block: IGNORE_BLOCK,\n template: 'npmignore.tmpl',\n description: '.npmignore noir managed block',\n },\n {\n path: '.prettierignore',\n mode: 'managedBlock',\n block: IGNORE_BLOCK,\n template: 'prettierignore.tmpl',\n description: '.prettierignore noir managed block',\n },\n ];\n}\n\n// ---------------------------------------------------------------------------\n// S10 — host-specific artifact generation. One entry point: `buildHostArtifacts`.\n// ---------------------------------------------------------------------------\n\n/** Context shape passed to {@link buildHostArtifacts}. A strict subset of\n * {@link BuildManifestContext} (no `projectId`/`host` — the adapter IS the\n * resolved host, and host artifacts never need the project id). Exported\n * separately so callers + tests can name the narrower contract. */\nexport interface BuildHostArtifactsContext {\n root: string;\n transport: 'stdio' | 'streamable-http';\n url?: string;\n}\n\n/**\n * Materialize the host-specific manifest entries from a resolved adapter.\n * SINGLE entry point — no scattered `if (host === '…')` conditionals in the\n * orchestrator. Returns entries in emission order:\n *\n * 1. **AGENTS.md** (universal baseline) — `regenerate` at\n * `adapter.agentsMdPath(ctx)` (default `<root>/AGENTS.md`), content from\n * the shared `emitAgentsMd(ctx)` helper. Emitted ONLY for hosts whose\n * `emitContext` IS the AGENTS.md content (agents-md, cursor, opencode) —\n * for them AGENTS.md is the SINGLE native context surface AND carries the\n * Noir working rules via its `@.noir/rules/RULES.md` import. claude and\n * gemini have their OWN native context file (CLAUDE.md / GEMINI.md) that\n * `@`-imports the canonical `.noir/` sources; emitting AGENTS.md too\n * would IMPORT THOSE FILES TWICE into the host's context (2× tokens +\n * drift risk), so for those two hosts AGENTS.md is SKIPPED. (Claude Code\n * still discovers AGENTS.md at the repo root when present — users who\n * want the universal file can drop one in by hand; Noir's auto-emission\n * stays single-source per host.)\n * 2. **Host-native context file** — emitted ONLY for hosts whose `emitContext`\n * is NOT the AGENTS.md content (i.e. the host has its OWN context file\n * with a distinct syntax). Concretely: claude → `CLAUDE.md` (CONTEXT +\n * RULES managed blocks, byte-identical to v1.1 via templates); gemini →\n * `GEMINI.md` (CONTEXT + RULES managed blocks with Gemini's bare `@`\n * import syntax). For `agents-md`/`cursor`/`opencode` the context IS the\n * AGENTS.md (already emitted in step 1) → SKIP to avoid a duplicate.\n * Rules live INSIDE the host's context file: claude's in CLAUDE.md,\n * gemini's in GEMINI.md, agents-md/cursor/opencode's in AGENTS.md — NO\n * host emits a separate rules file. (The prior cursor\n * `.cursor/rules/noir-contract.mdc` host-rules pointer was REMOVED: it\n * collided with the C3 cursor flat-skill prune of `noir-*.mdc` under\n * `.cursor/rules/`, and cursor's rules are already delivered via\n * AGENTS.md's `@.noir/rules/RULES.md` import.)\n * 3. **Host MCP config** — `regenerate` at `adapter.mcpConfigPath(ctx)`\n * (default `<root>/.mcp.json` for claude), content from\n * `adapter.emitMcpConfig(ctx, {transport,url})`. Claude KEEPS the template\n * path (byte-identical parity with v1.1 + the .mcp.json parity test that\n * compares against `claudeAdapter.emitMcpConfig`); other hosts use the\n * adapter directly.\n *\n * Skills are OUT OF SCOPE here — the cli composes `emitSkillsToDir` with\n * `adapter.skillsDir` + the host's `CompileTarget` (claude → `.claude/skills/`\n * as SKILL.md; cursor → `.cursor/rules/<skill>.mdc` FLAT per C3; gemini/\n * agents-md/opencode have no skill dir → skip).\n */\nexport function buildHostArtifacts(\n adapter: HostAdapter,\n ctx: BuildHostArtifactsContext,\n): ManifestEntry[] {\n const ectx: EmitContext = { root: ctx.root };\n const host = adapter.id;\n const entries: ManifestEntry[] = [];\n\n // 1. AGENTS.md — emitted for hosts whose emitContext IS the AGENTS.md content\n // (agents-md, cursor, opencode). SKIPPED for claude/gemini: their native\n // CLAUDE.md / GEMINI.md already @-import the canonical .noir/ sources, so\n // a root AGENTS.md would double-import (2× context tokens, drift risk).\n // This also restores the claude default `noir init` to byte-identity with\n // v1.1 (the prior additive AGENTS.md delta is removed).\n const emitsAgentsMd = host === 'agents-md' || host === 'cursor' || host === 'opencode';\n if (emitsAgentsMd) {\n entries.push({\n path: hostRel(adapter.agentsMdPath?.(ectx) ?? join(ctx.root, AGENTS_MD_FILENAME), ctx.root),\n mode: 'regenerate',\n host,\n content: emitAgentsMd(ectx),\n description: `AGENTS.md (${host}'s native context surface; @-imports .noir/)`,\n });\n }\n\n // 2. Host-native context file (when distinct from AGENTS.md) + folded rules.\n switch (host) {\n case 'claude':\n // CLAUDE.md keeps template-based bodies — byte-identical to v1.1 (the\n // scaffold.test.ts parity gates compare against claudeAdapter.emitContext\n // + emitRules; the templates render to the same body bytes).\n entries.push({\n path: 'CLAUDE.md',\n mode: 'managedBlock',\n host,\n block: CONTEXT_BLOCK,\n template: 'claude-context-block.md.tmpl',\n description: 'CLAUDE.md context @import block',\n });\n entries.push({\n path: 'CLAUDE.md',\n mode: 'managedBlock',\n host,\n block: RULES_BLOCK,\n template: 'claude-rules-block.md.tmpl',\n description: 'CLAUDE.md rules @import block',\n });\n break;\n case 'gemini':\n // GEMINI.md carries CONTEXT_BLOCK + RULES_BLOCK with Gemini's bare\n // `@file` import syntax (no `@import` keyword, no quotes — distinct from\n // Claude's form). Emitted as TWO managed regions so user content outside\n // the markers survives `noir sync` (same write path as CLAUDE.md — the\n // multi-region atomic `managedBlocks` writer).\n entries.push({\n path: 'GEMINI.md',\n mode: 'managedBlock',\n host,\n block: CONTEXT_BLOCK,\n content: '@.noir/NOIR.md',\n description: 'GEMINI.md context @-import block',\n });\n entries.push({\n path: 'GEMINI.md',\n mode: 'managedBlock',\n host,\n block: RULES_BLOCK,\n content: '@.noir/rules/RULES.md',\n description: 'GEMINI.md rules @-import block',\n });\n break;\n case 'agents-md':\n case 'cursor':\n case 'opencode':\n // emitContext IS the AGENTS.md content (already emitted in step 1) →\n // no separate context file. Rules are carried by AGENTS.md's\n // `@.noir/rules/RULES.md` import (agents-md/cursor/opencode share that\n // universal surface — NO host emits a separate rules file).\n break;\n }\n\n // 3. Host MCP config. Claude keeps the template path (byte-identical parity\n // gate); other hosts use adapter.emitMcpConfig directly.\n const mcpAbs = adapter.mcpConfigPath?.(ectx) ?? join(ctx.root, '.mcp.json');\n const mcpRel = hostRel(mcpAbs, ctx.root);\n if (host === 'claude') {\n const mcpTemplate =\n ctx.transport === 'streamable-http' ? 'mcp.http.json.tmpl' : 'mcp.stdio.json.tmpl';\n entries.push({\n path: mcpRel,\n mode: 'regenerate',\n host,\n template: mcpTemplate,\n description: 'host MCP server pointer',\n });\n } else {\n const mcpContent = `${adapter.emitMcpConfig(ectx, {\n transport: ctx.transport,\n ...(ctx.url !== undefined ? { url: ctx.url } : {}),\n })}\\n`;\n entries.push({\n path: mcpRel,\n mode: 'regenerate',\n host,\n content: mcpContent,\n description: `${host} MCP server pointer`,\n });\n }\n\n return entries;\n}\n\n/** Convert an absolute path under `root` to a repo-relative POSIX string (the\n * manifest's path shape). Throws if `abs` is NOT under `root` so a future\n * adapter that returns a stray path fails loudly instead of producing a\n * malformed manifest entry. */\nfunction hostRel(abs: string, root: string): string {\n const rel = relative(root, abs);\n if (rel.length === 0 || rel.startsWith('..') || rel.startsWith('/')) {\n throw new Error(`buildHostArtifacts: path '${abs}' is not under root '${root}'`);\n }\n // Normalize any platform separators to POSIX (manifest paths are POSIX).\n return rel.replace(/\\\\/g, '/');\n}\n","import { existsSync, readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport type { MigrationResult, MigrationScript } from './types.js';\n\nexport { runMigrations } from './runner.js';\nexport type { MigrationContext, MigrationResult, MigrationScript } from './types.js';\n\n/**\n * Migration registry — the linear history of scaffold-version upgrades.\n *\n * At v1.0.0 there are no real migrations (the package ships fresh, so every\n * install starts at `CURRENT_SCAFFOLD_VERSION`). The registry is the\n * deliverable: it proves the runner end-to-end and gives the next contributor\n * a copy-pasteable template. Add the first real entry here when a template or\n * manifest change merits a `1.0.0 → 1.1.0` step.\n *\n * Convention:\n * - `from`/`to` are bare `x.y.z` (no `v` prefix, no pre-release); the runner\n * compares them numerically.\n * - Every `run` MUST be idempotent and non-throwing (capture failures into\n * `result.conflicts`). See {@link types.ts}.\n * - Conflict resolution writes git-style markers inline — see\n * {@link applyWithConflict} for the canonical helper.\n */\n\n/** Synthetic 1.0.0 → 1.0.0 migration. Proves the runner wires up; also\n * demonstrates the conflict-marker path with a guarded, idempotent touch on\n * `.noir/scaffold-version` only when explicitly asked via\n * `NOIR_TEST_FORCE_CONFLICT`. Safe to remove once a real migration lands. */\nconst synthetic: MigrationScript = {\n from: '1.0.0',\n to: '1.0.0',\n description: 'no-op synthetic migration (runner smoke test)',\n run: (ctx) => {\n const result: MigrationResult = { changed: [], conflicts: [], notes: [] };\n // The only \"real\" thing it does: when the env var is set, write a conflict\n // marker into `.noir/scaffold-version` so the runner's conflict plumbing is\n // exercised by tests. In normal operation this branch never fires and the\n // script is a true no-op.\n if (process.env.NOIR_TEST_FORCE_CONFLICT === '1' && !ctx.dryRun) {\n const file = join(ctx.root, '.noir', 'scaffold-version');\n if (existsSync(file)) {\n const prev = readFileSync(file, 'utf8');\n const merged = applyInlineConflict(prev, 'noir-scaffold=1.0.0\\n', 'ours', 'theirs');\n writeFileSync(file, merged, 'utf8');\n result.conflicts.push('.noir/scaffold-version');\n }\n }\n result.notes.push('synthetic 1.0.0→1.0.0 migration ran');\n return result;\n },\n};\n\nexport const MIGRATIONS: readonly MigrationScript[] = [synthetic];\n\n// --- conflict-marker helpers (exported for migration authors) ---------------\n\n/** Write git-style inline conflict markers around `theirs`/`ours` so a human\n * or AI agent can resolve later. This is the CI-safe fallback the spec locks\n * in (S-OQ2) — no interactive prompts, ever. */\nexport function applyInlineConflict(\n ours: string,\n theirs: string,\n oursLabel = 'ours',\n theirsLabel = 'theirs',\n): string {\n return `<<<<<<< ${oursLabel}\\n${ours}=======\\n${theirs}>>>>>>> ${theirsLabel}\\n`;\n}\n\n/** Apply `(ours, theirs)` to a region: if they're equal, return `ours` (no\n * conflict); otherwise emit inline markers. Migration authors should prefer\n * this over {@link applyInlineConflict} when the \"no change needed\" case is\n * common — it keeps re-runs truly idempotent (no spurious markers on a clean\n * tree). */\nexport function applyWithConflict(\n ours: string,\n theirs: string,\n path: string,\n): {\n text: string;\n conflicted: boolean;\n} {\n if (ours === theirs) return { text: ours, conflicted: false };\n return {\n text: applyInlineConflict(ours, theirs, path, path),\n conflicted: true,\n };\n}\n","import { MIGRATIONS } from './index.js';\nimport type { MigrationContext, MigrationResult, MigrationScript } from './types.js';\n\n/**\n * Migration runner. Given a `from` version present on disk and a target `to`\n * version (usually {@link CURRENT_SCAFFOLD_VERSION}), walks the\n * {@link MIGRATIONS} registry forward in version order, executing each step.\n *\n * Selection rule: a script runs when its `from` is `>= fromArg` (semver-ish\n * string compare is enough at Noir's scale; we don't pull in a semver dep for\n * this) AND its `to` is `<= toArg`. Scripts strictly outside the window are\n * skipped. Within the window, scripts run sorted by `to` ascending so a\n * multi-step upgrade (1.0.0 → 1.1.0 → 1.2.0) composes in order.\n *\n * The runner NEVER throws on a per-script failure — it captures the error,\n * records a synthetic conflict entry (`<path>__error`), and continues so a\n * single broken migration doesn't block the rest of the chain. The orchestrator\n * decides whether non-empty `conflicts` is a hard failure (init --upgrade) or a\n * warning (doctor).\n *\n * Returns the aggregate of every step's `changed`/`conflicts`/`notes`.\n */\nexport function runMigrations(\n root: string,\n from: string | null,\n to: string,\n opts: { dryRun?: boolean } = {},\n): MigrationResult & { from: string | null; to: string; ran: string[] } {\n const window = pickWindow(from ?? '0.0.0', to, MIGRATIONS);\n const ctx: MigrationContext = { root, dryRun: opts.dryRun === true };\n const aggregate: MigrationResult = { changed: [], conflicts: [], notes: [] };\n const ran: string[] = [];\n\n for (const script of window) {\n ran.push(`${script.from}→${script.to}`);\n let res: MigrationResult;\n try {\n res = script.run(ctx);\n } catch (err) {\n // Non-fatal at the runner level: record and continue. The caller turns\n // non-empty conflicts into a CI failure with a real exit code.\n const msg = err instanceof Error ? err.message : String(err);\n aggregate.conflicts.push(`<runner>:${script.from}→${script.to} threw: ${msg}`);\n continue;\n }\n aggregate.changed.push(...res.changed);\n aggregate.conflicts.push(...res.conflicts);\n aggregate.notes.push(...res.notes);\n }\n\n return {\n ...aggregate,\n from,\n to,\n ran,\n };\n}\n\n/** Pick the ordered subset of `scripts` forming the chain `[from, to]`. */\nfunction pickWindow(\n from: string,\n to: string,\n scripts: readonly MigrationScript[],\n): MigrationScript[] {\n return scripts\n .filter((s) => compareVer(s.from) >= compareVer(from) && compareVer(s.to) <= compareVer(to))\n .sort((a, b) => compareVer(a.to) - compareVer(b.to));\n}\n\n/** Tiny numeric tuple compare: `'1.10.3'` → `[1,10,3]`, compared element-wise.\n * Pre-release suffixes (e.g. `-beta.1`) are ignored — Noir ships `x.y.z`\n * scaffold versions and the runner only needs a deterministic order. */\nfunction compareVer(v: string): number {\n const parts = v.split('-')[0]?.split('.') ?? [];\n let n = 0;\n for (let i = 0; i < 3; i++) {\n const p = Number(parts[i] ?? '0');\n n = n * 1000 + (Number.isFinite(p) ? p : 0);\n }\n return n;\n}\n","import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { createProjectId, paths } from '@noir-ai/core';\nimport {\n type BuildManifestContext,\n buildManifest,\n type HostTag,\n type ManifestEntry,\n} from './manifest.js';\nimport { runMigrations } from './migrations/index.js';\nimport {\n CURRENT_SCAFFOLD_VERSION,\n readScaffoldVersion,\n writeScaffoldVersion,\n} from './scaffold-version.js';\nimport { detectStack, type StackInfo } from './stack-detect.js';\nimport { render } from './template.js';\nimport { loadTemplate } from './template-loader.js';\nimport {\n buildRegion,\n managedBlock,\n managedBlocks,\n regenerate,\n skipIfExists,\n type WriteMode,\n} from './writers.js';\n\n/**\n * Scaffold orchestrator. One function the cli (S-T2) calls for `noir init`,\n * `noir create`, and `noir sync`; mode selects the manifest subset + how\n * project identity is resolved.\n *\n * High-level flow:\n * 1. Resolve project id (provided > existing > generated; sync requires existing).\n * 2. Detect stack (READ-ONLY; always runs; never throws).\n * 3. If {@link ScaffoldOptions.upgrade}, run migrations from the on-disk\n * scaffold-version → {@link CURRENT_SCAFFOLD_VERSION}.\n * 4. Build the manifest, filter by host + mode.\n * 5. For each entry: mkdir -p, render template/content, dispatch to the\n * matching writer.\n * 6. On `init`/`create`, stamp `.noir/scaffold-version` LAST so a crash leaves\n * an old/absent stamp rather than a misleading fresh one.\n *\n * The orchestrator owns dir creation (writers refuse to so that a missing dir\n * is one attributable failure, not N silent ones).\n */\n\nexport type ScaffoldMode = 'init' | 'create' | 'sync';\n\nexport interface ScaffoldOptions {\n /** Absolute repo root. For `create`, the new dir (created if absent). */\n root: string;\n mode: ScaffoldMode;\n /** Target host. Defaults to `'claude'` (the only shipped host). */\n host?: HostTag;\n /** MCP transport. Defaults to `'stdio'`. */\n transport?: 'stdio' | 'streamable-http';\n /** Required when transport is `streamable-http`. */\n url?: string;\n /** Explicit project id; bypasses generate/read. Mainly for tests + `create`\n * flows that want deterministic ids. */\n projectId?: string;\n /** `noir init --upgrade`: run migrations before re-emitting, and emit only\n * regenerate + managedBlock (skipIfExists left alone). Only meaningful\n * with mode `'init'`. */\n upgrade?: boolean;\n /** Preview: compute the same written/skipped/migrated lists without touching\n * disk. `noir doctor`/CI use this to report drift. */\n dryRun?: boolean;\n}\n\nexport interface ScaffoldResult {\n /** Repo-relative paths actually written. */\n written: string[];\n /** Repo-relative paths skipIfExists'd (already present). */\n skipped: string[];\n /** Migration steps executed (`<from>→<to>`), when upgrade ran. */\n migrationsRan: string[];\n /** Migration conflicts (repo-relative or `<runner>:…`), when upgrade ran. */\n migrationConflicts: string[];\n stack: StackInfo;\n projectId: string;\n fromVersion: string | null;\n toVersion: string;\n /** The host actually emitted (post-default). */\n host: HostTag;\n}\n\nconst WRITER_BY_MODE: Record<WriteMode, 'all' | 'runtime'> = {\n // 'runtime' subset = regenerate + managedBlock (the always-safe-to-rewrite\n // entries). sync + init --upgrade emit only this subset; skipIfExists is\n // reserved for first-run init/create so user edits survive.\n regenerate: 'runtime',\n managedBlock: 'runtime',\n skipIfExists: 'all',\n};\n\nexport async function scaffold(opts: ScaffoldOptions): Promise<ScaffoldResult> {\n const host: HostTag = opts.host ?? 'claude';\n const transport: BuildManifestContext['transport'] = opts.transport ?? 'stdio';\n if (transport === 'streamable-http' && !opts.url) {\n throw new Error(\"transport 'streamable-http' requires opts.url\");\n }\n\n // 1. Root for `create` may not exist yet — mirror `init.ts`'s mkdir of .noir/.\n if (opts.mode === 'create' && !opts.dryRun) {\n mkdirSync(opts.root, { recursive: true });\n }\n\n // 2. Resolve project id. Read the on-disk stamp ONCE and reuse the result\n // for the corrupt-file heal below (C1). sync requires a VALID existing id.\n const idFile = readProjectIdFile(opts.root);\n const projectId = resolveProjectId(opts, idFile);\n\n // C1: a `project.id` that EXISTS but is empty/unparseable is CORRUPT, not\n // absent. The manifest writes project.id via `skipIfExists`, which would\n // preserve the empty file while NOIR.md's BRIEF_BLOCK renders the freshly\n // resolved/generated id → silent identity split (NOIR.md states an id the\n // store DB can't open). project.id is Noir-owned canonical; heal a corrupt\n // stamp by removing it so `skipIfExists` writes the resolved id fresh.\n // Absent/valid files and dryRun are left to the manifest writer.\n if (!opts.dryRun && idFile.state === 'corrupt') {\n rmSync(paths.projectId(opts.root), { force: true });\n }\n\n const fromVersion = readScaffoldVersion(opts.root);\n\n // 3. Stack detect (read-only, never throws). Always populated so callers\n // (TUI, doctor) get a single source of truth regardless of mode.\n const stack = detectStack(opts.root);\n\n // 4. Migrations (only when explicitly upgrading). M4: a fresh project\n // (`fromVersion === null`) has NO prior stamp → nothing to migrate. Skip\n // entirely so `noir init --upgrade` on a never-initialized tree doesn't\n // report a synthetic no-op `1.0.0→1.0.0` step.\n const migrationsRan: string[] = [];\n const migrationConflicts: string[] = [];\n if (opts.mode === 'init' && opts.upgrade === true && fromVersion !== null) {\n const m = runMigrations(opts.root, fromVersion, CURRENT_SCAFFOLD_VERSION, {\n dryRun: opts.dryRun === true,\n });\n migrationsRan.push(...m.ran);\n migrationConflicts.push(...m.conflicts);\n }\n\n // 5. Build manifest + filter by host + mode.\n const manifest = buildManifest({ root: opts.root, projectId, host, transport, url: opts.url });\n const emitRuntimeOnly = opts.mode === 'sync' || (opts.mode === 'init' && opts.upgrade === true);\n const vars: BuildManifestContext = {\n root: opts.root,\n projectId,\n host,\n transport,\n url: opts.url,\n };\n\n const written: string[] = [];\n const skipped: string[] = [];\n\n // GROUP applicable entries by target path (manifest order preserved within\n // each group) so files carrying MULTIPLE managed blocks (CLAUDE.md today =\n // CONTEXT + RULES) get ONE atomic multi-region write (I1). Single-entry\n // paths keep the existing per-entry writer — byte-stable for the NOIR.md\n // brief, the ignore files, and the regenerated `.mcp.json`. dryRun uses the\n // SAME grouping so its reported paths match what a real run would write\n // (CLAUDE.md reported once, not twice).\n const groups = groupApplicableByPath(manifest, host, emitRuntimeOnly);\n for (const [relPath, entries] of groups) {\n const abs = join(opts.root, relPath);\n const managed = entries.filter((e) => e.mode === 'managedBlock');\n\n if (managed.length >= 2) {\n // Multi-managed-block file: ONE atomic write of all regions.\n if (opts.dryRun) {\n written.push(relPath);\n continue;\n }\n mkdirSync(dirname(abs), { recursive: true });\n const regions = managed.map((e) => {\n const block = e.block;\n if (!block) {\n throw new Error(`manifest entry ${e.path}: managedBlock mode missing 'block'`);\n }\n return { block, regionText: buildRegion(block, renderEntry(e, vars)) };\n });\n managedBlocks(abs, regions);\n written.push(relPath);\n continue;\n }\n\n // Per-entry path: single-region managed / regenerate / skipIfExists.\n if (!opts.dryRun) mkdirSync(dirname(abs), { recursive: true });\n for (const entry of entries) {\n if (opts.dryRun) {\n if (entry.mode === 'skipIfExists' && existsSync(abs)) skipped.push(entry.path);\n else written.push(entry.path);\n continue;\n }\n const body = renderEntry(entry, vars);\n if (entry.mode === 'regenerate') {\n regenerate(abs, body);\n written.push(entry.path);\n } else if (entry.mode === 'managedBlock') {\n const block = entry.block;\n if (!block) {\n throw new Error(`manifest entry ${entry.path}: managedBlock mode missing 'block'`);\n }\n // I2: a legacy (pre-Slice-S) .noir/NOIR.md is a whole-file auto-brief\n // with NO managed markers. The normal path would treat the old brief\n // as user content and append a SECOND managed brief → two \"Project\n // id:\" lines. Self-heal: when the existing file has NO noir managed\n // marker at all, wipe it first so the managed write emits a clean\n // single brief. (Pre-Slice-S NOIR.md was 100% auto-generated, so\n // there is no user content to preserve in that legacy shape.)\n if (isNoirMdPath(entry.path)) healLegacyNoirMd(abs);\n managedBlock(abs, block, buildRegion(block, body));\n written.push(entry.path);\n } else {\n const out = skipIfExists(abs, body);\n if (out.written) written.push(entry.path);\n else skipped.push(entry.path);\n }\n }\n }\n\n // 6. Stamp scaffold-version on init/create (NOT sync). Written last so a\n // crash leaves the previous stamp. Upgrade rewrites it to current.\n if ((opts.mode === 'init' || opts.mode === 'create') && !opts.dryRun) {\n writeScaffoldVersion(opts.root, CURRENT_SCAFFOLD_VERSION);\n }\n\n return {\n written,\n skipped,\n migrationsRan,\n migrationConflicts,\n stack,\n projectId,\n fromVersion,\n toVersion: CURRENT_SCAFFOLD_VERSION,\n host,\n };\n}\n\n// --- helpers -----------------------------------------------------------------\n\n/** Read the `.noir/project.id` stamp ONCE and classify it for BOTH id\n * resolution and the C1 corrupt-file heal. `absent` (ENOENT) and `valid`\n * (non-empty) are the normal cases; `corrupt` (file exists but trims to empty)\n * is healed by the orchestrator before the manifest loop so `skipIfExists`\n * writes the resolved id fresh instead of preserving the empty file. */\nfunction readProjectIdFile(\n root: string,\n): { state: 'absent'; id: null } | { state: 'valid'; id: string } | { state: 'corrupt'; id: null } {\n let raw: string;\n try {\n raw = readFileSync(paths.projectId(root), 'utf8');\n } catch {\n return { state: 'absent', id: null };\n }\n const trimmed = raw.trim();\n return trimmed.length > 0 ? { state: 'valid', id: trimmed } : { state: 'corrupt', id: null };\n}\n\nfunction resolveProjectId(\n opts: ScaffoldOptions,\n idFile: ReturnType<typeof readProjectIdFile>,\n): string {\n if (opts.projectId !== undefined) return opts.projectId;\n if (idFile.state === 'valid') return idFile.id;\n // absent OR corrupt → resolve a fresh id. sync still requires a valid\n // pre-existing id (a corrupt stamp can't be trusted to name the store DB).\n if (opts.mode === 'sync') {\n throw new Error(`Noir is not initialized in ${opts.root}. Run \\`noir init\\` first.`);\n }\n return createProjectId();\n}\n\n/** Group applicable manifest entries by target path, preserving manifest order\n * within each group. JS `Map` preserves insertion order, so iterating groups\n * visits paths in the same sequence the manifest declares them (CONTEXT before\n * RULES inside the CLAUDE.md group). */\nfunction groupApplicableByPath(\n manifest: readonly ManifestEntry[],\n host: HostTag,\n emitRuntimeOnly: boolean,\n): Map<string, ManifestEntry[]> {\n const groups = new Map<string, ManifestEntry[]>();\n for (const entry of manifest) {\n if (entry.host !== undefined && entry.host !== host) continue; // host filter\n if (emitRuntimeOnly && WRITER_BY_MODE[entry.mode] !== 'runtime') continue;\n const list = groups.get(entry.path);\n if (list) list.push(entry);\n else groups.set(entry.path, [entry]);\n }\n return groups;\n}\n\n/** True for the canonical NOIR.md path the manifest emits (the BRIEF_BLOCK\n * target). Scoped so the I2 legacy-heal only fires for that one file — we must\n * not wipe arbitrary co-owned managed files. */\nfunction isNoirMdPath(relPath: string): boolean {\n return relPath === '.noir/NOIR.md';\n}\n\n/** I2 self-heal: wipe a legacy (pre-Slice-S) NOIR.md before the managed write.\n * Legacy shape = file exists but contains NO `<!-- noir:<name> begin -->`\n * managed marker (the whole file was the auto-brief). Files that already have\n * markers, or are absent, are left untouched (normal managed-block path or\n * fresh write respectively). */\nfunction healLegacyNoirMd(absPath: string): void {\n let content: string;\n try {\n content = readFileSync(absPath, 'utf8');\n } catch {\n return; // absent — fresh write, nothing to heal\n }\n if (/<!-- noir:[a-z]+ begin -->/.test(content)) return; // already managed-shape\n rmSync(absPath, { force: true });\n}\n\nfunction renderEntry(entry: ManifestEntry, vars: BuildManifestContext): string {\n if (entry.template !== undefined) {\n return render(loadTemplate(entry.template), vars);\n }\n if (entry.content !== undefined) return entry.content;\n throw new Error(`manifest entry ${entry.path}: must define 'content' or 'template'`);\n}\n","import { mkdirSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { NOIR_DIR } from '@noir-ai/core';\nimport { regenerate } from './writers.js';\n\n/**\n * Scaffold version stamp — Noir's equivalent of Copier's `last-applied`.\n * Stored at `.noir/scaffold-version` as a single `noir-scaffold=<semver>` line\n * so `noir init --upgrade` / `noir doctor` can diff against\n * {@link CURRENT_SCAFFOLD_VERSION} and decide which migrations to run.\n *\n * Format is line-oriented (not YAML) on purpose: it must be readable before\n * `config.yml` is parsed (doctor runs even with a broken config), and the\n * `key=value` shape is trivial to grep from a shell.\n */\n\n/** The scaffold version this build of @noir-ai/create ships. Bumped atomically\n * whenever a manifest entry, template, or migration changes shape. */\nexport const CURRENT_SCAFFOLD_VERSION = '1.0.0';\n\nconst PREFIX = 'noir-scaffold=';\n\n/** Path to the stamp file under `root`. Exposed for tests + doctor. */\nexport function scaffoldVersionPath(root: string): string {\n return join(root, NOIR_DIR, 'scaffold-version');\n}\n\n/** Read the applied scaffold version, or `null` if the stamp is absent/unparseable.\n * Never throws — doctor must keep reporting even on a malformed stamp. */\nexport function readScaffoldVersion(root: string): string | null {\n let raw: string;\n try {\n raw = readFileSync(scaffoldVersionPath(root), 'utf8');\n } catch {\n return null;\n }\n for (const line of raw.split('\\n')) {\n const trimmed = line.trim();\n if (trimmed.startsWith(PREFIX)) {\n const v = trimmed.slice(PREFIX.length).trim();\n if (v.length > 0) return v;\n }\n }\n return null;\n}\n\n/** Write the stamp, creating `.noir/` if needed. The orchestrator writes it\n * LAST so a crash mid-scaffold leaves an old/absent stamp rather than a\n * misleading fresh one.\n *\n * N2: routed through the package's atomic `regenerate()` writer (tmp+rename in\n * the same dir) for consistency with the rest of the engine's durable writes —\n * a half-written stamp would mislead `noir doctor`/`init --upgrade`, so the\n * stamp deserves the same crash-atomicity as `.mcp.json` and the NOIR.md brief. */\nexport function writeScaffoldVersion(root: string, version: string): void {\n const file = scaffoldVersionPath(root);\n mkdirSync(dirname(file), { recursive: true });\n regenerate(file, `${PREFIX}${version}\\n`);\n}\n","import {\n closeSync,\n existsSync,\n openSync,\n readFileSync,\n renameSync,\n rmSync,\n writeFileSync,\n writeSync,\n} from 'node:fs';\nimport { basename, dirname, join } from 'node:path';\nimport type { ManagedBlock } from '@noir-ai/core';\nimport { stripManagedBlock, writeManagedRegion } from '@noir-ai/core';\n\n/**\n * The three-mode writer — generalizes keystone-K's `writeManagedRegion` into\n * the declarative dispatch the scaffold manifest drives. Each mode maps 1:1 to\n * an artifact class in the spec §4.5 matrix:\n *\n * - {@link regenerate} — pure pointers (`.mcp.json`, `NOIR.md` brief, …).\n * Always overwritten, atomically.\n * - {@link managedBlock} — co-owned files (`CLAUDE.md` context/rules,\n * `.gitignore` noir block, …). DELEGATES to\n * keystone-K's `writeManagedRegion` so user content\n * outside the markers is preserved byte-for-byte and\n * re-runs are idempotent. Never duplicate the\n * managed-region logic.\n * - {@link skipIfExists} — user-owned seeds (`RULES.md`, `config.yml`,\n * `project.id`). Write once; never clobber.\n *\n * The orchestrator (`scaffold.ts`) is the only intended caller; the per-mode\n * functions are exported so the cli (S-T2) and tests can drive them directly\n * when a one-off write is needed outside the manifest.\n */\n\nexport type WriteMode = 'regenerate' | 'managedBlock' | 'skipIfExists';\n\nexport interface WriteOutcome {\n /** The absolute path that was written. */\n path: string;\n mode: WriteMode;\n /** true when bytes hit disk; false for skipIfExists no-ops. regenerate and\n * managedBlock always write (managedBlock may write identical bytes — that\n * still counts as a write for telemetry purposes; the file IS up to date). */\n written: boolean;\n}\n\n/** Atomic overwrite. Writes to `<file>.tmp.<pid>.<rnd>` in the same directory,\n * fsyncs, then renames over the target so a crash never leaves a half-written\n * file (the pointer files this is used for are read by the host agent first —\n * a truncated CLAUDE.md/.mcp.json would break the very startup Noir serves).\n *\n * Parent directories are NOT created here — the orchestrator does that once\n * for the whole manifest so a missing dir is a single, attributable failure\n * rather than N silent ones inside the writer. */\nexport function regenerate(absPath: string, content: string): WriteOutcome {\n const dir = dirname(absPath);\n const tmp = join(\n dir,\n `.${basename(absPath)}.tmp.${process.pid}.${Math.random().toString(36).slice(2)}`,\n );\n // Open with 'w' truncates; writeSync + closeSync before rename so the bytes\n // are durable pre-swap. O_SYNC would be stronger but is platform-flaky; the\n // rename is the real atomicity guarantee on POSIX (and practical-enough on\n // the win32 targets Noir supports).\n //\n // M1: the tmp MUST be cleaned up on EVERY exit path. The previous shape only\n // ran `rmSync(tmp)` when `renameSync` threw, so a `writeSync` failure (disk\n // full, EPERM, …) left the tmp behind. A single try/finally with `force:true`\n // rmSync (no-op ENOENT after a successful rename consumed the file) covers\n // both.\n let fd: number | undefined;\n try {\n fd = openSync(tmp, 'w');\n writeSync(fd, content, 0, 'utf8');\n closeSync(fd);\n fd = undefined; // closed cleanly — don't re-close in finally\n renameSync(tmp, absPath);\n } finally {\n if (fd !== undefined) {\n try {\n closeSync(fd);\n } catch {\n /* best-effort: the rename/rm below are the meaningful cleanups */\n }\n }\n try {\n rmSync(tmp, { force: true });\n } catch {\n /* best-effort */\n }\n }\n return { path: absPath, mode: 'regenerate', written: true };\n}\n\n/** Re-emit a managed region, delegating to keystone-K's `writeManagedRegion`.\n * `regionText` MUST already include the begin/end markers (matches the shape\n * `writeManagedRegion` expects and that `IGNORE_BLOCK`/`CONTEXT_BLOCK`\n * callers build in core/cli). Use {@link buildRegion} to assemble it from a\n * block + body. */\nexport function managedBlock(\n absPath: string,\n block: ManagedBlock,\n regionText: string,\n): WriteOutcome {\n writeManagedRegion(absPath, block, regionText);\n return { path: absPath, mode: 'managedBlock', written: true };\n}\n\n/** Atomically (re)emit MULTIPLE managed regions into the SAME file in one\n * pass. Used when a co-owned target carries more than one managed block —\n * today only `CLAUDE.md` (CONTEXT + RULES).\n *\n * WHY this exists (I1): calling {@link managedBlock} twice on the same file is\n * NOT byte-idempotent. The 2nd call strips ONLY its own block, treats the 1st\n * region (and the `\\n\\n` separator) as user content, `trimEnd`s it, and\n * re-appends a fresh `\\n\\n` separator. Re-runs therefore accumulate ~2 leading\n * `\\n` bytes per init (verified: 158→168 over 5 runs). Doing both regions in a\n * SINGLE read → strip-all → append-all pass removes the interleaving: after\n * stripping BOTH blocks the only thing left is real user content, so re-runs\n * produce identical bytes.\n *\n * Strategy:\n * 1. Read the file (missing → empty).\n * 2. Strip EVERY named block (via core's `stripManagedBlock`, in the given\n * order) — what remains is user content + any managed blocks outside this\n * group.\n * 3. Append all `regionText`s in the GIVEN ORDER, joined by a single `\\n`.\n * Each `regionText` already ends with `\\n` (buildRegion appends the end\n * marker's trailing newline), so `\\n` between regions yields exactly one\n * blank-line separator (`END\\n` + `\\n` + `BEGIN`) — byte-identical to what\n * the single-block path emits on a first run, so the CONTEXT/RULES parity\n * gates against `claudeAdapter.emitContext/emitRules` keep passing.\n *\n * Single-region files (NOIR.md brief, ignore files) do NOT route through here\n * — the orchestrator only calls this for groups of ≥2 managed blocks, so\n * single-region byte-stability (delegated to keystone-K `writeManagedRegion`)\n * is unchanged. */\nexport function managedBlocks(\n absPath: string,\n regions: ReadonlyArray<{ block: ManagedBlock; regionText: string }>,\n): WriteOutcome {\n if (regions.length === 0) {\n throw new Error('managedBlocks requires at least one region');\n }\n if (regions.length === 1) {\n const only = regions[0];\n if (!only) throw new Error('managedBlocks: undefined region');\n return managedBlock(absPath, only.block, only.regionText);\n }\n let content = '';\n try {\n content = readFileSync(absPath, 'utf8');\n } catch {\n /* missing → treat as empty */\n }\n let stripped = content;\n for (const r of regions) {\n stripped = stripManagedBlock(stripped, r.block);\n }\n const regionsJoined = regions.map((r) => r.regionText).join('\\n');\n // Whitespace-only remainder (typical on re-run after both blocks are\n // stripped) → emit just the regions, no leading separator.\n const next =\n stripped.trim().length > 0 ? `${stripped.trimEnd()}\\n\\n${regionsJoined}` : regionsJoined;\n writeFileSync(absPath, next, 'utf8');\n return { path: absPath, mode: 'managedBlock', written: true };\n}\n\n/** Assemble `<begin>\\n<body>\\n<end>\\n` for a managed block. Centralized here so\n * every caller (manifest rendering, tests, future migrations) produces the\n * exact byte shape `writeManagedRegion` strips/expects. The trailing newline\n * is part of the contract — `stripManagedBlock`'s regex eats a trailing `\\n`\n * so re-runs stay idempotent instead of accumulating blank lines.\n *\n * `body` is `trimEnd()`-ed before wrapping so template authors can keep the\n * conventional trailing newline in `.tmpl` files without producing a\n * double-newline before the end marker. This keeps the output byte-identical\n * to `claudeAdapter.emitContext`/`emitRules` and core's `syncIgnores`, which\n * S-T2 relies on for a diff-free refactor. */\nexport function buildRegion(block: ManagedBlock, body: string): string {\n return `${block.begin}\\n${body.trimEnd()}\\n${block.end}\\n`;\n}\n\n/** Write `content` to `absPath` only if no file exists there. Returns whether\n * bytes were written. Parent dirs are NOT created (orchestrator's job). */\nexport function skipIfExists(absPath: string, content: string): WriteOutcome {\n if (existsSync(absPath)) {\n return { path: absPath, mode: 'skipIfExists', written: false };\n }\n writeFileSync(absPath, content, 'utf8');\n return { path: absPath, mode: 'skipIfExists', written: true };\n}\n","import { existsSync, readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\n/**\n * READ-ONLY stack detection. Probes for well-known marker files under `root`\n * and reports ONLY what is present — never assumes. The result feeds path\n * adaptation (where to drop `.claude/` vs `.cursor/` etc., future), ignore-file\n * selection, and the onboarding TUI's confirm step.\n *\n * Design rules (spec §4.5):\n * - Never throws. A foreign/empty dir returns `{ languages: [], monorepo:\n * false, frameworks: [], packageManager: null }`.\n * - Never opens network, never parses code beyond a `package.json`/`pyproject`\n * dependency list. Marker files + top-level manifests only.\n * - Frameworks are reported only when both the marker file is present AND the\n * framework's dependency is listed (avoids false positives from a stale\n * `package.json`).\n */\n\nexport interface StackInfo {\n /** Lower-cased language ids found via marker files:\n * `typescript` | `javascript` | `python` | `go` | `rust`. */\n languages: string[];\n /** True when a workspace manifest is present (pnpm-workspace, npm/yarn\n * workspaces in package.json, turbo.json, nx.json). */\n monorepo: boolean;\n /** Lower-cased framework ids (e.g. `next`, `vite`, `express`, `fastapi`,\n * `actix`). Empty when none detected. */\n frameworks: string[];\n /** `pnpm` | `npm` | `yarn` when a lockfile is present, else null. */\n packageManager: string | null;\n}\n\n/** Frameworks looked up against `package.json#dependencies`+`devDependencies`.\n * Keyed by the dependency name as published on npm. */\nconst NODE_FRAMEWORKS: ReadonlyArray<[dep: string, id: string]> = [\n ['next', 'next'],\n ['vite', 'vite'],\n ['express', 'express'],\n ['fastify', 'fastify'],\n ['nuxt', 'nuxt'],\n ['remix', 'remix'],\n ['@sveltejs/kit', 'sveltekit'],\n ['@angular/core', 'angular'],\n ['react', 'react'],\n ['vue', 'vue'],\n];\n\n/** Frameworks looked up against `[project] dependencies` /\n * `[project.optional-dependencies]` / `[tool.poetry.dependencies]` in\n * `pyproject.toml`. Keyed by the PyPI package name. */\nconst PYTHON_FRAMEWORKS: ReadonlyArray<[dep: string, id: string]> = [\n ['fastapi', 'fastapi'],\n ['flask', 'flask'],\n ['django', 'django'],\n ['sanic', 'sanic'],\n ['starlette', 'starlette'],\n ['tornado', 'tornado'],\n ['aiohttp', 'aiohttp'],\n ['bottle', 'bottle'],\n ['pyramid', 'pyramid'],\n ['falcon', 'falcon'],\n];\n\n/** Read+parse JSON without throwing; returns undefined on any error. JSON5/ESM\n * `package.json` with comments would land here too — `package.json` is plain\n * JSON in practice so a failed `JSON.parse` genuinely means \"not a node\n * project\" or \"broken file\", both of which we report as \"absent\". */\nfunction readJson<T = unknown>(file: string): T | undefined {\n try {\n return JSON.parse(readFileSync(file, 'utf8')) as T;\n } catch {\n return undefined;\n }\n}\n\ninterface PackageJson {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n workspaces?: string[] | { packages?: string[] };\n packageManager?: string;\n}\n\nexport function detectStack(root: string): StackInfo {\n const languages = new Set<string>();\n const frameworks = new Set<string>();\n let monorepo = false;\n let packageManager: string | null = null;\n\n // Node / JS / TS — gated on package.json presence.\n const pjPath = join(root, 'package.json');\n if (existsSync(pjPath)) {\n const pj = readJson<PackageJson>(pjPath);\n if (pj) {\n const hasTs =\n Boolean(pj.devDependencies?.typescript) || existsSync(join(root, 'tsconfig.json'));\n languages.add(hasTs ? 'typescript' : 'javascript');\n\n const deps = new Set([\n ...Object.keys(pj.dependencies ?? {}),\n ...Object.keys(pj.devDependencies ?? {}),\n ]);\n for (const [dep, id] of NODE_FRAMEWORKS) {\n if (deps.has(dep)) frameworks.add(id);\n }\n\n const ws = pj.workspaces;\n if (\n Array.isArray(ws) ||\n (typeof ws === 'object' && ws !== null && Array.isArray(ws.packages))\n ) {\n monorepo = true;\n }\n if (typeof pj.packageManager === 'string' && pj.packageManager.length > 0) {\n // `packageManager: pnpm@10.12.4` → `pnpm`. Yarn/npm similarly.\n const m = pj.packageManager.split('@')[0];\n if (m) packageManager = m;\n }\n }\n }\n\n // Workspace manifests (these override/confirm monorepo independent of pj).\n if (existsSync(join(root, 'pnpm-workspace.yaml'))) {\n monorepo = true;\n packageManager = packageManager ?? 'pnpm';\n }\n if (existsSync(join(root, 'turbo.json')) || existsSync(join(root, 'nx.json'))) {\n monorepo = true;\n }\n\n // Lockfiles pin packageManager when `package.json#packageManager` didn't.\n if (!packageManager) {\n if (existsSync(join(root, 'pnpm-lock.yaml'))) packageManager = 'pnpm';\n else if (existsSync(join(root, 'yarn.lock'))) packageManager = 'yarn';\n else if (existsSync(join(root, 'package-lock.json'))) packageManager = 'npm';\n }\n\n // Python — pyproject.toml / requirements.txt / Pipfile / setup.py.\n if (\n existsSync(join(root, 'pyproject.toml')) ||\n existsSync(join(root, 'requirements.txt')) ||\n existsSync(join(root, 'Pipfile')) ||\n existsSync(join(root, 'setup.py'))\n ) {\n languages.add('python');\n // Framework detection scans pyproject.toml for PEP 508 dependency names\n // under `[project] dependencies` / `[project.optional-dependencies]` /\n // `[tool.poetry.dependencies]`. A full section-aware TOML parse is overkill\n // at v1 (and would need a dep we don't ship); the boundary-aware text scan\n // in `pyprojectHasDep` is robust to the three formats users actually write\n // and never throws on malformed TOML (degrades to \"no match\").\n if (existsSync(join(root, 'pyproject.toml'))) {\n const raw = safeRead(join(root, 'pyproject.toml'));\n if (raw) {\n for (const [dep, id] of PYTHON_FRAMEWORKS) {\n if (pyprojectHasDep(raw, dep)) frameworks.add(id);\n }\n }\n }\n }\n\n // Go — go.mod.\n if (existsSync(join(root, 'go.mod'))) {\n languages.add('go');\n packageManager = packageManager ?? 'go-modules';\n }\n\n // Rust — Cargo.toml.\n if (existsSync(join(root, 'Cargo.toml'))) {\n languages.add('rust');\n const raw = safeRead(join(root, 'Cargo.toml'));\n if (raw) {\n // M3: Cargo.toml deps are always `name = \"ver\"` or `name = { … }`, so\n // require the `=` after the crate name. The old `/^\\s*actix\\b/m` matched\n // `actix-web` (word boundary between `x` and `-`) and falsely reported\n // `actix`. Treat the hyphenated runtime (`actix-web`) as its OWN id and\n // gate bare `actix` on the equals form. Same equals-form tightening is\n // applied to `axum`/`rocket` so `axum-extra`-style crates don't trip the\n // same bug. The `/m` flag makes `^` match any line (the previous\n // `/^actix\\s*=/` had no `/m` and only matched a string starting with\n // `actix`, i.e. effectively never — dead code).\n if (/^\\s*actix-web\\b/m.test(raw)) frameworks.add('actix-web');\n if (/^\\s*actix\\s*=/m.test(raw)) frameworks.add('actix');\n if (/^\\s*axum\\s*=/m.test(raw)) frameworks.add('axum');\n if (/^\\s*rocket\\s*=/m.test(raw)) frameworks.add('rocket');\n }\n packageManager = packageManager ?? 'cargo';\n }\n\n return {\n languages: [...languages].sort(),\n monorepo,\n frameworks: [...frameworks].sort(),\n packageManager,\n };\n}\n\nfunction safeRead(file: string): string | undefined {\n try {\n return readFileSync(file, 'utf8');\n } catch {\n return undefined;\n }\n}\n\n/** True iff `dep` appears as a PEP 508 dependency name in `pyproject.toml`\n * text — handles PEP 621 `dependencies`/`optional-dependencies` list entries\n * (`\"fastapi\"`, `\"fastapi[all]>=0.100\"`) AND Poetry `[tool.poetry.dependencies]`\n * bare keys (`fastapi = \"^0.100\"`).\n *\n * The `before` boundary (line-start, quote, or whitespace) plus the `after`\n * PEP 508 boundary (whitespace, quote, version specifier `<>=!~`, extras `[`,\n * marker `;`, or end-of-string) prevent substring mismatches — `flask` will\n * NOT match `flask-restful`, and `fastapi` will NOT match `x-fastapi`.\n *\n * Never throws: `dep` is escaped, the regex is constructed from a controlled\n * template, and `String.prototype.test` is total on string input. Malformed\n * TOML simply yields no matches. */\nfunction pyprojectHasDep(raw: string, dep: string): boolean {\n const esc = dep.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const before = `(?:^|[\"'\\\\s])`;\n const after = `(?=\\\\s|[\"'<>=!~;]|\\\\[|$)`;\n return new RegExp(`${before}${esc}${after}`, 'm').test(raw);\n}\n","/**\n * Hand-rolled `{{var}}` interpolation. No mustache/handlebars dependency —\n * Noir's markdown/yaml/json templates are simple enough that a tokenizer +\n * map lookup is sufficient and keeps `@noir-ai/create` dependency-light.\n *\n * Semantics (documented):\n * - Tokens are `{{ name }}` / `{{name}}` — any amount of ASCII whitespace\n * inside the braces is permitted. The name is trimmed.\n * - Known vars (value is a string) are substituted verbatim. NO escaping is\n * applied — callers must pre-escape for the target format (JSON/YAML/MD).\n * This is intentional: the engine renders into multiple formats and a\n * single universal escaper would be wrong for at least one of them.\n * - Unknown vars (not in `vars`, or value `undefined`) → the original token\n * is LEFT IN PLACE. Rationale: drift between template and ctx becomes\n * visible (an unrendered `{{projectId}}` in CLAUDE.md is immediately\n * obvious), rather than silently swallowed to empty. The spec called this\n * out as the preferred failure mode.\n * - Non-string var values are stringified via `String(value)` so a stray\n * number/boolean still interpolates rather than printing `[object Object]`.\n * - A lone `{{` with no closing `}}` is left untouched (not a token).\n */\n\nconst TOKEN = /\\{\\{\\s*([^}{}]+?)\\s*\\}\\}/g;\n\nexport function render(template: string, vars: Record<string, unknown>): string {\n return template.replace(TOKEN, (whole, name: string) => {\n if (!Object.hasOwn(vars, name)) return whole; // unknown → leave token\n const v = vars[name];\n if (v === undefined || v === null) return whole; // treat as unknown → leave token\n return String(v);\n });\n}\n","import { readFileSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\n/**\n * Template loader. Templates ship at `<pkg>/templates/` (see `files` in\n * package.json) and are read at runtime so non-code changes (copy tweaks, new\n * pointers) don't require a rebuild.\n *\n * Path resolution must work identically in three layouts:\n * - source (vitest, tsx): this file at `packages/create/src/template-loader.ts`\n * → `../templates/<name>`.\n * - built (tsup): this file at `packages/create/dist/template-loader.js` →\n * `../templates/<name>` (same relative offset — `dist/` and `src/` are both\n * direct children of the package root, so `../templates` lands correctly).\n * - packed (npm tarball): `templates/` is included per `files`, dist/ layout\n * preserved → same as built.\n *\n * `NOIR_TEMPLATES_DIR` overrides everything (used by tests + downstream packs\n * that want to substitute their own template set without forking the engine).\n */\n\nconst DEFAULT_TEMPLATES_DIR = resolveTemplatesDir();\n\nfunction resolveTemplatesDir(): string {\n const override = process.env.NOIR_TEMPLATES_DIR;\n if (override && override.length > 0) return resolve(override);\n // `import.meta.url` is this module's URL. Going up one level reaches the\n // package root in both source and built layouts (see file header).\n const here = dirname(fileURLToPath(import.meta.url));\n return join(here, '..', 'templates');\n}\n\n/** Read a template's raw text by name (e.g. `noir.md.tmpl`). Throws on missing\n * — an unknown template is a manifest bug and should fail loudly, not render\n * an empty string silently. */\nexport function loadTemplate(name: string): string {\n return readFileSync(join(DEFAULT_TEMPLATES_DIR, name), 'utf8');\n}\n\n/** Resolve a template name to its absolute path (for diagnostics + tests). */\nexport function templatesDir(): string {\n return DEFAULT_TEMPLATES_DIR;\n}\n"],"mappings":";AAAA,SAAS,MAAM,gBAAgB;AAC/B;AAAA,EACE;AAAA,EAEA;AAAA,EAGA;AAAA,OACK;AACP;AAAA,EACE;AAAA,EACA;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA2EA,IAAM,cAA4B,aAAa,SAAS,MAAM;AAMrE,IAAM,IAAI;AAAA,EACR,WAAW,GAAG,QAAQ;AAAA,EACtB,QAAQ,GAAG,QAAQ;AAAA,EACnB,QAAQ,GAAG,QAAQ;AAAA,EACnB,SAAS,GAAG,QAAQ;AACtB;AAIO,IAAM,uBAET;AAAA,EACF,CAAC,EAAE,WAAW,MAAM,SAAS;AAAA,EAC7B,CAAC,EAAE,QAAQ,MAAM,MAAM;AAAA,EACvB,CAAC,EAAE,QAAQ,MAAM,MAAM;AAAA,EACvB,CAAC,EAAE,SAAS,MAAM,OAAO;AAC3B;AAwBO,SAAS,cAAc,KAA4C;AACxE,SAAO,CAAC,GAAG,oBAAoB,GAAG,GAAG,GAAG,mBAAmB,eAAe,IAAI,IAAI,GAAG,GAAG,CAAC;AAC3F;AAMA,SAAS,oBAAoB,KAA4C;AACvE,SAAO;AAAA,IACL;AAAA,MACE,MAAM,EAAE;AAAA,MACR,MAAM;AAAA,MACN,SAAS,GAAG,IAAI,SAAS;AAAA;AAAA,MACzB,aAAa;AAAA,IACf;AAAA,IACA;AAAA,MACE,MAAM,EAAE;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,MACV,aAAa;AAAA,IACf;AAAA,IACA;AAAA,MACE,MAAM,EAAE;AAAA,MACR,MAAM;AAAA,MACN,OAAO;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,IACf;AAAA,IACA;AAAA,MACE,MAAM,EAAE;AAAA,MACR,MAAM;AAAA,MACN,UAAU;AAAA,MACV,aAAa;AAAA,IACf;AAAA;AAAA,IAGA;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,IACf;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,IACf;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,IACf;AAAA,IACA;AAAA,MACE,MAAM;AAAA,MACN,MAAM;AAAA,MACN,OAAO;AAAA,MACP,UAAU;AAAA,MACV,aAAa;AAAA,IACf;AAAA,EACF;AACF;AA4DO,SAAS,mBACd,SACA,KACiB;AACjB,QAAM,OAAoB,EAAE,MAAM,IAAI,KAAK;AAC3C,QAAM,OAAO,QAAQ;AACrB,QAAM,UAA2B,CAAC;AAQlC,QAAM,gBAAgB,SAAS,eAAe,SAAS,YAAY,SAAS;AAC5E,MAAI,eAAe;AACjB,YAAQ,KAAK;AAAA,MACX,MAAM,QAAQ,QAAQ,eAAe,IAAI,KAAK,KAAK,IAAI,MAAM,kBAAkB,GAAG,IAAI,IAAI;AAAA,MAC1F,MAAM;AAAA,MACN;AAAA,MACA,SAAS,aAAa,IAAI;AAAA,MAC1B,aAAa,cAAc,IAAI;AAAA,IACjC,CAAC;AAAA,EACH;AAGA,UAAQ,MAAM;AAAA,IACZ,KAAK;AAIH,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,MAAM;AAAA,QACN;AAAA,QACA,OAAO;AAAA,QACP,UAAU;AAAA,QACV,aAAa;AAAA,MACf,CAAC;AACD,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,MAAM;AAAA,QACN;AAAA,QACA,OAAO;AAAA,QACP,UAAU;AAAA,QACV,aAAa;AAAA,MACf,CAAC;AACD;AAAA,IACF,KAAK;AAMH,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,MAAM;AAAA,QACN;AAAA,QACA,OAAO;AAAA,QACP,SAAS;AAAA,QACT,aAAa;AAAA,MACf,CAAC;AACD,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,MAAM;AAAA,QACN;AAAA,QACA,OAAO;AAAA,QACP,SAAS;AAAA,QACT,aAAa;AAAA,MACf,CAAC;AACD;AAAA,IACF,KAAK;AAAA,IACL,KAAK;AAAA,IACL,KAAK;AAKH;AAAA,EACJ;AAIA,QAAM,SAAS,QAAQ,gBAAgB,IAAI,KAAK,KAAK,IAAI,MAAM,WAAW;AAC1E,QAAM,SAAS,QAAQ,QAAQ,IAAI,IAAI;AACvC,MAAI,SAAS,UAAU;AACrB,UAAM,cACJ,IAAI,cAAc,oBAAoB,uBAAuB;AAC/D,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,MAAM;AAAA,MACN;AAAA,MACA,UAAU;AAAA,MACV,aAAa;AAAA,IACf,CAAC;AAAA,EACH,OAAO;AACL,UAAM,aAAa,GAAG,QAAQ,cAAc,MAAM;AAAA,MAChD,WAAW,IAAI;AAAA,MACf,GAAI,IAAI,QAAQ,SAAY,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC;AAAA,IAClD,CAAC,CAAC;AAAA;AACF,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,MAAM;AAAA,MACN;AAAA,MACA,SAAS;AAAA,MACT,aAAa,GAAG,IAAI;AAAA,IACtB,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAMA,SAAS,QAAQ,KAAa,MAAsB;AAClD,QAAM,MAAM,SAAS,MAAM,GAAG;AAC9B,MAAI,IAAI,WAAW,KAAK,IAAI,WAAW,IAAI,KAAK,IAAI,WAAW,GAAG,GAAG;AACnE,UAAM,IAAI,MAAM,6BAA6B,GAAG,wBAAwB,IAAI,GAAG;AAAA,EACjF;AAEA,SAAO,IAAI,QAAQ,OAAO,GAAG;AAC/B;;;ACnYA,SAAS,YAAY,cAAc,qBAAqB;AACxD,SAAS,QAAAA,aAAY;;;ACqBd,SAAS,cACd,MACA,MACA,IACA,OAA6B,CAAC,GACwC;AACtE,QAAM,SAAS,WAAW,QAAQ,SAAS,IAAI,UAAU;AACzD,QAAM,MAAwB,EAAE,MAAM,QAAQ,KAAK,WAAW,KAAK;AACnE,QAAM,YAA6B,EAAE,SAAS,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,EAAE;AAC3E,QAAM,MAAgB,CAAC;AAEvB,aAAW,UAAU,QAAQ;AAC3B,QAAI,KAAK,GAAG,OAAO,IAAI,SAAI,OAAO,EAAE,EAAE;AACtC,QAAI;AACJ,QAAI;AACF,YAAM,OAAO,IAAI,GAAG;AAAA,IACtB,SAAS,KAAK;AAGZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,gBAAU,UAAU,KAAK,YAAY,OAAO,IAAI,SAAI,OAAO,EAAE,WAAW,GAAG,EAAE;AAC7E;AAAA,IACF;AACA,cAAU,QAAQ,KAAK,GAAG,IAAI,OAAO;AACrC,cAAU,UAAU,KAAK,GAAG,IAAI,SAAS;AACzC,cAAU,MAAM,KAAK,GAAG,IAAI,KAAK;AAAA,EACnC;AAEA,SAAO;AAAA,IACL,GAAG;AAAA,IACH;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAGA,SAAS,WACP,MACA,IACA,SACmB;AACnB,SAAO,QACJ,OAAO,CAAC,MAAM,WAAW,EAAE,IAAI,KAAK,WAAW,IAAI,KAAK,WAAW,EAAE,EAAE,KAAK,WAAW,EAAE,CAAC,EAC1F,KAAK,CAAC,GAAG,MAAM,WAAW,EAAE,EAAE,IAAI,WAAW,EAAE,EAAE,CAAC;AACvD;AAKA,SAAS,WAAW,GAAmB;AACrC,QAAM,QAAQ,EAAE,MAAM,GAAG,EAAE,CAAC,GAAG,MAAM,GAAG,KAAK,CAAC;AAC9C,MAAI,IAAI;AACR,WAAS,IAAI,GAAG,IAAI,GAAG,KAAK;AAC1B,UAAM,IAAI,OAAO,MAAM,CAAC,KAAK,GAAG;AAChC,QAAI,IAAI,OAAQ,OAAO,SAAS,CAAC,IAAI,IAAI;AAAA,EAC3C;AACA,SAAO;AACT;;;ADnDA,IAAM,YAA6B;AAAA,EACjC,MAAM;AAAA,EACN,IAAI;AAAA,EACJ,aAAa;AAAA,EACb,KAAK,CAAC,QAAQ;AACZ,UAAM,SAA0B,EAAE,SAAS,CAAC,GAAG,WAAW,CAAC,GAAG,OAAO,CAAC,EAAE;AAKxE,QAAI,QAAQ,IAAI,6BAA6B,OAAO,CAAC,IAAI,QAAQ;AAC/D,YAAM,OAAOC,MAAK,IAAI,MAAM,SAAS,kBAAkB;AACvD,UAAI,WAAW,IAAI,GAAG;AACpB,cAAM,OAAO,aAAa,MAAM,MAAM;AACtC,cAAM,SAAS,oBAAoB,MAAM,yBAAyB,QAAQ,QAAQ;AAClF,sBAAc,MAAM,QAAQ,MAAM;AAClC,eAAO,UAAU,KAAK,wBAAwB;AAAA,MAChD;AAAA,IACF;AACA,WAAO,MAAM,KAAK,0CAAqC;AACvD,WAAO;AAAA,EACT;AACF;AAEO,IAAM,aAAyC,CAAC,SAAS;AAOzD,SAAS,oBACd,MACA,QACA,YAAY,QACZ,cAAc,UACN;AACR,SAAO,WAAW,SAAS;AAAA,EAAK,IAAI;AAAA,EAAY,MAAM,WAAW,WAAW;AAAA;AAC9E;AAOO,SAAS,kBACd,MACA,QACA,MAIA;AACA,MAAI,SAAS,OAAQ,QAAO,EAAE,MAAM,MAAM,YAAY,MAAM;AAC5D,SAAO;AAAA,IACL,MAAM,oBAAoB,MAAM,QAAQ,MAAM,IAAI;AAAA,IAClD,YAAY;AAAA,EACd;AACF;;;AEvFA,SAAS,cAAAC,aAAY,aAAAC,YAAW,gBAAAC,eAAc,UAAAC,eAAc;AAC5D,SAAS,WAAAC,UAAS,QAAAC,aAAY;AAC9B,SAAS,iBAAiB,SAAAC,cAAa;;;ACFvC,SAAS,WAAW,gBAAAC,qBAAoB;AACxC,SAAS,WAAAC,UAAS,QAAAC,aAAY;AAC9B,SAAS,YAAAC,iBAAgB;;;ACFzB;AAAA,EACE;AAAA,EACA,cAAAC;AAAA,EACA;AAAA,EACA,gBAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAAC;AAAA,EACA;AAAA,OACK;AACP,SAAS,UAAU,SAAS,QAAAC,aAAY;AAExC,SAAS,mBAAmB,0BAA0B;AA2C/C,SAAS,WAAW,SAAiB,SAA+B;AACzE,QAAM,MAAM,QAAQ,OAAO;AAC3B,QAAM,MAAMA;AAAA,IACV;AAAA,IACA,IAAI,SAAS,OAAO,CAAC,QAAQ,QAAQ,GAAG,IAAI,KAAK,OAAO,EAAE,SAAS,EAAE,EAAE,MAAM,CAAC,CAAC;AAAA,EACjF;AAWA,MAAI;AACJ,MAAI;AACF,SAAK,SAAS,KAAK,GAAG;AACtB,cAAU,IAAI,SAAS,GAAG,MAAM;AAChC,cAAU,EAAE;AACZ,SAAK;AACL,eAAW,KAAK,OAAO;AAAA,EACzB,UAAE;AACA,QAAI,OAAO,QAAW;AACpB,UAAI;AACF,kBAAU,EAAE;AAAA,MACd,QAAQ;AAAA,MAER;AAAA,IACF;AACA,QAAI;AACF,aAAO,KAAK,EAAE,OAAO,KAAK,CAAC;AAAA,IAC7B,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO,EAAE,MAAM,SAAS,MAAM,cAAc,SAAS,KAAK;AAC5D;AAOO,SAASC,cACd,SACA,OACA,YACc;AACd,qBAAmB,SAAS,OAAO,UAAU;AAC7C,SAAO,EAAE,MAAM,SAAS,MAAM,gBAAgB,SAAS,KAAK;AAC9D;AA+BO,SAAS,cACd,SACA,SACc;AACd,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,IAAI,MAAM,4CAA4C;AAAA,EAC9D;AACA,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,OAAO,QAAQ,CAAC;AACtB,QAAI,CAAC,KAAM,OAAM,IAAI,MAAM,iCAAiC;AAC5D,WAAOA,cAAa,SAAS,KAAK,OAAO,KAAK,UAAU;AAAA,EAC1D;AACA,MAAI,UAAU;AACd,MAAI;AACF,cAAUH,cAAa,SAAS,MAAM;AAAA,EACxC,QAAQ;AAAA,EAER;AACA,MAAI,WAAW;AACf,aAAW,KAAK,SAAS;AACvB,eAAW,kBAAkB,UAAU,EAAE,KAAK;AAAA,EAChD;AACA,QAAM,gBAAgB,QAAQ,IAAI,CAAC,MAAM,EAAE,UAAU,EAAE,KAAK,IAAI;AAGhE,QAAM,OACJ,SAAS,KAAK,EAAE,SAAS,IAAI,GAAG,SAAS,QAAQ,CAAC;AAAA;AAAA,EAAO,aAAa,KAAK;AAC7E,EAAAC,eAAc,SAAS,MAAM,MAAM;AACnC,SAAO,EAAE,MAAM,SAAS,MAAM,gBAAgB,SAAS,KAAK;AAC9D;AAaO,SAAS,YAAY,OAAqB,MAAsB;AACrE,SAAO,GAAG,MAAM,KAAK;AAAA,EAAK,KAAK,QAAQ,CAAC;AAAA,EAAK,MAAM,GAAG;AAAA;AACxD;AAIO,SAAS,aAAa,SAAiB,SAA+B;AAC3E,MAAIF,YAAW,OAAO,GAAG;AACvB,WAAO,EAAE,MAAM,SAAS,MAAM,gBAAgB,SAAS,MAAM;AAAA,EAC/D;AACA,EAAAE,eAAc,SAAS,SAAS,MAAM;AACtC,SAAO,EAAE,MAAM,SAAS,MAAM,gBAAgB,SAAS,KAAK;AAC9D;;;AD9KO,IAAM,2BAA2B;AAExC,IAAM,SAAS;AAGR,SAAS,oBAAoB,MAAsB;AACxD,SAAOG,MAAK,MAAMC,WAAU,kBAAkB;AAChD;AAIO,SAAS,oBAAoB,MAA6B;AAC/D,MAAI;AACJ,MAAI;AACF,UAAMC,cAAa,oBAAoB,IAAI,GAAG,MAAM;AAAA,EACtD,QAAQ;AACN,WAAO;AAAA,EACT;AACA,aAAW,QAAQ,IAAI,MAAM,IAAI,GAAG;AAClC,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,QAAQ,WAAW,MAAM,GAAG;AAC9B,YAAM,IAAI,QAAQ,MAAM,OAAO,MAAM,EAAE,KAAK;AAC5C,UAAI,EAAE,SAAS,EAAG,QAAO;AAAA,IAC3B;AAAA,EACF;AACA,SAAO;AACT;AAUO,SAAS,qBAAqB,MAAc,SAAuB;AACxE,QAAM,OAAO,oBAAoB,IAAI;AACrC,YAAUC,SAAQ,IAAI,GAAG,EAAE,WAAW,KAAK,CAAC;AAC5C,aAAW,MAAM,GAAG,MAAM,GAAG,OAAO;AAAA,CAAI;AAC1C;;;AE1DA,SAAS,cAAAC,aAAY,gBAAAC,qBAAoB;AACzC,SAAS,QAAAC,aAAY;AAkCrB,IAAM,kBAA4D;AAAA,EAChE,CAAC,QAAQ,MAAM;AAAA,EACf,CAAC,QAAQ,MAAM;AAAA,EACf,CAAC,WAAW,SAAS;AAAA,EACrB,CAAC,WAAW,SAAS;AAAA,EACrB,CAAC,QAAQ,MAAM;AAAA,EACf,CAAC,SAAS,OAAO;AAAA,EACjB,CAAC,iBAAiB,WAAW;AAAA,EAC7B,CAAC,iBAAiB,SAAS;AAAA,EAC3B,CAAC,SAAS,OAAO;AAAA,EACjB,CAAC,OAAO,KAAK;AACf;AAKA,IAAM,oBAA8D;AAAA,EAClE,CAAC,WAAW,SAAS;AAAA,EACrB,CAAC,SAAS,OAAO;AAAA,EACjB,CAAC,UAAU,QAAQ;AAAA,EACnB,CAAC,SAAS,OAAO;AAAA,EACjB,CAAC,aAAa,WAAW;AAAA,EACzB,CAAC,WAAW,SAAS;AAAA,EACrB,CAAC,WAAW,SAAS;AAAA,EACrB,CAAC,UAAU,QAAQ;AAAA,EACnB,CAAC,WAAW,SAAS;AAAA,EACrB,CAAC,UAAU,QAAQ;AACrB;AAMA,SAAS,SAAsB,MAA6B;AAC1D,MAAI;AACF,WAAO,KAAK,MAAMD,cAAa,MAAM,MAAM,CAAC;AAAA,EAC9C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASO,SAAS,YAAY,MAAyB;AACnD,QAAM,YAAY,oBAAI,IAAY;AAClC,QAAM,aAAa,oBAAI,IAAY;AACnC,MAAI,WAAW;AACf,MAAI,iBAAgC;AAGpC,QAAM,SAASC,MAAK,MAAM,cAAc;AACxC,MAAIF,YAAW,MAAM,GAAG;AACtB,UAAM,KAAK,SAAsB,MAAM;AACvC,QAAI,IAAI;AACN,YAAM,QACJ,QAAQ,GAAG,iBAAiB,UAAU,KAAKA,YAAWE,MAAK,MAAM,eAAe,CAAC;AACnF,gBAAU,IAAI,QAAQ,eAAe,YAAY;AAEjD,YAAM,OAAO,oBAAI,IAAI;AAAA,QACnB,GAAG,OAAO,KAAK,GAAG,gBAAgB,CAAC,CAAC;AAAA,QACpC,GAAG,OAAO,KAAK,GAAG,mBAAmB,CAAC,CAAC;AAAA,MACzC,CAAC;AACD,iBAAW,CAAC,KAAK,EAAE,KAAK,iBAAiB;AACvC,YAAI,KAAK,IAAI,GAAG,EAAG,YAAW,IAAI,EAAE;AAAA,MACtC;AAEA,YAAM,KAAK,GAAG;AACd,UACE,MAAM,QAAQ,EAAE,KACf,OAAO,OAAO,YAAY,OAAO,QAAQ,MAAM,QAAQ,GAAG,QAAQ,GACnE;AACA,mBAAW;AAAA,MACb;AACA,UAAI,OAAO,GAAG,mBAAmB,YAAY,GAAG,eAAe,SAAS,GAAG;AAEzE,cAAM,IAAI,GAAG,eAAe,MAAM,GAAG,EAAE,CAAC;AACxC,YAAI,EAAG,kBAAiB;AAAA,MAC1B;AAAA,IACF;AAAA,EACF;AAGA,MAAIF,YAAWE,MAAK,MAAM,qBAAqB,CAAC,GAAG;AACjD,eAAW;AACX,qBAAiB,kBAAkB;AAAA,EACrC;AACA,MAAIF,YAAWE,MAAK,MAAM,YAAY,CAAC,KAAKF,YAAWE,MAAK,MAAM,SAAS,CAAC,GAAG;AAC7E,eAAW;AAAA,EACb;AAGA,MAAI,CAAC,gBAAgB;AACnB,QAAIF,YAAWE,MAAK,MAAM,gBAAgB,CAAC,EAAG,kBAAiB;AAAA,aACtDF,YAAWE,MAAK,MAAM,WAAW,CAAC,EAAG,kBAAiB;AAAA,aACtDF,YAAWE,MAAK,MAAM,mBAAmB,CAAC,EAAG,kBAAiB;AAAA,EACzE;AAGA,MACEF,YAAWE,MAAK,MAAM,gBAAgB,CAAC,KACvCF,YAAWE,MAAK,MAAM,kBAAkB,CAAC,KACzCF,YAAWE,MAAK,MAAM,SAAS,CAAC,KAChCF,YAAWE,MAAK,MAAM,UAAU,CAAC,GACjC;AACA,cAAU,IAAI,QAAQ;AAOtB,QAAIF,YAAWE,MAAK,MAAM,gBAAgB,CAAC,GAAG;AAC5C,YAAM,MAAM,SAASA,MAAK,MAAM,gBAAgB,CAAC;AACjD,UAAI,KAAK;AACP,mBAAW,CAAC,KAAK,EAAE,KAAK,mBAAmB;AACzC,cAAI,gBAAgB,KAAK,GAAG,EAAG,YAAW,IAAI,EAAE;AAAA,QAClD;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,MAAIF,YAAWE,MAAK,MAAM,QAAQ,CAAC,GAAG;AACpC,cAAU,IAAI,IAAI;AAClB,qBAAiB,kBAAkB;AAAA,EACrC;AAGA,MAAIF,YAAWE,MAAK,MAAM,YAAY,CAAC,GAAG;AACxC,cAAU,IAAI,MAAM;AACpB,UAAM,MAAM,SAASA,MAAK,MAAM,YAAY,CAAC;AAC7C,QAAI,KAAK;AAUP,UAAI,mBAAmB,KAAK,GAAG,EAAG,YAAW,IAAI,WAAW;AAC5D,UAAI,iBAAiB,KAAK,GAAG,EAAG,YAAW,IAAI,OAAO;AACtD,UAAI,gBAAgB,KAAK,GAAG,EAAG,YAAW,IAAI,MAAM;AACpD,UAAI,kBAAkB,KAAK,GAAG,EAAG,YAAW,IAAI,QAAQ;AAAA,IAC1D;AACA,qBAAiB,kBAAkB;AAAA,EACrC;AAEA,SAAO;AAAA,IACL,WAAW,CAAC,GAAG,SAAS,EAAE,KAAK;AAAA,IAC/B;AAAA,IACA,YAAY,CAAC,GAAG,UAAU,EAAE,KAAK;AAAA,IACjC;AAAA,EACF;AACF;AAEA,SAAS,SAAS,MAAkC;AAClD,MAAI;AACF,WAAOD,cAAa,MAAM,MAAM;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAeA,SAAS,gBAAgB,KAAa,KAAsB;AAC1D,QAAM,MAAM,IAAI,QAAQ,uBAAuB,MAAM;AACrD,QAAM,SAAS;AACf,QAAM,QAAQ;AACd,SAAO,IAAI,OAAO,GAAG,MAAM,GAAG,GAAG,GAAG,KAAK,IAAI,GAAG,EAAE,KAAK,GAAG;AAC5D;;;ACzMA,IAAM,QAAQ;AAEP,SAAS,OAAO,UAAkB,MAAuC;AAC9E,SAAO,SAAS,QAAQ,OAAO,CAAC,OAAO,SAAiB;AACtD,QAAI,CAAC,OAAO,OAAO,MAAM,IAAI,EAAG,QAAO;AACvC,UAAM,IAAI,KAAK,IAAI;AACnB,QAAI,MAAM,UAAa,MAAM,KAAM,QAAO;AAC1C,WAAO,OAAO,CAAC;AAAA,EACjB,CAAC;AACH;;;AC/BA,SAAS,gBAAAE,qBAAoB;AAC7B,SAAS,WAAAC,UAAS,QAAAC,OAAM,eAAe;AACvC,SAAS,qBAAqB;AAoB9B,IAAM,wBAAwB,oBAAoB;AAElD,SAAS,sBAA8B;AACrC,QAAM,WAAW,QAAQ,IAAI;AAC7B,MAAI,YAAY,SAAS,SAAS,EAAG,QAAO,QAAQ,QAAQ;AAG5D,QAAM,OAAOD,SAAQ,cAAc,YAAY,GAAG,CAAC;AACnD,SAAOC,MAAK,MAAM,MAAM,WAAW;AACrC;AAKO,SAAS,aAAa,MAAsB;AACjD,SAAOF,cAAaE,MAAK,uBAAuB,IAAI,GAAG,MAAM;AAC/D;AAGO,SAAS,eAAuB;AACrC,SAAO;AACT;;;AL6CA,IAAM,iBAAuD;AAAA;AAAA;AAAA;AAAA,EAI3D,YAAY;AAAA,EACZ,cAAc;AAAA,EACd,cAAc;AAChB;AAEA,eAAsB,SAAS,MAAgD;AAC7E,QAAM,OAAgB,KAAK,QAAQ;AACnC,QAAM,YAA+C,KAAK,aAAa;AACvE,MAAI,cAAc,qBAAqB,CAAC,KAAK,KAAK;AAChD,UAAM,IAAI,MAAM,+CAA+C;AAAA,EACjE;AAGA,MAAI,KAAK,SAAS,YAAY,CAAC,KAAK,QAAQ;AAC1C,IAAAC,WAAU,KAAK,MAAM,EAAE,WAAW,KAAK,CAAC;AAAA,EAC1C;AAIA,QAAM,SAAS,kBAAkB,KAAK,IAAI;AAC1C,QAAM,YAAY,iBAAiB,MAAM,MAAM;AAS/C,MAAI,CAAC,KAAK,UAAU,OAAO,UAAU,WAAW;AAC9C,IAAAC,QAAOC,OAAM,UAAU,KAAK,IAAI,GAAG,EAAE,OAAO,KAAK,CAAC;AAAA,EACpD;AAEA,QAAM,cAAc,oBAAoB,KAAK,IAAI;AAIjD,QAAM,QAAQ,YAAY,KAAK,IAAI;AAMnC,QAAM,gBAA0B,CAAC;AACjC,QAAM,qBAA+B,CAAC;AACtC,MAAI,KAAK,SAAS,UAAU,KAAK,YAAY,QAAQ,gBAAgB,MAAM;AACzE,UAAM,IAAI,cAAc,KAAK,MAAM,aAAa,0BAA0B;AAAA,MACxE,QAAQ,KAAK,WAAW;AAAA,IAC1B,CAAC;AACD,kBAAc,KAAK,GAAG,EAAE,GAAG;AAC3B,uBAAmB,KAAK,GAAG,EAAE,SAAS;AAAA,EACxC;AAGA,QAAM,WAAW,cAAc,EAAE,MAAM,KAAK,MAAM,WAAW,MAAM,WAAW,KAAK,KAAK,IAAI,CAAC;AAC7F,QAAM,kBAAkB,KAAK,SAAS,UAAW,KAAK,SAAS,UAAU,KAAK,YAAY;AAC1F,QAAM,OAA6B;AAAA,IACjC,MAAM,KAAK;AAAA,IACX;AAAA,IACA;AAAA,IACA;AAAA,IACA,KAAK,KAAK;AAAA,EACZ;AAEA,QAAM,UAAoB,CAAC;AAC3B,QAAM,UAAoB,CAAC;AAS3B,QAAM,SAAS,sBAAsB,UAAU,MAAM,eAAe;AACpE,aAAW,CAAC,SAAS,OAAO,KAAK,QAAQ;AACvC,UAAM,MAAMC,MAAK,KAAK,MAAM,OAAO;AACnC,UAAM,UAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,cAAc;AAE/D,QAAI,QAAQ,UAAU,GAAG;AAEvB,UAAI,KAAK,QAAQ;AACf,gBAAQ,KAAK,OAAO;AACpB;AAAA,MACF;AACA,MAAAH,WAAUI,SAAQ,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;AAC3C,YAAM,UAAU,QAAQ,IAAI,CAAC,MAAM;AACjC,cAAM,QAAQ,EAAE;AAChB,YAAI,CAAC,OAAO;AACV,gBAAM,IAAI,MAAM,kBAAkB,EAAE,IAAI,qCAAqC;AAAA,QAC/E;AACA,eAAO,EAAE,OAAO,YAAY,YAAY,OAAO,YAAY,GAAG,IAAI,CAAC,EAAE;AAAA,MACvE,CAAC;AACD,oBAAc,KAAK,OAAO;AAC1B,cAAQ,KAAK,OAAO;AACpB;AAAA,IACF;AAGA,QAAI,CAAC,KAAK,OAAQ,CAAAJ,WAAUI,SAAQ,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC;AAC7D,eAAW,SAAS,SAAS;AAC3B,UAAI,KAAK,QAAQ;AACf,YAAI,MAAM,SAAS,kBAAkBC,YAAW,GAAG,EAAG,SAAQ,KAAK,MAAM,IAAI;AAAA,YACxE,SAAQ,KAAK,MAAM,IAAI;AAC5B;AAAA,MACF;AACA,YAAM,OAAO,YAAY,OAAO,IAAI;AACpC,UAAI,MAAM,SAAS,cAAc;AAC/B,mBAAW,KAAK,IAAI;AACpB,gBAAQ,KAAK,MAAM,IAAI;AAAA,MACzB,WAAW,MAAM,SAAS,gBAAgB;AACxC,cAAM,QAAQ,MAAM;AACpB,YAAI,CAAC,OAAO;AACV,gBAAM,IAAI,MAAM,kBAAkB,MAAM,IAAI,qCAAqC;AAAA,QACnF;AAQA,YAAI,aAAa,MAAM,IAAI,EAAG,kBAAiB,GAAG;AAClD,QAAAC,cAAa,KAAK,OAAO,YAAY,OAAO,IAAI,CAAC;AACjD,gBAAQ,KAAK,MAAM,IAAI;AAAA,MACzB,OAAO;AACL,cAAM,MAAM,aAAa,KAAK,IAAI;AAClC,YAAI,IAAI,QAAS,SAAQ,KAAK,MAAM,IAAI;AAAA,YACnC,SAAQ,KAAK,MAAM,IAAI;AAAA,MAC9B;AAAA,IACF;AAAA,EACF;AAIA,OAAK,KAAK,SAAS,UAAU,KAAK,SAAS,aAAa,CAAC,KAAK,QAAQ;AACpE,yBAAqB,KAAK,MAAM,wBAAwB;AAAA,EAC1D;AAEA,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX;AAAA,EACF;AACF;AASA,SAAS,kBACP,MACiG;AACjG,MAAI;AACJ,MAAI;AACF,UAAMC,cAAaL,OAAM,UAAU,IAAI,GAAG,MAAM;AAAA,EAClD,QAAQ;AACN,WAAO,EAAE,OAAO,UAAU,IAAI,KAAK;AAAA,EACrC;AACA,QAAM,UAAU,IAAI,KAAK;AACzB,SAAO,QAAQ,SAAS,IAAI,EAAE,OAAO,SAAS,IAAI,QAAQ,IAAI,EAAE,OAAO,WAAW,IAAI,KAAK;AAC7F;AAEA,SAAS,iBACP,MACA,QACQ;AACR,MAAI,KAAK,cAAc,OAAW,QAAO,KAAK;AAC9C,MAAI,OAAO,UAAU,QAAS,QAAO,OAAO;AAG5C,MAAI,KAAK,SAAS,QAAQ;AACxB,UAAM,IAAI,MAAM,8BAA8B,KAAK,IAAI,4BAA4B;AAAA,EACrF;AACA,SAAO,gBAAgB;AACzB;AAMA,SAAS,sBACP,UACA,MACA,iBAC8B;AAC9B,QAAM,SAAS,oBAAI,IAA6B;AAChD,aAAW,SAAS,UAAU;AAC5B,QAAI,MAAM,SAAS,UAAa,MAAM,SAAS,KAAM;AACrD,QAAI,mBAAmB,eAAe,MAAM,IAAI,MAAM,UAAW;AACjE,UAAM,OAAO,OAAO,IAAI,MAAM,IAAI;AAClC,QAAI,KAAM,MAAK,KAAK,KAAK;AAAA,QACpB,QAAO,IAAI,MAAM,MAAM,CAAC,KAAK,CAAC;AAAA,EACrC;AACA,SAAO;AACT;AAKA,SAAS,aAAa,SAA0B;AAC9C,SAAO,YAAY;AACrB;AAOA,SAAS,iBAAiB,SAAuB;AAC/C,MAAI;AACJ,MAAI;AACF,cAAUK,cAAa,SAAS,MAAM;AAAA,EACxC,QAAQ;AACN;AAAA,EACF;AACA,MAAI,6BAA6B,KAAK,OAAO,EAAG;AAChD,EAAAN,QAAO,SAAS,EAAE,OAAO,KAAK,CAAC;AACjC;AAEA,SAAS,YAAY,OAAsB,MAAoC;AAC7E,MAAI,MAAM,aAAa,QAAW;AAChC,WAAO,OAAO,aAAa,MAAM,QAAQ,GAAG,IAAI;AAAA,EAClD;AACA,MAAI,MAAM,YAAY,OAAW,QAAO,MAAM;AAC9C,QAAM,IAAI,MAAM,kBAAkB,MAAM,IAAI,uCAAuC;AACrF;","names":["join","join","existsSync","mkdirSync","readFileSync","rmSync","dirname","join","paths","readFileSync","dirname","join","NOIR_DIR","existsSync","readFileSync","writeFileSync","join","managedBlock","join","NOIR_DIR","readFileSync","dirname","existsSync","readFileSync","join","readFileSync","dirname","join","mkdirSync","rmSync","paths","join","dirname","existsSync","managedBlock","readFileSync"]}
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@noir-ai/create",
3
+ "version": "1.2.0-beta.1",
4
+ "description": "Noir scaffold engine — the declarative three-mode writer, manifest, templates, stack-detect, and migrations that power `noir init`/`create`/`sync`.",
5
+ "license": "MIT",
6
+ "author": "agaaaptr",
7
+ "homepage": "https://github.com/agaaaptr/noir#readme",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/agaaaptr/noir.git",
11
+ "directory": "packages/create"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/agaaaptr/noir/issues"
15
+ },
16
+ "keywords": [
17
+ "noir",
18
+ "scaffold",
19
+ "init",
20
+ "create",
21
+ "manifest",
22
+ "templates",
23
+ "migrations",
24
+ "sdd",
25
+ "spec-driven",
26
+ "agent"
27
+ ],
28
+ "engines": {
29
+ "node": ">=20"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public",
33
+ "provenance": true
34
+ },
35
+ "type": "module",
36
+ "main": "./dist/index.js",
37
+ "types": "./dist/index.d.ts",
38
+ "exports": {
39
+ ".": {
40
+ "types": "./dist/index.d.ts",
41
+ "import": "./dist/index.js"
42
+ }
43
+ },
44
+ "files": [
45
+ "dist",
46
+ "templates"
47
+ ],
48
+ "dependencies": {
49
+ "@noir-ai/adapters": "1.2.0-beta.1",
50
+ "@noir-ai/core": "1.2.0-beta.1"
51
+ },
52
+ "devDependencies": {
53
+ "@types/node": "^26.1.1"
54
+ },
55
+ "scripts": {
56
+ "build": "tsup",
57
+ "typecheck": "tsc --noEmit"
58
+ }
59
+ }
@@ -0,0 +1 @@
1
+ @import ".noir/NOIR.md"
@@ -0,0 +1 @@
1
+ @import ".noir/rules/RULES.md"
@@ -0,0 +1,2 @@
1
+ host: {{host}}
2
+ mode: full
@@ -0,0 +1 @@
1
+ .noir/
@@ -0,0 +1,4 @@
1
+ /.noir/store/
2
+ /.noir/*.sock
3
+ /.noir/daemon.pid
4
+ /.noir/state/
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "noir": {
4
+ "type": "http",
5
+ "url": "{{url}}"
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,12 @@
1
+ {
2
+ "mcpServers": {
3
+ "noir": {
4
+ "command": "noir",
5
+ "args": [
6
+ "mcp",
7
+ "serve",
8
+ "--stdio"
9
+ ]
10
+ }
11
+ }
12
+ }
@@ -0,0 +1,5 @@
1
+ # Noir context
2
+
3
+ Project id: `{{projectId}}`
4
+
5
+ <!-- Noir auto-manages this file. Host context files @import it. -->
@@ -0,0 +1 @@
1
+ /.noir/
@@ -0,0 +1 @@
1
+ /.noir/
@@ -0,0 +1,34 @@
1
+ # Noir working rules
2
+
3
+ > Canonical AI working-contract for this project. The host context file (CLAUDE.md) @imports this.
4
+ > Keep LEAN: every line must be failure-backed, tool-enforceable, decision-encoding, or triggerable — else delete it.
5
+ > Edit freely — Noir re-emits only the @import pointer, never this body.
6
+
7
+ ## Identity & scope
8
+ - This project uses **Noir** (discipline/context/memory layer) inside **Claude Code**.
9
+ - Noir is an orchestration layer — the host CLI is the execution engine; Noir is the spec/context/memory brain.
10
+ - Stay in scope: do only what the current Noir task (`.noir/tasks/`) requires. Surface scope creep BEFORE acting.
11
+
12
+ ## Anti-assumption contract
13
+ - **Never fabricate** facts, APIs, file contents, or command output. Use only what you have read or verified.
14
+ - **Never assume** — if a path, signature, or convention is unclear, STOP and ask before acting.
15
+ - Cite the file/line or command you relied on for any non-obvious claim.
16
+ - Detect non-conventional setups; do not paper over them with assumed conventions.
17
+
18
+ ## Spec-Driven Development workflow
19
+ - Follow the Noir SDD lifecycle: intake → clarify → spec → plan → execute → verify → document.
20
+ - Gates are observable: every gate decision is recorded (approved / forced / skipped). `--force` requires a reason.
21
+ - Do not edit/run/execute before facts are gathered AND the human confirms understanding.
22
+
23
+ ## Verification (run before claiming done)
24
+ - `pnpm build && pnpm lint && pnpm typecheck && pnpm test` — all green, with real output. No hedging.
25
+ - Report failures truthfully with the actual output; never claim green without evidence.
26
+
27
+ ## Coding standards & architecture
28
+ - Follow existing patterns; match surrounding code's style, naming, comment density.
29
+ - Architecture decisions live in `.noir/decisions/` (ADR-style) — read them before cross-cutting changes.
30
+ - See `docs/roadmap.md` for direction and `docs/specs/` for design.
31
+
32
+ ## Conventions gotchas (project-specific — fill in)
33
+ - Commits stay local until explicitly pushed.
34
+ - The full test suite runs offline/free (no network in CI).