@kirchdev/gitignore-sync 0.2.0 → 0.3.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/README.md CHANGED
@@ -102,11 +102,12 @@ Everything below the closing `# endregion` is the **free zone**: yours, preserve
102
102
 
103
103
  ## 🧩 Stacks
104
104
 
105
- Twenty curated blocks, each derived from what the estate actually ignores rather than from a public template site.
105
+ Twenty-one curated blocks, each derived from what the estate actually ignores rather than from a public template site.
106
106
 
107
107
  | Stack | What it covers | Proposed by `init` when |
108
108
  | :--------- | :----------------------------------- | :---------------------------- |
109
- | `core` | `.DS_Store`, agent-local settings | always |
109
+ | `core` | `.DS_Store` | always |
110
+ | `agents` | agent working files, not their config| `.claude/`, `.codex/`, … |
110
111
  | `git` | merge and backup droppings | always (it is a git repo) |
111
112
  | `node` | modules, build output, logs, caches | `package.json` |
112
113
  | `dotenv` | `.env*`, minus the committed example | `.env.example` or `.env` |
@@ -22,7 +22,12 @@ var registry = {
22
22
  core: [{
23
23
  stack: "core",
24
24
  version: 1,
25
- lines: [".DS_Store", ".claude/settings.local.json"]
25
+ lines: [".DS_Store"]
26
+ }],
27
+ agents: [{
28
+ stack: "agents",
29
+ version: 1,
30
+ lines: [".claude/settings.local.json", ".claude/worktrees/"]
26
31
  }],
27
32
  git: [{
28
33
  stack: "git",
@@ -47,7 +52,7 @@ var registry = {
47
52
  "node_modules",
48
53
  "dist",
49
54
  "coverage",
50
- "logs",
55
+ "/logs",
51
56
  "*.log",
52
57
  "*.tsbuildinfo",
53
58
  ".eslintcache",
@@ -99,7 +104,6 @@ var registry = {
99
104
  "/public/hot",
100
105
  "/public/storage",
101
106
  "/storage/*.key",
102
- "/storage/pail",
103
107
  "/bootstrap/ssr",
104
108
  "_ide_helper.php",
105
109
  "_ide_helper_models.php",
@@ -153,7 +157,8 @@ var registry = {
153
157
  ".env",
154
158
  ".env.*",
155
159
  "!.env.example",
156
- "!.env.*.example"
160
+ "!.env.*.example",
161
+ "!.env.testing"
157
162
  ]
158
163
  }],
159
164
  vscode: [{
@@ -408,7 +413,8 @@ function matchStacks(line) {
408
413
  if (!m) return void 0;
409
414
  return m[1] ? m[1].split(",").map((s) => s.trim()).filter(Boolean) : [];
410
415
  }
411
- var stacksLine = (stacks) => `# stacks: ${stacks.join(", ")}`;
416
+ /** Deduplicated, first mention winning a name twice says nothing twice. */
417
+ var stacksLine = (stacks) => `# stacks: ${[...new Set(stacks)].join(", ")}`;
412
418
  /** Cosmetic rule under the header; regenerated on every render. */
413
419
  var DIVIDER = `# ${"─".repeat(41)}`;
414
420
  //#endregion
@@ -522,7 +528,7 @@ function parse(text) {
522
528
  function readStacks(doc) {
523
529
  for (const line of doc.header) {
524
530
  const stacks = matchStacks(line);
525
- if (stacks) return stacks;
531
+ if (stacks) return [...new Set(stacks)];
526
532
  }
527
533
  return [];
528
534
  }
@@ -604,7 +610,10 @@ function reconcile(doc) {
604
610
  });
605
611
  }
606
612
  const sections = [];
613
+ const seenStack = /* @__PURE__ */ new Set();
607
614
  for (const stack of declared) {
615
+ if (seenStack.has(stack)) continue;
616
+ seenStack.add(stack);
608
617
  const template = currentTemplate(stack);
609
618
  if (template) {
610
619
  sections.push({
@@ -1001,6 +1010,14 @@ var fingerprints = {
1001
1010
  source: "repo",
1002
1011
  always: true
1003
1012
  },
1013
+ agents: {
1014
+ source: "repo",
1015
+ files: [
1016
+ ".claude",
1017
+ ".codex",
1018
+ ".opencode"
1019
+ ]
1020
+ },
1004
1021
  git: {
1005
1022
  source: "repo",
1006
1023
  files: [".git"]
@@ -1390,7 +1407,7 @@ var infoCommand = defineCommand({
1390
1407
  run({ args }) {
1391
1408
  const binary = resolveBinary(process.argv[1]);
1392
1409
  const info = {
1393
- version: "0.2.0",
1410
+ version: "0.3.0",
1394
1411
  build: detectBuild(binary.resolved),
1395
1412
  binary,
1396
1413
  node: process.version,
@@ -1705,7 +1722,7 @@ var syncCommand = defineCommand({
1705
1722
  var rootCommand = defineCommand({
1706
1723
  meta: {
1707
1724
  name: "gitignore-sync",
1708
- version: "0.2.0",
1725
+ version: "0.3.0",
1709
1726
  description: "Keep a repo's .gitignore maintained: curated blocks in a managed region, re-rendered on demand"
1710
1727
  },
1711
1728
  subCommands: {
@@ -1 +1 @@
1
- {"version":3,"file":"gitignore-sync.mjs","names":[],"sources":["../../src/templates/index.ts","../../src/discover.ts","../../src/gitignore/markers.ts","../../src/gitignore/types.ts","../../src/gitignore/parse.ts","../../src/gitignore/reconcile.ts","../../src/commands/audit.ts","../../src/gitignore/render.ts","../../src/io.ts","../../src/commands/report.ts","../../src/commands/check.ts","../../src/detect.ts","../../src/commands/prompt.ts","../../src/commands/edit.ts","../../src/commands/info.ts","../../src/commands/stacks.ts","../../src/commands/init.ts","../../src/commands/list.ts","../../src/commands/sync.ts","../../src/cli.ts","../../src/bin/gitignore-sync.ts"],"sourcesContent":["import type { Template } from '../gitignore/types.ts';\n\n/**\n * Curated blocks, versioned with the binary. Data, not code.\n *\n * Every version a stack has ever shipped is kept, ascending. Reconciling reads\n * the version the section marker names, so upgrading `node@v1` to `node@v2`\n * knows which of the block's lines it put there itself and which the user\n * added — without that history an upgrade would \"rescue\" its own dropped lines\n * into the free zone.\n *\n * **No line may appear in two stacks.** A repo declaring both would otherwise\n * render it twice, and the equivalence report would nag about a collision the\n * tool created itself. `tests/templates.test.ts` enforces this.\n */\nconst registry: Record<string, Template[]> = {\n // Non-negotiable everywhere: the editor droppings file every repo in the\n // estate ignores (26 of 26 — a mixed-OS team collects it whoever commits),\n // and the per-machine Claude Code settings that must never reach a branch.\n // The rest of the macOS noise is rare and lives in the `macos` stack.\n core: [\n {\n stack: 'core',\n version: 1,\n lines: ['.DS_Store', '.claude/settings.local.json']\n }\n ],\n // Conflict and backup droppings every git repo can produce. Fingerprinted on\n // `.git` itself, so `init` proposes it everywhere — but it stays a stack you\n // can drop, which is why it is not folded into `core`.\n git: [\n {\n stack: 'git',\n version: 1,\n lines: [\n '*.orig',\n '*.rej',\n '*.BACKUP.*',\n '*.BASE.*',\n '*.LOCAL.*',\n '*.REMOTE.*',\n '*_BACKUP_*.txt',\n '*_BASE_*.txt',\n '*_LOCAL_*.txt',\n '*_REMOTE_*.txt'\n ]\n }\n ],\n // `node_modules` lives here, not in `core`: a pure Go repo then correctly\n // does not get it. That it lands almost everywhere in practice (26 of 28\n // local repos carry a package.json) is a consequence of the estate, not a\n // reason to blur the model.\n node: [\n {\n stack: 'node',\n version: 1,\n lines: [\n 'node_modules',\n 'dist',\n 'coverage',\n 'logs',\n '*.log',\n '*.tsbuildinfo',\n '.eslintcache',\n '.npm',\n '*.tgz'\n ]\n }\n ],\n // Build and test droppings only. `dist` is deliberately absent — goreleaser\n // writes there, but so does every bundler, and the line belongs to whichever\n // stack claims it once. `node` claims it.\n go: [\n {\n stack: 'go',\n version: 1,\n lines: ['*.exe', '*.test', '*.out', '*.prof']\n }\n ],\n // State and local overrides. `.terraform.lock.hcl` is deliberately **not**\n // ignored — it is meant to be committed. `*.tfvars` usually carries\n // credentials, so it is ignored with the example carved back out.\n tofu: [\n {\n stack: 'tofu',\n version: 1,\n lines: [\n '.terraform/',\n '*.tfstate',\n '*.tfstate.*',\n '*.tfvars',\n '!*.tfvars.example',\n 'override.tf',\n 'override.tf.json',\n '.terraformrc',\n 'terraform.rc',\n 'crash.log',\n 'crash.*.log'\n ]\n }\n ],\n // `composer.lock` is not here on purpose: a library ignores it, an\n // application commits it. That is a project decision, so it belongs in the\n // free zone rather than in a block the tool re-renders.\n php: [\n {\n stack: 'php',\n version: 1,\n lines: ['/vendor', '/.phpunit.cache', '/.phpunit.result.cache']\n }\n ],\n laravel: [\n {\n stack: 'laravel',\n version: 1,\n lines: [\n '/public/build',\n '/public/hot',\n '/public/storage',\n '/storage/*.key',\n '/storage/pail',\n '/bootstrap/ssr',\n '_ide_helper.php',\n '_ide_helper_models.php',\n '.phpstorm.meta.php'\n ]\n }\n ],\n // Three repos in the estate run turbo (`oggsbreinig`, `gildstone`, and the\n // campus monorepo); all three ignore its cache.\n turborepo: [\n {\n stack: 'turborepo',\n version: 1,\n lines: ['.turbo']\n }\n ],\n // The crate target dir. Fingerprinted on a **root** `Cargo.toml` only: a\n // Tauri app keeps one under `src-tauri/`, and that build output is already\n // the `tauri` stack's, so matching it here would render a `/target` line\n // pointing at nothing.\n rust: [\n {\n stack: 'rust',\n version: 1,\n lines: ['/target', '**/*.rs.bk']\n }\n ],\n playwright: [\n {\n stack: 'playwright',\n version: 1,\n lines: [\n 'test-results/',\n 'playwright-report/',\n 'blob-report/',\n '.last-run.json'\n ]\n }\n ],\n storybook: [\n {\n stack: 'storybook',\n version: 1,\n lines: ['storybook-static']\n }\n ],\n nuxt: [\n {\n stack: 'nuxt',\n version: 1,\n lines: ['.nuxt', '.output', '.nitro', '.data']\n }\n ],\n tauri: [\n {\n stack: 'tauri',\n version: 1,\n lines: ['src-tauri/target', 'src-tauri/gen/schemas']\n }\n ],\n // Language-agnostic: the three Go providers carry `.env` just as the Laravel\n // app does. The `!` exceptions matter — six local repos track a committed\n // `.env.example`, and a bare `.env.*` would swallow it.\n dotenv: [\n {\n stack: 'dotenv',\n version: 1,\n lines: ['.env', '.env.*', '!.env.example', '!.env.*.example']\n }\n ],\n // `.vscode/*` plus targeted `!` exceptions, never a bare `.vscode/`: git does\n // not descend into an ignored directory, which makes exceptions under it\n // technically impossible.\n //\n // The three exceptions are the ones actually tracked across the estate —\n // `extensions.json` in every repo that shares anything, `settings.json` in\n // seven, `mcp.json` in two. `tasks.json` and `*.code-snippets`, which the\n // toptal block unignores, are tracked by no repo at all and are left out.\n vscode: [\n {\n stack: 'vscode',\n version: 1,\n lines: [\n '.vscode/*',\n '!.vscode/extensions.json',\n '!.vscode/settings.json',\n '!.vscode/mcp.json'\n ]\n }\n ],\n // `.idea/*` and no exceptions: no repo in the estate tracks any `.idea`\n // content, so sharing it is so far theoretical. The `/*` form is kept anyway\n // so an exception can be added later without a breaking reshape.\n intellij: [\n {\n stack: 'intellij',\n version: 1,\n lines: ['.idea/*']\n }\n ],\n // `.DS_Store` is in `core`, not here — see the comment there.\n macos: [\n {\n stack: 'macos',\n version: 1,\n // `Icon` is deliberately absent: toptal writes it with a trailing CR, and\n // without that byte the pattern matches every file named \"Icon\".\n lines: [\n '.AppleDouble',\n '.LSOverride',\n '._*',\n '.Spotlight-V100',\n '.Trashes',\n '.DocumentRevisions-V100',\n '.fseventsd',\n '.TemporaryItems',\n '.VolumeIcon.icns',\n '.com.apple.timemachine.donotpresent',\n '.AppleDB',\n '.AppleDesktop',\n 'Network Trash Folder',\n 'Temporary Items',\n '.apdisk',\n '*.icloud'\n ]\n }\n ],\n windows: [\n {\n stack: 'windows',\n version: 1,\n lines: [\n 'Thumbs.db',\n 'Thumbs.db:encryptable',\n 'ehthumbs.db',\n 'ehthumbs_vista.db',\n '[Dd]esktop.ini',\n '$RECYCLE.BIN/',\n '*.stackdump',\n '*.cab',\n '*.msi',\n '*.msix',\n '*.msm',\n '*.msp',\n '*.lnk'\n ]\n }\n ],\n // Swap and session files. A person-level stack like the two editors above —\n // nothing in a repo says who edits it with vim.\n vim: [\n {\n stack: 'vim',\n version: 1,\n lines: ['*.swp', '*.swo', '*.swn', 'Session.vim', '.netrwhist']\n }\n ],\n linux: [\n {\n stack: 'linux',\n version: 1,\n lines: ['*~', '.fuse_hidden*', '.directory', '.Trash-*', '.nfs*']\n }\n ]\n};\n\n/**\n * Registry insertion order, which is the order `init` writes into the header:\n * `core` first, then repository stacks, then the person-level ones. Stable, so\n * the `# stacks:` line does not churn between runs.\n */\nexport const stackOrder = (): string[] => Object.keys(registry);\n\nexport const knownStacks = (): string[] => Object.keys(registry).sort();\n\nexport const isKnownStack = (stack: string): boolean => stack in registry;\n\n/** The version a fresh render of this stack produces. */\nexport function currentTemplate(stack: string): Template | undefined {\n return registry[stack]?.at(-1);\n}\n\n/** A specific past version, for working out what a section put there itself. */\nexport function templateAt(\n stack: string,\n version: number\n): Template | undefined {\n return registry[stack]?.find((t) => t.version === version);\n}\n","import { existsSync, readFileSync, readdirSync, type Dirent } from 'node:fs';\nimport { join, relative } from 'pathe';\nimport { currentTemplate, stackOrder } from './templates/index.ts';\n\n/**\n * Why a `.gitignore` found below the root is what it is.\n *\n * - `managed` — carries a gitignore-sync region already.\n * - `keeper` — exists only to hold an otherwise empty directory in git\n * (`*` plus `!.gitignore`). Generated by a framework, owned by it, and\n * nothing this tool should touch.\n * - `framework` — a stub at a path a framework owns (Laravel's `storage/`,\n * husky's `_`). Same verdict, different evidence.\n * - `plain` — an ordinary `.gitignore`. The only kind worth a decision.\n */\nexport type FoundKind = 'managed' | 'keeper' | 'framework' | 'plain';\n\nexport type Found = {\n /** Absolute path of the file. */\n file: string;\n /** Path relative to the root the scan started at. */\n relative: string;\n kind: FoundKind;\n};\n\n/**\n * Directory names never descended into, derived from the templates themselves.\n *\n * A stack that ignores a build directory is exactly a stack whose output must\n * not be scanned, so the list maintains itself: adding `turborepo` added\n * `.turbo` here with it. Hand-maintaining a second list would guarantee the two\n * drift.\n */\nexport function skipDirectories(): Set<string> {\n const skip = new Set(['.git']);\n for (const stack of stackOrder()) {\n for (const line of currentTemplate(stack)?.lines ?? []) {\n addDirectoryName(skip, line);\n }\n }\n return skip;\n}\n\n/**\n * Add a gitignore pattern to a skip set, if and only if it names a plain\n * directory. Only a bare name qualifies: no globs, and no path either. Taking\n * the last segment of `/public/build` or `/public/storage` would skip every\n * `build/` and `storage/` in the tree — including the Laravel stubs a\n * recursive scan is supposed to find and label, not hide.\n */\nfunction addDirectoryName(skip: Set<string>, line: string): void {\n const raw = line.trim();\n if (raw === '' || raw.startsWith('#') || raw.startsWith('!')) return;\n const name = raw.replace(/^\\/+/, '').replace(/\\/+$/, '');\n if (name === '' || /[*?[\\]]/.test(name) || name.includes('/')) return;\n skip.add(name);\n}\n\n/**\n * What a `.gitignore` says not to look at. A directory the repository itself\n * ignores is generated — a mutation-testing sandbox, a build tree, a vendored\n * copy — so a scan has no business walking into it, exactly as git has none.\n * This is what catches the output of tools no template knows about.\n */\nexport function ignoredDirectories(text: string): Set<string> {\n const skip = new Set<string>();\n for (const line of text.split('\\n')) addDirectoryName(skip, line);\n return skip;\n}\n\n/**\n * Directory paths a framework generates and owns. Matched against any position\n * in the path, not just its start, so a package inside a monorepo\n * (`services/core/storage/logs`) is recognised as readily as a root one.\n */\nconst FRAMEWORK_PATHS = [\n 'storage', // Laravel\n 'bootstrap/cache', // Laravel\n '.husky' // husky\n];\n\nconst underFrameworkPath = (rel: string): boolean => {\n const dir = rel.slice(0, rel.lastIndexOf('/') + 1);\n return FRAMEWORK_PATHS.some(\n (p) => dir === `${p}/` || dir.startsWith(`${p}/`) || dir.includes(`/${p}/`)\n );\n};\n\n/**\n * A file whose whole job is keeping an empty directory in git: it ignores\n * everything and un-ignores itself. Recognised by content, not by path, so it\n * holds for any framework that uses the idiom.\n */\nexport function isDirectoryKeeper(text: string): boolean {\n const lines = text\n .split('\\n')\n .map((l) => l.trim())\n .filter((l) => l !== '' && !l.startsWith('#'));\n if (lines.length === 0) return false;\n const ignoresEverything = lines.some((l) => l === '*' || l === '*.*');\n const keepsItself = lines.some((l) => l === '!.gitignore');\n return ignoresEverything && keepsItself;\n}\n\nfunction classify(file: string, rel: string): FoundKind {\n let text: string;\n try {\n text = readFileSync(file, 'utf8');\n } catch {\n return 'plain';\n }\n if (/^#\\s*region\\s+gitignore-sync\\s*$/m.test(text)) return 'managed';\n if (isDirectoryKeeper(text)) return 'keeper';\n if (underFrameworkPath(rel)) return 'framework';\n return 'plain';\n}\n\n/**\n * Every `.gitignore` at or below `root`.\n *\n * Non-recursive returns at most the root's own file, which is what every\n * command does by default — a repository has one managed region, and walking a\n * tree to find that out would be waste.\n */\nexport function findGitignores(\n root: string,\n options: { recursive?: boolean; maxDepth?: number } = {}\n): Found[] {\n const skip = skipDirectories();\n const found: Found[] = [];\n const maxDepth = options.maxDepth ?? 12;\n\n const walk = (dir: string, depth: number, inherited: Set<string>): void => {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n let here = inherited;\n for (const entry of entries) {\n if (entry.name === '.gitignore' && entry.isFile()) {\n const file = join(dir, '.gitignore');\n const rel = relative(root, file) || '.gitignore';\n found.push({ file, relative: rel, kind: classify(file, rel) });\n // What this file ignores applies to everything below it, so it joins\n // the skip set for this subtree and every subtree under it.\n try {\n here = new Set([\n ...here,\n ...ignoredDirectories(readFileSync(file, 'utf8'))\n ]);\n } catch {\n // Unreadable: carry on with what we already had.\n }\n }\n }\n if (!options.recursive || depth >= maxDepth) return;\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.isSymbolicLink()) continue;\n if (here.has(entry.name)) continue;\n const child = join(dir, entry.name);\n // A directory holding its own `.git` is a separate repository — a\n // submodule, a vendored checkout, or an agent worktree. Its `.gitignore`\n // belongs to that repo and would otherwise be counted here, several\n // times over.\n if (existsSync(join(child, '.git'))) continue;\n walk(child, depth + 1, here);\n }\n };\n\n walk(root, 0, skip);\n return found.sort((a, b) => a.relative.localeCompare(b.relative));\n}\n","/**\n * `# region` / `# endregion` rather than `# start` / `# end` for one concrete\n * reader: VSCode folds `#region`, so a 40-line managed block collapses to a\n * single line — and the nested form folds at both levels.\n */\nexport const REGION_NAME = 'gitignore-sync';\n\n/** `# region gitignore-sync` — the outer marker, matched leniently on spacing. */\nconst OUTER_START = new RegExp(`^#\\\\s*region\\\\s+${REGION_NAME}\\\\s*$`);\n\n/** `# region node@v1` — a section marker. */\nconst SECTION_START = /^#\\s*region\\s+([A-Za-z0-9][\\w.-]*)@v(\\d+)\\s*$/;\n\n/** Any region opener, whichever level it belongs to. */\nconst ANY_START = /^#\\s*region\\b/;\n\nconst ANY_END = /^#\\s*endregion\\s*$/;\n\nexport const isOuterStart = (line: string): boolean => OUTER_START.test(line);\nexport const isAnyStart = (line: string): boolean => ANY_START.test(line);\nexport const isEnd = (line: string): boolean => ANY_END.test(line);\n\nexport function matchSectionStart(\n line: string\n): { stack: string; version: number } | undefined {\n const m = SECTION_START.exec(line);\n if (!m?.[1] || !m[2]) return undefined;\n return { stack: m[1], version: Number(m[2]) };\n}\n\nexport const outerStartLine = (): string => `# region ${REGION_NAME}`;\nexport const sectionStartLine = (stack: string, version: number): string =>\n `# region ${stack}@v${version}`;\nexport const endLine = (): string => '# endregion';\n\n/** `# stacks: core, node` — the declaration the whole tool reads. */\nconst STACKS = /^#\\s*stacks:\\s*(.*)$/;\n\nexport function matchStacks(line: string): string[] | undefined {\n const m = STACKS.exec(line);\n if (!m) return undefined;\n return m[1]\n ? m[1]\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n : [];\n}\n\nexport const stacksLine = (stacks: string[]): string =>\n `# stacks: ${stacks.join(', ')}`;\n\n/** Cosmetic rule under the header; regenerated on every render. */\nexport const DIVIDER = `# ${'─'.repeat(41)}`;\n","/** One managed block inside the region, e.g. `# region node@v1` … `# endregion`. */\nexport type Section = {\n /** Stack name as written in the marker, e.g. `node`. */\n stack: string;\n /** Version from the marker (`node@v1` → 1), so `check` can spot a stale block. */\n version: number;\n /** Body lines, verbatim, markers excluded. */\n lines: string[];\n};\n\n/**\n * A parsed `.gitignore`.\n *\n * The header is **input** — it declares which stacks the repo wants. The\n * sections are **output** — what was last rendered. A difference between the\n * two is not an error, it is the pending change.\n */\nexport type Document = {\n /** Raw header lines of the region, up to the first section marker. */\n header: string[];\n sections: Section[];\n /** Everything outside the region. Never rendered from a template. */\n freeZone: string[];\n /** Whether the source text carried a managed region at all. */\n hasRegion: boolean;\n};\n\n/** A curated block shipped with the binary. Data, not code. */\nexport type Template = {\n stack: string;\n version: number;\n lines: string[];\n};\n\nexport class GitignoreParseError extends Error {\n readonly line: number;\n\n constructor(message: string, line: number) {\n super(`${message} (line ${line})`);\n this.name = 'GitignoreParseError';\n this.line = line;\n }\n}\n","import {\n isAnyStart,\n isEnd,\n isOuterStart,\n matchSectionStart,\n matchStacks,\n stacksLine\n} from './markers.ts';\nimport { GitignoreParseError, type Document, type Section } from './types.ts';\n\nconst isBlank = (line: string): boolean => line.trim() === '';\n\n/**\n * Split text into lines, dropping the single trailing empty string a\n * newline-terminated file produces. Touches no filesystem: string in, Document\n * out.\n */\nfunction toLines(text: string): string[] {\n const lines = text.split('\\n');\n if (lines.at(-1) === '') lines.pop();\n return lines.map((l) => (l.endsWith('\\r') ? l.slice(0, -1) : l));\n}\n\nfunction trimBlankEdges(lines: string[]): string[] {\n let start = 0;\n let end = lines.length;\n while (start < end && isBlank(lines[start] ?? '')) start++;\n while (end > start && isBlank(lines[end - 1] ?? '')) end--;\n return lines.slice(start, end);\n}\n\n/**\n * Recognise the header, the sections and the free zone.\n *\n * A file with no managed region parses as pure free zone — that is what `init`\n * sees on its first run.\n */\nexport function parse(text: string): Document {\n const lines = toLines(text);\n const outerStart = lines.findIndex(isOuterStart);\n\n if (outerStart === -1) {\n return {\n header: [],\n sections: [],\n freeZone: trimBlankEdges(lines),\n hasRegion: false\n };\n }\n\n const header: string[] = [];\n const sections: Section[] = [];\n /** Non-marker lines found at region level after the first section: strays. */\n const strays: string[] = [];\n\n let depth = 1;\n let index = outerStart + 1;\n let outerEnd = -1;\n let open: Section | undefined;\n\n /** A line the region holds but no marker of ours claims. */\n const keep = (line: string): void => {\n if (open) open.lines.push(line);\n else if (sections.length === 0) header.push(line);\n else if (!isBlank(line)) strays.push(line);\n };\n\n for (; index < lines.length; index++) {\n const line = lines[index] ?? '';\n\n if (isEnd(line)) {\n const next = depth - 1;\n if (next === 0) {\n outerEnd = index;\n break;\n }\n depth = next;\n if (next === 1 && open) {\n sections.push(open);\n open = undefined;\n continue;\n }\n // Closes a region we do not own, so it is content, not a marker.\n keep(line);\n continue;\n }\n\n if (isAnyStart(line)) {\n const section = matchSectionStart(line);\n depth++;\n if (depth === 2 && section && !open) {\n open = { stack: section.stack, version: section.version, lines: [] };\n continue;\n }\n // A foreign opener still nests, so its `# endregion` cannot be mistaken\n // for one of ours.\n keep(line);\n continue;\n }\n\n keep(line);\n }\n\n if (outerEnd === -1) {\n throw new GitignoreParseError(\n `unterminated '# region ${'gitignore-sync'}' — no matching '# endregion'`,\n outerStart + 1\n );\n }\n\n const before = lines.slice(0, outerStart);\n const after = lines.slice(outerEnd + 1);\n\n for (const section of sections) {\n section.lines = trimBlankEdges(section.lines);\n }\n\n return {\n header: trimBlankEdges(header),\n sections,\n freeZone: [...trimBlankEdges(before), ...trimBlankEdges(after), ...strays],\n hasRegion: true\n };\n}\n\n/** The stacks the header declares — the input side of the document. */\nexport function readStacks(doc: Document): string[] {\n for (const line of doc.header) {\n const stacks = matchStacks(line);\n if (stacks) return stacks;\n }\n return [];\n}\n\n/**\n * A copy of the document with a new `# stacks:` declaration. The header is the\n * only thing `add` and `remove` touch — the sections follow from it on the next\n * reconcile, which is the whole point of the header being input.\n */\nexport function withStacks(doc: Document, stacks: string[]): Document {\n const line = stacksLine(stacks);\n let replaced = false;\n const header = doc.header.map((l) => {\n if (replaced || matchStacks(l) === undefined) return l;\n replaced = true;\n return line;\n });\n return { ...doc, header: replaced ? header : [line, ...header] };\n}\n","import {\n currentTemplate,\n isKnownStack,\n templateAt\n} from '../templates/index.ts';\nimport { parse, readStacks } from './parse.ts';\nimport type { Document, Section } from './types.ts';\n\n/** Distinct spellings git treats as distinct, that a human reads as the same. */\nexport type Equivalence = { key: string; spellings: string[] };\n\n/**\n * A free-zone line that ignores a whole directory a managed block carves `!`\n * exceptions out of. Not a style problem: git does not descend into an ignored\n * directory, so the exceptions below it stop working entirely.\n */\nexport type SmotheredExceptions = {\n /** The offending free-zone line, e.g. `.idea` or `.vscode`. */\n line: string;\n /** The `!` lines it silently disables. */\n exceptions: string[];\n};\n\nexport type StaleSection = { stack: string; from: number; to: number };\n\nexport type Reconciliation = {\n document: Document;\n /** Hand-written lines lifted out of a managed section. Never dropped. */\n rescued: string[];\n /** Exact duplicates removed from the free zone. */\n duplicates: string[];\n /** Free-zone lines a managed section already covers, verbatim. */\n covered: string[];\n /** Headings whose every pattern a managed block absorbed. */\n orphanedComments: string[];\n /** Free-zone lines that disable a managed block's `!` exceptions. */\n smothered: SmotheredExceptions[];\n /** Reported only — `.idea`, `.idea/` and `/.idea` are not the same to git. */\n equivalences: Equivalence[];\n /** Declared in the header but shipped by no template. */\n unknownStacks: string[];\n /** Rendered at an older version than the binary ships. */\n staleSections: StaleSection[];\n};\n\nconst isBlank = (line: string): boolean => line.trim() === '';\nconst isComment = (line: string): boolean => line.trimStart().startsWith('#');\n\n/** Free-zone lines grouped the way a human reads them: blank lines separate. */\nfunction splitBlocks(lines: string[]): string[][] {\n const blocks: string[][] = [];\n let current: string[] = [];\n for (const line of lines) {\n if (isBlank(line)) {\n if (current.length > 0) blocks.push(current);\n current = [];\n continue;\n }\n current.push(line);\n }\n if (current.length > 0) blocks.push(current);\n return blocks;\n}\n\n/**\n * A human-eye normalisation, used for **reporting only**. `/.idea` anchors to\n * the repo root and `.idea/` matches directories only, so collapsing them would\n * change what git ignores — hence never a merge, only a note.\n */\nfunction equivalenceKey(line: string): string {\n let key = line.trim();\n const negated = key.startsWith('!');\n if (negated) key = key.slice(1);\n key = key\n .replace(/^\\/+/, '')\n .replace(/\\/\\*{1,2}$/, '')\n .replace(/\\/+$/, '');\n return negated ? `!${key}` : key;\n}\n\n/**\n * Bring a document in line with the templates its header declares.\n *\n * Pure: a Document in, a Document out. The rule that makes running this twice\n * safe lives here — a line inside a managed section that no template version\n * put there is moved to the free zone, never deleted.\n */\nexport function reconcile(doc: Document): Reconciliation {\n const declared = readStacks(doc);\n const unknownStacks = declared.filter((s) => !isKnownStack(s));\n const rescued: string[] = [];\n const staleSections: StaleSection[] = [];\n\n for (const section of doc.sections) {\n const shipped =\n templateAt(section.stack, section.version) ??\n currentTemplate(section.stack);\n // No template at all for this stack: this binary cannot tell a template\n // line from a hand-written one, so it rescues nothing and leaves the block\n // exactly as it found it.\n if (shipped) {\n const known = new Set(shipped.lines);\n for (const line of section.lines) {\n if (isBlank(line)) continue;\n if (!known.has(line)) rescued.push(line);\n }\n }\n\n const current = currentTemplate(section.stack);\n if (current && current.version !== section.version) {\n staleSections.push({\n stack: section.stack,\n from: section.version,\n to: current.version\n });\n }\n }\n\n const sections: Section[] = [];\n for (const stack of declared) {\n const template = currentTemplate(stack);\n if (template) {\n sections.push({\n stack: template.stack,\n version: template.version,\n lines: [...template.lines]\n });\n continue;\n }\n // Unknown stack: keep whatever is already there rather than delete a block\n // this binary simply does not understand.\n const existing = doc.sections.find((s) => s.stack === stack);\n if (existing) sections.push(existing);\n }\n\n const managed = new Set(sections.flatMap((s) => s.lines));\n const duplicates: string[] = [];\n const covered: string[] = [];\n const orphanedComments: string[] = [];\n const seen = new Set<string>();\n\n // Filtered block by block, not line by line. A comment is a heading for the\n // patterns under it: once a managed block has absorbed every one of them, the\n // heading is left pointing at nothing — which is most of what makes a\n // fetch-and-dump file long. A block that was only ever a note keeps standing.\n const freeZone: string[] = [];\n for (const block of splitBlocks([...doc.freeZone, ...rescued])) {\n const kept: string[] = [];\n let hadPattern = false;\n let keptPattern = false;\n\n for (const line of block) {\n if (isComment(line)) {\n kept.push(line);\n continue;\n }\n hadPattern = true;\n if (managed.has(line)) {\n covered.push(line);\n continue;\n }\n if (seen.has(line)) {\n duplicates.push(line);\n continue;\n }\n seen.add(line);\n keptPattern = true;\n kept.push(line);\n }\n\n if (hadPattern && !keptPattern) {\n orphanedComments.push(...kept);\n continue;\n }\n if (kept.length > 0) {\n if (freeZone.length > 0) freeZone.push('');\n freeZone.push(...kept);\n }\n }\n\n // A managed block that unignores `.vscode/extensions.json` is dead the moment\n // anything ignores `.vscode` outright — git never looks inside. This is the\n // single most common leftover across the estate, so it is worth its own\n // finding rather than a note about equivalent spellings.\n const smothered: SmotheredExceptions[] = [];\n const negations = [...managed].filter((line) => line.startsWith('!'));\n for (const line of freeZone) {\n if (isBlank(line) || isComment(line) || line.startsWith('!')) continue;\n const dir = line.trim().replace(/^\\/+/, '').replace(/\\/+$/, '');\n if (dir === '' || dir.includes('*')) continue;\n const exceptions = negations.filter((n) =>\n n.slice(1).replace(/^\\/+/, '').startsWith(`${dir}/`)\n );\n if (exceptions.length > 0) smothered.push({ line, exceptions });\n }\n\n const byKey = new Map<string, string[]>();\n for (const line of [...managed, ...freeZone]) {\n if (isBlank(line) || isComment(line)) continue;\n const key = equivalenceKey(line);\n const spellings = byKey.get(key);\n if (spellings) {\n if (!spellings.includes(line)) spellings.push(line);\n } else {\n byKey.set(key, [line]);\n }\n }\n // A line already reported as smothering a block's `!` exceptions does not\n // also need the general \"these are different patterns to git\" note — the\n // specific finding says everything the vague one would, and says what to do.\n // Other spellings in the same group still get theirs.\n const smotheredLines = new Set(smothered.map((s) => s.line));\n const equivalences = [...byKey]\n .map(([key, spellings]) => ({\n key,\n spellings: spellings.filter((s) => !smotheredLines.has(s))\n }))\n .filter(({ spellings }) => spellings.length > 1);\n\n return {\n document: {\n header: doc.header,\n sections,\n freeZone,\n hasRegion: doc.hasRegion\n },\n rescued,\n duplicates,\n covered,\n orphanedComments,\n smothered,\n equivalences,\n unknownStacks,\n staleSections\n };\n}\n\n/** Convenience for the commands and the tests: text in, reconciliation out. */\nexport const reconcileText = (text: string): Reconciliation =>\n reconcile(parse(text));\n","import { existsSync, readFileSync, statSync } from 'node:fs';\nimport { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { colors } from 'consola/utils';\nimport { basename, dirname } from 'pathe';\nimport { type Found, type FoundKind, findGitignores } from '../discover.ts';\nimport { DIVIDER, stacksLine } from '../gitignore/markers.ts';\nimport { parse } from '../gitignore/parse.ts';\nimport { reconcile } from '../gitignore/reconcile.ts';\nimport { stackOrder } from '../templates/index.ts';\n\ntype Row = {\n name: string;\n kind: FoundKind;\n file: string;\n /** Patterns in the file before reconciling: no blanks, no comments. */\n before: number;\n /** Patterns still in the free zone after every stack was applied. */\n after: number;\n leftovers: string[];\n};\n\ntype Report = {\n files: Row[];\n totals: { before: number; after: number; covered: number; percent: number };\n /** Stubs left unmeasured, and why. */\n skipped: { label: string; kind: FoundKind }[];\n /** Every leftover pattern with the files that carry it, most common first. */\n leftovers: { pattern: string; files: string[] }[];\n};\n\nconst isPattern = (line: string): boolean => {\n const t = line.trim();\n return t !== '' && !t.startsWith('#');\n};\n\n/**\n * Measure one file against every stack this binary ships.\n *\n * The point is not to rewrite anything — it is to answer \"what would still be\n * left over if this repo declared everything?\", because that remainder is what\n * decides whether a stack is missing or the lines are genuinely project rules.\n */\nfunction auditFile(found: Found, label: string): Row {\n const file = found.file;\n const text = readFileSync(file, 'utf8');\n const before = text.split('\\n').filter(isPattern).length;\n\n const doc = parse(text);\n const all = stackOrder();\n const result = reconcile({\n ...doc,\n header: [stacksLine(all), DIVIDER],\n hasRegion: true\n });\n const leftovers = result.document.freeZone\n .filter(isPattern)\n .map((l) => l.trim());\n\n return {\n name: label,\n kind: found.kind,\n file,\n before,\n after: leftovers.length,\n leftovers\n };\n}\n\nexport const auditCommand = defineCommand({\n meta: {\n name: 'audit',\n description:\n 'Measure how much of one or more repos the shipped stacks already cover'\n },\n args: {\n dirs: {\n type: 'positional',\n description: 'Repository directories (default: the current one)',\n required: false,\n default: '.'\n },\n 'min-files': {\n type: 'string',\n description: 'Only list leftovers carried by at least this many files',\n default: '1'\n },\n recursive: {\n type: 'boolean',\n description:\n 'Also measure .gitignore files below each directory, skipping build and dependency output',\n alias: 'r',\n default: false\n },\n 'include-stubs': {\n type: 'boolean',\n description:\n 'Measure framework stubs too, instead of listing them as skipped',\n default: false\n },\n json: {\n type: 'boolean',\n description: 'Emit a machine-readable JSON report',\n default: false\n }\n },\n run({ args }) {\n const raw = args._ && args._.length > 0 ? args._ : [args.dirs];\n const dirs = [\n ...new Set(\n raw.filter((d): d is string => typeof d === 'string' && d !== '')\n )\n ].filter((dir) => {\n if (!existsSync(dir) || !statSync(dir).isDirectory()) {\n consola.warn(`Not a directory, skipped: ${dir}`);\n return false;\n }\n return true;\n });\n\n if (dirs.length === 0) {\n consola.error('Nothing to audit.');\n process.exitCode = 1;\n return;\n }\n\n const targets: { found: Found; label: string }[] = [];\n const skipped: { label: string; kind: FoundKind }[] = [];\n for (const dir of dirs) {\n const hits = findGitignores(dir, { recursive: args.recursive });\n if (hits.length === 0) {\n consola.warn(`No .gitignore, skipped: ${dir}`);\n continue;\n }\n for (const found of hits) {\n // One directory reads as its own name; a recursive sweep needs the\n // path, or two `apps/*/.gitignore` rows are indistinguishable.\n const label =\n found.relative === '.gitignore'\n ? basename(dir)\n : `${basename(dir)}/${dirname(found.relative)}`;\n // A stub exists to hold an empty directory or is generated by a\n // framework. Measuring it against the stacks would report a leftover\n // for every line and suggest changing a file nobody owns.\n if (\n !args['include-stubs'] &&\n (found.kind === 'keeper' || found.kind === 'framework')\n ) {\n skipped.push({ label, kind: found.kind });\n continue;\n }\n targets.push({ found, label });\n }\n }\n\n if (targets.length === 0) {\n consola.error('Nothing to audit.');\n process.exitCode = 1;\n return;\n }\n\n const files = targets\n .map(({ found, label }) => auditFile(found, label))\n .sort((a, b) => b.before - a.before);\n const before = files.reduce((n, f) => n + f.before, 0);\n const after = files.reduce((n, f) => n + f.after, 0);\n\n const carriers = new Map<string, string[]>();\n for (const row of files) {\n for (const line of row.leftovers) {\n const seen = carriers.get(line);\n if (seen) seen.push(row.name);\n else carriers.set(line, [row.name]);\n }\n }\n const minFiles = Math.max(1, Number(args['min-files']) || 1);\n const leftovers = [...carriers]\n .map(([pattern, names]) => ({ pattern, files: names }))\n .filter((l) => l.files.length >= minFiles)\n .sort(\n (a, b) =>\n b.files.length - a.files.length || a.pattern.localeCompare(b.pattern)\n );\n\n const report: Report = {\n files,\n totals: {\n before,\n after,\n covered: before - after,\n percent:\n before === 0\n ? 100\n : Math.round(((before - after) / before) * 1000) / 10\n },\n leftovers,\n skipped\n };\n\n if (args.json) {\n process.stdout.write(`${JSON.stringify(report, null, 2)}\\n`);\n return;\n }\n\n const width = Math.max(...files.map((f) => f.name.length), 4);\n consola.log(\n ` ${colors.dim('repo'.padEnd(width))} ${colors.dim('before')} ${colors.dim('left')}`\n );\n for (const row of files) {\n consola.log(\n ` ${row.name.padEnd(width)} ${String(row.before).padStart(6)} ${String(row.after).padStart(4)}`\n );\n }\n consola.log(\n `\\n ${colors.bold(`${report.totals.before} patterns → ${report.totals.after} left`)} ${colors.dim(`(${report.totals.percent}% covered by the shipped stacks)`)}`\n );\n\n if (skipped.length > 0) {\n const keepers = skipped.filter((s) => s.kind === 'keeper').length;\n const framework = skipped.length - keepers;\n const parts = [\n keepers > 0 ? `${keepers} directory keeper(s)` : '',\n framework > 0 ? `${framework} framework stub(s)` : ''\n ].filter(Boolean);\n consola.log(\n `\\n ${colors.dim(`skipped ${parts.join(' and ')} — generated, and owned by the framework. --include-stubs measures them anyway.`)}`\n );\n }\n\n if (leftovers.length > 0) {\n consola.log(\n `\\n ${colors.dim(`leftovers carried by ${minFiles}+ file(s), most common first`)}`\n );\n for (const { pattern, files: names } of leftovers) {\n consola.log(\n ` ${String(names.length).padStart(3)}x ${pattern} ${colors.dim(names.slice(0, 4).join(', ') + (names.length > 4 ? ', …' : ''))}`\n );\n }\n }\n }\n});\n","import { endLine, outerStartLine, sectionStartLine } from './markers.ts';\nimport type { Document } from './types.ts';\n\n/**\n * Document back to text. The managed region comes first, the free zone below\n * it, exactly one blank line between every block — so a second render of a\n * rendered document is a no-op, which is what makes `check` a usable CI gate.\n */\nexport function render(doc: Document): string {\n const out: string[] = [];\n\n if (doc.hasRegion || doc.header.length > 0 || doc.sections.length > 0) {\n out.push(outerStartLine(), ...doc.header);\n\n for (const section of doc.sections) {\n out.push(\n '',\n sectionStartLine(section.stack, section.version),\n ...section.lines,\n endLine()\n );\n }\n\n out.push('', endLine());\n }\n\n if (doc.freeZone.length > 0) {\n if (out.length > 0) out.push('');\n out.push(...doc.freeZone);\n }\n\n return out.length > 0 ? `${out.join('\\n')}\\n` : '';\n}\n","import { readFileSync, writeFileSync } from 'node:fs';\nimport { isAbsolute, join, resolve } from 'pathe';\n\nexport const gitignorePath = (dir: string): string =>\n join(isAbsolute(dir) ? dir : resolve(dir), '.gitignore');\n\n/** A missing `.gitignore` reads as empty — that is `init`'s first-run case. */\nexport function readGitignore(dir: string): string {\n try {\n return readFileSync(gitignorePath(dir), 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return '';\n throw error;\n }\n}\n\nexport function writeGitignore(dir: string, text: string): void {\n writeFileSync(gitignorePath(dir), text, 'utf8');\n}\n","import consola from 'consola';\nimport { colors } from 'consola/utils';\nimport type { Reconciliation } from '../gitignore/reconcile.ts';\n\n/** Everything a run wants to say about a reconciliation, in one place. */\nexport function report(result: Reconciliation): void {\n for (const stack of result.unknownStacks) {\n consola.warn(\n `Header declares unknown stack ${colors.cyan(stack)} — no template ships for it, its block is left untouched.`\n );\n }\n for (const stale of result.staleSections) {\n consola.info(\n `${colors.cyan(stale.stack)} block is at v${stale.from}, this binary ships v${stale.to}.`\n );\n }\n if (result.rescued.length > 0) {\n consola.info(\n `Rescued ${result.rescued.length} hand-written line(s) from managed blocks into the free zone:`\n );\n for (const line of result.rescued) consola.log(` ${colors.green(line)}`);\n }\n if (result.covered.length > 0) {\n consola.info(\n `Removed ${result.covered.length} free-zone line(s) a managed block already covers:`\n );\n for (const line of result.covered) consola.log(` ${colors.dim(line)}`);\n }\n if (result.orphanedComments.length > 0) {\n consola.info(\n `Removed ${result.orphanedComments.length} heading(s) left pointing at nothing:`\n );\n for (const line of result.orphanedComments) {\n consola.log(` ${colors.dim(line)}`);\n }\n }\n if (result.duplicates.length > 0) {\n consola.info(`Removed ${result.duplicates.length} exact duplicate(s):`);\n for (const line of result.duplicates)\n consola.log(` ${colors.dim(line)}`);\n }\n for (const s of result.smothered) {\n // Not a style note: git never descends into an ignored directory.\n consola.warn(\n `${colors.yellow(s.line)} ignores the whole directory, which disables ${s.exceptions.map((e) => colors.cyan(e)).join(', ')}. Remove it — the managed block already covers it.`\n );\n }\n for (const eq of result.equivalences) {\n // Reported, never merged: these are different patterns to git.\n consola.warn(\n `${eq.spellings.map((s) => colors.yellow(s)).join(' / ')} mean different things to git — reported, not merged.`\n );\n }\n}\n","import { readFileSync } from 'node:fs';\nimport { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { colors } from 'consola/utils';\nimport { findGitignores } from '../discover.ts';\nimport { parse } from '../gitignore/parse.ts';\nimport { reconcile } from '../gitignore/reconcile.ts';\nimport { render } from '../gitignore/render.ts';\nimport { gitignorePath, readGitignore } from '../io.ts';\nimport { report } from './report.ts';\n\nexport const checkCommand = defineCommand({\n meta: {\n name: 'check',\n description:\n 'Dry run for CI: report drift and duplicates, exit non-zero on deviation'\n },\n args: {\n dir: {\n type: 'positional',\n description: 'Repository directory (default: the current one)',\n required: false,\n default: '.'\n },\n recursive: {\n type: 'boolean',\n description:\n 'Also check managed regions below this directory, skipping build and dependency output',\n alias: 'r',\n default: false\n }\n },\n run({ args }) {\n const dir = args.dir;\n\n // Recursive checks only what is already managed. A file without a region\n // is not drift — nobody asked for one there, and a gate that fails on\n // every unmanaged sub-directory would be unusable.\n if (args.recursive) {\n const managed = findGitignores(dir, { recursive: true }).filter(\n (f) => f.kind === 'managed'\n );\n if (managed.length === 0) {\n consola.error(\n `No managed region under ${dir} — run ${colors.cyan('gitignore-sync init')} first.`\n );\n process.exitCode = 1;\n return;\n }\n let drifted = 0;\n for (const found of managed) {\n const text = readFileSync(found.file, 'utf8');\n const result = reconcile(parse(text));\n report(result);\n if (render(result.document) === text) {\n consola.success(`${found.relative} is in sync.`);\n } else {\n consola.error(\n `${found.relative} is out of sync — run ${colors.cyan(`gitignore-sync sync ${found.file.slice(0, -'/.gitignore'.length)}`)}.`\n );\n drifted++;\n }\n }\n if (drifted > 0) process.exitCode = 1;\n return;\n }\n\n const text = readGitignore(dir);\n const doc = parse(text);\n\n if (!doc.hasRegion) {\n consola.error(\n `${gitignorePath(dir)} has no managed region — run ${colors.cyan('gitignore-sync init')} first.`\n );\n process.exitCode = 1;\n return;\n }\n\n const result = reconcile(doc);\n report(result);\n\n if (render(result.document) !== text) {\n consola.error(\n `${gitignorePath(dir)} is out of sync — run ${colors.cyan('gitignore-sync sync')}.`\n );\n process.exitCode = 1;\n return;\n }\n consola.success(`${gitignorePath(dir)} is in sync.`);\n }\n});\n","import { existsSync, readdirSync, type Dirent } from 'node:fs';\nimport { join } from 'pathe';\nimport { stackOrder } from './templates/index.ts';\n\n/**\n * Where a signal comes from, and therefore how much it may decide on its own.\n *\n * - `repo` — a committed marker (`package.json`, `go.mod`, a `*.tf` file). It\n * describes the project, so it holds for everyone who clones.\n * - `machine` — the checkout or the machine running the command: a `.vscode/`\n * directory (itself usually gitignored), `$EDITOR`, `process.platform`. It\n * describes one keyboard.\n *\n * Both are real evidence and both are detected the same way — one table, one\n * pass. The distinction only decides one thing: whether a signal may write the\n * header **without a human confirming it**. The header is committed, so a CI\n * runner's platform must not end up claiming something for the whole team.\n */\nexport type Source = 'repo' | 'machine';\n\nexport type Detected = { stack: string; source: Source };\n\ntype Fingerprint = {\n source: Source;\n /** Always found; `core` alone uses this. */\n always?: boolean;\n /** Any of these existing in the directory is enough. */\n files?: string[];\n /** Or any entry matching one of these, searched to `PATTERN_DEPTH`. */\n patterns?: RegExp[];\n /** Or the platform running the command. */\n platform?: string;\n /** Or `$VISUAL` / `$EDITOR`. */\n editor?: RegExp;\n};\n\n/**\n * How deep a pattern fingerprint looks. One level below the root, because a\n * Terraform repo keeps its stacks in `tofu/` or `terraform/` rather than\n * scattered across the root — and because going deeper starts finding other\n * people's examples.\n */\nconst PATTERN_DEPTH = 2;\n\n/**\n * Never descended into. `examples/` is here on purpose: a `.tf` file shown as\n * documentation is not a workspace that produces state, and treating it as one\n * makes every provider repo look like an infrastructure repo.\n */\nconst SKIP = new Set([\n '.git',\n 'node_modules',\n 'vendor',\n 'dist',\n 'coverage',\n 'examples',\n 'testdata',\n 'fixtures'\n]);\n\nconst fingerprints: Record<string, Fingerprint> = {\n core: { source: 'repo', always: true },\n // Every repository this runs in is one.\n git: { source: 'repo', files: ['.git'] },\n node: { source: 'repo', files: ['package.json'] },\n turborepo: { source: 'repo', files: ['turbo.json'] },\n // Root `Cargo.toml` only — see the comment on the template.\n rust: { source: 'repo', files: ['Cargo.toml'] },\n playwright: {\n source: 'repo',\n patterns: [/^playwright\\.config\\.[cm]?[jt]s$/]\n },\n storybook: { source: 'repo', files: ['.storybook'] },\n php: { source: 'repo', files: ['composer.json'] },\n laravel: { source: 'repo', files: ['artisan'] },\n go: { source: 'repo', files: ['go.mod'] },\n tofu: {\n source: 'repo',\n patterns: [/\\.tf$/, /\\.tofu$/, /^\\.terraform\\.lock\\.hcl$/]\n },\n nuxt: { source: 'repo', patterns: [/^nuxt\\.config\\.[cm]?[jt]s$/] },\n tauri: { source: 'repo', files: ['src-tauri'] },\n // A committed `.env.example` is the marker, not `.env` — the latter is the\n // very file the stack exists to keep out, so it is often absent on a fresh\n // clone and present on a machine that already ran the setup.\n dotenv: { source: 'repo', files: ['.env.example', '.env'] },\n vscode: { source: 'machine', files: ['.vscode'] },\n intellij: { source: 'machine', files: ['.idea'] },\n vim: { source: 'machine', editor: /\\b(?:vi|vim|nvim)\\b/ },\n macos: { source: 'machine', platform: 'darwin' },\n windows: { source: 'machine', platform: 'win32' },\n linux: { source: 'machine', platform: 'linux' }\n};\n\n/** Always proposed; removing it is then a visible act. */\nexport const BASE_STACK = 'core';\n\nfunction matchesPatterns(\n dir: string,\n patterns: RegExp[],\n depth: number\n): boolean {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return false;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) continue;\n if (patterns.some((pattern) => pattern.test(entry.name))) return true;\n }\n if (depth <= 1) return false;\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name.startsWith('.')) continue;\n if (SKIP.has(entry.name)) continue;\n if (matchesPatterns(join(dir, entry.name), patterns, depth - 1))\n return true;\n }\n return false;\n}\n\nfunction matches(dir: string, print: Fingerprint): boolean {\n if (print.always) return true;\n if (print.files?.some((file) => existsSync(join(dir, file)))) return true;\n if (print.platform && process.platform === print.platform) return true;\n if (print.editor) {\n const editor = process.env.VISUAL ?? process.env.EDITOR ?? '';\n if (print.editor.test(editor)) return true;\n }\n if (print.patterns)\n return matchesPatterns(dir, print.patterns, PATTERN_DEPTH);\n return false;\n}\n\n/**\n * The only filesystem contact in the tool. `parse`, `render` and `reconcile`\n * stay string-in/string-out so the hard part is testable with fixture pairs.\n *\n * Returns registry order, so the header reads the same on every run.\n */\nexport function detect(dir: string): Detected[] {\n const found: Detected[] = [];\n for (const stack of stackOrder()) {\n const print = fingerprints[stack];\n if (print && matches(dir, print)) {\n found.push({ stack, source: print.source });\n }\n }\n return found;\n}\n\n/** Just the names, for callers that do not care where a signal came from. */\nexport const stacksOf = (found: Detected[]): string[] =>\n found.map((d) => d.stack);\n\n/**\n * What may be written without asking. Only committed evidence: `init --yes`\n * and the no-TTY path run in CI, where the platform is the runner's and the\n * editor directory is nobody's.\n */\nexport const committedOnly = (found: Detected[]): Detected[] =>\n found.filter((d) => d.source === 'repo');\n","import consola from 'consola';\nimport type { Detected } from '../detect.ts';\nimport { currentTemplate, stackOrder } from '../templates/index.ts';\n\n/**\n * The one multiselect both `init` and `edit` show, so the two read the same\n * however you got there. Returns `null` when the prompt was cancelled — the\n * caller writes nothing in that case.\n */\nexport async function promptStacks(options: {\n message: string;\n /** Pre-ticked. `init` seeds this from detection, `edit` from the header. */\n initial: Set<string>;\n /** What detection found, with where each signal came from. */\n found: Detected[];\n declared: Set<string>;\n /** Restrict the list — `init` uses it to offer only what it did not find. */\n choices?: string[];\n}): Promise<string[] | null> {\n const offered = options.choices ?? stackOrder();\n const source = new Map(options.found.map((d) => [d.stack, d.source]));\n const choices = offered.map((stack) => {\n const template = currentTemplate(stack);\n const marks: string[] = [];\n if (source.has(stack)) marks.push('detected');\n if (options.declared.has(stack)) marks.push('declared');\n return {\n value: stack,\n label: stack,\n hint: `${template?.lines.length ?? 0} lines${marks.length > 0 ? ` · ${marks.join(', ')}` : ''}`\n };\n });\n\n const answer = await consola.prompt(options.message, {\n type: 'multiselect',\n options: choices,\n initial: offered.filter((s) => options.initial.has(s)),\n // \"None of these\" is a real answer — most repos want no extra stack at all.\n required: false,\n cancel: 'null'\n });\n\n if (answer === null || answer === undefined) return null;\n const chosen = new Set((answer as unknown as string[]).map(String));\n const picked = offered.filter((stack) => chosen.has(stack));\n\n // A declared stack this binary ships no template for is kept: neither verb\n // may quietly drop something it simply cannot render.\n for (const stack of options.declared) {\n if (!picked.includes(stack) && !stackOrder().includes(stack)) {\n picked.push(stack);\n }\n }\n return picked;\n}\n\n/** A prompt with no terminal to read from would hang or take its defaults. */\nexport const canPrompt = (): boolean => Boolean(process.stdin.isTTY);\n","import { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { colors } from 'consola/utils';\nimport { detect } from '../detect.ts';\nimport { stacksLine } from '../gitignore/markers.ts';\nimport { parse, readStacks, withStacks } from '../gitignore/parse.ts';\nimport { reconcile } from '../gitignore/reconcile.ts';\nimport { render } from '../gitignore/render.ts';\nimport { gitignorePath, readGitignore, writeGitignore } from '../io.ts';\nimport { canPrompt, promptStacks } from './prompt.ts';\nimport { report } from './report.ts';\n\nexport const editCommand = defineCommand({\n meta: {\n name: 'edit',\n description: 'Tick stacks on and off in a prompt, then re-render'\n },\n args: {\n dir: {\n type: 'positional',\n description: 'Repository directory (default: the current one)',\n required: false,\n default: '.'\n },\n 'dry-run': {\n type: 'boolean',\n description: 'Print the result instead of writing it',\n default: false\n }\n },\n async run({ args }) {\n const dir = args.dir;\n\n const text = readGitignore(dir);\n const doc = parse(text);\n\n // The state of the file first, the state of the terminal second: without a\n // region there is nothing to edit however you invoked this, and \"run init\"\n // is the useful thing to say.\n if (!doc.hasRegion) {\n consola.error(\n `${gitignorePath(dir)} has no managed region — run ${colors.cyan('gitignore-sync init')} first.`\n );\n process.exitCode = 1;\n return;\n }\n\n if (!canPrompt()) {\n consola.error(\n `${colors.cyan('edit')} needs an interactive terminal. Use ${colors.cyan('add')} / ${colors.cyan('remove')} in a script or CI.`\n );\n process.exitCode = 1;\n return;\n }\n\n const declared = new Set(readStacks(doc));\n const next = await promptStacks({\n message: 'Which stacks does this repo want?',\n initial: declared,\n found: detect(dir),\n declared\n });\n\n if (next === null) {\n consola.info('Cancelled — nothing written.');\n return;\n }\n\n const result = reconcile(withStacks(doc, next));\n const output = render(result.document);\n\n consola.log(` ${colors.dim(stacksLine(next))}`);\n report(result);\n\n if (args['dry-run']) {\n consola.log(output);\n return;\n }\n if (output === text) {\n consola.success(`${gitignorePath(dir)} is already in sync.`);\n return;\n }\n writeGitignore(dir, output);\n consola.success(`Wrote ${gitignorePath(dir)}`);\n }\n});\n","import { existsSync, readFileSync, realpathSync } from 'node:fs';\nimport { defineCommand } from 'citty';\nimport { colors } from 'consola/utils';\nimport { dirname, join, resolve } from 'pathe';\nimport { parse, readStacks } from '../gitignore/parse.ts';\nimport { reconcile } from '../gitignore/reconcile.ts';\nimport { render } from '../gitignore/render.ts';\nimport { gitignorePath, readGitignore } from '../io.ts';\nimport { currentTemplate, stackOrder } from '../templates/index.ts';\n\n// Injected at build time by vite's `define`, sourced from package.json's\n// version. Read it the same way `cli.ts` does rather than re-inventing a\n// drift-prone literal.\ndeclare const __APP_VERSION__: string;\n\n/** The package.json `name` this build is identified by. */\nconst PACKAGE_NAME = '@kirchdev/gitignore-sync';\n/** What a person types, and what the report is headed with. */\nconst BIN_NAME = 'gitignore-sync';\n\n/** `linked` = runs from a git work tree; `release` = an installed package. */\ntype BuildKind = 'linked' | 'release' | 'unknown';\n\ntype Info = {\n version: string;\n build: { kind: BuildKind; packageRoot: string | null; reason: string };\n binary: { invoked: string | null; resolved: string | null };\n node: string;\n templates: { stacks: number; patterns: number };\n repository: {\n file: string;\n exists: boolean;\n hasRegion: boolean;\n stacks: string[];\n /** `in sync`, `drifted`, `no region`, or the parse error. */\n status: string;\n };\n};\n\n/** Resolve the real executed file behind any shim or symlink. */\nfunction resolveBinary(entry: string | undefined): Info['binary'] {\n if (!entry) return { invoked: null, resolved: null };\n try {\n return { invoked: entry, resolved: realpathSync(entry) };\n } catch {\n // The file vanished or is unreadable — report the invoked path as-is\n // rather than failing. `info` describes, it never judges.\n return { invoked: entry, resolved: entry };\n }\n}\n\n/** Walk up from `start` to the nearest package.json, returning its dir + name. */\nfunction findPackageRoot(\n start: string\n): { dir: string; name: string | undefined } | null {\n let dir = resolve(start);\n for (;;) {\n const pkgPath = join(dir, 'package.json');\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as {\n name?: string;\n };\n return { dir, name: pkg.name };\n } catch {\n return { dir, name: undefined };\n }\n }\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * Tell a linked dev build from an installed release: the resolved binary lives\n * inside a git work tree whose package.json carries this package's name. When\n * neither can be established, report `unknown` rather than guessing.\n */\nfunction detectBuild(resolved: string | null): Info['build'] {\n if (!resolved) {\n return {\n kind: 'unknown',\n packageRoot: null,\n reason: 'binary path could not be resolved'\n };\n }\n const pkg = findPackageRoot(dirname(resolved));\n if (!pkg) {\n return {\n kind: 'unknown',\n packageRoot: null,\n reason: 'no package.json found above the binary'\n };\n }\n if (pkg.name !== PACKAGE_NAME) {\n return {\n kind: 'unknown',\n packageRoot: pkg.dir,\n reason: `nearest package.json is \"${pkg.name ?? 'unnamed'}\", not ${PACKAGE_NAME}`\n };\n }\n // Check `.git` at the package root only, never above it: an installed copy\n // under a consumer's node_modules would otherwise inherit that repo's .git\n // and be mislabelled as linked.\n const inGitTree = existsSync(join(pkg.dir, '.git'));\n return {\n kind: inGitTree ? 'linked' : 'release',\n packageRoot: pkg.dir,\n reason: inGitTree\n ? `runs from a git work tree named ${PACKAGE_NAME}`\n : `installed ${PACKAGE_NAME} package (no git work tree beside it)`\n };\n}\n\nfunction inspectRepository(dir: string): Info['repository'] {\n const file = gitignorePath(dir);\n const exists = existsSync(file);\n if (!exists) {\n return { file, exists, hasRegion: false, stacks: [], status: 'no file' };\n }\n const text = readGitignore(dir);\n try {\n const doc = parse(text);\n if (!doc.hasRegion) {\n return {\n file,\n exists,\n hasRegion: false,\n stacks: [],\n status: 'no region'\n };\n }\n const result = reconcile(doc);\n return {\n file,\n exists,\n hasRegion: true,\n stacks: readStacks(doc),\n status: render(result.document) === text ? 'in sync' : 'drifted'\n };\n } catch (error) {\n // A file this binary cannot parse is still worth describing.\n return {\n file,\n exists,\n hasRegion: false,\n stacks: [],\n status: (error as Error).message\n };\n }\n}\n\nconst BUILD_LABELS: Record<BuildKind, string> = {\n linked: 'linked / dev build',\n release: 'installed release',\n unknown: 'unknown'\n};\n\nconst row = (label: string, value: string): string =>\n ` ${colors.dim(label.padEnd(9))} ${value}\\n`;\n\nfunction renderPretty(info: Info): string {\n let out = `${colors.bold(BIN_NAME)} ${colors.cyan(`v${info.version}`)} ${colors.dim(`(${BUILD_LABELS[info.build.kind]})`)}\\n`;\n out += row('build', colors.dim(info.build.reason));\n if (info.build.packageRoot) out += row('package', info.build.packageRoot);\n out += row('binary', info.binary.resolved ?? colors.dim('unknown'));\n if (info.binary.invoked && info.binary.invoked !== info.binary.resolved) {\n out += row('', colors.dim(`via ${info.binary.invoked}`));\n }\n out += row('node', info.node);\n\n out += `\\n${colors.bold('templates')}\\n`;\n out += row(\n 'ships',\n `${info.templates.stacks} stacks, ${info.templates.patterns} patterns`\n );\n\n out += `\\n${colors.bold('repository')}\\n`;\n out += row('file', info.repository.file);\n const status =\n info.repository.status === 'in sync'\n ? colors.green(info.repository.status)\n : info.repository.status === 'drifted'\n ? colors.yellow(info.repository.status)\n : colors.dim(info.repository.status);\n out += row('status', status);\n if (info.repository.stacks.length > 0) {\n out += row(\n 'stacks',\n info.repository.stacks.map((s) => colors.cyan(s)).join(', ')\n );\n }\n return out;\n}\n\nexport const infoCommand = defineCommand({\n meta: {\n name: 'info',\n description:\n 'Describe this installation: version, how it was installed, and the repo it sees'\n },\n args: {\n dir: {\n type: 'positional',\n description: 'Repository directory (default: the current one)',\n required: false,\n default: '.'\n },\n json: {\n type: 'boolean',\n description: 'Emit a machine-readable JSON report',\n default: false\n }\n },\n run({ args }) {\n const binary = resolveBinary(process.argv[1]);\n const info: Info = {\n version: __APP_VERSION__,\n build: detectBuild(binary.resolved),\n binary,\n node: process.version,\n templates: {\n stacks: stackOrder().length,\n patterns: stackOrder().reduce(\n (n, s) => n + (currentTemplate(s)?.lines.length ?? 0),\n 0\n )\n },\n repository: inspectRepository(args.dir)\n };\n\n if (args.json) {\n process.stdout.write(`${JSON.stringify(info, null, 2)}\\n`);\n return;\n }\n process.stdout.write(renderPretty(info));\n }\n});\n\nexport const __test = { resolveBinary, findPackageRoot, detectBuild };\n","import { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { colors } from 'consola/utils';\nimport { parse, readStacks, withStacks } from '../gitignore/parse.ts';\nimport { reconcile } from '../gitignore/reconcile.ts';\nimport { render } from '../gitignore/render.ts';\nimport { gitignorePath, readGitignore, writeGitignore } from '../io.ts';\nimport { isKnownStack, knownStacks, stackOrder } from '../templates/index.ts';\nimport { report } from './report.ts';\n\nconst args = {\n stacks: {\n type: 'positional' as const,\n description: 'One or more stack names',\n required: true\n },\n dir: {\n type: 'string' as const,\n description: 'Repository directory (default: the current one)',\n default: '.'\n },\n 'dry-run': {\n type: 'boolean' as const,\n description: 'Print the result instead of writing it',\n default: false\n }\n};\n\n/**\n * `add` and `remove` edit one line: the `# stacks:` declaration. Everything\n * below it is re-rendered from that line, so the two verbs stay a convenience\n * over an edit you could also make by hand — never a second source of truth.\n */\nfunction edit(mode: 'add' | 'remove') {\n return (context: {\n args: {\n stacks?: string;\n dir?: string;\n 'dry-run'?: boolean;\n _?: string[];\n };\n }) => {\n const a = context.args;\n const dir = a.dir ?? '.';\n // citty puts every positional in `_` and the first one *also* in `stacks`,\n // so reading both would count the first name twice.\n const raw = a._ && a._.length > 0 ? a._ : [a.stacks];\n const names = [\n ...new Set(\n raw.filter((n): n is string => typeof n === 'string' && n !== '')\n )\n ];\n\n if (names.length === 0) {\n consola.error('Name at least one stack.');\n consola.info(`Available: ${knownStacks().join(', ')}`);\n process.exitCode = 1;\n return;\n }\n\n const text = readGitignore(dir);\n const doc = parse(text);\n\n if (!doc.hasRegion) {\n consola.error(\n `${gitignorePath(dir)} has no managed region — run ${colors.cyan('gitignore-sync init')} first.`\n );\n process.exitCode = 1;\n return;\n }\n\n if (mode === 'add') {\n const unknown = names.filter((n) => !isKnownStack(n));\n if (unknown.length > 0) {\n consola.error(\n `Unknown stack: ${unknown.map((u) => colors.yellow(u)).join(', ')}`\n );\n consola.info(`Available: ${knownStacks().join(', ')}`);\n process.exitCode = 1;\n return;\n }\n }\n\n const before = readStacks(doc);\n const set = new Set(before);\n const changed: string[] = [];\n\n for (const name of names) {\n if (mode === 'add') {\n if (set.has(name)) continue;\n set.add(name);\n } else {\n if (!set.has(name)) continue;\n set.delete(name);\n }\n changed.push(name);\n }\n\n if (changed.length === 0) {\n consola.info(\n mode === 'add'\n ? 'Already declared — nothing to add.'\n : 'Not declared — nothing to remove.'\n );\n return;\n }\n\n // Registry order, so the header reads the same however the stacks arrived.\n const order = stackOrder();\n const next = [...set].sort((x, y) => {\n const ix = order.indexOf(x);\n const iy = order.indexOf(y);\n if (ix === -1 && iy === -1) return x.localeCompare(y);\n if (ix === -1) return 1;\n if (iy === -1) return -1;\n return ix - iy;\n });\n\n const result = reconcile(withStacks(doc, next));\n const output = render(result.document);\n\n consola.info(\n `${mode === 'add' ? 'Added' : 'Removed'} ${changed.map((c) => colors.cyan(c)).join(', ')}`\n );\n consola.log(` ${colors.dim(`# stacks: ${next.join(', ')}`)}`);\n report(result);\n\n if (a['dry-run']) {\n consola.log(output);\n return;\n }\n writeGitignore(dir, output);\n consola.success(`Wrote ${gitignorePath(dir)}`);\n };\n}\n\nexport const addCommand = defineCommand({\n meta: {\n name: 'add',\n description: 'Add stacks to the header and re-render'\n },\n args,\n run: edit('add')\n});\n\nexport const removeCommand = defineCommand({\n meta: {\n name: 'remove',\n alias: 'rm',\n description: 'Remove stacks from the header and re-render'\n },\n args,\n run: edit('remove')\n});\n","import { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { colors } from 'consola/utils';\nimport { committedOnly, detect, stacksOf } from '../detect.ts';\nimport { DIVIDER, stacksLine } from '../gitignore/markers.ts';\nimport { parse } from '../gitignore/parse.ts';\nimport { reconcile } from '../gitignore/reconcile.ts';\nimport { render } from '../gitignore/render.ts';\nimport { gitignorePath, readGitignore, writeGitignore } from '../io.ts';\nimport { stackOrder } from '../templates/index.ts';\nimport { canPrompt, promptStacks } from './prompt.ts';\nimport { report } from './report.ts';\n\nexport const initCommand = defineCommand({\n meta: {\n name: 'init',\n description:\n 'Fingerprint the repo, confirm the stacks in a prompt and write the managed region'\n },\n args: {\n dir: {\n type: 'positional',\n description: 'Repository directory (default: the current one)',\n required: false,\n default: '.'\n },\n stacks: {\n type: 'string',\n description: 'Comma-separated stacks; skips the prompt'\n },\n yes: {\n type: 'boolean',\n description: 'Take what detection proposes without asking',\n alias: 'y',\n default: false\n },\n 'dry-run': {\n type: 'boolean',\n description: 'Print the result instead of writing it',\n default: false\n },\n force: {\n type: 'boolean',\n description: 'Replace an existing header instead of refusing',\n default: false\n }\n },\n async run({ args }) {\n const dir = args.dir;\n const text = readGitignore(dir);\n const existing = parse(text);\n\n if (existing.hasRegion && !args.force) {\n consola.error(\n `${gitignorePath(dir)} already carries a managed region. Use ${colors.cyan('gitignore-sync edit')} to change it, ${colors.cyan('sync')} to re-render, or pass --force to start over.`\n );\n process.exitCode = 1;\n return;\n }\n\n const found = detect(dir);\n const detected = new Set(stacksOf(found));\n let stacks: string[];\n\n if (args.stacks) {\n stacks = args.stacks\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n } else if (args.yes || !canPrompt()) {\n // No terminal, or told not to ask: only committed evidence writes. This\n // is the path CI takes, so it must never wait for input — and a runner's\n // platform must not end up claiming something for the whole team.\n stacks = stacksOf(committedOnly(found));\n consola.info(`Detected ${stacks.map((s) => colors.cyan(s)).join(', ')}`);\n const skipped = stacksOf(found).filter((s) => !stacks.includes(s));\n if (skipped.length > 0) {\n consola.log(\n ` ${colors.dim(`Skipped ${skipped.join(', ')} — editor and platform need a human to confirm them. Add with \\`gitignore-sync add\\`.`)}`\n );\n }\n } else {\n // Two steps rather than one long multiselect: confirm what was found,\n // then offer the rest.\n consola.info(\n `Found ${detected.size} stack${detected.size === 1 ? '' : 's'}: ${[...detected].map((s) => colors.cyan(s)).join(', ')}`\n );\n\n const takeProposal = await consola.prompt('Use them?', {\n type: 'confirm',\n initial: true,\n cancel: 'null'\n });\n\n if (takeProposal === null || takeProposal === undefined) {\n consola.info('Cancelled — nothing written.');\n return;\n }\n\n if (takeProposal) {\n const rest = stackOrder().filter((s) => !detected.has(s));\n const extra = await promptStacks({\n message: 'Anything else?',\n initial: new Set(),\n found,\n declared: new Set(),\n choices: rest\n });\n if (extra === null) {\n consola.info('Cancelled — nothing written.');\n return;\n }\n stacks = stackOrder().filter(\n (s) => detected.has(s) || extra.includes(s)\n );\n } else {\n // Nothing pre-ticked: \"no\" rejected the proposal, so re-offering it as\n // the default would just be something to un-tick. Each row still says\n // where its signal came from.\n const picked = await promptStacks({\n message: 'Pick the stacks yourself:',\n initial: new Set(),\n found,\n declared: new Set()\n });\n if (picked === null) {\n consola.info('Cancelled — nothing written.');\n return;\n }\n stacks = picked;\n }\n }\n\n // The header is input, the sections are output: init only ever writes the\n // declaration, and reconcile renders the blocks from it.\n const seeded = {\n ...existing,\n header: [stacksLine(stacks), DIVIDER],\n hasRegion: true\n };\n const result = reconcile(seeded);\n const output = render(result.document);\n\n consola.log(` ${colors.dim(stacksLine(stacks))}`);\n report(result);\n\n if (args['dry-run']) {\n consola.log(output);\n return;\n }\n writeGitignore(dir, output);\n consola.success(`Wrote ${gitignorePath(dir)}`);\n }\n});\n","import { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { colors } from 'consola/utils';\nimport { parse, readStacks } from '../gitignore/parse.ts';\nimport { readGitignore } from '../io.ts';\nimport { currentTemplate, knownStacks } from '../templates/index.ts';\n\nexport const listCommand = defineCommand({\n meta: {\n name: 'list',\n alias: 'ls',\n description: 'List the stacks this binary ships and which the repo declares'\n },\n args: {\n dir: {\n type: 'positional',\n description: 'Repository directory (default: the current one)',\n required: false,\n default: '.'\n }\n },\n run({ args }) {\n const declared = new Set(readStacks(parse(readGitignore(args.dir))));\n for (const stack of knownStacks()) {\n const template = currentTemplate(stack);\n const mark = declared.has(stack) ? colors.green('●') : colors.dim('○');\n consola.log(\n ` ${mark} ${colors.cyan(stack)}@v${template?.version ?? '?'} ${colors.dim(`${template?.lines.length ?? 0} ${template?.lines.length === 1 ? 'line' : 'lines'}`)}`\n );\n }\n for (const stack of declared) {\n if (!currentTemplate(stack)) {\n consola.log(\n ` ${colors.yellow('?')} ${colors.cyan(stack)} ${colors.dim('declared, no template')}`\n );\n }\n }\n }\n});\n","import { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { colors } from 'consola/utils';\nimport { detect, stacksOf } from '../detect.ts';\nimport { parse, readStacks } from '../gitignore/parse.ts';\nimport { reconcile } from '../gitignore/reconcile.ts';\nimport { render } from '../gitignore/render.ts';\nimport { gitignorePath, readGitignore, writeGitignore } from '../io.ts';\nimport { report } from './report.ts';\n\nexport const syncCommand = defineCommand({\n meta: {\n name: 'sync',\n description:\n 'Read the header, re-render the managed blocks from it. Detects nothing.'\n },\n args: {\n dir: {\n type: 'positional',\n description: 'Repository directory (default: the current one)',\n required: false,\n default: '.'\n },\n detect: {\n type: 'boolean',\n description:\n 'Additionally propose header changes from the tree — never applies them',\n default: false\n },\n 'dry-run': {\n type: 'boolean',\n description: 'Print the result instead of writing it',\n default: false\n }\n },\n run({ args }) {\n const dir = args.dir;\n const text = readGitignore(dir);\n const doc = parse(text);\n\n if (!doc.hasRegion) {\n consola.error(\n `${gitignorePath(dir)} has no managed region — run ${colors.cyan('gitignore-sync init')} first.`\n );\n process.exitCode = 1;\n return;\n }\n\n const result = reconcile(doc);\n const output = render(result.document);\n\n report(result);\n\n // Detection changes its moment, it does not disappear: adding a\n // package.json must not quietly rewrite someone's .gitignore, so this only\n // ever prints a suggestion.\n if (args.detect) {\n const declared = new Set(readStacks(doc));\n const missing = stacksOf(detect(dir)).filter((s) => !declared.has(s));\n if (missing.length === 0) {\n consola.info('Detection proposes no header change.');\n } else {\n consola.info(\n `Detection suggests adding ${missing.map((s) => colors.cyan(s)).join(', ')} to the ${colors.cyan('# stacks:')} line. Not applied.`\n );\n }\n }\n\n if (args['dry-run']) {\n consola.log(output);\n return;\n }\n if (output === text) {\n consola.success(`${gitignorePath(dir)} is already in sync.`);\n return;\n }\n writeGitignore(dir, output);\n consola.success(`Wrote ${gitignorePath(dir)}`);\n }\n});\n","import { defineCommand } from 'citty';\nimport { auditCommand } from './commands/audit.ts';\nimport { checkCommand } from './commands/check.ts';\nimport { editCommand } from './commands/edit.ts';\nimport { infoCommand } from './commands/info.ts';\nimport { addCommand, removeCommand } from './commands/stacks.ts';\nimport { initCommand } from './commands/init.ts';\nimport { listCommand } from './commands/list.ts';\nimport { syncCommand } from './commands/sync.ts';\n\n// Injected at build time by vite's `define` (see vite.config.ts), sourced from\n// package.json's `version`, which release-please owns.\ndeclare const __APP_VERSION__: string;\n\nexport const rootCommand = defineCommand({\n meta: {\n name: 'gitignore-sync',\n version: __APP_VERSION__,\n description:\n \"Keep a repo's .gitignore maintained: curated blocks in a managed region, re-rendered on demand\"\n },\n subCommands: {\n init: initCommand,\n edit: editCommand,\n add: addCommand,\n remove: removeCommand,\n sync: syncCommand,\n check: checkCommand,\n list: listCommand,\n audit: auditCommand,\n info: infoCommand\n }\n});\n","import { runMain } from 'citty';\nimport { rootCommand } from '../cli.ts';\n\n// `gitignore-sync list | head` closes stdout while consola is still writing to\n// it. Node turns that into an unhandled 'error' event and a stack trace; every\n// other CLI in a pipe just stops. Swallow it and exit quietly.\nprocess.stdout.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EPIPE') process.exit(0);\n throw error;\n});\n\nrunMain(rootCommand);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAeA,IAAM,WAAuC;CAK3C,MAAM,CACJ;EACE,OAAO;EACP,SAAS;EACT,OAAO,CAAC,aAAa,6BAA6B;CACpD,CACF;CAIA,KAAK,CACH;EACE,OAAO;EACP,SAAS;EACT,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;CACF,CACF;CAKA,MAAM,CACJ;EACE,OAAO;EACP,SAAS;EACT,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;CACF,CACF;CAIA,IAAI,CACF;EACE,OAAO;EACP,SAAS;EACT,OAAO;GAAC;GAAS;GAAU;GAAS;EAAQ;CAC9C,CACF;CAIA,MAAM,CACJ;EACE,OAAO;EACP,SAAS;EACT,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;CACF,CACF;CAIA,KAAK,CACH;EACE,OAAO;EACP,SAAS;EACT,OAAO;GAAC;GAAW;GAAmB;EAAwB;CAChE,CACF;CACA,SAAS,CACP;EACE,OAAO;EACP,SAAS;EACT,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;CACF,CACF;CAGA,WAAW,CACT;EACE,OAAO;EACP,SAAS;EACT,OAAO,CAAC,QAAQ;CAClB,CACF;CAKA,MAAM,CACJ;EACE,OAAO;EACP,SAAS;EACT,OAAO,CAAC,WAAW,YAAY;CACjC,CACF;CACA,YAAY,CACV;EACE,OAAO;EACP,SAAS;EACT,OAAO;GACL;GACA;GACA;GACA;EACF;CACF,CACF;CACA,WAAW,CACT;EACE,OAAO;EACP,SAAS;EACT,OAAO,CAAC,kBAAkB;CAC5B,CACF;CACA,MAAM,CACJ;EACE,OAAO;EACP,SAAS;EACT,OAAO;GAAC;GAAS;GAAW;GAAU;EAAO;CAC/C,CACF;CACA,OAAO,CACL;EACE,OAAO;EACP,SAAS;EACT,OAAO,CAAC,oBAAoB,uBAAuB;CACrD,CACF;CAIA,QAAQ,CACN;EACE,OAAO;EACP,SAAS;EACT,OAAO;GAAC;GAAQ;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"}
1
+ {"version":3,"file":"gitignore-sync.mjs","names":[],"sources":["../../src/templates/index.ts","../../src/discover.ts","../../src/gitignore/markers.ts","../../src/gitignore/types.ts","../../src/gitignore/parse.ts","../../src/gitignore/reconcile.ts","../../src/commands/audit.ts","../../src/gitignore/render.ts","../../src/io.ts","../../src/commands/report.ts","../../src/commands/check.ts","../../src/detect.ts","../../src/commands/prompt.ts","../../src/commands/edit.ts","../../src/commands/info.ts","../../src/commands/stacks.ts","../../src/commands/init.ts","../../src/commands/list.ts","../../src/commands/sync.ts","../../src/cli.ts","../../src/bin/gitignore-sync.ts"],"sourcesContent":["import type { Template } from '../gitignore/types.ts';\n\n/**\n * Curated blocks, versioned with the binary. Data, not code.\n *\n * Every version a stack has ever shipped is kept, ascending. Reconciling reads\n * the version the section marker names, so upgrading `node@v1` to `node@v2`\n * knows which of the block's lines it put there itself and which the user\n * added — without that history an upgrade would \"rescue\" its own dropped lines\n * into the free zone.\n *\n * **No line may appear in two stacks.** A repo declaring both would otherwise\n * render it twice, and the equivalence report would nag about a collision the\n * tool created itself. `tests/templates.test.ts` enforces this.\n */\nconst registry: Record<string, Template[]> = {\n // The one file every repo in the estate ignores (26 of 26): a mixed-OS team\n // collects it whoever commits, which is why it sits here rather than in\n // `macos`. The rest of the macOS noise is rare and does live there.\n //\n // Nothing tool-specific belongs in `core` — the agent settings moved to\n // `agents` for exactly that reason.\n core: [\n {\n stack: 'core',\n version: 1,\n lines: ['.DS_Store']\n }\n ],\n // Agent working files, not agent configuration. The distinction is the whole\n // point: across the estate 126 files under `.claude/`, `.codex/` and\n // `.opencode/` are committed — settings.json, skills, rules, agent\n // definitions. Only these two describe one machine.\n //\n // `.claude/worktrees/` is included because it accumulates for real, and today\n // the only repo that ignores it does so in `.git/info/exclude` — private\n // knowledge that belongs in a file the whole team can see.\n agents: [\n {\n stack: 'agents',\n version: 1,\n lines: ['.claude/settings.local.json', '.claude/worktrees/']\n }\n ],\n // Conflict and backup droppings every git repo can produce. Fingerprinted on\n // `.git` itself, so `init` proposes it everywhere — but it stays a stack you\n // can drop, which is why it is not folded into `core`.\n git: [\n {\n stack: 'git',\n version: 1,\n lines: [\n '*.orig',\n '*.rej',\n '*.BACKUP.*',\n '*.BASE.*',\n '*.LOCAL.*',\n '*.REMOTE.*',\n '*_BACKUP_*.txt',\n '*_BASE_*.txt',\n '*_LOCAL_*.txt',\n '*_REMOTE_*.txt'\n ]\n }\n ],\n // `node_modules` lives here, not in `core`: a pure Go repo then correctly\n // does not get it. That it lands almost everywhere in practice (26 of 28\n // local repos carry a package.json) is a consequence of the estate, not a\n // reason to blur the model.\n node: [\n {\n stack: 'node',\n version: 1,\n lines: [\n 'node_modules',\n 'dist',\n 'coverage',\n // Anchored: a bare `logs` matches every directory of that name at any\n // depth, including Laravel's `storage/logs` and the keeper it commits\n // there. Nothing in the estate has a root `logs/`, so the anchor costs\n // nothing. Found by running the rollout against `gildstone`.\n '/logs',\n '*.log',\n '*.tsbuildinfo',\n '.eslintcache',\n '.npm',\n '*.tgz'\n ]\n }\n ],\n // Build and test droppings only. `dist` is deliberately absent — goreleaser\n // writes there, but so does every bundler, and the line belongs to whichever\n // stack claims it once. `node` claims it.\n go: [\n {\n stack: 'go',\n version: 1,\n lines: ['*.exe', '*.test', '*.out', '*.prof']\n }\n ],\n // State and local overrides. `.terraform.lock.hcl` is deliberately **not**\n // ignored — it is meant to be committed. `*.tfvars` usually carries\n // credentials, so it is ignored with the example carved back out.\n tofu: [\n {\n stack: 'tofu',\n version: 1,\n lines: [\n '.terraform/',\n '*.tfstate',\n '*.tfstate.*',\n '*.tfvars',\n '!*.tfvars.example',\n 'override.tf',\n 'override.tf.json',\n '.terraformrc',\n 'terraform.rc',\n 'crash.log',\n 'crash.*.log'\n ]\n }\n ],\n // `composer.lock` is not here on purpose: a library ignores it, an\n // application commits it. That is a project decision, so it belongs in the\n // free zone rather than in a block the tool re-renders.\n php: [\n {\n stack: 'php',\n version: 1,\n lines: ['/vendor', '/.phpunit.cache', '/.phpunit.result.cache']\n }\n ],\n // `/storage/pail` is deliberately absent, and so is every other `storage/`\n // subdirectory: Laravel commits a directory keeper in each of them (`*` plus\n // `!.gitignore`), which already ignores their contents. Repeating the path\n // here would ignore the keeper itself — the same defect a bare `.vscode`\n // causes, only self-inflicted. Found by running the rollout against `app`.\n laravel: [\n {\n stack: 'laravel',\n version: 1,\n lines: [\n '/public/build',\n '/public/hot',\n '/public/storage',\n '/storage/*.key',\n '/bootstrap/ssr',\n '_ide_helper.php',\n '_ide_helper_models.php',\n '.phpstorm.meta.php'\n ]\n }\n ],\n // Three repos in the estate run turbo (`oggsbreinig`, `gildstone`, and the\n // campus monorepo); all three ignore its cache.\n turborepo: [\n {\n stack: 'turborepo',\n version: 1,\n lines: ['.turbo']\n }\n ],\n // The crate target dir. Fingerprinted on a **root** `Cargo.toml` only: a\n // Tauri app keeps one under `src-tauri/`, and that build output is already\n // the `tauri` stack's, so matching it here would render a `/target` line\n // pointing at nothing.\n rust: [\n {\n stack: 'rust',\n version: 1,\n lines: ['/target', '**/*.rs.bk']\n }\n ],\n playwright: [\n {\n stack: 'playwright',\n version: 1,\n lines: [\n 'test-results/',\n 'playwright-report/',\n 'blob-report/',\n '.last-run.json'\n ]\n }\n ],\n storybook: [\n {\n stack: 'storybook',\n version: 1,\n lines: ['storybook-static']\n }\n ],\n nuxt: [\n {\n stack: 'nuxt',\n version: 1,\n lines: ['.nuxt', '.output', '.nitro', '.data']\n }\n ],\n tauri: [\n {\n stack: 'tauri',\n version: 1,\n lines: ['src-tauri/target', 'src-tauri/gen/schemas']\n }\n ],\n // Language-agnostic: the three Go providers carry `.env` just as the Laravel\n // app does. The `!` exceptions matter — six local repos track a committed\n // `.env.example`, and a bare `.env.*` would swallow it.\n dotenv: [\n {\n stack: 'dotenv',\n version: 1,\n lines: [\n '.env',\n '.env.*',\n '!.env.example',\n '!.env.*.example',\n // Laravel commits this one alongside the example: it holds the test\n // suite's settings, not credentials.\n '!.env.testing'\n ]\n }\n ],\n // `.vscode/*` plus targeted `!` exceptions, never a bare `.vscode/`: git does\n // not descend into an ignored directory, which makes exceptions under it\n // technically impossible.\n //\n // The three exceptions are the ones actually tracked across the estate —\n // `extensions.json` in every repo that shares anything, `settings.json` in\n // seven, `mcp.json` in two. `tasks.json` and `*.code-snippets`, which the\n // toptal block unignores, are tracked by no repo at all and are left out.\n vscode: [\n {\n stack: 'vscode',\n version: 1,\n lines: [\n '.vscode/*',\n '!.vscode/extensions.json',\n '!.vscode/settings.json',\n '!.vscode/mcp.json'\n ]\n }\n ],\n // `.idea/*` and no exceptions: no repo in the estate tracks any `.idea`\n // content, so sharing it is so far theoretical. The `/*` form is kept anyway\n // so an exception can be added later without a breaking reshape.\n intellij: [\n {\n stack: 'intellij',\n version: 1,\n lines: ['.idea/*']\n }\n ],\n // `.DS_Store` is in `core`, not here — see the comment there.\n macos: [\n {\n stack: 'macos',\n version: 1,\n // `Icon` is deliberately absent: toptal writes it with a trailing CR, and\n // without that byte the pattern matches every file named \"Icon\".\n lines: [\n '.AppleDouble',\n '.LSOverride',\n '._*',\n '.Spotlight-V100',\n '.Trashes',\n '.DocumentRevisions-V100',\n '.fseventsd',\n '.TemporaryItems',\n '.VolumeIcon.icns',\n '.com.apple.timemachine.donotpresent',\n '.AppleDB',\n '.AppleDesktop',\n 'Network Trash Folder',\n 'Temporary Items',\n '.apdisk',\n '*.icloud'\n ]\n }\n ],\n windows: [\n {\n stack: 'windows',\n version: 1,\n lines: [\n 'Thumbs.db',\n 'Thumbs.db:encryptable',\n 'ehthumbs.db',\n 'ehthumbs_vista.db',\n '[Dd]esktop.ini',\n '$RECYCLE.BIN/',\n '*.stackdump',\n '*.cab',\n '*.msi',\n '*.msix',\n '*.msm',\n '*.msp',\n '*.lnk'\n ]\n }\n ],\n // Swap and session files. A person-level stack like the two editors above —\n // nothing in a repo says who edits it with vim.\n vim: [\n {\n stack: 'vim',\n version: 1,\n lines: ['*.swp', '*.swo', '*.swn', 'Session.vim', '.netrwhist']\n }\n ],\n linux: [\n {\n stack: 'linux',\n version: 1,\n lines: ['*~', '.fuse_hidden*', '.directory', '.Trash-*', '.nfs*']\n }\n ]\n};\n\n/**\n * Registry insertion order, which is the order `init` writes into the header:\n * `core` first, then repository stacks, then the person-level ones. Stable, so\n * the `# stacks:` line does not churn between runs.\n */\nexport const stackOrder = (): string[] => Object.keys(registry);\n\nexport const knownStacks = (): string[] => Object.keys(registry).sort();\n\nexport const isKnownStack = (stack: string): boolean => stack in registry;\n\n/** The version a fresh render of this stack produces. */\nexport function currentTemplate(stack: string): Template | undefined {\n return registry[stack]?.at(-1);\n}\n\n/** A specific past version, for working out what a section put there itself. */\nexport function templateAt(\n stack: string,\n version: number\n): Template | undefined {\n return registry[stack]?.find((t) => t.version === version);\n}\n","import { existsSync, readFileSync, readdirSync, type Dirent } from 'node:fs';\nimport { join, relative } from 'pathe';\nimport { currentTemplate, stackOrder } from './templates/index.ts';\n\n/**\n * Why a `.gitignore` found below the root is what it is.\n *\n * - `managed` — carries a gitignore-sync region already.\n * - `keeper` — exists only to hold an otherwise empty directory in git\n * (`*` plus `!.gitignore`). Generated by a framework, owned by it, and\n * nothing this tool should touch.\n * - `framework` — a stub at a path a framework owns (Laravel's `storage/`,\n * husky's `_`). Same verdict, different evidence.\n * - `plain` — an ordinary `.gitignore`. The only kind worth a decision.\n */\nexport type FoundKind = 'managed' | 'keeper' | 'framework' | 'plain';\n\nexport type Found = {\n /** Absolute path of the file. */\n file: string;\n /** Path relative to the root the scan started at. */\n relative: string;\n kind: FoundKind;\n};\n\n/**\n * Directory names never descended into, derived from the templates themselves.\n *\n * A stack that ignores a build directory is exactly a stack whose output must\n * not be scanned, so the list maintains itself: adding `turborepo` added\n * `.turbo` here with it. Hand-maintaining a second list would guarantee the two\n * drift.\n */\nexport function skipDirectories(): Set<string> {\n const skip = new Set(['.git']);\n for (const stack of stackOrder()) {\n for (const line of currentTemplate(stack)?.lines ?? []) {\n addDirectoryName(skip, line);\n }\n }\n return skip;\n}\n\n/**\n * Add a gitignore pattern to a skip set, if and only if it names a plain\n * directory. Only a bare name qualifies: no globs, and no path either. Taking\n * the last segment of `/public/build` or `/public/storage` would skip every\n * `build/` and `storage/` in the tree — including the Laravel stubs a\n * recursive scan is supposed to find and label, not hide.\n */\nfunction addDirectoryName(skip: Set<string>, line: string): void {\n const raw = line.trim();\n if (raw === '' || raw.startsWith('#') || raw.startsWith('!')) return;\n const name = raw.replace(/^\\/+/, '').replace(/\\/+$/, '');\n if (name === '' || /[*?[\\]]/.test(name) || name.includes('/')) return;\n skip.add(name);\n}\n\n/**\n * What a `.gitignore` says not to look at. A directory the repository itself\n * ignores is generated — a mutation-testing sandbox, a build tree, a vendored\n * copy — so a scan has no business walking into it, exactly as git has none.\n * This is what catches the output of tools no template knows about.\n */\nexport function ignoredDirectories(text: string): Set<string> {\n const skip = new Set<string>();\n for (const line of text.split('\\n')) addDirectoryName(skip, line);\n return skip;\n}\n\n/**\n * Directory paths a framework generates and owns. Matched against any position\n * in the path, not just its start, so a package inside a monorepo\n * (`services/core/storage/logs`) is recognised as readily as a root one.\n */\nconst FRAMEWORK_PATHS = [\n 'storage', // Laravel\n 'bootstrap/cache', // Laravel\n '.husky' // husky\n];\n\nconst underFrameworkPath = (rel: string): boolean => {\n const dir = rel.slice(0, rel.lastIndexOf('/') + 1);\n return FRAMEWORK_PATHS.some(\n (p) => dir === `${p}/` || dir.startsWith(`${p}/`) || dir.includes(`/${p}/`)\n );\n};\n\n/**\n * A file whose whole job is keeping an empty directory in git: it ignores\n * everything and un-ignores itself. Recognised by content, not by path, so it\n * holds for any framework that uses the idiom.\n */\nexport function isDirectoryKeeper(text: string): boolean {\n const lines = text\n .split('\\n')\n .map((l) => l.trim())\n .filter((l) => l !== '' && !l.startsWith('#'));\n if (lines.length === 0) return false;\n const ignoresEverything = lines.some((l) => l === '*' || l === '*.*');\n const keepsItself = lines.some((l) => l === '!.gitignore');\n return ignoresEverything && keepsItself;\n}\n\nfunction classify(file: string, rel: string): FoundKind {\n let text: string;\n try {\n text = readFileSync(file, 'utf8');\n } catch {\n return 'plain';\n }\n if (/^#\\s*region\\s+gitignore-sync\\s*$/m.test(text)) return 'managed';\n if (isDirectoryKeeper(text)) return 'keeper';\n if (underFrameworkPath(rel)) return 'framework';\n return 'plain';\n}\n\n/**\n * Every `.gitignore` at or below `root`.\n *\n * Non-recursive returns at most the root's own file, which is what every\n * command does by default — a repository has one managed region, and walking a\n * tree to find that out would be waste.\n */\nexport function findGitignores(\n root: string,\n options: { recursive?: boolean; maxDepth?: number } = {}\n): Found[] {\n const skip = skipDirectories();\n const found: Found[] = [];\n const maxDepth = options.maxDepth ?? 12;\n\n const walk = (dir: string, depth: number, inherited: Set<string>): void => {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return;\n }\n let here = inherited;\n for (const entry of entries) {\n if (entry.name === '.gitignore' && entry.isFile()) {\n const file = join(dir, '.gitignore');\n const rel = relative(root, file) || '.gitignore';\n found.push({ file, relative: rel, kind: classify(file, rel) });\n // What this file ignores applies to everything below it, so it joins\n // the skip set for this subtree and every subtree under it.\n try {\n here = new Set([\n ...here,\n ...ignoredDirectories(readFileSync(file, 'utf8'))\n ]);\n } catch {\n // Unreadable: carry on with what we already had.\n }\n }\n }\n if (!options.recursive || depth >= maxDepth) return;\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.isSymbolicLink()) continue;\n if (here.has(entry.name)) continue;\n const child = join(dir, entry.name);\n // A directory holding its own `.git` is a separate repository — a\n // submodule, a vendored checkout, or an agent worktree. Its `.gitignore`\n // belongs to that repo and would otherwise be counted here, several\n // times over.\n if (existsSync(join(child, '.git'))) continue;\n walk(child, depth + 1, here);\n }\n };\n\n walk(root, 0, skip);\n return found.sort((a, b) => a.relative.localeCompare(b.relative));\n}\n","/**\n * `# region` / `# endregion` rather than `# start` / `# end` for one concrete\n * reader: VSCode folds `#region`, so a 40-line managed block collapses to a\n * single line — and the nested form folds at both levels.\n */\nexport const REGION_NAME = 'gitignore-sync';\n\n/** `# region gitignore-sync` — the outer marker, matched leniently on spacing. */\nconst OUTER_START = new RegExp(`^#\\\\s*region\\\\s+${REGION_NAME}\\\\s*$`);\n\n/** `# region node@v1` — a section marker. */\nconst SECTION_START = /^#\\s*region\\s+([A-Za-z0-9][\\w.-]*)@v(\\d+)\\s*$/;\n\n/** Any region opener, whichever level it belongs to. */\nconst ANY_START = /^#\\s*region\\b/;\n\nconst ANY_END = /^#\\s*endregion\\s*$/;\n\nexport const isOuterStart = (line: string): boolean => OUTER_START.test(line);\nexport const isAnyStart = (line: string): boolean => ANY_START.test(line);\nexport const isEnd = (line: string): boolean => ANY_END.test(line);\n\nexport function matchSectionStart(\n line: string\n): { stack: string; version: number } | undefined {\n const m = SECTION_START.exec(line);\n if (!m?.[1] || !m[2]) return undefined;\n return { stack: m[1], version: Number(m[2]) };\n}\n\nexport const outerStartLine = (): string => `# region ${REGION_NAME}`;\nexport const sectionStartLine = (stack: string, version: number): string =>\n `# region ${stack}@v${version}`;\nexport const endLine = (): string => '# endregion';\n\n/** `# stacks: core, node` — the declaration the whole tool reads. */\nconst STACKS = /^#\\s*stacks:\\s*(.*)$/;\n\nexport function matchStacks(line: string): string[] | undefined {\n const m = STACKS.exec(line);\n if (!m) return undefined;\n return m[1]\n ? m[1]\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean)\n : [];\n}\n\n/** Deduplicated, first mention winning — a name twice says nothing twice. */\nexport const stacksLine = (stacks: string[]): string =>\n `# stacks: ${[...new Set(stacks)].join(', ')}`;\n\n/** Cosmetic rule under the header; regenerated on every render. */\nexport const DIVIDER = `# ${'─'.repeat(41)}`;\n","/** One managed block inside the region, e.g. `# region node@v1` … `# endregion`. */\nexport type Section = {\n /** Stack name as written in the marker, e.g. `node`. */\n stack: string;\n /** Version from the marker (`node@v1` → 1), so `check` can spot a stale block. */\n version: number;\n /** Body lines, verbatim, markers excluded. */\n lines: string[];\n};\n\n/**\n * A parsed `.gitignore`.\n *\n * The header is **input** — it declares which stacks the repo wants. The\n * sections are **output** — what was last rendered. A difference between the\n * two is not an error, it is the pending change.\n */\nexport type Document = {\n /** Raw header lines of the region, up to the first section marker. */\n header: string[];\n sections: Section[];\n /** Everything outside the region. Never rendered from a template. */\n freeZone: string[];\n /** Whether the source text carried a managed region at all. */\n hasRegion: boolean;\n};\n\n/** A curated block shipped with the binary. Data, not code. */\nexport type Template = {\n stack: string;\n version: number;\n lines: string[];\n};\n\nexport class GitignoreParseError extends Error {\n readonly line: number;\n\n constructor(message: string, line: number) {\n super(`${message} (line ${line})`);\n this.name = 'GitignoreParseError';\n this.line = line;\n }\n}\n","import {\n isAnyStart,\n isEnd,\n isOuterStart,\n matchSectionStart,\n matchStacks,\n stacksLine\n} from './markers.ts';\nimport { GitignoreParseError, type Document, type Section } from './types.ts';\n\nconst isBlank = (line: string): boolean => line.trim() === '';\n\n/**\n * Split text into lines, dropping the single trailing empty string a\n * newline-terminated file produces. Touches no filesystem: string in, Document\n * out.\n */\nfunction toLines(text: string): string[] {\n const lines = text.split('\\n');\n if (lines.at(-1) === '') lines.pop();\n return lines.map((l) => (l.endsWith('\\r') ? l.slice(0, -1) : l));\n}\n\nfunction trimBlankEdges(lines: string[]): string[] {\n let start = 0;\n let end = lines.length;\n while (start < end && isBlank(lines[start] ?? '')) start++;\n while (end > start && isBlank(lines[end - 1] ?? '')) end--;\n return lines.slice(start, end);\n}\n\n/**\n * Recognise the header, the sections and the free zone.\n *\n * A file with no managed region parses as pure free zone — that is what `init`\n * sees on its first run.\n */\nexport function parse(text: string): Document {\n const lines = toLines(text);\n const outerStart = lines.findIndex(isOuterStart);\n\n if (outerStart === -1) {\n return {\n header: [],\n sections: [],\n freeZone: trimBlankEdges(lines),\n hasRegion: false\n };\n }\n\n const header: string[] = [];\n const sections: Section[] = [];\n /** Non-marker lines found at region level after the first section: strays. */\n const strays: string[] = [];\n\n let depth = 1;\n let index = outerStart + 1;\n let outerEnd = -1;\n let open: Section | undefined;\n\n /** A line the region holds but no marker of ours claims. */\n const keep = (line: string): void => {\n if (open) open.lines.push(line);\n else if (sections.length === 0) header.push(line);\n else if (!isBlank(line)) strays.push(line);\n };\n\n for (; index < lines.length; index++) {\n const line = lines[index] ?? '';\n\n if (isEnd(line)) {\n const next = depth - 1;\n if (next === 0) {\n outerEnd = index;\n break;\n }\n depth = next;\n if (next === 1 && open) {\n sections.push(open);\n open = undefined;\n continue;\n }\n // Closes a region we do not own, so it is content, not a marker.\n keep(line);\n continue;\n }\n\n if (isAnyStart(line)) {\n const section = matchSectionStart(line);\n depth++;\n if (depth === 2 && section && !open) {\n open = { stack: section.stack, version: section.version, lines: [] };\n continue;\n }\n // A foreign opener still nests, so its `# endregion` cannot be mistaken\n // for one of ours.\n keep(line);\n continue;\n }\n\n keep(line);\n }\n\n if (outerEnd === -1) {\n throw new GitignoreParseError(\n `unterminated '# region ${'gitignore-sync'}' — no matching '# endregion'`,\n outerStart + 1\n );\n }\n\n const before = lines.slice(0, outerStart);\n const after = lines.slice(outerEnd + 1);\n\n for (const section of sections) {\n section.lines = trimBlankEdges(section.lines);\n }\n\n return {\n header: trimBlankEdges(header),\n sections,\n freeZone: [...trimBlankEdges(before), ...trimBlankEdges(after), ...strays],\n hasRegion: true\n };\n}\n\n/** The stacks the header declares — the input side of the document. */\nexport function readStacks(doc: Document): string[] {\n for (const line of doc.header) {\n const stacks = matchStacks(line);\n // Deduplicated, first mention winning: a header that names a stack twice\n // must not render its block twice.\n if (stacks) return [...new Set(stacks)];\n }\n return [];\n}\n\n/**\n * A copy of the document with a new `# stacks:` declaration. The header is the\n * only thing `add` and `remove` touch — the sections follow from it on the next\n * reconcile, which is the whole point of the header being input.\n */\nexport function withStacks(doc: Document, stacks: string[]): Document {\n const line = stacksLine(stacks);\n let replaced = false;\n const header = doc.header.map((l) => {\n if (replaced || matchStacks(l) === undefined) return l;\n replaced = true;\n return line;\n });\n return { ...doc, header: replaced ? header : [line, ...header] };\n}\n","import {\n currentTemplate,\n isKnownStack,\n templateAt\n} from '../templates/index.ts';\nimport { parse, readStacks } from './parse.ts';\nimport type { Document, Section } from './types.ts';\n\n/** Distinct spellings git treats as distinct, that a human reads as the same. */\nexport type Equivalence = { key: string; spellings: string[] };\n\n/**\n * A free-zone line that ignores a whole directory a managed block carves `!`\n * exceptions out of. Not a style problem: git does not descend into an ignored\n * directory, so the exceptions below it stop working entirely.\n */\nexport type SmotheredExceptions = {\n /** The offending free-zone line, e.g. `.idea` or `.vscode`. */\n line: string;\n /** The `!` lines it silently disables. */\n exceptions: string[];\n};\n\nexport type StaleSection = { stack: string; from: number; to: number };\n\nexport type Reconciliation = {\n document: Document;\n /** Hand-written lines lifted out of a managed section. Never dropped. */\n rescued: string[];\n /** Exact duplicates removed from the free zone. */\n duplicates: string[];\n /** Free-zone lines a managed section already covers, verbatim. */\n covered: string[];\n /** Headings whose every pattern a managed block absorbed. */\n orphanedComments: string[];\n /** Free-zone lines that disable a managed block's `!` exceptions. */\n smothered: SmotheredExceptions[];\n /** Reported only — `.idea`, `.idea/` and `/.idea` are not the same to git. */\n equivalences: Equivalence[];\n /** Declared in the header but shipped by no template. */\n unknownStacks: string[];\n /** Rendered at an older version than the binary ships. */\n staleSections: StaleSection[];\n};\n\nconst isBlank = (line: string): boolean => line.trim() === '';\nconst isComment = (line: string): boolean => line.trimStart().startsWith('#');\n\n/** Free-zone lines grouped the way a human reads them: blank lines separate. */\nfunction splitBlocks(lines: string[]): string[][] {\n const blocks: string[][] = [];\n let current: string[] = [];\n for (const line of lines) {\n if (isBlank(line)) {\n if (current.length > 0) blocks.push(current);\n current = [];\n continue;\n }\n current.push(line);\n }\n if (current.length > 0) blocks.push(current);\n return blocks;\n}\n\n/**\n * A human-eye normalisation, used for **reporting only**. `/.idea` anchors to\n * the repo root and `.idea/` matches directories only, so collapsing them would\n * change what git ignores — hence never a merge, only a note.\n */\nfunction equivalenceKey(line: string): string {\n let key = line.trim();\n const negated = key.startsWith('!');\n if (negated) key = key.slice(1);\n key = key\n .replace(/^\\/+/, '')\n .replace(/\\/\\*{1,2}$/, '')\n .replace(/\\/+$/, '');\n return negated ? `!${key}` : key;\n}\n\n/**\n * Bring a document in line with the templates its header declares.\n *\n * Pure: a Document in, a Document out. The rule that makes running this twice\n * safe lives here — a line inside a managed section that no template version\n * put there is moved to the free zone, never deleted.\n */\nexport function reconcile(doc: Document): Reconciliation {\n const declared = readStacks(doc);\n const unknownStacks = declared.filter((s) => !isKnownStack(s));\n const rescued: string[] = [];\n const staleSections: StaleSection[] = [];\n\n for (const section of doc.sections) {\n const shipped =\n templateAt(section.stack, section.version) ??\n currentTemplate(section.stack);\n // No template at all for this stack: this binary cannot tell a template\n // line from a hand-written one, so it rescues nothing and leaves the block\n // exactly as it found it.\n if (shipped) {\n const known = new Set(shipped.lines);\n for (const line of section.lines) {\n if (isBlank(line)) continue;\n if (!known.has(line)) rescued.push(line);\n }\n }\n\n const current = currentTemplate(section.stack);\n if (current && current.version !== section.version) {\n staleSections.push({\n stack: section.stack,\n from: section.version,\n to: current.version\n });\n }\n }\n\n const sections: Section[] = [];\n // A stack named twice in the header renders its block twice, and the\n // duplicate lines then read as drift on every later run. First mention wins.\n const seenStack = new Set<string>();\n for (const stack of declared) {\n if (seenStack.has(stack)) continue;\n seenStack.add(stack);\n const template = currentTemplate(stack);\n if (template) {\n sections.push({\n stack: template.stack,\n version: template.version,\n lines: [...template.lines]\n });\n continue;\n }\n // Unknown stack: keep whatever is already there rather than delete a block\n // this binary simply does not understand.\n const existing = doc.sections.find((s) => s.stack === stack);\n if (existing) sections.push(existing);\n }\n\n const managed = new Set(sections.flatMap((s) => s.lines));\n const duplicates: string[] = [];\n const covered: string[] = [];\n const orphanedComments: string[] = [];\n const seen = new Set<string>();\n\n // Filtered block by block, not line by line. A comment is a heading for the\n // patterns under it: once a managed block has absorbed every one of them, the\n // heading is left pointing at nothing — which is most of what makes a\n // fetch-and-dump file long. A block that was only ever a note keeps standing.\n const freeZone: string[] = [];\n for (const block of splitBlocks([...doc.freeZone, ...rescued])) {\n const kept: string[] = [];\n let hadPattern = false;\n let keptPattern = false;\n\n for (const line of block) {\n if (isComment(line)) {\n kept.push(line);\n continue;\n }\n hadPattern = true;\n if (managed.has(line)) {\n covered.push(line);\n continue;\n }\n if (seen.has(line)) {\n duplicates.push(line);\n continue;\n }\n seen.add(line);\n keptPattern = true;\n kept.push(line);\n }\n\n if (hadPattern && !keptPattern) {\n orphanedComments.push(...kept);\n continue;\n }\n if (kept.length > 0) {\n if (freeZone.length > 0) freeZone.push('');\n freeZone.push(...kept);\n }\n }\n\n // A managed block that unignores `.vscode/extensions.json` is dead the moment\n // anything ignores `.vscode` outright — git never looks inside. This is the\n // single most common leftover across the estate, so it is worth its own\n // finding rather than a note about equivalent spellings.\n const smothered: SmotheredExceptions[] = [];\n const negations = [...managed].filter((line) => line.startsWith('!'));\n for (const line of freeZone) {\n if (isBlank(line) || isComment(line) || line.startsWith('!')) continue;\n const dir = line.trim().replace(/^\\/+/, '').replace(/\\/+$/, '');\n if (dir === '' || dir.includes('*')) continue;\n const exceptions = negations.filter((n) =>\n n.slice(1).replace(/^\\/+/, '').startsWith(`${dir}/`)\n );\n if (exceptions.length > 0) smothered.push({ line, exceptions });\n }\n\n const byKey = new Map<string, string[]>();\n for (const line of [...managed, ...freeZone]) {\n if (isBlank(line) || isComment(line)) continue;\n const key = equivalenceKey(line);\n const spellings = byKey.get(key);\n if (spellings) {\n if (!spellings.includes(line)) spellings.push(line);\n } else {\n byKey.set(key, [line]);\n }\n }\n // A line already reported as smothering a block's `!` exceptions does not\n // also need the general \"these are different patterns to git\" note — the\n // specific finding says everything the vague one would, and says what to do.\n // Other spellings in the same group still get theirs.\n const smotheredLines = new Set(smothered.map((s) => s.line));\n const equivalences = [...byKey]\n .map(([key, spellings]) => ({\n key,\n spellings: spellings.filter((s) => !smotheredLines.has(s))\n }))\n .filter(({ spellings }) => spellings.length > 1);\n\n return {\n document: {\n header: doc.header,\n sections,\n freeZone,\n hasRegion: doc.hasRegion\n },\n rescued,\n duplicates,\n covered,\n orphanedComments,\n smothered,\n equivalences,\n unknownStacks,\n staleSections\n };\n}\n\n/** Convenience for the commands and the tests: text in, reconciliation out. */\nexport const reconcileText = (text: string): Reconciliation =>\n reconcile(parse(text));\n","import { existsSync, readFileSync, statSync } from 'node:fs';\nimport { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { colors } from 'consola/utils';\nimport { basename, dirname } from 'pathe';\nimport { type Found, type FoundKind, findGitignores } from '../discover.ts';\nimport { DIVIDER, stacksLine } from '../gitignore/markers.ts';\nimport { parse } from '../gitignore/parse.ts';\nimport { reconcile } from '../gitignore/reconcile.ts';\nimport { stackOrder } from '../templates/index.ts';\n\ntype Row = {\n name: string;\n kind: FoundKind;\n file: string;\n /** Patterns in the file before reconciling: no blanks, no comments. */\n before: number;\n /** Patterns still in the free zone after every stack was applied. */\n after: number;\n leftovers: string[];\n};\n\ntype Report = {\n files: Row[];\n totals: { before: number; after: number; covered: number; percent: number };\n /** Stubs left unmeasured, and why. */\n skipped: { label: string; kind: FoundKind }[];\n /** Every leftover pattern with the files that carry it, most common first. */\n leftovers: { pattern: string; files: string[] }[];\n};\n\nconst isPattern = (line: string): boolean => {\n const t = line.trim();\n return t !== '' && !t.startsWith('#');\n};\n\n/**\n * Measure one file against every stack this binary ships.\n *\n * The point is not to rewrite anything — it is to answer \"what would still be\n * left over if this repo declared everything?\", because that remainder is what\n * decides whether a stack is missing or the lines are genuinely project rules.\n */\nfunction auditFile(found: Found, label: string): Row {\n const file = found.file;\n const text = readFileSync(file, 'utf8');\n const before = text.split('\\n').filter(isPattern).length;\n\n const doc = parse(text);\n const all = stackOrder();\n const result = reconcile({\n ...doc,\n header: [stacksLine(all), DIVIDER],\n hasRegion: true\n });\n const leftovers = result.document.freeZone\n .filter(isPattern)\n .map((l) => l.trim());\n\n return {\n name: label,\n kind: found.kind,\n file,\n before,\n after: leftovers.length,\n leftovers\n };\n}\n\nexport const auditCommand = defineCommand({\n meta: {\n name: 'audit',\n description:\n 'Measure how much of one or more repos the shipped stacks already cover'\n },\n args: {\n dirs: {\n type: 'positional',\n description: 'Repository directories (default: the current one)',\n required: false,\n default: '.'\n },\n 'min-files': {\n type: 'string',\n description: 'Only list leftovers carried by at least this many files',\n default: '1'\n },\n recursive: {\n type: 'boolean',\n description:\n 'Also measure .gitignore files below each directory, skipping build and dependency output',\n alias: 'r',\n default: false\n },\n 'include-stubs': {\n type: 'boolean',\n description:\n 'Measure framework stubs too, instead of listing them as skipped',\n default: false\n },\n json: {\n type: 'boolean',\n description: 'Emit a machine-readable JSON report',\n default: false\n }\n },\n run({ args }) {\n const raw = args._ && args._.length > 0 ? args._ : [args.dirs];\n const dirs = [\n ...new Set(\n raw.filter((d): d is string => typeof d === 'string' && d !== '')\n )\n ].filter((dir) => {\n if (!existsSync(dir) || !statSync(dir).isDirectory()) {\n consola.warn(`Not a directory, skipped: ${dir}`);\n return false;\n }\n return true;\n });\n\n if (dirs.length === 0) {\n consola.error('Nothing to audit.');\n process.exitCode = 1;\n return;\n }\n\n const targets: { found: Found; label: string }[] = [];\n const skipped: { label: string; kind: FoundKind }[] = [];\n for (const dir of dirs) {\n const hits = findGitignores(dir, { recursive: args.recursive });\n if (hits.length === 0) {\n consola.warn(`No .gitignore, skipped: ${dir}`);\n continue;\n }\n for (const found of hits) {\n // One directory reads as its own name; a recursive sweep needs the\n // path, or two `apps/*/.gitignore` rows are indistinguishable.\n const label =\n found.relative === '.gitignore'\n ? basename(dir)\n : `${basename(dir)}/${dirname(found.relative)}`;\n // A stub exists to hold an empty directory or is generated by a\n // framework. Measuring it against the stacks would report a leftover\n // for every line and suggest changing a file nobody owns.\n if (\n !args['include-stubs'] &&\n (found.kind === 'keeper' || found.kind === 'framework')\n ) {\n skipped.push({ label, kind: found.kind });\n continue;\n }\n targets.push({ found, label });\n }\n }\n\n if (targets.length === 0) {\n consola.error('Nothing to audit.');\n process.exitCode = 1;\n return;\n }\n\n const files = targets\n .map(({ found, label }) => auditFile(found, label))\n .sort((a, b) => b.before - a.before);\n const before = files.reduce((n, f) => n + f.before, 0);\n const after = files.reduce((n, f) => n + f.after, 0);\n\n const carriers = new Map<string, string[]>();\n for (const row of files) {\n for (const line of row.leftovers) {\n const seen = carriers.get(line);\n if (seen) seen.push(row.name);\n else carriers.set(line, [row.name]);\n }\n }\n const minFiles = Math.max(1, Number(args['min-files']) || 1);\n const leftovers = [...carriers]\n .map(([pattern, names]) => ({ pattern, files: names }))\n .filter((l) => l.files.length >= minFiles)\n .sort(\n (a, b) =>\n b.files.length - a.files.length || a.pattern.localeCompare(b.pattern)\n );\n\n const report: Report = {\n files,\n totals: {\n before,\n after,\n covered: before - after,\n percent:\n before === 0\n ? 100\n : Math.round(((before - after) / before) * 1000) / 10\n },\n leftovers,\n skipped\n };\n\n if (args.json) {\n process.stdout.write(`${JSON.stringify(report, null, 2)}\\n`);\n return;\n }\n\n const width = Math.max(...files.map((f) => f.name.length), 4);\n consola.log(\n ` ${colors.dim('repo'.padEnd(width))} ${colors.dim('before')} ${colors.dim('left')}`\n );\n for (const row of files) {\n consola.log(\n ` ${row.name.padEnd(width)} ${String(row.before).padStart(6)} ${String(row.after).padStart(4)}`\n );\n }\n consola.log(\n `\\n ${colors.bold(`${report.totals.before} patterns → ${report.totals.after} left`)} ${colors.dim(`(${report.totals.percent}% covered by the shipped stacks)`)}`\n );\n\n if (skipped.length > 0) {\n const keepers = skipped.filter((s) => s.kind === 'keeper').length;\n const framework = skipped.length - keepers;\n const parts = [\n keepers > 0 ? `${keepers} directory keeper(s)` : '',\n framework > 0 ? `${framework} framework stub(s)` : ''\n ].filter(Boolean);\n consola.log(\n `\\n ${colors.dim(`skipped ${parts.join(' and ')} — generated, and owned by the framework. --include-stubs measures them anyway.`)}`\n );\n }\n\n if (leftovers.length > 0) {\n consola.log(\n `\\n ${colors.dim(`leftovers carried by ${minFiles}+ file(s), most common first`)}`\n );\n for (const { pattern, files: names } of leftovers) {\n consola.log(\n ` ${String(names.length).padStart(3)}x ${pattern} ${colors.dim(names.slice(0, 4).join(', ') + (names.length > 4 ? ', …' : ''))}`\n );\n }\n }\n }\n});\n","import { endLine, outerStartLine, sectionStartLine } from './markers.ts';\nimport type { Document } from './types.ts';\n\n/**\n * Document back to text. The managed region comes first, the free zone below\n * it, exactly one blank line between every block — so a second render of a\n * rendered document is a no-op, which is what makes `check` a usable CI gate.\n */\nexport function render(doc: Document): string {\n const out: string[] = [];\n\n if (doc.hasRegion || doc.header.length > 0 || doc.sections.length > 0) {\n out.push(outerStartLine(), ...doc.header);\n\n for (const section of doc.sections) {\n out.push(\n '',\n sectionStartLine(section.stack, section.version),\n ...section.lines,\n endLine()\n );\n }\n\n out.push('', endLine());\n }\n\n if (doc.freeZone.length > 0) {\n if (out.length > 0) out.push('');\n out.push(...doc.freeZone);\n }\n\n return out.length > 0 ? `${out.join('\\n')}\\n` : '';\n}\n","import { readFileSync, writeFileSync } from 'node:fs';\nimport { isAbsolute, join, resolve } from 'pathe';\n\nexport const gitignorePath = (dir: string): string =>\n join(isAbsolute(dir) ? dir : resolve(dir), '.gitignore');\n\n/** A missing `.gitignore` reads as empty — that is `init`'s first-run case. */\nexport function readGitignore(dir: string): string {\n try {\n return readFileSync(gitignorePath(dir), 'utf8');\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === 'ENOENT') return '';\n throw error;\n }\n}\n\nexport function writeGitignore(dir: string, text: string): void {\n writeFileSync(gitignorePath(dir), text, 'utf8');\n}\n","import consola from 'consola';\nimport { colors } from 'consola/utils';\nimport type { Reconciliation } from '../gitignore/reconcile.ts';\n\n/** Everything a run wants to say about a reconciliation, in one place. */\nexport function report(result: Reconciliation): void {\n for (const stack of result.unknownStacks) {\n consola.warn(\n `Header declares unknown stack ${colors.cyan(stack)} — no template ships for it, its block is left untouched.`\n );\n }\n for (const stale of result.staleSections) {\n consola.info(\n `${colors.cyan(stale.stack)} block is at v${stale.from}, this binary ships v${stale.to}.`\n );\n }\n if (result.rescued.length > 0) {\n consola.info(\n `Rescued ${result.rescued.length} hand-written line(s) from managed blocks into the free zone:`\n );\n for (const line of result.rescued) consola.log(` ${colors.green(line)}`);\n }\n if (result.covered.length > 0) {\n consola.info(\n `Removed ${result.covered.length} free-zone line(s) a managed block already covers:`\n );\n for (const line of result.covered) consola.log(` ${colors.dim(line)}`);\n }\n if (result.orphanedComments.length > 0) {\n consola.info(\n `Removed ${result.orphanedComments.length} heading(s) left pointing at nothing:`\n );\n for (const line of result.orphanedComments) {\n consola.log(` ${colors.dim(line)}`);\n }\n }\n if (result.duplicates.length > 0) {\n consola.info(`Removed ${result.duplicates.length} exact duplicate(s):`);\n for (const line of result.duplicates)\n consola.log(` ${colors.dim(line)}`);\n }\n for (const s of result.smothered) {\n // Not a style note: git never descends into an ignored directory.\n consola.warn(\n `${colors.yellow(s.line)} ignores the whole directory, which disables ${s.exceptions.map((e) => colors.cyan(e)).join(', ')}. Remove it — the managed block already covers it.`\n );\n }\n for (const eq of result.equivalences) {\n // Reported, never merged: these are different patterns to git.\n consola.warn(\n `${eq.spellings.map((s) => colors.yellow(s)).join(' / ')} mean different things to git — reported, not merged.`\n );\n }\n}\n","import { readFileSync } from 'node:fs';\nimport { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { colors } from 'consola/utils';\nimport { findGitignores } from '../discover.ts';\nimport { parse } from '../gitignore/parse.ts';\nimport { reconcile } from '../gitignore/reconcile.ts';\nimport { render } from '../gitignore/render.ts';\nimport { gitignorePath, readGitignore } from '../io.ts';\nimport { report } from './report.ts';\n\nexport const checkCommand = defineCommand({\n meta: {\n name: 'check',\n description:\n 'Dry run for CI: report drift and duplicates, exit non-zero on deviation'\n },\n args: {\n dir: {\n type: 'positional',\n description: 'Repository directory (default: the current one)',\n required: false,\n default: '.'\n },\n recursive: {\n type: 'boolean',\n description:\n 'Also check managed regions below this directory, skipping build and dependency output',\n alias: 'r',\n default: false\n }\n },\n run({ args }) {\n const dir = args.dir;\n\n // Recursive checks only what is already managed. A file without a region\n // is not drift — nobody asked for one there, and a gate that fails on\n // every unmanaged sub-directory would be unusable.\n if (args.recursive) {\n const managed = findGitignores(dir, { recursive: true }).filter(\n (f) => f.kind === 'managed'\n );\n if (managed.length === 0) {\n consola.error(\n `No managed region under ${dir} — run ${colors.cyan('gitignore-sync init')} first.`\n );\n process.exitCode = 1;\n return;\n }\n let drifted = 0;\n for (const found of managed) {\n const text = readFileSync(found.file, 'utf8');\n const result = reconcile(parse(text));\n report(result);\n if (render(result.document) === text) {\n consola.success(`${found.relative} is in sync.`);\n } else {\n consola.error(\n `${found.relative} is out of sync — run ${colors.cyan(`gitignore-sync sync ${found.file.slice(0, -'/.gitignore'.length)}`)}.`\n );\n drifted++;\n }\n }\n if (drifted > 0) process.exitCode = 1;\n return;\n }\n\n const text = readGitignore(dir);\n const doc = parse(text);\n\n if (!doc.hasRegion) {\n consola.error(\n `${gitignorePath(dir)} has no managed region — run ${colors.cyan('gitignore-sync init')} first.`\n );\n process.exitCode = 1;\n return;\n }\n\n const result = reconcile(doc);\n report(result);\n\n if (render(result.document) !== text) {\n consola.error(\n `${gitignorePath(dir)} is out of sync — run ${colors.cyan('gitignore-sync sync')}.`\n );\n process.exitCode = 1;\n return;\n }\n consola.success(`${gitignorePath(dir)} is in sync.`);\n }\n});\n","import { existsSync, readdirSync, type Dirent } from 'node:fs';\nimport { join } from 'pathe';\nimport { stackOrder } from './templates/index.ts';\n\n/**\n * Where a signal comes from, and therefore how much it may decide on its own.\n *\n * - `repo` — a committed marker (`package.json`, `go.mod`, a `*.tf` file). It\n * describes the project, so it holds for everyone who clones.\n * - `machine` — the checkout or the machine running the command: a `.vscode/`\n * directory (itself usually gitignored), `$EDITOR`, `process.platform`. It\n * describes one keyboard.\n *\n * Both are real evidence and both are detected the same way — one table, one\n * pass. The distinction only decides one thing: whether a signal may write the\n * header **without a human confirming it**. The header is committed, so a CI\n * runner's platform must not end up claiming something for the whole team.\n */\nexport type Source = 'repo' | 'machine';\n\nexport type Detected = { stack: string; source: Source };\n\ntype Fingerprint = {\n source: Source;\n /** Always found; `core` alone uses this. */\n always?: boolean;\n /** Any of these existing in the directory is enough. */\n files?: string[];\n /** Or any entry matching one of these, searched to `PATTERN_DEPTH`. */\n patterns?: RegExp[];\n /** Or the platform running the command. */\n platform?: string;\n /** Or `$VISUAL` / `$EDITOR`. */\n editor?: RegExp;\n};\n\n/**\n * How deep a pattern fingerprint looks. One level below the root, because a\n * Terraform repo keeps its stacks in `tofu/` or `terraform/` rather than\n * scattered across the root — and because going deeper starts finding other\n * people's examples.\n */\nconst PATTERN_DEPTH = 2;\n\n/**\n * Never descended into. `examples/` is here on purpose: a `.tf` file shown as\n * documentation is not a workspace that produces state, and treating it as one\n * makes every provider repo look like an infrastructure repo.\n */\nconst SKIP = new Set([\n '.git',\n 'node_modules',\n 'vendor',\n 'dist',\n 'coverage',\n 'examples',\n 'testdata',\n 'fixtures'\n]);\n\nconst fingerprints: Record<string, Fingerprint> = {\n core: { source: 'repo', always: true },\n // Any repo an agent has ever run in has the directory; proposing the stack\n // there is what keeps settings.local.json out of a branch.\n agents: { source: 'repo', files: ['.claude', '.codex', '.opencode'] },\n // Every repository this runs in is one.\n git: { source: 'repo', files: ['.git'] },\n node: { source: 'repo', files: ['package.json'] },\n turborepo: { source: 'repo', files: ['turbo.json'] },\n // Root `Cargo.toml` only — see the comment on the template.\n rust: { source: 'repo', files: ['Cargo.toml'] },\n playwright: {\n source: 'repo',\n patterns: [/^playwright\\.config\\.[cm]?[jt]s$/]\n },\n storybook: { source: 'repo', files: ['.storybook'] },\n php: { source: 'repo', files: ['composer.json'] },\n laravel: { source: 'repo', files: ['artisan'] },\n go: { source: 'repo', files: ['go.mod'] },\n tofu: {\n source: 'repo',\n patterns: [/\\.tf$/, /\\.tofu$/, /^\\.terraform\\.lock\\.hcl$/]\n },\n nuxt: { source: 'repo', patterns: [/^nuxt\\.config\\.[cm]?[jt]s$/] },\n tauri: { source: 'repo', files: ['src-tauri'] },\n // A committed `.env.example` is the marker, not `.env` — the latter is the\n // very file the stack exists to keep out, so it is often absent on a fresh\n // clone and present on a machine that already ran the setup.\n dotenv: { source: 'repo', files: ['.env.example', '.env'] },\n vscode: { source: 'machine', files: ['.vscode'] },\n intellij: { source: 'machine', files: ['.idea'] },\n vim: { source: 'machine', editor: /\\b(?:vi|vim|nvim)\\b/ },\n macos: { source: 'machine', platform: 'darwin' },\n windows: { source: 'machine', platform: 'win32' },\n linux: { source: 'machine', platform: 'linux' }\n};\n\n/** Always proposed; removing it is then a visible act. */\nexport const BASE_STACK = 'core';\n\nfunction matchesPatterns(\n dir: string,\n patterns: RegExp[],\n depth: number\n): boolean {\n let entries: Dirent[];\n try {\n entries = readdirSync(dir, { withFileTypes: true });\n } catch {\n return false;\n }\n for (const entry of entries) {\n if (entry.isDirectory()) continue;\n if (patterns.some((pattern) => pattern.test(entry.name))) return true;\n }\n if (depth <= 1) return false;\n for (const entry of entries) {\n if (!entry.isDirectory() || entry.name.startsWith('.')) continue;\n if (SKIP.has(entry.name)) continue;\n if (matchesPatterns(join(dir, entry.name), patterns, depth - 1))\n return true;\n }\n return false;\n}\n\nfunction matches(dir: string, print: Fingerprint): boolean {\n if (print.always) return true;\n if (print.files?.some((file) => existsSync(join(dir, file)))) return true;\n if (print.platform && process.platform === print.platform) return true;\n if (print.editor) {\n const editor = process.env.VISUAL ?? process.env.EDITOR ?? '';\n if (print.editor.test(editor)) return true;\n }\n if (print.patterns)\n return matchesPatterns(dir, print.patterns, PATTERN_DEPTH);\n return false;\n}\n\n/**\n * The only filesystem contact in the tool. `parse`, `render` and `reconcile`\n * stay string-in/string-out so the hard part is testable with fixture pairs.\n *\n * Returns registry order, so the header reads the same on every run.\n */\nexport function detect(dir: string): Detected[] {\n const found: Detected[] = [];\n for (const stack of stackOrder()) {\n const print = fingerprints[stack];\n if (print && matches(dir, print)) {\n found.push({ stack, source: print.source });\n }\n }\n return found;\n}\n\n/** Just the names, for callers that do not care where a signal came from. */\nexport const stacksOf = (found: Detected[]): string[] =>\n found.map((d) => d.stack);\n\n/**\n * What may be written without asking. Only committed evidence: `init --yes`\n * and the no-TTY path run in CI, where the platform is the runner's and the\n * editor directory is nobody's.\n */\nexport const committedOnly = (found: Detected[]): Detected[] =>\n found.filter((d) => d.source === 'repo');\n","import consola from 'consola';\nimport type { Detected } from '../detect.ts';\nimport { currentTemplate, stackOrder } from '../templates/index.ts';\n\n/**\n * The one multiselect both `init` and `edit` show, so the two read the same\n * however you got there. Returns `null` when the prompt was cancelled — the\n * caller writes nothing in that case.\n */\nexport async function promptStacks(options: {\n message: string;\n /** Pre-ticked. `init` seeds this from detection, `edit` from the header. */\n initial: Set<string>;\n /** What detection found, with where each signal came from. */\n found: Detected[];\n declared: Set<string>;\n /** Restrict the list — `init` uses it to offer only what it did not find. */\n choices?: string[];\n}): Promise<string[] | null> {\n const offered = options.choices ?? stackOrder();\n const source = new Map(options.found.map((d) => [d.stack, d.source]));\n const choices = offered.map((stack) => {\n const template = currentTemplate(stack);\n const marks: string[] = [];\n if (source.has(stack)) marks.push('detected');\n if (options.declared.has(stack)) marks.push('declared');\n return {\n value: stack,\n label: stack,\n hint: `${template?.lines.length ?? 0} lines${marks.length > 0 ? ` · ${marks.join(', ')}` : ''}`\n };\n });\n\n const answer = await consola.prompt(options.message, {\n type: 'multiselect',\n options: choices,\n initial: offered.filter((s) => options.initial.has(s)),\n // \"None of these\" is a real answer — most repos want no extra stack at all.\n required: false,\n cancel: 'null'\n });\n\n if (answer === null || answer === undefined) return null;\n const chosen = new Set((answer as unknown as string[]).map(String));\n const picked = offered.filter((stack) => chosen.has(stack));\n\n // A declared stack this binary ships no template for is kept: neither verb\n // may quietly drop something it simply cannot render.\n for (const stack of options.declared) {\n if (!picked.includes(stack) && !stackOrder().includes(stack)) {\n picked.push(stack);\n }\n }\n return picked;\n}\n\n/** A prompt with no terminal to read from would hang or take its defaults. */\nexport const canPrompt = (): boolean => Boolean(process.stdin.isTTY);\n","import { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { colors } from 'consola/utils';\nimport { detect } from '../detect.ts';\nimport { stacksLine } from '../gitignore/markers.ts';\nimport { parse, readStacks, withStacks } from '../gitignore/parse.ts';\nimport { reconcile } from '../gitignore/reconcile.ts';\nimport { render } from '../gitignore/render.ts';\nimport { gitignorePath, readGitignore, writeGitignore } from '../io.ts';\nimport { canPrompt, promptStacks } from './prompt.ts';\nimport { report } from './report.ts';\n\nexport const editCommand = defineCommand({\n meta: {\n name: 'edit',\n description: 'Tick stacks on and off in a prompt, then re-render'\n },\n args: {\n dir: {\n type: 'positional',\n description: 'Repository directory (default: the current one)',\n required: false,\n default: '.'\n },\n 'dry-run': {\n type: 'boolean',\n description: 'Print the result instead of writing it',\n default: false\n }\n },\n async run({ args }) {\n const dir = args.dir;\n\n const text = readGitignore(dir);\n const doc = parse(text);\n\n // The state of the file first, the state of the terminal second: without a\n // region there is nothing to edit however you invoked this, and \"run init\"\n // is the useful thing to say.\n if (!doc.hasRegion) {\n consola.error(\n `${gitignorePath(dir)} has no managed region — run ${colors.cyan('gitignore-sync init')} first.`\n );\n process.exitCode = 1;\n return;\n }\n\n if (!canPrompt()) {\n consola.error(\n `${colors.cyan('edit')} needs an interactive terminal. Use ${colors.cyan('add')} / ${colors.cyan('remove')} in a script or CI.`\n );\n process.exitCode = 1;\n return;\n }\n\n const declared = new Set(readStacks(doc));\n const next = await promptStacks({\n message: 'Which stacks does this repo want?',\n initial: declared,\n found: detect(dir),\n declared\n });\n\n if (next === null) {\n consola.info('Cancelled — nothing written.');\n return;\n }\n\n const result = reconcile(withStacks(doc, next));\n const output = render(result.document);\n\n consola.log(` ${colors.dim(stacksLine(next))}`);\n report(result);\n\n if (args['dry-run']) {\n consola.log(output);\n return;\n }\n if (output === text) {\n consola.success(`${gitignorePath(dir)} is already in sync.`);\n return;\n }\n writeGitignore(dir, output);\n consola.success(`Wrote ${gitignorePath(dir)}`);\n }\n});\n","import { existsSync, readFileSync, realpathSync } from 'node:fs';\nimport { defineCommand } from 'citty';\nimport { colors } from 'consola/utils';\nimport { dirname, join, resolve } from 'pathe';\nimport { parse, readStacks } from '../gitignore/parse.ts';\nimport { reconcile } from '../gitignore/reconcile.ts';\nimport { render } from '../gitignore/render.ts';\nimport { gitignorePath, readGitignore } from '../io.ts';\nimport { currentTemplate, stackOrder } from '../templates/index.ts';\n\n// Injected at build time by vite's `define`, sourced from package.json's\n// version. Read it the same way `cli.ts` does rather than re-inventing a\n// drift-prone literal.\ndeclare const __APP_VERSION__: string;\n\n/** The package.json `name` this build is identified by. */\nconst PACKAGE_NAME = '@kirchdev/gitignore-sync';\n/** What a person types, and what the report is headed with. */\nconst BIN_NAME = 'gitignore-sync';\n\n/** `linked` = runs from a git work tree; `release` = an installed package. */\ntype BuildKind = 'linked' | 'release' | 'unknown';\n\ntype Info = {\n version: string;\n build: { kind: BuildKind; packageRoot: string | null; reason: string };\n binary: { invoked: string | null; resolved: string | null };\n node: string;\n templates: { stacks: number; patterns: number };\n repository: {\n file: string;\n exists: boolean;\n hasRegion: boolean;\n stacks: string[];\n /** `in sync`, `drifted`, `no region`, or the parse error. */\n status: string;\n };\n};\n\n/** Resolve the real executed file behind any shim or symlink. */\nfunction resolveBinary(entry: string | undefined): Info['binary'] {\n if (!entry) return { invoked: null, resolved: null };\n try {\n return { invoked: entry, resolved: realpathSync(entry) };\n } catch {\n // The file vanished or is unreadable — report the invoked path as-is\n // rather than failing. `info` describes, it never judges.\n return { invoked: entry, resolved: entry };\n }\n}\n\n/** Walk up from `start` to the nearest package.json, returning its dir + name. */\nfunction findPackageRoot(\n start: string\n): { dir: string; name: string | undefined } | null {\n let dir = resolve(start);\n for (;;) {\n const pkgPath = join(dir, 'package.json');\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as {\n name?: string;\n };\n return { dir, name: pkg.name };\n } catch {\n return { dir, name: undefined };\n }\n }\n const parent = dirname(dir);\n if (parent === dir) return null;\n dir = parent;\n }\n}\n\n/**\n * Tell a linked dev build from an installed release: the resolved binary lives\n * inside a git work tree whose package.json carries this package's name. When\n * neither can be established, report `unknown` rather than guessing.\n */\nfunction detectBuild(resolved: string | null): Info['build'] {\n if (!resolved) {\n return {\n kind: 'unknown',\n packageRoot: null,\n reason: 'binary path could not be resolved'\n };\n }\n const pkg = findPackageRoot(dirname(resolved));\n if (!pkg) {\n return {\n kind: 'unknown',\n packageRoot: null,\n reason: 'no package.json found above the binary'\n };\n }\n if (pkg.name !== PACKAGE_NAME) {\n return {\n kind: 'unknown',\n packageRoot: pkg.dir,\n reason: `nearest package.json is \"${pkg.name ?? 'unnamed'}\", not ${PACKAGE_NAME}`\n };\n }\n // Check `.git` at the package root only, never above it: an installed copy\n // under a consumer's node_modules would otherwise inherit that repo's .git\n // and be mislabelled as linked.\n const inGitTree = existsSync(join(pkg.dir, '.git'));\n return {\n kind: inGitTree ? 'linked' : 'release',\n packageRoot: pkg.dir,\n reason: inGitTree\n ? `runs from a git work tree named ${PACKAGE_NAME}`\n : `installed ${PACKAGE_NAME} package (no git work tree beside it)`\n };\n}\n\nfunction inspectRepository(dir: string): Info['repository'] {\n const file = gitignorePath(dir);\n const exists = existsSync(file);\n if (!exists) {\n return { file, exists, hasRegion: false, stacks: [], status: 'no file' };\n }\n const text = readGitignore(dir);\n try {\n const doc = parse(text);\n if (!doc.hasRegion) {\n return {\n file,\n exists,\n hasRegion: false,\n stacks: [],\n status: 'no region'\n };\n }\n const result = reconcile(doc);\n return {\n file,\n exists,\n hasRegion: true,\n stacks: readStacks(doc),\n status: render(result.document) === text ? 'in sync' : 'drifted'\n };\n } catch (error) {\n // A file this binary cannot parse is still worth describing.\n return {\n file,\n exists,\n hasRegion: false,\n stacks: [],\n status: (error as Error).message\n };\n }\n}\n\nconst BUILD_LABELS: Record<BuildKind, string> = {\n linked: 'linked / dev build',\n release: 'installed release',\n unknown: 'unknown'\n};\n\nconst row = (label: string, value: string): string =>\n ` ${colors.dim(label.padEnd(9))} ${value}\\n`;\n\nfunction renderPretty(info: Info): string {\n let out = `${colors.bold(BIN_NAME)} ${colors.cyan(`v${info.version}`)} ${colors.dim(`(${BUILD_LABELS[info.build.kind]})`)}\\n`;\n out += row('build', colors.dim(info.build.reason));\n if (info.build.packageRoot) out += row('package', info.build.packageRoot);\n out += row('binary', info.binary.resolved ?? colors.dim('unknown'));\n if (info.binary.invoked && info.binary.invoked !== info.binary.resolved) {\n out += row('', colors.dim(`via ${info.binary.invoked}`));\n }\n out += row('node', info.node);\n\n out += `\\n${colors.bold('templates')}\\n`;\n out += row(\n 'ships',\n `${info.templates.stacks} stacks, ${info.templates.patterns} patterns`\n );\n\n out += `\\n${colors.bold('repository')}\\n`;\n out += row('file', info.repository.file);\n const status =\n info.repository.status === 'in sync'\n ? colors.green(info.repository.status)\n : info.repository.status === 'drifted'\n ? colors.yellow(info.repository.status)\n : colors.dim(info.repository.status);\n out += row('status', status);\n if (info.repository.stacks.length > 0) {\n out += row(\n 'stacks',\n info.repository.stacks.map((s) => colors.cyan(s)).join(', ')\n );\n }\n return out;\n}\n\nexport const infoCommand = defineCommand({\n meta: {\n name: 'info',\n description:\n 'Describe this installation: version, how it was installed, and the repo it sees'\n },\n args: {\n dir: {\n type: 'positional',\n description: 'Repository directory (default: the current one)',\n required: false,\n default: '.'\n },\n json: {\n type: 'boolean',\n description: 'Emit a machine-readable JSON report',\n default: false\n }\n },\n run({ args }) {\n const binary = resolveBinary(process.argv[1]);\n const info: Info = {\n version: __APP_VERSION__,\n build: detectBuild(binary.resolved),\n binary,\n node: process.version,\n templates: {\n stacks: stackOrder().length,\n patterns: stackOrder().reduce(\n (n, s) => n + (currentTemplate(s)?.lines.length ?? 0),\n 0\n )\n },\n repository: inspectRepository(args.dir)\n };\n\n if (args.json) {\n process.stdout.write(`${JSON.stringify(info, null, 2)}\\n`);\n return;\n }\n process.stdout.write(renderPretty(info));\n }\n});\n\nexport const __test = { resolveBinary, findPackageRoot, detectBuild };\n","import { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { colors } from 'consola/utils';\nimport { parse, readStacks, withStacks } from '../gitignore/parse.ts';\nimport { reconcile } from '../gitignore/reconcile.ts';\nimport { render } from '../gitignore/render.ts';\nimport { gitignorePath, readGitignore, writeGitignore } from '../io.ts';\nimport { isKnownStack, knownStacks, stackOrder } from '../templates/index.ts';\nimport { report } from './report.ts';\n\nconst args = {\n stacks: {\n type: 'positional' as const,\n description: 'One or more stack names',\n required: true\n },\n dir: {\n type: 'string' as const,\n description: 'Repository directory (default: the current one)',\n default: '.'\n },\n 'dry-run': {\n type: 'boolean' as const,\n description: 'Print the result instead of writing it',\n default: false\n }\n};\n\n/**\n * `add` and `remove` edit one line: the `# stacks:` declaration. Everything\n * below it is re-rendered from that line, so the two verbs stay a convenience\n * over an edit you could also make by hand — never a second source of truth.\n */\nfunction edit(mode: 'add' | 'remove') {\n return (context: {\n args: {\n stacks?: string;\n dir?: string;\n 'dry-run'?: boolean;\n _?: string[];\n };\n }) => {\n const a = context.args;\n const dir = a.dir ?? '.';\n // citty puts every positional in `_` and the first one *also* in `stacks`,\n // so reading both would count the first name twice.\n const raw = a._ && a._.length > 0 ? a._ : [a.stacks];\n const names = [\n ...new Set(\n raw.filter((n): n is string => typeof n === 'string' && n !== '')\n )\n ];\n\n if (names.length === 0) {\n consola.error('Name at least one stack.');\n consola.info(`Available: ${knownStacks().join(', ')}`);\n process.exitCode = 1;\n return;\n }\n\n const text = readGitignore(dir);\n const doc = parse(text);\n\n if (!doc.hasRegion) {\n consola.error(\n `${gitignorePath(dir)} has no managed region — run ${colors.cyan('gitignore-sync init')} first.`\n );\n process.exitCode = 1;\n return;\n }\n\n if (mode === 'add') {\n const unknown = names.filter((n) => !isKnownStack(n));\n if (unknown.length > 0) {\n consola.error(\n `Unknown stack: ${unknown.map((u) => colors.yellow(u)).join(', ')}`\n );\n consola.info(`Available: ${knownStacks().join(', ')}`);\n process.exitCode = 1;\n return;\n }\n }\n\n const before = readStacks(doc);\n const set = new Set(before);\n const changed: string[] = [];\n\n for (const name of names) {\n if (mode === 'add') {\n if (set.has(name)) continue;\n set.add(name);\n } else {\n if (!set.has(name)) continue;\n set.delete(name);\n }\n changed.push(name);\n }\n\n if (changed.length === 0) {\n consola.info(\n mode === 'add'\n ? 'Already declared — nothing to add.'\n : 'Not declared — nothing to remove.'\n );\n return;\n }\n\n // Registry order, so the header reads the same however the stacks arrived.\n const order = stackOrder();\n const next = [...set].sort((x, y) => {\n const ix = order.indexOf(x);\n const iy = order.indexOf(y);\n if (ix === -1 && iy === -1) return x.localeCompare(y);\n if (ix === -1) return 1;\n if (iy === -1) return -1;\n return ix - iy;\n });\n\n const result = reconcile(withStacks(doc, next));\n const output = render(result.document);\n\n consola.info(\n `${mode === 'add' ? 'Added' : 'Removed'} ${changed.map((c) => colors.cyan(c)).join(', ')}`\n );\n consola.log(` ${colors.dim(`# stacks: ${next.join(', ')}`)}`);\n report(result);\n\n if (a['dry-run']) {\n consola.log(output);\n return;\n }\n writeGitignore(dir, output);\n consola.success(`Wrote ${gitignorePath(dir)}`);\n };\n}\n\nexport const addCommand = defineCommand({\n meta: {\n name: 'add',\n description: 'Add stacks to the header and re-render'\n },\n args,\n run: edit('add')\n});\n\nexport const removeCommand = defineCommand({\n meta: {\n name: 'remove',\n alias: 'rm',\n description: 'Remove stacks from the header and re-render'\n },\n args,\n run: edit('remove')\n});\n","import { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { colors } from 'consola/utils';\nimport { committedOnly, detect, stacksOf } from '../detect.ts';\nimport { DIVIDER, stacksLine } from '../gitignore/markers.ts';\nimport { parse } from '../gitignore/parse.ts';\nimport { reconcile } from '../gitignore/reconcile.ts';\nimport { render } from '../gitignore/render.ts';\nimport { gitignorePath, readGitignore, writeGitignore } from '../io.ts';\nimport { stackOrder } from '../templates/index.ts';\nimport { canPrompt, promptStacks } from './prompt.ts';\nimport { report } from './report.ts';\n\nexport const initCommand = defineCommand({\n meta: {\n name: 'init',\n description:\n 'Fingerprint the repo, confirm the stacks in a prompt and write the managed region'\n },\n args: {\n dir: {\n type: 'positional',\n description: 'Repository directory (default: the current one)',\n required: false,\n default: '.'\n },\n stacks: {\n type: 'string',\n description: 'Comma-separated stacks; skips the prompt'\n },\n yes: {\n type: 'boolean',\n description: 'Take what detection proposes without asking',\n alias: 'y',\n default: false\n },\n 'dry-run': {\n type: 'boolean',\n description: 'Print the result instead of writing it',\n default: false\n },\n force: {\n type: 'boolean',\n description: 'Replace an existing header instead of refusing',\n default: false\n }\n },\n async run({ args }) {\n const dir = args.dir;\n const text = readGitignore(dir);\n const existing = parse(text);\n\n if (existing.hasRegion && !args.force) {\n consola.error(\n `${gitignorePath(dir)} already carries a managed region. Use ${colors.cyan('gitignore-sync edit')} to change it, ${colors.cyan('sync')} to re-render, or pass --force to start over.`\n );\n process.exitCode = 1;\n return;\n }\n\n const found = detect(dir);\n const detected = new Set(stacksOf(found));\n let stacks: string[];\n\n if (args.stacks) {\n stacks = args.stacks\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n } else if (args.yes || !canPrompt()) {\n // No terminal, or told not to ask: only committed evidence writes. This\n // is the path CI takes, so it must never wait for input — and a runner's\n // platform must not end up claiming something for the whole team.\n stacks = stacksOf(committedOnly(found));\n consola.info(`Detected ${stacks.map((s) => colors.cyan(s)).join(', ')}`);\n const skipped = stacksOf(found).filter((s) => !stacks.includes(s));\n if (skipped.length > 0) {\n consola.log(\n ` ${colors.dim(`Skipped ${skipped.join(', ')} — editor and platform need a human to confirm them. Add with \\`gitignore-sync add\\`.`)}`\n );\n }\n } else {\n // Two steps rather than one long multiselect: confirm what was found,\n // then offer the rest.\n consola.info(\n `Found ${detected.size} stack${detected.size === 1 ? '' : 's'}: ${[...detected].map((s) => colors.cyan(s)).join(', ')}`\n );\n\n const takeProposal = await consola.prompt('Use them?', {\n type: 'confirm',\n initial: true,\n cancel: 'null'\n });\n\n if (takeProposal === null || takeProposal === undefined) {\n consola.info('Cancelled — nothing written.');\n return;\n }\n\n if (takeProposal) {\n const rest = stackOrder().filter((s) => !detected.has(s));\n const extra = await promptStacks({\n message: 'Anything else?',\n initial: new Set(),\n found,\n declared: new Set(),\n choices: rest\n });\n if (extra === null) {\n consola.info('Cancelled — nothing written.');\n return;\n }\n stacks = stackOrder().filter(\n (s) => detected.has(s) || extra.includes(s)\n );\n } else {\n // Nothing pre-ticked: \"no\" rejected the proposal, so re-offering it as\n // the default would just be something to un-tick. Each row still says\n // where its signal came from.\n const picked = await promptStacks({\n message: 'Pick the stacks yourself:',\n initial: new Set(),\n found,\n declared: new Set()\n });\n if (picked === null) {\n consola.info('Cancelled — nothing written.');\n return;\n }\n stacks = picked;\n }\n }\n\n // The header is input, the sections are output: init only ever writes the\n // declaration, and reconcile renders the blocks from it.\n const seeded = {\n ...existing,\n header: [stacksLine(stacks), DIVIDER],\n hasRegion: true\n };\n const result = reconcile(seeded);\n const output = render(result.document);\n\n consola.log(` ${colors.dim(stacksLine(stacks))}`);\n report(result);\n\n if (args['dry-run']) {\n consola.log(output);\n return;\n }\n writeGitignore(dir, output);\n consola.success(`Wrote ${gitignorePath(dir)}`);\n }\n});\n","import { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { colors } from 'consola/utils';\nimport { parse, readStacks } from '../gitignore/parse.ts';\nimport { readGitignore } from '../io.ts';\nimport { currentTemplate, knownStacks } from '../templates/index.ts';\n\nexport const listCommand = defineCommand({\n meta: {\n name: 'list',\n alias: 'ls',\n description: 'List the stacks this binary ships and which the repo declares'\n },\n args: {\n dir: {\n type: 'positional',\n description: 'Repository directory (default: the current one)',\n required: false,\n default: '.'\n }\n },\n run({ args }) {\n const declared = new Set(readStacks(parse(readGitignore(args.dir))));\n for (const stack of knownStacks()) {\n const template = currentTemplate(stack);\n const mark = declared.has(stack) ? colors.green('●') : colors.dim('○');\n consola.log(\n ` ${mark} ${colors.cyan(stack)}@v${template?.version ?? '?'} ${colors.dim(`${template?.lines.length ?? 0} ${template?.lines.length === 1 ? 'line' : 'lines'}`)}`\n );\n }\n for (const stack of declared) {\n if (!currentTemplate(stack)) {\n consola.log(\n ` ${colors.yellow('?')} ${colors.cyan(stack)} ${colors.dim('declared, no template')}`\n );\n }\n }\n }\n});\n","import { defineCommand } from 'citty';\nimport consola from 'consola';\nimport { colors } from 'consola/utils';\nimport { detect, stacksOf } from '../detect.ts';\nimport { parse, readStacks } from '../gitignore/parse.ts';\nimport { reconcile } from '../gitignore/reconcile.ts';\nimport { render } from '../gitignore/render.ts';\nimport { gitignorePath, readGitignore, writeGitignore } from '../io.ts';\nimport { report } from './report.ts';\n\nexport const syncCommand = defineCommand({\n meta: {\n name: 'sync',\n description:\n 'Read the header, re-render the managed blocks from it. Detects nothing.'\n },\n args: {\n dir: {\n type: 'positional',\n description: 'Repository directory (default: the current one)',\n required: false,\n default: '.'\n },\n detect: {\n type: 'boolean',\n description:\n 'Additionally propose header changes from the tree — never applies them',\n default: false\n },\n 'dry-run': {\n type: 'boolean',\n description: 'Print the result instead of writing it',\n default: false\n }\n },\n run({ args }) {\n const dir = args.dir;\n const text = readGitignore(dir);\n const doc = parse(text);\n\n if (!doc.hasRegion) {\n consola.error(\n `${gitignorePath(dir)} has no managed region — run ${colors.cyan('gitignore-sync init')} first.`\n );\n process.exitCode = 1;\n return;\n }\n\n const result = reconcile(doc);\n const output = render(result.document);\n\n report(result);\n\n // Detection changes its moment, it does not disappear: adding a\n // package.json must not quietly rewrite someone's .gitignore, so this only\n // ever prints a suggestion.\n if (args.detect) {\n const declared = new Set(readStacks(doc));\n const missing = stacksOf(detect(dir)).filter((s) => !declared.has(s));\n if (missing.length === 0) {\n consola.info('Detection proposes no header change.');\n } else {\n consola.info(\n `Detection suggests adding ${missing.map((s) => colors.cyan(s)).join(', ')} to the ${colors.cyan('# stacks:')} line. Not applied.`\n );\n }\n }\n\n if (args['dry-run']) {\n consola.log(output);\n return;\n }\n if (output === text) {\n consola.success(`${gitignorePath(dir)} is already in sync.`);\n return;\n }\n writeGitignore(dir, output);\n consola.success(`Wrote ${gitignorePath(dir)}`);\n }\n});\n","import { defineCommand } from 'citty';\nimport { auditCommand } from './commands/audit.ts';\nimport { checkCommand } from './commands/check.ts';\nimport { editCommand } from './commands/edit.ts';\nimport { infoCommand } from './commands/info.ts';\nimport { addCommand, removeCommand } from './commands/stacks.ts';\nimport { initCommand } from './commands/init.ts';\nimport { listCommand } from './commands/list.ts';\nimport { syncCommand } from './commands/sync.ts';\n\n// Injected at build time by vite's `define` (see vite.config.ts), sourced from\n// package.json's `version`, which release-please owns.\ndeclare const __APP_VERSION__: string;\n\nexport const rootCommand = defineCommand({\n meta: {\n name: 'gitignore-sync',\n version: __APP_VERSION__,\n description:\n \"Keep a repo's .gitignore maintained: curated blocks in a managed region, re-rendered on demand\"\n },\n subCommands: {\n init: initCommand,\n edit: editCommand,\n add: addCommand,\n remove: removeCommand,\n sync: syncCommand,\n check: checkCommand,\n list: listCommand,\n audit: auditCommand,\n info: infoCommand\n }\n});\n","import { runMain } from 'citty';\nimport { rootCommand } from '../cli.ts';\n\n// `gitignore-sync list | head` closes stdout while consola is still writing to\n// it. Node turns that into an unhandled 'error' event and a stack trace; every\n// other CLI in a pipe just stops. Swallow it and exit quietly.\nprocess.stdout.on('error', (error: NodeJS.ErrnoException) => {\n if (error.code === 'EPIPE') process.exit(0);\n throw error;\n});\n\nrunMain(rootCommand);\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAeA,IAAM,WAAuC;CAO3C,MAAM,CACJ;EACE,OAAO;EACP,SAAS;EACT,OAAO,CAAC,WAAW;CACrB,CACF;CASA,QAAQ,CACN;EACE,OAAO;EACP,SAAS;EACT,OAAO,CAAC,+BAA+B,oBAAoB;CAC7D,CACF;CAIA,KAAK,CACH;EACE,OAAO;EACP,SAAS;EACT,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;CACF,CACF;CAKA,MAAM,CACJ;EACE,OAAO;EACP,SAAS;EACT,OAAO;GACL;GACA;GACA;GAKA;GACA;GACA;GACA;GACA;GACA;EACF;CACF,CACF;CAIA,IAAI,CACF;EACE,OAAO;EACP,SAAS;EACT,OAAO;GAAC;GAAS;GAAU;GAAS;EAAQ;CAC9C,CACF;CAIA,MAAM,CACJ;EACE,OAAO;EACP,SAAS;EACT,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;CACF,CACF;CAIA,KAAK,CACH;EACE,OAAO;EACP,SAAS;EACT,OAAO;GAAC;GAAW;GAAmB;EAAwB;CAChE,CACF;CAMA,SAAS,CACP;EACE,OAAO;EACP,SAAS;EACT,OAAO;GACL;GACA;GACA;GACA;GACA;GACA;GACA;GACA;EACF;CACF,CACF;CAGA,WAAW,CACT;EACE,OAAO;EACP,SAAS;EACT,OAAO,CAAC,QAAQ;CAClB,CACF;CAKA,MAAM,CACJ;EACE,OAAO;EACP,SAAS;EACT,OAAO,CAAC,WAAW,YAAY;CACjC,CACF;CACA,YAAY,CACV;EACE,OAAO;EACP,SAAS;EACT,OAAO;GACL;GACA;GACA;GACA;EACF;CACF,CACF;CACA,WAAW,CACT;EACE,OAAO;EACP,SAAS;EACT,OAAO,CAAC,kBAAkB;CAC5B,CACF;CACA,MAAM,CACJ;EACE,OAAO;EACP,SAAS;EACT,OAAO;GAAC;GAAS;GAAW;GAAU;EAAO;CAC/C,CACF;CACA,OAAO,CACL;EACE,OAAO;EACP,SAAS;EACT,OAAO,CAAC,oBAAoB,uBAAuB;CAC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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://www.schemastore.org/package.json",
3
3
  "name": "@kirchdev/gitignore-sync",
4
- "version": "0.2.0",
4
+ "version": "0.3.0",
5
5
  "description": "CLI that keeps a repo's .gitignore maintained — curated blocks inside a managed region, re-rendered idempotently, nothing outside it touched.",
6
6
  "keywords": [
7
7
  "cli",