@open-tomato/rafa 0.10.0 → 0.11.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 +4 -1
- package/dist/cli.js +1867 -1030
- package/dist/index.js +791 -26
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1895,7 +1895,7 @@ async function report(args, repoRoot) {
|
|
|
1895
1895
|
}
|
|
1896
1896
|
}
|
|
1897
1897
|
// package.json
|
|
1898
|
-
var version = "0.
|
|
1898
|
+
var version = "0.11.0";
|
|
1899
1899
|
|
|
1900
1900
|
// src/modules/manifest.ts
|
|
1901
1901
|
var MANIFEST_VERSION = 1;
|
|
@@ -2185,9 +2185,11 @@ function requireProject(context, command) {
|
|
|
2185
2185
|
return context.project;
|
|
2186
2186
|
}
|
|
2187
2187
|
function resolvePlansDir(project, command, warn) {
|
|
2188
|
+
return plansDirAt(project.root, resolveProjectConfig(project, command, warn).planDir);
|
|
2189
|
+
}
|
|
2190
|
+
function resolveProjectConfig(project, command, warn) {
|
|
2188
2191
|
try {
|
|
2189
|
-
|
|
2190
|
-
return plansDirAt(project.root, config.planDir);
|
|
2192
|
+
return loadConfig({ root: project.root, home: project.home }, {}, warn).config;
|
|
2191
2193
|
} catch (error) {
|
|
2192
2194
|
if (!(error instanceof ConfigError))
|
|
2193
2195
|
throw error;
|
|
@@ -9689,12 +9691,768 @@ async function finishRelease(input, seams = {}) {
|
|
|
9689
9691
|
return { outcome: "released", version: version2, subject, sha, sentence: null, body: null };
|
|
9690
9692
|
}
|
|
9691
9693
|
|
|
9694
|
+
// src/start/risk-total.ts
|
|
9695
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
9696
|
+
|
|
9697
|
+
// src/plan/risk.ts
|
|
9698
|
+
import { readFileSync as readFileSync17 } from "fs";
|
|
9699
|
+
import { isAbsolute as isAbsolute3, join as join13, relative as relative2 } from "path";
|
|
9700
|
+
|
|
9701
|
+
// src/schema/tiers.ts
|
|
9702
|
+
import { existsSync as existsSync16, realpathSync as realpathSync3 } from "fs";
|
|
9703
|
+
import { dirname as dirname6, join as join12 } from "path";
|
|
9704
|
+
var SKILL_TIERS = ["project", "rafa", "user"];
|
|
9705
|
+
var CLAUDE_SKILLS_PATH = join12(".claude", "skills");
|
|
9706
|
+
var RAFA_INSTINCTS_PATH = join12(".rafa", "instincts");
|
|
9707
|
+
var BUNDLED_SKILLS_DIR = "skills";
|
|
9708
|
+
function realEntry(entry) {
|
|
9709
|
+
try {
|
|
9710
|
+
return realpathSync3(entry);
|
|
9711
|
+
} catch {
|
|
9712
|
+
return entry;
|
|
9713
|
+
}
|
|
9714
|
+
}
|
|
9715
|
+
function bundledSkillsDirectory(entry) {
|
|
9716
|
+
return join12(dirname6(realEntry(entry ?? Bun.main)), BUNDLED_SKILLS_DIR);
|
|
9717
|
+
}
|
|
9718
|
+
function skillTierDirectory(tier, seams) {
|
|
9719
|
+
if (tier === "user")
|
|
9720
|
+
return join12(seams.home, CLAUDE_SKILLS_PATH);
|
|
9721
|
+
if (tier === "rafa")
|
|
9722
|
+
return bundledSkillsDirectory(seams.entry);
|
|
9723
|
+
return seams.projectRoot === null ? null : join12(seams.projectRoot, CLAUDE_SKILLS_PATH);
|
|
9724
|
+
}
|
|
9725
|
+
function resolveSkillTiers(seams) {
|
|
9726
|
+
const located = [];
|
|
9727
|
+
for (const tier of SKILL_TIERS) {
|
|
9728
|
+
const dir = skillTierDirectory(tier, seams);
|
|
9729
|
+
if (dir !== null)
|
|
9730
|
+
located.push({ tier, dir });
|
|
9731
|
+
}
|
|
9732
|
+
return located;
|
|
9733
|
+
}
|
|
9734
|
+
|
|
9735
|
+
// src/plan/risk/accounts.ts
|
|
9736
|
+
var PUSH_REMOTE = "origin";
|
|
9737
|
+
var GITHUB_TRACKER = "github";
|
|
9738
|
+
var URL_USER_INFO = /^([A-Za-z][A-Za-z0-9+.-]*:\/\/)[^@/]+@/;
|
|
9739
|
+
function redactRemote(url) {
|
|
9740
|
+
return url.trim().replace(URL_USER_INFO, "$1***@");
|
|
9741
|
+
}
|
|
9742
|
+
function trackerChain(settings) {
|
|
9743
|
+
return [...new Set([settings.trackerDefault, ...settings.trackerFallback])];
|
|
9744
|
+
}
|
|
9745
|
+
async function readAccounts(settings, seams) {
|
|
9746
|
+
const pushUrl = remoteUrl(seams.git, ["remote", "get-url", "--push", PUSH_REMOTE]);
|
|
9747
|
+
const fetchUrl = remoteUrl(seams.git, ["remote", "get-url", PUSH_REMOTE]);
|
|
9748
|
+
const chain = trackerChain(settings);
|
|
9749
|
+
const reading2 = resolvePrProvider({
|
|
9750
|
+
configured: settings.prProvider,
|
|
9751
|
+
dir: "",
|
|
9752
|
+
readRemote: () => fetchUrl
|
|
9753
|
+
});
|
|
9754
|
+
const needsOwnRepo = chain.includes(GITHUB_TRACKER) || reading2.provider === "gh";
|
|
9755
|
+
const ownRepo = needsOwnRepo ? await viewRepo(seams.gh, []) : null;
|
|
9756
|
+
return [
|
|
9757
|
+
finding("push-remote", pushRemoteText(pushUrl)),
|
|
9758
|
+
finding("visibility", await visibilityText(pushUrl, seams.gh)),
|
|
9759
|
+
finding("issue-tracker", trackerText(chain, ownRepo)),
|
|
9760
|
+
finding("pr-provider", providerText(reading2.provider, reading2.source, ownRepo))
|
|
9761
|
+
];
|
|
9762
|
+
}
|
|
9763
|
+
function finding(subject, text) {
|
|
9764
|
+
return { level: "note", kind: "account", subject, text };
|
|
9765
|
+
}
|
|
9766
|
+
function remoteUrl(git, args) {
|
|
9767
|
+
const result = git(args);
|
|
9768
|
+
const url = result.stdout.trim();
|
|
9769
|
+
return result.ok && url !== "" ? url : null;
|
|
9770
|
+
}
|
|
9771
|
+
function pushRemoteText(pushUrl) {
|
|
9772
|
+
return pushUrl === null ? `push remote: no ${PUSH_REMOTE} remote, so the loop's push would fail` : `push remote: ${PUSH_REMOTE} \u2192 ${redactRemote(pushUrl)}`;
|
|
9773
|
+
}
|
|
9774
|
+
async function visibilityText(pushUrl, gh) {
|
|
9775
|
+
if (pushUrl === null) {
|
|
9776
|
+
return "visibility: not read, there is no push remote";
|
|
9777
|
+
}
|
|
9778
|
+
if (!isGitHubRemote(pushUrl)) {
|
|
9779
|
+
const host = remoteHost(pushUrl) ?? "a local path";
|
|
9780
|
+
return `visibility: not read, ${host} is not a GitHub host`;
|
|
9781
|
+
}
|
|
9782
|
+
const slug = repoSlug(pushUrl);
|
|
9783
|
+
const view = await viewRepo(gh, [slug]);
|
|
9784
|
+
if (!view.ok) {
|
|
9785
|
+
return `visibility: not read, ${view.reason}`;
|
|
9786
|
+
}
|
|
9787
|
+
const name = view.name ?? slug;
|
|
9788
|
+
return view.visibility === null ? `visibility: not read, gh named no visibility for ${name}` : `visibility: ${name} is ${view.visibility}`;
|
|
9789
|
+
}
|
|
9790
|
+
function repoSlug(url) {
|
|
9791
|
+
return normalizeRemote(url).replace(/^([^/]+):\d+\//, "$1/").replace(/^ssh\.github\.com\//, "github.com/");
|
|
9792
|
+
}
|
|
9793
|
+
function trackerText(chain, ownRepo) {
|
|
9794
|
+
const [first, ...rest] = chain;
|
|
9795
|
+
const fallback = rest.length === 0 ? "" : `, falling back to ${rest.join(", then ")}`;
|
|
9796
|
+
const where = chain.includes(GITHUB_TRACKER) && ownRepo !== null ? `; github files on ${repoPhrase(ownRepo)}` : "";
|
|
9797
|
+
return `issue tracker: ${first ?? "none configured"}${fallback}${where}`;
|
|
9798
|
+
}
|
|
9799
|
+
function providerText(provider, source, ownRepo) {
|
|
9800
|
+
const from = source === "config" ? "set in the config" : `inferred from ${PUSH_REMOTE}`;
|
|
9801
|
+
const effect = provider === "gh" && ownRepo !== null ? `, opening pull requests on ${repoPhrase(ownRepo)}` : "";
|
|
9802
|
+
const none = provider === "none" ? ", so the loop pushes and opens no pull request" : "";
|
|
9803
|
+
return `pull request provider: ${provider} (${from})${effect}${none}`;
|
|
9804
|
+
}
|
|
9805
|
+
function repoPhrase(view) {
|
|
9806
|
+
if (!view.ok) {
|
|
9807
|
+
return `the repository gh resolves, which it could not: ${view.reason}`;
|
|
9808
|
+
}
|
|
9809
|
+
return view.name ?? "the repository gh resolves, which it did not name";
|
|
9810
|
+
}
|
|
9811
|
+
async function viewRepo(gh, target) {
|
|
9812
|
+
const result = await gh(["repo", "view", ...target, "--json", "nameWithOwner,visibility"]);
|
|
9813
|
+
if (!result.ok) {
|
|
9814
|
+
return { ok: false, reason: `gh repo view: ${firstLine(result.stderr) ?? "gh exited non-zero"}` };
|
|
9815
|
+
}
|
|
9816
|
+
const parsed = parseJsonObject(result.stdout);
|
|
9817
|
+
if (parsed === null) {
|
|
9818
|
+
return { ok: false, reason: "gh repo view: answered no JSON object" };
|
|
9819
|
+
}
|
|
9820
|
+
return {
|
|
9821
|
+
ok: true,
|
|
9822
|
+
name: stringField(parsed, "nameWithOwner"),
|
|
9823
|
+
visibility: stringField(parsed, "visibility")?.toLowerCase() ?? null
|
|
9824
|
+
};
|
|
9825
|
+
}
|
|
9826
|
+
function parseJsonObject(text) {
|
|
9827
|
+
try {
|
|
9828
|
+
const value = JSON.parse(text);
|
|
9829
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
9830
|
+
} catch {
|
|
9831
|
+
return null;
|
|
9832
|
+
}
|
|
9833
|
+
}
|
|
9834
|
+
function stringField(object, key) {
|
|
9835
|
+
const value = object[key];
|
|
9836
|
+
return typeof value === "string" && value !== "" ? value : null;
|
|
9837
|
+
}
|
|
9838
|
+
function firstLine(text) {
|
|
9839
|
+
return text.split(`
|
|
9840
|
+
`).map((line) => line.trim()).find((line) => line !== "") ?? null;
|
|
9841
|
+
}
|
|
9842
|
+
|
|
9843
|
+
// src/plan/risk/commands.ts
|
|
9844
|
+
import { posix } from "path";
|
|
9845
|
+
|
|
9846
|
+
// src/check/shell-lines.ts
|
|
9847
|
+
var SHELL_FENCE_LANGUAGES = ["bash", "sh", "shell", "zsh", "console"];
|
|
9848
|
+
var FOREIGN_KEYWORDS = [
|
|
9849
|
+
"async",
|
|
9850
|
+
"await",
|
|
9851
|
+
"class",
|
|
9852
|
+
"const",
|
|
9853
|
+
"def",
|
|
9854
|
+
"enum",
|
|
9855
|
+
"fn",
|
|
9856
|
+
"from",
|
|
9857
|
+
"func",
|
|
9858
|
+
"impl",
|
|
9859
|
+
"import",
|
|
9860
|
+
"interface",
|
|
9861
|
+
"new",
|
|
9862
|
+
"package",
|
|
9863
|
+
"private",
|
|
9864
|
+
"protected",
|
|
9865
|
+
"public",
|
|
9866
|
+
"static",
|
|
9867
|
+
"struct",
|
|
9868
|
+
"throw",
|
|
9869
|
+
"val",
|
|
9870
|
+
"var"
|
|
9871
|
+
];
|
|
9872
|
+
var HEREDOC = /<<-?\s*['"]?([A-Za-z_][A-Za-z0-9_]*)['"]?/;
|
|
9873
|
+
var CONTINUES = /\\$/;
|
|
9874
|
+
var SPACED_ASSIGNMENT = /^\s*[A-Za-z_$][A-Za-z0-9_$.]*\s+=(?!=)\s+\S/;
|
|
9875
|
+
var FOREIGN_COMMENT = /^\s*(?:\/\/|\/\*|\*\/|\*\s)/;
|
|
9876
|
+
function isShellFence(info) {
|
|
9877
|
+
return SHELL_FENCE_LANGUAGES.includes(info);
|
|
9878
|
+
}
|
|
9879
|
+
function commandOf(line, info) {
|
|
9880
|
+
const text = line.trim();
|
|
9881
|
+
if (text === "" || text.startsWith("#"))
|
|
9882
|
+
return null;
|
|
9883
|
+
if (text.startsWith("$ ") || text === "$")
|
|
9884
|
+
return text.slice(1).trim() || null;
|
|
9885
|
+
if (info === "console")
|
|
9886
|
+
return null;
|
|
9887
|
+
if (text.startsWith("> "))
|
|
9888
|
+
return null;
|
|
9889
|
+
return text;
|
|
9890
|
+
}
|
|
9891
|
+
function heredocTerminator(command) {
|
|
9892
|
+
return HEREDOC.exec(command)?.[1] ?? null;
|
|
9893
|
+
}
|
|
9894
|
+
function continuesLine(command) {
|
|
9895
|
+
return CONTINUES.test(command);
|
|
9896
|
+
}
|
|
9897
|
+
function isForeignCodeLine(line) {
|
|
9898
|
+
const text = line.trim();
|
|
9899
|
+
if (text === "")
|
|
9900
|
+
return false;
|
|
9901
|
+
if (FOREIGN_COMMENT.test(text))
|
|
9902
|
+
return true;
|
|
9903
|
+
if (SPACED_ASSIGNMENT.test(text))
|
|
9904
|
+
return true;
|
|
9905
|
+
return FOREIGN_KEYWORDS.includes(text.split(/\s+/)[0] ?? "");
|
|
9906
|
+
}
|
|
9907
|
+
|
|
9908
|
+
// src/plan/risk/patterns.ts
|
|
9909
|
+
var COMMAND_WRAPPERS = [
|
|
9910
|
+
"command",
|
|
9911
|
+
"do",
|
|
9912
|
+
"else",
|
|
9913
|
+
"env",
|
|
9914
|
+
"exec",
|
|
9915
|
+
"nohup",
|
|
9916
|
+
"sudo",
|
|
9917
|
+
"then",
|
|
9918
|
+
"time",
|
|
9919
|
+
"xargs"
|
|
9920
|
+
];
|
|
9921
|
+
var OPENER = String.raw`(?:^|[;&|({\x60]|\s-exec(?:dir)?\s)`;
|
|
9922
|
+
var ASSIGNMENTS = String.raw`(?:[A-Za-z_]\w*=\S*\s+)*`;
|
|
9923
|
+
var WRAPPERS = String.raw`(?:(?:${COMMAND_WRAPPERS.join("|")})(?:\s+-\S+)*\s+${ASSIGNMENTS})*`;
|
|
9924
|
+
var AT_COMMAND = String.raw`${OPENER}\s*${ASSIGNMENTS}${WRAPPERS}`;
|
|
9925
|
+
var REST = "[^;&|]*";
|
|
9926
|
+
var GIT_OPTIONS = String.raw`(?:\s+(?:-[Cc]\s+\S+|--?[\w-]+(?:=\S+)?))*`;
|
|
9927
|
+
function hasFlag(short, long) {
|
|
9928
|
+
return String.raw`(?=(?:${REST}\s)?(?:-[A-Za-z]*${short}[A-Za-z]*|--${long})(?=\s|$))`;
|
|
9929
|
+
}
|
|
9930
|
+
function atCommand(name, body) {
|
|
9931
|
+
return { name, expression: new RegExp(`${AT_COMMAND}${body}`) };
|
|
9932
|
+
}
|
|
9933
|
+
var DESTRUCTIVE_PATTERNS = [
|
|
9934
|
+
atCommand("rm -rf", String.raw`rm\s+${hasFlag("[rR]", "recursive")}${hasFlag("f", "force")}`),
|
|
9935
|
+
atCommand("git push --force", String.raw`git${GIT_OPTIONS}\s+push\b(?=${REST}\s(?:--force|-[A-Za-z]*f[A-Za-z]*)(?=\s|$))`),
|
|
9936
|
+
atCommand("git reset --hard", String.raw`git${GIT_OPTIONS}\s+reset\b(?=${REST}\s--hard(?=\s|$))`),
|
|
9937
|
+
atCommand("git clean -f", String.raw`git${GIT_OPTIONS}\s+clean\s+${hasFlag("f", "force")}`),
|
|
9938
|
+
atCommand("sudo", String.raw`sudo(?=\s|$)`),
|
|
9939
|
+
atCommand("brew install", String.raw`brew(?:\s+-\S+)*\s+install\b`),
|
|
9940
|
+
atCommand("npm i -g", String.raw`npm\s+(?:i|install|add)(?=\s)(?=${REST}\s(?:-g|--global)(?=\s|$))`),
|
|
9941
|
+
atCommand("pip install", String.raw`(?:python3?\s+-m\s+)?pip3?\s+install\b`),
|
|
9942
|
+
atCommand("npm publish", String.raw`npm\s+publish\b(?!${REST}--dry-run)`),
|
|
9943
|
+
atCommand("curl \u2026 | sh", String.raw`curl\b[^;&|]*\|(?!\|)\s*${WRAPPERS}(?:ba|z|da)?sh(?=\s|$)`),
|
|
9944
|
+
atCommand("chmod -R", String.raw`chmod\s+${hasFlag("R", "recursive")}`),
|
|
9945
|
+
{ name: "DROP TABLE", expression: /\bDROP\s+TABLE\b/i },
|
|
9946
|
+
atCommand("docker system prune", String.raw`docker\s+system\s+prune\b`)
|
|
9947
|
+
];
|
|
9948
|
+
function destructiveMatches(command) {
|
|
9949
|
+
return DESTRUCTIVE_PATTERNS.filter((pattern) => pattern.expression.test(command));
|
|
9950
|
+
}
|
|
9951
|
+
|
|
9952
|
+
// src/plan/risk/commands.ts
|
|
9953
|
+
var DEVICE_FILES = [
|
|
9954
|
+
"/dev/null",
|
|
9955
|
+
"/dev/stdin",
|
|
9956
|
+
"/dev/stdout",
|
|
9957
|
+
"/dev/stderr",
|
|
9958
|
+
"/dev/tty"
|
|
9959
|
+
];
|
|
9960
|
+
var WRITES_EVERY_OPERAND = ["mv", "rm", "tee"];
|
|
9961
|
+
var COMMAND_PREFIXES = [...COMMAND_WRAPPERS, "!", "{"];
|
|
9962
|
+
var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})[ \t]*(\S*)/;
|
|
9963
|
+
var FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
|
|
9964
|
+
var CODE_SPAN2 = /`([^`\n]+)`/g;
|
|
9965
|
+
var REDIRECT = /^(?:&>>|&>|>>|>\||>&|<<<|<<-|<<|<&|<>|>|<)/;
|
|
9966
|
+
var OUTPUT_REDIRECTS = /^(?:\d*(?:>>|>\||>&|<>|>)|&>>|&>)$/;
|
|
9967
|
+
var HEREDOC_REDIRECTS = /^\d*<<[-<]?$/;
|
|
9968
|
+
var SEPARATORS2 = ";&|()`";
|
|
9969
|
+
var ASSIGNMENT = /^[A-Za-z_]\w*=/;
|
|
9970
|
+
var SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*:\/\//;
|
|
9971
|
+
function openFence(open) {
|
|
9972
|
+
const run = open[1] ?? "";
|
|
9973
|
+
return {
|
|
9974
|
+
info: (open[2] ?? "").split(/[\s,{]/)[0]?.toLowerCase() ?? "",
|
|
9975
|
+
delimiter: run[0] ?? "`",
|
|
9976
|
+
length: run.length,
|
|
9977
|
+
terminator: null,
|
|
9978
|
+
pending: null,
|
|
9979
|
+
foreign: false,
|
|
9980
|
+
commands: []
|
|
9981
|
+
};
|
|
9982
|
+
}
|
|
9983
|
+
function closesFence(line, fence) {
|
|
9984
|
+
const run = FENCE_CLOSE.exec(line)?.[1] ?? "";
|
|
9985
|
+
return run !== "" && run[0] === fence.delimiter && run.length >= fence.length;
|
|
9986
|
+
}
|
|
9987
|
+
function withoutBackslash(piece) {
|
|
9988
|
+
return piece.replace(/\\$/, "").trim();
|
|
9989
|
+
}
|
|
9990
|
+
function flush(fence) {
|
|
9991
|
+
if (fence.pending !== null)
|
|
9992
|
+
fence.commands.push({ text: fence.pending.text, line: fence.pending.line });
|
|
9993
|
+
fence.pending = null;
|
|
9994
|
+
}
|
|
9995
|
+
function continuationOf(line, info) {
|
|
9996
|
+
const text = line.trim();
|
|
9997
|
+
return info === "console" && text.startsWith("> ") ? text.slice(2) : text;
|
|
9998
|
+
}
|
|
9999
|
+
function readFenceLine(fence, line, number) {
|
|
10000
|
+
const pending = fence.pending;
|
|
10001
|
+
if (pending === null && fence.terminator !== null) {
|
|
10002
|
+
if (line.trim() === fence.terminator)
|
|
10003
|
+
fence.terminator = null;
|
|
10004
|
+
return;
|
|
10005
|
+
}
|
|
10006
|
+
const piece = pending === null ? commandOf(line, fence.info) : continuationOf(line, fence.info);
|
|
10007
|
+
if (piece === null || piece === "")
|
|
10008
|
+
return;
|
|
10009
|
+
if (isForeignCodeLine(piece)) {
|
|
10010
|
+
fence.foreign = true;
|
|
10011
|
+
return;
|
|
10012
|
+
}
|
|
10013
|
+
fence.terminator = heredocTerminator(piece) ?? fence.terminator;
|
|
10014
|
+
if (pending === null)
|
|
10015
|
+
fence.pending = { text: withoutBackslash(piece), line: number };
|
|
10016
|
+
else
|
|
10017
|
+
pending.text = `${pending.text} ${withoutBackslash(piece)}`;
|
|
10018
|
+
if (!continuesLine(piece))
|
|
10019
|
+
flush(fence);
|
|
10020
|
+
}
|
|
10021
|
+
function commandsOf(fence) {
|
|
10022
|
+
flush(fence);
|
|
10023
|
+
return fence.foreign ? [] : fence.commands;
|
|
10024
|
+
}
|
|
10025
|
+
function shellCommandLines(body) {
|
|
10026
|
+
const found = [];
|
|
10027
|
+
let fence = null;
|
|
10028
|
+
for (const [index, line] of body.split(`
|
|
10029
|
+
`).entries()) {
|
|
10030
|
+
if (fence === null) {
|
|
10031
|
+
const open = FENCE_OPEN.exec(line);
|
|
10032
|
+
if (open !== null)
|
|
10033
|
+
fence = openFence(open);
|
|
10034
|
+
continue;
|
|
10035
|
+
}
|
|
10036
|
+
if (closesFence(line, fence)) {
|
|
10037
|
+
found.push(...commandsOf(fence));
|
|
10038
|
+
fence = null;
|
|
10039
|
+
continue;
|
|
10040
|
+
}
|
|
10041
|
+
if (isShellFence(fence.info) && !fence.foreign)
|
|
10042
|
+
readFenceLine(fence, line, index + 1);
|
|
10043
|
+
}
|
|
10044
|
+
if (fence !== null)
|
|
10045
|
+
found.push(...commandsOf(fence));
|
|
10046
|
+
return found;
|
|
10047
|
+
}
|
|
10048
|
+
function taskLineCommands(taskLine, line) {
|
|
10049
|
+
const found = [];
|
|
10050
|
+
for (const match of taskLine.matchAll(CODE_SPAN2)) {
|
|
10051
|
+
const command = commandOf(match[1] ?? "", "sh");
|
|
10052
|
+
if (command !== null && !isForeignCodeLine(command))
|
|
10053
|
+
found.push({ text: command, line });
|
|
10054
|
+
}
|
|
10055
|
+
return found;
|
|
10056
|
+
}
|
|
10057
|
+
function quotedEnd(command, start) {
|
|
10058
|
+
const quote = command[start];
|
|
10059
|
+
let index = start + 1;
|
|
10060
|
+
while (index < command.length && command[index] !== quote) {
|
|
10061
|
+
index += quote === '"' && command[index] === "\\" ? 2 : 1;
|
|
10062
|
+
}
|
|
10063
|
+
return Math.min(index + 1, command.length);
|
|
10064
|
+
}
|
|
10065
|
+
function unquote(run) {
|
|
10066
|
+
const closed = run.length > 1 && run.endsWith(run[0] ?? "");
|
|
10067
|
+
const inner = run.slice(1, closed ? -1 : undefined);
|
|
10068
|
+
return run.startsWith('"') ? inner.replace(/\\(["\\$`])/g, "$1") : inner;
|
|
10069
|
+
}
|
|
10070
|
+
function opensRedirect(rest) {
|
|
10071
|
+
return rest.startsWith(">") || rest.startsWith("<") || rest.startsWith("&>");
|
|
10072
|
+
}
|
|
10073
|
+
function shellTokens(command) {
|
|
10074
|
+
const tokens = [];
|
|
10075
|
+
let word = "";
|
|
10076
|
+
let hasWord = false;
|
|
10077
|
+
const endWord = () => {
|
|
10078
|
+
if (hasWord)
|
|
10079
|
+
tokens.push({ kind: "word", text: word });
|
|
10080
|
+
word = "";
|
|
10081
|
+
hasWord = false;
|
|
10082
|
+
};
|
|
10083
|
+
let index = 0;
|
|
10084
|
+
while (index < command.length) {
|
|
10085
|
+
const char = command[index] ?? "";
|
|
10086
|
+
const rest = command.slice(index);
|
|
10087
|
+
if (opensRedirect(rest)) {
|
|
10088
|
+
const fd = /^\d+$/.test(word) ? word : "";
|
|
10089
|
+
if (fd === "")
|
|
10090
|
+
endWord();
|
|
10091
|
+
word = "";
|
|
10092
|
+
hasWord = false;
|
|
10093
|
+
const operator = REDIRECT.exec(rest)?.[0] ?? char;
|
|
10094
|
+
tokens.push({ kind: "redirect", text: `${fd}${operator}` });
|
|
10095
|
+
index += operator.length;
|
|
10096
|
+
} else if (char === "'" || char === '"') {
|
|
10097
|
+
const end = quotedEnd(command, index);
|
|
10098
|
+
word += unquote(command.slice(index, end));
|
|
10099
|
+
hasWord = true;
|
|
10100
|
+
index = end;
|
|
10101
|
+
} else if (char === "\\") {
|
|
10102
|
+
word += command[index + 1] ?? "";
|
|
10103
|
+
hasWord = true;
|
|
10104
|
+
index += 2;
|
|
10105
|
+
} else if (/\s/.test(char)) {
|
|
10106
|
+
endWord();
|
|
10107
|
+
index += 1;
|
|
10108
|
+
} else if (SEPARATORS2.includes(char)) {
|
|
10109
|
+
endWord();
|
|
10110
|
+
const operator = /^(?:&&|\|\||[;&|()`])/.exec(rest)?.[0] ?? char;
|
|
10111
|
+
tokens.push({ kind: "separator", text: operator });
|
|
10112
|
+
index += operator.length;
|
|
10113
|
+
} else {
|
|
10114
|
+
word += char;
|
|
10115
|
+
hasWord = true;
|
|
10116
|
+
index += 1;
|
|
10117
|
+
}
|
|
10118
|
+
}
|
|
10119
|
+
endWord();
|
|
10120
|
+
return tokens;
|
|
10121
|
+
}
|
|
10122
|
+
function segmentsOf(tokens) {
|
|
10123
|
+
const segments = [[]];
|
|
10124
|
+
for (const token of tokens) {
|
|
10125
|
+
if (token.kind === "separator")
|
|
10126
|
+
segments.push([]);
|
|
10127
|
+
else
|
|
10128
|
+
segments.at(-1)?.push(token);
|
|
10129
|
+
}
|
|
10130
|
+
return segments.filter((segment) => segment.length > 0);
|
|
10131
|
+
}
|
|
10132
|
+
function wordsOf2(segment) {
|
|
10133
|
+
const argv = [];
|
|
10134
|
+
const redirected = [];
|
|
10135
|
+
let operator = null;
|
|
10136
|
+
for (const token of segment) {
|
|
10137
|
+
if (token.kind === "redirect") {
|
|
10138
|
+
operator = token.text;
|
|
10139
|
+
} else if (operator === null) {
|
|
10140
|
+
argv.push(token.text);
|
|
10141
|
+
} else {
|
|
10142
|
+
if (!HEREDOC_REDIRECTS.test(operator)) {
|
|
10143
|
+
redirected.push({ word: token.text, writes: OUTPUT_REDIRECTS.test(operator) });
|
|
10144
|
+
}
|
|
10145
|
+
operator = null;
|
|
10146
|
+
}
|
|
10147
|
+
}
|
|
10148
|
+
return { argv, redirected };
|
|
10149
|
+
}
|
|
10150
|
+
function commandIndex(argv) {
|
|
10151
|
+
let index = 0;
|
|
10152
|
+
let afterWrapper = false;
|
|
10153
|
+
while (index < argv.length) {
|
|
10154
|
+
const word = argv[index] ?? "";
|
|
10155
|
+
const isWrapperFlag = afterWrapper && word.startsWith("-");
|
|
10156
|
+
if (!ASSIGNMENT.test(word) && !COMMAND_PREFIXES.includes(word) && !isWrapperFlag)
|
|
10157
|
+
return index;
|
|
10158
|
+
afterWrapper = COMMAND_PREFIXES.includes(word) || isWrapperFlag;
|
|
10159
|
+
index += 1;
|
|
10160
|
+
}
|
|
10161
|
+
return index;
|
|
10162
|
+
}
|
|
10163
|
+
function operandsOf(args) {
|
|
10164
|
+
const end = args.indexOf("--");
|
|
10165
|
+
const flagged = end === -1 ? args : args.slice(0, end);
|
|
10166
|
+
const after = end === -1 ? [] : args.slice(end + 1);
|
|
10167
|
+
return [...flagged.filter((word) => !word.startsWith("-")), ...after];
|
|
10168
|
+
}
|
|
10169
|
+
function copyTargets(args) {
|
|
10170
|
+
const flag = args.indexOf("-t");
|
|
10171
|
+
if (flag !== -1 && args[flag + 1] !== undefined)
|
|
10172
|
+
return [args[flag + 1] ?? ""];
|
|
10173
|
+
const long = args.find((word) => word.startsWith("--target-directory="));
|
|
10174
|
+
if (long !== undefined)
|
|
10175
|
+
return [long.slice("--target-directory=".length)];
|
|
10176
|
+
return operandsOf(args).slice(-1);
|
|
10177
|
+
}
|
|
10178
|
+
function writtenWords(argv) {
|
|
10179
|
+
const at = commandIndex(argv);
|
|
10180
|
+
const name = posix.basename(argv[at] ?? "");
|
|
10181
|
+
const args = argv.slice(at + 1);
|
|
10182
|
+
if (WRITES_EVERY_OPERAND.includes(name))
|
|
10183
|
+
return new Set(operandsOf(args));
|
|
10184
|
+
if (name === "cp")
|
|
10185
|
+
return new Set(copyTargets(args));
|
|
10186
|
+
return new Set;
|
|
10187
|
+
}
|
|
10188
|
+
function pathCandidate(word) {
|
|
10189
|
+
if (word.startsWith("/") || word.startsWith("~"))
|
|
10190
|
+
return word;
|
|
10191
|
+
const equals = word.indexOf("=");
|
|
10192
|
+
return equals === -1 ? word : word.slice(equals + 1);
|
|
10193
|
+
}
|
|
10194
|
+
function isWithin(root, target) {
|
|
10195
|
+
const step = posix.relative(posix.resolve(root), posix.resolve(target));
|
|
10196
|
+
return !step.startsWith("..") && !posix.isAbsolute(step);
|
|
10197
|
+
}
|
|
10198
|
+
function isOutsidePath(path5, seams) {
|
|
10199
|
+
if (SCHEME.test(path5) || path5.startsWith("//") || DEVICE_FILES.includes(path5))
|
|
10200
|
+
return false;
|
|
10201
|
+
if (path5 === "~" || path5.startsWith("~/"))
|
|
10202
|
+
return !isWithin(seams.repoRoot, posix.join(seams.home, path5.slice(1)));
|
|
10203
|
+
if (path5.startsWith("~"))
|
|
10204
|
+
return true;
|
|
10205
|
+
return path5.startsWith("/") && !isWithin(seams.repoRoot, path5);
|
|
10206
|
+
}
|
|
10207
|
+
function outsidePaths(command, seams) {
|
|
10208
|
+
const found = new Map;
|
|
10209
|
+
const note = (word, writes) => {
|
|
10210
|
+
const path5 = pathCandidate(word);
|
|
10211
|
+
if (isOutsidePath(path5, seams))
|
|
10212
|
+
found.set(path5, (found.get(path5) ?? false) || writes);
|
|
10213
|
+
};
|
|
10214
|
+
for (const segment of segmentsOf(shellTokens(command))) {
|
|
10215
|
+
const { argv, redirected } = wordsOf2(segment);
|
|
10216
|
+
const written = writtenWords(argv);
|
|
10217
|
+
for (const word of argv)
|
|
10218
|
+
note(word, written.has(word) || written.has(pathCandidate(word)));
|
|
10219
|
+
for (const target of redirected)
|
|
10220
|
+
note(target.word, target.writes);
|
|
10221
|
+
}
|
|
10222
|
+
return Array.from(found, ([path5, writes]) => ({ path: path5, writes }));
|
|
10223
|
+
}
|
|
10224
|
+
function findingsOf(command, seams) {
|
|
10225
|
+
const destructive = destructiveMatches(command.text).map((pattern) => ({
|
|
10226
|
+
level: "high",
|
|
10227
|
+
kind: "destructive",
|
|
10228
|
+
text: `${pattern.name} \u2014 ${command.text}`,
|
|
10229
|
+
subject: pattern.name,
|
|
10230
|
+
command: command.text,
|
|
10231
|
+
line: command.line
|
|
10232
|
+
}));
|
|
10233
|
+
const paths = outsidePaths(command.text, seams).map((path5) => ({
|
|
10234
|
+
level: path5.writes ? "high" : "note",
|
|
10235
|
+
kind: "outside-path",
|
|
10236
|
+
text: `${path5.path} \u2014 ${path5.writes ? "written by" : "named in"} ${command.text}`,
|
|
10237
|
+
subject: path5.path,
|
|
10238
|
+
command: command.text,
|
|
10239
|
+
line: command.line
|
|
10240
|
+
}));
|
|
10241
|
+
return [...destructive, ...paths];
|
|
10242
|
+
}
|
|
10243
|
+
function readCommands2(commands, seams) {
|
|
10244
|
+
return commands.flatMap((command) => findingsOf(command, seams));
|
|
10245
|
+
}
|
|
10246
|
+
function readBodyCommands(body, seams) {
|
|
10247
|
+
return readCommands2(shellCommandLines(body), seams);
|
|
10248
|
+
}
|
|
10249
|
+
function readTaskLineCommands(taskLine, line, seams) {
|
|
10250
|
+
return readCommands2(taskLineCommands(taskLine, line), seams);
|
|
10251
|
+
}
|
|
10252
|
+
|
|
10253
|
+
// src/plan/risk/secrets.ts
|
|
10254
|
+
var SECRET_NAME_SHAPES = [
|
|
10255
|
+
{ name: "*_TOKEN", expression: /_TOKEN$/ },
|
|
10256
|
+
{ name: "*_KEY", expression: /_KEY$/ },
|
|
10257
|
+
{ name: "*_SECRET", expression: /_SECRET$/ },
|
|
10258
|
+
{ name: "*PASSWORD*", expression: /PASSWORD/ },
|
|
10259
|
+
{ name: "AWS_*", expression: /^AWS_/ }
|
|
10260
|
+
];
|
|
10261
|
+
function secretShapesOf(name) {
|
|
10262
|
+
return SECRET_NAME_SHAPES.filter((shape) => shape.expression.test(name));
|
|
10263
|
+
}
|
|
10264
|
+
function scanSecretNames(environment) {
|
|
10265
|
+
return [...Object.keys(environment)].sort().flatMap((name) => {
|
|
10266
|
+
const shapes = secretShapesOf(name);
|
|
10267
|
+
if (shapes.length === 0) {
|
|
10268
|
+
return [];
|
|
10269
|
+
}
|
|
10270
|
+
const matched = shapes.map((shape) => shape.name).join(", ");
|
|
10271
|
+
return [{ level: "note", kind: "secret", text: `${name} \u2014 matches ${matched}`, subject: name }];
|
|
10272
|
+
});
|
|
10273
|
+
}
|
|
10274
|
+
|
|
10275
|
+
// src/plan/risk.ts
|
|
10276
|
+
var RISK_KINDS = ["tools", "destructive", "outside-path", "account", "secret"];
|
|
10277
|
+
var EVERY_TOOL = "every tool";
|
|
10278
|
+
var SHELL_TOOL = "Bash";
|
|
10279
|
+
function agentTools(frontmatter) {
|
|
10280
|
+
const value = frontmatter["tools"];
|
|
10281
|
+
let names = [];
|
|
10282
|
+
if (typeof value === "string")
|
|
10283
|
+
names = value.split(",");
|
|
10284
|
+
else if (Array.isArray(value))
|
|
10285
|
+
names = value.filter((entry) => typeof entry === "string");
|
|
10286
|
+
const tools = [...new Set(names.map((name) => name.trim()).filter((name) => name !== ""))];
|
|
10287
|
+
return tools.length === 0 ? null : tools;
|
|
10288
|
+
}
|
|
10289
|
+
function holdsShell(tools) {
|
|
10290
|
+
return tools.some((tool) => tool === SHELL_TOOL || tool.startsWith(`${SHELL_TOOL}(`));
|
|
10291
|
+
}
|
|
10292
|
+
function frontmatterString(frontmatter, key) {
|
|
10293
|
+
const value = frontmatter[key];
|
|
10294
|
+
return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
|
|
10295
|
+
}
|
|
10296
|
+
function taskAccess(declaration, seams) {
|
|
10297
|
+
const budget = declaration?.budget ?? null;
|
|
10298
|
+
const agent = declaration?.agent ?? null;
|
|
10299
|
+
if (agent === null) {
|
|
10300
|
+
const tools2 = declaration?.tools ?? null;
|
|
10301
|
+
return {
|
|
10302
|
+
tools: tools2,
|
|
10303
|
+
source: tools2 === null ? "no agent= and no tools=" : "tools=",
|
|
10304
|
+
model: declaration?.model ?? null,
|
|
10305
|
+
budget
|
|
10306
|
+
};
|
|
10307
|
+
}
|
|
10308
|
+
const definition = findAgentDefinition(agent, { repoRoot: seams.repoRoot, home: seams.home }, seams.settingSources);
|
|
10309
|
+
if (definition === null) {
|
|
10310
|
+
return { tools: null, source: `agent=${agent} resolves to no definition`, model: null, budget };
|
|
10311
|
+
}
|
|
10312
|
+
const tools = agentTools(definition.frontmatter);
|
|
10313
|
+
return {
|
|
10314
|
+
tools,
|
|
10315
|
+
source: tools === null ? `agent=${agent} has no tools: line` : `agent=${agent}`,
|
|
10316
|
+
model: frontmatterString(definition.frontmatter, "model"),
|
|
10317
|
+
budget
|
|
10318
|
+
};
|
|
10319
|
+
}
|
|
10320
|
+
function toolsFinding(task, plan2, seams) {
|
|
10321
|
+
const access = taskAccess(task.declaration, seams);
|
|
10322
|
+
const model = `model ${access.model ?? "the session default"}`;
|
|
10323
|
+
const budget = access.budget === null ? "no budget=" : `budget $${String(access.budget)}`;
|
|
10324
|
+
const where = { task: task.text, file: plan2, line: task.lineNum + 1 };
|
|
10325
|
+
if (access.tools === null) {
|
|
10326
|
+
return { level: "high", kind: "tools", text: `${EVERY_TOOL} \u2014 ${access.source}; ${model}; ${budget}`, ...where };
|
|
10327
|
+
}
|
|
10328
|
+
if (holdsShell(access.tools) && access.budget === null) {
|
|
10329
|
+
const listed = access.tools.join(", ");
|
|
10330
|
+
return { level: "note", kind: "tools", text: `${SHELL_TOOL} with no budget= \u2014 ${access.source}: ${listed}; ${model}`, ...where };
|
|
10331
|
+
}
|
|
10332
|
+
return null;
|
|
10333
|
+
}
|
|
10334
|
+
function displayPath(path5, seams) {
|
|
10335
|
+
const absolute = isAbsolute3(path5) ? path5 : join13(seams.repoRoot, path5);
|
|
10336
|
+
const within = (root) => {
|
|
10337
|
+
const step = relative2(root, absolute);
|
|
10338
|
+
return step === "" || step.startsWith("..") || isAbsolute3(step) ? null : step;
|
|
10339
|
+
};
|
|
10340
|
+
const inRepo = within(seams.repoRoot);
|
|
10341
|
+
if (inRepo !== null)
|
|
10342
|
+
return inRepo;
|
|
10343
|
+
const inHome = within(seams.home);
|
|
10344
|
+
return inHome === null ? absolute : `~/${inHome}`;
|
|
10345
|
+
}
|
|
10346
|
+
function located(finding2, file, task) {
|
|
10347
|
+
const base = { level: finding2.level, kind: finding2.kind, text: finding2.text };
|
|
10348
|
+
return task === undefined ? { ...base, file, line: finding2.line } : { ...base, task, file, line: finding2.line };
|
|
10349
|
+
}
|
|
10350
|
+
function readText4(path5) {
|
|
10351
|
+
try {
|
|
10352
|
+
return readFileSync17(path5, "utf8");
|
|
10353
|
+
} catch {
|
|
10354
|
+
return null;
|
|
10355
|
+
}
|
|
10356
|
+
}
|
|
10357
|
+
function findSkillFile(name, seams) {
|
|
10358
|
+
const tiers = resolveSkillTiers({ home: seams.home, projectRoot: seams.repoRoot, entry: seams.entry });
|
|
10359
|
+
for (const { dir } of tiers) {
|
|
10360
|
+
const path5 = join13(dir, name, "SKILL.md");
|
|
10361
|
+
if (readText4(path5) !== null)
|
|
10362
|
+
return path5;
|
|
10363
|
+
}
|
|
10364
|
+
return null;
|
|
10365
|
+
}
|
|
10366
|
+
function taskFiles(declaration, seams) {
|
|
10367
|
+
if (declaration === null)
|
|
10368
|
+
return [];
|
|
10369
|
+
const agent = declaration.agent === null ? null : findAgentDefinition(declaration.agent, { repoRoot: seams.repoRoot, home: seams.home }, seams.settingSources);
|
|
10370
|
+
const skills = (declaration.skills ?? []).map((name) => findSkillFile(name, seams));
|
|
10371
|
+
return [agent?.path ?? null, ...skills].filter((path5) => path5 !== null);
|
|
10372
|
+
}
|
|
10373
|
+
function commandFindings(plan2, display, tasks, seams) {
|
|
10374
|
+
const paths = { repoRoot: seams.repoRoot, home: seams.home };
|
|
10375
|
+
const found = readBodyCommands(plan2.text, paths).map((finding2) => located(finding2, display));
|
|
10376
|
+
const read = new Set;
|
|
10377
|
+
const lines = plan2.text.split(`
|
|
10378
|
+
`);
|
|
10379
|
+
for (const task of tasks) {
|
|
10380
|
+
const spans = readTaskLineCommands(lines[task.lineNum] ?? "", task.lineNum + 1, paths);
|
|
10381
|
+
found.push(...spans.map((finding2) => located(finding2, display, task.text)));
|
|
10382
|
+
for (const file of taskFiles(task.declaration, seams)) {
|
|
10383
|
+
if (read.has(file))
|
|
10384
|
+
continue;
|
|
10385
|
+
read.add(file);
|
|
10386
|
+
const shown = displayPath(file, seams);
|
|
10387
|
+
found.push(...readBodyCommands(readText4(file) ?? "", paths).map((finding2) => located(finding2, shown)));
|
|
10388
|
+
}
|
|
10389
|
+
}
|
|
10390
|
+
return found;
|
|
10391
|
+
}
|
|
10392
|
+
function openTasks(tasks, hidden) {
|
|
10393
|
+
return [...tasks, ...hidden].filter((task) => task.status !== "done").sort((a, b) => a.lineNum - b.lineNum);
|
|
10394
|
+
}
|
|
10395
|
+
function riskTotal(findings) {
|
|
10396
|
+
return {
|
|
10397
|
+
high: findings.filter((finding2) => finding2.level === "high").length,
|
|
10398
|
+
note: findings.filter((finding2) => finding2.level === "note").length
|
|
10399
|
+
};
|
|
10400
|
+
}
|
|
10401
|
+
async function assessPlanRisk(plan2, seams) {
|
|
10402
|
+
const model = parsePlan(plan2.text);
|
|
10403
|
+
const display = displayPath(plan2.path, seams);
|
|
10404
|
+
const tasks = openTasks(model.tasks, model.hiddenTasks);
|
|
10405
|
+
const tools = tasks.flatMap((task) => toolsFinding(task, display, seams) ?? []);
|
|
10406
|
+
const commands = commandFindings(plan2, display, tasks, seams);
|
|
10407
|
+
const accounts = (await readAccounts(seams.accounts, seams.runners)).map(({ level, kind, text }) => ({ level, kind, text }));
|
|
10408
|
+
const secrets = scanSecretNames(seams.environment).map(({ level, kind, text }) => ({ level, kind, text }));
|
|
10409
|
+
const findings = [...tools, ...commands, ...accounts, ...secrets];
|
|
10410
|
+
return { plan: display, total: riskTotal(findings), findings };
|
|
10411
|
+
}
|
|
10412
|
+
function counted2(count, word) {
|
|
10413
|
+
return count === 1 ? `${String(count)} ${word}` : `${String(count)} ${word}s`;
|
|
10414
|
+
}
|
|
10415
|
+
function riskTotalLine(report2) {
|
|
10416
|
+
return `\uD83D\uDEE1 Risk: ${String(report2.total.high)} high, ${counted2(report2.total.note, "note")} \u2014 rafa plan risk ${report2.plan}`;
|
|
10417
|
+
}
|
|
10418
|
+
var KIND_WIDTH = Math.max(...RISK_KINDS.map((kind) => kind.length));
|
|
10419
|
+
|
|
10420
|
+
// src/start/risk-total.ts
|
|
10421
|
+
function spawningRunners(root) {
|
|
10422
|
+
return { git: createGitRunner(root), gh: createGhRunner({ cwd: root }) };
|
|
10423
|
+
}
|
|
10424
|
+
async function readRiskTotalLine(input, seams = {}) {
|
|
10425
|
+
const makeRunners = seams.runners ?? spawningRunners;
|
|
10426
|
+
const report2 = await assessPlanRisk({ path: input.planPath, text: readFileSync18(input.planPath, "utf8") }, {
|
|
10427
|
+
repoRoot: input.repoRoot,
|
|
10428
|
+
home: input.home,
|
|
10429
|
+
settingSources: input.config.settingSources,
|
|
10430
|
+
environment: input.environment,
|
|
10431
|
+
accounts: {
|
|
10432
|
+
prProvider: input.config.prProvider,
|
|
10433
|
+
trackerDefault: input.config.trackerDefault,
|
|
10434
|
+
trackerFallback: input.config.trackerFallback
|
|
10435
|
+
},
|
|
10436
|
+
runners: makeRunners(input.repoRoot)
|
|
10437
|
+
});
|
|
10438
|
+
return riskTotalLine(report2);
|
|
10439
|
+
}
|
|
10440
|
+
async function announceRiskTotal(input, seams = {}) {
|
|
10441
|
+
const output = seams.output ?? activeOutput();
|
|
10442
|
+
try {
|
|
10443
|
+
output.info(await readRiskTotalLine(input, seams));
|
|
10444
|
+
} catch (error) {
|
|
10445
|
+
const said = error instanceof Error ? error.message : String(error);
|
|
10446
|
+
output.warn(`\u26A0\uFE0F Risk: could not read ${input.planPath} \u2014 ${said}`);
|
|
10447
|
+
}
|
|
10448
|
+
}
|
|
10449
|
+
|
|
9692
10450
|
// src/start/session.ts
|
|
9693
10451
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
9694
|
-
import { relative as
|
|
10452
|
+
import { relative as relative3 } from "path";
|
|
9695
10453
|
var NOTHING_DISPATCHED = " Nothing was checked and nothing was dispatched.";
|
|
9696
10454
|
function recordLabel(repoRoot, sessionId) {
|
|
9697
|
-
return
|
|
10455
|
+
return relative3(repoRoot, sessionFilePath(repoRoot, sessionId));
|
|
9698
10456
|
}
|
|
9699
10457
|
function conflictLine(repoRoot, conflict) {
|
|
9700
10458
|
const { record } = conflict;
|
|
@@ -9724,7 +10482,7 @@ function openRefusal(repoRoot, draft, error) {
|
|
|
9724
10482
|
if (!(error instanceof SessionRecordError) && !isSystemError)
|
|
9725
10483
|
return null;
|
|
9726
10484
|
return [
|
|
9727
|
-
`\u274C Refusing to start: the session records under ${
|
|
10485
|
+
`\u274C Refusing to start: the session records under ${relative3(repoRoot, runsDir(repoRoot))}/ cannot be read or written.`,
|
|
9728
10486
|
` ${messageOf(error)}`,
|
|
9729
10487
|
NOTHING_DISPATCHED
|
|
9730
10488
|
].join(`
|
|
@@ -9760,7 +10518,7 @@ function openRunSession(options) {
|
|
|
9760
10518
|
const draft = {
|
|
9761
10519
|
sessionId: (seams.newSessionId ?? randomUUID8)(),
|
|
9762
10520
|
planStub: options.planStub,
|
|
9763
|
-
plan:
|
|
10521
|
+
plan: relative3(repoRoot, options.planPath),
|
|
9764
10522
|
branch: options.branch,
|
|
9765
10523
|
pid: seams.pid ?? process.pid,
|
|
9766
10524
|
startedAt: (seams.now ?? (() => new Date))().toISOString()
|
|
@@ -9837,11 +10595,11 @@ function announcePlanIssues(planContent) {
|
|
|
9837
10595
|
}
|
|
9838
10596
|
|
|
9839
10597
|
// src/start/runtime.ts
|
|
9840
|
-
import { existsSync as
|
|
10598
|
+
import { existsSync as existsSync17, readdirSync as readdirSync6, realpathSync as realpathSync4, statSync as statSync9 } from "fs";
|
|
9841
10599
|
import { homedir as homedir6 } from "os";
|
|
9842
|
-
import { isAbsolute as
|
|
10600
|
+
import { isAbsolute as isAbsolute4, join as join14, relative as relative4, resolve as resolve10, sep } from "path";
|
|
9843
10601
|
var RUNTIME_FLAG = "--runtime";
|
|
9844
|
-
var RUNTIME_SUBDIR =
|
|
10602
|
+
var RUNTIME_SUBDIR = join14(".rafa", "runtime");
|
|
9845
10603
|
var RUNTIME_ENTRY = "cli.js";
|
|
9846
10604
|
var SOURCE_DIR = "src";
|
|
9847
10605
|
var RUNTIME_COMMAND = ["start"];
|
|
@@ -9858,10 +10616,10 @@ function resolveRuntime(value, places) {
|
|
|
9858
10616
|
throw refusal3([`\u274C Refusing to start: \`--runtime\` names no runtime. Spell it ${SPELLING}.`]);
|
|
9859
10617
|
}
|
|
9860
10618
|
const isVersion = VERSION_SHAPE2.test(value);
|
|
9861
|
-
const named = isVersion ?
|
|
10619
|
+
const named = isVersion ? join14(places.home, RUNTIME_SUBDIR, value, RUNTIME_ENTRY) : resolve10(places.cwd, value);
|
|
9862
10620
|
refuseInsideSource(value, named, places);
|
|
9863
10621
|
const entry = entryOf(value, named, isVersion, places.home);
|
|
9864
|
-
const real =
|
|
10622
|
+
const real = realpathSync4(entry);
|
|
9865
10623
|
refuseInsideSource(value, real, places);
|
|
9866
10624
|
return real;
|
|
9867
10625
|
}
|
|
@@ -9869,7 +10627,7 @@ function entryOf(value, named, isVersion, home) {
|
|
|
9869
10627
|
if (isVersion) {
|
|
9870
10628
|
if (isFile2(named))
|
|
9871
10629
|
return named;
|
|
9872
|
-
const root =
|
|
10630
|
+
const root = join14(home, RUNTIME_SUBDIR);
|
|
9873
10631
|
const installed = installedVersions(root);
|
|
9874
10632
|
throw refusal3([
|
|
9875
10633
|
`\u274C Refusing to start: \`--runtime=${value}\` names no installed runtime: ${named} is no file.`,
|
|
@@ -9878,7 +10636,7 @@ function entryOf(value, named, isVersion, home) {
|
|
|
9878
10636
|
}
|
|
9879
10637
|
if (isFile2(named))
|
|
9880
10638
|
return named;
|
|
9881
|
-
const inside =
|
|
10639
|
+
const inside = join14(named, RUNTIME_ENTRY);
|
|
9882
10640
|
if (isDirectory(named) && isFile2(inside))
|
|
9883
10641
|
return inside;
|
|
9884
10642
|
throw refusal3([
|
|
@@ -9896,17 +10654,17 @@ function refuseInsideSource(value, path5, places) {
|
|
|
9896
10654
|
]);
|
|
9897
10655
|
}
|
|
9898
10656
|
function sourceDirs({ cwd, root }) {
|
|
9899
|
-
const dirs = [
|
|
9900
|
-
const real = dirs.filter((dir) =>
|
|
10657
|
+
const dirs = [join14(cwd, SOURCE_DIR), join14(root, SOURCE_DIR)];
|
|
10658
|
+
const real = dirs.filter((dir) => existsSync17(dir)).map((dir) => realpathSync4(dir));
|
|
9901
10659
|
return [...new Set([...dirs, ...real])];
|
|
9902
10660
|
}
|
|
9903
10661
|
function isInside(path5, dir) {
|
|
9904
|
-
const rel =
|
|
9905
|
-
return rel === "" || !
|
|
10662
|
+
const rel = relative4(dir, path5);
|
|
10663
|
+
return rel === "" || !isAbsolute4(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
|
|
9906
10664
|
}
|
|
9907
10665
|
function installedVersions(root) {
|
|
9908
10666
|
try {
|
|
9909
|
-
return readdirSync6(root).filter((name) => isFile2(
|
|
10667
|
+
return readdirSync6(root).filter((name) => isFile2(join14(root, name, RUNTIME_ENTRY))).sort((a, b) => a.localeCompare(b));
|
|
9910
10668
|
} catch {
|
|
9911
10669
|
return [];
|
|
9912
10670
|
}
|
|
@@ -9949,7 +10707,7 @@ async function runFromSelectedRuntime(seams) {
|
|
|
9949
10707
|
}
|
|
9950
10708
|
function realOrResolved(path5) {
|
|
9951
10709
|
try {
|
|
9952
|
-
return
|
|
10710
|
+
return realpathSync4(path5);
|
|
9953
10711
|
} catch {
|
|
9954
10712
|
return resolve10(path5);
|
|
9955
10713
|
}
|
|
@@ -10037,11 +10795,11 @@ function eventOf(line) {
|
|
|
10037
10795
|
}
|
|
10038
10796
|
|
|
10039
10797
|
// src/triage/triage.ts
|
|
10040
|
-
import { basename as basename6, join as
|
|
10798
|
+
import { basename as basename6, join as join15 } from "path";
|
|
10041
10799
|
|
|
10042
10800
|
// src/effort/store/tracker-refs.ts
|
|
10043
10801
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
10044
|
-
import { existsSync as
|
|
10802
|
+
import { existsSync as existsSync18 } from "fs";
|
|
10045
10803
|
var SELECT_HELD = `
|
|
10046
10804
|
SELECT seq, tracker_ref FROM findings
|
|
10047
10805
|
WHERE session_id = ? AND artifact = ?
|
|
@@ -10164,7 +10922,7 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
|
|
|
10164
10922
|
wanted.json,
|
|
10165
10923
|
collectedAt
|
|
10166
10924
|
];
|
|
10167
|
-
const [action, stored] = withSqliteStore(path5, !
|
|
10925
|
+
const [action, stored] = withSqliteStore(path5, !existsSync18(path5), (db) => {
|
|
10168
10926
|
const place = db.transaction(() => placeRef(db, path5, write, wanted, insertValues));
|
|
10169
10927
|
return place.immediate();
|
|
10170
10928
|
});
|
|
@@ -10173,14 +10931,14 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
|
|
|
10173
10931
|
function readTrackerRef(repoRoot, artifact) {
|
|
10174
10932
|
checkArtifact("read", artifact);
|
|
10175
10933
|
const path5 = sqliteStorePath(repoRoot);
|
|
10176
|
-
if (!
|
|
10934
|
+
if (!existsSync18(path5))
|
|
10177
10935
|
return null;
|
|
10178
10936
|
const oldest = withSqliteStore(path5, false, (db) => db.query(SELECT_OLDEST).get(artifact));
|
|
10179
10937
|
return oldest === null ? null : heldRefOf(path5, oldest.seq, oldest.tracker_ref).ref;
|
|
10180
10938
|
}
|
|
10181
10939
|
|
|
10182
10940
|
// src/triage/triage.ts
|
|
10183
|
-
var PRIVATE_TRIAGE_DIR =
|
|
10941
|
+
var PRIVATE_TRIAGE_DIR = join15(".rafa", "triage", "private");
|
|
10184
10942
|
var SECRET_ENV_NAMES = ["GITHUB_TOKEN", "ANTHROPIC_API_KEY", "LINEAR_API_KEY"];
|
|
10185
10943
|
var TRIAGE_MODULE = "unassigned";
|
|
10186
10944
|
var BLOCKER_SEPARATOR = "; ";
|
|
@@ -10191,7 +10949,7 @@ var MIN_FENCE_LENGTH = 3;
|
|
|
10191
10949
|
var ISSUE_OPENING = "An out-of-scope bug a rafa task session reported. The loop filed it and" + " dispatches no task for it: a plan that wants it fixed declares a task.";
|
|
10192
10950
|
var COMMENT_OPENING = "Reported again by a rafa task session.";
|
|
10193
10951
|
function privateTriageDir(repoRoot) {
|
|
10194
|
-
return
|
|
10952
|
+
return join15(repoRoot, PRIVATE_TRIAGE_DIR);
|
|
10195
10953
|
}
|
|
10196
10954
|
function createPrivateTriageTracker(repoRoot, options = {}) {
|
|
10197
10955
|
return createLocalTracker({
|
|
@@ -10768,6 +11526,13 @@ async function start(args, repoRoot) {
|
|
|
10768
11526
|
args
|
|
10769
11527
|
});
|
|
10770
11528
|
guardRunBranch(planStub2, branch, args);
|
|
11529
|
+
await announceRiskTotal({
|
|
11530
|
+
repoRoot,
|
|
11531
|
+
home: homedir7(),
|
|
11532
|
+
planPath,
|
|
11533
|
+
config: runConfig.config,
|
|
11534
|
+
environment: process.env
|
|
11535
|
+
});
|
|
10771
11536
|
await requireNoticesAnswered();
|
|
10772
11537
|
setActivePlanStub(planStub2);
|
|
10773
11538
|
const session = openRunSession({ repoRoot, planPath, planStub: planStub2, branch });
|