getaura 0.1.6 → 0.1.7
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/.claude-plugin/plugin.json +8 -0
- package/README.md +1 -1
- package/dist/index.js +743 -291
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
- package/plugin/skills/fix/SKILL.md +20 -0
- package/plugin/skills/next/SKILL.md +19 -0
- package/plugin/skills/scan/SKILL.md +16 -0
- package/plugin/skills/setup/SKILL.md +16 -0
- package/templates/skills/aura/SKILL.md +2 -2
package/dist/index.js
CHANGED
|
@@ -9,8 +9,8 @@ var __export = (target, all) => {
|
|
|
9
9
|
import { Command, Option } from "commander";
|
|
10
10
|
|
|
11
11
|
// src/commands/apply.ts
|
|
12
|
-
import { existsSync as
|
|
13
|
-
import
|
|
12
|
+
import { existsSync as existsSync16, rmSync as rmSync6 } from "fs";
|
|
13
|
+
import path18 from "path";
|
|
14
14
|
|
|
15
15
|
// src/core/agents.ts
|
|
16
16
|
var AGENT_LAYOUT = {
|
|
@@ -36,6 +36,25 @@ function uniqueSkillDirs(agents) {
|
|
|
36
36
|
}
|
|
37
37
|
var AURA_BLOCK_START = "<!-- aura:start -->";
|
|
38
38
|
var AURA_BLOCK_END = "<!-- aura:end -->";
|
|
39
|
+
function detectAgentsFrom(exists2) {
|
|
40
|
+
const found = [];
|
|
41
|
+
if (exists2("CLAUDE.md") || exists2(".claude")) found.push("claude");
|
|
42
|
+
if (exists2(".codex")) found.push("codex");
|
|
43
|
+
if (exists2(".cursor") || exists2(".cursorrules")) found.push("cursor");
|
|
44
|
+
if (exists2(".pi")) found.push("pi");
|
|
45
|
+
if (found.length === 0 && exists2("AGENTS.md")) found.push("codex");
|
|
46
|
+
return found;
|
|
47
|
+
}
|
|
48
|
+
function runningAgent(env = process.env) {
|
|
49
|
+
if (env.CLAUDECODE) return "claude";
|
|
50
|
+
if (env.CODEX_THREAD_ID) return "codex";
|
|
51
|
+
if (env.CURSOR_AGENT) return "cursor";
|
|
52
|
+
if (env.PI_CODING_AGENT) return "pi";
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
function inCI(env = process.env) {
|
|
56
|
+
return Boolean(env.CI) && !runningAgent(env);
|
|
57
|
+
}
|
|
39
58
|
|
|
40
59
|
// ../shared/src/types.ts
|
|
41
60
|
var CATEGORIES = ["security", "agent-setup", "foundations", "structure"];
|
|
@@ -990,10 +1009,10 @@ function mergeDefs(...defs) {
|
|
|
990
1009
|
function cloneDef(schema) {
|
|
991
1010
|
return mergeDefs(schema._zod.def);
|
|
992
1011
|
}
|
|
993
|
-
function getElementAtPath(obj,
|
|
994
|
-
if (!
|
|
1012
|
+
function getElementAtPath(obj, path25) {
|
|
1013
|
+
if (!path25)
|
|
995
1014
|
return obj;
|
|
996
|
-
return
|
|
1015
|
+
return path25.reduce((acc, key) => acc?.[key], obj);
|
|
997
1016
|
}
|
|
998
1017
|
function promiseAllObject(promisesObj) {
|
|
999
1018
|
const keys = Object.keys(promisesObj);
|
|
@@ -1333,11 +1352,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
1333
1352
|
}
|
|
1334
1353
|
return false;
|
|
1335
1354
|
}
|
|
1336
|
-
function prefixIssues(
|
|
1355
|
+
function prefixIssues(path25, issues) {
|
|
1337
1356
|
return issues.map((iss) => {
|
|
1338
1357
|
var _a3;
|
|
1339
1358
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
1340
|
-
iss.path.unshift(
|
|
1359
|
+
iss.path.unshift(path25);
|
|
1341
1360
|
return iss;
|
|
1342
1361
|
});
|
|
1343
1362
|
}
|
|
@@ -1787,16 +1806,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
|
|
|
1787
1806
|
}
|
|
1788
1807
|
function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
1789
1808
|
const fieldErrors = { _errors: [] };
|
|
1790
|
-
const processError = (error63,
|
|
1809
|
+
const processError = (error63, path25 = []) => {
|
|
1791
1810
|
for (const issue2 of error63.issues) {
|
|
1792
1811
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
1793
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
1812
|
+
issue2.errors.map((issues) => processError({ issues }, [...path25, ...issue2.path]));
|
|
1794
1813
|
} else if (issue2.code === "invalid_key") {
|
|
1795
|
-
processError({ issues: issue2.issues }, [...
|
|
1814
|
+
processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
|
|
1796
1815
|
} else if (issue2.code === "invalid_element") {
|
|
1797
|
-
processError({ issues: issue2.issues }, [...
|
|
1816
|
+
processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
|
|
1798
1817
|
} else {
|
|
1799
|
-
const fullpath = [...
|
|
1818
|
+
const fullpath = [...path25, ...issue2.path];
|
|
1800
1819
|
if (fullpath.length === 0) {
|
|
1801
1820
|
fieldErrors._errors.push(mapper(issue2));
|
|
1802
1821
|
} else {
|
|
@@ -1835,17 +1854,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
|
1835
1854
|
}
|
|
1836
1855
|
function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
1837
1856
|
const result = { errors: [] };
|
|
1838
|
-
const processError = (error63,
|
|
1857
|
+
const processError = (error63, path25 = []) => {
|
|
1839
1858
|
var _a3;
|
|
1840
1859
|
for (const issue2 of error63.issues) {
|
|
1841
1860
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
1842
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
1861
|
+
issue2.errors.map((issues) => processError({ issues }, [...path25, ...issue2.path]));
|
|
1843
1862
|
} else if (issue2.code === "invalid_key") {
|
|
1844
|
-
processError({ issues: issue2.issues }, [...
|
|
1863
|
+
processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
|
|
1845
1864
|
} else if (issue2.code === "invalid_element") {
|
|
1846
|
-
processError({ issues: issue2.issues }, [...
|
|
1865
|
+
processError({ issues: issue2.issues }, [...path25, ...issue2.path]);
|
|
1847
1866
|
} else {
|
|
1848
|
-
const fullpath = [...
|
|
1867
|
+
const fullpath = [...path25, ...issue2.path];
|
|
1849
1868
|
if (fullpath.length === 0) {
|
|
1850
1869
|
result.errors.push(mapper(issue2));
|
|
1851
1870
|
continue;
|
|
@@ -1884,8 +1903,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
|
1884
1903
|
}
|
|
1885
1904
|
function toDotPath(_path) {
|
|
1886
1905
|
const segs = [];
|
|
1887
|
-
const
|
|
1888
|
-
for (const seg of
|
|
1906
|
+
const path25 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
1907
|
+
for (const seg of path25) {
|
|
1889
1908
|
if (typeof seg === "number")
|
|
1890
1909
|
segs.push(`[${seg}]`);
|
|
1891
1910
|
else if (typeof seg === "symbol")
|
|
@@ -18987,13 +19006,13 @@ function resolveRef(ref, ctx) {
|
|
|
18987
19006
|
if (!ref.startsWith("#")) {
|
|
18988
19007
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
18989
19008
|
}
|
|
18990
|
-
const
|
|
18991
|
-
if (
|
|
19009
|
+
const path25 = ref.slice(1).split("/").filter(Boolean);
|
|
19010
|
+
if (path25.length === 0) {
|
|
18992
19011
|
return ctx.rootSchema;
|
|
18993
19012
|
}
|
|
18994
19013
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
18995
|
-
if (
|
|
18996
|
-
const key =
|
|
19014
|
+
if (path25[0] === defsKey) {
|
|
19015
|
+
const key = path25[1] === void 0 ? void 0 : decodeJSONPointerSegment(path25[1]);
|
|
18997
19016
|
if (!key || !ctx.defs[key]) {
|
|
18998
19017
|
throw new Error(`Reference not found: ${ref}`);
|
|
18999
19018
|
}
|
|
@@ -19939,14 +19958,58 @@ var SummaryRequest = external_exports.object({
|
|
|
19939
19958
|
resolvedFindings: external_exports.array(external_exports.string().max(300)).max(50)
|
|
19940
19959
|
});
|
|
19941
19960
|
var SummaryResponse = external_exports.object({ summary: external_exports.string() });
|
|
19961
|
+
var SETUP_STEPS = [
|
|
19962
|
+
"started",
|
|
19963
|
+
"key_connected",
|
|
19964
|
+
"brief_written",
|
|
19965
|
+
"skill_installed",
|
|
19966
|
+
"command_installed",
|
|
19967
|
+
"command_install_failed",
|
|
19968
|
+
"first_scan"
|
|
19969
|
+
];
|
|
19970
|
+
var FIX_OUTCOMES = [
|
|
19971
|
+
"pr_opened",
|
|
19972
|
+
"committed",
|
|
19973
|
+
"no_changes",
|
|
19974
|
+
"failed",
|
|
19975
|
+
"cancelled",
|
|
19976
|
+
"task_written",
|
|
19977
|
+
"shown",
|
|
19978
|
+
"merged",
|
|
19979
|
+
"closed",
|
|
19980
|
+
"resolved",
|
|
19981
|
+
"not_resolved"
|
|
19982
|
+
];
|
|
19942
19983
|
var TelemetryRequest = external_exports.object({
|
|
19943
|
-
event: external_exports.enum(["scan", "action"]),
|
|
19984
|
+
event: external_exports.enum(["scan", "action", "setup", "fix_result", "error"]),
|
|
19944
19985
|
auraVersion: external_exports.string().max(40),
|
|
19945
19986
|
/** Anonymous, stable per-repo id (hash of the first commit), never the repo name. */
|
|
19946
19987
|
repoId: external_exports.string().max(80),
|
|
19988
|
+
/** Who ran Aura: a person at a terminal, a coding agent or CI. */
|
|
19989
|
+
source: external_exports.enum(["terminal", "agent", "ci"]).optional(),
|
|
19990
|
+
/** The coding agent that ran this command, when it could be told. */
|
|
19991
|
+
agent: external_exports.enum(["claude", "codex", "cursor", "pi"]).optional(),
|
|
19992
|
+
/** The coding agents set up in this repo. */
|
|
19993
|
+
agents: external_exports.array(external_exports.enum(["claude", "codex", "cursor", "pi"])).max(4).optional(),
|
|
19994
|
+
os: external_exports.enum(["darwin", "win32", "linux", "other"]).optional(),
|
|
19995
|
+
nodeVersion: external_exports.string().regex(/^v?\d+\.\d+\.\d+$/).optional(),
|
|
19947
19996
|
score: external_exports.number().optional(),
|
|
19948
19997
|
categories: external_exports.record(external_exports.string(), external_exports.number().nullable()).optional(),
|
|
19949
|
-
|
|
19998
|
+
/** Findings by severity, and the ids of checks that didn't fully pass (never file names). */
|
|
19999
|
+
findings: external_exports.record(severity, external_exports.number().int().nonnegative()).optional(),
|
|
20000
|
+
failingChecks: external_exports.array(external_exports.string().regex(/^[a-z-]+\.[a-z-]+$/)).max(40).optional(),
|
|
20001
|
+
actionId: external_exports.string().regex(/^[a-z0-9-]{1,80}$/).optional(),
|
|
20002
|
+
step: external_exports.enum(SETUP_STEPS).optional(),
|
|
20003
|
+
outcome: external_exports.enum(FIX_OUTCOMES).optional(),
|
|
20004
|
+
delivery: external_exports.enum(["pr", "agent-fix", "agent-task", "guide", "checklist"]).optional(),
|
|
20005
|
+
/** Whether the user picked it from the next-steps checklist or ran the command. */
|
|
20006
|
+
via: external_exports.enum(["checklist", "command"]).optional(),
|
|
20007
|
+
durationMs: external_exports.number().int().nonnegative().max(864e5).optional(),
|
|
20008
|
+
agentSteps: external_exports.number().int().nonnegative().max(1e3).optional(),
|
|
20009
|
+
scoreChange: external_exports.number().optional(),
|
|
20010
|
+
command: external_exports.string().regex(/^[a-z-]{1,30}$/).optional(),
|
|
20011
|
+
/** The kind of error (its class or code), never its message, which can contain paths. */
|
|
20012
|
+
errorType: external_exports.string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,59}$/).optional()
|
|
19950
20013
|
});
|
|
19951
20014
|
var MeResponse = external_exports.object({
|
|
19952
20015
|
name: external_exports.string(),
|
|
@@ -19969,7 +20032,6 @@ var ConnectStartResponse = external_exports.object({
|
|
|
19969
20032
|
var ConnectPollRequest = external_exports.object({ deviceSecret: external_exports.string().min(20).max(200) });
|
|
19970
20033
|
var ConnectPollResponse = external_exports.discriminatedUnion("status", [
|
|
19971
20034
|
external_exports.object({ status: external_exports.literal("pending") }),
|
|
19972
|
-
external_exports.object({ status: external_exports.literal("skipped") }),
|
|
19973
20035
|
external_exports.object({ status: external_exports.literal("expired") }),
|
|
19974
20036
|
/** `encryptedKey` is base64 RSA-OAEP (SHA-256) ciphertext of the pilot key. */
|
|
19975
20037
|
external_exports.object({ status: external_exports.literal("complete"), encryptedKey: external_exports.string(), name: external_exports.string() })
|
|
@@ -20036,15 +20098,17 @@ function quoteForCmd(arg) {
|
|
|
20036
20098
|
return /^[\w@%+=:,./\\-]+$/.test(arg) ? arg : `"${arg.replace(/"/g, '\\"')}"`;
|
|
20037
20099
|
}
|
|
20038
20100
|
function spawnPlan(cmd, args, platform = process.platform) {
|
|
20039
|
-
const
|
|
20040
|
-
|
|
20101
|
+
const isPath = cmd.includes("/") || cmd.includes("\\");
|
|
20102
|
+
const script = /\.(?:cmd|bat)$/i.test(cmd);
|
|
20103
|
+
const direct = platform !== "win32" || isPath && !script || WINDOWS_PROGRAMS.has(cmd.toLowerCase());
|
|
20104
|
+
return direct ? { command: cmd, args, shell: false } : { command: quoteForCmd(cmd), args: args.map(quoteForCmd), shell: true };
|
|
20041
20105
|
}
|
|
20042
20106
|
function run(cmd, args, opts = {}) {
|
|
20043
20107
|
return new Promise((resolve) => {
|
|
20044
20108
|
let child;
|
|
20045
20109
|
try {
|
|
20046
20110
|
const plan = spawnPlan(cmd, args);
|
|
20047
|
-
child = spawn(
|
|
20111
|
+
child = spawn(plan.command, plan.args, { cwd: opts.cwd, env: opts.env ?? process.env, stdio: ["pipe", "pipe", "pipe"], shell: plan.shell });
|
|
20048
20112
|
} catch (err) {
|
|
20049
20113
|
resolve({ code: 127, stdout: "", stderr: String(err) });
|
|
20050
20114
|
return;
|
|
@@ -20203,6 +20267,7 @@ var AURA_GITIGNORE = `# Aura local files: they can describe security weaknesses,
|
|
|
20203
20267
|
report.md
|
|
20204
20268
|
scan.json
|
|
20205
20269
|
history.json
|
|
20270
|
+
fixes.json
|
|
20206
20271
|
cache/
|
|
20207
20272
|
tasks/
|
|
20208
20273
|
`;
|
|
@@ -20267,6 +20332,12 @@ var ENV_SERVICES = [
|
|
|
20267
20332
|
[/^SLACK_/, "Slack"]
|
|
20268
20333
|
];
|
|
20269
20334
|
var BUILTIN_ENV = /* @__PURE__ */ new Set([
|
|
20335
|
+
"USERPROFILE",
|
|
20336
|
+
"APPDATA",
|
|
20337
|
+
"LOCALAPPDATA",
|
|
20338
|
+
"SYSTEMROOT",
|
|
20339
|
+
"COMSPEC",
|
|
20340
|
+
"PROGRAMFILES",
|
|
20270
20341
|
"NODE_ENV",
|
|
20271
20342
|
"CI",
|
|
20272
20343
|
"PORT",
|
|
@@ -20288,7 +20359,7 @@ var BUILTIN_ENV = /* @__PURE__ */ new Set([
|
|
|
20288
20359
|
"TMPDIR",
|
|
20289
20360
|
"HOSTNAME"
|
|
20290
20361
|
]);
|
|
20291
|
-
var ENV_REF = /process\.env\.([A-
|
|
20362
|
+
var ENV_REF = /process\.env\.([A-Za-z_][A-Za-z0-9_]*)|process\.env\[\s*['"]([A-Za-z_][A-Za-z0-9_]*)['"]\s*\]|import\.meta\.env\.([A-Za-z_][A-Za-z0-9_]*)|Deno\.env\.get\(\s*['"]([A-Za-z_][A-Za-z0-9_]*)['"]\s*\)/g;
|
|
20292
20363
|
function serviceForEnv(name) {
|
|
20293
20364
|
return ENV_SERVICES.find(([re]) => re.test(name))?.[1];
|
|
20294
20365
|
}
|
|
@@ -20320,7 +20391,7 @@ function findEnvRefs(files, read2) {
|
|
|
20320
20391
|
if (!text) continue;
|
|
20321
20392
|
for (const m of text.matchAll(ENV_REF)) {
|
|
20322
20393
|
const name = m[1] ?? m[2] ?? m[3] ?? m[4];
|
|
20323
|
-
if (!name || BUILTIN_ENV.has(name) || inStringLiteral(text, m.index)) continue;
|
|
20394
|
+
if (!name || /[a-z]/.test(name) || BUILTIN_ENV.has(name) || inStringLiteral(text, m.index)) continue;
|
|
20324
20395
|
if (!map2.has(name)) map2.set(name, /* @__PURE__ */ new Set());
|
|
20325
20396
|
map2.get(name).add(f);
|
|
20326
20397
|
}
|
|
@@ -20429,7 +20500,7 @@ function pmCommands(pm) {
|
|
|
20429
20500
|
// package.json
|
|
20430
20501
|
var package_default = {
|
|
20431
20502
|
name: "getaura",
|
|
20432
|
-
version: "0.1.
|
|
20503
|
+
version: "0.1.7",
|
|
20433
20504
|
description: "Aura helps you and your coding agent follow best practices while you vibe code.",
|
|
20434
20505
|
type: "module",
|
|
20435
20506
|
bin: {
|
|
@@ -20438,7 +20509,9 @@ var package_default = {
|
|
|
20438
20509
|
files: [
|
|
20439
20510
|
"dist",
|
|
20440
20511
|
"templates",
|
|
20441
|
-
"README.md"
|
|
20512
|
+
"README.md",
|
|
20513
|
+
".claude-plugin",
|
|
20514
|
+
"plugin"
|
|
20442
20515
|
],
|
|
20443
20516
|
engines: {
|
|
20444
20517
|
node: ">=22"
|
|
@@ -20553,7 +20626,7 @@ function templateVars(root, scan, pkg, extra = {}) {
|
|
|
20553
20626
|
scriptLine(pkg, cmds.run, "lint", "lint the code"),
|
|
20554
20627
|
scriptLine(pkg, cmds.run, "typecheck", "check types") ?? (scan.stack.typescript ? `- \`${cmds.exec} tsc --noEmit\`: check types` : null),
|
|
20555
20628
|
scriptLine(pkg, cmds.run, "build", "production build"),
|
|
20556
|
-
"- `aura scan`: check the Aura score"
|
|
20629
|
+
"- `aura scan`: check the Aura score (coding agents: `aura scan --json`, which never waits for input)"
|
|
20557
20630
|
].filter(Boolean);
|
|
20558
20631
|
const nodeMajor = pkg?.engines?.node?.match(/(\d{2})/)?.[1];
|
|
20559
20632
|
return {
|
|
@@ -20826,8 +20899,8 @@ function nextSteps(scan, opts = {}) {
|
|
|
20826
20899
|
}
|
|
20827
20900
|
|
|
20828
20901
|
// src/actions/generators.ts
|
|
20829
|
-
import { existsSync as existsSync7, readFileSync as
|
|
20830
|
-
import
|
|
20902
|
+
import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
|
|
20903
|
+
import path9 from "path";
|
|
20831
20904
|
|
|
20832
20905
|
// src/checks/agent-setup.ts
|
|
20833
20906
|
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync4 } from "fs";
|
|
@@ -20883,16 +20956,13 @@ function skillsIn(ctx, dir) {
|
|
|
20883
20956
|
}
|
|
20884
20957
|
function targetAgents(ctx) {
|
|
20885
20958
|
if (ctx.config.agents.length) return ctx.config.agents;
|
|
20886
|
-
|
|
20887
|
-
if (exists(ctx, "CLAUDE.md") || exists(ctx, ".claude")) seen.push("claude");
|
|
20888
|
-
if (exists(ctx, "AGENTS.md")) seen.push("codex");
|
|
20889
|
-
if (exists(ctx, ".cursor") || exists(ctx, ".cursorrules")) seen.push("cursor");
|
|
20890
|
-
if (exists(ctx, ".pi")) seen.push("pi");
|
|
20891
|
-
return seen;
|
|
20959
|
+
return detectAgentsFrom((rel) => exists(ctx, rel));
|
|
20892
20960
|
}
|
|
20893
20961
|
function rulesFileFor(ctx, agent) {
|
|
20894
20962
|
const preferred = AGENT_LAYOUT[agent].rulesFile;
|
|
20895
20963
|
if (exists(ctx, preferred)) return preferred;
|
|
20964
|
+
if (agent === "claude" && exists(ctx, ".claude/CLAUDE.md")) return ".claude/CLAUDE.md";
|
|
20965
|
+
if (agent === "pi" && exists(ctx, "CLAUDE.md")) return "CLAUDE.md";
|
|
20896
20966
|
if (agent === "cursor") {
|
|
20897
20967
|
if (exists(ctx, ".cursorrules")) return ".cursorrules";
|
|
20898
20968
|
try {
|
|
@@ -20973,7 +21043,7 @@ var skills = {
|
|
|
20973
21043
|
const agents = targetAgents(ctx);
|
|
20974
21044
|
const dirs = agents.length ? uniqueSkillDirs(agents) : ALL_SKILLS_DIRS;
|
|
20975
21045
|
const starterNames = new Set(STARTER_SKILLS.map((s) => s.name));
|
|
20976
|
-
const perDir = dirs.map((d) => ({ dir: d, skills: skillsIn(ctx, d).filter((s) => s !== "aura") }));
|
|
21046
|
+
const perDir = dirs.map((d) => ({ dir: d, skills: skillsIn(ctx, d).filter((s) => s !== "aura" && !s.startsWith("aura-")) }));
|
|
20977
21047
|
const best = Math.max(0, ...perDir.map((p2) => p2.skills.length));
|
|
20978
21048
|
const weakest = agents.length ? Math.min(...perDir.map((p2) => p2.skills.length)) : best;
|
|
20979
21049
|
if (weakest >= 3) return { status: "pass", summary: `${weakest}+ skills installed.` };
|
|
@@ -21023,13 +21093,13 @@ var brief = {
|
|
|
21023
21093
|
weight: 5,
|
|
21024
21094
|
run(ctx) {
|
|
21025
21095
|
const file2 = BRIEF_PATHS.find((f) => exists(ctx, f));
|
|
21026
|
-
const
|
|
21096
|
+
const words2 = file2 ? readDisk(ctx, file2).split(/\s+/).filter(Boolean).length : 0;
|
|
21027
21097
|
const committed = file2 && ctx.tracked ? ctx.tracked.has(file2) : true;
|
|
21028
|
-
if (file2 &&
|
|
21098
|
+
if (file2 && words2 >= 80 && committed) return { status: "pass", summary: `${file2} (${words2} words).` };
|
|
21029
21099
|
const finding = makeFinding(this, {
|
|
21030
21100
|
severity: "medium",
|
|
21031
21101
|
title: !file2 ? "No product brief" : !committed ? "Product brief isn\u2019t committed" : "Product brief is too short",
|
|
21032
|
-
detail: !file2 ? "There is no product brief describing what the app does, who it\u2019s for and what data it handles." : !committed ? `${file2} exists on your machine but isn\u2019t committed, so your agent in other sessions and other people can\u2019t see it.` : `${file2} has only ${
|
|
21102
|
+
detail: !file2 ? "There is no product brief describing what the app does, who it\u2019s for and what data it handles." : !committed ? `${file2} exists on your machine but isn\u2019t committed, so your agent in other sessions and other people can\u2019t see it.` : `${file2} has only ${words2} words.`,
|
|
21033
21103
|
why: "The brief grounds every agent session in what you\u2019re actually building, and becomes page one of any handoff to engineers.",
|
|
21034
21104
|
locations: file2 ? [{ file: file2 }] : [],
|
|
21035
21105
|
actionId: "add-brief",
|
|
@@ -21128,12 +21198,64 @@ function unreachableFiles(files, read2, pkgScripts) {
|
|
|
21128
21198
|
return source.filter((f) => !seen.has(f));
|
|
21129
21199
|
}
|
|
21130
21200
|
|
|
21131
|
-
// src/actions/agent-
|
|
21132
|
-
import {
|
|
21201
|
+
// src/actions/agent-commands.ts
|
|
21202
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
21133
21203
|
import path7 from "path";
|
|
21204
|
+
var AURA_COMMANDS = ["scan", "next", "fix", "setup"];
|
|
21205
|
+
function invocation(agent, command) {
|
|
21206
|
+
return agent === "claude" ? `/aura:${command}` : agent === "codex" ? `$aura-${command}` : `/aura-${command}`;
|
|
21207
|
+
}
|
|
21208
|
+
function pluginCommand(command) {
|
|
21209
|
+
const text = readFileSync5(path7.join(path7.dirname(templatesDir()), "plugin", "skills", command, "SKILL.md"), "utf8").replace(/\r\n/g, "\n");
|
|
21210
|
+
const [, frontmatter = "", body = ""] = text.match(/^---\n([\s\S]*?)\n---\n+([\s\S]*)$/) ?? [];
|
|
21211
|
+
const description = frontmatter.match(/^description: (.+)$/m)?.[1] ?? "";
|
|
21212
|
+
return { description, body };
|
|
21213
|
+
}
|
|
21214
|
+
function adapt(agent, body) {
|
|
21215
|
+
const args = agent === "pi" ? "Fix: $@\n\n" : "Fix the action or finding the user named after the command, if any.\n\n";
|
|
21216
|
+
return body.replace(/\/aura:(scan|next|fix|setup)\b/g, (_, c) => invocation(agent, c)).replace("Fix: $ARGUMENTS\n\n", args).replace("--agent claude", `--agent ${agent}`);
|
|
21217
|
+
}
|
|
21218
|
+
function commandChanges(agents) {
|
|
21219
|
+
const changes = [];
|
|
21220
|
+
for (const agent of new Set(agents)) {
|
|
21221
|
+
for (const command of AURA_COMMANDS) {
|
|
21222
|
+
const { description, body } = pluginCommand(command);
|
|
21223
|
+
const text = adapt(agent, body);
|
|
21224
|
+
if (agent === "cursor") changes.push({ path: `.cursor/commands/aura-${command}.md`, content: `# Aura: ${description}
|
|
21225
|
+
|
|
21226
|
+
${text}` });
|
|
21227
|
+
if (agent === "pi") changes.push({ path: `.pi/prompts/aura-${command}.md`, content: `---
|
|
21228
|
+
description: ${description}
|
|
21229
|
+
---
|
|
21230
|
+
|
|
21231
|
+
${text}` });
|
|
21232
|
+
if (agent === "codex") {
|
|
21233
|
+
const dir = `.agents/skills/aura-${command}`;
|
|
21234
|
+
changes.push({
|
|
21235
|
+
path: `${dir}/SKILL.md`,
|
|
21236
|
+
content: `---
|
|
21237
|
+
name: aura-${command}
|
|
21238
|
+
description: ${description} Only when the user runs ${invocation(agent, command)}.
|
|
21239
|
+
---
|
|
21240
|
+
|
|
21241
|
+
${text}`
|
|
21242
|
+
});
|
|
21243
|
+
changes.push({ path: `${dir}/agents/openai.yaml`, content: "policy:\n allow_implicit_invocation: false\n" });
|
|
21244
|
+
}
|
|
21245
|
+
}
|
|
21246
|
+
}
|
|
21247
|
+
return changes;
|
|
21248
|
+
}
|
|
21249
|
+
function commandList(agent) {
|
|
21250
|
+
return AURA_COMMANDS.map((c) => invocation(agent, c)).join(", ");
|
|
21251
|
+
}
|
|
21252
|
+
|
|
21253
|
+
// src/actions/agent-files.ts
|
|
21254
|
+
import { existsSync as existsSync6, readFileSync as readFileSync6 } from "fs";
|
|
21255
|
+
import path8 from "path";
|
|
21134
21256
|
function current(root, file2) {
|
|
21135
|
-
const full =
|
|
21136
|
-
return existsSync6(full) ?
|
|
21257
|
+
const full = path8.join(root, file2);
|
|
21258
|
+
return existsSync6(full) ? readFileSync6(full, "utf8") : null;
|
|
21137
21259
|
}
|
|
21138
21260
|
function upsertBlock(existing, body) {
|
|
21139
21261
|
const block = `${AURA_BLOCK_START}
|
|
@@ -21177,8 +21299,8 @@ function skillChanges(agents, names) {
|
|
|
21177
21299
|
|
|
21178
21300
|
// src/actions/generators.ts
|
|
21179
21301
|
function read(root, file2) {
|
|
21180
|
-
const full =
|
|
21181
|
-
return existsSync7(full) ?
|
|
21302
|
+
const full = path9.join(root, file2);
|
|
21303
|
+
return existsSync7(full) ? readFileSync7(full, "utf8") : null;
|
|
21182
21304
|
}
|
|
21183
21305
|
function withScripts(root, scripts, overwrite = false) {
|
|
21184
21306
|
const text = read(root, "package.json");
|
|
@@ -21199,7 +21321,7 @@ function findingMeta(scan, checkId, key) {
|
|
|
21199
21321
|
return scan.checks.find((c) => c.checkId === checkId)?.findings[0]?.meta?.[key];
|
|
21200
21322
|
}
|
|
21201
21323
|
var addBrief = ({ root }) => {
|
|
21202
|
-
const brief2 = BRIEF_PATHS.find((f) => existsSync7(
|
|
21324
|
+
const brief2 = BRIEF_PATHS.find((f) => existsSync7(path9.join(root, f)));
|
|
21203
21325
|
if (!brief2) return { skip: "There is no product brief yet. Run `aura init` to draft and confirm it first." };
|
|
21204
21326
|
const changes = [
|
|
21205
21327
|
{ path: brief2, content: read(root, brief2) },
|
|
@@ -21214,22 +21336,24 @@ var addAgentRules = ({ root, agents, vars }) => {
|
|
|
21214
21336
|
};
|
|
21215
21337
|
var installSkills = ({ agents, skills: skills2 }) => {
|
|
21216
21338
|
const names = [.../* @__PURE__ */ new Set([...skills2 ?? STARTER_SKILLS.map((s) => s.name), "aura"])];
|
|
21217
|
-
const changes = skillChanges(agents, names);
|
|
21218
21339
|
return {
|
|
21219
|
-
changes,
|
|
21220
|
-
details:
|
|
21340
|
+
changes: [...skillChanges(agents, names), ...commandChanges(agents)],
|
|
21341
|
+
details: [
|
|
21342
|
+
...names.map((n) => `- \`${n}\`: ${STARTER_SKILLS.find((s) => s.name === n)?.description ?? "check the Aura score and fix findings."}`),
|
|
21343
|
+
...agents.filter((a) => a !== "claude").map((a) => `- Aura's commands for ${AGENT_LABELS[a]}: ${commandList(a)}`)
|
|
21344
|
+
]
|
|
21221
21345
|
};
|
|
21222
21346
|
};
|
|
21223
21347
|
var tokenEfficiency = (g) => {
|
|
21224
21348
|
const changes = [];
|
|
21225
|
-
if (!existsSync7(
|
|
21349
|
+
if (!existsSync7(path9.join(g.root, "docs/index.md"))) changes.push({ path: "docs/index.md", content: render("docs/index.md", g.vars) });
|
|
21226
21350
|
changes.push(...rulesChanges(g.root, g.agents, g.vars));
|
|
21227
21351
|
return { changes, details: ["- `docs/index.md`: a map of the repo your agent reads first", "- Lean-context rules in the agent rules file"] };
|
|
21228
21352
|
};
|
|
21229
21353
|
var addCi = ({ root, ctx, vars }) => {
|
|
21230
21354
|
const existing = ctx.files.filter((f) => /^\.github\/workflows\//.test(f));
|
|
21231
21355
|
const file2 = existing.includes(".github/workflows/ci.yml") ? ".github/workflows/checks.yml" : ".github/workflows/ci.yml";
|
|
21232
|
-
if (existsSync7(
|
|
21356
|
+
if (existsSync7(path9.join(root, file2))) return { skip: `${file2} already exists.` };
|
|
21233
21357
|
return {
|
|
21234
21358
|
changes: [{ path: file2, content: render("github/workflows/ci.yml", vars) }],
|
|
21235
21359
|
details: [`- \`${file2}\`: lint, type check, tests and build on every pull request`]
|
|
@@ -21253,7 +21377,7 @@ var setupTesting = ({ root, ctx, vars }) => {
|
|
|
21253
21377
|
};
|
|
21254
21378
|
};
|
|
21255
21379
|
var addEnvExample = ({ root, scan }) => {
|
|
21256
|
-
const file2 = [".env.example", ".env.sample", ".env.template"].find((f) => existsSync7(
|
|
21380
|
+
const file2 = [".env.example", ".env.sample", ".env.template"].find((f) => existsSync7(path9.join(root, f))) ?? ".env.example";
|
|
21257
21381
|
const existing = read(root, file2);
|
|
21258
21382
|
const declared = new Set(
|
|
21259
21383
|
(existing ?? "").split(/\r?\n/).map((l) => l.match(/^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)\s*=/)?.[1]).filter(Boolean)
|
|
@@ -21310,7 +21434,7 @@ var addLinting = ({ root, ctx }) => {
|
|
|
21310
21434
|
return { changes, devDeps, details };
|
|
21311
21435
|
};
|
|
21312
21436
|
var addPreCommit = ({ root, ctx, vars }) => {
|
|
21313
|
-
if (existsSync7(
|
|
21437
|
+
if (existsSync7(path9.join(root, ".husky/pre-commit"))) return { skip: "A husky pre-commit hook already exists." };
|
|
21314
21438
|
const changes = [{ path: ".husky/pre-commit", content: render("configs/husky-pre-commit", vars) }];
|
|
21315
21439
|
const scripts = withScripts(root, { prepare: "husky" }, false);
|
|
21316
21440
|
if (scripts) changes.push(scripts);
|
|
@@ -21325,10 +21449,10 @@ var addReadme = ({ root, ctx, vars }) => {
|
|
|
21325
21449
|
const file2 = ctx.files.find((f) => /^readme\.mdx?$/i.test(f)) ?? "README.md";
|
|
21326
21450
|
const current2 = read(root, file2);
|
|
21327
21451
|
const boilerplate = current2 && /create-next-app|bootstrapped with/i.test(current2);
|
|
21328
|
-
const
|
|
21329
|
-
if (current2 && !boilerplate &&
|
|
21452
|
+
const words2 = current2?.split(/\s+/).filter(Boolean).length ?? 0;
|
|
21453
|
+
if (current2 && !boilerplate && words2 >= 80) return { skip: `${file2} already describes the app.` };
|
|
21330
21454
|
let content = render("readme/README.md", vars);
|
|
21331
|
-
if (current2 && !boilerplate &&
|
|
21455
|
+
if (current2 && !boilerplate && words2 > 0) content = `${content.trimEnd()}
|
|
21332
21456
|
|
|
21333
21457
|
## Notes
|
|
21334
21458
|
|
|
@@ -21340,7 +21464,7 @@ ${current2.trim()}
|
|
|
21340
21464
|
};
|
|
21341
21465
|
};
|
|
21342
21466
|
var enableDependencyUpdates = ({ root, vars }) => {
|
|
21343
|
-
if ([".github/dependabot.yml", ".github/dependabot.yaml", "renovate.json", ".github/renovate.json"].some((f) => existsSync7(
|
|
21467
|
+
if ([".github/dependabot.yml", ".github/dependabot.yaml", "renovate.json", ".github/renovate.json"].some((f) => existsSync7(path9.join(root, f)))) {
|
|
21344
21468
|
return { skip: "Dependabot or Renovate is already configured." };
|
|
21345
21469
|
}
|
|
21346
21470
|
return {
|
|
@@ -21420,7 +21544,7 @@ function planMoves(root, ctx, moves) {
|
|
|
21420
21544
|
const alias = aliasFor(toFile);
|
|
21421
21545
|
if (alias) return alias[0] + noExt.slice(alias[1].length);
|
|
21422
21546
|
}
|
|
21423
|
-
let rel =
|
|
21547
|
+
let rel = path9.posix.relative(path9.posix.dirname(fromFile), noExt);
|
|
21424
21548
|
if (!rel.startsWith(".")) rel = `./${rel}`;
|
|
21425
21549
|
return rel;
|
|
21426
21550
|
};
|
|
@@ -21454,7 +21578,7 @@ function planMoves(root, ctx, moves) {
|
|
|
21454
21578
|
}
|
|
21455
21579
|
var moveMisplacedFiles = ({ root, ctx, scan }) => {
|
|
21456
21580
|
const raw = findingMeta(scan, "structure.layout", "moves") ?? [];
|
|
21457
|
-
const moves = raw.map((m) => m.split("=>")).filter(([, to]) => !existsSync7(
|
|
21581
|
+
const moves = raw.map((m) => m.split("=>")).filter(([, to]) => !existsSync7(path9.join(root, to))).slice(0, MAX_MOVES);
|
|
21458
21582
|
if (moves.length === 0) return { skip: "No files to move (or their targets already exist)." };
|
|
21459
21583
|
return { changes: planMoves(root, ctx, moves), details: moves.map(([from, to]) => `- \`${from}\` \u2192 \`${to}\``), verifyTypes: true };
|
|
21460
21584
|
};
|
|
@@ -21545,7 +21669,7 @@ var AuraClient = class _AuraClient {
|
|
|
21545
21669
|
get hasKey() {
|
|
21546
21670
|
return Boolean(this.key);
|
|
21547
21671
|
}
|
|
21548
|
-
async request(method,
|
|
21672
|
+
async request(method, path25, body, timeoutMs = 6e4) {
|
|
21549
21673
|
if (!this.key) throw new AuraApiError("no_key", "No pilot key");
|
|
21550
21674
|
let payload;
|
|
21551
21675
|
if (body !== void 0) {
|
|
@@ -21555,7 +21679,7 @@ var AuraClient = class _AuraClient {
|
|
|
21555
21679
|
}
|
|
21556
21680
|
let res;
|
|
21557
21681
|
try {
|
|
21558
|
-
res = await fetch(`${this.baseUrl}/v1/${
|
|
21682
|
+
res = await fetch(`${this.baseUrl}/v1/${path25}`, {
|
|
21559
21683
|
method,
|
|
21560
21684
|
headers: { authorization: `Bearer ${this.key}`, "content-type": "application/json", "user-agent": `getaura/${AURA_VERSION}` },
|
|
21561
21685
|
body: payload,
|
|
@@ -21580,22 +21704,162 @@ var AuraClient = class _AuraClient {
|
|
|
21580
21704
|
|
|
21581
21705
|
// src/api/telemetry.ts
|
|
21582
21706
|
import { createHash as createHash2 } from "crypto";
|
|
21707
|
+
|
|
21708
|
+
// src/ui.ts
|
|
21709
|
+
import * as p from "@clack/prompts";
|
|
21710
|
+
import pc from "picocolors";
|
|
21711
|
+
function interactive() {
|
|
21712
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY && !inCI() && !runningAgent());
|
|
21713
|
+
}
|
|
21714
|
+
var UserCancelled = class extends Error {
|
|
21715
|
+
};
|
|
21716
|
+
function unwrap(value) {
|
|
21717
|
+
if (p.isCancel(value)) {
|
|
21718
|
+
p.cancel("Cancelled.");
|
|
21719
|
+
throw new UserCancelled();
|
|
21720
|
+
}
|
|
21721
|
+
return value;
|
|
21722
|
+
}
|
|
21723
|
+
function spin(enabled) {
|
|
21724
|
+
if (!enabled || !process.stdout.isTTY) {
|
|
21725
|
+
return { start: (_m) => {
|
|
21726
|
+
}, message: (_m) => {
|
|
21727
|
+
}, stop: (_m) => {
|
|
21728
|
+
}, error: (_m) => {
|
|
21729
|
+
} };
|
|
21730
|
+
}
|
|
21731
|
+
const s = p.spinner();
|
|
21732
|
+
return { start: (m) => s.start(m), message: (m) => s.message(m), stop: (m) => s.stop(m), error: (m) => s.error(m) };
|
|
21733
|
+
}
|
|
21734
|
+
function printJson(value) {
|
|
21735
|
+
process.stdout.write(JSON.stringify(value, null, 2) + "\n");
|
|
21736
|
+
}
|
|
21737
|
+
var say = {
|
|
21738
|
+
info: (m) => console.log(m),
|
|
21739
|
+
ok: (m) => console.log(`${pc.green("\u2714")} ${m}`),
|
|
21740
|
+
warn: (m) => console.error(`${pc.yellow("!")} ${m}`),
|
|
21741
|
+
error: (m) => console.error(`${pc.red("\u2716")} ${m}`),
|
|
21742
|
+
dim: (m) => console.log(pc.dim(m))
|
|
21743
|
+
};
|
|
21744
|
+
|
|
21745
|
+
// src/fix-tracking.ts
|
|
21746
|
+
import { existsSync as existsSync8, readFileSync as readFileSync8 } from "fs";
|
|
21747
|
+
var FILE = "fixes.json";
|
|
21748
|
+
var MAX_AGE_MS = 60 * 24 * 60 * 60 * 1e3;
|
|
21749
|
+
var MAX_CHECKS_PER_SCAN = 5;
|
|
21750
|
+
function loadTrackedFixes(root) {
|
|
21751
|
+
try {
|
|
21752
|
+
return existsSync8(auraPath(root, FILE)) ? JSON.parse(readFileSync8(auraPath(root, FILE), "utf8")) : [];
|
|
21753
|
+
} catch {
|
|
21754
|
+
return [];
|
|
21755
|
+
}
|
|
21756
|
+
}
|
|
21757
|
+
function save(root, fixes) {
|
|
21758
|
+
writeAuraFile(root, FILE, JSON.stringify(fixes, null, 2));
|
|
21759
|
+
}
|
|
21760
|
+
function trackFix(root, config2, fix) {
|
|
21761
|
+
if (!sharesUsage(config2)) return;
|
|
21762
|
+
save(root, [...loadTrackedFixes(root).filter((f) => f.branch !== fix.branch), { ...fix, openedAt: (/* @__PURE__ */ new Date()).toISOString() }]);
|
|
21763
|
+
}
|
|
21764
|
+
async function prState(root, branch) {
|
|
21765
|
+
const r = await run("gh", ["pr", "view", branch, "--json", "state,mergeCommit", "--jq", '[.state, .mergeCommit.oid // ""] | join(" ")'], {
|
|
21766
|
+
cwd: root,
|
|
21767
|
+
timeoutMs: 15e3
|
|
21768
|
+
});
|
|
21769
|
+
const [state, sha] = r.stdout.trim().split(" ");
|
|
21770
|
+
if (r.code !== 0) return null;
|
|
21771
|
+
if (state === "OPEN" || state === "CLOSED") return { state };
|
|
21772
|
+
if (state !== "MERGED") return null;
|
|
21773
|
+
const inCheckout = Boolean(sha) && (await run("git", ["merge-base", "--is-ancestor", sha, "HEAD"], { cwd: root })).code === 0;
|
|
21774
|
+
return { state, inCheckout };
|
|
21775
|
+
}
|
|
21776
|
+
async function followUpFixes(root, config2, scan, state = prState) {
|
|
21777
|
+
const tracked = loadTrackedFixes(root);
|
|
21778
|
+
if (tracked.length === 0 || !sharesUsage(config2)) return;
|
|
21779
|
+
const current2 = new Set(scan.checks.flatMap((c) => c.findings.map((f) => f.id)));
|
|
21780
|
+
const keep = [];
|
|
21781
|
+
for (const [i, fix] of tracked.entries()) {
|
|
21782
|
+
if (Date.now() - Date.parse(fix.openedAt) > MAX_AGE_MS) continue;
|
|
21783
|
+
const s = i < MAX_CHECKS_PER_SCAN ? await state(root, fix.branch) : null;
|
|
21784
|
+
if (s?.state === "MERGED") {
|
|
21785
|
+
if (!fix.merged) await recordUsage(root, config2, { event: "fix_result", actionId: fix.actionId, outcome: "merged" });
|
|
21786
|
+
if (!s.inCheckout) {
|
|
21787
|
+
keep.push({ ...fix, merged: true });
|
|
21788
|
+
continue;
|
|
21789
|
+
}
|
|
21790
|
+
const stillThere = fix.findingIds.some((id) => current2.has(id));
|
|
21791
|
+
await recordUsage(root, config2, {
|
|
21792
|
+
event: "fix_result",
|
|
21793
|
+
actionId: fix.actionId,
|
|
21794
|
+
outcome: stillThere ? "not_resolved" : "resolved",
|
|
21795
|
+
scoreChange: scan.score - fix.scoreBefore
|
|
21796
|
+
});
|
|
21797
|
+
} else if (s?.state === "CLOSED") {
|
|
21798
|
+
await recordUsage(root, config2, { event: "fix_result", actionId: fix.actionId, outcome: "closed" });
|
|
21799
|
+
} else keep.push(fix);
|
|
21800
|
+
}
|
|
21801
|
+
save(root, keep);
|
|
21802
|
+
}
|
|
21803
|
+
|
|
21804
|
+
// src/api/telemetry.ts
|
|
21805
|
+
function sharesUsage(config2, key = getApiKey()) {
|
|
21806
|
+
if (!key) return false;
|
|
21807
|
+
if (config2.shareUsage !== void 0) return config2.shareUsage;
|
|
21808
|
+
return key.startsWith("aura_live_") || config2.telemetry === true;
|
|
21809
|
+
}
|
|
21810
|
+
function runSource() {
|
|
21811
|
+
return inCI() ? "ci" : interactive() ? "terminal" : "agent";
|
|
21812
|
+
}
|
|
21813
|
+
function osName() {
|
|
21814
|
+
return process.platform === "darwin" || process.platform === "win32" || process.platform === "linux" ? process.platform : "other";
|
|
21815
|
+
}
|
|
21583
21816
|
async function repoId(root) {
|
|
21584
21817
|
const r = await run("git", ["rev-list", "--max-parents=0", "HEAD"], { cwd: root });
|
|
21585
21818
|
const seed = r.code === 0 ? r.stdout.trim().split("\n")[0] : root;
|
|
21586
21819
|
return createHash2("sha256").update(seed).digest("hex").slice(0, 24);
|
|
21587
21820
|
}
|
|
21588
|
-
async function
|
|
21589
|
-
if (!
|
|
21821
|
+
async function recordUsage(root, config2, event) {
|
|
21822
|
+
if (!sharesUsage(config2)) return;
|
|
21590
21823
|
try {
|
|
21591
|
-
|
|
21824
|
+
const body = {
|
|
21825
|
+
...event,
|
|
21826
|
+
auraVersion: AURA_VERSION,
|
|
21827
|
+
repoId: await repoId(root),
|
|
21828
|
+
source: runSource(),
|
|
21829
|
+
agent: runningAgent() ?? void 0,
|
|
21830
|
+
agents: config2.agents.length ? config2.agents : void 0,
|
|
21831
|
+
os: osName(),
|
|
21832
|
+
nodeVersion: process.version
|
|
21833
|
+
};
|
|
21834
|
+
await AuraClient.from(config2).request("POST", "telemetry", body, 5e3);
|
|
21592
21835
|
} catch {
|
|
21593
21836
|
}
|
|
21594
21837
|
}
|
|
21838
|
+
function errorKind(err) {
|
|
21839
|
+
const e = err;
|
|
21840
|
+
const kind = typeof e?.code === "string" ? e.code : typeof e?.name === "string" ? e.name : "Error";
|
|
21841
|
+
return /^[A-Za-z_][A-Za-z0-9_]{0,59}$/.test(kind) ? kind : "Error";
|
|
21842
|
+
}
|
|
21843
|
+
async function recordAction(root, config2, actionId, report, startedAt, via) {
|
|
21844
|
+
if (!report.outcome) return;
|
|
21845
|
+
await recordUsage(root, config2, {
|
|
21846
|
+
event: "action",
|
|
21847
|
+
actionId,
|
|
21848
|
+
outcome: report.outcome,
|
|
21849
|
+
delivery: report.delivery,
|
|
21850
|
+
via,
|
|
21851
|
+
durationMs: Date.now() - startedAt,
|
|
21852
|
+
agentSteps: report.agentSteps,
|
|
21853
|
+
errorType: report.errorType
|
|
21854
|
+
});
|
|
21855
|
+
if (report.outcome === "pr_opened" && report.branch) {
|
|
21856
|
+
trackFix(root, config2, { actionId, branch: report.branch, findingIds: report.findingIds ?? [], scoreBefore: report.scoreBefore ?? 0 });
|
|
21857
|
+
}
|
|
21858
|
+
}
|
|
21595
21859
|
|
|
21596
21860
|
// src/core/git.ts
|
|
21597
21861
|
import { mkdirSync as mkdirSync2, rmSync, writeFileSync as writeFileSync2 } from "fs";
|
|
21598
|
-
import
|
|
21862
|
+
import path10 from "path";
|
|
21599
21863
|
async function git(root, ...args) {
|
|
21600
21864
|
return run("git", args, { cwd: root });
|
|
21601
21865
|
}
|
|
@@ -21610,7 +21874,7 @@ async function ghStatus(root) {
|
|
|
21610
21874
|
const remote = await git(root, "remote", "get-url", "origin");
|
|
21611
21875
|
return { installed: true, authed: auth.code === 0, hasRemote: remote.code === 0 && /github\.com/.test(remote.stdout) };
|
|
21612
21876
|
}
|
|
21613
|
-
var AURA_OWNED = /^(?:\.aura\/|\.claude\/skills\/aura\/|\.agents\/skills\/aura\/)/;
|
|
21877
|
+
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$)/;
|
|
21614
21878
|
async function dirtyFiles(root) {
|
|
21615
21879
|
const r = await git(root, "status", "--porcelain", "-uall");
|
|
21616
21880
|
return r.stdout.split("\n").filter(Boolean).map((l) => l.slice(3).replace(/^"|"$/g, "").split(" -> ").pop()).filter((f) => !AURA_OWNED.test(f));
|
|
@@ -21629,11 +21893,11 @@ async function freeBranchName(root, base) {
|
|
|
21629
21893
|
}
|
|
21630
21894
|
function writeChanges(root, changes) {
|
|
21631
21895
|
for (const c of changes) {
|
|
21632
|
-
const full =
|
|
21896
|
+
const full = path10.join(root, c.path);
|
|
21633
21897
|
if (!isInside(root, full)) throw new Error(`Refusing to write outside the repo: ${c.path}`);
|
|
21634
21898
|
if (c.content === null) rmSync(full, { force: true });
|
|
21635
21899
|
else {
|
|
21636
|
-
mkdirSync2(
|
|
21900
|
+
mkdirSync2(path10.dirname(full), { recursive: true });
|
|
21637
21901
|
writeFileSync2(full, c.content);
|
|
21638
21902
|
}
|
|
21639
21903
|
}
|
|
@@ -21643,7 +21907,7 @@ async function commitPushPr(root, opts) {
|
|
|
21643
21907
|
const base = { branch: opts.branch, url: null };
|
|
21644
21908
|
if (commit.code !== 0) {
|
|
21645
21909
|
const out = commit.stderr || commit.stdout;
|
|
21646
|
-
const error62 = /tell me who you are|user\.email/i.test(out) ? 'git doesn\u2019t know your name and email yet. Run
|
|
21910
|
+
const error62 = /tell me who you are|user\.email/i.test(out) ? 'git doesn\u2019t know your name and email yet. Run these two commands:\n git config --global user.name "Your Name"\n git config --global user.email "you@example.com"' : out;
|
|
21647
21911
|
return { ...base, committed: false, pushed: false, error: error62 };
|
|
21648
21912
|
}
|
|
21649
21913
|
if (!opts.openPr) return { ...base, committed: true, pushed: false };
|
|
@@ -21747,13 +22011,13 @@ var readme = {
|
|
|
21747
22011
|
run(ctx) {
|
|
21748
22012
|
const file2 = ctx.files.find((f) => /^readme(\.md|\.mdx|\.txt)?$/i.test(f));
|
|
21749
22013
|
const text = file2 ? ctx.read(file2) ?? "" : "";
|
|
21750
|
-
const
|
|
21751
|
-
if (file2 && !BOILERPLATE.test(text) &&
|
|
22014
|
+
const words2 = text.split(/\s+/).filter(Boolean).length;
|
|
22015
|
+
if (file2 && !BOILERPLATE.test(text) && words2 >= 80) return { status: "pass", summary: `${file2} (${words2} words).` };
|
|
21752
22016
|
const boilerplate = Boolean(file2) && BOILERPLATE.test(text);
|
|
21753
22017
|
const finding = makeFinding(this, {
|
|
21754
22018
|
severity: "low",
|
|
21755
22019
|
title: !file2 ? "No README" : boilerplate ? "README is still the starter template" : "README is very short",
|
|
21756
|
-
detail: !file2 ? "There is no README describing what the app is, how to run it or how it is deployed." : boilerplate ? "The README is the default text from create-next-app and says nothing about this app." : `The README has only ${
|
|
22020
|
+
detail: !file2 ? "There is no README describing what the app is, how to run it or how it is deployed." : boilerplate ? "The README is the default text from create-next-app and says nothing about this app." : `The README has only ${words2} words, so it can't explain how to run or deploy the app.`,
|
|
21757
22021
|
why: "A good README is the first thing a new engineer or your coding agent reads; without it they guess.",
|
|
21758
22022
|
locations: file2 ? [{ file: file2 }] : [],
|
|
21759
22023
|
actionId: "add-readme",
|
|
@@ -21856,10 +22120,10 @@ var typecheck = {
|
|
|
21856
22120
|
applies: (ctx) => ctx.stack.typescript,
|
|
21857
22121
|
async run(ctx) {
|
|
21858
22122
|
if (ctx.options.fast) return { status: "unknown", summary: "Skipped in fast mode." };
|
|
21859
|
-
const { existsSync:
|
|
21860
|
-
const
|
|
21861
|
-
const tsc =
|
|
21862
|
-
if (!
|
|
22123
|
+
const { existsSync: existsSync18 } = await import("fs");
|
|
22124
|
+
const path25 = await import("path");
|
|
22125
|
+
const tsc = path25.join(ctx.root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
|
|
22126
|
+
if (!existsSync18(tsc)) return { status: "unknown", summary: "Dependencies not installed, so the type check was skipped." };
|
|
21863
22127
|
const r = await run(tsc, ["--noEmit", "--pretty", "false"], { cwd: ctx.root, timeoutMs: 18e4 });
|
|
21864
22128
|
if (r.timedOut) return { status: "unknown", summary: "Type check timed out." };
|
|
21865
22129
|
if (r.code === 0) return { status: "pass", summary: "No type errors.", engine: "tsc" };
|
|
@@ -21985,9 +22249,9 @@ var apiRoutes = {
|
|
|
21985
22249
|
|
|
21986
22250
|
// src/core/tools.ts
|
|
21987
22251
|
import { createHash as createHash3 } from "crypto";
|
|
21988
|
-
import { chmodSync as chmodSync2, existsSync as
|
|
22252
|
+
import { chmodSync as chmodSync2, existsSync as existsSync9, mkdirSync as mkdirSync3, mkdtempSync, readdirSync as readdirSync3, renameSync, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
21989
22253
|
import os2 from "os";
|
|
21990
|
-
import
|
|
22254
|
+
import path11 from "path";
|
|
21991
22255
|
var GITLEAKS_SHA = {
|
|
21992
22256
|
darwin_arm64: "b40ab0ae55c505963e365f271a8d3846efbc170aa17f2607f13df610a9aeb6a5",
|
|
21993
22257
|
darwin_x64: "dfe101a4db2255fc85120ac7f3d25e4342c3c20cf749f2c20a18081af1952709",
|
|
@@ -22004,13 +22268,16 @@ var OSV_SHA = {
|
|
|
22004
22268
|
windows_amd64: "e0ed7644118b717b028c249ee9d3515024e55e8510747ca08906eb96765354d6",
|
|
22005
22269
|
windows_arm64: "ca1379da0e408279ef2c85cde0846a903495738a91c7b065a208048437c1686c"
|
|
22006
22270
|
};
|
|
22271
|
+
function tarCommand(platform = process.platform, systemRoot = process.env.SystemRoot) {
|
|
22272
|
+
return platform === "win32" ? path11.win32.join(systemRoot || "C:\\Windows", "System32", "tar.exe") : "tar";
|
|
22273
|
+
}
|
|
22007
22274
|
var TOOLS = {
|
|
22008
22275
|
gitleaks: {
|
|
22009
22276
|
version: "8.30.1",
|
|
22010
22277
|
asset(platform, arch) {
|
|
22011
22278
|
const p2 = platform === "win32" ? "windows" : platform;
|
|
22012
|
-
const a = arch === "arm64" ? "arm64" : "x64";
|
|
22013
|
-
const sha = GITLEAKS_SHA[`${p2}_${a}`];
|
|
22279
|
+
const a = arch === "arm64" ? "arm64" : arch === "x64" ? "x64" : null;
|
|
22280
|
+
const sha = a && GITLEAKS_SHA[`${p2}_${a}`];
|
|
22014
22281
|
if (!sha) return null;
|
|
22015
22282
|
const archive = p2 === "windows" ? "zip" : "tar.gz";
|
|
22016
22283
|
return { file: `gitleaks_8.30.1_${p2}_${a}.${archive}`, sha256: sha, archive };
|
|
@@ -22021,8 +22288,8 @@ var TOOLS = {
|
|
|
22021
22288
|
version: "2.6.0",
|
|
22022
22289
|
asset(platform, arch) {
|
|
22023
22290
|
const p2 = platform === "win32" ? "windows" : platform;
|
|
22024
|
-
const a = arch === "arm64" ? "arm64" : "amd64";
|
|
22025
|
-
const sha = OSV_SHA[`${p2}_${a}`];
|
|
22291
|
+
const a = arch === "arm64" ? "arm64" : arch === "x64" ? "amd64" : null;
|
|
22292
|
+
const sha = a && OSV_SHA[`${p2}_${a}`];
|
|
22026
22293
|
if (!sha) return null;
|
|
22027
22294
|
return { file: `osv-scanner_${p2}_${a}${p2 === "windows" ? ".exe" : ""}`, sha256: sha, archive: null };
|
|
22028
22295
|
},
|
|
@@ -22038,8 +22305,8 @@ function ensureTool(name, noDownload = false) {
|
|
|
22038
22305
|
async function resolveTool(name, noDownload) {
|
|
22039
22306
|
const spec = TOOLS[name];
|
|
22040
22307
|
const exe = process.platform === "win32" ? `${name}.exe` : name;
|
|
22041
|
-
const cached2 =
|
|
22042
|
-
if (
|
|
22308
|
+
const cached2 = path11.join(auraBinDir(), `${name}-${spec.version}`, exe);
|
|
22309
|
+
if (existsSync9(cached2)) return { path: cached2, version: spec.version };
|
|
22043
22310
|
if (await commandExists(name)) {
|
|
22044
22311
|
const v = await run(name, ["version"]);
|
|
22045
22312
|
return { path: name, version: (v.stdout.match(/\d+\.\d+\.\d+/)?.[0] ?? "system").trim() };
|
|
@@ -22059,20 +22326,20 @@ async function download(name, spec, target) {
|
|
|
22059
22326
|
const buf = Buffer.from(await res.arrayBuffer());
|
|
22060
22327
|
const digest = createHash3("sha256").update(buf).digest("hex");
|
|
22061
22328
|
if (digest !== asset.sha256) throw new Error(`Checksum mismatch for ${asset.file}`);
|
|
22062
|
-
const dir =
|
|
22329
|
+
const dir = path11.dirname(target);
|
|
22063
22330
|
mkdirSync3(dir, { recursive: true });
|
|
22064
|
-
const tmp = mkdtempSync(
|
|
22331
|
+
const tmp = mkdtempSync(path11.join(os2.tmpdir(), `aura-${name}-`));
|
|
22065
22332
|
try {
|
|
22066
22333
|
if (asset.archive === null) {
|
|
22067
22334
|
writeFileSync3(target, buf);
|
|
22068
22335
|
} else {
|
|
22069
|
-
const archivePath =
|
|
22336
|
+
const archivePath = path11.join(tmp, asset.file);
|
|
22070
22337
|
writeFileSync3(archivePath, buf);
|
|
22071
|
-
const r = await run(
|
|
22338
|
+
const r = await run(tarCommand(), ["-xf", archivePath, "-C", tmp]);
|
|
22072
22339
|
if (r.code !== 0) return null;
|
|
22073
22340
|
const exe = readdirSync3(tmp).find((f) => f === name || f === `${name}.exe`);
|
|
22074
22341
|
if (!exe) return null;
|
|
22075
|
-
renameSync(
|
|
22342
|
+
renameSync(path11.join(tmp, exe), target);
|
|
22076
22343
|
}
|
|
22077
22344
|
chmodSync2(target, 493);
|
|
22078
22345
|
return { path: target, version: spec.version };
|
|
@@ -22343,9 +22610,9 @@ var rls = {
|
|
|
22343
22610
|
};
|
|
22344
22611
|
|
|
22345
22612
|
// src/checks/security/secrets.ts
|
|
22346
|
-
import { existsSync as
|
|
22613
|
+
import { existsSync as existsSync10, mkdtempSync as mkdtempSync2, readFileSync as readFileSync9, rmSync as rmSync3 } from "fs";
|
|
22347
22614
|
import os3 from "os";
|
|
22348
|
-
import
|
|
22615
|
+
import path12 from "path";
|
|
22349
22616
|
|
|
22350
22617
|
// src/core/ignore.ts
|
|
22351
22618
|
function globToRegex(glob) {
|
|
@@ -22383,15 +22650,15 @@ async function gitleaksHistory(ctx) {
|
|
|
22383
22650
|
if (!ctx.tracked) return null;
|
|
22384
22651
|
const bin = await ensureTool("gitleaks", ctx.options.noDownload);
|
|
22385
22652
|
if (!bin) return null;
|
|
22386
|
-
const dir = mkdtempSync2(
|
|
22387
|
-
const report =
|
|
22653
|
+
const dir = mkdtempSync2(path12.join(os3.tmpdir(), "aura-gitleaks-"));
|
|
22654
|
+
const report = path12.join(dir, "report.json");
|
|
22388
22655
|
try {
|
|
22389
22656
|
const r = await run(bin.path, ["git", "--no-banner", "--redact", "--report-format", "json", "--report-path", report, "--exit-code", "0", "."], {
|
|
22390
22657
|
cwd: ctx.root,
|
|
22391
22658
|
timeoutMs: 18e4
|
|
22392
22659
|
});
|
|
22393
|
-
if (r.code !== 0 || !
|
|
22394
|
-
return { hits: JSON.parse(
|
|
22660
|
+
if (r.code !== 0 || !existsSync10(report)) return null;
|
|
22661
|
+
return { hits: JSON.parse(readFileSync9(report, "utf8")), engine: `gitleaks ${bin.version}` };
|
|
22395
22662
|
} catch {
|
|
22396
22663
|
return null;
|
|
22397
22664
|
} finally {
|
|
@@ -22506,7 +22773,7 @@ var envFiles = {
|
|
|
22506
22773
|
var securityChecks = [secrets, envFiles, deps, rls, apiRoutes, headers];
|
|
22507
22774
|
|
|
22508
22775
|
// src/checks/structure.ts
|
|
22509
|
-
import
|
|
22776
|
+
import path13 from "path";
|
|
22510
22777
|
var GENERATED = /(?:^|\/)(?:supabase\/migrations|generated|__generated__|\.generated)\/|\.(?:d\.ts|min\.js)$|(?:^|\/)(?:database|supabase)\.types\.ts$|(?:^|\/)components\/ui\//;
|
|
22511
22778
|
function appSource(ctx) {
|
|
22512
22779
|
return ctx.files.filter((f) => isSourceFile(f) && !GENERATED.test(f));
|
|
@@ -22595,7 +22862,7 @@ var duplicates = {
|
|
|
22595
22862
|
const findings = pairs.map(
|
|
22596
22863
|
([a, b, sim]) => makeFinding(this, {
|
|
22597
22864
|
severity: "low",
|
|
22598
|
-
title: `${
|
|
22865
|
+
title: `${path13.basename(a)} and ${path13.basename(b)} are near-duplicates`,
|
|
22599
22866
|
detail: `${a} and ${b} are ${Math.round(sim * 100)}% the same.`,
|
|
22600
22867
|
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.",
|
|
22601
22868
|
locations: [{ file: a }, { file: b }],
|
|
@@ -22609,7 +22876,7 @@ var duplicates = {
|
|
|
22609
22876
|
};
|
|
22610
22877
|
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)$/;
|
|
22611
22878
|
function suggestTarget(file2, srcDir) {
|
|
22612
|
-
const base =
|
|
22879
|
+
const base = path13.posix.basename(file2);
|
|
22613
22880
|
const prefix = srcDir ? "src/" : "";
|
|
22614
22881
|
const isComponent = /^[A-Z][A-Za-z0-9]*\.(?:tsx|jsx)$/.test(base);
|
|
22615
22882
|
if (isComponent) return `${prefix}components/${base}`;
|
|
@@ -22721,59 +22988,22 @@ async function runScan(root, options, onProgress) {
|
|
|
22721
22988
|
};
|
|
22722
22989
|
}
|
|
22723
22990
|
|
|
22724
|
-
// src/ui.ts
|
|
22725
|
-
import * as p from "@clack/prompts";
|
|
22726
|
-
import pc from "picocolors";
|
|
22727
|
-
function interactive() {
|
|
22728
|
-
return Boolean(process.stdin.isTTY && process.stdout.isTTY && !process.env.CI);
|
|
22729
|
-
}
|
|
22730
|
-
var UserCancelled = class extends Error {
|
|
22731
|
-
};
|
|
22732
|
-
function unwrap(value) {
|
|
22733
|
-
if (p.isCancel(value)) {
|
|
22734
|
-
p.cancel("Cancelled.");
|
|
22735
|
-
throw new UserCancelled();
|
|
22736
|
-
}
|
|
22737
|
-
return value;
|
|
22738
|
-
}
|
|
22739
|
-
function spin(enabled) {
|
|
22740
|
-
if (!enabled || !process.stdout.isTTY) {
|
|
22741
|
-
return { start: (_m) => {
|
|
22742
|
-
}, message: (_m) => {
|
|
22743
|
-
}, stop: (_m) => {
|
|
22744
|
-
}, error: (_m) => {
|
|
22745
|
-
} };
|
|
22746
|
-
}
|
|
22747
|
-
const s = p.spinner();
|
|
22748
|
-
return { start: (m) => s.start(m), message: (m) => s.message(m), stop: (m) => s.stop(m), error: (m) => s.error(m) };
|
|
22749
|
-
}
|
|
22750
|
-
function printJson(value) {
|
|
22751
|
-
process.stdout.write(JSON.stringify(value, null, 2) + "\n");
|
|
22752
|
-
}
|
|
22753
|
-
var say = {
|
|
22754
|
-
info: (m) => console.log(m),
|
|
22755
|
-
ok: (m) => console.log(`${pc.green("\u2714")} ${m}`),
|
|
22756
|
-
warn: (m) => console.error(`${pc.yellow("!")} ${m}`),
|
|
22757
|
-
error: (m) => console.error(`${pc.red("\u2716")} ${m}`),
|
|
22758
|
-
dim: (m) => console.log(pc.dim(m))
|
|
22759
|
-
};
|
|
22760
|
-
|
|
22761
22991
|
// src/commands/fix.ts
|
|
22762
|
-
import { existsSync as
|
|
22763
|
-
import
|
|
22992
|
+
import { existsSync as existsSync15, readFileSync as readFileSync16, writeFileSync as writeFileSync6 } from "fs";
|
|
22993
|
+
import path17 from "path";
|
|
22764
22994
|
|
|
22765
22995
|
// src/agent/tools.ts
|
|
22766
|
-
import { existsSync as
|
|
22767
|
-
import
|
|
22996
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync10, rmSync as rmSync4, statSync as statSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
22997
|
+
import path14 from "path";
|
|
22768
22998
|
var MAX_OUTPUT = 12e3;
|
|
22769
22999
|
var MAX_FILE_LINES = 800;
|
|
22770
23000
|
var FORBIDDEN = /^(?:\.git|node_modules|\.next|\.vercel)(?:\/|$)|(?:^|\/)\.env(?:\.(?!example$|sample$|template$)[^/]+)?$/;
|
|
22771
23001
|
function safePath(root, rel) {
|
|
22772
23002
|
if (typeof rel !== "string" || !rel.trim()) throw new Error("A file path is required.");
|
|
22773
|
-
const normalized =
|
|
22774
|
-
if (normalized.startsWith("../") || normalized === ".." ||
|
|
23003
|
+
const normalized = path14.posix.normalize(rel.replace(/\\/g, "/").replace(/^\.\//, ""));
|
|
23004
|
+
if (normalized.startsWith("../") || normalized === ".." || path14.isAbsolute(normalized)) throw new Error(`Path is outside the repo: ${rel}`);
|
|
22775
23005
|
if (FORBIDDEN.test(normalized)) throw new Error(`Aura doesn\u2019t let the agent touch ${normalized}.`);
|
|
22776
|
-
const full =
|
|
23006
|
+
const full = path14.join(root, normalized);
|
|
22777
23007
|
if (!isInside(root, full)) throw new Error(`Path is outside the repo: ${rel}`);
|
|
22778
23008
|
return normalized;
|
|
22779
23009
|
}
|
|
@@ -22794,10 +23024,10 @@ async function runTool(root, call, allowedCommands2) {
|
|
|
22794
23024
|
}
|
|
22795
23025
|
case "read_file": {
|
|
22796
23026
|
const rel = safePath(root, input2.path);
|
|
22797
|
-
const full =
|
|
22798
|
-
if (!
|
|
23027
|
+
const full = path14.join(root, rel);
|
|
23028
|
+
if (!existsSync11(full)) return { output: `File not found: ${rel}` };
|
|
22799
23029
|
if (statSync2(full).size > 1e6) return { output: `${rel} is too large to read.` };
|
|
22800
|
-
const lines =
|
|
23030
|
+
const lines = readFileSync10(full, "utf8").split("\n");
|
|
22801
23031
|
const start = Math.max(1, Number(input2.startLine) || 1);
|
|
22802
23032
|
const end = Math.min(lines.length, Number(input2.endLine) || start + MAX_FILE_LINES - 1);
|
|
22803
23033
|
const body = lines.slice(start - 1, end).map((l, i) => `${start + i}: ${l}`).join("\n");
|
|
@@ -22814,7 +23044,7 @@ async function runTool(root, call, allowedCommands2) {
|
|
|
22814
23044
|
if (FORBIDDEN.test(f) || glob && !glob.test(f)) continue;
|
|
22815
23045
|
let text;
|
|
22816
23046
|
try {
|
|
22817
|
-
text =
|
|
23047
|
+
text = readFileSync10(path14.join(root, f), "utf8");
|
|
22818
23048
|
} catch {
|
|
22819
23049
|
continue;
|
|
22820
23050
|
}
|
|
@@ -22833,14 +23063,14 @@ async function runTool(root, call, allowedCommands2) {
|
|
|
22833
23063
|
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."
|
|
22834
23064
|
};
|
|
22835
23065
|
}
|
|
22836
|
-
mkdirSync4(
|
|
22837
|
-
writeFileSync4(
|
|
23066
|
+
mkdirSync4(path14.dirname(path14.join(root, rel)), { recursive: true });
|
|
23067
|
+
writeFileSync4(path14.join(root, rel), content);
|
|
22838
23068
|
return { output: `Wrote ${rel} (${content.split("\n").length} lines).`, touched: rel };
|
|
22839
23069
|
}
|
|
22840
23070
|
case "delete_file": {
|
|
22841
23071
|
const rel = safePath(root, input2.path);
|
|
22842
|
-
if (!
|
|
22843
|
-
rmSync4(
|
|
23072
|
+
if (!existsSync11(path14.join(root, rel))) return { output: `File not found: ${rel}` };
|
|
23073
|
+
rmSync4(path14.join(root, rel));
|
|
22844
23074
|
return { output: `Deleted ${rel}.`, touched: rel };
|
|
22845
23075
|
}
|
|
22846
23076
|
case "run_command": {
|
|
@@ -22862,7 +23092,7 @@ ${(r.stdout + "\n" + r.stderr).trim().slice(-6e3)}`;
|
|
|
22862
23092
|
|
|
22863
23093
|
// src/api/explain.ts
|
|
22864
23094
|
import { createHash as createHash4 } from "crypto";
|
|
22865
|
-
import { existsSync as
|
|
23095
|
+
import { existsSync as existsSync12, readFileSync as readFileSync11 } from "fs";
|
|
22866
23096
|
|
|
22867
23097
|
// src/report.ts
|
|
22868
23098
|
import pc2 from "picocolors";
|
|
@@ -23051,7 +23281,7 @@ var CACHE_FILE = "cache/explanations.json";
|
|
|
23051
23281
|
var MAX_EXPLAIN = 25;
|
|
23052
23282
|
function loadCache(root) {
|
|
23053
23283
|
try {
|
|
23054
|
-
return JSON.parse(
|
|
23284
|
+
return JSON.parse(readFileSync11(auraPath(root, CACHE_FILE), "utf8"));
|
|
23055
23285
|
} catch {
|
|
23056
23286
|
return {};
|
|
23057
23287
|
}
|
|
@@ -23072,8 +23302,8 @@ function excerptFor(root, f, context = 4) {
|
|
|
23072
23302
|
}
|
|
23073
23303
|
function productContext(root) {
|
|
23074
23304
|
const file2 = auraPath(root, "brief.md");
|
|
23075
|
-
if (!
|
|
23076
|
-
return redact(
|
|
23305
|
+
if (!existsSync12(file2)) return void 0;
|
|
23306
|
+
return redact(readFileSync11(file2, "utf8")).slice(0, 1800);
|
|
23077
23307
|
}
|
|
23078
23308
|
async function addExplanations(root, scan, client) {
|
|
23079
23309
|
const cache = loadCache(root);
|
|
@@ -23117,19 +23347,19 @@ async function addExplanations(root, scan, client) {
|
|
|
23117
23347
|
|
|
23118
23348
|
// src/commands/simple.ts
|
|
23119
23349
|
import { execFileSync } from "child_process";
|
|
23120
|
-
import { existsSync as
|
|
23121
|
-
import
|
|
23350
|
+
import { existsSync as existsSync14, readdirSync as readdirSync4, readFileSync as readFileSync15 } from "fs";
|
|
23351
|
+
import path16 from "path";
|
|
23122
23352
|
import YAML2 from "yaml";
|
|
23123
23353
|
|
|
23124
23354
|
// src/commands/inventory.ts
|
|
23125
|
-
import { readFileSync as
|
|
23355
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
23126
23356
|
function accountState(root, services) {
|
|
23127
23357
|
const inv = loadInventory(root);
|
|
23128
23358
|
return { inventoryComplete: services.every((s) => inv.services[s]?.owner) && Boolean(inv.githubOrg), githubSecurity: Boolean(inv.githubSecurity) };
|
|
23129
23359
|
}
|
|
23130
23360
|
function loadInventory(root) {
|
|
23131
23361
|
try {
|
|
23132
|
-
return JSON.parse(
|
|
23362
|
+
return JSON.parse(readFileSync12(auraPath(root, "inventory.json"), "utf8"));
|
|
23133
23363
|
} catch {
|
|
23134
23364
|
return { version: 1, updatedAt: (/* @__PURE__ */ new Date()).toISOString(), services: {} };
|
|
23135
23365
|
}
|
|
@@ -23216,10 +23446,10 @@ async function inventoryCommand(flags) {
|
|
|
23216
23446
|
}
|
|
23217
23447
|
|
|
23218
23448
|
// src/history.ts
|
|
23219
|
-
import { readFileSync as
|
|
23449
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
23220
23450
|
function loadHistory(root) {
|
|
23221
23451
|
try {
|
|
23222
|
-
const data = JSON.parse(
|
|
23452
|
+
const data = JSON.parse(readFileSync13(auraPath(root, "history.json"), "utf8"));
|
|
23223
23453
|
return Array.isArray(data) ? data : [];
|
|
23224
23454
|
} catch {
|
|
23225
23455
|
return [];
|
|
@@ -23267,10 +23497,10 @@ function banner(colors = pc3.isColorSupported) {
|
|
|
23267
23497
|
// src/api/connect.ts
|
|
23268
23498
|
import { spawn as spawn2 } from "child_process";
|
|
23269
23499
|
import { constants, generateKeyPairSync, privateDecrypt } from "crypto";
|
|
23270
|
-
import { existsSync as
|
|
23500
|
+
import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync14, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
23271
23501
|
import os4 from "os";
|
|
23272
|
-
import
|
|
23273
|
-
var pendingFile = () =>
|
|
23502
|
+
import path15 from "path";
|
|
23503
|
+
var pendingFile = () => path15.join(os4.homedir(), ".aura", "pending-connect.json");
|
|
23274
23504
|
async function postJson(apiUrl, route, body) {
|
|
23275
23505
|
let res;
|
|
23276
23506
|
try {
|
|
@@ -23289,14 +23519,14 @@ async function postJson(apiUrl, route, body) {
|
|
|
23289
23519
|
}
|
|
23290
23520
|
function loadPending(apiUrl, now) {
|
|
23291
23521
|
try {
|
|
23292
|
-
const p2 = JSON.parse(
|
|
23522
|
+
const p2 = JSON.parse(readFileSync14(pendingFile(), "utf8"));
|
|
23293
23523
|
if (p2.apiUrl === apiUrl && Date.parse(p2.expiresAt) - now > 6e4) return p2;
|
|
23294
23524
|
} catch {
|
|
23295
23525
|
}
|
|
23296
23526
|
return null;
|
|
23297
23527
|
}
|
|
23298
23528
|
function clearPendingConnect() {
|
|
23299
|
-
if (
|
|
23529
|
+
if (existsSync13(pendingFile())) rmSync5(pendingFile(), { force: true });
|
|
23300
23530
|
}
|
|
23301
23531
|
async function startOrResumeConnect(apiUrl, now = Date.now()) {
|
|
23302
23532
|
const existing = loadPending(apiUrl, now);
|
|
@@ -23308,7 +23538,7 @@ async function startOrResumeConnect(apiUrl, now = Date.now()) {
|
|
|
23308
23538
|
});
|
|
23309
23539
|
const res = ConnectStartResponse.parse(await postJson(apiUrl, "connect", { publicKey }));
|
|
23310
23540
|
const pending = { apiUrl, privateKey, ...res };
|
|
23311
|
-
mkdirSync5(
|
|
23541
|
+
mkdirSync5(path15.dirname(pendingFile()), { recursive: true });
|
|
23312
23542
|
writeFileSync5(pendingFile(), JSON.stringify(pending, null, 2) + "\n", { mode: 384 });
|
|
23313
23543
|
return { ...pending, resumed: false };
|
|
23314
23544
|
}
|
|
@@ -23333,7 +23563,7 @@ async function waitForConnect(pending, waitSeconds, sleep = (ms) => new Promise(
|
|
|
23333
23563
|
}
|
|
23334
23564
|
}
|
|
23335
23565
|
function openInBrowser(url2) {
|
|
23336
|
-
if (process.env.AURA_NO_BROWSER ||
|
|
23566
|
+
if (process.env.AURA_NO_BROWSER || inCI()) return;
|
|
23337
23567
|
const [cmd, args] = process.platform === "darwin" ? ["open", [url2]] : process.platform === "win32" ? ["cmd", ["/c", "start", "", url2]] : ["xdg-open", [url2]];
|
|
23338
23568
|
try {
|
|
23339
23569
|
const child = spawn2(cmd, args, { stdio: "ignore", detached: true });
|
|
@@ -23362,7 +23592,7 @@ ${pc.magenta("\u2726")} ${pc.bold("Connect your Aura pilot key")}`);
|
|
|
23362
23592
|
console.log(pc.dim(` Check the page shows the code ${pending.code}. Press Ctrl+C to stop.
|
|
23363
23593
|
`));
|
|
23364
23594
|
} else {
|
|
23365
|
-
console.log(
|
|
23595
|
+
console.log(" Ask the user to open this link and paste their pilot key there:");
|
|
23366
23596
|
console.log(` ${pending.url}`);
|
|
23367
23597
|
console.log(` Don\u2019t ask for the key in chat. The page sends it to this computer encrypted. Code: ${pending.code}`);
|
|
23368
23598
|
console.log(` Waiting up to ${Math.round(wait)} seconds\u2026
|
|
@@ -23371,7 +23601,7 @@ ${pc.magenta("\u2726")} ${pc.bold("Connect your Aura pilot key")}`);
|
|
|
23371
23601
|
const s = spin(opts.person);
|
|
23372
23602
|
s.start("Waiting for you to finish in the browser");
|
|
23373
23603
|
const outcome = await waitForConnect(pending, wait);
|
|
23374
|
-
s.stop(outcome.status === "complete" ? "Key received" :
|
|
23604
|
+
s.stop(outcome.status === "complete" ? "Key received" : "Stopped waiting");
|
|
23375
23605
|
return outcome;
|
|
23376
23606
|
} catch (err) {
|
|
23377
23607
|
return { status: "error", message: friendlyApiError(err) };
|
|
@@ -23379,18 +23609,15 @@ ${pc.magenta("\u2726")} ${pc.bold("Connect your Aura pilot key")}`);
|
|
|
23379
23609
|
}
|
|
23380
23610
|
function explainSignIn(outcome) {
|
|
23381
23611
|
switch (outcome.status) {
|
|
23382
|
-
case "skipped":
|
|
23383
|
-
say.dim("No pilot key: Aura will still score your repo, with standard explanations. Add one later with `aura login`.");
|
|
23384
|
-
return true;
|
|
23385
23612
|
case "pending":
|
|
23386
23613
|
say.warn("Still waiting for the user to finish in the browser. When they have, run the same command again: it carries on with the same link.");
|
|
23387
|
-
|
|
23614
|
+
break;
|
|
23388
23615
|
case "expired":
|
|
23389
23616
|
say.warn("The sign-in link expired. Run the same command again for a new one.");
|
|
23390
|
-
|
|
23617
|
+
break;
|
|
23391
23618
|
case "error":
|
|
23392
23619
|
say.warn(`Couldn\u2019t start sign-in: ${outcome.message}`);
|
|
23393
|
-
|
|
23620
|
+
break;
|
|
23394
23621
|
}
|
|
23395
23622
|
}
|
|
23396
23623
|
|
|
@@ -23430,7 +23657,17 @@ async function pickAndRunSteps(steps, limit = 10) {
|
|
|
23430
23657
|
console.log(`
|
|
23431
23658
|
${pc.magenta("\u2726")} ${pc.bold(step.title)}`);
|
|
23432
23659
|
const run2 = stepRun(step, hasKey);
|
|
23433
|
-
const code = run2.kind === "apply" ? await applyCommand(run2.actionId, { yes: true }) : run2.kind === "fix" ? await fixCommand(run2.actionId, run2.withOwnAgent ? { agent: true } : { yes: true }) : run2.kind === "guide" ? await guideCommand(run2.topic, {}) : await inventoryCommand({});
|
|
23660
|
+
const code = run2.kind === "apply" ? await applyCommand(run2.actionId, { yes: true, via: "checklist" }) : run2.kind === "fix" ? await fixCommand(run2.actionId, run2.withOwnAgent ? { agent: true, via: "checklist" } : { yes: true, via: "checklist" }) : run2.kind === "guide" ? await guideCommand(run2.topic, {}) : await inventoryCommand({});
|
|
23661
|
+
if (run2.kind === "guide" || run2.kind === "inventory") {
|
|
23662
|
+
const root = await findRepoRoot();
|
|
23663
|
+
await recordUsage(root, loadConfig(root), {
|
|
23664
|
+
event: "action",
|
|
23665
|
+
actionId: step.actionId,
|
|
23666
|
+
delivery: run2.kind === "guide" ? "guide" : "checklist",
|
|
23667
|
+
via: "checklist",
|
|
23668
|
+
outcome: "shown"
|
|
23669
|
+
});
|
|
23670
|
+
}
|
|
23434
23671
|
results.push({ title: step.title, ok: code === 0 });
|
|
23435
23672
|
}
|
|
23436
23673
|
if (results.length > 1) {
|
|
@@ -23444,9 +23681,9 @@ ${pc.bold(" Done")}`);
|
|
|
23444
23681
|
// src/commands/simple.ts
|
|
23445
23682
|
async function latestScan(root) {
|
|
23446
23683
|
const file2 = auraPath(root, "scan.json");
|
|
23447
|
-
if (
|
|
23684
|
+
if (existsSync14(file2)) {
|
|
23448
23685
|
try {
|
|
23449
|
-
const scan = JSON.parse(
|
|
23686
|
+
const scan = JSON.parse(readFileSync15(file2, "utf8"));
|
|
23450
23687
|
const head = execFileSync("git", ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
23451
23688
|
const dirty = execFileSync("git", ["status", "--porcelain"], { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
|
|
23452
23689
|
if (scan.commit === head && !dirty && Date.now() - Date.parse(scan.scannedAt) < 24 * 36e5) return scan;
|
|
@@ -23481,14 +23718,14 @@ async function loginCommand(flags) {
|
|
|
23481
23718
|
let key = flags.key ?? process.env.AURA_KEY;
|
|
23482
23719
|
if (!key && flags.paste && person) key = unwrap(await p.password({ message: "Paste your Aura pilot key", mask: "\u2022" }));
|
|
23483
23720
|
if (!key) {
|
|
23484
|
-
if (
|
|
23721
|
+
if (inCI()) {
|
|
23485
23722
|
say.error("Pass your key with --key or set AURA_KEY.");
|
|
23486
23723
|
return 1;
|
|
23487
23724
|
}
|
|
23488
23725
|
const outcome = await signInWithBrowser(apiUrl, { person, waitSeconds: parseWait(flags.wait) });
|
|
23489
23726
|
if (outcome.status !== "complete") {
|
|
23490
23727
|
explainSignIn(outcome);
|
|
23491
|
-
return
|
|
23728
|
+
return 1;
|
|
23492
23729
|
}
|
|
23493
23730
|
key = outcome.key;
|
|
23494
23731
|
}
|
|
@@ -23534,8 +23771,8 @@ async function configCommand(key, value) {
|
|
|
23534
23771
|
const bool = (v) => ["on", "true", "yes", "1"].includes(v.toLowerCase());
|
|
23535
23772
|
switch (key) {
|
|
23536
23773
|
case "telemetry":
|
|
23537
|
-
if (value === void 0) return say.info(config2
|
|
23538
|
-
config2.
|
|
23774
|
+
if (value === void 0) return say.info(sharesUsage(config2) ? "on" : "off"), 0;
|
|
23775
|
+
config2.shareUsage = bool(value);
|
|
23539
23776
|
break;
|
|
23540
23777
|
case "ciMaxDrop":
|
|
23541
23778
|
case "ci-max-drop":
|
|
@@ -23561,9 +23798,9 @@ async function configCommand(key, value) {
|
|
|
23561
23798
|
return 0;
|
|
23562
23799
|
}
|
|
23563
23800
|
function loadGuides() {
|
|
23564
|
-
const dir =
|
|
23801
|
+
const dir = path16.join(templatesDir(), "guides");
|
|
23565
23802
|
return readdirSync4(dir).filter((f) => f.endsWith(".md")).map((f) => {
|
|
23566
|
-
const text =
|
|
23803
|
+
const text = readFileSync15(path16.join(dir, f), "utf8");
|
|
23567
23804
|
const fm = text.match(/^---\n([\s\S]*?)\n---\n?/);
|
|
23568
23805
|
const meta3 = fm ? YAML2.parse(fm[1]) : {};
|
|
23569
23806
|
return { id: f.replace(/\.md$/, ""), title: meta3.title ?? f, summary: meta3.summary ?? "", body: fm ? text.slice(fm[0].length) : text };
|
|
@@ -23631,7 +23868,7 @@ function allowedCommands(root, pm, typescript) {
|
|
|
23631
23868
|
const pkg = readJson(root, "package.json");
|
|
23632
23869
|
const run2 = pmCommands(pm).run;
|
|
23633
23870
|
const cmds = ["test", "lint", "typecheck", "build"].filter((s) => pkg?.scripts?.[s]).map((s) => `${run2} ${s}`);
|
|
23634
|
-
if (typescript && !pkg?.scripts?.typecheck &&
|
|
23871
|
+
if (typescript && !pkg?.scripts?.typecheck && existsSync15(path17.join(root, "node_modules/.bin/tsc"))) cmds.push(`${pmCommands(pm).exec} tsc --noEmit`);
|
|
23635
23872
|
return cmds;
|
|
23636
23873
|
}
|
|
23637
23874
|
function taskFor(root, scan, action2, findings) {
|
|
@@ -23682,8 +23919,8 @@ async function untrackEnvFiles(root, findings) {
|
|
|
23682
23919
|
const files = findings.filter((f) => f.checkId === "security.env-files").map((f) => f.locations[0].file);
|
|
23683
23920
|
for (const f of files) await git(root, "rm", "-q", "--cached", "--", f);
|
|
23684
23921
|
if (files.length) {
|
|
23685
|
-
const gi =
|
|
23686
|
-
const current2 =
|
|
23922
|
+
const gi = path17.join(root, ".gitignore");
|
|
23923
|
+
const current2 = existsSync15(gi) ? readFileSync16(gi, "utf8") : "";
|
|
23687
23924
|
if (!/^\.env\*?$/m.test(current2)) writeFileSync6(gi, `${current2.trimEnd()}
|
|
23688
23925
|
|
|
23689
23926
|
# Environment files hold secrets
|
|
@@ -23695,6 +23932,15 @@ async function untrackEnvFiles(root, findings) {
|
|
|
23695
23932
|
return files;
|
|
23696
23933
|
}
|
|
23697
23934
|
async function fixCommand(target, flags) {
|
|
23935
|
+
const started = Date.now();
|
|
23936
|
+
const report = { delivery: flags.agent ? "agent-task" : "agent-fix" };
|
|
23937
|
+
const actionId = { value: target };
|
|
23938
|
+
const code = await fixAction(target, flags, report, actionId);
|
|
23939
|
+
const root = await findRepoRoot();
|
|
23940
|
+
await recordAction(root, loadConfig(root), actionId.value, report, started, flags.via ?? "command");
|
|
23941
|
+
return code;
|
|
23942
|
+
}
|
|
23943
|
+
async function fixAction(target, flags, report, resolvedId) {
|
|
23698
23944
|
const root = await findRepoRoot();
|
|
23699
23945
|
const config2 = loadConfig(root);
|
|
23700
23946
|
const scan = await latestScan(root);
|
|
@@ -23704,8 +23950,10 @@ async function fixCommand(target, flags) {
|
|
|
23704
23950
|
return 1;
|
|
23705
23951
|
}
|
|
23706
23952
|
const { action: action2, findings } = resolved;
|
|
23953
|
+
resolvedId.value = action2.id;
|
|
23707
23954
|
if (findings.length === 0 && !["write-critical-tests"].includes(action2.id)) {
|
|
23708
23955
|
say.info(`Aura found nothing to fix for "${action2.title}".`);
|
|
23956
|
+
report.outcome = "no_changes";
|
|
23709
23957
|
return 0;
|
|
23710
23958
|
}
|
|
23711
23959
|
const task = taskFor(root, scan, action2, findings);
|
|
@@ -23715,11 +23963,13 @@ async function fixCommand(target, flags) {
|
|
|
23715
23963
|
console.log(`
|
|
23716
23964
|
Ask your coding agent:
|
|
23717
23965
|
${pc.cyan(`Complete the Aura task in ${file2}`)}`);
|
|
23966
|
+
report.outcome = "task_written";
|
|
23718
23967
|
return 0;
|
|
23719
23968
|
}
|
|
23720
23969
|
const client = AuraClient.from(config2);
|
|
23721
23970
|
if (!client.hasKey) {
|
|
23722
23971
|
say.error("Aura\u2019s agent needs a pilot key (aura login). Or run with --agent to hand this to your own coding agent.");
|
|
23972
|
+
Object.assign(report, { outcome: "failed", errorType: "NoPilotKey" });
|
|
23723
23973
|
return 1;
|
|
23724
23974
|
}
|
|
23725
23975
|
console.log(`
|
|
@@ -23732,6 +23982,7 @@ ${pc.bold(action2.title)} ${pc.dim(`${findings.length} finding(s)`)}`);
|
|
|
23732
23982
|
const dirty = await dirtyFiles(root);
|
|
23733
23983
|
if (dirty.length) {
|
|
23734
23984
|
say.error(`You have uncommitted changes (${dirty.slice(0, 5).join(", ")}). Commit or stash them first.`);
|
|
23985
|
+
Object.assign(report, { outcome: "failed", errorType: "UncommittedChanges" });
|
|
23735
23986
|
return 1;
|
|
23736
23987
|
}
|
|
23737
23988
|
const openPr = flags.pr !== false;
|
|
@@ -23739,15 +23990,19 @@ ${pc.bold(action2.title)} ${pc.dim(`${findings.length} finding(s)`)}`);
|
|
|
23739
23990
|
const gh = await ghStatus(root);
|
|
23740
23991
|
if (!gh.installed || !gh.authed || !gh.hasRemote) {
|
|
23741
23992
|
say.error("Opening a PR needs the GitHub CLI, signed in, and a GitHub remote. Run with --no-pr to keep the change on a local branch.");
|
|
23993
|
+
Object.assign(report, { outcome: "failed", errorType: gh.installed ? "GhNotReady" : "GhNotInstalled" });
|
|
23742
23994
|
return 1;
|
|
23743
23995
|
}
|
|
23744
23996
|
}
|
|
23745
|
-
if (interactive() && !flags.yes && !unwrap(await p.confirm({ message: "Start Aura\u2019s agent on a new branch? It reads only the files it needs, with secrets hidden." })))
|
|
23997
|
+
if (interactive() && !flags.yes && !unwrap(await p.confirm({ message: "Start Aura\u2019s agent on a new branch? It reads only the files it needs, with secrets hidden." }))) {
|
|
23998
|
+
report.outcome = "cancelled";
|
|
23746
23999
|
return 0;
|
|
24000
|
+
}
|
|
23747
24001
|
const original = await currentBranch(root);
|
|
23748
24002
|
const branch = await freeBranchName(root, `aura/fix-${action2.id}`);
|
|
23749
24003
|
if ((await git(root, "switch", "-c", branch)).code !== 0) {
|
|
23750
24004
|
say.error(`Couldn\u2019t create branch ${branch}.`);
|
|
24005
|
+
Object.assign(report, { outcome: "failed", errorType: "BranchFailed" });
|
|
23751
24006
|
return 1;
|
|
23752
24007
|
}
|
|
23753
24008
|
const s = spin(true);
|
|
@@ -23757,6 +24012,7 @@ ${pc.bold(action2.title)} ${pc.dim(`${findings.length} finding(s)`)}`);
|
|
|
23757
24012
|
try {
|
|
23758
24013
|
s.start("Aura\u2019s agent is working");
|
|
23759
24014
|
for (let step = 0; step <= MAX_STEPS && !finish; step++) {
|
|
24015
|
+
report.agentSteps = step + 1;
|
|
23760
24016
|
const res = await client.request("POST", "agent/step", { task, messages, step }, 18e4);
|
|
23761
24017
|
messages = [...messages, ...res.responseMessages];
|
|
23762
24018
|
if (res.toolCalls.length === 0) break;
|
|
@@ -23785,6 +24041,7 @@ ${pc.bold(action2.title)} ${pc.dim(`${findings.length} finding(s)`)}`);
|
|
|
23785
24041
|
say.warn("Aura\u2019s agent didn\u2019t change anything. " + (finish?.summary ?? ""));
|
|
23786
24042
|
await git(root, "switch", original);
|
|
23787
24043
|
await git(root, "branch", "-D", branch);
|
|
24044
|
+
report.outcome = "no_changes";
|
|
23788
24045
|
return 1;
|
|
23789
24046
|
}
|
|
23790
24047
|
const guides = [
|
|
@@ -23819,11 +24076,12 @@ ${pc.bold(action2.title)} ${pc.dim(`${findings.length} finding(s)`)}`);
|
|
|
23819
24076
|
else if (result.error) say.warn(`Committed on ${branch}, but couldn\u2019t open the PR: ${result.error.trim()}`);
|
|
23820
24077
|
else say.ok(`Committed on branch ${branch}.`);
|
|
23821
24078
|
if (finish?.risks.length) say.info(pc.dim(`Check before merging: ${finish.risks.join(" ")}`));
|
|
23822
|
-
|
|
24079
|
+
Object.assign(report, { outcome: result.url ? "pr_opened" : "committed", branch, scoreBefore: scan.score, findingIds: findings.map((f) => f.id) });
|
|
23823
24080
|
return 0;
|
|
23824
24081
|
} catch (err) {
|
|
23825
24082
|
s.error("Aura\u2019s agent hit a problem");
|
|
23826
24083
|
say.error(friendlyApiError(err));
|
|
24084
|
+
Object.assign(report, { outcome: "failed", errorType: errorKind(err) });
|
|
23827
24085
|
await git(root, "reset", "-q");
|
|
23828
24086
|
for (const f of touched) {
|
|
23829
24087
|
const inHead = (await git(root, "cat-file", "-e", `HEAD:${f}`)).code === 0;
|
|
@@ -23842,6 +24100,14 @@ ${pc.bold(action2.title)} ${pc.dim(`${findings.length} finding(s)`)}`);
|
|
|
23842
24100
|
// src/commands/apply.ts
|
|
23843
24101
|
var LOCKFILES = ["package-lock.json", "pnpm-lock.yaml", "yarn.lock", "bun.lock", "bun.lockb"];
|
|
23844
24102
|
async function applyCommand(actionId, flags) {
|
|
24103
|
+
const started = Date.now();
|
|
24104
|
+
const report = { delivery: "pr" };
|
|
24105
|
+
const code = await applyAction(actionId, flags, report);
|
|
24106
|
+
const root = await findRepoRoot();
|
|
24107
|
+
await recordAction(root, loadConfig(root), actionId, report, started, flags.via ?? "command");
|
|
24108
|
+
return code;
|
|
24109
|
+
}
|
|
24110
|
+
async function applyAction(actionId, flags, report) {
|
|
23845
24111
|
const root = await findRepoRoot();
|
|
23846
24112
|
const action2 = getAction(actionId);
|
|
23847
24113
|
if (!action2) {
|
|
@@ -23853,7 +24119,7 @@ async function applyCommand(actionId, flags) {
|
|
|
23853
24119
|
if (delivery === "guide") return guideCommand(actionId, {});
|
|
23854
24120
|
if (delivery === "agent-fix") {
|
|
23855
24121
|
if (fellBack) say.info(pc.dim(`Aura only opens template PRs on stacks it recognizes (Next.js). Handing "${actionId}" to Aura\u2019s agent instead.`));
|
|
23856
|
-
return fixCommand(actionId, { yes: flags.yes, dryRun: flags.dryRun, pr: flags.pr });
|
|
24122
|
+
return fixCommand(actionId, { yes: flags.yes, dryRun: flags.dryRun, pr: flags.pr, via: flags.via });
|
|
23857
24123
|
}
|
|
23858
24124
|
if (delivery !== "pr") {
|
|
23859
24125
|
say.error(`"${actionId}" isn\u2019t a PR action. Try aura ${delivery === "checklist" ? "inventory" : "next"}.`);
|
|
@@ -23875,18 +24141,15 @@ async function applyCommand(actionId, flags) {
|
|
|
23875
24141
|
}
|
|
23876
24142
|
const g = { root, scan, ctx, agents: [...agents], vars: templateVars(root, scan, ctx.pkg), skills: skills2 };
|
|
23877
24143
|
const plan = actionId === "foundation" ? foundationPlan(g) : GENERATORS[actionId]?.(g) ?? { skip: "Nothing to do." };
|
|
23878
|
-
if ("skip" in plan) {
|
|
23879
|
-
say.info(plan.skip);
|
|
23880
|
-
|
|
23881
|
-
}
|
|
23882
|
-
if (plan.changes.length === 0 && !plan.commands?.length && !plan.devDeps?.length) {
|
|
23883
|
-
say.info("Nothing to change.");
|
|
24144
|
+
if ("skip" in plan || plan.changes.length === 0 && !plan.commands?.length && !plan.devDeps?.length) {
|
|
24145
|
+
say.info("skip" in plan ? plan.skip : "Nothing to change.");
|
|
24146
|
+
report.outcome = "no_changes";
|
|
23884
24147
|
return 0;
|
|
23885
24148
|
}
|
|
23886
24149
|
console.log(`
|
|
23887
24150
|
${pc.bold(action2.title)}`);
|
|
23888
24151
|
for (const c of plan.changes)
|
|
23889
|
-
console.log(` ${c.content === null ? pc.red("delete") :
|
|
24152
|
+
console.log(` ${c.content === null ? pc.red("delete") : existsSync16(path18.join(root, c.path)) ? pc.yellow("update") : pc.green("add ")} ${c.path}`);
|
|
23890
24153
|
const pm = pmCommands(scan.stack.packageManager);
|
|
23891
24154
|
if (plan.devDeps?.length) console.log(` ${pc.cyan("install")} ${plan.devDeps.join(", ")} ${pc.dim(`(${pm.addDev})`)}`);
|
|
23892
24155
|
for (const cmd of plan.commands ?? []) console.log(` ${pc.cyan("run ")} ${cmd.join(" ")}`);
|
|
@@ -23899,6 +24162,7 @@ ${pc.bold(action2.title)}`);
|
|
|
23899
24162
|
say.error(
|
|
23900
24163
|
`You have uncommitted changes (${dirty.slice(0, 5).join(", ")}${dirty.length > 5 ? "\u2026" : ""}). Commit or stash them first so they don\u2019t end up in Aura\u2019s PR.`
|
|
23901
24164
|
);
|
|
24165
|
+
Object.assign(report, { outcome: "failed", errorType: "UncommittedChanges" });
|
|
23902
24166
|
return 1;
|
|
23903
24167
|
}
|
|
23904
24168
|
const openPr = flags.pr !== false;
|
|
@@ -23909,13 +24173,16 @@ ${pc.bold(action2.title)}`);
|
|
|
23909
24173
|
!gh.installed ? `The GitHub CLI isn\u2019t installed. ${ghInstallHint()}` : !gh.authed ? "Run `gh auth login` first." : "This repo has no GitHub remote. Push it to GitHub first."
|
|
23910
24174
|
);
|
|
23911
24175
|
say.dim("Or run with --no-pr to commit the change on a local branch.");
|
|
24176
|
+
Object.assign(report, { outcome: "failed", errorType: gh.installed ? "GhNotReady" : "GhNotInstalled" });
|
|
23912
24177
|
return 1;
|
|
23913
24178
|
}
|
|
23914
24179
|
}
|
|
23915
|
-
if (interactive() && !flags.yes && !unwrap(await p.confirm({ message: openPr ? "Create a branch and open a pull request?" : "Create a branch and commit?" })))
|
|
24180
|
+
if (interactive() && !flags.yes && !unwrap(await p.confirm({ message: openPr ? "Create a branch and open a pull request?" : "Create a branch and commit?" }))) {
|
|
24181
|
+
report.outcome = "cancelled";
|
|
23916
24182
|
return 0;
|
|
23917
|
-
|
|
23918
|
-
const
|
|
24183
|
+
}
|
|
24184
|
+
const tsc = path18.join(root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
|
|
24185
|
+
const checkTypes = plan.verifyTypes && scan.stack.typescript && existsSync16(tsc);
|
|
23919
24186
|
const typeErrors = async () => {
|
|
23920
24187
|
const r = await run(tsc, ["--noEmit", "--pretty", "false"], { cwd: root, timeoutMs: 3e5 });
|
|
23921
24188
|
return (r.stdout.match(/error TS\d+/g) ?? []).length;
|
|
@@ -23926,6 +24193,7 @@ ${pc.bold(action2.title)}`);
|
|
|
23926
24193
|
const sw = await git(root, "switch", "-c", branch);
|
|
23927
24194
|
if (sw.code !== 0) {
|
|
23928
24195
|
say.error(`Couldn\u2019t create branch ${branch}: ${sw.stderr.trim()}`);
|
|
24196
|
+
Object.assign(report, { outcome: "failed", errorType: "BranchFailed" });
|
|
23929
24197
|
return 1;
|
|
23930
24198
|
}
|
|
23931
24199
|
try {
|
|
@@ -23953,6 +24221,7 @@ ${r.stderr.slice(-1500)}`);
|
|
|
23953
24221
|
say.info("Nothing changed, so there\u2019s no PR to open.");
|
|
23954
24222
|
await git(root, "switch", original);
|
|
23955
24223
|
await git(root, "branch", "-D", branch);
|
|
24224
|
+
report.outcome = "no_changes";
|
|
23956
24225
|
return 0;
|
|
23957
24226
|
}
|
|
23958
24227
|
const prTemplate = `pr/${actionId}.md`;
|
|
@@ -23963,22 +24232,31 @@ ${plan.details.join("\n")}`;
|
|
|
23963
24232
|
const result = await commitPushPr(root, { branch, base: original, title: `Aura: ${action2.title}`, body, openPr });
|
|
23964
24233
|
if (result.error && result.committed) {
|
|
23965
24234
|
say.error(`Committed on branch ${branch}, but couldn\u2019t ${result.pushed ? "open the pull request" : "push it"}: ${result.error.trim()}`);
|
|
23966
|
-
say.dim(`Finish by hand
|
|
24235
|
+
say.dim(`Finish by hand with these two commands:
|
|
24236
|
+
git push -u origin ${branch}
|
|
24237
|
+
gh pr create --head ${branch}`);
|
|
24238
|
+
Object.assign(report, { outcome: "failed", errorType: result.pushed ? "PrFailed" : "PushFailed" });
|
|
23967
24239
|
return 1;
|
|
23968
24240
|
}
|
|
23969
24241
|
if (result.error) throw new Error(result.error.trim());
|
|
23970
24242
|
if (result.url) say.ok(`Opened ${result.url}`);
|
|
23971
24243
|
else say.ok(`Committed on branch ${branch}.`);
|
|
24244
|
+
Object.assign(report, {
|
|
24245
|
+
outcome: result.url ? "pr_opened" : "committed",
|
|
24246
|
+
branch,
|
|
24247
|
+
scoreBefore: scan.score,
|
|
24248
|
+
findingIds: nextSteps(scan, { includeFoundationBundle: true }).find((s) => s.actionId === actionId)?.findingIds ?? []
|
|
24249
|
+
});
|
|
23972
24250
|
if (plan.changes.some((c) => /^\.aura\/|\/skills\/aura\//.test(c.path))) {
|
|
23973
24251
|
say.dim(
|
|
23974
24252
|
openPr ? "Your brief and Aura files are in this PR. Merge it, then run `git pull` to have them locally again." : `Your brief and Aura files are on ${branch}. Merge that branch to have them locally again.`
|
|
23975
24253
|
);
|
|
23976
24254
|
}
|
|
23977
24255
|
if (plan.changes.some((c) => c.path === ".github/workflows/aura.yml") && openPr && !flags.yes) await offerPilotSecret(root);
|
|
23978
|
-
if (config2.telemetry) await sendTelemetry(root, AuraClient.from(config2), { event: "action", actionId });
|
|
23979
24256
|
return 0;
|
|
23980
24257
|
} catch (err) {
|
|
23981
24258
|
say.error(err.message);
|
|
24259
|
+
Object.assign(report, { outcome: "failed", errorType: errorKind(err) });
|
|
23982
24260
|
await undoPlan(root, plan);
|
|
23983
24261
|
await git(root, "switch", original);
|
|
23984
24262
|
await git(root, "branch", "-D", branch);
|
|
@@ -24013,16 +24291,17 @@ async function undoPlan(root, plan) {
|
|
|
24013
24291
|
for (const file2 of touched) {
|
|
24014
24292
|
const inHead = (await git(root, "cat-file", "-e", `HEAD:${file2}`)).code === 0;
|
|
24015
24293
|
if (inHead) await git(root, "checkout", "HEAD", "--", file2);
|
|
24016
|
-
else if (plan.changes.some((c) => c.path === file2)) rmSync6(
|
|
24294
|
+
else if (plan.changes.some((c) => c.path === file2)) rmSync6(path18.join(root, file2), { force: true });
|
|
24017
24295
|
}
|
|
24018
24296
|
}
|
|
24019
24297
|
|
|
24020
24298
|
// src/commands/init.ts
|
|
24021
|
-
import { existsSync as
|
|
24299
|
+
import { existsSync as existsSync17, readFileSync as readFileSync17 } from "fs";
|
|
24022
24300
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
24301
|
+
import path22 from "path";
|
|
24023
24302
|
|
|
24024
24303
|
// src/brief.ts
|
|
24025
|
-
import
|
|
24304
|
+
import path19 from "path";
|
|
24026
24305
|
function repoSummary(ctx) {
|
|
24027
24306
|
const routes = ctx.files.filter((f) => /^(?:src\/)?app\/(?:.+\/)?(?:page|route)\.[cm]?[jt]sx?$/.test(f) || /^(?:src\/)?pages\/.+\.[cm]?[jt]sx?$/.test(f)).map(
|
|
24028
24307
|
(f) => "/" + f.replace(/^(?:src\/)?(?:app|pages)\//, "").replace(/(?:^|\/)(?:page|route|index)\.[cm]?[jt]sx?$|\.[cm]?[jt]sx?$/, "").replace(/\([^)]+\)\//g, "")
|
|
@@ -24038,7 +24317,7 @@ function repoSummary(ctx) {
|
|
|
24038
24317
|
const readme2 = ctx.files.find((f) => /^readme\.mdx?$/i.test(f));
|
|
24039
24318
|
const readmeText = readme2 ? ctx.read(readme2) ?? "" : "";
|
|
24040
24319
|
return {
|
|
24041
|
-
name: ctx.pkg?.name ??
|
|
24320
|
+
name: ctx.pkg?.name ?? path19.basename(ctx.root),
|
|
24042
24321
|
packageJson: ctx.pkg ? {
|
|
24043
24322
|
name: ctx.pkg.name,
|
|
24044
24323
|
description: ctx.pkg.description,
|
|
@@ -24129,13 +24408,135 @@ function installFallback(result, platform = process.platform) {
|
|
|
24129
24408
|
return `${admin} \`npx getaura <command>\` in place of \`aura <command>\` (for example \`npx getaura scan\`).`;
|
|
24130
24409
|
}
|
|
24131
24410
|
|
|
24132
|
-
// src/
|
|
24133
|
-
import {
|
|
24411
|
+
// src/core/claude-plugin.ts
|
|
24412
|
+
import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync7 } from "fs";
|
|
24134
24413
|
import os5 from "os";
|
|
24135
|
-
import
|
|
24414
|
+
import path20 from "path";
|
|
24415
|
+
var CLAUDE_PLUGIN_ID = "aura@aura";
|
|
24416
|
+
var marketplaceDir = () => path20.join(os5.homedir(), ".aura", "claude-marketplace");
|
|
24417
|
+
var MARKETPLACE = {
|
|
24418
|
+
name: "aura",
|
|
24419
|
+
description: "Aura for Claude Code",
|
|
24420
|
+
owner: { name: "Aura" },
|
|
24421
|
+
plugins: [
|
|
24422
|
+
{
|
|
24423
|
+
name: "aura",
|
|
24424
|
+
description: "Slash commands for Aura: /aura:scan, /aura:next, /aura:fix and /aura:setup.",
|
|
24425
|
+
source: { source: "npm", package: "getaura" }
|
|
24426
|
+
}
|
|
24427
|
+
]
|
|
24428
|
+
};
|
|
24429
|
+
function writeMarketplace() {
|
|
24430
|
+
const dir = marketplaceDir();
|
|
24431
|
+
mkdirSync6(path20.join(dir, ".claude-plugin"), { recursive: true });
|
|
24432
|
+
writeFileSync7(path20.join(dir, ".claude-plugin", "marketplace.json"), `${JSON.stringify(MARKETPLACE, null, 2)}
|
|
24433
|
+
`);
|
|
24434
|
+
return dir;
|
|
24435
|
+
}
|
|
24436
|
+
function manualInstall() {
|
|
24437
|
+
return [`claude plugin marketplace add "${writeMarketplace()}"`, `claude plugin install ${CLAUDE_PLUGIN_ID} --scope project`];
|
|
24438
|
+
}
|
|
24439
|
+
async function installClaudePlugin(root) {
|
|
24440
|
+
if (!await commandExists("claude")) return "no-claude";
|
|
24441
|
+
const dir = writeMarketplace();
|
|
24442
|
+
const add = await run("claude", ["plugin", "marketplace", "add", dir], { cwd: root, timeoutMs: 12e4 });
|
|
24443
|
+
if (add.code !== 0) return "failed";
|
|
24444
|
+
const install = await run("claude", ["plugin", "install", CLAUDE_PLUGIN_ID, "--scope", "project"], { cwd: root, timeoutMs: 18e4 });
|
|
24445
|
+
return install.code === 0 ? "installed" : "failed";
|
|
24446
|
+
}
|
|
24447
|
+
|
|
24448
|
+
// src/onboarding.ts
|
|
24449
|
+
var DATA_LABELS2 = {
|
|
24450
|
+
personal: "personal info (names, emails)",
|
|
24451
|
+
payments: "payments",
|
|
24452
|
+
health: "health",
|
|
24453
|
+
legal: "legal",
|
|
24454
|
+
children: "children\u2019s data",
|
|
24455
|
+
none: "none of these"
|
|
24456
|
+
};
|
|
24457
|
+
var BOILERPLATE2 = /bootstrapped with|create-next-app|getting started|this template|vite \+|turborepo|starter kit/i;
|
|
24458
|
+
function whatItDoes(ctx) {
|
|
24459
|
+
const description = ctx.pkg?.description?.trim();
|
|
24460
|
+
if (description && description.length >= 15 && !BOILERPLATE2.test(description)) return { value: description, from: "package.json" };
|
|
24461
|
+
const readme2 = ctx.files.find((f) => /^readme\.mdx?$/i.test(f));
|
|
24462
|
+
const readmeText = readme2 ? ctx.read(readme2) ?? "" : "";
|
|
24463
|
+
if (/create-next-app|create-react-app|create-vite|create-t3-app|bootstrapped with/i.test(readmeText)) return null;
|
|
24464
|
+
const paragraphs = readmeText.replace(/\r\n/g, "\n").split(/\n\s*\n/);
|
|
24465
|
+
for (const raw of paragraphs) {
|
|
24466
|
+
const text = raw.replace(/\s+/g, " ").trim();
|
|
24467
|
+
if (!text || /^(?:#|!\[|\[|<|```|[-*|>]|\d+\.)/.test(text) || text.length < 30 || BOILERPLATE2.test(text)) continue;
|
|
24468
|
+
return { value: text.length > 300 ? `${text.slice(0, 297).replace(/\s+\S*$/, "")}\u2026` : text, from: "the README" };
|
|
24469
|
+
}
|
|
24470
|
+
return null;
|
|
24471
|
+
}
|
|
24472
|
+
var PAYMENT_PACKAGES = [
|
|
24473
|
+
[/^stripe$|^@stripe\//, "Stripe"],
|
|
24474
|
+
[/^@lemonsqueezy\//, "Lemon Squeezy"],
|
|
24475
|
+
[/^@paddle\//, "Paddle"],
|
|
24476
|
+
[/^@polar-sh\//, "Polar"],
|
|
24477
|
+
[/^@revenuecat\/|^react-native-purchases$/, "RevenueCat"]
|
|
24478
|
+
];
|
|
24479
|
+
var AUTH_PACKAGES = [
|
|
24480
|
+
[/^@supabase\//, "Supabase"],
|
|
24481
|
+
[/^@clerk\//, "Clerk"],
|
|
24482
|
+
[/^next-auth$|^@auth\//, "Auth.js"],
|
|
24483
|
+
[/^firebase$|^firebase-admin$/, "Firebase"],
|
|
24484
|
+
[/^better-auth$/, "Better Auth"],
|
|
24485
|
+
[/^@kinde-oss\//, "Kinde"],
|
|
24486
|
+
[/^@auth0\//, "Auth0"]
|
|
24487
|
+
];
|
|
24488
|
+
var words = (list) => new RegExp(`(?<![a-z])(?:${list})(?![a-z])`, "i");
|
|
24489
|
+
var SENSITIVE = [
|
|
24490
|
+
["health", words("patients?|diagnos[a-z]*|medical|medications?|prescriptions?|symptoms?|health_records?"), "health"],
|
|
24491
|
+
["legal", words("case_number|lawsuits?|attorneys?|legal_matters?|court[a-z]*"), "legal"],
|
|
24492
|
+
["children", words("child|children|kids?|minors?|guardians?|parental_consent"), "children"]
|
|
24493
|
+
];
|
|
24494
|
+
function schemaText(ctx) {
|
|
24495
|
+
const files = ctx.files.filter((f) => /\.sql$|schema\.prisma$|(?:^|\/)schema\.[cm]?[jt]s$|(?:^|\/)db\/schema\//.test(f) && !/node_modules\//.test(f));
|
|
24496
|
+
return files.slice(0, 200).map((f) => ctx.read(f) ?? "").join("\n");
|
|
24497
|
+
}
|
|
24498
|
+
var matchPackage = (deps2, list) => list.find(([re]) => deps2.some((d) => re.test(d)))?.[1];
|
|
24499
|
+
function inferAnswers(ctx) {
|
|
24500
|
+
const deps2 = Object.keys(allDeps(ctx.pkg));
|
|
24501
|
+
const payment = matchPackage(deps2, PAYMENT_PACKAGES);
|
|
24502
|
+
const paymentPage = ctx.files.find((f) => /(?:^|\/)(?:pricing|checkout|billing|subscribe)(?:\/|\.|$)/i.test(f));
|
|
24503
|
+
const chargesMoney = payment ? { value: true, from: `${payment} is installed` } : paymentPage ? { value: true, from: `there\u2019s a ${paymentPage.match(/pricing|checkout|billing|subscribe/i)[0].toLowerCase()} page` } : { value: false, from: "no payment provider or pricing page found" };
|
|
24504
|
+
const kinds = [];
|
|
24505
|
+
const reasons = [];
|
|
24506
|
+
const auth = matchPackage(deps2, AUTH_PACKAGES);
|
|
24507
|
+
const schema = schemaText(ctx);
|
|
24508
|
+
if (auth || /\b(?:users?|profiles?|accounts?)\b[\s\S]{0,400}\bemail\b/i.test(schema)) {
|
|
24509
|
+
kinds.push("personal");
|
|
24510
|
+
reasons.push(auth ? `sign-in with ${auth}` : "a users table with emails");
|
|
24511
|
+
}
|
|
24512
|
+
if (chargesMoney.value) {
|
|
24513
|
+
kinds.push("payments");
|
|
24514
|
+
reasons.push("it charges money");
|
|
24515
|
+
}
|
|
24516
|
+
for (const [kind, re, label] of SENSITIVE) {
|
|
24517
|
+
if (re.test(schema)) {
|
|
24518
|
+
kinds.push(kind);
|
|
24519
|
+
reasons.push(`${label} words in the database tables`);
|
|
24520
|
+
}
|
|
24521
|
+
}
|
|
24522
|
+
const dataHandled = kinds.length ? { value: kinds, from: reasons.join("; ") } : { value: ["none"], from: "no sign-in, payments or sensitive tables found" };
|
|
24523
|
+
return { whatItDoes: whatItDoes(ctx), chargesMoney, dataHandled };
|
|
24524
|
+
}
|
|
24525
|
+
function describeInferred(inferred) {
|
|
24526
|
+
return [
|
|
24527
|
+
`What it does: ${inferred.whatItDoes ? `${inferred.whatItDoes.value} (from ${inferred.whatItDoes.from})` : "not found, please describe it"}`,
|
|
24528
|
+
`Charges money: ${inferred.chargesMoney.value ? "yes" : "no"} (${inferred.chargesMoney.from})`,
|
|
24529
|
+
`Data it handles: ${inferred.dataHandled.value.map((k) => DATA_LABELS2[k]).join(", ")} (${inferred.dataHandled.from})`
|
|
24530
|
+
];
|
|
24531
|
+
}
|
|
24532
|
+
|
|
24533
|
+
// src/commands/scan.ts
|
|
24534
|
+
import { mkdtempSync as mkdtempSync3, rmSync as rmSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
24535
|
+
import os6 from "os";
|
|
24536
|
+
import path21 from "path";
|
|
24136
24537
|
async function scanRef(root, ref, opts) {
|
|
24137
|
-
const dir = mkdtempSync3(
|
|
24138
|
-
const wt =
|
|
24538
|
+
const dir = mkdtempSync3(path21.join(os6.tmpdir(), "aura-base-"));
|
|
24539
|
+
const wt = path21.join(dir, "tree");
|
|
24139
24540
|
const add = await run("git", ["worktree", "add", "--detach", wt, ref], { cwd: root });
|
|
24140
24541
|
if (add.code !== 0) {
|
|
24141
24542
|
rmSync7(dir, { recursive: true, force: true });
|
|
@@ -24149,6 +24550,7 @@ async function scanRef(root, ref, opts) {
|
|
|
24149
24550
|
}
|
|
24150
24551
|
}
|
|
24151
24552
|
async function scanCommand(flags) {
|
|
24553
|
+
const started = Date.now();
|
|
24152
24554
|
const root = await findRepoRoot();
|
|
24153
24555
|
const config2 = loadConfig(root);
|
|
24154
24556
|
const quiet = Boolean(flags.json);
|
|
@@ -24176,8 +24578,22 @@ async function scanCommand(flags) {
|
|
|
24176
24578
|
writeAuraFile(root, "scan.json", JSON.stringify(scan, null, 2));
|
|
24177
24579
|
writeAuraFile(root, "report.md", renderMarkdown(scan, steps));
|
|
24178
24580
|
appendHistory(root, scan);
|
|
24179
|
-
|
|
24180
|
-
|
|
24581
|
+
const findings = scan.checks.flatMap((c) => c.findings);
|
|
24582
|
+
await recordUsage(root, config2, {
|
|
24583
|
+
event: "scan",
|
|
24584
|
+
score: scan.score,
|
|
24585
|
+
categories: Object.fromEntries(scan.categories.map((c) => [c.category, c.score])),
|
|
24586
|
+
findings: {
|
|
24587
|
+
critical: findings.filter((f) => f.severity === "critical").length,
|
|
24588
|
+
high: findings.filter((f) => f.severity === "high").length,
|
|
24589
|
+
medium: findings.filter((f) => f.severity === "medium").length,
|
|
24590
|
+
low: findings.filter((f) => f.severity === "low").length,
|
|
24591
|
+
info: findings.filter((f) => f.severity === "info").length
|
|
24592
|
+
},
|
|
24593
|
+
failingChecks: scan.checks.filter((c) => c.status === "fail" || c.status === "partial").map((c) => c.checkId),
|
|
24594
|
+
durationMs: Date.now() - started
|
|
24595
|
+
});
|
|
24596
|
+
if (!flags.ci) await followUpFixes(root, config2, scan);
|
|
24181
24597
|
let exitCode = 0;
|
|
24182
24598
|
let base = null;
|
|
24183
24599
|
if (flags.base) {
|
|
@@ -24188,7 +24604,7 @@ async function scanCommand(flags) {
|
|
|
24188
24604
|
const baseIds = new Set(base ? base.checks.flatMap((c) => c.findings.map((f) => f.id)) : []);
|
|
24189
24605
|
const newCritical = base ? scan.checks.flatMap((c) => c.findings).filter((f) => f.severity === "critical" && !baseIds.has(f.id)) : [];
|
|
24190
24606
|
if (base && (base.score - scan.score > maxDrop || newCritical.length > 0)) exitCode = 1;
|
|
24191
|
-
if (flags.commentFile)
|
|
24607
|
+
if (flags.commentFile) writeFileSync8(flags.commentFile, renderPrComment(scan, base, maxDrop));
|
|
24192
24608
|
if (quiet) {
|
|
24193
24609
|
printJson({ ...scan, nextSteps: steps, base: base ? { score: base.score, categories: base.categories } : void 0 });
|
|
24194
24610
|
return exitCode;
|
|
@@ -24209,31 +24625,34 @@ async function scanCommand(flags) {
|
|
|
24209
24625
|
// src/commands/init.ts
|
|
24210
24626
|
var AGENT_IDS = ["claude", "codex", "cursor", "pi"];
|
|
24211
24627
|
function detectAgents(root) {
|
|
24212
|
-
|
|
24213
|
-
if (existsSync16(`${root}/CLAUDE.md`) || existsSync16(`${root}/.claude`)) found.push("claude");
|
|
24214
|
-
if (existsSync16(`${root}/AGENTS.md`)) found.push("codex");
|
|
24215
|
-
if (existsSync16(`${root}/.cursor`) || existsSync16(`${root}/.cursorrules`)) found.push("cursor");
|
|
24216
|
-
if (existsSync16(`${root}/.pi`)) found.push("pi");
|
|
24217
|
-
return found;
|
|
24628
|
+
return detectAgentsFrom((rel) => existsSync17(path22.join(root, rel)));
|
|
24218
24629
|
}
|
|
24219
24630
|
function openEditor(file2) {
|
|
24220
|
-
const
|
|
24221
|
-
const
|
|
24222
|
-
return
|
|
24631
|
+
const fallback = process.platform === "win32" ? "notepad" : spawnSync3("sh", ["-c", "command -v nano"]).status === 0 ? "nano" : "vi";
|
|
24632
|
+
const editor = process.env.VISUAL || process.env.EDITOR || fallback;
|
|
24633
|
+
return spawnSync3(`${editor} "${file2}"`, { stdio: "inherit", shell: true }).status === 0;
|
|
24634
|
+
}
|
|
24635
|
+
function answersTemplate(inferred) {
|
|
24636
|
+
return JSON.stringify({
|
|
24637
|
+
whatItDoes: inferred.whatItDoes?.value ?? "\u2026",
|
|
24638
|
+
audience: "\u2026",
|
|
24639
|
+
chargesMoney: inferred.chargesMoney.value,
|
|
24640
|
+
dataHandled: inferred.dataHandled.value,
|
|
24641
|
+
userRegions: ["\u2026"]
|
|
24642
|
+
});
|
|
24223
24643
|
}
|
|
24224
|
-
|
|
24225
|
-
function briefHandoff() {
|
|
24644
|
+
function briefHandoff(inferred) {
|
|
24226
24645
|
return [
|
|
24227
24646
|
"",
|
|
24228
|
-
`${pc.magenta("\u2726")} ${pc.bold("Next:
|
|
24229
|
-
"
|
|
24230
|
-
|
|
24231
|
-
"
|
|
24232
|
-
"
|
|
24233
|
-
"
|
|
24234
|
-
|
|
24235
|
-
|
|
24236
|
-
|
|
24647
|
+
`${pc.magenta("\u2726")} ${pc.bold("Next: check the product brief with the user")}`,
|
|
24648
|
+
" Aura worked these out from the code. Show them to the user and correct anything they say is wrong:",
|
|
24649
|
+
...describeInferred(inferred).map((l) => ` - ${l}`),
|
|
24650
|
+
" Then ask the user:",
|
|
24651
|
+
" - Who is the app for?",
|
|
24652
|
+
" - Where are its users? (countries or regions)",
|
|
24653
|
+
' Save the answers to .aura/answers.json. Start from this and replace each "\u2026":',
|
|
24654
|
+
` ${answersTemplate(inferred)}`,
|
|
24655
|
+
" dataHandled can include: personal, payments, health, legal, children, none.",
|
|
24237
24656
|
" Then run: npx getaura@latest init --yes --answers .aura/answers.json",
|
|
24238
24657
|
""
|
|
24239
24658
|
].join("\n");
|
|
@@ -24262,6 +24681,8 @@ async function initCommand(flags) {
|
|
|
24262
24681
|
else if (!gh.hasRemote) say.warn("This repo has no GitHub remote yet. Push it to GitHub before applying fixes (`gh repo create`).");
|
|
24263
24682
|
else say.ok("git and the GitHub CLI are ready.");
|
|
24264
24683
|
const config2 = loadConfig(root);
|
|
24684
|
+
if (flags.telemetry !== void 0) config2.shareUsage = flags.telemetry;
|
|
24685
|
+
const setupStep = (step) => recordUsage(root, config2, { event: "setup", step });
|
|
24265
24686
|
let agents = (flags.agent ?? []).flatMap((a) => a.split(",")).map((a) => a.trim().toLowerCase());
|
|
24266
24687
|
const unknown2 = agents.filter((a) => !AGENT_IDS.includes(a));
|
|
24267
24688
|
if (unknown2.length) {
|
|
@@ -24283,20 +24704,15 @@ async function initCommand(flags) {
|
|
|
24283
24704
|
}
|
|
24284
24705
|
config2.agents = agents;
|
|
24285
24706
|
let key = flags.key ?? getApiKey();
|
|
24286
|
-
let
|
|
24287
|
-
if (!key && !
|
|
24707
|
+
let connectedNow = false;
|
|
24708
|
+
if (!key && !inCI()) {
|
|
24288
24709
|
const outcome = await signInWithBrowser(getApiUrl(config2), { person: interactive(), waitSeconds: parseWait(flags.wait) });
|
|
24289
|
-
if (outcome.status
|
|
24290
|
-
|
|
24291
|
-
|
|
24292
|
-
} else {
|
|
24293
|
-
if (outcome.status === "skipped") config2.noKey = true;
|
|
24294
|
-
if (!explainSignIn(outcome)) {
|
|
24295
|
-
if (outcome.status === "pending") return 1;
|
|
24296
|
-
say.dim("Continuing without a pilot key for now. Connect one later with `aura login`.");
|
|
24297
|
-
}
|
|
24298
|
-
explained = true;
|
|
24710
|
+
if (outcome.status !== "complete") {
|
|
24711
|
+
explainSignIn(outcome);
|
|
24712
|
+
return 1;
|
|
24299
24713
|
}
|
|
24714
|
+
key = outcome.key;
|
|
24715
|
+
connectedNow = true;
|
|
24300
24716
|
}
|
|
24301
24717
|
let client = AuraClient.from(config2);
|
|
24302
24718
|
if (key) {
|
|
@@ -24305,11 +24721,18 @@ async function initCommand(flags) {
|
|
|
24305
24721
|
const me = await client.request("GET", "me");
|
|
24306
24722
|
saveApiKey(key);
|
|
24307
24723
|
say.ok(`Signed in as ${me.name}.`);
|
|
24724
|
+
if (sharesUsage(config2, key)) {
|
|
24725
|
+
say.dim(
|
|
24726
|
+
"During the pilot, Aura shares usage data with the Aura team: scores, fixes and errors, never your code. Turn it off with `aura config telemetry off`."
|
|
24727
|
+
);
|
|
24728
|
+
await setupStep("started");
|
|
24729
|
+
if (connectedNow) await setupStep("key_connected");
|
|
24730
|
+
}
|
|
24308
24731
|
} catch (err) {
|
|
24309
24732
|
say.warn(`${friendlyApiError(err)} Continuing without Aura\u2019s agent.`);
|
|
24310
24733
|
client = new AuraClient(getApiUrl(config2), null);
|
|
24311
24734
|
}
|
|
24312
|
-
} else
|
|
24735
|
+
} else {
|
|
24313
24736
|
say.dim("No pilot key: Aura will still score your repo, with standard explanations.");
|
|
24314
24737
|
}
|
|
24315
24738
|
const ctx = await buildContext(root, { fast: true, noDownload: true }, config2);
|
|
@@ -24317,35 +24740,49 @@ async function initCommand(flags) {
|
|
|
24317
24740
|
let brief2 = null;
|
|
24318
24741
|
let keptBrief = false;
|
|
24319
24742
|
if (flags.briefFile) {
|
|
24320
|
-
brief2 =
|
|
24321
|
-
} else if (!flags.answers &&
|
|
24743
|
+
brief2 = readFileSync17(flags.briefFile, "utf8");
|
|
24744
|
+
} else if (!flags.answers && existsSync17(briefPath) && !(tty && unwrap(await p.confirm({ message: "A product brief already exists. Draft a new one?", initialValue: false })))) {
|
|
24322
24745
|
keptBrief = true;
|
|
24323
24746
|
} else {
|
|
24324
24747
|
let answers = null;
|
|
24325
24748
|
if (flags.answers) {
|
|
24326
|
-
answers = OnboardingAnswersSchema.parse(JSON.parse(
|
|
24749
|
+
answers = OnboardingAnswersSchema.parse(JSON.parse(readFileSync17(flags.answers, "utf8")));
|
|
24327
24750
|
} else if (tty) {
|
|
24328
|
-
|
|
24329
|
-
|
|
24330
|
-
const
|
|
24331
|
-
|
|
24332
|
-
|
|
24751
|
+
const inferred = inferAnswers(ctx);
|
|
24752
|
+
p.note(describeInferred(inferred).join("\n"), "Here\u2019s what Aura found about your app");
|
|
24753
|
+
const fix = new Set(
|
|
24754
|
+
unwrap(
|
|
24755
|
+
await p.multiselect({
|
|
24756
|
+
message: "Anything Aura got wrong? Select it to correct it, or press enter if it\u2019s all right.",
|
|
24757
|
+
options: [
|
|
24758
|
+
{ value: "whatItDoes", label: "What it does" },
|
|
24759
|
+
{ value: "chargesMoney", label: "Charges money" },
|
|
24760
|
+
{ value: "dataHandled", label: "Data it handles" }
|
|
24761
|
+
],
|
|
24762
|
+
required: false
|
|
24763
|
+
})
|
|
24764
|
+
)
|
|
24765
|
+
);
|
|
24766
|
+
const whatItDoes2 = inferred.whatItDoes && !fix.has("whatItDoes") ? inferred.whatItDoes.value : unwrap(
|
|
24767
|
+
await p.text({
|
|
24768
|
+
message: "What does your app do?",
|
|
24769
|
+
placeholder: "e.g. Lets dog owners book and pay for walks",
|
|
24770
|
+
initialValue: inferred.whatItDoes?.value
|
|
24771
|
+
})
|
|
24772
|
+
);
|
|
24773
|
+
const chargesMoney = fix.has("chargesMoney") ? unwrap(await p.confirm({ message: "Does it charge money?", initialValue: !inferred.chargesMoney.value })) : inferred.chargesMoney.value;
|
|
24774
|
+
const dataHandled = fix.has("dataHandled") ? unwrap(
|
|
24333
24775
|
await p.multiselect({
|
|
24334
24776
|
message: "What data does it handle?",
|
|
24335
|
-
options: [
|
|
24336
|
-
|
|
24337
|
-
{ value: "payments", label: "Payments" },
|
|
24338
|
-
{ value: "health", label: "Health" },
|
|
24339
|
-
{ value: "legal", label: "Legal" },
|
|
24340
|
-
{ value: "children", label: "Children\u2019s data" },
|
|
24341
|
-
{ value: "none", label: "None of these" }
|
|
24342
|
-
],
|
|
24777
|
+
options: Object.entries(DATA_LABELS2).map(([value, label]) => ({ value, label: label[0].toUpperCase() + label.slice(1) })),
|
|
24778
|
+
initialValues: inferred.dataHandled.value,
|
|
24343
24779
|
required: true
|
|
24344
24780
|
})
|
|
24345
|
-
);
|
|
24781
|
+
) : inferred.dataHandled.value;
|
|
24782
|
+
const audience = unwrap(await p.text({ message: "Who is it for?", placeholder: "e.g. Busy dog owners in Toronto" }));
|
|
24346
24783
|
const regions = unwrap(await p.text({ message: "Where are your users?", placeholder: "e.g. Canada, United States" }));
|
|
24347
24784
|
answers = {
|
|
24348
|
-
whatItDoes,
|
|
24785
|
+
whatItDoes: whatItDoes2,
|
|
24349
24786
|
audience,
|
|
24350
24787
|
chargesMoney,
|
|
24351
24788
|
dataHandled,
|
|
@@ -24368,13 +24805,14 @@ async function initCommand(flags) {
|
|
|
24368
24805
|
}
|
|
24369
24806
|
brief2 ??= offlineBrief(answers, summary, ctx);
|
|
24370
24807
|
} else {
|
|
24371
|
-
console.log(briefHandoff());
|
|
24808
|
+
console.log(briefHandoff(inferAnswers(ctx)));
|
|
24372
24809
|
}
|
|
24373
24810
|
}
|
|
24374
24811
|
if (keptBrief) {
|
|
24375
24812
|
say.ok("Kept your existing product brief (.aura/brief.md).");
|
|
24376
24813
|
} else if (brief2) {
|
|
24377
24814
|
writeAuraFile(root, "brief.md", brief2);
|
|
24815
|
+
await setupStep("brief_written");
|
|
24378
24816
|
if (tty) {
|
|
24379
24817
|
p.note(brief2.split("\n").slice(0, 24).join("\n") + (brief2.split("\n").length > 24 ? "\n\u2026" : ""), "Product brief draft");
|
|
24380
24818
|
const choice = unwrap(
|
|
@@ -24390,22 +24828,28 @@ async function initCommand(flags) {
|
|
|
24390
24828
|
}
|
|
24391
24829
|
say.ok("Saved the product brief to .aura/brief.md. It gets committed in the first-run PR (aura apply foundation).");
|
|
24392
24830
|
}
|
|
24393
|
-
writeChanges(root, skillChanges(agents, ["aura"]));
|
|
24831
|
+
writeChanges(root, [...skillChanges(agents, ["aura"]), ...commandChanges(agents)]);
|
|
24394
24832
|
say.ok(`Installed the Aura skill for ${agents.map((a) => AGENT_LABELS[a]).join(", ")}.`);
|
|
24395
|
-
|
|
24396
|
-
|
|
24397
|
-
|
|
24398
|
-
|
|
24399
|
-
|
|
24833
|
+
await setupStep("skill_installed");
|
|
24834
|
+
if (agents.includes("claude") && !inCI() && !process.env.AURA_NO_CLAUDE_PLUGIN) {
|
|
24835
|
+
const plugin = await installClaudePlugin(root);
|
|
24836
|
+
if (plugin === "installed")
|
|
24837
|
+
say.ok(
|
|
24838
|
+
"Added Aura\u2019s slash commands to Claude Code: /aura:scan, /aura:next, /aura:fix and /aura:setup. Restart Claude Code (or run /reload-plugins) to use them."
|
|
24839
|
+
);
|
|
24840
|
+
else say.dim(`To add Aura\u2019s slash commands (/aura:scan, /aura:fix\u2026) to Claude Code, run these two commands:
|
|
24841
|
+
${manualInstall().join("\n ")}`);
|
|
24400
24842
|
}
|
|
24843
|
+
for (const agent of agents.filter((a) => a !== "claude")) say.ok(`Added Aura\u2019s commands for ${AGENT_LABELS[agent]}: ${commandList(agent)}.`);
|
|
24401
24844
|
saveConfig(root, config2);
|
|
24402
24845
|
ensureGitignore(root);
|
|
24403
|
-
if (!
|
|
24846
|
+
if (!inCI() && !await hasAuraCommand()) {
|
|
24404
24847
|
if (tty) {
|
|
24405
24848
|
if (unwrap(await p.confirm({ message: "Install the `aura` command so you can run `aura scan` any time?", initialValue: true }))) {
|
|
24406
24849
|
const s = spin(true);
|
|
24407
24850
|
s.start("Installing the aura command");
|
|
24408
24851
|
const installed = await installAuraCommand();
|
|
24852
|
+
await setupStep(installed.ok ? "command_installed" : "command_install_failed");
|
|
24409
24853
|
if (installed.ok)
|
|
24410
24854
|
s.stop(installed.onPath ? "Installed. Run `aura scan` from any folder." : "Installed. Open a new terminal window, then run `aura scan`.");
|
|
24411
24855
|
else {
|
|
@@ -24428,11 +24872,13 @@ ${pc.magenta("\u2726")} ${pc.bold("Give the user the aura command")}`);
|
|
|
24428
24872
|
return 0;
|
|
24429
24873
|
}
|
|
24430
24874
|
if (tty) p.outro("Running your first scan\u2026");
|
|
24431
|
-
|
|
24875
|
+
const scanned = await scanCommand({});
|
|
24876
|
+
await setupStep("first_scan");
|
|
24877
|
+
return scanned;
|
|
24432
24878
|
}
|
|
24433
24879
|
|
|
24434
24880
|
// src/commands/summary.ts
|
|
24435
|
-
import
|
|
24881
|
+
import path23 from "path";
|
|
24436
24882
|
async function activity(root, since) {
|
|
24437
24883
|
const log = await git(root, "log", `--since=${since.toISOString()}`, "--no-merges", "--format=%H%x09%an%x09%s");
|
|
24438
24884
|
const commits = log.stdout.split("\n").filter(Boolean).slice(0, 300).map((l) => {
|
|
@@ -24489,7 +24935,7 @@ async function summaryCommand(flags) {
|
|
|
24489
24935
|
const beforeIds = new Set(before ? allFindings(before).map((f) => f.id) : []);
|
|
24490
24936
|
const nowIds = new Set(allFindings(now).map((f) => f.id));
|
|
24491
24937
|
const req = {
|
|
24492
|
-
repo:
|
|
24938
|
+
repo: path23.basename(root),
|
|
24493
24939
|
since: since.toISOString(),
|
|
24494
24940
|
until: until.toISOString(),
|
|
24495
24941
|
product: productContext(root),
|
|
@@ -24537,11 +24983,11 @@ ${body}`);
|
|
|
24537
24983
|
import pc4 from "picocolors";
|
|
24538
24984
|
|
|
24539
24985
|
// src/core/update-check.ts
|
|
24540
|
-
import { mkdirSync as
|
|
24541
|
-
import
|
|
24542
|
-
import
|
|
24986
|
+
import { mkdirSync as mkdirSync7, readFileSync as readFileSync18, writeFileSync as writeFileSync9 } from "fs";
|
|
24987
|
+
import os7 from "os";
|
|
24988
|
+
import path24 from "path";
|
|
24543
24989
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
24544
|
-
var cacheFile = () =>
|
|
24990
|
+
var cacheFile = () => path24.join(os7.homedir(), ".aura", "update-check.json");
|
|
24545
24991
|
function isNewer(a, b) {
|
|
24546
24992
|
if (!/^\d+\.\d+\.\d+$/.test(a) || !/^\d+\.\d+\.\d+$/.test(b)) return false;
|
|
24547
24993
|
const [x, y] = [a, b].map((v) => v.split(".").map(Number));
|
|
@@ -24561,21 +25007,21 @@ async function newerVersion(opts = {}) {
|
|
|
24561
25007
|
const now = opts.now ?? Date.now();
|
|
24562
25008
|
let latest = null;
|
|
24563
25009
|
try {
|
|
24564
|
-
const cached2 = JSON.parse(
|
|
25010
|
+
const cached2 = JSON.parse(readFileSync18(cacheFile(), "utf8"));
|
|
24565
25011
|
if (now - cached2.checkedAt < DAY_MS) latest = cached2.latest;
|
|
24566
25012
|
else throw new Error("stale");
|
|
24567
25013
|
} catch {
|
|
24568
25014
|
latest = await (opts.fetchLatest ?? fetchLatest)();
|
|
24569
25015
|
try {
|
|
24570
|
-
|
|
24571
|
-
|
|
25016
|
+
mkdirSync7(path24.dirname(cacheFile()), { recursive: true });
|
|
25017
|
+
writeFileSync9(cacheFile(), JSON.stringify({ checkedAt: now, latest }));
|
|
24572
25018
|
} catch {
|
|
24573
25019
|
}
|
|
24574
25020
|
}
|
|
24575
25021
|
return latest && isNewer(latest, opts.current ?? AURA_VERSION) ? latest : null;
|
|
24576
25022
|
}
|
|
24577
25023
|
function startUpdateCheck() {
|
|
24578
|
-
if (!process.stdout.isTTY ||
|
|
25024
|
+
if (!process.stdout.isTTY || inCI() || runningAgent() || process.env.AURA_NO_UPDATE_CHECK) return Promise.resolve(null);
|
|
24579
25025
|
return newerVersion().catch(() => null);
|
|
24580
25026
|
}
|
|
24581
25027
|
function updateNotice(latest, argv1 = process.argv[1] ?? "") {
|
|
@@ -24601,6 +25047,12 @@ function action(fn) {
|
|
|
24601
25047
|
say.error(err.message);
|
|
24602
25048
|
if (process.env.AURA_DEBUG) console.error(err);
|
|
24603
25049
|
process.exitCode = 1;
|
|
25050
|
+
const command = args.at(-1) instanceof Command ? args.at(-1).name() : "unknown";
|
|
25051
|
+
try {
|
|
25052
|
+
const root = await findRepoRoot();
|
|
25053
|
+
await recordUsage(root, loadConfig(root), { event: "error", command, errorType: errorKind(err) });
|
|
25054
|
+
} catch {
|
|
25055
|
+
}
|
|
24604
25056
|
}
|
|
24605
25057
|
const latest = await update;
|
|
24606
25058
|
if (latest) console.error(pc4.dim(`
|