getaura 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/dist/index.js +451 -53
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/plugin/skills/pr/SKILL.md +18 -0
- package/templates/skills/aura/SKILL.md +10 -0
package/dist/index.js
CHANGED
|
@@ -978,10 +978,10 @@ function mergeDefs(...defs) {
|
|
|
978
978
|
function cloneDef(schema) {
|
|
979
979
|
return mergeDefs(schema._zod.def);
|
|
980
980
|
}
|
|
981
|
-
function getElementAtPath(obj,
|
|
982
|
-
if (!
|
|
981
|
+
function getElementAtPath(obj, path52) {
|
|
982
|
+
if (!path52)
|
|
983
983
|
return obj;
|
|
984
|
-
return
|
|
984
|
+
return path52.reduce((acc, key) => acc?.[key], obj);
|
|
985
985
|
}
|
|
986
986
|
function promiseAllObject(promisesObj) {
|
|
987
987
|
const keys = Object.keys(promisesObj);
|
|
@@ -1321,11 +1321,11 @@ function explicitlyAborted(x, startIndex = 0) {
|
|
|
1321
1321
|
}
|
|
1322
1322
|
return false;
|
|
1323
1323
|
}
|
|
1324
|
-
function prefixIssues(
|
|
1324
|
+
function prefixIssues(path52, issues) {
|
|
1325
1325
|
return issues.map((iss) => {
|
|
1326
1326
|
var _a3;
|
|
1327
1327
|
(_a3 = iss).path ?? (_a3.path = []);
|
|
1328
|
-
iss.path.unshift(
|
|
1328
|
+
iss.path.unshift(path52);
|
|
1329
1329
|
return iss;
|
|
1330
1330
|
});
|
|
1331
1331
|
}
|
|
@@ -1775,16 +1775,16 @@ function flattenError(error62, mapper = (issue2) => issue2.message) {
|
|
|
1775
1775
|
}
|
|
1776
1776
|
function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
1777
1777
|
const fieldErrors = { _errors: [] };
|
|
1778
|
-
const processError = (error63,
|
|
1778
|
+
const processError = (error63, path52 = []) => {
|
|
1779
1779
|
for (const issue2 of error63.issues) {
|
|
1780
1780
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
1781
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
1781
|
+
issue2.errors.map((issues) => processError({ issues }, [...path52, ...issue2.path]));
|
|
1782
1782
|
} else if (issue2.code === "invalid_key") {
|
|
1783
|
-
processError({ issues: issue2.issues }, [...
|
|
1783
|
+
processError({ issues: issue2.issues }, [...path52, ...issue2.path]);
|
|
1784
1784
|
} else if (issue2.code === "invalid_element") {
|
|
1785
|
-
processError({ issues: issue2.issues }, [...
|
|
1785
|
+
processError({ issues: issue2.issues }, [...path52, ...issue2.path]);
|
|
1786
1786
|
} else {
|
|
1787
|
-
const fullpath = [...
|
|
1787
|
+
const fullpath = [...path52, ...issue2.path];
|
|
1788
1788
|
if (fullpath.length === 0) {
|
|
1789
1789
|
fieldErrors._errors.push(mapper(issue2));
|
|
1790
1790
|
} else {
|
|
@@ -1823,17 +1823,17 @@ function formatError(error62, mapper = (issue2) => issue2.message) {
|
|
|
1823
1823
|
}
|
|
1824
1824
|
function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
1825
1825
|
const result = { errors: [] };
|
|
1826
|
-
const processError = (error63,
|
|
1826
|
+
const processError = (error63, path52 = []) => {
|
|
1827
1827
|
var _a3;
|
|
1828
1828
|
for (const issue2 of error63.issues) {
|
|
1829
1829
|
if (issue2.code === "invalid_union" && issue2.errors.length) {
|
|
1830
|
-
issue2.errors.map((issues) => processError({ issues }, [...
|
|
1830
|
+
issue2.errors.map((issues) => processError({ issues }, [...path52, ...issue2.path]));
|
|
1831
1831
|
} else if (issue2.code === "invalid_key") {
|
|
1832
|
-
processError({ issues: issue2.issues }, [...
|
|
1832
|
+
processError({ issues: issue2.issues }, [...path52, ...issue2.path]);
|
|
1833
1833
|
} else if (issue2.code === "invalid_element") {
|
|
1834
|
-
processError({ issues: issue2.issues }, [...
|
|
1834
|
+
processError({ issues: issue2.issues }, [...path52, ...issue2.path]);
|
|
1835
1835
|
} else {
|
|
1836
|
-
const fullpath = [...
|
|
1836
|
+
const fullpath = [...path52, ...issue2.path];
|
|
1837
1837
|
if (fullpath.length === 0) {
|
|
1838
1838
|
result.errors.push(mapper(issue2));
|
|
1839
1839
|
continue;
|
|
@@ -1872,8 +1872,8 @@ function treeifyError(error62, mapper = (issue2) => issue2.message) {
|
|
|
1872
1872
|
}
|
|
1873
1873
|
function toDotPath(_path) {
|
|
1874
1874
|
const segs = [];
|
|
1875
|
-
const
|
|
1876
|
-
for (const seg of
|
|
1875
|
+
const path52 = _path.map((seg) => typeof seg === "object" ? seg.key : seg);
|
|
1876
|
+
for (const seg of path52) {
|
|
1877
1877
|
if (typeof seg === "number")
|
|
1878
1878
|
segs.push(`[${seg}]`);
|
|
1879
1879
|
else if (typeof seg === "symbol")
|
|
@@ -18975,13 +18975,13 @@ function resolveRef(ref, ctx) {
|
|
|
18975
18975
|
if (!ref.startsWith("#")) {
|
|
18976
18976
|
throw new Error("External $ref is not supported, only local refs (#/...) are allowed");
|
|
18977
18977
|
}
|
|
18978
|
-
const
|
|
18979
|
-
if (
|
|
18978
|
+
const path52 = ref.slice(1).split("/").filter(Boolean);
|
|
18979
|
+
if (path52.length === 0) {
|
|
18980
18980
|
return ctx.rootSchema;
|
|
18981
18981
|
}
|
|
18982
18982
|
const defsKey = ctx.version === "draft-2020-12" ? "$defs" : "definitions";
|
|
18983
|
-
if (
|
|
18984
|
-
const key =
|
|
18983
|
+
if (path52[0] === defsKey) {
|
|
18984
|
+
const key = path52[1] === void 0 ? void 0 : decodeJSONPointerSegment(path52[1]);
|
|
18985
18985
|
if (!key || !ctx.defs[key]) {
|
|
18986
18986
|
throw new Error(`Reference not found: ${ref}`);
|
|
18987
18987
|
}
|
|
@@ -20931,7 +20931,7 @@ async function ghStatus(root) {
|
|
|
20931
20931
|
const remote = await git(root, "remote", "get-url", "origin");
|
|
20932
20932
|
return { installed: true, authed: auth.code === 0, hasRemote: remote.code === 0 && /github\.com/.test(remote.stdout) };
|
|
20933
20933
|
}
|
|
20934
|
-
var AURA_OWNED = /^(?:\.aura\/|\.claude\/skills\/aura\/|\.agents\/skills\/aura(?:-(?:check|scan|next|fix|setup))?\/|\.cursor\/commands\/aura-[a-z]+\.md$|\.pi\/prompts\/aura-[a-z]+\.md$|\.claude\/settings\.json$)/;
|
|
20934
|
+
var AURA_OWNED = /^(?:\.aura\/|\.claude\/skills\/aura\/|\.agents\/skills\/aura(?:-(?:check|scan|next|fix|setup|pr))?\/|\.cursor\/commands\/aura-[a-z]+\.md$|\.pi\/prompts\/aura-[a-z]+\.md$|\.claude\/settings\.json$)/;
|
|
20935
20935
|
async function dirtyFiles(root) {
|
|
20936
20936
|
const r = await git(root, "status", "--porcelain", "-uall");
|
|
20937
20937
|
return r.stdout.split("\n").filter(Boolean).map((l) => l.slice(3).replace(/^"|"$/g, "").split(" -> ").pop() ?? "").filter((f) => !AURA_OWNED.test(f) && !isLocalOnly(f));
|
|
@@ -21076,11 +21076,11 @@ var github = {
|
|
|
21076
21076
|
isPrivate: Boolean(info.isPrivate),
|
|
21077
21077
|
defaultBranch: info.defaultBranchRef?.name || "main",
|
|
21078
21078
|
gh,
|
|
21079
|
-
get: (
|
|
21080
|
-
let result = cache3.get(
|
|
21079
|
+
get: (path52) => {
|
|
21080
|
+
let result = cache3.get(path52);
|
|
21081
21081
|
if (!result) {
|
|
21082
|
-
result = gh(["api",
|
|
21083
|
-
cache3.set(
|
|
21082
|
+
result = gh(["api", path52]);
|
|
21083
|
+
cache3.set(path52, result);
|
|
21084
21084
|
}
|
|
21085
21085
|
return result;
|
|
21086
21086
|
}
|
|
@@ -22244,8 +22244,8 @@ function applyOverride(profile, o) {
|
|
|
22244
22244
|
profile.evidence = ["set in .aura/config.json", ...profile.evidence];
|
|
22245
22245
|
}
|
|
22246
22246
|
}
|
|
22247
|
-
function setKindOverride(overrides = [],
|
|
22248
|
-
const at = normPath(
|
|
22247
|
+
function setKindOverride(overrides = [], path52, kind) {
|
|
22248
|
+
const at = normPath(path52);
|
|
22249
22249
|
const rest = overrides.filter((o) => normPath(o.path) !== at);
|
|
22250
22250
|
const existing = overrides.find((o) => normPath(o.path) === at);
|
|
22251
22251
|
const next = { ...existing, path: at, kind };
|
|
@@ -22284,7 +22284,7 @@ function describeProject(p2) {
|
|
|
22284
22284
|
// package.json
|
|
22285
22285
|
var package_default = {
|
|
22286
22286
|
name: "getaura",
|
|
22287
|
-
version: "0.
|
|
22287
|
+
version: "0.4.0",
|
|
22288
22288
|
description: "Aura helps you and your coding agent follow best practices while you vibe code.",
|
|
22289
22289
|
type: "module",
|
|
22290
22290
|
bin: {
|
|
@@ -22381,11 +22381,11 @@ function briefSummary(root, pkg2) {
|
|
|
22381
22381
|
const file2 = auraPath(root, "brief.md");
|
|
22382
22382
|
if (!existsSync7(file2)) return { name: fallbackName, summary: pkg2?.description ?? "See `.aura/brief.md` once the product brief is added." };
|
|
22383
22383
|
const text = readFileSync5(file2, "utf8");
|
|
22384
|
-
const
|
|
22384
|
+
const title2 = text.match(/^#\s+(.+)$/m)?.[1]?.replace(/[:\-–—]?\s*product brief$/i, "").trim();
|
|
22385
22385
|
const paragraph = text.split(/\n\s*\n/).map((p2) => p2.trim()).find((p2) => p2 && !p2.startsWith("#") && !p2.startsWith(">") && !p2.startsWith("-") && !p2.startsWith("|") && p2.length > 40) ?? "";
|
|
22386
22386
|
const sentences = paragraph.replace(/\s+/g, " ").match(/[^.!?]+[.!?]+/g) ?? [paragraph];
|
|
22387
22387
|
return {
|
|
22388
|
-
name:
|
|
22388
|
+
name: title2 || fallbackName,
|
|
22389
22389
|
summary: sentences.slice(0, 3).map((x) => x.trim()).join(" ")
|
|
22390
22390
|
};
|
|
22391
22391
|
}
|
|
@@ -23568,7 +23568,7 @@ function unreachableFiles(files, read2, pkgScripts) {
|
|
|
23568
23568
|
// src/actions/agent-commands.ts
|
|
23569
23569
|
import { existsSync as existsSync11, readFileSync as readFileSync8 } from "fs";
|
|
23570
23570
|
import path15 from "path";
|
|
23571
|
-
var AURA_COMMANDS = ["check", "next", "fix", "setup"];
|
|
23571
|
+
var AURA_COMMANDS = ["check", "next", "fix", "setup", "pr"];
|
|
23572
23572
|
var RETIRED_COMMANDS = ["scan"];
|
|
23573
23573
|
function invocation(agent, command) {
|
|
23574
23574
|
return agent === "claude" ? `/aura:${command}` : agent === "codex" ? `$aura-${command}` : `/aura-${command}`;
|
|
@@ -23581,7 +23581,7 @@ function pluginCommand(command) {
|
|
|
23581
23581
|
}
|
|
23582
23582
|
function adapt(agent, body) {
|
|
23583
23583
|
const args = agent === "pi" ? "Fix: $@\n\n" : "Fix the action or finding the user named after the command, if any.\n\n";
|
|
23584
|
-
return body.replace(/\/aura:(check|next|fix|setup)\b/g, (_, c) => invocation(agent, c)).replace("Fix: $ARGUMENTS\n\n", args).replace("--agent claude", `--agent ${agent}`);
|
|
23584
|
+
return body.replace(/\/aura:(check|next|fix|setup|pr)\b/g, (_, c) => invocation(agent, c)).replace("Fix: $ARGUMENTS\n\n", args).replace("--agent claude", `--agent ${agent}`);
|
|
23585
23585
|
}
|
|
23586
23586
|
function commandFiles(agent, command) {
|
|
23587
23587
|
if (agent === "cursor") return [`.cursor/commands/aura-${command}.md`];
|
|
@@ -23676,8 +23676,8 @@ function agentSafetyChanges(root, agents2) {
|
|
|
23676
23676
|
}
|
|
23677
23677
|
function leftForYou(root, tracked, unreadable) {
|
|
23678
23678
|
const notes = dangerousSettings(root, tracked).map((d) => {
|
|
23679
|
-
const { title, detail } = describeDanger(d);
|
|
23680
|
-
return `- **${
|
|
23679
|
+
const { title: title2, detail } = describeDanger(d);
|
|
23680
|
+
return `- **${title2}.** ${detail} If you didn\u2019t mean to allow this, remove it from \`${d.file}\`.`;
|
|
23681
23681
|
});
|
|
23682
23682
|
const unhides = cursorEnvUnhides(readRepoFile(root, CURSOR_IGNORE));
|
|
23683
23683
|
if (unhides.length)
|
|
@@ -24829,7 +24829,7 @@ var AuraClient = class _AuraClient {
|
|
|
24829
24829
|
get hasKey() {
|
|
24830
24830
|
return Boolean(this.key);
|
|
24831
24831
|
}
|
|
24832
|
-
async request(method,
|
|
24832
|
+
async request(method, path52, body, timeoutMs = 6e4) {
|
|
24833
24833
|
if (!this.key) throw new AuraApiError("no_key", "No pilot key");
|
|
24834
24834
|
let payload;
|
|
24835
24835
|
if (body !== void 0) {
|
|
@@ -24839,7 +24839,7 @@ var AuraClient = class _AuraClient {
|
|
|
24839
24839
|
}
|
|
24840
24840
|
let res;
|
|
24841
24841
|
try {
|
|
24842
|
-
res = await fetch(`${this.baseUrl}/v1/${
|
|
24842
|
+
res = await fetch(`${this.baseUrl}/v1/${path52}`, {
|
|
24843
24843
|
method,
|
|
24844
24844
|
headers: { authorization: `Bearer ${this.key}`, "content-type": "application/json", "user-agent": `getaura/${AURA_VERSION}` },
|
|
24845
24845
|
body: payload,
|
|
@@ -28201,10 +28201,10 @@ var typecheck = {
|
|
|
28201
28201
|
applies: (ctx) => ctx.stack.typescript,
|
|
28202
28202
|
async observe(ctx) {
|
|
28203
28203
|
if (ctx.options.fast) return { facts: { outcome: "skipped-fast-mode" }, items: [] };
|
|
28204
|
-
const { existsSync:
|
|
28205
|
-
const
|
|
28206
|
-
const tsc =
|
|
28207
|
-
if (!
|
|
28204
|
+
const { existsSync: existsSync31 } = await import("fs");
|
|
28205
|
+
const path52 = await import("path");
|
|
28206
|
+
const tsc = path52.join(ctx.root, "node_modules", ".bin", process.platform === "win32" ? "tsc.cmd" : "tsc");
|
|
28207
|
+
if (!existsSync31(tsc)) return { facts: { outcome: "not-installed" }, items: [] };
|
|
28208
28208
|
const r = await run(tsc, ["--noEmit", "--pretty", "false"], { cwd: ctx.root, timeoutMs: 18e4 });
|
|
28209
28209
|
if (r.timedOut) return { facts: { outcome: "timed-out" }, items: [] };
|
|
28210
28210
|
if (r.code === 0) return { facts: { outcome: "ran" }, items: [], engine: "tsc" };
|
|
@@ -32299,14 +32299,14 @@ function observedResult(pending2, answer) {
|
|
|
32299
32299
|
const part = parts[f.part];
|
|
32300
32300
|
const item = part?.items[Number(f.ref.slice(1))];
|
|
32301
32301
|
const vars = item?.vars ?? {};
|
|
32302
|
-
const [
|
|
32303
|
-
if (!part || !item ||
|
|
32302
|
+
const [title2, detail, why] = [fill2(f.title, vars), fill2(f.detail, vars), fill2(f.why, vars)];
|
|
32303
|
+
if (!part || !item || title2 === null || detail === null || why === null) return { ...base2, status: "unknown", score: 0, summary: UPDATE, findings: [] };
|
|
32304
32304
|
const finding = {
|
|
32305
32305
|
id: item.id,
|
|
32306
32306
|
checkId: check2.id,
|
|
32307
32307
|
category: check2.category,
|
|
32308
32308
|
severity: f.severity,
|
|
32309
|
-
title,
|
|
32309
|
+
title: title2,
|
|
32310
32310
|
detail,
|
|
32311
32311
|
why,
|
|
32312
32312
|
locations: item.locations,
|
|
@@ -33508,7 +33508,7 @@ async function inventoryCommand(flags) {
|
|
|
33508
33508
|
inv.updatedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
33509
33509
|
writeAuraFile(root, "inventory.json", `${JSON.stringify(inv, null, 2)}
|
|
33510
33510
|
`);
|
|
33511
|
-
say.ok("Saved .aura/inventory.json.
|
|
33511
|
+
say.ok("Saved .aura/inventory.json. Run `aura pr` when you\u2019re done to put it in a pull request, so the record travels with the code.");
|
|
33512
33512
|
const personal = Object.entries(inv.services).filter(([, v]) => /@(?:gmail|yahoo|hotmail|outlook|icloud|proton)\./i.test(v.owner ?? ""));
|
|
33513
33513
|
if (personal.length)
|
|
33514
33514
|
say.info(
|
|
@@ -33760,9 +33760,9 @@ async function applyAction(actionId, flags, report) {
|
|
|
33760
33760
|
large = { plan: breakdown, chunk, file: file2 };
|
|
33761
33761
|
}
|
|
33762
33762
|
const pm = pmCommands(scan.stack.packageManager);
|
|
33763
|
-
const showChanges = (
|
|
33763
|
+
const showChanges = (title2, changes) => {
|
|
33764
33764
|
console.log(`
|
|
33765
|
-
${pc.bold(
|
|
33765
|
+
${pc.bold(title2)}`);
|
|
33766
33766
|
for (const c of changes.changes)
|
|
33767
33767
|
console.log(` ${c.content === null ? pc.red("delete") : existsSync25(path42.join(root, c.path)) ? pc.yellow("update") : pc.green("add ")} ${c.path}`);
|
|
33768
33768
|
if (changes.devDeps?.length) console.log(` ${pc.cyan("install")} ${changes.devDeps.join(", ")} ${pc.dim(`(${pm.addDev})`)}`);
|
|
@@ -34975,19 +34975,19 @@ async function summaryCommand(flags) {
|
|
|
34975
34975
|
say.warn(`${friendlyApiError(err)} Using the standard summary.`);
|
|
34976
34976
|
}
|
|
34977
34977
|
}
|
|
34978
|
-
const
|
|
34978
|
+
const title2 = `Aura weekly summary: ${since.toISOString().slice(0, 10)} to ${until.toISOString().slice(0, 10)}`;
|
|
34979
34979
|
const body = `${summary.trim()}
|
|
34980
34980
|
|
|
34981
34981
|
---
|
|
34982
34982
|
<sub>Posted by [Aura](https://github.com/JohnFazio1/aura). Run \`aura next\` for what to do next.</sub>
|
|
34983
34983
|
`;
|
|
34984
|
-
if (flags.json) printJson({ title, body, request: { ...req, product: void 0 } });
|
|
34984
|
+
if (flags.json) printJson({ title: title2, body, request: { ...req, product: void 0 } });
|
|
34985
34985
|
else console.log(`
|
|
34986
|
-
${
|
|
34986
|
+
${title2}
|
|
34987
34987
|
|
|
34988
34988
|
${body}`);
|
|
34989
34989
|
if (flags.post) {
|
|
34990
|
-
const r = await run("gh", ["issue", "create", "--title",
|
|
34990
|
+
const r = await run("gh", ["issue", "create", "--title", title2, "--body-file", "-"], { cwd: root, input: body });
|
|
34991
34991
|
if (r.code !== 0) {
|
|
34992
34992
|
say.error(`Couldn\u2019t post the summary as an issue: ${r.stderr.trim()}`);
|
|
34993
34993
|
return 1;
|
|
@@ -35149,9 +35149,406 @@ async function updateCommand(flags, deps2 = { fetchLatest, run, argv1: process.a
|
|
|
35149
35149
|
return 0;
|
|
35150
35150
|
}
|
|
35151
35151
|
|
|
35152
|
+
// src/commands/pr.ts
|
|
35153
|
+
import { existsSync as existsSync29, readFileSync as readFileSync31, statSync as statSync6 } from "fs";
|
|
35154
|
+
import path51 from "path";
|
|
35155
|
+
|
|
35156
|
+
// src/actions/pr-plan.ts
|
|
35157
|
+
var PR_MAX_LINES = 400;
|
|
35158
|
+
var PR_MAX_FILES = 25;
|
|
35159
|
+
var MAX_FILE_BYTES = 10 * 1024 * 1024;
|
|
35160
|
+
var LEFT_OUT_REASONS = {
|
|
35161
|
+
local: "made by a tool on your computer",
|
|
35162
|
+
"aura-local": "Aura\u2019s local notes; they can describe security weaknesses",
|
|
35163
|
+
env: "may hold secrets; keep it out of git",
|
|
35164
|
+
"too-big": "over 10 MB; large files don\u2019t belong in git"
|
|
35165
|
+
};
|
|
35166
|
+
var AURA_LOCAL = /^\.aura\/(?:report\.md|scan\.json|history\.json|fixes\.json|cache\/|tasks\/|plans\/)/;
|
|
35167
|
+
var ENV_FILE = /(?:^|\/)\.env(?:\.[^/]+)?$/;
|
|
35168
|
+
var ENV_EXAMPLE2 = /\.(?:example|sample|template)$/;
|
|
35169
|
+
var LOCKFILE = /(?:^|\/)(?:package-lock\.json|pnpm-lock\.yaml|yarn\.lock|bun\.lockb?|npm-shrinkwrap\.json)$/;
|
|
35170
|
+
var MANIFEST = /(?:^|\/)package\.json$/;
|
|
35171
|
+
var AGENT_SETUP2 = /(?:^|\/)(?:CLAUDE\.md|AGENTS\.md|\.cursorrules|\.mcp\.json)$|^(?:\.claude|\.agents|\.cursor|\.pi|\.codex)\/|^\.github\/copilot-instructions\.md$/;
|
|
35172
|
+
var CI = /^(?:\.github|\.husky)\//;
|
|
35173
|
+
var DATABASE = /(?:^|\/)supabase\//;
|
|
35174
|
+
var DOCS = /^(?:docs\/.*|[^/]+\.mdx?)$/;
|
|
35175
|
+
function leftOutReason(file2) {
|
|
35176
|
+
if (AURA_LOCAL.test(file2.path)) return "aura-local";
|
|
35177
|
+
if (isLocalOnly(file2.path)) return "local";
|
|
35178
|
+
if (ENV_FILE.test(file2.path) && !ENV_EXAMPLE2.test(file2.path)) return "env";
|
|
35179
|
+
if (file2.bytes > MAX_FILE_BYTES) return "too-big";
|
|
35180
|
+
return null;
|
|
35181
|
+
}
|
|
35182
|
+
function kindOf(file2) {
|
|
35183
|
+
if (file2 === ".aura/brief.md") return "docs";
|
|
35184
|
+
if (file2.startsWith(".aura/")) return "aura-records";
|
|
35185
|
+
if (AGENT_SETUP2.test(file2)) return "agent-setup";
|
|
35186
|
+
if (CI.test(file2)) return "ci";
|
|
35187
|
+
if (DATABASE.test(file2)) return "database";
|
|
35188
|
+
if (LOCKFILE.test(file2) || MANIFEST.test(file2)) return "dependencies";
|
|
35189
|
+
if (DOCS.test(file2)) return "docs";
|
|
35190
|
+
return "code";
|
|
35191
|
+
}
|
|
35192
|
+
var ORDER2 = ["database", "dependencies", "code", "docs", "agent-setup", "ci", "aura-records"];
|
|
35193
|
+
var WHY = {
|
|
35194
|
+
database: "Database changes are reviewed on their own and merged first, so the code that needs them never runs without them.",
|
|
35195
|
+
dependencies: "Package updates are kept apart from feature work, so a problem can be traced to one or the other.",
|
|
35196
|
+
code: "One area of the code per pull request keeps each review focused.",
|
|
35197
|
+
docs: "Documentation only; nothing about how the app runs changes.",
|
|
35198
|
+
"agent-setup": "Rules and skills for coding agents change how future work is done, so they get their own review.",
|
|
35199
|
+
ci: "Automated checks and GitHub settings protect every future change, so they get their own review.",
|
|
35200
|
+
"aura-records": "Aura\u2019s records describe the project, not its code, so they never mix with code changes."
|
|
35201
|
+
};
|
|
35202
|
+
var projectName = (project) => project === "." ? "the repo root" : project;
|
|
35203
|
+
function title(kind, files, projects) {
|
|
35204
|
+
if (kind === "aura-records") {
|
|
35205
|
+
const parts = [
|
|
35206
|
+
files.some((f) => f.endsWith("inventory.json")) && "who owns each account",
|
|
35207
|
+
files.some((f) => f.endsWith("config.json")) && "Aura settings"
|
|
35208
|
+
].filter(Boolean);
|
|
35209
|
+
return parts.length ? `Aura: update ${parts.join(", ")}` : "Aura: update Aura\u2019s records";
|
|
35210
|
+
}
|
|
35211
|
+
if (kind === "code") return projects.length === 1 && projects[0] === "." ? "Code changes" : `Code changes in ${projects.map(projectName).join(", ")}`;
|
|
35212
|
+
return {
|
|
35213
|
+
database: "Database changes",
|
|
35214
|
+
dependencies: "Update dependencies",
|
|
35215
|
+
docs: "Update documentation",
|
|
35216
|
+
"agent-setup": "Update coding agent rules and skills",
|
|
35217
|
+
ci: "Update automated checks"
|
|
35218
|
+
}[kind];
|
|
35219
|
+
}
|
|
35220
|
+
function planPrs(changed) {
|
|
35221
|
+
const leftOut = [];
|
|
35222
|
+
const keep = [];
|
|
35223
|
+
for (const f of changed) {
|
|
35224
|
+
const reason = leftOutReason(f);
|
|
35225
|
+
if (reason) leftOut.push({ path: f.path, reason });
|
|
35226
|
+
else keep.push(f);
|
|
35227
|
+
}
|
|
35228
|
+
const drafts = /* @__PURE__ */ new Map();
|
|
35229
|
+
const keyOf2 = (kind, project) => kind === "code" ? `code:${project}` : kind;
|
|
35230
|
+
const add = (key, kind, f) => {
|
|
35231
|
+
const d = drafts.get(key) ?? { kind, projects: [], files: [], joined: [] };
|
|
35232
|
+
if (!d.projects.includes(f.project)) d.projects.push(f.project);
|
|
35233
|
+
d.files.push(f);
|
|
35234
|
+
drafts.set(key, d);
|
|
35235
|
+
};
|
|
35236
|
+
for (const f of keep) {
|
|
35237
|
+
const kind = kindOf(f.path);
|
|
35238
|
+
add(keyOf2(kind, f.project), kind, f);
|
|
35239
|
+
}
|
|
35240
|
+
const codeKeys = () => [...drafts.keys()].filter((k) => k.startsWith("code:"));
|
|
35241
|
+
const merge2 = (from, into, reason) => {
|
|
35242
|
+
const src = drafts.get(from);
|
|
35243
|
+
const dst = drafts.get(into);
|
|
35244
|
+
if (!src || !dst || from === into) return;
|
|
35245
|
+
dst.files.push(...src.files);
|
|
35246
|
+
if (src.kind === "code") {
|
|
35247
|
+
for (const p2 of src.projects) if (!dst.projects.includes(p2)) dst.projects.push(p2);
|
|
35248
|
+
}
|
|
35249
|
+
dst.joined.push(reason, ...src.joined);
|
|
35250
|
+
drafts.delete(from);
|
|
35251
|
+
};
|
|
35252
|
+
const deps2 = drafts.get("dependencies");
|
|
35253
|
+
if (deps2) {
|
|
35254
|
+
const targets2 = /* @__PURE__ */ new Set();
|
|
35255
|
+
for (const f of deps2.files.filter((x) => MANIFEST.test(x.path))) {
|
|
35256
|
+
const key = `code:${f.project}`;
|
|
35257
|
+
if (!drafts.has(key)) continue;
|
|
35258
|
+
deps2.files = deps2.files.filter((x) => x !== f);
|
|
35259
|
+
drafts.get(key)?.files.push(f);
|
|
35260
|
+
targets2.add(key);
|
|
35261
|
+
}
|
|
35262
|
+
const [first, ...rest] = [...targets2];
|
|
35263
|
+
if (first && deps2.files.some((x) => LOCKFILE.test(x.path))) {
|
|
35264
|
+
for (const other of rest) merge2(other, first, "These projects share one lockfile, so their package changes must merge together.");
|
|
35265
|
+
merge2("dependencies", first, "The lockfile goes with the package.json files it records.");
|
|
35266
|
+
} else if (!deps2.files.length) drafts.delete("dependencies");
|
|
35267
|
+
}
|
|
35268
|
+
const [onlyCode, ...moreCode] = codeKeys();
|
|
35269
|
+
if (onlyCode && moreCode.length === 0) {
|
|
35270
|
+
merge2("docs", onlyCode, "Documentation is updated in the same change as the behaviour it describes.");
|
|
35271
|
+
merge2("database", onlyCode, "The database change and the code that uses it need each other.");
|
|
35272
|
+
}
|
|
35273
|
+
const groups = ORDER2.flatMap(
|
|
35274
|
+
(kind) => [...drafts.entries()].filter(([, d]) => d.kind === kind).sort(([a], [b]) => a.localeCompare(b)).map(([key, d]) => {
|
|
35275
|
+
const files = [...new Set(d.files.flatMap((f) => f.from ? [f.from, f.path] : [f.path]))].sort();
|
|
35276
|
+
const lines = d.files.filter((f) => !LOCKFILE.test(f.path)).reduce((n, f) => n + f.lines, 0);
|
|
35277
|
+
const project = key.startsWith("code:") && key !== "code:." ? `-${key.slice("code:".length)}` : "";
|
|
35278
|
+
return {
|
|
35279
|
+
id: `${d.kind}${project}`.replace(/[^a-z0-9-]+/gi, "-").toLowerCase(),
|
|
35280
|
+
kind: d.kind,
|
|
35281
|
+
title: title(d.kind, files, d.projects.sort()),
|
|
35282
|
+
why: [WHY[d.kind], ...new Set(d.joined)].join(" "),
|
|
35283
|
+
files,
|
|
35284
|
+
lines,
|
|
35285
|
+
large: lines > PR_MAX_LINES || files.length > PR_MAX_FILES
|
|
35286
|
+
};
|
|
35287
|
+
})
|
|
35288
|
+
);
|
|
35289
|
+
return { groups, leftOut: leftOut.sort((a, b) => a.path.localeCompare(b.path)) };
|
|
35290
|
+
}
|
|
35291
|
+
function prBody(group, note) {
|
|
35292
|
+
const check2 = note?.check?.length ? note.check : CHECKS[group.kind];
|
|
35293
|
+
return [
|
|
35294
|
+
note?.summary ? `## What changed
|
|
35295
|
+
|
|
35296
|
+
${note.summary}
|
|
35297
|
+
` : `## What changed
|
|
35298
|
+
|
|
35299
|
+
${group.files.map((f) => `- \`${f}\``).join("\n")}
|
|
35300
|
+
`,
|
|
35301
|
+
`## Why this is its own pull request
|
|
35302
|
+
|
|
35303
|
+
${group.why}
|
|
35304
|
+
`,
|
|
35305
|
+
`## Before merging
|
|
35306
|
+
|
|
35307
|
+
${check2.map((c) => `- [ ] ${c}`).join("\n")}
|
|
35308
|
+
`,
|
|
35309
|
+
...note?.summary ? [`<details><summary>Files</summary>
|
|
35310
|
+
|
|
35311
|
+
${group.files.map((f) => `- \`${f}\``).join("\n")}
|
|
35312
|
+
</details>
|
|
35313
|
+
`] : [],
|
|
35314
|
+
group.large ? `> This pull request is large (${group.lines} lines in ${group.files.length} files). Large pull requests are harder to review; split it if you can.
|
|
35315
|
+
` : "",
|
|
35316
|
+
"Opened by Aura (https://github.com/JohnFazio1/aura)."
|
|
35317
|
+
].filter(Boolean).join("\n");
|
|
35318
|
+
}
|
|
35319
|
+
var CHECKS = {
|
|
35320
|
+
database: [
|
|
35321
|
+
"Every new table turns on row level security and has policies.",
|
|
35322
|
+
"No migration that was already applied has been edited.",
|
|
35323
|
+
"Merge this before the pull requests that use it."
|
|
35324
|
+
],
|
|
35325
|
+
dependencies: ["The automated checks pass.", "Each new package is one you meant to add."],
|
|
35326
|
+
code: ["The automated checks pass.", "Try the changed pages or features in the preview deployment.", "New behaviour has tests."],
|
|
35327
|
+
docs: ["The instructions match how the app works now."],
|
|
35328
|
+
"agent-setup": ["The rules describe how you want your coding agent to work."],
|
|
35329
|
+
ci: ["The automated checks pass on this pull request.", "No secret values are written in the workflow files."],
|
|
35330
|
+
"aura-records": ["Accounts are owned by the company, not a personal or freelancer email."]
|
|
35331
|
+
};
|
|
35332
|
+
|
|
35333
|
+
// src/commands/pr.ts
|
|
35334
|
+
var Notes = external_exports.record(
|
|
35335
|
+
external_exports.string(),
|
|
35336
|
+
external_exports.object({ title: external_exports.string().min(1).max(120).optional(), summary: external_exports.string().max(4e3).optional(), check: external_exports.array(external_exports.string().max(300)).max(10).optional() })
|
|
35337
|
+
);
|
|
35338
|
+
function parseStatus(out) {
|
|
35339
|
+
const parts = out.split("\0");
|
|
35340
|
+
const entries = [];
|
|
35341
|
+
for (let i = 0; i < parts.length; i++) {
|
|
35342
|
+
const entry = parts[i] ?? "";
|
|
35343
|
+
if (entry.length < 4) continue;
|
|
35344
|
+
const xy = entry.slice(0, 2);
|
|
35345
|
+
const file2 = entry.slice(3);
|
|
35346
|
+
if (/[RC]/.test(xy)) entries.push({ path: file2, from: parts[++i] });
|
|
35347
|
+
else entries.push({ path: file2 });
|
|
35348
|
+
}
|
|
35349
|
+
return entries;
|
|
35350
|
+
}
|
|
35351
|
+
function projectOf(root, file2, exists2 = (rel) => existsSync29(path51.join(root, rel))) {
|
|
35352
|
+
const parts = file2.split("/").slice(0, -1);
|
|
35353
|
+
for (let n = parts.length; n > 0; n--) {
|
|
35354
|
+
const dir = parts.slice(0, n).join("/");
|
|
35355
|
+
if (exists2(`${dir}/package.json`)) return dir;
|
|
35356
|
+
}
|
|
35357
|
+
return ".";
|
|
35358
|
+
}
|
|
35359
|
+
async function changedFiles(root) {
|
|
35360
|
+
const status = await git(root, "status", "--porcelain=v1", "-z", "-uall");
|
|
35361
|
+
const numstat = await git(root, "diff", "--numstat", "-z", "--no-renames", "HEAD");
|
|
35362
|
+
const lines = /* @__PURE__ */ new Map();
|
|
35363
|
+
for (const rec of numstat.stdout.split("\0")) {
|
|
35364
|
+
const [added, removed, file2] = rec.split(" ");
|
|
35365
|
+
if (file2) lines.set(file2, (Number(added) || 0) + (Number(removed) || 0));
|
|
35366
|
+
}
|
|
35367
|
+
return parseStatus(status.stdout).map(({ path: file2, from }) => {
|
|
35368
|
+
const full = path51.join(root, file2);
|
|
35369
|
+
const bytes = existsSync29(full) ? statSync6(full).size : 0;
|
|
35370
|
+
let count3 = lines.get(file2);
|
|
35371
|
+
if (count3 === void 0 && bytes && bytes < 2e6) {
|
|
35372
|
+
const text = readFileSync31(full, "utf8");
|
|
35373
|
+
count3 = text.includes("\0") ? 0 : text.split("\n").length;
|
|
35374
|
+
}
|
|
35375
|
+
return { path: file2, ...from ? { from } : {}, lines: count3 ?? 0, bytes, project: projectOf(root, file2) };
|
|
35376
|
+
});
|
|
35377
|
+
}
|
|
35378
|
+
async function filesWithSecrets(root, files, ignores) {
|
|
35379
|
+
const hits = [];
|
|
35380
|
+
for (const file2 of files) {
|
|
35381
|
+
if (ignores.some((re) => re.test(file2))) continue;
|
|
35382
|
+
const full = path51.join(root, file2);
|
|
35383
|
+
if (!existsSync29(full) || statSync6(full).size > 2e6) continue;
|
|
35384
|
+
const tracked = (await git(root, "cat-file", "-e", `HEAD:${file2}`)).code === 0;
|
|
35385
|
+
const added = tracked ? (await git(root, "diff", "-U0", "--no-color", "HEAD", "--", file2)).stdout.split("\n").filter((l) => l.startsWith("+") && !l.startsWith("+++")).map((l) => l.slice(1)) : readFileSync31(full, "utf8").split("\n");
|
|
35386
|
+
if (added.some((l) => findSecrets(l).some((m) => m.confidence === "high"))) hits.push(file2);
|
|
35387
|
+
}
|
|
35388
|
+
return hits;
|
|
35389
|
+
}
|
|
35390
|
+
function loadNotes(root, file2) {
|
|
35391
|
+
if (!file2) return {};
|
|
35392
|
+
const full = path51.resolve(root, file2);
|
|
35393
|
+
if (!isInside(root, full) || !existsSync29(full)) return `Couldn\u2019t find the notes file ${file2}.`;
|
|
35394
|
+
try {
|
|
35395
|
+
const parsed = Notes.safeParse(JSON.parse(readFileSync31(full, "utf8")));
|
|
35396
|
+
return parsed.success ? parsed.data : `The notes file ${file2} isn\u2019t in the expected shape: { "<group id>": { "title", "summary", "check": [] } }.`;
|
|
35397
|
+
} catch {
|
|
35398
|
+
return `The notes file ${file2} isn\u2019t valid JSON.`;
|
|
35399
|
+
}
|
|
35400
|
+
}
|
|
35401
|
+
function showPlan(plan, held, base2, notes) {
|
|
35402
|
+
if (!plan.groups.length) say.info("\nNothing to put in a pull request: no uncommitted changes.");
|
|
35403
|
+
else console.log(pc.bold(`
|
|
35404
|
+
${plan.groups.length} pull request${plan.groups.length === 1 ? "" : "s"} into ${base2}, in the order to merge them
|
|
35405
|
+
`));
|
|
35406
|
+
plan.groups.forEach((g, i) => {
|
|
35407
|
+
const hold = held.get(g.id);
|
|
35408
|
+
console.log(
|
|
35409
|
+
` ${i + 1}. ${pc.bold(notes[g.id]?.title ?? g.title)} ${pc.dim(`(${g.id}, ${g.files.length} file${g.files.length === 1 ? "" : "s"}, ${g.lines} lines)`)}`
|
|
35410
|
+
);
|
|
35411
|
+
console.log(pc.dim(` ${g.why}`));
|
|
35412
|
+
for (const f of g.files.slice(0, 8)) console.log(` ${f}`);
|
|
35413
|
+
if (g.files.length > 8) console.log(pc.dim(` \u2026and ${g.files.length - 8} more`));
|
|
35414
|
+
if (g.large) console.log(pc.yellow(" Large: reviewers miss more in big pull requests. Split it if you can."));
|
|
35415
|
+
if (hold) console.log(pc.red(` Held back: ${hold.join(", ")} ${hold.length === 1 ? "contains" : "contain"} a secret. Move it into .env.local first.`));
|
|
35416
|
+
});
|
|
35417
|
+
if (plan.leftOut.length) {
|
|
35418
|
+
console.log(pc.bold("\n Left out (they stay on your computer)\n"));
|
|
35419
|
+
for (const l of plan.leftOut) console.log(` ${l.path} ${pc.dim(`(${LEFT_OUT_REASONS[l.reason]})`)}`);
|
|
35420
|
+
}
|
|
35421
|
+
}
|
|
35422
|
+
async function prCommand(flags) {
|
|
35423
|
+
const root = await findRepoRoot();
|
|
35424
|
+
if ((await git(root, "rev-parse", "--verify", "--quiet", "HEAD")).code !== 0) {
|
|
35425
|
+
say.error("This repo has no commits yet. Make a first commit, push it to GitHub, then run aura pr.");
|
|
35426
|
+
return 1;
|
|
35427
|
+
}
|
|
35428
|
+
const base2 = await currentBranch(root);
|
|
35429
|
+
if (base2 === "HEAD") {
|
|
35430
|
+
say.error("You\u2019re not on a branch. Switch to your main branch first: git switch main");
|
|
35431
|
+
return 1;
|
|
35432
|
+
}
|
|
35433
|
+
const notes = loadNotes(root, flags.notes);
|
|
35434
|
+
if (typeof notes === "string") {
|
|
35435
|
+
say.error(notes);
|
|
35436
|
+
return 1;
|
|
35437
|
+
}
|
|
35438
|
+
const plan = planPrs(await changedFiles(root));
|
|
35439
|
+
const only = flags.only?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
35440
|
+
const unknown2 = only?.filter((id) => !plan.groups.some((g) => g.id === id)) ?? [];
|
|
35441
|
+
if (unknown2.length) {
|
|
35442
|
+
say.error(`No pull request called ${unknown2.join(", ")}. Run aura pr --dry-run to see their ids.`);
|
|
35443
|
+
return 1;
|
|
35444
|
+
}
|
|
35445
|
+
if (only) plan.groups = plan.groups.filter((g) => only.includes(g.id));
|
|
35446
|
+
const secrets2 = new Set(
|
|
35447
|
+
await filesWithSecrets(
|
|
35448
|
+
root,
|
|
35449
|
+
plan.groups.flatMap((g) => g.files),
|
|
35450
|
+
pathIgnores(loadConfig(root))
|
|
35451
|
+
)
|
|
35452
|
+
);
|
|
35453
|
+
const held = new Map(plan.groups.map((g) => [g.id, g.files.filter((f) => secrets2.has(f))]).filter(([, f]) => f.length));
|
|
35454
|
+
const ready = plan.groups.filter((g) => !held.has(g.id));
|
|
35455
|
+
const ahead = await git(root, "rev-list", "--count", "@{u}..HEAD");
|
|
35456
|
+
const unpushed = ahead.code === 0 ? Number(ahead.stdout.trim()) : 0;
|
|
35457
|
+
const onRemote = ahead.code === 0 || (await git(root, "ls-remote", "--exit-code", "--heads", "origin", base2)).code === 0;
|
|
35458
|
+
const openPr = flags.pr !== false;
|
|
35459
|
+
const blocker = unpushed > 0 ? `${base2} has ${unpushed === 1 ? "1 commit that isn\u2019t" : `${unpushed} commits that aren\u2019t`} on GitHub yet, so ${unpushed === 1 ? "it" : "they"}\u2019d end up in every pull request. Push ${unpushed === 1 ? "it" : "them"} or open a pull request for ${unpushed === 1 ? "it" : "them"} first.` : openPr && !onRemote ? `${base2} isn\u2019t on GitHub yet, so there\u2019s nothing to open a pull request into. Push it first: git push -u origin ${base2}` : null;
|
|
35460
|
+
if (flags.json && (flags.dryRun || !flags.yes && !interactive())) {
|
|
35461
|
+
printJson({
|
|
35462
|
+
base: base2,
|
|
35463
|
+
groups: plan.groups.map((g) => ({ ...g, title: notes[g.id]?.title ?? g.title, heldBack: held.get(g.id) ?? [] })),
|
|
35464
|
+
leftOut: plan.leftOut,
|
|
35465
|
+
blocker
|
|
35466
|
+
});
|
|
35467
|
+
return blocker ? 1 : 0;
|
|
35468
|
+
}
|
|
35469
|
+
if (!flags.json) showPlan(plan, held, base2, notes);
|
|
35470
|
+
if (!ready.length) return held.size ? 1 : 0;
|
|
35471
|
+
if (blocker) {
|
|
35472
|
+
say.error(blocker);
|
|
35473
|
+
return 1;
|
|
35474
|
+
}
|
|
35475
|
+
if (flags.dryRun) {
|
|
35476
|
+
say.dim("\nDry run: nothing was changed.");
|
|
35477
|
+
return 0;
|
|
35478
|
+
}
|
|
35479
|
+
if (openPr) {
|
|
35480
|
+
const gh = await ghStatus(root);
|
|
35481
|
+
if (!gh.installed || !gh.authed || !gh.hasRemote) {
|
|
35482
|
+
say.error(
|
|
35483
|
+
!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."
|
|
35484
|
+
);
|
|
35485
|
+
say.dim("Or run with --no-pr to commit each group on a local branch.");
|
|
35486
|
+
return 1;
|
|
35487
|
+
}
|
|
35488
|
+
}
|
|
35489
|
+
if (!flags.yes) {
|
|
35490
|
+
if (!interactive()) {
|
|
35491
|
+
say.info("\nShow this plan to the user. When they agree, run the same command with --yes.");
|
|
35492
|
+
return 0;
|
|
35493
|
+
}
|
|
35494
|
+
say.dim(`
|
|
35495
|
+
Each group moves onto its own branch, so your computer goes back to ${base2} without them until they\u2019re merged.`);
|
|
35496
|
+
const n = ready.length;
|
|
35497
|
+
if (!unwrap(
|
|
35498
|
+
await p.confirm({
|
|
35499
|
+
message: openPr ? `Open ${n === 1 ? "this pull request" : `these ${n} pull requests`}?` : `Commit ${n === 1 ? "it" : `these ${n}`} on local branches?`
|
|
35500
|
+
})
|
|
35501
|
+
))
|
|
35502
|
+
return 0;
|
|
35503
|
+
}
|
|
35504
|
+
await git(root, "reset", "-q");
|
|
35505
|
+
const opened = [];
|
|
35506
|
+
let failed = null;
|
|
35507
|
+
for (const g of ready) {
|
|
35508
|
+
failed = await openOne(root, base2, g, notes, openPr, opened);
|
|
35509
|
+
if (failed) break;
|
|
35510
|
+
}
|
|
35511
|
+
if (await currentBranch(root) !== base2) await git(root, "switch", base2);
|
|
35512
|
+
if (flags.json) printJson({ base: base2, opened, heldBack: [...held.entries()].map(([id, files]) => ({ id, files })), error: failed });
|
|
35513
|
+
else {
|
|
35514
|
+
for (const o of opened) say.ok(o.url ? `Opened ${o.url}` : `Committed on branch ${o.branch}.`);
|
|
35515
|
+
if (failed) say.error(failed);
|
|
35516
|
+
if (opened.length > 1) say.info(`
|
|
35517
|
+
Merge them in the order above${ready[0]?.kind === "database" ? ", database changes first" : ""}.`);
|
|
35518
|
+
if (opened.length) say.dim(`When they\u2019re merged, run \`git pull\` to have the changes on your computer again, then \`aura check\`.`);
|
|
35519
|
+
}
|
|
35520
|
+
return failed ? 1 : 0;
|
|
35521
|
+
}
|
|
35522
|
+
async function openOne(root, base2, g, notes, openPr, opened) {
|
|
35523
|
+
const branch = await freeBranchName(root, `aura/${g.id}`);
|
|
35524
|
+
const sw = await git(root, "switch", "-c", branch);
|
|
35525
|
+
if (sw.code !== 0) return `Couldn\u2019t create branch ${branch}: ${sw.stderr.trim()}`;
|
|
35526
|
+
const add = await git(root, "add", "-A", "--", ...g.files);
|
|
35527
|
+
if (add.code !== 0) {
|
|
35528
|
+
await git(root, "reset", "-q");
|
|
35529
|
+
await git(root, "switch", base2);
|
|
35530
|
+
await git(root, "branch", "-D", branch);
|
|
35531
|
+
return `Couldn\u2019t add the files for \u201C${g.title}\u201D: ${add.stderr.trim()}`;
|
|
35532
|
+
}
|
|
35533
|
+
const note = notes[g.id];
|
|
35534
|
+
const result = await commitPushPr(root, { branch, base: base2, title: note?.title ?? g.title, body: prBody(g, note), openPr });
|
|
35535
|
+
await git(root, "switch", base2);
|
|
35536
|
+
if (!result.committed) {
|
|
35537
|
+
await git(root, "branch", "-D", branch);
|
|
35538
|
+
return `Couldn\u2019t commit \u201C${g.title}\u201D: ${result.error?.trim() ?? "unknown error"}`;
|
|
35539
|
+
}
|
|
35540
|
+
opened.push({ id: g.id, branch, url: result.url });
|
|
35541
|
+
if (result.error)
|
|
35542
|
+
return `Committed \u201C${g.title}\u201D on branch ${branch}, but couldn\u2019t ${result.pushed ? "open the pull request" : "push it"}: ${result.error.trim()}
|
|
35543
|
+
Finish by hand with these two commands:
|
|
35544
|
+
git push -u origin ${branch}
|
|
35545
|
+
gh pr create --head ${branch}`;
|
|
35546
|
+
return null;
|
|
35547
|
+
}
|
|
35548
|
+
|
|
35152
35549
|
// src/commands/simple.ts
|
|
35153
35550
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
35154
|
-
import { existsSync as
|
|
35551
|
+
import { existsSync as existsSync30 } from "fs";
|
|
35155
35552
|
async function nextCommand(flags) {
|
|
35156
35553
|
const root = await findRepoRoot();
|
|
35157
35554
|
const scan = await latestScan(root);
|
|
@@ -35210,7 +35607,7 @@ async function kindSetting(root, config2, value, project) {
|
|
|
35210
35607
|
const paths = profile.projects.map((p2) => p2.path);
|
|
35211
35608
|
const at = project ?? (paths.length === 1 ? paths[0] : void 0);
|
|
35212
35609
|
if (!at) return `This repo has ${paths.length} projects, so say which one, for example: aura config kind ${value} ${paths.find((p2) => p2 !== ".")}`;
|
|
35213
|
-
if (!
|
|
35610
|
+
if (!existsSync30(packageFolder(root, normDir(at)))) return `There is no folder "${at}" in this repo.`;
|
|
35214
35611
|
config2.projects = setKindOverride(config2.projects, at, kind);
|
|
35215
35612
|
return "saved";
|
|
35216
35613
|
}
|
|
@@ -35334,6 +35731,7 @@ program.command("check").alias("scan").description("Run every check, show the Au
|
|
|
35334
35731
|
program.command("next").description("Recommended next steps in priority order").option("--json", "print as JSON").option("--limit <n>", "how many to show").action(action((flags) => nextCommand(flags)));
|
|
35335
35732
|
program.command("apply").argument("<action>", "action id, e.g. foundation, add-ci, install-skills").description("Make a template change on a new branch and open a pull request").option("--dry-run", "show what would change without changing anything").option("--no-pr", "commit on a local branch without pushing or opening a PR").option("-y, --yes", "don\u2019t ask for confirmation (large changes still need a person\u2019s approval)").option("--approved", "a person read and approved the breakdown of this large change: do its next chunk").option("--skills <names>", "for install-skills: comma-separated skill names").action(action((id, flags) => applyCommand(id, flags)));
|
|
35336
35733
|
program.command("fix").argument("<target>", "finding id or action id, e.g. protect-api-routes").description("Aura\u2019s agent fixes it on a new branch and opens a PR for your approval").option("--agent", "hand the task to your own coding agent instead (writes .aura/tasks/<id>.md)").option("--dry-run", "show what would be fixed without starting the agent").option("--no-pr", "commit on a local branch without pushing or opening a PR").option("-y, --yes", "don\u2019t ask for confirmation (large changes still need a person\u2019s approval)").option("--approved", "a person read and approved the breakdown of this large change: do its next chunk").action(action((target, flags) => fixCommand(target, flags)));
|
|
35734
|
+
program.command("pr").description("End of a session: turn your uncommitted work into focused pull requests, one concern each").option("--dry-run", "show the pull requests Aura would open without changing anything").option("--json", "print the plan (and what was opened) as JSON").option("--only <ids>", "open only these pull requests (comma-separated ids from --dry-run)").option("--notes <file>", "JSON with a title, summary and checks per pull request id").option("--no-pr", "commit each group on a local branch without pushing or opening a PR").option("-y, --yes", "don\u2019t ask for confirmation (an agent passes it only after the user agreed to the plan)").action(action((flags) => prCommand(flags)));
|
|
35337
35735
|
program.command("inventory").description("Services and environment variables the app uses, and who owns each account").option("--json", "print as JSON").option("-y, --yes", "don\u2019t ask questions").action(action((flags) => inventoryCommand(flags)));
|
|
35338
35736
|
program.command("guide").argument("[topic]", "guide to show, e.g. rotate-stripe-key").description("Step-by-step guides for things outside the code").option("--list", "list all guides").action(action((topic, flags) => guideCommand(topic, flags), { key: false }));
|
|
35339
35737
|
program.command("history").description("Aura score trend from past scans").option("--json", "print as JSON").action(action((flags) => historyCommand(flags)));
|