@open-tomato/rafa 0.24.0 → 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.
- package/README.md +17 -0
- package/dist/bundled/skills/dev-planner/SKILL.md +11 -0
- package/dist/cli.js +3607 -1715
- package/dist/{describe-66gth329.js → describe-vbwqxj1f.js} +3 -3
- package/dist/effort/store/index.js +4 -4
- package/dist/{index-zzvc0gkb.js → index-2yd8bber.js} +1292 -799
- package/dist/{index-4fwgqveh.js → index-4j1e3h26.js} +1 -1
- package/dist/{index-gt9acb1y.js → index-59x3zsh4.js} +2869 -2259
- package/dist/{index-7qgng1rf.js → index-azptzvp3.js} +945 -193
- package/dist/{index-21qkxk63.js → index-p1265xgj.js} +4 -33
- package/dist/{index-twbp38kz.js → index-rv7y8y1j.js} +36 -2
- package/dist/{index-pmkgvtfe.js → index-sf2brh4w.js} +7 -1
- package/dist/{index-w1dnwja1.js → index-xwr54rzt.js} +2 -2
- package/dist/index.js +16 -16
- package/dist/plan/index.js +3 -3
- package/package.json +1 -1
|
@@ -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
|
-
|
|
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
|
-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
317
|
+
effortStoreDir,
|
|
318
|
+
effortStoreFileName,
|
|
319
|
+
errorCode,
|
|
317
320
|
findRecords,
|
|
318
321
|
gitRemoteUrl,
|
|
322
|
+
guardTestProcess,
|
|
319
323
|
instinctProject,
|
|
324
|
+
isPidAlive,
|
|
320
325
|
mergeFrontmatter,
|
|
321
|
-
|
|
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
|
-
|
|
335
|
-
|
|
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-
|
|
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-
|
|
377
|
+
} from "./index-xwr54rzt.js";
|
|
358
378
|
import {
|
|
359
379
|
CommandExit
|
|
360
|
-
} from "./index-
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
6682
|
-
var LEGACY_EFFORT_STORE_DIR =
|
|
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
|
-
|
|
6686
|
-
...Object.keys(NDJSON_KINDS).map((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(
|
|
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 =
|
|
6714
|
-
const storeDir =
|
|
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
|
|
6723
|
-
var RAFA_BIN_DIR =
|
|
6724
|
-
var BUN_BIN_DIR =
|
|
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 =
|
|
6758
|
-
const bunBin =
|
|
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
|
|
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" &&
|
|
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 =
|
|
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 =
|
|
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
|
|
7132
|
-
|
|
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
|
-
]
|
|
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
|
|
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
|
-
|
|
7207
|
-
|
|
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/
|
|
7353
|
-
import {
|
|
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
|
-
|
|
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 = "
|
|
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
|
|
7372
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
7432
|
-
const
|
|
7433
|
-
if (
|
|
7434
|
-
throw new
|
|
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
|
|
7678
|
+
function buildParallel(path, parallelPath, migrations, stamp) {
|
|
7679
|
+
const db = new Database2(parallelPath, { create: true, readwrite: true });
|
|
7438
7680
|
try {
|
|
7439
|
-
|
|
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
|
|
7455
|
-
|
|
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
|
|
7459
|
-
|
|
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
|
|
7468
|
-
|
|
7469
|
-
|
|
7470
|
-
|
|
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
|
|
7480
|
-
const
|
|
7481
|
-
|
|
7482
|
-
|
|
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
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
7488
|
-
|
|
7489
|
-
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
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 (
|
|
7498
|
-
|
|
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
|
-
|
|
7502
|
-
|
|
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,102 +7773,401 @@ 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."];
|
|
7788
|
+
return [
|
|
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}.`,
|
|
7791
|
+
`Keeps ${String(result.kept.length)} tables, ${String(rows)} rows: ${kept}.`,
|
|
7792
|
+
left.length === 0 ? "Leaves nothing behind." : `Leaves behind, kept only in the backup: ${left.join("; ")}.`,
|
|
7793
|
+
...newer
|
|
7794
|
+
];
|
|
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
|
+
}
|
|
7799
|
+
function renderFixSchema(result) {
|
|
7800
|
+
switch (result.status) {
|
|
7801
|
+
case "missing":
|
|
7802
|
+
return [`No effort store at ${result.path}: nothing to repair.`];
|
|
7803
|
+
case "current":
|
|
7804
|
+
return [
|
|
7805
|
+
`\u2705 ${result.path} is current: this rafa reads and writes it as it is. Nothing to repair.`,
|
|
7806
|
+
...unknownAdditiveLines(result)
|
|
7807
|
+
];
|
|
7808
|
+
case "behind":
|
|
7809
|
+
return [
|
|
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)
|
|
7812
|
+
];
|
|
7813
|
+
case "would-rebuild":
|
|
7814
|
+
return [
|
|
7815
|
+
...rebuildLines(result),
|
|
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."
|
|
7817
|
+
];
|
|
7818
|
+
case "rebuilt":
|
|
7819
|
+
return [
|
|
7820
|
+
...rebuildLines(result),
|
|
7821
|
+
`\u2705 Rebuilt at this rafa's migrations. The original is kept whole at ${String(result.backupPath)};` + " rename it back to undo."
|
|
7822
|
+
];
|
|
7823
|
+
}
|
|
7824
|
+
}
|
|
7825
|
+
function runFixSchema(context, seams) {
|
|
7826
|
+
expectNoArgument(context.args, FIX_SCHEMA_USAGE);
|
|
7827
|
+
const dryRun = readSwitch(DRY_RUN_FLAG2, context.flags[DRY_RUN_FLAG2], `Usage: ${FIX_SCHEMA_USAGE}`);
|
|
7828
|
+
if (context.project === null)
|
|
7829
|
+
throw new Error(`${COMMAND_NAME5} runs inside a project, and was handed none`);
|
|
7830
|
+
const root = context.project.root;
|
|
7831
|
+
if (!dryRun)
|
|
7832
|
+
refuseLiveLoop(root, seams.isAlive);
|
|
7833
|
+
const now = seams.now ?? (() => new Date);
|
|
7834
|
+
let result;
|
|
7835
|
+
try {
|
|
7836
|
+
result = fixStoreSchema({
|
|
7837
|
+
path: sqliteStorePath(root),
|
|
7838
|
+
dryRun,
|
|
7839
|
+
stamp: fileStamp(now()),
|
|
7840
|
+
now,
|
|
7841
|
+
...seams.identity === undefined ? {} : { identity: seams.identity }
|
|
7842
|
+
});
|
|
7843
|
+
} catch (error) {
|
|
7844
|
+
if (error instanceof RebuildRefusal)
|
|
7845
|
+
throw new CommandExit(1, `\u274C ${COMMAND_NAME5}: ${error.message}`);
|
|
7846
|
+
throw error;
|
|
7847
|
+
}
|
|
7848
|
+
if (context.outputMode === "json") {
|
|
7849
|
+
context.output.result(result);
|
|
7850
|
+
return;
|
|
7851
|
+
}
|
|
7852
|
+
for (const line of renderFixSchema(result))
|
|
7853
|
+
context.output.info(line);
|
|
7854
|
+
}
|
|
7855
|
+
function createFixSchemaCommand(seams = {}) {
|
|
7856
|
+
const command = {
|
|
7857
|
+
name: "effort fix-schema",
|
|
7858
|
+
subject: "effort",
|
|
7859
|
+
action: "fix-schema",
|
|
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.",
|
|
7862
|
+
args: [],
|
|
7863
|
+
flags: [
|
|
7864
|
+
{
|
|
7865
|
+
name: DRY_RUN_FLAG2,
|
|
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.",
|
|
7867
|
+
type: "boolean"
|
|
7868
|
+
}
|
|
7869
|
+
],
|
|
7870
|
+
examples: [
|
|
7871
|
+
{
|
|
7872
|
+
cmd: "rafa effort fix-schema --dry-run",
|
|
7873
|
+
note: "Prints what a rebuild would keep and leave behind, changing nothing."
|
|
7874
|
+
},
|
|
7875
|
+
{
|
|
7876
|
+
cmd: "rafa effort fix-schema",
|
|
7877
|
+
note: "Swaps the rebuild in and prints where the original was kept."
|
|
7878
|
+
}
|
|
7879
|
+
],
|
|
7880
|
+
outputs: ["text", "json"],
|
|
7881
|
+
run: async (context) => {
|
|
7882
|
+
runFixSchema(context, seams);
|
|
7883
|
+
await Promise.resolve();
|
|
7884
|
+
}
|
|
7885
|
+
};
|
|
7886
|
+
return Object.freeze(command);
|
|
7887
|
+
}
|
|
7888
|
+
var fix_schema_default = createFixSchemaCommand();
|
|
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.`;
|
|
7531
8077
|
return [
|
|
7532
|
-
|
|
7533
|
-
`
|
|
7534
|
-
left.length === 0 ? "Leaves nothing behind." : `Leaves behind, kept only in the backup: ${left.join("; ")}.`
|
|
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.`
|
|
7535
8080
|
];
|
|
7536
8081
|
}
|
|
7537
|
-
function
|
|
8082
|
+
function renderMigrate(result) {
|
|
7538
8083
|
switch (result.status) {
|
|
7539
8084
|
case "missing":
|
|
7540
|
-
return [`No effort store at ${result.path}: nothing to
|
|
8085
|
+
return [`No effort store at ${result.path}: nothing to migrate.`];
|
|
7541
8086
|
case "current":
|
|
7542
|
-
return [`\u2705 ${result.path} is
|
|
7543
|
-
case "
|
|
7544
|
-
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."
|
|
7546
|
-
];
|
|
7547
|
-
case "would-rebuild":
|
|
8087
|
+
return [`\u2705 ${result.path} is current: no migration is pending. Nothing to migrate.`];
|
|
8088
|
+
case "would-migrate":
|
|
7548
8089
|
return [
|
|
7549
|
-
...
|
|
7550
|
-
|
|
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."
|
|
7551
8092
|
];
|
|
7552
|
-
case "
|
|
8093
|
+
case "migrated":
|
|
7553
8094
|
return [
|
|
7554
|
-
...
|
|
7555
|
-
`\u2705
|
|
8095
|
+
...builtLines(result),
|
|
8096
|
+
`\u2705 Migrated. The original is kept whole at ${String(result.backupPath)}; rename it back to undo.`
|
|
7556
8097
|
];
|
|
7557
8098
|
}
|
|
7558
8099
|
}
|
|
7559
|
-
function
|
|
7560
|
-
|
|
7561
|
-
|
|
7562
|
-
|
|
7563
|
-
throw new
|
|
7564
|
-
|
|
7565
|
-
|
|
7566
|
-
|
|
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);
|
|
7567
8112
|
let result;
|
|
7568
8113
|
try {
|
|
7569
|
-
result =
|
|
7570
|
-
path
|
|
8114
|
+
result = migrateStore({
|
|
8115
|
+
path,
|
|
7571
8116
|
dryRun,
|
|
7572
|
-
stamp: fileStamp(
|
|
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 }
|
|
7573
8123
|
});
|
|
7574
8124
|
} catch (error) {
|
|
7575
|
-
if (error instanceof
|
|
7576
|
-
throw new CommandExit(
|
|
8125
|
+
if (error instanceof RebuildRefusal || error instanceof DevelopmentBuildRefusedError) {
|
|
8126
|
+
throw new CommandExit(REFUSED_EXIT2, `\u274C ${COMMAND_NAME7}: ${error.message}`);
|
|
8127
|
+
}
|
|
7577
8128
|
throw error;
|
|
7578
8129
|
}
|
|
7579
8130
|
if (context.outputMode === "json") {
|
|
7580
8131
|
context.output.result(result);
|
|
7581
8132
|
return;
|
|
7582
8133
|
}
|
|
7583
|
-
for (const line of
|
|
8134
|
+
for (const line of renderMigrate(result))
|
|
7584
8135
|
context.output.info(line);
|
|
7585
8136
|
}
|
|
7586
|
-
function
|
|
8137
|
+
function createMigrateCommand(seams = {}) {
|
|
7587
8138
|
const command = {
|
|
7588
|
-
name: "effort
|
|
8139
|
+
name: "effort migrate",
|
|
7589
8140
|
subject: "effort",
|
|
7590
|
-
action: "
|
|
7591
|
-
summary: "
|
|
7592
|
-
description: "
|
|
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.",
|
|
7593
8144
|
args: [],
|
|
7594
8145
|
flags: [
|
|
7595
8146
|
{
|
|
7596
|
-
name:
|
|
7597
|
-
description: "Build and check the
|
|
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.",
|
|
7598
8149
|
type: "boolean"
|
|
7599
8150
|
}
|
|
7600
8151
|
],
|
|
7601
8152
|
examples: [
|
|
7602
8153
|
{
|
|
7603
|
-
cmd: "rafa effort
|
|
7604
|
-
note: "Prints what
|
|
8154
|
+
cmd: "rafa effort migrate --dry-run",
|
|
8155
|
+
note: "Prints what the migration applies and how it checked, changing nothing."
|
|
7605
8156
|
},
|
|
7606
8157
|
{
|
|
7607
|
-
cmd: "rafa effort
|
|
7608
|
-
note: "Swaps the
|
|
8158
|
+
cmd: "rafa effort migrate",
|
|
8159
|
+
note: "Swaps the migrated store in and prints where the original was kept."
|
|
7609
8160
|
}
|
|
7610
8161
|
],
|
|
7611
8162
|
outputs: ["text", "json"],
|
|
7612
8163
|
run: async (context) => {
|
|
7613
|
-
|
|
8164
|
+
runMigrate(context, seams);
|
|
7614
8165
|
await Promise.resolve();
|
|
7615
8166
|
}
|
|
7616
8167
|
};
|
|
7617
8168
|
return Object.freeze(command);
|
|
7618
8169
|
}
|
|
7619
|
-
var
|
|
8170
|
+
var migrate_default = createMigrateCommand();
|
|
7620
8171
|
|
|
7621
8172
|
// src/commands/effort/report.ts
|
|
7622
8173
|
var report_default = wrapPhaseZeroCommand({
|
|
@@ -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
|
|
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,
|
|
8724
|
+
const to = readChoiceFlag(flags, TO_FLAG2, action.targets, action.usage);
|
|
8067
8725
|
if (to === undefined)
|
|
8068
|
-
throw lineRefusal(`--${
|
|
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)} --${
|
|
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,
|
|
8926
|
+
const target = readTextFlag(flags, TO_FLAG2, USAGE8);
|
|
8269
8927
|
if (target === undefined)
|
|
8270
|
-
throw lineRefusal(`--${
|
|
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:
|
|
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
|
|
9616
|
+
import { join as join25 } from "path";
|
|
8959
9617
|
|
|
8960
9618
|
// src/board/epic-template.ts
|
|
8961
|
-
import { existsSync as
|
|
8962
|
-
import { dirname as dirname3, join as
|
|
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
|
|
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 (!
|
|
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
|
|
9070
|
-
import { dirname as dirname4, join as
|
|
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 [
|
|
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) =>
|
|
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
|
|
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
|
|
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
|
|
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:
|
|
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:
|
|
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
|
|
10736
|
+
import { existsSync as existsSync13, readFileSync as readFileSync14 } from "fs";
|
|
10079
10737
|
import { homedir } from "os";
|
|
10080
|
-
import { join as
|
|
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
|
|
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 =
|
|
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 :
|
|
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 =
|
|
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
|
|
10142
|
-
import { dirname as dirname5, isAbsolute as isAbsolute6, join as
|
|
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
|
|
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 (!
|
|
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 =
|
|
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 ${
|
|
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 =
|
|
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
|
-
|
|
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
|
|
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
|
|
10299
|
-
import { isAbsolute as isAbsolute7, join as
|
|
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) =>
|
|
10321
|
-
assertWritable: (path) => accessSync(path,
|
|
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
|
|
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 ??
|
|
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(
|
|
10433
|
-
const files = MONOREPO_MARKER_FILES.filter((name2) => fs.exists(
|
|
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 =
|
|
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
|
|
10545
|
-
import { join as
|
|
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) =>
|
|
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
|
|
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
|
|
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) && !
|
|
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 (
|
|
11341
|
+
if (existsSync12(path))
|
|
10680
11342
|
return { path, kind: "directory", change: "unchanged" };
|
|
10681
11343
|
attempt2(path, "cannot be created", () => {
|
|
10682
|
-
|
|
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 (
|
|
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 =
|
|
11665
|
+
const file = join30(root, GITIGNORE_FILE);
|
|
11004
11666
|
try {
|
|
11005
|
-
const text =
|
|
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 =
|
|
11037
|
-
const digestExisted =
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
11885
|
-
import { join as
|
|
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 =
|
|
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) =>
|
|
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(
|
|
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) => ` ${
|
|
12589
|
+
...names.map((name2) => ` ${join31(specsDir, name2)}`)
|
|
11928
12590
|
].join(`
|
|
11929
12591
|
`));
|
|
11930
12592
|
}
|
|
11931
|
-
const path =
|
|
11932
|
-
return { path, absolute:
|
|
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
|
|
12864
|
+
var USAGE18 = "rafa issue move <id> <state>";
|
|
12471
12865
|
async function moveIssue(context, seams) {
|
|
12472
|
-
const [id2, word] = expectTwoArguments(context.args,
|
|
12473
|
-
const state = readChoice(word, "The state", ISSUE_STATES,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
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
|
|
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,
|
|
13009
|
-
const { root, record } = pickSession(context,
|
|
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
|
|
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,
|
|
13058
|
-
const { root, record } = pickSession(context,
|
|
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
|
|
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,
|
|
13235
|
-
const { root, record } = pickSession(context,
|
|
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
|
|
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,
|
|
13342
|
-
const { root, record } = pickSession(context,
|
|
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
|
|
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: ${
|
|
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
|
|
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,
|
|
13843
|
+
expectNoArgument(context.args, USAGE26);
|
|
13450
13844
|
const { project } = context;
|
|
13451
13845
|
if (project === null)
|
|
13452
|
-
throw new CommandExit(1, `\u274C ${
|
|
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
|
|
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} --${
|
|
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[
|
|
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(`--${
|
|
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:
|
|
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
|
|
14325
|
+
import { resolve as resolve12 } from "path";
|
|
13932
14326
|
var COMMAND2 = "rafa plan risk";
|
|
13933
|
-
var
|
|
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([
|
|
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: ${
|
|
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,
|
|
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
|
|
14043
|
-
var
|
|
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 =
|
|
14454
|
+
const file = join32(plans.path, name2);
|
|
14061
14455
|
if (!isFile(file))
|
|
14062
|
-
throw new CommandExit(1, missingText(stub, tracker,
|
|
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 =
|
|
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,
|
|
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
|
|
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,
|
|
14704
|
+
expectNoArguments(context.args, USAGE29);
|
|
14311
14705
|
const pr = openPrContext(context, seams);
|
|
14312
|
-
const pick = await pickPullRequest(pr, null,
|
|
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
|
|
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,
|
|
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
|
|
14507
|
-
import { join as
|
|
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
|
|
14513
|
-
existsSync as
|
|
14514
|
-
mkdirSync as
|
|
14515
|
-
readdirSync as
|
|
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
|
|
14520
|
-
statSync as
|
|
14913
|
+
rmSync as rmSync4,
|
|
14914
|
+
statSync as statSync11,
|
|
14521
14915
|
symlinkSync
|
|
14522
14916
|
} from "fs";
|
|
14523
|
-
import { basename as
|
|
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 =
|
|
14585
|
-
return entryNames(dir).filter((entry) => TRACKER_NAME.test(entry)).sort((a, b) => a.localeCompare(b)).map((entry) =>
|
|
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(
|
|
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(
|
|
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(`${
|
|
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
|
-
|
|
15000
|
+
mkdirSync6(to, { recursive: true });
|
|
14607
15001
|
let copied = 0;
|
|
14608
|
-
const entries =
|
|
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 =
|
|
14611
|
-
const target =
|
|
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
|
-
|
|
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
|
-
|
|
15025
|
+
rmSync4(staging, { recursive: true, force: true });
|
|
14632
15026
|
throw err;
|
|
14633
15027
|
}
|
|
14634
15028
|
}
|
|
14635
15029
|
function swapIn2(staging, to) {
|
|
14636
|
-
if (!
|
|
15030
|
+
if (!existsSync14(to)) {
|
|
14637
15031
|
renameSync2(staging, to);
|
|
14638
15032
|
return;
|
|
14639
15033
|
}
|
|
14640
15034
|
const outgoing = `${temporaryName(to)}.outgoing`;
|
|
14641
|
-
|
|
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
|
-
|
|
15043
|
+
rmSync4(outgoing, { recursive: true, force: true });
|
|
14650
15044
|
}
|
|
14651
15045
|
function relink(linkPath, target) {
|
|
14652
|
-
|
|
15046
|
+
mkdirSync6(dirname6(linkPath), { recursive: true });
|
|
14653
15047
|
const temporary = temporaryName(linkPath);
|
|
14654
|
-
|
|
15048
|
+
rmSync4(temporary, { force: true });
|
|
14655
15049
|
try {
|
|
14656
15050
|
symlinkSync(target, temporary);
|
|
14657
15051
|
renameSync2(temporary, linkPath);
|
|
14658
15052
|
} catch (err) {
|
|
14659
|
-
|
|
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 =
|
|
14707
|
-
const linkPath =
|
|
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", () =>
|
|
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 =
|
|
14718
|
-
const runtimeDir =
|
|
14719
|
-
const installed =
|
|
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 (!
|
|
14728
|
-
throw new StageFailure("build", `the build exited 0 and left no ${
|
|
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,
|
|
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
|
|
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
|
-
|
|
14759
|
-
chmodSync(temporary,
|
|
15152
|
+
copyFileSync4(source, temporary);
|
|
15153
|
+
chmodSync(temporary, statSync11(source).mode & 4095);
|
|
14760
15154
|
renameSync2(temporary, target);
|
|
14761
15155
|
} catch (err) {
|
|
14762
|
-
|
|
15156
|
+
rmSync4(temporary, { force: true });
|
|
14763
15157
|
throw err;
|
|
14764
15158
|
}
|
|
14765
15159
|
}
|
|
14766
15160
|
function temporaryName(path) {
|
|
14767
|
-
return
|
|
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
|
|
15467
|
+
var USAGE31 = PR_USAGE.merge;
|
|
15074
15468
|
var REMOTE3 = "origin";
|
|
15075
15469
|
var INDENT4 = " ";
|
|
15076
15470
|
var YES_ANSWERS4 = ["y", "yes"];
|
|
@@ -15149,6 +15543,16 @@ function remoteHoldsBranch(git, branch, warn3) {
|
|
|
15149
15543
|
warn3(`${REMOTE3} could not be asked whether it still holds ${branch}, so it is left alone: ${gitSaid(probe2)}`);
|
|
15150
15544
|
return false;
|
|
15151
15545
|
}
|
|
15546
|
+
function localHoldsBranch(git, branch, warn3) {
|
|
15547
|
+
const probe2 = git(["show-ref", "--verify", "--quiet", `refs/heads/${branch}`]);
|
|
15548
|
+
if (probe2.ok)
|
|
15549
|
+
return true;
|
|
15550
|
+
const said = gitSaid(probe2);
|
|
15551
|
+
if (said === "")
|
|
15552
|
+
return false;
|
|
15553
|
+
warn3(`could not read whether this checkout holds a local branch ${branch}, so its delete runs: ${said}`);
|
|
15554
|
+
return true;
|
|
15555
|
+
}
|
|
15152
15556
|
function textOf(path) {
|
|
15153
15557
|
try {
|
|
15154
15558
|
return readFileSync19(path, "utf8");
|
|
@@ -15157,7 +15561,7 @@ function textOf(path) {
|
|
|
15157
15561
|
}
|
|
15158
15562
|
}
|
|
15159
15563
|
function followUpsFor(pr, git) {
|
|
15160
|
-
const facts = readPackageFacts(textOf(
|
|
15564
|
+
const facts = readPackageFacts(textOf(join34(pr.project.root, "package.json")));
|
|
15161
15565
|
if (facts.version === null)
|
|
15162
15566
|
return [];
|
|
15163
15567
|
const tags = git(["tag", "--list", versionTag(facts.version)]);
|
|
@@ -15165,7 +15569,7 @@ function followUpsFor(pr, git) {
|
|
|
15165
15569
|
version: facts.version,
|
|
15166
15570
|
tagged: tags.ok && tags.stdout.trim() !== "",
|
|
15167
15571
|
rafaCheckout: facts.rafaCheckout,
|
|
15168
|
-
runtimeInstalled:
|
|
15572
|
+
runtimeInstalled: existsSync15(join34(pr.project.home, RUNTIME_SUBDIR, facts.version))
|
|
15169
15573
|
});
|
|
15170
15574
|
}
|
|
15171
15575
|
function openGh(pr, seams) {
|
|
@@ -15226,25 +15630,34 @@ async function reportTick(context, pr, seams, detail) {
|
|
|
15226
15630
|
}
|
|
15227
15631
|
return tick;
|
|
15228
15632
|
}
|
|
15229
|
-
function readyLine(base, branch,
|
|
15230
|
-
const
|
|
15231
|
-
|
|
15633
|
+
function readyLine(base, branch, present) {
|
|
15634
|
+
const pulled = `${base} is checked out and pulled`;
|
|
15635
|
+
if (present.local) {
|
|
15636
|
+
return present.remote ? `${pulled}, and ${branch} is gone locally and on ${REMOTE3}.` : `${pulled}, and ${branch} is gone locally; ${REMOTE3} had already deleted it.`;
|
|
15637
|
+
}
|
|
15638
|
+
return present.remote ? `${pulled}, and ${branch} is gone on ${REMOTE3}; there was no local branch to delete.` : `${pulled}; ${branch} had no local branch, and ${REMOTE3} had already deleted it.`;
|
|
15232
15639
|
}
|
|
15233
15640
|
function quotedLines2(said) {
|
|
15234
15641
|
return said === "" ? [] : said.split(`
|
|
15235
15642
|
`).map((line) => `${INDENT4}${line}`);
|
|
15236
15643
|
}
|
|
15237
|
-
function runCleanUp(context, git, detail,
|
|
15644
|
+
function runCleanUp(context, git, detail, present) {
|
|
15238
15645
|
const steps = cleanUpSteps({
|
|
15239
15646
|
branch: detail.headRefName,
|
|
15240
15647
|
base: detail.baseRefName,
|
|
15241
15648
|
remote: REMOTE3,
|
|
15242
|
-
|
|
15649
|
+
localBranchPresent: present.local,
|
|
15650
|
+
remoteBranchPresent: present.remote
|
|
15243
15651
|
});
|
|
15244
15652
|
const reports = [];
|
|
15245
15653
|
for (const step of steps) {
|
|
15654
|
+
if (step.skip !== undefined) {
|
|
15655
|
+
reports.push({ id: step.id, label: step.label, command: commandLine(step), ok: true, skipped: true });
|
|
15656
|
+
context.output.info(`${step.label}: skipped \u2014 ${step.skip}`);
|
|
15657
|
+
continue;
|
|
15658
|
+
}
|
|
15246
15659
|
const result = git(step.argv.slice(1));
|
|
15247
|
-
reports.push({ id: step.id, label: step.label, command: commandLine(step), ok: result.ok });
|
|
15660
|
+
reports.push({ id: step.id, label: step.label, command: commandLine(step), ok: result.ok, skipped: false });
|
|
15248
15661
|
if (result.ok) {
|
|
15249
15662
|
context.output.info(`${step.label}: done`);
|
|
15250
15663
|
continue;
|
|
@@ -15287,17 +15700,17 @@ async function commentIfUnchecked(context, pr, number, unchecked) {
|
|
|
15287
15700
|
return posted.url;
|
|
15288
15701
|
}
|
|
15289
15702
|
async function runMerge(context, seams) {
|
|
15290
|
-
const yes = readBooleanFlag(context.flags, "yes",
|
|
15291
|
-
const skipChecks = readBooleanFlag(context.flags, "skip-checks",
|
|
15292
|
-
const wanted = readMethodFlag(context.flags,
|
|
15293
|
-
const asked = readPullArgument(context.args,
|
|
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);
|
|
15294
15707
|
const pr = openPrContext(context, seams);
|
|
15295
15708
|
const git = (seams.git ?? createGitRunner)(pr.project.root);
|
|
15296
15709
|
const method = wanted ?? pr.mergeMethod;
|
|
15297
|
-
const pick = await pickPullRequest(pr, asked,
|
|
15710
|
+
const pick = await pickPullRequest(pr, asked, USAGE31);
|
|
15298
15711
|
const detail = await onProvider(`read pull request #${pick.number}`, () => pr.pulls.get(pick.number));
|
|
15299
15712
|
if (detail === null) {
|
|
15300
|
-
throw lineRefusal3(`No pull request #${pick.number} at ${pr.project.root}`,
|
|
15713
|
+
throw lineRefusal3(`No pull request #${pick.number} at ${pr.project.root}`, USAGE31);
|
|
15301
15714
|
}
|
|
15302
15715
|
const checks = await onProvider(`read the checks of #${pick.number}`, () => pr.pulls.checks(pick.number));
|
|
15303
15716
|
recordPlanCi({
|
|
@@ -15340,11 +15753,15 @@ async function runMerge(context, seams) {
|
|
|
15340
15753
|
context.output.info(`Merged #${detail.number} into ${detail.baseRefName} (${method}).`);
|
|
15341
15754
|
const commentUrl = await commentIfUnchecked(context, pr, detail.number, answer2.unchecked);
|
|
15342
15755
|
const roadmapTicks = await reportTick(context, pr, seams, detail);
|
|
15343
|
-
const
|
|
15756
|
+
const warn3 = (message) => {
|
|
15344
15757
|
context.output.warn(message);
|
|
15345
|
-
}
|
|
15346
|
-
const
|
|
15347
|
-
|
|
15758
|
+
};
|
|
15759
|
+
const present = {
|
|
15760
|
+
remote: remoteHoldsBranch(git, detail.headRefName, warn3),
|
|
15761
|
+
local: localHoldsBranch(git, detail.headRefName, warn3)
|
|
15762
|
+
};
|
|
15763
|
+
const steps = runCleanUp(context, git, detail, present);
|
|
15764
|
+
context.output.info(readyLine(detail.baseRefName, detail.headRefName, present));
|
|
15348
15765
|
const followUps = followUpsFor(pr, git);
|
|
15349
15766
|
if (followUps.length > 0) {
|
|
15350
15767
|
context.output.info("Follow-ups:");
|
|
@@ -15371,7 +15788,7 @@ function createPrMergeCommand(seams = DEFAULT_MERGE_SEAMS) {
|
|
|
15371
15788
|
subject: "pr",
|
|
15372
15789
|
action: "merge",
|
|
15373
15790
|
summary: "merge a pull request and clean up both branches",
|
|
15374
|
-
description: "Merges one pull request through the GitHub CLI and then, in code, switches to the base branch," + " pulls it fast-forward only, deletes the head branch locally and on the remote where it is still there," + " and prunes, reporting each step. Refuses before it asks anything on a dirty working tree, on a pull" + " request that is not green or does not merge, and on a head branch checked out in another worktree. It" + " shows the pull request, its branches and the method and asks `Merge? [y/N]`; `--yes` skips the question," + " and without a terminal and without `--yes` it refuses. A pull request that reports no checks at all is" + " refused unless `--skip-checks` is given, which is refused on any pull request that does report checks;" + " with it the command reads how many workflows the repository defines, prints a warning for that case, asks" + " `Merge #<n> with no checks? [y/N]` (which `--yes` answers only where the repository defines no workflow)," + " and after the merge posts one comment on the pull request saying so. A step that fails never undoes the merge: it" + " prints what is left as commands to paste and exits 1. After the merge it ticks the `Closes #<n>` line of" + " every issue the pull request closes on every open board whose checklist lists it, or on the roadmap issue while no issue carries `type:roadmap`, warning rather than failing when that write" + ' does not land. It ends by reading every open issue whose "Blocked by:" line names an issue this pull' + " request closes, asking whether to remove `spec:blocked` from each one whose blockers have all closed;" + " `--yes` does not answer that question, and every failure of that reading is a warning. With" + " `--output=json` the pull request, the method, the steps that ran, the follow-ups, the roadmap tick and" + " the unblock reading are the data of the terminal result event. Refuses with exit code 2 where" + " `pr.provider` is not `gh`.",
|
|
15791
|
+
description: "Merges one pull request through the GitHub CLI and then, in code, switches to the base branch," + " pulls it fast-forward only, deletes the head branch locally and on the remote where it is still there," + " and prunes, reporting each step; a head branch this checkout has no local branch for is reported as a" + " skipped local delete, and the rest still run. Refuses before it asks anything on a dirty working tree, on a pull" + " request that is not green or does not merge, and on a head branch checked out in another worktree. It" + " shows the pull request, its branches and the method and asks `Merge? [y/N]`; `--yes` skips the question," + " and without a terminal and without `--yes` it refuses. A pull request that reports no checks at all is" + " refused unless `--skip-checks` is given, which is refused on any pull request that does report checks;" + " with it the command reads how many workflows the repository defines, prints a warning for that case, asks" + " `Merge #<n> with no checks? [y/N]` (which `--yes` answers only where the repository defines no workflow)," + " and after the merge posts one comment on the pull request saying so. A step that fails never undoes the merge: it" + " prints what is left as commands to paste and exits 1. After the merge it ticks the `Closes #<n>` line of" + " every issue the pull request closes on every open board whose checklist lists it, or on the roadmap issue while no issue carries `type:roadmap`, warning rather than failing when that write" + ' does not land. It ends by reading every open issue whose "Blocked by:" line names an issue this pull' + " request closes, asking whether to remove `spec:blocked` from each one whose blockers have all closed;" + " `--yes` does not answer that question, and every failure of that reading is a warning. With" + " `--output=json` the pull request, the method, the steps that ran, the follow-ups, the roadmap tick and" + " the unblock reading are the data of the terminal result event. Refuses with exit code 2 where" + " `pr.provider` is not `gh`.",
|
|
15375
15792
|
args: [
|
|
15376
15793
|
{
|
|
15377
15794
|
name: "n",
|
|
@@ -15864,7 +16281,7 @@ function readLastTriage(comments) {
|
|
|
15864
16281
|
}
|
|
15865
16282
|
|
|
15866
16283
|
// src/commands/pr/show.ts
|
|
15867
|
-
var
|
|
16284
|
+
var USAGE32 = PR_USAGE.show;
|
|
15868
16285
|
var INDENT5 = " ";
|
|
15869
16286
|
var SHORT_SHA = 7;
|
|
15870
16287
|
function headLine3(detail) {
|
|
@@ -15962,12 +16379,12 @@ async function probe2(call) {
|
|
|
15962
16379
|
}
|
|
15963
16380
|
}
|
|
15964
16381
|
async function readShow(context, seams) {
|
|
15965
|
-
const asked = readPullArgument(context.args,
|
|
16382
|
+
const asked = readPullArgument(context.args, USAGE32);
|
|
15966
16383
|
const pr = openPrContext(context, seams);
|
|
15967
|
-
const pick = await pickPullRequest(pr, asked,
|
|
16384
|
+
const pick = await pickPullRequest(pr, asked, USAGE32);
|
|
15968
16385
|
const detail = await onProvider(`read pull request #${pick.number}`, () => pr.pulls.get(pick.number));
|
|
15969
16386
|
if (detail === null) {
|
|
15970
|
-
throw lineRefusal3(`No pull request #${pick.number} in the repository at ${pr.project.root}`,
|
|
16387
|
+
throw lineRefusal3(`No pull request #${pick.number} in the repository at ${pr.project.root}`, USAGE32);
|
|
15971
16388
|
}
|
|
15972
16389
|
const checks = await probe2(() => pr.pulls.checks(pick.number));
|
|
15973
16390
|
const comments = await probe2(() => pr.pulls.comments(pick.number));
|
|
@@ -16390,8 +16807,8 @@ function describeAge(updatedAt, now) {
|
|
|
16390
16807
|
return `${plural4(hours, "hour", "hours")} ago`;
|
|
16391
16808
|
return `${plural4(Math.floor(age / DAY_MS2), "day", "days")} ago`;
|
|
16392
16809
|
}
|
|
16393
|
-
function triageCommand(number,
|
|
16394
|
-
return
|
|
16810
|
+
function triageCommand(number, resolve14 = false) {
|
|
16811
|
+
return resolve14 ? `rafa pr triage ${number} --resolve` : `rafa pr triage ${number}`;
|
|
16395
16812
|
}
|
|
16396
16813
|
function joinNumbers(pulls) {
|
|
16397
16814
|
const names = pulls.map((pull) => `#${pull.number}`);
|
|
@@ -16402,14 +16819,14 @@ function joinNumbers(pulls) {
|
|
|
16402
16819
|
function byNumber(pulls) {
|
|
16403
16820
|
return Object.freeze([...pulls].sort((left, right) => left.number - right.number));
|
|
16404
16821
|
}
|
|
16405
|
-
function skipped(pull, now,
|
|
16822
|
+
function skipped(pull, now, resolve14) {
|
|
16406
16823
|
return Object.freeze({
|
|
16407
16824
|
number: pull.number,
|
|
16408
16825
|
title: pull.title,
|
|
16409
16826
|
updatedAt: pull.updatedAt,
|
|
16410
16827
|
age: describeAge(pull.updatedAt, now),
|
|
16411
16828
|
fresh: isFreshCandidate(pull.updatedAt, now),
|
|
16412
|
-
command: triageCommand(pull.number,
|
|
16829
|
+
command: triageCommand(pull.number, resolve14)
|
|
16413
16830
|
});
|
|
16414
16831
|
}
|
|
16415
16832
|
function skippedLine(one) {
|
|
@@ -16453,15 +16870,15 @@ function listReading(rest, count2) {
|
|
|
16453
16870
|
return listing("list", Object.freeze([]), rest, headline);
|
|
16454
16871
|
}
|
|
16455
16872
|
function selectTriagePullRequests(input) {
|
|
16456
|
-
const
|
|
16873
|
+
const resolve14 = input.resolve === true;
|
|
16457
16874
|
const { current, now } = input;
|
|
16458
16875
|
if (current !== null) {
|
|
16459
16876
|
return plain("branch", Object.freeze([current]), `assessing #${current.number}, the pull request open on ${current.headRefName}`);
|
|
16460
16877
|
}
|
|
16461
16878
|
const candidates = byNumber(input.candidates);
|
|
16462
16879
|
const count2 = candidates.length;
|
|
16463
|
-
const lines2 = candidates.map((pull) => skipped(pull, now,
|
|
16464
|
-
if (
|
|
16880
|
+
const lines2 = candidates.map((pull) => skipped(pull, now, resolve14));
|
|
16881
|
+
if (resolve14 && count2 > 1)
|
|
16465
16882
|
return refusal12(lines2, count2);
|
|
16466
16883
|
if (count2 === 0) {
|
|
16467
16884
|
return plain("none", Object.freeze([]), "no red pull request to triage, and no pull request on the current branch");
|
|
@@ -16796,8 +17213,8 @@ function withTaskBudget(plan2, usd) {
|
|
|
16796
17213
|
}
|
|
16797
17214
|
|
|
16798
17215
|
// src/pr/plans/load.ts
|
|
16799
|
-
import { existsSync as
|
|
16800
|
-
import { dirname as dirname7, join as
|
|
17216
|
+
import { existsSync as existsSync16, readFileSync as readFileSync20 } from "fs";
|
|
17217
|
+
import { dirname as dirname7, join as join35 } from "path";
|
|
16801
17218
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
16802
17219
|
var MODULE_DIR4 = dirname7(fileURLToPath4(import.meta.url));
|
|
16803
17220
|
var PINNED_PLANS_DIRNAME = "plans";
|
|
@@ -16814,8 +17231,8 @@ function hasPinnedPlan(triageClass) {
|
|
|
16814
17231
|
function pinnedPlanCandidates(moduleDir, triageClass) {
|
|
16815
17232
|
const name2 = pinnedPlanFileName(triageClass);
|
|
16816
17233
|
return [
|
|
16817
|
-
|
|
16818
|
-
|
|
17234
|
+
join35(moduleDir, PINNED_PLANS_DIRNAME, name2),
|
|
17235
|
+
join35(moduleDir, name2)
|
|
16819
17236
|
];
|
|
16820
17237
|
}
|
|
16821
17238
|
function readPinnedPlan(triageClass, lookup = {}) {
|
|
@@ -16823,7 +17240,7 @@ function readPinnedPlan(triageClass, lookup = {}) {
|
|
|
16823
17240
|
throw new Error(`No pinned resolve plan ships for ${triageClass}; ` + `one ships for ${PINNED_PLAN_CLASSES.join(", ")}`);
|
|
16824
17241
|
}
|
|
16825
17242
|
const candidates = pinnedPlanCandidates(lookup.moduleDir ?? MODULE_DIR4, triageClass);
|
|
16826
|
-
const found = candidates.find((candidate) =>
|
|
17243
|
+
const found = candidates.find((candidate) => existsSync16(candidate));
|
|
16827
17244
|
if (found === undefined) {
|
|
16828
17245
|
throw new Error(`The pinned resolve plan for ${triageClass} is missing: ` + `no file at ${candidates.join(" or ")}`);
|
|
16829
17246
|
}
|
|
@@ -16967,20 +17384,20 @@ function readAttemptRepeat(input) {
|
|
|
16967
17384
|
}
|
|
16968
17385
|
|
|
16969
17386
|
// src/commands/pr/resolve-loop.ts
|
|
16970
|
-
import { mkdirSync as
|
|
16971
|
-
import { join as
|
|
16972
|
-
var RESOLVE_SUBDIR =
|
|
17387
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
17388
|
+
import { join as join36 } from "path";
|
|
17389
|
+
var RESOLVE_SUBDIR = join36(".rafa", "resolve");
|
|
16973
17390
|
var RUN_PREFIX = "pr-";
|
|
16974
17391
|
var ATTEMPT_PREFIX = "attempt-";
|
|
16975
17392
|
var LOOP_WORDS = Object.freeze(["loop", "start"]);
|
|
16976
17393
|
var CHILD_OUTPUT = "text";
|
|
16977
17394
|
function resolveRunDir(home, number, attempt3) {
|
|
16978
|
-
return
|
|
17395
|
+
return join36(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt3}`);
|
|
16979
17396
|
}
|
|
16980
17397
|
function writeResolvePlan(write) {
|
|
16981
17398
|
const dir = resolveRunDir(write.home, write.number, write.attempt);
|
|
16982
|
-
|
|
16983
|
-
const path =
|
|
17399
|
+
mkdirSync7(dir, { recursive: true });
|
|
17400
|
+
const path = join36(dir, pinnedPlanFileName(write.triageClass));
|
|
16984
17401
|
writeFileSync5(path, write.plan, "utf8");
|
|
16985
17402
|
return path;
|
|
16986
17403
|
}
|
|
@@ -17339,7 +17756,7 @@ async function runAttempts(run, detail, first) {
|
|
|
17339
17756
|
}
|
|
17340
17757
|
|
|
17341
17758
|
// src/commands/pr/triage.ts
|
|
17342
|
-
var
|
|
17759
|
+
var USAGE33 = PR_USAGE.triage;
|
|
17343
17760
|
var DEFAULT_MAX_ATTEMPTS = 2;
|
|
17344
17761
|
var DETACHED_HEAD2 = "HEAD";
|
|
17345
17762
|
var RESOLVED = false;
|
|
@@ -17380,22 +17797,22 @@ async function redPullRequests(pr) {
|
|
|
17380
17797
|
}
|
|
17381
17798
|
return red;
|
|
17382
17799
|
}
|
|
17383
|
-
async function selectTargets(pr, warn3, now,
|
|
17800
|
+
async function selectTargets(pr, warn3, now, resolve14) {
|
|
17384
17801
|
const branch = readBranchOrNone(pr, warn3);
|
|
17385
17802
|
const current = branch === null ? null : await onProvider(`read the open pull request for the branch "${branch}"`, () => pr.pulls.findOpen(branch));
|
|
17386
17803
|
if (current !== null)
|
|
17387
|
-
return selectTriagePullRequests({ current, candidates: [], now, resolve:
|
|
17804
|
+
return selectTriagePullRequests({ current, candidates: [], now, resolve: resolve14 });
|
|
17388
17805
|
return selectTriagePullRequests({
|
|
17389
17806
|
current: null,
|
|
17390
17807
|
candidates: await redPullRequests(pr),
|
|
17391
17808
|
now,
|
|
17392
|
-
resolve:
|
|
17809
|
+
resolve: resolve14
|
|
17393
17810
|
});
|
|
17394
17811
|
}
|
|
17395
17812
|
async function detailOf4(pr, number) {
|
|
17396
17813
|
const detail = await onProvider(`read pull request #${number}`, () => pr.pulls.get(number));
|
|
17397
17814
|
if (detail === null) {
|
|
17398
|
-
throw lineRefusal3(`No pull request #${number} in the repository at ${pr.project.root}`,
|
|
17815
|
+
throw lineRefusal3(`No pull request #${number} in the repository at ${pr.project.root}`, USAGE33);
|
|
17399
17816
|
}
|
|
17400
17817
|
return detail;
|
|
17401
17818
|
}
|
|
@@ -17498,14 +17915,14 @@ async function assessOne(options) {
|
|
|
17498
17915
|
function refuseResolveWithoutComment(wantsResolve, wantsComment) {
|
|
17499
17916
|
if (!wantsResolve || wantsComment)
|
|
17500
17917
|
return;
|
|
17501
|
-
throw lineRefusal3("--resolve writes the attempt count into the triage comment, and --no-comment writes none;" + " run one or the other",
|
|
17918
|
+
throw lineRefusal3("--resolve writes the attempt count into the triage comment, and --no-comment writes none;" + " run one or the other", USAGE33);
|
|
17502
17919
|
}
|
|
17503
17920
|
async function runTriage(context, seams) {
|
|
17504
|
-
const wantsComment = readBooleanFlag(context.flags, "comment",
|
|
17505
|
-
const wantsResolve = readBooleanFlag(context.flags, "resolve",
|
|
17506
|
-
const maxAttempts = readMaxAttempts(context.flags,
|
|
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);
|
|
17507
17924
|
refuseResolveWithoutComment(wantsResolve, wantsComment);
|
|
17508
|
-
const asked = readPullArgument(context.args,
|
|
17925
|
+
const asked = readPullArgument(context.args, USAGE33);
|
|
17509
17926
|
const pr = openPrContext(context, seams);
|
|
17510
17927
|
const git = (seams.git ?? createGitRunner)(pr.project.root);
|
|
17511
17928
|
const permissions = (seams.permissions ?? ghPermissionsIn)(pr.project.root);
|
|
@@ -17518,7 +17935,7 @@ async function runTriage(context, seams) {
|
|
|
17518
17935
|
}
|
|
17519
17936
|
const numbers = asked === null ? (selection?.assess ?? []).map((one) => one.number) : [asked];
|
|
17520
17937
|
const readings = [];
|
|
17521
|
-
let
|
|
17938
|
+
let resolve14 = null;
|
|
17522
17939
|
for (const number of numbers) {
|
|
17523
17940
|
const assess = () => assessOne({
|
|
17524
17941
|
pr,
|
|
@@ -17537,7 +17954,7 @@ async function runTriage(context, seams) {
|
|
|
17537
17954
|
readings.push(reading);
|
|
17538
17955
|
continue;
|
|
17539
17956
|
}
|
|
17540
|
-
|
|
17957
|
+
resolve14 = await resolvePullRequest({
|
|
17541
17958
|
trust: {
|
|
17542
17959
|
permissions,
|
|
17543
17960
|
trustedAuthors: pr.trustedAuthors,
|
|
@@ -17558,16 +17975,16 @@ async function runTriage(context, seams) {
|
|
|
17558
17975
|
...seams.clock === undefined ? {} : { clock: seams.clock },
|
|
17559
17976
|
...seams.sleep === undefined ? {} : { sleep: seams.sleep }
|
|
17560
17977
|
});
|
|
17561
|
-
readings.push(
|
|
17978
|
+
readings.push(resolve14.reading);
|
|
17562
17979
|
}
|
|
17563
17980
|
const text = [
|
|
17564
17981
|
...selection === null ? [] : [selection.message],
|
|
17565
17982
|
...readings.length === 0 ? [] : [renderTriages(readings)],
|
|
17566
|
-
...
|
|
17983
|
+
...resolve14 === null ? [] : resolve14.lines
|
|
17567
17984
|
].join(`
|
|
17568
17985
|
|
|
17569
17986
|
`);
|
|
17570
|
-
return { selection, readings, resolve:
|
|
17987
|
+
return { selection, readings, resolve: resolve14, text, exitCode: resolve14?.exitCode ?? 0 };
|
|
17571
17988
|
}
|
|
17572
17989
|
function createPrTriageCommand(seams = DEFAULT_TRIAGE_SEAMS) {
|
|
17573
17990
|
const command = {
|
|
@@ -17641,7 +18058,7 @@ function createPrTriageCommand(seams = DEFAULT_TRIAGE_SEAMS) {
|
|
|
17641
18058
|
var triage_default = createPrTriageCommand();
|
|
17642
18059
|
|
|
17643
18060
|
// src/commands/pr/view.ts
|
|
17644
|
-
var
|
|
18061
|
+
var USAGE34 = PR_USAGE.view;
|
|
17645
18062
|
function trimmedOrNull3(value) {
|
|
17646
18063
|
const trimmed = value.trim();
|
|
17647
18064
|
return trimmed === "" ? null : trimmed;
|
|
@@ -17651,9 +18068,9 @@ function renderView(number, url) {
|
|
|
17651
18068
|
return url === null ? opened : `${opened}${SEPARATOR2}${url}`;
|
|
17652
18069
|
}
|
|
17653
18070
|
async function runView2(context, seams) {
|
|
17654
|
-
const asked = readPullArgument(context.args,
|
|
18071
|
+
const asked = readPullArgument(context.args, USAGE34);
|
|
17655
18072
|
const pr = openPrContext(context, seams);
|
|
17656
|
-
const pick = await pickPullRequest(pr, asked,
|
|
18073
|
+
const pick = await pickPullRequest(pr, asked, USAGE34);
|
|
17657
18074
|
await onProvider(`open pull request #${pick.number} in the browser`, () => pr.pulls.browse(pick.number));
|
|
17658
18075
|
const url = trimmedOrNull3(pick.summary?.url ?? "");
|
|
17659
18076
|
return {
|
|
@@ -17708,7 +18125,7 @@ function createPrViewCommand(seams = DEFAULT_PR_SEAMS) {
|
|
|
17708
18125
|
var view_default = createPrViewCommand();
|
|
17709
18126
|
|
|
17710
18127
|
// src/commands/pr/wait.ts
|
|
17711
|
-
var
|
|
18128
|
+
var USAGE35 = PR_USAGE.wait;
|
|
17712
18129
|
var TIMEOUT_FLAG = "timeout";
|
|
17713
18130
|
var DEFAULT_WAIT_TIMEOUT_MIN = DEFAULT_CI_TIMEOUT_MIN;
|
|
17714
18131
|
var MS_PER_MIN = 60000;
|
|
@@ -17774,10 +18191,10 @@ function renderWait(reading) {
|
|
|
17774
18191
|
`);
|
|
17775
18192
|
}
|
|
17776
18193
|
async function runWait(context, seams) {
|
|
17777
|
-
const minutes = readTimeoutMinutes(context.flags,
|
|
17778
|
-
const asked = readPullArgument(context.args,
|
|
18194
|
+
const minutes = readTimeoutMinutes(context.flags, USAGE35);
|
|
18195
|
+
const asked = readPullArgument(context.args, USAGE35);
|
|
17779
18196
|
const pr = openPrContext(context, seams);
|
|
17780
|
-
const pick = await pickPullRequest(pr, asked,
|
|
18197
|
+
const pick = await pickPullRequest(pr, asked, USAGE35);
|
|
17781
18198
|
const timeoutMs = minutes * MS_PER_MIN;
|
|
17782
18199
|
context.output.info(`Waiting for the checks of #${pick.number}, up to ${minutes} min.`);
|
|
17783
18200
|
const waited = await waitForChecks({
|
|
@@ -17857,12 +18274,51 @@ var wait_default = createPrWaitCommand();
|
|
|
17857
18274
|
|
|
17858
18275
|
// src/commands/release/tag.ts
|
|
17859
18276
|
import { readFileSync as readFileSync21 } from "fs";
|
|
17860
|
-
import { join as
|
|
18277
|
+
import { join as join37 } from "path";
|
|
18278
|
+
|
|
18279
|
+
// src/commands/release/release-commit.ts
|
|
18280
|
+
function unread2(reason, message) {
|
|
18281
|
+
return { commit: null, ahead: 0, problem: { reason, message } };
|
|
18282
|
+
}
|
|
18283
|
+
function versionAt(git, commit, path) {
|
|
18284
|
+
const shown = git(["show", `${commit}:./${path}`]);
|
|
18285
|
+
return shown.ok ? readManifestVersion(shown.stdout) : null;
|
|
18286
|
+
}
|
|
18287
|
+
function readReleaseCommit(git, path, version2) {
|
|
18288
|
+
const log = git(["log", "--first-parent", "--format=%H", "HEAD", "--", `./${path}`]);
|
|
18289
|
+
if (!log.ok)
|
|
18290
|
+
return unread2("git", `the history of ${path} could not be read: ${gitSaid(log)}`);
|
|
18291
|
+
const changed = log.stdout.split(`
|
|
18292
|
+
`).map((line) => line.trim()).filter((line) => line !== "");
|
|
18293
|
+
if (changed.length === 0) {
|
|
18294
|
+
return unread2("version", `no commit on this branch holds ${path}; commit it before tagging`);
|
|
18295
|
+
}
|
|
18296
|
+
let release = null;
|
|
18297
|
+
let newest;
|
|
18298
|
+
for (const commit of changed) {
|
|
18299
|
+
const declared = versionAt(git, commit, path);
|
|
18300
|
+
if (newest === undefined)
|
|
18301
|
+
newest = declared;
|
|
18302
|
+
if (declared !== version2)
|
|
18303
|
+
break;
|
|
18304
|
+
release = commit;
|
|
18305
|
+
}
|
|
18306
|
+
if (release === null) {
|
|
18307
|
+
return unread2("version", `${path} says ${version2}, and the last commit on this branch that changed it` + ` says ${newest ?? "no version"}; commit the version before tagging it`);
|
|
18308
|
+
}
|
|
18309
|
+
const count2 = git(["rev-list", "--count", "--first-parent", `${release}..HEAD`]);
|
|
18310
|
+
if (!count2.ok)
|
|
18311
|
+
return unread2("git", `the commits past ${release} could not be counted: ${gitSaid(count2)}`);
|
|
18312
|
+
return { commit: release, ahead: Number.parseInt(count2.stdout.trim(), 10), problem: null };
|
|
18313
|
+
}
|
|
18314
|
+
|
|
18315
|
+
// src/commands/release/tag.ts
|
|
17861
18316
|
var RELEASE_TAG_USAGE = "rafa release tag";
|
|
17862
18317
|
var DEFAULT_RELEASE_BRANCH = "main";
|
|
17863
18318
|
var DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
17864
18319
|
var RELEASE_REMOTE = "origin";
|
|
17865
18320
|
var INDENT8 = " ";
|
|
18321
|
+
var SHORT_COMMIT = 7;
|
|
17866
18322
|
function readBranch2(git) {
|
|
17867
18323
|
const result = git(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
17868
18324
|
if (!result.ok) {
|
|
@@ -17882,7 +18338,7 @@ function textOf2(path) {
|
|
|
17882
18338
|
}
|
|
17883
18339
|
}
|
|
17884
18340
|
function readVersion(root, configured) {
|
|
17885
|
-
const resolved =
|
|
18341
|
+
const resolved = join37(root, configured);
|
|
17886
18342
|
const text = textOf2(resolved);
|
|
17887
18343
|
if (text === null) {
|
|
17888
18344
|
return {
|
|
@@ -17896,7 +18352,7 @@ function readVersion(root, configured) {
|
|
|
17896
18352
|
return version2 === null ? { path: configured, text, version: null, problem: `${resolved} declares no version this can read` } : { path: configured, text, version: version2, problem: null };
|
|
17897
18353
|
}
|
|
17898
18354
|
function readNewestRelease(root, configured) {
|
|
17899
|
-
const resolved =
|
|
18355
|
+
const resolved = join37(root, configured);
|
|
17900
18356
|
const text = textOf2(resolved);
|
|
17901
18357
|
if (text === null) {
|
|
17902
18358
|
return { path: configured, version: null, problem: `the changelog could not be read at ${resolved}` };
|
|
@@ -17939,18 +18395,22 @@ function readPublishTarget(text) {
|
|
|
17939
18395
|
isPrivate: fields["private"] === true
|
|
17940
18396
|
};
|
|
17941
18397
|
}
|
|
17942
|
-
|
|
18398
|
+
var ON_HEAD = Object.freeze({ ahead: 0, branch: DEFAULT_RELEASE_BRANCH });
|
|
18399
|
+
function followUpsFor2(tag, version2, target, place2 = ON_HEAD) {
|
|
17943
18400
|
const push = {
|
|
17944
18401
|
command: `git push ${RELEASE_REMOTE} ${tag}`,
|
|
17945
18402
|
why: `the tag is local until ${RELEASE_REMOTE} has it`
|
|
17946
18403
|
};
|
|
17947
18404
|
if (target.name === null || target.isPrivate)
|
|
17948
18405
|
return [push];
|
|
18406
|
+
const publishes = `publishes ${target.name}@${version2} to ${target.registry}`;
|
|
18407
|
+
if (place2.ahead === 0)
|
|
18408
|
+
return [push, { command: `${target.manager} publish`, why: publishes }];
|
|
17949
18409
|
return [
|
|
17950
18410
|
push,
|
|
17951
18411
|
{
|
|
17952
|
-
command:
|
|
17953
|
-
why:
|
|
18412
|
+
command: `git switch --detach ${tag} && ${target.manager} publish && git switch ${place2.branch}`,
|
|
18413
|
+
why: `${publishes} from the tagged commit, not HEAD; skip it if ${target.registry} has that version already`
|
|
17954
18414
|
}
|
|
17955
18415
|
];
|
|
17956
18416
|
}
|
|
@@ -17990,7 +18450,14 @@ function decideTag(inputs) {
|
|
|
17990
18450
|
const disagrees = changelogRefusal(inputs, version2);
|
|
17991
18451
|
if (disagrees !== null)
|
|
17992
18452
|
return disagrees;
|
|
17993
|
-
|
|
18453
|
+
const release = inputs.release;
|
|
18454
|
+
if (release === null)
|
|
18455
|
+
return refuse2("version", `${inputs.version.path} was not looked up in the history`);
|
|
18456
|
+
if (release.problem !== null)
|
|
18457
|
+
return refuse2(release.problem.reason, release.problem.message);
|
|
18458
|
+
if (release.commit === null)
|
|
18459
|
+
return refuse2("git", `no commit could be named for ${version2}`);
|
|
18460
|
+
return { kind: "ready", version: version2, tag: versionTag(version2), commit: release.commit, ahead: release.ahead };
|
|
17994
18461
|
}
|
|
17995
18462
|
function projectOf9(context) {
|
|
17996
18463
|
if (context.project === null)
|
|
@@ -18013,23 +18480,34 @@ function tagConfig(project, warn3) {
|
|
|
18013
18480
|
}
|
|
18014
18481
|
}
|
|
18015
18482
|
function readTagInputs(root, git, config) {
|
|
18483
|
+
const branch = readBranch2(git);
|
|
18484
|
+
const version2 = readVersion(root, config.versionFile);
|
|
18485
|
+
const walkable = version2.version !== null && branch.branch === config.releaseBranch;
|
|
18016
18486
|
return {
|
|
18017
18487
|
releaseBranch: config.releaseBranch,
|
|
18018
|
-
branch
|
|
18019
|
-
version:
|
|
18488
|
+
branch,
|
|
18489
|
+
version: version2,
|
|
18020
18490
|
changelog: readNewestRelease(root, config.changelog),
|
|
18021
|
-
tags: readTags(git)
|
|
18491
|
+
tags: readTags(git),
|
|
18492
|
+
release: walkable && version2.version !== null ? readReleaseCommit(git, config.versionFile, version2.version) : null
|
|
18022
18493
|
};
|
|
18023
18494
|
}
|
|
18024
|
-
function writeTag(git, tag) {
|
|
18025
|
-
const result = git(["tag", tag,
|
|
18495
|
+
function writeTag(git, tag, commit) {
|
|
18496
|
+
const result = git(["tag", tag, commit]);
|
|
18026
18497
|
if (result.ok)
|
|
18027
18498
|
return;
|
|
18028
18499
|
throw new CommandExit(1, `\u274C ${tag} could not be written: ${gitSaid(result)}`);
|
|
18029
18500
|
}
|
|
18501
|
+
function pastReleaseWarning(written, branch) {
|
|
18502
|
+
if (written.ahead === 0)
|
|
18503
|
+
return null;
|
|
18504
|
+
const commits = written.ahead === 1 ? "1 commit" : `${String(written.ahead)} commits`;
|
|
18505
|
+
return `HEAD of ${branch} is ${commits} past ${written.commit.slice(0, SHORT_COMMIT)}, where ${written.version}` + ` was set; they are not in ${written.tag}.`;
|
|
18506
|
+
}
|
|
18030
18507
|
function renderTagged(written, branch, followUps) {
|
|
18508
|
+
const where = written.ahead === 0 ? `the HEAD of ${branch}` : `${written.commit.slice(0, SHORT_COMMIT)}, the commit of ${branch} that set ${written.version}`;
|
|
18031
18509
|
return [
|
|
18032
|
-
`\u2705 Tagged ${written.tag} at
|
|
18510
|
+
`\u2705 Tagged ${written.tag} at ${where}.`,
|
|
18033
18511
|
"Next:",
|
|
18034
18512
|
...followUps.map((followUp2) => `${INDENT8}${followUp2.command} \u2014 ${followUp2.why}`)
|
|
18035
18513
|
];
|
|
@@ -18047,8 +18525,9 @@ function runTag(context, seams = DEFAULT_RELEASE_SEAMS) {
|
|
|
18047
18525
|
throw new CommandExit(1, `\u274C ${decision.message}
|
|
18048
18526
|
Usage: ${RELEASE_TAG_USAGE}`);
|
|
18049
18527
|
}
|
|
18050
|
-
writeTag(git, decision.tag);
|
|
18051
|
-
const
|
|
18528
|
+
writeTag(git, decision.tag, decision.commit);
|
|
18529
|
+
const place2 = { ahead: decision.ahead, branch: config.releaseBranch };
|
|
18530
|
+
const followUps = followUpsFor2(decision.tag, decision.version, readPublishTarget(inputs.version.text), place2);
|
|
18052
18531
|
return { inputs, written: decision, followUps };
|
|
18053
18532
|
}
|
|
18054
18533
|
function createReleaseTagCommand(seams = DEFAULT_RELEASE_SEAMS) {
|
|
@@ -18056,8 +18535,8 @@ function createReleaseTagCommand(seams = DEFAULT_RELEASE_SEAMS) {
|
|
|
18056
18535
|
name: "release tag",
|
|
18057
18536
|
subject: "release",
|
|
18058
18537
|
action: "tag",
|
|
18059
|
-
summary: "tag the
|
|
18060
|
-
description: "Writes `v<version>` on the
|
|
18538
|
+
summary: "tag the commit that set the version both release files agree on",
|
|
18539
|
+
description: "Writes `v<version>` on the release branch \u2014 `pr.base`, or `main` \u2014 when the" + " version `release.versionFile` declares is the one the newest section of `release.changelog` names." + " The tag goes on the commit that set that version: HEAD, or, where later commits landed on the branch," + " the earlier commit, with a warning naming how many commits HEAD is past it." + " It refuses, and writes nothing, when another branch is checked out, when a tag already names that" + " version, when the two files disagree, when no commit holds the version yet, and when the version" + " file, the tag list or the history could not be read. After the tag it prints what to run next: the" + " push that puts the tag on the remote, and the publish line for the registry the version file" + " configures, spelled to publish from the tag when HEAD is past it, which it does not run. With" + " `--output=json` the readings, the decision and the follow-ups are the data of the terminal result" + " event.",
|
|
18061
18540
|
args: [],
|
|
18062
18541
|
flags: [],
|
|
18063
18542
|
examples: [
|
|
@@ -18073,6 +18552,9 @@ function createReleaseTagCommand(seams = DEFAULT_RELEASE_SEAMS) {
|
|
|
18073
18552
|
outputs: ["text", "json"],
|
|
18074
18553
|
run: async (context) => {
|
|
18075
18554
|
const result = runTag(context, seams);
|
|
18555
|
+
const past = pastReleaseWarning(result.written, result.inputs.releaseBranch);
|
|
18556
|
+
if (past !== null)
|
|
18557
|
+
context.output.warn(past);
|
|
18076
18558
|
if (context.outputMode === "json") {
|
|
18077
18559
|
context.output.result(result);
|
|
18078
18560
|
return;
|
|
@@ -18095,7 +18577,7 @@ function createRoadmapCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
18095
18577
|
subject: "roadmap",
|
|
18096
18578
|
action: "roadmap",
|
|
18097
18579
|
summary: "list the Roadmap's lines in its order, with spec, blocked by, has and refs",
|
|
18098
|
-
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
|
|
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.",
|
|
18099
18581
|
args: [],
|
|
18100
18582
|
flags: [...ROADMAP_FLAGS],
|
|
18101
18583
|
examples: [
|
|
@@ -18111,6 +18593,14 @@ function createRoadmapCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
18111
18593
|
cmd: "rafa roadmap --full",
|
|
18112
18594
|
note: "Prints the now epics with each epic's issues underneath, then the spec lines."
|
|
18113
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
|
+
},
|
|
18114
18604
|
{
|
|
18115
18605
|
cmd: "rafa roadmap --check",
|
|
18116
18606
|
note: "Prints the Roadmap, then exits 1 when an epic is open with its work done or closed with it not done."
|
|
@@ -18129,14 +18619,14 @@ var roadmap_default = createRoadmapCommand();
|
|
|
18129
18619
|
|
|
18130
18620
|
// src/commands/self-update.ts
|
|
18131
18621
|
var DEFAULT_SELF_UPDATE_SEAMS = Object.freeze({ build: runBuild });
|
|
18132
|
-
var
|
|
18622
|
+
var USAGE36 = "rafa self-update [--force]";
|
|
18133
18623
|
function readForceFlag(value) {
|
|
18134
18624
|
if (value === undefined || value === false || value === "false")
|
|
18135
18625
|
return false;
|
|
18136
18626
|
if (value === true || value === "true")
|
|
18137
18627
|
return true;
|
|
18138
18628
|
throw new CommandExit(1, `\u274C --force takes no value, and read "${value}" as one
|
|
18139
|
-
Usage: ${
|
|
18629
|
+
Usage: ${USAGE36}`);
|
|
18140
18630
|
}
|
|
18141
18631
|
function projectOf10(context) {
|
|
18142
18632
|
if (context.project === null)
|
|
@@ -18144,7 +18634,7 @@ function projectOf10(context) {
|
|
|
18144
18634
|
return context.project;
|
|
18145
18635
|
}
|
|
18146
18636
|
async function runSelfUpdate(context, seams) {
|
|
18147
|
-
expectNoArgument(context.args,
|
|
18637
|
+
expectNoArgument(context.args, USAGE36);
|
|
18148
18638
|
const force = readForceFlag(context.flags["force"]);
|
|
18149
18639
|
const project = projectOf10(context);
|
|
18150
18640
|
const info2 = (line) => context.output.info(line);
|
|
@@ -18202,17 +18692,17 @@ function createSelfUpdateCommand(seams = DEFAULT_SELF_UPDATE_SEAMS) {
|
|
|
18202
18692
|
var self_update_default = createSelfUpdateCommand();
|
|
18203
18693
|
|
|
18204
18694
|
// src/commands/skill/backfill.ts
|
|
18205
|
-
import { statSync as
|
|
18206
|
-
import { join as
|
|
18695
|
+
import { statSync as statSync13 } from "fs";
|
|
18696
|
+
import { join as join42, resolve as resolve16 } from "path";
|
|
18207
18697
|
|
|
18208
18698
|
// src/backfill/backup.ts
|
|
18209
|
-
import { copyFileSync as
|
|
18210
|
-
import { dirname as dirname8, isAbsolute as isAbsolute8, join as
|
|
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";
|
|
18211
18701
|
|
|
18212
18702
|
// src/backfill/proposal-file.ts
|
|
18213
|
-
import { readdirSync as
|
|
18214
|
-
import { join as
|
|
18215
|
-
var BACKFILL_PATH =
|
|
18703
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync22 } from "fs";
|
|
18704
|
+
import { join as join38 } from "path";
|
|
18705
|
+
var BACKFILL_PATH = join38(".rafa", "backfill");
|
|
18216
18706
|
var PROPOSAL_FILE_PREFIX = "proposals-";
|
|
18217
18707
|
var PROPOSAL_FILE_EXTENSION = ".yaml";
|
|
18218
18708
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
@@ -18238,7 +18728,7 @@ var PROPOSAL_FILE_HEADER = [
|
|
|
18238
18728
|
].join(`
|
|
18239
18729
|
`);
|
|
18240
18730
|
function proposalPath(dir, batch) {
|
|
18241
|
-
return
|
|
18731
|
+
return join38(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
|
|
18242
18732
|
}
|
|
18243
18733
|
function isProposalFileName(name2) {
|
|
18244
18734
|
return name2.startsWith(PROPOSAL_FILE_PREFIX) && name2.endsWith(PROPOSAL_FILE_EXTENSION);
|
|
@@ -18325,7 +18815,7 @@ function parseRow(value, index) {
|
|
|
18325
18815
|
note: filledStringAt(value, "note")
|
|
18326
18816
|
};
|
|
18327
18817
|
}
|
|
18328
|
-
function
|
|
18818
|
+
function unreadable2(problem2) {
|
|
18329
18819
|
return { file: null, problem: problem2 };
|
|
18330
18820
|
}
|
|
18331
18821
|
function parseHeader(data) {
|
|
@@ -18344,15 +18834,15 @@ function numberAt(data, key) {
|
|
|
18344
18834
|
function parseProposalFile(text) {
|
|
18345
18835
|
const parsed = readYamlMapping(text);
|
|
18346
18836
|
if (parsed === null)
|
|
18347
|
-
return
|
|
18837
|
+
return unreadable2("the file holds no YAML mapping");
|
|
18348
18838
|
const headerProblem = parseHeader(parsed);
|
|
18349
18839
|
if (headerProblem !== null)
|
|
18350
|
-
return
|
|
18840
|
+
return unreadable2(headerProblem);
|
|
18351
18841
|
const rows = [];
|
|
18352
18842
|
for (const [index, value] of parsed["rows"].entries()) {
|
|
18353
18843
|
const row2 = parseRow(value, index);
|
|
18354
18844
|
if (typeof row2 === "string")
|
|
18355
|
-
return
|
|
18845
|
+
return unreadable2(row2);
|
|
18356
18846
|
rows.push(row2);
|
|
18357
18847
|
}
|
|
18358
18848
|
return {
|
|
@@ -18369,34 +18859,34 @@ function parseProposalFile(text) {
|
|
|
18369
18859
|
function readProposalFiles(dir) {
|
|
18370
18860
|
let names;
|
|
18371
18861
|
try {
|
|
18372
|
-
names =
|
|
18862
|
+
names = readdirSync5(dir);
|
|
18373
18863
|
} catch {
|
|
18374
18864
|
return [];
|
|
18375
18865
|
}
|
|
18376
18866
|
return names.filter((name2) => isProposalFileName(name2)).sort((left, right) => left.localeCompare(right)).map((name2) => {
|
|
18377
|
-
const path =
|
|
18867
|
+
const path = join38(dir, name2);
|
|
18378
18868
|
try {
|
|
18379
18869
|
return { path, result: parseProposalFile(readFileSync22(path, "utf8")) };
|
|
18380
18870
|
} catch (error) {
|
|
18381
|
-
return { path, result:
|
|
18871
|
+
return { path, result: unreadable2(`the file cannot be read (${error instanceof Error ? error.message : String(error)})`) };
|
|
18382
18872
|
}
|
|
18383
18873
|
});
|
|
18384
18874
|
}
|
|
18385
18875
|
|
|
18386
18876
|
// src/backfill/backup.ts
|
|
18387
|
-
var BACKUP_PATH =
|
|
18877
|
+
var BACKUP_PATH = join39(BACKFILL_PATH, "backup");
|
|
18388
18878
|
var BACKUP_KINDS = ["copied", "kept", "tracked", "outside", "missing"];
|
|
18389
18879
|
function backupDirectory(base) {
|
|
18390
|
-
return
|
|
18880
|
+
return join39(base, BACKUP_PATH);
|
|
18391
18881
|
}
|
|
18392
18882
|
function isUnder(root, path) {
|
|
18393
|
-
const step = relative8(
|
|
18883
|
+
const step = relative8(resolve14(root), resolve14(path));
|
|
18394
18884
|
return step === "" || !step.startsWith(`..${sep5}`) && step !== ".." && !isAbsolute8(step);
|
|
18395
18885
|
}
|
|
18396
18886
|
function backupPathFor(path, root, backupDir) {
|
|
18397
18887
|
if (!isUnder(root, path))
|
|
18398
18888
|
return null;
|
|
18399
|
-
return
|
|
18889
|
+
return join39(backupDir, relative8(resolve14(root), resolve14(path)));
|
|
18400
18890
|
}
|
|
18401
18891
|
function backupFile(path, options) {
|
|
18402
18892
|
if (options.repoRoot !== null && isUnder(options.repoRoot, path)) {
|
|
@@ -18405,12 +18895,12 @@ function backupFile(path, options) {
|
|
|
18405
18895
|
const backup = backupPathFor(path, options.root, options.backupDir);
|
|
18406
18896
|
if (backup === null)
|
|
18407
18897
|
return { path, kind: "outside", backup: null };
|
|
18408
|
-
if (!
|
|
18898
|
+
if (!existsSync17(path))
|
|
18409
18899
|
return { path, kind: "missing", backup: null };
|
|
18410
|
-
if (
|
|
18900
|
+
if (existsSync17(backup))
|
|
18411
18901
|
return { path, kind: "kept", backup };
|
|
18412
|
-
|
|
18413
|
-
|
|
18902
|
+
mkdirSync8(dirname8(backup), { recursive: true });
|
|
18903
|
+
copyFileSync5(path, backup);
|
|
18414
18904
|
return { path, kind: "copied", backup };
|
|
18415
18905
|
}
|
|
18416
18906
|
function backupFiles(paths, options) {
|
|
@@ -18433,7 +18923,7 @@ function countBackups(results) {
|
|
|
18433
18923
|
|
|
18434
18924
|
// src/backfill/derive.ts
|
|
18435
18925
|
import { readFileSync as readFileSync23, writeFileSync as writeFileSync6 } from "fs";
|
|
18436
|
-
import { basename as
|
|
18926
|
+
import { basename as basename7 } from "path";
|
|
18437
18927
|
var DERIVED_FIELDS = ["stack", "paths", "when_to_use"];
|
|
18438
18928
|
function renderWhenToUse(trigger, prevents) {
|
|
18439
18929
|
return `${sentenceEnded(trigger)} Prevents: ${prevents}`;
|
|
@@ -18550,8 +19040,8 @@ function fileChanges(name2, data, body, options) {
|
|
|
18550
19040
|
return { changes, notes };
|
|
18551
19041
|
}
|
|
18552
19042
|
function planFile(entry, options) {
|
|
18553
|
-
if (
|
|
18554
|
-
return inert(entry, "skipped", `${
|
|
19043
|
+
if (basename7(entry.path) !== SKILL_FILE) {
|
|
19044
|
+
return inert(entry, "skipped", `${basename7(entry.path)} is no ${SKILL_FILE}, so it registers no skill`);
|
|
18555
19045
|
}
|
|
18556
19046
|
if (entry.name === null) {
|
|
18557
19047
|
return inert(entry, "skipped", "its place implies no skill name, so no stack can be derived from it");
|
|
@@ -19038,8 +19528,8 @@ function parseSessionAnswer(stdout) {
|
|
|
19038
19528
|
}
|
|
19039
19529
|
|
|
19040
19530
|
// src/backfill/propose.ts
|
|
19041
|
-
import { mkdirSync as
|
|
19042
|
-
import { join as
|
|
19531
|
+
import { mkdirSync as mkdirSync9, readFileSync as readFileSync25, writeFileSync as writeFileSync7 } from "fs";
|
|
19532
|
+
import { join as join40, resolve as resolve15 } from "path";
|
|
19043
19533
|
var PROPOSAL_ACTION_KINDS = [
|
|
19044
19534
|
"applied",
|
|
19045
19535
|
"unchanged",
|
|
@@ -19118,7 +19608,7 @@ async function runProposalPass(options) {
|
|
|
19118
19608
|
const batches = batchCandidates(candidates);
|
|
19119
19609
|
const written = [];
|
|
19120
19610
|
if (batches.length > 0)
|
|
19121
|
-
|
|
19611
|
+
mkdirSync9(options.backfillDir, { recursive: true });
|
|
19122
19612
|
for (const [index, batch] of batches.entries()) {
|
|
19123
19613
|
const number = index + 1;
|
|
19124
19614
|
const file = await runProposalBatch(number, batch, options);
|
|
@@ -19188,7 +19678,7 @@ function writeRow(path, row2, original, options) {
|
|
|
19188
19678
|
};
|
|
19189
19679
|
}
|
|
19190
19680
|
function applyRow(root, row2, options) {
|
|
19191
|
-
const path =
|
|
19681
|
+
const path = join40(root, ...row2.path.split("/"));
|
|
19192
19682
|
if (row2.status === UNANSWERED_ROW) {
|
|
19193
19683
|
return inert2(path, row2, "unanswered", row2.note ?? "the row carries no answer");
|
|
19194
19684
|
}
|
|
@@ -19211,7 +19701,7 @@ function fileRefusal(file, root) {
|
|
|
19211
19701
|
if (file.status !== REVIEWED_FILE) {
|
|
19212
19702
|
return `the proposal file is still marked ${file.status}: review it and mark it ${REVIEWED_FILE}`;
|
|
19213
19703
|
}
|
|
19214
|
-
return
|
|
19704
|
+
return resolve15(file.skills) === resolve15(root) ? null : `the proposal file names the skills directory ${file.skills}, not ${root}`;
|
|
19215
19705
|
}
|
|
19216
19706
|
function countProposalActions(actions) {
|
|
19217
19707
|
const counts = Object.fromEntries(PROPOSAL_ACTION_KINDS.map((kind) => [kind, 0]));
|
|
@@ -19225,7 +19715,7 @@ function applyProposals(files, root, options) {
|
|
|
19225
19715
|
for (const file of files) {
|
|
19226
19716
|
const refusal13 = fileRefusal(file, root);
|
|
19227
19717
|
for (const row2 of file.rows) {
|
|
19228
|
-
const action = refusal13 === null ? applyRow(root, row2, options) : inert2(
|
|
19718
|
+
const action = refusal13 === null ? applyRow(root, row2, options) : inert2(join40(root, ...row2.path.split("/")), row2, "refused", refusal13);
|
|
19229
19719
|
actions.push(action);
|
|
19230
19720
|
if (row2.trigger !== null && action.kind !== "refused" && action.kind !== "unanswered") {
|
|
19231
19721
|
triggers[row2.name] = row2.trigger;
|
|
@@ -19236,13 +19726,13 @@ function applyProposals(files, root, options) {
|
|
|
19236
19726
|
}
|
|
19237
19727
|
|
|
19238
19728
|
// src/demote/select.ts
|
|
19239
|
-
import { readdirSync as
|
|
19240
|
-
import { basename as
|
|
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";
|
|
19241
19731
|
var SKILLS_DIRECTORY = "skills";
|
|
19242
19732
|
var CLAUDE_DIRECTORY = ".claude";
|
|
19243
19733
|
var AUTO_EXTRACTED_ORIGIN = "auto-extracted";
|
|
19244
|
-
var DEMOTED_PATH =
|
|
19245
|
-
var INSTINCTS_PATH =
|
|
19734
|
+
var DEMOTED_PATH = join41(".rafa", "demoted");
|
|
19735
|
+
var INSTINCTS_PATH = join41(".rafa", "instincts");
|
|
19246
19736
|
var REPORT_FILE = "report.md";
|
|
19247
19737
|
function realOrSelf(path) {
|
|
19248
19738
|
try {
|
|
@@ -19259,25 +19749,25 @@ function samePath(left, right) {
|
|
|
19259
19749
|
return resolved === other || resolved.toLowerCase() === other.toLowerCase();
|
|
19260
19750
|
}
|
|
19261
19751
|
function resolveDemotionScope(dir, home) {
|
|
19262
|
-
if (
|
|
19752
|
+
if (basename8(dir) !== SKILLS_DIRECTORY)
|
|
19263
19753
|
return null;
|
|
19264
19754
|
const claude = dirname9(dir);
|
|
19265
|
-
if (
|
|
19755
|
+
if (basename8(claude) !== CLAUDE_DIRECTORY)
|
|
19266
19756
|
return null;
|
|
19267
19757
|
const base = dirname9(claude);
|
|
19268
|
-
const demotedDir =
|
|
19758
|
+
const demotedDir = join41(base, DEMOTED_PATH);
|
|
19269
19759
|
return {
|
|
19270
19760
|
scope: samePath(base, home) ? "user" : "project",
|
|
19271
19761
|
dir,
|
|
19272
19762
|
base,
|
|
19273
19763
|
demotedDir,
|
|
19274
|
-
instinctsDir:
|
|
19275
|
-
reportPath:
|
|
19764
|
+
instinctsDir: join41(base, INSTINCTS_PATH),
|
|
19765
|
+
reportPath: join41(demotedDir, REPORT_FILE)
|
|
19276
19766
|
};
|
|
19277
19767
|
}
|
|
19278
19768
|
function entryKind(path) {
|
|
19279
19769
|
try {
|
|
19280
|
-
const stats =
|
|
19770
|
+
const stats = statSync12(path);
|
|
19281
19771
|
if (stats.isFile())
|
|
19282
19772
|
return "file";
|
|
19283
19773
|
if (stats.isDirectory())
|
|
@@ -19289,7 +19779,7 @@ function entryKind(path) {
|
|
|
19289
19779
|
}
|
|
19290
19780
|
function visibleNames(dir) {
|
|
19291
19781
|
try {
|
|
19292
|
-
return
|
|
19782
|
+
return readdirSync6(dir).filter((name2) => !name2.startsWith(".")).sort((left, right) => left.localeCompare(right));
|
|
19293
19783
|
} catch {
|
|
19294
19784
|
return [];
|
|
19295
19785
|
}
|
|
@@ -19299,7 +19789,7 @@ function readSelected(absolute, path, entries) {
|
|
|
19299
19789
|
let mtime;
|
|
19300
19790
|
try {
|
|
19301
19791
|
text = readFileSync26(absolute, "utf8");
|
|
19302
|
-
mtime =
|
|
19792
|
+
mtime = statSync12(absolute).mtime.toISOString();
|
|
19303
19793
|
} catch {
|
|
19304
19794
|
return null;
|
|
19305
19795
|
}
|
|
@@ -19314,14 +19804,14 @@ function selectsLearned(text, scope) {
|
|
|
19314
19804
|
return origin === AUTO_EXTRACTED_ORIGIN;
|
|
19315
19805
|
}
|
|
19316
19806
|
function selectLearned(scope) {
|
|
19317
|
-
const dir =
|
|
19807
|
+
const dir = join41(scope.dir, LEARNED_DIRECTORY);
|
|
19318
19808
|
if (entryKind(dir) !== "directory")
|
|
19319
19809
|
return [];
|
|
19320
19810
|
const selected = [];
|
|
19321
19811
|
for (const name2 of visibleNames(dir)) {
|
|
19322
19812
|
if (!name2.endsWith(MARKDOWN_EXTENSION) || name2 === MARKDOWN_EXTENSION)
|
|
19323
19813
|
continue;
|
|
19324
|
-
const absolute =
|
|
19814
|
+
const absolute = join41(dir, name2);
|
|
19325
19815
|
if (entryKind(absolute) !== "file")
|
|
19326
19816
|
continue;
|
|
19327
19817
|
const file = readSelected(absolute, `${LEARNED_DIRECTORY}/${name2}`, undefined);
|
|
@@ -19335,10 +19825,10 @@ function selectSkillFiles(scope) {
|
|
|
19335
19825
|
for (const name2 of visibleNames(scope.dir)) {
|
|
19336
19826
|
if (name2 === LEARNED_DIRECTORY)
|
|
19337
19827
|
continue;
|
|
19338
|
-
const skillDir =
|
|
19828
|
+
const skillDir = join41(scope.dir, name2);
|
|
19339
19829
|
if (entryKind(skillDir) !== "directory")
|
|
19340
19830
|
continue;
|
|
19341
|
-
const absolute =
|
|
19831
|
+
const absolute = join41(skillDir, SKILL_FILE);
|
|
19342
19832
|
if (entryKind(absolute) !== "file")
|
|
19343
19833
|
continue;
|
|
19344
19834
|
const file = readSelected(absolute, `${name2}/${SKILL_FILE}`, visibleNames(skillDir));
|
|
@@ -19386,7 +19876,7 @@ Usage: ${BACKFILL_USAGE}`);
|
|
|
19386
19876
|
throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
|
|
19387
19877
|
Usage: ${BACKFILL_USAGE}`);
|
|
19388
19878
|
}
|
|
19389
|
-
return
|
|
19879
|
+
return resolve16(cwd(), dir);
|
|
19390
19880
|
}
|
|
19391
19881
|
function readConsumerRoot(value, cwd) {
|
|
19392
19882
|
if (value === undefined || value === false)
|
|
@@ -19395,11 +19885,11 @@ function readConsumerRoot(value, cwd) {
|
|
|
19395
19885
|
throw new CommandExit(1, `\u274C --project needs a path, and the line gave it none
|
|
19396
19886
|
Usage: ${BACKFILL_USAGE}`);
|
|
19397
19887
|
}
|
|
19398
|
-
return
|
|
19888
|
+
return resolve16(cwd(), value);
|
|
19399
19889
|
}
|
|
19400
19890
|
function isDirectory3(path) {
|
|
19401
19891
|
try {
|
|
19402
|
-
return
|
|
19892
|
+
return statSync13(path).isDirectory();
|
|
19403
19893
|
} catch {
|
|
19404
19894
|
return false;
|
|
19405
19895
|
}
|
|
@@ -19523,7 +20013,7 @@ function reviewedProposals(dir) {
|
|
|
19523
20013
|
return read2.flatMap((entry) => entry.result.file === null ? [] : [entry.result.file]);
|
|
19524
20014
|
}
|
|
19525
20015
|
function rowPaths(files, root) {
|
|
19526
|
-
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) =>
|
|
20016
|
+
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join42(root, ...row2.path.split("/"))));
|
|
19527
20017
|
}
|
|
19528
20018
|
function runApply(context, scope, backfillDir, projectRoot3) {
|
|
19529
20019
|
const options = checkOptions(context, projectRoot3);
|
|
@@ -19569,7 +20059,7 @@ async function runBackfill(context, seams) {
|
|
|
19569
20059
|
const dir = readBackfillDirectory(context.args, seams.cwd);
|
|
19570
20060
|
const projectRoot3 = readConsumerRoot(context.flags["project"], seams.cwd);
|
|
19571
20061
|
const scope = backfillScopeOf(dir, projectOf11(context).home);
|
|
19572
|
-
const backfillDir =
|
|
20062
|
+
const backfillDir = join42(scope.base, BACKFILL_PATH);
|
|
19573
20063
|
if (mode === "propose") {
|
|
19574
20064
|
await runPropose(context, scope, backfillDir, seams);
|
|
19575
20065
|
return;
|
|
@@ -19683,13 +20173,13 @@ function createSkillCheckCommand(seams = DEFAULT_CHECK_SEAMS) {
|
|
|
19683
20173
|
var check_default3 = createSkillCheckCommand();
|
|
19684
20174
|
|
|
19685
20175
|
// src/commands/skill/demote.ts
|
|
19686
|
-
import { existsSync as
|
|
19687
|
-
import { dirname as dirname11, resolve as
|
|
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";
|
|
19688
20178
|
|
|
19689
20179
|
// src/demote/apply.ts
|
|
19690
|
-
import { copyFileSync as
|
|
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";
|
|
19691
20181
|
import { tmpdir as tmpdir2 } from "os";
|
|
19692
|
-
import { dirname as dirname10, join as
|
|
20182
|
+
import { dirname as dirname10, join as join43 } from "path";
|
|
19693
20183
|
var DEMOTION_ACTION_KINDS = [
|
|
19694
20184
|
"demoted",
|
|
19695
20185
|
"kept",
|
|
@@ -19710,18 +20200,18 @@ function learnedSkillName(path) {
|
|
|
19710
20200
|
return base.slice(0, -MARKDOWN_EXTENSION.length);
|
|
19711
20201
|
}
|
|
19712
20202
|
function sourcePath(scope, path) {
|
|
19713
|
-
return
|
|
20203
|
+
return join43(scope.dir, ...path.split("/"));
|
|
19714
20204
|
}
|
|
19715
20205
|
function demotedPath(scope, path) {
|
|
19716
|
-
return
|
|
20206
|
+
return join43(scope.demotedDir, ...path.split("/"));
|
|
19717
20207
|
}
|
|
19718
20208
|
function promotedPath(scope, path) {
|
|
19719
|
-
return
|
|
20209
|
+
return join43(scope.dir, learnedSkillName(path), SKILL_FILE);
|
|
19720
20210
|
}
|
|
19721
20211
|
function takenInstinctIds(dir) {
|
|
19722
20212
|
let names;
|
|
19723
20213
|
try {
|
|
19724
|
-
names =
|
|
20214
|
+
names = readdirSync7(dir);
|
|
19725
20215
|
} catch {
|
|
19726
20216
|
return [];
|
|
19727
20217
|
}
|
|
@@ -19729,7 +20219,7 @@ function takenInstinctIds(dir) {
|
|
|
19729
20219
|
}
|
|
19730
20220
|
function readText4(path) {
|
|
19731
20221
|
try {
|
|
19732
|
-
return
|
|
20222
|
+
return statSync14(path).isFile() ? readFileSync27(path, "utf8") : null;
|
|
19733
20223
|
} catch {
|
|
19734
20224
|
return null;
|
|
19735
20225
|
}
|
|
@@ -19757,7 +20247,7 @@ function planKept(scope, row2, file) {
|
|
|
19757
20247
|
return inert3(row2.path, "procedure", "kept", "procedure-shaped: left where it is, for the backfill");
|
|
19758
20248
|
}
|
|
19759
20249
|
const to = promotedPath(scope, row2.path);
|
|
19760
|
-
if (
|
|
20250
|
+
if (existsSync18(to) || existsSync18(dirname10(to))) {
|
|
19761
20251
|
return inert3(row2.path, "procedure", "refused", `kept as a skill, but ${to} is already there`);
|
|
19762
20252
|
}
|
|
19763
20253
|
return {
|
|
@@ -19771,7 +20261,7 @@ function planKept(scope, row2, file) {
|
|
|
19771
20261
|
};
|
|
19772
20262
|
}
|
|
19773
20263
|
function checkCandidate(dir, id2, text, pathDirs) {
|
|
19774
|
-
const path =
|
|
20264
|
+
const path = join43(dir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
19775
20265
|
writeFileSync8(path, text, "utf8");
|
|
19776
20266
|
const report3 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
|
|
19777
20267
|
return failureLines2(report3.issues);
|
|
@@ -19792,11 +20282,11 @@ function planDemoted(scope, row2, file, context) {
|
|
|
19792
20282
|
if (failures.length > 0) {
|
|
19793
20283
|
return inert3(row2.path, "observation", "refused", `the record ${id2} fails the checker \u2014 ${failures.join("; ")}`);
|
|
19794
20284
|
}
|
|
19795
|
-
const destination =
|
|
20285
|
+
const destination = join43(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
19796
20286
|
const kept = demotedPath(scope, row2.path);
|
|
19797
|
-
if (
|
|
20287
|
+
if (existsSync18(destination))
|
|
19798
20288
|
return inert3(row2.path, "observation", "refused", `${destination} is already there`);
|
|
19799
|
-
if (
|
|
20289
|
+
if (existsSync18(kept))
|
|
19800
20290
|
return inert3(row2.path, "observation", "refused", `${kept} is already there`);
|
|
19801
20291
|
context.taken.push(id2);
|
|
19802
20292
|
return {
|
|
@@ -19828,7 +20318,7 @@ function planRow(scope, row2, context) {
|
|
|
19828
20318
|
entries: undefined,
|
|
19829
20319
|
absolute,
|
|
19830
20320
|
hash,
|
|
19831
|
-
mtime:
|
|
20321
|
+
mtime: statSync14(absolute).mtime.toISOString()
|
|
19832
20322
|
};
|
|
19833
20323
|
return verdict === "procedure" ? planKept(scope, row2, file) : planDemoted(scope, row2, file, context);
|
|
19834
20324
|
}
|
|
@@ -19839,28 +20329,28 @@ function countActions(actions) {
|
|
|
19839
20329
|
return counts;
|
|
19840
20330
|
}
|
|
19841
20331
|
function planDemotion(report3, scope, seams = DEFAULT_APPLY_SEAMS) {
|
|
19842
|
-
const temp = mkdtempSync2(
|
|
20332
|
+
const temp = mkdtempSync2(join43(tmpdir2(), "rafa-demote-"));
|
|
19843
20333
|
const taken = [...takenInstinctIds(scope.instinctsDir)];
|
|
19844
20334
|
try {
|
|
19845
20335
|
const actions = report3.rows.map((row2) => planRow(scope, row2, { temp, taken, seams }));
|
|
19846
20336
|
return { scope, actions, counts: countActions(actions) };
|
|
19847
20337
|
} finally {
|
|
19848
|
-
|
|
20338
|
+
rmSync5(temp, { recursive: true, force: true });
|
|
19849
20339
|
}
|
|
19850
20340
|
}
|
|
19851
20341
|
function moveFile(move3) {
|
|
19852
|
-
|
|
20342
|
+
mkdirSync10(dirname10(move3.to), { recursive: true });
|
|
19853
20343
|
try {
|
|
19854
20344
|
renameSync3(move3.from, move3.to);
|
|
19855
20345
|
} catch {
|
|
19856
|
-
|
|
20346
|
+
copyFileSync6(move3.from, move3.to);
|
|
19857
20347
|
unlinkSync(move3.from);
|
|
19858
20348
|
}
|
|
19859
20349
|
}
|
|
19860
20350
|
function applyDemotion(plan2) {
|
|
19861
20351
|
for (const action of plan2.actions) {
|
|
19862
20352
|
if (action.write !== null) {
|
|
19863
|
-
|
|
20353
|
+
mkdirSync10(dirname10(action.write.path), { recursive: true });
|
|
19864
20354
|
writeFileSync8(action.write.path, action.write.text, "utf8");
|
|
19865
20355
|
}
|
|
19866
20356
|
if (action.move !== null)
|
|
@@ -19926,11 +20416,11 @@ Usage: ${DEMOTE_USAGE}`);
|
|
|
19926
20416
|
throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
|
|
19927
20417
|
Usage: ${DEMOTE_USAGE}`);
|
|
19928
20418
|
}
|
|
19929
|
-
return
|
|
20419
|
+
return resolve17(cwd(), dir);
|
|
19930
20420
|
}
|
|
19931
20421
|
function isDirectory4(path) {
|
|
19932
20422
|
try {
|
|
19933
|
-
return
|
|
20423
|
+
return statSync15(path).isDirectory();
|
|
19934
20424
|
} catch {
|
|
19935
20425
|
return false;
|
|
19936
20426
|
}
|
|
@@ -19952,13 +20442,13 @@ function projectOf12(context) {
|
|
|
19952
20442
|
throw new Error("rafa skill demote runs inside a project, and was handed none");
|
|
19953
20443
|
return context.project;
|
|
19954
20444
|
}
|
|
19955
|
-
function
|
|
20445
|
+
function verdictLine2(report3) {
|
|
19956
20446
|
const counts = countVerdicts(report3.rows);
|
|
19957
20447
|
const parts = DEMOTION_VERDICTS.map((verdict) => `${String(counts[verdict])} ${verdict}`);
|
|
19958
20448
|
return `${String(report3.rows.length)} file(s): ${parts.join(", ")}`;
|
|
19959
20449
|
}
|
|
19960
20450
|
function previousReport(path, context) {
|
|
19961
|
-
if (!
|
|
20451
|
+
if (!existsSync19(path))
|
|
19962
20452
|
return null;
|
|
19963
20453
|
const parsed = parseDemotionReport(readFileSync28(path, "utf8"));
|
|
19964
20454
|
if (parsed.report !== null)
|
|
@@ -19969,7 +20459,7 @@ function previousReport(path, context) {
|
|
|
19969
20459
|
function runWrite(context, scope) {
|
|
19970
20460
|
const files = selectSources(scope);
|
|
19971
20461
|
const report3 = buildDemotionReport(files, previousReport(scope.reportPath, context));
|
|
19972
|
-
|
|
20462
|
+
mkdirSync11(dirname11(scope.reportPath), { recursive: true });
|
|
19973
20463
|
writeFileSync9(scope.reportPath, writeDemotionReport(report3), "utf8");
|
|
19974
20464
|
const result = {
|
|
19975
20465
|
applied: false,
|
|
@@ -19986,14 +20476,14 @@ function runWrite(context, scope) {
|
|
|
19986
20476
|
}
|
|
19987
20477
|
for (const line of [
|
|
19988
20478
|
`${scope.scope} scope: ${scope.dir}`,
|
|
19989
|
-
|
|
20479
|
+
verdictLine2(report3),
|
|
19990
20480
|
`Report written to ${scope.reportPath} (status: ${report3.status}); nothing was moved`,
|
|
19991
20481
|
`Review each row, then mark the report ${REVIEWED_STATUS} and re-run with --apply`
|
|
19992
20482
|
])
|
|
19993
20483
|
context.output.info(line);
|
|
19994
20484
|
}
|
|
19995
20485
|
function reviewedReport(scope) {
|
|
19996
|
-
if (!
|
|
20486
|
+
if (!existsSync19(scope.reportPath)) {
|
|
19997
20487
|
throw new CommandExit(1, `\u274C No report at ${scope.reportPath}; run "rafa skill demote ${scope.dir}" first, then review it`);
|
|
19998
20488
|
}
|
|
19999
20489
|
const parsed = parseDemotionReport(readFileSync28(scope.reportPath, "utf8"));
|
|
@@ -20102,8 +20592,8 @@ var demote_default = createSkillDemoteCommand();
|
|
|
20102
20592
|
|
|
20103
20593
|
// src/commands/skill/show.ts
|
|
20104
20594
|
var DEFAULT_SKILL_SHOW_SEAMS = DEFAULT_SKILL_LIST_SEAMS;
|
|
20105
|
-
var
|
|
20106
|
-
var
|
|
20595
|
+
var COMMAND_NAME9 = "rafa skill show";
|
|
20596
|
+
var USAGE37 = "rafa skill show <name> [--full]";
|
|
20107
20597
|
function unknownSkillMessage(name2, inventory) {
|
|
20108
20598
|
const agent = findShown(inventory.records, "agent", name2);
|
|
20109
20599
|
const hint = agent === null ? [] : [`An agent is named "${name2}" (${agent.source}): rafa agent show ${name2}`];
|
|
@@ -20111,7 +20601,7 @@ function unknownSkillMessage(name2, inventory) {
|
|
|
20111
20601
|
`\u274C No skill is named "${name2}".`,
|
|
20112
20602
|
...hint,
|
|
20113
20603
|
"Run `rafa skill list` to see every skill and where it comes from.",
|
|
20114
|
-
`Usage: ${
|
|
20604
|
+
`Usage: ${USAGE37}`
|
|
20115
20605
|
].join(`
|
|
20116
20606
|
`);
|
|
20117
20607
|
}
|
|
@@ -20127,10 +20617,10 @@ function projectOf13(context) {
|
|
|
20127
20617
|
return context.project;
|
|
20128
20618
|
}
|
|
20129
20619
|
async function runShow3(context, seams) {
|
|
20130
|
-
const full = readSwitch("full", context.flags["full"], `Type the name ahead of it: ${
|
|
20131
|
-
const name2 = expectOneArgument(context.args,
|
|
20620
|
+
const full = readSwitch("full", context.flags["full"], `Type the name ahead of it: ${USAGE37}`);
|
|
20621
|
+
const name2 = expectOneArgument(context.args, USAGE37);
|
|
20132
20622
|
const project = projectOf13(context);
|
|
20133
|
-
const { inventory, settingSources } = await projectInventory(project, context, seams,
|
|
20623
|
+
const { inventory, settingSources } = await projectInventory(project, context, seams, COMMAND_NAME9);
|
|
20134
20624
|
const warnings = warningLines(inventory);
|
|
20135
20625
|
for (const warning of warnings)
|
|
20136
20626
|
context.output.warn(warning);
|
|
@@ -20323,12 +20813,12 @@ function statusData(sections) {
|
|
|
20323
20813
|
}
|
|
20324
20814
|
|
|
20325
20815
|
// src/status/sections.ts
|
|
20326
|
-
import { existsSync as
|
|
20816
|
+
import { existsSync as existsSync20 } from "fs";
|
|
20327
20817
|
var STATUS_NETWORK_TIMEOUT_MS = 5000;
|
|
20328
20818
|
function isIdleWorktree(row2) {
|
|
20329
20819
|
return !row2.blockers.some((blocker) => blocker.kind === "recent");
|
|
20330
20820
|
}
|
|
20331
|
-
function
|
|
20821
|
+
function unread3(problem2) {
|
|
20332
20822
|
return { read: false, problem: problem2 };
|
|
20333
20823
|
}
|
|
20334
20824
|
function read2(reading) {
|
|
@@ -20339,7 +20829,7 @@ function localSection(work) {
|
|
|
20339
20829
|
const reading = work();
|
|
20340
20830
|
return isUnread(reading) ? reading : read2(reading);
|
|
20341
20831
|
} catch (error) {
|
|
20342
|
-
return
|
|
20832
|
+
return unread3(messageOf(error));
|
|
20343
20833
|
}
|
|
20344
20834
|
}
|
|
20345
20835
|
function isUnread(value) {
|
|
@@ -20370,15 +20860,15 @@ function boundedRunner(open, root, deadline) {
|
|
|
20370
20860
|
}
|
|
20371
20861
|
async function networkSection(what, deadline, work) {
|
|
20372
20862
|
let timer;
|
|
20373
|
-
const expired = new Promise((
|
|
20863
|
+
const expired = new Promise((resolve18) => {
|
|
20374
20864
|
timer = setTimeout(() => {
|
|
20375
|
-
|
|
20865
|
+
resolve18(unread3(`the ${what} was not read within the ${String(deadline.ms)}ms network deadline`));
|
|
20376
20866
|
}, Math.max(0, deadline.left()));
|
|
20377
20867
|
});
|
|
20378
20868
|
try {
|
|
20379
20869
|
return await Promise.race([work().then((reading) => read2(reading)), expired]);
|
|
20380
20870
|
} catch (error) {
|
|
20381
|
-
return
|
|
20871
|
+
return unread3(messageOf(error));
|
|
20382
20872
|
} finally {
|
|
20383
20873
|
clearTimeout(timer);
|
|
20384
20874
|
}
|
|
@@ -20394,7 +20884,7 @@ function readBranchSection(sources) {
|
|
|
20394
20884
|
};
|
|
20395
20885
|
const branch = readBranch(sources, note);
|
|
20396
20886
|
if (branch === "") {
|
|
20397
|
-
return
|
|
20887
|
+
return unread3(notes.join("; ") || "the branch at the project root could not be read");
|
|
20398
20888
|
}
|
|
20399
20889
|
const plans = readPlans(sources, note);
|
|
20400
20890
|
return { branch, plan: readBranchPlan(branch, plans), notes };
|
|
@@ -20421,9 +20911,9 @@ function readLoopsSection(root, sources) {
|
|
|
20421
20911
|
}
|
|
20422
20912
|
function pullSection(sources, branch, provider, deadline) {
|
|
20423
20913
|
if (provider.provider !== "gh")
|
|
20424
|
-
return Promise.resolve(
|
|
20914
|
+
return Promise.resolve(unread3(notGhProblem("pull request", provider)));
|
|
20425
20915
|
if (!branch.read) {
|
|
20426
|
-
return Promise.resolve(
|
|
20916
|
+
return Promise.resolve(unread3(`no pull request was looked for, as the branch was not read: ${branch.problem}`));
|
|
20427
20917
|
}
|
|
20428
20918
|
return networkSection("pull request", deadline, () => readPull(sources, branch.branch));
|
|
20429
20919
|
}
|
|
@@ -20462,7 +20952,7 @@ function nextOfPlace(current, epic, walk) {
|
|
|
20462
20952
|
return epic?.members.some((member) => member.number === line.issue) === true ? line.issue : null;
|
|
20463
20953
|
}
|
|
20464
20954
|
function hasBoards(root, listing2) {
|
|
20465
|
-
if (
|
|
20955
|
+
if (existsSync20(positionFilePath(root)))
|
|
20466
20956
|
return true;
|
|
20467
20957
|
return listing2.some((issue2) => issue2.state === "OPEN" && issue2.labels.includes(ROADMAP_LABEL));
|
|
20468
20958
|
}
|
|
@@ -20507,11 +20997,11 @@ async function readHousekeeping(input, cleanup, now) {
|
|
|
20507
20997
|
const settings = doctorCleanupSettings({ root: input.root, home: input.home, config: input.config, gh: null }, now);
|
|
20508
20998
|
const reading = await readCleanup(cleanup, settings);
|
|
20509
20999
|
if (!reading.ok)
|
|
20510
|
-
return
|
|
21000
|
+
return unread3(reading.detail);
|
|
20511
21001
|
const idle = reading.worktrees.filter((row2) => isIdleWorktree(row2));
|
|
20512
21002
|
return read2({ counts: cleanupCounts(reading), idleWorktrees: idle.length, notes: reading.notes });
|
|
20513
21003
|
} catch (error) {
|
|
20514
|
-
return
|
|
21004
|
+
return unread3(messageOf(error));
|
|
20515
21005
|
}
|
|
20516
21006
|
}
|
|
20517
21007
|
async function readStatusSections(input, seams = {}) {
|
|
@@ -20540,7 +21030,7 @@ async function readStatusSections(input, seams = {}) {
|
|
|
20540
21030
|
const blockedIssues = seams.blockedIssues ?? ((runner) => readBlockedIssues({ gh: runner }));
|
|
20541
21031
|
deadline.start();
|
|
20542
21032
|
const pull = pullSection(sources, branch, provider, deadline);
|
|
20543
|
-
const boardSection = isGh ? networkSection("board", deadline, () => readBoard(root, board, listing2, blockedIssues(gh))) : Promise.resolve(
|
|
21033
|
+
const boardSection = isGh ? networkSection("board", deadline, () => readBoard(root, board, listing2, blockedIssues(gh))) : Promise.resolve(unread3(notGhProblem("board", provider)));
|
|
20544
21034
|
const housekeeping = readHousekeeping(input, cleanup, now);
|
|
20545
21035
|
return {
|
|
20546
21036
|
branch,
|
|
@@ -20552,7 +21042,7 @@ async function readStatusSections(input, seams = {}) {
|
|
|
20552
21042
|
}
|
|
20553
21043
|
|
|
20554
21044
|
// src/commands/status.ts
|
|
20555
|
-
var
|
|
21045
|
+
var COMMAND_NAME10 = "rafa status";
|
|
20556
21046
|
var STATUS_USAGE = "rafa status";
|
|
20557
21047
|
var STATUS_ARGUMENT_EXIT = 2;
|
|
20558
21048
|
var DEFAULT_STATUS_SEAMS = Object.freeze({});
|
|
@@ -20564,13 +21054,13 @@ function projectOf14(context) {
|
|
|
20564
21054
|
function expectNoWord(args) {
|
|
20565
21055
|
if (args.length === 0)
|
|
20566
21056
|
return;
|
|
20567
|
-
throw new CommandExit(STATUS_ARGUMENT_EXIT, `\u274C ${
|
|
21057
|
+
throw new CommandExit(STATUS_ARGUMENT_EXIT, `\u274C ${COMMAND_NAME10}: expected no argument, got ${String(args.length)}: ${args.join(" ")}
|
|
20568
21058
|
Usage: ${STATUS_USAGE}`);
|
|
20569
21059
|
}
|
|
20570
21060
|
async function runStatus2(context, seams) {
|
|
20571
21061
|
expectNoWord(context.args);
|
|
20572
21062
|
const project = projectOf14(context);
|
|
20573
|
-
const config = resolveProjectConfig(project,
|
|
21063
|
+
const config = resolveProjectConfig(project, COMMAND_NAME10, (message) => {
|
|
20574
21064
|
context.output.warn(message);
|
|
20575
21065
|
});
|
|
20576
21066
|
const read3 = seams.read ?? ((input) => readStatusSections(input));
|
|
@@ -20636,18 +21126,18 @@ var CORE_SUBJECTS = Object.freeze([
|
|
|
20636
21126
|
{ name: "loop", summary: "start a plan; stop, pause, resume, show and list its sessions" },
|
|
20637
21127
|
{ name: "issue", summary: "the tracker: list, show, create, comment on and move issues; mark one ready, unblock it and check its references" },
|
|
20638
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" },
|
|
20639
|
-
{ name: "effort", summary: "collect session and commit rows; report per plan; repair
|
|
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" },
|
|
20640
21130
|
{ name: "module", summary: "list the configured modules; run an action a module provides" },
|
|
20641
21131
|
{ name: "agent", summary: "copy an agent definition into the project; list what a session sees" },
|
|
20642
21132
|
{ name: "skill", summary: "check a skills directory; list each tier; demote and backfill it" },
|
|
20643
21133
|
{ name: "instinct", summary: "check an instincts directory; list, show, flag and promote its records" },
|
|
20644
|
-
{ name: "release", summary: "read the release state of the project; tag the
|
|
21134
|
+
{ name: "release", summary: "read the release state of the project; tag the commit that set the version" },
|
|
20645
21135
|
{ name: "board", summary: "list the boards with their owner, epic count, and which is current and home" },
|
|
20646
21136
|
{ name: "epic", summary: "show one epic's issues as the Roadmap table; create an epic; defer or promote it; move an issue to it; close it through the gate or cancel it" }
|
|
20647
21137
|
]);
|
|
20648
21138
|
var CORE_COMMANDS = Object.freeze([
|
|
20649
21139
|
create_default2,
|
|
20650
|
-
|
|
21140
|
+
list_default2,
|
|
20651
21141
|
show_default5,
|
|
20652
21142
|
validate_default,
|
|
20653
21143
|
risk_default,
|
|
@@ -20658,7 +21148,7 @@ var CORE_COMMANDS = Object.freeze([
|
|
|
20658
21148
|
resume_default,
|
|
20659
21149
|
status_default2,
|
|
20660
21150
|
list_default7,
|
|
20661
|
-
|
|
21151
|
+
list_default,
|
|
20662
21152
|
show_default4,
|
|
20663
21153
|
create_default,
|
|
20664
21154
|
comment_default,
|
|
@@ -20676,26 +21166,29 @@ var CORE_COMMANDS = Object.freeze([
|
|
|
20676
21166
|
collect_default,
|
|
20677
21167
|
report_default,
|
|
20678
21168
|
fix_schema_default,
|
|
21169
|
+
copy_default,
|
|
21170
|
+
schema_default,
|
|
21171
|
+
migrate_default,
|
|
20679
21172
|
list_default8,
|
|
20680
21173
|
exec_default,
|
|
20681
21174
|
vendor_default,
|
|
20682
|
-
|
|
21175
|
+
list_default4,
|
|
20683
21176
|
show_default2,
|
|
20684
21177
|
search_default2,
|
|
20685
21178
|
check_default3,
|
|
20686
|
-
|
|
21179
|
+
list_default3,
|
|
20687
21180
|
show_default7,
|
|
20688
21181
|
search_default,
|
|
20689
21182
|
demote_default,
|
|
20690
21183
|
backfill_default,
|
|
20691
21184
|
check_default,
|
|
20692
|
-
|
|
21185
|
+
list_default6,
|
|
20693
21186
|
show_default3,
|
|
20694
21187
|
flag_default,
|
|
20695
21188
|
promote_default2,
|
|
20696
21189
|
status_default,
|
|
20697
21190
|
tag_default,
|
|
20698
|
-
|
|
21191
|
+
list_default5,
|
|
20699
21192
|
show_default,
|
|
20700
21193
|
new_default,
|
|
20701
21194
|
defer_default,
|