@open-tomato/rafa 0.24.1 → 0.26.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.
@@ -12,17 +12,18 @@ import {
12
12
  DEFAULT_RELEASE_SEAMS,
13
13
  DEMOTION_VERDICTS,
14
14
  DESCRIPTION_LIMIT,
15
- DISK_FILE_SYSTEM,
16
15
  EPIC_LABEL_PREFIX,
17
16
  EPIC_TYPE_LABEL,
18
17
  HINT_FLAG_SPEC,
19
18
  HORIZONS,
20
19
  HORIZON_LABEL_PREFIX,
20
+ ISSUE_LIST_FLAGS,
21
21
  LABEL_LIST_LIMIT,
22
22
  LEARNED_DIRECTORY,
23
23
  LISTING_LIMIT,
24
24
  MECHANICAL_CONFLICT_SENTENCE,
25
25
  NEXT_COMMAND_ACTIONS,
26
+ NO_NEXT_STEP,
26
27
  ORIGIN_FIELD,
27
28
  POSITION_FILE,
28
29
  PREVIOUS_COPY_WARN_ABOVE,
@@ -30,7 +31,6 @@ import {
30
31
  REASON_FLAG,
31
32
  REMOTE,
32
33
  ROADMAP_LABEL,
33
- ROADMAP_REFUSAL_EXIT,
34
34
  ROADMAP_SETTING,
35
35
  ROADMAP_TITLE,
36
36
  RUNNING_MANIFEST_SEAMS,
@@ -38,7 +38,6 @@ import {
38
38
  RUNTIME_SUBDIR,
39
39
  RefVerifyError,
40
40
  RefsBlockError,
41
- SCOPE_DIR,
42
41
  SERVE_CLI_VERSION,
43
42
  SESSION_ID_FLAG,
44
43
  SKILL_FILE as SKILL_FILE2,
@@ -47,7 +46,6 @@ import {
47
46
  SPEC_BLOCKED_LABEL,
48
47
  SPEC_EXTENSION,
49
48
  SPEC_TEMPLATE_PATH,
50
- SessionRecordError,
51
49
  TEMPLATES_DIRNAME,
52
50
  TRIAGE_MODULE,
53
51
  VENDOR_COMMAND,
@@ -98,12 +96,12 @@ import {
98
96
  createLinePrompter,
99
97
  createPlanDirNames,
100
98
  createPlanRefsVerifier,
99
+ developmentApplied,
101
100
  endWithNextStep,
102
101
  endingWith,
103
102
  epicEndLines,
104
103
  epicProblemMessage,
105
104
  epicSlugsOf,
106
- errorCode,
107
105
  estimateEta,
108
106
  etaLine,
109
107
  expectAtMostOneArgument,
@@ -119,6 +117,7 @@ import {
119
117
  formatCounts,
120
118
  formatDuration,
121
119
  formatRows,
120
+ gateMeaning,
122
121
  ghHostOf,
123
122
  ghMissingMessage,
124
123
  ghNextBoard,
@@ -128,7 +127,6 @@ import {
128
127
  gitSaid,
129
128
  groupByEpicLabel,
130
129
  hasDiverged,
131
- hasEpicLines,
132
130
  hasPlanFor,
133
131
  hasSpecBlockedLabel,
134
132
  hop,
@@ -147,13 +145,13 @@ import {
147
145
  lineRefusal,
148
146
  listBoardLabels,
149
147
  list_default,
148
+ list_default1 as list_default2,
150
149
  loadConfig,
151
150
  localDay,
152
151
  memoiseVerifier,
153
152
  minutesBetween,
154
153
  missingBoardLabels,
155
154
  missingPlanAgents,
156
- name,
157
155
  namedSecrets,
158
156
  nextQuestion,
159
157
  notesFileName,
@@ -200,12 +198,10 @@ import {
200
198
  readRecords,
201
199
  readRefsText,
202
200
  readRequiredFlag,
203
- readRoadmapEpicRows,
204
201
  readRoadmapSetting,
205
- readSession,
202
+ readSchemaReport,
206
203
  readSessionChecklist,
207
204
  readSessionFinishes,
208
- readSessions,
209
205
  readSwitch,
210
206
  readTags,
211
207
  readTextFlag,
@@ -220,12 +216,10 @@ import {
220
216
  renderCloseComment,
221
217
  renderDependentComment,
222
218
  renderDoctorRefs,
223
- renderEpicTable,
224
219
  renderHorizonComment,
225
220
  renderMoveComment,
226
221
  renderParkedPullRequestComment,
227
222
  renderRiskText,
228
- renderRoadmapTable,
229
223
  renderUnblockNote,
230
224
  report,
231
225
  requireGhProvider,
@@ -244,13 +238,10 @@ import {
244
238
  resolveReleaseEnabled,
245
239
  resolveTracker,
246
240
  restampCopyRefs,
247
- roadmapRefsCells,
248
241
  runAction,
249
- runsDir,
242
+ runIssueList,
250
243
  samePlace,
251
244
  scanClaimBranches,
252
- scopeAt,
253
- selfAndAncestors,
254
245
  serveVerdict,
255
246
  sessionIdFlag,
256
247
  sessionLine,
@@ -269,6 +260,8 @@ import {
269
260
  triageReport,
270
261
  trustRefusalClause,
271
262
  unaskedReasonMessage,
263
+ unknownAdditive,
264
+ unknownAdditiveWarning,
272
265
  unlabelledRoadmapMessage,
273
266
  unresolvedTracker,
274
267
  urlLines,
@@ -276,18 +269,15 @@ import {
276
269
  validateManifest,
277
270
  validate_default,
278
271
  verdictOf,
279
- version,
280
- versionLine,
281
272
  waitForChecks,
282
273
  watchDryEpic,
283
274
  writePositionFile,
284
275
  writeSession,
285
276
  writeShippedFile
286
- } from "./index-h369efkh.js";
277
+ } from "./index-59x3zsh4.js";
287
278
  import {
288
279
  PROBE_TIMEOUT_MS,
289
280
  findNextTask,
290
- isStampableStub,
291
281
  loadPlanPrerequisites,
292
282
  malformedPrerequisiteLines,
293
283
  mergePlanPrerequisites,
@@ -297,42 +287,71 @@ import {
297
287
  runPreflight,
298
288
  splitBlockerComment,
299
289
  trackerPathFor
300
- } from "./index-21qkxk63.js";
290
+ } from "./index-p1265xgj.js";
301
291
  import {
302
292
  ACTION_HEADING,
293
+ ADOPTION_NAME,
303
294
  CAUSE_HEADING,
304
295
  CLAUDE_BIN,
305
296
  CORE_ADAPTER_REGISTRY,
297
+ DISK_FILE_SYSTEM,
298
+ DevelopmentBuildRefusedError,
299
+ EFFORT_DIR_VARIABLE,
306
300
  EFFORT_STORE_DIR,
307
301
  ISSUE_PRIORITIES,
308
302
  ISSUE_STATES,
309
303
  ISSUE_TYPES,
310
304
  MARKDOWN_EXTENSION,
305
+ MIGRATION_LOG_TABLE,
306
+ SCOPE_DIR,
311
307
  SKILL_FILE,
312
308
  SQLITE_MIGRATIONS,
309
+ SQLITE_STORE_FILE_NAME,
310
+ SessionRecordError,
311
+ activeStoreSettings,
313
312
  allRecords,
313
+ appliedByName,
314
+ bringForward,
314
315
  claudeArgs,
315
316
  createGhRunner,
316
- effortStorePath,
317
+ effortStoreDir,
318
+ effortStoreFileName,
319
+ errorCode,
317
320
  findRecords,
318
321
  gitRemoteUrl,
322
+ guardTestProcess,
319
323
  instinctProject,
324
+ isPidAlive,
320
325
  mergeFrontmatter,
321
- migrateSchema,
326
+ name,
322
327
  normalizeRemote,
328
+ planSchema,
323
329
  readFrontmatter,
324
330
  readFrontmatterDocument,
331
+ readRuntimeIdentity,
325
332
  readScopes,
333
+ readSession,
334
+ readSessions,
335
+ readStoreSchema,
336
+ refuseUnownedDevelopmentWrite,
326
337
  renderFrontmatter,
327
338
  runClaudeCaptured,
339
+ runsDir,
328
340
  scanLayout,
341
+ scopeAt,
329
342
  selectEffortStore,
343
+ selfAndAncestors,
330
344
  sessionSpawnEnv,
331
345
  spawnClaudeCaptured,
346
+ sqliteCatalogue,
332
347
  sqliteStorePath,
348
+ storeProjectRoot,
333
349
  toHeldRecords,
334
- writeFrontmatter
335
- } from "./index-7qgng1rf.js";
350
+ version,
351
+ versionLine,
352
+ writeFrontmatter,
353
+ writeNames
354
+ } from "./index-azptzvp3.js";
336
355
  import {
337
356
  CONFIG_DEFAULTS,
338
357
  CONFIG_FILE,
@@ -341,12 +360,13 @@ import {
341
360
  activeOutput,
342
361
  configFilePath,
343
362
  findTierItem,
363
+ isStampableStub,
344
364
  parseConfigText,
345
365
  pinKey,
346
366
  readItemBytes,
347
367
  readRafaBlocks,
348
368
  resolveTiers
349
- } from "./index-twbp38kz.js";
369
+ } from "./index-rv7y8y1j.js";
350
370
  import {
351
371
  promotable
352
372
  } from "./index-d6d0942q.js";
@@ -354,10 +374,10 @@ import {
354
374
  createCommandRegistry,
355
375
  describeRegistry,
356
376
  mountKey
357
- } from "./index-w1dnwja1.js";
377
+ } from "./index-xwr54rzt.js";
358
378
  import {
359
379
  CommandExit
360
- } from "./index-4fwgqveh.js";
380
+ } from "./index-4j1e3h26.js";
361
381
  import {
362
382
  MERGE_METHODS,
363
383
  RELEASE_AUTO,
@@ -369,7 +389,7 @@ import {
369
389
  isSkillTier,
370
390
  messageOf,
371
391
  parseBudgetUsd
372
- } from "./index-pmkgvtfe.js";
392
+ } from "./index-sf2brh4w.js";
373
393
  import {
374
394
  triggerKey
375
395
  } from "./index-wp8c28fd.js";
@@ -1929,7 +1949,7 @@ function createSkillListCommand(seams = DEFAULT_SKILL_LIST_SEAMS) {
1929
1949
  };
1930
1950
  return Object.freeze(command);
1931
1951
  }
1932
- var list_default2 = createSkillListCommand();
1952
+ var list_default3 = createSkillListCommand();
1933
1953
 
1934
1954
  // src/commands/agent/list.ts
1935
1955
  var DEFAULT_AGENT_LIST_SEAMS = Object.freeze({
@@ -2131,7 +2151,7 @@ function createAgentListCommand(seams = DEFAULT_AGENT_LIST_SEAMS) {
2131
2151
  };
2132
2152
  return Object.freeze(command);
2133
2153
  }
2134
- var list_default3 = createAgentListCommand();
2154
+ var list_default4 = createAgentListCommand();
2135
2155
 
2136
2156
  // src/inventory/search/index.ts
2137
2157
  import { randomUUID } from "crypto";
@@ -3362,7 +3382,7 @@ function createBoardListCommand(seams = {}) {
3362
3382
  };
3363
3383
  return Object.freeze(command);
3364
3384
  }
3365
- var list_default4 = createBoardListCommand();
3385
+ var list_default5 = createBoardListCommand();
3366
3386
 
3367
3387
  // src/cleanup/branches.ts
3368
3388
  var BRANCH_FORMAT = [
@@ -4634,7 +4654,7 @@ var describeCommand = {
4634
4654
  var describe_default = Object.freeze(describeCommand);
4635
4655
 
4636
4656
  // src/commands/doctor.ts
4637
- import { basename as basename6, resolve as resolve5 } from "path";
4657
+ import { basename as basename5, resolve as resolve5 } from "path";
4638
4658
 
4639
4659
  // src/board/status.ts
4640
4660
  import { statSync as statSync4 } from "fs";
@@ -6676,14 +6696,77 @@ function renderDeep(reading) {
6676
6696
  return deepSections(reading).flatMap(renderDeepSection);
6677
6697
  }
6678
6698
 
6699
+ // src/commands/doctor-effort-schema.ts
6700
+ import { join as join15 } from "path";
6701
+ var EFFORT_SCHEMA_ROW = "effort store schema";
6702
+ var SCHEMA_COMMAND = "rafa effort schema";
6703
+ function developmentAppliedWarning(path, id2, appliedBy) {
6704
+ return `\u26A0 effort store ${path} logs migration ${id2} applied by a development build (${appliedBy});` + ` a development build migrates only a copy. Read the log with ${SCHEMA_COMMAND}.`;
6705
+ }
6706
+ function unreadable(path, live, problem) {
6707
+ return { outcome: "fail", path, live, status: "unreadable", problem, warnings: [], nextStep: NO_NEXT_STEP };
6708
+ }
6709
+ function readingOf(report2, live) {
6710
+ const { path, status, nextStep } = report2;
6711
+ if (report2.refused) {
6712
+ const problem = `this rafa refuses ${path} (${status}): ${String(report2.refusal)}`;
6713
+ return { outcome: "fail", path, live, status, problem, warnings: [], nextStep };
6714
+ }
6715
+ const developed = live ? developmentApplied(report2).map(({ id: id2, appliedBy }) => developmentAppliedWarning(path, id2, appliedBy)) : [];
6716
+ const warnings = [...unknownAdditive(report2).map(unknownAdditiveWarning), ...developed];
6717
+ const outcome = warnings.length === 0 ? "ok" : "warn";
6718
+ return { outcome, path, live, status, problem: null, warnings, nextStep };
6719
+ }
6720
+ function readDoctorEffortSchema(root, env, options = {}) {
6721
+ const override = env[EFFORT_DIR_VARIABLE];
6722
+ const live = override === undefined || override === "";
6723
+ let path;
6724
+ try {
6725
+ path = join15(effortStoreDir(root, env), SQLITE_STORE_FILE_NAME);
6726
+ } catch (error) {
6727
+ return unreadable(null, live, messageOf(error));
6728
+ }
6729
+ try {
6730
+ return readingOf(readSchemaReport(path, options), live);
6731
+ } catch (error) {
6732
+ return unreadable(path, live, `${path} cannot be read as an effort store: ${messageOf(error)}`);
6733
+ }
6734
+ }
6735
+ function wayOut(reading) {
6736
+ return reading.nextStep === NO_NEXT_STEP ? "" : ` Next safe step: ${reading.nextStep}`;
6737
+ }
6738
+ function renderDoctorEffortSchema(reading) {
6739
+ if (reading.outcome === "ok" && reading.status === "absent")
6740
+ return [];
6741
+ const where = reading.path === null ? "" : ` (${reading.path})`;
6742
+ const row = `Effort store schema: ${reading.outcome}, ${reading.status}${where}`;
6743
+ if (reading.problem === null)
6744
+ return [row];
6745
+ const next = reading.nextStep === NO_NEXT_STEP ? [] : [` Next safe step: ${reading.nextStep}`];
6746
+ return [row, ` ${reading.problem}`, ...next];
6747
+ }
6748
+ function writeDoctorEffortSchema(context, reading) {
6749
+ if (context.outputMode !== "json") {
6750
+ for (const line of renderDoctorEffortSchema(reading))
6751
+ context.output.info(line);
6752
+ }
6753
+ for (const warning of reading.warnings)
6754
+ context.output.warn(warning);
6755
+ }
6756
+ function effortSchemaRefusal(reading) {
6757
+ if (reading.outcome !== "fail")
6758
+ return null;
6759
+ return `rafa doctor: ${EFFORT_SCHEMA_ROW}: ${String(reading.problem)}${wayOut(reading)}`;
6760
+ }
6761
+
6679
6762
  // src/effort/store/legacy.ts
6680
6763
  import { statSync as statSync6 } from "fs";
6681
- import { basename as basename4, join as join15 } from "path";
6682
- var LEGACY_EFFORT_STORE_DIR = join15(".ralph", "effort");
6764
+ import { join as join16 } from "path";
6765
+ var LEGACY_EFFORT_STORE_DIR = join16(".ralph", "effort");
6683
6766
  var NDJSON_KINDS = { sessions: true, commits: true };
6684
6767
  var STORE_FILE_NAMES = Object.freeze([
6685
- basename4(sqliteStorePath("")),
6686
- ...Object.keys(NDJSON_KINDS).map((kind) => basename4(effortStorePath("", kind)))
6768
+ SQLITE_STORE_FILE_NAME,
6769
+ ...Object.keys(NDJSON_KINDS).map((kind) => effortStoreFileName(kind))
6687
6770
  ]);
6688
6771
  function isAbsence(error) {
6689
6772
  if (typeof error !== "object" || error === null || !("code" in error))
@@ -6700,7 +6783,7 @@ function isStoreFile(path) {
6700
6783
  }
6701
6784
  }
6702
6785
  function storeFilesIn(dir) {
6703
- return Object.freeze(STORE_FILE_NAMES.filter((name2) => isStoreFile(join15(dir, name2))));
6786
+ return Object.freeze(STORE_FILE_NAMES.filter((name2) => isStoreFile(join16(dir, name2))));
6704
6787
  }
6705
6788
  function shellQuoted(path) {
6706
6789
  return `'${path.replaceAll("'", "'\\''")}'`;
@@ -6710,8 +6793,8 @@ function legacyWarning(legacyDir, storeDir, legacyFiles) {
6710
6793
  return `${legacyDir} holds an effort store (${legacyFiles.join(", ")}) and ${storeDir} holds none;` + ` rafa reads ${storeDir} alone, so its reports start from an empty store. Once no loop is running,` + ` move it: ${move3}`;
6711
6794
  }
6712
6795
  function readLegacyStore(root) {
6713
- const legacyDir = join15(root, LEGACY_EFFORT_STORE_DIR);
6714
- const storeDir = join15(root, EFFORT_STORE_DIR);
6796
+ const legacyDir = join16(root, LEGACY_EFFORT_STORE_DIR);
6797
+ const storeDir = join16(root, EFFORT_STORE_DIR);
6715
6798
  const legacyFiles = storeFilesIn(legacyDir);
6716
6799
  const storeFiles = storeFilesIn(storeDir);
6717
6800
  const warning = legacyFiles.length > 0 && storeFiles.length === 0 ? legacyWarning(legacyDir, storeDir, legacyFiles) : null;
@@ -6719,9 +6802,9 @@ function readLegacyStore(root) {
6719
6802
  }
6720
6803
 
6721
6804
  // src/project/bin-path.ts
6722
- import { delimiter, isAbsolute as isAbsolute4, join as join16, resolve as resolve4 } from "path";
6723
- var RAFA_BIN_DIR = join16(SCOPE_DIR, "bin");
6724
- var BUN_BIN_DIR = join16(".bun", "bin");
6805
+ import { delimiter, isAbsolute as isAbsolute4, join as join17, resolve as resolve4 } from "path";
6806
+ var RAFA_BIN_DIR = join17(SCOPE_DIR, "bin");
6807
+ var BUN_BIN_DIR = join17(".bun", "bin");
6725
6808
  function exportLine(rafaBin) {
6726
6809
  return `export PATH="${rafaBin}:$PATH"`;
6727
6810
  }
@@ -6754,8 +6837,8 @@ function stateOf(rafaIndex, bunIndex) {
6754
6837
  return "ahead";
6755
6838
  }
6756
6839
  function readBinPath(pathValue, home, fs = DISK_FILE_SYSTEM) {
6757
- const rafaBin = join16(home, RAFA_BIN_DIR);
6758
- const bunBin = join16(home, BUN_BIN_DIR);
6840
+ const rafaBin = join17(home, RAFA_BIN_DIR);
6841
+ const bunBin = join17(home, BUN_BIN_DIR);
6759
6842
  const entries = (pathValue ?? "").split(delimiter);
6760
6843
  const rafaIndex = indexOf(entries, rafaBin, fs);
6761
6844
  const bunIndex = indexOf(entries, bunBin, fs);
@@ -6859,7 +6942,7 @@ function writeInstall(context, install) {
6859
6942
 
6860
6943
  // src/commands/doctor-tiers.ts
6861
6944
  import { readFileSync as readFileSync7, realpathSync as realpathSync4 } from "fs";
6862
- import { basename as basename5, dirname as dirname2 } from "path";
6945
+ import { basename as basename4, dirname as dirname2 } from "path";
6863
6946
  var TIERS_SECTION_TITLE = "Skill tiers";
6864
6947
  var CLAUDE_VERSION_TIMEOUT_MS = 1e4;
6865
6948
  var VERSION_PATTERN = /^\s*(\d+\.\d+\.\d+)/;
@@ -6920,7 +7003,7 @@ function keptHolder(item) {
6920
7003
  return [item.winner, ...item.copies].find((holder) => holder.source === "rafa") ?? item.winner;
6921
7004
  }
6922
7005
  function deletion(holder) {
6923
- return holder.kind === "skill" && basename5(holder.path) === SKILL_FILE2 ? `delete ${dirname2(holder.path)} (only its ${SKILL_FILE2} was compared)` : `delete ${holder.path}`;
7006
+ return holder.kind === "skill" && basename4(holder.path) === SKILL_FILE2 ? `delete ${dirname2(holder.path)} (only its ${SKILL_FILE2} was compared)` : `delete ${holder.path}`;
6924
7007
  }
6925
7008
  function copyRows(items, realPath = realPathOf) {
6926
7009
  return items.flatMap((item) => {
@@ -7064,7 +7147,7 @@ async function loadItems(plan2, config) {
7064
7147
  function refuseMalformedItems(plan2, items) {
7065
7148
  if (plan2 === null || items.malformed.length === 0)
7066
7149
  return;
7067
- const file = basename6(prerequisitesPathForPlan(plan2) ?? plan2);
7150
+ const file = basename5(prerequisitesPathForPlan(plan2) ?? plan2);
7068
7151
  const [head = "", ...rest] = malformedPrerequisiteLines(file, items.malformed);
7069
7152
  throw refusal3([
7070
7153
  `rafa doctor: ${head}`,
@@ -7091,7 +7174,7 @@ function readStartTier(plan2, items) {
7091
7174
  return { tier: NO_START_TIER, items: NO_ITEMS };
7092
7175
  if (isFirstDispatch(plan2))
7093
7176
  return { tier: { checked: start2.length, skipped: 0, tracker: null }, items: start2 };
7094
- const tracker = basename6(trackerPathFor(plan2));
7177
+ const tracker = basename5(trackerPathFor(plan2));
7095
7178
  return { tier: { checked: 0, skipped: start2.length, tracker }, items: NO_ITEMS };
7096
7179
  }
7097
7180
  async function checkPreflight(context, project, seams) {
@@ -7128,11 +7211,15 @@ async function checkPreflight(context, project, seams) {
7128
7211
  deep
7129
7212
  });
7130
7213
  }
7131
- function haltRefusal(halt) {
7132
- return new CommandExit(1, [
7214
+ function doctorRefusal(halt, schema) {
7215
+ if (halt === null) {
7216
+ return schema === null ? null : new CommandExit(1, schema);
7217
+ }
7218
+ const lines2 = [
7133
7219
  `rafa doctor: ${halt}`,
7134
7220
  "rafa loop start would halt here, before any session. No run was started and nothing was stored."
7135
- ].join(`
7221
+ ];
7222
+ return new CommandExit(1, [...lines2, ...schema === null ? [] : [schema]].join(`
7136
7223
  `));
7137
7224
  }
7138
7225
  async function checkDeep(context, preflight, seams) {
@@ -7162,6 +7249,7 @@ function resultOf(preflight, install, readings, deep) {
7162
7249
  cleanup: readings.cleanup,
7163
7250
  refs: readings.refs,
7164
7251
  tiers: readings.tiers,
7252
+ effortSchema: readings.effortSchema,
7165
7253
  deep
7166
7254
  };
7167
7255
  }
@@ -7195,16 +7283,20 @@ async function runDoctor(context, seams) {
7195
7283
  const cleanup = await readDoctorCleanup({ root: project.root, home: project.home, config: preflight.config, gh }, seams);
7196
7284
  const refs = await readDoctorRefs({ root: project.root, specsDir: preflight.config.specsDir, gh, env: context.env }, seams);
7197
7285
  const tiers = await checkDoctorTiers({ project, env: context.env, resolved: preflight.resolved, plan: null }, seams);
7198
- const readings = { ...await readDoctorBoard(gh, project.root, preflight.config.roadmapIssue), cleanup, refs, tiers };
7286
+ const effortSchema = readDoctorEffortSchema(project.root, context.env);
7287
+ const board = await readDoctorBoard(gh, project.root, preflight.config.roadmapIssue);
7288
+ const readings = { ...board, cleanup, refs, tiers, effortSchema };
7199
7289
  writeText(context, renderDoctor(preflight));
7200
7290
  await announceRisk(context, preflight, seams);
7201
7291
  const repository = [...renderDoctorBoard(readings), ...renderDoctorCleanup(readings.cleanup)];
7202
7292
  writeText(context, [...repository, ...renderDoctorRefs(readings.refs)]);
7203
7293
  writeText(context, renderDoctorTiers(readings.tiers));
7294
+ writeDoctorEffortSchema(context, effortSchema);
7204
7295
  const deep = await checkDeep(context, preflight, seams);
7205
7296
  writeText(context, deep === null ? [] : renderDeep(deep));
7206
- if (preflight.report.halt !== null)
7207
- throw haltRefusal(preflight.report.halt);
7297
+ const refusal4 = doctorRefusal(preflight.report.halt, effortSchemaRefusal(effortSchema));
7298
+ if (refusal4 !== null)
7299
+ throw refusal4;
7208
7300
  if (context.outputMode === "json")
7209
7301
  context.output.result(resultOf(preflight, install, readings, deep));
7210
7302
  } finally {
@@ -7217,7 +7309,7 @@ function createDoctorCommand(seams = DEFAULT_DOCTOR_SEAMS) {
7217
7309
  subject: "doctor",
7218
7310
  action: "doctor",
7219
7311
  summary: "check the prerequisites rafa loop start checks, and the install, starting no run",
7220
- description: "Checks the prerequisites `rafa loop start` checks before its first session and prints each" + " check, starting no run and storing nothing: the required and optional items of" + " `.rafa/config.yaml`, with the `PREREQUISITES-<stub>.md` beside the plan merged in, and, when the" + " repository resolves to `pr.provider: gh`, the two required items that provider adds ahead of them:" + " `gh` on PATH and `gh auth status` for the remote's host. A plan's `[start]` items are checked" + " after those and ahead of the configured tiers, on a first dispatch alone: a" + " `PLAN_TRACKER-<stub>.md` beside the plan already holding a ticked task makes the next run a" + " resume, which checks none of them and says in one line how many it passed over." + " The plan is the" + " one `--plan=<file>` names, relative to the project root, or the default plan `rafa loop start`" + ` runs. Each probe runs in the project root with stdin closed and a ${String(PROBE_TIMEOUT_MS / 1000)}-second` + " timeout. It exits 1 when a required item fails, naming the item, its probe and its exit code or" + " first line of stderr, where `rafa loop start` would halt, and 0 otherwise. It warns when" + " `.ralph/effort/` holds an effort store and `.rafa/effort/` holds none, and when `~/.rafa/bin` is" + " not on PATH ahead of `~/.bun/bin`, and when `previous/` under `specs.dir` holds more than fifty" + " previous copies of issue specs, which are safe to delete; a warning never changes the exit code. On a repository whose" + " provider is `gh` it also reads the GitHub board `rafa init --board` sets up and prints one row per" + " part \u2014 the seven labels, the spec issue template, the Roadmap issue and `roadmap.issue` \u2014 as present," + " missing, or unknown for a reading that failed, naming `rafa init --board` as the fix; it writes" + " nothing to the board and a row never changes the exit code. It then names, under `Blocked" + " issues:`, every open issue labelled `spec:blocked` whose `Blocked by:` line is missing, names no" + " issue, names itself, or names an id the board has no issue for, with what an author does about" + " it; that reading writes nothing and never changes the exit code either. It then names, under" + " `Epic labels:`, every issue carrying two `epic:` labels and every `epic:` label no `type:epic` issue" + " carries, read off one board listing, writing nothing and never changing the exit code. It then names, under" + " `Boards:`, every type:roadmap board whose Owner: handle resolves to nobody GitHub shows, every open issue titled" + ` "Roadmap" without type:roadmap while labelled boards exist, and every slot of this checkout's position on a` + " board or epic that is closed, unlabelled or gone, read off that same listing and printing nothing when there is" + " none; it writes nothing and never changes the exit code. It then counts, without" + " fetching, the branches and worktrees `rafa cleanup` would list, and prints them in one row naming" + " `rafa cleanup` when any group holds one. It then counts the suspect, dangling and unknown references" + " of every saved copy under `specs.dir`, writing nothing, and names `rafa issue check <n>` for each" + " copy holding a suspect or dangling one. It then prints, under `Skill tiers`, a warning per" + " skill or agent name two tiers hold with different contents, per unreviewed third-party rafa or" + " add-on item, and for an installed Claude Code other than the version skill serving was probed" + " against, and a note per byte-identical copy that can be deleted and per user-tier item with no" + " `provenance`; none changes the exit code. With `--output=json` the" + " checks, both readings, those rows, those issues, those labels and those boards are the data of the terminal result event," + " unless a required item failed. A plan `--plan` names also gets the one-line risk total" + " `rafa loop start` prints before its notices, which never changes the exit code. With `--deep` it" + " also prints the machine as a loop session sees it, starting no session: the session's" + " Environment and working directory against the shell's, the Settings each agent, skill and MCP" + " server is read from and whether a session sees it, the Providers `gh` answers under the session's" + " environment, and the Stack tools the project's stack needs, with Plan needs for a plan `--plan`" + " names; each row is ok, warn or note with its fix, printed after the blocked issues and before a" + " refusal, and none changes the exit code. With `--output=json` they are the `deep` of the data.",
7312
+ description: "Checks the prerequisites `rafa loop start` checks before its first session and prints each" + " check, starting no run and storing nothing: the required and optional items of" + " `.rafa/config.yaml`, with the `PREREQUISITES-<stub>.md` beside the plan merged in, and, when the" + " repository resolves to `pr.provider: gh`, the two required items that provider adds ahead of them:" + " `gh` on PATH and `gh auth status` for the remote's host. A plan's `[start]` items are checked" + " after those and ahead of the configured tiers, on a first dispatch alone: a" + " `PLAN_TRACKER-<stub>.md` beside the plan already holding a ticked task makes the next run a" + " resume, which checks none of them and says in one line how many it passed over." + " The plan is the" + " one `--plan=<file>` names, relative to the project root, or the default plan `rafa loop start`" + ` runs. Each probe runs in the project root with stdin closed and a ${String(PROBE_TIMEOUT_MS / 1000)}-second` + " timeout. It exits 1 when a required item fails, naming the item, its probe and its exit code or" + " first line of stderr, where `rafa loop start` would halt. It reads the effort store as" + " `rafa effort schema --check` does, changing nothing, and prints its `effort store schema` row:" + " it exits 1 where that check fails, naming the next safe step, and warns on a migration this rafa" + " does not know that is additive and on one the project's own store logs as applied by a development" + " build. It exits 0 otherwise. It warns when" + " `.ralph/effort/` holds an effort store and `.rafa/effort/` holds none, and when `~/.rafa/bin` is" + " not on PATH ahead of `~/.bun/bin`, and when `previous/` under `specs.dir` holds more than fifty" + " previous copies of issue specs, which are safe to delete; a warning never changes the exit code. On a repository whose" + " provider is `gh` it also reads the GitHub board `rafa init --board` sets up and prints one row per" + " part \u2014 the seven labels, the spec issue template, the Roadmap issue and `roadmap.issue` \u2014 as present," + " missing, or unknown for a reading that failed, naming `rafa init --board` as the fix; it writes" + " nothing to the board and a row never changes the exit code. It then names, under `Blocked" + " issues:`, every open issue labelled `spec:blocked` whose `Blocked by:` line is missing, names no" + " issue, names itself, or names an id the board has no issue for, with what an author does about" + " it; that reading writes nothing and never changes the exit code either. It then names, under" + " `Epic labels:`, every issue carrying two `epic:` labels and every `epic:` label no `type:epic` issue" + " carries, read off one board listing, writing nothing and never changing the exit code. It then names, under" + " `Boards:`, every type:roadmap board whose Owner: handle resolves to nobody GitHub shows, every open issue titled" + ` "Roadmap" without type:roadmap while labelled boards exist, and every slot of this checkout's position on a` + " board or epic that is closed, unlabelled or gone, read off that same listing and printing nothing when there is" + " none; it writes nothing and never changes the exit code. It then counts, without" + " fetching, the branches and worktrees `rafa cleanup` would list, and prints them in one row naming" + " `rafa cleanup` when any group holds one. It then counts the suspect, dangling and unknown references" + " of every saved copy under `specs.dir`, writing nothing, and names `rafa issue check <n>` for each" + " copy holding a suspect or dangling one. It then prints, under `Skill tiers`, a warning per" + " skill or agent name two tiers hold with different contents, per unreviewed third-party rafa or" + " add-on item, and for an installed Claude Code other than the version skill serving was probed" + " against, and a note per byte-identical copy that can be deleted and per user-tier item with no" + " `provenance`; none changes the exit code. With `--output=json` the" + " checks, both readings, those rows, those issues, those labels and those boards are the data of the terminal result event," + " unless a required item failed. A plan `--plan` names also gets the one-line risk total" + " `rafa loop start` prints before its notices, which never changes the exit code. With `--deep` it" + " also prints the machine as a loop session sees it, starting no session: the session's" + " Environment and working directory against the shell's, the Settings each agent, skill and MCP" + " server is read from and whether a session sees it, the Providers `gh` answers under the session's" + " environment, and the Stack tools the project's stack needs, with Plan needs for a plan `--plan`" + " names; each row is ok, warn or note with its fix, printed after the blocked issues and before a" + " refusal, and none changes the exit code. With `--output=json` they are the `deep` of the data.",
7221
7313
  args: [],
7222
7314
  flags: [
7223
7315
  {
@@ -7349,52 +7441,209 @@ var collect_default = wrapPhaseZeroCommand({
7349
7441
  outputs: ["text", "json"]
7350
7442
  }, collect);
7351
7443
 
7352
- // src/effort/store/fix-schema.ts
7353
- import { existsSync as existsSync5, renameSync, rmSync as rmSync2 } from "fs";
7444
+ // src/commands/effort/copy.ts
7445
+ import { join as join19, resolve as resolve7 } from "path";
7446
+
7447
+ // src/effort/store/copy.ts
7448
+ import { copyFileSync as copyFileSync3, constants, existsSync as existsSync5, mkdirSync as mkdirSync3, readdirSync, rmSync as rmSync2, statSync as statSync7 } from "fs";
7449
+ import { join as join18, resolve as resolve6 } from "path";
7354
7450
  import { Database } from "bun:sqlite";
7355
- class SchemaFixRefusal extends Error {
7451
+ var NDJSON_KINDS2 = { sessions: true, commits: true };
7452
+ var EFFORT_COPY_FILE_NAMES = [
7453
+ SQLITE_STORE_FILE_NAME,
7454
+ ...Object.keys(NDJSON_KINDS2).map((kind) => effortStoreFileName(kind))
7455
+ ];
7456
+
7457
+ class EffortCopyRefusal extends Error {
7458
+ name = "EffortCopyRefusal";
7459
+ }
7460
+
7461
+ class EffortCopyFailure extends Error {
7462
+ name = "EffortCopyFailure";
7463
+ }
7464
+ function refuseUsedTarget(target) {
7465
+ if (!existsSync5(target))
7466
+ return;
7467
+ let entries;
7468
+ try {
7469
+ if (!statSync7(target).isDirectory()) {
7470
+ throw new EffortCopyRefusal(`${target} is a file, not a directory; name an empty or absent directory with --to=<dir>.`);
7471
+ }
7472
+ entries = readdirSync(target);
7473
+ } catch (error) {
7474
+ if (error instanceof EffortCopyRefusal)
7475
+ throw error;
7476
+ throw new EffortCopyFailure(`${target} cannot be read: ${String(error)}`);
7477
+ }
7478
+ if (entries.length === 0)
7479
+ return;
7480
+ throw new EffortCopyRefusal(`${target} is not empty (${String(entries.length)} entries); nothing was copied.` + " Name an empty or absent directory with --to=<dir>.");
7481
+ }
7482
+ function vacuumInto(source, target) {
7483
+ const db = new Database(source, { readonly: true });
7484
+ try {
7485
+ db.run(`PRAGMA busy_timeout = ${String(activeStoreSettings().busyTimeoutMs)}`);
7486
+ db.run("VACUUM INTO ?", [target]);
7487
+ } finally {
7488
+ db.close();
7489
+ }
7490
+ }
7491
+ function copyOne2(name2, source, target) {
7492
+ if (name2 === SQLITE_STORE_FILE_NAME) {
7493
+ vacuumInto(join18(source, name2), join18(target, name2));
7494
+ return;
7495
+ }
7496
+ copyFileSync3(join18(source, name2), join18(target, name2), constants.COPYFILE_EXCL);
7497
+ }
7498
+ function undo(target, written, madeDir) {
7499
+ try {
7500
+ if (madeDir === undefined) {
7501
+ for (const name2 of written)
7502
+ rmSync2(join18(target, name2), { force: true });
7503
+ } else {
7504
+ rmSync2(madeDir, { recursive: true, force: true });
7505
+ }
7506
+ return "";
7507
+ } catch (error) {
7508
+ return ` Removing what it made failed too: ${String(error)}`;
7509
+ }
7510
+ }
7511
+ function copyEffortStore(request) {
7512
+ const source = resolve6(request.source);
7513
+ const target = resolve6(request.target);
7514
+ guardTestProcess(source);
7515
+ guardTestProcess(target);
7516
+ const files = EFFORT_COPY_FILE_NAMES.filter((name2) => existsSync5(join18(source, name2)));
7517
+ if (files.length === 0) {
7518
+ throw new EffortCopyRefusal(`no effort store at ${source}: it holds none of ${EFFORT_COPY_FILE_NAMES.join(", ")}. Nothing to copy.`);
7519
+ }
7520
+ refuseUsedTarget(target);
7521
+ const written = [];
7522
+ let madeDir;
7523
+ try {
7524
+ madeDir = mkdirSync3(target, { recursive: true });
7525
+ for (const name2 of files) {
7526
+ written.push(name2);
7527
+ copyOne2(name2, source, target);
7528
+ }
7529
+ } catch (error) {
7530
+ const left = undo(target, written, madeDir);
7531
+ throw new EffortCopyFailure(`copying ${source} to ${target} failed: ${String(error)}.${left}`);
7532
+ }
7533
+ return Object.freeze({ source, directory: target, files: Object.freeze(files) });
7534
+ }
7535
+
7536
+ // src/effort/store/fix-schema.ts
7537
+ import { existsSync as existsSync7 } from "fs";
7538
+ import { Database as Database2 } from "bun:sqlite";
7539
+
7540
+ // src/effort/store/rebuild-aside.ts
7541
+ import { existsSync as existsSync6, renameSync, rmSync as rmSync3 } from "fs";
7542
+
7543
+ class RebuildRefusal extends Error {
7356
7544
  constructor(message) {
7357
7545
  super(message);
7358
- this.name = "SchemaFixRefusal";
7546
+ this.name = "RebuildRefusal";
7359
7547
  }
7360
7548
  }
7361
- var LIVE = "live";
7362
7549
  function quoted(name2) {
7363
7550
  return `"${name2.replaceAll('"', '""')}"`;
7364
7551
  }
7552
+ function count(db, sql) {
7553
+ return db.query(sql).get()?.n ?? 0;
7554
+ }
7555
+ function refuseInFlight(path) {
7556
+ for (const suffix of ["-journal", "-wal"]) {
7557
+ if (existsSync6(`${path}${suffix}`)) {
7558
+ throw new RebuildRefusal(`${path}${suffix} is there, so a write is in flight or was interrupted; stop whatever uses the store` + " and open it once with rafa before repairing it");
7559
+ }
7560
+ }
7561
+ }
7562
+ function checkedCounts(db, tables, original) {
7563
+ return tables.map((table) => {
7564
+ const rows = count(db, `SELECT count(*) AS n FROM main.${quoted(table)}`);
7565
+ const originalRows = count(db, `SELECT count(*) AS n FROM ${original}.${quoted(table)}`);
7566
+ if (rows !== originalRows) {
7567
+ throw new RebuildRefusal(`the rebuilt ${table} holds ${String(rows)} rows, the store ${String(originalRows)}`);
7568
+ }
7569
+ return { table, rows };
7570
+ });
7571
+ }
7572
+ function refuseCorrupt(db) {
7573
+ const answer = db.query("PRAGMA main.integrity_check").get()?.integrity_check;
7574
+ if (answer !== "ok")
7575
+ throw new RebuildRefusal(`the rebuilt store failed integrity_check: ${answer ?? "no answer"}`);
7576
+ }
7577
+ function removeParallel(parallelPath) {
7578
+ rmSync3(parallelPath, { force: true });
7579
+ rmSync3(`${parallelPath}-journal`, { force: true });
7580
+ }
7581
+ function swapIn(path, parallelPath, backupPath) {
7582
+ renameSync(path, backupPath);
7583
+ try {
7584
+ renameSync(parallelPath, path);
7585
+ } catch (error) {
7586
+ renameSync(backupPath, path);
7587
+ throw error;
7588
+ }
7589
+ }
7590
+ function refuseTaken(paths) {
7591
+ for (const taken of paths) {
7592
+ if (existsSync6(taken))
7593
+ throw new RebuildRefusal(`${taken} is already there; move it aside or pass another stamp`);
7594
+ }
7595
+ }
7596
+ function rebuildAside(options) {
7597
+ const { path, parallelPath, backupPath, dryRun, build } = options;
7598
+ refuseInFlight(path);
7599
+ refuseTaken([parallelPath, backupPath]);
7600
+ let built;
7601
+ try {
7602
+ built = build(parallelPath);
7603
+ } catch (error) {
7604
+ removeParallel(parallelPath);
7605
+ throw error;
7606
+ }
7607
+ if (dryRun) {
7608
+ removeParallel(parallelPath);
7609
+ return { built, backupPath: null };
7610
+ }
7611
+ swapIn(path, parallelPath, backupPath);
7612
+ return { built, backupPath };
7613
+ }
7614
+
7615
+ // src/effort/store/fix-schema.ts
7616
+ var REPAIRABLE_REASONS = [
7617
+ "pre-log-unreleased",
7618
+ "gate-mismatch",
7619
+ "edited",
7620
+ "unknown-breaks-readers",
7621
+ "unknown-breaks-writers"
7622
+ ];
7623
+ var LIVE = "live";
7365
7624
  function tableNames(db, schema) {
7366
- return db.query(`SELECT name FROM ${schema}.sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' ORDER BY name`).all().map((row2) => row2.name);
7625
+ return db.query(`SELECT name FROM ${schema}.sqlite_master WHERE type = 'table' AND name NOT LIKE 'sqlite_%' AND name <> ? ORDER BY name`).all(MIGRATION_LOG_TABLE).map((row2) => row2.name);
7367
7626
  }
7368
7627
  function columnNames(db, schema, table) {
7369
7628
  return db.query("SELECT name FROM pragma_table_info(?, ?)").all(table, schema).map((row2) => row2.name);
7370
7629
  }
7371
- function count(db, sql) {
7372
- return db.query(sql).get()?.n ?? 0;
7373
- }
7374
- function versionOf(path) {
7375
- const db = new Database(path, { readonly: true });
7630
+ function readOnlySchema(path) {
7631
+ const db = new Database2(path, { readonly: true });
7376
7632
  try {
7377
- return db.query("PRAGMA user_version").get()?.user_version ?? 0;
7633
+ return readStoreSchema(db, path);
7378
7634
  } finally {
7379
7635
  db.close();
7380
7636
  }
7381
7637
  }
7382
- function refuseInFlight(path) {
7383
- for (const suffix of ["-journal", "-wal"]) {
7384
- if (existsSync5(`${path}${suffix}`)) {
7385
- throw new SchemaFixRefusal(`${path}${suffix} is there, so a write is in flight or was interrupted; stop whatever uses the store` + " and open it once with rafa before repairing it");
7386
- }
7387
- }
7388
- }
7389
7638
  function refuseRemoved(db, known, live) {
7390
7639
  for (const table of known) {
7391
7640
  if (!live.has(table)) {
7392
- throw new SchemaFixRefusal(`the store holds no table ${table}, which this rafa writes; its newer schema is not additive`);
7641
+ throw new RebuildRefusal(`the store holds no table ${table}, which this rafa writes; its newer schema is not additive`);
7393
7642
  }
7394
7643
  const liveColumns = new Set(columnNames(db, LIVE, table));
7395
7644
  const missing = columnNames(db, "main", table).filter((column) => !liveColumns.has(column));
7396
7645
  if (missing.length > 0) {
7397
- throw new SchemaFixRefusal(`the store holds no column ${missing.map((column) => `${table}.${column}`).join(", ")}, which this rafa` + " writes; its newer schema is not additive");
7646
+ throw new RebuildRefusal(`the store holds no column ${missing.map((column) => `${table}.${column}`).join(", ")}, which this rafa` + " writes; its newer schema is not additive");
7398
7647
  }
7399
7648
  }
7400
7649
  }
@@ -7406,16 +7655,6 @@ function copyKnown(db, known) {
7406
7655
  }
7407
7656
  })();
7408
7657
  }
7409
- function checkedCounts(db, known) {
7410
- return known.map((table) => {
7411
- const rows = count(db, `SELECT count(*) AS n FROM main.${quoted(table)}`);
7412
- const liveRows = count(db, `SELECT count(*) AS n FROM ${LIVE}.${quoted(table)}`);
7413
- if (rows !== liveRows) {
7414
- throw new SchemaFixRefusal(`the rebuilt ${table} holds ${String(rows)} rows, the store ${String(liveRows)}`);
7415
- }
7416
- return { table, rows };
7417
- });
7418
- }
7419
7658
  function leftBehind(db, known, live) {
7420
7659
  const leftTables = live.filter((table) => !known.has(table)).map((table) => ({ table, rows: count(db, `SELECT count(*) AS n FROM ${LIVE}.${quoted(table)}`) }));
7421
7660
  const leftColumns = live.filter((table) => known.has(table)).flatMap((table) => {
@@ -7428,78 +7667,91 @@ function leftBehind(db, known, live) {
7428
7667
  });
7429
7668
  return { leftTables, leftColumns };
7430
7669
  }
7431
- function refuseCorrupt(db) {
7432
- const answer = db.query("PRAGMA main.integrity_check").get()?.integrity_check;
7433
- if (answer !== "ok")
7434
- throw new SchemaFixRefusal(`the rebuilt store failed integrity_check: ${answer ?? "no answer"}`);
7670
+ function refuseUnusable(db, parallelPath, migrations) {
7671
+ const plan2 = planSchema(readStoreSchema(db, parallelPath), sqliteCatalogue(migrations), "write", "open");
7672
+ if (plan2.verdict === "refuse")
7673
+ throw new RebuildRefusal(`the rebuilt store is refused as ${plan2.reason}: ${plan2.message}`);
7674
+ const needs = writeNames(plan2);
7675
+ if (needs.length > 0)
7676
+ throw new RebuildRefusal(`the rebuilt store still needs ${needs.join(", ")}`);
7435
7677
  }
7436
- function buildParallel(path, parallelPath, migrations) {
7437
- const db = new Database(parallelPath, { create: true, readwrite: true });
7678
+ function buildParallel(path, parallelPath, migrations, stamp) {
7679
+ const db = new Database2(parallelPath, { create: true, readwrite: true });
7438
7680
  try {
7439
- migrateSchema(db, parallelPath, migrations);
7681
+ bringForward(db, parallelPath, "write", "open", { migrations, ...stamp, builtAside: true });
7440
7682
  db.run(`ATTACH DATABASE ? AS ${LIVE}`, [path]);
7441
7683
  const known = tableNames(db, "main");
7442
7684
  const live = tableNames(db, LIVE);
7443
7685
  refuseRemoved(db, known, new Set(live));
7444
7686
  copyKnown(db, known);
7445
- const kept = checkedCounts(db, known);
7687
+ const kept = checkedCounts(db, known, LIVE);
7446
7688
  const left = leftBehind(db, new Set(known), live);
7447
7689
  db.run(`DETACH DATABASE ${LIVE}`);
7448
7690
  refuseCorrupt(db);
7691
+ refuseUnusable(db, parallelPath, migrations);
7449
7692
  return { kept, ...left };
7450
7693
  } finally {
7451
7694
  db.close();
7452
7695
  }
7453
7696
  }
7454
- function removeParallel(parallelPath) {
7455
- rmSync2(parallelPath, { force: true });
7456
- rmSync2(`${parallelPath}-journal`, { force: true });
7697
+ function asidePaths(path, version2, stamp) {
7698
+ return { parallelPath: `${path}.fix-${stamp}`, backupPath: `${path}.v${String(version2)}-${stamp}.bak` };
7457
7699
  }
7458
- function swapIn(path, parallelPath, backupPath) {
7459
- renameSync(path, backupPath);
7460
- try {
7461
- renameSync(parallelPath, path);
7462
- } catch (error) {
7463
- renameSync(backupPath, path);
7464
- throw error;
7465
- }
7700
+ function isRepairable(reason) {
7701
+ return REPAIRABLE_REASONS.includes(reason);
7466
7702
  }
7467
- function freshPaths(path, version2, stamp) {
7468
- const parallelPath = `${path}.fix-${stamp}`;
7469
- const backupPath = `${path}.v${String(version2)}-${stamp}.bak`;
7470
- for (const taken of [parallelPath, backupPath]) {
7471
- if (existsSync5(taken))
7472
- throw new SchemaFixRefusal(`${taken} is already there; move it aside or pass another stamp`);
7473
- }
7474
- return { parallelPath, backupPath };
7703
+ function refuseDevelopmentSwap(path, reason, identity) {
7704
+ if (identity.kind !== "development")
7705
+ return;
7706
+ throw new RebuildRefusal(`${path} needs a rebuild (${reason}), and this rafa is a development build (${identity.checkout}), whose` + " migrations may be a branch's; a development build never swaps a rebuild in. Nothing was changed, and" + " --dry-run runs from here. Run the swap from an installed rafa. Next safe step: rafa effort fix-schema");
7475
7707
  }
7476
7708
  function fixStoreSchema(options) {
7477
7709
  const { path, dryRun, stamp } = options;
7710
+ guardTestProcess(path);
7478
7711
  const migrations = options.migrations ?? SQLITE_MIGRATIONS;
7479
- const knownVersion = migrations.length;
7480
- const nothing = { kept: [], leftTables: [], leftColumns: [], backupPath: null };
7481
- if (!existsSync5(path))
7482
- return { path, status: "missing", storeVersion: null, knownVersion, ...nothing };
7712
+ const identity = options.identity ?? readRuntimeIdentity();
7713
+ const appliedBy = appliedByName(identity);
7714
+ const known = migrations.map(({ id: id2 }) => id2);
7715
+ const nothing = { known, appliedBy, pending: [], kept: [], leftTables: [], leftColumns: [], backupPath: null };
7716
+ if (!existsSync7(path)) {
7717
+ return { path, status: "missing", storeVersion: null, logged: false, reason: null, refusal: null, unknown: [], ...nothing };
7718
+ }
7483
7719
  refuseInFlight(path);
7484
- const storeVersion = versionOf(path);
7485
- if (storeVersion === knownVersion)
7486
- return { path, status: "current", storeVersion, knownVersion, ...nothing };
7487
- if (storeVersion < knownVersion)
7488
- return { path, status: "behind", storeVersion, knownVersion, ...nothing };
7489
- const { parallelPath, backupPath } = freshPaths(path, storeVersion, stamp);
7490
- let built;
7491
- try {
7492
- built = buildParallel(path, parallelPath, migrations);
7493
- } catch (error) {
7494
- removeParallel(parallelPath);
7495
- throw error;
7720
+ const store = readOnlySchema(path);
7721
+ const knownIds = new Set(known);
7722
+ const found = {
7723
+ path,
7724
+ storeVersion: store.userVersion,
7725
+ logged: store.log !== null,
7726
+ unknown: (store.log ?? []).filter(({ id: id2 }) => !knownIds.has(id2))
7727
+ };
7728
+ const plan2 = planSchema(store, sqliteCatalogue(migrations), "write", "open");
7729
+ if (plan2.verdict === "use") {
7730
+ const pending = writeNames(plan2);
7731
+ const status2 = pending.length === 0 ? "current" : "behind";
7732
+ return { ...found, ...nothing, status: status2, reason: null, refusal: null, pending };
7496
7733
  }
7497
- if (dryRun) {
7498
- removeParallel(parallelPath);
7499
- return { path, status: "would-rebuild", storeVersion, knownVersion, ...built, backupPath: null };
7734
+ if (!isRepairable(plan2.reason)) {
7735
+ throw new RebuildRefusal(`a rebuild at this rafa's migrations does not repair ${plan2.reason}. ${plan2.message}`);
7500
7736
  }
7501
- swapIn(path, parallelPath, backupPath);
7502
- return { path, status: "rebuilt", storeVersion, knownVersion, ...built, backupPath };
7737
+ if (!dryRun)
7738
+ refuseDevelopmentSwap(path, plan2.reason, identity);
7739
+ const { built, backupPath } = rebuildAside({
7740
+ path,
7741
+ ...asidePaths(path, store.userVersion, stamp),
7742
+ dryRun,
7743
+ build: (parallelPath) => buildParallel(path, parallelPath, migrations, {
7744
+ appliedBy,
7745
+ now: options.now ?? (() => new Date)
7746
+ })
7747
+ });
7748
+ const status = dryRun ? "would-rebuild" : "rebuilt";
7749
+ const refusal4 = withoutWayOut(plan2.message, plan2.nextStep);
7750
+ return { ...found, ...nothing, status, reason: plan2.reason, refusal: refusal4, ...built, backupPath };
7751
+ }
7752
+ function withoutWayOut(message, nextStep) {
7753
+ const ending = ` Next safe step: ${nextStep}`;
7754
+ return message.endsWith(ending) ? message.slice(0, -ending.length) : message;
7503
7755
  }
7504
7756
 
7505
7757
  // src/commands/effort/fix-schema.ts
@@ -7521,38 +7773,52 @@ function refuseLiveLoop(root, isAlive) {
7521
7773
  return;
7522
7774
  throw new CommandExit(1, `\u274C ${COMMAND_NAME5}: loop session ${live.sessionId} on ${live.branch} is ${live.state} and writes the store;` + ` stop it with \`rafa loop stop -s ${live.sessionId}\` first. \`--dry-run\` runs beside it.`);
7523
7775
  }
7776
+ function migrationNames(result) {
7777
+ return result.unknown.map(({ id: id2, appliedBy }) => `${id2} (applied by ${appliedBy})`).join(", ");
7778
+ }
7524
7779
  function rebuildLines(result) {
7525
7780
  const rows = result.kept.reduce((sum, table) => sum + table.rows, 0);
7526
7781
  const kept = result.kept.map((table) => `${table.table} ${String(table.rows)}`).join(", ");
7527
7782
  const left = [
7783
+ ...result.unknown.map(({ id: id2, appliedBy }) => `migration ${id2} (applied by ${appliedBy})`),
7528
7784
  ...result.leftTables.map((table) => `table ${table.table} (${String(table.rows)} rows)`),
7529
7785
  ...result.leftColumns.map((column) => `column ${column.table}.${column.column} (${String(column.values)} values)`)
7530
7786
  ];
7787
+ const newer = result.unknown.length === 0 ? [] : [`A rafa that knows ${result.unknown.map(({ id: id2 }) => id2).join(", ")} uses the store as it is and keeps that data` + " in it; install one instead where you can."];
7531
7788
  return [
7532
- `${result.path} is at schema version ${String(result.storeVersion)}, past the ${String(result.knownVersion)} this rafa knows.`,
7789
+ `Refused as ${String(result.reason)}: ${String(result.refusal)}`,
7790
+ `Rebuilds it at the ${String(result.known.length)} migrations this rafa knows, logged as applied by ${result.appliedBy}.`,
7533
7791
  `Keeps ${String(result.kept.length)} tables, ${String(rows)} rows: ${kept}.`,
7534
- left.length === 0 ? "Leaves nothing behind." : `Leaves behind, kept only in the backup: ${left.join("; ")}.`
7792
+ left.length === 0 ? "Leaves nothing behind." : `Leaves behind, kept only in the backup: ${left.join("; ")}.`,
7793
+ ...newer
7535
7794
  ];
7536
7795
  }
7796
+ function unknownAdditiveLines(result) {
7797
+ return result.unknown.length === 0 ? [] : [`It logs ${migrationNames(result)}, which this rafa does not know; each is additive, so this rafa uses the store` + " through them."];
7798
+ }
7537
7799
  function renderFixSchema(result) {
7538
7800
  switch (result.status) {
7539
7801
  case "missing":
7540
7802
  return [`No effort store at ${result.path}: nothing to repair.`];
7541
7803
  case "current":
7542
- return [`\u2705 ${result.path} is at schema version ${String(result.knownVersion)}, the one this rafa knows: nothing to repair.`];
7804
+ return [
7805
+ `\u2705 ${result.path} is current: this rafa reads and writes it as it is. Nothing to repair.`,
7806
+ ...unknownAdditiveLines(result)
7807
+ ];
7543
7808
  case "behind":
7544
7809
  return [
7545
- `${result.path} is at schema version ${String(result.storeVersion)}, behind the ${String(result.knownVersion)} this rafa` + " knows; the next command that opens it migrates it. Nothing to repair."
7810
+ `${result.path} is behind this rafa's migrations; the next command that opens it records` + ` ${result.pending.join(", ")}. Nothing to repair.`,
7811
+ ...unknownAdditiveLines(result)
7546
7812
  ];
7547
7813
  case "would-rebuild":
7548
7814
  return [
7549
7815
  ...rebuildLines(result),
7550
- `\uD83D\uDD0D Dry run: the rebuild at version ${String(result.knownVersion)} was built, checked (row counts, integrity_check)` + " and deleted. Run without `--dry-run` to swap it in."
7816
+ "\uD83D\uDD0D Dry run: the rebuild was built, checked (row counts, integrity_check, this rafa's schema plan) and" + " deleted. Run without `--dry-run` to swap it in."
7551
7817
  ];
7552
7818
  case "rebuilt":
7553
7819
  return [
7554
7820
  ...rebuildLines(result),
7555
- `\u2705 Rebuilt at version ${String(result.knownVersion)}. The original is kept whole at ${String(result.backupPath)};` + " rename it back to undo."
7821
+ `\u2705 Rebuilt at this rafa's migrations. The original is kept whole at ${String(result.backupPath)};` + " rename it back to undo."
7556
7822
  ];
7557
7823
  }
7558
7824
  }
@@ -7564,15 +7830,18 @@ function runFixSchema(context, seams) {
7564
7830
  const root = context.project.root;
7565
7831
  if (!dryRun)
7566
7832
  refuseLiveLoop(root, seams.isAlive);
7833
+ const now = seams.now ?? (() => new Date);
7567
7834
  let result;
7568
7835
  try {
7569
7836
  result = fixStoreSchema({
7570
7837
  path: sqliteStorePath(root),
7571
7838
  dryRun,
7572
- stamp: fileStamp((seams.now ?? (() => new Date))())
7839
+ stamp: fileStamp(now()),
7840
+ now,
7841
+ ...seams.identity === undefined ? {} : { identity: seams.identity }
7573
7842
  });
7574
7843
  } catch (error) {
7575
- if (error instanceof SchemaFixRefusal)
7844
+ if (error instanceof RebuildRefusal)
7576
7845
  throw new CommandExit(1, `\u274C ${COMMAND_NAME5}: ${error.message}`);
7577
7846
  throw error;
7578
7847
  }
@@ -7588,13 +7857,13 @@ function createFixSchemaCommand(seams = {}) {
7588
7857
  name: "effort fix-schema",
7589
7858
  subject: "effort",
7590
7859
  action: "fix-schema",
7591
- summary: "rebuild an effort store a newer rafa migrated at the version this rafa knows, keeping the original",
7592
- description: "Repairs `.rafa/effort/effort.sqlite` when it is past the schema version this rafa knows, which" + " happens when newer code opens it, such as a plan's own branch code run while an older installed rafa" + " drives the loop. It builds a parallel store beside it at this rafa's version, copies every table and" + " column this rafa knows, checks the row counts and SQLite's integrity_check, and lists what only the" + " newer schema holds. Then it renames the original to `effort.sqlite.v<version>-<stamp>.bak`, whole, and" + " moves the rebuild into its place. A store at or behind this version, or no store, is left alone. It" + " refuses, changing nothing, while a loop session under the project is running or paused, while a" + " journal beside the store shows a write in flight, and when the newer schema lacks a table or column" + " this rafa writes. With `--output=json` the outcome is the data of the terminal result event. Starts no" + " session.",
7860
+ summary: "rebuild an effort store this rafa refuses at the migrations it knows, keeping the original",
7861
+ description: "Repairs `.rafa/effort/effort.sqlite`, or the store under `RAFA_EFFORT_DIR`, when this rafa refuses" + " it for a reason a rebuild repairs: no migration log past the legacy entries (pre-log-unreleased), a log" + " with a schema version it does not match (gate-mismatch), a logged checksum this rafa holds otherwise" + " (edited), or a logged migration this rafa does not know that breaks older readers or writers" + " (unknown-breaks-readers, unknown-breaks-writers). Installing a rafa that knows the store's migrations" + " comes first where one exists. It builds a parallel store beside it at this rafa's migrations, with a" + " migration log naming this rafa, copies every table and column this rafa knows, checks the row counts," + " SQLite's integrity_check and the schema plan, and lists what only the newer schema holds, unknown" + " migrations included. Then it renames the original to `effort.sqlite.v<user_version>-<stamp>.bak`," + " whole, and moves the rebuild into its place. A store this rafa uses, current or behind, or no store, is" + " left alone. It refuses, changing nothing, while a loop session under the project is running or paused," + " from a development build, while a journal beside the store shows a write in flight, when the newer" + " schema lacks a table or column this rafa writes, and on a refusal a rebuild does not repair. With" + " `--output=json` the outcome is the data of the terminal result event. Starts no session.",
7593
7862
  args: [],
7594
7863
  flags: [
7595
7864
  {
7596
7865
  name: DRY_RUN_FLAG2,
7597
- description: "Build and check the rebuild, print what it keeps and leaves behind, then delete it. The" + " live store is only read, so this runs beside a live loop and can be repeated.",
7866
+ description: "Build and check the rebuild, print what it keeps and leaves behind, then delete it. The" + " live store is only read, so this runs beside a live loop and from a development build, and can be" + " repeated.",
7598
7867
  type: "boolean"
7599
7868
  }
7600
7869
  ],
@@ -7618,6 +7887,288 @@ function createFixSchemaCommand(seams = {}) {
7618
7887
  }
7619
7888
  var fix_schema_default = createFixSchemaCommand();
7620
7889
 
7890
+ // src/commands/effort/copy.ts
7891
+ var COMMAND_NAME6 = "rafa effort copy";
7892
+ var COPY_USAGE = "rafa effort copy [--to=<dir>]";
7893
+ var TO_FLAG = "to";
7894
+ var REFUSED_EXIT = 1;
7895
+ var FAILED_EXIT = 2;
7896
+ var SCRATCH_DIR = join19(".rafa", "scratch");
7897
+ function effortDirLine(directory) {
7898
+ return `${EFFORT_DIR_VARIABLE}=${directory}`;
7899
+ }
7900
+ function readTarget(context, root) {
7901
+ const value = context.flags[TO_FLAG];
7902
+ if (value === undefined)
7903
+ return null;
7904
+ if (typeof value !== "string" || value === "") {
7905
+ throw new CommandExit(REFUSED_EXIT, `\u274C --${TO_FLAG} takes a directory: --${TO_FLAG}=<dir>
7906
+ Usage: ${COPY_USAGE}`);
7907
+ }
7908
+ return resolve7(root, value);
7909
+ }
7910
+ function refuseEffortDirOverride(context) {
7911
+ const value = context.env[EFFORT_DIR_VARIABLE];
7912
+ if (value === undefined || value === "")
7913
+ return;
7914
+ throw new CommandExit(REFUSED_EXIT, `\u274C ${COMMAND_NAME6}: ${EFFORT_DIR_VARIABLE} is set (${value}); a copy is taken from the project's own store.` + ` Nothing was copied. Unset it and run again: ${EFFORT_DIR_VARIABLE}= ${COMMAND_NAME6}`);
7915
+ }
7916
+ function renderCopy(result) {
7917
+ return [
7918
+ `\u2705 Copied ${result.files.join(", ")} from ${result.source} to ${result.directory}.`,
7919
+ "Put this in front of each command that should use the copy, on the same line:",
7920
+ effortDirLine(result.directory)
7921
+ ];
7922
+ }
7923
+ function runCopy(context, seams) {
7924
+ expectNoArgument(context.args, COPY_USAGE);
7925
+ const root = requireProject(context, COMMAND_NAME6).root;
7926
+ const named = readTarget(context, root);
7927
+ refuseEffortDirOverride(context);
7928
+ const target = named ?? join19(root, SCRATCH_DIR, `effort-${fileStamp((seams.now ?? (() => new Date))())}`);
7929
+ let result;
7930
+ try {
7931
+ result = copyEffortStore({ source: join19(root, EFFORT_STORE_DIR), target });
7932
+ } catch (error) {
7933
+ if (error instanceof EffortCopyRefusal)
7934
+ throw new CommandExit(REFUSED_EXIT, `\u274C ${COMMAND_NAME6}: ${error.message}`);
7935
+ if (error instanceof EffortCopyFailure)
7936
+ throw new CommandExit(FAILED_EXIT, `\u274C ${COMMAND_NAME6}: ${error.message}`);
7937
+ throw error;
7938
+ }
7939
+ if (context.outputMode === "json") {
7940
+ context.output.result({ ...result, env: effortDirLine(result.directory) });
7941
+ return;
7942
+ }
7943
+ for (const line of renderCopy(result))
7944
+ context.output.info(line);
7945
+ }
7946
+ function createCopyCommand(seams = {}) {
7947
+ const command = {
7948
+ name: "effort copy",
7949
+ subject: "effort",
7950
+ action: "copy",
7951
+ summary: "copy the effort store into a scratch directory and print the RAFA_EFFORT_DIR= line that points a command at it",
7952
+ description: "Copies `.rafa/effort/` into a directory of its own: the SQLite file through `VACUUM INTO`, which only" + " reads the store and keeps its schema version and migration log, and the NDJSON files by file copy. The" + " copy lands in `--to`, or in `.rafa/scratch/effort-<stamp>/`. It prints the directory and the" + " `RAFA_EFFORT_DIR=` line to put in front of each command that should run over the copy instead of the" + " live store. It plans no schema, so a development build may run it and a running loop is not disturbed." + " Exit code 1, making nothing, when `RAFA_EFFORT_DIR` is set, when there is no store, or when the target" + " is not empty; exit code 2 when a read or write failed, with what it made removed. With `--output=json`" + " the copy is the data of the terminal result event. Starts no session.",
7953
+ args: [],
7954
+ flags: [
7955
+ {
7956
+ name: TO_FLAG,
7957
+ description: "The directory to copy into, absent or empty; a relative one is read from the project root." + " Defaults to `.rafa/scratch/effort-<stamp>/`.",
7958
+ type: "string"
7959
+ }
7960
+ ],
7961
+ examples: [
7962
+ {
7963
+ cmd: "rafa effort copy",
7964
+ note: "Copies the store into `.rafa/scratch/effort-<stamp>/` and prints the `RAFA_EFFORT_DIR=` line."
7965
+ },
7966
+ {
7967
+ cmd: "rafa effort copy --to=.rafa/scratch/rafa-234-effort",
7968
+ note: "Copies the store into a directory a plan names."
7969
+ }
7970
+ ],
7971
+ outputs: ["text", "json"],
7972
+ run: async (context) => {
7973
+ runCopy(context, seams);
7974
+ await Promise.resolve();
7975
+ }
7976
+ };
7977
+ return Object.freeze(command);
7978
+ }
7979
+ var copy_default = createCopyCommand();
7980
+
7981
+ // src/commands/effort/migrate.ts
7982
+ import { join as join20 } from "path";
7983
+
7984
+ // src/effort/store/migrate.ts
7985
+ import { existsSync as existsSync8 } from "fs";
7986
+ import { Database as Database3 } from "bun:sqlite";
7987
+ var LIVE2 = "live";
7988
+ function asidePaths2(path, first, stamp) {
7989
+ return { parallelPath: `${path}.migrate-${stamp}`, backupPath: `${path}.before-${first}-${stamp}.bak` };
7990
+ }
7991
+ function liveLoop(path, options) {
7992
+ const root = storeProjectRoot(path);
7993
+ if (root === null)
7994
+ return;
7995
+ let records;
7996
+ try {
7997
+ records = readSessions(root, { isAlive: options.isAlive ?? isPidAlive });
7998
+ } catch (error) {
7999
+ throw new RebuildRefusal(`the loop's run records cannot be read, so a live loop on ${path} cannot be ruled out` + ` (${messageOf(error)}). Nothing was migrated.`);
8000
+ }
8001
+ return records.find((record) => record.state === "running" || record.state === "paused");
8002
+ }
8003
+ function refuseLiveLoop2(path, breaking, options) {
8004
+ const loop = liveLoop(path, options);
8005
+ if (loop === undefined)
8006
+ return;
8007
+ const named = `loop ${loop.sessionId} (pid ${String(loop.pid)}, plan ${loop.planStub ?? loop.plan}) is running on this store`;
8008
+ const [first] = breaking;
8009
+ const opening = first === undefined ? `REFUSED \u2014 ${named}, and swapping the migrated store in under it would lose what it writes.` : `REFUSED \u2014 migration ${first} breaks older runtimes, and ${named}.`;
8010
+ throw new RebuildRefusal(`${opening} Nothing was migrated. Finish or stop that loop, then run it again.`);
8011
+ }
8012
+ function buildMigrated(path, parallelPath, migrations, stamp) {
8013
+ vacuumInto(path, parallelPath);
8014
+ const db = new Database3(parallelPath, { readwrite: true });
8015
+ try {
8016
+ const brought = bringForward(db, parallelPath, "write", "migrate", { migrations, ...stamp, builtAside: true });
8017
+ db.run(`ATTACH DATABASE ? AS ${LIVE2}`, [path]);
8018
+ const live = new Set(tableNames(db, LIVE2));
8019
+ const counted2 = checkedCounts(db, tableNames(db, "main").filter((table) => live.has(table)), LIVE2);
8020
+ db.run(`DETACH DATABASE ${LIVE2}`);
8021
+ refuseCorrupt(db);
8022
+ refuseUnusable(db, parallelPath, migrations);
8023
+ return { userVersion: brought.userVersion, counted: counted2 };
8024
+ } finally {
8025
+ db.close();
8026
+ }
8027
+ }
8028
+ function migrateStore(options) {
8029
+ const { path, dryRun, stamp } = options;
8030
+ guardTestProcess(path);
8031
+ const migrations = options.migrations ?? SQLITE_MIGRATIONS;
8032
+ const appliedBy = appliedByName(options.identity ?? readRuntimeIdentity());
8033
+ const nothing = { applying: [], breaking: [], unknown: [], userVersion: null, counted: [], backupPath: null };
8034
+ if (!existsSync8(path)) {
8035
+ return { path, status: "missing", storeVersion: null, logged: false, appliedBy, ...nothing };
8036
+ }
8037
+ refuseInFlight(path);
8038
+ const store = readOnlySchema(path);
8039
+ const found = { path, storeVersion: store.userVersion, logged: store.log !== null, appliedBy };
8040
+ const plan2 = planSchema(store, sqliteCatalogue(migrations), "write", "migrate");
8041
+ if (plan2.verdict === "refuse")
8042
+ throw new RebuildRefusal(plan2.message);
8043
+ const applying = writeNames(plan2);
8044
+ const planned = {
8045
+ applying,
8046
+ breaking: plan2.pending.filter(({ breaks }) => breaks.length > 0).map(({ id: id2 }) => id2),
8047
+ unknown: plan2.unknown
8048
+ };
8049
+ if (applying.length === 0)
8050
+ return { ...found, ...nothing, ...planned, status: "current" };
8051
+ if (!dryRun) {
8052
+ refuseUnownedDevelopmentWrite(path, applying, options);
8053
+ refuseLiveLoop2(path, planned.breaking, options);
8054
+ }
8055
+ const first = plan2.pending[0]?.id ?? ADOPTION_NAME;
8056
+ const { built, backupPath } = rebuildAside({
8057
+ path,
8058
+ ...asidePaths2(path, first, stamp),
8059
+ dryRun,
8060
+ build: (parallelPath) => buildMigrated(path, parallelPath, migrations, {
8061
+ appliedBy,
8062
+ now: options.now ?? (() => new Date)
8063
+ })
8064
+ });
8065
+ const status = dryRun ? "would-migrate" : "migrated";
8066
+ return { ...found, ...planned, ...built, status, backupPath };
8067
+ }
8068
+
8069
+ // src/commands/effort/migrate.ts
8070
+ var COMMAND_NAME7 = "rafa effort migrate";
8071
+ var MIGRATE_USAGE = "rafa effort migrate [--dry-run]";
8072
+ var DRY_RUN_FLAG3 = "dry-run";
8073
+ var REFUSED_EXIT2 = 1;
8074
+ function builtLines(result) {
8075
+ const rows = result.counted.reduce((sum, table) => sum + table.rows, 0);
8076
+ const breaking = result.breaking.length === 0 ? "Every one is additive." : `${result.breaking.join(", ")} breaks older runtimes: a rafa that does not know it refuses the store once it runs.`;
8077
+ return [
8078
+ `Applies ${result.applying.join(", ")} to ${result.path}, logged as applied by ${result.appliedBy}. ${breaking}`,
8079
+ `Row counts match in ${String(result.counted.length)} tables, ${String(rows)} rows; integrity_check ok;` + ` schema version ${String(result.userVersion)} after.`
8080
+ ];
8081
+ }
8082
+ function renderMigrate(result) {
8083
+ switch (result.status) {
8084
+ case "missing":
8085
+ return [`No effort store at ${result.path}: nothing to migrate.`];
8086
+ case "current":
8087
+ return [`\u2705 ${result.path} is current: no migration is pending. Nothing to migrate.`];
8088
+ case "would-migrate":
8089
+ return [
8090
+ ...builtLines(result),
8091
+ "\uD83D\uDD0D Dry run: the migrated store was built beside it, checked (row counts, integrity_check, this rafa's" + " schema plan) and deleted. Run `rafa effort migrate` to swap it in."
8092
+ ];
8093
+ case "migrated":
8094
+ return [
8095
+ ...builtLines(result),
8096
+ `\u2705 Migrated. The original is kept whole at ${String(result.backupPath)}; rename it back to undo.`
8097
+ ];
8098
+ }
8099
+ }
8100
+ function storePath(context, root) {
8101
+ try {
8102
+ return join20(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
8103
+ } catch (error) {
8104
+ throw new CommandExit(REFUSED_EXIT2, `\u274C ${COMMAND_NAME7}: ${messageOf(error)}`);
8105
+ }
8106
+ }
8107
+ function runMigrate(context, seams) {
8108
+ expectNoArgument(context.args, MIGRATE_USAGE);
8109
+ const dryRun = readSwitch(DRY_RUN_FLAG3, context.flags[DRY_RUN_FLAG3], `Usage: ${MIGRATE_USAGE}`);
8110
+ const path = storePath(context, requireProject(context, COMMAND_NAME7).root);
8111
+ const now = seams.now ?? (() => new Date);
8112
+ let result;
8113
+ try {
8114
+ result = migrateStore({
8115
+ path,
8116
+ dryRun,
8117
+ stamp: fileStamp(now()),
8118
+ now,
8119
+ env: context.env,
8120
+ ...seams.identity === undefined ? {} : { identity: seams.identity },
8121
+ ...seams.isAlive === undefined ? {} : { isAlive: seams.isAlive },
8122
+ ...seams.migrations === undefined ? {} : { migrations: seams.migrations }
8123
+ });
8124
+ } catch (error) {
8125
+ if (error instanceof RebuildRefusal || error instanceof DevelopmentBuildRefusedError) {
8126
+ throw new CommandExit(REFUSED_EXIT2, `\u274C ${COMMAND_NAME7}: ${error.message}`);
8127
+ }
8128
+ throw error;
8129
+ }
8130
+ if (context.outputMode === "json") {
8131
+ context.output.result(result);
8132
+ return;
8133
+ }
8134
+ for (const line of renderMigrate(result))
8135
+ context.output.info(line);
8136
+ }
8137
+ function createMigrateCommand(seams = {}) {
8138
+ const command = {
8139
+ name: "effort migrate",
8140
+ subject: "effort",
8141
+ action: "migrate",
8142
+ summary: "apply the effort store's pending migrations, one that breaks older runtimes included, keeping the original",
8143
+ description: "Brings `.rafa/effort/effort.sqlite`, or the store under `RAFA_EFFORT_DIR`, to the migrations this" + " rafa knows, including one that breaks older runtimes, which no ordinary open applies. It writes the" + " store to `effort.sqlite.migrate-<stamp>` beside it with `VACUUM INTO`, applies the pending migrations" + " there with log rows naming this rafa, checks the row count of every table against the store," + " SQLite's integrity_check and the schema plan, then renames the original to" + " `effort.sqlite.before-<id>-<stamp>.bak`, whole, and moves the migrated file into its place. A store" + " with nothing pending, or no store, is left alone. It refuses, changing nothing, while a loop session" + " under the project is running or paused, from a development build over any store but a copy under" + " `RAFA_EFFORT_DIR`, while a journal beside the store shows a write in flight, when a row count" + " changes, and on any refusal of the schema plan, whose text ends with the next safe step. With" + " `--output=json` the outcome is the data of the terminal result event. Starts no session.",
8144
+ args: [],
8145
+ flags: [
8146
+ {
8147
+ name: DRY_RUN_FLAG3,
8148
+ description: "Build and check the migrated store, print what it applies, then delete it. The live store" + " is only read, so this runs beside a live loop and from a development build, and can be repeated.",
8149
+ type: "boolean"
8150
+ }
8151
+ ],
8152
+ examples: [
8153
+ {
8154
+ cmd: "rafa effort migrate --dry-run",
8155
+ note: "Prints what the migration applies and how it checked, changing nothing."
8156
+ },
8157
+ {
8158
+ cmd: "rafa effort migrate",
8159
+ note: "Swaps the migrated store in and prints where the original was kept."
8160
+ }
8161
+ ],
8162
+ outputs: ["text", "json"],
8163
+ run: async (context) => {
8164
+ runMigrate(context, seams);
8165
+ await Promise.resolve();
8166
+ }
8167
+ };
8168
+ return Object.freeze(command);
8169
+ }
8170
+ var migrate_default = createMigrateCommand();
8171
+
7621
8172
  // src/commands/effort/report.ts
7622
8173
  var report_default = wrapPhaseZeroCommand({
7623
8174
  name: "effort report",
@@ -7671,6 +8222,113 @@ var report_default = wrapPhaseZeroCommand({
7671
8222
  outputs: ["text", "json"]
7672
8223
  }, report);
7673
8224
 
8225
+ // src/commands/effort/schema.ts
8226
+ import { join as join21 } from "path";
8227
+ var COMMAND_NAME8 = "rafa effort schema";
8228
+ var SCHEMA_USAGE = "rafa effort schema [--check]";
8229
+ var CHECK_FLAG = "check";
8230
+ var REFUSED_EXIT3 = 1;
8231
+ var FAILED_EXIT2 = 2;
8232
+ function listLine(name2, items) {
8233
+ const shown = items.length === 0 ? "none" : items.join(", ");
8234
+ return `${name2} (${String(items.length)}): ${shown}`;
8235
+ }
8236
+ function gateLine(report2) {
8237
+ const held = report2.userVersion ?? 0;
8238
+ const now = report2.logged ? `${String(held)} (${gateMeaning(held)})` : `${String(held)} (a count of legacy migrations: no log yet)`;
8239
+ const next = report2.gate === null || report2.gate === report2.userVersion ? "" : `; ${String(report2.gate)} (${gateMeaning(report2.gate)}) once the next open brings it forward`;
8240
+ return `Gate (user_version): ${now}${next}`;
8241
+ }
8242
+ function verdictLine(report2) {
8243
+ switch (report2.status) {
8244
+ case "absent":
8245
+ return "No store yet: the first write creates it at this rafa's migrations.";
8246
+ case "current":
8247
+ return "\u2705 Current: this rafa reads and writes the store as it is.";
8248
+ case "behind":
8249
+ return "\u2705 Behind: the next command that opens the store brings it forward in one transaction.";
8250
+ default:
8251
+ return `\u274C Refused (${report2.status}): ${String(report2.refusal)}`;
8252
+ }
8253
+ }
8254
+ function renderSchema(report2) {
8255
+ if (!report2.exists)
8256
+ return [`Effort store: ${report2.path}`, verdictLine(report2), `Next safe step: ${report2.nextStep}`];
8257
+ const log = report2.logged ? "Migration log: present." : "Migration log: none; a release before the log wrote this store, and the next open adopts it.";
8258
+ return [
8259
+ `Effort store: ${report2.path}`,
8260
+ log,
8261
+ gateLine(report2),
8262
+ listLine("Applied", report2.applied.map(({ id: id2, appliedBy }) => appliedBy === null ? id2 : `${id2} (${appliedBy})`)),
8263
+ listLine("Pending", report2.pending),
8264
+ listLine("Unknown", report2.unknown.map(({ id: id2, breaks, appliedBy, appliedAt }) => `${id2} (breaks ${breaks.length === 0 ? "nothing" : breaks.join(" and ")}; applied by ${appliedBy} on ${appliedAt})`)),
8265
+ listLine("Edited", report2.edited.map(({ id: id2, recorded, expected }) => `${id2} (store ${recorded}, this rafa ${expected})`)),
8266
+ verdictLine(report2),
8267
+ `Next safe step: ${report2.nextStep}`
8268
+ ];
8269
+ }
8270
+ function storePath2(context, root) {
8271
+ try {
8272
+ return join21(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
8273
+ } catch (error) {
8274
+ throw new CommandExit(REFUSED_EXIT3, `\u274C ${COMMAND_NAME8}: ${messageOf(error)}`);
8275
+ }
8276
+ }
8277
+ function runSchema(context, seams) {
8278
+ expectNoArgument(context.args, SCHEMA_USAGE);
8279
+ const check = readSwitch(CHECK_FLAG, context.flags[CHECK_FLAG], `Usage: ${SCHEMA_USAGE}`);
8280
+ const path = storePath2(context, requireProject(context, COMMAND_NAME8).root);
8281
+ let report2;
8282
+ try {
8283
+ report2 = readSchemaReport(path, seams);
8284
+ } catch (error) {
8285
+ throw new CommandExit(FAILED_EXIT2, `\u274C ${COMMAND_NAME8}: ${path} cannot be read as an effort store: ${messageOf(error)}`);
8286
+ }
8287
+ if (context.outputMode !== "json") {
8288
+ for (const line of renderSchema(report2))
8289
+ context.output.info(line);
8290
+ }
8291
+ if (check && report2.refused) {
8292
+ throw new CommandExit(REFUSED_EXIT3, `\u274C ${COMMAND_NAME8} --check: this rafa refuses ${path} (${report2.status}). Next safe step: ${report2.nextStep}`);
8293
+ }
8294
+ if (context.outputMode === "json")
8295
+ context.output.result(report2);
8296
+ }
8297
+ function createSchemaCommand(seams = {}) {
8298
+ const command = {
8299
+ name: "effort schema",
8300
+ subject: "effort",
8301
+ action: "schema",
8302
+ summary: "say whether this rafa can read and write the effort store, and what to run next when it cannot",
8303
+ description: "Opens the SQLite effort store read-only, the one under `RAFA_EFFORT_DIR` when it is set, and" + " migrates nothing. Prints the migrations it holds that this rafa knows (applied), the ones it lacks" + " (pending), the ones this rafa does not know with what they break (unknown), and the ones recorded under" + " another checksum (edited), then the legacy gate `user_version` holds and whether this rafa would use" + " the store or refuse it, and why. It ends with `Next safe step: <command>`, the command the matching" + " refusal names, or `none`. With `--check` it exits 1 when this rafa would refuse to read or to write" + " the store. Exit code 2 when the file cannot be read as a store. With `--output=json` the report, with" + " its `nextStep`, is the data of the terminal result event. Starts no session.",
8304
+ args: [],
8305
+ flags: [
8306
+ {
8307
+ name: CHECK_FLAG,
8308
+ description: "Exit 1 when this rafa would refuse to read or to write the store; exit 0 otherwise.",
8309
+ type: "boolean"
8310
+ }
8311
+ ],
8312
+ examples: [
8313
+ {
8314
+ cmd: "rafa effort schema",
8315
+ note: "Prints the store's migrations, the gate and the verdict, ending with the next safe step."
8316
+ },
8317
+ {
8318
+ cmd: "rafa effort schema --check",
8319
+ note: "The same report, exiting 1 when this rafa would refuse the store."
8320
+ }
8321
+ ],
8322
+ outputs: ["text", "json"],
8323
+ run: async (context) => {
8324
+ runSchema(context, seams);
8325
+ await Promise.resolve();
8326
+ }
8327
+ };
8328
+ return Object.freeze(command);
8329
+ }
8330
+ var schema_default = createSchemaCommand();
8331
+
7674
8332
  // src/board/roadmap-tick.ts
7675
8333
  var PREFIX2 = "board roadmap";
7676
8334
  var REPO_PATH = "{owner}/{repo}";
@@ -8037,7 +8695,7 @@ async function applyHorizonChange(board, reading, reason) {
8037
8695
  }
8038
8696
 
8039
8697
  // src/commands/epic/horizon-change.ts
8040
- var TO_FLAG = "to";
8698
+ var TO_FLAG2 = "to";
8041
8699
  var ISSUE_NUMBER2 = /^[1-9]\d*$/u;
8042
8700
  var NO_ANSWERS = ["n", "no"];
8043
8701
  var DEFER_ACTION = Object.freeze({
@@ -8063,9 +8721,9 @@ function readHorizonLine(context, action) {
8063
8721
  if (!ISSUE_NUMBER2.test(word)) {
8064
8722
  throw lineRefusal(`"${word}" is no epic number, which is a whole number from 1`, action.usage);
8065
8723
  }
8066
- const to = readChoiceFlag(flags, TO_FLAG, action.targets, action.usage);
8724
+ const to = readChoiceFlag(flags, TO_FLAG2, action.targets, action.usage);
8067
8725
  if (to === undefined)
8068
- throw lineRefusal(`--${TO_FLAG} is required: --${TO_FLAG}=<horizon>`, action.usage);
8726
+ throw lineRefusal(`--${TO_FLAG2} is required: --${TO_FLAG2}=<horizon>`, action.usage);
8069
8727
  const reason = readTextFlag(flags, REASON_FLAG, action.usage) ?? null;
8070
8728
  return Object.freeze({ epic: Number(word), to, reason });
8071
8729
  }
@@ -8078,7 +8736,7 @@ function wrongDirection(action, change) {
8078
8736
  return null;
8079
8737
  const other = action.name === "defer" ? "promote" : "defer";
8080
8738
  const verb = later ? "a deferral" : "a promotion";
8081
- return new CommandExit(EPIC_HORIZON_REFUSAL_EXIT, `\u274C Epic ${ref2(change.epic)} stands on ${change.from}, so ${change.from} \u2192 ${change.to} is ${verb};` + ` run rafa epic ${other} ${String(change.epic)} --${TO_FLAG}=${change.to}. Nothing was changed`);
8739
+ return new CommandExit(EPIC_HORIZON_REFUSAL_EXIT, `\u274C Epic ${ref2(change.epic)} stands on ${change.from}, so ${change.from} \u2192 ${change.to} is ${verb};` + ` run rafa epic ${other} ${String(change.epic)} --${TO_FLAG2}=${change.to}. Nothing was changed`);
8082
8740
  }
8083
8741
  function workPhrase(work) {
8084
8742
  return [
@@ -8265,9 +8923,9 @@ function readMoveLine(context) {
8265
8923
  throw lineRefusal(`Expected one issue number, got ${got}`, USAGE8);
8266
8924
  }
8267
8925
  const issue = issueNumber(word, "issue number");
8268
- const target = readTextFlag(flags, TO_FLAG, USAGE8);
8926
+ const target = readTextFlag(flags, TO_FLAG2, USAGE8);
8269
8927
  if (target === undefined)
8270
- throw lineRefusal(`--${TO_FLAG} is required: --${TO_FLAG}=<epic>`, USAGE8);
8928
+ throw lineRefusal(`--${TO_FLAG2} is required: --${TO_FLAG2}=<epic>`, USAGE8);
8271
8929
  const to = issueNumber(target, "epic number");
8272
8930
  const reason = readTextFlag(flags, REASON_FLAG, USAGE8) ?? null;
8273
8931
  return Object.freeze({ issue, to, reason });
@@ -8526,7 +9184,7 @@ function createEpicMoveCommand(seams = {}) {
8526
9184
  ],
8527
9185
  flags: [
8528
9186
  {
8529
- name: TO_FLAG,
9187
+ name: TO_FLAG2,
8530
9188
  description: "The number of the open epic it moves to. Required.",
8531
9189
  type: "string",
8532
9190
  required: true
@@ -8955,11 +9613,11 @@ var cancel_default = createEpicCancelCommand();
8955
9613
 
8956
9614
  // src/commands/epic/close.ts
8957
9615
  import { randomUUID as randomUUID3 } from "crypto";
8958
- import { join as join20 } from "path";
9616
+ import { join as join25 } from "path";
8959
9617
 
8960
9618
  // src/board/epic-template.ts
8961
- import { existsSync as existsSync6, readFileSync as readFileSync8 } from "fs";
8962
- import { dirname as dirname3, join as join17 } from "path";
9619
+ import { existsSync as existsSync9, readFileSync as readFileSync8 } from "fs";
9620
+ import { dirname as dirname3, join as join22 } from "path";
8963
9621
  import { fileURLToPath as fileURLToPath2 } from "url";
8964
9622
  var MODULE_DIR2 = dirname3(fileURLToPath2(import.meta.url));
8965
9623
  var EPIC_TEMPLATE_FILE = "epic.md";
@@ -8968,11 +9626,11 @@ var CRITERIA_PLACEHOLDERS = Object.freeze([
8968
9626
  ]);
8969
9627
  var PLACEHOLDER_REASON = "the line is still the epic template's placeholder, so there is nothing to check";
8970
9628
  function epicTemplateSource(moduleDir = MODULE_DIR2) {
8971
- return join17(moduleDir, TEMPLATES_DIRNAME, EPIC_TEMPLATE_FILE);
9629
+ return join22(moduleDir, TEMPLATES_DIRNAME, EPIC_TEMPLATE_FILE);
8972
9630
  }
8973
9631
  function readEpicTemplate(moduleDir = MODULE_DIR2) {
8974
9632
  const source = epicTemplateSource(moduleDir);
8975
- if (!existsSync6(source)) {
9633
+ if (!existsSync9(source)) {
8976
9634
  throw new Error(`epic template: the epic issue template is missing: no file at ${source}`);
8977
9635
  }
8978
9636
  return readFileSync8(source, "utf8");
@@ -9066,8 +9724,8 @@ function renderEpicCost(cost, estimate) {
9066
9724
  }
9067
9725
 
9068
9726
  // src/epic/verify-plan.ts
9069
- import { existsSync as existsSync7, readFileSync as readFileSync9 } from "fs";
9070
- import { dirname as dirname4, join as join18 } from "path";
9727
+ import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
9728
+ import { dirname as dirname4, join as join23 } from "path";
9071
9729
  import { fileURLToPath as fileURLToPath3 } from "url";
9072
9730
  var MODULE_DIR3 = dirname4(fileURLToPath3(import.meta.url));
9073
9731
  var VERIFY_PROMPT_FILE = "epic-verify-prompt.md";
@@ -9124,11 +9782,11 @@ function criteriaToAsk(criteria) {
9124
9782
  return Object.freeze(criteria.filter((criterion) => !criterion.placeholder));
9125
9783
  }
9126
9784
  function verifyPromptCandidates(moduleDir = MODULE_DIR3) {
9127
- return [join18(moduleDir, VERIFY_PROMPT_FILE), join18(dirname4(moduleDir), VERIFY_PROMPT_FILE)];
9785
+ return [join23(moduleDir, VERIFY_PROMPT_FILE), join23(dirname4(moduleDir), VERIFY_PROMPT_FILE)];
9128
9786
  }
9129
9787
  function readVerifyPrompt(moduleDir = MODULE_DIR3) {
9130
9788
  const candidates = verifyPromptCandidates(moduleDir);
9131
- const found = candidates.find((candidate) => existsSync7(candidate));
9789
+ const found = candidates.find((candidate) => existsSync10(candidate));
9132
9790
  if (found === undefined) {
9133
9791
  throw new Error(`epic verify prompt: the verification prompt is missing: no file at ${candidates.join(" or ")}`);
9134
9792
  }
@@ -9269,7 +9927,7 @@ function parseVerifyPlan(output, criteria) {
9269
9927
 
9270
9928
  // src/epic/verify-run.ts
9271
9929
  import { randomUUID as randomUUID2 } from "crypto";
9272
- import { join as join19 } from "path";
9930
+ import { join as join24 } from "path";
9273
9931
  var CHECK_TOOLS = ["Read", "Grep", "Glob", "Bash"];
9274
9932
  var DEFAULT_VERIFY_REMOTE = "origin";
9275
9933
  var CHECK_PROMPT_PREFIX = "# Epic acceptance check instructions";
@@ -9278,7 +9936,7 @@ var CHECK_RESULTS = ["pass", "fail"];
9278
9936
  var WORKTREE_PREFIX2 = "epic-";
9279
9937
  var VERDICT_KIND = VERDICT_BLOCK_FENCE.slice("rafa:".length);
9280
9938
  function verifyWorktreePath(home, epic) {
9281
- return join19(home, WORKTREES_SUBDIR, `${WORKTREE_PREFIX2}${String(epic)}`);
9939
+ return join24(home, WORKTREES_SUBDIR, `${WORKTREE_PREFIX2}${String(epic)}`);
9282
9940
  }
9283
9941
  function fetchStep(remote) {
9284
9942
  return { id: "fetch", args: ["fetch", remote] };
@@ -9538,7 +10196,7 @@ async function planVerification(input) {
9538
10196
  return Object.freeze({ status: "planned", sessionId, verdicts: reading.verdicts, issues: reading.issues });
9539
10197
  }
9540
10198
  function closeTriageFile(root, epic) {
9541
- return join20(root, ".rafa", `epic-${String(epic)}-close`);
10199
+ return join25(root, ".rafa", `epic-${String(epic)}-close`);
9542
10200
  }
9543
10201
  function failedCheckArtifact(epic, criterion) {
9544
10202
  return `epic ${ref5(epic)} acceptance criterion: ${oneLine(criterion.text)}`;
@@ -9814,7 +10472,7 @@ function createEpicDeferCommand(seams = {}) {
9814
10472
  ],
9815
10473
  flags: [
9816
10474
  {
9817
- name: TO_FLAG,
10475
+ name: TO_FLAG2,
9818
10476
  description: `The horizon it moves to: one of ${DEFER_ACTION.targets.join(", ")}. Required.`,
9819
10477
  type: "string",
9820
10478
  required: true
@@ -10050,7 +10708,7 @@ function createEpicPromoteCommand(seams = {}) {
10050
10708
  ],
10051
10709
  flags: [
10052
10710
  {
10053
- name: TO_FLAG,
10711
+ name: TO_FLAG2,
10054
10712
  description: `The horizon it moves to: one of ${PROMOTE_ACTION.targets.join(", ")}. Required.`,
10055
10713
  type: "string",
10056
10714
  required: true
@@ -10075,17 +10733,17 @@ function createEpicPromoteCommand(seams = {}) {
10075
10733
  var promote_default = createEpicPromoteCommand();
10076
10734
 
10077
10735
  // src/commands/init.ts
10078
- import { existsSync as existsSync10, readFileSync as readFileSync14 } from "fs";
10736
+ import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
10079
10737
  import { homedir } from "os";
10080
- import { join as join25, relative as relative6, sep as sep4 } from "path";
10738
+ import { join as join30, relative as relative6, sep as sep4 } from "path";
10081
10739
 
10082
10740
  // src/agents/vendorable.ts
10083
- import { readdirSync, readFileSync as readFileSync10 } from "fs";
10084
- import { isAbsolute as isAbsolute5, join as join21 } from "path";
10741
+ import { readdirSync as readdirSync2, readFileSync as readFileSync10 } from "fs";
10742
+ import { isAbsolute as isAbsolute5, join as join26 } from "path";
10085
10743
  function planFiles(dir) {
10086
10744
  let entries;
10087
10745
  try {
10088
- entries = readdirSync(dir, { withFileTypes: true });
10746
+ entries = readdirSync2(dir, { withFileTypes: true });
10089
10747
  } catch {
10090
10748
  return [];
10091
10749
  }
@@ -10103,7 +10761,7 @@ function rafaHolds(roster, name2) {
10103
10761
  return item?.holders.some((holder) => holder.source === "rafa") === true;
10104
10762
  }
10105
10763
  function vendorableAgents(scan) {
10106
- const dir = isAbsolute5(scan.planDir) ? scan.planDir : join21(scan.repoRoot, scan.planDir);
10764
+ const dir = isAbsolute5(scan.planDir) ? scan.planDir : join26(scan.repoRoot, scan.planDir);
10107
10765
  const roots = {
10108
10766
  repoRoot: scan.repoRoot,
10109
10767
  home: scan.home,
@@ -10112,7 +10770,7 @@ function vendorableAgents(scan) {
10112
10770
  const roster = resolveAgentRoster(roots, scan.settings);
10113
10771
  const found = [];
10114
10772
  for (const file of planFiles(dir)) {
10115
- const path = join21(dir, file);
10773
+ const path = join26(dir, file);
10116
10774
  const markdown = readText2(path);
10117
10775
  if (markdown === null)
10118
10776
  continue;
@@ -10138,12 +10796,13 @@ function vendorableAgentWarnings(agents, root) {
10138
10796
 
10139
10797
  // src/project/gitignore.ts
10140
10798
  import { createHash } from "crypto";
10141
- import { existsSync as existsSync8, mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync2 } from "fs";
10142
- import { dirname as dirname5, isAbsolute as isAbsolute6, join as join22 } from "path";
10799
+ import { existsSync as existsSync11, mkdirSync as mkdirSync4, readFileSync as readFileSync11, writeFileSync as writeFileSync2 } from "fs";
10800
+ import { dirname as dirname5, isAbsolute as isAbsolute6, join as join27 } from "path";
10143
10801
  var GITIGNORE_FILE = ".gitignore";
10144
10802
  var DIGEST_NAME = "tracking.digest";
10145
10803
  var PRIVATE_TRIAGE = "triage/private";
10146
- var TRACKING_DIGEST_FILE = join22(SCOPE_DIR, DIGEST_NAME);
10804
+ var CACHE_DIR = "cache";
10805
+ var TRACKING_DIGEST_FILE = join27(SCOPE_DIR, DIGEST_NAME);
10147
10806
  var BLOCK_BEGIN = "# >>> rafa tracking: rafa rewrites the lines up to the end marker";
10148
10807
  var BLOCK_END = "# <<< rafa tracking";
10149
10808
  var SUB_PATHS = [
@@ -10163,7 +10822,8 @@ function trackingEntry(flags) {
10163
10822
  `!/${SCOPE_DIR}/`,
10164
10823
  `${SCOPE_DIR}/${PRIVATE_TRIAGE}/`,
10165
10824
  `${SCOPE_DIR}/${DIGEST_NAME}`,
10166
- `${SCOPE_DIR}/${POSITION_FILE}`
10825
+ `${SCOPE_DIR}/${POSITION_FILE}`,
10826
+ `${SCOPE_DIR}/${CACHE_DIR}/`
10167
10827
  ];
10168
10828
  }
10169
10829
  const tracked = SUB_PATHS.filter((sub) => flags[sub.flag]).map((sub) => `!${SCOPE_DIR}/${sub.dir}/`);
@@ -10230,13 +10890,13 @@ function attempt(file, failure3, act) {
10230
10890
  }
10231
10891
  }
10232
10892
  function readIfPresent2(file) {
10233
- if (!existsSync8(file))
10893
+ if (!existsSync11(file))
10234
10894
  return null;
10235
10895
  return attempt(file, "cannot be read", () => readFileSync11(file, "utf8"));
10236
10896
  }
10237
10897
  function writeTrackingGitignore(root, flags) {
10238
10898
  requireAbsolute(root);
10239
- const file = join22(root, GITIGNORE_FILE);
10899
+ const file = join27(root, GITIGNORE_FILE);
10240
10900
  const text = readIfPresent2(file);
10241
10901
  const next = withTrackingBlock(text, flags, file);
10242
10902
  if (next === text)
@@ -10258,7 +10918,7 @@ function trackingDigest(flags) {
10258
10918
  }
10259
10919
  function exposureNotice(root) {
10260
10920
  return [
10261
- `tracking.all is true, so ${join22(root, GITIGNORE_FILE)} no longer ignores ${SCOPE_DIR}/` + " and a commit can carry what it holds, including:",
10921
+ `tracking.all is true, so ${join27(root, GITIGNORE_FILE)} no longer ignores ${SCOPE_DIR}/` + " and a commit can carry what it holds, including:",
10262
10922
  " - unpatched issues described in specs and plans",
10263
10923
  " - effort rows with model names and token counts",
10264
10924
  " - instincts that may quote error output",
@@ -10271,7 +10931,7 @@ function printWarning(message) {
10271
10931
  }
10272
10932
  function noticeTrackingChange(root, flags, print2 = printWarning) {
10273
10933
  requireAbsolute(root);
10274
- const digestFile = join22(root, TRACKING_DIGEST_FILE);
10934
+ const digestFile = join27(root, TRACKING_DIGEST_FILE);
10275
10935
  const recorded = `${trackingDigest(flags)}
10276
10936
  `;
10277
10937
  if (readIfPresent2(digestFile) === recorded) {
@@ -10280,7 +10940,7 @@ function noticeTrackingChange(root, flags, print2 = printWarning) {
10280
10940
  if (flags.trackingAll)
10281
10941
  print2(exposureNotice(root));
10282
10942
  attempt(digestFile, "cannot be written", () => {
10283
- mkdirSync3(dirname5(digestFile), { recursive: true });
10943
+ mkdirSync4(dirname5(digestFile), { recursive: true });
10284
10944
  writeFileSync2(digestFile, recorded);
10285
10945
  });
10286
10946
  return { digestFile, changed: true, printed: flags.trackingAll };
@@ -10291,12 +10951,12 @@ function applyTracking(root, flags, print2 = printWarning) {
10291
10951
  }
10292
10952
 
10293
10953
  // src/project/root-choice.ts
10294
- import { resolve as resolve7 } from "path";
10954
+ import { resolve as resolve9 } from "path";
10295
10955
 
10296
10956
  // src/project/roots.ts
10297
10957
  import { spawnSync } from "child_process";
10298
- import { accessSync, constants, readFileSync as readFileSync12, statSync as statSync7 } from "fs";
10299
- import { isAbsolute as isAbsolute7, join as join23, resolve as resolve6 } from "path";
10958
+ import { accessSync, constants as constants2, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
10959
+ import { isAbsolute as isAbsolute7, join as join28, resolve as resolve8 } from "path";
10300
10960
  var REFUSED_ROOTS = Object.freeze([
10301
10961
  { path: "/", why: "is the filesystem root" },
10302
10962
  { path: "/var", why: "holds system state" },
@@ -10317,8 +10977,8 @@ var NOT_A_REPOSITORY = "fatal: not a git repository (or any ";
10317
10977
  var DISK_ROOTS_FILE_SYSTEM = Object.freeze({
10318
10978
  ...DISK_FILE_SYSTEM,
10319
10979
  readFile: (path) => readFileSync12(path, "utf8"),
10320
- isDirectory: (path) => statSync7(path).isDirectory(),
10321
- assertWritable: (path) => accessSync(path, constants.W_OK)
10980
+ isDirectory: (path) => statSync8(path).isDirectory(),
10981
+ assertWritable: (path) => accessSync(path, constants2.W_OK)
10322
10982
  });
10323
10983
 
10324
10984
  class RootsError extends Error {
@@ -10359,7 +11019,7 @@ function realOrSpelled(path, fs) {
10359
11019
  try {
10360
11020
  return fs.realpath(path);
10361
11021
  } catch {
10362
- return resolve6(path);
11022
+ return resolve8(path);
10363
11023
  }
10364
11024
  }
10365
11025
  function namedRoots(home, fs) {
@@ -10401,7 +11061,7 @@ function writeRefusal(given, real, fs) {
10401
11061
  }
10402
11062
  function refusalOf(given, named2, fs) {
10403
11063
  const resolved = resolveReal(given, fs);
10404
- const byName2 = namedRefusal(given, resolved.real ?? resolve6(given), named2);
11064
+ const byName2 = namedRefusal(given, resolved.real ?? resolve8(given), named2);
10405
11065
  if (byName2 !== null)
10406
11066
  return byName2;
10407
11067
  if (resolved.real === null) {
@@ -10429,8 +11089,8 @@ function readManifest2(file, fs) {
10429
11089
  }
10430
11090
  }
10431
11091
  function readDir(dir, fs) {
10432
- const manifest = readManifest2(join23(dir, PACKAGE_JSON), fs);
10433
- const files = MONOREPO_MARKER_FILES.filter((name2) => fs.exists(join23(dir, name2)));
11092
+ const manifest = readManifest2(join28(dir, PACKAGE_JSON), fs);
11093
+ const files = MONOREPO_MARKER_FILES.filter((name2) => fs.exists(join28(dir, name2)));
10434
11094
  const markers = manifest.marks ? [PACKAGE_JSON, ...files] : files;
10435
11095
  return { ...manifest, dir, markers };
10436
11096
  }
@@ -10500,7 +11160,7 @@ function firstCandidate(found) {
10500
11160
  return first.refusal === null ? chosen(first.path, first.source) : problem(first.refusal.reason);
10501
11161
  }
10502
11162
  function namedRoot(typed, start2, source, seams) {
10503
- const path = resolve7(start2, typed);
11163
+ const path = resolve9(start2, typed);
10504
11164
  const refusal9 = rootRefusal(path, seams);
10505
11165
  if (refusal9 !== null)
10506
11166
  return problem(refusal9.reason);
@@ -10541,12 +11201,14 @@ async function promptForRoot(found, prompter, seams) {
10541
11201
  }
10542
11202
 
10543
11203
  // src/project/scaffold.ts
10544
- import { existsSync as existsSync9, lstatSync, mkdirSync as mkdirSync4, statSync as statSync8, writeFileSync as writeFileSync3 } from "fs";
10545
- import { join as join24 } from "path";
11204
+ import { existsSync as existsSync12, lstatSync, mkdirSync as mkdirSync5, statSync as statSync9, writeFileSync as writeFileSync3 } from "fs";
11205
+ import { join as join29 } from "path";
10546
11206
  var PROJECT_TREE = Object.freeze(["specs", "plans", "runs", "effort", "instincts"]);
10547
11207
  var USER_TREE = Object.freeze(["instincts"]);
10548
11208
  var CONFIG_SETTINGS_LINES = Object.freeze([
10549
11209
  `# store: ${CONFIG_DEFAULTS.store} # sqlite | ndjson`,
11210
+ "# effort:",
11211
+ `# busyTimeoutMs: ${String(CONFIG_DEFAULTS.effortBusyTimeoutMs)} # 1..60000 ms; how long a store open waits for another process's lock`,
10550
11212
  "# plan:",
10551
11213
  `# inject: ${CONFIG_DEFAULTS.inject} # full | stage | task`,
10552
11214
  `# dir: ${CONFIG_DEFAULTS.planDir}`,
@@ -10643,18 +11305,18 @@ class ScaffoldError extends Error {
10643
11305
  }
10644
11306
  function layoutOf2(base, tree) {
10645
11307
  const scope = scopeAt(base);
10646
- return { dirs: [scope.dir, ...tree.map((name2) => join24(scope.dir, name2))], configFile: configFilePath(base) };
11308
+ return { dirs: [scope.dir, ...tree.map((name2) => join29(scope.dir, name2))], configFile: configFilePath(base) };
10647
11309
  }
10648
11310
  function occupied(path) {
10649
11311
  return lstatSync(path, { throwIfNoEntry: false }) !== undefined;
10650
11312
  }
10651
11313
  function isDirectory2(path) {
10652
- return statSync8(path, { throwIfNoEntry: false })?.isDirectory() === true;
11314
+ return statSync9(path, { throwIfNoEntry: false })?.isDirectory() === true;
10653
11315
  }
10654
11316
  function directoryConflict(dir) {
10655
11317
  if (!occupied(dir) || isDirectory2(dir))
10656
11318
  return null;
10657
- return existsSync9(dir) ? `${dir} is not a directory` : `${dir} is a link to nothing`;
11319
+ return existsSync12(dir) ? `${dir} is not a directory` : `${dir} is a link to nothing`;
10658
11320
  }
10659
11321
  function layoutConflicts(layout) {
10660
11322
  const [scopeDir, ...tree] = layout.dirs;
@@ -10662,7 +11324,7 @@ function layoutConflicts(layout) {
10662
11324
  if (opening !== null)
10663
11325
  return [opening];
10664
11326
  const dirs = tree.flatMap((dir) => directoryConflict(dir) ?? []);
10665
- const file = occupied(layout.configFile) && !existsSync9(layout.configFile) ? [`${layout.configFile} is a link to nothing`] : [];
11327
+ const file = occupied(layout.configFile) && !existsSync12(layout.configFile) ? [`${layout.configFile} is a link to nothing`] : [];
10666
11328
  return [...dirs, ...file];
10667
11329
  }
10668
11330
  function scaffoldConflicts(root, home) {
@@ -10676,15 +11338,15 @@ function attempt2(path, failure3, act) {
10676
11338
  }
10677
11339
  }
10678
11340
  function ensureDirectory(path) {
10679
- if (existsSync9(path))
11341
+ if (existsSync12(path))
10680
11342
  return { path, kind: "directory", change: "unchanged" };
10681
11343
  attempt2(path, "cannot be created", () => {
10682
- mkdirSync4(path, { recursive: true });
11344
+ mkdirSync5(path, { recursive: true });
10683
11345
  });
10684
11346
  return { path, kind: "directory", change: "created" };
10685
11347
  }
10686
11348
  function ensureFile(path, text) {
10687
- if (existsSync9(path))
11349
+ if (existsSync12(path))
10688
11350
  return { path, kind: "file", change: "unchanged" };
10689
11351
  attempt2(path, "cannot be written", () => {
10690
11352
  writeFileSync3(path, text, { encoding: "utf8", flag: "wx" });
@@ -11000,9 +11662,9 @@ function resolvedConfig2(root, home, warn3) {
11000
11662
  }
11001
11663
  }
11002
11664
  function checkGitignore(root, flags) {
11003
- const file = join25(root, GITIGNORE_FILE);
11665
+ const file = join30(root, GITIGNORE_FILE);
11004
11666
  try {
11005
- const text = existsSync10(file) ? readFileSync14(file, "utf8") : null;
11667
+ const text = existsSync13(file) ? readFileSync14(file, "utf8") : null;
11006
11668
  withTrackingBlock(text, flags, file);
11007
11669
  } catch (error) {
11008
11670
  const message = error instanceof GitignoreError ? error.message : `rafa init: ${file} cannot be read (${messageOf(error)})`;
@@ -11033,8 +11695,8 @@ function initialise(root, start2, home, context, entry) {
11033
11695
  checkConflicts(root.path, home);
11034
11696
  const config = resolvedConfig2(root.path, home, warn3);
11035
11697
  checkGitignore(root.path, config);
11036
- const configExisted = existsSync10(configFilePath(root.path));
11037
- const digestExisted = existsSync10(join25(root.path, TRACKING_DIGEST_FILE));
11698
+ const configExisted = existsSync13(configFilePath(root.path));
11699
+ const digestExisted = existsSync13(join30(root.path, TRACKING_DIGEST_FILE));
11038
11700
  const project = writeProjectScope(root.path);
11039
11701
  const tracking = applyTracking(root.path, config, warn3);
11040
11702
  const writes = [...project, ...trackingWrites(tracking, digestExisted), ...writeUserScope(home)];
@@ -11212,7 +11874,7 @@ function createInitCommand(seams = DEFAULT_INIT_SEAMS) {
11212
11874
  var init_default = createInitCommand();
11213
11875
 
11214
11876
  // src/commands/check-report.ts
11215
- import { resolve as resolve8 } from "path";
11877
+ import { resolve as resolve10 } from "path";
11216
11878
  var DEFAULT_CHECK_SEAMS = Object.freeze({
11217
11879
  cwd: () => process.cwd()
11218
11880
  });
@@ -11228,7 +11890,7 @@ Usage: ${usage2}`);
11228
11890
  throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
11229
11891
  Usage: ${usage2}`);
11230
11892
  }
11231
- return resolve8(cwd(), dir);
11893
+ return resolve10(cwd(), dir);
11232
11894
  }
11233
11895
  function readFix(value, usage2) {
11234
11896
  if (value === undefined || value === false || value === "false")
@@ -11245,7 +11907,7 @@ function readProjectRoot(value, usage2, cwd) {
11245
11907
  throw new CommandExit(1, `\u274C --project needs a path, and the line gave it none
11246
11908
  Usage: ${usage2}`);
11247
11909
  }
11248
- return resolve8(cwd(), value);
11910
+ return resolve10(cwd(), value);
11249
11911
  }
11250
11912
  function reportMarker(report2) {
11251
11913
  if (report2.failed)
@@ -11743,7 +12405,7 @@ function createInstinctListCommand(seams = {}) {
11743
12405
  };
11744
12406
  return Object.freeze(command);
11745
12407
  }
11746
- var list_default5 = createInstinctListCommand();
12408
+ var list_default6 = createInstinctListCommand();
11747
12409
 
11748
12410
  // src/commands/instinct/show.ts
11749
12411
  var USAGE15 = "rafa instinct show <id>";
@@ -11881,8 +12543,8 @@ function createInstinctShowCommand() {
11881
12543
  var show_default3 = createInstinctShowCommand();
11882
12544
 
11883
12545
  // src/commands/issue/check.ts
11884
- import { readdirSync as readdirSync2, readFileSync as readFileSync15, statSync as statSync9 } from "fs";
11885
- import { join as join26, resolve as resolve9 } from "path";
12546
+ import { readdirSync as readdirSync3, readFileSync as readFileSync15, statSync as statSync10 } from "fs";
12547
+ import { join as join31, resolve as resolve11 } from "path";
11886
12548
  var CHECK_USAGE = "rafa issue check <n> [--stamp]";
11887
12549
  var STAMP_FLAG = "stamp";
11888
12550
  var ISSUE_NUMBER6 = /^[1-9]\d*$/u;
@@ -11906,7 +12568,7 @@ function missingCopyMessage(issue, specsDir) {
11906
12568
  function copyNames(dir, issue) {
11907
12569
  let names;
11908
12570
  try {
11909
- names = readdirSync2(dir);
12571
+ names = readdirSync3(dir);
11910
12572
  } catch (error) {
11911
12573
  if (error instanceof Error && "code" in error && error.code === "ENOENT")
11912
12574
  return [];
@@ -11914,22 +12576,22 @@ function copyNames(dir, issue) {
11914
12576
  }
11915
12577
  const prefix = `${boardId(issue)}-`;
11916
12578
  const notes = notesFileName(issue);
11917
- return names.filter((name2) => name2.startsWith(prefix) && name2.endsWith(SPEC_EXTENSION) && name2 !== notes).filter((name2) => name2.length > prefix.length + SPEC_EXTENSION.length).filter((name2) => statSync9(join26(dir, name2)).isFile()).sort();
12579
+ return names.filter((name2) => name2.startsWith(prefix) && name2.endsWith(SPEC_EXTENSION) && name2 !== notes).filter((name2) => name2.length > prefix.length + SPEC_EXTENSION.length).filter((name2) => statSync10(join31(dir, name2)).isFile()).sort();
11918
12580
  }
11919
12581
  function findSavedCopy(root, specsDir, issue) {
11920
- const names = copyNames(resolve9(root, specsDir), issue);
12582
+ const names = copyNames(resolve11(root, specsDir), issue);
11921
12583
  const [only] = names;
11922
12584
  if (only === undefined)
11923
12585
  throw new CommandExit(1, missingCopyMessage(issue, specsDir));
11924
12586
  if (names.length > 1) {
11925
12587
  throw new CommandExit(1, [
11926
12588
  `\u274C Issue #${String(issue)} has ${String(names.length)} saved copies under ${specsDir}; remove the stale one:`,
11927
- ...names.map((name2) => ` ${join26(specsDir, name2)}`)
12589
+ ...names.map((name2) => ` ${join31(specsDir, name2)}`)
11928
12590
  ].join(`
11929
12591
  `));
11930
12592
  }
11931
- const path = join26(specsDir, only);
11932
- return { path, absolute: resolve9(root, path) };
12593
+ const path = join31(specsDir, only);
12594
+ return { path, absolute: resolve11(root, path) };
11933
12595
  }
11934
12596
  function liveText(live) {
11935
12597
  return live.kind === "unreadable" ? "unreadable" : fingerprintText(live);
@@ -12198,279 +12860,11 @@ function createIssueCreateCommand(seams = DEFAULT_ISSUE_SEAMS) {
12198
12860
  }
12199
12861
  var create_default = createIssueCreateCommand();
12200
12862
 
12201
- // src/commands/issue/roadmap-check.ts
12202
- var EPIC_CHECK_EXIT = 1;
12203
- function checkHead(count2) {
12204
- return count2 === 1 ? "\u274C 1 epic's stored state disagrees with its computed one:" : `\u274C ${String(count2)} epics' stored states disagree with their computed ones:`;
12205
- }
12206
- function epicCheckFailure(epics) {
12207
- if (epics.unknown !== null)
12208
- return `\u274C Could not check the epics: ${epics.unknown}`;
12209
- const lines2 = epics.epics.flatMap((epic) => epic.disagreement === null ? [] : [` ${epic.disagreement}`]);
12210
- if (lines2.length === 0)
12211
- return null;
12212
- return [checkHead(lines2.length), ...lines2].join(`
12213
- `);
12214
- }
12215
-
12216
- // src/commands/issue/list.ts
12217
- var USAGE18 = "rafa issue list [--roadmap [--all] [--full] [--check]] [--state=<state>] [--type=<type>] [--module=<name>]" + " [--search=<text>] [--limit=<n>]";
12218
- var SWITCH_HINT = `Type it bare: ${USAGE18}`;
12219
- var LIMIT = /^[1-9]\d*$/;
12220
- function readLimit(flags) {
12221
- const value = readTextFlag(flags, "limit", USAGE18);
12222
- if (value === undefined)
12223
- return;
12224
- const limit = Number(value);
12225
- if (LIMIT.test(value) && Number.isSafeInteger(limit))
12226
- return limit;
12227
- throw lineRefusal(`--limit is "${value}", expected a positive whole number`, USAGE18);
12228
- }
12229
- function readIssueQuery(flags) {
12230
- const narrowed = {
12231
- state: readChoiceFlag(flags, "state", ISSUE_STATES, USAGE18),
12232
- type: readChoiceFlag(flags, "type", ISSUE_TYPES, USAGE18),
12233
- module: readNonBlankFlag(flags, "module", USAGE18),
12234
- text: readNonBlankFlag(flags, "search", USAGE18),
12235
- limit: readLimit(flags)
12236
- };
12237
- return Object.fromEntries(Object.entries(narrowed).filter(([, value]) => value !== undefined));
12238
- }
12239
- function readIssueListLine(flags) {
12240
- const roadmap = readSwitch("roadmap", flags["roadmap"], SWITCH_HINT);
12241
- const all = readSwitch("all", flags["all"], SWITCH_HINT);
12242
- const full = readSwitch("full", flags["full"], SWITCH_HINT);
12243
- const check = readSwitch("check", flags["check"], SWITCH_HINT);
12244
- if (roadmap && flags["state"] !== undefined) {
12245
- throw lineRefusal("--state cannot narrow --roadmap: the Roadmap is read off the GitHub board, which holds an issue open or" + " closed and so no state of the tracker", USAGE18);
12246
- }
12247
- if (all && !roadmap)
12248
- throw lineRefusal("--all keeps the ticked Roadmap lines, so it needs --roadmap", USAGE18);
12249
- if (full && !roadmap)
12250
- throw lineRefusal("--full prints the issues of each Roadmap epic, so it needs --roadmap", USAGE18);
12251
- if (check && !roadmap)
12252
- throw lineRefusal("--check weighs the epics the board read finds, so it needs --roadmap", USAGE18);
12253
- return { roadmap, all, full, check };
12254
- }
12255
- function renderIssueList(kind, issues) {
12256
- const head = `Tracker: ${kind}`;
12257
- if (issues.length === 0)
12258
- return [head, "No issues."];
12259
- const widest = (column) => Math.max(...issues.map((issue) => column(issue).length));
12260
- const idWidth = widest((issue) => issue.ref.externalId);
12261
- const stateWidth = widest((issue) => issue.state);
12262
- const typeWidth = widest((issue) => issue.type);
12263
- const rows = issues.map((issue) => [
12264
- issue.ref.externalId.padStart(idWidth),
12265
- issue.state.padEnd(stateWidth),
12266
- issue.type.padEnd(typeWidth),
12267
- issue.title
12268
- ].join(" "));
12269
- return [head, ...rows.map((row2) => ` ${row2}`)];
12270
- }
12271
- function matchesFilter(row2, filter) {
12272
- const { issue } = row2;
12273
- if (filter.type !== undefined && issue?.type !== filter.type)
12274
- return false;
12275
- if (filter.module !== undefined && issue?.module !== filter.module)
12276
- return false;
12277
- if (filter.text === undefined)
12278
- return true;
12279
- const text = issue === null ? row2.line.why : `${issue.title}
12280
- ${issue.body}`;
12281
- return text.toLowerCase().includes(filter.text.toLowerCase());
12282
- }
12283
- function narrowRoadmapRows(rows, filter) {
12284
- const kept = rows.filter((row2) => matchesFilter(row2, filter));
12285
- return Object.freeze(filter.limit === undefined ? kept : kept.slice(0, filter.limit));
12286
- }
12287
- function renderRoadmapList(listed2, width, full = false) {
12288
- if (listed2.epics !== undefined) {
12289
- return renderEpicTable({ ...listed2.epics, roadmap: listed2.roadmap, specs: listed2.rows }, width, full);
12290
- }
12291
- const head = `Roadmap: #${String(listed2.roadmap)}`;
12292
- if (listed2.rows.length === 0)
12293
- return [head, "No issues."];
12294
- return [head, ...renderRoadmapTable(listed2.rows, width)];
12295
- }
12296
- async function listRoadmap(context, seams, all, filter) {
12297
- const project = issueProject(context);
12298
- const warn3 = (message) => {
12299
- context.output.warn(message);
12300
- };
12301
- const config = issueSubjectConfig(project, warn3);
12302
- const gh = seams.gh ?? createGhRunner({ cwd: project.root });
12303
- const plans = plansDirAt(project.root, config.planDir);
12304
- const planNames = (seams.planNames ?? createPlanDirNames)(plans.path);
12305
- const refs2 = async (issues) => roadmapRefsCells(await readDoctorRefs({ root: project.root, specsDir: config.specsDir, gh, env: context.env, issues }, { refsVerifier: seams.refsVerifier }));
12306
- let read;
12307
- try {
12308
- read = await readRoadmapEpicRows({
12309
- configured: config.roadmapIssue,
12310
- listBoards: createGhBoardLister({ gh }),
12311
- search: createGhRoadmapSearch({ gh }),
12312
- issues: createGhSpecIssueReader({ gh }),
12313
- board: createGhBoardListing({ gh }),
12314
- git: seams.git ?? createGitRunner(project.root),
12315
- pullRequests: createGhOpenPullRequests({ gh }),
12316
- planNames,
12317
- refs: refs2,
12318
- all,
12319
- root: project.root
12320
- });
12321
- } catch (error) {
12322
- const code = error instanceof CommandExit ? error.exitCode : ROADMAP_REFUSAL_EXIT;
12323
- throw new CommandExit(code, `\u274C Could not read the roadmap: ${messageOf(error)}`);
12324
- }
12325
- for (const warning of read.warnings)
12326
- warn3(warning);
12327
- const listed2 = {
12328
- roadmap: read.roadmap,
12329
- all,
12330
- filter,
12331
- rows: narrowRoadmapRows(read.specs, filter),
12332
- warnings: read.warnings
12333
- };
12334
- if (!hasEpicLines(read) && read.unknown === null)
12335
- return Object.freeze({ result: Object.freeze(listed2), epics: read.epics });
12336
- const { groups, hidden, unknown, problems } = read;
12337
- const result = Object.freeze({ ...listed2, epics: Object.freeze({ groups, hidden, unknown, problems }) });
12338
- return Object.freeze({ result, epics: read.epics });
12339
- }
12340
- async function listIssues(context, seams) {
12341
- expectNoArgument(context.args, USAGE18);
12342
- const query = readIssueQuery(context.flags);
12343
- const { tracker, data } = await resolveIssueTracker(context, seams);
12344
- const refs2 = await onTracker(tracker, "list issues", () => tracker.find(query));
12345
- const issues = await onTracker(tracker, "read the issues listed", () => Promise.all(refs2.map((ref6) => tracker.get(ref6))));
12346
- return { tracker: data, query, issues };
12347
- }
12348
- async function runIssueList(context, seams) {
12349
- const line = readIssueListLine(context.flags);
12350
- if (!line.roadmap) {
12351
- const listed2 = await listIssues(context, seams);
12352
- if (context.outputMode === "json") {
12353
- context.output.result(listed2);
12354
- return;
12355
- }
12356
- for (const text of renderIssueList(listed2.tracker.kind, listed2.issues))
12357
- context.output.info(text);
12358
- return;
12359
- }
12360
- expectNoArgument(context.args, USAGE18);
12361
- const filter = readIssueQuery(context.flags);
12362
- const { result, epics } = await listRoadmap(context, seams, line.all, filter);
12363
- const failure3 = line.check ? epicCheckFailure(epics) : null;
12364
- if (context.outputMode === "json") {
12365
- if (failure3 !== null)
12366
- throw new CommandExit(EPIC_CHECK_EXIT, failure3);
12367
- context.output.result(result);
12368
- return;
12369
- }
12370
- const width = (seams.terminalWidth ?? (() => process.stdout.columns))();
12371
- for (const text of renderRoadmapList(result, width, line.full))
12372
- context.output.info(text);
12373
- if (failure3 !== null)
12374
- throw new CommandExit(EPIC_CHECK_EXIT, failure3);
12375
- }
12376
- var ISSUE_LIST_FLAGS = Object.freeze([
12377
- {
12378
- name: "roadmap",
12379
- description: "List the unticked lines of the Roadmap issue instead, in its order, with the spec, blocked by," + " has and refs columns, and the epics it names grouped by horizon. Reads the GitHub board whatever" + " tracker the chain lands on.",
12380
- type: "boolean"
12381
- },
12382
- {
12383
- name: "all",
12384
- description: "With --roadmap, keep the ticked lines too, each in its place, and show the epics of every" + " horizon, not only now. Refused without --roadmap.",
12385
- type: "boolean"
12386
- },
12387
- {
12388
- name: "full",
12389
- description: "With --roadmap, print each epic's issues under its row, on two rows each: number, state and" + " title, then labels and blockers. Refused without --roadmap.",
12390
- type: "boolean"
12391
- },
12392
- {
12393
- name: "check",
12394
- description: "With --roadmap, exit 1 when any type:epic issue on the board is open with its work done, or" + " closed with it not done, or when the board could not be read, naming each. Refused without --roadmap.",
12395
- type: "boolean"
12396
- },
12397
- {
12398
- name: "state",
12399
- description: `Only issues in this state: one of ${ISSUE_STATES.join(", ")}. Refused on the github tracker` + " and beside --roadmap.",
12400
- type: "string"
12401
- },
12402
- {
12403
- name: "type",
12404
- description: `Only issues of this type: one of ${ISSUE_TYPES.join(", ")}.`,
12405
- type: "string"
12406
- },
12407
- {
12408
- name: "module",
12409
- description: "Only issues of this module.",
12410
- type: "string"
12411
- },
12412
- {
12413
- name: "search",
12414
- description: "Only issues whose title or body holds this text.",
12415
- type: "string"
12416
- },
12417
- {
12418
- name: "limit",
12419
- description: "At most this many issues, a positive whole number. The limit of the tracker when left out," + " and none under --roadmap.",
12420
- type: "number"
12421
- }
12422
- ]);
12423
- function createIssueListCommand(seams = DEFAULT_ISSUE_SEAMS) {
12424
- const command = {
12425
- name: "issue list",
12426
- subject: "issue",
12427
- action: "list",
12428
- summary: "list the issues on the tracker, or the Roadmap's lines in its order, narrowed by type, module or text",
12429
- description: "Lists the issues on the tracker `tracker.default` names in `.rafa/config.yaml`, or on the first" + " `tracker.fallback` kind whose preflight passes when it fails, each kind passed over warned about." + " Prints the tracker, then one row per issue: its id, state, type and title. Each flag narrows the" + " list, and a flag left out narrows by nothing. The github tracker holds an issue open or closed, so" + " it refuses `--state`, and lists 30 issues unless `--limit` says otherwise. With `--roadmap` it lists" + " the unticked lines of the Roadmap issue (`roadmap.issue`, else the lowest-numbered open type:roadmap" + " board, else the open issue titled Roadmap) in its" + " order instead, read off the GitHub board, as a table adding four columns: spec, whether the body" + " passes the readiness gate; blocked by, each blocker and whether it is open; has, a plan, a branch" + " or a pull request already made for it; and refs, how many references of the issue's saved copy under" + " `specs.dir` read suspect or dangling, `-` with no copy. A line naming an epic prints as an epic row" + " instead, grouped by horizon under a `Roadmap #<n> \xB7 <horizon>` heading with the columns #, state," + " done/total, blocked, title and date, the now horizon only unless `--all`, and the spec rows follow" + " under Specs; `--full` prints each epic's issues under its row, on two rows each: number, state and" + " title, then labels and blockers; `--check` exits 1 when any type:epic issue's stored state" + " disagrees with its computed one. `--type`, `--module`, `--search` and `--limit` then narrow the spec rows, keeping their" + " order, and an unreachable board is warned about with the rows still printed. With `--output=json`" + " the tracker, the query and every issue, or under `--roadmap` the roadmap, the rows, the epics and the" + " warnings, are the data of the terminal result event.",
12430
- args: [],
12431
- flags: [...ISSUE_LIST_FLAGS],
12432
- examples: [
12433
- {
12434
- cmd: "rafa issue list",
12435
- note: "Lists the issues on the tracker the chain lands on, one row each."
12436
- },
12437
- {
12438
- cmd: "rafa issue list --type=bug --search=timeout",
12439
- note: "Lists the bugs whose title or body holds the word timeout."
12440
- },
12441
- {
12442
- cmd: "rafa issue list --limit=5 --output=json",
12443
- note: "Writes a start event, then a result event whose data holds the tracker, the query and five issues at most."
12444
- },
12445
- {
12446
- cmd: "rafa issue list --roadmap",
12447
- note: "Prints the Roadmap's unticked lines in its order, with the spec, blocked by, has and refs of each."
12448
- },
12449
- {
12450
- cmd: "rafa issue list --roadmap --full",
12451
- note: "Prints the Roadmap's now epics with each epic's issues underneath, then its spec lines."
12452
- },
12453
- {
12454
- cmd: "rafa issue list --roadmap --check",
12455
- note: "Prints the Roadmap, then exits 1 when an epic is open with its work done or closed with it not done."
12456
- },
12457
- {
12458
- cmd: "rafa issue list --roadmap --all --type=bug",
12459
- note: "Prints the bugs on the Roadmap, ticked lines included, in the Roadmap's order, beside its epics of every horizon."
12460
- }
12461
- ],
12462
- outputs: ["text", "json"],
12463
- run: (context) => runIssueList(context, seams)
12464
- };
12465
- return Object.freeze(command);
12466
- }
12467
- var list_default6 = createIssueListCommand();
12468
-
12469
12863
  // src/commands/issue/move.ts
12470
- var USAGE19 = "rafa issue move <id> <state>";
12864
+ var USAGE18 = "rafa issue move <id> <state>";
12471
12865
  async function moveIssue(context, seams) {
12472
- const [id2, word] = expectTwoArguments(context.args, USAGE19);
12473
- const state = readChoice(word, "The state", ISSUE_STATES, USAGE19);
12866
+ const [id2, word] = expectTwoArguments(context.args, USAGE18);
12867
+ const state = readChoice(word, "The state", ISSUE_STATES, USAGE18);
12474
12868
  const { tracker, data } = await resolveIssueTracker(context, seams);
12475
12869
  const ref6 = issueRef(tracker, id2);
12476
12870
  const moved = await onTracker(tracker, `move issue ${id2} to ${state}`, () => tracker.transition(ref6, state));
@@ -12529,7 +12923,7 @@ function createIssueMoveCommand(seams = DEFAULT_ISSUE_SEAMS) {
12529
12923
  var move_default2 = createIssueMoveCommand();
12530
12924
 
12531
12925
  // src/commands/issue/show.ts
12532
- var USAGE20 = "rafa issue show <id>";
12926
+ var USAGE19 = "rafa issue show <id>";
12533
12927
  var NO_PRIORITY = "needs-triage";
12534
12928
  function renderIssue(kind, issue) {
12535
12929
  const project = issue.project === null ? [] : [`Project: ${issue.project}`];
@@ -12551,7 +12945,7 @@ function renderIssue(kind, issue) {
12551
12945
  ];
12552
12946
  }
12553
12947
  async function showIssue(context, seams) {
12554
- const id2 = expectOneArgument(context.args, USAGE20);
12948
+ const id2 = expectOneArgument(context.args, USAGE19);
12555
12949
  const { tracker, data } = await resolveIssueTracker(context, seams);
12556
12950
  const issue = await onTracker(tracker, `read issue ${id2}`, () => tracker.get(issueRef(tracker, id2)));
12557
12951
  return { tracker: data, issue };
@@ -12941,7 +13335,7 @@ function createIssueUnblockCommand(seams = DEFAULT_UNBLOCK_SEAMS) {
12941
13335
  var unblock_default = createIssueUnblockCommand();
12942
13336
 
12943
13337
  // src/commands/loop/list.ts
12944
- var USAGE21 = "rafa loop list";
13338
+ var USAGE20 = "rafa loop list";
12945
13339
  function renderSessionList(list) {
12946
13340
  if (list.sessions.length === 0)
12947
13341
  return ["No running sessions."];
@@ -12951,7 +13345,7 @@ function renderSessionList(list) {
12951
13345
  ];
12952
13346
  }
12953
13347
  async function runList5(context, seams) {
12954
- expectNoArgument(context.args, USAGE21);
13348
+ expectNoArgument(context.args, USAGE20);
12955
13349
  const root = projectRoot(context);
12956
13350
  const sessions = readRecords(root, seams).filter(isLive).map((session) => {
12957
13351
  const checklist = readSessionChecklist(root, session);
@@ -12996,7 +13390,7 @@ function createLoopListCommand(seams = {}) {
12996
13390
  var list_default7 = createLoopListCommand();
12997
13391
 
12998
13392
  // src/commands/loop/pause.ts
12999
- var USAGE22 = "rafa loop pause [-s|--session-id=<id>]";
13393
+ var USAGE21 = "rafa loop pause [-s|--session-id=<id>]";
13000
13394
  function renderPause(result) {
13001
13395
  const { session } = result;
13002
13396
  if (!result.changed)
@@ -13005,8 +13399,8 @@ function renderPause(result) {
13005
13399
  return [when, " Nothing is marked. `rafa loop resume` goes on, and `rafa loop stop` ends the run."];
13006
13400
  }
13007
13401
  async function runPause(context, seams) {
13008
- expectNoArgument(context.args, USAGE22);
13009
- const { root, record } = pickSession(context, USAGE22, "live", seams);
13402
+ expectNoArgument(context.args, USAGE21);
13403
+ const { root, record } = pickSession(context, USAGE21, "live", seams);
13010
13404
  if (!isLive(record)) {
13011
13405
  throw refusal(`Session ${record.sessionId} reads ${record.state}: no run is left to pause.`);
13012
13406
  }
@@ -13046,7 +13440,7 @@ function createLoopPauseCommand(seams = {}) {
13046
13440
  var pause_default = createLoopPauseCommand();
13047
13441
 
13048
13442
  // src/commands/loop/resume.ts
13049
- var USAGE23 = "rafa loop resume [-s|--session-id=<id>]";
13443
+ var USAGE22 = "rafa loop resume [-s|--session-id=<id>]";
13050
13444
  function renderResume(result) {
13051
13445
  const { session } = result;
13052
13446
  if (!result.changed)
@@ -13054,8 +13448,8 @@ function renderResume(result) {
13054
13448
  return [session.task === null ? `Resumed session ${session.sessionId}: it goes on with its next task.` : `Resumed session ${session.sessionId}: it goes on past its running task, line ${session.task.line}, ${session.task.text}`];
13055
13449
  }
13056
13450
  async function runResume(context, seams) {
13057
- expectNoArgument(context.args, USAGE23);
13058
- const { root, record } = pickSession(context, USAGE23, "live", seams);
13451
+ expectNoArgument(context.args, USAGE22);
13452
+ const { root, record } = pickSession(context, USAGE22, "live", seams);
13059
13453
  if (!isLive(record)) {
13060
13454
  throw refusal(`Session ${record.sessionId} reads ${record.state}: its run has ended, so there is nothing to resume.`, `\`rafa loop start --plan=${record.plan}\` runs its plan again as a new session, its blocked tasks first.`);
13061
13455
  }
@@ -13100,7 +13494,7 @@ var wrapped = wrapPhaseZeroCommand({
13100
13494
  subject: "loop",
13101
13495
  action: "start",
13102
13496
  summary: "run a plan task by task, then wrap up, open the PR and wait for CI",
13103
- description: "Walks the tracker of a plan one task at a time, each in a Claude Code session whose work" + " is committed once it exits, and retries blocked tasks first. After the last task a wrap-up" + " session promotes findings, syncs with main, pushes and opens or updates the PR, and the loop" + " then waits on its checks, spending repair sessions on a red or conflicting PR. With no" + " `--plan` it runs `PLAN.md` in `plan.dir`, `.rafa/plans` unless the config names another, or" + " `PLAN.md` at the project root when that one does not exist. Before any session it checks the" + " prerequisites the config and the plan's `PREREQUISITES-<stub>.md` name, halting when a required" + " one fails and naming each failed optional one known-missing in every task prompt. Started on" + " `main` or `master` it offers to create the plan's `feat/<stub>` from the latest" + " `origin/<base>` and run there, `--create-branch` answering that without asking, and refuses" + " the run when the offer is not taken. Each run writes its session record to `.rafa/runs/<session-id>.json`:" + " the plan, the branch, the pid, the start, the state and the running task. It refuses a plan whose" + " record names another branch, and a plan a session is still running. `rafa loop stop`, `pause`," + " `resume`, `status` and `list` reach the run through that record. A run holds its terminal: until" + " phase 6 it refuses `--detached`. With `--runtime` the whole run goes on in that installed rafa," + " never in a `src/` directory.",
13497
+ description: "Walks the tracker of a plan one task at a time, each in a Claude Code session whose work" + " is committed once it exits, and retries blocked tasks first. After the last task a wrap-up" + " session promotes findings, syncs with main, pushes and opens or updates the PR, and the loop" + " then waits on its checks, spending repair sessions on a red or conflicting PR. With no" + " `--plan` it runs `PLAN.md` in `plan.dir`, `.rafa/plans` unless the config names another, or" + " `PLAN.md` at the project root when that one does not exist. Before any session it checks the" + " prerequisites the config and the plan's `PREREQUISITES-<stub>.md` name, halting when a required" + " one fails and naming each failed optional one known-missing in every task prompt, and warns once" + " for each migration the effort store holds that this rafa does not know and that is additive. It" + " refuses to start while `RAFA_EFFORT_DIR` is set, since a loop records to the project's own" + " store. Started on" + " `main` or `master` it offers to create the plan's `feat/<stub>` from the latest" + " `origin/<base>` and run there, `--create-branch` answering that without asking, and refuses" + " the run when the offer is not taken. Each run writes its session record to `.rafa/runs/<session-id>.json`:" + " the plan, the branch, the pid, the start, the state and the running task. It refuses a plan whose" + " record names another branch, and a plan a session is still running. `rafa loop stop`, `pause`," + " `resume`, `status` and `list` reach the run through that record. A run holds its terminal: until" + " phase 6 it refuses `--detached`. With `--runtime` the whole run goes on in that installed rafa," + " never in a `src/` directory.",
13104
13498
  args: [],
13105
13499
  flags: [
13106
13500
  {
@@ -13195,7 +13589,7 @@ var loopStart = Object.freeze({
13195
13589
  var start_default = endingWith(loopStart);
13196
13590
 
13197
13591
  // src/commands/loop/status.ts
13198
- var USAGE24 = "rafa loop status [-s|--session-id=<id>]";
13592
+ var USAGE23 = "rafa loop status [-s|--session-id=<id>]";
13199
13593
  var BLOCKED_TASK_LINE = /^- \[BLOCKED\] (.+)/;
13200
13594
  var NO_BLOCKER = "the line trails no blocker comment";
13201
13595
  function blockerAt(checklist, lineNum) {
@@ -13231,8 +13625,8 @@ function renderStatus(status) {
13231
13625
  ];
13232
13626
  }
13233
13627
  async function runStatus(context, seams) {
13234
- expectNoArgument(context.args, USAGE24);
13235
- const { root, record } = pickSession(context, USAGE24, "live-or-newest", seams);
13628
+ expectNoArgument(context.args, USAGE23);
13629
+ const { root, record } = pickSession(context, USAGE23, "live-or-newest", seams);
13236
13630
  const checklist = readSessionChecklist(root, record);
13237
13631
  const tasks = checklist === null ? null : countTasks(checklist.tasks);
13238
13632
  let eta = null;
@@ -13285,7 +13679,7 @@ function createLoopStatusCommand(seams = {}) {
13285
13679
  var status_default2 = createLoopStatusCommand();
13286
13680
 
13287
13681
  // src/commands/loop/stop.ts
13288
- var USAGE25 = "rafa loop stop [-s|--session-id=<id>]";
13682
+ var USAGE24 = "rafa loop stop [-s|--session-id=<id>]";
13289
13683
  function signalRun(record, interrupt) {
13290
13684
  try {
13291
13685
  interrupt(record.pid);
@@ -13338,8 +13732,8 @@ function renderStop(result) {
13338
13732
  ];
13339
13733
  }
13340
13734
  async function runStop(context, seams) {
13341
- expectNoArgument(context.args, USAGE25);
13342
- const { root, record } = pickSession(context, USAGE25, "live", seams);
13735
+ expectNoArgument(context.args, USAGE24);
13736
+ const { root, record } = pickSession(context, USAGE24, "live", seams);
13343
13737
  if (!isLive(record)) {
13344
13738
  throw refusal(`Session ${record.sessionId} reads ${record.state}: no run is left to stop.`);
13345
13739
  }
@@ -13390,7 +13784,7 @@ function createLoopStopCommand(seams = {}) {
13390
13784
  var stop_default = createLoopStopCommand();
13391
13785
 
13392
13786
  // src/commands/module/exec.ts
13393
- var USAGE26 = "rafa module exec <module> <action> [args] [flags]";
13787
+ var USAGE25 = "rafa module exec <module> <action> [args] [flags]";
13394
13788
  var execCommand = {
13395
13789
  name: "module exec",
13396
13790
  subject: "module",
@@ -13419,13 +13813,13 @@ var execCommand = {
13419
13813
  const mounts = context.registry.mounts();
13420
13814
  const mounted = mounts.length === 0 ? "none is mounted; a module is mounted when allowList: names it and modules: gives its path source" : mounts.map((mount) => `${mount.name} (${context.registry.actionsOf(mountKey(mount.name)).map((held) => held.action).join(", ")})`).join(", ");
13421
13815
  throw new CommandExit(1, `\u274C Expected a module and one of its actions; mounted: ${mounted}
13422
- Usage: ${USAGE26}`);
13816
+ Usage: ${USAGE25}`);
13423
13817
  }
13424
13818
  };
13425
13819
  var exec_default = Object.freeze(execCommand);
13426
13820
 
13427
13821
  // src/commands/module/list.ts
13428
- var USAGE27 = "rafa module list";
13822
+ var USAGE26 = "rafa module list";
13429
13823
  function sourceText(source) {
13430
13824
  const ref6 = source.ref === null ? "" : `@${source.ref}`;
13431
13825
  return `${source.kind} ${source.location}${ref6}`;
@@ -13446,10 +13840,10 @@ function renderModuleList(list) {
13446
13840
  return ["Modules:", ...list.modules.flatMap(moduleLines)];
13447
13841
  }
13448
13842
  async function runList6(context, seams) {
13449
- expectNoArgument(context.args, USAGE27);
13843
+ expectNoArgument(context.args, USAGE26);
13450
13844
  const { project } = context;
13451
13845
  if (project === null)
13452
- throw new CommandExit(1, `\u274C ${USAGE27} runs inside a project`);
13846
+ throw new CommandExit(1, `\u274C ${USAGE26} runs inside a project`);
13453
13847
  let loaded;
13454
13848
  try {
13455
13849
  const resolved = loadConfig({ root: project.root, home: project.home });
@@ -13602,7 +13996,7 @@ function fastForwardBase(git, plan2) {
13602
13996
 
13603
13997
  // src/commands/next.ts
13604
13998
  var NEXT_USAGE = "rafa next [--dry-run] [--yes[=<action ids>]]";
13605
- var DRY_RUN_FLAG3 = "dry-run";
13999
+ var DRY_RUN_FLAG4 = "dry-run";
13606
14000
  var STATE_MARK = "\uD83D\uDCCD";
13607
14001
  var PROPOSAL_MARK = "\uD83D\uDC49";
13608
14002
  var STOP_MARK = "\u23F9";
@@ -13623,7 +14017,7 @@ function dryRunLine(dryRun) {
13623
14017
  if (dryRun === "no-terminal") {
13624
14018
  return `${STOP_MARK} There is no terminal to answer on, so nothing ran; run rafa next where you can answer,` + ` or type --${YES_FLAG}=${BARE_YES_ACTIONS.join(",")} to allow those steps unasked.`;
13625
14019
  }
13626
- return `${STOP_MARK} --${DRY_RUN_FLAG3}: nothing ran.`;
14020
+ return `${STOP_MARK} --${DRY_RUN_FLAG4}: nothing ran.`;
13627
14021
  }
13628
14022
  function stopLine(stop, state, ceiling, dryRun) {
13629
14023
  if (stop === "dry-run")
@@ -13719,14 +14113,14 @@ function lineRefusal2(problem2) {
13719
14113
  Usage: ${NEXT_USAGE}`);
13720
14114
  }
13721
14115
  function readDryRun(flags) {
13722
- const value = flags[DRY_RUN_FLAG3];
14116
+ const value = flags[DRY_RUN_FLAG4];
13723
14117
  if (value === undefined)
13724
14118
  return false;
13725
14119
  if (typeof value === "boolean")
13726
14120
  return value;
13727
14121
  if (value === "true" || value === "false")
13728
14122
  return value === "true";
13729
- throw lineRefusal2(`--${DRY_RUN_FLAG3} takes no value, and read "${value}" as one`);
14123
+ throw lineRefusal2(`--${DRY_RUN_FLAG4} takes no value, and read "${value}" as one`);
13730
14124
  }
13731
14125
  function dryRunOf(flag, ceiling, hasTerminal) {
13732
14126
  if (flag)
@@ -13790,7 +14184,7 @@ async function runNext(context, seams) {
13790
14184
  var YES_ID_LIST = YES_ACTIONS.join(", ");
13791
14185
  var NEXT_FLAGS = Object.freeze([
13792
14186
  {
13793
- name: DRY_RUN_FLAG3,
14187
+ name: DRY_RUN_FLAG4,
13794
14188
  description: "Print where the project stands and what to do about it, and stop without asking or running it." + " A run with no terminal to answer on and no --yes does this whether or not the flag is typed.",
13795
14189
  type: "boolean"
13796
14190
  },
@@ -13928,9 +14322,9 @@ var create_default2 = endingWith(wrapped2);
13928
14322
 
13929
14323
  // src/commands/plan/risk.ts
13930
14324
  import { readFileSync as readFileSync16 } from "fs";
13931
- import { resolve as resolve10 } from "path";
14325
+ import { resolve as resolve12 } from "path";
13932
14326
  var COMMAND2 = "rafa plan risk";
13933
- var USAGE28 = "rafa plan risk [<plan>] [--strict]";
14327
+ var USAGE27 = "rafa plan risk [<plan>] [--strict]";
13934
14328
  var STRICT_HINT = `Type the plan first: ${COMMAND2} <plan> --strict`;
13935
14329
  function spawningRunners(root) {
13936
14330
  return { git: createGitRunner(root), gh: createGhRunner({ cwd: root }) };
@@ -13941,9 +14335,9 @@ function riskPlanPath(root, planDir, typed) {
13941
14335
  return path;
13942
14336
  if (typed !== null)
13943
14337
  throw new CommandExit(1, `\u274C Plan file not found: ${path}`);
13944
- const lookedFor = [...new Set([resolve10(root, planDir, DEFAULT_PLAN_FILE), resolve10(root, DEFAULT_PLAN_FILE)])];
14338
+ const lookedFor = [...new Set([resolve12(root, planDir, DEFAULT_PLAN_FILE), resolve12(root, DEFAULT_PLAN_FILE)])];
13945
14339
  throw new CommandExit(1, `\u274C No plan named, and no default plan at ${lookedFor.join(" or ")}
13946
- Usage: ${USAGE28}`);
14340
+ Usage: ${USAGE27}`);
13947
14341
  }
13948
14342
  function riskLine(finding) {
13949
14343
  const line = finding.line === undefined ? "" : `:${String(finding.line)}`;
@@ -14007,7 +14401,7 @@ function createPlanRiskCommand(seams = {}) {
14007
14401
  outputs: ["text", "json"],
14008
14402
  run: async (context) => {
14009
14403
  const strict = readSwitch("strict", context.flags["strict"], STRICT_HINT);
14010
- const typed = expectAtMostOneArgument(context.args, USAGE28);
14404
+ const typed = expectAtMostOneArgument(context.args, USAGE27);
14011
14405
  const project = requireProject(context, COMMAND2);
14012
14406
  const config = resolveProjectConfig(project, COMMAND2, (message) => {
14013
14407
  context.output.warn(message);
@@ -14039,8 +14433,8 @@ var risk_default = createPlanRiskCommand();
14039
14433
 
14040
14434
  // src/commands/plan/show.ts
14041
14435
  import { readFileSync as readFileSync17 } from "fs";
14042
- import { join as join27 } from "path";
14043
- var USAGE29 = "rafa plan show <stub> [--tracker]";
14436
+ import { join as join32 } from "path";
14437
+ var USAGE28 = "rafa plan show <stub> [--tracker]";
14044
14438
  var COMMAND3 = "rafa plan show";
14045
14439
  function readTrackerFlag(value) {
14046
14440
  if (value === undefined || value === false || value === "false")
@@ -14057,9 +14451,9 @@ function showPlan(plans, stub, tracker) {
14057
14451
  throw new CommandExit(1, `\u274C "${stub}" is no plan stub: a stub is letters, digits, ".", "_" and "-"`);
14058
14452
  }
14059
14453
  const name2 = planFileName(stub, tracker);
14060
- const file = join27(plans.path, name2);
14454
+ const file = join32(plans.path, name2);
14061
14455
  if (!isFile(file))
14062
- throw new CommandExit(1, missingText(stub, tracker, join27(plans.label, name2)));
14456
+ throw new CommandExit(1, missingText(stub, tracker, join32(plans.label, name2)));
14063
14457
  const model = parsePlan(readFileSync17(file, "utf8"));
14064
14458
  const { header } = model;
14065
14459
  return {
@@ -14083,7 +14477,7 @@ function taskLine(task) {
14083
14477
  return ` ${checkbox(task.status)} ${task.text}`;
14084
14478
  }
14085
14479
  function renderShownPlan(shown, dirLabel) {
14086
- const relative7 = join27(dirLabel, planFileName(shown.stub, shown.tracker));
14480
+ const relative7 = join32(dirLabel, planFileName(shown.stub, shown.tracker));
14087
14481
  const fileLabel = shown.tracker ? "Tracker" : "File";
14088
14482
  const lines2 = [`Plan: ${shown.stub}`, `${fileLabel}: ${relative7}`];
14089
14483
  if (shown.header.issue !== null)
@@ -14136,7 +14530,7 @@ var planShowCommand = {
14136
14530
  outputs: ["text", "json"],
14137
14531
  run: async (context) => {
14138
14532
  const tracker = readTrackerFlag(context.flags.tracker);
14139
- const stub = expectOneArgument(context.args, USAGE29);
14533
+ const stub = expectOneArgument(context.args, USAGE28);
14140
14534
  const plans = resolvePlansDir(requireProject(context, COMMAND3), COMMAND3, (message) => {
14141
14535
  context.output.warn(message);
14142
14536
  });
@@ -14276,7 +14670,7 @@ Run rafa pr list to see the open pull requests.`, usage2);
14276
14670
  }
14277
14671
 
14278
14672
  // src/commands/pr/current.ts
14279
- var USAGE30 = PR_USAGE.current;
14673
+ var USAGE29 = PR_USAGE.current;
14280
14674
  var SEPARATOR2 = " \u2014 ";
14281
14675
  function trimmedOrNull(value) {
14282
14676
  const trimmed = value.trim();
@@ -14307,9 +14701,9 @@ async function probeChecks(pulls, number) {
14307
14701
  }
14308
14702
  }
14309
14703
  async function readCurrent(context, seams) {
14310
- expectNoArguments(context.args, USAGE30);
14704
+ expectNoArguments(context.args, USAGE29);
14311
14705
  const pr = openPrContext(context, seams);
14312
- const pick = await pickPullRequest(pr, null, USAGE30);
14706
+ const pick = await pickPullRequest(pr, null, USAGE29);
14313
14707
  const summary = pick.summary;
14314
14708
  const probe = await probeChecks(pr.pulls, pick.number);
14315
14709
  const reading = {
@@ -14362,7 +14756,7 @@ function createPrCurrentCommand(seams = DEFAULT_PR_SEAMS) {
14362
14756
  var current_default = createPrCurrentCommand();
14363
14757
 
14364
14758
  // src/commands/pr/list.ts
14365
- var USAGE31 = PR_USAGE.list;
14759
+ var USAGE30 = PR_USAGE.list;
14366
14760
  var UNREADABLE = "?";
14367
14761
  var PROBE_LIMIT = 6;
14368
14762
  var TITLE_WIDTH = 60;
@@ -14463,7 +14857,7 @@ async function rowOf2(pulls, pull) {
14463
14857
  };
14464
14858
  }
14465
14859
  async function readList(context, seams, now) {
14466
- expectNoArguments(context.args, USAGE31);
14860
+ expectNoArguments(context.args, USAGE30);
14467
14861
  const pr = openPrContext(context, seams);
14468
14862
  const open = await onProvider("list the open pull requests", () => pr.pulls.list());
14469
14863
  const rows = await mapWithLimit(open, PROBE_LIMIT, (pull) => rowOf2(pr.pulls, pull));
@@ -14503,24 +14897,24 @@ function createPrListCommand(seams = DEFAULT_PR_SEAMS) {
14503
14897
  var list_default9 = createPrListCommand();
14504
14898
 
14505
14899
  // src/commands/pr/merge.ts
14506
- import { existsSync as existsSync12, readFileSync as readFileSync19 } from "fs";
14507
- import { join as join29 } from "path";
14900
+ import { existsSync as existsSync15, readFileSync as readFileSync19 } from "fs";
14901
+ import { join as join34 } from "path";
14508
14902
 
14509
14903
  // src/runtime/install.ts
14510
14904
  import {
14511
14905
  chmodSync,
14512
- copyFileSync as copyFileSync3,
14513
- existsSync as existsSync11,
14514
- mkdirSync as mkdirSync5,
14515
- readdirSync as readdirSync3,
14906
+ copyFileSync as copyFileSync4,
14907
+ existsSync as existsSync14,
14908
+ mkdirSync as mkdirSync6,
14909
+ readdirSync as readdirSync4,
14516
14910
  readFileSync as readFileSync18,
14517
14911
  realpathSync as realpathSync5,
14518
14912
  renameSync as renameSync2,
14519
- rmSync as rmSync3,
14520
- statSync as statSync10,
14913
+ rmSync as rmSync4,
14914
+ statSync as statSync11,
14521
14915
  symlinkSync
14522
14916
  } from "fs";
14523
- import { basename as basename7, delimiter as delimiter2, dirname as dirname6, join as join28, relative as relative7, resolve as resolve11 } from "path";
14917
+ import { basename as basename6, delimiter as delimiter2, dirname as dirname6, join as join33, relative as relative7, resolve as resolve13 } from "path";
14524
14918
  var LINK_NAME = "rafa";
14525
14919
  var FORCE_FLAG = "--force";
14526
14920
  var EXIT_DONE = 0;
@@ -14581,20 +14975,20 @@ function runtimeRefusalLines(runtimeDir, version2) {
14581
14975
  ];
14582
14976
  }
14583
14977
  function listTrackers(repoRoot, planDir) {
14584
- const dir = resolve11(repoRoot, planDir);
14585
- return entryNames(dir).filter((entry) => TRACKER_NAME.test(entry)).sort((a, b) => a.localeCompare(b)).map((entry) => join28(dir, entry)).filter((path) => statSync10(path).isFile()).map((path) => relative7(repoRoot, path));
14978
+ const dir = resolve13(repoRoot, planDir);
14979
+ return entryNames(dir).filter((entry) => TRACKER_NAME.test(entry)).sort((a, b) => a.localeCompare(b)).map((entry) => join33(dir, entry)).filter((path) => statSync11(path).isFile()).map((path) => relative7(repoRoot, path));
14586
14980
  }
14587
14981
  function openTrackers(repoRoot, planDir) {
14588
14982
  return listTrackers(repoRoot, planDir).flatMap((path) => {
14589
- const task = findNextTask(readFileSync18(join28(repoRoot, path), "utf8"));
14983
+ const task = findNextTask(readFileSync18(join33(repoRoot, path), "utf8"));
14590
14984
  return task ? [{ path, task }] : [];
14591
14985
  });
14592
14986
  }
14593
14987
  function readManifestVersion2(repoRoot) {
14594
- const manifest = JSON.parse(readFileSync18(join28(repoRoot, "package.json"), "utf8"));
14988
+ const manifest = JSON.parse(readFileSync18(join33(repoRoot, "package.json"), "utf8"));
14595
14989
  const record = typeof manifest === "object" && manifest !== null ? manifest : {};
14596
14990
  if (record["name"] !== name) {
14597
- throw new Error(`${join28(repoRoot, "package.json")} names ${JSON.stringify(record["name"])}, not ${name}: this is no rafa checkout`);
14991
+ throw new Error(`${join33(repoRoot, "package.json")} names ${JSON.stringify(record["name"])}, not ${name}: this is no rafa checkout`);
14598
14992
  }
14599
14993
  const version2 = record["version"];
14600
14994
  if (typeof version2 !== "string" || !VERSION_SHAPE.test(version2)) {
@@ -14603,12 +14997,12 @@ function readManifestVersion2(repoRoot) {
14603
14997
  return version2;
14604
14998
  }
14605
14999
  function copyTree(from, to) {
14606
- mkdirSync5(to, { recursive: true });
15000
+ mkdirSync6(to, { recursive: true });
14607
15001
  let copied = 0;
14608
- const entries = readdirSync3(from, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
15002
+ const entries = readdirSync4(from, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
14609
15003
  for (const entry of entries) {
14610
- const source = join28(from, entry.name);
14611
- const target = join28(to, entry.name);
15004
+ const source = join33(from, entry.name);
15005
+ const target = join33(to, entry.name);
14612
15006
  if (entry.isDirectory()) {
14613
15007
  copied += copyTree(source, target);
14614
15008
  } else if (entry.isFile()) {
@@ -14622,23 +15016,23 @@ function copyTree(from, to) {
14622
15016
  }
14623
15017
  function replaceTree(from, to) {
14624
15018
  const staging = temporaryName(to);
14625
- rmSync3(staging, { recursive: true, force: true });
15019
+ rmSync4(staging, { recursive: true, force: true });
14626
15020
  try {
14627
15021
  const copied = copyTree(from, staging);
14628
15022
  swapIn2(staging, to);
14629
15023
  return copied;
14630
15024
  } catch (err) {
14631
- rmSync3(staging, { recursive: true, force: true });
15025
+ rmSync4(staging, { recursive: true, force: true });
14632
15026
  throw err;
14633
15027
  }
14634
15028
  }
14635
15029
  function swapIn2(staging, to) {
14636
- if (!existsSync11(to)) {
15030
+ if (!existsSync14(to)) {
14637
15031
  renameSync2(staging, to);
14638
15032
  return;
14639
15033
  }
14640
15034
  const outgoing = `${temporaryName(to)}.outgoing`;
14641
- rmSync3(outgoing, { recursive: true, force: true });
15035
+ rmSync4(outgoing, { recursive: true, force: true });
14642
15036
  renameSync2(to, outgoing);
14643
15037
  try {
14644
15038
  renameSync2(staging, to);
@@ -14646,17 +15040,17 @@ function swapIn2(staging, to) {
14646
15040
  renameSync2(outgoing, to);
14647
15041
  throw err;
14648
15042
  }
14649
- rmSync3(outgoing, { recursive: true, force: true });
15043
+ rmSync4(outgoing, { recursive: true, force: true });
14650
15044
  }
14651
15045
  function relink(linkPath, target) {
14652
- mkdirSync5(dirname6(linkPath), { recursive: true });
15046
+ mkdirSync6(dirname6(linkPath), { recursive: true });
14653
15047
  const temporary = temporaryName(linkPath);
14654
- rmSync3(temporary, { force: true });
15048
+ rmSync4(temporary, { force: true });
14655
15049
  try {
14656
15050
  symlinkSync(target, temporary);
14657
15051
  renameSync2(temporary, linkPath);
14658
15052
  } catch (err) {
14659
- rmSync3(temporary, { force: true });
15053
+ rmSync4(temporary, { force: true });
14660
15054
  throw err;
14661
15055
  }
14662
15056
  const resolved = realpathSync5(linkPath);
@@ -14703,20 +15097,20 @@ function installRuntime(seams, options = {}) {
14703
15097
  }
14704
15098
  function runInstall(seams, options) {
14705
15099
  const { repoRoot, home, log } = seams;
14706
- const runtimeRoot = join28(home, RUNTIME_SUBDIR);
14707
- const linkPath = join28(home, RAFA_BIN_DIR, LINK_NAME);
15100
+ const runtimeRoot = join33(home, RUNTIME_SUBDIR);
15101
+ const linkPath = join33(home, RAFA_BIN_DIR, LINK_NAME);
14708
15102
  log(`repo root: ${repoRoot}`);
14709
15103
  log(`runtime root: ${runtimeRoot}`);
14710
15104
  log(`link: ${linkPath}`);
14711
15105
  const version2 = inStage("manifest", () => readManifestVersion2(repoRoot));
14712
- const planDir = inStage("config", () => resolve11(repoRoot, loadConfig({ root: repoRoot, home }, {}, seams.warn).config.planDir));
15106
+ const planDir = inStage("config", () => resolve13(repoRoot, loadConfig({ root: repoRoot, home }, {}, seams.warn).config.planDir));
14713
15107
  log(`plan dir: ${planDir}`);
14714
15108
  const open = inStage("trackers", () => openTrackers(repoRoot, planDir));
14715
15109
  if (open.length > 0)
14716
15110
  return { kind: "refused", reason: "trackers", planDir, trackers: open };
14717
- const distDir = join28(repoRoot, "dist");
14718
- const runtimeDir = join28(runtimeRoot, version2);
14719
- const installed = existsSync11(runtimeDir);
15111
+ const distDir = join33(repoRoot, "dist");
15112
+ const runtimeDir = join33(runtimeRoot, version2);
15113
+ const installed = existsSync14(runtimeDir);
14720
15114
  if (installed && options.force !== true) {
14721
15115
  return { kind: "refused", reason: "runtime-exists", version: version2, runtimeDir };
14722
15116
  }
@@ -14724,13 +15118,13 @@ function runInstall(seams, options) {
14724
15118
  const code = inStage("build", () => seams.build(repoRoot));
14725
15119
  if (code !== 0)
14726
15120
  throw new StageFailure("build", `the build exited ${code}`);
14727
- if (!existsSync11(join28(distDir, RUNTIME_ENTRY))) {
14728
- throw new StageFailure("build", `the build exited 0 and left no ${join28(distDir, RUNTIME_ENTRY)}`);
15121
+ if (!existsSync14(join33(distDir, RUNTIME_ENTRY))) {
15122
+ throw new StageFailure("build", `the build exited 0 and left no ${join33(distDir, RUNTIME_ENTRY)}`);
14729
15123
  }
14730
15124
  log(installed ? `copying ${distDir} over ${runtimeDir}, replacing it whole` : `copying ${distDir} into ${runtimeDir}`);
14731
15125
  const copied = inStage("copy", () => replaceTree(distDir, runtimeDir));
14732
15126
  log(`copied ${copied} file(s)`);
14733
- const resolved = inStage("link", () => relink(linkPath, join28(runtimeDir, RUNTIME_ENTRY)));
15127
+ const resolved = inStage("link", () => relink(linkPath, join33(runtimeDir, RUNTIME_ENTRY)));
14734
15128
  log(`${linkPath} resolves to ${resolved}`);
14735
15129
  return { kind: "done", version: version2, runtimeDir, linkPath, resolved, copied };
14736
15130
  }
@@ -14745,7 +15139,7 @@ function inStage(stage, run) {
14745
15139
  }
14746
15140
  function entryNames(dir) {
14747
15141
  try {
14748
- return readdirSync3(dir);
15142
+ return readdirSync4(dir);
14749
15143
  } catch (err) {
14750
15144
  if (typeof err === "object" && err !== null && "code" in err && err.code === "ENOENT")
14751
15145
  return [];
@@ -14755,16 +15149,16 @@ function entryNames(dir) {
14755
15149
  function replaceFile(source, target) {
14756
15150
  const temporary = temporaryName(target);
14757
15151
  try {
14758
- copyFileSync3(source, temporary);
14759
- chmodSync(temporary, statSync10(source).mode & 4095);
15152
+ copyFileSync4(source, temporary);
15153
+ chmodSync(temporary, statSync11(source).mode & 4095);
14760
15154
  renameSync2(temporary, target);
14761
15155
  } catch (err) {
14762
- rmSync3(temporary, { force: true });
15156
+ rmSync4(temporary, { force: true });
14763
15157
  throw err;
14764
15158
  }
14765
15159
  }
14766
15160
  function temporaryName(path) {
14767
- return join28(dirname6(path), `.${basename7(path)}.snapshot-${process.pid}`);
15161
+ return join33(dirname6(path), `.${basename6(path)}.snapshot-${process.pid}`);
14768
15162
  }
14769
15163
 
14770
15164
  // src/commands/pr/merge-followups.ts
@@ -15070,7 +15464,7 @@ async function postUncheckedComment(pulls, number, unchecked, warn3) {
15070
15464
  }
15071
15465
 
15072
15466
  // src/commands/pr/merge.ts
15073
- var USAGE32 = PR_USAGE.merge;
15467
+ var USAGE31 = PR_USAGE.merge;
15074
15468
  var REMOTE3 = "origin";
15075
15469
  var INDENT4 = " ";
15076
15470
  var YES_ANSWERS4 = ["y", "yes"];
@@ -15167,7 +15561,7 @@ function textOf(path) {
15167
15561
  }
15168
15562
  }
15169
15563
  function followUpsFor(pr, git) {
15170
- const facts = readPackageFacts(textOf(join29(pr.project.root, "package.json")));
15564
+ const facts = readPackageFacts(textOf(join34(pr.project.root, "package.json")));
15171
15565
  if (facts.version === null)
15172
15566
  return [];
15173
15567
  const tags = git(["tag", "--list", versionTag(facts.version)]);
@@ -15175,7 +15569,7 @@ function followUpsFor(pr, git) {
15175
15569
  version: facts.version,
15176
15570
  tagged: tags.ok && tags.stdout.trim() !== "",
15177
15571
  rafaCheckout: facts.rafaCheckout,
15178
- runtimeInstalled: existsSync12(join29(pr.project.home, RUNTIME_SUBDIR, facts.version))
15572
+ runtimeInstalled: existsSync15(join34(pr.project.home, RUNTIME_SUBDIR, facts.version))
15179
15573
  });
15180
15574
  }
15181
15575
  function openGh(pr, seams) {
@@ -15306,17 +15700,17 @@ async function commentIfUnchecked(context, pr, number, unchecked) {
15306
15700
  return posted.url;
15307
15701
  }
15308
15702
  async function runMerge(context, seams) {
15309
- const yes = readBooleanFlag(context.flags, "yes", USAGE32);
15310
- const skipChecks = readBooleanFlag(context.flags, "skip-checks", USAGE32);
15311
- const wanted = readMethodFlag(context.flags, USAGE32);
15312
- const asked = readPullArgument(context.args, USAGE32);
15703
+ const yes = readBooleanFlag(context.flags, "yes", USAGE31);
15704
+ const skipChecks = readBooleanFlag(context.flags, "skip-checks", USAGE31);
15705
+ const wanted = readMethodFlag(context.flags, USAGE31);
15706
+ const asked = readPullArgument(context.args, USAGE31);
15313
15707
  const pr = openPrContext(context, seams);
15314
15708
  const git = (seams.git ?? createGitRunner)(pr.project.root);
15315
15709
  const method = wanted ?? pr.mergeMethod;
15316
- const pick = await pickPullRequest(pr, asked, USAGE32);
15710
+ const pick = await pickPullRequest(pr, asked, USAGE31);
15317
15711
  const detail = await onProvider(`read pull request #${pick.number}`, () => pr.pulls.get(pick.number));
15318
15712
  if (detail === null) {
15319
- throw lineRefusal3(`No pull request #${pick.number} at ${pr.project.root}`, USAGE32);
15713
+ throw lineRefusal3(`No pull request #${pick.number} at ${pr.project.root}`, USAGE31);
15320
15714
  }
15321
15715
  const checks = await onProvider(`read the checks of #${pick.number}`, () => pr.pulls.checks(pick.number));
15322
15716
  recordPlanCi({
@@ -15887,7 +16281,7 @@ function readLastTriage(comments) {
15887
16281
  }
15888
16282
 
15889
16283
  // src/commands/pr/show.ts
15890
- var USAGE33 = PR_USAGE.show;
16284
+ var USAGE32 = PR_USAGE.show;
15891
16285
  var INDENT5 = " ";
15892
16286
  var SHORT_SHA = 7;
15893
16287
  function headLine3(detail) {
@@ -15985,12 +16379,12 @@ async function probe2(call) {
15985
16379
  }
15986
16380
  }
15987
16381
  async function readShow(context, seams) {
15988
- const asked = readPullArgument(context.args, USAGE33);
16382
+ const asked = readPullArgument(context.args, USAGE32);
15989
16383
  const pr = openPrContext(context, seams);
15990
- const pick = await pickPullRequest(pr, asked, USAGE33);
16384
+ const pick = await pickPullRequest(pr, asked, USAGE32);
15991
16385
  const detail = await onProvider(`read pull request #${pick.number}`, () => pr.pulls.get(pick.number));
15992
16386
  if (detail === null) {
15993
- throw lineRefusal3(`No pull request #${pick.number} in the repository at ${pr.project.root}`, USAGE33);
16387
+ throw lineRefusal3(`No pull request #${pick.number} in the repository at ${pr.project.root}`, USAGE32);
15994
16388
  }
15995
16389
  const checks = await probe2(() => pr.pulls.checks(pick.number));
15996
16390
  const comments = await probe2(() => pr.pulls.comments(pick.number));
@@ -16413,8 +16807,8 @@ function describeAge(updatedAt, now) {
16413
16807
  return `${plural4(hours, "hour", "hours")} ago`;
16414
16808
  return `${plural4(Math.floor(age / DAY_MS2), "day", "days")} ago`;
16415
16809
  }
16416
- function triageCommand(number, resolve12 = false) {
16417
- return resolve12 ? `rafa pr triage ${number} --resolve` : `rafa pr triage ${number}`;
16810
+ function triageCommand(number, resolve14 = false) {
16811
+ return resolve14 ? `rafa pr triage ${number} --resolve` : `rafa pr triage ${number}`;
16418
16812
  }
16419
16813
  function joinNumbers(pulls) {
16420
16814
  const names = pulls.map((pull) => `#${pull.number}`);
@@ -16425,14 +16819,14 @@ function joinNumbers(pulls) {
16425
16819
  function byNumber(pulls) {
16426
16820
  return Object.freeze([...pulls].sort((left, right) => left.number - right.number));
16427
16821
  }
16428
- function skipped(pull, now, resolve12) {
16822
+ function skipped(pull, now, resolve14) {
16429
16823
  return Object.freeze({
16430
16824
  number: pull.number,
16431
16825
  title: pull.title,
16432
16826
  updatedAt: pull.updatedAt,
16433
16827
  age: describeAge(pull.updatedAt, now),
16434
16828
  fresh: isFreshCandidate(pull.updatedAt, now),
16435
- command: triageCommand(pull.number, resolve12)
16829
+ command: triageCommand(pull.number, resolve14)
16436
16830
  });
16437
16831
  }
16438
16832
  function skippedLine(one) {
@@ -16476,15 +16870,15 @@ function listReading(rest, count2) {
16476
16870
  return listing("list", Object.freeze([]), rest, headline);
16477
16871
  }
16478
16872
  function selectTriagePullRequests(input) {
16479
- const resolve12 = input.resolve === true;
16873
+ const resolve14 = input.resolve === true;
16480
16874
  const { current, now } = input;
16481
16875
  if (current !== null) {
16482
16876
  return plain("branch", Object.freeze([current]), `assessing #${current.number}, the pull request open on ${current.headRefName}`);
16483
16877
  }
16484
16878
  const candidates = byNumber(input.candidates);
16485
16879
  const count2 = candidates.length;
16486
- const lines2 = candidates.map((pull) => skipped(pull, now, resolve12));
16487
- if (resolve12 && count2 > 1)
16880
+ const lines2 = candidates.map((pull) => skipped(pull, now, resolve14));
16881
+ if (resolve14 && count2 > 1)
16488
16882
  return refusal12(lines2, count2);
16489
16883
  if (count2 === 0) {
16490
16884
  return plain("none", Object.freeze([]), "no red pull request to triage, and no pull request on the current branch");
@@ -16819,8 +17213,8 @@ function withTaskBudget(plan2, usd) {
16819
17213
  }
16820
17214
 
16821
17215
  // src/pr/plans/load.ts
16822
- import { existsSync as existsSync13, readFileSync as readFileSync20 } from "fs";
16823
- import { dirname as dirname7, join as join30 } from "path";
17216
+ import { existsSync as existsSync16, readFileSync as readFileSync20 } from "fs";
17217
+ import { dirname as dirname7, join as join35 } from "path";
16824
17218
  import { fileURLToPath as fileURLToPath4 } from "url";
16825
17219
  var MODULE_DIR4 = dirname7(fileURLToPath4(import.meta.url));
16826
17220
  var PINNED_PLANS_DIRNAME = "plans";
@@ -16837,8 +17231,8 @@ function hasPinnedPlan(triageClass) {
16837
17231
  function pinnedPlanCandidates(moduleDir, triageClass) {
16838
17232
  const name2 = pinnedPlanFileName(triageClass);
16839
17233
  return [
16840
- join30(moduleDir, PINNED_PLANS_DIRNAME, name2),
16841
- join30(moduleDir, name2)
17234
+ join35(moduleDir, PINNED_PLANS_DIRNAME, name2),
17235
+ join35(moduleDir, name2)
16842
17236
  ];
16843
17237
  }
16844
17238
  function readPinnedPlan(triageClass, lookup = {}) {
@@ -16846,7 +17240,7 @@ function readPinnedPlan(triageClass, lookup = {}) {
16846
17240
  throw new Error(`No pinned resolve plan ships for ${triageClass}; ` + `one ships for ${PINNED_PLAN_CLASSES.join(", ")}`);
16847
17241
  }
16848
17242
  const candidates = pinnedPlanCandidates(lookup.moduleDir ?? MODULE_DIR4, triageClass);
16849
- const found = candidates.find((candidate) => existsSync13(candidate));
17243
+ const found = candidates.find((candidate) => existsSync16(candidate));
16850
17244
  if (found === undefined) {
16851
17245
  throw new Error(`The pinned resolve plan for ${triageClass} is missing: ` + `no file at ${candidates.join(" or ")}`);
16852
17246
  }
@@ -16990,20 +17384,20 @@ function readAttemptRepeat(input) {
16990
17384
  }
16991
17385
 
16992
17386
  // src/commands/pr/resolve-loop.ts
16993
- import { mkdirSync as mkdirSync6, writeFileSync as writeFileSync5 } from "fs";
16994
- import { join as join31 } from "path";
16995
- var RESOLVE_SUBDIR = join31(".rafa", "resolve");
17387
+ import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "fs";
17388
+ import { join as join36 } from "path";
17389
+ var RESOLVE_SUBDIR = join36(".rafa", "resolve");
16996
17390
  var RUN_PREFIX = "pr-";
16997
17391
  var ATTEMPT_PREFIX = "attempt-";
16998
17392
  var LOOP_WORDS = Object.freeze(["loop", "start"]);
16999
17393
  var CHILD_OUTPUT = "text";
17000
17394
  function resolveRunDir(home, number, attempt3) {
17001
- return join31(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt3}`);
17395
+ return join36(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt3}`);
17002
17396
  }
17003
17397
  function writeResolvePlan(write) {
17004
17398
  const dir = resolveRunDir(write.home, write.number, write.attempt);
17005
- mkdirSync6(dir, { recursive: true });
17006
- const path = join31(dir, pinnedPlanFileName(write.triageClass));
17399
+ mkdirSync7(dir, { recursive: true });
17400
+ const path = join36(dir, pinnedPlanFileName(write.triageClass));
17007
17401
  writeFileSync5(path, write.plan, "utf8");
17008
17402
  return path;
17009
17403
  }
@@ -17362,7 +17756,7 @@ async function runAttempts(run, detail, first) {
17362
17756
  }
17363
17757
 
17364
17758
  // src/commands/pr/triage.ts
17365
- var USAGE34 = PR_USAGE.triage;
17759
+ var USAGE33 = PR_USAGE.triage;
17366
17760
  var DEFAULT_MAX_ATTEMPTS = 2;
17367
17761
  var DETACHED_HEAD2 = "HEAD";
17368
17762
  var RESOLVED = false;
@@ -17403,22 +17797,22 @@ async function redPullRequests(pr) {
17403
17797
  }
17404
17798
  return red;
17405
17799
  }
17406
- async function selectTargets(pr, warn3, now, resolve12) {
17800
+ async function selectTargets(pr, warn3, now, resolve14) {
17407
17801
  const branch = readBranchOrNone(pr, warn3);
17408
17802
  const current = branch === null ? null : await onProvider(`read the open pull request for the branch "${branch}"`, () => pr.pulls.findOpen(branch));
17409
17803
  if (current !== null)
17410
- return selectTriagePullRequests({ current, candidates: [], now, resolve: resolve12 });
17804
+ return selectTriagePullRequests({ current, candidates: [], now, resolve: resolve14 });
17411
17805
  return selectTriagePullRequests({
17412
17806
  current: null,
17413
17807
  candidates: await redPullRequests(pr),
17414
17808
  now,
17415
- resolve: resolve12
17809
+ resolve: resolve14
17416
17810
  });
17417
17811
  }
17418
17812
  async function detailOf4(pr, number) {
17419
17813
  const detail = await onProvider(`read pull request #${number}`, () => pr.pulls.get(number));
17420
17814
  if (detail === null) {
17421
- throw lineRefusal3(`No pull request #${number} in the repository at ${pr.project.root}`, USAGE34);
17815
+ throw lineRefusal3(`No pull request #${number} in the repository at ${pr.project.root}`, USAGE33);
17422
17816
  }
17423
17817
  return detail;
17424
17818
  }
@@ -17521,14 +17915,14 @@ async function assessOne(options) {
17521
17915
  function refuseResolveWithoutComment(wantsResolve, wantsComment) {
17522
17916
  if (!wantsResolve || wantsComment)
17523
17917
  return;
17524
- throw lineRefusal3("--resolve writes the attempt count into the triage comment, and --no-comment writes none;" + " run one or the other", USAGE34);
17918
+ throw lineRefusal3("--resolve writes the attempt count into the triage comment, and --no-comment writes none;" + " run one or the other", USAGE33);
17525
17919
  }
17526
17920
  async function runTriage(context, seams) {
17527
- const wantsComment = readBooleanFlag(context.flags, "comment", USAGE34, true);
17528
- const wantsResolve = readBooleanFlag(context.flags, "resolve", USAGE34, false);
17529
- const maxAttempts = readMaxAttempts(context.flags, USAGE34);
17921
+ const wantsComment = readBooleanFlag(context.flags, "comment", USAGE33, true);
17922
+ const wantsResolve = readBooleanFlag(context.flags, "resolve", USAGE33, false);
17923
+ const maxAttempts = readMaxAttempts(context.flags, USAGE33);
17530
17924
  refuseResolveWithoutComment(wantsResolve, wantsComment);
17531
- const asked = readPullArgument(context.args, USAGE34);
17925
+ const asked = readPullArgument(context.args, USAGE33);
17532
17926
  const pr = openPrContext(context, seams);
17533
17927
  const git = (seams.git ?? createGitRunner)(pr.project.root);
17534
17928
  const permissions = (seams.permissions ?? ghPermissionsIn)(pr.project.root);
@@ -17541,7 +17935,7 @@ async function runTriage(context, seams) {
17541
17935
  }
17542
17936
  const numbers = asked === null ? (selection?.assess ?? []).map((one) => one.number) : [asked];
17543
17937
  const readings = [];
17544
- let resolve12 = null;
17938
+ let resolve14 = null;
17545
17939
  for (const number of numbers) {
17546
17940
  const assess = () => assessOne({
17547
17941
  pr,
@@ -17560,7 +17954,7 @@ async function runTriage(context, seams) {
17560
17954
  readings.push(reading);
17561
17955
  continue;
17562
17956
  }
17563
- resolve12 = await resolvePullRequest({
17957
+ resolve14 = await resolvePullRequest({
17564
17958
  trust: {
17565
17959
  permissions,
17566
17960
  trustedAuthors: pr.trustedAuthors,
@@ -17581,16 +17975,16 @@ async function runTriage(context, seams) {
17581
17975
  ...seams.clock === undefined ? {} : { clock: seams.clock },
17582
17976
  ...seams.sleep === undefined ? {} : { sleep: seams.sleep }
17583
17977
  });
17584
- readings.push(resolve12.reading);
17978
+ readings.push(resolve14.reading);
17585
17979
  }
17586
17980
  const text = [
17587
17981
  ...selection === null ? [] : [selection.message],
17588
17982
  ...readings.length === 0 ? [] : [renderTriages(readings)],
17589
- ...resolve12 === null ? [] : resolve12.lines
17983
+ ...resolve14 === null ? [] : resolve14.lines
17590
17984
  ].join(`
17591
17985
 
17592
17986
  `);
17593
- return { selection, readings, resolve: resolve12, text, exitCode: resolve12?.exitCode ?? 0 };
17987
+ return { selection, readings, resolve: resolve14, text, exitCode: resolve14?.exitCode ?? 0 };
17594
17988
  }
17595
17989
  function createPrTriageCommand(seams = DEFAULT_TRIAGE_SEAMS) {
17596
17990
  const command = {
@@ -17664,7 +18058,7 @@ function createPrTriageCommand(seams = DEFAULT_TRIAGE_SEAMS) {
17664
18058
  var triage_default = createPrTriageCommand();
17665
18059
 
17666
18060
  // src/commands/pr/view.ts
17667
- var USAGE35 = PR_USAGE.view;
18061
+ var USAGE34 = PR_USAGE.view;
17668
18062
  function trimmedOrNull3(value) {
17669
18063
  const trimmed = value.trim();
17670
18064
  return trimmed === "" ? null : trimmed;
@@ -17674,9 +18068,9 @@ function renderView(number, url) {
17674
18068
  return url === null ? opened : `${opened}${SEPARATOR2}${url}`;
17675
18069
  }
17676
18070
  async function runView2(context, seams) {
17677
- const asked = readPullArgument(context.args, USAGE35);
18071
+ const asked = readPullArgument(context.args, USAGE34);
17678
18072
  const pr = openPrContext(context, seams);
17679
- const pick = await pickPullRequest(pr, asked, USAGE35);
18073
+ const pick = await pickPullRequest(pr, asked, USAGE34);
17680
18074
  await onProvider(`open pull request #${pick.number} in the browser`, () => pr.pulls.browse(pick.number));
17681
18075
  const url = trimmedOrNull3(pick.summary?.url ?? "");
17682
18076
  return {
@@ -17731,7 +18125,7 @@ function createPrViewCommand(seams = DEFAULT_PR_SEAMS) {
17731
18125
  var view_default = createPrViewCommand();
17732
18126
 
17733
18127
  // src/commands/pr/wait.ts
17734
- var USAGE36 = PR_USAGE.wait;
18128
+ var USAGE35 = PR_USAGE.wait;
17735
18129
  var TIMEOUT_FLAG = "timeout";
17736
18130
  var DEFAULT_WAIT_TIMEOUT_MIN = DEFAULT_CI_TIMEOUT_MIN;
17737
18131
  var MS_PER_MIN = 60000;
@@ -17797,10 +18191,10 @@ function renderWait(reading) {
17797
18191
  `);
17798
18192
  }
17799
18193
  async function runWait(context, seams) {
17800
- const minutes = readTimeoutMinutes(context.flags, USAGE36);
17801
- const asked = readPullArgument(context.args, USAGE36);
18194
+ const minutes = readTimeoutMinutes(context.flags, USAGE35);
18195
+ const asked = readPullArgument(context.args, USAGE35);
17802
18196
  const pr = openPrContext(context, seams);
17803
- const pick = await pickPullRequest(pr, asked, USAGE36);
18197
+ const pick = await pickPullRequest(pr, asked, USAGE35);
17804
18198
  const timeoutMs = minutes * MS_PER_MIN;
17805
18199
  context.output.info(`Waiting for the checks of #${pick.number}, up to ${minutes} min.`);
17806
18200
  const waited = await waitForChecks({
@@ -17880,7 +18274,7 @@ var wait_default = createPrWaitCommand();
17880
18274
 
17881
18275
  // src/commands/release/tag.ts
17882
18276
  import { readFileSync as readFileSync21 } from "fs";
17883
- import { join as join32 } from "path";
18277
+ import { join as join37 } from "path";
17884
18278
 
17885
18279
  // src/commands/release/release-commit.ts
17886
18280
  function unread2(reason, message) {
@@ -17944,7 +18338,7 @@ function textOf2(path) {
17944
18338
  }
17945
18339
  }
17946
18340
  function readVersion(root, configured) {
17947
- const resolved = join32(root, configured);
18341
+ const resolved = join37(root, configured);
17948
18342
  const text = textOf2(resolved);
17949
18343
  if (text === null) {
17950
18344
  return {
@@ -17958,7 +18352,7 @@ function readVersion(root, configured) {
17958
18352
  return version2 === null ? { path: configured, text, version: null, problem: `${resolved} declares no version this can read` } : { path: configured, text, version: version2, problem: null };
17959
18353
  }
17960
18354
  function readNewestRelease(root, configured) {
17961
- const resolved = join32(root, configured);
18355
+ const resolved = join37(root, configured);
17962
18356
  const text = textOf2(resolved);
17963
18357
  if (text === null) {
17964
18358
  return { path: configured, version: null, problem: `the changelog could not be read at ${resolved}` };
@@ -18183,7 +18577,7 @@ function createRoadmapCommand(seams = DEFAULT_ISSUE_SEAMS) {
18183
18577
  subject: "roadmap",
18184
18578
  action: "roadmap",
18185
18579
  summary: "list the Roadmap's lines in its order, with spec, blocked by, has and refs",
18186
- description: "Runs `rafa issue list --roadmap` with the flags typed: the unticked lines of the Roadmap issue" + " (the current place's board, the one `rafa switch` moved to; with no position file `roadmap.issue`," + " else the lowest-numbered open type:roadmap board, else the open issue titled Roadmap) in its order," + " read off the GitHub board, as a" + " table with four columns the plain list has not got: spec, whether the body passes the readiness" + " gate; blocked by, each blocker and whether it is open; has, a plan, a branch or a pull request" + " already made for it; and refs, how many references of the issue's saved copy under `specs.dir` read" + " suspect or dangling, `-` with no copy. A line naming an epic prints as an epic row instead, grouped" + " by horizon under a `Roadmap #<n> \xB7 <horizon>` heading with the columns #, state, done/total, blocked," + " title and date, and the spec rows follow under Specs. `--full` prints each epic's issues under its" + " row, on two rows each: number, state and title, then labels and blockers. `--check` exits 1 when any" + " type:epic issue's stored state disagrees with its computed one, or the board could not be read, so CI" + " can run it. `--all` keeps the ticked lines and shows the" + " epics of every horizon, not only now, and `--type`, `--module`, `--search` and `--limit` narrow the" + " spec rows, keeping their order. An unreachable board is warned about with the rows still printed." + " With `--output=json` the roadmap, the rows, the epics and the warnings are the data of the terminal" + " result event.",
18580
+ description: "Runs `rafa issue list --roadmap` with the flags typed: the unticked lines of the Roadmap issue" + " (the current place's board, the one `rafa switch` moved to; with no position file `roadmap.issue`," + " else the lowest-numbered open type:roadmap board, else the open issue titled Roadmap) in its order," + " read off the GitHub board, as a" + " table with four columns the plain list has not got: spec, whether the issue can be planned (every" + " section of the spec template filled, and the spec:ready label on it), as a symbol; blocked by, the" + " blockers grouped by whether they are still open; has, a plan, a branch" + " or a pull request already made for it; and refs, how many references of the issue's saved copy under" + " `specs.dir` read suspect or dangling, `-` with no copy. A legend under the table names each symbol" + " in plain words, one line per column," + " and `--texts` (`-t`) spells both columns in words instead. Labels are no column: `--labels` prints" + " them on a row under each issue. A Roadmap naming epics prints them alone, grouped by horizon under a" + " `Roadmap #<n> \xB7 <horizon>` heading with the columns #, state, done/total, blocked, date and title," + " and one line counts the lines naming no epic. `--full` prints each epic's issues under its row, a" + " second row led by \u2514\u2192 for a member's blockers, and the lines naming no epic as the issue table. The" + " board listing is kept under .rafa/cache/ and read for what changed since; `--refresh` reads the whole" + " board again. `--check` exits 1 when any" + " type:epic issue's stored state disagrees with its computed one, or the board could not be read, so CI" + " can run it. `--all` keeps the ticked lines and shows the" + " epics of every horizon, not only now, and `--type`, `--module`, `--search` and `--limit` narrow the" + " spec rows, keeping their order. An unreachable board is warned about with the rows still printed." + " With `--output=json` the roadmap, the rows, the epics and the warnings are the data of the terminal" + " result event.",
18187
18581
  args: [],
18188
18582
  flags: [...ROADMAP_FLAGS],
18189
18583
  examples: [
@@ -18199,6 +18593,14 @@ function createRoadmapCommand(seams = DEFAULT_ISSUE_SEAMS) {
18199
18593
  cmd: "rafa roadmap --full",
18200
18594
  note: "Prints the now epics with each epic's issues underneath, then the spec lines."
18201
18595
  },
18596
+ {
18597
+ cmd: "rafa roadmap --labels --texts",
18598
+ note: "Prints each line's labels on a row under it, and the spec and blocked by columns in words."
18599
+ },
18600
+ {
18601
+ cmd: "rafa roadmap --refresh",
18602
+ note: "Reads the whole board again instead of only what changed since the kept listing."
18603
+ },
18202
18604
  {
18203
18605
  cmd: "rafa roadmap --check",
18204
18606
  note: "Prints the Roadmap, then exits 1 when an epic is open with its work done or closed with it not done."
@@ -18217,14 +18619,14 @@ var roadmap_default = createRoadmapCommand();
18217
18619
 
18218
18620
  // src/commands/self-update.ts
18219
18621
  var DEFAULT_SELF_UPDATE_SEAMS = Object.freeze({ build: runBuild });
18220
- var USAGE37 = "rafa self-update [--force]";
18622
+ var USAGE36 = "rafa self-update [--force]";
18221
18623
  function readForceFlag(value) {
18222
18624
  if (value === undefined || value === false || value === "false")
18223
18625
  return false;
18224
18626
  if (value === true || value === "true")
18225
18627
  return true;
18226
18628
  throw new CommandExit(1, `\u274C --force takes no value, and read "${value}" as one
18227
- Usage: ${USAGE37}`);
18629
+ Usage: ${USAGE36}`);
18228
18630
  }
18229
18631
  function projectOf10(context) {
18230
18632
  if (context.project === null)
@@ -18232,7 +18634,7 @@ function projectOf10(context) {
18232
18634
  return context.project;
18233
18635
  }
18234
18636
  async function runSelfUpdate(context, seams) {
18235
- expectNoArgument(context.args, USAGE37);
18637
+ expectNoArgument(context.args, USAGE36);
18236
18638
  const force = readForceFlag(context.flags["force"]);
18237
18639
  const project = projectOf10(context);
18238
18640
  const info2 = (line) => context.output.info(line);
@@ -18290,17 +18692,17 @@ function createSelfUpdateCommand(seams = DEFAULT_SELF_UPDATE_SEAMS) {
18290
18692
  var self_update_default = createSelfUpdateCommand();
18291
18693
 
18292
18694
  // src/commands/skill/backfill.ts
18293
- import { statSync as statSync12 } from "fs";
18294
- import { join as join37, resolve as resolve14 } from "path";
18695
+ import { statSync as statSync13 } from "fs";
18696
+ import { join as join42, resolve as resolve16 } from "path";
18295
18697
 
18296
18698
  // src/backfill/backup.ts
18297
- import { copyFileSync as copyFileSync4, existsSync as existsSync14, mkdirSync as mkdirSync7 } from "fs";
18298
- import { dirname as dirname8, isAbsolute as isAbsolute8, join as join34, relative as relative8, resolve as resolve12, sep as sep5 } from "path";
18699
+ import { copyFileSync as copyFileSync5, existsSync as existsSync17, mkdirSync as mkdirSync8 } from "fs";
18700
+ import { dirname as dirname8, isAbsolute as isAbsolute8, join as join39, relative as relative8, resolve as resolve14, sep as sep5 } from "path";
18299
18701
 
18300
18702
  // src/backfill/proposal-file.ts
18301
- import { readdirSync as readdirSync4, readFileSync as readFileSync22 } from "fs";
18302
- import { join as join33 } from "path";
18303
- var BACKFILL_PATH = join33(".rafa", "backfill");
18703
+ import { readdirSync as readdirSync5, readFileSync as readFileSync22 } from "fs";
18704
+ import { join as join38 } from "path";
18705
+ var BACKFILL_PATH = join38(".rafa", "backfill");
18304
18706
  var PROPOSAL_FILE_PREFIX = "proposals-";
18305
18707
  var PROPOSAL_FILE_EXTENSION = ".yaml";
18306
18708
  var SHA256_PATTERN = /^[0-9a-f]{64}$/;
@@ -18326,7 +18728,7 @@ var PROPOSAL_FILE_HEADER = [
18326
18728
  ].join(`
18327
18729
  `);
18328
18730
  function proposalPath(dir, batch) {
18329
- return join33(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
18731
+ return join38(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
18330
18732
  }
18331
18733
  function isProposalFileName(name2) {
18332
18734
  return name2.startsWith(PROPOSAL_FILE_PREFIX) && name2.endsWith(PROPOSAL_FILE_EXTENSION);
@@ -18413,7 +18815,7 @@ function parseRow(value, index) {
18413
18815
  note: filledStringAt(value, "note")
18414
18816
  };
18415
18817
  }
18416
- function unreadable(problem2) {
18818
+ function unreadable2(problem2) {
18417
18819
  return { file: null, problem: problem2 };
18418
18820
  }
18419
18821
  function parseHeader(data) {
@@ -18432,15 +18834,15 @@ function numberAt(data, key) {
18432
18834
  function parseProposalFile(text) {
18433
18835
  const parsed = readYamlMapping(text);
18434
18836
  if (parsed === null)
18435
- return unreadable("the file holds no YAML mapping");
18837
+ return unreadable2("the file holds no YAML mapping");
18436
18838
  const headerProblem = parseHeader(parsed);
18437
18839
  if (headerProblem !== null)
18438
- return unreadable(headerProblem);
18840
+ return unreadable2(headerProblem);
18439
18841
  const rows = [];
18440
18842
  for (const [index, value] of parsed["rows"].entries()) {
18441
18843
  const row2 = parseRow(value, index);
18442
18844
  if (typeof row2 === "string")
18443
- return unreadable(row2);
18845
+ return unreadable2(row2);
18444
18846
  rows.push(row2);
18445
18847
  }
18446
18848
  return {
@@ -18457,34 +18859,34 @@ function parseProposalFile(text) {
18457
18859
  function readProposalFiles(dir) {
18458
18860
  let names;
18459
18861
  try {
18460
- names = readdirSync4(dir);
18862
+ names = readdirSync5(dir);
18461
18863
  } catch {
18462
18864
  return [];
18463
18865
  }
18464
18866
  return names.filter((name2) => isProposalFileName(name2)).sort((left, right) => left.localeCompare(right)).map((name2) => {
18465
- const path = join33(dir, name2);
18867
+ const path = join38(dir, name2);
18466
18868
  try {
18467
18869
  return { path, result: parseProposalFile(readFileSync22(path, "utf8")) };
18468
18870
  } catch (error) {
18469
- return { path, result: unreadable(`the file cannot be read (${error instanceof Error ? error.message : String(error)})`) };
18871
+ return { path, result: unreadable2(`the file cannot be read (${error instanceof Error ? error.message : String(error)})`) };
18470
18872
  }
18471
18873
  });
18472
18874
  }
18473
18875
 
18474
18876
  // src/backfill/backup.ts
18475
- var BACKUP_PATH = join34(BACKFILL_PATH, "backup");
18877
+ var BACKUP_PATH = join39(BACKFILL_PATH, "backup");
18476
18878
  var BACKUP_KINDS = ["copied", "kept", "tracked", "outside", "missing"];
18477
18879
  function backupDirectory(base) {
18478
- return join34(base, BACKUP_PATH);
18880
+ return join39(base, BACKUP_PATH);
18479
18881
  }
18480
18882
  function isUnder(root, path) {
18481
- const step = relative8(resolve12(root), resolve12(path));
18883
+ const step = relative8(resolve14(root), resolve14(path));
18482
18884
  return step === "" || !step.startsWith(`..${sep5}`) && step !== ".." && !isAbsolute8(step);
18483
18885
  }
18484
18886
  function backupPathFor(path, root, backupDir) {
18485
18887
  if (!isUnder(root, path))
18486
18888
  return null;
18487
- return join34(backupDir, relative8(resolve12(root), resolve12(path)));
18889
+ return join39(backupDir, relative8(resolve14(root), resolve14(path)));
18488
18890
  }
18489
18891
  function backupFile(path, options) {
18490
18892
  if (options.repoRoot !== null && isUnder(options.repoRoot, path)) {
@@ -18493,12 +18895,12 @@ function backupFile(path, options) {
18493
18895
  const backup = backupPathFor(path, options.root, options.backupDir);
18494
18896
  if (backup === null)
18495
18897
  return { path, kind: "outside", backup: null };
18496
- if (!existsSync14(path))
18898
+ if (!existsSync17(path))
18497
18899
  return { path, kind: "missing", backup: null };
18498
- if (existsSync14(backup))
18900
+ if (existsSync17(backup))
18499
18901
  return { path, kind: "kept", backup };
18500
- mkdirSync7(dirname8(backup), { recursive: true });
18501
- copyFileSync4(path, backup);
18902
+ mkdirSync8(dirname8(backup), { recursive: true });
18903
+ copyFileSync5(path, backup);
18502
18904
  return { path, kind: "copied", backup };
18503
18905
  }
18504
18906
  function backupFiles(paths, options) {
@@ -18521,7 +18923,7 @@ function countBackups(results) {
18521
18923
 
18522
18924
  // src/backfill/derive.ts
18523
18925
  import { readFileSync as readFileSync23, writeFileSync as writeFileSync6 } from "fs";
18524
- import { basename as basename8 } from "path";
18926
+ import { basename as basename7 } from "path";
18525
18927
  var DERIVED_FIELDS = ["stack", "paths", "when_to_use"];
18526
18928
  function renderWhenToUse(trigger, prevents) {
18527
18929
  return `${sentenceEnded(trigger)} Prevents: ${prevents}`;
@@ -18638,8 +19040,8 @@ function fileChanges(name2, data, body, options) {
18638
19040
  return { changes, notes };
18639
19041
  }
18640
19042
  function planFile(entry, options) {
18641
- if (basename8(entry.path) !== SKILL_FILE) {
18642
- return inert(entry, "skipped", `${basename8(entry.path)} is no ${SKILL_FILE}, so it registers no skill`);
19043
+ if (basename7(entry.path) !== SKILL_FILE) {
19044
+ return inert(entry, "skipped", `${basename7(entry.path)} is no ${SKILL_FILE}, so it registers no skill`);
18643
19045
  }
18644
19046
  if (entry.name === null) {
18645
19047
  return inert(entry, "skipped", "its place implies no skill name, so no stack can be derived from it");
@@ -19126,8 +19528,8 @@ function parseSessionAnswer(stdout) {
19126
19528
  }
19127
19529
 
19128
19530
  // src/backfill/propose.ts
19129
- import { mkdirSync as mkdirSync8, readFileSync as readFileSync25, writeFileSync as writeFileSync7 } from "fs";
19130
- import { join as join35, resolve as resolve13 } from "path";
19531
+ import { mkdirSync as mkdirSync9, readFileSync as readFileSync25, writeFileSync as writeFileSync7 } from "fs";
19532
+ import { join as join40, resolve as resolve15 } from "path";
19131
19533
  var PROPOSAL_ACTION_KINDS = [
19132
19534
  "applied",
19133
19535
  "unchanged",
@@ -19206,7 +19608,7 @@ async function runProposalPass(options) {
19206
19608
  const batches = batchCandidates(candidates);
19207
19609
  const written = [];
19208
19610
  if (batches.length > 0)
19209
- mkdirSync8(options.backfillDir, { recursive: true });
19611
+ mkdirSync9(options.backfillDir, { recursive: true });
19210
19612
  for (const [index, batch] of batches.entries()) {
19211
19613
  const number = index + 1;
19212
19614
  const file = await runProposalBatch(number, batch, options);
@@ -19276,7 +19678,7 @@ function writeRow(path, row2, original, options) {
19276
19678
  };
19277
19679
  }
19278
19680
  function applyRow(root, row2, options) {
19279
- const path = join35(root, ...row2.path.split("/"));
19681
+ const path = join40(root, ...row2.path.split("/"));
19280
19682
  if (row2.status === UNANSWERED_ROW) {
19281
19683
  return inert2(path, row2, "unanswered", row2.note ?? "the row carries no answer");
19282
19684
  }
@@ -19299,7 +19701,7 @@ function fileRefusal(file, root) {
19299
19701
  if (file.status !== REVIEWED_FILE) {
19300
19702
  return `the proposal file is still marked ${file.status}: review it and mark it ${REVIEWED_FILE}`;
19301
19703
  }
19302
- return resolve13(file.skills) === resolve13(root) ? null : `the proposal file names the skills directory ${file.skills}, not ${root}`;
19704
+ return resolve15(file.skills) === resolve15(root) ? null : `the proposal file names the skills directory ${file.skills}, not ${root}`;
19303
19705
  }
19304
19706
  function countProposalActions(actions) {
19305
19707
  const counts = Object.fromEntries(PROPOSAL_ACTION_KINDS.map((kind) => [kind, 0]));
@@ -19313,7 +19715,7 @@ function applyProposals(files, root, options) {
19313
19715
  for (const file of files) {
19314
19716
  const refusal13 = fileRefusal(file, root);
19315
19717
  for (const row2 of file.rows) {
19316
- const action = refusal13 === null ? applyRow(root, row2, options) : inert2(join35(root, ...row2.path.split("/")), row2, "refused", refusal13);
19718
+ const action = refusal13 === null ? applyRow(root, row2, options) : inert2(join40(root, ...row2.path.split("/")), row2, "refused", refusal13);
19317
19719
  actions.push(action);
19318
19720
  if (row2.trigger !== null && action.kind !== "refused" && action.kind !== "unanswered") {
19319
19721
  triggers[row2.name] = row2.trigger;
@@ -19324,13 +19726,13 @@ function applyProposals(files, root, options) {
19324
19726
  }
19325
19727
 
19326
19728
  // src/demote/select.ts
19327
- import { readdirSync as readdirSync5, readFileSync as readFileSync26, realpathSync as realpathSync6, statSync as statSync11 } from "fs";
19328
- import { basename as basename9, dirname as dirname9, join as join36 } from "path";
19729
+ import { readdirSync as readdirSync6, readFileSync as readFileSync26, realpathSync as realpathSync6, statSync as statSync12 } from "fs";
19730
+ import { basename as basename8, dirname as dirname9, join as join41 } from "path";
19329
19731
  var SKILLS_DIRECTORY = "skills";
19330
19732
  var CLAUDE_DIRECTORY = ".claude";
19331
19733
  var AUTO_EXTRACTED_ORIGIN = "auto-extracted";
19332
- var DEMOTED_PATH = join36(".rafa", "demoted");
19333
- var INSTINCTS_PATH = join36(".rafa", "instincts");
19734
+ var DEMOTED_PATH = join41(".rafa", "demoted");
19735
+ var INSTINCTS_PATH = join41(".rafa", "instincts");
19334
19736
  var REPORT_FILE = "report.md";
19335
19737
  function realOrSelf(path) {
19336
19738
  try {
@@ -19347,25 +19749,25 @@ function samePath(left, right) {
19347
19749
  return resolved === other || resolved.toLowerCase() === other.toLowerCase();
19348
19750
  }
19349
19751
  function resolveDemotionScope(dir, home) {
19350
- if (basename9(dir) !== SKILLS_DIRECTORY)
19752
+ if (basename8(dir) !== SKILLS_DIRECTORY)
19351
19753
  return null;
19352
19754
  const claude = dirname9(dir);
19353
- if (basename9(claude) !== CLAUDE_DIRECTORY)
19755
+ if (basename8(claude) !== CLAUDE_DIRECTORY)
19354
19756
  return null;
19355
19757
  const base = dirname9(claude);
19356
- const demotedDir = join36(base, DEMOTED_PATH);
19758
+ const demotedDir = join41(base, DEMOTED_PATH);
19357
19759
  return {
19358
19760
  scope: samePath(base, home) ? "user" : "project",
19359
19761
  dir,
19360
19762
  base,
19361
19763
  demotedDir,
19362
- instinctsDir: join36(base, INSTINCTS_PATH),
19363
- reportPath: join36(demotedDir, REPORT_FILE)
19764
+ instinctsDir: join41(base, INSTINCTS_PATH),
19765
+ reportPath: join41(demotedDir, REPORT_FILE)
19364
19766
  };
19365
19767
  }
19366
19768
  function entryKind(path) {
19367
19769
  try {
19368
- const stats = statSync11(path);
19770
+ const stats = statSync12(path);
19369
19771
  if (stats.isFile())
19370
19772
  return "file";
19371
19773
  if (stats.isDirectory())
@@ -19377,7 +19779,7 @@ function entryKind(path) {
19377
19779
  }
19378
19780
  function visibleNames(dir) {
19379
19781
  try {
19380
- return readdirSync5(dir).filter((name2) => !name2.startsWith(".")).sort((left, right) => left.localeCompare(right));
19782
+ return readdirSync6(dir).filter((name2) => !name2.startsWith(".")).sort((left, right) => left.localeCompare(right));
19381
19783
  } catch {
19382
19784
  return [];
19383
19785
  }
@@ -19387,7 +19789,7 @@ function readSelected(absolute, path, entries) {
19387
19789
  let mtime;
19388
19790
  try {
19389
19791
  text = readFileSync26(absolute, "utf8");
19390
- mtime = statSync11(absolute).mtime.toISOString();
19792
+ mtime = statSync12(absolute).mtime.toISOString();
19391
19793
  } catch {
19392
19794
  return null;
19393
19795
  }
@@ -19402,14 +19804,14 @@ function selectsLearned(text, scope) {
19402
19804
  return origin === AUTO_EXTRACTED_ORIGIN;
19403
19805
  }
19404
19806
  function selectLearned(scope) {
19405
- const dir = join36(scope.dir, LEARNED_DIRECTORY);
19807
+ const dir = join41(scope.dir, LEARNED_DIRECTORY);
19406
19808
  if (entryKind(dir) !== "directory")
19407
19809
  return [];
19408
19810
  const selected = [];
19409
19811
  for (const name2 of visibleNames(dir)) {
19410
19812
  if (!name2.endsWith(MARKDOWN_EXTENSION) || name2 === MARKDOWN_EXTENSION)
19411
19813
  continue;
19412
- const absolute = join36(dir, name2);
19814
+ const absolute = join41(dir, name2);
19413
19815
  if (entryKind(absolute) !== "file")
19414
19816
  continue;
19415
19817
  const file = readSelected(absolute, `${LEARNED_DIRECTORY}/${name2}`, undefined);
@@ -19423,10 +19825,10 @@ function selectSkillFiles(scope) {
19423
19825
  for (const name2 of visibleNames(scope.dir)) {
19424
19826
  if (name2 === LEARNED_DIRECTORY)
19425
19827
  continue;
19426
- const skillDir = join36(scope.dir, name2);
19828
+ const skillDir = join41(scope.dir, name2);
19427
19829
  if (entryKind(skillDir) !== "directory")
19428
19830
  continue;
19429
- const absolute = join36(skillDir, SKILL_FILE);
19831
+ const absolute = join41(skillDir, SKILL_FILE);
19430
19832
  if (entryKind(absolute) !== "file")
19431
19833
  continue;
19432
19834
  const file = readSelected(absolute, `${name2}/${SKILL_FILE}`, visibleNames(skillDir));
@@ -19474,7 +19876,7 @@ Usage: ${BACKFILL_USAGE}`);
19474
19876
  throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
19475
19877
  Usage: ${BACKFILL_USAGE}`);
19476
19878
  }
19477
- return resolve14(cwd(), dir);
19879
+ return resolve16(cwd(), dir);
19478
19880
  }
19479
19881
  function readConsumerRoot(value, cwd) {
19480
19882
  if (value === undefined || value === false)
@@ -19483,11 +19885,11 @@ function readConsumerRoot(value, cwd) {
19483
19885
  throw new CommandExit(1, `\u274C --project needs a path, and the line gave it none
19484
19886
  Usage: ${BACKFILL_USAGE}`);
19485
19887
  }
19486
- return resolve14(cwd(), value);
19888
+ return resolve16(cwd(), value);
19487
19889
  }
19488
19890
  function isDirectory3(path) {
19489
19891
  try {
19490
- return statSync12(path).isDirectory();
19892
+ return statSync13(path).isDirectory();
19491
19893
  } catch {
19492
19894
  return false;
19493
19895
  }
@@ -19611,7 +20013,7 @@ function reviewedProposals(dir) {
19611
20013
  return read2.flatMap((entry) => entry.result.file === null ? [] : [entry.result.file]);
19612
20014
  }
19613
20015
  function rowPaths(files, root) {
19614
- return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join37(root, ...row2.path.split("/"))));
20016
+ return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join42(root, ...row2.path.split("/"))));
19615
20017
  }
19616
20018
  function runApply(context, scope, backfillDir, projectRoot3) {
19617
20019
  const options = checkOptions(context, projectRoot3);
@@ -19657,7 +20059,7 @@ async function runBackfill(context, seams) {
19657
20059
  const dir = readBackfillDirectory(context.args, seams.cwd);
19658
20060
  const projectRoot3 = readConsumerRoot(context.flags["project"], seams.cwd);
19659
20061
  const scope = backfillScopeOf(dir, projectOf11(context).home);
19660
- const backfillDir = join37(scope.base, BACKFILL_PATH);
20062
+ const backfillDir = join42(scope.base, BACKFILL_PATH);
19661
20063
  if (mode === "propose") {
19662
20064
  await runPropose(context, scope, backfillDir, seams);
19663
20065
  return;
@@ -19771,13 +20173,13 @@ function createSkillCheckCommand(seams = DEFAULT_CHECK_SEAMS) {
19771
20173
  var check_default3 = createSkillCheckCommand();
19772
20174
 
19773
20175
  // src/commands/skill/demote.ts
19774
- import { existsSync as existsSync16, mkdirSync as mkdirSync10, readFileSync as readFileSync28, statSync as statSync14, writeFileSync as writeFileSync9 } from "fs";
19775
- import { dirname as dirname11, resolve as resolve15 } from "path";
20176
+ import { existsSync as existsSync19, mkdirSync as mkdirSync11, readFileSync as readFileSync28, statSync as statSync15, writeFileSync as writeFileSync9 } from "fs";
20177
+ import { dirname as dirname11, resolve as resolve17 } from "path";
19776
20178
 
19777
20179
  // src/demote/apply.ts
19778
- import { copyFileSync as copyFileSync5, existsSync as existsSync15, mkdirSync as mkdirSync9, mkdtempSync as mkdtempSync2, readdirSync as readdirSync6, readFileSync as readFileSync27, renameSync as renameSync3, rmSync as rmSync4, statSync as statSync13, unlinkSync, writeFileSync as writeFileSync8 } from "fs";
20180
+ import { copyFileSync as copyFileSync6, existsSync as existsSync18, mkdirSync as mkdirSync10, mkdtempSync as mkdtempSync2, readdirSync as readdirSync7, readFileSync as readFileSync27, renameSync as renameSync3, rmSync as rmSync5, statSync as statSync14, unlinkSync, writeFileSync as writeFileSync8 } from "fs";
19779
20181
  import { tmpdir as tmpdir2 } from "os";
19780
- import { dirname as dirname10, join as join38 } from "path";
20182
+ import { dirname as dirname10, join as join43 } from "path";
19781
20183
  var DEMOTION_ACTION_KINDS = [
19782
20184
  "demoted",
19783
20185
  "kept",
@@ -19798,18 +20200,18 @@ function learnedSkillName(path) {
19798
20200
  return base.slice(0, -MARKDOWN_EXTENSION.length);
19799
20201
  }
19800
20202
  function sourcePath(scope, path) {
19801
- return join38(scope.dir, ...path.split("/"));
20203
+ return join43(scope.dir, ...path.split("/"));
19802
20204
  }
19803
20205
  function demotedPath(scope, path) {
19804
- return join38(scope.demotedDir, ...path.split("/"));
20206
+ return join43(scope.demotedDir, ...path.split("/"));
19805
20207
  }
19806
20208
  function promotedPath(scope, path) {
19807
- return join38(scope.dir, learnedSkillName(path), SKILL_FILE);
20209
+ return join43(scope.dir, learnedSkillName(path), SKILL_FILE);
19808
20210
  }
19809
20211
  function takenInstinctIds(dir) {
19810
20212
  let names;
19811
20213
  try {
19812
- names = readdirSync6(dir);
20214
+ names = readdirSync7(dir);
19813
20215
  } catch {
19814
20216
  return [];
19815
20217
  }
@@ -19817,7 +20219,7 @@ function takenInstinctIds(dir) {
19817
20219
  }
19818
20220
  function readText4(path) {
19819
20221
  try {
19820
- return statSync13(path).isFile() ? readFileSync27(path, "utf8") : null;
20222
+ return statSync14(path).isFile() ? readFileSync27(path, "utf8") : null;
19821
20223
  } catch {
19822
20224
  return null;
19823
20225
  }
@@ -19845,7 +20247,7 @@ function planKept(scope, row2, file) {
19845
20247
  return inert3(row2.path, "procedure", "kept", "procedure-shaped: left where it is, for the backfill");
19846
20248
  }
19847
20249
  const to = promotedPath(scope, row2.path);
19848
- if (existsSync15(to) || existsSync15(dirname10(to))) {
20250
+ if (existsSync18(to) || existsSync18(dirname10(to))) {
19849
20251
  return inert3(row2.path, "procedure", "refused", `kept as a skill, but ${to} is already there`);
19850
20252
  }
19851
20253
  return {
@@ -19859,7 +20261,7 @@ function planKept(scope, row2, file) {
19859
20261
  };
19860
20262
  }
19861
20263
  function checkCandidate(dir, id2, text, pathDirs) {
19862
- const path = join38(dir, `${id2}${MARKDOWN_EXTENSION}`);
20264
+ const path = join43(dir, `${id2}${MARKDOWN_EXTENSION}`);
19863
20265
  writeFileSync8(path, text, "utf8");
19864
20266
  const report3 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
19865
20267
  return failureLines2(report3.issues);
@@ -19880,11 +20282,11 @@ function planDemoted(scope, row2, file, context) {
19880
20282
  if (failures.length > 0) {
19881
20283
  return inert3(row2.path, "observation", "refused", `the record ${id2} fails the checker \u2014 ${failures.join("; ")}`);
19882
20284
  }
19883
- const destination = join38(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
20285
+ const destination = join43(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
19884
20286
  const kept = demotedPath(scope, row2.path);
19885
- if (existsSync15(destination))
20287
+ if (existsSync18(destination))
19886
20288
  return inert3(row2.path, "observation", "refused", `${destination} is already there`);
19887
- if (existsSync15(kept))
20289
+ if (existsSync18(kept))
19888
20290
  return inert3(row2.path, "observation", "refused", `${kept} is already there`);
19889
20291
  context.taken.push(id2);
19890
20292
  return {
@@ -19916,7 +20318,7 @@ function planRow(scope, row2, context) {
19916
20318
  entries: undefined,
19917
20319
  absolute,
19918
20320
  hash,
19919
- mtime: statSync13(absolute).mtime.toISOString()
20321
+ mtime: statSync14(absolute).mtime.toISOString()
19920
20322
  };
19921
20323
  return verdict === "procedure" ? planKept(scope, row2, file) : planDemoted(scope, row2, file, context);
19922
20324
  }
@@ -19927,28 +20329,28 @@ function countActions(actions) {
19927
20329
  return counts;
19928
20330
  }
19929
20331
  function planDemotion(report3, scope, seams = DEFAULT_APPLY_SEAMS) {
19930
- const temp = mkdtempSync2(join38(tmpdir2(), "rafa-demote-"));
20332
+ const temp = mkdtempSync2(join43(tmpdir2(), "rafa-demote-"));
19931
20333
  const taken = [...takenInstinctIds(scope.instinctsDir)];
19932
20334
  try {
19933
20335
  const actions = report3.rows.map((row2) => planRow(scope, row2, { temp, taken, seams }));
19934
20336
  return { scope, actions, counts: countActions(actions) };
19935
20337
  } finally {
19936
- rmSync4(temp, { recursive: true, force: true });
20338
+ rmSync5(temp, { recursive: true, force: true });
19937
20339
  }
19938
20340
  }
19939
20341
  function moveFile(move3) {
19940
- mkdirSync9(dirname10(move3.to), { recursive: true });
20342
+ mkdirSync10(dirname10(move3.to), { recursive: true });
19941
20343
  try {
19942
20344
  renameSync3(move3.from, move3.to);
19943
20345
  } catch {
19944
- copyFileSync5(move3.from, move3.to);
20346
+ copyFileSync6(move3.from, move3.to);
19945
20347
  unlinkSync(move3.from);
19946
20348
  }
19947
20349
  }
19948
20350
  function applyDemotion(plan2) {
19949
20351
  for (const action of plan2.actions) {
19950
20352
  if (action.write !== null) {
19951
- mkdirSync9(dirname10(action.write.path), { recursive: true });
20353
+ mkdirSync10(dirname10(action.write.path), { recursive: true });
19952
20354
  writeFileSync8(action.write.path, action.write.text, "utf8");
19953
20355
  }
19954
20356
  if (action.move !== null)
@@ -20014,11 +20416,11 @@ Usage: ${DEMOTE_USAGE}`);
20014
20416
  throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
20015
20417
  Usage: ${DEMOTE_USAGE}`);
20016
20418
  }
20017
- return resolve15(cwd(), dir);
20419
+ return resolve17(cwd(), dir);
20018
20420
  }
20019
20421
  function isDirectory4(path) {
20020
20422
  try {
20021
- return statSync14(path).isDirectory();
20423
+ return statSync15(path).isDirectory();
20022
20424
  } catch {
20023
20425
  return false;
20024
20426
  }
@@ -20040,13 +20442,13 @@ function projectOf12(context) {
20040
20442
  throw new Error("rafa skill demote runs inside a project, and was handed none");
20041
20443
  return context.project;
20042
20444
  }
20043
- function verdictLine(report3) {
20445
+ function verdictLine2(report3) {
20044
20446
  const counts = countVerdicts(report3.rows);
20045
20447
  const parts = DEMOTION_VERDICTS.map((verdict) => `${String(counts[verdict])} ${verdict}`);
20046
20448
  return `${String(report3.rows.length)} file(s): ${parts.join(", ")}`;
20047
20449
  }
20048
20450
  function previousReport(path, context) {
20049
- if (!existsSync16(path))
20451
+ if (!existsSync19(path))
20050
20452
  return null;
20051
20453
  const parsed = parseDemotionReport(readFileSync28(path, "utf8"));
20052
20454
  if (parsed.report !== null)
@@ -20057,7 +20459,7 @@ function previousReport(path, context) {
20057
20459
  function runWrite(context, scope) {
20058
20460
  const files = selectSources(scope);
20059
20461
  const report3 = buildDemotionReport(files, previousReport(scope.reportPath, context));
20060
- mkdirSync10(dirname11(scope.reportPath), { recursive: true });
20462
+ mkdirSync11(dirname11(scope.reportPath), { recursive: true });
20061
20463
  writeFileSync9(scope.reportPath, writeDemotionReport(report3), "utf8");
20062
20464
  const result = {
20063
20465
  applied: false,
@@ -20074,14 +20476,14 @@ function runWrite(context, scope) {
20074
20476
  }
20075
20477
  for (const line of [
20076
20478
  `${scope.scope} scope: ${scope.dir}`,
20077
- verdictLine(report3),
20479
+ verdictLine2(report3),
20078
20480
  `Report written to ${scope.reportPath} (status: ${report3.status}); nothing was moved`,
20079
20481
  `Review each row, then mark the report ${REVIEWED_STATUS} and re-run with --apply`
20080
20482
  ])
20081
20483
  context.output.info(line);
20082
20484
  }
20083
20485
  function reviewedReport(scope) {
20084
- if (!existsSync16(scope.reportPath)) {
20486
+ if (!existsSync19(scope.reportPath)) {
20085
20487
  throw new CommandExit(1, `\u274C No report at ${scope.reportPath}; run "rafa skill demote ${scope.dir}" first, then review it`);
20086
20488
  }
20087
20489
  const parsed = parseDemotionReport(readFileSync28(scope.reportPath, "utf8"));
@@ -20190,8 +20592,8 @@ var demote_default = createSkillDemoteCommand();
20190
20592
 
20191
20593
  // src/commands/skill/show.ts
20192
20594
  var DEFAULT_SKILL_SHOW_SEAMS = DEFAULT_SKILL_LIST_SEAMS;
20193
- var COMMAND_NAME6 = "rafa skill show";
20194
- var USAGE38 = "rafa skill show <name> [--full]";
20595
+ var COMMAND_NAME9 = "rafa skill show";
20596
+ var USAGE37 = "rafa skill show <name> [--full]";
20195
20597
  function unknownSkillMessage(name2, inventory) {
20196
20598
  const agent = findShown(inventory.records, "agent", name2);
20197
20599
  const hint = agent === null ? [] : [`An agent is named "${name2}" (${agent.source}): rafa agent show ${name2}`];
@@ -20199,7 +20601,7 @@ function unknownSkillMessage(name2, inventory) {
20199
20601
  `\u274C No skill is named "${name2}".`,
20200
20602
  ...hint,
20201
20603
  "Run `rafa skill list` to see every skill and where it comes from.",
20202
- `Usage: ${USAGE38}`
20604
+ `Usage: ${USAGE37}`
20203
20605
  ].join(`
20204
20606
  `);
20205
20607
  }
@@ -20215,10 +20617,10 @@ function projectOf13(context) {
20215
20617
  return context.project;
20216
20618
  }
20217
20619
  async function runShow3(context, seams) {
20218
- const full = readSwitch("full", context.flags["full"], `Type the name ahead of it: ${USAGE38}`);
20219
- const name2 = expectOneArgument(context.args, USAGE38);
20620
+ const full = readSwitch("full", context.flags["full"], `Type the name ahead of it: ${USAGE37}`);
20621
+ const name2 = expectOneArgument(context.args, USAGE37);
20220
20622
  const project = projectOf13(context);
20221
- const { inventory, settingSources } = await projectInventory(project, context, seams, COMMAND_NAME6);
20623
+ const { inventory, settingSources } = await projectInventory(project, context, seams, COMMAND_NAME9);
20222
20624
  const warnings = warningLines(inventory);
20223
20625
  for (const warning of warnings)
20224
20626
  context.output.warn(warning);
@@ -20411,7 +20813,7 @@ function statusData(sections) {
20411
20813
  }
20412
20814
 
20413
20815
  // src/status/sections.ts
20414
- import { existsSync as existsSync17 } from "fs";
20816
+ import { existsSync as existsSync20 } from "fs";
20415
20817
  var STATUS_NETWORK_TIMEOUT_MS = 5000;
20416
20818
  function isIdleWorktree(row2) {
20417
20819
  return !row2.blockers.some((blocker) => blocker.kind === "recent");
@@ -20458,9 +20860,9 @@ function boundedRunner(open, root, deadline) {
20458
20860
  }
20459
20861
  async function networkSection(what, deadline, work) {
20460
20862
  let timer;
20461
- const expired = new Promise((resolve16) => {
20863
+ const expired = new Promise((resolve18) => {
20462
20864
  timer = setTimeout(() => {
20463
- resolve16(unread3(`the ${what} was not read within the ${String(deadline.ms)}ms network deadline`));
20865
+ resolve18(unread3(`the ${what} was not read within the ${String(deadline.ms)}ms network deadline`));
20464
20866
  }, Math.max(0, deadline.left()));
20465
20867
  });
20466
20868
  try {
@@ -20550,7 +20952,7 @@ function nextOfPlace(current, epic, walk) {
20550
20952
  return epic?.members.some((member) => member.number === line.issue) === true ? line.issue : null;
20551
20953
  }
20552
20954
  function hasBoards(root, listing2) {
20553
- if (existsSync17(positionFilePath(root)))
20955
+ if (existsSync20(positionFilePath(root)))
20554
20956
  return true;
20555
20957
  return listing2.some((issue2) => issue2.state === "OPEN" && issue2.labels.includes(ROADMAP_LABEL));
20556
20958
  }
@@ -20640,7 +21042,7 @@ async function readStatusSections(input, seams = {}) {
20640
21042
  }
20641
21043
 
20642
21044
  // src/commands/status.ts
20643
- var COMMAND_NAME7 = "rafa status";
21045
+ var COMMAND_NAME10 = "rafa status";
20644
21046
  var STATUS_USAGE = "rafa status";
20645
21047
  var STATUS_ARGUMENT_EXIT = 2;
20646
21048
  var DEFAULT_STATUS_SEAMS = Object.freeze({});
@@ -20652,13 +21054,13 @@ function projectOf14(context) {
20652
21054
  function expectNoWord(args) {
20653
21055
  if (args.length === 0)
20654
21056
  return;
20655
- throw new CommandExit(STATUS_ARGUMENT_EXIT, `\u274C ${COMMAND_NAME7}: expected no argument, got ${String(args.length)}: ${args.join(" ")}
21057
+ throw new CommandExit(STATUS_ARGUMENT_EXIT, `\u274C ${COMMAND_NAME10}: expected no argument, got ${String(args.length)}: ${args.join(" ")}
20656
21058
  Usage: ${STATUS_USAGE}`);
20657
21059
  }
20658
21060
  async function runStatus2(context, seams) {
20659
21061
  expectNoWord(context.args);
20660
21062
  const project = projectOf14(context);
20661
- const config = resolveProjectConfig(project, COMMAND_NAME7, (message) => {
21063
+ const config = resolveProjectConfig(project, COMMAND_NAME10, (message) => {
20662
21064
  context.output.warn(message);
20663
21065
  });
20664
21066
  const read3 = seams.read ?? ((input) => readStatusSections(input));
@@ -20724,7 +21126,7 @@ var CORE_SUBJECTS = Object.freeze([
20724
21126
  { name: "loop", summary: "start a plan; stop, pause, resume, show and list its sessions" },
20725
21127
  { name: "issue", summary: "the tracker: list, show, create, comment on and move issues; mark one ready, unblock it and check its references" },
20726
21128
  { name: "pr", summary: "the pull request of a branch: one line, in full or in the browser; list, wait on, merge and triage them" },
20727
- { name: "effort", summary: "collect session and commit rows; report per plan; repair a store a newer rafa migrated" },
21129
+ { name: "effort", summary: "collect session and commit rows; report per plan; read and repair the store: its schema, a copy for testing, migrations, and fixes" },
20728
21130
  { name: "module", summary: "list the configured modules; run an action a module provides" },
20729
21131
  { name: "agent", summary: "copy an agent definition into the project; list what a session sees" },
20730
21132
  { name: "skill", summary: "check a skills directory; list each tier; demote and backfill it" },
@@ -20735,7 +21137,7 @@ var CORE_SUBJECTS = Object.freeze([
20735
21137
  ]);
20736
21138
  var CORE_COMMANDS = Object.freeze([
20737
21139
  create_default2,
20738
- list_default,
21140
+ list_default2,
20739
21141
  show_default5,
20740
21142
  validate_default,
20741
21143
  risk_default,
@@ -20746,7 +21148,7 @@ var CORE_COMMANDS = Object.freeze([
20746
21148
  resume_default,
20747
21149
  status_default2,
20748
21150
  list_default7,
20749
- list_default6,
21151
+ list_default,
20750
21152
  show_default4,
20751
21153
  create_default,
20752
21154
  comment_default,
@@ -20764,26 +21166,29 @@ var CORE_COMMANDS = Object.freeze([
20764
21166
  collect_default,
20765
21167
  report_default,
20766
21168
  fix_schema_default,
21169
+ copy_default,
21170
+ schema_default,
21171
+ migrate_default,
20767
21172
  list_default8,
20768
21173
  exec_default,
20769
21174
  vendor_default,
20770
- list_default3,
21175
+ list_default4,
20771
21176
  show_default2,
20772
21177
  search_default2,
20773
21178
  check_default3,
20774
- list_default2,
21179
+ list_default3,
20775
21180
  show_default7,
20776
21181
  search_default,
20777
21182
  demote_default,
20778
21183
  backfill_default,
20779
21184
  check_default,
20780
- list_default5,
21185
+ list_default6,
20781
21186
  show_default3,
20782
21187
  flag_default,
20783
21188
  promote_default2,
20784
21189
  status_default,
20785
21190
  tag_default,
20786
- list_default4,
21191
+ list_default5,
20787
21192
  show_default,
20788
21193
  new_default,
20789
21194
  defer_default,