@open-tomato/rafa 0.10.0 → 0.12.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/dist/index.js CHANGED
@@ -50,7 +50,7 @@ import {
50
50
  sqliteStorePath,
51
51
  withSqliteStore,
52
52
  writeSqliteStore
53
- } from "./index-srdtw7w1.js";
53
+ } from "./index-4g6vyznd.js";
54
54
  import {
55
55
  BUDGET_FLAG,
56
56
  CLAUDE_SETTING_SOURCES,
@@ -337,6 +337,13 @@ var PROMPT_SHAPES = [
337
337
  prefix: "The pull request for branch ",
338
338
  firstLineInfix: " is not mergeable: ",
339
339
  source: "src/start/pr-lifecycle.ts"
340
+ },
341
+ {
342
+ kind: "search",
343
+ label: "search",
344
+ prefix: "# Inventory search instructions",
345
+ firstLineInfix: null,
346
+ source: "src/inventory/search/prompt.ts"
340
347
  }
341
348
  ];
342
349
  var ENQUEUE_SCAN_WINDOW = 10;
@@ -1895,7 +1902,7 @@ async function report(args, repoRoot) {
1895
1902
  }
1896
1903
  }
1897
1904
  // package.json
1898
- var version = "0.10.0";
1905
+ var version = "0.12.0";
1899
1906
 
1900
1907
  // src/modules/manifest.ts
1901
1908
  var MANIFEST_VERSION = 1;
@@ -2185,9 +2192,11 @@ function requireProject(context, command) {
2185
2192
  return context.project;
2186
2193
  }
2187
2194
  function resolvePlansDir(project, command, warn) {
2195
+ return plansDirAt(project.root, resolveProjectConfig(project, command, warn).planDir);
2196
+ }
2197
+ function resolveProjectConfig(project, command, warn) {
2188
2198
  try {
2189
- const { config } = loadConfig({ root: project.root, home: project.home }, {}, warn);
2190
- return plansDirAt(project.root, config.planDir);
2199
+ return loadConfig({ root: project.root, home: project.home }, {}, warn).config;
2191
2200
  } catch (error) {
2192
2201
  if (!(error instanceof ConfigError))
2193
2202
  throw error;
@@ -2376,8 +2385,7 @@ function definitionName(text) {
2376
2385
  const name = frontmatter["name"];
2377
2386
  return typeof name === "string" && name.length > 0 ? name : null;
2378
2387
  }
2379
- function readAgentDefinitions(root) {
2380
- const dir = join3(root, AGENT_DEFINITION_DIR);
2388
+ function readAgentDirectory(dir) {
2381
2389
  const found = [];
2382
2390
  for (const file of markdownFiles(dir)) {
2383
2391
  const path = join3(dir, file);
@@ -2391,6 +2399,9 @@ function readAgentDefinitions(root) {
2391
2399
  }
2392
2400
  return found.sort((a, b) => a.name.localeCompare(b.name));
2393
2401
  }
2402
+ function readAgentDefinitions(root) {
2403
+ return readAgentDirectory(join3(root, AGENT_DEFINITION_DIR));
2404
+ }
2394
2405
  function firstByName(files) {
2395
2406
  const byName = new Map;
2396
2407
  for (const file of files) {
@@ -4724,112 +4735,8 @@ async function resolvePlanSpec(options) {
4724
4735
  });
4725
4736
  }
4726
4737
 
4727
- // src/project/root-choice.ts
4738
+ // src/cli/prompt/confirm.ts
4728
4739
  import { createInterface as createInterface2 } from "readline";
4729
-
4730
- // src/project/roots.ts
4731
- import { accessSync, constants, readFileSync as readFileSync8, statSync as statSync7 } from "fs";
4732
-
4733
- // src/project/scope.ts
4734
- import { existsSync as existsSync9, realpathSync as realpathSync2 } from "fs";
4735
- import { dirname as dirname3, isAbsolute as isAbsolute2, join as join6 } from "path";
4736
- var SCOPE_DIR = dirname3(CONFIG_FILE);
4737
- var INIT_COMMAND = "rafa init";
4738
- var DISK_FILE_SYSTEM = Object.freeze({
4739
- exists: (path3) => existsSync9(path3),
4740
- realpath: (path3) => realpathSync2(path3)
4741
- });
4742
-
4743
- class ScopeError extends Error {
4744
- constructor(message, options) {
4745
- super(`rafa scope: ${message}`, options);
4746
- this.name = "ScopeError";
4747
- }
4748
- }
4749
- function scopeAt(base) {
4750
- return { dir: join6(base, SCOPE_DIR), configFile: configFilePath(base) };
4751
- }
4752
- function initHint(start, passedUserConfig = null) {
4753
- const passed2 = passedUserConfig === null ? [] : [`${passedUserConfig} is the user scope's config and marks no project`];
4754
- return [
4755
- `not inside a rafa project: no ${CONFIG_FILE} in ${start} or any directory above it`,
4756
- ...passed2,
4757
- `run \`${INIT_COMMAND}\` to set one up`
4758
- ].join(`
4759
- `);
4760
- }
4761
- function requireAbsolute(label, path3) {
4762
- if (!isAbsolute2(path3)) {
4763
- throw new ScopeError(`${label} is ${describeValue(path3)}, expected an absolute path`);
4764
- }
4765
- }
4766
- function realStartOf(start, fs2) {
4767
- try {
4768
- return fs2.realpath(start);
4769
- } catch (error) {
4770
- const message = `start directory ${start} does not resolve (${messageOf(error)})`;
4771
- throw new ScopeError(message, { cause: error });
4772
- }
4773
- }
4774
- function realHomeOf(home, fs2) {
4775
- try {
4776
- return fs2.realpath(home);
4777
- } catch {
4778
- return null;
4779
- }
4780
- }
4781
- function selfAndAncestors(path3) {
4782
- const parent = dirname3(path3);
4783
- return parent === path3 ? [path3] : [path3, ...selfAndAncestors(parent)];
4784
- }
4785
- function walkUp(realStart, realHome, fs2) {
4786
- let passedUserConfig = false;
4787
- for (const dir of selfAndAncestors(realStart)) {
4788
- if (!fs2.exists(configFilePath(dir)))
4789
- continue;
4790
- if (dir !== realHome)
4791
- return { root: dir, passedUserConfig };
4792
- passedUserConfig = true;
4793
- }
4794
- return { root: null, passedUserConfig };
4795
- }
4796
- function resolveScope(start, seams) {
4797
- const { home, fs: fs2 = DISK_FILE_SYSTEM } = seams;
4798
- requireAbsolute("start directory", start);
4799
- requireAbsolute("home directory", home);
4800
- const user = scopeAt(home);
4801
- const walk = walkUp(realStartOf(start, fs2), realHomeOf(home, fs2), fs2);
4802
- if (walk.root === null) {
4803
- const passed2 = walk.passedUserConfig ? user.configFile : null;
4804
- return { found: false, start, home, user, hint: initHint(start, passed2) };
4805
- }
4806
- return { found: true, root: walk.root, home, project: scopeAt(walk.root), user };
4807
- }
4808
-
4809
- // src/project/roots.ts
4810
- var REFUSED_ROOTS = Object.freeze([
4811
- { path: "/", why: "is the filesystem root" },
4812
- { path: "/var", why: "holds system state" },
4813
- { path: "/etc", why: "holds system configuration" },
4814
- { path: "/usr", why: "holds system programs" },
4815
- { path: "/home", why: "holds the home directories of every user" },
4816
- { path: "/Users", why: "holds the home directories of every user" }
4817
- ]);
4818
- var HOME_WHY = `is the home directory, whose ${SCOPE_DIR}/ is the user scope`;
4819
- var MONOREPO_MARKER_FILES = Object.freeze([
4820
- "turbo.json",
4821
- "pnpm-workspace.yaml",
4822
- "nx.json",
4823
- "lerna.json"
4824
- ]);
4825
- var DISK_ROOTS_FILE_SYSTEM = Object.freeze({
4826
- ...DISK_FILE_SYSTEM,
4827
- readFile: (path3) => readFileSync8(path3, "utf8"),
4828
- isDirectory: (path3) => statSync7(path3).isDirectory(),
4829
- assertWritable: (path3) => accessSync(path3, constants.W_OK)
4830
- });
4831
-
4832
- // src/project/root-choice.ts
4833
4740
  function createLinePrompter(input, output) {
4834
4741
  const buffered = [];
4835
4742
  const waiting = [];
@@ -5637,12 +5544,90 @@ import {
5637
5544
  linkSync as linkSync2,
5638
5545
  mkdirSync as mkdirSync4,
5639
5546
  readdirSync as readdirSync4,
5640
- readFileSync as readFileSync9,
5547
+ readFileSync as readFileSync8,
5641
5548
  renameSync as renameSync2,
5642
5549
  rmSync,
5643
5550
  writeFileSync as writeFileSync3
5644
5551
  } from "fs";
5645
5552
  import { basename as basename4, dirname as dirname4, join as join7 } from "path";
5553
+
5554
+ // src/project/scope.ts
5555
+ import { existsSync as existsSync9, realpathSync as realpathSync2 } from "fs";
5556
+ import { dirname as dirname3, isAbsolute as isAbsolute2, join as join6 } from "path";
5557
+ var SCOPE_DIR = dirname3(CONFIG_FILE);
5558
+ var INIT_COMMAND = "rafa init";
5559
+ var DISK_FILE_SYSTEM = Object.freeze({
5560
+ exists: (path3) => existsSync9(path3),
5561
+ realpath: (path3) => realpathSync2(path3)
5562
+ });
5563
+
5564
+ class ScopeError extends Error {
5565
+ constructor(message, options) {
5566
+ super(`rafa scope: ${message}`, options);
5567
+ this.name = "ScopeError";
5568
+ }
5569
+ }
5570
+ function scopeAt(base) {
5571
+ return { dir: join6(base, SCOPE_DIR), configFile: configFilePath(base) };
5572
+ }
5573
+ function initHint(start, passedUserConfig = null) {
5574
+ const passed2 = passedUserConfig === null ? [] : [`${passedUserConfig} is the user scope's config and marks no project`];
5575
+ return [
5576
+ `not inside a rafa project: no ${CONFIG_FILE} in ${start} or any directory above it`,
5577
+ ...passed2,
5578
+ `run \`${INIT_COMMAND}\` to set one up`
5579
+ ].join(`
5580
+ `);
5581
+ }
5582
+ function requireAbsolute(label, path3) {
5583
+ if (!isAbsolute2(path3)) {
5584
+ throw new ScopeError(`${label} is ${describeValue(path3)}, expected an absolute path`);
5585
+ }
5586
+ }
5587
+ function realStartOf(start, fs2) {
5588
+ try {
5589
+ return fs2.realpath(start);
5590
+ } catch (error) {
5591
+ const message = `start directory ${start} does not resolve (${messageOf(error)})`;
5592
+ throw new ScopeError(message, { cause: error });
5593
+ }
5594
+ }
5595
+ function realHomeOf(home, fs2) {
5596
+ try {
5597
+ return fs2.realpath(home);
5598
+ } catch {
5599
+ return null;
5600
+ }
5601
+ }
5602
+ function selfAndAncestors(path3) {
5603
+ const parent = dirname3(path3);
5604
+ return parent === path3 ? [path3] : [path3, ...selfAndAncestors(parent)];
5605
+ }
5606
+ function walkUp(realStart, realHome, fs2) {
5607
+ let passedUserConfig = false;
5608
+ for (const dir of selfAndAncestors(realStart)) {
5609
+ if (!fs2.exists(configFilePath(dir)))
5610
+ continue;
5611
+ if (dir !== realHome)
5612
+ return { root: dir, passedUserConfig };
5613
+ passedUserConfig = true;
5614
+ }
5615
+ return { root: null, passedUserConfig };
5616
+ }
5617
+ function resolveScope(start, seams) {
5618
+ const { home, fs: fs2 = DISK_FILE_SYSTEM } = seams;
5619
+ requireAbsolute("start directory", start);
5620
+ requireAbsolute("home directory", home);
5621
+ const user = scopeAt(home);
5622
+ const walk = walkUp(realStartOf(start, fs2), realHomeOf(home, fs2), fs2);
5623
+ if (walk.root === null) {
5624
+ const passed2 = walk.passedUserConfig ? user.configFile : null;
5625
+ return { found: false, start, home, user, hint: initHint(start, passed2) };
5626
+ }
5627
+ return { found: true, root: walk.root, home, project: scopeAt(walk.root), user };
5628
+ }
5629
+
5630
+ // src/loop/sessions.ts
5646
5631
  var SESSION_STATES = Object.freeze(["running", "paused", "stopped", "done"]);
5647
5632
  var RUNS_DIR = "runs";
5648
5633
  var RECORD_EXTENSION = ".json";
@@ -5790,7 +5775,7 @@ function readState2(record, isAlive = isPidAlive) {
5790
5775
  function readRecordFile(file) {
5791
5776
  let text;
5792
5777
  try {
5793
- text = readFileSync9(file, "utf8");
5778
+ text = readFileSync8(file, "utf8");
5794
5779
  } catch (error) {
5795
5780
  throw new SessionRecordError(file, `cannot be read: ${messageOf(error)}`);
5796
5781
  }
@@ -6003,15 +5988,15 @@ function openNextSources(context, seams = DEFAULT_NEXT_SOURCE_SEAMS) {
6003
5988
  }
6004
5989
 
6005
5990
  // src/commands/plan/list.ts
6006
- import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync10 } from "fs";
5991
+ import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync9 } from "fs";
6007
5992
  import { join as join9 } from "path";
6008
5993
  var USAGE2 = "rafa plan list";
6009
5994
  function listing(dir, stub) {
6010
5995
  const plan = join9(dir, planFileName(stub, false));
6011
5996
  const trackerPath = join9(dir, planFileName(stub, true));
6012
5997
  const tracker = isFile(trackerPath) ? trackerPath : null;
6013
- const planModel = parsePlan(readFileSync10(plan, "utf8"));
6014
- const counted2 = tracker === null ? planModel : parsePlan(readFileSync10(tracker, "utf8"));
5998
+ const planModel = parsePlan(readFileSync9(plan, "utf8"));
5999
+ const counted2 = tracker === null ? planModel : parsePlan(readFileSync9(tracker, "utf8"));
6015
6000
  return { stub, plan, tracker, tasks: countTasks(counted2.tasks), issues: planModel.issues.length };
6016
6001
  }
6017
6002
  function listPlans(plans) {
@@ -7027,7 +7012,7 @@ import { homedir as homedir3 } from "os";
7027
7012
  var RAFA_VERSION = version;
7028
7013
 
7029
7014
  // src/notices/notices.ts
7030
- import { mkdirSync as mkdirSync5, readFileSync as readFileSync11, writeFileSync as writeFileSync4 } from "fs";
7015
+ import { mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync4 } from "fs";
7031
7016
  import { dirname as dirname5, join as join10 } from "path";
7032
7017
  var NOTICE_IDS = Object.freeze(["alpha", "danger"]);
7033
7018
  var FEEDBACK_URL = "https://github.com/open-tomato/rafa/issues/new/choose";
@@ -7041,7 +7026,7 @@ function isNoticeId(value) {
7041
7026
  function readDismissed(home) {
7042
7027
  let raw;
7043
7028
  try {
7044
- raw = readFileSync11(noticesPath(home), "utf8");
7029
+ raw = readFileSync10(noticesPath(home), "utf8");
7045
7030
  } catch {
7046
7031
  return [];
7047
7032
  }
@@ -8258,16 +8243,16 @@ function writeProgress(repoRoot, planStub2) {
8258
8243
 
8259
8244
  // src/start/preflight.ts
8260
8245
  import { randomUUID as randomUUID6 } from "crypto";
8261
- import { existsSync as existsSync14, readFileSync as readFileSync13 } from "fs";
8246
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "fs";
8262
8247
  import { homedir as homedir5 } from "os";
8263
8248
  import { basename as basename5 } from "path";
8264
8249
 
8265
8250
  // src/preflight/first-dispatch.ts
8266
- import { readFileSync as readFileSync12 } from "fs";
8251
+ import { readFileSync as readFileSync11 } from "fs";
8267
8252
  var TICKED_TASK = /^- \[[xX]\] (.+)/;
8268
8253
  function readIfReadable(path5) {
8269
8254
  try {
8270
- return readFileSync12(path5, "utf8");
8255
+ return readFileSync11(path5, "utf8");
8271
8256
  } catch {
8272
8257
  return null;
8273
8258
  }
@@ -8304,7 +8289,7 @@ function agentSourcePath(planPath) {
8304
8289
  }
8305
8290
  function readIfReadable2(path5) {
8306
8291
  try {
8307
- return readFileSync13(path5, "utf8");
8292
+ return readFileSync12(path5, "utf8");
8308
8293
  } catch {
8309
8294
  return null;
8310
8295
  }
@@ -8798,7 +8783,7 @@ async function handleRedChecks(io, branch, prNumber, rows) {
8798
8783
  }
8799
8784
 
8800
8785
  // src/release/prepare.ts
8801
- import { readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
8786
+ import { readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
8802
8787
 
8803
8788
  // src/release/changelog.ts
8804
8789
  var RELEASE_HEADING_FIELDS = ["version", "date", "title"];
@@ -8983,11 +8968,11 @@ function changelogDate(now) {
8983
8968
  }
8984
8969
 
8985
8970
  // src/release/enabled.ts
8986
- import { statSync as statSync8 } from "fs";
8971
+ import { statSync as statSync7 } from "fs";
8987
8972
  import { resolve as resolve9 } from "path";
8988
8973
  function isRegularFile(full) {
8989
8974
  try {
8990
- return statSync8(full).isFile();
8975
+ return statSync7(full).isFile();
8991
8976
  } catch {
8992
8977
  return false;
8993
8978
  }
@@ -9030,7 +9015,7 @@ function resolveReleaseLevel(declared, notes) {
9030
9015
  }
9031
9016
 
9032
9017
  // src/release/version.ts
9033
- import { readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
9018
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
9034
9019
  var RELEASE_REMOTE = "origin";
9035
9020
  var RELEASE_BASE_BRANCH = "main";
9036
9021
  var VERSION_SHAPE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+([0-9A-Za-z.-]+))?$/;
@@ -9122,7 +9107,7 @@ function writeManifestVersion(path5, next) {
9122
9107
  const unwritten = { path: path5, written: false, previous: null, version: next };
9123
9108
  let text;
9124
9109
  try {
9125
- text = readFileSync14(path5, "utf8");
9110
+ text = readFileSync13(path5, "utf8");
9126
9111
  } catch (error) {
9127
9112
  return { ...unwritten, problem: `${path5} could not be read: ${messageOf(error)}` };
9128
9113
  }
@@ -9225,7 +9210,7 @@ function planVersion(input, versionFile, level) {
9225
9210
  }
9226
9211
  function textOf(path5) {
9227
9212
  try {
9228
- return readFileSync15(path5, "utf8");
9213
+ return readFileSync14(path5, "utf8");
9229
9214
  } catch {
9230
9215
  return null;
9231
9216
  }
@@ -9338,7 +9323,7 @@ function changelogFailure(failed3, versionEdit, restored) {
9338
9323
  }
9339
9324
 
9340
9325
  // src/release/verify.ts
9341
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync8 } from "fs";
9326
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
9342
9327
  function changelogInsertionSpan(before, after) {
9343
9328
  const beforeLines = before.split(`
9344
9329
  `);
@@ -9355,7 +9340,7 @@ function changelogInsertionSpan(before, after) {
9355
9340
  }
9356
9341
  function textOf2(path5) {
9357
9342
  try {
9358
- return readFileSync16(path5, "utf8");
9343
+ return readFileSync15(path5, "utf8");
9359
9344
  } catch {
9360
9345
  return null;
9361
9346
  }
@@ -9689,12 +9674,768 @@ async function finishRelease(input, seams = {}) {
9689
9674
  return { outcome: "released", version: version2, subject, sha, sentence: null, body: null };
9690
9675
  }
9691
9676
 
9677
+ // src/start/risk-total.ts
9678
+ import { readFileSync as readFileSync17 } from "fs";
9679
+
9680
+ // src/plan/risk.ts
9681
+ import { readFileSync as readFileSync16 } from "fs";
9682
+ import { isAbsolute as isAbsolute3, join as join13, relative as relative2 } from "path";
9683
+
9684
+ // src/schema/tiers.ts
9685
+ import { existsSync as existsSync16, realpathSync as realpathSync3 } from "fs";
9686
+ import { dirname as dirname6, join as join12 } from "path";
9687
+ var SKILL_TIERS = ["project", "rafa", "user"];
9688
+ var CLAUDE_SKILLS_PATH = join12(".claude", "skills");
9689
+ var RAFA_INSTINCTS_PATH = join12(".rafa", "instincts");
9690
+ var BUNDLED_SKILLS_DIR = "skills";
9691
+ function realEntry(entry) {
9692
+ try {
9693
+ return realpathSync3(entry);
9694
+ } catch {
9695
+ return entry;
9696
+ }
9697
+ }
9698
+ function bundledSkillsDirectory(entry) {
9699
+ return join12(dirname6(realEntry(entry ?? Bun.main)), BUNDLED_SKILLS_DIR);
9700
+ }
9701
+ function skillTierDirectory(tier, seams) {
9702
+ if (tier === "user")
9703
+ return join12(seams.home, CLAUDE_SKILLS_PATH);
9704
+ if (tier === "rafa")
9705
+ return bundledSkillsDirectory(seams.entry);
9706
+ return seams.projectRoot === null ? null : join12(seams.projectRoot, CLAUDE_SKILLS_PATH);
9707
+ }
9708
+ function resolveSkillTiers(seams) {
9709
+ const located = [];
9710
+ for (const tier of SKILL_TIERS) {
9711
+ const dir = skillTierDirectory(tier, seams);
9712
+ if (dir !== null)
9713
+ located.push({ tier, dir });
9714
+ }
9715
+ return located;
9716
+ }
9717
+
9718
+ // src/plan/risk/accounts.ts
9719
+ var PUSH_REMOTE = "origin";
9720
+ var GITHUB_TRACKER = "github";
9721
+ var URL_USER_INFO = /^([A-Za-z][A-Za-z0-9+.-]*:\/\/)[^@/]+@/;
9722
+ function redactRemote(url) {
9723
+ return url.trim().replace(URL_USER_INFO, "$1***@");
9724
+ }
9725
+ function trackerChain(settings) {
9726
+ return [...new Set([settings.trackerDefault, ...settings.trackerFallback])];
9727
+ }
9728
+ async function readAccounts(settings, seams) {
9729
+ const pushUrl = remoteUrl(seams.git, ["remote", "get-url", "--push", PUSH_REMOTE]);
9730
+ const fetchUrl = remoteUrl(seams.git, ["remote", "get-url", PUSH_REMOTE]);
9731
+ const chain = trackerChain(settings);
9732
+ const reading2 = resolvePrProvider({
9733
+ configured: settings.prProvider,
9734
+ dir: "",
9735
+ readRemote: () => fetchUrl
9736
+ });
9737
+ const needsOwnRepo = chain.includes(GITHUB_TRACKER) || reading2.provider === "gh";
9738
+ const ownRepo = needsOwnRepo ? await viewRepo(seams.gh, []) : null;
9739
+ return [
9740
+ finding("push-remote", pushRemoteText(pushUrl)),
9741
+ finding("visibility", await visibilityText(pushUrl, seams.gh)),
9742
+ finding("issue-tracker", trackerText(chain, ownRepo)),
9743
+ finding("pr-provider", providerText(reading2.provider, reading2.source, ownRepo))
9744
+ ];
9745
+ }
9746
+ function finding(subject, text) {
9747
+ return { level: "note", kind: "account", subject, text };
9748
+ }
9749
+ function remoteUrl(git, args) {
9750
+ const result = git(args);
9751
+ const url = result.stdout.trim();
9752
+ return result.ok && url !== "" ? url : null;
9753
+ }
9754
+ function pushRemoteText(pushUrl) {
9755
+ return pushUrl === null ? `push remote: no ${PUSH_REMOTE} remote, so the loop's push would fail` : `push remote: ${PUSH_REMOTE} \u2192 ${redactRemote(pushUrl)}`;
9756
+ }
9757
+ async function visibilityText(pushUrl, gh) {
9758
+ if (pushUrl === null) {
9759
+ return "visibility: not read, there is no push remote";
9760
+ }
9761
+ if (!isGitHubRemote(pushUrl)) {
9762
+ const host = remoteHost(pushUrl) ?? "a local path";
9763
+ return `visibility: not read, ${host} is not a GitHub host`;
9764
+ }
9765
+ const slug = repoSlug(pushUrl);
9766
+ const view = await viewRepo(gh, [slug]);
9767
+ if (!view.ok) {
9768
+ return `visibility: not read, ${view.reason}`;
9769
+ }
9770
+ const name = view.name ?? slug;
9771
+ return view.visibility === null ? `visibility: not read, gh named no visibility for ${name}` : `visibility: ${name} is ${view.visibility}`;
9772
+ }
9773
+ function repoSlug(url) {
9774
+ return normalizeRemote(url).replace(/^([^/]+):\d+\//, "$1/").replace(/^ssh\.github\.com\//, "github.com/");
9775
+ }
9776
+ function trackerText(chain, ownRepo) {
9777
+ const [first, ...rest] = chain;
9778
+ const fallback = rest.length === 0 ? "" : `, falling back to ${rest.join(", then ")}`;
9779
+ const where = chain.includes(GITHUB_TRACKER) && ownRepo !== null ? `; github files on ${repoPhrase(ownRepo)}` : "";
9780
+ return `issue tracker: ${first ?? "none configured"}${fallback}${where}`;
9781
+ }
9782
+ function providerText(provider, source, ownRepo) {
9783
+ const from = source === "config" ? "set in the config" : `inferred from ${PUSH_REMOTE}`;
9784
+ const effect = provider === "gh" && ownRepo !== null ? `, opening pull requests on ${repoPhrase(ownRepo)}` : "";
9785
+ const none = provider === "none" ? ", so the loop pushes and opens no pull request" : "";
9786
+ return `pull request provider: ${provider} (${from})${effect}${none}`;
9787
+ }
9788
+ function repoPhrase(view) {
9789
+ if (!view.ok) {
9790
+ return `the repository gh resolves, which it could not: ${view.reason}`;
9791
+ }
9792
+ return view.name ?? "the repository gh resolves, which it did not name";
9793
+ }
9794
+ async function viewRepo(gh, target) {
9795
+ const result = await gh(["repo", "view", ...target, "--json", "nameWithOwner,visibility"]);
9796
+ if (!result.ok) {
9797
+ return { ok: false, reason: `gh repo view: ${firstLine(result.stderr) ?? "gh exited non-zero"}` };
9798
+ }
9799
+ const parsed = parseJsonObject(result.stdout);
9800
+ if (parsed === null) {
9801
+ return { ok: false, reason: "gh repo view: answered no JSON object" };
9802
+ }
9803
+ return {
9804
+ ok: true,
9805
+ name: stringField(parsed, "nameWithOwner"),
9806
+ visibility: stringField(parsed, "visibility")?.toLowerCase() ?? null
9807
+ };
9808
+ }
9809
+ function parseJsonObject(text) {
9810
+ try {
9811
+ const value = JSON.parse(text);
9812
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
9813
+ } catch {
9814
+ return null;
9815
+ }
9816
+ }
9817
+ function stringField(object, key) {
9818
+ const value = object[key];
9819
+ return typeof value === "string" && value !== "" ? value : null;
9820
+ }
9821
+ function firstLine(text) {
9822
+ return text.split(`
9823
+ `).map((line) => line.trim()).find((line) => line !== "") ?? null;
9824
+ }
9825
+
9826
+ // src/plan/risk/commands.ts
9827
+ import { posix } from "path";
9828
+
9829
+ // src/check/shell-lines.ts
9830
+ var SHELL_FENCE_LANGUAGES = ["bash", "sh", "shell", "zsh", "console"];
9831
+ var FOREIGN_KEYWORDS = [
9832
+ "async",
9833
+ "await",
9834
+ "class",
9835
+ "const",
9836
+ "def",
9837
+ "enum",
9838
+ "fn",
9839
+ "from",
9840
+ "func",
9841
+ "impl",
9842
+ "import",
9843
+ "interface",
9844
+ "new",
9845
+ "package",
9846
+ "private",
9847
+ "protected",
9848
+ "public",
9849
+ "static",
9850
+ "struct",
9851
+ "throw",
9852
+ "val",
9853
+ "var"
9854
+ ];
9855
+ var HEREDOC = /<<-?\s*['"]?([A-Za-z_][A-Za-z0-9_]*)['"]?/;
9856
+ var CONTINUES = /\\$/;
9857
+ var SPACED_ASSIGNMENT = /^\s*[A-Za-z_$][A-Za-z0-9_$.]*\s+=(?!=)\s+\S/;
9858
+ var FOREIGN_COMMENT = /^\s*(?:\/\/|\/\*|\*\/|\*\s)/;
9859
+ function isShellFence(info) {
9860
+ return SHELL_FENCE_LANGUAGES.includes(info);
9861
+ }
9862
+ function commandOf(line, info) {
9863
+ const text = line.trim();
9864
+ if (text === "" || text.startsWith("#"))
9865
+ return null;
9866
+ if (text.startsWith("$ ") || text === "$")
9867
+ return text.slice(1).trim() || null;
9868
+ if (info === "console")
9869
+ return null;
9870
+ if (text.startsWith("> "))
9871
+ return null;
9872
+ return text;
9873
+ }
9874
+ function heredocTerminator(command) {
9875
+ return HEREDOC.exec(command)?.[1] ?? null;
9876
+ }
9877
+ function continuesLine(command) {
9878
+ return CONTINUES.test(command);
9879
+ }
9880
+ function isForeignCodeLine(line) {
9881
+ const text = line.trim();
9882
+ if (text === "")
9883
+ return false;
9884
+ if (FOREIGN_COMMENT.test(text))
9885
+ return true;
9886
+ if (SPACED_ASSIGNMENT.test(text))
9887
+ return true;
9888
+ return FOREIGN_KEYWORDS.includes(text.split(/\s+/)[0] ?? "");
9889
+ }
9890
+
9891
+ // src/plan/risk/patterns.ts
9892
+ var COMMAND_WRAPPERS = [
9893
+ "command",
9894
+ "do",
9895
+ "else",
9896
+ "env",
9897
+ "exec",
9898
+ "nohup",
9899
+ "sudo",
9900
+ "then",
9901
+ "time",
9902
+ "xargs"
9903
+ ];
9904
+ var OPENER = String.raw`(?:^|[;&|({\x60]|\s-exec(?:dir)?\s)`;
9905
+ var ASSIGNMENTS = String.raw`(?:[A-Za-z_]\w*=\S*\s+)*`;
9906
+ var WRAPPERS = String.raw`(?:(?:${COMMAND_WRAPPERS.join("|")})(?:\s+-\S+)*\s+${ASSIGNMENTS})*`;
9907
+ var AT_COMMAND = String.raw`${OPENER}\s*${ASSIGNMENTS}${WRAPPERS}`;
9908
+ var REST = "[^;&|]*";
9909
+ var GIT_OPTIONS = String.raw`(?:\s+(?:-[Cc]\s+\S+|--?[\w-]+(?:=\S+)?))*`;
9910
+ function hasFlag(short, long) {
9911
+ return String.raw`(?=(?:${REST}\s)?(?:-[A-Za-z]*${short}[A-Za-z]*|--${long})(?=\s|$))`;
9912
+ }
9913
+ function atCommand(name, body) {
9914
+ return { name, expression: new RegExp(`${AT_COMMAND}${body}`) };
9915
+ }
9916
+ var DESTRUCTIVE_PATTERNS = [
9917
+ atCommand("rm -rf", String.raw`rm\s+${hasFlag("[rR]", "recursive")}${hasFlag("f", "force")}`),
9918
+ atCommand("git push --force", String.raw`git${GIT_OPTIONS}\s+push\b(?=${REST}\s(?:--force|-[A-Za-z]*f[A-Za-z]*)(?=\s|$))`),
9919
+ atCommand("git reset --hard", String.raw`git${GIT_OPTIONS}\s+reset\b(?=${REST}\s--hard(?=\s|$))`),
9920
+ atCommand("git clean -f", String.raw`git${GIT_OPTIONS}\s+clean\s+${hasFlag("f", "force")}`),
9921
+ atCommand("sudo", String.raw`sudo(?=\s|$)`),
9922
+ atCommand("brew install", String.raw`brew(?:\s+-\S+)*\s+install\b`),
9923
+ atCommand("npm i -g", String.raw`npm\s+(?:i|install|add)(?=\s)(?=${REST}\s(?:-g|--global)(?=\s|$))`),
9924
+ atCommand("pip install", String.raw`(?:python3?\s+-m\s+)?pip3?\s+install\b`),
9925
+ atCommand("npm publish", String.raw`npm\s+publish\b(?!${REST}--dry-run)`),
9926
+ atCommand("curl \u2026 | sh", String.raw`curl\b[^;&|]*\|(?!\|)\s*${WRAPPERS}(?:ba|z|da)?sh(?=\s|$)`),
9927
+ atCommand("chmod -R", String.raw`chmod\s+${hasFlag("R", "recursive")}`),
9928
+ { name: "DROP TABLE", expression: /\bDROP\s+TABLE\b/i },
9929
+ atCommand("docker system prune", String.raw`docker\s+system\s+prune\b`)
9930
+ ];
9931
+ function destructiveMatches(command) {
9932
+ return DESTRUCTIVE_PATTERNS.filter((pattern) => pattern.expression.test(command));
9933
+ }
9934
+
9935
+ // src/plan/risk/commands.ts
9936
+ var DEVICE_FILES = [
9937
+ "/dev/null",
9938
+ "/dev/stdin",
9939
+ "/dev/stdout",
9940
+ "/dev/stderr",
9941
+ "/dev/tty"
9942
+ ];
9943
+ var WRITES_EVERY_OPERAND = ["mv", "rm", "tee"];
9944
+ var COMMAND_PREFIXES = [...COMMAND_WRAPPERS, "!", "{"];
9945
+ var FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})[ \t]*(\S*)/;
9946
+ var FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
9947
+ var CODE_SPAN2 = /`([^`\n]+)`/g;
9948
+ var REDIRECT = /^(?:&>>|&>|>>|>\||>&|<<<|<<-|<<|<&|<>|>|<)/;
9949
+ var OUTPUT_REDIRECTS = /^(?:\d*(?:>>|>\||>&|<>|>)|&>>|&>)$/;
9950
+ var HEREDOC_REDIRECTS = /^\d*<<[-<]?$/;
9951
+ var SEPARATORS2 = ";&|()`";
9952
+ var ASSIGNMENT = /^[A-Za-z_]\w*=/;
9953
+ var SCHEME = /^[A-Za-z][A-Za-z0-9+.-]*:\/\//;
9954
+ function openFence(open) {
9955
+ const run = open[1] ?? "";
9956
+ return {
9957
+ info: (open[2] ?? "").split(/[\s,{]/)[0]?.toLowerCase() ?? "",
9958
+ delimiter: run[0] ?? "`",
9959
+ length: run.length,
9960
+ terminator: null,
9961
+ pending: null,
9962
+ foreign: false,
9963
+ commands: []
9964
+ };
9965
+ }
9966
+ function closesFence(line, fence) {
9967
+ const run = FENCE_CLOSE.exec(line)?.[1] ?? "";
9968
+ return run !== "" && run[0] === fence.delimiter && run.length >= fence.length;
9969
+ }
9970
+ function withoutBackslash(piece) {
9971
+ return piece.replace(/\\$/, "").trim();
9972
+ }
9973
+ function flush(fence) {
9974
+ if (fence.pending !== null)
9975
+ fence.commands.push({ text: fence.pending.text, line: fence.pending.line });
9976
+ fence.pending = null;
9977
+ }
9978
+ function continuationOf(line, info) {
9979
+ const text = line.trim();
9980
+ return info === "console" && text.startsWith("> ") ? text.slice(2) : text;
9981
+ }
9982
+ function readFenceLine(fence, line, number) {
9983
+ const pending = fence.pending;
9984
+ if (pending === null && fence.terminator !== null) {
9985
+ if (line.trim() === fence.terminator)
9986
+ fence.terminator = null;
9987
+ return;
9988
+ }
9989
+ const piece = pending === null ? commandOf(line, fence.info) : continuationOf(line, fence.info);
9990
+ if (piece === null || piece === "")
9991
+ return;
9992
+ if (isForeignCodeLine(piece)) {
9993
+ fence.foreign = true;
9994
+ return;
9995
+ }
9996
+ fence.terminator = heredocTerminator(piece) ?? fence.terminator;
9997
+ if (pending === null)
9998
+ fence.pending = { text: withoutBackslash(piece), line: number };
9999
+ else
10000
+ pending.text = `${pending.text} ${withoutBackslash(piece)}`;
10001
+ if (!continuesLine(piece))
10002
+ flush(fence);
10003
+ }
10004
+ function commandsOf(fence) {
10005
+ flush(fence);
10006
+ return fence.foreign ? [] : fence.commands;
10007
+ }
10008
+ function shellCommandLines(body) {
10009
+ const found = [];
10010
+ let fence = null;
10011
+ for (const [index, line] of body.split(`
10012
+ `).entries()) {
10013
+ if (fence === null) {
10014
+ const open = FENCE_OPEN.exec(line);
10015
+ if (open !== null)
10016
+ fence = openFence(open);
10017
+ continue;
10018
+ }
10019
+ if (closesFence(line, fence)) {
10020
+ found.push(...commandsOf(fence));
10021
+ fence = null;
10022
+ continue;
10023
+ }
10024
+ if (isShellFence(fence.info) && !fence.foreign)
10025
+ readFenceLine(fence, line, index + 1);
10026
+ }
10027
+ if (fence !== null)
10028
+ found.push(...commandsOf(fence));
10029
+ return found;
10030
+ }
10031
+ function taskLineCommands(taskLine, line) {
10032
+ const found = [];
10033
+ for (const match of taskLine.matchAll(CODE_SPAN2)) {
10034
+ const command = commandOf(match[1] ?? "", "sh");
10035
+ if (command !== null && !isForeignCodeLine(command))
10036
+ found.push({ text: command, line });
10037
+ }
10038
+ return found;
10039
+ }
10040
+ function quotedEnd(command, start) {
10041
+ const quote = command[start];
10042
+ let index = start + 1;
10043
+ while (index < command.length && command[index] !== quote) {
10044
+ index += quote === '"' && command[index] === "\\" ? 2 : 1;
10045
+ }
10046
+ return Math.min(index + 1, command.length);
10047
+ }
10048
+ function unquote(run) {
10049
+ const closed = run.length > 1 && run.endsWith(run[0] ?? "");
10050
+ const inner = run.slice(1, closed ? -1 : undefined);
10051
+ return run.startsWith('"') ? inner.replace(/\\(["\\$`])/g, "$1") : inner;
10052
+ }
10053
+ function opensRedirect(rest) {
10054
+ return rest.startsWith(">") || rest.startsWith("<") || rest.startsWith("&>");
10055
+ }
10056
+ function shellTokens(command) {
10057
+ const tokens = [];
10058
+ let word = "";
10059
+ let hasWord = false;
10060
+ const endWord = () => {
10061
+ if (hasWord)
10062
+ tokens.push({ kind: "word", text: word });
10063
+ word = "";
10064
+ hasWord = false;
10065
+ };
10066
+ let index = 0;
10067
+ while (index < command.length) {
10068
+ const char = command[index] ?? "";
10069
+ const rest = command.slice(index);
10070
+ if (opensRedirect(rest)) {
10071
+ const fd = /^\d+$/.test(word) ? word : "";
10072
+ if (fd === "")
10073
+ endWord();
10074
+ word = "";
10075
+ hasWord = false;
10076
+ const operator = REDIRECT.exec(rest)?.[0] ?? char;
10077
+ tokens.push({ kind: "redirect", text: `${fd}${operator}` });
10078
+ index += operator.length;
10079
+ } else if (char === "'" || char === '"') {
10080
+ const end = quotedEnd(command, index);
10081
+ word += unquote(command.slice(index, end));
10082
+ hasWord = true;
10083
+ index = end;
10084
+ } else if (char === "\\") {
10085
+ word += command[index + 1] ?? "";
10086
+ hasWord = true;
10087
+ index += 2;
10088
+ } else if (/\s/.test(char)) {
10089
+ endWord();
10090
+ index += 1;
10091
+ } else if (SEPARATORS2.includes(char)) {
10092
+ endWord();
10093
+ const operator = /^(?:&&|\|\||[;&|()`])/.exec(rest)?.[0] ?? char;
10094
+ tokens.push({ kind: "separator", text: operator });
10095
+ index += operator.length;
10096
+ } else {
10097
+ word += char;
10098
+ hasWord = true;
10099
+ index += 1;
10100
+ }
10101
+ }
10102
+ endWord();
10103
+ return tokens;
10104
+ }
10105
+ function segmentsOf(tokens) {
10106
+ const segments = [[]];
10107
+ for (const token of tokens) {
10108
+ if (token.kind === "separator")
10109
+ segments.push([]);
10110
+ else
10111
+ segments.at(-1)?.push(token);
10112
+ }
10113
+ return segments.filter((segment) => segment.length > 0);
10114
+ }
10115
+ function wordsOf2(segment) {
10116
+ const argv = [];
10117
+ const redirected = [];
10118
+ let operator = null;
10119
+ for (const token of segment) {
10120
+ if (token.kind === "redirect") {
10121
+ operator = token.text;
10122
+ } else if (operator === null) {
10123
+ argv.push(token.text);
10124
+ } else {
10125
+ if (!HEREDOC_REDIRECTS.test(operator)) {
10126
+ redirected.push({ word: token.text, writes: OUTPUT_REDIRECTS.test(operator) });
10127
+ }
10128
+ operator = null;
10129
+ }
10130
+ }
10131
+ return { argv, redirected };
10132
+ }
10133
+ function commandIndex(argv) {
10134
+ let index = 0;
10135
+ let afterWrapper = false;
10136
+ while (index < argv.length) {
10137
+ const word = argv[index] ?? "";
10138
+ const isWrapperFlag = afterWrapper && word.startsWith("-");
10139
+ if (!ASSIGNMENT.test(word) && !COMMAND_PREFIXES.includes(word) && !isWrapperFlag)
10140
+ return index;
10141
+ afterWrapper = COMMAND_PREFIXES.includes(word) || isWrapperFlag;
10142
+ index += 1;
10143
+ }
10144
+ return index;
10145
+ }
10146
+ function operandsOf(args) {
10147
+ const end = args.indexOf("--");
10148
+ const flagged = end === -1 ? args : args.slice(0, end);
10149
+ const after = end === -1 ? [] : args.slice(end + 1);
10150
+ return [...flagged.filter((word) => !word.startsWith("-")), ...after];
10151
+ }
10152
+ function copyTargets(args) {
10153
+ const flag = args.indexOf("-t");
10154
+ if (flag !== -1 && args[flag + 1] !== undefined)
10155
+ return [args[flag + 1] ?? ""];
10156
+ const long = args.find((word) => word.startsWith("--target-directory="));
10157
+ if (long !== undefined)
10158
+ return [long.slice("--target-directory=".length)];
10159
+ return operandsOf(args).slice(-1);
10160
+ }
10161
+ function writtenWords(argv) {
10162
+ const at = commandIndex(argv);
10163
+ const name = posix.basename(argv[at] ?? "");
10164
+ const args = argv.slice(at + 1);
10165
+ if (WRITES_EVERY_OPERAND.includes(name))
10166
+ return new Set(operandsOf(args));
10167
+ if (name === "cp")
10168
+ return new Set(copyTargets(args));
10169
+ return new Set;
10170
+ }
10171
+ function pathCandidate(word) {
10172
+ if (word.startsWith("/") || word.startsWith("~"))
10173
+ return word;
10174
+ const equals = word.indexOf("=");
10175
+ return equals === -1 ? word : word.slice(equals + 1);
10176
+ }
10177
+ function isWithin(root, target) {
10178
+ const step = posix.relative(posix.resolve(root), posix.resolve(target));
10179
+ return !step.startsWith("..") && !posix.isAbsolute(step);
10180
+ }
10181
+ function isOutsidePath(path5, seams) {
10182
+ if (SCHEME.test(path5) || path5.startsWith("//") || DEVICE_FILES.includes(path5))
10183
+ return false;
10184
+ if (path5 === "~" || path5.startsWith("~/"))
10185
+ return !isWithin(seams.repoRoot, posix.join(seams.home, path5.slice(1)));
10186
+ if (path5.startsWith("~"))
10187
+ return true;
10188
+ return path5.startsWith("/") && !isWithin(seams.repoRoot, path5);
10189
+ }
10190
+ function outsidePaths(command, seams) {
10191
+ const found = new Map;
10192
+ const note = (word, writes) => {
10193
+ const path5 = pathCandidate(word);
10194
+ if (isOutsidePath(path5, seams))
10195
+ found.set(path5, (found.get(path5) ?? false) || writes);
10196
+ };
10197
+ for (const segment of segmentsOf(shellTokens(command))) {
10198
+ const { argv, redirected } = wordsOf2(segment);
10199
+ const written = writtenWords(argv);
10200
+ for (const word of argv)
10201
+ note(word, written.has(word) || written.has(pathCandidate(word)));
10202
+ for (const target of redirected)
10203
+ note(target.word, target.writes);
10204
+ }
10205
+ return Array.from(found, ([path5, writes]) => ({ path: path5, writes }));
10206
+ }
10207
+ function findingsOf(command, seams) {
10208
+ const destructive = destructiveMatches(command.text).map((pattern) => ({
10209
+ level: "high",
10210
+ kind: "destructive",
10211
+ text: `${pattern.name} \u2014 ${command.text}`,
10212
+ subject: pattern.name,
10213
+ command: command.text,
10214
+ line: command.line
10215
+ }));
10216
+ const paths = outsidePaths(command.text, seams).map((path5) => ({
10217
+ level: path5.writes ? "high" : "note",
10218
+ kind: "outside-path",
10219
+ text: `${path5.path} \u2014 ${path5.writes ? "written by" : "named in"} ${command.text}`,
10220
+ subject: path5.path,
10221
+ command: command.text,
10222
+ line: command.line
10223
+ }));
10224
+ return [...destructive, ...paths];
10225
+ }
10226
+ function readCommands2(commands, seams) {
10227
+ return commands.flatMap((command) => findingsOf(command, seams));
10228
+ }
10229
+ function readBodyCommands(body, seams) {
10230
+ return readCommands2(shellCommandLines(body), seams);
10231
+ }
10232
+ function readTaskLineCommands(taskLine, line, seams) {
10233
+ return readCommands2(taskLineCommands(taskLine, line), seams);
10234
+ }
10235
+
10236
+ // src/plan/risk/secrets.ts
10237
+ var SECRET_NAME_SHAPES = [
10238
+ { name: "*_TOKEN", expression: /_TOKEN$/ },
10239
+ { name: "*_KEY", expression: /_KEY$/ },
10240
+ { name: "*_SECRET", expression: /_SECRET$/ },
10241
+ { name: "*PASSWORD*", expression: /PASSWORD/ },
10242
+ { name: "AWS_*", expression: /^AWS_/ }
10243
+ ];
10244
+ function secretShapesOf(name) {
10245
+ return SECRET_NAME_SHAPES.filter((shape) => shape.expression.test(name));
10246
+ }
10247
+ function scanSecretNames(environment) {
10248
+ return [...Object.keys(environment)].sort().flatMap((name) => {
10249
+ const shapes = secretShapesOf(name);
10250
+ if (shapes.length === 0) {
10251
+ return [];
10252
+ }
10253
+ const matched = shapes.map((shape) => shape.name).join(", ");
10254
+ return [{ level: "note", kind: "secret", text: `${name} \u2014 matches ${matched}`, subject: name }];
10255
+ });
10256
+ }
10257
+
10258
+ // src/plan/risk.ts
10259
+ var RISK_KINDS = ["tools", "destructive", "outside-path", "account", "secret"];
10260
+ var EVERY_TOOL = "every tool";
10261
+ var SHELL_TOOL = "Bash";
10262
+ function agentTools(frontmatter) {
10263
+ const value = frontmatter["tools"];
10264
+ let names = [];
10265
+ if (typeof value === "string")
10266
+ names = value.split(",");
10267
+ else if (Array.isArray(value))
10268
+ names = value.filter((entry) => typeof entry === "string");
10269
+ const tools = [...new Set(names.map((name) => name.trim()).filter((name) => name !== ""))];
10270
+ return tools.length === 0 ? null : tools;
10271
+ }
10272
+ function holdsShell(tools) {
10273
+ return tools.some((tool) => tool === SHELL_TOOL || tool.startsWith(`${SHELL_TOOL}(`));
10274
+ }
10275
+ function frontmatterString(frontmatter, key) {
10276
+ const value = frontmatter[key];
10277
+ return typeof value === "string" && value.trim() !== "" ? value.trim() : null;
10278
+ }
10279
+ function taskAccess(declaration, seams) {
10280
+ const budget = declaration?.budget ?? null;
10281
+ const agent = declaration?.agent ?? null;
10282
+ if (agent === null) {
10283
+ const tools2 = declaration?.tools ?? null;
10284
+ return {
10285
+ tools: tools2,
10286
+ source: tools2 === null ? "no agent= and no tools=" : "tools=",
10287
+ model: declaration?.model ?? null,
10288
+ budget
10289
+ };
10290
+ }
10291
+ const definition = findAgentDefinition(agent, { repoRoot: seams.repoRoot, home: seams.home }, seams.settingSources);
10292
+ if (definition === null) {
10293
+ return { tools: null, source: `agent=${agent} resolves to no definition`, model: null, budget };
10294
+ }
10295
+ const tools = agentTools(definition.frontmatter);
10296
+ return {
10297
+ tools,
10298
+ source: tools === null ? `agent=${agent} has no tools: line` : `agent=${agent}`,
10299
+ model: frontmatterString(definition.frontmatter, "model"),
10300
+ budget
10301
+ };
10302
+ }
10303
+ function toolsFinding(task, plan2, seams) {
10304
+ const access = taskAccess(task.declaration, seams);
10305
+ const model = `model ${access.model ?? "the session default"}`;
10306
+ const budget = access.budget === null ? "no budget=" : `budget $${String(access.budget)}`;
10307
+ const where = { task: task.text, file: plan2, line: task.lineNum + 1 };
10308
+ if (access.tools === null) {
10309
+ return { level: "high", kind: "tools", text: `${EVERY_TOOL} \u2014 ${access.source}; ${model}; ${budget}`, ...where };
10310
+ }
10311
+ if (holdsShell(access.tools) && access.budget === null) {
10312
+ const listed = access.tools.join(", ");
10313
+ return { level: "note", kind: "tools", text: `${SHELL_TOOL} with no budget= \u2014 ${access.source}: ${listed}; ${model}`, ...where };
10314
+ }
10315
+ return null;
10316
+ }
10317
+ function displayPath(path5, seams) {
10318
+ const absolute = isAbsolute3(path5) ? path5 : join13(seams.repoRoot, path5);
10319
+ const within = (root) => {
10320
+ const step = relative2(root, absolute);
10321
+ return step === "" || step.startsWith("..") || isAbsolute3(step) ? null : step;
10322
+ };
10323
+ const inRepo = within(seams.repoRoot);
10324
+ if (inRepo !== null)
10325
+ return inRepo;
10326
+ const inHome = within(seams.home);
10327
+ return inHome === null ? absolute : `~/${inHome}`;
10328
+ }
10329
+ function located(finding2, file, task) {
10330
+ const base = { level: finding2.level, kind: finding2.kind, text: finding2.text };
10331
+ return task === undefined ? { ...base, file, line: finding2.line } : { ...base, task, file, line: finding2.line };
10332
+ }
10333
+ function readText4(path5) {
10334
+ try {
10335
+ return readFileSync16(path5, "utf8");
10336
+ } catch {
10337
+ return null;
10338
+ }
10339
+ }
10340
+ function findSkillFile(name, seams) {
10341
+ const tiers = resolveSkillTiers({ home: seams.home, projectRoot: seams.repoRoot, entry: seams.entry });
10342
+ for (const { dir } of tiers) {
10343
+ const path5 = join13(dir, name, "SKILL.md");
10344
+ if (readText4(path5) !== null)
10345
+ return path5;
10346
+ }
10347
+ return null;
10348
+ }
10349
+ function taskFiles(declaration, seams) {
10350
+ if (declaration === null)
10351
+ return [];
10352
+ const agent = declaration.agent === null ? null : findAgentDefinition(declaration.agent, { repoRoot: seams.repoRoot, home: seams.home }, seams.settingSources);
10353
+ const skills = (declaration.skills ?? []).map((name) => findSkillFile(name, seams));
10354
+ return [agent?.path ?? null, ...skills].filter((path5) => path5 !== null);
10355
+ }
10356
+ function commandFindings(plan2, display, tasks, seams) {
10357
+ const paths = { repoRoot: seams.repoRoot, home: seams.home };
10358
+ const found = readBodyCommands(plan2.text, paths).map((finding2) => located(finding2, display));
10359
+ const read = new Set;
10360
+ const lines = plan2.text.split(`
10361
+ `);
10362
+ for (const task of tasks) {
10363
+ const spans = readTaskLineCommands(lines[task.lineNum] ?? "", task.lineNum + 1, paths);
10364
+ found.push(...spans.map((finding2) => located(finding2, display, task.text)));
10365
+ for (const file of taskFiles(task.declaration, seams)) {
10366
+ if (read.has(file))
10367
+ continue;
10368
+ read.add(file);
10369
+ const shown = displayPath(file, seams);
10370
+ found.push(...readBodyCommands(readText4(file) ?? "", paths).map((finding2) => located(finding2, shown)));
10371
+ }
10372
+ }
10373
+ return found;
10374
+ }
10375
+ function openTasks(tasks, hidden) {
10376
+ return [...tasks, ...hidden].filter((task) => task.status !== "done").sort((a, b) => a.lineNum - b.lineNum);
10377
+ }
10378
+ function riskTotal(findings) {
10379
+ return {
10380
+ high: findings.filter((finding2) => finding2.level === "high").length,
10381
+ note: findings.filter((finding2) => finding2.level === "note").length
10382
+ };
10383
+ }
10384
+ async function assessPlanRisk(plan2, seams) {
10385
+ const model = parsePlan(plan2.text);
10386
+ const display = displayPath(plan2.path, seams);
10387
+ const tasks = openTasks(model.tasks, model.hiddenTasks);
10388
+ const tools = tasks.flatMap((task) => toolsFinding(task, display, seams) ?? []);
10389
+ const commands = commandFindings(plan2, display, tasks, seams);
10390
+ const accounts = (await readAccounts(seams.accounts, seams.runners)).map(({ level, kind, text }) => ({ level, kind, text }));
10391
+ const secrets = scanSecretNames(seams.environment).map(({ level, kind, text }) => ({ level, kind, text }));
10392
+ const findings = [...tools, ...commands, ...accounts, ...secrets];
10393
+ return { plan: display, total: riskTotal(findings), findings };
10394
+ }
10395
+ function counted2(count, word) {
10396
+ return count === 1 ? `${String(count)} ${word}` : `${String(count)} ${word}s`;
10397
+ }
10398
+ function riskTotalLine(report2) {
10399
+ return `\uD83D\uDEE1 Risk: ${String(report2.total.high)} high, ${counted2(report2.total.note, "note")} \u2014 rafa plan risk ${report2.plan}`;
10400
+ }
10401
+ var KIND_WIDTH = Math.max(...RISK_KINDS.map((kind) => kind.length));
10402
+
10403
+ // src/start/risk-total.ts
10404
+ function spawningRunners(root) {
10405
+ return { git: createGitRunner(root), gh: createGhRunner({ cwd: root }) };
10406
+ }
10407
+ async function readRiskTotalLine(input, seams = {}) {
10408
+ const makeRunners = seams.runners ?? spawningRunners;
10409
+ const report2 = await assessPlanRisk({ path: input.planPath, text: readFileSync17(input.planPath, "utf8") }, {
10410
+ repoRoot: input.repoRoot,
10411
+ home: input.home,
10412
+ settingSources: input.config.settingSources,
10413
+ environment: input.environment,
10414
+ accounts: {
10415
+ prProvider: input.config.prProvider,
10416
+ trackerDefault: input.config.trackerDefault,
10417
+ trackerFallback: input.config.trackerFallback
10418
+ },
10419
+ runners: makeRunners(input.repoRoot)
10420
+ });
10421
+ return riskTotalLine(report2);
10422
+ }
10423
+ async function announceRiskTotal(input, seams = {}) {
10424
+ const output = seams.output ?? activeOutput();
10425
+ try {
10426
+ output.info(await readRiskTotalLine(input, seams));
10427
+ } catch (error) {
10428
+ const said = error instanceof Error ? error.message : String(error);
10429
+ output.warn(`\u26A0\uFE0F Risk: could not read ${input.planPath} \u2014 ${said}`);
10430
+ }
10431
+ }
10432
+
9692
10433
  // src/start/session.ts
9693
10434
  import { randomUUID as randomUUID8 } from "crypto";
9694
- import { relative as relative2 } from "path";
10435
+ import { relative as relative3 } from "path";
9695
10436
  var NOTHING_DISPATCHED = " Nothing was checked and nothing was dispatched.";
9696
10437
  function recordLabel(repoRoot, sessionId) {
9697
- return relative2(repoRoot, sessionFilePath(repoRoot, sessionId));
10438
+ return relative3(repoRoot, sessionFilePath(repoRoot, sessionId));
9698
10439
  }
9699
10440
  function conflictLine(repoRoot, conflict) {
9700
10441
  const { record } = conflict;
@@ -9724,7 +10465,7 @@ function openRefusal(repoRoot, draft, error) {
9724
10465
  if (!(error instanceof SessionRecordError) && !isSystemError)
9725
10466
  return null;
9726
10467
  return [
9727
- `\u274C Refusing to start: the session records under ${relative2(repoRoot, runsDir(repoRoot))}/ cannot be read or written.`,
10468
+ `\u274C Refusing to start: the session records under ${relative3(repoRoot, runsDir(repoRoot))}/ cannot be read or written.`,
9728
10469
  ` ${messageOf(error)}`,
9729
10470
  NOTHING_DISPATCHED
9730
10471
  ].join(`
@@ -9760,7 +10501,7 @@ function openRunSession(options) {
9760
10501
  const draft = {
9761
10502
  sessionId: (seams.newSessionId ?? randomUUID8)(),
9762
10503
  planStub: options.planStub,
9763
- plan: relative2(repoRoot, options.planPath),
10504
+ plan: relative3(repoRoot, options.planPath),
9764
10505
  branch: options.branch,
9765
10506
  pid: seams.pid ?? process.pid,
9766
10507
  startedAt: (seams.now ?? (() => new Date))().toISOString()
@@ -9837,11 +10578,11 @@ function announcePlanIssues(planContent) {
9837
10578
  }
9838
10579
 
9839
10580
  // src/start/runtime.ts
9840
- import { existsSync as existsSync16, readdirSync as readdirSync6, realpathSync as realpathSync3, statSync as statSync9 } from "fs";
10581
+ import { existsSync as existsSync17, readdirSync as readdirSync6, realpathSync as realpathSync4, statSync as statSync8 } from "fs";
9841
10582
  import { homedir as homedir6 } from "os";
9842
- import { isAbsolute as isAbsolute3, join as join12, relative as relative3, resolve as resolve10, sep } from "path";
10583
+ import { isAbsolute as isAbsolute4, join as join14, relative as relative4, resolve as resolve10, sep } from "path";
9843
10584
  var RUNTIME_FLAG = "--runtime";
9844
- var RUNTIME_SUBDIR = join12(".rafa", "runtime");
10585
+ var RUNTIME_SUBDIR = join14(".rafa", "runtime");
9845
10586
  var RUNTIME_ENTRY = "cli.js";
9846
10587
  var SOURCE_DIR = "src";
9847
10588
  var RUNTIME_COMMAND = ["start"];
@@ -9858,10 +10599,10 @@ function resolveRuntime(value, places) {
9858
10599
  throw refusal3([`\u274C Refusing to start: \`--runtime\` names no runtime. Spell it ${SPELLING}.`]);
9859
10600
  }
9860
10601
  const isVersion = VERSION_SHAPE2.test(value);
9861
- const named = isVersion ? join12(places.home, RUNTIME_SUBDIR, value, RUNTIME_ENTRY) : resolve10(places.cwd, value);
10602
+ const named = isVersion ? join14(places.home, RUNTIME_SUBDIR, value, RUNTIME_ENTRY) : resolve10(places.cwd, value);
9862
10603
  refuseInsideSource(value, named, places);
9863
10604
  const entry = entryOf(value, named, isVersion, places.home);
9864
- const real = realpathSync3(entry);
10605
+ const real = realpathSync4(entry);
9865
10606
  refuseInsideSource(value, real, places);
9866
10607
  return real;
9867
10608
  }
@@ -9869,7 +10610,7 @@ function entryOf(value, named, isVersion, home) {
9869
10610
  if (isVersion) {
9870
10611
  if (isFile2(named))
9871
10612
  return named;
9872
- const root = join12(home, RUNTIME_SUBDIR);
10613
+ const root = join14(home, RUNTIME_SUBDIR);
9873
10614
  const installed = installedVersions(root);
9874
10615
  throw refusal3([
9875
10616
  `\u274C Refusing to start: \`--runtime=${value}\` names no installed runtime: ${named} is no file.`,
@@ -9878,7 +10619,7 @@ function entryOf(value, named, isVersion, home) {
9878
10619
  }
9879
10620
  if (isFile2(named))
9880
10621
  return named;
9881
- const inside = join12(named, RUNTIME_ENTRY);
10622
+ const inside = join14(named, RUNTIME_ENTRY);
9882
10623
  if (isDirectory(named) && isFile2(inside))
9883
10624
  return inside;
9884
10625
  throw refusal3([
@@ -9896,31 +10637,31 @@ function refuseInsideSource(value, path5, places) {
9896
10637
  ]);
9897
10638
  }
9898
10639
  function sourceDirs({ cwd, root }) {
9899
- const dirs = [join12(cwd, SOURCE_DIR), join12(root, SOURCE_DIR)];
9900
- const real = dirs.filter((dir) => existsSync16(dir)).map((dir) => realpathSync3(dir));
10640
+ const dirs = [join14(cwd, SOURCE_DIR), join14(root, SOURCE_DIR)];
10641
+ const real = dirs.filter((dir) => existsSync17(dir)).map((dir) => realpathSync4(dir));
9901
10642
  return [...new Set([...dirs, ...real])];
9902
10643
  }
9903
10644
  function isInside(path5, dir) {
9904
- const rel = relative3(dir, path5);
9905
- return rel === "" || !isAbsolute3(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
10645
+ const rel = relative4(dir, path5);
10646
+ return rel === "" || !isAbsolute4(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
9906
10647
  }
9907
10648
  function installedVersions(root) {
9908
10649
  try {
9909
- return readdirSync6(root).filter((name) => isFile2(join12(root, name, RUNTIME_ENTRY))).sort((a, b) => a.localeCompare(b));
10650
+ return readdirSync6(root).filter((name) => isFile2(join14(root, name, RUNTIME_ENTRY))).sort((a, b) => a.localeCompare(b));
9910
10651
  } catch {
9911
10652
  return [];
9912
10653
  }
9913
10654
  }
9914
10655
  function isFile2(path5) {
9915
10656
  try {
9916
- return statSync9(path5).isFile();
10657
+ return statSync8(path5).isFile();
9917
10658
  } catch {
9918
10659
  return false;
9919
10660
  }
9920
10661
  }
9921
10662
  function isDirectory(path5) {
9922
10663
  try {
9923
- return statSync9(path5).isDirectory();
10664
+ return statSync8(path5).isDirectory();
9924
10665
  } catch {
9925
10666
  return false;
9926
10667
  }
@@ -9949,7 +10690,7 @@ async function runFromSelectedRuntime(seams) {
9949
10690
  }
9950
10691
  function realOrResolved(path5) {
9951
10692
  try {
9952
- return realpathSync3(path5);
10693
+ return realpathSync4(path5);
9953
10694
  } catch {
9954
10695
  return resolve10(path5);
9955
10696
  }
@@ -10037,11 +10778,11 @@ function eventOf(line) {
10037
10778
  }
10038
10779
 
10039
10780
  // src/triage/triage.ts
10040
- import { basename as basename6, join as join13 } from "path";
10781
+ import { basename as basename6, join as join15 } from "path";
10041
10782
 
10042
10783
  // src/effort/store/tracker-refs.ts
10043
10784
  import { randomUUID as randomUUID9 } from "crypto";
10044
- import { existsSync as existsSync17 } from "fs";
10785
+ import { existsSync as existsSync18 } from "fs";
10045
10786
  var SELECT_HELD = `
10046
10787
  SELECT seq, tracker_ref FROM findings
10047
10788
  WHERE session_id = ? AND artifact = ?
@@ -10164,7 +10905,7 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
10164
10905
  wanted.json,
10165
10906
  collectedAt
10166
10907
  ];
10167
- const [action, stored] = withSqliteStore(path5, !existsSync17(path5), (db) => {
10908
+ const [action, stored] = withSqliteStore(path5, !existsSync18(path5), (db) => {
10168
10909
  const place = db.transaction(() => placeRef(db, path5, write, wanted, insertValues));
10169
10910
  return place.immediate();
10170
10911
  });
@@ -10173,14 +10914,14 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
10173
10914
  function readTrackerRef(repoRoot, artifact) {
10174
10915
  checkArtifact("read", artifact);
10175
10916
  const path5 = sqliteStorePath(repoRoot);
10176
- if (!existsSync17(path5))
10917
+ if (!existsSync18(path5))
10177
10918
  return null;
10178
10919
  const oldest = withSqliteStore(path5, false, (db) => db.query(SELECT_OLDEST).get(artifact));
10179
10920
  return oldest === null ? null : heldRefOf(path5, oldest.seq, oldest.tracker_ref).ref;
10180
10921
  }
10181
10922
 
10182
10923
  // src/triage/triage.ts
10183
- var PRIVATE_TRIAGE_DIR = join13(".rafa", "triage", "private");
10924
+ var PRIVATE_TRIAGE_DIR = join15(".rafa", "triage", "private");
10184
10925
  var SECRET_ENV_NAMES = ["GITHUB_TOKEN", "ANTHROPIC_API_KEY", "LINEAR_API_KEY"];
10185
10926
  var TRIAGE_MODULE = "unassigned";
10186
10927
  var BLOCKER_SEPARATOR = "; ";
@@ -10191,7 +10932,7 @@ var MIN_FENCE_LENGTH = 3;
10191
10932
  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
10933
  var COMMENT_OPENING = "Reported again by a rafa task session.";
10193
10934
  function privateTriageDir(repoRoot) {
10194
- return join13(repoRoot, PRIVATE_TRIAGE_DIR);
10935
+ return join15(repoRoot, PRIVATE_TRIAGE_DIR);
10195
10936
  }
10196
10937
  function createPrivateTriageTracker(repoRoot, options = {}) {
10197
10938
  return createLocalTracker({
@@ -10768,6 +11509,13 @@ async function start(args, repoRoot) {
10768
11509
  args
10769
11510
  });
10770
11511
  guardRunBranch(planStub2, branch, args);
11512
+ await announceRiskTotal({
11513
+ repoRoot,
11514
+ home: homedir7(),
11515
+ planPath,
11516
+ config: runConfig.config,
11517
+ environment: process.env
11518
+ });
10771
11519
  await requireNoticesAnswered();
10772
11520
  setActivePlanStub(planStub2);
10773
11521
  const session = openRunSession({ repoRoot, planPath, planStub: planStub2, branch });