@open-tomato/rafa 0.24.1 → 0.26.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/bundled/skills/dev-planner/SKILL.md +11 -0
- package/dist/cli.js +3480 -1679
- package/dist/{describe-66gth329.js → describe-vbwqxj1f.js} +3 -3
- package/dist/effort/store/index.js +4 -4
- package/dist/{index-amyd4dse.js → index-2yd8bber.js} +1137 -732
- package/dist/{index-4fwgqveh.js → index-4j1e3h26.js} +1 -1
- package/dist/{index-h369efkh.js → index-59x3zsh4.js} +2865 -2256
- 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,38 +7773,52 @@ function refuseLiveLoop(root, isAlive) {
|
|
|
7521
7773
|
return;
|
|
7522
7774
|
throw new CommandExit(1, `\u274C ${COMMAND_NAME5}: loop session ${live.sessionId} on ${live.branch} is ${live.state} and writes the store;` + ` stop it with \`rafa loop stop -s ${live.sessionId}\` first. \`--dry-run\` runs beside it.`);
|
|
7523
7775
|
}
|
|
7776
|
+
function migrationNames(result) {
|
|
7777
|
+
return result.unknown.map(({ id: id2, appliedBy }) => `${id2} (applied by ${appliedBy})`).join(", ");
|
|
7778
|
+
}
|
|
7524
7779
|
function rebuildLines(result) {
|
|
7525
7780
|
const rows = result.kept.reduce((sum, table) => sum + table.rows, 0);
|
|
7526
7781
|
const kept = result.kept.map((table) => `${table.table} ${String(table.rows)}`).join(", ");
|
|
7527
7782
|
const left = [
|
|
7783
|
+
...result.unknown.map(({ id: id2, appliedBy }) => `migration ${id2} (applied by ${appliedBy})`),
|
|
7528
7784
|
...result.leftTables.map((table) => `table ${table.table} (${String(table.rows)} rows)`),
|
|
7529
7785
|
...result.leftColumns.map((column) => `column ${column.table}.${column.column} (${String(column.values)} values)`)
|
|
7530
7786
|
];
|
|
7787
|
+
const newer = result.unknown.length === 0 ? [] : [`A rafa that knows ${result.unknown.map(({ id: id2 }) => id2).join(", ")} uses the store as it is and keeps that data` + " in it; install one instead where you can."];
|
|
7531
7788
|
return [
|
|
7532
|
-
|
|
7789
|
+
`Refused as ${String(result.reason)}: ${String(result.refusal)}`,
|
|
7790
|
+
`Rebuilds it at the ${String(result.known.length)} migrations this rafa knows, logged as applied by ${result.appliedBy}.`,
|
|
7533
7791
|
`Keeps ${String(result.kept.length)} tables, ${String(rows)} rows: ${kept}.`,
|
|
7534
|
-
left.length === 0 ? "Leaves nothing behind." : `Leaves behind, kept only in the backup: ${left.join("; ")}
|
|
7792
|
+
left.length === 0 ? "Leaves nothing behind." : `Leaves behind, kept only in the backup: ${left.join("; ")}.`,
|
|
7793
|
+
...newer
|
|
7535
7794
|
];
|
|
7536
7795
|
}
|
|
7796
|
+
function unknownAdditiveLines(result) {
|
|
7797
|
+
return result.unknown.length === 0 ? [] : [`It logs ${migrationNames(result)}, which this rafa does not know; each is additive, so this rafa uses the store` + " through them."];
|
|
7798
|
+
}
|
|
7537
7799
|
function renderFixSchema(result) {
|
|
7538
7800
|
switch (result.status) {
|
|
7539
7801
|
case "missing":
|
|
7540
7802
|
return [`No effort store at ${result.path}: nothing to repair.`];
|
|
7541
7803
|
case "current":
|
|
7542
|
-
return [
|
|
7804
|
+
return [
|
|
7805
|
+
`\u2705 ${result.path} is current: this rafa reads and writes it as it is. Nothing to repair.`,
|
|
7806
|
+
...unknownAdditiveLines(result)
|
|
7807
|
+
];
|
|
7543
7808
|
case "behind":
|
|
7544
7809
|
return [
|
|
7545
|
-
`${result.path} is
|
|
7810
|
+
`${result.path} is behind this rafa's migrations; the next command that opens it records` + ` ${result.pending.join(", ")}. Nothing to repair.`,
|
|
7811
|
+
...unknownAdditiveLines(result)
|
|
7546
7812
|
];
|
|
7547
7813
|
case "would-rebuild":
|
|
7548
7814
|
return [
|
|
7549
7815
|
...rebuildLines(result),
|
|
7550
|
-
|
|
7816
|
+
"\uD83D\uDD0D Dry run: the rebuild was built, checked (row counts, integrity_check, this rafa's schema plan) and" + " deleted. Run without `--dry-run` to swap it in."
|
|
7551
7817
|
];
|
|
7552
7818
|
case "rebuilt":
|
|
7553
7819
|
return [
|
|
7554
7820
|
...rebuildLines(result),
|
|
7555
|
-
`\u2705 Rebuilt at
|
|
7821
|
+
`\u2705 Rebuilt at this rafa's migrations. The original is kept whole at ${String(result.backupPath)};` + " rename it back to undo."
|
|
7556
7822
|
];
|
|
7557
7823
|
}
|
|
7558
7824
|
}
|
|
@@ -7564,15 +7830,18 @@ function runFixSchema(context, seams) {
|
|
|
7564
7830
|
const root = context.project.root;
|
|
7565
7831
|
if (!dryRun)
|
|
7566
7832
|
refuseLiveLoop(root, seams.isAlive);
|
|
7833
|
+
const now = seams.now ?? (() => new Date);
|
|
7567
7834
|
let result;
|
|
7568
7835
|
try {
|
|
7569
7836
|
result = fixStoreSchema({
|
|
7570
7837
|
path: sqliteStorePath(root),
|
|
7571
7838
|
dryRun,
|
|
7572
|
-
stamp: fileStamp(
|
|
7839
|
+
stamp: fileStamp(now()),
|
|
7840
|
+
now,
|
|
7841
|
+
...seams.identity === undefined ? {} : { identity: seams.identity }
|
|
7573
7842
|
});
|
|
7574
7843
|
} catch (error) {
|
|
7575
|
-
if (error instanceof
|
|
7844
|
+
if (error instanceof RebuildRefusal)
|
|
7576
7845
|
throw new CommandExit(1, `\u274C ${COMMAND_NAME5}: ${error.message}`);
|
|
7577
7846
|
throw error;
|
|
7578
7847
|
}
|
|
@@ -7588,13 +7857,13 @@ function createFixSchemaCommand(seams = {}) {
|
|
|
7588
7857
|
name: "effort fix-schema",
|
|
7589
7858
|
subject: "effort",
|
|
7590
7859
|
action: "fix-schema",
|
|
7591
|
-
summary: "rebuild an effort store
|
|
7592
|
-
description: "Repairs `.rafa/effort/effort.sqlite` when it
|
|
7860
|
+
summary: "rebuild an effort store this rafa refuses at the migrations it knows, keeping the original",
|
|
7861
|
+
description: "Repairs `.rafa/effort/effort.sqlite`, or the store under `RAFA_EFFORT_DIR`, when this rafa refuses" + " it for a reason a rebuild repairs: no migration log past the legacy entries (pre-log-unreleased), a log" + " with a schema version it does not match (gate-mismatch), a logged checksum this rafa holds otherwise" + " (edited), or a logged migration this rafa does not know that breaks older readers or writers" + " (unknown-breaks-readers, unknown-breaks-writers). Installing a rafa that knows the store's migrations" + " comes first where one exists. It builds a parallel store beside it at this rafa's migrations, with a" + " migration log naming this rafa, copies every table and column this rafa knows, checks the row counts," + " SQLite's integrity_check and the schema plan, and lists what only the newer schema holds, unknown" + " migrations included. Then it renames the original to `effort.sqlite.v<user_version>-<stamp>.bak`," + " whole, and moves the rebuild into its place. A store this rafa uses, current or behind, or no store, is" + " left alone. It refuses, changing nothing, while a loop session under the project is running or paused," + " from a development build, while a journal beside the store shows a write in flight, when the newer" + " schema lacks a table or column this rafa writes, and on a refusal a rebuild does not repair. With" + " `--output=json` the outcome is the data of the terminal result event. Starts no session.",
|
|
7593
7862
|
args: [],
|
|
7594
7863
|
flags: [
|
|
7595
7864
|
{
|
|
7596
7865
|
name: DRY_RUN_FLAG2,
|
|
7597
|
-
description: "Build and check the rebuild, print what it keeps and leaves behind, then delete it. The" + " live store is only read, so this runs beside a live loop and can be repeated.",
|
|
7866
|
+
description: "Build and check the rebuild, print what it keeps and leaves behind, then delete it. The" + " live store is only read, so this runs beside a live loop and from a development build, and can be" + " repeated.",
|
|
7598
7867
|
type: "boolean"
|
|
7599
7868
|
}
|
|
7600
7869
|
],
|
|
@@ -7618,6 +7887,288 @@ function createFixSchemaCommand(seams = {}) {
|
|
|
7618
7887
|
}
|
|
7619
7888
|
var fix_schema_default = createFixSchemaCommand();
|
|
7620
7889
|
|
|
7890
|
+
// src/commands/effort/copy.ts
|
|
7891
|
+
var COMMAND_NAME6 = "rafa effort copy";
|
|
7892
|
+
var COPY_USAGE = "rafa effort copy [--to=<dir>]";
|
|
7893
|
+
var TO_FLAG = "to";
|
|
7894
|
+
var REFUSED_EXIT = 1;
|
|
7895
|
+
var FAILED_EXIT = 2;
|
|
7896
|
+
var SCRATCH_DIR = join19(".rafa", "scratch");
|
|
7897
|
+
function effortDirLine(directory) {
|
|
7898
|
+
return `${EFFORT_DIR_VARIABLE}=${directory}`;
|
|
7899
|
+
}
|
|
7900
|
+
function readTarget(context, root) {
|
|
7901
|
+
const value = context.flags[TO_FLAG];
|
|
7902
|
+
if (value === undefined)
|
|
7903
|
+
return null;
|
|
7904
|
+
if (typeof value !== "string" || value === "") {
|
|
7905
|
+
throw new CommandExit(REFUSED_EXIT, `\u274C --${TO_FLAG} takes a directory: --${TO_FLAG}=<dir>
|
|
7906
|
+
Usage: ${COPY_USAGE}`);
|
|
7907
|
+
}
|
|
7908
|
+
return resolve7(root, value);
|
|
7909
|
+
}
|
|
7910
|
+
function refuseEffortDirOverride(context) {
|
|
7911
|
+
const value = context.env[EFFORT_DIR_VARIABLE];
|
|
7912
|
+
if (value === undefined || value === "")
|
|
7913
|
+
return;
|
|
7914
|
+
throw new CommandExit(REFUSED_EXIT, `\u274C ${COMMAND_NAME6}: ${EFFORT_DIR_VARIABLE} is set (${value}); a copy is taken from the project's own store.` + ` Nothing was copied. Unset it and run again: ${EFFORT_DIR_VARIABLE}= ${COMMAND_NAME6}`);
|
|
7915
|
+
}
|
|
7916
|
+
function renderCopy(result) {
|
|
7917
|
+
return [
|
|
7918
|
+
`\u2705 Copied ${result.files.join(", ")} from ${result.source} to ${result.directory}.`,
|
|
7919
|
+
"Put this in front of each command that should use the copy, on the same line:",
|
|
7920
|
+
effortDirLine(result.directory)
|
|
7921
|
+
];
|
|
7922
|
+
}
|
|
7923
|
+
function runCopy(context, seams) {
|
|
7924
|
+
expectNoArgument(context.args, COPY_USAGE);
|
|
7925
|
+
const root = requireProject(context, COMMAND_NAME6).root;
|
|
7926
|
+
const named = readTarget(context, root);
|
|
7927
|
+
refuseEffortDirOverride(context);
|
|
7928
|
+
const target = named ?? join19(root, SCRATCH_DIR, `effort-${fileStamp((seams.now ?? (() => new Date))())}`);
|
|
7929
|
+
let result;
|
|
7930
|
+
try {
|
|
7931
|
+
result = copyEffortStore({ source: join19(root, EFFORT_STORE_DIR), target });
|
|
7932
|
+
} catch (error) {
|
|
7933
|
+
if (error instanceof EffortCopyRefusal)
|
|
7934
|
+
throw new CommandExit(REFUSED_EXIT, `\u274C ${COMMAND_NAME6}: ${error.message}`);
|
|
7935
|
+
if (error instanceof EffortCopyFailure)
|
|
7936
|
+
throw new CommandExit(FAILED_EXIT, `\u274C ${COMMAND_NAME6}: ${error.message}`);
|
|
7937
|
+
throw error;
|
|
7938
|
+
}
|
|
7939
|
+
if (context.outputMode === "json") {
|
|
7940
|
+
context.output.result({ ...result, env: effortDirLine(result.directory) });
|
|
7941
|
+
return;
|
|
7942
|
+
}
|
|
7943
|
+
for (const line of renderCopy(result))
|
|
7944
|
+
context.output.info(line);
|
|
7945
|
+
}
|
|
7946
|
+
function createCopyCommand(seams = {}) {
|
|
7947
|
+
const command = {
|
|
7948
|
+
name: "effort copy",
|
|
7949
|
+
subject: "effort",
|
|
7950
|
+
action: "copy",
|
|
7951
|
+
summary: "copy the effort store into a scratch directory and print the RAFA_EFFORT_DIR= line that points a command at it",
|
|
7952
|
+
description: "Copies `.rafa/effort/` into a directory of its own: the SQLite file through `VACUUM INTO`, which only" + " reads the store and keeps its schema version and migration log, and the NDJSON files by file copy. The" + " copy lands in `--to`, or in `.rafa/scratch/effort-<stamp>/`. It prints the directory and the" + " `RAFA_EFFORT_DIR=` line to put in front of each command that should run over the copy instead of the" + " live store. It plans no schema, so a development build may run it and a running loop is not disturbed." + " Exit code 1, making nothing, when `RAFA_EFFORT_DIR` is set, when there is no store, or when the target" + " is not empty; exit code 2 when a read or write failed, with what it made removed. With `--output=json`" + " the copy is the data of the terminal result event. Starts no session.",
|
|
7953
|
+
args: [],
|
|
7954
|
+
flags: [
|
|
7955
|
+
{
|
|
7956
|
+
name: TO_FLAG,
|
|
7957
|
+
description: "The directory to copy into, absent or empty; a relative one is read from the project root." + " Defaults to `.rafa/scratch/effort-<stamp>/`.",
|
|
7958
|
+
type: "string"
|
|
7959
|
+
}
|
|
7960
|
+
],
|
|
7961
|
+
examples: [
|
|
7962
|
+
{
|
|
7963
|
+
cmd: "rafa effort copy",
|
|
7964
|
+
note: "Copies the store into `.rafa/scratch/effort-<stamp>/` and prints the `RAFA_EFFORT_DIR=` line."
|
|
7965
|
+
},
|
|
7966
|
+
{
|
|
7967
|
+
cmd: "rafa effort copy --to=.rafa/scratch/rafa-234-effort",
|
|
7968
|
+
note: "Copies the store into a directory a plan names."
|
|
7969
|
+
}
|
|
7970
|
+
],
|
|
7971
|
+
outputs: ["text", "json"],
|
|
7972
|
+
run: async (context) => {
|
|
7973
|
+
runCopy(context, seams);
|
|
7974
|
+
await Promise.resolve();
|
|
7975
|
+
}
|
|
7976
|
+
};
|
|
7977
|
+
return Object.freeze(command);
|
|
7978
|
+
}
|
|
7979
|
+
var copy_default = createCopyCommand();
|
|
7980
|
+
|
|
7981
|
+
// src/commands/effort/migrate.ts
|
|
7982
|
+
import { join as join20 } from "path";
|
|
7983
|
+
|
|
7984
|
+
// src/effort/store/migrate.ts
|
|
7985
|
+
import { existsSync as existsSync8 } from "fs";
|
|
7986
|
+
import { Database as Database3 } from "bun:sqlite";
|
|
7987
|
+
var LIVE2 = "live";
|
|
7988
|
+
function asidePaths2(path, first, stamp) {
|
|
7989
|
+
return { parallelPath: `${path}.migrate-${stamp}`, backupPath: `${path}.before-${first}-${stamp}.bak` };
|
|
7990
|
+
}
|
|
7991
|
+
function liveLoop(path, options) {
|
|
7992
|
+
const root = storeProjectRoot(path);
|
|
7993
|
+
if (root === null)
|
|
7994
|
+
return;
|
|
7995
|
+
let records;
|
|
7996
|
+
try {
|
|
7997
|
+
records = readSessions(root, { isAlive: options.isAlive ?? isPidAlive });
|
|
7998
|
+
} catch (error) {
|
|
7999
|
+
throw new RebuildRefusal(`the loop's run records cannot be read, so a live loop on ${path} cannot be ruled out` + ` (${messageOf(error)}). Nothing was migrated.`);
|
|
8000
|
+
}
|
|
8001
|
+
return records.find((record) => record.state === "running" || record.state === "paused");
|
|
8002
|
+
}
|
|
8003
|
+
function refuseLiveLoop2(path, breaking, options) {
|
|
8004
|
+
const loop = liveLoop(path, options);
|
|
8005
|
+
if (loop === undefined)
|
|
8006
|
+
return;
|
|
8007
|
+
const named = `loop ${loop.sessionId} (pid ${String(loop.pid)}, plan ${loop.planStub ?? loop.plan}) is running on this store`;
|
|
8008
|
+
const [first] = breaking;
|
|
8009
|
+
const opening = first === undefined ? `REFUSED \u2014 ${named}, and swapping the migrated store in under it would lose what it writes.` : `REFUSED \u2014 migration ${first} breaks older runtimes, and ${named}.`;
|
|
8010
|
+
throw new RebuildRefusal(`${opening} Nothing was migrated. Finish or stop that loop, then run it again.`);
|
|
8011
|
+
}
|
|
8012
|
+
function buildMigrated(path, parallelPath, migrations, stamp) {
|
|
8013
|
+
vacuumInto(path, parallelPath);
|
|
8014
|
+
const db = new Database3(parallelPath, { readwrite: true });
|
|
8015
|
+
try {
|
|
8016
|
+
const brought = bringForward(db, parallelPath, "write", "migrate", { migrations, ...stamp, builtAside: true });
|
|
8017
|
+
db.run(`ATTACH DATABASE ? AS ${LIVE2}`, [path]);
|
|
8018
|
+
const live = new Set(tableNames(db, LIVE2));
|
|
8019
|
+
const counted2 = checkedCounts(db, tableNames(db, "main").filter((table) => live.has(table)), LIVE2);
|
|
8020
|
+
db.run(`DETACH DATABASE ${LIVE2}`);
|
|
8021
|
+
refuseCorrupt(db);
|
|
8022
|
+
refuseUnusable(db, parallelPath, migrations);
|
|
8023
|
+
return { userVersion: brought.userVersion, counted: counted2 };
|
|
8024
|
+
} finally {
|
|
8025
|
+
db.close();
|
|
8026
|
+
}
|
|
8027
|
+
}
|
|
8028
|
+
function migrateStore(options) {
|
|
8029
|
+
const { path, dryRun, stamp } = options;
|
|
8030
|
+
guardTestProcess(path);
|
|
8031
|
+
const migrations = options.migrations ?? SQLITE_MIGRATIONS;
|
|
8032
|
+
const appliedBy = appliedByName(options.identity ?? readRuntimeIdentity());
|
|
8033
|
+
const nothing = { applying: [], breaking: [], unknown: [], userVersion: null, counted: [], backupPath: null };
|
|
8034
|
+
if (!existsSync8(path)) {
|
|
8035
|
+
return { path, status: "missing", storeVersion: null, logged: false, appliedBy, ...nothing };
|
|
8036
|
+
}
|
|
8037
|
+
refuseInFlight(path);
|
|
8038
|
+
const store = readOnlySchema(path);
|
|
8039
|
+
const found = { path, storeVersion: store.userVersion, logged: store.log !== null, appliedBy };
|
|
8040
|
+
const plan2 = planSchema(store, sqliteCatalogue(migrations), "write", "migrate");
|
|
8041
|
+
if (plan2.verdict === "refuse")
|
|
8042
|
+
throw new RebuildRefusal(plan2.message);
|
|
8043
|
+
const applying = writeNames(plan2);
|
|
8044
|
+
const planned = {
|
|
8045
|
+
applying,
|
|
8046
|
+
breaking: plan2.pending.filter(({ breaks }) => breaks.length > 0).map(({ id: id2 }) => id2),
|
|
8047
|
+
unknown: plan2.unknown
|
|
8048
|
+
};
|
|
8049
|
+
if (applying.length === 0)
|
|
8050
|
+
return { ...found, ...nothing, ...planned, status: "current" };
|
|
8051
|
+
if (!dryRun) {
|
|
8052
|
+
refuseUnownedDevelopmentWrite(path, applying, options);
|
|
8053
|
+
refuseLiveLoop2(path, planned.breaking, options);
|
|
8054
|
+
}
|
|
8055
|
+
const first = plan2.pending[0]?.id ?? ADOPTION_NAME;
|
|
8056
|
+
const { built, backupPath } = rebuildAside({
|
|
8057
|
+
path,
|
|
8058
|
+
...asidePaths2(path, first, stamp),
|
|
8059
|
+
dryRun,
|
|
8060
|
+
build: (parallelPath) => buildMigrated(path, parallelPath, migrations, {
|
|
8061
|
+
appliedBy,
|
|
8062
|
+
now: options.now ?? (() => new Date)
|
|
8063
|
+
})
|
|
8064
|
+
});
|
|
8065
|
+
const status = dryRun ? "would-migrate" : "migrated";
|
|
8066
|
+
return { ...found, ...planned, ...built, status, backupPath };
|
|
8067
|
+
}
|
|
8068
|
+
|
|
8069
|
+
// src/commands/effort/migrate.ts
|
|
8070
|
+
var COMMAND_NAME7 = "rafa effort migrate";
|
|
8071
|
+
var MIGRATE_USAGE = "rafa effort migrate [--dry-run]";
|
|
8072
|
+
var DRY_RUN_FLAG3 = "dry-run";
|
|
8073
|
+
var REFUSED_EXIT2 = 1;
|
|
8074
|
+
function builtLines(result) {
|
|
8075
|
+
const rows = result.counted.reduce((sum, table) => sum + table.rows, 0);
|
|
8076
|
+
const breaking = result.breaking.length === 0 ? "Every one is additive." : `${result.breaking.join(", ")} breaks older runtimes: a rafa that does not know it refuses the store once it runs.`;
|
|
8077
|
+
return [
|
|
8078
|
+
`Applies ${result.applying.join(", ")} to ${result.path}, logged as applied by ${result.appliedBy}. ${breaking}`,
|
|
8079
|
+
`Row counts match in ${String(result.counted.length)} tables, ${String(rows)} rows; integrity_check ok;` + ` schema version ${String(result.userVersion)} after.`
|
|
8080
|
+
];
|
|
8081
|
+
}
|
|
8082
|
+
function renderMigrate(result) {
|
|
8083
|
+
switch (result.status) {
|
|
8084
|
+
case "missing":
|
|
8085
|
+
return [`No effort store at ${result.path}: nothing to migrate.`];
|
|
8086
|
+
case "current":
|
|
8087
|
+
return [`\u2705 ${result.path} is current: no migration is pending. Nothing to migrate.`];
|
|
8088
|
+
case "would-migrate":
|
|
8089
|
+
return [
|
|
8090
|
+
...builtLines(result),
|
|
8091
|
+
"\uD83D\uDD0D Dry run: the migrated store was built beside it, checked (row counts, integrity_check, this rafa's" + " schema plan) and deleted. Run `rafa effort migrate` to swap it in."
|
|
8092
|
+
];
|
|
8093
|
+
case "migrated":
|
|
8094
|
+
return [
|
|
8095
|
+
...builtLines(result),
|
|
8096
|
+
`\u2705 Migrated. The original is kept whole at ${String(result.backupPath)}; rename it back to undo.`
|
|
8097
|
+
];
|
|
8098
|
+
}
|
|
8099
|
+
}
|
|
8100
|
+
function storePath(context, root) {
|
|
8101
|
+
try {
|
|
8102
|
+
return join20(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
|
|
8103
|
+
} catch (error) {
|
|
8104
|
+
throw new CommandExit(REFUSED_EXIT2, `\u274C ${COMMAND_NAME7}: ${messageOf(error)}`);
|
|
8105
|
+
}
|
|
8106
|
+
}
|
|
8107
|
+
function runMigrate(context, seams) {
|
|
8108
|
+
expectNoArgument(context.args, MIGRATE_USAGE);
|
|
8109
|
+
const dryRun = readSwitch(DRY_RUN_FLAG3, context.flags[DRY_RUN_FLAG3], `Usage: ${MIGRATE_USAGE}`);
|
|
8110
|
+
const path = storePath(context, requireProject(context, COMMAND_NAME7).root);
|
|
8111
|
+
const now = seams.now ?? (() => new Date);
|
|
8112
|
+
let result;
|
|
8113
|
+
try {
|
|
8114
|
+
result = migrateStore({
|
|
8115
|
+
path,
|
|
8116
|
+
dryRun,
|
|
8117
|
+
stamp: fileStamp(now()),
|
|
8118
|
+
now,
|
|
8119
|
+
env: context.env,
|
|
8120
|
+
...seams.identity === undefined ? {} : { identity: seams.identity },
|
|
8121
|
+
...seams.isAlive === undefined ? {} : { isAlive: seams.isAlive },
|
|
8122
|
+
...seams.migrations === undefined ? {} : { migrations: seams.migrations }
|
|
8123
|
+
});
|
|
8124
|
+
} catch (error) {
|
|
8125
|
+
if (error instanceof RebuildRefusal || error instanceof DevelopmentBuildRefusedError) {
|
|
8126
|
+
throw new CommandExit(REFUSED_EXIT2, `\u274C ${COMMAND_NAME7}: ${error.message}`);
|
|
8127
|
+
}
|
|
8128
|
+
throw error;
|
|
8129
|
+
}
|
|
8130
|
+
if (context.outputMode === "json") {
|
|
8131
|
+
context.output.result(result);
|
|
8132
|
+
return;
|
|
8133
|
+
}
|
|
8134
|
+
for (const line of renderMigrate(result))
|
|
8135
|
+
context.output.info(line);
|
|
8136
|
+
}
|
|
8137
|
+
function createMigrateCommand(seams = {}) {
|
|
8138
|
+
const command = {
|
|
8139
|
+
name: "effort migrate",
|
|
8140
|
+
subject: "effort",
|
|
8141
|
+
action: "migrate",
|
|
8142
|
+
summary: "apply the effort store's pending migrations, one that breaks older runtimes included, keeping the original",
|
|
8143
|
+
description: "Brings `.rafa/effort/effort.sqlite`, or the store under `RAFA_EFFORT_DIR`, to the migrations this" + " rafa knows, including one that breaks older runtimes, which no ordinary open applies. It writes the" + " store to `effort.sqlite.migrate-<stamp>` beside it with `VACUUM INTO`, applies the pending migrations" + " there with log rows naming this rafa, checks the row count of every table against the store," + " SQLite's integrity_check and the schema plan, then renames the original to" + " `effort.sqlite.before-<id>-<stamp>.bak`, whole, and moves the migrated file into its place. A store" + " with nothing pending, or no store, is left alone. It refuses, changing nothing, while a loop session" + " under the project is running or paused, from a development build over any store but a copy under" + " `RAFA_EFFORT_DIR`, while a journal beside the store shows a write in flight, when a row count" + " changes, and on any refusal of the schema plan, whose text ends with the next safe step. With" + " `--output=json` the outcome is the data of the terminal result event. Starts no session.",
|
|
8144
|
+
args: [],
|
|
8145
|
+
flags: [
|
|
8146
|
+
{
|
|
8147
|
+
name: DRY_RUN_FLAG3,
|
|
8148
|
+
description: "Build and check the migrated store, print what it applies, then delete it. The live store" + " is only read, so this runs beside a live loop and from a development build, and can be repeated.",
|
|
8149
|
+
type: "boolean"
|
|
8150
|
+
}
|
|
8151
|
+
],
|
|
8152
|
+
examples: [
|
|
8153
|
+
{
|
|
8154
|
+
cmd: "rafa effort migrate --dry-run",
|
|
8155
|
+
note: "Prints what the migration applies and how it checked, changing nothing."
|
|
8156
|
+
},
|
|
8157
|
+
{
|
|
8158
|
+
cmd: "rafa effort migrate",
|
|
8159
|
+
note: "Swaps the migrated store in and prints where the original was kept."
|
|
8160
|
+
}
|
|
8161
|
+
],
|
|
8162
|
+
outputs: ["text", "json"],
|
|
8163
|
+
run: async (context) => {
|
|
8164
|
+
runMigrate(context, seams);
|
|
8165
|
+
await Promise.resolve();
|
|
8166
|
+
}
|
|
8167
|
+
};
|
|
8168
|
+
return Object.freeze(command);
|
|
8169
|
+
}
|
|
8170
|
+
var migrate_default = createMigrateCommand();
|
|
8171
|
+
|
|
7621
8172
|
// src/commands/effort/report.ts
|
|
7622
8173
|
var report_default = wrapPhaseZeroCommand({
|
|
7623
8174
|
name: "effort report",
|
|
@@ -7671,6 +8222,113 @@ var report_default = wrapPhaseZeroCommand({
|
|
|
7671
8222
|
outputs: ["text", "json"]
|
|
7672
8223
|
}, report);
|
|
7673
8224
|
|
|
8225
|
+
// src/commands/effort/schema.ts
|
|
8226
|
+
import { join as join21 } from "path";
|
|
8227
|
+
var COMMAND_NAME8 = "rafa effort schema";
|
|
8228
|
+
var SCHEMA_USAGE = "rafa effort schema [--check]";
|
|
8229
|
+
var CHECK_FLAG = "check";
|
|
8230
|
+
var REFUSED_EXIT3 = 1;
|
|
8231
|
+
var FAILED_EXIT2 = 2;
|
|
8232
|
+
function listLine(name2, items) {
|
|
8233
|
+
const shown = items.length === 0 ? "none" : items.join(", ");
|
|
8234
|
+
return `${name2} (${String(items.length)}): ${shown}`;
|
|
8235
|
+
}
|
|
8236
|
+
function gateLine(report2) {
|
|
8237
|
+
const held = report2.userVersion ?? 0;
|
|
8238
|
+
const now = report2.logged ? `${String(held)} (${gateMeaning(held)})` : `${String(held)} (a count of legacy migrations: no log yet)`;
|
|
8239
|
+
const next = report2.gate === null || report2.gate === report2.userVersion ? "" : `; ${String(report2.gate)} (${gateMeaning(report2.gate)}) once the next open brings it forward`;
|
|
8240
|
+
return `Gate (user_version): ${now}${next}`;
|
|
8241
|
+
}
|
|
8242
|
+
function verdictLine(report2) {
|
|
8243
|
+
switch (report2.status) {
|
|
8244
|
+
case "absent":
|
|
8245
|
+
return "No store yet: the first write creates it at this rafa's migrations.";
|
|
8246
|
+
case "current":
|
|
8247
|
+
return "\u2705 Current: this rafa reads and writes the store as it is.";
|
|
8248
|
+
case "behind":
|
|
8249
|
+
return "\u2705 Behind: the next command that opens the store brings it forward in one transaction.";
|
|
8250
|
+
default:
|
|
8251
|
+
return `\u274C Refused (${report2.status}): ${String(report2.refusal)}`;
|
|
8252
|
+
}
|
|
8253
|
+
}
|
|
8254
|
+
function renderSchema(report2) {
|
|
8255
|
+
if (!report2.exists)
|
|
8256
|
+
return [`Effort store: ${report2.path}`, verdictLine(report2), `Next safe step: ${report2.nextStep}`];
|
|
8257
|
+
const log = report2.logged ? "Migration log: present." : "Migration log: none; a release before the log wrote this store, and the next open adopts it.";
|
|
8258
|
+
return [
|
|
8259
|
+
`Effort store: ${report2.path}`,
|
|
8260
|
+
log,
|
|
8261
|
+
gateLine(report2),
|
|
8262
|
+
listLine("Applied", report2.applied.map(({ id: id2, appliedBy }) => appliedBy === null ? id2 : `${id2} (${appliedBy})`)),
|
|
8263
|
+
listLine("Pending", report2.pending),
|
|
8264
|
+
listLine("Unknown", report2.unknown.map(({ id: id2, breaks, appliedBy, appliedAt }) => `${id2} (breaks ${breaks.length === 0 ? "nothing" : breaks.join(" and ")}; applied by ${appliedBy} on ${appliedAt})`)),
|
|
8265
|
+
listLine("Edited", report2.edited.map(({ id: id2, recorded, expected }) => `${id2} (store ${recorded}, this rafa ${expected})`)),
|
|
8266
|
+
verdictLine(report2),
|
|
8267
|
+
`Next safe step: ${report2.nextStep}`
|
|
8268
|
+
];
|
|
8269
|
+
}
|
|
8270
|
+
function storePath2(context, root) {
|
|
8271
|
+
try {
|
|
8272
|
+
return join21(effortStoreDir(root, context.env), SQLITE_STORE_FILE_NAME);
|
|
8273
|
+
} catch (error) {
|
|
8274
|
+
throw new CommandExit(REFUSED_EXIT3, `\u274C ${COMMAND_NAME8}: ${messageOf(error)}`);
|
|
8275
|
+
}
|
|
8276
|
+
}
|
|
8277
|
+
function runSchema(context, seams) {
|
|
8278
|
+
expectNoArgument(context.args, SCHEMA_USAGE);
|
|
8279
|
+
const check = readSwitch(CHECK_FLAG, context.flags[CHECK_FLAG], `Usage: ${SCHEMA_USAGE}`);
|
|
8280
|
+
const path = storePath2(context, requireProject(context, COMMAND_NAME8).root);
|
|
8281
|
+
let report2;
|
|
8282
|
+
try {
|
|
8283
|
+
report2 = readSchemaReport(path, seams);
|
|
8284
|
+
} catch (error) {
|
|
8285
|
+
throw new CommandExit(FAILED_EXIT2, `\u274C ${COMMAND_NAME8}: ${path} cannot be read as an effort store: ${messageOf(error)}`);
|
|
8286
|
+
}
|
|
8287
|
+
if (context.outputMode !== "json") {
|
|
8288
|
+
for (const line of renderSchema(report2))
|
|
8289
|
+
context.output.info(line);
|
|
8290
|
+
}
|
|
8291
|
+
if (check && report2.refused) {
|
|
8292
|
+
throw new CommandExit(REFUSED_EXIT3, `\u274C ${COMMAND_NAME8} --check: this rafa refuses ${path} (${report2.status}). Next safe step: ${report2.nextStep}`);
|
|
8293
|
+
}
|
|
8294
|
+
if (context.outputMode === "json")
|
|
8295
|
+
context.output.result(report2);
|
|
8296
|
+
}
|
|
8297
|
+
function createSchemaCommand(seams = {}) {
|
|
8298
|
+
const command = {
|
|
8299
|
+
name: "effort schema",
|
|
8300
|
+
subject: "effort",
|
|
8301
|
+
action: "schema",
|
|
8302
|
+
summary: "say whether this rafa can read and write the effort store, and what to run next when it cannot",
|
|
8303
|
+
description: "Opens the SQLite effort store read-only, the one under `RAFA_EFFORT_DIR` when it is set, and" + " migrates nothing. Prints the migrations it holds that this rafa knows (applied), the ones it lacks" + " (pending), the ones this rafa does not know with what they break (unknown), and the ones recorded under" + " another checksum (edited), then the legacy gate `user_version` holds and whether this rafa would use" + " the store or refuse it, and why. It ends with `Next safe step: <command>`, the command the matching" + " refusal names, or `none`. With `--check` it exits 1 when this rafa would refuse to read or to write" + " the store. Exit code 2 when the file cannot be read as a store. With `--output=json` the report, with" + " its `nextStep`, is the data of the terminal result event. Starts no session.",
|
|
8304
|
+
args: [],
|
|
8305
|
+
flags: [
|
|
8306
|
+
{
|
|
8307
|
+
name: CHECK_FLAG,
|
|
8308
|
+
description: "Exit 1 when this rafa would refuse to read or to write the store; exit 0 otherwise.",
|
|
8309
|
+
type: "boolean"
|
|
8310
|
+
}
|
|
8311
|
+
],
|
|
8312
|
+
examples: [
|
|
8313
|
+
{
|
|
8314
|
+
cmd: "rafa effort schema",
|
|
8315
|
+
note: "Prints the store's migrations, the gate and the verdict, ending with the next safe step."
|
|
8316
|
+
},
|
|
8317
|
+
{
|
|
8318
|
+
cmd: "rafa effort schema --check",
|
|
8319
|
+
note: "The same report, exiting 1 when this rafa would refuse the store."
|
|
8320
|
+
}
|
|
8321
|
+
],
|
|
8322
|
+
outputs: ["text", "json"],
|
|
8323
|
+
run: async (context) => {
|
|
8324
|
+
runSchema(context, seams);
|
|
8325
|
+
await Promise.resolve();
|
|
8326
|
+
}
|
|
8327
|
+
};
|
|
8328
|
+
return Object.freeze(command);
|
|
8329
|
+
}
|
|
8330
|
+
var schema_default = createSchemaCommand();
|
|
8331
|
+
|
|
7674
8332
|
// src/board/roadmap-tick.ts
|
|
7675
8333
|
var PREFIX2 = "board roadmap";
|
|
7676
8334
|
var REPO_PATH = "{owner}/{repo}";
|
|
@@ -8037,7 +8695,7 @@ async function applyHorizonChange(board, reading, reason) {
|
|
|
8037
8695
|
}
|
|
8038
8696
|
|
|
8039
8697
|
// src/commands/epic/horizon-change.ts
|
|
8040
|
-
var
|
|
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"];
|
|
@@ -15167,7 +15561,7 @@ function textOf(path) {
|
|
|
15167
15561
|
}
|
|
15168
15562
|
}
|
|
15169
15563
|
function followUpsFor(pr, git) {
|
|
15170
|
-
const facts = readPackageFacts(textOf(
|
|
15564
|
+
const facts = readPackageFacts(textOf(join34(pr.project.root, "package.json")));
|
|
15171
15565
|
if (facts.version === null)
|
|
15172
15566
|
return [];
|
|
15173
15567
|
const tags = git(["tag", "--list", versionTag(facts.version)]);
|
|
@@ -15175,7 +15569,7 @@ function followUpsFor(pr, git) {
|
|
|
15175
15569
|
version: facts.version,
|
|
15176
15570
|
tagged: tags.ok && tags.stdout.trim() !== "",
|
|
15177
15571
|
rafaCheckout: facts.rafaCheckout,
|
|
15178
|
-
runtimeInstalled:
|
|
15572
|
+
runtimeInstalled: existsSync15(join34(pr.project.home, RUNTIME_SUBDIR, facts.version))
|
|
15179
15573
|
});
|
|
15180
15574
|
}
|
|
15181
15575
|
function openGh(pr, seams) {
|
|
@@ -15306,17 +15700,17 @@ async function commentIfUnchecked(context, pr, number, unchecked) {
|
|
|
15306
15700
|
return posted.url;
|
|
15307
15701
|
}
|
|
15308
15702
|
async function runMerge(context, seams) {
|
|
15309
|
-
const yes = readBooleanFlag(context.flags, "yes",
|
|
15310
|
-
const skipChecks = readBooleanFlag(context.flags, "skip-checks",
|
|
15311
|
-
const wanted = readMethodFlag(context.flags,
|
|
15312
|
-
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);
|
|
15313
15707
|
const pr = openPrContext(context, seams);
|
|
15314
15708
|
const git = (seams.git ?? createGitRunner)(pr.project.root);
|
|
15315
15709
|
const method = wanted ?? pr.mergeMethod;
|
|
15316
|
-
const pick = await pickPullRequest(pr, asked,
|
|
15710
|
+
const pick = await pickPullRequest(pr, asked, USAGE31);
|
|
15317
15711
|
const detail = await onProvider(`read pull request #${pick.number}`, () => pr.pulls.get(pick.number));
|
|
15318
15712
|
if (detail === null) {
|
|
15319
|
-
throw lineRefusal3(`No pull request #${pick.number} at ${pr.project.root}`,
|
|
15713
|
+
throw lineRefusal3(`No pull request #${pick.number} at ${pr.project.root}`, USAGE31);
|
|
15320
15714
|
}
|
|
15321
15715
|
const checks = await onProvider(`read the checks of #${pick.number}`, () => pr.pulls.checks(pick.number));
|
|
15322
15716
|
recordPlanCi({
|
|
@@ -15887,7 +16281,7 @@ function readLastTriage(comments) {
|
|
|
15887
16281
|
}
|
|
15888
16282
|
|
|
15889
16283
|
// src/commands/pr/show.ts
|
|
15890
|
-
var
|
|
16284
|
+
var USAGE32 = PR_USAGE.show;
|
|
15891
16285
|
var INDENT5 = " ";
|
|
15892
16286
|
var SHORT_SHA = 7;
|
|
15893
16287
|
function headLine3(detail) {
|
|
@@ -15985,12 +16379,12 @@ async function probe2(call) {
|
|
|
15985
16379
|
}
|
|
15986
16380
|
}
|
|
15987
16381
|
async function readShow(context, seams) {
|
|
15988
|
-
const asked = readPullArgument(context.args,
|
|
16382
|
+
const asked = readPullArgument(context.args, USAGE32);
|
|
15989
16383
|
const pr = openPrContext(context, seams);
|
|
15990
|
-
const pick = await pickPullRequest(pr, asked,
|
|
16384
|
+
const pick = await pickPullRequest(pr, asked, USAGE32);
|
|
15991
16385
|
const detail = await onProvider(`read pull request #${pick.number}`, () => pr.pulls.get(pick.number));
|
|
15992
16386
|
if (detail === null) {
|
|
15993
|
-
throw lineRefusal3(`No pull request #${pick.number} in the repository at ${pr.project.root}`,
|
|
16387
|
+
throw lineRefusal3(`No pull request #${pick.number} in the repository at ${pr.project.root}`, USAGE32);
|
|
15994
16388
|
}
|
|
15995
16389
|
const checks = await probe2(() => pr.pulls.checks(pick.number));
|
|
15996
16390
|
const comments = await probe2(() => pr.pulls.comments(pick.number));
|
|
@@ -16413,8 +16807,8 @@ function describeAge(updatedAt, now) {
|
|
|
16413
16807
|
return `${plural4(hours, "hour", "hours")} ago`;
|
|
16414
16808
|
return `${plural4(Math.floor(age / DAY_MS2), "day", "days")} ago`;
|
|
16415
16809
|
}
|
|
16416
|
-
function triageCommand(number,
|
|
16417
|
-
return
|
|
16810
|
+
function triageCommand(number, resolve14 = false) {
|
|
16811
|
+
return resolve14 ? `rafa pr triage ${number} --resolve` : `rafa pr triage ${number}`;
|
|
16418
16812
|
}
|
|
16419
16813
|
function joinNumbers(pulls) {
|
|
16420
16814
|
const names = pulls.map((pull) => `#${pull.number}`);
|
|
@@ -16425,14 +16819,14 @@ function joinNumbers(pulls) {
|
|
|
16425
16819
|
function byNumber(pulls) {
|
|
16426
16820
|
return Object.freeze([...pulls].sort((left, right) => left.number - right.number));
|
|
16427
16821
|
}
|
|
16428
|
-
function skipped(pull, now,
|
|
16822
|
+
function skipped(pull, now, resolve14) {
|
|
16429
16823
|
return Object.freeze({
|
|
16430
16824
|
number: pull.number,
|
|
16431
16825
|
title: pull.title,
|
|
16432
16826
|
updatedAt: pull.updatedAt,
|
|
16433
16827
|
age: describeAge(pull.updatedAt, now),
|
|
16434
16828
|
fresh: isFreshCandidate(pull.updatedAt, now),
|
|
16435
|
-
command: triageCommand(pull.number,
|
|
16829
|
+
command: triageCommand(pull.number, resolve14)
|
|
16436
16830
|
});
|
|
16437
16831
|
}
|
|
16438
16832
|
function skippedLine(one) {
|
|
@@ -16476,15 +16870,15 @@ function listReading(rest, count2) {
|
|
|
16476
16870
|
return listing("list", Object.freeze([]), rest, headline);
|
|
16477
16871
|
}
|
|
16478
16872
|
function selectTriagePullRequests(input) {
|
|
16479
|
-
const
|
|
16873
|
+
const resolve14 = input.resolve === true;
|
|
16480
16874
|
const { current, now } = input;
|
|
16481
16875
|
if (current !== null) {
|
|
16482
16876
|
return plain("branch", Object.freeze([current]), `assessing #${current.number}, the pull request open on ${current.headRefName}`);
|
|
16483
16877
|
}
|
|
16484
16878
|
const candidates = byNumber(input.candidates);
|
|
16485
16879
|
const count2 = candidates.length;
|
|
16486
|
-
const lines2 = candidates.map((pull) => skipped(pull, now,
|
|
16487
|
-
if (
|
|
16880
|
+
const lines2 = candidates.map((pull) => skipped(pull, now, resolve14));
|
|
16881
|
+
if (resolve14 && count2 > 1)
|
|
16488
16882
|
return refusal12(lines2, count2);
|
|
16489
16883
|
if (count2 === 0) {
|
|
16490
16884
|
return plain("none", Object.freeze([]), "no red pull request to triage, and no pull request on the current branch");
|
|
@@ -16819,8 +17213,8 @@ function withTaskBudget(plan2, usd) {
|
|
|
16819
17213
|
}
|
|
16820
17214
|
|
|
16821
17215
|
// src/pr/plans/load.ts
|
|
16822
|
-
import { existsSync as
|
|
16823
|
-
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";
|
|
16824
17218
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
16825
17219
|
var MODULE_DIR4 = dirname7(fileURLToPath4(import.meta.url));
|
|
16826
17220
|
var PINNED_PLANS_DIRNAME = "plans";
|
|
@@ -16837,8 +17231,8 @@ function hasPinnedPlan(triageClass) {
|
|
|
16837
17231
|
function pinnedPlanCandidates(moduleDir, triageClass) {
|
|
16838
17232
|
const name2 = pinnedPlanFileName(triageClass);
|
|
16839
17233
|
return [
|
|
16840
|
-
|
|
16841
|
-
|
|
17234
|
+
join35(moduleDir, PINNED_PLANS_DIRNAME, name2),
|
|
17235
|
+
join35(moduleDir, name2)
|
|
16842
17236
|
];
|
|
16843
17237
|
}
|
|
16844
17238
|
function readPinnedPlan(triageClass, lookup = {}) {
|
|
@@ -16846,7 +17240,7 @@ function readPinnedPlan(triageClass, lookup = {}) {
|
|
|
16846
17240
|
throw new Error(`No pinned resolve plan ships for ${triageClass}; ` + `one ships for ${PINNED_PLAN_CLASSES.join(", ")}`);
|
|
16847
17241
|
}
|
|
16848
17242
|
const candidates = pinnedPlanCandidates(lookup.moduleDir ?? MODULE_DIR4, triageClass);
|
|
16849
|
-
const found = candidates.find((candidate) =>
|
|
17243
|
+
const found = candidates.find((candidate) => existsSync16(candidate));
|
|
16850
17244
|
if (found === undefined) {
|
|
16851
17245
|
throw new Error(`The pinned resolve plan for ${triageClass} is missing: ` + `no file at ${candidates.join(" or ")}`);
|
|
16852
17246
|
}
|
|
@@ -16990,20 +17384,20 @@ function readAttemptRepeat(input) {
|
|
|
16990
17384
|
}
|
|
16991
17385
|
|
|
16992
17386
|
// src/commands/pr/resolve-loop.ts
|
|
16993
|
-
import { mkdirSync as
|
|
16994
|
-
import { join as
|
|
16995
|
-
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");
|
|
16996
17390
|
var RUN_PREFIX = "pr-";
|
|
16997
17391
|
var ATTEMPT_PREFIX = "attempt-";
|
|
16998
17392
|
var LOOP_WORDS = Object.freeze(["loop", "start"]);
|
|
16999
17393
|
var CHILD_OUTPUT = "text";
|
|
17000
17394
|
function resolveRunDir(home, number, attempt3) {
|
|
17001
|
-
return
|
|
17395
|
+
return join36(home, RESOLVE_SUBDIR, `${RUN_PREFIX}${number}`, `${ATTEMPT_PREFIX}${attempt3}`);
|
|
17002
17396
|
}
|
|
17003
17397
|
function writeResolvePlan(write) {
|
|
17004
17398
|
const dir = resolveRunDir(write.home, write.number, write.attempt);
|
|
17005
|
-
|
|
17006
|
-
const path =
|
|
17399
|
+
mkdirSync7(dir, { recursive: true });
|
|
17400
|
+
const path = join36(dir, pinnedPlanFileName(write.triageClass));
|
|
17007
17401
|
writeFileSync5(path, write.plan, "utf8");
|
|
17008
17402
|
return path;
|
|
17009
17403
|
}
|
|
@@ -17362,7 +17756,7 @@ async function runAttempts(run, detail, first) {
|
|
|
17362
17756
|
}
|
|
17363
17757
|
|
|
17364
17758
|
// src/commands/pr/triage.ts
|
|
17365
|
-
var
|
|
17759
|
+
var USAGE33 = PR_USAGE.triage;
|
|
17366
17760
|
var DEFAULT_MAX_ATTEMPTS = 2;
|
|
17367
17761
|
var DETACHED_HEAD2 = "HEAD";
|
|
17368
17762
|
var RESOLVED = false;
|
|
@@ -17403,22 +17797,22 @@ async function redPullRequests(pr) {
|
|
|
17403
17797
|
}
|
|
17404
17798
|
return red;
|
|
17405
17799
|
}
|
|
17406
|
-
async function selectTargets(pr, warn3, now,
|
|
17800
|
+
async function selectTargets(pr, warn3, now, resolve14) {
|
|
17407
17801
|
const branch = readBranchOrNone(pr, warn3);
|
|
17408
17802
|
const current = branch === null ? null : await onProvider(`read the open pull request for the branch "${branch}"`, () => pr.pulls.findOpen(branch));
|
|
17409
17803
|
if (current !== null)
|
|
17410
|
-
return selectTriagePullRequests({ current, candidates: [], now, resolve:
|
|
17804
|
+
return selectTriagePullRequests({ current, candidates: [], now, resolve: resolve14 });
|
|
17411
17805
|
return selectTriagePullRequests({
|
|
17412
17806
|
current: null,
|
|
17413
17807
|
candidates: await redPullRequests(pr),
|
|
17414
17808
|
now,
|
|
17415
|
-
resolve:
|
|
17809
|
+
resolve: resolve14
|
|
17416
17810
|
});
|
|
17417
17811
|
}
|
|
17418
17812
|
async function detailOf4(pr, number) {
|
|
17419
17813
|
const detail = await onProvider(`read pull request #${number}`, () => pr.pulls.get(number));
|
|
17420
17814
|
if (detail === null) {
|
|
17421
|
-
throw lineRefusal3(`No pull request #${number} in the repository at ${pr.project.root}`,
|
|
17815
|
+
throw lineRefusal3(`No pull request #${number} in the repository at ${pr.project.root}`, USAGE33);
|
|
17422
17816
|
}
|
|
17423
17817
|
return detail;
|
|
17424
17818
|
}
|
|
@@ -17521,14 +17915,14 @@ async function assessOne(options) {
|
|
|
17521
17915
|
function refuseResolveWithoutComment(wantsResolve, wantsComment) {
|
|
17522
17916
|
if (!wantsResolve || wantsComment)
|
|
17523
17917
|
return;
|
|
17524
|
-
throw lineRefusal3("--resolve writes the attempt count into the triage comment, and --no-comment writes none;" + " run one or the other",
|
|
17918
|
+
throw lineRefusal3("--resolve writes the attempt count into the triage comment, and --no-comment writes none;" + " run one or the other", USAGE33);
|
|
17525
17919
|
}
|
|
17526
17920
|
async function runTriage(context, seams) {
|
|
17527
|
-
const wantsComment = readBooleanFlag(context.flags, "comment",
|
|
17528
|
-
const wantsResolve = readBooleanFlag(context.flags, "resolve",
|
|
17529
|
-
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);
|
|
17530
17924
|
refuseResolveWithoutComment(wantsResolve, wantsComment);
|
|
17531
|
-
const asked = readPullArgument(context.args,
|
|
17925
|
+
const asked = readPullArgument(context.args, USAGE33);
|
|
17532
17926
|
const pr = openPrContext(context, seams);
|
|
17533
17927
|
const git = (seams.git ?? createGitRunner)(pr.project.root);
|
|
17534
17928
|
const permissions = (seams.permissions ?? ghPermissionsIn)(pr.project.root);
|
|
@@ -17541,7 +17935,7 @@ async function runTriage(context, seams) {
|
|
|
17541
17935
|
}
|
|
17542
17936
|
const numbers = asked === null ? (selection?.assess ?? []).map((one) => one.number) : [asked];
|
|
17543
17937
|
const readings = [];
|
|
17544
|
-
let
|
|
17938
|
+
let resolve14 = null;
|
|
17545
17939
|
for (const number of numbers) {
|
|
17546
17940
|
const assess = () => assessOne({
|
|
17547
17941
|
pr,
|
|
@@ -17560,7 +17954,7 @@ async function runTriage(context, seams) {
|
|
|
17560
17954
|
readings.push(reading);
|
|
17561
17955
|
continue;
|
|
17562
17956
|
}
|
|
17563
|
-
|
|
17957
|
+
resolve14 = await resolvePullRequest({
|
|
17564
17958
|
trust: {
|
|
17565
17959
|
permissions,
|
|
17566
17960
|
trustedAuthors: pr.trustedAuthors,
|
|
@@ -17581,16 +17975,16 @@ async function runTriage(context, seams) {
|
|
|
17581
17975
|
...seams.clock === undefined ? {} : { clock: seams.clock },
|
|
17582
17976
|
...seams.sleep === undefined ? {} : { sleep: seams.sleep }
|
|
17583
17977
|
});
|
|
17584
|
-
readings.push(
|
|
17978
|
+
readings.push(resolve14.reading);
|
|
17585
17979
|
}
|
|
17586
17980
|
const text = [
|
|
17587
17981
|
...selection === null ? [] : [selection.message],
|
|
17588
17982
|
...readings.length === 0 ? [] : [renderTriages(readings)],
|
|
17589
|
-
...
|
|
17983
|
+
...resolve14 === null ? [] : resolve14.lines
|
|
17590
17984
|
].join(`
|
|
17591
17985
|
|
|
17592
17986
|
`);
|
|
17593
|
-
return { selection, readings, resolve:
|
|
17987
|
+
return { selection, readings, resolve: resolve14, text, exitCode: resolve14?.exitCode ?? 0 };
|
|
17594
17988
|
}
|
|
17595
17989
|
function createPrTriageCommand(seams = DEFAULT_TRIAGE_SEAMS) {
|
|
17596
17990
|
const command = {
|
|
@@ -17664,7 +18058,7 @@ function createPrTriageCommand(seams = DEFAULT_TRIAGE_SEAMS) {
|
|
|
17664
18058
|
var triage_default = createPrTriageCommand();
|
|
17665
18059
|
|
|
17666
18060
|
// src/commands/pr/view.ts
|
|
17667
|
-
var
|
|
18061
|
+
var USAGE34 = PR_USAGE.view;
|
|
17668
18062
|
function trimmedOrNull3(value) {
|
|
17669
18063
|
const trimmed = value.trim();
|
|
17670
18064
|
return trimmed === "" ? null : trimmed;
|
|
@@ -17674,9 +18068,9 @@ function renderView(number, url) {
|
|
|
17674
18068
|
return url === null ? opened : `${opened}${SEPARATOR2}${url}`;
|
|
17675
18069
|
}
|
|
17676
18070
|
async function runView2(context, seams) {
|
|
17677
|
-
const asked = readPullArgument(context.args,
|
|
18071
|
+
const asked = readPullArgument(context.args, USAGE34);
|
|
17678
18072
|
const pr = openPrContext(context, seams);
|
|
17679
|
-
const pick = await pickPullRequest(pr, asked,
|
|
18073
|
+
const pick = await pickPullRequest(pr, asked, USAGE34);
|
|
17680
18074
|
await onProvider(`open pull request #${pick.number} in the browser`, () => pr.pulls.browse(pick.number));
|
|
17681
18075
|
const url = trimmedOrNull3(pick.summary?.url ?? "");
|
|
17682
18076
|
return {
|
|
@@ -17731,7 +18125,7 @@ function createPrViewCommand(seams = DEFAULT_PR_SEAMS) {
|
|
|
17731
18125
|
var view_default = createPrViewCommand();
|
|
17732
18126
|
|
|
17733
18127
|
// src/commands/pr/wait.ts
|
|
17734
|
-
var
|
|
18128
|
+
var USAGE35 = PR_USAGE.wait;
|
|
17735
18129
|
var TIMEOUT_FLAG = "timeout";
|
|
17736
18130
|
var DEFAULT_WAIT_TIMEOUT_MIN = DEFAULT_CI_TIMEOUT_MIN;
|
|
17737
18131
|
var MS_PER_MIN = 60000;
|
|
@@ -17797,10 +18191,10 @@ function renderWait(reading) {
|
|
|
17797
18191
|
`);
|
|
17798
18192
|
}
|
|
17799
18193
|
async function runWait(context, seams) {
|
|
17800
|
-
const minutes = readTimeoutMinutes(context.flags,
|
|
17801
|
-
const asked = readPullArgument(context.args,
|
|
18194
|
+
const minutes = readTimeoutMinutes(context.flags, USAGE35);
|
|
18195
|
+
const asked = readPullArgument(context.args, USAGE35);
|
|
17802
18196
|
const pr = openPrContext(context, seams);
|
|
17803
|
-
const pick = await pickPullRequest(pr, asked,
|
|
18197
|
+
const pick = await pickPullRequest(pr, asked, USAGE35);
|
|
17804
18198
|
const timeoutMs = minutes * MS_PER_MIN;
|
|
17805
18199
|
context.output.info(`Waiting for the checks of #${pick.number}, up to ${minutes} min.`);
|
|
17806
18200
|
const waited = await waitForChecks({
|
|
@@ -17880,7 +18274,7 @@ var wait_default = createPrWaitCommand();
|
|
|
17880
18274
|
|
|
17881
18275
|
// src/commands/release/tag.ts
|
|
17882
18276
|
import { readFileSync as readFileSync21 } from "fs";
|
|
17883
|
-
import { join as
|
|
18277
|
+
import { join as join37 } from "path";
|
|
17884
18278
|
|
|
17885
18279
|
// src/commands/release/release-commit.ts
|
|
17886
18280
|
function unread2(reason, message) {
|
|
@@ -17944,7 +18338,7 @@ function textOf2(path) {
|
|
|
17944
18338
|
}
|
|
17945
18339
|
}
|
|
17946
18340
|
function readVersion(root, configured) {
|
|
17947
|
-
const resolved =
|
|
18341
|
+
const resolved = join37(root, configured);
|
|
17948
18342
|
const text = textOf2(resolved);
|
|
17949
18343
|
if (text === null) {
|
|
17950
18344
|
return {
|
|
@@ -17958,7 +18352,7 @@ function readVersion(root, configured) {
|
|
|
17958
18352
|
return version2 === null ? { path: configured, text, version: null, problem: `${resolved} declares no version this can read` } : { path: configured, text, version: version2, problem: null };
|
|
17959
18353
|
}
|
|
17960
18354
|
function readNewestRelease(root, configured) {
|
|
17961
|
-
const resolved =
|
|
18355
|
+
const resolved = join37(root, configured);
|
|
17962
18356
|
const text = textOf2(resolved);
|
|
17963
18357
|
if (text === null) {
|
|
17964
18358
|
return { path: configured, version: null, problem: `the changelog could not be read at ${resolved}` };
|
|
@@ -18183,7 +18577,7 @@ function createRoadmapCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
18183
18577
|
subject: "roadmap",
|
|
18184
18578
|
action: "roadmap",
|
|
18185
18579
|
summary: "list the Roadmap's lines in its order, with spec, blocked by, has and refs",
|
|
18186
|
-
description: "Runs `rafa issue list --roadmap` with the flags typed: the unticked lines of the Roadmap issue" + " (the current place's board, the one `rafa switch` moved to; with no position file `roadmap.issue`," + " else the lowest-numbered open type:roadmap board, else the open issue titled Roadmap) in its order," + " read off the GitHub board, as a" + " table with four columns the plain list has not got: spec, whether the
|
|
18580
|
+
description: "Runs `rafa issue list --roadmap` with the flags typed: the unticked lines of the Roadmap issue" + " (the current place's board, the one `rafa switch` moved to; with no position file `roadmap.issue`," + " else the lowest-numbered open type:roadmap board, else the open issue titled Roadmap) in its order," + " read off the GitHub board, as a" + " table with four columns the plain list has not got: spec, whether the issue can be planned (every" + " section of the spec template filled, and the spec:ready label on it), as a symbol; blocked by, the" + " blockers grouped by whether they are still open; has, a plan, a branch" + " or a pull request already made for it; and refs, how many references of the issue's saved copy under" + " `specs.dir` read suspect or dangling, `-` with no copy. A legend under the table names each symbol" + " in plain words, one line per column," + " and `--texts` (`-t`) spells both columns in words instead. Labels are no column: `--labels` prints" + " them on a row under each issue. A Roadmap naming epics prints them alone, grouped by horizon under a" + " `Roadmap #<n> \xB7 <horizon>` heading with the columns #, state, done/total, blocked, date and title," + " and one line counts the lines naming no epic. `--full` prints each epic's issues under its row, a" + " second row led by \u2514\u2192 for a member's blockers, and the lines naming no epic as the issue table. The" + " board listing is kept under .rafa/cache/ and read for what changed since; `--refresh` reads the whole" + " board again. `--check` exits 1 when any" + " type:epic issue's stored state disagrees with its computed one, or the board could not be read, so CI" + " can run it. `--all` keeps the ticked lines and shows the" + " epics of every horizon, not only now, and `--type`, `--module`, `--search` and `--limit` narrow the" + " spec rows, keeping their order. An unreachable board is warned about with the rows still printed." + " With `--output=json` the roadmap, the rows, the epics and the warnings are the data of the terminal" + " result event.",
|
|
18187
18581
|
args: [],
|
|
18188
18582
|
flags: [...ROADMAP_FLAGS],
|
|
18189
18583
|
examples: [
|
|
@@ -18199,6 +18593,14 @@ function createRoadmapCommand(seams = DEFAULT_ISSUE_SEAMS) {
|
|
|
18199
18593
|
cmd: "rafa roadmap --full",
|
|
18200
18594
|
note: "Prints the now epics with each epic's issues underneath, then the spec lines."
|
|
18201
18595
|
},
|
|
18596
|
+
{
|
|
18597
|
+
cmd: "rafa roadmap --labels --texts",
|
|
18598
|
+
note: "Prints each line's labels on a row under it, and the spec and blocked by columns in words."
|
|
18599
|
+
},
|
|
18600
|
+
{
|
|
18601
|
+
cmd: "rafa roadmap --refresh",
|
|
18602
|
+
note: "Reads the whole board again instead of only what changed since the kept listing."
|
|
18603
|
+
},
|
|
18202
18604
|
{
|
|
18203
18605
|
cmd: "rafa roadmap --check",
|
|
18204
18606
|
note: "Prints the Roadmap, then exits 1 when an epic is open with its work done or closed with it not done."
|
|
@@ -18217,14 +18619,14 @@ var roadmap_default = createRoadmapCommand();
|
|
|
18217
18619
|
|
|
18218
18620
|
// src/commands/self-update.ts
|
|
18219
18621
|
var DEFAULT_SELF_UPDATE_SEAMS = Object.freeze({ build: runBuild });
|
|
18220
|
-
var
|
|
18622
|
+
var USAGE36 = "rafa self-update [--force]";
|
|
18221
18623
|
function readForceFlag(value) {
|
|
18222
18624
|
if (value === undefined || value === false || value === "false")
|
|
18223
18625
|
return false;
|
|
18224
18626
|
if (value === true || value === "true")
|
|
18225
18627
|
return true;
|
|
18226
18628
|
throw new CommandExit(1, `\u274C --force takes no value, and read "${value}" as one
|
|
18227
|
-
Usage: ${
|
|
18629
|
+
Usage: ${USAGE36}`);
|
|
18228
18630
|
}
|
|
18229
18631
|
function projectOf10(context) {
|
|
18230
18632
|
if (context.project === null)
|
|
@@ -18232,7 +18634,7 @@ function projectOf10(context) {
|
|
|
18232
18634
|
return context.project;
|
|
18233
18635
|
}
|
|
18234
18636
|
async function runSelfUpdate(context, seams) {
|
|
18235
|
-
expectNoArgument(context.args,
|
|
18637
|
+
expectNoArgument(context.args, USAGE36);
|
|
18236
18638
|
const force = readForceFlag(context.flags["force"]);
|
|
18237
18639
|
const project = projectOf10(context);
|
|
18238
18640
|
const info2 = (line) => context.output.info(line);
|
|
@@ -18290,17 +18692,17 @@ function createSelfUpdateCommand(seams = DEFAULT_SELF_UPDATE_SEAMS) {
|
|
|
18290
18692
|
var self_update_default = createSelfUpdateCommand();
|
|
18291
18693
|
|
|
18292
18694
|
// src/commands/skill/backfill.ts
|
|
18293
|
-
import { statSync as
|
|
18294
|
-
import { join as
|
|
18695
|
+
import { statSync as statSync13 } from "fs";
|
|
18696
|
+
import { join as join42, resolve as resolve16 } from "path";
|
|
18295
18697
|
|
|
18296
18698
|
// src/backfill/backup.ts
|
|
18297
|
-
import { copyFileSync as
|
|
18298
|
-
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";
|
|
18299
18701
|
|
|
18300
18702
|
// src/backfill/proposal-file.ts
|
|
18301
|
-
import { readdirSync as
|
|
18302
|
-
import { join as
|
|
18303
|
-
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");
|
|
18304
18706
|
var PROPOSAL_FILE_PREFIX = "proposals-";
|
|
18305
18707
|
var PROPOSAL_FILE_EXTENSION = ".yaml";
|
|
18306
18708
|
var SHA256_PATTERN = /^[0-9a-f]{64}$/;
|
|
@@ -18326,7 +18728,7 @@ var PROPOSAL_FILE_HEADER = [
|
|
|
18326
18728
|
].join(`
|
|
18327
18729
|
`);
|
|
18328
18730
|
function proposalPath(dir, batch) {
|
|
18329
|
-
return
|
|
18731
|
+
return join38(dir, `${PROPOSAL_FILE_PREFIX}${String(batch).padStart(2, "0")}${PROPOSAL_FILE_EXTENSION}`);
|
|
18330
18732
|
}
|
|
18331
18733
|
function isProposalFileName(name2) {
|
|
18332
18734
|
return name2.startsWith(PROPOSAL_FILE_PREFIX) && name2.endsWith(PROPOSAL_FILE_EXTENSION);
|
|
@@ -18413,7 +18815,7 @@ function parseRow(value, index) {
|
|
|
18413
18815
|
note: filledStringAt(value, "note")
|
|
18414
18816
|
};
|
|
18415
18817
|
}
|
|
18416
|
-
function
|
|
18818
|
+
function unreadable2(problem2) {
|
|
18417
18819
|
return { file: null, problem: problem2 };
|
|
18418
18820
|
}
|
|
18419
18821
|
function parseHeader(data) {
|
|
@@ -18432,15 +18834,15 @@ function numberAt(data, key) {
|
|
|
18432
18834
|
function parseProposalFile(text) {
|
|
18433
18835
|
const parsed = readYamlMapping(text);
|
|
18434
18836
|
if (parsed === null)
|
|
18435
|
-
return
|
|
18837
|
+
return unreadable2("the file holds no YAML mapping");
|
|
18436
18838
|
const headerProblem = parseHeader(parsed);
|
|
18437
18839
|
if (headerProblem !== null)
|
|
18438
|
-
return
|
|
18840
|
+
return unreadable2(headerProblem);
|
|
18439
18841
|
const rows = [];
|
|
18440
18842
|
for (const [index, value] of parsed["rows"].entries()) {
|
|
18441
18843
|
const row2 = parseRow(value, index);
|
|
18442
18844
|
if (typeof row2 === "string")
|
|
18443
|
-
return
|
|
18845
|
+
return unreadable2(row2);
|
|
18444
18846
|
rows.push(row2);
|
|
18445
18847
|
}
|
|
18446
18848
|
return {
|
|
@@ -18457,34 +18859,34 @@ function parseProposalFile(text) {
|
|
|
18457
18859
|
function readProposalFiles(dir) {
|
|
18458
18860
|
let names;
|
|
18459
18861
|
try {
|
|
18460
|
-
names =
|
|
18862
|
+
names = readdirSync5(dir);
|
|
18461
18863
|
} catch {
|
|
18462
18864
|
return [];
|
|
18463
18865
|
}
|
|
18464
18866
|
return names.filter((name2) => isProposalFileName(name2)).sort((left, right) => left.localeCompare(right)).map((name2) => {
|
|
18465
|
-
const path =
|
|
18867
|
+
const path = join38(dir, name2);
|
|
18466
18868
|
try {
|
|
18467
18869
|
return { path, result: parseProposalFile(readFileSync22(path, "utf8")) };
|
|
18468
18870
|
} catch (error) {
|
|
18469
|
-
return { path, result:
|
|
18871
|
+
return { path, result: unreadable2(`the file cannot be read (${error instanceof Error ? error.message : String(error)})`) };
|
|
18470
18872
|
}
|
|
18471
18873
|
});
|
|
18472
18874
|
}
|
|
18473
18875
|
|
|
18474
18876
|
// src/backfill/backup.ts
|
|
18475
|
-
var BACKUP_PATH =
|
|
18877
|
+
var BACKUP_PATH = join39(BACKFILL_PATH, "backup");
|
|
18476
18878
|
var BACKUP_KINDS = ["copied", "kept", "tracked", "outside", "missing"];
|
|
18477
18879
|
function backupDirectory(base) {
|
|
18478
|
-
return
|
|
18880
|
+
return join39(base, BACKUP_PATH);
|
|
18479
18881
|
}
|
|
18480
18882
|
function isUnder(root, path) {
|
|
18481
|
-
const step = relative8(
|
|
18883
|
+
const step = relative8(resolve14(root), resolve14(path));
|
|
18482
18884
|
return step === "" || !step.startsWith(`..${sep5}`) && step !== ".." && !isAbsolute8(step);
|
|
18483
18885
|
}
|
|
18484
18886
|
function backupPathFor(path, root, backupDir) {
|
|
18485
18887
|
if (!isUnder(root, path))
|
|
18486
18888
|
return null;
|
|
18487
|
-
return
|
|
18889
|
+
return join39(backupDir, relative8(resolve14(root), resolve14(path)));
|
|
18488
18890
|
}
|
|
18489
18891
|
function backupFile(path, options) {
|
|
18490
18892
|
if (options.repoRoot !== null && isUnder(options.repoRoot, path)) {
|
|
@@ -18493,12 +18895,12 @@ function backupFile(path, options) {
|
|
|
18493
18895
|
const backup = backupPathFor(path, options.root, options.backupDir);
|
|
18494
18896
|
if (backup === null)
|
|
18495
18897
|
return { path, kind: "outside", backup: null };
|
|
18496
|
-
if (!
|
|
18898
|
+
if (!existsSync17(path))
|
|
18497
18899
|
return { path, kind: "missing", backup: null };
|
|
18498
|
-
if (
|
|
18900
|
+
if (existsSync17(backup))
|
|
18499
18901
|
return { path, kind: "kept", backup };
|
|
18500
|
-
|
|
18501
|
-
|
|
18902
|
+
mkdirSync8(dirname8(backup), { recursive: true });
|
|
18903
|
+
copyFileSync5(path, backup);
|
|
18502
18904
|
return { path, kind: "copied", backup };
|
|
18503
18905
|
}
|
|
18504
18906
|
function backupFiles(paths, options) {
|
|
@@ -18521,7 +18923,7 @@ function countBackups(results) {
|
|
|
18521
18923
|
|
|
18522
18924
|
// src/backfill/derive.ts
|
|
18523
18925
|
import { readFileSync as readFileSync23, writeFileSync as writeFileSync6 } from "fs";
|
|
18524
|
-
import { basename as
|
|
18926
|
+
import { basename as basename7 } from "path";
|
|
18525
18927
|
var DERIVED_FIELDS = ["stack", "paths", "when_to_use"];
|
|
18526
18928
|
function renderWhenToUse(trigger, prevents) {
|
|
18527
18929
|
return `${sentenceEnded(trigger)} Prevents: ${prevents}`;
|
|
@@ -18638,8 +19040,8 @@ function fileChanges(name2, data, body, options) {
|
|
|
18638
19040
|
return { changes, notes };
|
|
18639
19041
|
}
|
|
18640
19042
|
function planFile(entry, options) {
|
|
18641
|
-
if (
|
|
18642
|
-
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`);
|
|
18643
19045
|
}
|
|
18644
19046
|
if (entry.name === null) {
|
|
18645
19047
|
return inert(entry, "skipped", "its place implies no skill name, so no stack can be derived from it");
|
|
@@ -19126,8 +19528,8 @@ function parseSessionAnswer(stdout) {
|
|
|
19126
19528
|
}
|
|
19127
19529
|
|
|
19128
19530
|
// src/backfill/propose.ts
|
|
19129
|
-
import { mkdirSync as
|
|
19130
|
-
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";
|
|
19131
19533
|
var PROPOSAL_ACTION_KINDS = [
|
|
19132
19534
|
"applied",
|
|
19133
19535
|
"unchanged",
|
|
@@ -19206,7 +19608,7 @@ async function runProposalPass(options) {
|
|
|
19206
19608
|
const batches = batchCandidates(candidates);
|
|
19207
19609
|
const written = [];
|
|
19208
19610
|
if (batches.length > 0)
|
|
19209
|
-
|
|
19611
|
+
mkdirSync9(options.backfillDir, { recursive: true });
|
|
19210
19612
|
for (const [index, batch] of batches.entries()) {
|
|
19211
19613
|
const number = index + 1;
|
|
19212
19614
|
const file = await runProposalBatch(number, batch, options);
|
|
@@ -19276,7 +19678,7 @@ function writeRow(path, row2, original, options) {
|
|
|
19276
19678
|
};
|
|
19277
19679
|
}
|
|
19278
19680
|
function applyRow(root, row2, options) {
|
|
19279
|
-
const path =
|
|
19681
|
+
const path = join40(root, ...row2.path.split("/"));
|
|
19280
19682
|
if (row2.status === UNANSWERED_ROW) {
|
|
19281
19683
|
return inert2(path, row2, "unanswered", row2.note ?? "the row carries no answer");
|
|
19282
19684
|
}
|
|
@@ -19299,7 +19701,7 @@ function fileRefusal(file, root) {
|
|
|
19299
19701
|
if (file.status !== REVIEWED_FILE) {
|
|
19300
19702
|
return `the proposal file is still marked ${file.status}: review it and mark it ${REVIEWED_FILE}`;
|
|
19301
19703
|
}
|
|
19302
|
-
return
|
|
19704
|
+
return resolve15(file.skills) === resolve15(root) ? null : `the proposal file names the skills directory ${file.skills}, not ${root}`;
|
|
19303
19705
|
}
|
|
19304
19706
|
function countProposalActions(actions) {
|
|
19305
19707
|
const counts = Object.fromEntries(PROPOSAL_ACTION_KINDS.map((kind) => [kind, 0]));
|
|
@@ -19313,7 +19715,7 @@ function applyProposals(files, root, options) {
|
|
|
19313
19715
|
for (const file of files) {
|
|
19314
19716
|
const refusal13 = fileRefusal(file, root);
|
|
19315
19717
|
for (const row2 of file.rows) {
|
|
19316
|
-
const action = refusal13 === null ? applyRow(root, row2, options) : inert2(
|
|
19718
|
+
const action = refusal13 === null ? applyRow(root, row2, options) : inert2(join40(root, ...row2.path.split("/")), row2, "refused", refusal13);
|
|
19317
19719
|
actions.push(action);
|
|
19318
19720
|
if (row2.trigger !== null && action.kind !== "refused" && action.kind !== "unanswered") {
|
|
19319
19721
|
triggers[row2.name] = row2.trigger;
|
|
@@ -19324,13 +19726,13 @@ function applyProposals(files, root, options) {
|
|
|
19324
19726
|
}
|
|
19325
19727
|
|
|
19326
19728
|
// src/demote/select.ts
|
|
19327
|
-
import { readdirSync as
|
|
19328
|
-
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";
|
|
19329
19731
|
var SKILLS_DIRECTORY = "skills";
|
|
19330
19732
|
var CLAUDE_DIRECTORY = ".claude";
|
|
19331
19733
|
var AUTO_EXTRACTED_ORIGIN = "auto-extracted";
|
|
19332
|
-
var DEMOTED_PATH =
|
|
19333
|
-
var INSTINCTS_PATH =
|
|
19734
|
+
var DEMOTED_PATH = join41(".rafa", "demoted");
|
|
19735
|
+
var INSTINCTS_PATH = join41(".rafa", "instincts");
|
|
19334
19736
|
var REPORT_FILE = "report.md";
|
|
19335
19737
|
function realOrSelf(path) {
|
|
19336
19738
|
try {
|
|
@@ -19347,25 +19749,25 @@ function samePath(left, right) {
|
|
|
19347
19749
|
return resolved === other || resolved.toLowerCase() === other.toLowerCase();
|
|
19348
19750
|
}
|
|
19349
19751
|
function resolveDemotionScope(dir, home) {
|
|
19350
|
-
if (
|
|
19752
|
+
if (basename8(dir) !== SKILLS_DIRECTORY)
|
|
19351
19753
|
return null;
|
|
19352
19754
|
const claude = dirname9(dir);
|
|
19353
|
-
if (
|
|
19755
|
+
if (basename8(claude) !== CLAUDE_DIRECTORY)
|
|
19354
19756
|
return null;
|
|
19355
19757
|
const base = dirname9(claude);
|
|
19356
|
-
const demotedDir =
|
|
19758
|
+
const demotedDir = join41(base, DEMOTED_PATH);
|
|
19357
19759
|
return {
|
|
19358
19760
|
scope: samePath(base, home) ? "user" : "project",
|
|
19359
19761
|
dir,
|
|
19360
19762
|
base,
|
|
19361
19763
|
demotedDir,
|
|
19362
|
-
instinctsDir:
|
|
19363
|
-
reportPath:
|
|
19764
|
+
instinctsDir: join41(base, INSTINCTS_PATH),
|
|
19765
|
+
reportPath: join41(demotedDir, REPORT_FILE)
|
|
19364
19766
|
};
|
|
19365
19767
|
}
|
|
19366
19768
|
function entryKind(path) {
|
|
19367
19769
|
try {
|
|
19368
|
-
const stats =
|
|
19770
|
+
const stats = statSync12(path);
|
|
19369
19771
|
if (stats.isFile())
|
|
19370
19772
|
return "file";
|
|
19371
19773
|
if (stats.isDirectory())
|
|
@@ -19377,7 +19779,7 @@ function entryKind(path) {
|
|
|
19377
19779
|
}
|
|
19378
19780
|
function visibleNames(dir) {
|
|
19379
19781
|
try {
|
|
19380
|
-
return
|
|
19782
|
+
return readdirSync6(dir).filter((name2) => !name2.startsWith(".")).sort((left, right) => left.localeCompare(right));
|
|
19381
19783
|
} catch {
|
|
19382
19784
|
return [];
|
|
19383
19785
|
}
|
|
@@ -19387,7 +19789,7 @@ function readSelected(absolute, path, entries) {
|
|
|
19387
19789
|
let mtime;
|
|
19388
19790
|
try {
|
|
19389
19791
|
text = readFileSync26(absolute, "utf8");
|
|
19390
|
-
mtime =
|
|
19792
|
+
mtime = statSync12(absolute).mtime.toISOString();
|
|
19391
19793
|
} catch {
|
|
19392
19794
|
return null;
|
|
19393
19795
|
}
|
|
@@ -19402,14 +19804,14 @@ function selectsLearned(text, scope) {
|
|
|
19402
19804
|
return origin === AUTO_EXTRACTED_ORIGIN;
|
|
19403
19805
|
}
|
|
19404
19806
|
function selectLearned(scope) {
|
|
19405
|
-
const dir =
|
|
19807
|
+
const dir = join41(scope.dir, LEARNED_DIRECTORY);
|
|
19406
19808
|
if (entryKind(dir) !== "directory")
|
|
19407
19809
|
return [];
|
|
19408
19810
|
const selected = [];
|
|
19409
19811
|
for (const name2 of visibleNames(dir)) {
|
|
19410
19812
|
if (!name2.endsWith(MARKDOWN_EXTENSION) || name2 === MARKDOWN_EXTENSION)
|
|
19411
19813
|
continue;
|
|
19412
|
-
const absolute =
|
|
19814
|
+
const absolute = join41(dir, name2);
|
|
19413
19815
|
if (entryKind(absolute) !== "file")
|
|
19414
19816
|
continue;
|
|
19415
19817
|
const file = readSelected(absolute, `${LEARNED_DIRECTORY}/${name2}`, undefined);
|
|
@@ -19423,10 +19825,10 @@ function selectSkillFiles(scope) {
|
|
|
19423
19825
|
for (const name2 of visibleNames(scope.dir)) {
|
|
19424
19826
|
if (name2 === LEARNED_DIRECTORY)
|
|
19425
19827
|
continue;
|
|
19426
|
-
const skillDir =
|
|
19828
|
+
const skillDir = join41(scope.dir, name2);
|
|
19427
19829
|
if (entryKind(skillDir) !== "directory")
|
|
19428
19830
|
continue;
|
|
19429
|
-
const absolute =
|
|
19831
|
+
const absolute = join41(skillDir, SKILL_FILE);
|
|
19430
19832
|
if (entryKind(absolute) !== "file")
|
|
19431
19833
|
continue;
|
|
19432
19834
|
const file = readSelected(absolute, `${name2}/${SKILL_FILE}`, visibleNames(skillDir));
|
|
@@ -19474,7 +19876,7 @@ Usage: ${BACKFILL_USAGE}`);
|
|
|
19474
19876
|
throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
|
|
19475
19877
|
Usage: ${BACKFILL_USAGE}`);
|
|
19476
19878
|
}
|
|
19477
|
-
return
|
|
19879
|
+
return resolve16(cwd(), dir);
|
|
19478
19880
|
}
|
|
19479
19881
|
function readConsumerRoot(value, cwd) {
|
|
19480
19882
|
if (value === undefined || value === false)
|
|
@@ -19483,11 +19885,11 @@ function readConsumerRoot(value, cwd) {
|
|
|
19483
19885
|
throw new CommandExit(1, `\u274C --project needs a path, and the line gave it none
|
|
19484
19886
|
Usage: ${BACKFILL_USAGE}`);
|
|
19485
19887
|
}
|
|
19486
|
-
return
|
|
19888
|
+
return resolve16(cwd(), value);
|
|
19487
19889
|
}
|
|
19488
19890
|
function isDirectory3(path) {
|
|
19489
19891
|
try {
|
|
19490
|
-
return
|
|
19892
|
+
return statSync13(path).isDirectory();
|
|
19491
19893
|
} catch {
|
|
19492
19894
|
return false;
|
|
19493
19895
|
}
|
|
@@ -19611,7 +20013,7 @@ function reviewedProposals(dir) {
|
|
|
19611
20013
|
return read2.flatMap((entry) => entry.result.file === null ? [] : [entry.result.file]);
|
|
19612
20014
|
}
|
|
19613
20015
|
function rowPaths(files, root) {
|
|
19614
|
-
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) =>
|
|
20016
|
+
return files.flatMap((file) => file.status !== REVIEWED_FILE ? [] : file.rows.filter((row2) => row2.status === ANSWERED_ROW).map((row2) => join42(root, ...row2.path.split("/"))));
|
|
19615
20017
|
}
|
|
19616
20018
|
function runApply(context, scope, backfillDir, projectRoot3) {
|
|
19617
20019
|
const options = checkOptions(context, projectRoot3);
|
|
@@ -19657,7 +20059,7 @@ async function runBackfill(context, seams) {
|
|
|
19657
20059
|
const dir = readBackfillDirectory(context.args, seams.cwd);
|
|
19658
20060
|
const projectRoot3 = readConsumerRoot(context.flags["project"], seams.cwd);
|
|
19659
20061
|
const scope = backfillScopeOf(dir, projectOf11(context).home);
|
|
19660
|
-
const backfillDir =
|
|
20062
|
+
const backfillDir = join42(scope.base, BACKFILL_PATH);
|
|
19661
20063
|
if (mode === "propose") {
|
|
19662
20064
|
await runPropose(context, scope, backfillDir, seams);
|
|
19663
20065
|
return;
|
|
@@ -19771,13 +20173,13 @@ function createSkillCheckCommand(seams = DEFAULT_CHECK_SEAMS) {
|
|
|
19771
20173
|
var check_default3 = createSkillCheckCommand();
|
|
19772
20174
|
|
|
19773
20175
|
// src/commands/skill/demote.ts
|
|
19774
|
-
import { existsSync as
|
|
19775
|
-
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";
|
|
19776
20178
|
|
|
19777
20179
|
// src/demote/apply.ts
|
|
19778
|
-
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";
|
|
19779
20181
|
import { tmpdir as tmpdir2 } from "os";
|
|
19780
|
-
import { dirname as dirname10, join as
|
|
20182
|
+
import { dirname as dirname10, join as join43 } from "path";
|
|
19781
20183
|
var DEMOTION_ACTION_KINDS = [
|
|
19782
20184
|
"demoted",
|
|
19783
20185
|
"kept",
|
|
@@ -19798,18 +20200,18 @@ function learnedSkillName(path) {
|
|
|
19798
20200
|
return base.slice(0, -MARKDOWN_EXTENSION.length);
|
|
19799
20201
|
}
|
|
19800
20202
|
function sourcePath(scope, path) {
|
|
19801
|
-
return
|
|
20203
|
+
return join43(scope.dir, ...path.split("/"));
|
|
19802
20204
|
}
|
|
19803
20205
|
function demotedPath(scope, path) {
|
|
19804
|
-
return
|
|
20206
|
+
return join43(scope.demotedDir, ...path.split("/"));
|
|
19805
20207
|
}
|
|
19806
20208
|
function promotedPath(scope, path) {
|
|
19807
|
-
return
|
|
20209
|
+
return join43(scope.dir, learnedSkillName(path), SKILL_FILE);
|
|
19808
20210
|
}
|
|
19809
20211
|
function takenInstinctIds(dir) {
|
|
19810
20212
|
let names;
|
|
19811
20213
|
try {
|
|
19812
|
-
names =
|
|
20214
|
+
names = readdirSync7(dir);
|
|
19813
20215
|
} catch {
|
|
19814
20216
|
return [];
|
|
19815
20217
|
}
|
|
@@ -19817,7 +20219,7 @@ function takenInstinctIds(dir) {
|
|
|
19817
20219
|
}
|
|
19818
20220
|
function readText4(path) {
|
|
19819
20221
|
try {
|
|
19820
|
-
return
|
|
20222
|
+
return statSync14(path).isFile() ? readFileSync27(path, "utf8") : null;
|
|
19821
20223
|
} catch {
|
|
19822
20224
|
return null;
|
|
19823
20225
|
}
|
|
@@ -19845,7 +20247,7 @@ function planKept(scope, row2, file) {
|
|
|
19845
20247
|
return inert3(row2.path, "procedure", "kept", "procedure-shaped: left where it is, for the backfill");
|
|
19846
20248
|
}
|
|
19847
20249
|
const to = promotedPath(scope, row2.path);
|
|
19848
|
-
if (
|
|
20250
|
+
if (existsSync18(to) || existsSync18(dirname10(to))) {
|
|
19849
20251
|
return inert3(row2.path, "procedure", "refused", `kept as a skill, but ${to} is already there`);
|
|
19850
20252
|
}
|
|
19851
20253
|
return {
|
|
@@ -19859,7 +20261,7 @@ function planKept(scope, row2, file) {
|
|
|
19859
20261
|
};
|
|
19860
20262
|
}
|
|
19861
20263
|
function checkCandidate(dir, id2, text, pathDirs) {
|
|
19862
|
-
const path =
|
|
20264
|
+
const path = join43(dir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
19863
20265
|
writeFileSync8(path, text, "utf8");
|
|
19864
20266
|
const report3 = checkFile(path, "instinct", { projectRoot: null, pathDirs });
|
|
19865
20267
|
return failureLines2(report3.issues);
|
|
@@ -19880,11 +20282,11 @@ function planDemoted(scope, row2, file, context) {
|
|
|
19880
20282
|
if (failures.length > 0) {
|
|
19881
20283
|
return inert3(row2.path, "observation", "refused", `the record ${id2} fails the checker \u2014 ${failures.join("; ")}`);
|
|
19882
20284
|
}
|
|
19883
|
-
const destination =
|
|
20285
|
+
const destination = join43(scope.instinctsDir, `${id2}${MARKDOWN_EXTENSION}`);
|
|
19884
20286
|
const kept = demotedPath(scope, row2.path);
|
|
19885
|
-
if (
|
|
20287
|
+
if (existsSync18(destination))
|
|
19886
20288
|
return inert3(row2.path, "observation", "refused", `${destination} is already there`);
|
|
19887
|
-
if (
|
|
20289
|
+
if (existsSync18(kept))
|
|
19888
20290
|
return inert3(row2.path, "observation", "refused", `${kept} is already there`);
|
|
19889
20291
|
context.taken.push(id2);
|
|
19890
20292
|
return {
|
|
@@ -19916,7 +20318,7 @@ function planRow(scope, row2, context) {
|
|
|
19916
20318
|
entries: undefined,
|
|
19917
20319
|
absolute,
|
|
19918
20320
|
hash,
|
|
19919
|
-
mtime:
|
|
20321
|
+
mtime: statSync14(absolute).mtime.toISOString()
|
|
19920
20322
|
};
|
|
19921
20323
|
return verdict === "procedure" ? planKept(scope, row2, file) : planDemoted(scope, row2, file, context);
|
|
19922
20324
|
}
|
|
@@ -19927,28 +20329,28 @@ function countActions(actions) {
|
|
|
19927
20329
|
return counts;
|
|
19928
20330
|
}
|
|
19929
20331
|
function planDemotion(report3, scope, seams = DEFAULT_APPLY_SEAMS) {
|
|
19930
|
-
const temp = mkdtempSync2(
|
|
20332
|
+
const temp = mkdtempSync2(join43(tmpdir2(), "rafa-demote-"));
|
|
19931
20333
|
const taken = [...takenInstinctIds(scope.instinctsDir)];
|
|
19932
20334
|
try {
|
|
19933
20335
|
const actions = report3.rows.map((row2) => planRow(scope, row2, { temp, taken, seams }));
|
|
19934
20336
|
return { scope, actions, counts: countActions(actions) };
|
|
19935
20337
|
} finally {
|
|
19936
|
-
|
|
20338
|
+
rmSync5(temp, { recursive: true, force: true });
|
|
19937
20339
|
}
|
|
19938
20340
|
}
|
|
19939
20341
|
function moveFile(move3) {
|
|
19940
|
-
|
|
20342
|
+
mkdirSync10(dirname10(move3.to), { recursive: true });
|
|
19941
20343
|
try {
|
|
19942
20344
|
renameSync3(move3.from, move3.to);
|
|
19943
20345
|
} catch {
|
|
19944
|
-
|
|
20346
|
+
copyFileSync6(move3.from, move3.to);
|
|
19945
20347
|
unlinkSync(move3.from);
|
|
19946
20348
|
}
|
|
19947
20349
|
}
|
|
19948
20350
|
function applyDemotion(plan2) {
|
|
19949
20351
|
for (const action of plan2.actions) {
|
|
19950
20352
|
if (action.write !== null) {
|
|
19951
|
-
|
|
20353
|
+
mkdirSync10(dirname10(action.write.path), { recursive: true });
|
|
19952
20354
|
writeFileSync8(action.write.path, action.write.text, "utf8");
|
|
19953
20355
|
}
|
|
19954
20356
|
if (action.move !== null)
|
|
@@ -20014,11 +20416,11 @@ Usage: ${DEMOTE_USAGE}`);
|
|
|
20014
20416
|
throw new CommandExit(1, `\u274C Expected one directory, and read "${extra}" as a second
|
|
20015
20417
|
Usage: ${DEMOTE_USAGE}`);
|
|
20016
20418
|
}
|
|
20017
|
-
return
|
|
20419
|
+
return resolve17(cwd(), dir);
|
|
20018
20420
|
}
|
|
20019
20421
|
function isDirectory4(path) {
|
|
20020
20422
|
try {
|
|
20021
|
-
return
|
|
20423
|
+
return statSync15(path).isDirectory();
|
|
20022
20424
|
} catch {
|
|
20023
20425
|
return false;
|
|
20024
20426
|
}
|
|
@@ -20040,13 +20442,13 @@ function projectOf12(context) {
|
|
|
20040
20442
|
throw new Error("rafa skill demote runs inside a project, and was handed none");
|
|
20041
20443
|
return context.project;
|
|
20042
20444
|
}
|
|
20043
|
-
function
|
|
20445
|
+
function verdictLine2(report3) {
|
|
20044
20446
|
const counts = countVerdicts(report3.rows);
|
|
20045
20447
|
const parts = DEMOTION_VERDICTS.map((verdict) => `${String(counts[verdict])} ${verdict}`);
|
|
20046
20448
|
return `${String(report3.rows.length)} file(s): ${parts.join(", ")}`;
|
|
20047
20449
|
}
|
|
20048
20450
|
function previousReport(path, context) {
|
|
20049
|
-
if (!
|
|
20451
|
+
if (!existsSync19(path))
|
|
20050
20452
|
return null;
|
|
20051
20453
|
const parsed = parseDemotionReport(readFileSync28(path, "utf8"));
|
|
20052
20454
|
if (parsed.report !== null)
|
|
@@ -20057,7 +20459,7 @@ function previousReport(path, context) {
|
|
|
20057
20459
|
function runWrite(context, scope) {
|
|
20058
20460
|
const files = selectSources(scope);
|
|
20059
20461
|
const report3 = buildDemotionReport(files, previousReport(scope.reportPath, context));
|
|
20060
|
-
|
|
20462
|
+
mkdirSync11(dirname11(scope.reportPath), { recursive: true });
|
|
20061
20463
|
writeFileSync9(scope.reportPath, writeDemotionReport(report3), "utf8");
|
|
20062
20464
|
const result = {
|
|
20063
20465
|
applied: false,
|
|
@@ -20074,14 +20476,14 @@ function runWrite(context, scope) {
|
|
|
20074
20476
|
}
|
|
20075
20477
|
for (const line of [
|
|
20076
20478
|
`${scope.scope} scope: ${scope.dir}`,
|
|
20077
|
-
|
|
20479
|
+
verdictLine2(report3),
|
|
20078
20480
|
`Report written to ${scope.reportPath} (status: ${report3.status}); nothing was moved`,
|
|
20079
20481
|
`Review each row, then mark the report ${REVIEWED_STATUS} and re-run with --apply`
|
|
20080
20482
|
])
|
|
20081
20483
|
context.output.info(line);
|
|
20082
20484
|
}
|
|
20083
20485
|
function reviewedReport(scope) {
|
|
20084
|
-
if (!
|
|
20486
|
+
if (!existsSync19(scope.reportPath)) {
|
|
20085
20487
|
throw new CommandExit(1, `\u274C No report at ${scope.reportPath}; run "rafa skill demote ${scope.dir}" first, then review it`);
|
|
20086
20488
|
}
|
|
20087
20489
|
const parsed = parseDemotionReport(readFileSync28(scope.reportPath, "utf8"));
|
|
@@ -20190,8 +20592,8 @@ var demote_default = createSkillDemoteCommand();
|
|
|
20190
20592
|
|
|
20191
20593
|
// src/commands/skill/show.ts
|
|
20192
20594
|
var DEFAULT_SKILL_SHOW_SEAMS = DEFAULT_SKILL_LIST_SEAMS;
|
|
20193
|
-
var
|
|
20194
|
-
var
|
|
20595
|
+
var COMMAND_NAME9 = "rafa skill show";
|
|
20596
|
+
var USAGE37 = "rafa skill show <name> [--full]";
|
|
20195
20597
|
function unknownSkillMessage(name2, inventory) {
|
|
20196
20598
|
const agent = findShown(inventory.records, "agent", name2);
|
|
20197
20599
|
const hint = agent === null ? [] : [`An agent is named "${name2}" (${agent.source}): rafa agent show ${name2}`];
|
|
@@ -20199,7 +20601,7 @@ function unknownSkillMessage(name2, inventory) {
|
|
|
20199
20601
|
`\u274C No skill is named "${name2}".`,
|
|
20200
20602
|
...hint,
|
|
20201
20603
|
"Run `rafa skill list` to see every skill and where it comes from.",
|
|
20202
|
-
`Usage: ${
|
|
20604
|
+
`Usage: ${USAGE37}`
|
|
20203
20605
|
].join(`
|
|
20204
20606
|
`);
|
|
20205
20607
|
}
|
|
@@ -20215,10 +20617,10 @@ function projectOf13(context) {
|
|
|
20215
20617
|
return context.project;
|
|
20216
20618
|
}
|
|
20217
20619
|
async function runShow3(context, seams) {
|
|
20218
|
-
const full = readSwitch("full", context.flags["full"], `Type the name ahead of it: ${
|
|
20219
|
-
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);
|
|
20220
20622
|
const project = projectOf13(context);
|
|
20221
|
-
const { inventory, settingSources } = await projectInventory(project, context, seams,
|
|
20623
|
+
const { inventory, settingSources } = await projectInventory(project, context, seams, COMMAND_NAME9);
|
|
20222
20624
|
const warnings = warningLines(inventory);
|
|
20223
20625
|
for (const warning of warnings)
|
|
20224
20626
|
context.output.warn(warning);
|
|
@@ -20411,7 +20813,7 @@ function statusData(sections) {
|
|
|
20411
20813
|
}
|
|
20412
20814
|
|
|
20413
20815
|
// src/status/sections.ts
|
|
20414
|
-
import { existsSync as
|
|
20816
|
+
import { existsSync as existsSync20 } from "fs";
|
|
20415
20817
|
var STATUS_NETWORK_TIMEOUT_MS = 5000;
|
|
20416
20818
|
function isIdleWorktree(row2) {
|
|
20417
20819
|
return !row2.blockers.some((blocker) => blocker.kind === "recent");
|
|
@@ -20458,9 +20860,9 @@ function boundedRunner(open, root, deadline) {
|
|
|
20458
20860
|
}
|
|
20459
20861
|
async function networkSection(what, deadline, work) {
|
|
20460
20862
|
let timer;
|
|
20461
|
-
const expired = new Promise((
|
|
20863
|
+
const expired = new Promise((resolve18) => {
|
|
20462
20864
|
timer = setTimeout(() => {
|
|
20463
|
-
|
|
20865
|
+
resolve18(unread3(`the ${what} was not read within the ${String(deadline.ms)}ms network deadline`));
|
|
20464
20866
|
}, Math.max(0, deadline.left()));
|
|
20465
20867
|
});
|
|
20466
20868
|
try {
|
|
@@ -20550,7 +20952,7 @@ function nextOfPlace(current, epic, walk) {
|
|
|
20550
20952
|
return epic?.members.some((member) => member.number === line.issue) === true ? line.issue : null;
|
|
20551
20953
|
}
|
|
20552
20954
|
function hasBoards(root, listing2) {
|
|
20553
|
-
if (
|
|
20955
|
+
if (existsSync20(positionFilePath(root)))
|
|
20554
20956
|
return true;
|
|
20555
20957
|
return listing2.some((issue2) => issue2.state === "OPEN" && issue2.labels.includes(ROADMAP_LABEL));
|
|
20556
20958
|
}
|
|
@@ -20640,7 +21042,7 @@ async function readStatusSections(input, seams = {}) {
|
|
|
20640
21042
|
}
|
|
20641
21043
|
|
|
20642
21044
|
// src/commands/status.ts
|
|
20643
|
-
var
|
|
21045
|
+
var COMMAND_NAME10 = "rafa status";
|
|
20644
21046
|
var STATUS_USAGE = "rafa status";
|
|
20645
21047
|
var STATUS_ARGUMENT_EXIT = 2;
|
|
20646
21048
|
var DEFAULT_STATUS_SEAMS = Object.freeze({});
|
|
@@ -20652,13 +21054,13 @@ function projectOf14(context) {
|
|
|
20652
21054
|
function expectNoWord(args) {
|
|
20653
21055
|
if (args.length === 0)
|
|
20654
21056
|
return;
|
|
20655
|
-
throw new CommandExit(STATUS_ARGUMENT_EXIT, `\u274C ${
|
|
21057
|
+
throw new CommandExit(STATUS_ARGUMENT_EXIT, `\u274C ${COMMAND_NAME10}: expected no argument, got ${String(args.length)}: ${args.join(" ")}
|
|
20656
21058
|
Usage: ${STATUS_USAGE}`);
|
|
20657
21059
|
}
|
|
20658
21060
|
async function runStatus2(context, seams) {
|
|
20659
21061
|
expectNoWord(context.args);
|
|
20660
21062
|
const project = projectOf14(context);
|
|
20661
|
-
const config = resolveProjectConfig(project,
|
|
21063
|
+
const config = resolveProjectConfig(project, COMMAND_NAME10, (message) => {
|
|
20662
21064
|
context.output.warn(message);
|
|
20663
21065
|
});
|
|
20664
21066
|
const read3 = seams.read ?? ((input) => readStatusSections(input));
|
|
@@ -20724,7 +21126,7 @@ var CORE_SUBJECTS = Object.freeze([
|
|
|
20724
21126
|
{ name: "loop", summary: "start a plan; stop, pause, resume, show and list its sessions" },
|
|
20725
21127
|
{ name: "issue", summary: "the tracker: list, show, create, comment on and move issues; mark one ready, unblock it and check its references" },
|
|
20726
21128
|
{ name: "pr", summary: "the pull request of a branch: one line, in full or in the browser; list, wait on, merge and triage them" },
|
|
20727
|
-
{ name: "effort", summary: "collect session and commit rows; report per plan; repair
|
|
21129
|
+
{ name: "effort", summary: "collect session and commit rows; report per plan; read and repair the store: its schema, a copy for testing, migrations, and fixes" },
|
|
20728
21130
|
{ name: "module", summary: "list the configured modules; run an action a module provides" },
|
|
20729
21131
|
{ name: "agent", summary: "copy an agent definition into the project; list what a session sees" },
|
|
20730
21132
|
{ name: "skill", summary: "check a skills directory; list each tier; demote and backfill it" },
|
|
@@ -20735,7 +21137,7 @@ var CORE_SUBJECTS = Object.freeze([
|
|
|
20735
21137
|
]);
|
|
20736
21138
|
var CORE_COMMANDS = Object.freeze([
|
|
20737
21139
|
create_default2,
|
|
20738
|
-
|
|
21140
|
+
list_default2,
|
|
20739
21141
|
show_default5,
|
|
20740
21142
|
validate_default,
|
|
20741
21143
|
risk_default,
|
|
@@ -20746,7 +21148,7 @@ var CORE_COMMANDS = Object.freeze([
|
|
|
20746
21148
|
resume_default,
|
|
20747
21149
|
status_default2,
|
|
20748
21150
|
list_default7,
|
|
20749
|
-
|
|
21151
|
+
list_default,
|
|
20750
21152
|
show_default4,
|
|
20751
21153
|
create_default,
|
|
20752
21154
|
comment_default,
|
|
@@ -20764,26 +21166,29 @@ var CORE_COMMANDS = Object.freeze([
|
|
|
20764
21166
|
collect_default,
|
|
20765
21167
|
report_default,
|
|
20766
21168
|
fix_schema_default,
|
|
21169
|
+
copy_default,
|
|
21170
|
+
schema_default,
|
|
21171
|
+
migrate_default,
|
|
20767
21172
|
list_default8,
|
|
20768
21173
|
exec_default,
|
|
20769
21174
|
vendor_default,
|
|
20770
|
-
|
|
21175
|
+
list_default4,
|
|
20771
21176
|
show_default2,
|
|
20772
21177
|
search_default2,
|
|
20773
21178
|
check_default3,
|
|
20774
|
-
|
|
21179
|
+
list_default3,
|
|
20775
21180
|
show_default7,
|
|
20776
21181
|
search_default,
|
|
20777
21182
|
demote_default,
|
|
20778
21183
|
backfill_default,
|
|
20779
21184
|
check_default,
|
|
20780
|
-
|
|
21185
|
+
list_default6,
|
|
20781
21186
|
show_default3,
|
|
20782
21187
|
flag_default,
|
|
20783
21188
|
promote_default2,
|
|
20784
21189
|
status_default,
|
|
20785
21190
|
tag_default,
|
|
20786
|
-
|
|
21191
|
+
list_default5,
|
|
20787
21192
|
show_default,
|
|
20788
21193
|
new_default,
|
|
20789
21194
|
defer_default,
|