getaura 0.1.9 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -10,7 +10,7 @@ import { Command, Option } from "commander";
10
10
 
11
11
  // src/commands/apply.ts
12
12
  import { existsSync as existsSync19, rmSync as rmSync6 } from "fs";
13
- import path20 from "path";
13
+ import path21 from "path";
14
14
 
15
15
  // src/core/agents.ts
16
16
  var AGENT_LAYOUT = {
@@ -1009,10 +1009,10 @@ function mergeDefs(...defs) {
1009
1009
  function cloneDef(schema) {
1010
1010
  return mergeDefs(schema._zod.def);
1011
1011
  }
1012
- function getElementAtPath(obj, path27) {
1013
- if (!path27)
1012
+ function getElementAtPath(obj, path28) {
1013
+ if (!path28)
1014
1014
  return obj;
1015
- return path27.reduce((acc, key) => acc?.[key], obj);
1015
+ return path28.reduce((acc, key) => acc?.[key], obj);
1016
1016
  }
1017
1017
  function promiseAllObject(promisesObj) {
1018
1018
  const keys = Object.keys(promisesObj);
@@ -1352,11 +1352,11 @@ function explicitlyAborted(x, startIndex = 0) {
1352
1352
  }
1353
1353
  return false;
1354
1354
  }
1355
- function prefixIssues(path27, issues) {
1355
+ function prefixIssues(path28, issues) {
1356
1356
  return issues.map((iss) => {
1357
1357
  var _a3;
1358
1358
  (_a3 = iss).path ?? (_a3.path = []);
1359
- iss.path.unshift(path27);
1359
+ iss.path.unshift(path28);
1360
1360
  return iss;
1361
1361
  });
1362
1362
  }
@@ -1806,16 +1806,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
1806
1806
  }
1807
1807
  function formatError(error62, mapper = (issue2) => issue2.message) {
1808
1808
  const fieldErrors = { _errors: [] };
1809
- const processError = (error63, path27 = []) => {
1809
+ const processError = (error63, path28 = []) => {
1810
1810
  for (const issue2 of error63.issues) {
1811
1811
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1812
- issue2.errors.map((issues) => processError({ issues }, [...path27, ...issue2.path]));
1812
+ issue2.errors.map((issues) => processError({ issues }, [...path28, ...issue2.path]));
1813
1813
  } else if (issue2.code === "invalid_key") {
1814
- processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
1814
+ processError({ issues: issue2.issues }, [...path28, ...issue2.path]);
1815
1815
  } else if (issue2.code === "invalid_element") {
1816
- processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
1816
+ processError({ issues: issue2.issues }, [...path28, ...issue2.path]);
1817
1817
  } else {
1818
- const fullpath = [...path27, ...issue2.path];
1818
+ const fullpath = [...path28, ...issue2.path];
1819
1819
  if (fullpath.length === 0) {
1820
1820
  fieldErrors._errors.push(mapper(issue2));
1821
1821
  } else {
@@ -1854,17 +1854,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
1854
1854
  }
1855
1855
  function treeifyError(error62, mapper = (issue2) => issue2.message) {
1856
1856
  const result = { errors: [] };
1857
- const processError = (error63, path27 = []) => {
1857
+ const processError = (error63, path28 = []) => {
1858
1858
  var _a3;
1859
1859
  for (const issue2 of error63.issues) {
1860
1860
  if (issue2.code === "invalid_union" && issue2.errors.length) {
1861
- issue2.errors.map((issues) => processError({ issues }, [...path27, ...issue2.path]));
1861
+ issue2.errors.map((issues) => processError({ issues }, [...path28, ...issue2.path]));
1862
1862
  } else if (issue2.code === "invalid_key") {
1863
- processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
1863
+ processError({ issues: issue2.issues }, [...path28, ...issue2.path]);
1864
1864
  } else if (issue2.code === "invalid_element") {
1865
- processError({ issues: issue2.issues }, [...path27, ...issue2.path]);
1865
+ processError({ issues: issue2.issues }, [...path28, ...issue2.path]);
1866
1866
  } else {
1867
- const fullpath = [...path27, ...issue2.path];
1867
+ const fullpath = [...path28, ...issue2.path];
1868
1868
  if (fullpath.length === 0) {
1869
1869
  result.errors.push(mapper(issue2));
1870
1870
  continue;
@@ -1903,8 +1903,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
1903
1903
  }
1904
1904
  function toDotPath(_path) {
1905
1905
  const segs = [];
1906
- const path27 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1907
- for (const seg of path27) {
1906
+ const path28 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
1907
+ for (const seg of path28) {
1908
1908
  if (typeof seg === "number")
1909
1909
  segs.push(`[${seg}]`);
1910
1910
  else if (typeof seg === "symbol")
@@ -19006,13 +19006,13 @@ function resolveRef(ref, ctx) {
19006
19006
  if (!ref.startsWith("#")) {
19007
19007
  throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
19008
19008
  }
19009
- const path27 = ref.slice(1).split("/").filter(Boolean);
19010
- if (path27.length === 0) {
19009
+ const path28 = ref.slice(1).split("/").filter(Boolean);
19010
+ if (path28.length === 0) {
19011
19011
  return ctx.rootSchema;
19012
19012
  }
19013
19013
  const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
19014
- if (path27[0] === defsKey) {
19015
- const key = path27[1] === void 0 ? void 0 : decodeJSONPointerSegment(path27[1]);
19014
+ if (path28[0] === defsKey) {
19015
+ const key = path28[1] === void 0 ? void 0 : decodeJSONPointerSegment(path28[1]);
19016
19016
  if (!key || !ctx.defs[key]) {
19017
19017
  throw new Error(`Reference not found: ${ref}`);
19018
19018
  }
@@ -20130,6 +20130,26 @@ async function commandExists(cmd) {
20130
20130
  import { mkdirSync, rmSync, writeFileSync } from "fs";
20131
20131
  import path2 from "path";
20132
20132
 
20133
+ // src/core/local-files.ts
20134
+ var LOCAL_ONLY = [
20135
+ { rule: "node_modules/", test: /(?:^|\/)node_modules\//, what: "installed packages" },
20136
+ { rule: ".next/", test: /(?:^|\/)\.next\//, what: "Next.js build output" },
20137
+ { rule: ".vercel/", test: /(?:^|\/)\.vercel\//, what: "the Vercel project link" },
20138
+ { rule: "supabase/.temp/", test: /(?:^|\/)supabase\/\.temp\//, what: "Supabase CLI working files" },
20139
+ { rule: ".turbo/", test: /(?:^|\/)\.turbo\//, what: "Turborepo cache" },
20140
+ { rule: "coverage/", test: /(?:^|\/)coverage\//, what: "test coverage reports" },
20141
+ { rule: ".env*.local", test: /(?:^|\/)\.env(?:\.[^/]+)?\.local$/, what: "local secrets" },
20142
+ { rule: ".claude/settings.local.json", test: /(?:^|\/)\.claude\/settings\.local\.json$/, what: "personal Claude Code settings" },
20143
+ { rule: ".DS_Store", test: /(?:^|\/)\.DS_Store$/, what: "macOS folder files" },
20144
+ { rule: "Thumbs.db", test: /(?:^|\/)Thumbs\.db$/i, what: "Windows thumbnail files" },
20145
+ { rule: "*.log", test: /\.log$/, what: "log files" },
20146
+ { rule: "*.tsbuildinfo", test: /\.tsbuildinfo$/, what: "TypeScript build caches" },
20147
+ { rule: ".eslintcache", test: /(?:^|\/)\.eslintcache$/, what: "ESLint cache" }
20148
+ ];
20149
+ function isLocalOnly(file2) {
20150
+ return LOCAL_ONLY.some(({ test }) => test.test(file2) || test.test(`${file2}x`));
20151
+ }
20152
+
20133
20153
  // src/core/repo.ts
20134
20154
  import { existsSync, readdirSync, readFileSync, statSync } from "fs";
20135
20155
  import path from "path";
@@ -20216,7 +20236,7 @@ async function ghStatus(root) {
20216
20236
  var AURA_OWNED = /^(?:\.aura\/|\.claude\/skills\/aura\/|\.agents\/skills\/aura(?:-(?:scan|next|fix|setup))?\/|\.cursor\/commands\/aura-[a-z]+\.md$|\.pi\/prompts\/aura-[a-z]+\.md$|\.claude\/settings\.json$)/;
20217
20237
  async function dirtyFiles(root) {
20218
20238
  const r = await git(root, "status", "--porcelain", "-uall");
20219
- return r.stdout.split("\n").filter(Boolean).map((l) => l.slice(3).replace(/^"|"$/g, "").split(" -> ").pop()).filter((f) => !AURA_OWNED.test(f));
20239
+ return r.stdout.split("\n").filter(Boolean).map((l) => l.slice(3).replace(/^"|"$/g, "").split(" -> ").pop()).filter((f) => !AURA_OWNED.test(f) && !isLocalOnly(f));
20220
20240
  }
20221
20241
  async function currentBranch(root) {
20222
20242
  return (await git(root, "rev-parse", "--abbrev-ref", "HEAD")).stdout.trim();
@@ -20358,9 +20378,9 @@ var github = {
20358
20378
  isPrivate: Boolean(info.isPrivate),
20359
20379
  defaultBranch: info.defaultBranchRef?.name || "main",
20360
20380
  gh,
20361
- get: (path27) => {
20362
- if (!cache.has(path27)) cache.set(path27, gh(["api", path27]));
20363
- return cache.get(path27);
20381
+ get: (path28) => {
20382
+ if (!cache.has(path28)) cache.set(path28, gh(["api", path28]));
20383
+ return cache.get(path28);
20364
20384
  }
20365
20385
  };
20366
20386
  return { ok: true, ctx, target: `${ctx.repo} ${ctx.isPrivate ? "(private)" : "(public)"}` };
@@ -21046,7 +21066,7 @@ function pmCommands(pm) {
21046
21066
  // package.json
21047
21067
  var package_default = {
21048
21068
  name: "getaura",
21049
- version: "0.1.9",
21069
+ version: "0.1.10",
21050
21070
  description: "Aura helps you and your coding agent follow best practices while you vibe code.",
21051
21071
  type: "module",
21052
21072
  bin: {
@@ -21284,6 +21304,15 @@ var ACTIONS = [
21284
21304
  },
21285
21305
  { id: "add-security-headers", title: "Add security headers", category: "security", delivery: "pr", resolves: ["security.headers"] },
21286
21306
  // Structure
21307
+ {
21308
+ id: "ignore-local-files",
21309
+ buildSafe: true,
21310
+ title: "Keep local and tool files out of git",
21311
+ category: "structure",
21312
+ delivery: "pr",
21313
+ resolves: ["structure.local-files"]
21314
+ },
21315
+ { id: "save-aura-setup", buildSafe: true, title: "Save Aura\u2019s setup in git", category: "agent-setup", delivery: "pr", resolves: ["agent.setup-saved"] },
21287
21316
  { id: "remove-dead-files", title: "Remove dead files", category: "structure", delivery: "pr", resolves: ["structure.dead-files"] },
21288
21317
  { id: "move-misplaced-files", title: "Move misplaced files", category: "structure", delivery: "pr", resolves: ["structure.layout"] },
21289
21318
  {
@@ -21454,8 +21483,8 @@ function nextSteps(scan, opts = {}) {
21454
21483
  }
21455
21484
 
21456
21485
  // src/actions/generators.ts
21457
- import { existsSync as existsSync9, readFileSync as readFileSync9 } from "fs";
21458
- import path12 from "path";
21486
+ import { existsSync as existsSync9, readFileSync as readFileSync10 } from "fs";
21487
+ import path13 from "path";
21459
21488
 
21460
21489
  // src/checks/agent-setup.ts
21461
21490
  import { existsSync as existsSync7, readdirSync as readdirSync3, readFileSync as readFileSync6 } from "fs";
@@ -21491,6 +21520,81 @@ function lineOf(text, index) {
21491
21520
  return line;
21492
21521
  }
21493
21522
 
21523
+ // src/checks/git-hygiene.ts
21524
+ async function untracked(root, collapse) {
21525
+ const r = await run("git", ["ls-files", "--others", "--exclude-standard", ...collapse ? ["--directory"] : []], { cwd: root });
21526
+ return r.code === 0 ? r.stdout.split("\n").filter(Boolean) : null;
21527
+ }
21528
+ async function modified(root) {
21529
+ const r = await run("git", ["diff", "--name-only", "HEAD"], { cwd: root });
21530
+ return r.code === 0 ? r.stdout.split("\n").filter(Boolean) : [];
21531
+ }
21532
+ async function localFileProblems(ctx) {
21533
+ const loose = await untracked(ctx.root, true);
21534
+ if (!loose || !ctx.tracked) return null;
21535
+ return LOCAL_ONLY.map(({ rule, test, what }) => ({
21536
+ rule,
21537
+ what,
21538
+ // Committed .env files are already a security finding, so they aren't counted twice here.
21539
+ committed: rule === ".env*.local" ? [] : [...ctx.tracked].filter((f) => test.test(f)),
21540
+ // A collapsed folder like "supabase/.temp/" matches through a file inside it.
21541
+ notIgnored: loose.filter((f) => test.test(f) || test.test(`${f}x`))
21542
+ })).filter((p2) => p2.committed.length || p2.notIgnored.length);
21543
+ }
21544
+ var localFiles = {
21545
+ id: "structure.local-files",
21546
+ category: "structure",
21547
+ title: "Local and tool files are kept out of git",
21548
+ weight: 2,
21549
+ applies: (ctx) => ctx.tracked !== null,
21550
+ async run(ctx) {
21551
+ const problems = await localFileProblems(ctx);
21552
+ if (!problems) return { status: "unknown", summary: "Couldn\u2019t ask git which files it ignores." };
21553
+ if (problems.length === 0) return { status: "pass", summary: "Tool and machine files are ignored by git." };
21554
+ const committed = problems.flatMap((p2) => p2.committed);
21555
+ const finding = makeFinding(this, {
21556
+ severity: committed.length ? "medium" : "low",
21557
+ title: committed.length ? `${committed.length} local file(s) are committed to git` : `${problems.length} kind(s) of local file aren\u2019t ignored by git`,
21558
+ detail: problems.map(
21559
+ (p2) => `${p2.what} (${[...p2.committed, ...p2.notIgnored].slice(0, 3).join(", ")}): add \`${p2.rule}\` to .gitignore${p2.committed.length ? " and stop tracking them" : ""}`
21560
+ ).join("; "),
21561
+ why: "Files your computer or a tool makes for itself can hold logins and machine paths, and they clutter every change. Ignored, they can\u2019t be committed by accident.",
21562
+ locations: [{ file: ".gitignore" }],
21563
+ actionId: "ignore-local-files",
21564
+ key: "local-files"
21565
+ });
21566
+ return { status: committed.length ? "fail" : "partial", score: committed.length ? 0 : 0.5, summary: finding.title, findings: [finding] };
21567
+ }
21568
+ };
21569
+ async function unsavedSetup(ctx) {
21570
+ const loose = await untracked(ctx.root, false);
21571
+ if (!loose) return null;
21572
+ return [.../* @__PURE__ */ new Set([...loose, ...await modified(ctx.root)])].filter((f) => AURA_OWNED.test(f)).sort();
21573
+ }
21574
+ var setupSaved = {
21575
+ id: "agent.setup-saved",
21576
+ category: "agent-setup",
21577
+ title: "Aura\u2019s setup is saved in git",
21578
+ weight: 2,
21579
+ // Before the first-run PR is merged, that PR is the next step, not this.
21580
+ applies: (ctx) => Boolean(ctx.tracked?.has(".aura/brief.md")),
21581
+ async run(ctx) {
21582
+ const files = await unsavedSetup(ctx);
21583
+ if (!files) return { status: "unknown", summary: "Couldn\u2019t ask git for uncommitted files." };
21584
+ if (files.length === 0) return { status: "pass", summary: "Everything Aura set up is committed." };
21585
+ const finding = makeFinding(this, {
21586
+ severity: "low",
21587
+ title: `${files.length} Aura setup file(s) aren\u2019t committed`,
21588
+ detail: `Not in git yet: ${files.slice(0, 8).join(", ")}${files.length > 8 ? "\u2026" : ""}.`,
21589
+ why: "Your teammates, CI and your coding agent on other computers only get the setup that\u2019s committed.",
21590
+ locations: files.map((file2) => ({ file: file2 })),
21591
+ actionId: "save-aura-setup",
21592
+ key: "setup-saved"
21593
+ });
21594
+ return { status: "partial", score: 0.5, summary: finding.title, findings: [finding] };
21595
+ }
21596
+ };
21597
+
21494
21598
  // src/checks/agent-setup.ts
21495
21599
  function exists(ctx, file2) {
21496
21600
  return existsSync7(path8.join(ctx.root, file2));
@@ -21663,7 +21767,7 @@ var brief = {
21663
21767
  return { status: file2 ? "partial" : "fail", score: file2 ? 0.5 : 0, summary: finding.title, findings: [finding] };
21664
21768
  }
21665
21769
  };
21666
- var agentChecks = [rulesFile, rulesCurrent, skills, auraSkill, brief];
21770
+ var agentChecks = [rulesFile, rulesCurrent, skills, auraSkill, brief, setupSaved];
21667
21771
 
21668
21772
  // src/core/graph.ts
21669
21773
  import path9 from "path";
@@ -21805,12 +21909,54 @@ function commandList(agent) {
21805
21909
  return AURA_COMMANDS.map((c) => invocation(agent, c)).join(", ");
21806
21910
  }
21807
21911
 
21808
- // src/actions/agent-files.ts
21809
- import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
21912
+ // src/actions/git-hygiene.ts
21913
+ import { readFileSync as readFileSync8 } from "fs";
21810
21914
  import path11 from "path";
21915
+ var ignoreLocalFiles = async ({ root, ctx }) => {
21916
+ const problems = await localFileProblems(ctx) ?? [];
21917
+ if (problems.length === 0) return { skip: "Tool and machine files are already ignored." };
21918
+ let current2 = "";
21919
+ try {
21920
+ current2 = readFileSync8(path11.join(root, ".gitignore"), "utf8");
21921
+ } catch {
21922
+ }
21923
+ const lines = new Set(current2.split(/\r?\n/).map((l) => l.trim()));
21924
+ const missing = problems.map((p2) => p2.rule).filter((rule) => !lines.has(rule));
21925
+ const committed = problems.flatMap((p2) => p2.committed);
21926
+ const changes = missing.length ? [
21927
+ {
21928
+ path: ".gitignore",
21929
+ content: `${current2}${current2 && !current2.endsWith("\n") ? "\n" : ""}
21930
+ # Local and tool files (added by Aura)
21931
+ ${missing.join("\n")}
21932
+ `
21933
+ }
21934
+ ] : [];
21935
+ return {
21936
+ changes,
21937
+ commands: committed.length ? [["git", "rm", "-r", "-q", "--cached", "--", ...committed]] : [],
21938
+ details: [
21939
+ ...problems.map(
21940
+ (p2) => `- ${p2.what}: \`${p2.rule}\`${p2.committed.length ? ` (stops tracking ${p2.committed.length} file(s); they stay on your computer)` : ""}`
21941
+ )
21942
+ ]
21943
+ };
21944
+ };
21945
+ var saveAuraSetup = async ({ root }) => {
21946
+ const files = await unsavedSetup({ root }) ?? [];
21947
+ if (files.length === 0) return { skip: "Everything Aura set up is already committed." };
21948
+ return {
21949
+ changes: files.map((file2) => ({ path: file2, content: readFileSync8(path11.join(root, file2), "utf8") })),
21950
+ details: files.map((f) => `- \`${f}\``)
21951
+ };
21952
+ };
21953
+
21954
+ // src/actions/agent-files.ts
21955
+ import { existsSync as existsSync8, readFileSync as readFileSync9 } from "fs";
21956
+ import path12 from "path";
21811
21957
  function current(root, file2) {
21812
- const full = path11.join(root, file2);
21813
- return existsSync8(full) ? readFileSync8(full, "utf8") : null;
21958
+ const full = path12.join(root, file2);
21959
+ return existsSync8(full) ? readFileSync9(full, "utf8") : null;
21814
21960
  }
21815
21961
  function upsertBlock(existing, body) {
21816
21962
  const block = `${AURA_BLOCK_START}
@@ -21854,8 +22000,8 @@ function skillChanges(agents, names) {
21854
22000
 
21855
22001
  // src/actions/generators.ts
21856
22002
  function read(root, file2) {
21857
- const full = path12.join(root, file2);
21858
- return existsSync9(full) ? readFileSync9(full, "utf8") : null;
22003
+ const full = path13.join(root, file2);
22004
+ return existsSync9(full) ? readFileSync10(full, "utf8") : null;
21859
22005
  }
21860
22006
  function withScripts(root, scripts, overwrite = false) {
21861
22007
  const text = read(root, "package.json");
@@ -21876,7 +22022,7 @@ function findingMeta(scan, checkId, key) {
21876
22022
  return scan.checks.find((c) => c.checkId === checkId)?.findings[0]?.meta?.[key];
21877
22023
  }
21878
22024
  var addBrief = ({ root }) => {
21879
- const brief2 = BRIEF_PATHS.find((f) => existsSync9(path12.join(root, f)));
22025
+ const brief2 = BRIEF_PATHS.find((f) => existsSync9(path13.join(root, f)));
21880
22026
  if (!brief2) return { skip: "There is no product brief yet. Run `aura init` to draft and confirm it first." };
21881
22027
  const changes = [
21882
22028
  { path: brief2, content: read(root, brief2) },
@@ -21901,14 +22047,14 @@ var installSkills = ({ agents, skills: skills2 }) => {
21901
22047
  };
21902
22048
  var tokenEfficiency = (g) => {
21903
22049
  const changes = [];
21904
- if (!existsSync9(path12.join(g.root, "docs/index.md"))) changes.push({ path: "docs/index.md", content: render("docs/index.md", g.vars) });
22050
+ if (!existsSync9(path13.join(g.root, "docs/index.md"))) changes.push({ path: "docs/index.md", content: render("docs/index.md", g.vars) });
21905
22051
  changes.push(...rulesChanges(g.root, g.agents, g.vars));
21906
22052
  return { changes, details: ["- `docs/index.md`: a map of the repo your agent reads first", "- Lean-context rules in the agent rules file"] };
21907
22053
  };
21908
22054
  var addCi = ({ root, ctx, vars }) => {
21909
22055
  const existing = ctx.files.filter((f) => /^\.github\/workflows\//.test(f));
21910
22056
  const file2 = existing.includes(".github/workflows/ci.yml") ? ".github/workflows/checks.yml" : ".github/workflows/ci.yml";
21911
- if (existsSync9(path12.join(root, file2))) return { skip: `${file2} already exists.` };
22057
+ if (existsSync9(path13.join(root, file2))) return { skip: `${file2} already exists.` };
21912
22058
  return {
21913
22059
  changes: [{ path: file2, content: render("github/workflows/ci.yml", vars) }],
21914
22060
  details: [`- \`${file2}\`: lint, type check, tests and build on every pull request`]
@@ -21932,7 +22078,7 @@ var setupTesting = ({ root, ctx, vars }) => {
21932
22078
  };
21933
22079
  };
21934
22080
  var addEnvExample = ({ root, scan }) => {
21935
- const file2 = [".env.example", ".env.sample", ".env.template"].find((f) => existsSync9(path12.join(root, f))) ?? ".env.example";
22081
+ const file2 = [".env.example", ".env.sample", ".env.template"].find((f) => existsSync9(path13.join(root, f))) ?? ".env.example";
21936
22082
  const existing = read(root, file2);
21937
22083
  const declared = new Set(
21938
22084
  (existing ?? "").split(/\r?\n/).map((l) => l.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/)?.[1]).filter(Boolean)
@@ -21989,7 +22135,7 @@ var addLinting = ({ root, ctx }) => {
21989
22135
  return { changes, devDeps, details };
21990
22136
  };
21991
22137
  var addPreCommit = ({ root, ctx, vars }) => {
21992
- if (existsSync9(path12.join(root, ".husky/pre-commit"))) return { skip: "A husky pre-commit hook already exists." };
22138
+ if (existsSync9(path13.join(root, ".husky/pre-commit"))) return { skip: "A husky pre-commit hook already exists." };
21993
22139
  const changes = [{ path: ".husky/pre-commit", content: render("configs/husky-pre-commit", vars) }];
21994
22140
  const scripts = withScripts(root, { prepare: "husky" }, false);
21995
22141
  if (scripts) changes.push(scripts);
@@ -22019,7 +22165,7 @@ ${current2.trim()}
22019
22165
  };
22020
22166
  };
22021
22167
  var enableDependencyUpdates = ({ root, vars }) => {
22022
- if ([".github/dependabot.yml", ".github/dependabot.yaml", "renovate.json", ".github/renovate.json"].some((f) => existsSync9(path12.join(root, f)))) {
22168
+ if ([".github/dependabot.yml", ".github/dependabot.yaml", "renovate.json", ".github/renovate.json"].some((f) => existsSync9(path13.join(root, f)))) {
22023
22169
  return { skip: "Dependabot or Renovate is already configured." };
22024
22170
  }
22025
22171
  return {
@@ -22099,7 +22245,7 @@ function planMoves(root, ctx, moves) {
22099
22245
  const alias = aliasFor(toFile);
22100
22246
  if (alias) return alias[0] + noExt.slice(alias[1].length);
22101
22247
  }
22102
- let rel = path12.posix.relative(path12.posix.dirname(fromFile), noExt);
22248
+ let rel = path13.posix.relative(path13.posix.dirname(fromFile), noExt);
22103
22249
  if (!rel.startsWith(".")) rel = `./${rel}`;
22104
22250
  return rel;
22105
22251
  };
@@ -22133,7 +22279,7 @@ function planMoves(root, ctx, moves) {
22133
22279
  }
22134
22280
  var moveMisplacedFiles = ({ root, ctx, scan }) => {
22135
22281
  const raw = findingMeta(scan, "structure.layout", "moves") ?? [];
22136
- const moves = raw.map((m) => m.split("=>")).filter(([, to]) => !existsSync9(path12.join(root, to))).slice(0, MAX_MOVES);
22282
+ const moves = raw.map((m) => m.split("=>")).filter(([, to]) => !existsSync9(path13.join(root, to))).slice(0, MAX_MOVES);
22137
22283
  if (moves.length === 0) return { skip: "No files to move (or their targets already exist)." };
22138
22284
  return { changes: planMoves(root, ctx, moves), details: moves.map(([from, to]) => `- \`${from}\` \u2192 \`${to}\``), verifyTypes: true };
22139
22285
  };
@@ -22169,16 +22315,20 @@ var GENERATORS = {
22169
22315
  "add-security-headers": addSecurityHeaders,
22170
22316
  "remove-dead-files": removeDeadFiles,
22171
22317
  "move-misplaced-files": moveMisplacedFiles,
22172
- "add-aura-workflow": addAuraWorkflow
22318
+ "add-aura-workflow": addAuraWorkflow,
22319
+ "ignore-local-files": ignoreLocalFiles,
22320
+ "save-aura-setup": saveAuraSetup
22173
22321
  };
22174
- function foundationPlan(g) {
22322
+
22323
+ // src/actions/foundation.ts
22324
+ async function foundationPlan(g) {
22175
22325
  const parts = ["add-brief", "install-skills", "token-efficiency", "add-env-example", "add-aura-workflow"];
22176
22326
  const ciFailing = g.scan.checks.find((c) => c.checkId === "foundations.ci")?.status !== "pass";
22177
22327
  if (g.scan.stack.recognized && ciFailing) parts.push("add-ci");
22178
22328
  const byPath = /* @__PURE__ */ new Map();
22179
22329
  const details = [];
22180
22330
  for (const id of parts) {
22181
- const plan = GENERATORS[id](g);
22331
+ const plan = await GENERATORS[id](g);
22182
22332
  if ("skip" in plan) continue;
22183
22333
  for (const c of plan.changes) byPath.set(c.path, c);
22184
22334
  details.push(`**${id.replace(/-/g, " ").replace(/^\w/, (c) => c.toUpperCase())}**`, ...plan.details, "");
@@ -22224,7 +22374,7 @@ var AuraClient = class _AuraClient {
22224
22374
  get hasKey() {
22225
22375
  return Boolean(this.key);
22226
22376
  }
22227
- async request(method, path27, body, timeoutMs = 6e4) {
22377
+ async request(method, path28, body, timeoutMs = 6e4) {
22228
22378
  if (!this.key) throw new AuraApiError("no_key", "No pilot key");
22229
22379
  let payload;
22230
22380
  if (body !== void 0) {
@@ -22234,7 +22384,7 @@ var AuraClient = class _AuraClient {
22234
22384
  }
22235
22385
  let res;
22236
22386
  try {
22237
- res = await fetch(`${this.baseUrl}/v1/${path27}`, {
22387
+ res = await fetch(`${this.baseUrl}/v1/${path28}`, {
22238
22388
  method,
22239
22389
  headers: { authorization: `Bearer ${this.key}`, "content-type": "application/json", "user-agent": `getaura/${AURA_VERSION}` },
22240
22390
  body: payload,
@@ -22298,13 +22448,13 @@ var say = {
22298
22448
  };
22299
22449
 
22300
22450
  // src/fix-tracking.ts
22301
- import { existsSync as existsSync10, readFileSync as readFileSync10 } from "fs";
22451
+ import { existsSync as existsSync10, readFileSync as readFileSync11 } from "fs";
22302
22452
  var FILE = "fixes.json";
22303
22453
  var MAX_AGE_MS = 60 * 24 * 60 * 60 * 1e3;
22304
22454
  var MAX_CHECKS_PER_SCAN = 5;
22305
22455
  function loadTrackedFixes(root) {
22306
22456
  try {
22307
- return existsSync10(auraPath(root, FILE)) ? JSON.parse(readFileSync10(auraPath(root, FILE), "utf8")) : [];
22457
+ return existsSync10(auraPath(root, FILE)) ? JSON.parse(readFileSync11(auraPath(root, FILE), "utf8")) : [];
22308
22458
  } catch {
22309
22459
  return [];
22310
22460
  }
@@ -22612,8 +22762,8 @@ var typecheck = {
22612
22762
  async run(ctx) {
22613
22763
  if (ctx.options.fast) return { status: "unknown", summary: "Skipped in fast mode." };
22614
22764
  const { existsSync: existsSync21 } = await import("fs");
22615
- const path27 = await import("path");
22616
- const tsc = path27.join(ctx.root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
22765
+ const path28 = await import("path");
22766
+ const tsc = path28.join(ctx.root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
22617
22767
  if (!existsSync21(tsc)) return { status: "unknown", summary: "Dependencies not installed, so the type check was skipped." };
22618
22768
  const r = await run(tsc, ["--noEmit", "--pretty", "false"], { cwd: ctx.root, timeoutMs: 18e4 });
22619
22769
  if (r.timedOut) return { status: "unknown", summary: "Type check timed out." };
@@ -22635,7 +22785,7 @@ var typecheck = {
22635
22785
  var foundationChecks = [ciWorkflow, tests, readme, envExample, lockfile, lintFormat, typecheck];
22636
22786
 
22637
22787
  // src/connectors/snapshot.ts
22638
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "fs";
22788
+ import { existsSync as existsSync11, readFileSync as readFileSync12 } from "fs";
22639
22789
  var REFUSAL_MS = 30 * 24 * 60 * 60 * 1e3;
22640
22790
  function withRefusals(states, snap, now = Date.now()) {
22641
22791
  return Object.fromEntries(
@@ -22648,7 +22798,7 @@ function withRefusals(states, snap, now = Date.now()) {
22648
22798
  var FILE2 = "cache/connectors.json";
22649
22799
  function loadSnapshots(root) {
22650
22800
  try {
22651
- return existsSync11(auraPath(root, FILE2)) ? JSON.parse(readFileSync11(auraPath(root, FILE2), "utf8")) : {};
22801
+ return existsSync11(auraPath(root, FILE2)) ? JSON.parse(readFileSync12(auraPath(root, FILE2), "utf8")) : {};
22652
22802
  } catch {
22653
22803
  return {};
22654
22804
  }
@@ -22809,7 +22959,7 @@ var apiRoutes = {
22809
22959
  import { createHash as createHash3 } from "crypto";
22810
22960
  import { chmodSync as chmodSync2, existsSync as existsSync12, mkdirSync as mkdirSync3, mkdtempSync, readdirSync as readdirSync4, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
22811
22961
  import os3 from "os";
22812
- import path13 from "path";
22962
+ import path14 from "path";
22813
22963
  var GITLEAKS_SHA = {
22814
22964
  darwin_arm64: "b40ab0ae55c505963e365f271a8d3846efbc170aa17f2607f13df610a9aeb6a5",
22815
22965
  darwin_x64: "dfe101a4db2255fc85120ac7f3d25e4342c3c20cf749f2c20a18081af1952709",
@@ -22827,7 +22977,7 @@ var OSV_SHA = {
22827
22977
  windows_arm64: "ca1379da0e408279ef2c85cde0846a903495738a91c7b065a208048437c1686c"
22828
22978
  };
22829
22979
  function tarCommand(platform = process.platform, systemRoot = process.env.SystemRoot) {
22830
- return platform === "win32" ? path13.win32.join(systemRoot || "C:\\Windows", "System32", "tar.exe") : "tar";
22980
+ return platform === "win32" ? path14.win32.join(systemRoot || "C:\\Windows", "System32", "tar.exe") : "tar";
22831
22981
  }
22832
22982
  var TOOLS = {
22833
22983
  gitleaks: {
@@ -22863,7 +23013,7 @@ function ensureTool(name, noDownload = false) {
22863
23013
  async function resolveTool(name, noDownload) {
22864
23014
  const spec = TOOLS[name];
22865
23015
  const exe = process.platform === "win32" ? `${name}.exe` : name;
22866
- const cached2 = path13.join(auraBinDir(), `${name}-${spec.version}`, exe);
23016
+ const cached2 = path14.join(auraBinDir(), `${name}-${spec.version}`, exe);
22867
23017
  if (existsSync12(cached2)) return { path: cached2, version: spec.version };
22868
23018
  if (await commandExists(name)) {
22869
23019
  const v = await run(name, ["version"]);
@@ -22884,20 +23034,20 @@ async function download(name, spec, target) {
22884
23034
  const buf = Buffer.from(await res.arrayBuffer());
22885
23035
  const digest = createHash3("sha256").update(buf).digest("hex");
22886
23036
  if (digest !== asset.sha256) throw new Error(`Checksum mismatch for ${asset.file}`);
22887
- const dir = path13.dirname(target);
23037
+ const dir = path14.dirname(target);
22888
23038
  mkdirSync3(dir, { recursive: true });
22889
- const tmp = mkdtempSync(path13.join(os3.tmpdir(), `aura-${name}-`));
23039
+ const tmp = mkdtempSync(path14.join(os3.tmpdir(), `aura-${name}-`));
22890
23040
  try {
22891
23041
  if (asset.archive === null) {
22892
23042
  writeFileSync3(target, buf);
22893
23043
  } else {
22894
- const archivePath = path13.join(tmp, asset.file);
23044
+ const archivePath = path14.join(tmp, asset.file);
22895
23045
  writeFileSync3(archivePath, buf);
22896
23046
  const r = await run(tarCommand(), ["-xf", archivePath, "-C", tmp]);
22897
23047
  if (r.code !== 0) return null;
22898
23048
  const exe = readdirSync4(tmp).find((f) => f === name || f === `${name}.exe`);
22899
23049
  if (!exe) return null;
22900
- renameSync(path13.join(tmp, exe), target);
23050
+ renameSync(path14.join(tmp, exe), target);
22901
23051
  }
22902
23052
  chmodSync2(target, 493);
22903
23053
  return { path: target, version: spec.version };
@@ -23168,9 +23318,9 @@ var rls = {
23168
23318
  };
23169
23319
 
23170
23320
  // src/checks/security/secrets.ts
23171
- import { existsSync as existsSync13, mkdtempSync as mkdtempSync2, readFileSync as readFileSync12, rmSync as rmSync3 } from "fs";
23321
+ import { existsSync as existsSync13, mkdtempSync as mkdtempSync2, readFileSync as readFileSync13, rmSync as rmSync3 } from "fs";
23172
23322
  import os4 from "os";
23173
- import path14 from "path";
23323
+ import path15 from "path";
23174
23324
 
23175
23325
  // src/core/ignore.ts
23176
23326
  function globToRegex(glob) {
@@ -23208,15 +23358,15 @@ async function gitleaksHistory(ctx) {
23208
23358
  if (!ctx.tracked) return null;
23209
23359
  const bin = await ensureTool("gitleaks", ctx.options.noDownload);
23210
23360
  if (!bin) return null;
23211
- const dir = mkdtempSync2(path14.join(os4.tmpdir(), "aura-gitleaks-"));
23212
- const report = path14.join(dir, "report.json");
23361
+ const dir = mkdtempSync2(path15.join(os4.tmpdir(), "aura-gitleaks-"));
23362
+ const report = path15.join(dir, "report.json");
23213
23363
  try {
23214
23364
  const r = await run(bin.path, ["git", "--no-banner", "--redact", "--report-format", "json", "--report-path", report, "--exit-code", "0", "."], {
23215
23365
  cwd: ctx.root,
23216
23366
  timeoutMs: 18e4
23217
23367
  });
23218
23368
  if (r.code !== 0 || !existsSync13(report)) return null;
23219
- return { hits: JSON.parse(readFileSync12(report, "utf8")), engine: `gitleaks ${bin.version}` };
23369
+ return { hits: JSON.parse(readFileSync13(report, "utf8")), engine: `gitleaks ${bin.version}` };
23220
23370
  } catch {
23221
23371
  return null;
23222
23372
  } finally {
@@ -23331,7 +23481,7 @@ var envFiles = {
23331
23481
  var securityChecks = [secrets, envFiles, deps, rls, apiRoutes, headers, ...CONNECTORS.map(toolSettingsCheck)];
23332
23482
 
23333
23483
  // src/checks/structure.ts
23334
- import path15 from "path";
23484
+ import path16 from "path";
23335
23485
  var GENERATED = /(?:^|\/)(?:supabase\/migrations|generated|__generated__|\.generated)\/|\.(?:d\.ts|min\.js)$|(?:^|\/)(?:database|supabase)\.types\.ts$|(?:^|\/)components\/ui\//;
23336
23486
  function appSource(ctx) {
23337
23487
  return ctx.files.filter((f) => isSourceFile(f) && !GENERATED.test(f));
@@ -23420,7 +23570,7 @@ var duplicates = {
23420
23570
  const findings = pairs.map(
23421
23571
  ([a, b, sim]) => makeFinding(this, {
23422
23572
  severity: "low",
23423
- title: `${path15.basename(a)} and ${path15.basename(b)} are near-duplicates`,
23573
+ title: `${path16.basename(a)} and ${path16.basename(b)} are near-duplicates`,
23424
23574
  detail: `${a} and ${b} are ${Math.round(sim * 100)}% the same.`,
23425
23575
  why: "Duplicates drift apart over time: a bug gets fixed in one copy and not the other, and your agent can\u2019t tell which one is right.",
23426
23576
  locations: [{ file: a }, { file: b }],
@@ -23434,7 +23584,7 @@ var duplicates = {
23434
23584
  };
23435
23585
  var ROOT_ALLOWED = /^(?:[^/]+\.config\.[cm]?[jt]s|middleware\.[cm]?[jt]s|proxy\.[cm]?[jt]s|instrumentation(?:-client)?\.[cm]?[jt]s|next-env\.d\.ts|global\.d\.ts|env\.d\.ts|sentry\.[\w.]+\.[cm]?[jt]s)$/;
23436
23586
  function suggestTarget(file2, srcDir) {
23437
- const base = path15.posix.basename(file2);
23587
+ const base = path16.posix.basename(file2);
23438
23588
  const prefix = srcDir ? "src/" : "";
23439
23589
  const isComponent = /^[A-Z][A-Za-z0-9]*\.(?:tsx|jsx)$/.test(base);
23440
23590
  if (isComponent) return `${prefix}components/${base}`;
@@ -23484,7 +23634,7 @@ var layout = {
23484
23634
  return { status: "fail", score: Math.max(0, 1 - misplaced.length / 8), summary: finding.title, findings: [finding] };
23485
23635
  }
23486
23636
  };
23487
- var structureChecks = [deadFiles, oversized, duplicates, layout];
23637
+ var structureChecks = [deadFiles, oversized, duplicates, layout, localFiles];
23488
23638
 
23489
23639
  // src/scan.ts
23490
23640
  var ALL_CHECKS = [...securityChecks, ...agentChecks, ...foundationChecks, ...structureChecks];
@@ -23547,21 +23697,21 @@ async function runScan(root, options, onProgress) {
23547
23697
  }
23548
23698
 
23549
23699
  // src/commands/fix.ts
23550
- import { existsSync as existsSync18, readFileSync as readFileSync19, writeFileSync as writeFileSync6 } from "fs";
23551
- import path19 from "path";
23700
+ import { existsSync as existsSync18, readFileSync as readFileSync20, writeFileSync as writeFileSync6 } from "fs";
23701
+ import path20 from "path";
23552
23702
 
23553
23703
  // src/agent/tools.ts
23554
- import { existsSync as existsSync14, mkdirSync as mkdirSync4, readFileSync as readFileSync13, rmSync as rmSync4, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
23555
- import path16 from "path";
23704
+ import { existsSync as existsSync14, mkdirSync as mkdirSync4, readFileSync as readFileSync14, rmSync as rmSync4, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
23705
+ import path17 from "path";
23556
23706
  var MAX_OUTPUT = 12e3;
23557
23707
  var MAX_FILE_LINES = 800;
23558
23708
  var FORBIDDEN = /^(?:\.git|node_modules|\.next|\.vercel)(?:\/|$)|(?:^|\/)\.env(?:\.(?!example$|sample$|template$)[^/]+)?$/;
23559
23709
  function safePath(root, rel) {
23560
23710
  if (typeof rel !== "string" || !rel.trim()) throw new Error("A file path is required.");
23561
- const normalized = path16.posix.normalize(rel.replace(/\\/g, "/").replace(/^\.\//, ""));
23562
- if (normalized.startsWith("../") || normalized === ".." || path16.isAbsolute(normalized)) throw new Error(`Path is outside the repo: ${rel}`);
23711
+ const normalized = path17.posix.normalize(rel.replace(/\\/g, "/").replace(/^\.\//, ""));
23712
+ if (normalized.startsWith("../") || normalized === ".." || path17.isAbsolute(normalized)) throw new Error(`Path is outside the repo: ${rel}`);
23563
23713
  if (FORBIDDEN.test(normalized)) throw new Error(`Aura doesn\u2019t let the agent touch ${normalized}.`);
23564
- const full = path16.join(root, normalized);
23714
+ const full = path17.join(root, normalized);
23565
23715
  if (!isInside(root, full)) throw new Error(`Path is outside the repo: ${rel}`);
23566
23716
  return normalized;
23567
23717
  }
@@ -23582,10 +23732,10 @@ async function runTool(root, call, allowedCommands2) {
23582
23732
  }
23583
23733
  case "read_file": {
23584
23734
  const rel = safePath(root, input2.path);
23585
- const full = path16.join(root, rel);
23735
+ const full = path17.join(root, rel);
23586
23736
  if (!existsSync14(full)) return { output: `File not found: ${rel}` };
23587
23737
  if (statSync2(full).size > 1e6) return { output: `${rel} is too large to read.` };
23588
- const lines = readFileSync13(full, "utf8").split("\n");
23738
+ const lines = readFileSync14(full, "utf8").split("\n");
23589
23739
  const start = Math.max(1, Number(input2.startLine) || 1);
23590
23740
  const end = Math.min(lines.length, Number(input2.endLine) || start + MAX_FILE_LINES - 1);
23591
23741
  const body = lines.slice(start - 1, end).map((l, i) => `${start + i}: ${l}`).join("\n");
@@ -23602,7 +23752,7 @@ async function runTool(root, call, allowedCommands2) {
23602
23752
  if (FORBIDDEN.test(f) || glob && !glob.test(f)) continue;
23603
23753
  let text;
23604
23754
  try {
23605
- text = readFileSync13(path16.join(root, f), "utf8");
23755
+ text = readFileSync14(path17.join(root, f), "utf8");
23606
23756
  } catch {
23607
23757
  continue;
23608
23758
  }
@@ -23621,14 +23771,14 @@ async function runTool(root, call, allowedCommands2) {
23621
23771
  output: "Refused: the content contains a [REDACTED:\u2026] placeholder. Secrets are hidden from you on purpose. Read the value from an environment variable (process.env.NAME) instead of writing it."
23622
23772
  };
23623
23773
  }
23624
- mkdirSync4(path16.dirname(path16.join(root, rel)), { recursive: true });
23625
- writeFileSync4(path16.join(root, rel), content);
23774
+ mkdirSync4(path17.dirname(path17.join(root, rel)), { recursive: true });
23775
+ writeFileSync4(path17.join(root, rel), content);
23626
23776
  return { output: `Wrote ${rel} (${content.split("\n").length} lines).`, touched: rel };
23627
23777
  }
23628
23778
  case "delete_file": {
23629
23779
  const rel = safePath(root, input2.path);
23630
- if (!existsSync14(path16.join(root, rel))) return { output: `File not found: ${rel}` };
23631
- rmSync4(path16.join(root, rel));
23780
+ if (!existsSync14(path17.join(root, rel))) return { output: `File not found: ${rel}` };
23781
+ rmSync4(path17.join(root, rel));
23632
23782
  return { output: `Deleted ${rel}.`, touched: rel };
23633
23783
  }
23634
23784
  case "run_command": {
@@ -23650,7 +23800,7 @@ ${(r.stdout + "\n" + r.stderr).trim().slice(-6e3)}`;
23650
23800
 
23651
23801
  // src/api/explain.ts
23652
23802
  import { createHash as createHash4 } from "crypto";
23653
- import { existsSync as existsSync15, readFileSync as readFileSync14 } from "fs";
23803
+ import { existsSync as existsSync15, readFileSync as readFileSync15 } from "fs";
23654
23804
 
23655
23805
  // src/report.ts
23656
23806
  import pc2 from "picocolors";
@@ -23839,7 +23989,7 @@ var CACHE_FILE = "cache/explanations.json";
23839
23989
  var MAX_EXPLAIN = 25;
23840
23990
  function loadCache(root) {
23841
23991
  try {
23842
- return JSON.parse(readFileSync14(auraPath(root, CACHE_FILE), "utf8"));
23992
+ return JSON.parse(readFileSync15(auraPath(root, CACHE_FILE), "utf8"));
23843
23993
  } catch {
23844
23994
  return {};
23845
23995
  }
@@ -23861,7 +24011,7 @@ function excerptFor(root, f, context = 4) {
23861
24011
  function productContext(root) {
23862
24012
  const file2 = auraPath(root, "brief.md");
23863
24013
  if (!existsSync15(file2)) return void 0;
23864
- return redact(readFileSync14(file2, "utf8")).slice(0, 1800);
24014
+ return redact(readFileSync15(file2, "utf8")).slice(0, 1800);
23865
24015
  }
23866
24016
  async function addExplanations(root, scan, client) {
23867
24017
  const cache = loadCache(root);
@@ -23905,22 +24055,30 @@ async function addExplanations(root, scan, client) {
23905
24055
 
23906
24056
  // src/commands/simple.ts
23907
24057
  import { execFileSync } from "child_process";
23908
- import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync18 } from "fs";
23909
- import path18 from "path";
24058
+ import { existsSync as existsSync17, readdirSync as readdirSync5, readFileSync as readFileSync19 } from "fs";
24059
+ import path19 from "path";
23910
24060
  import YAML2 from "yaml";
23911
24061
 
23912
24062
  // src/commands/inventory.ts
23913
- import { readFileSync as readFileSync15 } from "fs";
24063
+ import { readFileSync as readFileSync16 } from "fs";
23914
24064
  function accountState(root, services) {
23915
24065
  const inv = loadInventory(root);
24066
+ const read2 = Object.fromEntries(
24067
+ Object.entries(loadSnapshots(root)).map(([id, snap]) => [id, Object.values(snap.settings).every((state) => state !== "off")])
24068
+ );
23916
24069
  return {
23917
24070
  inventoryComplete: services.every((s) => inv.services[s]?.owner) && Boolean(inv.githubOrg),
23918
- secured: { ...inv.secured, github: Boolean(inv.githubSecurity || inv.secured?.github) }
24071
+ secured: Object.fromEntries(
24072
+ [.../* @__PURE__ */ new Set([...Object.keys(read2), ...Object.keys(inv.secured ?? {}), "github"])].map((id) => [
24073
+ id,
24074
+ Boolean(read2[id] || inv.secured?.[id] || id === "github" && inv.githubSecurity)
24075
+ ])
24076
+ )
23919
24077
  };
23920
24078
  }
23921
24079
  function loadInventory(root) {
23922
24080
  try {
23923
- return JSON.parse(readFileSync15(auraPath(root, "inventory.json"), "utf8"));
24081
+ return JSON.parse(readFileSync16(auraPath(root, "inventory.json"), "utf8"));
23924
24082
  } catch {
23925
24083
  return { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), services: {} };
23926
24084
  }
@@ -24007,10 +24165,10 @@ async function inventoryCommand(flags) {
24007
24165
  }
24008
24166
 
24009
24167
  // src/history.ts
24010
- import { readFileSync as readFileSync16 } from "fs";
24168
+ import { readFileSync as readFileSync17 } from "fs";
24011
24169
  function loadHistory(root) {
24012
24170
  try {
24013
- const data = JSON.parse(readFileSync16(auraPath(root, "history.json"), "utf8"));
24171
+ const data = JSON.parse(readFileSync17(auraPath(root, "history.json"), "utf8"));
24014
24172
  return Array.isArray(data) ? data : [];
24015
24173
  } catch {
24016
24174
  return [];
@@ -24058,10 +24216,10 @@ function banner(colors = pc3.isColorSupported) {
24058
24216
  // src/api/connect.ts
24059
24217
  import { spawn as spawn2 } from "child_process";
24060
24218
  import { constants, generateKeyPairSync, privateDecrypt } from "crypto";
24061
- import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync17, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
24219
+ import { existsSync as existsSync16, mkdirSync as mkdirSync5, readFileSync as readFileSync18, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
24062
24220
  import os5 from "os";
24063
- import path17 from "path";
24064
- var pendingFile = () => path17.join(os5.homedir(), ".aura", "pending-connect.json");
24221
+ import path18 from "path";
24222
+ var pendingFile = () => path18.join(os5.homedir(), ".aura", "pending-connect.json");
24065
24223
  async function postJson(apiUrl, route, body) {
24066
24224
  let res;
24067
24225
  try {
@@ -24080,7 +24238,7 @@ async function postJson(apiUrl, route, body) {
24080
24238
  }
24081
24239
  function loadPending(apiUrl, now) {
24082
24240
  try {
24083
- const p2 = JSON.parse(readFileSync17(pendingFile(), "utf8"));
24241
+ const p2 = JSON.parse(readFileSync18(pendingFile(), "utf8"));
24084
24242
  if (p2.apiUrl === apiUrl && Date.parse(p2.expiresAt) - now > 6e4) return p2;
24085
24243
  } catch {
24086
24244
  }
@@ -24099,7 +24257,7 @@ async function startOrResumeConnect(apiUrl, now = Date.now()) {
24099
24257
  });
24100
24258
  const res = ConnectStartResponse.parse(await postJson(apiUrl, "connect", { publicKey }));
24101
24259
  const pending = { apiUrl, privateKey, ...res };
24102
- mkdirSync5(path17.dirname(pendingFile()), { recursive: true });
24260
+ mkdirSync5(path18.dirname(pendingFile()), { recursive: true });
24103
24261
  writeFileSync5(pendingFile(), JSON.stringify(pending, null, 2) + "\n", { mode: 384 });
24104
24262
  return { ...pending, resumed: false };
24105
24263
  }
@@ -24247,7 +24405,7 @@ async function latestScan(root) {
24247
24405
  const file2 = auraPath(root, "scan.json");
24248
24406
  if (existsSync17(file2)) {
24249
24407
  try {
24250
- const scan = JSON.parse(readFileSync18(file2, "utf8"));
24408
+ const scan = JSON.parse(readFileSync19(file2, "utf8"));
24251
24409
  const head = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
24252
24410
  const dirty = execFileSync("git", ["status", "--porcelain"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
24253
24411
  if (scan.commit === head && !dirty && Date.now() - Date.parse(scan.scannedAt) < 24 * 36e5) return scan;
@@ -24362,9 +24520,9 @@ async function configCommand(key, value) {
24362
24520
  return 0;
24363
24521
  }
24364
24522
  function loadGuides() {
24365
- const dir = path18.join(templatesDir(), "guides");
24523
+ const dir = path19.join(templatesDir(), "guides");
24366
24524
  return readdirSync5(dir).filter((f) => f.endsWith(".md")).map((f) => {
24367
- const text = readFileSync18(path18.join(dir, f), "utf8");
24525
+ const text = readFileSync19(path19.join(dir, f), "utf8");
24368
24526
  const fm = text.match(/^---\n([\s\S]*?)\n---\n?/);
24369
24527
  const meta3 = fm ? YAML2.parse(fm[1]) : {};
24370
24528
  return { id: f.replace(/\.md$/, ""), title: meta3.title ?? f, summary: meta3.summary ?? "", body: fm ? text.slice(fm[0].length) : text };
@@ -24432,7 +24590,7 @@ function allowedCommands(root, pm, typescript) {
24432
24590
  const pkg = readJson(root, "package.json");
24433
24591
  const run2 = pmCommands(pm).run;
24434
24592
  const cmds = ["test", "lint", "typecheck", "build"].filter((s) => pkg?.scripts?.[s]).map((s) => `${run2} ${s}`);
24435
- if (typescript && !pkg?.scripts?.typecheck && existsSync18(path19.join(root, "node_modules/.bin/tsc"))) cmds.push(`${pmCommands(pm).exec} tsc --noEmit`);
24593
+ if (typescript && !pkg?.scripts?.typecheck && existsSync18(path20.join(root, "node_modules/.bin/tsc"))) cmds.push(`${pmCommands(pm).exec} tsc --noEmit`);
24436
24594
  return cmds;
24437
24595
  }
24438
24596
  function taskFor(root, scan, action2, findings) {
@@ -24483,8 +24641,8 @@ async function untrackEnvFiles(root, findings) {
24483
24641
  const files = findings.filter((f) => f.checkId === "security.env-files").map((f) => f.locations[0].file);
24484
24642
  for (const f of files) await git(root, "rm", "-q", "--cached", "--", f);
24485
24643
  if (files.length) {
24486
- const gi = path19.join(root, ".gitignore");
24487
- const current2 = existsSync18(gi) ? readFileSync19(gi, "utf8") : "";
24644
+ const gi = path20.join(root, ".gitignore");
24645
+ const current2 = existsSync18(gi) ? readFileSync20(gi, "utf8") : "";
24488
24646
  if (!/^\.env\*?$/m.test(current2)) writeFileSync6(gi, `${current2.trimEnd()}
24489
24647
 
24490
24648
  # Environment files hold secrets
@@ -24598,7 +24756,7 @@ ${pc.bold(action2.title)} ${pc.dim(`${findings.length} finding(s)`)}`);
24598
24756
  messages.push({ role: "tool", content: results });
24599
24757
  }
24600
24758
  s.stop(finish ? "Aura\u2019s agent finished" : "Aura\u2019s agent stopped");
24601
- const untracked = action2.id === "remove-leaked-secrets" ? await untrackEnvFiles(root, findings) : [];
24759
+ const untracked2 = action2.id === "remove-leaked-secrets" ? await untrackEnvFiles(root, findings) : [];
24602
24760
  for (const f of touched) await git(root, "add", "-A", "--", f);
24603
24761
  const staged = (await git(root, "diff", "--cached", "--name-only")).stdout.trim();
24604
24762
  if (!staged) {
@@ -24620,7 +24778,7 @@ ${pc.bold(action2.title)} ${pc.dim(`${findings.length} finding(s)`)}`);
24620
24778
  "",
24621
24779
  ...findings.slice(0, 20).map((f) => `- ${f.title}`),
24622
24780
  "",
24623
- ...untracked.length ? ["### Removed from git", "", ...untracked.map((f) => `- \`${f}\` (still on your computer, now ignored by git)`), ""] : [],
24781
+ ...untracked2.length ? ["### Removed from git", "", ...untracked2.map((f) => `- \`${f}\` (still on your computer, now ignored by git)`), ""] : [],
24624
24782
  ...finish?.testsRun.length ? ["### Checks run", "", ...finish.testsRun.map((t) => `- ${t}`), ""] : [],
24625
24783
  ...finish?.risks.length ? ["### Things to check before merging", "", ...finish.risks.map((r) => `- ${r}`), ""] : [],
24626
24784
  ...guides.length ? [
@@ -24828,7 +24986,7 @@ async function applyAction(actionId, flags, report) {
24828
24986
  );
24829
24987
  }
24830
24988
  const g = { root, scan, ctx, agents: [...agents], vars: templateVars(root, scan, ctx.pkg), skills: skills2 };
24831
- const plan = actionId === "foundation" ? foundationPlan(g) : GENERATORS[actionId]?.(g) ?? { skip: "Nothing to do." };
24989
+ const plan = actionId === "foundation" ? await foundationPlan(g) : await GENERATORS[actionId]?.(g) ?? { skip: "Nothing to do." };
24832
24990
  if ("skip" in plan || plan.changes.length === 0 && !plan.commands?.length && !plan.devDeps?.length) {
24833
24991
  say.info("skip" in plan ? plan.skip : "Nothing to change.");
24834
24992
  report.outcome = "no_changes";
@@ -24837,7 +24995,7 @@ async function applyAction(actionId, flags, report) {
24837
24995
  console.log(`
24838
24996
  ${pc.bold(action2.title)}`);
24839
24997
  for (const c of plan.changes)
24840
- console.log(` ${c.content === null ? pc.red("delete") : existsSync19(path20.join(root, c.path)) ? pc.yellow("update") : pc.green("add ")} ${c.path}`);
24998
+ console.log(` ${c.content === null ? pc.red("delete") : existsSync19(path21.join(root, c.path)) ? pc.yellow("update") : pc.green("add ")} ${c.path}`);
24841
24999
  const pm = pmCommands(scan.stack.packageManager);
24842
25000
  if (plan.devDeps?.length) console.log(` ${pc.cyan("install")} ${plan.devDeps.join(", ")} ${pc.dim(`(${pm.addDev})`)}`);
24843
25001
  for (const cmd of plan.commands ?? []) console.log(` ${pc.cyan("run ")} ${cmd.join(" ")}`);
@@ -24869,7 +25027,7 @@ ${pc.bold(action2.title)}`);
24869
25027
  report.outcome = "cancelled";
24870
25028
  return 0;
24871
25029
  }
24872
- const tsc = path20.join(root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
25030
+ const tsc = path21.join(root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
24873
25031
  const checkTypes = plan.verifyTypes && scan.stack.typescript && existsSync19(tsc);
24874
25032
  const typeErrors = async () => {
24875
25033
  const r = await run(tsc, ["--noEmit", "--pretty", "false"], { cwd: root, timeoutMs: 3e5 });
@@ -24979,17 +25137,17 @@ async function undoPlan(root, plan) {
24979
25137
  for (const file2 of touched) {
24980
25138
  const inHead = (await git(root, "cat-file", "-e", `HEAD:${file2}`)).code === 0;
24981
25139
  if (inHead) await git(root, "checkout", "HEAD", "--", file2);
24982
- else if (plan.changes.some((c) => c.path === file2)) rmSync6(path20.join(root, file2), { force: true });
25140
+ else if (plan.changes.some((c) => c.path === file2)) rmSync6(path21.join(root, file2), { force: true });
24983
25141
  }
24984
25142
  }
24985
25143
 
24986
25144
  // src/commands/init.ts
24987
- import { existsSync as existsSync20, readFileSync as readFileSync21 } from "fs";
25145
+ import { existsSync as existsSync20, readFileSync as readFileSync22 } from "fs";
24988
25146
  import { spawnSync as spawnSync3 } from "child_process";
24989
- import path24 from "path";
25147
+ import path25 from "path";
24990
25148
 
24991
25149
  // src/brief.ts
24992
- import path21 from "path";
25150
+ import path22 from "path";
24993
25151
  function repoSummary(ctx) {
24994
25152
  const routes = ctx.files.filter((f) => /^(?:src\/)?app\/(?:.+\/)?(?:page|route)\.[cm]?[jt]sx?$/.test(f) || /^(?:src\/)?pages\/.+\.[cm]?[jt]sx?$/.test(f)).map(
24995
25153
  (f) => "/" + f.replace(/^(?:src\/)?(?:app|pages)\//, "").replace(/(?:^|\/)(?:page|route|index)\.[cm]?[jt]sx?$|\.[cm]?[jt]sx?$/, "").replace(/\([^)]+\)\//g, "")
@@ -25005,7 +25163,7 @@ function repoSummary(ctx) {
25005
25163
  const readme2 = ctx.files.find((f) => /^readme\.mdx?$/i.test(f));
25006
25164
  const readmeText = readme2 ? ctx.read(readme2) ?? "" : "";
25007
25165
  return {
25008
- name: ctx.pkg?.name ?? path21.basename(ctx.root),
25166
+ name: ctx.pkg?.name ?? path22.basename(ctx.root),
25009
25167
  packageJson: ctx.pkg ? {
25010
25168
  name: ctx.pkg.name,
25011
25169
  description: ctx.pkg.description,
@@ -25099,9 +25257,9 @@ function installFallback(result, platform = process.platform) {
25099
25257
  // src/core/claude-plugin.ts
25100
25258
  import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
25101
25259
  import os6 from "os";
25102
- import path22 from "path";
25260
+ import path23 from "path";
25103
25261
  var CLAUDE_PLUGIN_ID = "aura@aura";
25104
- var marketplaceDir = () => path22.join(os6.homedir(), ".aura", "claude-marketplace");
25262
+ var marketplaceDir = () => path23.join(os6.homedir(), ".aura", "claude-marketplace");
25105
25263
  var MARKETPLACE = {
25106
25264
  name: "aura",
25107
25265
  description: "Aura for Claude Code",
@@ -25116,8 +25274,8 @@ var MARKETPLACE = {
25116
25274
  };
25117
25275
  function writeMarketplace() {
25118
25276
  const dir = marketplaceDir();
25119
- mkdirSync6(path22.join(dir, ".claude-plugin"), { recursive: true });
25120
- writeFileSync7(path22.join(dir, ".claude-plugin", "marketplace.json"), `${JSON.stringify(MARKETPLACE, null, 2)}
25277
+ mkdirSync6(path23.join(dir, ".claude-plugin"), { recursive: true });
25278
+ writeFileSync7(path23.join(dir, ".claude-plugin", "marketplace.json"), `${JSON.stringify(MARKETPLACE, null, 2)}
25121
25279
  `);
25122
25280
  return dir;
25123
25281
  }
@@ -25221,15 +25379,15 @@ function describeInferred(inferred) {
25221
25379
  // src/commands/scan.ts
25222
25380
  import { mkdtempSync as mkdtempSync3, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "fs";
25223
25381
  import os7 from "os";
25224
- import path23 from "path";
25382
+ import path24 from "path";
25225
25383
 
25226
25384
  // src/connectors/refresh.ts
25227
- import { readFileSync as readFileSync20 } from "fs";
25385
+ import { readFileSync as readFileSync21 } from "fs";
25228
25386
  async function refreshToolSettings(root) {
25229
25387
  if (process.env.AURA_NO_CONNECTORS) return;
25230
25388
  let previous = null;
25231
25389
  try {
25232
- previous = JSON.parse(readFileSync20(auraPath(root, "scan.json"), "utf8"));
25390
+ previous = JSON.parse(readFileSync21(auraPath(root, "scan.json"), "utf8"));
25233
25391
  } catch {
25234
25392
  }
25235
25393
  const snapshots = loadSnapshots(root);
@@ -25242,8 +25400,8 @@ async function refreshToolSettings(root) {
25242
25400
 
25243
25401
  // src/commands/scan.ts
25244
25402
  async function scanRef(root, ref, opts) {
25245
- const dir = mkdtempSync3(path23.join(os7.tmpdir(), "aura-base-"));
25246
- const wt = path23.join(dir, "tree");
25403
+ const dir = mkdtempSync3(path24.join(os7.tmpdir(), "aura-base-"));
25404
+ const wt = path24.join(dir, "tree");
25247
25405
  const add = await run("git", ["worktree", "add", "--detach", wt, ref], { cwd: root });
25248
25406
  if (add.code !== 0) {
25249
25407
  rmSync7(dir, { recursive: true, force: true });
@@ -25333,7 +25491,7 @@ async function scanCommand(flags) {
25333
25491
  // src/commands/init.ts
25334
25492
  var AGENT_IDS = ["claude", "codex", "cursor", "pi"];
25335
25493
  function detectAgents(root) {
25336
- return detectAgentsFrom((rel) => existsSync20(path24.join(root, rel)));
25494
+ return detectAgentsFrom((rel) => existsSync20(path25.join(root, rel)));
25337
25495
  }
25338
25496
  function openEditor(file2) {
25339
25497
  const fallback = process.platform === "win32" ? "notepad" : spawnSync3("sh", ["-c", "command -v nano"]).status === 0 ? "nano" : "vi";
@@ -25448,13 +25606,13 @@ async function initCommand(flags) {
25448
25606
  let brief2 = null;
25449
25607
  let keptBrief = false;
25450
25608
  if (flags.briefFile) {
25451
- brief2 = readFileSync21(flags.briefFile, "utf8");
25609
+ brief2 = readFileSync22(flags.briefFile, "utf8");
25452
25610
  } else if (!flags.answers && existsSync20(briefPath) && !(tty && unwrap(await p.confirm({ message: "A product brief already exists. Draft a new one?", initialValue: false })))) {
25453
25611
  keptBrief = true;
25454
25612
  } else {
25455
25613
  let answers = null;
25456
25614
  if (flags.answers) {
25457
- answers = OnboardingAnswersSchema.parse(JSON.parse(readFileSync21(flags.answers, "utf8")));
25615
+ answers = OnboardingAnswersSchema.parse(JSON.parse(readFileSync22(flags.answers, "utf8")));
25458
25616
  } else if (tty) {
25459
25617
  const inferred = inferAnswers(ctx);
25460
25618
  p.note(describeInferred(inferred).join("\n"), "Here\u2019s what Aura found about your app");
@@ -25586,7 +25744,7 @@ ${pc.magenta("\u2726")} ${pc.bold("Give the user the aura command")}`);
25586
25744
  }
25587
25745
 
25588
25746
  // src/commands/summary.ts
25589
- import path25 from "path";
25747
+ import path26 from "path";
25590
25748
  async function activity(root, since) {
25591
25749
  const log = await git(root, "log", `--since=${since.toISOString()}`, "--no-merges", "--format=%H%x09%an%x09%s");
25592
25750
  const commits = log.stdout.split("\n").filter(Boolean).slice(0, 300).map((l) => {
@@ -25643,7 +25801,7 @@ async function summaryCommand(flags) {
25643
25801
  const beforeIds = new Set(before ? allFindings(before).map((f) => f.id) : []);
25644
25802
  const nowIds = new Set(allFindings(now).map((f) => f.id));
25645
25803
  const req = {
25646
- repo: path25.basename(root),
25804
+ repo: path26.basename(root),
25647
25805
  since: since.toISOString(),
25648
25806
  until: until.toISOString(),
25649
25807
  product: productContext(root),
@@ -25691,11 +25849,11 @@ ${body}`);
25691
25849
  import pc4 from "picocolors";
25692
25850
 
25693
25851
  // src/core/update-check.ts
25694
- import { mkdirSync as mkdirSync7, readFileSync as readFileSync22, writeFileSync as writeFileSync9 } from "fs";
25852
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync23, writeFileSync as writeFileSync9 } from "fs";
25695
25853
  import os8 from "os";
25696
- import path26 from "path";
25854
+ import path27 from "path";
25697
25855
  var DAY_MS = 24 * 60 * 60 * 1e3;
25698
- var cacheFile = () => path26.join(os8.homedir(), ".aura", "update-check.json");
25856
+ var cacheFile = () => path27.join(os8.homedir(), ".aura", "update-check.json");
25699
25857
  function isNewer(a, b) {
25700
25858
  if (!/^\d+\.\d+\.\d+$/.test(a) || !/^\d+\.\d+\.\d+$/.test(b)) return false;
25701
25859
  const [x, y] = [a, b].map((v) => v.split(".").map(Number));
@@ -25715,13 +25873,13 @@ async function newerVersion(opts = {}) {
25715
25873
  const now = opts.now ?? Date.now();
25716
25874
  let latest = null;
25717
25875
  try {
25718
- const cached2 = JSON.parse(readFileSync22(cacheFile(), "utf8"));
25876
+ const cached2 = JSON.parse(readFileSync23(cacheFile(), "utf8"));
25719
25877
  if (now - cached2.checkedAt < DAY_MS) latest = cached2.latest;
25720
25878
  else throw new Error("stale");
25721
25879
  } catch {
25722
25880
  latest = await (opts.fetchLatest ?? fetchLatest)();
25723
25881
  try {
25724
- mkdirSync7(path26.dirname(cacheFile()), { recursive: true });
25882
+ mkdirSync7(path27.dirname(cacheFile()), { recursive: true });
25725
25883
  writeFileSync9(cacheFile(), JSON.stringify({ checkedAt: now, latest }));
25726
25884
  } catch {
25727
25885
  }