@open-tomato/rafa 0.9.2 → 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 +7 -1
- package/dist/SKILL.md +21 -0
- package/dist/cli.js +1987 -1018
- package/dist/effort/store/index.js +1 -1
- package/dist/{index-eqsm86n7.js → index-srdtw7w1.js} +68 -2
- package/dist/index.js +794 -48
- package/dist/plan-prompt.md +4 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -30,10 +30,12 @@ import {
|
|
|
30
30
|
import {
|
|
31
31
|
CORE_ADAPTER_REGISTRY,
|
|
32
32
|
ClaudePlannerError,
|
|
33
|
+
CommandExit,
|
|
33
34
|
EFFORT_KEY_PROJECTIONS,
|
|
34
35
|
LONE_SURROGATE,
|
|
35
36
|
PORT_VERSIONS,
|
|
36
37
|
checkUsage,
|
|
38
|
+
commandSpelling,
|
|
37
39
|
createAdapterRegistry,
|
|
38
40
|
createGhRunner,
|
|
39
41
|
createLocalTracker,
|
|
@@ -48,7 +50,7 @@ import {
|
|
|
48
50
|
sqliteStorePath,
|
|
49
51
|
withSqliteStore,
|
|
50
52
|
writeSqliteStore
|
|
51
|
-
} from "./index-
|
|
53
|
+
} from "./index-srdtw7w1.js";
|
|
52
54
|
import {
|
|
53
55
|
BUDGET_FLAG,
|
|
54
56
|
CLAUDE_SETTING_SOURCES,
|
|
@@ -132,27 +134,6 @@ import { existsSync as existsSync2, readdirSync, statSync } from "fs";
|
|
|
132
134
|
import { homedir } from "os";
|
|
133
135
|
import { join, resolve } from "path";
|
|
134
136
|
|
|
135
|
-
// src/cli/command.ts
|
|
136
|
-
var MAX_EXIT_CODE = 255;
|
|
137
|
-
|
|
138
|
-
class CommandExit extends Error {
|
|
139
|
-
exitCode;
|
|
140
|
-
constructor(exitCode, message = "") {
|
|
141
|
-
super(message);
|
|
142
|
-
if (!Number.isInteger(exitCode) || exitCode < 0 || exitCode > MAX_EXIT_CODE) {
|
|
143
|
-
throw new TypeError(`CommandExit: exit code is ${describeValue(exitCode)}, expected a whole number from 0 to ${MAX_EXIT_CODE}`);
|
|
144
|
-
}
|
|
145
|
-
this.name = "CommandExit";
|
|
146
|
-
this.exitCode = exitCode;
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
function isTopLevel(command) {
|
|
150
|
-
return command.subject === command.action;
|
|
151
|
-
}
|
|
152
|
-
function commandSpelling(command) {
|
|
153
|
-
return isTopLevel(command) ? command.subject : `${command.subject} ${command.action}`;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
137
|
// src/effort/session-log.ts
|
|
157
138
|
import { createReadStream } from "fs";
|
|
158
139
|
import { basename } from "path";
|
|
@@ -1914,7 +1895,7 @@ async function report(args, repoRoot) {
|
|
|
1914
1895
|
}
|
|
1915
1896
|
}
|
|
1916
1897
|
// package.json
|
|
1917
|
-
var version = "0.
|
|
1898
|
+
var version = "0.11.0";
|
|
1918
1899
|
|
|
1919
1900
|
// src/modules/manifest.ts
|
|
1920
1901
|
var MANIFEST_VERSION = 1;
|
|
@@ -2204,9 +2185,11 @@ function requireProject(context, command) {
|
|
|
2204
2185
|
return context.project;
|
|
2205
2186
|
}
|
|
2206
2187
|
function resolvePlansDir(project, command, warn) {
|
|
2188
|
+
return plansDirAt(project.root, resolveProjectConfig(project, command, warn).planDir);
|
|
2189
|
+
}
|
|
2190
|
+
function resolveProjectConfig(project, command, warn) {
|
|
2207
2191
|
try {
|
|
2208
|
-
|
|
2209
|
-
return plansDirAt(project.root, config.planDir);
|
|
2192
|
+
return loadConfig({ root: project.root, home: project.home }, {}, warn).config;
|
|
2210
2193
|
} catch (error) {
|
|
2211
2194
|
if (!(error instanceof ConfigError))
|
|
2212
2195
|
throw error;
|
|
@@ -9708,12 +9691,768 @@ async function finishRelease(input, seams = {}) {
|
|
|
9708
9691
|
return { outcome: "released", version: version2, subject, sha, sentence: null, body: null };
|
|
9709
9692
|
}
|
|
9710
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
|
+
|
|
9711
10450
|
// src/start/session.ts
|
|
9712
10451
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
9713
|
-
import { relative as
|
|
10452
|
+
import { relative as relative3 } from "path";
|
|
9714
10453
|
var NOTHING_DISPATCHED = " Nothing was checked and nothing was dispatched.";
|
|
9715
10454
|
function recordLabel(repoRoot, sessionId) {
|
|
9716
|
-
return
|
|
10455
|
+
return relative3(repoRoot, sessionFilePath(repoRoot, sessionId));
|
|
9717
10456
|
}
|
|
9718
10457
|
function conflictLine(repoRoot, conflict) {
|
|
9719
10458
|
const { record } = conflict;
|
|
@@ -9743,7 +10482,7 @@ function openRefusal(repoRoot, draft, error) {
|
|
|
9743
10482
|
if (!(error instanceof SessionRecordError) && !isSystemError)
|
|
9744
10483
|
return null;
|
|
9745
10484
|
return [
|
|
9746
|
-
`\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.`,
|
|
9747
10486
|
` ${messageOf(error)}`,
|
|
9748
10487
|
NOTHING_DISPATCHED
|
|
9749
10488
|
].join(`
|
|
@@ -9779,7 +10518,7 @@ function openRunSession(options) {
|
|
|
9779
10518
|
const draft = {
|
|
9780
10519
|
sessionId: (seams.newSessionId ?? randomUUID8)(),
|
|
9781
10520
|
planStub: options.planStub,
|
|
9782
|
-
plan:
|
|
10521
|
+
plan: relative3(repoRoot, options.planPath),
|
|
9783
10522
|
branch: options.branch,
|
|
9784
10523
|
pid: seams.pid ?? process.pid,
|
|
9785
10524
|
startedAt: (seams.now ?? (() => new Date))().toISOString()
|
|
@@ -9856,11 +10595,11 @@ function announcePlanIssues(planContent) {
|
|
|
9856
10595
|
}
|
|
9857
10596
|
|
|
9858
10597
|
// src/start/runtime.ts
|
|
9859
|
-
import { existsSync as
|
|
10598
|
+
import { existsSync as existsSync17, readdirSync as readdirSync6, realpathSync as realpathSync4, statSync as statSync9 } from "fs";
|
|
9860
10599
|
import { homedir as homedir6 } from "os";
|
|
9861
|
-
import { isAbsolute as
|
|
10600
|
+
import { isAbsolute as isAbsolute4, join as join14, relative as relative4, resolve as resolve10, sep } from "path";
|
|
9862
10601
|
var RUNTIME_FLAG = "--runtime";
|
|
9863
|
-
var RUNTIME_SUBDIR =
|
|
10602
|
+
var RUNTIME_SUBDIR = join14(".rafa", "runtime");
|
|
9864
10603
|
var RUNTIME_ENTRY = "cli.js";
|
|
9865
10604
|
var SOURCE_DIR = "src";
|
|
9866
10605
|
var RUNTIME_COMMAND = ["start"];
|
|
@@ -9877,10 +10616,10 @@ function resolveRuntime(value, places) {
|
|
|
9877
10616
|
throw refusal3([`\u274C Refusing to start: \`--runtime\` names no runtime. Spell it ${SPELLING}.`]);
|
|
9878
10617
|
}
|
|
9879
10618
|
const isVersion = VERSION_SHAPE2.test(value);
|
|
9880
|
-
const named = isVersion ?
|
|
10619
|
+
const named = isVersion ? join14(places.home, RUNTIME_SUBDIR, value, RUNTIME_ENTRY) : resolve10(places.cwd, value);
|
|
9881
10620
|
refuseInsideSource(value, named, places);
|
|
9882
10621
|
const entry = entryOf(value, named, isVersion, places.home);
|
|
9883
|
-
const real =
|
|
10622
|
+
const real = realpathSync4(entry);
|
|
9884
10623
|
refuseInsideSource(value, real, places);
|
|
9885
10624
|
return real;
|
|
9886
10625
|
}
|
|
@@ -9888,7 +10627,7 @@ function entryOf(value, named, isVersion, home) {
|
|
|
9888
10627
|
if (isVersion) {
|
|
9889
10628
|
if (isFile2(named))
|
|
9890
10629
|
return named;
|
|
9891
|
-
const root =
|
|
10630
|
+
const root = join14(home, RUNTIME_SUBDIR);
|
|
9892
10631
|
const installed = installedVersions(root);
|
|
9893
10632
|
throw refusal3([
|
|
9894
10633
|
`\u274C Refusing to start: \`--runtime=${value}\` names no installed runtime: ${named} is no file.`,
|
|
@@ -9897,7 +10636,7 @@ function entryOf(value, named, isVersion, home) {
|
|
|
9897
10636
|
}
|
|
9898
10637
|
if (isFile2(named))
|
|
9899
10638
|
return named;
|
|
9900
|
-
const inside =
|
|
10639
|
+
const inside = join14(named, RUNTIME_ENTRY);
|
|
9901
10640
|
if (isDirectory(named) && isFile2(inside))
|
|
9902
10641
|
return inside;
|
|
9903
10642
|
throw refusal3([
|
|
@@ -9915,17 +10654,17 @@ function refuseInsideSource(value, path5, places) {
|
|
|
9915
10654
|
]);
|
|
9916
10655
|
}
|
|
9917
10656
|
function sourceDirs({ cwd, root }) {
|
|
9918
|
-
const dirs = [
|
|
9919
|
-
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));
|
|
9920
10659
|
return [...new Set([...dirs, ...real])];
|
|
9921
10660
|
}
|
|
9922
10661
|
function isInside(path5, dir) {
|
|
9923
|
-
const rel =
|
|
9924
|
-
return rel === "" || !
|
|
10662
|
+
const rel = relative4(dir, path5);
|
|
10663
|
+
return rel === "" || !isAbsolute4(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
|
|
9925
10664
|
}
|
|
9926
10665
|
function installedVersions(root) {
|
|
9927
10666
|
try {
|
|
9928
|
-
return readdirSync6(root).filter((name) => isFile2(
|
|
10667
|
+
return readdirSync6(root).filter((name) => isFile2(join14(root, name, RUNTIME_ENTRY))).sort((a, b) => a.localeCompare(b));
|
|
9929
10668
|
} catch {
|
|
9930
10669
|
return [];
|
|
9931
10670
|
}
|
|
@@ -9968,7 +10707,7 @@ async function runFromSelectedRuntime(seams) {
|
|
|
9968
10707
|
}
|
|
9969
10708
|
function realOrResolved(path5) {
|
|
9970
10709
|
try {
|
|
9971
|
-
return
|
|
10710
|
+
return realpathSync4(path5);
|
|
9972
10711
|
} catch {
|
|
9973
10712
|
return resolve10(path5);
|
|
9974
10713
|
}
|
|
@@ -10056,11 +10795,11 @@ function eventOf(line) {
|
|
|
10056
10795
|
}
|
|
10057
10796
|
|
|
10058
10797
|
// src/triage/triage.ts
|
|
10059
|
-
import { basename as basename6, join as
|
|
10798
|
+
import { basename as basename6, join as join15 } from "path";
|
|
10060
10799
|
|
|
10061
10800
|
// src/effort/store/tracker-refs.ts
|
|
10062
10801
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
10063
|
-
import { existsSync as
|
|
10802
|
+
import { existsSync as existsSync18 } from "fs";
|
|
10064
10803
|
var SELECT_HELD = `
|
|
10065
10804
|
SELECT seq, tracker_ref FROM findings
|
|
10066
10805
|
WHERE session_id = ? AND artifact = ?
|
|
@@ -10183,7 +10922,7 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
|
|
|
10183
10922
|
wanted.json,
|
|
10184
10923
|
collectedAt
|
|
10185
10924
|
];
|
|
10186
|
-
const [action, stored] = withSqliteStore(path5, !
|
|
10925
|
+
const [action, stored] = withSqliteStore(path5, !existsSync18(path5), (db) => {
|
|
10187
10926
|
const place = db.transaction(() => placeRef(db, path5, write, wanted, insertValues));
|
|
10188
10927
|
return place.immediate();
|
|
10189
10928
|
});
|
|
@@ -10192,14 +10931,14 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
|
|
|
10192
10931
|
function readTrackerRef(repoRoot, artifact) {
|
|
10193
10932
|
checkArtifact("read", artifact);
|
|
10194
10933
|
const path5 = sqliteStorePath(repoRoot);
|
|
10195
|
-
if (!
|
|
10934
|
+
if (!existsSync18(path5))
|
|
10196
10935
|
return null;
|
|
10197
10936
|
const oldest = withSqliteStore(path5, false, (db) => db.query(SELECT_OLDEST).get(artifact));
|
|
10198
10937
|
return oldest === null ? null : heldRefOf(path5, oldest.seq, oldest.tracker_ref).ref;
|
|
10199
10938
|
}
|
|
10200
10939
|
|
|
10201
10940
|
// src/triage/triage.ts
|
|
10202
|
-
var PRIVATE_TRIAGE_DIR =
|
|
10941
|
+
var PRIVATE_TRIAGE_DIR = join15(".rafa", "triage", "private");
|
|
10203
10942
|
var SECRET_ENV_NAMES = ["GITHUB_TOKEN", "ANTHROPIC_API_KEY", "LINEAR_API_KEY"];
|
|
10204
10943
|
var TRIAGE_MODULE = "unassigned";
|
|
10205
10944
|
var BLOCKER_SEPARATOR = "; ";
|
|
@@ -10210,7 +10949,7 @@ var MIN_FENCE_LENGTH = 3;
|
|
|
10210
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.";
|
|
10211
10950
|
var COMMENT_OPENING = "Reported again by a rafa task session.";
|
|
10212
10951
|
function privateTriageDir(repoRoot) {
|
|
10213
|
-
return
|
|
10952
|
+
return join15(repoRoot, PRIVATE_TRIAGE_DIR);
|
|
10214
10953
|
}
|
|
10215
10954
|
function createPrivateTriageTracker(repoRoot, options = {}) {
|
|
10216
10955
|
return createLocalTracker({
|
|
@@ -10787,6 +11526,13 @@ async function start(args, repoRoot) {
|
|
|
10787
11526
|
args
|
|
10788
11527
|
});
|
|
10789
11528
|
guardRunBranch(planStub2, branch, args);
|
|
11529
|
+
await announceRiskTotal({
|
|
11530
|
+
repoRoot,
|
|
11531
|
+
home: homedir7(),
|
|
11532
|
+
planPath,
|
|
11533
|
+
config: runConfig.config,
|
|
11534
|
+
environment: process.env
|
|
11535
|
+
});
|
|
10790
11536
|
await requireNoticesAnswered();
|
|
10791
11537
|
setActivePlanStub(planStub2);
|
|
10792
11538
|
const session = openRunSession({ repoRoot, planPath, planStub: planStub2, branch });
|