@open-tomato/rafa 0.18.0 → 0.19.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 +13 -10
- package/dist/PROMPT.md +2 -1
- package/dist/bundled/skills/dev-planner/SKILL.md +2 -0
- package/dist/cli.js +2212 -950
- package/dist/{describe-vq037dyt.js → describe-cenk70n5.js} +4 -3
- package/dist/effort/store/index.js +6 -4
- package/dist/{index-0pdsq2hy.js → index-09p41s0n.js} +11 -224
- package/dist/{index-kvnggvyk.js → index-c5cecsga.js} +2 -2
- package/dist/{index-6vkd153j.js → index-ca0fccvr.js} +1220 -1104
- package/dist/{index-944pncd8.js → index-cedghpva.js} +10 -2
- package/dist/index-d6d0942q.js +218 -0
- package/dist/{index-at9f1z2z.js → index-gb51x34c.js} +1007 -51
- package/dist/{index-cvpdvmtg.js → index-mh5hyw8g.js} +602 -665
- package/dist/{index-jva32qe6.js → index-pqrpcfaz.js} +246 -2
- package/dist/{index-ddf85qac.js → index-vg7ycs94.js} +1 -1
- package/dist/index-wp8c28fd.js +18 -0
- package/dist/index.js +13 -11
- package/dist/learning/index.js +25 -0
- package/dist/plan/index.js +8 -7
- package/package.json +4 -3
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
// @bun
|
|
2
2
|
import {
|
|
3
|
-
CHANGE_LEVELS,
|
|
4
|
-
FINDING_KINDS,
|
|
5
|
-
FINDING_SIGNALS,
|
|
6
|
-
REPORT_STATUSES,
|
|
7
3
|
escapeBlockerText,
|
|
8
4
|
findNextTask,
|
|
9
5
|
isStampableStub,
|
|
10
6
|
loadPlanPrerequisites,
|
|
11
7
|
malformedPrerequisiteLines,
|
|
12
8
|
parsePlan,
|
|
13
|
-
parseReport,
|
|
14
9
|
planStubFromPath,
|
|
15
10
|
planStubFromPrompt,
|
|
16
11
|
prerequisitesPathForPlan,
|
|
@@ -20,30 +15,46 @@ import {
|
|
|
20
15
|
trackerPathFor,
|
|
21
16
|
updateTrackerLine,
|
|
22
17
|
writeTrackerBlocker
|
|
23
|
-
} from "./index-
|
|
18
|
+
} from "./index-09p41s0n.js";
|
|
24
19
|
import {
|
|
25
20
|
CORE_ADAPTER_REGISTRY,
|
|
26
21
|
ClaudePlannerError,
|
|
27
22
|
LONE_SURROGATE,
|
|
23
|
+
MARKDOWN_EXTENSION,
|
|
28
24
|
PORT_VERSIONS,
|
|
25
|
+
SKILL_FILE,
|
|
29
26
|
checkUsage,
|
|
30
27
|
createGhRunner,
|
|
31
28
|
createLocalTracker,
|
|
29
|
+
declaredNameIssue,
|
|
32
30
|
getClaudeUsagePercent,
|
|
31
|
+
gitRemoteUrl,
|
|
33
32
|
interruptClaudeSessions,
|
|
33
|
+
mergeFrontmatter,
|
|
34
|
+
normalizeRemote,
|
|
35
|
+
parseInstinct,
|
|
34
36
|
planFilePath,
|
|
37
|
+
readFrontmatter,
|
|
38
|
+
readFrontmatterDocument,
|
|
35
39
|
runClaude,
|
|
36
40
|
runClaudeCaptured,
|
|
41
|
+
scanLayout,
|
|
37
42
|
selectEffortStore,
|
|
38
43
|
sqliteStorePath,
|
|
39
44
|
withSqliteStore,
|
|
45
|
+
writeFrontmatter,
|
|
46
|
+
writeInstinct,
|
|
40
47
|
writeSqliteStore
|
|
41
|
-
} from "./index-
|
|
48
|
+
} from "./index-gb51x34c.js";
|
|
42
49
|
import {
|
|
50
|
+
CHANGE_LEVELS,
|
|
43
51
|
CONFIG_DEFAULTS,
|
|
44
52
|
CONFIG_FILE,
|
|
45
53
|
ConfigError,
|
|
46
54
|
DEFAULT_ROUTING,
|
|
55
|
+
FINDING_KINDS,
|
|
56
|
+
FINDING_SIGNALS,
|
|
57
|
+
REPORT_STATUSES,
|
|
47
58
|
SETTINGS,
|
|
48
59
|
activeOutput,
|
|
49
60
|
activeOutputMode,
|
|
@@ -51,23 +62,27 @@ import {
|
|
|
51
62
|
configFilePath,
|
|
52
63
|
findTierItem,
|
|
53
64
|
parseConfigText,
|
|
65
|
+
parseReport,
|
|
54
66
|
pinKey,
|
|
55
67
|
pinLine,
|
|
56
68
|
readItemBytes,
|
|
57
69
|
readRafaBlocks,
|
|
58
70
|
resolveConfig,
|
|
59
71
|
resolveTiers
|
|
60
|
-
} from "./index-
|
|
72
|
+
} from "./index-pqrpcfaz.js";
|
|
73
|
+
import {
|
|
74
|
+
promotable
|
|
75
|
+
} from "./index-d6d0942q.js";
|
|
61
76
|
import {
|
|
62
77
|
DESCRIBE_BINARY,
|
|
63
78
|
DESCRIBE_SCHEMA_VERSION,
|
|
64
79
|
pluralOf
|
|
65
|
-
} from "./index-
|
|
80
|
+
} from "./index-c5cecsga.js";
|
|
66
81
|
import {
|
|
67
82
|
CommandExit,
|
|
68
83
|
SPENDS_GLYPH,
|
|
69
84
|
commandSpelling
|
|
70
|
-
} from "./index-
|
|
85
|
+
} from "./index-vg7ycs94.js";
|
|
71
86
|
import {
|
|
72
87
|
BUDGET_FLAG,
|
|
73
88
|
BUNDLED_SKILLS_DIR,
|
|
@@ -75,7 +90,6 @@ import {
|
|
|
75
90
|
PREREQUISITE_KINDS,
|
|
76
91
|
RELEASE_AUTO,
|
|
77
92
|
SKILL_TIERS,
|
|
78
|
-
__require,
|
|
79
93
|
describeValue,
|
|
80
94
|
isAgentName,
|
|
81
95
|
isGitHubLogin,
|
|
@@ -93,7 +107,12 @@ import {
|
|
|
93
107
|
skillTierDirectory,
|
|
94
108
|
stripTaskDeclaration,
|
|
95
109
|
tierExists
|
|
96
|
-
} from "./index-
|
|
110
|
+
} from "./index-cedghpva.js";
|
|
111
|
+
import {
|
|
112
|
+
__require,
|
|
113
|
+
actionHash,
|
|
114
|
+
triggerKey
|
|
115
|
+
} from "./index-wp8c28fd.js";
|
|
97
116
|
|
|
98
117
|
// src/config-load.ts
|
|
99
118
|
import { existsSync, readFileSync, realpathSync } from "fs";
|
|
@@ -1914,7 +1933,7 @@ async function report(args, repoRoot) {
|
|
|
1914
1933
|
|
|
1915
1934
|
// package.json
|
|
1916
1935
|
var name = "@open-tomato/rafa";
|
|
1917
|
-
var version = "0.
|
|
1936
|
+
var version = "0.19.0";
|
|
1918
1937
|
|
|
1919
1938
|
// src/modules/manifest.ts
|
|
1920
1939
|
var MANIFEST_VERSION = 1;
|
|
@@ -2264,429 +2283,27 @@ import { readFileSync as readFileSync8 } from "fs";
|
|
|
2264
2283
|
import { resolve as resolve4 } from "path";
|
|
2265
2284
|
|
|
2266
2285
|
// src/agents/roster.ts
|
|
2267
|
-
import { readdirSync as
|
|
2268
|
-
import { join as
|
|
2286
|
+
import { readdirSync as readdirSync3, readFileSync as readFileSync7, statSync as statSync3 } from "fs";
|
|
2287
|
+
import { join as join7 } from "path";
|
|
2269
2288
|
|
|
2270
2289
|
// src/inventory/trees.ts
|
|
2271
2290
|
import { readFileSync as readFileSync4 } from "fs";
|
|
2272
|
-
import { dirname as dirname3, join as
|
|
2291
|
+
import { dirname as dirname3, join as join4, relative as relative2 } from "path";
|
|
2273
2292
|
|
|
2274
2293
|
// src/check/run.ts
|
|
2275
2294
|
import { readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
2276
2295
|
import { basename as basename2, dirname as dirname2 } from "path";
|
|
2277
2296
|
|
|
2278
|
-
// src/schema/frontmatter.ts
|
|
2279
|
-
var FRONTMATTER_FENCE = "---";
|
|
2280
|
-
var FRONTMATTER_INDENT = 2;
|
|
2281
|
-
function scanLine(text, offset) {
|
|
2282
|
-
const index = text.indexOf(`
|
|
2283
|
-
`, offset);
|
|
2284
|
-
if (index === -1) {
|
|
2285
|
-
return { line: text.slice(offset), newline: "", next: text.length };
|
|
2286
|
-
}
|
|
2287
|
-
const carriage = index > offset && text[index - 1] === "\r";
|
|
2288
|
-
const end = carriage ? index - 1 : index;
|
|
2289
|
-
return {
|
|
2290
|
-
line: text.slice(offset, end),
|
|
2291
|
-
newline: text.slice(end, index + 1),
|
|
2292
|
-
next: index + 1
|
|
2293
|
-
};
|
|
2294
|
-
}
|
|
2295
|
-
function isRecord(value) {
|
|
2296
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2297
|
-
}
|
|
2298
|
-
function parseBlock(yaml) {
|
|
2299
|
-
let parsed;
|
|
2300
|
-
try {
|
|
2301
|
-
parsed = Bun.YAML.parse(yaml.replace(/\r\n/g, `
|
|
2302
|
-
`));
|
|
2303
|
-
} catch {
|
|
2304
|
-
return null;
|
|
2305
|
-
}
|
|
2306
|
-
return isRecord(parsed) ? parsed : null;
|
|
2307
|
-
}
|
|
2308
|
-
function readFrontmatterDocument(text) {
|
|
2309
|
-
const opening = scanLine(text, 0);
|
|
2310
|
-
if (opening.line !== FRONTMATTER_FENCE || opening.newline === "")
|
|
2311
|
-
return null;
|
|
2312
|
-
let offset = opening.next;
|
|
2313
|
-
while (offset < text.length) {
|
|
2314
|
-
const scanned = scanLine(text, offset);
|
|
2315
|
-
if (scanned.line === FRONTMATTER_FENCE) {
|
|
2316
|
-
const data = parseBlock(text.slice(opening.next, offset));
|
|
2317
|
-
return data === null ? null : {
|
|
2318
|
-
data,
|
|
2319
|
-
yaml: text.slice(opening.next, offset),
|
|
2320
|
-
body: text.slice(scanned.next),
|
|
2321
|
-
newline: opening.newline,
|
|
2322
|
-
closingNewline: scanned.newline
|
|
2323
|
-
};
|
|
2324
|
-
}
|
|
2325
|
-
offset = scanned.next;
|
|
2326
|
-
}
|
|
2327
|
-
return null;
|
|
2328
|
-
}
|
|
2329
|
-
function readFrontmatter(text) {
|
|
2330
|
-
return readFrontmatterDocument(text)?.data ?? null;
|
|
2331
|
-
}
|
|
2332
|
-
function renderFrontmatter(data, newline = `
|
|
2333
|
-
`) {
|
|
2334
|
-
return Bun.YAML.stringify(data, null, FRONTMATTER_INDENT).split(`
|
|
2335
|
-
`).map((line) => line.replace(/[ \t]+$/, "")).join(newline);
|
|
2336
|
-
}
|
|
2337
|
-
function mergeFrontmatter(data, changes) {
|
|
2338
|
-
const merged = {};
|
|
2339
|
-
for (const [key, value] of Object.entries(data)) {
|
|
2340
|
-
if (!Object.hasOwn(changes, key))
|
|
2341
|
-
merged[key] = value;
|
|
2342
|
-
else if (changes[key] !== undefined)
|
|
2343
|
-
merged[key] = changes[key];
|
|
2344
|
-
}
|
|
2345
|
-
for (const [key, value] of Object.entries(changes)) {
|
|
2346
|
-
if (value !== undefined && !Object.hasOwn(data, key))
|
|
2347
|
-
merged[key] = value;
|
|
2348
|
-
}
|
|
2349
|
-
return merged;
|
|
2350
|
-
}
|
|
2351
|
-
function writeFrontmatter(document, data) {
|
|
2352
|
-
const block = data === undefined ? document.yaml : `${renderFrontmatter(data, document.newline)}${document.newline}`;
|
|
2353
|
-
return [
|
|
2354
|
-
FRONTMATTER_FENCE,
|
|
2355
|
-
document.newline,
|
|
2356
|
-
block,
|
|
2357
|
-
FRONTMATTER_FENCE,
|
|
2358
|
-
document.closingNewline,
|
|
2359
|
-
document.body
|
|
2360
|
-
].join("");
|
|
2361
|
-
}
|
|
2362
|
-
|
|
2363
|
-
// src/schema/instinct.ts
|
|
2364
|
-
import { createHash } from "crypto";
|
|
2365
|
-
|
|
2366
|
-
// src/schema/project-id.ts
|
|
2367
|
-
import { spawnSync } from "child_process";
|
|
2368
|
-
var PROJECT_ID_LENGTH = 12;
|
|
2369
|
-
var PROJECT_ID_PATTERN = new RegExp(`^[0-9a-f]{${PROJECT_ID_LENGTH}}$`);
|
|
2370
|
-
function hasColonAfterAt(url) {
|
|
2371
|
-
const at = url.indexOf("@");
|
|
2372
|
-
return at !== -1 && url.lastIndexOf(":") > at;
|
|
2373
|
-
}
|
|
2374
|
-
function normalizeRemote(url) {
|
|
2375
|
-
const stripped = url.replace(/:\/\/[^@]+@/, "://");
|
|
2376
|
-
const network = !stripped.startsWith("file://") && (stripped.includes("://") || hasColonAfterAt(stripped));
|
|
2377
|
-
const trimmed = stripped.replace(/^[A-Za-z][A-Za-z0-9+.-]*:\/\//, "").replace(/^[^@/:]+@([^:/]+):/, "$1/").replace(/\.git\/?$/, "").replace(/\/+$/, "");
|
|
2378
|
-
return network ? trimmed.toLowerCase() : trimmed;
|
|
2379
|
-
}
|
|
2380
|
-
function gitRemoteUrl(dir) {
|
|
2381
|
-
const result = spawnSync("git", ["remote", "get-url", "origin"], {
|
|
2382
|
-
cwd: dir,
|
|
2383
|
-
encoding: "utf8",
|
|
2384
|
-
env: { ...process.env, LC_ALL: "C" }
|
|
2385
|
-
});
|
|
2386
|
-
if (result.error !== undefined || result.status !== 0)
|
|
2387
|
-
return null;
|
|
2388
|
-
const url = result.stdout.trim();
|
|
2389
|
-
return url === "" ? null : url;
|
|
2390
|
-
}
|
|
2391
|
-
|
|
2392
|
-
// src/schema/instinct.ts
|
|
2393
|
-
var INSTINCT_KINDS = FINDING_KINDS;
|
|
2394
|
-
var INSTINCT_SIGNALS = FINDING_SIGNALS;
|
|
2395
|
-
var INSTINCT_DOMAINS = [
|
|
2396
|
-
"code-style",
|
|
2397
|
-
"testing",
|
|
2398
|
-
"git",
|
|
2399
|
-
"debugging",
|
|
2400
|
-
"workflow",
|
|
2401
|
-
"security",
|
|
2402
|
-
"general-best-practices"
|
|
2403
|
-
];
|
|
2404
|
-
var INSTINCT_SCOPES = ["project", "user"];
|
|
2405
|
-
var INSTINCT_SOURCES = ["task-report", "loop-observed", "imported", "demoted"];
|
|
2406
|
-
var CONFIDENCE_MIN = 0.3;
|
|
2407
|
-
var CONFIDENCE_MAX = 0.9;
|
|
2408
|
-
var DEFAULT_USAGE_COUNT = 1;
|
|
2409
|
-
var EVIDENCE_REQUIRED_SOURCE = "task-report";
|
|
2410
|
-
var ACTION_HEADING = "## Action";
|
|
2411
|
-
var CAUSE_HEADING = "## Cause";
|
|
2412
|
-
var INSTINCT_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
2413
|
-
var TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/;
|
|
2414
|
-
var INSTINCT_KEY_ORDER = [
|
|
2415
|
-
"id",
|
|
2416
|
-
"trigger",
|
|
2417
|
-
"kind",
|
|
2418
|
-
"domain",
|
|
2419
|
-
"confidence",
|
|
2420
|
-
"usage_count",
|
|
2421
|
-
"artifact",
|
|
2422
|
-
"signal",
|
|
2423
|
-
"scope",
|
|
2424
|
-
"project_id",
|
|
2425
|
-
"source",
|
|
2426
|
-
"evidence",
|
|
2427
|
-
"created_at",
|
|
2428
|
-
"updated_at"
|
|
2429
|
-
];
|
|
2430
|
-
function actionHash(action) {
|
|
2431
|
-
return createHash("sha256").update(action.trim().toLowerCase()).digest("hex");
|
|
2432
|
-
}
|
|
2433
|
-
function issue(code, field2, message) {
|
|
2434
|
-
return { code, field: field2, message };
|
|
2435
|
-
}
|
|
2436
|
-
function typeName(value) {
|
|
2437
|
-
if (value === null)
|
|
2438
|
-
return "an empty value";
|
|
2439
|
-
if (Array.isArray(value))
|
|
2440
|
-
return "a list";
|
|
2441
|
-
if (typeof value === "object")
|
|
2442
|
-
return "a mapping";
|
|
2443
|
-
if (typeof value === "boolean")
|
|
2444
|
-
return "a boolean";
|
|
2445
|
-
if (typeof value === "number")
|
|
2446
|
-
return "a number";
|
|
2447
|
-
return `a ${typeof value}`;
|
|
2448
|
-
}
|
|
2449
|
-
function requiredString(data, field2) {
|
|
2450
|
-
const value = data[field2];
|
|
2451
|
-
if (value === undefined || value === null) {
|
|
2452
|
-
return [issue("missing-field", field2, `${field2} is required and is absent`)];
|
|
2453
|
-
}
|
|
2454
|
-
if (typeof value !== "string") {
|
|
2455
|
-
return [issue("wrong-type", field2, `${field2} must be a string, not ${typeName(value)}`)];
|
|
2456
|
-
}
|
|
2457
|
-
if (value.trim() === "") {
|
|
2458
|
-
return [issue("missing-field", field2, `${field2} is required and is empty`)];
|
|
2459
|
-
}
|
|
2460
|
-
return [];
|
|
2461
|
-
}
|
|
2462
|
-
function requiredChoice(data, field2, allowed, code) {
|
|
2463
|
-
const issues = requiredString(data, field2);
|
|
2464
|
-
if (issues.length > 0)
|
|
2465
|
-
return issues;
|
|
2466
|
-
const value = data[field2];
|
|
2467
|
-
return allowed.includes(value) ? [] : [issue(code, field2, `${field2} "${value}" is not one of ${allowed.join(", ")}`)];
|
|
2468
|
-
}
|
|
2469
|
-
function checkId(data) {
|
|
2470
|
-
const issues = requiredString(data, "id");
|
|
2471
|
-
if (issues.length > 0)
|
|
2472
|
-
return issues;
|
|
2473
|
-
const id = data["id"];
|
|
2474
|
-
return INSTINCT_ID_PATTERN.test(id) ? [] : [issue("invalid-id", "id", `id "${id}" is not lowercase words joined by single hyphens`)];
|
|
2475
|
-
}
|
|
2476
|
-
function checkConfidence(data) {
|
|
2477
|
-
const value = data["confidence"];
|
|
2478
|
-
if (value === undefined || value === null) {
|
|
2479
|
-
return [issue("missing-field", "confidence", "confidence is required and is absent")];
|
|
2480
|
-
}
|
|
2481
|
-
if (typeof value !== "number" || Number.isNaN(value)) {
|
|
2482
|
-
return [issue("wrong-type", "confidence", `confidence must be a number, not ${typeName(value)}`)];
|
|
2483
|
-
}
|
|
2484
|
-
return value >= CONFIDENCE_MIN && value <= CONFIDENCE_MAX ? [] : [issue("confidence-out-of-range", "confidence", `confidence ${value} is outside ${CONFIDENCE_MIN} to ${CONFIDENCE_MAX}`)];
|
|
2485
|
-
}
|
|
2486
|
-
function checkUsageCount(data) {
|
|
2487
|
-
const value = data["usage_count"];
|
|
2488
|
-
if (value === undefined)
|
|
2489
|
-
return [];
|
|
2490
|
-
if (typeof value !== "number") {
|
|
2491
|
-
return [issue("wrong-type", "usage_count", `usage_count must be a number, not ${typeName(value)}`)];
|
|
2492
|
-
}
|
|
2493
|
-
return Number.isInteger(value) && value >= DEFAULT_USAGE_COUNT ? [] : [issue("invalid-usage-count", "usage_count", `usage_count ${value} is not a whole number of at least ${DEFAULT_USAGE_COUNT}`)];
|
|
2494
|
-
}
|
|
2495
|
-
function checkArtifact(data) {
|
|
2496
|
-
const value = data["artifact"];
|
|
2497
|
-
if (value === undefined || typeof value === "string")
|
|
2498
|
-
return [];
|
|
2499
|
-
return [issue("wrong-type", "artifact", `artifact must be a string, not ${typeName(value)}`)];
|
|
2500
|
-
}
|
|
2501
|
-
function checkProjectId(data) {
|
|
2502
|
-
const value = data["project_id"];
|
|
2503
|
-
if (value === undefined)
|
|
2504
|
-
return [];
|
|
2505
|
-
if (typeof value !== "string") {
|
|
2506
|
-
return [issue("wrong-type", "project_id", `project_id must be a string, not ${typeName(value)}`)];
|
|
2507
|
-
}
|
|
2508
|
-
return PROJECT_ID_PATTERN.test(value) ? [] : [issue("invalid-project-id", "project_id", `project_id "${value}" is not ${PROJECT_ID_LENGTH} lowercase hex characters`)];
|
|
2509
|
-
}
|
|
2510
|
-
function checkTimestamp(data, field2) {
|
|
2511
|
-
const issues = requiredString(data, field2);
|
|
2512
|
-
if (issues.length > 0)
|
|
2513
|
-
return issues;
|
|
2514
|
-
const value = data[field2];
|
|
2515
|
-
return TIMESTAMP_PATTERN.test(value) && !Number.isNaN(Date.parse(value)) ? [] : [issue("invalid-timestamp", field2, `${field2} "${value}" is not an ISO 8601 instant such as 2026-09-11T10:00:00Z`)];
|
|
2516
|
-
}
|
|
2517
|
-
function isScalar(value) {
|
|
2518
|
-
return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
2519
|
-
}
|
|
2520
|
-
function checkEvidenceEntry(entry, index) {
|
|
2521
|
-
const field2 = `evidence[${index}]`;
|
|
2522
|
-
if (typeof entry !== "object" || entry === null || Array.isArray(entry)) {
|
|
2523
|
-
return [issue("wrong-type", field2, `evidence entries must be mappings, and this one is ${typeName(entry)}`)];
|
|
2524
|
-
}
|
|
2525
|
-
const entries = Object.entries(entry);
|
|
2526
|
-
if (entries.length === 0) {
|
|
2527
|
-
return [issue("wrong-type", field2, "evidence entries must say something, and this one is empty")];
|
|
2528
|
-
}
|
|
2529
|
-
return entries.flatMap(([key, value]) => isScalar(value) ? [] : [issue("wrong-type", `${field2}.${key}`, `evidence values must be text, numbers or booleans, and this one is ${typeName(value)}`)]);
|
|
2530
|
-
}
|
|
2531
|
-
function checkEvidence(data) {
|
|
2532
|
-
const value = data["evidence"];
|
|
2533
|
-
const owed = data["source"] === EVIDENCE_REQUIRED_SOURCE;
|
|
2534
|
-
if (value === undefined || value === null)
|
|
2535
|
-
return owed ? [missingEvidence("is absent")] : [];
|
|
2536
|
-
if (!Array.isArray(value)) {
|
|
2537
|
-
return [issue("wrong-type", "evidence", `evidence must be a list, not ${typeName(value)}`)];
|
|
2538
|
-
}
|
|
2539
|
-
if (value.length === 0)
|
|
2540
|
-
return owed ? [missingEvidence("is an empty list")] : [];
|
|
2541
|
-
return value.flatMap((entry, index) => checkEvidenceEntry(entry, index));
|
|
2542
|
-
}
|
|
2543
|
-
function missingEvidence(state) {
|
|
2544
|
-
return issue("missing-evidence", "evidence", `a ${EVIDENCE_REQUIRED_SOURCE} record must name what it was learned from, and evidence ${state}`);
|
|
2545
|
-
}
|
|
2546
|
-
function checkStoredHash(data) {
|
|
2547
|
-
if (!Object.hasOwn(data, "action_hash"))
|
|
2548
|
-
return [];
|
|
2549
|
-
return [issue("stored-action-hash", "action_hash", "action_hash is computed from the Action section at read time and is never stored, " + "because a hand edit would leave a stored one stale")];
|
|
2550
|
-
}
|
|
2551
|
-
function checkInstinctFrontmatter(data) {
|
|
2552
|
-
return [
|
|
2553
|
-
...checkId(data),
|
|
2554
|
-
...requiredString(data, "trigger"),
|
|
2555
|
-
...requiredChoice(data, "kind", INSTINCT_KINDS, "unknown-kind"),
|
|
2556
|
-
...requiredChoice(data, "domain", INSTINCT_DOMAINS, "unknown-domain"),
|
|
2557
|
-
...checkConfidence(data),
|
|
2558
|
-
...checkUsageCount(data),
|
|
2559
|
-
...checkArtifact(data),
|
|
2560
|
-
...requiredChoice(data, "signal", INSTINCT_SIGNALS, "unknown-signal"),
|
|
2561
|
-
...requiredChoice(data, "scope", INSTINCT_SCOPES, "unknown-scope"),
|
|
2562
|
-
...checkProjectId(data),
|
|
2563
|
-
...requiredChoice(data, "source", INSTINCT_SOURCES, "unknown-source"),
|
|
2564
|
-
...checkEvidence(data),
|
|
2565
|
-
...checkTimestamp(data, "created_at"),
|
|
2566
|
-
...checkTimestamp(data, "updated_at"),
|
|
2567
|
-
...checkStoredHash(data)
|
|
2568
|
-
];
|
|
2569
|
-
}
|
|
2570
|
-
function isFence(line) {
|
|
2571
|
-
return /^\s*(?:```|~~~)/.test(line);
|
|
2572
|
-
}
|
|
2573
|
-
function sectionText(body, heading) {
|
|
2574
|
-
const lines = body.split(`
|
|
2575
|
-
`).map((line) => line.replace(/\r$/, ""));
|
|
2576
|
-
const collected = [];
|
|
2577
|
-
let fenced = false;
|
|
2578
|
-
let inside = false;
|
|
2579
|
-
for (const line of lines) {
|
|
2580
|
-
if (isFence(line))
|
|
2581
|
-
fenced = !fenced;
|
|
2582
|
-
else if (!fenced && /^##\s/.test(line)) {
|
|
2583
|
-
if (inside)
|
|
2584
|
-
break;
|
|
2585
|
-
inside = line.trim() === heading;
|
|
2586
|
-
continue;
|
|
2587
|
-
}
|
|
2588
|
-
if (inside)
|
|
2589
|
-
collected.push(line);
|
|
2590
|
-
}
|
|
2591
|
-
return inside ? collected.join(`
|
|
2592
|
-
`).trim() : null;
|
|
2593
|
-
}
|
|
2594
|
-
function checkSection(body, heading) {
|
|
2595
|
-
const text = sectionText(body, heading);
|
|
2596
|
-
if (text === null) {
|
|
2597
|
-
return [issue("missing-section", heading, `the body carries no ${heading} section`)];
|
|
2598
|
-
}
|
|
2599
|
-
return text === "" ? [issue("missing-section", heading, `the ${heading} section is empty`)] : [];
|
|
2600
|
-
}
|
|
2601
|
-
function checkInstinctBody(body) {
|
|
2602
|
-
return [...checkSection(body, ACTION_HEADING), ...checkSection(body, CAUSE_HEADING)];
|
|
2603
|
-
}
|
|
2604
|
-
function parseInstinct(text) {
|
|
2605
|
-
const document = readFrontmatterDocument(text);
|
|
2606
|
-
if (document === null) {
|
|
2607
|
-
return {
|
|
2608
|
-
issues: [issue("missing-frontmatter", "frontmatter", "the file opens with no --- block holding a YAML mapping")],
|
|
2609
|
-
instinct: null
|
|
2610
|
-
};
|
|
2611
|
-
}
|
|
2612
|
-
const issues = [
|
|
2613
|
-
...checkInstinctFrontmatter(document.data),
|
|
2614
|
-
...checkInstinctBody(document.body)
|
|
2615
|
-
];
|
|
2616
|
-
return issues.length > 0 ? { issues, instinct: null } : { issues, instinct: buildInstinct(document.data, document.body) };
|
|
2617
|
-
}
|
|
2618
|
-
function buildInstinct(data, body) {
|
|
2619
|
-
const action = sectionText(body, ACTION_HEADING) ?? "";
|
|
2620
|
-
const artifact = typeof data["artifact"] === "string" && data["artifact"].trim() !== "" ? data["artifact"] : null;
|
|
2621
|
-
return {
|
|
2622
|
-
id: data["id"],
|
|
2623
|
-
trigger: data["trigger"],
|
|
2624
|
-
kind: data["kind"],
|
|
2625
|
-
domain: data["domain"],
|
|
2626
|
-
confidence: data["confidence"],
|
|
2627
|
-
usageCount: typeof data["usage_count"] === "number" ? data["usage_count"] : DEFAULT_USAGE_COUNT,
|
|
2628
|
-
artifact,
|
|
2629
|
-
signal: data["signal"],
|
|
2630
|
-
scope: data["scope"],
|
|
2631
|
-
projectId: typeof data["project_id"] === "string" ? data["project_id"] : null,
|
|
2632
|
-
source: data["source"],
|
|
2633
|
-
evidence: Array.isArray(data["evidence"]) ? data["evidence"] : [],
|
|
2634
|
-
createdAt: data["created_at"],
|
|
2635
|
-
updatedAt: data["updated_at"],
|
|
2636
|
-
action,
|
|
2637
|
-
cause: sectionText(body, CAUSE_HEADING) ?? "",
|
|
2638
|
-
actionHash: actionHash(action)
|
|
2639
|
-
};
|
|
2640
|
-
}
|
|
2641
|
-
function instinctFrontmatter(instinct) {
|
|
2642
|
-
const written = {
|
|
2643
|
-
id: instinct.id,
|
|
2644
|
-
trigger: instinct.trigger,
|
|
2645
|
-
kind: instinct.kind,
|
|
2646
|
-
domain: instinct.domain,
|
|
2647
|
-
confidence: instinct.confidence,
|
|
2648
|
-
usage_count: instinct.usageCount,
|
|
2649
|
-
signal: instinct.signal,
|
|
2650
|
-
scope: instinct.scope,
|
|
2651
|
-
source: instinct.source,
|
|
2652
|
-
created_at: instinct.createdAt,
|
|
2653
|
-
updated_at: instinct.updatedAt
|
|
2654
|
-
};
|
|
2655
|
-
if (instinct.artifact !== null)
|
|
2656
|
-
written["artifact"] = instinct.artifact;
|
|
2657
|
-
if (instinct.projectId !== null)
|
|
2658
|
-
written["project_id"] = instinct.projectId;
|
|
2659
|
-
if (instinct.evidence.length > 0)
|
|
2660
|
-
written["evidence"] = instinct.evidence;
|
|
2661
|
-
return Object.fromEntries(INSTINCT_KEY_ORDER.filter((key) => Object.hasOwn(written, key)).map((key) => [key, written[key]]));
|
|
2662
|
-
}
|
|
2663
|
-
function writeInstinct(instinct, newline = `
|
|
2664
|
-
`) {
|
|
2665
|
-
const block = renderFrontmatter(instinctFrontmatter(instinct), newline);
|
|
2666
|
-
return [
|
|
2667
|
-
"---",
|
|
2668
|
-
block,
|
|
2669
|
-
"---",
|
|
2670
|
-
"",
|
|
2671
|
-
ACTION_HEADING,
|
|
2672
|
-
instinct.action,
|
|
2673
|
-
"",
|
|
2674
|
-
CAUSE_HEADING,
|
|
2675
|
-
instinct.cause,
|
|
2676
|
-
""
|
|
2677
|
-
].join(newline);
|
|
2678
|
-
}
|
|
2679
|
-
|
|
2680
2297
|
// src/schema/provenance.ts
|
|
2681
2298
|
var PROVENANCE_FIELD = "provenance";
|
|
2682
2299
|
var FIRST_PARTY = "first-party";
|
|
2683
2300
|
var PROVENANCE_KEYS = ["origin", "license", "reviewed"];
|
|
2684
2301
|
var REQUIRED_PROVENANCE_KEYS = ["origin", "license"];
|
|
2685
2302
|
var REVIEWED_PATTERN = /^(\S(?:.*\S)?)\s+(\d{4}-\d{2}-\d{2})$/;
|
|
2686
|
-
function
|
|
2303
|
+
function issue(code, field2, message) {
|
|
2687
2304
|
return { code, field: field2, message };
|
|
2688
2305
|
}
|
|
2689
|
-
function
|
|
2306
|
+
function typeName(value) {
|
|
2690
2307
|
if (value === null)
|
|
2691
2308
|
return "an empty value";
|
|
2692
2309
|
if (Array.isArray(value))
|
|
@@ -2715,13 +2332,13 @@ function requiredEntry(value, key) {
|
|
|
2715
2332
|
const field2 = `${PROVENANCE_FIELD}.${key}`;
|
|
2716
2333
|
const entry = value[key];
|
|
2717
2334
|
if (entry === undefined || entry === null) {
|
|
2718
|
-
return [
|
|
2335
|
+
return [issue("missing-field", field2, `${field2} is required on a third-party item and is absent`)];
|
|
2719
2336
|
}
|
|
2720
2337
|
if (typeof entry !== "string") {
|
|
2721
|
-
return [
|
|
2338
|
+
return [issue("wrong-type", field2, `${field2} must be a string, not ${typeName(entry)}`)];
|
|
2722
2339
|
}
|
|
2723
2340
|
if (entry.trim() === "") {
|
|
2724
|
-
return [
|
|
2341
|
+
return [issue("missing-field", field2, `${field2} is required on a third-party item and is empty`)];
|
|
2725
2342
|
}
|
|
2726
2343
|
return [];
|
|
2727
2344
|
}
|
|
@@ -2731,33 +2348,33 @@ function reviewedEntry(value) {
|
|
|
2731
2348
|
const field2 = `${PROVENANCE_FIELD}.reviewed`;
|
|
2732
2349
|
const entry = value["reviewed"];
|
|
2733
2350
|
if (typeof entry !== "string") {
|
|
2734
|
-
return [
|
|
2351
|
+
return [issue("wrong-type", field2, `${field2} must be a string, not ${typeName(entry)}`)];
|
|
2735
2352
|
}
|
|
2736
2353
|
const problem = reviewProblem(entry);
|
|
2737
|
-
return problem === null ? [] : [
|
|
2354
|
+
return problem === null ? [] : [issue("invalid-reviewed", field2, `${field2} "${entry}" ${problem}`)];
|
|
2738
2355
|
}
|
|
2739
2356
|
function unknownKeys(value) {
|
|
2740
2357
|
const known = PROVENANCE_KEYS;
|
|
2741
2358
|
const unknown = Object.keys(value).filter((key) => !known.includes(key));
|
|
2742
|
-
return unknown.map((key) =>
|
|
2359
|
+
return unknown.map((key) => issue("unknown-provenance", `${PROVENANCE_FIELD}.${key}`, `${key} is not a provenance key; the keys are ${PROVENANCE_KEYS.join(", ")}`));
|
|
2743
2360
|
}
|
|
2744
2361
|
function stringProblems(value) {
|
|
2745
2362
|
if (value.trim() === "") {
|
|
2746
|
-
return [
|
|
2363
|
+
return [issue("missing-field", PROVENANCE_FIELD, `${PROVENANCE_FIELD} is present and empty`)];
|
|
2747
2364
|
}
|
|
2748
|
-
return value === FIRST_PARTY ? [] : [
|
|
2365
|
+
return value === FIRST_PARTY ? [] : [issue("unknown-provenance", PROVENANCE_FIELD, `${PROVENANCE_FIELD} "${value}" is neither ${FIRST_PARTY} nor a mapping of origin and license`)];
|
|
2749
2366
|
}
|
|
2750
2367
|
function checkProvenance(data) {
|
|
2751
2368
|
if (!Object.hasOwn(data, PROVENANCE_FIELD))
|
|
2752
2369
|
return [];
|
|
2753
2370
|
const value = data[PROVENANCE_FIELD];
|
|
2754
2371
|
if (value === null) {
|
|
2755
|
-
return [
|
|
2372
|
+
return [issue("missing-field", PROVENANCE_FIELD, `${PROVENANCE_FIELD} is present and empty`)];
|
|
2756
2373
|
}
|
|
2757
2374
|
if (typeof value === "string")
|
|
2758
2375
|
return stringProblems(value);
|
|
2759
2376
|
if (!isMapping2(value)) {
|
|
2760
|
-
return [
|
|
2377
|
+
return [issue("wrong-type", PROVENANCE_FIELD, `${PROVENANCE_FIELD} must be ${FIRST_PARTY} or a mapping, not ${typeName(value)}`)];
|
|
2761
2378
|
}
|
|
2762
2379
|
return [
|
|
2763
2380
|
...REQUIRED_PROVENANCE_KEYS.flatMap((key) => requiredEntry(value, key)),
|
|
@@ -3074,26 +2691,26 @@ function classLiteralEnd(pattern, start) {
|
|
|
3074
2691
|
const negated = pattern[start + 1] === "!" || pattern[start + 1] === "^";
|
|
3075
2692
|
return negated ? start + 2 : start + 1;
|
|
3076
2693
|
}
|
|
3077
|
-
function
|
|
2694
|
+
function issue2(code, field2, message) {
|
|
3078
2695
|
return { code, field: field2, message };
|
|
3079
2696
|
}
|
|
3080
2697
|
function isFilled(value) {
|
|
3081
2698
|
return typeof value === "string" && value.trim() !== "";
|
|
3082
2699
|
}
|
|
3083
|
-
function
|
|
2700
|
+
function requiredString(data, field2) {
|
|
3084
2701
|
const value = data[field2];
|
|
3085
2702
|
if (value === undefined || value === null) {
|
|
3086
|
-
return [
|
|
2703
|
+
return [issue2("missing-field", field2, `${field2} is required and is absent`)];
|
|
3087
2704
|
}
|
|
3088
2705
|
if (typeof value !== "string") {
|
|
3089
|
-
return [
|
|
2706
|
+
return [issue2("wrong-type", field2, `${field2} must be a string, not ${typeName2(value)}`)];
|
|
3090
2707
|
}
|
|
3091
2708
|
if (value.trim() === "") {
|
|
3092
|
-
return [
|
|
2709
|
+
return [issue2("missing-field", field2, `${field2} is required and is empty`)];
|
|
3093
2710
|
}
|
|
3094
2711
|
return [];
|
|
3095
2712
|
}
|
|
3096
|
-
function
|
|
2713
|
+
function typeName2(value) {
|
|
3097
2714
|
if (value === null)
|
|
3098
2715
|
return "an empty value";
|
|
3099
2716
|
if (Array.isArray(value))
|
|
@@ -3107,29 +2724,29 @@ function typeName3(value) {
|
|
|
3107
2724
|
return `a ${typeof value}`;
|
|
3108
2725
|
}
|
|
3109
2726
|
function checkName(data) {
|
|
3110
|
-
const issues =
|
|
2727
|
+
const issues = requiredString(data, "name");
|
|
3111
2728
|
if (issues.length > 0)
|
|
3112
2729
|
return issues;
|
|
3113
2730
|
const name2 = data["name"];
|
|
3114
|
-
return isSkillName(name2) ? [] : [
|
|
2731
|
+
return isSkillName(name2) ? [] : [issue2("invalid-name", "name", `name "${name2}" is not lowercase words joined by single hyphens`)];
|
|
3115
2732
|
}
|
|
3116
2733
|
function checkDescription(data) {
|
|
3117
|
-
const issues =
|
|
2734
|
+
const issues = requiredString(data, "description");
|
|
3118
2735
|
if (issues.length > 0)
|
|
3119
2736
|
return issues;
|
|
3120
2737
|
const length = countCharacters(data["description"]);
|
|
3121
|
-
return length < DESCRIPTION_LIMIT ? [] : [
|
|
2738
|
+
return length < DESCRIPTION_LIMIT ? [] : [issue2("description-too-long", "description", `description is ${length} characters, and the limit is ${DESCRIPTION_LIMIT}`)];
|
|
3122
2739
|
}
|
|
3123
2740
|
function requiredStringList(data, field2) {
|
|
3124
2741
|
const value = data[field2];
|
|
3125
2742
|
if (value === undefined || value === null) {
|
|
3126
|
-
return [
|
|
2743
|
+
return [issue2("missing-field", field2, `${field2} is required and is absent`)];
|
|
3127
2744
|
}
|
|
3128
2745
|
if (!Array.isArray(value)) {
|
|
3129
|
-
return [
|
|
2746
|
+
return [issue2("wrong-type", field2, `${field2} must be a list, not ${typeName2(value)}`)];
|
|
3130
2747
|
}
|
|
3131
2748
|
if (value.length === 0) {
|
|
3132
|
-
return [
|
|
2749
|
+
return [issue2("missing-field", field2, `${field2} is required and is an empty list`)];
|
|
3133
2750
|
}
|
|
3134
2751
|
return entryTypeIssues(value, field2);
|
|
3135
2752
|
}
|
|
@@ -3137,7 +2754,7 @@ function entryTypeIssues(values, field2) {
|
|
|
3137
2754
|
const issues = [];
|
|
3138
2755
|
for (const [index, entry] of values.entries()) {
|
|
3139
2756
|
if (!isFilled(entry)) {
|
|
3140
|
-
issues.push(
|
|
2757
|
+
issues.push(issue2("wrong-type", `${field2}[${index}]`, `${field2} entries must be non-empty strings, and this one is ${typeName2(entry)}`));
|
|
3141
2758
|
}
|
|
3142
2759
|
}
|
|
3143
2760
|
return issues;
|
|
@@ -3148,36 +2765,36 @@ function checkStack(data) {
|
|
|
3148
2765
|
return issues;
|
|
3149
2766
|
const values = data["stack"];
|
|
3150
2767
|
if (values.length > 1 && values.includes(AGNOSTIC_STACK)) {
|
|
3151
|
-
return [
|
|
2768
|
+
return [issue2("unknown-stack", "stack", `${AGNOSTIC_STACK} is either the whole stack list or no part of it`)];
|
|
3152
2769
|
}
|
|
3153
2770
|
const unknown = new Set(unknownStacks(values));
|
|
3154
|
-
return values.flatMap((name2, index) => unknown.has(name2) ? [
|
|
2771
|
+
return values.flatMap((name2, index) => unknown.has(name2) ? [issue2("unknown-stack", `stack[${index}]`, `stack "${name2}" is not a vocabulary name`)] : []);
|
|
3155
2772
|
}
|
|
3156
2773
|
function checkWhenToUse(data) {
|
|
3157
2774
|
const issues = optionalString(data, "when_to_use");
|
|
3158
2775
|
if (issues.length > 0)
|
|
3159
2776
|
return issues;
|
|
3160
2777
|
const length = listingLength(data);
|
|
3161
|
-
return length < LISTING_LIMIT ? [] : [
|
|
2778
|
+
return length < LISTING_LIMIT ? [] : [issue2("listing-too-long", "description + when_to_use", `description and when_to_use are ${length} characters together, ` + `and Claude Code truncates the listing at ${LISTING_LIMIT}`)];
|
|
3162
2779
|
}
|
|
3163
2780
|
function optionalString(data, field2) {
|
|
3164
2781
|
if (!Object.hasOwn(data, field2))
|
|
3165
2782
|
return [];
|
|
3166
|
-
return
|
|
2783
|
+
return requiredString(data, field2);
|
|
3167
2784
|
}
|
|
3168
2785
|
function checkTrigger(data) {
|
|
3169
2786
|
const issues = [...optionalString(data, "prevents"), ...optionalString(data, "signal")];
|
|
3170
2787
|
const hasPrevents = Object.hasOwn(data, "prevents");
|
|
3171
2788
|
const hasSignal = Object.hasOwn(data, "signal");
|
|
3172
2789
|
if (hasPrevents && !hasSignal) {
|
|
3173
|
-
issues.push(
|
|
2790
|
+
issues.push(issue2("unpaired-prevents", "signal", "prevents names a failure, so signal must say whether it announces itself"));
|
|
3174
2791
|
}
|
|
3175
2792
|
if (hasSignal && !hasPrevents) {
|
|
3176
|
-
issues.push(
|
|
2793
|
+
issues.push(issue2("unpaired-prevents", "prevents", "signal describes a failure, so prevents must name the failure it describes"));
|
|
3177
2794
|
}
|
|
3178
2795
|
const signal = stringAt(data, "signal");
|
|
3179
2796
|
if (signal !== null && signal.trim() !== "" && !isSkillSignal(signal)) {
|
|
3180
|
-
issues.push(
|
|
2797
|
+
issues.push(issue2("unknown-signal", "signal", `signal "${signal}" is neither ${SKILL_SIGNALS.join(" nor ")}`));
|
|
3181
2798
|
}
|
|
3182
2799
|
return issues;
|
|
3183
2800
|
}
|
|
@@ -3189,29 +2806,29 @@ function checkNameList(data, field2) {
|
|
|
3189
2806
|
if (value === undefined)
|
|
3190
2807
|
return [];
|
|
3191
2808
|
if (!Array.isArray(value)) {
|
|
3192
|
-
return [
|
|
2809
|
+
return [issue2("wrong-type", field2, `${field2} must be a list, not ${typeName2(value)}`)];
|
|
3193
2810
|
}
|
|
3194
2811
|
const issues = entryTypeIssues(value, field2);
|
|
3195
2812
|
if (issues.length > 0)
|
|
3196
2813
|
return issues;
|
|
3197
|
-
return value.flatMap((entry, index) => isSkillName(entry) ? [] : [
|
|
2814
|
+
return value.flatMap((entry, index) => isSkillName(entry) ? [] : [issue2("invalid-name", `${field2}[${index}]`, `${field2} entry "${entry}" is not a skill name`)]);
|
|
3198
2815
|
}
|
|
3199
2816
|
function checkPaths(data) {
|
|
3200
2817
|
const value = data["paths"];
|
|
3201
2818
|
if (value === undefined)
|
|
3202
2819
|
return [];
|
|
3203
2820
|
if (!Array.isArray(value)) {
|
|
3204
|
-
return [
|
|
2821
|
+
return [issue2("wrong-type", "paths", `paths must be a list, not ${typeName2(value)}`)];
|
|
3205
2822
|
}
|
|
3206
2823
|
const issues = [];
|
|
3207
2824
|
for (const [index, entry] of value.entries()) {
|
|
3208
2825
|
if (typeof entry !== "string") {
|
|
3209
|
-
issues.push(
|
|
2826
|
+
issues.push(issue2("wrong-type", `paths[${index}]`, `paths entries must be strings, and this one is ${typeName2(entry)}`));
|
|
3210
2827
|
continue;
|
|
3211
2828
|
}
|
|
3212
2829
|
const problem = globProblem(entry);
|
|
3213
2830
|
if (problem !== null) {
|
|
3214
|
-
issues.push(
|
|
2831
|
+
issues.push(issue2("unusable-glob", `paths[${index}]`, `the glob "${entry}" ${problem}`));
|
|
3215
2832
|
}
|
|
3216
2833
|
}
|
|
3217
2834
|
return issues;
|
|
@@ -3220,10 +2837,10 @@ function checkBoolean(data, field2) {
|
|
|
3220
2837
|
const value = data[field2];
|
|
3221
2838
|
if (value === undefined || typeof value === "boolean")
|
|
3222
2839
|
return [];
|
|
3223
|
-
return [
|
|
2840
|
+
return [issue2("wrong-type", field2, `${field2} must be true or false unquoted, and this is ${typeName2(value)}`)];
|
|
3224
2841
|
}
|
|
3225
2842
|
function checkForbidden(data) {
|
|
3226
|
-
return FORBIDDEN_SKILL_FIELDS.filter((field2) => Object.hasOwn(data, field2)).map((field2) =>
|
|
2843
|
+
return FORBIDDEN_SKILL_FIELDS.filter((field2) => Object.hasOwn(data, field2)).map((field2) => issue2("forbidden-field", field2, `${field2} belongs to an instinct record and never to a skill`));
|
|
3227
2844
|
}
|
|
3228
2845
|
function checkSkillFrontmatter(data) {
|
|
3229
2846
|
return [
|
|
@@ -3241,248 +2858,9 @@ function checkSkillFrontmatter(data) {
|
|
|
3241
2858
|
];
|
|
3242
2859
|
}
|
|
3243
2860
|
|
|
3244
|
-
// src/check/layout.ts
|
|
3245
|
-
import { readdirSync as readdirSync2, statSync as statSync2 } from "fs";
|
|
3246
|
-
import { join as join3, relative } from "path";
|
|
3247
|
-
var SKILL_FILE = "SKILL.md";
|
|
3248
|
-
var MARKDOWN_EXTENSION = ".md";
|
|
3249
|
-
var LAYOUT_SEVERITY = Object.freeze({
|
|
3250
|
-
"flat-grouped-layout": "failure",
|
|
3251
|
-
"nested-group-layout": "failure",
|
|
3252
|
-
"loose-file": "failure",
|
|
3253
|
-
"missing-skill-file": "failure",
|
|
3254
|
-
"nested-instinct-file": "failure",
|
|
3255
|
-
"unreadable-entry": "failure",
|
|
3256
|
-
"name-mismatch": "failure",
|
|
3257
|
-
"dotfile-only-directory": "warning"
|
|
3258
|
-
});
|
|
3259
|
-
function layoutIssue(code, message) {
|
|
3260
|
-
return { code, severity: LAYOUT_SEVERITY[code], message };
|
|
3261
|
-
}
|
|
3262
|
-
function entryKind(path) {
|
|
3263
|
-
try {
|
|
3264
|
-
const stats = statSync2(path);
|
|
3265
|
-
if (stats.isFile())
|
|
3266
|
-
return "file";
|
|
3267
|
-
if (stats.isDirectory())
|
|
3268
|
-
return "directory";
|
|
3269
|
-
return null;
|
|
3270
|
-
} catch {
|
|
3271
|
-
return null;
|
|
3272
|
-
}
|
|
3273
|
-
}
|
|
3274
|
-
function visibleNames(dir) {
|
|
3275
|
-
return readdirSync2(dir).filter((name2) => !name2.startsWith(".")).sort((left, right) => left.localeCompare(right));
|
|
3276
|
-
}
|
|
3277
|
-
function isMarkdown(name2) {
|
|
3278
|
-
return name2.endsWith(MARKDOWN_EXTENSION);
|
|
3279
|
-
}
|
|
3280
|
-
function markdownStem(name2) {
|
|
3281
|
-
return name2.slice(0, -MARKDOWN_EXTENSION.length);
|
|
3282
|
-
}
|
|
3283
|
-
function shown(root, path) {
|
|
3284
|
-
return relative(root, path) || ".";
|
|
3285
|
-
}
|
|
3286
|
-
function dotfileOnlyEntry(kind, root, dir) {
|
|
3287
|
-
return {
|
|
3288
|
-
kind,
|
|
3289
|
-
path: dir,
|
|
3290
|
-
isFile: false,
|
|
3291
|
-
shape: "dotfile-only",
|
|
3292
|
-
name: null,
|
|
3293
|
-
issues: [layoutIssue("dotfile-only-directory", `${shown(root, dir)}/ holds nothing but dotfiles, so it registers nothing`)]
|
|
3294
|
-
};
|
|
3295
|
-
}
|
|
3296
|
-
function unreadableEntry(kind, root, path) {
|
|
3297
|
-
return {
|
|
3298
|
-
kind,
|
|
3299
|
-
path,
|
|
3300
|
-
isFile: false,
|
|
3301
|
-
shape: "unreadable",
|
|
3302
|
-
name: null,
|
|
3303
|
-
issues: [layoutIssue("unreadable-entry", `${shown(root, path)} cannot be read (a broken symlink, or a path stat refuses)`)]
|
|
3304
|
-
};
|
|
3305
|
-
}
|
|
3306
|
-
function skillFileEntry(path, name2) {
|
|
3307
|
-
return { kind: "skill", path, isFile: true, shape: "skill-file", name: name2, issues: [] };
|
|
3308
|
-
}
|
|
3309
|
-
function flatGroupedEntry(root, path, name2) {
|
|
3310
|
-
return {
|
|
3311
|
-
kind: "skill",
|
|
3312
|
-
path,
|
|
3313
|
-
isFile: true,
|
|
3314
|
-
shape: "flat-grouped",
|
|
3315
|
-
name: name2,
|
|
3316
|
-
issues: [layoutIssue("flat-grouped-layout", `${shown(root, path)} is the flat grouped layout <dir>/<group>/<name>.md,` + ` which does not register; move it to <dir>/${name2}/${SKILL_FILE}`)]
|
|
3317
|
-
};
|
|
3318
|
-
}
|
|
3319
|
-
function nestedGroupEntry(root, path, name2) {
|
|
3320
|
-
return {
|
|
3321
|
-
kind: "skill",
|
|
3322
|
-
path,
|
|
3323
|
-
isFile: true,
|
|
3324
|
-
shape: "nested-group",
|
|
3325
|
-
name: name2,
|
|
3326
|
-
issues: [layoutIssue("nested-group-layout", `${shown(root, path)} is the nested layout <dir>/<group>/<name>/${SKILL_FILE},` + ` which does not register; move it to <dir>/${name2}/${SKILL_FILE}`)]
|
|
3327
|
-
};
|
|
3328
|
-
}
|
|
3329
|
-
function looseFileEntry(root, path, name2) {
|
|
3330
|
-
return {
|
|
3331
|
-
kind: "skill",
|
|
3332
|
-
path,
|
|
3333
|
-
isFile: true,
|
|
3334
|
-
shape: "loose-file",
|
|
3335
|
-
name: name2,
|
|
3336
|
-
issues: [layoutIssue("loose-file", `${shown(root, path)} sits directly under the skills directory, which registers` + ` only <dir>/<name>/${SKILL_FILE}; move it to <dir>/${name2}/${SKILL_FILE}`)]
|
|
3337
|
-
};
|
|
3338
|
-
}
|
|
3339
|
-
function noSkillFileEntry(root, dir) {
|
|
3340
|
-
return {
|
|
3341
|
-
kind: "skill",
|
|
3342
|
-
path: dir,
|
|
3343
|
-
isFile: false,
|
|
3344
|
-
shape: "no-skill-file",
|
|
3345
|
-
name: null,
|
|
3346
|
-
issues: [layoutIssue("missing-skill-file", `${shown(root, dir)}/ holds no ${SKILL_FILE}, so it registers no skill`)]
|
|
3347
|
-
};
|
|
3348
|
-
}
|
|
3349
|
-
function nestedInstinctEntry(root, path, name2) {
|
|
3350
|
-
return {
|
|
3351
|
-
kind: "instinct",
|
|
3352
|
-
path,
|
|
3353
|
-
isFile: true,
|
|
3354
|
-
shape: "nested-instinct",
|
|
3355
|
-
name: name2,
|
|
3356
|
-
issues: [layoutIssue("nested-instinct-file", `${shown(root, path)} sits below the instincts directory, which is flat;` + ` move it to <dir>/${name2}${MARKDOWN_EXTENSION}`)]
|
|
3357
|
-
};
|
|
3358
|
-
}
|
|
3359
|
-
function scanGroupMember(root, dir, name2) {
|
|
3360
|
-
const children = visibleNames(dir);
|
|
3361
|
-
if (children.length === 0)
|
|
3362
|
-
return [dotfileOnlyEntry("skill", root, dir)];
|
|
3363
|
-
const skillPath = join3(dir, SKILL_FILE);
|
|
3364
|
-
if (children.includes(SKILL_FILE) && entryKind(skillPath) === "file") {
|
|
3365
|
-
return [nestedGroupEntry(root, skillPath, name2)];
|
|
3366
|
-
}
|
|
3367
|
-
return [noSkillFileEntry(root, dir)];
|
|
3368
|
-
}
|
|
3369
|
-
function scanSkillGroup(root, dir, children) {
|
|
3370
|
-
const entries = [];
|
|
3371
|
-
for (const child of children) {
|
|
3372
|
-
const path = join3(dir, child);
|
|
3373
|
-
const found = entryKind(path);
|
|
3374
|
-
if (found === null) {
|
|
3375
|
-
entries.push(unreadableEntry("skill", root, path));
|
|
3376
|
-
continue;
|
|
3377
|
-
}
|
|
3378
|
-
if (found === "directory") {
|
|
3379
|
-
entries.push(...scanGroupMember(root, path, child));
|
|
3380
|
-
continue;
|
|
3381
|
-
}
|
|
3382
|
-
if (isMarkdown(child))
|
|
3383
|
-
entries.push(flatGroupedEntry(root, path, markdownStem(child)));
|
|
3384
|
-
}
|
|
3385
|
-
if (entries.length === 0)
|
|
3386
|
-
return [noSkillFileEntry(root, dir)];
|
|
3387
|
-
return entries;
|
|
3388
|
-
}
|
|
3389
|
-
function scanSkillDirectory(root, dir, name2) {
|
|
3390
|
-
const children = visibleNames(dir);
|
|
3391
|
-
if (children.length === 0)
|
|
3392
|
-
return [dotfileOnlyEntry("skill", root, dir)];
|
|
3393
|
-
const skillPath = join3(dir, SKILL_FILE);
|
|
3394
|
-
if (children.includes(SKILL_FILE) && entryKind(skillPath) === "file") {
|
|
3395
|
-
return [skillFileEntry(skillPath, name2)];
|
|
3396
|
-
}
|
|
3397
|
-
return scanSkillGroup(root, dir, children);
|
|
3398
|
-
}
|
|
3399
|
-
function scanSkillRoot(root) {
|
|
3400
|
-
const entries = [];
|
|
3401
|
-
for (const name2 of visibleNames(root)) {
|
|
3402
|
-
const path = join3(root, name2);
|
|
3403
|
-
const found = entryKind(path);
|
|
3404
|
-
if (found === null) {
|
|
3405
|
-
entries.push(unreadableEntry("skill", root, path));
|
|
3406
|
-
continue;
|
|
3407
|
-
}
|
|
3408
|
-
if (found === "directory") {
|
|
3409
|
-
entries.push(...scanSkillDirectory(root, path, name2));
|
|
3410
|
-
continue;
|
|
3411
|
-
}
|
|
3412
|
-
if (isMarkdown(name2))
|
|
3413
|
-
entries.push(looseFileEntry(root, path, markdownStem(name2)));
|
|
3414
|
-
}
|
|
3415
|
-
return entries;
|
|
3416
|
-
}
|
|
3417
|
-
function scanInstinctSubdirectory(root, dir) {
|
|
3418
|
-
const children = visibleNames(dir);
|
|
3419
|
-
if (children.length === 0)
|
|
3420
|
-
return [dotfileOnlyEntry("instinct", root, dir)];
|
|
3421
|
-
const entries = [];
|
|
3422
|
-
for (const child of children) {
|
|
3423
|
-
const path = join3(dir, child);
|
|
3424
|
-
const found = entryKind(path);
|
|
3425
|
-
if (found === null) {
|
|
3426
|
-
entries.push(unreadableEntry("instinct", root, path));
|
|
3427
|
-
continue;
|
|
3428
|
-
}
|
|
3429
|
-
if (found === "directory") {
|
|
3430
|
-
entries.push(...scanInstinctSubdirectory(root, path));
|
|
3431
|
-
continue;
|
|
3432
|
-
}
|
|
3433
|
-
if (isMarkdown(child))
|
|
3434
|
-
entries.push(nestedInstinctEntry(root, path, markdownStem(child)));
|
|
3435
|
-
}
|
|
3436
|
-
return entries;
|
|
3437
|
-
}
|
|
3438
|
-
function scanInstinctRoot(root) {
|
|
3439
|
-
const entries = [];
|
|
3440
|
-
for (const name2 of visibleNames(root)) {
|
|
3441
|
-
const path = join3(root, name2);
|
|
3442
|
-
const found = entryKind(path);
|
|
3443
|
-
if (found === null) {
|
|
3444
|
-
entries.push(unreadableEntry("instinct", root, path));
|
|
3445
|
-
continue;
|
|
3446
|
-
}
|
|
3447
|
-
if (found === "directory") {
|
|
3448
|
-
entries.push(...scanInstinctSubdirectory(root, path));
|
|
3449
|
-
continue;
|
|
3450
|
-
}
|
|
3451
|
-
if (!isMarkdown(name2))
|
|
3452
|
-
continue;
|
|
3453
|
-
entries.push({
|
|
3454
|
-
kind: "instinct",
|
|
3455
|
-
path,
|
|
3456
|
-
isFile: true,
|
|
3457
|
-
shape: "instinct-file",
|
|
3458
|
-
name: markdownStem(name2),
|
|
3459
|
-
issues: []
|
|
3460
|
-
});
|
|
3461
|
-
}
|
|
3462
|
-
return entries;
|
|
3463
|
-
}
|
|
3464
|
-
function scanLayout(root, kind) {
|
|
3465
|
-
if (entryKind(root) !== "directory") {
|
|
3466
|
-
throw new Error(`check layout: no ${kind} directory at ${root}`);
|
|
3467
|
-
}
|
|
3468
|
-
const entries = kind === "skill" ? scanSkillRoot(root) : scanInstinctRoot(root);
|
|
3469
|
-
return {
|
|
3470
|
-
kind,
|
|
3471
|
-
root,
|
|
3472
|
-
entries: entries.sort((left, right) => left.path.localeCompare(right.path))
|
|
3473
|
-
};
|
|
3474
|
-
}
|
|
3475
|
-
function declaredNameIssue(entry, declared) {
|
|
3476
|
-
if (entry.name === null || declared === null || declared === entry.name)
|
|
3477
|
-
return null;
|
|
3478
|
-
const field2 = entry.kind === "skill" ? "name" : "id";
|
|
3479
|
-
const source = entry.kind === "skill" ? "directory name" : "file name";
|
|
3480
|
-
return layoutIssue("name-mismatch", `frontmatter ${field2} "${declared}" is not the ${source} "${entry.name}"`);
|
|
3481
|
-
}
|
|
3482
|
-
|
|
3483
2861
|
// src/check/references.ts
|
|
3484
|
-
import { existsSync as existsSync7, statSync as
|
|
3485
|
-
import { delimiter, isAbsolute as isAbsolute3, relative
|
|
2862
|
+
import { existsSync as existsSync7, statSync as statSync2 } from "fs";
|
|
2863
|
+
import { delimiter, isAbsolute as isAbsolute3, relative, resolve as resolve2 } from "path";
|
|
3486
2864
|
|
|
3487
2865
|
// src/check/shell-lines.ts
|
|
3488
2866
|
var SHELL_FENCE_LANGUAGES = ["bash", "sh", "shell", "zsh", "console"];
|
|
@@ -3702,12 +3080,12 @@ function isAbsoluteReference(token) {
|
|
|
3702
3080
|
return KNOWN_ROOTS.includes(`/${first}`) || EXTENSION.test(token);
|
|
3703
3081
|
}
|
|
3704
3082
|
function isInside(root, target) {
|
|
3705
|
-
const step =
|
|
3083
|
+
const step = relative(resolve2(root), resolve2(target));
|
|
3706
3084
|
return step !== "" && !step.startsWith("..") && !isAbsolute3(step);
|
|
3707
3085
|
}
|
|
3708
3086
|
function isExecutableFile(path) {
|
|
3709
3087
|
try {
|
|
3710
|
-
const stats =
|
|
3088
|
+
const stats = statSync2(path);
|
|
3711
3089
|
return stats.isFile() && (stats.mode & 73) !== 0;
|
|
3712
3090
|
} catch {
|
|
3713
3091
|
return false;
|
|
@@ -3910,9 +3288,9 @@ function checkReferences(body, seams) {
|
|
|
3910
3288
|
if (seen.has(key))
|
|
3911
3289
|
continue;
|
|
3912
3290
|
seen.add(key);
|
|
3913
|
-
const
|
|
3914
|
-
if (
|
|
3915
|
-
issues.push(
|
|
3291
|
+
const issue3 = referenceIssue(ref, seams, functions);
|
|
3292
|
+
if (issue3 !== null)
|
|
3293
|
+
issues.push(issue3);
|
|
3916
3294
|
}
|
|
3917
3295
|
return { references, issues };
|
|
3918
3296
|
}
|
|
@@ -3929,66 +3307,66 @@ var CHECK_STAGES = [
|
|
|
3929
3307
|
var LOCALITY_CODES = ["home-path", "foreign-path"];
|
|
3930
3308
|
var FIXABLE_FIELDS = ["tags", "stack"];
|
|
3931
3309
|
function hasCheckFailure(issues) {
|
|
3932
|
-
return issues.some((
|
|
3310
|
+
return issues.some((issue3) => issue3.severity === "failure");
|
|
3933
3311
|
}
|
|
3934
3312
|
function failureExitCode(count) {
|
|
3935
3313
|
return Math.min(count, FAILING_FILE_CAP);
|
|
3936
3314
|
}
|
|
3937
|
-
function fromLayout(
|
|
3315
|
+
function fromLayout(issue3) {
|
|
3938
3316
|
return {
|
|
3939
3317
|
stage: "layout",
|
|
3940
|
-
code:
|
|
3941
|
-
severity:
|
|
3318
|
+
code: issue3.code,
|
|
3319
|
+
severity: issue3.severity,
|
|
3942
3320
|
field: null,
|
|
3943
3321
|
line: null,
|
|
3944
|
-
message:
|
|
3322
|
+
message: issue3.message
|
|
3945
3323
|
};
|
|
3946
3324
|
}
|
|
3947
|
-
function fromSkill(
|
|
3325
|
+
function fromSkill(issue3) {
|
|
3948
3326
|
return {
|
|
3949
3327
|
stage: "schema",
|
|
3950
|
-
code:
|
|
3328
|
+
code: issue3.code,
|
|
3951
3329
|
severity: "failure",
|
|
3952
|
-
field:
|
|
3330
|
+
field: issue3.field,
|
|
3953
3331
|
line: null,
|
|
3954
|
-
message:
|
|
3332
|
+
message: issue3.message
|
|
3955
3333
|
};
|
|
3956
3334
|
}
|
|
3957
|
-
function fromInstinct(
|
|
3335
|
+
function fromInstinct(issue3) {
|
|
3958
3336
|
return {
|
|
3959
3337
|
stage: "instinct",
|
|
3960
|
-
code:
|
|
3338
|
+
code: issue3.code,
|
|
3961
3339
|
severity: "failure",
|
|
3962
|
-
field:
|
|
3340
|
+
field: issue3.field,
|
|
3963
3341
|
line: null,
|
|
3964
|
-
message:
|
|
3342
|
+
message: issue3.message
|
|
3965
3343
|
};
|
|
3966
3344
|
}
|
|
3967
|
-
function fromReference(
|
|
3345
|
+
function fromReference(issue3) {
|
|
3968
3346
|
return {
|
|
3969
|
-
stage: LOCALITY_CODES.includes(
|
|
3970
|
-
code:
|
|
3971
|
-
severity:
|
|
3347
|
+
stage: LOCALITY_CODES.includes(issue3.code) ? "locality" : "resolution",
|
|
3348
|
+
code: issue3.code,
|
|
3349
|
+
severity: issue3.severity,
|
|
3972
3350
|
field: null,
|
|
3973
|
-
line:
|
|
3974
|
-
message:
|
|
3351
|
+
line: issue3.reference.line,
|
|
3352
|
+
message: issue3.message
|
|
3975
3353
|
};
|
|
3976
3354
|
}
|
|
3977
3355
|
function checkerIssue(stage, code, message) {
|
|
3978
3356
|
return { stage, code, severity: "failure", field: null, line: null, message };
|
|
3979
3357
|
}
|
|
3980
|
-
function
|
|
3358
|
+
function isMarkdown(name2) {
|
|
3981
3359
|
return name2.endsWith(MARKDOWN_EXTENSION);
|
|
3982
3360
|
}
|
|
3983
3361
|
function fileEntry(path, kind) {
|
|
3984
3362
|
const base = basename2(path);
|
|
3985
|
-
const stem =
|
|
3363
|
+
const stem = isMarkdown(base) ? base.slice(0, -MARKDOWN_EXTENSION.length) : base;
|
|
3986
3364
|
if (kind === "instinct") {
|
|
3987
3365
|
return {
|
|
3988
3366
|
kind,
|
|
3989
3367
|
path,
|
|
3990
3368
|
isFile: true,
|
|
3991
|
-
shape:
|
|
3369
|
+
shape: isMarkdown(base) ? "instinct-file" : "loose-file",
|
|
3992
3370
|
name: stem,
|
|
3993
3371
|
issues: []
|
|
3994
3372
|
};
|
|
@@ -3998,7 +3376,7 @@ function fileEntry(path, kind) {
|
|
|
3998
3376
|
function unregisteredIssue(path, kind) {
|
|
3999
3377
|
const base = basename2(path);
|
|
4000
3378
|
if (kind === "instinct") {
|
|
4001
|
-
return
|
|
3379
|
+
return isMarkdown(base) ? null : checkerIssue("layout", "unregistered-path", `${base} is not a ${MARKDOWN_EXTENSION} file, so it is no instinct record`);
|
|
4002
3380
|
}
|
|
4003
3381
|
return base === SKILL_FILE ? null : checkerIssue("layout", "unregistered-path", `${base} is not a ${SKILL_FILE}, so it registers no skill wherever it sits`);
|
|
4004
3382
|
}
|
|
@@ -4019,7 +3397,7 @@ function inStageOrder(issues) {
|
|
|
4019
3397
|
return [...issues].sort((left, right) => CHECK_STAGES.indexOf(left.stage) - CHECK_STAGES.indexOf(right.stage));
|
|
4020
3398
|
}
|
|
4021
3399
|
function fixableFields(issues) {
|
|
4022
|
-
const failures = issues.filter((
|
|
3400
|
+
const failures = issues.filter((issue3) => issue3.severity === "failure");
|
|
4023
3401
|
if (failures.length === 0)
|
|
4024
3402
|
return null;
|
|
4025
3403
|
const found = new Set;
|
|
@@ -4152,19 +3530,19 @@ function checkDirectory(root, kind, options) {
|
|
|
4152
3530
|
|
|
4153
3531
|
// src/utils/agent-definition.ts
|
|
4154
3532
|
import { readFileSync as readFileSync3 } from "fs";
|
|
4155
|
-
import { join as
|
|
4156
|
-
var AGENT_DEFINITION_DIR =
|
|
3533
|
+
import { join as join3 } from "path";
|
|
3534
|
+
var AGENT_DEFINITION_DIR = join3(".claude", "agents");
|
|
4157
3535
|
function agentDefinitionCandidates(name2, roots, settingSources) {
|
|
4158
3536
|
if (!isAgentName(name2))
|
|
4159
3537
|
return [];
|
|
4160
3538
|
const file = `${name2}.md`;
|
|
4161
3539
|
const project = {
|
|
4162
3540
|
scope: "project",
|
|
4163
|
-
path:
|
|
3541
|
+
path: join3(roots.repoRoot, AGENT_DEFINITION_DIR, file)
|
|
4164
3542
|
};
|
|
4165
3543
|
if (!settingSources.includes("user"))
|
|
4166
3544
|
return [project];
|
|
4167
|
-
return [project, { scope: "user", path:
|
|
3545
|
+
return [project, { scope: "user", path: join3(roots.home, AGENT_DEFINITION_DIR, file) }];
|
|
4168
3546
|
}
|
|
4169
3547
|
function readText2(path) {
|
|
4170
3548
|
try {
|
|
@@ -4246,16 +3624,16 @@ function checkVerdict(issues) {
|
|
|
4246
3624
|
}
|
|
4247
3625
|
|
|
4248
3626
|
// src/inventory/trees.ts
|
|
4249
|
-
var BUNDLED_AGENTS_DIR =
|
|
3627
|
+
var BUNDLED_AGENTS_DIR = join4("bundled", "agents");
|
|
4250
3628
|
function bundledAgentsDirectory(entry) {
|
|
4251
|
-
return
|
|
3629
|
+
return join4(dirname3(realEntry(entry ?? Bun.main)), BUNDLED_AGENTS_DIR);
|
|
4252
3630
|
}
|
|
4253
3631
|
function agentTreeDirectory(tier, seams) {
|
|
4254
3632
|
if (tier === "user")
|
|
4255
|
-
return
|
|
3633
|
+
return join4(seams.home, AGENT_DEFINITION_DIR);
|
|
4256
3634
|
if (tier === "rafa")
|
|
4257
3635
|
return bundledAgentsDirectory(seams.entry);
|
|
4258
|
-
return seams.projectRoot === null ? null :
|
|
3636
|
+
return seams.projectRoot === null ? null : join4(seams.projectRoot, AGENT_DEFINITION_DIR);
|
|
4259
3637
|
}
|
|
4260
3638
|
function readText3(path) {
|
|
4261
3639
|
try {
|
|
@@ -4284,7 +3662,7 @@ function byName(items) {
|
|
|
4284
3662
|
return [...items].sort((a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path));
|
|
4285
3663
|
}
|
|
4286
3664
|
function skillItem(report2, source, dir) {
|
|
4287
|
-
const name2 = report2.name ??
|
|
3665
|
+
const name2 = report2.name ?? relative2(dir, report2.path);
|
|
4288
3666
|
return sourceItem("skill", source, name2, report2.path, checkVerdict(report2.issues));
|
|
4289
3667
|
}
|
|
4290
3668
|
function readSkillTree(source, seams) {
|
|
@@ -4316,19 +3694,19 @@ function readTrees(seams) {
|
|
|
4316
3694
|
|
|
4317
3695
|
// src/tiers/serve.ts
|
|
4318
3696
|
import { cpSync, mkdirSync as mkdirSync2, readFileSync as readFileSync6, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
4319
|
-
import { basename as basename4, dirname as dirname5, join as
|
|
3697
|
+
import { basename as basename4, dirname as dirname5, join as join6 } from "path";
|
|
4320
3698
|
|
|
4321
3699
|
// src/loop/sessions.ts
|
|
4322
3700
|
import {
|
|
4323
3701
|
linkSync,
|
|
4324
3702
|
mkdirSync,
|
|
4325
|
-
readdirSync as
|
|
3703
|
+
readdirSync as readdirSync2,
|
|
4326
3704
|
readFileSync as readFileSync5,
|
|
4327
3705
|
renameSync,
|
|
4328
3706
|
rmSync,
|
|
4329
3707
|
writeFileSync as writeFileSync2
|
|
4330
3708
|
} from "fs";
|
|
4331
|
-
import { basename as basename3, dirname as dirname4, join as
|
|
3709
|
+
import { basename as basename3, dirname as dirname4, join as join5 } from "path";
|
|
4332
3710
|
var SESSION_STATES = Object.freeze(["running", "paused", "stopped", "done"]);
|
|
4333
3711
|
var RUNS_DIR = "runs";
|
|
4334
3712
|
var RECORD_EXTENSION = ".json";
|
|
@@ -4368,13 +3746,13 @@ function isSessionId(value) {
|
|
|
4368
3746
|
return SESSION_ID_PATTERN.test(value);
|
|
4369
3747
|
}
|
|
4370
3748
|
function runsDir(root) {
|
|
4371
|
-
return
|
|
3749
|
+
return join5(scopeAt(root).dir, RUNS_DIR);
|
|
4372
3750
|
}
|
|
4373
3751
|
function sessionFilePath(root, sessionId) {
|
|
4374
3752
|
if (!isSessionId(sessionId)) {
|
|
4375
3753
|
throw new Error(`session record: unusable session id ${JSON.stringify(sessionId)}`);
|
|
4376
3754
|
}
|
|
4377
|
-
return
|
|
3755
|
+
return join5(runsDir(root), `${sessionId}${RECORD_EXTENSION}`);
|
|
4378
3756
|
}
|
|
4379
3757
|
function describeValue3(value) {
|
|
4380
3758
|
return value === undefined ? "missing" : JSON.stringify(value) ?? String(value);
|
|
@@ -4484,7 +3862,7 @@ function readRecordFile(file) {
|
|
|
4484
3862
|
}
|
|
4485
3863
|
function recordNames(dir) {
|
|
4486
3864
|
try {
|
|
4487
|
-
return
|
|
3865
|
+
return readdirSync2(dir);
|
|
4488
3866
|
} catch (error) {
|
|
4489
3867
|
if (errorCode(error) === "ENOENT")
|
|
4490
3868
|
return [];
|
|
@@ -4497,7 +3875,7 @@ function byStart(a, b) {
|
|
|
4497
3875
|
function readSessions(root, seams = {}) {
|
|
4498
3876
|
const dir = runsDir(root);
|
|
4499
3877
|
const isAlive = seams.isAlive ?? isPidAlive;
|
|
4500
|
-
const records = recordNames(dir).filter((name2) => name2.endsWith(RECORD_EXTENSION)).map((name2) => readRecordFile(
|
|
3878
|
+
const records = recordNames(dir).filter((name2) => name2.endsWith(RECORD_EXTENSION)).map((name2) => readRecordFile(join5(dir, name2))).map((record) => Object.freeze({ ...record, state: readState(record, isAlive) }));
|
|
4501
3879
|
return Object.freeze(records.sort(byStart));
|
|
4502
3880
|
}
|
|
4503
3881
|
function readSession(root, sessionId, seams = {}) {
|
|
@@ -4570,7 +3948,7 @@ var SERVED_AGENTS_FILE = "agents.json";
|
|
|
4570
3948
|
var SERVED_PLUGIN_NAME = "rafa";
|
|
4571
3949
|
var SKILL_FILE2 = "SKILL.md";
|
|
4572
3950
|
var SERVED_SKILLS_PATH = {
|
|
4573
|
-
"add-dir":
|
|
3951
|
+
"add-dir": join6(".claude", "skills"),
|
|
4574
3952
|
"plugin-dir": "skills"
|
|
4575
3953
|
};
|
|
4576
3954
|
var SKILL_DELIVERY_FLAG = {
|
|
@@ -4581,7 +3959,7 @@ function servedDirectory(root, run) {
|
|
|
4581
3959
|
if (!isSessionId(run)) {
|
|
4582
3960
|
throw new Error(`served directory: unusable run id ${JSON.stringify(run)}`);
|
|
4583
3961
|
}
|
|
4584
|
-
return
|
|
3962
|
+
return join6(runsDir(root), run, SERVED_DIR);
|
|
4585
3963
|
}
|
|
4586
3964
|
function rafaWinners(resolution) {
|
|
4587
3965
|
return resolution.items.flatMap((item) => item.state === "served" && item.winner.source === "rafa" ? [item] : []);
|
|
@@ -4651,15 +4029,15 @@ function admit(row) {
|
|
|
4651
4029
|
return block.ok ? read : block;
|
|
4652
4030
|
}
|
|
4653
4031
|
function copySkill(row, skills) {
|
|
4654
|
-
const to =
|
|
4032
|
+
const to = join6(skills, row.name);
|
|
4655
4033
|
cpSync(dirname5(row.path), to, { recursive: true, dereference: true });
|
|
4656
4034
|
return { kind: "skill", name: row.name, from: row.path, to };
|
|
4657
4035
|
}
|
|
4658
4036
|
function writePluginManifest(dir) {
|
|
4659
|
-
const manifestDir =
|
|
4037
|
+
const manifestDir = join6(dir, ".claude-plugin");
|
|
4660
4038
|
mkdirSync2(manifestDir, { recursive: true });
|
|
4661
4039
|
const manifest = { name: SERVED_PLUGIN_NAME, description: "rafa-tier skills served to one loop session" };
|
|
4662
|
-
writeFileSync3(
|
|
4040
|
+
writeFileSync3(join6(manifestDir, "plugin.json"), `${JSON.stringify(manifest, null, 2)}
|
|
4663
4041
|
`);
|
|
4664
4042
|
}
|
|
4665
4043
|
function servedFlags(dir, delivery, skillCount, agents) {
|
|
@@ -4679,11 +4057,11 @@ function serveResolution(resolution, options) {
|
|
|
4679
4057
|
rmSync2(dir, { recursive: true, force: true });
|
|
4680
4058
|
mkdirSync2(dir, { recursive: true });
|
|
4681
4059
|
const judged = rafaWinners(resolution).map((item) => ({ row: item.winner, verdict: serveVerdict(item.winner) }));
|
|
4682
|
-
const skillsDir =
|
|
4060
|
+
const skillsDir = join6(dir, SERVED_SKILLS_PATH[delivery]);
|
|
4683
4061
|
const skills = judged.flatMap(({ row, verdict }) => verdict.ok && row.kind === "skill" ? [copySkill(row, skillsDir)] : []);
|
|
4684
4062
|
if (skills.length > 0 && delivery === "plugin-dir")
|
|
4685
4063
|
writePluginManifest(dir);
|
|
4686
|
-
const agentsFile =
|
|
4064
|
+
const agentsFile = join6(dir, SERVED_AGENTS_FILE);
|
|
4687
4065
|
const admitted = judged.flatMap(({ row, verdict }) => verdict.ok && verdict.value !== null ? [{ row, definition: verdict.value }] : []);
|
|
4688
4066
|
const agentMap = Object.fromEntries(admitted.map(({ row, definition }) => [row.name, definition]));
|
|
4689
4067
|
if (admitted.length > 0)
|
|
@@ -4720,12 +4098,12 @@ function isFileEntry(dir, entry) {
|
|
|
4720
4098
|
return true;
|
|
4721
4099
|
if (!entry.isSymbolicLink())
|
|
4722
4100
|
return false;
|
|
4723
|
-
return
|
|
4101
|
+
return statSync3(join7(dir, entry.name), { throwIfNoEntry: false })?.isFile() === true;
|
|
4724
4102
|
}
|
|
4725
4103
|
function markdownFiles(dir) {
|
|
4726
4104
|
let entries;
|
|
4727
4105
|
try {
|
|
4728
|
-
entries =
|
|
4106
|
+
entries = readdirSync3(dir, { withFileTypes: true });
|
|
4729
4107
|
} catch {
|
|
4730
4108
|
return [];
|
|
4731
4109
|
}
|
|
@@ -4741,7 +4119,7 @@ function definitionName(text) {
|
|
|
4741
4119
|
function readAgentDirectory(dir) {
|
|
4742
4120
|
const found = [];
|
|
4743
4121
|
for (const file of markdownFiles(dir)) {
|
|
4744
|
-
const path =
|
|
4122
|
+
const path = join7(dir, file);
|
|
4745
4123
|
const text = readText4(path);
|
|
4746
4124
|
if (text === null)
|
|
4747
4125
|
continue;
|
|
@@ -4883,7 +4261,7 @@ function skillCollisionLine(colliding) {
|
|
|
4883
4261
|
}
|
|
4884
4262
|
|
|
4885
4263
|
// src/commands/plan/plan-files.ts
|
|
4886
|
-
import { statSync as
|
|
4264
|
+
import { statSync as statSync4 } from "fs";
|
|
4887
4265
|
import { resolve as resolve3 } from "path";
|
|
4888
4266
|
function plansDirAt(root, planDir) {
|
|
4889
4267
|
return { label: planDir, path: resolve3(root, planDir) };
|
|
@@ -4933,10 +4311,10 @@ function stubOfPlanFile(name2) {
|
|
|
4933
4311
|
return stub !== undefined && isStampableStub(stub) ? stub : null;
|
|
4934
4312
|
}
|
|
4935
4313
|
function isFile(path) {
|
|
4936
|
-
return
|
|
4314
|
+
return statSync4(path, { throwIfNoEntry: false })?.isFile() ?? false;
|
|
4937
4315
|
}
|
|
4938
|
-
function issueLine(file,
|
|
4939
|
-
return `${file}:${
|
|
4316
|
+
function issueLine(file, issue3) {
|
|
4317
|
+
return `${file}:${issue3.line}: ${issue3.reason}: ${issue3.text}`;
|
|
4940
4318
|
}
|
|
4941
4319
|
function plural(count, noun) {
|
|
4942
4320
|
return count === 1 ? `${count} ${noun}` : `${count} ${noun}s`;
|
|
@@ -5053,8 +4431,8 @@ function createPlanValidateCommand(workingDirectory = () => process.cwd()) {
|
|
|
5053
4431
|
const { missingAgents, skillCollisions } = checkRoster(context, readFileSync8(file, "utf8"));
|
|
5054
4432
|
const result = { ...validation, missingAgents, skillCollisions };
|
|
5055
4433
|
if (issues.length > 0 || missingAgents.length > 0 || skillCollisions.length > 0) {
|
|
5056
|
-
for (const
|
|
5057
|
-
context.output.error(issueLine(typed,
|
|
4434
|
+
for (const issue3 of issues)
|
|
4435
|
+
context.output.error(issueLine(typed, issue3));
|
|
5058
4436
|
for (const agent of missingAgents)
|
|
5059
4437
|
context.output.error(`${typed}: ${missingAgentLine(agent)}`);
|
|
5060
4438
|
for (const skill of skillCollisions)
|
|
@@ -5073,12 +4451,12 @@ function createPlanValidateCommand(workingDirectory = () => process.cwd()) {
|
|
|
5073
4451
|
var validate_default = createPlanValidateCommand();
|
|
5074
4452
|
|
|
5075
4453
|
// src/commands/plan/list.ts
|
|
5076
|
-
import { existsSync as existsSync8, readdirSync as
|
|
5077
|
-
import { join as
|
|
4454
|
+
import { existsSync as existsSync8, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "fs";
|
|
4455
|
+
import { join as join8 } from "path";
|
|
5078
4456
|
var USAGE2 = "rafa plan list";
|
|
5079
4457
|
function listing(dir, stub) {
|
|
5080
|
-
const plan =
|
|
5081
|
-
const trackerPath =
|
|
4458
|
+
const plan = join8(dir, planFileName(stub, false));
|
|
4459
|
+
const trackerPath = join8(dir, planFileName(stub, true));
|
|
5082
4460
|
const tracker = isFile(trackerPath) ? trackerPath : null;
|
|
5083
4461
|
const planModel = parsePlan(readFileSync9(plan, "utf8"));
|
|
5084
4462
|
const counted = tracker === null ? planModel : parsePlan(readFileSync9(tracker, "utf8"));
|
|
@@ -5088,7 +4466,7 @@ function listPlans(plans) {
|
|
|
5088
4466
|
const dir = plans.path;
|
|
5089
4467
|
if (!existsSync8(dir))
|
|
5090
4468
|
return { dir, plans: [] };
|
|
5091
|
-
const stubs =
|
|
4469
|
+
const stubs = readdirSync4(dir).filter((name2) => isFile(join8(dir, name2))).map((name2) => stubOfPlanFile(name2)).filter((stub) => stub !== null).sort((a, b) => a.localeCompare(b));
|
|
5092
4470
|
return { dir, plans: stubs.map((stub) => listing(dir, stub)) };
|
|
5093
4471
|
}
|
|
5094
4472
|
function rowNotes(plan) {
|
|
@@ -5142,8 +4520,8 @@ var planListCommand = {
|
|
|
5142
4520
|
var list_default = Object.freeze(planListCommand);
|
|
5143
4521
|
|
|
5144
4522
|
// src/board/gate.ts
|
|
5145
|
-
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync10, renameSync as renameSync2, statSync as
|
|
5146
|
-
import { basename as basename5, dirname as dirname6, join as
|
|
4523
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync3, readFileSync as readFileSync10, renameSync as renameSync2, statSync as statSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
4524
|
+
import { basename as basename5, dirname as dirname6, join as join9, resolve as resolve5 } from "path";
|
|
5147
4525
|
|
|
5148
4526
|
// src/board/flags.ts
|
|
5149
4527
|
var SPEC_FLAG = "--spec";
|
|
@@ -5252,9 +4630,9 @@ function readLines2(body) {
|
|
|
5252
4630
|
let fenced = false;
|
|
5253
4631
|
raw.forEach((text, index) => {
|
|
5254
4632
|
const number = index + 1;
|
|
5255
|
-
const
|
|
5256
|
-
if (fenced || !carried &&
|
|
5257
|
-
fenced = !fenced || !
|
|
4633
|
+
const isFence = FENCE_LINE.test(text);
|
|
4634
|
+
if (fenced || !carried && isFence) {
|
|
4635
|
+
fenced = !fenced || !isFence;
|
|
5258
4636
|
lines.push(fencedLine(number, text));
|
|
5259
4637
|
return;
|
|
5260
4638
|
}
|
|
@@ -5367,13 +4745,13 @@ function findReadinessGaps(body) {
|
|
|
5367
4745
|
function hasSpecReadyLabel(labels) {
|
|
5368
4746
|
return labels.some((label) => label.trim().toLowerCase() === SPEC_READY_LABEL);
|
|
5369
4747
|
}
|
|
5370
|
-
function specReadyRefusalMessage(
|
|
5371
|
-
return `issue #${String(
|
|
4748
|
+
function specReadyRefusalMessage(issue3) {
|
|
4749
|
+
return `issue #${String(issue3)} is not marked ${SPEC_READY_LABEL}`;
|
|
5372
4750
|
}
|
|
5373
|
-
function requireSpecReadyLabel(
|
|
4751
|
+
function requireSpecReadyLabel(issue3, labels) {
|
|
5374
4752
|
if (hasSpecReadyLabel(labels))
|
|
5375
4753
|
return;
|
|
5376
|
-
throw new CommandExit(READINESS_REFUSAL_EXIT, specReadyRefusalMessage(
|
|
4754
|
+
throw new CommandExit(READINESS_REFUSAL_EXIT, specReadyRefusalMessage(issue3));
|
|
5377
4755
|
}
|
|
5378
4756
|
function describeGap(gap) {
|
|
5379
4757
|
return `"${gap.heading}" ${gap.what}`;
|
|
@@ -5569,11 +4947,11 @@ async function readTrustedSpecReviewComment(comments, trust) {
|
|
|
5569
4947
|
return { comment: null, ignored };
|
|
5570
4948
|
}
|
|
5571
4949
|
async function writeSpecReviewComment(options) {
|
|
5572
|
-
const { body, issue:
|
|
5573
|
-
const found = await readTrustedSpecReviewComment(await board.comments(
|
|
4950
|
+
const { body, issue: issue3, board, trust } = options;
|
|
4951
|
+
const found = await readTrustedSpecReviewComment(await board.comments(issue3), trust);
|
|
5574
4952
|
const { comment: existing, ignored } = found;
|
|
5575
4953
|
if (existing === null) {
|
|
5576
|
-
return { action: "posted", comment: await board.comment(
|
|
4954
|
+
return { action: "posted", comment: await board.comment(issue3, body), ignored };
|
|
5577
4955
|
}
|
|
5578
4956
|
return { action: "edited", comment: await board.editComment(existing.id, body), ignored };
|
|
5579
4957
|
}
|
|
@@ -5647,14 +5025,14 @@ function stampPlanField(plan, field3, value) {
|
|
|
5647
5025
|
note: `line ${String(at + 1)} of the plan now records ${line}`
|
|
5648
5026
|
};
|
|
5649
5027
|
}
|
|
5650
|
-
function issueFieldLine(
|
|
5651
|
-
if (!Number.isSafeInteger(
|
|
5652
|
-
throw new TypeError(`board plan field: issue ${String(
|
|
5028
|
+
function issueFieldLine(issue3) {
|
|
5029
|
+
if (!Number.isSafeInteger(issue3) || issue3 < 1) {
|
|
5030
|
+
throw new TypeError(`board plan field: issue ${String(issue3)} is no issue number, expected a positive whole one`);
|
|
5653
5031
|
}
|
|
5654
|
-
return `${ISSUE_FIELD}: ${JSON.stringify(String(
|
|
5032
|
+
return `${ISSUE_FIELD}: ${JSON.stringify(String(issue3))}`;
|
|
5655
5033
|
}
|
|
5656
|
-
function stampPlanIssue(plan,
|
|
5657
|
-
return stampPlanField(plan, ISSUE_FIELD, JSON.stringify(String(
|
|
5034
|
+
function stampPlanIssue(plan, issue3) {
|
|
5035
|
+
return stampPlanField(plan, ISSUE_FIELD, JSON.stringify(String(issue3)));
|
|
5658
5036
|
}
|
|
5659
5037
|
|
|
5660
5038
|
// src/board/review-stamp.ts
|
|
@@ -5701,14 +5079,14 @@ function readGateFlags(args) {
|
|
|
5701
5079
|
function describeGap2(gap) {
|
|
5702
5080
|
return ` \u2022 "${gap.heading}": ${gap.what}`;
|
|
5703
5081
|
}
|
|
5704
|
-
function remedyFor(
|
|
5705
|
-
return
|
|
5082
|
+
function remedyFor(issue3) {
|
|
5083
|
+
return issue3 === null ? " Close the gaps in the spec, then plan from it again." : ` Close the gaps, label the issue ${SPEC_READY_LABEL} again, and plan from it again.`;
|
|
5706
5084
|
}
|
|
5707
|
-
function specNotReadyMessage(source, reading, gaps,
|
|
5085
|
+
function specNotReadyMessage(source, reading, gaps, issue3 = null) {
|
|
5708
5086
|
return [
|
|
5709
5087
|
`\u274C ${source} is not ready to plan from: ${reading}`,
|
|
5710
5088
|
...gaps.map(describeGap2),
|
|
5711
|
-
remedyFor(
|
|
5089
|
+
remedyFor(issue3)
|
|
5712
5090
|
].join(`
|
|
5713
5091
|
`);
|
|
5714
5092
|
}
|
|
@@ -5728,14 +5106,14 @@ function unreadReviewMessage(source, reading, planIssues) {
|
|
|
5728
5106
|
}
|
|
5729
5107
|
function isWrittenFile(repoRoot, path) {
|
|
5730
5108
|
const full = resolve5(repoRoot, path);
|
|
5731
|
-
return existsSync9(full) &&
|
|
5109
|
+
return existsSync9(full) && statSync5(full).isFile();
|
|
5732
5110
|
}
|
|
5733
5111
|
var NOT_READY_REASON = "the planner judged the spec not ready, so no plan stands.";
|
|
5734
5112
|
var UNREAD_REASON = "no review came back and the plan does not read as written, so no plan stands.";
|
|
5735
5113
|
var REJECTED_DIR = "rejected";
|
|
5736
5114
|
function rejectedPath(path) {
|
|
5737
5115
|
const parent = dirname6(path);
|
|
5738
|
-
return parent === "." ?
|
|
5116
|
+
return parent === "." ? join9(REJECTED_DIR, basename5(path)) : join9(parent, REJECTED_DIR, basename5(path));
|
|
5739
5117
|
}
|
|
5740
5118
|
function moveWritten(options, output, reason) {
|
|
5741
5119
|
for (const path of [options.planPath, options.prerequisitesPath]) {
|
|
@@ -5758,7 +5136,7 @@ function readWrittenPlan(options) {
|
|
|
5758
5136
|
}
|
|
5759
5137
|
try {
|
|
5760
5138
|
const { issues } = validatePlan(resolve5(repoRoot, planPath));
|
|
5761
|
-
return { reads: issues.length === 0, issues: issues.map((
|
|
5139
|
+
return { reads: issues.length === 0, issues: issues.map((issue3) => issueLine(planPath, issue3)) };
|
|
5762
5140
|
} catch (error) {
|
|
5763
5141
|
return { reads: false, issues: [`${planPath}: ${messageOf(error)}`] };
|
|
5764
5142
|
}
|
|
@@ -5796,27 +5174,27 @@ function recordAssumptions(options, gaps, output) {
|
|
|
5796
5174
|
}
|
|
5797
5175
|
output.info(`\uD83E\uDDED ${planPath} opens under the review's assumptions and records ${REVIEW_ASSUMED_LINE}.`);
|
|
5798
5176
|
}
|
|
5799
|
-
async function publishGaps(
|
|
5177
|
+
async function publishGaps(issue3, gaps, body, output) {
|
|
5800
5178
|
try {
|
|
5801
5179
|
const write = await writeSpecReviewComment({
|
|
5802
|
-
issue:
|
|
5180
|
+
issue: issue3.number,
|
|
5803
5181
|
body: body(gaps),
|
|
5804
|
-
board:
|
|
5805
|
-
trust:
|
|
5182
|
+
board: issue3.board,
|
|
5183
|
+
trust: issue3.trust
|
|
5806
5184
|
});
|
|
5807
5185
|
for (const passed2 of write.ignored)
|
|
5808
5186
|
output.warn(passed2.reason);
|
|
5809
|
-
output.info(`\uD83D\uDCAC ${write.action === "posted" ? "Posted" : "Edited"} the review comment on issue #${String(
|
|
5187
|
+
output.info(`\uD83D\uDCAC ${write.action === "posted" ? "Posted" : "Edited"} the review comment on issue #${String(issue3.number)}.`);
|
|
5810
5188
|
} catch (error) {
|
|
5811
|
-
output.warn(`the review comment on issue #${String(
|
|
5189
|
+
output.warn(`the review comment on issue #${String(issue3.number)} was not written: ${messageOf(error)}`);
|
|
5812
5190
|
}
|
|
5813
5191
|
}
|
|
5814
|
-
async function swapLabels(
|
|
5192
|
+
async function swapLabels(issue3, output) {
|
|
5815
5193
|
try {
|
|
5816
|
-
await
|
|
5817
|
-
output.info(`\uD83C\uDFF7 Swapped ${SPEC_READY_LABEL} for ${SPEC_NEEDS_WORK_LABEL} on issue #${String(
|
|
5194
|
+
await issue3.board.swapLabels(issue3.number, SPEC_READY_LABEL, SPEC_NEEDS_WORK_LABEL);
|
|
5195
|
+
output.info(`\uD83C\uDFF7 Swapped ${SPEC_READY_LABEL} for ${SPEC_NEEDS_WORK_LABEL} on issue #${String(issue3.number)}.`);
|
|
5818
5196
|
} catch (error) {
|
|
5819
|
-
output.warn(`${SPEC_READY_LABEL} was not swapped for ${SPEC_NEEDS_WORK_LABEL} on issue` + ` #${String(
|
|
5197
|
+
output.warn(`${SPEC_READY_LABEL} was not swapped for ${SPEC_NEEDS_WORK_LABEL} on issue` + ` #${String(issue3.number)}: ${messageOf(error)}`);
|
|
5820
5198
|
}
|
|
5821
5199
|
}
|
|
5822
5200
|
function nothingBlocks(review) {
|
|
@@ -5825,18 +5203,18 @@ function nothingBlocks(review) {
|
|
|
5825
5203
|
async function planUnderAssumptions(options, review, output) {
|
|
5826
5204
|
output.warn(assumedReviewWarning(review.text, options.planPath));
|
|
5827
5205
|
recordAssumptions(options, review.gaps, output);
|
|
5828
|
-
const { issue:
|
|
5829
|
-
if (
|
|
5206
|
+
const { issue: issue3 } = options;
|
|
5207
|
+
if (issue3 !== null) {
|
|
5830
5208
|
if (options.comment) {
|
|
5831
|
-
await publishGaps(
|
|
5209
|
+
await publishGaps(issue3, review.gaps, assumedReviewCommentBody, output);
|
|
5832
5210
|
} else {
|
|
5833
|
-
output.info(`\uD83D\uDCAC ${NO_COMMENT_FLAG}: the gaps were not posted on issue #${String(
|
|
5211
|
+
output.info(`\uD83D\uDCAC ${NO_COMMENT_FLAG}: the gaps were not posted on issue #${String(issue3.number)}.`);
|
|
5834
5212
|
}
|
|
5835
5213
|
}
|
|
5836
5214
|
return "assumed";
|
|
5837
5215
|
}
|
|
5838
5216
|
async function enforceSpecReview(options) {
|
|
5839
|
-
const { review, issue:
|
|
5217
|
+
const { review, issue: issue3 } = options;
|
|
5840
5218
|
if (review === undefined)
|
|
5841
5219
|
return "unjudged";
|
|
5842
5220
|
if (review.ready)
|
|
@@ -5847,15 +5225,15 @@ async function enforceSpecReview(options) {
|
|
|
5847
5225
|
if (nothingBlocks(review))
|
|
5848
5226
|
return planUnderAssumptions(options, review, output);
|
|
5849
5227
|
moveWritten(options, output, NOT_READY_REASON);
|
|
5850
|
-
if (
|
|
5228
|
+
if (issue3 !== null) {
|
|
5851
5229
|
if (options.comment) {
|
|
5852
|
-
await publishGaps(
|
|
5230
|
+
await publishGaps(issue3, review.gaps, specReviewCommentBody, output);
|
|
5853
5231
|
} else {
|
|
5854
|
-
output.info(`\uD83D\uDCAC ${NO_COMMENT_FLAG}: the gaps were not posted on issue #${String(
|
|
5232
|
+
output.info(`\uD83D\uDCAC ${NO_COMMENT_FLAG}: the gaps were not posted on issue #${String(issue3.number)}.`);
|
|
5855
5233
|
}
|
|
5856
|
-
await swapLabels(
|
|
5234
|
+
await swapLabels(issue3, output);
|
|
5857
5235
|
}
|
|
5858
|
-
throw new CommandExit(SPEC_NOT_READY_EXIT, specNotReadyMessage(options.source, review.text, review.gaps,
|
|
5236
|
+
throw new CommandExit(SPEC_NOT_READY_EXIT, specNotReadyMessage(options.source, review.text, review.gaps, issue3));
|
|
5859
5237
|
}
|
|
5860
5238
|
|
|
5861
5239
|
// src/board/issue-board.ts
|
|
@@ -5930,8 +5308,8 @@ function createGhIssueBoard(options) {
|
|
|
5930
5308
|
}
|
|
5931
5309
|
const commentsPath = (number) => `repos/${REPO_PATH2}/issues/${number}/comments`;
|
|
5932
5310
|
const board = {
|
|
5933
|
-
comments: async (
|
|
5934
|
-
const path = commentsPath(issueNumber(
|
|
5311
|
+
comments: async (issue3) => {
|
|
5312
|
+
const path = commentsPath(issueNumber(issue3, "comments"));
|
|
5935
5313
|
const command = `gh api ${path}`;
|
|
5936
5314
|
const rows = parseJson(await succeed(["api", path], command), command);
|
|
5937
5315
|
if (!Array.isArray(rows)) {
|
|
@@ -5939,8 +5317,8 @@ function createGhIssueBoard(options) {
|
|
|
5939
5317
|
}
|
|
5940
5318
|
return rows.map((row, index) => readComment(row, command, `comment ${String(index)}`));
|
|
5941
5319
|
},
|
|
5942
|
-
comment: async (
|
|
5943
|
-
const path = commentsPath(issueNumber(
|
|
5320
|
+
comment: async (issue3, body) => {
|
|
5321
|
+
const path = commentsPath(issueNumber(issue3, "comment"));
|
|
5944
5322
|
const text = commentBody2(body, "comment");
|
|
5945
5323
|
const command = `gh api -X POST ${path}`;
|
|
5946
5324
|
const stdout = await succeed(["api", path, "-X", "POST", "-f", `body=${text}`], command);
|
|
@@ -5953,14 +5331,14 @@ function createGhIssueBoard(options) {
|
|
|
5953
5331
|
const stdout = await succeed(["api", path, "-X", "PATCH", "-f", `body=${text}`], command);
|
|
5954
5332
|
return readComment(parseJson(stdout, command), command, "the comment");
|
|
5955
5333
|
},
|
|
5956
|
-
swapLabels: async (
|
|
5957
|
-
const number = issueNumber(
|
|
5334
|
+
swapLabels: async (issue3, removed, added) => {
|
|
5335
|
+
const number = issueNumber(issue3, "swapLabels");
|
|
5958
5336
|
const off = labelName(removed, "swapLabels", "the removed label");
|
|
5959
5337
|
const on = labelName(added, "swapLabels", "the added label");
|
|
5960
5338
|
await succeed(["issue", "edit", number, "--remove-label", off, "--add-label", on], `gh issue edit ${number} --remove-label ${off} --add-label ${on}`);
|
|
5961
5339
|
},
|
|
5962
|
-
removeLabel: async (
|
|
5963
|
-
const number = issueNumber(
|
|
5340
|
+
removeLabel: async (issue3, label) => {
|
|
5341
|
+
const number = issueNumber(issue3, "removeLabel");
|
|
5964
5342
|
const off = labelName(label, "removeLabel", "the label");
|
|
5965
5343
|
await succeed(["issue", "edit", number, "--remove-label", off], `gh issue edit ${number} --remove-label ${off}`);
|
|
5966
5344
|
}
|
|
@@ -5969,11 +5347,11 @@ function createGhIssueBoard(options) {
|
|
|
5969
5347
|
}
|
|
5970
5348
|
|
|
5971
5349
|
// src/board/issue.ts
|
|
5972
|
-
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync11, statSync as
|
|
5350
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync5, readFileSync as readFileSync11, statSync as statSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
5973
5351
|
import { dirname as dirname7, resolve as resolve7 } from "path";
|
|
5974
5352
|
|
|
5975
5353
|
// src/refs/stamp.ts
|
|
5976
|
-
import { createHash
|
|
5354
|
+
import { createHash } from "crypto";
|
|
5977
5355
|
var PRESENT = Object.freeze({ kind: "present" });
|
|
5978
5356
|
var ABSENT = Object.freeze({ kind: "absent" });
|
|
5979
5357
|
var UNREADABLE = Object.freeze({ kind: "unreadable" });
|
|
@@ -6007,7 +5385,7 @@ function normaliseIssueText(text) {
|
|
|
6007
5385
|
`).trimEnd();
|
|
6008
5386
|
}
|
|
6009
5387
|
function sha256(text) {
|
|
6010
|
-
return
|
|
5388
|
+
return createHash("sha256").update(text, "utf8").digest("hex");
|
|
6011
5389
|
}
|
|
6012
5390
|
function sectionName(line) {
|
|
6013
5391
|
const text = line.trim().replace(/^##/u, "").replace(/[ \t]+#+$/u, "").trim();
|
|
@@ -6034,15 +5412,15 @@ function sections(body) {
|
|
|
6034
5412
|
}
|
|
6035
5413
|
return byName2;
|
|
6036
5414
|
}
|
|
6037
|
-
function issueFingerprint(
|
|
6038
|
-
const title = normaliseIssueText(
|
|
6039
|
-
const body = normaliseIssueText(
|
|
5415
|
+
function issueFingerprint(issue3) {
|
|
5416
|
+
const title = normaliseIssueText(issue3.title);
|
|
5417
|
+
const body = normaliseIssueText(issue3.body);
|
|
6040
5418
|
const headings = [...sections(body)].map(([name2, lines]) => Object.freeze({ name: name2, digest: sha256(lines.join(`
|
|
6041
5419
|
`)) }));
|
|
6042
5420
|
return Object.freeze({
|
|
6043
5421
|
kind: "issue",
|
|
6044
5422
|
digest: sha256(JSON.stringify([title, body])),
|
|
6045
|
-
state:
|
|
5423
|
+
state: issue3.state,
|
|
6046
5424
|
headings: Object.freeze(headings)
|
|
6047
5425
|
});
|
|
6048
5426
|
}
|
|
@@ -6264,11 +5642,11 @@ var STOP_WORDS = new Set([
|
|
|
6264
5642
|
var CLAUSE_BREAK = /[,;:([\u2013\u2014]|\s-\s/u;
|
|
6265
5643
|
var LEADING_ID = /^\s*#?rafa[-\s]?\d+\s*[-:.\u2013\u2014]?\s*/iu;
|
|
6266
5644
|
var NON_WORD = /[^a-z0-9]+/gu;
|
|
6267
|
-
function requireIssueNumber(
|
|
6268
|
-
if (!Number.isInteger(
|
|
6269
|
-
throw new RangeError(`An issue number must be a positive integer, not ${String(
|
|
5645
|
+
function requireIssueNumber(issue3) {
|
|
5646
|
+
if (!Number.isInteger(issue3) || issue3 <= 0) {
|
|
5647
|
+
throw new RangeError(`An issue number must be a positive integer, not ${String(issue3)}.`);
|
|
6270
5648
|
}
|
|
6271
|
-
return
|
|
5649
|
+
return issue3;
|
|
6272
5650
|
}
|
|
6273
5651
|
function wordsOf(title) {
|
|
6274
5652
|
return title.toLowerCase().replace(NON_WORD, " ").split(" ").filter((word) => word !== "");
|
|
@@ -6287,35 +5665,35 @@ function slugFromTitle(title) {
|
|
|
6287
5665
|
return FALLBACK_SLUG;
|
|
6288
5666
|
return chosen.slice(0, MAX_SLUG_WORDS).join("-");
|
|
6289
5667
|
}
|
|
6290
|
-
function boardId(
|
|
6291
|
-
return `${ID_PREFIX}-${requireIssueNumber(
|
|
5668
|
+
function boardId(issue3) {
|
|
5669
|
+
return `${ID_PREFIX}-${requireIssueNumber(issue3)}`;
|
|
6292
5670
|
}
|
|
6293
|
-
function planStub(
|
|
6294
|
-
return `${boardId(
|
|
5671
|
+
function planStub(issue3, title) {
|
|
5672
|
+
return `${boardId(issue3)}-${slugFromTitle(title)}`;
|
|
6295
5673
|
}
|
|
6296
|
-
function specFileName(
|
|
6297
|
-
return `${planStub(
|
|
5674
|
+
function specFileName(issue3, title) {
|
|
5675
|
+
return `${planStub(issue3, title)}${SPEC_EXTENSION}`;
|
|
6298
5676
|
}
|
|
6299
|
-
function specPath(specsDir,
|
|
6300
|
-
return path.join(specsDir, specFileName(
|
|
5677
|
+
function specPath(specsDir, issue3, title) {
|
|
5678
|
+
return path.join(specsDir, specFileName(issue3, title));
|
|
6301
5679
|
}
|
|
6302
|
-
function notesFileName(
|
|
6303
|
-
return `${boardId(
|
|
5680
|
+
function notesFileName(issue3) {
|
|
5681
|
+
return `${boardId(issue3)}${NOTES_SUFFIX}${SPEC_EXTENSION}`;
|
|
6304
5682
|
}
|
|
6305
|
-
function notesPath(specsDir,
|
|
6306
|
-
return path.join(specsDir, notesFileName(
|
|
5683
|
+
function notesPath(specsDir, issue3) {
|
|
5684
|
+
return path.join(specsDir, notesFileName(issue3));
|
|
6307
5685
|
}
|
|
6308
5686
|
|
|
6309
5687
|
// src/board/previous-copy.ts
|
|
6310
|
-
import { linkSync as linkSync2, mkdirSync as mkdirSync4, readdirSync as
|
|
6311
|
-
import { basename as basename6, extname, join as
|
|
5688
|
+
import { linkSync as linkSync2, mkdirSync as mkdirSync4, readdirSync as readdirSync5, statSync as statSync6, unlinkSync } from "fs";
|
|
5689
|
+
import { basename as basename6, extname, join as join10, resolve as resolve6 } from "path";
|
|
6312
5690
|
var PREFIX2 = "board previous copy";
|
|
6313
5691
|
var PREVIOUS_DIR_NAME = "previous";
|
|
6314
5692
|
var PREVIOUS_COPY_WARN_ABOVE = 50;
|
|
6315
5693
|
var COPY_EXTENSION = ".md";
|
|
6316
5694
|
var FIRST_SUFFIX = 2;
|
|
6317
5695
|
function previousDir(specsDir) {
|
|
6318
|
-
return
|
|
5696
|
+
return join10(specsDir, PREVIOUS_DIR_NAME);
|
|
6319
5697
|
}
|
|
6320
5698
|
function two(value) {
|
|
6321
5699
|
return String(value).padStart(2, "0");
|
|
@@ -6337,7 +5715,7 @@ function linkUnderFreeName(source, directory, savedName, timestamp) {
|
|
|
6337
5715
|
for (let attempt = 1;; attempt += 1) {
|
|
6338
5716
|
const name2 = previousCopyName(savedName, timestamp, attempt);
|
|
6339
5717
|
try {
|
|
6340
|
-
linkSync2(source,
|
|
5718
|
+
linkSync2(source, join10(directory, name2));
|
|
6341
5719
|
return name2;
|
|
6342
5720
|
} catch (error) {
|
|
6343
5721
|
if (!isTaken(error))
|
|
@@ -6353,15 +5731,15 @@ function movePreviousCopy(options) {
|
|
|
6353
5731
|
const savedName = basename6(path2);
|
|
6354
5732
|
let name2;
|
|
6355
5733
|
try {
|
|
6356
|
-
const timestamp = previousTimestamp(
|
|
5734
|
+
const timestamp = previousTimestamp(statSync6(source).mtime);
|
|
6357
5735
|
mkdirSync4(absoluteDirectory, { recursive: true });
|
|
6358
5736
|
name2 = linkUnderFreeName(source, absoluteDirectory, savedName, timestamp);
|
|
6359
5737
|
} catch (error) {
|
|
6360
5738
|
throw new Error(`${PREFIX2}: ${path2} could not be moved to ${directory}: ${messageOf(error)}`, { cause: error });
|
|
6361
5739
|
}
|
|
6362
5740
|
const moved = {
|
|
6363
|
-
path:
|
|
6364
|
-
absolute:
|
|
5741
|
+
path: join10(directory, name2),
|
|
5742
|
+
absolute: join10(absoluteDirectory, name2)
|
|
6365
5743
|
};
|
|
6366
5744
|
try {
|
|
6367
5745
|
unlinkSync(source);
|
|
@@ -6376,7 +5754,7 @@ function isMissing(error) {
|
|
|
6376
5754
|
function countPreviousCopies(repoRoot, specsDir) {
|
|
6377
5755
|
const directory = previousDir(specsDir);
|
|
6378
5756
|
try {
|
|
6379
|
-
return
|
|
5757
|
+
return readdirSync5(resolve6(repoRoot, directory), { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.endsWith(COPY_EXTENSION)).length;
|
|
6380
5758
|
} catch (error) {
|
|
6381
5759
|
if (isMissing(error))
|
|
6382
5760
|
return 0;
|
|
@@ -6448,30 +5826,30 @@ function readViewedIssue(stdout, number, command) {
|
|
|
6448
5826
|
}
|
|
6449
5827
|
function createGhSpecIssueReader(options) {
|
|
6450
5828
|
const { gh } = options;
|
|
6451
|
-
return async (
|
|
6452
|
-
const number = issueNumber2(
|
|
5829
|
+
return async (issue3) => {
|
|
5830
|
+
const number = issueNumber2(issue3);
|
|
6453
5831
|
const command = `gh issue view ${number} --json ${ISSUE_VIEW_FIELDS}`;
|
|
6454
5832
|
const result = await gh(["issue", "view", number, "--json", ISSUE_VIEW_FIELDS]);
|
|
6455
5833
|
if (!result.ok)
|
|
6456
5834
|
throw new Error(`${PREFIX3}: ${detailOf3(result, command)}`);
|
|
6457
|
-
return readViewedIssue(result.stdout,
|
|
5835
|
+
return readViewedIssue(result.stdout, issue3, command);
|
|
6458
5836
|
};
|
|
6459
5837
|
}
|
|
6460
5838
|
function hasSpecLabel(labels) {
|
|
6461
5839
|
return labels.some((label) => label.trim().toLowerCase() === SPEC_LABEL);
|
|
6462
5840
|
}
|
|
6463
|
-
function closedIssueMessage(
|
|
6464
|
-
return `issue #${String(
|
|
5841
|
+
function closedIssueMessage(issue3) {
|
|
5842
|
+
return `issue #${String(issue3)} is closed, and a plan is written from an issue that is open;` + " reopen it, or plan from the issue that took its place";
|
|
6465
5843
|
}
|
|
6466
|
-
function missingSpecLabelMessage(
|
|
6467
|
-
return `issue #${String(
|
|
5844
|
+
function missingSpecLabelMessage(issue3) {
|
|
5845
|
+
return `issue #${String(issue3)} is not labelled ${SPEC_LABEL}, so it is not a spec;` + ` label it ${SPEC_LABEL}, or plan from the spec issue instead`;
|
|
6468
5846
|
}
|
|
6469
|
-
function requireSpecIssue(
|
|
6470
|
-
if (
|
|
6471
|
-
throw new CommandExit(ISSUE_REFUSAL_EXIT, closedIssueMessage(
|
|
5847
|
+
function requireSpecIssue(issue3) {
|
|
5848
|
+
if (issue3.state === "CLOSED") {
|
|
5849
|
+
throw new CommandExit(ISSUE_REFUSAL_EXIT, closedIssueMessage(issue3.number));
|
|
6472
5850
|
}
|
|
6473
|
-
if (!hasSpecLabel(
|
|
6474
|
-
throw new CommandExit(ISSUE_REFUSAL_EXIT, missingSpecLabelMessage(
|
|
5851
|
+
if (!hasSpecLabel(issue3.labels)) {
|
|
5852
|
+
throw new CommandExit(ISSUE_REFUSAL_EXIT, missingSpecLabelMessage(issue3.number));
|
|
6475
5853
|
}
|
|
6476
5854
|
}
|
|
6477
5855
|
function normalise(text) {
|
|
@@ -6576,7 +5954,7 @@ function readSnapshotChange(options) {
|
|
|
6576
5954
|
function readIfFile(file, what) {
|
|
6577
5955
|
if (!existsSync10(file))
|
|
6578
5956
|
return null;
|
|
6579
|
-
if (!
|
|
5957
|
+
if (!statSync7(file).isFile()) {
|
|
6580
5958
|
throw new CommandExit(ISSUE_REFUSAL_EXIT, `${what} ${file} is not a file, and a spec is one`);
|
|
6581
5959
|
}
|
|
6582
5960
|
try {
|
|
@@ -6585,26 +5963,26 @@ function readIfFile(file, what) {
|
|
|
6585
5963
|
throw new CommandExit(ISSUE_REFUSAL_EXIT, `${what} ${file} could not be read: ${messageOf(error)}`);
|
|
6586
5964
|
}
|
|
6587
5965
|
}
|
|
6588
|
-
function readLocalNotes(repoRoot, specsDir,
|
|
6589
|
-
return readIfFile(resolve7(repoRoot, notesPath(specsDir,
|
|
5966
|
+
function readLocalNotes(repoRoot, specsDir, issue3) {
|
|
5967
|
+
return readIfFile(resolve7(repoRoot, notesPath(specsDir, issue3)), "the local notes");
|
|
6590
5968
|
}
|
|
6591
|
-
function snapshotDiffersMessage(path2,
|
|
6592
|
-
return `${path2} is a snapshot of issue #${String(
|
|
5969
|
+
function snapshotDiffersMessage(path2, issue3) {
|
|
5970
|
+
return `${path2} is a snapshot of issue #${String(issue3)} that no longer matches it:` + " the issue body, or the local notes appended to it, changed since the snapshot was written;" + ` pass ${REFRESH_FLAG} to plan from the issue as it reads now, or --spec=${path2} to plan from` + " the snapshot as it stands";
|
|
6593
5971
|
}
|
|
6594
|
-
function notesCollisionMessage(path2,
|
|
6595
|
-
return `the snapshot of issue #${String(
|
|
5972
|
+
function notesCollisionMessage(path2, issue3) {
|
|
5973
|
+
return `the snapshot of issue #${String(issue3)} and its local notes are both ${path2},` + " so writing the snapshot would overwrite the notes; retitle the issue";
|
|
6596
5974
|
}
|
|
6597
5975
|
function writeSpecSnapshot(options) {
|
|
6598
|
-
const { repoRoot, specsDir, issue:
|
|
6599
|
-
const path2 = specPath(specsDir,
|
|
6600
|
-
const notes = notesPath(specsDir,
|
|
5976
|
+
const { repoRoot, specsDir, issue: issue3, refresh } = options;
|
|
5977
|
+
const path2 = specPath(specsDir, issue3.number, issue3.title);
|
|
5978
|
+
const notes = notesPath(specsDir, issue3.number);
|
|
6601
5979
|
if (path2 === notes) {
|
|
6602
|
-
throw new CommandExit(ISSUE_REFUSAL_EXIT, notesCollisionMessage(path2,
|
|
5980
|
+
throw new CommandExit(ISSUE_REFUSAL_EXIT, notesCollisionMessage(path2, issue3.number));
|
|
6603
5981
|
}
|
|
6604
|
-
const local = readLocalNotes(repoRoot, specsDir,
|
|
5982
|
+
const local = readLocalNotes(repoRoot, specsDir, issue3.number);
|
|
6605
5983
|
const absolute = resolve7(repoRoot, path2);
|
|
6606
5984
|
const existing = readIfFile(absolute, "the spec snapshot");
|
|
6607
|
-
const text = carryRefsBlock(existing, snapshotText(
|
|
5985
|
+
const text = carryRefsBlock(existing, snapshotText(issue3.body, local));
|
|
6608
5986
|
const written = {
|
|
6609
5987
|
path: path2,
|
|
6610
5988
|
absolute,
|
|
@@ -6613,11 +5991,11 @@ function writeSpecSnapshot(options) {
|
|
|
6613
5991
|
previous: null,
|
|
6614
5992
|
text
|
|
6615
5993
|
};
|
|
6616
|
-
const action = rewriteAction(existing,
|
|
5994
|
+
const action = rewriteAction(existing, issue3.body, local, notes, refresh);
|
|
6617
5995
|
if (action === "unchanged")
|
|
6618
5996
|
return { ...written, action, text: existing ?? text };
|
|
6619
5997
|
if (action === null) {
|
|
6620
|
-
throw new CommandExit(ISSUE_REFUSAL_EXIT, snapshotDiffersMessage(path2,
|
|
5998
|
+
throw new CommandExit(ISSUE_REFUSAL_EXIT, snapshotDiffersMessage(path2, issue3.number));
|
|
6621
5999
|
}
|
|
6622
6000
|
const previous = action === "created" ? null : movePreviousCopy({ repoRoot, specsDir, path: path2 }).path;
|
|
6623
6001
|
try {
|
|
@@ -6640,10 +6018,10 @@ function rewriteAction(existing, body, notes, notesFile, refresh) {
|
|
|
6640
6018
|
}
|
|
6641
6019
|
|
|
6642
6020
|
// src/pr/git.ts
|
|
6643
|
-
import { spawnSync
|
|
6021
|
+
import { spawnSync } from "child_process";
|
|
6644
6022
|
function createGitRunner(cwd) {
|
|
6645
6023
|
return (args) => {
|
|
6646
|
-
const result =
|
|
6024
|
+
const result = spawnSync("git", [...args], {
|
|
6647
6025
|
cwd,
|
|
6648
6026
|
encoding: "utf8",
|
|
6649
6027
|
env: { ...process.env, LC_ALL: "C" }
|
|
@@ -6787,11 +6165,11 @@ function parseRoadmapBody(body) {
|
|
|
6787
6165
|
if (found === null)
|
|
6788
6166
|
return;
|
|
6789
6167
|
const [, box = "", digits = "", rest = ""] = found;
|
|
6790
|
-
const
|
|
6791
|
-
if (!Number.isSafeInteger(
|
|
6168
|
+
const issue3 = Number.parseInt(digits, 10);
|
|
6169
|
+
if (!Number.isSafeInteger(issue3) || issue3 < 1)
|
|
6792
6170
|
return;
|
|
6793
6171
|
lines = [...lines, {
|
|
6794
|
-
issue:
|
|
6172
|
+
issue: issue3,
|
|
6795
6173
|
ticked: box.trim() !== "",
|
|
6796
6174
|
why: whyOf(rest),
|
|
6797
6175
|
lineNumber: index + 1
|
|
@@ -6856,11 +6234,11 @@ async function resolveRoadmapIssue(options) {
|
|
|
6856
6234
|
if (configured !== null)
|
|
6857
6235
|
return configured;
|
|
6858
6236
|
const wanted = ROADMAP_TITLE.toLowerCase();
|
|
6859
|
-
const titled = (await search()).filter((
|
|
6237
|
+
const titled = (await search()).filter((issue3) => issue3.title.trim().toLowerCase() === wanted);
|
|
6860
6238
|
if (titled.length === 0)
|
|
6861
6239
|
throw new CommandExit(ROADMAP_REFUSAL_EXIT, noRoadmapMessage());
|
|
6862
6240
|
if (titled.length > 1) {
|
|
6863
|
-
throw new CommandExit(ROADMAP_REFUSAL_EXIT, severalRoadmapsMessage(titled.map((
|
|
6241
|
+
throw new CommandExit(ROADMAP_REFUSAL_EXIT, severalRoadmapsMessage(titled.map((issue3) => issue3.number)));
|
|
6864
6242
|
}
|
|
6865
6243
|
return titled[0]?.number ?? 0;
|
|
6866
6244
|
}
|
|
@@ -6881,10 +6259,10 @@ function createGhOpenPullRequests(options) {
|
|
|
6881
6259
|
};
|
|
6882
6260
|
}
|
|
6883
6261
|
function closedIssuesIn(body) {
|
|
6884
|
-
return Object.freeze([...body.matchAll(CLOSING)].map((found) => Number.parseInt(found[1] ?? "", 10)).filter((
|
|
6262
|
+
return Object.freeze([...body.matchAll(CLOSING)].map((found) => Number.parseInt(found[1] ?? "", 10)).filter((issue3) => Number.isSafeInteger(issue3) && issue3 > 0));
|
|
6885
6263
|
}
|
|
6886
|
-
function branchClaims(ref,
|
|
6887
|
-
const claim = `${BRANCH_PREFIX}/${boardId(
|
|
6264
|
+
function branchClaims(ref, issue3) {
|
|
6265
|
+
const claim = `${BRANCH_PREFIX}/${boardId(issue3)}`;
|
|
6888
6266
|
return new RegExp(`(?:^|/)${claim}(?:-|$)`, "u").test(ref);
|
|
6889
6267
|
}
|
|
6890
6268
|
function outputLines(stdout) {
|
|
@@ -6909,11 +6287,11 @@ function createRoadmapReadings(options) {
|
|
|
6909
6287
|
const { issues, branches, pullRequests } = options;
|
|
6910
6288
|
let open = null;
|
|
6911
6289
|
return Object.freeze({
|
|
6912
|
-
isClosed: async (
|
|
6913
|
-
branchFor: (
|
|
6914
|
-
pullRequestFor: async (
|
|
6290
|
+
isClosed: async (issue3) => (await issues(issue3)).state === "CLOSED",
|
|
6291
|
+
branchFor: (issue3) => branches.refs.find((ref) => branchClaims(ref, issue3)) ?? null,
|
|
6292
|
+
pullRequestFor: async (issue3) => {
|
|
6915
6293
|
open = open ?? pullRequests();
|
|
6916
|
-
const closing = (await open).find((pull) => closedIssuesIn(pull.body).includes(
|
|
6294
|
+
const closing = (await open).find((pull) => closedIssuesIn(pull.body).includes(issue3));
|
|
6917
6295
|
return closing?.number ?? null;
|
|
6918
6296
|
}
|
|
6919
6297
|
});
|
|
@@ -7000,10 +6378,10 @@ function foreignIn(text) {
|
|
|
7000
6378
|
return found;
|
|
7001
6379
|
}
|
|
7002
6380
|
var NAMED_NOTHING = { ids: [], foreign: [] };
|
|
7003
|
-
function reading(kind,
|
|
6381
|
+
function reading(kind, issue3, found, named, unknown) {
|
|
7004
6382
|
return {
|
|
7005
6383
|
kind,
|
|
7006
|
-
issue:
|
|
6384
|
+
issue: issue3,
|
|
7007
6385
|
line: found?.number ?? null,
|
|
7008
6386
|
text: found?.text ?? null,
|
|
7009
6387
|
blockers: named.ids,
|
|
@@ -7011,21 +6389,21 @@ function reading(kind, issue4, found, named, unknown) {
|
|
|
7011
6389
|
unknown
|
|
7012
6390
|
};
|
|
7013
6391
|
}
|
|
7014
|
-
function readBlockedBy(
|
|
6392
|
+
function readBlockedBy(issue3, body, known) {
|
|
7015
6393
|
const found = findFieldLine(body);
|
|
7016
6394
|
if (found === null)
|
|
7017
|
-
return reading("no-line",
|
|
6395
|
+
return reading("no-line", issue3, null, NAMED_NOTHING, []);
|
|
7018
6396
|
const foreign = foreignIn(found.text);
|
|
7019
6397
|
const ids = idsIn(found.text.replace(FOREIGN_ID, " "));
|
|
7020
6398
|
const named = { ids, foreign };
|
|
7021
6399
|
if (ids.length === 0)
|
|
7022
|
-
return reading("no-ids",
|
|
7023
|
-
const unknown = known === undefined ? [] : ids.filter((id) => id !==
|
|
7024
|
-
if (ids.includes(
|
|
7025
|
-
return reading("self-reference",
|
|
6400
|
+
return reading("no-ids", issue3, found, named, []);
|
|
6401
|
+
const unknown = known === undefined ? [] : ids.filter((id) => id !== issue3 && !known.has(id));
|
|
6402
|
+
if (ids.includes(issue3))
|
|
6403
|
+
return reading("self-reference", issue3, found, named, unknown);
|
|
7026
6404
|
if (unknown.length > 0)
|
|
7027
|
-
return reading("unknown-issue",
|
|
7028
|
-
return reading("blocked",
|
|
6405
|
+
return reading("unknown-issue", issue3, found, named, unknown);
|
|
6406
|
+
return reading("blocked", issue3, found, named, unknown);
|
|
7029
6407
|
}
|
|
7030
6408
|
function nameIds(ids) {
|
|
7031
6409
|
return ids.map((id) => `#${String(id)}`).join(" ");
|
|
@@ -7055,17 +6433,17 @@ function blockedFaultMessage(read) {
|
|
|
7055
6433
|
|
|
7056
6434
|
// src/board/blocked-line.ts
|
|
7057
6435
|
function blockerStatesOf(issues) {
|
|
7058
|
-
return async (
|
|
6436
|
+
return async (issue3) => {
|
|
7059
6437
|
try {
|
|
7060
|
-
return (await issues(
|
|
6438
|
+
return (await issues(issue3)).state;
|
|
7061
6439
|
} catch {
|
|
7062
6440
|
return null;
|
|
7063
6441
|
}
|
|
7064
6442
|
};
|
|
7065
6443
|
}
|
|
7066
|
-
function blocked(
|
|
6444
|
+
function blocked(issue3, blockers, open, unread, fault) {
|
|
7067
6445
|
return Object.freeze({
|
|
7068
|
-
issue:
|
|
6446
|
+
issue: issue3,
|
|
7069
6447
|
blockers: Object.freeze([...blockers]),
|
|
7070
6448
|
open: Object.freeze([...open]),
|
|
7071
6449
|
unread: Object.freeze([...unread]),
|
|
@@ -7079,17 +6457,17 @@ async function heldStates(blockers, states) {
|
|
|
7079
6457
|
}
|
|
7080
6458
|
return held;
|
|
7081
6459
|
}
|
|
7082
|
-
async function readBlockedLine(
|
|
7083
|
-
if (!hasSpecBlockedLabel(
|
|
6460
|
+
async function readBlockedLine(issue3, states) {
|
|
6461
|
+
if (!hasSpecBlockedLabel(issue3.labels))
|
|
7084
6462
|
return null;
|
|
7085
|
-
const read = readBlockedBy(
|
|
6463
|
+
const read = readBlockedBy(issue3.number, issue3.body);
|
|
7086
6464
|
if (read.kind !== "blocked") {
|
|
7087
|
-
return blocked(
|
|
6465
|
+
return blocked(issue3.number, read.blockers, [], [], blockedFaultMessage(read));
|
|
7088
6466
|
}
|
|
7089
6467
|
const held = await heldStates(read.blockers, states);
|
|
7090
6468
|
const open = held.filter((one) => one.state === "OPEN").map((one) => one.id);
|
|
7091
6469
|
const unread = held.filter((one) => one.state === null).map((one) => one.id);
|
|
7092
|
-
return open.length + unread.length === 0 ? null : blocked(
|
|
6470
|
+
return open.length + unread.length === 0 ? null : blocked(issue3.number, read.blockers, open, unread, null);
|
|
7093
6471
|
}
|
|
7094
6472
|
function nameBlockers(line) {
|
|
7095
6473
|
return [
|
|
@@ -7105,12 +6483,12 @@ function plannableReadings(options) {
|
|
|
7105
6483
|
const states = blockerStatesOf(issues);
|
|
7106
6484
|
return Object.freeze({
|
|
7107
6485
|
skip: (line) => readRoadmapSkip(line, readings),
|
|
7108
|
-
blocking: async (
|
|
7109
|
-
isReady: async (
|
|
6486
|
+
blocking: async (issue3) => readBlockedLine(await issues(issue3), states),
|
|
6487
|
+
isReady: async (issue3) => hasSpecReadyLabel((await issues(issue3)).labels)
|
|
7110
6488
|
});
|
|
7111
6489
|
}
|
|
7112
|
-
function notReadySentence(
|
|
7113
|
-
return `#${String(
|
|
6490
|
+
function notReadySentence(issue3) {
|
|
6491
|
+
return `#${String(issue3)} not ready: it carries no ${SPEC_READY_LABEL} label`;
|
|
7114
6492
|
}
|
|
7115
6493
|
async function passOf(line, readings) {
|
|
7116
6494
|
const skip = await readings.skip(line);
|
|
@@ -7132,40 +6510,40 @@ async function pickPlannableLine(lines, after, readings) {
|
|
|
7132
6510
|
}
|
|
7133
6511
|
return Object.freeze({ line: null, passed: Object.freeze(passed2) });
|
|
7134
6512
|
}
|
|
7135
|
-
function alternativeQuestion(
|
|
7136
|
-
return `Plan #${String(
|
|
6513
|
+
function alternativeQuestion(issue3) {
|
|
6514
|
+
return `Plan #${String(issue3)} instead? [y/N] `;
|
|
7137
6515
|
}
|
|
7138
|
-
function noAlternativeMessage(
|
|
7139
|
-
const number = String(
|
|
6516
|
+
function noAlternativeMessage(issue3) {
|
|
6517
|
+
const number = String(issue3);
|
|
7140
6518
|
return `no line under #${number} is ready, not blocked and not taken, so there is nothing to plan instead;` + ` take ${SPEC_BLOCKED_LABEL} off #${number} with rafa issue unblock ${number} once its blockers close`;
|
|
7141
6519
|
}
|
|
7142
|
-
function unaskedMessage(
|
|
6520
|
+
function unaskedMessage(issue3, alternative) {
|
|
7143
6521
|
const number = String(alternative);
|
|
7144
|
-
return `#${number} takes a yes and this run has nobody to ask, so nothing was planned;` + ` plan it with rafa plan create --issue=${number},` + ` or clear #${String(
|
|
6522
|
+
return `#${number} takes a yes and this run has nobody to ask, so nothing was planned;` + ` plan it with rafa plan create --issue=${number},` + ` or clear #${String(issue3)} with rafa issue unblock ${String(issue3)}`;
|
|
7145
6523
|
}
|
|
7146
6524
|
function declinedMessage(alternative) {
|
|
7147
6525
|
return `#${String(alternative)} was not planned, and nothing was written`;
|
|
7148
6526
|
}
|
|
7149
6527
|
|
|
7150
6528
|
// src/board/snapshot-settle.ts
|
|
7151
|
-
import { existsSync as existsSync11, readFileSync as readFileSync12, statSync as
|
|
6529
|
+
import { existsSync as existsSync11, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
|
|
7152
6530
|
import { resolve as resolve8 } from "path";
|
|
7153
6531
|
var PREFIX5 = "board snapshot settle";
|
|
7154
6532
|
var DATE_LENGTH = 10;
|
|
7155
6533
|
function savedCopyDate(savedAt) {
|
|
7156
6534
|
return savedAt.toISOString().slice(0, DATE_LENGTH);
|
|
7157
6535
|
}
|
|
7158
|
-
function refreshQuestion(
|
|
7159
|
-
return `Issue #${String(
|
|
6536
|
+
function refreshQuestion(issue3, savedAt) {
|
|
6537
|
+
return `Issue #${String(issue3)} changed since the saved copy of ${savedCopyDate(savedAt)}.` + " Plan from it as it reads now? [y/N] ";
|
|
7160
6538
|
}
|
|
7161
|
-
function notesRebuiltLine(path2,
|
|
7162
|
-
return `local notes changed; rebuilt ${path2} from issue #${String(
|
|
6539
|
+
function notesRebuiltLine(path2, issue3, previous) {
|
|
6540
|
+
return `local notes changed; rebuilt ${path2} from issue #${String(issue3)} and the notes,` + ` the old copy is at ${previous}`;
|
|
7163
6541
|
}
|
|
7164
6542
|
function readSavedCopy(absolute, path2) {
|
|
7165
6543
|
if (!existsSync11(absolute))
|
|
7166
6544
|
return null;
|
|
7167
6545
|
try {
|
|
7168
|
-
const stat =
|
|
6546
|
+
const stat = statSync8(absolute);
|
|
7169
6547
|
if (!stat.isFile())
|
|
7170
6548
|
return null;
|
|
7171
6549
|
return { text: readFileSync12(absolute, "utf8"), savedAt: stat.mtime };
|
|
@@ -7180,10 +6558,10 @@ function printChange(change, output) {
|
|
|
7180
6558
|
}
|
|
7181
6559
|
}
|
|
7182
6560
|
async function settleSpecSnapshot(options) {
|
|
7183
|
-
const { repoRoot, specsDir, issue:
|
|
7184
|
-
const write = (rewrite) => writeSpecSnapshot({ repoRoot, specsDir, issue:
|
|
7185
|
-
const path2 = specPath(specsDir,
|
|
7186
|
-
const notes = notesPath(specsDir,
|
|
6561
|
+
const { repoRoot, specsDir, issue: issue3, refresh, output } = options;
|
|
6562
|
+
const write = (rewrite) => writeSpecSnapshot({ repoRoot, specsDir, issue: issue3, refresh: rewrite });
|
|
6563
|
+
const path2 = specPath(specsDir, issue3.number, issue3.title);
|
|
6564
|
+
const notes = notesPath(specsDir, issue3.number);
|
|
7187
6565
|
if (path2 === notes)
|
|
7188
6566
|
return write(refresh);
|
|
7189
6567
|
const saved = readSavedCopy(resolve8(repoRoot, path2), path2);
|
|
@@ -7191,8 +6569,8 @@ async function settleSpecSnapshot(options) {
|
|
|
7191
6569
|
return write(refresh);
|
|
7192
6570
|
const change = readSnapshotChange({
|
|
7193
6571
|
saved: saved.text,
|
|
7194
|
-
body:
|
|
7195
|
-
notes: readLocalNotes(repoRoot, specsDir,
|
|
6572
|
+
body: issue3.body,
|
|
6573
|
+
notes: readLocalNotes(repoRoot, specsDir, issue3.number),
|
|
7196
6574
|
notesPath: notes
|
|
7197
6575
|
});
|
|
7198
6576
|
if (change.kind === "unchanged")
|
|
@@ -7201,15 +6579,15 @@ async function settleSpecSnapshot(options) {
|
|
|
7201
6579
|
if (change.kind === "notes") {
|
|
7202
6580
|
const rebuilt = write(refresh);
|
|
7203
6581
|
if (rebuilt.previous !== null)
|
|
7204
|
-
output.info(notesRebuiltLine(rebuilt.path,
|
|
6582
|
+
output.info(notesRebuiltLine(rebuilt.path, issue3.number, rebuilt.previous));
|
|
7205
6583
|
return rebuilt;
|
|
7206
6584
|
}
|
|
7207
6585
|
if (refresh)
|
|
7208
6586
|
return write(true);
|
|
7209
6587
|
const offer = options.offerRefresh ?? null;
|
|
7210
|
-
const accepted = offer !== null && await offer({ issue:
|
|
6588
|
+
const accepted = offer !== null && await offer({ issue: issue3.number, path: path2, savedAt: saved.savedAt });
|
|
7211
6589
|
if (!accepted)
|
|
7212
|
-
throw new CommandExit(ISSUE_REFUSAL_EXIT, snapshotDiffersMessage(path2,
|
|
6590
|
+
throw new CommandExit(ISSUE_REFUSAL_EXIT, snapshotDiffersMessage(path2, issue3.number));
|
|
7213
6591
|
return write(true);
|
|
7214
6592
|
}
|
|
7215
6593
|
|
|
@@ -7238,11 +6616,11 @@ function readFlagWord(args, flag) {
|
|
|
7238
6616
|
}
|
|
7239
6617
|
function issueNumberOf(word, flag) {
|
|
7240
6618
|
const trimmed = word.trim();
|
|
7241
|
-
const
|
|
7242
|
-
if (!/^\d+$/u.test(trimmed) || !Number.isSafeInteger(
|
|
6619
|
+
const issue3 = Number.parseInt(trimmed, 10);
|
|
6620
|
+
if (!/^\d+$/u.test(trimmed) || !Number.isSafeInteger(issue3) || issue3 < 1) {
|
|
7243
6621
|
throw new CommandExit(SOURCE_REFUSAL_EXIT, notAnIssueMessage(flag, word));
|
|
7244
6622
|
}
|
|
7245
|
-
return
|
|
6623
|
+
return issue3;
|
|
7246
6624
|
}
|
|
7247
6625
|
function requestOf(flag, word) {
|
|
7248
6626
|
if (flag === NEXT_FLAG) {
|
|
@@ -7292,8 +6670,8 @@ function alternativeLine(line) {
|
|
|
7292
6670
|
const id = `\u25B6 Ready instead: issue #${String(line.issue)}`;
|
|
7293
6671
|
return line.why === "" ? id : `${id} \u2014 ${line.why}`;
|
|
7294
6672
|
}
|
|
7295
|
-
function describeIssue(
|
|
7296
|
-
return `issue #${String(
|
|
6673
|
+
function describeIssue(issue3) {
|
|
6674
|
+
return `issue #${String(issue3.number)} "${issue3.title}"`;
|
|
7297
6675
|
}
|
|
7298
6676
|
function dryRunLine(what) {
|
|
7299
6677
|
return `\uD83D\uDD0E ${DRY_RUN_FLAG}: would plan from ${what}. Nothing was written.`;
|
|
@@ -7303,9 +6681,9 @@ function stopped(reason) {
|
|
|
7303
6681
|
}
|
|
7304
6682
|
function memoiseIssues(issues) {
|
|
7305
6683
|
const read = new Map;
|
|
7306
|
-
return (
|
|
7307
|
-
const taken = read.get(
|
|
7308
|
-
read.set(
|
|
6684
|
+
return (issue3) => {
|
|
6685
|
+
const taken = read.get(issue3) ?? issues(issue3);
|
|
6686
|
+
read.set(issue3, taken);
|
|
7309
6687
|
return taken;
|
|
7310
6688
|
};
|
|
7311
6689
|
}
|
|
@@ -7429,8 +6807,8 @@ async function resolveSpecSource(options) {
|
|
|
7429
6807
|
|
|
7430
6808
|
// src/board/plan-spec.ts
|
|
7431
6809
|
var BOARD_REFUSAL_EXIT = 2;
|
|
7432
|
-
function issueSource(
|
|
7433
|
-
return `issue #${String(
|
|
6810
|
+
function issueSource(issue3) {
|
|
6811
|
+
return `issue #${String(issue3)}`;
|
|
7434
6812
|
}
|
|
7435
6813
|
var UNNAMED_REPO = "this repository";
|
|
7436
6814
|
function boardRepoLabel(git) {
|
|
@@ -7438,25 +6816,25 @@ function boardRepoLabel(git) {
|
|
|
7438
6816
|
const normalized = result.ok ? normalizeRemote(result.stdout.trim()) : "";
|
|
7439
6817
|
return normalized === "" ? UNNAMED_REPO : normalized;
|
|
7440
6818
|
}
|
|
7441
|
-
async function passReadyLabel(
|
|
7442
|
-
if (hasSpecReadyLabel(
|
|
6819
|
+
async function passReadyLabel(issue3, offer) {
|
|
6820
|
+
if (hasSpecReadyLabel(issue3.labels))
|
|
7443
6821
|
return;
|
|
7444
6822
|
if (offer !== undefined) {
|
|
7445
|
-
requireNoLeak(issueSource(
|
|
7446
|
-
if (await offer(
|
|
6823
|
+
requireNoLeak(issueSource(issue3.number), issue3.body);
|
|
6824
|
+
if (await offer(issue3))
|
|
7447
6825
|
return;
|
|
7448
6826
|
}
|
|
7449
|
-
requireSpecReadyLabel(
|
|
6827
|
+
requireSpecReadyLabel(issue3.number, issue3.labels);
|
|
7450
6828
|
}
|
|
7451
|
-
async function inspectSpecIssue(
|
|
7452
|
-
const source = issueSource(
|
|
7453
|
-
await requireTrustedBoardAuthor({ kind: "issue", number:
|
|
7454
|
-
await passReadyLabel(
|
|
7455
|
-
requireNoLeak(source,
|
|
7456
|
-
requireCompleteSpec(source,
|
|
6829
|
+
async function inspectSpecIssue(issue3, trust, offer) {
|
|
6830
|
+
const source = issueSource(issue3.number);
|
|
6831
|
+
await requireTrustedBoardAuthor({ kind: "issue", number: issue3.number }, trust, issue3.author);
|
|
6832
|
+
await passReadyLabel(issue3, offer);
|
|
6833
|
+
requireNoLeak(source, issue3.body);
|
|
6834
|
+
requireCompleteSpec(source, issue3.body);
|
|
7457
6835
|
}
|
|
7458
|
-
async function inspectRoadmapIssue(
|
|
7459
|
-
await requireTrustedBoardAuthor({ kind: "issue", number:
|
|
6836
|
+
async function inspectRoadmapIssue(issue3, trust) {
|
|
6837
|
+
await requireTrustedBoardAuthor({ kind: "issue", number: issue3.number }, trust, issue3.author);
|
|
7460
6838
|
}
|
|
7461
6839
|
function memoisePermissions(permissions) {
|
|
7462
6840
|
const read = new Map;
|
|
@@ -7484,7 +6862,7 @@ async function resolvePlanSpec(options) {
|
|
|
7484
6862
|
return board;
|
|
7485
6863
|
};
|
|
7486
6864
|
const offerReady = options.offerReady ?? null;
|
|
7487
|
-
const offer = offerReady === null || options.dryRun ? undefined : (
|
|
6865
|
+
const offer = offerReady === null || options.dryRun ? undefined : (issue3) => offerReady({ issue: issue3, gh, trust: trust(), output });
|
|
7488
6866
|
const offerAlternative = options.offerAlternative ?? null;
|
|
7489
6867
|
const alternative = offerAlternative === null || options.dryRun ? undefined : offerAlternative;
|
|
7490
6868
|
const offerRefresh = options.dryRun ? null : options.offerRefresh ?? null;
|
|
@@ -7496,14 +6874,14 @@ async function resolvePlanSpec(options) {
|
|
|
7496
6874
|
specsDir: options.specsDir,
|
|
7497
6875
|
findSpec: options.findSpec,
|
|
7498
6876
|
issues: createGhSpecIssueReader({ gh }),
|
|
7499
|
-
inspect: (
|
|
6877
|
+
inspect: (issue3) => inspectSpecIssue(issue3, trust(), offer),
|
|
7500
6878
|
offerRefresh,
|
|
7501
6879
|
roadmap: {
|
|
7502
6880
|
configured: options.roadmapIssue,
|
|
7503
6881
|
search: createGhRoadmapSearch({ gh }),
|
|
7504
6882
|
git,
|
|
7505
6883
|
pullRequests: createGhOpenPullRequests({ gh }),
|
|
7506
|
-
inspectRoadmap: (
|
|
6884
|
+
inspectRoadmap: (issue3) => inspectRoadmapIssue(issue3, trust()),
|
|
7507
6885
|
offerAlternative: alternative
|
|
7508
6886
|
},
|
|
7509
6887
|
output
|
|
@@ -7753,9 +7131,9 @@ function plural2(count, singular, many) {
|
|
|
7753
7131
|
return count === 1 ? `1 ${singular}` : `${count} ${many}`;
|
|
7754
7132
|
}
|
|
7755
7133
|
function listChanges(entries) {
|
|
7756
|
-
const
|
|
7757
|
-
const hidden = entries.length -
|
|
7758
|
-
return hidden > 0 ? [...
|
|
7134
|
+
const shown = entries.slice(0, MAX_LISTED_CHANGES).map((entry) => `${INDENT}${entry}`);
|
|
7135
|
+
const hidden = entries.length - shown.length;
|
|
7136
|
+
return hidden > 0 ? [...shown, `${INDENT}... and ${hidden} more`] : shown;
|
|
7759
7137
|
}
|
|
7760
7138
|
function triagePointer(number) {
|
|
7761
7139
|
return `Run rafa pr triage ${number} to see why.`;
|
|
@@ -7897,7 +7275,7 @@ function remainingFrom(steps, failed2) {
|
|
|
7897
7275
|
return at === -1 ? Object.freeze([]) : Object.freeze(steps.slice(at));
|
|
7898
7276
|
}
|
|
7899
7277
|
// src/pr/none.ts
|
|
7900
|
-
import { spawnSync as
|
|
7278
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
7901
7279
|
|
|
7902
7280
|
// src/pr/provider.ts
|
|
7903
7281
|
var GITHUB_HOST = "github.com";
|
|
@@ -7938,7 +7316,7 @@ function requireGhProvider(reading2) {
|
|
|
7938
7316
|
// src/pr/none.ts
|
|
7939
7317
|
var COMPARE_QUERY = "?expand=1";
|
|
7940
7318
|
function pushBranch(dir, branch) {
|
|
7941
|
-
const result =
|
|
7319
|
+
const result = spawnSync2("git", ["push", "--set-upstream", "origin", branch], {
|
|
7942
7320
|
cwd: dir,
|
|
7943
7321
|
encoding: "utf8",
|
|
7944
7322
|
env: { ...process.env, LC_ALL: "C" }
|
|
@@ -8324,9 +7702,9 @@ function plural3(count, singular, many) {
|
|
|
8324
7702
|
return count === 1 ? `1 ${singular}` : `${count} ${many}`;
|
|
8325
7703
|
}
|
|
8326
7704
|
function listChanges2(entries) {
|
|
8327
|
-
const
|
|
8328
|
-
const hidden = entries.length -
|
|
8329
|
-
return hidden > 0 ? [...
|
|
7705
|
+
const shown = entries.slice(0, MAX_LISTED_CHANGES2).map((entry) => `${INDENT2}${entry}`);
|
|
7706
|
+
const hidden = entries.length - shown.length;
|
|
7707
|
+
return hidden > 0 ? [...shown, `${INDENT2}... and ${hidden} more`] : shown;
|
|
8330
7708
|
}
|
|
8331
7709
|
function trackedChanges(tree) {
|
|
8332
7710
|
return Object.freeze(tree.entries.filter((entry) => !entry.startsWith(UNTRACKED_PREFIX)));
|
|
@@ -8525,7 +7903,7 @@ function parseArgs(argv, spec = {}) {
|
|
|
8525
7903
|
// src/commands/loop/loop-sessions.ts
|
|
8526
7904
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
8527
7905
|
import { readFileSync as readFileSync13 } from "fs";
|
|
8528
|
-
import { join as
|
|
7906
|
+
import { join as join11, relative as relative3 } from "path";
|
|
8529
7907
|
|
|
8530
7908
|
// src/effort/store/task-finishes.ts
|
|
8531
7909
|
import { existsSync as existsSync12 } from "fs";
|
|
@@ -8612,7 +7990,7 @@ function readSessionIdFlag(context, usage) {
|
|
|
8612
7990
|
return value;
|
|
8613
7991
|
}
|
|
8614
7992
|
function runsLabel(root) {
|
|
8615
|
-
return `${
|
|
7993
|
+
return `${relative3(root, runsDir(root))}/`;
|
|
8616
7994
|
}
|
|
8617
7995
|
function readRecords(root, seams) {
|
|
8618
7996
|
try {
|
|
@@ -8669,7 +8047,7 @@ function writeSession(root, record, change) {
|
|
|
8669
8047
|
}
|
|
8670
8048
|
}
|
|
8671
8049
|
function readSessionChecklist(root, record) {
|
|
8672
|
-
const plan =
|
|
8050
|
+
const plan = join11(root, record.plan);
|
|
8673
8051
|
const file = [trackerPathFor(plan), plan].find((path2) => isFile(path2));
|
|
8674
8052
|
if (file === undefined)
|
|
8675
8053
|
return null;
|
|
@@ -8716,9 +8094,9 @@ var DEFAULT_BASE_BRANCH = "main";
|
|
|
8716
8094
|
var DEFAULT_NEXT_SOURCE_SEAMS = Object.freeze({});
|
|
8717
8095
|
function memoiseIssues2(issues) {
|
|
8718
8096
|
const read = new Map;
|
|
8719
|
-
return (
|
|
8720
|
-
const taken = read.get(
|
|
8721
|
-
read.set(
|
|
8097
|
+
return (issue3) => {
|
|
8098
|
+
const taken = read.get(issue3) ?? issues(issue3);
|
|
8099
|
+
read.set(issue3, taken);
|
|
8722
8100
|
return taken;
|
|
8723
8101
|
};
|
|
8724
8102
|
}
|
|
@@ -8743,8 +8121,8 @@ function ghNextBoard(options) {
|
|
|
8743
8121
|
problems: branches.problems
|
|
8744
8122
|
};
|
|
8745
8123
|
},
|
|
8746
|
-
blocking: async (
|
|
8747
|
-
isReady: async (
|
|
8124
|
+
blocking: async (issue3) => readBlockedLine(await issues(issue3), blockerStatesOf(issues)),
|
|
8125
|
+
isReady: async (issue3) => hasSpecReadyLabel((await issues(issue3)).labels)
|
|
8748
8126
|
});
|
|
8749
8127
|
}
|
|
8750
8128
|
function nextProject(context) {
|
|
@@ -8942,9 +8320,9 @@ var NEXT_PRECONDITIONS = Object.freeze([
|
|
|
8942
8320
|
"pulls-unusable"
|
|
8943
8321
|
]);
|
|
8944
8322
|
function nameFiles(paths) {
|
|
8945
|
-
const
|
|
8946
|
-
const hidden = paths.length -
|
|
8947
|
-
return hidden > 0 ? `${
|
|
8323
|
+
const shown = paths.slice(0, MAX_NAMED_FILES).map((path2) => `\`${path2}\``);
|
|
8324
|
+
const hidden = paths.length - shown.length;
|
|
8325
|
+
return hidden > 0 ? `${shown.join(", ")} and ${hidden} more` : shown.join(", ");
|
|
8948
8326
|
}
|
|
8949
8327
|
function oneLine2(text) {
|
|
8950
8328
|
return text.replace(/\s+/g, " ").trim();
|
|
@@ -9111,39 +8489,39 @@ async function readIssueReady(world) {
|
|
|
9111
8489
|
const picked = await world.picked();
|
|
9112
8490
|
if (picked === null || !picked.ready || picked.blocked !== null)
|
|
9113
8491
|
return null;
|
|
9114
|
-
const { issue:
|
|
8492
|
+
const { issue: issue3 } = picked.line;
|
|
9115
8493
|
return {
|
|
9116
8494
|
id: "issue-ready",
|
|
9117
8495
|
action: "plan",
|
|
9118
|
-
reading: `#${
|
|
9119
|
-
proposal: `create the plan for #${
|
|
9120
|
-
issue:
|
|
8496
|
+
reading: `#${issue3} is next on the roadmap and carries \`${SPEC_READY_LABEL}\``,
|
|
8497
|
+
proposal: `create the plan for #${issue3}`,
|
|
8498
|
+
issue: issue3
|
|
9121
8499
|
};
|
|
9122
8500
|
}
|
|
9123
8501
|
async function readIssueBlocked(world) {
|
|
9124
8502
|
const picked = await world.picked();
|
|
9125
8503
|
if (picked === null || picked.blocked === null)
|
|
9126
8504
|
return null;
|
|
9127
|
-
const { issue:
|
|
8505
|
+
const { issue: issue3 } = picked.line;
|
|
9128
8506
|
return {
|
|
9129
8507
|
id: "issue-blocked",
|
|
9130
8508
|
action: "unblock",
|
|
9131
8509
|
reading: blockedLineSentence(picked.blocked),
|
|
9132
|
-
proposal: `re-read the blockers of #${
|
|
9133
|
-
issue:
|
|
8510
|
+
proposal: `re-read the blockers of #${issue3} and take \`${SPEC_BLOCKED_LABEL}\` off once they have all closed`,
|
|
8511
|
+
issue: issue3
|
|
9134
8512
|
};
|
|
9135
8513
|
}
|
|
9136
8514
|
async function readIssueNotReady(world) {
|
|
9137
8515
|
const picked = await world.picked();
|
|
9138
8516
|
if (picked === null)
|
|
9139
8517
|
return null;
|
|
9140
|
-
const { issue:
|
|
8518
|
+
const { issue: issue3 } = picked.line;
|
|
9141
8519
|
return {
|
|
9142
8520
|
id: "issue-not-ready",
|
|
9143
8521
|
action: "ready",
|
|
9144
|
-
reading: notReadySentence(
|
|
9145
|
-
proposal: `check the spec of #${
|
|
9146
|
-
issue:
|
|
8522
|
+
reading: notReadySentence(issue3),
|
|
8523
|
+
proposal: `check the spec of #${issue3} and mark it ready`,
|
|
8524
|
+
issue: issue3
|
|
9147
8525
|
};
|
|
9148
8526
|
}
|
|
9149
8527
|
function readNothingHere(world) {
|
|
@@ -9543,26 +8921,26 @@ async function onTracker(tracker, doing, call) {
|
|
|
9543
8921
|
var READY_USAGE = "rafa issue ready <n>";
|
|
9544
8922
|
var READY_WRITE_EXIT = 1;
|
|
9545
8923
|
var ISSUE_NUMBER = /^[1-9]\d*$/u;
|
|
9546
|
-
function readyQuestion(
|
|
9547
|
-
return `Mark #${String(
|
|
8924
|
+
function readyQuestion(issue3) {
|
|
8925
|
+
return `Mark #${String(issue3)} ${SPEC_READY_LABEL}? [y/N] `;
|
|
9548
8926
|
}
|
|
9549
|
-
function trustPassLine(
|
|
8927
|
+
function trustPassLine(issue3, repo, reading2) {
|
|
9550
8928
|
if (!reading2.trusted) {
|
|
9551
8929
|
throw new TypeError(`issue ready: ${reading2.login} is not trusted, and has no pass to name`);
|
|
9552
8930
|
}
|
|
9553
8931
|
const because = reading2.source === "allow-list" ? "listed in board.trustedAuthors" : `who has write access to ${repo}`;
|
|
9554
|
-
return `#${String(
|
|
8932
|
+
return `#${String(issue3)} was opened by ${reading2.login}, ${because}`;
|
|
9555
8933
|
}
|
|
9556
|
-
function completeLine(
|
|
9557
|
-
return `#${String(
|
|
8934
|
+
function completeLine(issue3) {
|
|
8935
|
+
return `#${String(issue3)} fills every heading the spec template asks for, with no placeholder left`;
|
|
9558
8936
|
}
|
|
9559
|
-
function unaskedMessage2(
|
|
9560
|
-
const number = String(
|
|
8937
|
+
function unaskedMessage2(issue3) {
|
|
8938
|
+
const number = String(issue3);
|
|
9561
8939
|
return `#${number} is ready to mark; there is no terminal to ask on, so ${SPEC_READY_LABEL} was not added.` + ` Run rafa issue ready ${number} where an answer can be typed`;
|
|
9562
8940
|
}
|
|
9563
|
-
function readyReport(
|
|
8941
|
+
function readyReport(issue3, status, reading2, lines) {
|
|
9564
8942
|
return Object.freeze({
|
|
9565
|
-
issue:
|
|
8943
|
+
issue: issue3,
|
|
9566
8944
|
status,
|
|
9567
8945
|
author: reading2.login,
|
|
9568
8946
|
trustedBy: reading2.source,
|
|
@@ -9572,36 +8950,36 @@ function readyReport(issue4, status, reading2, lines) {
|
|
|
9572
8950
|
});
|
|
9573
8951
|
}
|
|
9574
8952
|
async function runIssueReady(options) {
|
|
9575
|
-
const { gh, issue:
|
|
8953
|
+
const { gh, issue: issue3, trust, ask } = options;
|
|
9576
8954
|
const board = options.board ?? createGhIssueBoard({ gh });
|
|
9577
8955
|
const readIssue = options.readIssue ?? createGhSpecIssueReader({ gh });
|
|
9578
|
-
const found = await readIssue(
|
|
9579
|
-
const reading2 = await requireTrustedBoardAuthor({ kind: "issue", number:
|
|
9580
|
-
requireCompleteSpec(issueSource(
|
|
9581
|
-
const lines = { trust: trustPassLine(
|
|
8956
|
+
const found = await readIssue(issue3);
|
|
8957
|
+
const reading2 = await requireTrustedBoardAuthor({ kind: "issue", number: issue3 }, trust, found.author);
|
|
8958
|
+
requireCompleteSpec(issueSource(issue3), found.body);
|
|
8959
|
+
const lines = { trust: trustPassLine(issue3, trust.repo, reading2), checked: completeLine(issue3) };
|
|
9582
8960
|
if (hasSpecReadyLabel(found.labels)) {
|
|
9583
|
-
return readyReport(
|
|
8961
|
+
return readyReport(issue3, "already", reading2, {
|
|
9584
8962
|
...lines,
|
|
9585
|
-
message: `#${String(
|
|
8963
|
+
message: `#${String(issue3)} is already marked ${SPEC_READY_LABEL}`
|
|
9586
8964
|
});
|
|
9587
8965
|
}
|
|
9588
8966
|
if (ask === null) {
|
|
9589
|
-
return readyReport(
|
|
8967
|
+
return readyReport(issue3, "unasked", reading2, { ...lines, message: unaskedMessage2(issue3) });
|
|
9590
8968
|
}
|
|
9591
|
-
if (!await ask(readyQuestion(
|
|
9592
|
-
return readyReport(
|
|
8969
|
+
if (!await ask(readyQuestion(issue3))) {
|
|
8970
|
+
return readyReport(issue3, "declined", reading2, {
|
|
9593
8971
|
...lines,
|
|
9594
|
-
message: `#${String(
|
|
8972
|
+
message: `#${String(issue3)} was left unmarked`
|
|
9595
8973
|
});
|
|
9596
8974
|
}
|
|
9597
8975
|
try {
|
|
9598
|
-
await board.swapLabels(
|
|
8976
|
+
await board.swapLabels(issue3, SPEC_NEEDS_WORK_LABEL, SPEC_READY_LABEL);
|
|
9599
8977
|
} catch (error) {
|
|
9600
|
-
throw new CommandExit(READY_WRITE_EXIT, `\u274C ${SPEC_READY_LABEL} could not be put on #${String(
|
|
8978
|
+
throw new CommandExit(READY_WRITE_EXIT, `\u274C ${SPEC_READY_LABEL} could not be put on #${String(issue3)}: ${messageOf(error)}`);
|
|
9601
8979
|
}
|
|
9602
|
-
return readyReport(
|
|
8980
|
+
return readyReport(issue3, "marked", reading2, {
|
|
9603
8981
|
...lines,
|
|
9604
|
-
message: `Marked #${String(
|
|
8982
|
+
message: `Marked #${String(issue3)} ${SPEC_READY_LABEL}, and took ${SPEC_NEEDS_WORK_LABEL} off it`
|
|
9605
8983
|
});
|
|
9606
8984
|
}
|
|
9607
8985
|
function readReadyIssue(context) {
|
|
@@ -9633,7 +9011,7 @@ function lazyPrompter(open) {
|
|
|
9633
9011
|
};
|
|
9634
9012
|
}
|
|
9635
9013
|
async function markIssueReady(context, seams) {
|
|
9636
|
-
const
|
|
9014
|
+
const issue3 = readReadyIssue(context);
|
|
9637
9015
|
const project = issueProject(context);
|
|
9638
9016
|
const config = issueSubjectConfig(project, (message) => {
|
|
9639
9017
|
context.output.warn(message);
|
|
@@ -9652,7 +9030,7 @@ async function markIssueReady(context, seams) {
|
|
|
9652
9030
|
try {
|
|
9653
9031
|
return await runIssueReady({
|
|
9654
9032
|
gh,
|
|
9655
|
-
issue:
|
|
9033
|
+
issue: issue3,
|
|
9656
9034
|
trust,
|
|
9657
9035
|
ask: isTerminal() ? prompter.ask : null
|
|
9658
9036
|
});
|
|
@@ -9720,12 +9098,12 @@ function versionLine() {
|
|
|
9720
9098
|
|
|
9721
9099
|
// src/notices/notices.ts
|
|
9722
9100
|
import { mkdirSync as mkdirSync6, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
|
|
9723
|
-
import { dirname as dirname8, join as
|
|
9101
|
+
import { dirname as dirname8, join as join12 } from "path";
|
|
9724
9102
|
var NOTICE_IDS = Object.freeze(["alpha", "danger"]);
|
|
9725
9103
|
var FEEDBACK_URL = "https://github.com/open-tomato/rafa/issues/new/choose";
|
|
9726
9104
|
var ROADMAP_URL = "https://github.com/open-tomato/rafa/issues/31";
|
|
9727
9105
|
function noticesPath(home) {
|
|
9728
|
-
return
|
|
9106
|
+
return join12(home, ".rafa", "notices.json");
|
|
9729
9107
|
}
|
|
9730
9108
|
function isNoticeId(value) {
|
|
9731
9109
|
return typeof value === "string" && NOTICE_IDS.includes(value);
|
|
@@ -9934,7 +9312,7 @@ function markBudgetExit(options) {
|
|
|
9934
9312
|
}
|
|
9935
9313
|
|
|
9936
9314
|
// src/utils/commit.ts
|
|
9937
|
-
import { spawnSync as
|
|
9315
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
9938
9316
|
var DEFAULT_COMMIT_TYPE = "chore";
|
|
9939
9317
|
var TEST_NOUN_WINDOW = 40;
|
|
9940
9318
|
var FIX_VERBS = ["fix", "repair", "correct", "resolve"];
|
|
@@ -10146,7 +9524,7 @@ function failed2(step, subject, result) {
|
|
|
10146
9524
|
}
|
|
10147
9525
|
function spawnGit(cwd) {
|
|
10148
9526
|
return (args) => {
|
|
10149
|
-
const result =
|
|
9527
|
+
const result = spawnSync3("git", args, {
|
|
10150
9528
|
cwd,
|
|
10151
9529
|
encoding: "utf8",
|
|
10152
9530
|
maxBuffer: MAX_GIT_BUFFER
|
|
@@ -10217,10 +9595,10 @@ function holdsOf(reading2) {
|
|
|
10217
9595
|
for (const blocker of report2.blockers) {
|
|
10218
9596
|
holds.push(`blocker: ${blocker.what ?? "an entry with no usable what"}`);
|
|
10219
9597
|
}
|
|
10220
|
-
for (const
|
|
10221
|
-
if (!DROPPED_BLOCKER_FIELD.test(
|
|
9598
|
+
for (const issue3 of issues) {
|
|
9599
|
+
if (!DROPPED_BLOCKER_FIELD.test(issue3.field))
|
|
10222
9600
|
continue;
|
|
10223
|
-
holds.push(`blocker: ${
|
|
9601
|
+
holds.push(`blocker: ${issue3.field}, an entry the report parser dropped`);
|
|
10224
9602
|
}
|
|
10225
9603
|
return holds;
|
|
10226
9604
|
}
|
|
@@ -10293,6 +9671,411 @@ function finishCleanExit(options) {
|
|
|
10293
9671
|
// src/start/dispatch.ts
|
|
10294
9672
|
import { randomUUID as randomUUID7 } from "crypto";
|
|
10295
9673
|
|
|
9674
|
+
// src/report/lessons.ts
|
|
9675
|
+
import { createHash as createHash2 } from "crypto";
|
|
9676
|
+
|
|
9677
|
+
// src/demote/classify.ts
|
|
9678
|
+
var LEARNED_DIRECTORY = "learned";
|
|
9679
|
+
var ORIGIN_FIELD = "origin";
|
|
9680
|
+
var DEMOTION_VERDICTS = [
|
|
9681
|
+
"observation",
|
|
9682
|
+
"procedure",
|
|
9683
|
+
"unclassified"
|
|
9684
|
+
];
|
|
9685
|
+
var RULE_VERDICTS = Object.freeze({
|
|
9686
|
+
"no-frontmatter": "unclassified",
|
|
9687
|
+
"learned-without-origin": "unclassified",
|
|
9688
|
+
"no-section-pair": "unclassified",
|
|
9689
|
+
"repeated-section": "procedure",
|
|
9690
|
+
"numbered-procedure": "procedure",
|
|
9691
|
+
"referenced-script": "procedure",
|
|
9692
|
+
"observation-shape": "observation"
|
|
9693
|
+
});
|
|
9694
|
+
var PROCEDURE_STEP_MINIMUM = 3;
|
|
9695
|
+
var PROBLEM_HEADING = /^(?:the\s+)?problem\b/i;
|
|
9696
|
+
var SOLUTION_HEADING = /^(?:the\s+)?solution\b/i;
|
|
9697
|
+
var WHEN_TO_USE_HEADING = /^when\s+to\s+use\b/i;
|
|
9698
|
+
var EXTRACTED_LINE = /^\s*\*\*Extracted:\*\*\s*(\S+)/m;
|
|
9699
|
+
var SCRIPT_EXTENSIONS = [
|
|
9700
|
+
".sh",
|
|
9701
|
+
".bash",
|
|
9702
|
+
".zsh",
|
|
9703
|
+
".py",
|
|
9704
|
+
".rb",
|
|
9705
|
+
".pl",
|
|
9706
|
+
".js",
|
|
9707
|
+
".mjs",
|
|
9708
|
+
".cjs",
|
|
9709
|
+
".ts"
|
|
9710
|
+
];
|
|
9711
|
+
var FENCE_LINE4 = /^ {0,3}(?:```|~~~)/;
|
|
9712
|
+
var HEADING_LINE2 = /^ {0,3}(#{1,6})\s+(.*)$/;
|
|
9713
|
+
var NUMBERED_ITEM = /^ {0,3}\d+[.)]\s+\S/;
|
|
9714
|
+
var CONTINUATION = /^ {2,}\S/;
|
|
9715
|
+
var DEMOTED_SOURCE = "demoted";
|
|
9716
|
+
var DEMOTED_CONFIDENCE = 0.5;
|
|
9717
|
+
var DEMOTED_KIND = "gotcha";
|
|
9718
|
+
var DEMOTED_SIGNAL = "silent";
|
|
9719
|
+
var DEMOTED_USAGE_COUNT = 1;
|
|
9720
|
+
var EVIDENCE_PATH_KEY = "path";
|
|
9721
|
+
var EVIDENCE_EXTRACTED_KEY = "extracted";
|
|
9722
|
+
var DEFAULT_DOMAIN = "general-best-practices";
|
|
9723
|
+
var DOMAIN_MARKERS = [
|
|
9724
|
+
["security", /\b(?:security|secret|credential|token|auth|csrf|xss|injection)\b/i],
|
|
9725
|
+
["testing", /\b(?:test|tests|testing|spec|assertion|assert|mock|stub|fixture|coverage)\b/i],
|
|
9726
|
+
["git", /\b(?:git|commit|rebase|worktree|branch|merge|checkout)\b/i],
|
|
9727
|
+
["debugging", /\b(?:debug|debugging|traceback|reproduce|bisect)\b/i],
|
|
9728
|
+
["code-style", /\b(?:lint|eslint|format|formatter|prettier|naming)\b/i],
|
|
9729
|
+
["workflow", /\b(?:workflow|pipeline|release|deploy|build|ci|loop|dispatch)\b/i]
|
|
9730
|
+
];
|
|
9731
|
+
var ERROR_WORDS = /\b(?:error|exception|traceback|panic|fatal|refused|denied|not found|no such|cannot|could not|failed|failure|unexpected|undefined)\b/i;
|
|
9732
|
+
var SENTENCE_ABBREVIATIONS = ["e.g", "i.e", "vs", "cf", "etc"];
|
|
9733
|
+
function isFilled2(value) {
|
|
9734
|
+
return typeof value === "string" && value.trim() !== "";
|
|
9735
|
+
}
|
|
9736
|
+
function bodyLines(body) {
|
|
9737
|
+
return body.split(`
|
|
9738
|
+
`).map((line) => line.replace(/\r$/, ""));
|
|
9739
|
+
}
|
|
9740
|
+
function bodySections(body) {
|
|
9741
|
+
const lines = bodyLines(body);
|
|
9742
|
+
const headings = [];
|
|
9743
|
+
let fenced = false;
|
|
9744
|
+
lines.forEach((line, index) => {
|
|
9745
|
+
if (FENCE_LINE4.test(line)) {
|
|
9746
|
+
fenced = !fenced;
|
|
9747
|
+
return;
|
|
9748
|
+
}
|
|
9749
|
+
if (fenced)
|
|
9750
|
+
return;
|
|
9751
|
+
const match = HEADING_LINE2.exec(line);
|
|
9752
|
+
if (match === null)
|
|
9753
|
+
return;
|
|
9754
|
+
const [, hashes = "", title = ""] = match;
|
|
9755
|
+
headings.push({
|
|
9756
|
+
level: hashes.length,
|
|
9757
|
+
title: title.replace(/\s+#+\s*$/, "").trim(),
|
|
9758
|
+
index
|
|
9759
|
+
});
|
|
9760
|
+
});
|
|
9761
|
+
return headings.map((heading, position) => {
|
|
9762
|
+
const next = headings.slice(position + 1).find((other) => other.level <= heading.level);
|
|
9763
|
+
const end = next === undefined ? lines.length : next.index;
|
|
9764
|
+
return {
|
|
9765
|
+
level: heading.level,
|
|
9766
|
+
title: heading.title,
|
|
9767
|
+
line: heading.index + 1,
|
|
9768
|
+
text: lines.slice(heading.index + 1, end).join(`
|
|
9769
|
+
`).trim()
|
|
9770
|
+
};
|
|
9771
|
+
});
|
|
9772
|
+
}
|
|
9773
|
+
function numberedRuns(body) {
|
|
9774
|
+
const runs = [];
|
|
9775
|
+
let fenced = false;
|
|
9776
|
+
let steps = 0;
|
|
9777
|
+
let line = 0;
|
|
9778
|
+
const close = () => {
|
|
9779
|
+
if (steps > 0)
|
|
9780
|
+
runs.push({ steps, line });
|
|
9781
|
+
steps = 0;
|
|
9782
|
+
};
|
|
9783
|
+
bodyLines(body).forEach((text, index) => {
|
|
9784
|
+
if (FENCE_LINE4.test(text)) {
|
|
9785
|
+
fenced = !fenced;
|
|
9786
|
+
return;
|
|
9787
|
+
}
|
|
9788
|
+
if (fenced || text.trim() === "")
|
|
9789
|
+
return;
|
|
9790
|
+
if (NUMBERED_ITEM.test(text)) {
|
|
9791
|
+
if (steps === 0)
|
|
9792
|
+
line = index + 1;
|
|
9793
|
+
steps += 1;
|
|
9794
|
+
return;
|
|
9795
|
+
}
|
|
9796
|
+
if (CONTINUATION.test(text))
|
|
9797
|
+
return;
|
|
9798
|
+
close();
|
|
9799
|
+
});
|
|
9800
|
+
close();
|
|
9801
|
+
return runs;
|
|
9802
|
+
}
|
|
9803
|
+
function isScriptPath(token) {
|
|
9804
|
+
if (token.startsWith("/"))
|
|
9805
|
+
return false;
|
|
9806
|
+
const last = token.split("/").at(-1) ?? "";
|
|
9807
|
+
return SCRIPT_EXTENSIONS.some((extension) => last.endsWith(extension));
|
|
9808
|
+
}
|
|
9809
|
+
function referencedScript(body, entries = []) {
|
|
9810
|
+
const owned = new Set(entries.map((entry) => entry.replace(/^\.\//, "")));
|
|
9811
|
+
for (const reference2 of collectReferences(body)) {
|
|
9812
|
+
if (reference2.kind !== "path")
|
|
9813
|
+
continue;
|
|
9814
|
+
const token = reference2.text.replace(/^\.\//, "");
|
|
9815
|
+
const [first = ""] = token.split("/");
|
|
9816
|
+
if (owned.has(first) && isScriptPath(token))
|
|
9817
|
+
return reference2;
|
|
9818
|
+
}
|
|
9819
|
+
return null;
|
|
9820
|
+
}
|
|
9821
|
+
function isLearnedFile(path2) {
|
|
9822
|
+
const [first] = path2.split(/[\\/]/);
|
|
9823
|
+
return first === LEARNED_DIRECTORY;
|
|
9824
|
+
}
|
|
9825
|
+
function decide(rule, reason) {
|
|
9826
|
+
return { verdict: RULE_VERDICTS[rule], rule, reason };
|
|
9827
|
+
}
|
|
9828
|
+
function pairReason(problems, solutions) {
|
|
9829
|
+
if (problems === 0 && solutions === 0) {
|
|
9830
|
+
return "the body carries no Problem section and no Solution section";
|
|
9831
|
+
}
|
|
9832
|
+
return problems === 0 ? "the body carries a Solution section and no Problem section, so there is no pair" : "the body carries a Problem section and no Solution section, so there is no pair";
|
|
9833
|
+
}
|
|
9834
|
+
function repeatReason(problems, solutions) {
|
|
9835
|
+
const counted2 = problems > 1 ? `${problems} Problem sections` : `${solutions} Solution sections`;
|
|
9836
|
+
return `the body carries ${counted2}, and an observation carries exactly one of each`;
|
|
9837
|
+
}
|
|
9838
|
+
function classifySkill(source) {
|
|
9839
|
+
const document = readFrontmatterDocument(source.text);
|
|
9840
|
+
if (document === null) {
|
|
9841
|
+
return decide("no-frontmatter", "the file opens with no --- block holding a YAML mapping");
|
|
9842
|
+
}
|
|
9843
|
+
if (isLearnedFile(source.path) && !isFilled2(document.data[ORIGIN_FIELD])) {
|
|
9844
|
+
return decide("learned-without-origin", `a ${LEARNED_DIRECTORY}/ file whose frontmatter carries no ${ORIGIN_FIELD}`);
|
|
9845
|
+
}
|
|
9846
|
+
const sections2 = bodySections(document.body);
|
|
9847
|
+
const problems = sections2.filter((section) => PROBLEM_HEADING.test(section.title));
|
|
9848
|
+
const solutions = sections2.filter((section) => SOLUTION_HEADING.test(section.title));
|
|
9849
|
+
if (problems.length === 0 || solutions.length === 0) {
|
|
9850
|
+
return decide("no-section-pair", pairReason(problems.length, solutions.length));
|
|
9851
|
+
}
|
|
9852
|
+
if (problems.length > 1 || solutions.length > 1) {
|
|
9853
|
+
return decide("repeated-section", repeatReason(problems.length, solutions.length));
|
|
9854
|
+
}
|
|
9855
|
+
const run = numberedRuns(document.body).find((each) => each.steps >= PROCEDURE_STEP_MINIMUM);
|
|
9856
|
+
if (run !== undefined) {
|
|
9857
|
+
return decide("numbered-procedure", `a numbered procedure of ${run.steps} consecutive steps at line ${run.line}`);
|
|
9858
|
+
}
|
|
9859
|
+
const script = referencedScript(document.body, source.entries ?? []);
|
|
9860
|
+
if (script !== null) {
|
|
9861
|
+
return decide("referenced-script", `the body names the script ${script.text} at line ${script.line}`);
|
|
9862
|
+
}
|
|
9863
|
+
return decide("observation-shape", "one Problem section and one Solution section, no numbered procedure of " + `${PROCEDURE_STEP_MINIMUM} or more consecutive steps, and no referenced script`);
|
|
9864
|
+
}
|
|
9865
|
+
function firstSection(sections2, heading) {
|
|
9866
|
+
return sections2.find((section) => heading.test(section.title));
|
|
9867
|
+
}
|
|
9868
|
+
function withoutFences(text) {
|
|
9869
|
+
const kept = [];
|
|
9870
|
+
let fenced = false;
|
|
9871
|
+
for (const line of bodyLines(text)) {
|
|
9872
|
+
if (FENCE_LINE4.test(line)) {
|
|
9873
|
+
fenced = !fenced;
|
|
9874
|
+
continue;
|
|
9875
|
+
}
|
|
9876
|
+
if (!fenced)
|
|
9877
|
+
kept.push(line);
|
|
9878
|
+
}
|
|
9879
|
+
return kept;
|
|
9880
|
+
}
|
|
9881
|
+
function stripMarkers(line) {
|
|
9882
|
+
return line.replace(/^\s*(?:[-*+]|\d+[.)]|>)\s+/, "").replace(/\*\*|__|\*|`/g, "").trim();
|
|
9883
|
+
}
|
|
9884
|
+
function isAbbreviation(text, index) {
|
|
9885
|
+
const before = text.slice(0, index);
|
|
9886
|
+
return SENTENCE_ABBREVIATIONS.some((word) => before.toLowerCase().endsWith(word));
|
|
9887
|
+
}
|
|
9888
|
+
function firstSentence(text) {
|
|
9889
|
+
const lines = withoutFences(text);
|
|
9890
|
+
const start = lines.findIndex((line) => line.trim() !== "");
|
|
9891
|
+
if (start === -1)
|
|
9892
|
+
return "";
|
|
9893
|
+
const collected = [lines[start] ?? ""];
|
|
9894
|
+
for (const line of lines.slice(start + 1)) {
|
|
9895
|
+
if (line.trim() === "" || /^\s*(?:[-*+]|\d+[.)]|#)/.test(line))
|
|
9896
|
+
break;
|
|
9897
|
+
collected.push(line);
|
|
9898
|
+
}
|
|
9899
|
+
const joined = stripMarkers(collected.join(" ")).replace(/\s+/g, " ");
|
|
9900
|
+
for (let index = 0;index < joined.length; index += 1) {
|
|
9901
|
+
if (!".!?".includes(joined[index] ?? ""))
|
|
9902
|
+
continue;
|
|
9903
|
+
if (isAbbreviation(joined, index))
|
|
9904
|
+
continue;
|
|
9905
|
+
const next = joined[index + 1];
|
|
9906
|
+
if (next === undefined)
|
|
9907
|
+
return joined.slice(0, index + 1).trim();
|
|
9908
|
+
if (next === " ")
|
|
9909
|
+
return joined.slice(0, index + 1).trim();
|
|
9910
|
+
}
|
|
9911
|
+
return joined.trim();
|
|
9912
|
+
}
|
|
9913
|
+
function demotedTrigger(sections2, data) {
|
|
9914
|
+
const whenToUse = firstSection(sections2, WHEN_TO_USE_HEADING);
|
|
9915
|
+
const fromSection = whenToUse === undefined ? "" : firstSentence(whenToUse.text);
|
|
9916
|
+
if (fromSection !== "")
|
|
9917
|
+
return fromSection;
|
|
9918
|
+
const description = data["description"];
|
|
9919
|
+
if (isFilled2(description))
|
|
9920
|
+
return description.trim();
|
|
9921
|
+
const problem = firstSection(sections2, PROBLEM_HEADING);
|
|
9922
|
+
return problem === undefined ? "" : firstSentence(problem.text);
|
|
9923
|
+
}
|
|
9924
|
+
function extractedDate(body) {
|
|
9925
|
+
const match = EXTRACTED_LINE.exec(body);
|
|
9926
|
+
return match?.[1] ?? null;
|
|
9927
|
+
}
|
|
9928
|
+
function errorString(body) {
|
|
9929
|
+
let fenced = false;
|
|
9930
|
+
for (const line of bodyLines(body)) {
|
|
9931
|
+
if (FENCE_LINE4.test(line)) {
|
|
9932
|
+
fenced = !fenced;
|
|
9933
|
+
continue;
|
|
9934
|
+
}
|
|
9935
|
+
if (fenced) {
|
|
9936
|
+
if (ERROR_WORDS.test(line))
|
|
9937
|
+
return line.trim();
|
|
9938
|
+
continue;
|
|
9939
|
+
}
|
|
9940
|
+
for (const span of line.matchAll(/`([^`\n]+)`/g)) {
|
|
9941
|
+
const inner = span[1] ?? "";
|
|
9942
|
+
if (ERROR_WORDS.test(inner))
|
|
9943
|
+
return inner.trim();
|
|
9944
|
+
}
|
|
9945
|
+
}
|
|
9946
|
+
return null;
|
|
9947
|
+
}
|
|
9948
|
+
function inferDomain(name2, body) {
|
|
9949
|
+
for (const [domain, pattern] of DOMAIN_MARKERS) {
|
|
9950
|
+
if (pattern.test(name2))
|
|
9951
|
+
return domain;
|
|
9952
|
+
}
|
|
9953
|
+
for (const [domain, pattern] of DOMAIN_MARKERS) {
|
|
9954
|
+
if (pattern.test(body))
|
|
9955
|
+
return domain;
|
|
9956
|
+
}
|
|
9957
|
+
return DEFAULT_DOMAIN;
|
|
9958
|
+
}
|
|
9959
|
+
function slugifyId(value) {
|
|
9960
|
+
return value.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
9961
|
+
}
|
|
9962
|
+
function uniqueInstinctId(base, taken) {
|
|
9963
|
+
const used = new Set(taken);
|
|
9964
|
+
if (!used.has(base))
|
|
9965
|
+
return base;
|
|
9966
|
+
let suffix = 2;
|
|
9967
|
+
while (used.has(`${base}-${suffix}`))
|
|
9968
|
+
suffix += 1;
|
|
9969
|
+
return `${base}-${suffix}`;
|
|
9970
|
+
}
|
|
9971
|
+
function sourceName(source, data) {
|
|
9972
|
+
const declared = data["name"];
|
|
9973
|
+
if (isFilled2(declared))
|
|
9974
|
+
return slugifyId(declared);
|
|
9975
|
+
const segments = source.path.split(/[\\/]/).filter((segment) => segment !== "");
|
|
9976
|
+
const last = segments.at(-1) ?? "";
|
|
9977
|
+
const stem = last.toUpperCase() === "SKILL.MD" ? segments.at(-2) ?? "" : last.replace(/\.md$/i, "");
|
|
9978
|
+
return slugifyId(stem);
|
|
9979
|
+
}
|
|
9980
|
+
function convertToInstinct(source, options) {
|
|
9981
|
+
const document = readFrontmatterDocument(source.text);
|
|
9982
|
+
const data = document?.data ?? {};
|
|
9983
|
+
const body = document?.body ?? source.text;
|
|
9984
|
+
const sections2 = bodySections(body);
|
|
9985
|
+
const action = firstSection(sections2, SOLUTION_HEADING)?.text ?? "";
|
|
9986
|
+
const named = errorString(body);
|
|
9987
|
+
const name2 = sourceName(source, data);
|
|
9988
|
+
const draft = {
|
|
9989
|
+
id: uniqueInstinctId(name2, options.takenIds ?? []),
|
|
9990
|
+
trigger: demotedTrigger(sections2, data),
|
|
9991
|
+
kind: DEMOTED_KIND,
|
|
9992
|
+
domain: inferDomain(name2, body),
|
|
9993
|
+
confidence: DEMOTED_CONFIDENCE,
|
|
9994
|
+
usageCount: DEMOTED_USAGE_COUNT,
|
|
9995
|
+
sources: [],
|
|
9996
|
+
artifact: null,
|
|
9997
|
+
signal: named === null ? DEMOTED_SIGNAL : "loud",
|
|
9998
|
+
scope: options.scope,
|
|
9999
|
+
projectId: options.projectId ?? null,
|
|
10000
|
+
source: DEMOTED_SOURCE,
|
|
10001
|
+
evidence: [{
|
|
10002
|
+
[EVIDENCE_PATH_KEY]: source.path,
|
|
10003
|
+
[EVIDENCE_EXTRACTED_KEY]: extractedDate(body) ?? options.mtime
|
|
10004
|
+
}],
|
|
10005
|
+
promotedTo: null,
|
|
10006
|
+
createdAt: options.now,
|
|
10007
|
+
updatedAt: options.now,
|
|
10008
|
+
action,
|
|
10009
|
+
cause: firstSection(sections2, PROBLEM_HEADING)?.text ?? "",
|
|
10010
|
+
actionHash: actionHash(action)
|
|
10011
|
+
};
|
|
10012
|
+
const text = writeInstinct(draft);
|
|
10013
|
+
const parsed = parseInstinct(text);
|
|
10014
|
+
return parsed.instinct === null ? { instinct: null, text: null, issues: parsed.issues } : { instinct: parsed.instinct, text, issues: parsed.issues };
|
|
10015
|
+
}
|
|
10016
|
+
|
|
10017
|
+
// src/report/lessons.ts
|
|
10018
|
+
var LESSON_CONFIDENCE = 0.5;
|
|
10019
|
+
var BLOCKED_LESSON_CONFIDENCE = 0.4;
|
|
10020
|
+
var DEFAULT_LESSON_DOMAIN = "workflow";
|
|
10021
|
+
var LESSON_SLUG_LENGTH = 48;
|
|
10022
|
+
var LESSON_HASH_LENGTH = 8;
|
|
10023
|
+
var LESSON_ID_FALLBACK = "lesson";
|
|
10024
|
+
function lessonId(trigger, action) {
|
|
10025
|
+
const slug = slugifyId(trigger).slice(0, LESSON_SLUG_LENGTH).replace(/-+$/, "");
|
|
10026
|
+
const digest = createHash2("sha256").update(`${triggerKey(trigger)}
|
|
10027
|
+
${actionHash(action)}`).digest("hex").slice(0, LESSON_HASH_LENGTH);
|
|
10028
|
+
return `${slug === "" ? LESSON_ID_FALLBACK : slug}-${digest}`;
|
|
10029
|
+
}
|
|
10030
|
+
function lessonEvidence(context) {
|
|
10031
|
+
const { dispatch, outcome } = context;
|
|
10032
|
+
return {
|
|
10033
|
+
...dispatch.planStub === null ? {} : { plan: dispatch.planStub },
|
|
10034
|
+
task: dispatch.taskLine,
|
|
10035
|
+
session: dispatch.sessionId,
|
|
10036
|
+
outcome
|
|
10037
|
+
};
|
|
10038
|
+
}
|
|
10039
|
+
function toLesson(finding, context, at) {
|
|
10040
|
+
const { trigger, kind, signal, resolution, artifact } = finding;
|
|
10041
|
+
const cause = finding.cause ?? finding.what;
|
|
10042
|
+
if (resolution === null || trigger === null || kind === null || signal === null)
|
|
10043
|
+
return null;
|
|
10044
|
+
if (cause === null)
|
|
10045
|
+
return null;
|
|
10046
|
+
const { sessionId } = context.dispatch;
|
|
10047
|
+
return {
|
|
10048
|
+
id: lessonId(trigger, resolution),
|
|
10049
|
+
trigger,
|
|
10050
|
+
action: resolution,
|
|
10051
|
+
action_hash: actionHash(resolution),
|
|
10052
|
+
confidence: context.outcome === "blocked" ? BLOCKED_LESSON_CONFIDENCE : LESSON_CONFIDENCE,
|
|
10053
|
+
usage_count: 1,
|
|
10054
|
+
sources: [sessionId],
|
|
10055
|
+
...artifact === null ? {} : { artifact },
|
|
10056
|
+
signal,
|
|
10057
|
+
status: "active",
|
|
10058
|
+
created_at: at,
|
|
10059
|
+
updated_at: at,
|
|
10060
|
+
description: {
|
|
10061
|
+
kind,
|
|
10062
|
+
domain: finding.domain ?? DEFAULT_LESSON_DOMAIN,
|
|
10063
|
+
scope: "project",
|
|
10064
|
+
source: "task-report",
|
|
10065
|
+
evidence: [lessonEvidence(context)],
|
|
10066
|
+
cause,
|
|
10067
|
+
projectId: null
|
|
10068
|
+
}
|
|
10069
|
+
};
|
|
10070
|
+
}
|
|
10071
|
+
function reportLessons(record, context, now) {
|
|
10072
|
+
if (!record.present)
|
|
10073
|
+
return null;
|
|
10074
|
+
const at = now.toISOString();
|
|
10075
|
+
const instincts = record.reading.report.findings.map((finding) => toLesson(finding, context, at)).filter((lesson) => lesson !== null);
|
|
10076
|
+
return instincts.length === 0 ? null : { source_id: context.dispatch.sessionId, instincts };
|
|
10077
|
+
}
|
|
10078
|
+
|
|
10296
10079
|
// src/effort/store/absences.ts
|
|
10297
10080
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
10298
10081
|
import { existsSync as existsSync13 } from "fs";
|
|
@@ -10673,7 +10456,7 @@ function describeTaskReportRecord(record) {
|
|
|
10673
10456
|
|
|
10674
10457
|
// src/utils/progress.ts
|
|
10675
10458
|
import { existsSync as existsSync15, writeFileSync as writeFileSync7 } from "fs";
|
|
10676
|
-
import { join as
|
|
10459
|
+
import { join as join13 } from "path";
|
|
10677
10460
|
var PROGRESS_FILE_NAME = "progress.txt";
|
|
10678
10461
|
var PROGRESS_CAP_BYTES = 16000;
|
|
10679
10462
|
var LISTED_FIELDS = [
|
|
@@ -10715,7 +10498,7 @@ var ESCAPED_CODE_POINTS = new Set([
|
|
|
10715
10498
|
8233
|
|
10716
10499
|
]);
|
|
10717
10500
|
function progressFilePath(repoRoot) {
|
|
10718
|
-
return
|
|
10501
|
+
return join13(repoRoot, PROGRESS_FILE_NAME);
|
|
10719
10502
|
}
|
|
10720
10503
|
function escapeValue(value) {
|
|
10721
10504
|
let escaped = "";
|
|
@@ -11073,8 +10856,8 @@ async function dispatchTask(options) {
|
|
|
11073
10856
|
const note = suppressed.length === 0 ? "" : ` (${suppressed.join(", ")} left to the agent)`;
|
|
11074
10857
|
activeOutput().info(` Routed as: ${flags.join(" ")}${note}`);
|
|
11075
10858
|
}
|
|
11076
|
-
for (const
|
|
11077
|
-
activeOutput().warn(` Declaration: ignoring ${
|
|
10859
|
+
for (const issue3 of declaration?.issues ?? []) {
|
|
10860
|
+
activeOutput().warn(` Declaration: ignoring ${issue3.reason} \`${issue3.text}\`.`);
|
|
11078
10861
|
}
|
|
11079
10862
|
const injection = renderInjection({
|
|
11080
10863
|
mode: options.inject,
|
|
@@ -11126,8 +10909,37 @@ function renderProgressForDispatch(repoRoot, planStub2) {
|
|
|
11126
10909
|
return false;
|
|
11127
10910
|
}
|
|
11128
10911
|
}
|
|
11129
|
-
function
|
|
10912
|
+
async function pushTaskLessons(options, record) {
|
|
10913
|
+
const { learning, dispatch } = options;
|
|
10914
|
+
if (learning === null)
|
|
10915
|
+
return;
|
|
10916
|
+
const now = options.now ?? (() => new Date);
|
|
10917
|
+
const context = {
|
|
10918
|
+
dispatch: { sessionId: dispatch.sessionId, planStub: options.planStub, taskLine: dispatch.taskText },
|
|
10919
|
+
outcome: options.outcome
|
|
10920
|
+
};
|
|
10921
|
+
const payload = reportLessons(record, context, now());
|
|
10922
|
+
if (payload === null)
|
|
10923
|
+
return;
|
|
10924
|
+
const count = payload.instincts.length;
|
|
10925
|
+
try {
|
|
10926
|
+
const registry = learning.registry ?? CORE_ADAPTER_REGISTRY;
|
|
10927
|
+
const adapter = registry.resolve("learning", learning.kind).create({
|
|
10928
|
+
repoRoot: options.repoRoot,
|
|
10929
|
+
home: learning.home,
|
|
10930
|
+
learningBlessMinConfidence: learning.blessMinConfidence
|
|
10931
|
+
});
|
|
10932
|
+
const result = await adapter.push(payload);
|
|
10933
|
+
const rules = result.decisions.map((decision) => decision.rule).join(", ");
|
|
10934
|
+
activeOutput().info(` Pushed ${count} lesson(s) to the \`${learning.kind}\` learning adapter: ${rules}.`);
|
|
10935
|
+
} catch (error) {
|
|
10936
|
+
activeOutput().warn(` ${count} lesson(s) of session ${dispatch.sessionId} were not pushed to the \`${learning.kind}\` learning adapter: ${messageOf3(error)}`);
|
|
10937
|
+
activeOutput().warn(" The task is not failed for it: its report is stored, findings and all.");
|
|
10938
|
+
}
|
|
10939
|
+
}
|
|
10940
|
+
async function storeTaskReport(options) {
|
|
11130
10941
|
const { dispatch } = options;
|
|
10942
|
+
let record;
|
|
11131
10943
|
try {
|
|
11132
10944
|
writeDispatch(options.repoRoot, {
|
|
11133
10945
|
sessionId: dispatch.sessionId,
|
|
@@ -11136,7 +10948,7 @@ function storeTaskReport(options) {
|
|
|
11136
10948
|
declaration: dispatch.declaration,
|
|
11137
10949
|
flags: dispatch.flags
|
|
11138
10950
|
});
|
|
11139
|
-
|
|
10951
|
+
record = recordTaskReport(options.repoRoot, {
|
|
11140
10952
|
dispatch: {
|
|
11141
10953
|
sessionId: dispatch.sessionId,
|
|
11142
10954
|
planStub: options.planStub,
|
|
@@ -11150,13 +10962,14 @@ function storeTaskReport(options) {
|
|
|
11150
10962
|
activeOutput().info(` ${note}`);
|
|
11151
10963
|
for (const warning of warnings)
|
|
11152
10964
|
activeOutput().warn(` ${warning}`);
|
|
11153
|
-
return true;
|
|
11154
10965
|
} catch (error) {
|
|
11155
10966
|
activeOutput().error(`
|
|
11156
10967
|
\u274C The report of session ${dispatch.sessionId} was not stored: ${messageOf3(error)}`);
|
|
11157
10968
|
activeOutput().error(" The session printed it above as it ran.");
|
|
11158
10969
|
return false;
|
|
11159
10970
|
}
|
|
10971
|
+
await pushTaskLessons(options, record);
|
|
10972
|
+
return true;
|
|
11160
10973
|
}
|
|
11161
10974
|
|
|
11162
10975
|
// src/start/pause.ts
|
|
@@ -11583,11 +11396,11 @@ function changelogDate(now) {
|
|
|
11583
11396
|
}
|
|
11584
11397
|
|
|
11585
11398
|
// src/release/enabled.ts
|
|
11586
|
-
import { statSync as
|
|
11399
|
+
import { statSync as statSync9 } from "fs";
|
|
11587
11400
|
import { resolve as resolve10 } from "path";
|
|
11588
11401
|
function isRegularFile(full) {
|
|
11589
11402
|
try {
|
|
11590
|
-
return
|
|
11403
|
+
return statSync9(full).isFile();
|
|
11591
11404
|
} catch {
|
|
11592
11405
|
return false;
|
|
11593
11406
|
}
|
|
@@ -11758,12 +11571,12 @@ function readBaseVersion(git, versionFile, options = {}) {
|
|
|
11758
11571
|
if (!fetched.ok) {
|
|
11759
11572
|
problems.push(`${branch} could not be fetched from ${remote}, so the base version is whatever this clone already holds for ${ref}: ${gitSaid(fetched)}`);
|
|
11760
11573
|
}
|
|
11761
|
-
const
|
|
11762
|
-
if (!
|
|
11763
|
-
problems.push(`${ref}:${path2} could not be read: ${gitSaid(
|
|
11574
|
+
const shown = git(["show", `${ref}:${path2}`]);
|
|
11575
|
+
if (!shown.ok) {
|
|
11576
|
+
problems.push(`${ref}:${path2} could not be read: ${gitSaid(shown)}`);
|
|
11764
11577
|
return { ref, path: path2, version: null, fetched: fetched.ok, problems };
|
|
11765
11578
|
}
|
|
11766
|
-
const version2 = readManifestVersion(
|
|
11579
|
+
const version2 = readManifestVersion(shown.stdout);
|
|
11767
11580
|
if (version2 === null)
|
|
11768
11581
|
problems.push(`${ref}:${path2} declares no version`);
|
|
11769
11582
|
return { ref, path: path2, version: version2, fetched: fetched.ok, problems };
|
|
@@ -12294,7 +12107,7 @@ import { readFileSync as readFileSync21 } from "fs";
|
|
|
12294
12107
|
|
|
12295
12108
|
// src/plan/risk.ts
|
|
12296
12109
|
import { readFileSync as readFileSync20 } from "fs";
|
|
12297
|
-
import { isAbsolute as isAbsolute4, join as
|
|
12110
|
+
import { isAbsolute as isAbsolute4, join as join14, relative as relative4 } from "path";
|
|
12298
12111
|
|
|
12299
12112
|
// src/plan/risk/accounts.ts
|
|
12300
12113
|
var PUSH_REMOTE = "origin";
|
|
@@ -12836,9 +12649,9 @@ function toolsFinding(task, plan, seams) {
|
|
|
12836
12649
|
return null;
|
|
12837
12650
|
}
|
|
12838
12651
|
function displayPath(path2, seams) {
|
|
12839
|
-
const absolute = isAbsolute4(path2) ? path2 :
|
|
12652
|
+
const absolute = isAbsolute4(path2) ? path2 : join14(seams.repoRoot, path2);
|
|
12840
12653
|
const within = (root) => {
|
|
12841
|
-
const step =
|
|
12654
|
+
const step = relative4(root, absolute);
|
|
12842
12655
|
return step === "" || step.startsWith("..") || isAbsolute4(step) ? null : step;
|
|
12843
12656
|
};
|
|
12844
12657
|
const inRepo = within(seams.repoRoot);
|
|
@@ -12861,7 +12674,7 @@ function readText5(path2) {
|
|
|
12861
12674
|
function findSkillFile(name2, seams) {
|
|
12862
12675
|
const tiers = resolveSkillTiers({ home: seams.home, projectRoot: seams.repoRoot, entry: seams.entry });
|
|
12863
12676
|
for (const { dir } of tiers) {
|
|
12864
|
-
const path2 =
|
|
12677
|
+
const path2 = join14(dir, name2, "SKILL.md");
|
|
12865
12678
|
if (readText5(path2) !== null)
|
|
12866
12679
|
return path2;
|
|
12867
12680
|
}
|
|
@@ -12887,8 +12700,8 @@ function commandFindings(plan, display, tasks, seams) {
|
|
|
12887
12700
|
if (read.has(file))
|
|
12888
12701
|
continue;
|
|
12889
12702
|
read.add(file);
|
|
12890
|
-
const
|
|
12891
|
-
found.push(...readBodyCommands(readText5(file) ?? "", paths).map((finding2) => located(finding2,
|
|
12703
|
+
const shown = displayPath(file, seams);
|
|
12704
|
+
found.push(...readBodyCommands(readText5(file) ?? "", paths).map((finding2) => located(finding2, shown)));
|
|
12892
12705
|
}
|
|
12893
12706
|
}
|
|
12894
12707
|
return found;
|
|
@@ -12981,10 +12794,10 @@ async function announceRiskTotal(input, seams = {}) {
|
|
|
12981
12794
|
|
|
12982
12795
|
// src/start/session.ts
|
|
12983
12796
|
import { randomUUID as randomUUID8 } from "crypto";
|
|
12984
|
-
import { relative as
|
|
12797
|
+
import { relative as relative5 } from "path";
|
|
12985
12798
|
var NOTHING_DISPATCHED = " Nothing was checked and nothing was dispatched.";
|
|
12986
12799
|
function recordLabel(repoRoot, sessionId) {
|
|
12987
|
-
return
|
|
12800
|
+
return relative5(repoRoot, sessionFilePath(repoRoot, sessionId));
|
|
12988
12801
|
}
|
|
12989
12802
|
function conflictLine(repoRoot, conflict) {
|
|
12990
12803
|
const { record } = conflict;
|
|
@@ -13014,7 +12827,7 @@ function openRefusal(repoRoot, draft, error) {
|
|
|
13014
12827
|
if (!(error instanceof SessionRecordError) && !isSystemError)
|
|
13015
12828
|
return null;
|
|
13016
12829
|
return [
|
|
13017
|
-
`\u274C Refusing to start: the session records under ${
|
|
12830
|
+
`\u274C Refusing to start: the session records under ${relative5(repoRoot, runsDir(repoRoot))}/ cannot be read or written.`,
|
|
13018
12831
|
` ${messageOf(error)}`,
|
|
13019
12832
|
NOTHING_DISPATCHED
|
|
13020
12833
|
].join(`
|
|
@@ -13050,7 +12863,7 @@ function openRunSession(options) {
|
|
|
13050
12863
|
const draft = {
|
|
13051
12864
|
sessionId: (seams.newSessionId ?? randomUUID8)(),
|
|
13052
12865
|
planStub: options.planStub,
|
|
13053
|
-
plan:
|
|
12866
|
+
plan: relative5(repoRoot, options.planPath),
|
|
13054
12867
|
branch: options.branch,
|
|
13055
12868
|
pid: seams.pid ?? process.pid,
|
|
13056
12869
|
startedAt: (seams.now ?? (() => new Date))().toISOString()
|
|
@@ -13121,17 +12934,17 @@ function announcePlanIssues(planContent) {
|
|
|
13121
12934
|
const output = activeOutput();
|
|
13122
12935
|
output.warn(`
|
|
13123
12936
|
\u26A0\uFE0F The plan holds ${issues.length} part(s) the loop does not read as written:`);
|
|
13124
|
-
for (const
|
|
13125
|
-
output.warn(` line ${
|
|
12937
|
+
for (const issue3 of issues)
|
|
12938
|
+
output.warn(` line ${issue3.line}: ${issue3.text}`);
|
|
13126
12939
|
return issues;
|
|
13127
12940
|
}
|
|
13128
12941
|
|
|
13129
12942
|
// src/start/runtime.ts
|
|
13130
|
-
import { existsSync as existsSync18, readdirSync as
|
|
12943
|
+
import { existsSync as existsSync18, readdirSync as readdirSync6, realpathSync as realpathSync3, statSync as statSync10 } from "fs";
|
|
13131
12944
|
import { homedir as homedir5 } from "os";
|
|
13132
|
-
import { isAbsolute as isAbsolute5, join as
|
|
12945
|
+
import { isAbsolute as isAbsolute5, join as join15, relative as relative6, resolve as resolve11, sep } from "path";
|
|
13133
12946
|
var RUNTIME_FLAG = "--runtime";
|
|
13134
|
-
var RUNTIME_SUBDIR =
|
|
12947
|
+
var RUNTIME_SUBDIR = join15(".rafa", "runtime");
|
|
13135
12948
|
var RUNTIME_ENTRY = "cli.js";
|
|
13136
12949
|
var SOURCE_DIR = "src";
|
|
13137
12950
|
var RUNTIME_COMMAND = ["start"];
|
|
@@ -13158,7 +12971,7 @@ function resolveRuntime(value, places) {
|
|
|
13158
12971
|
throw refusal3([`\u274C Refusing to start: \`--runtime\` names no runtime. Spell it ${SPELLING}.`]);
|
|
13159
12972
|
}
|
|
13160
12973
|
const isVersion = VERSION_SHAPE2.test(value);
|
|
13161
|
-
const named = isVersion ?
|
|
12974
|
+
const named = isVersion ? join15(places.home, RUNTIME_SUBDIR, value, RUNTIME_ENTRY) : resolve11(places.cwd, value);
|
|
13162
12975
|
refuseInsideSource(value, named, places);
|
|
13163
12976
|
const entry = entryOf(value, named, isVersion, places.home);
|
|
13164
12977
|
const real = realpathSync3(entry);
|
|
@@ -13169,7 +12982,7 @@ function entryOf(value, named, isVersion, home) {
|
|
|
13169
12982
|
if (isVersion) {
|
|
13170
12983
|
if (isFile2(named))
|
|
13171
12984
|
return named;
|
|
13172
|
-
const root =
|
|
12985
|
+
const root = join15(home, RUNTIME_SUBDIR);
|
|
13173
12986
|
const installed = installedVersions(root);
|
|
13174
12987
|
throw refusal3([
|
|
13175
12988
|
`\u274C Refusing to start: \`--runtime=${value}\` names no installed runtime: ${named} is no file.`,
|
|
@@ -13178,7 +12991,7 @@ function entryOf(value, named, isVersion, home) {
|
|
|
13178
12991
|
}
|
|
13179
12992
|
if (isFile2(named))
|
|
13180
12993
|
return named;
|
|
13181
|
-
const inside =
|
|
12994
|
+
const inside = join15(named, RUNTIME_ENTRY);
|
|
13182
12995
|
if (isDirectory(named) && isFile2(inside))
|
|
13183
12996
|
return inside;
|
|
13184
12997
|
throw refusal3([
|
|
@@ -13196,31 +13009,31 @@ function refuseInsideSource(value, path2, places) {
|
|
|
13196
13009
|
]);
|
|
13197
13010
|
}
|
|
13198
13011
|
function sourceDirs({ cwd, root }) {
|
|
13199
|
-
const dirs = [
|
|
13012
|
+
const dirs = [join15(cwd, SOURCE_DIR), join15(root, SOURCE_DIR)];
|
|
13200
13013
|
const real = dirs.filter((dir) => existsSync18(dir)).map((dir) => realpathSync3(dir));
|
|
13201
13014
|
return [...new Set([...dirs, ...real])];
|
|
13202
13015
|
}
|
|
13203
13016
|
function isInside2(path2, dir) {
|
|
13204
|
-
const rel =
|
|
13017
|
+
const rel = relative6(dir, path2);
|
|
13205
13018
|
return rel === "" || !isAbsolute5(rel) && rel !== ".." && !rel.startsWith(`..${sep}`);
|
|
13206
13019
|
}
|
|
13207
13020
|
function installedVersions(root) {
|
|
13208
13021
|
try {
|
|
13209
|
-
return
|
|
13022
|
+
return readdirSync6(root).filter((name2) => isFile2(join15(root, name2, RUNTIME_ENTRY))).sort((a, b) => a.localeCompare(b));
|
|
13210
13023
|
} catch {
|
|
13211
13024
|
return [];
|
|
13212
13025
|
}
|
|
13213
13026
|
}
|
|
13214
13027
|
function isFile2(path2) {
|
|
13215
13028
|
try {
|
|
13216
|
-
return
|
|
13029
|
+
return statSync10(path2).isFile();
|
|
13217
13030
|
} catch {
|
|
13218
13031
|
return false;
|
|
13219
13032
|
}
|
|
13220
13033
|
}
|
|
13221
13034
|
function isDirectory(path2) {
|
|
13222
13035
|
try {
|
|
13223
|
-
return
|
|
13036
|
+
return statSync10(path2).isDirectory();
|
|
13224
13037
|
} catch {
|
|
13225
13038
|
return false;
|
|
13226
13039
|
}
|
|
@@ -13337,7 +13150,7 @@ function eventOf(line) {
|
|
|
13337
13150
|
}
|
|
13338
13151
|
|
|
13339
13152
|
// src/triage/triage.ts
|
|
13340
|
-
import { basename as basename8, join as
|
|
13153
|
+
import { basename as basename8, join as join16 } from "path";
|
|
13341
13154
|
|
|
13342
13155
|
// src/effort/store/tracker-refs.ts
|
|
13343
13156
|
import { randomUUID as randomUUID9 } from "crypto";
|
|
@@ -13365,7 +13178,7 @@ var REFUSAL_TAILS = { write: "nothing written", read: "nothing opened" };
|
|
|
13365
13178
|
function refused(call, reason) {
|
|
13366
13179
|
return new Error(`effort store: tracker ref ${call} ${reason}; ${REFUSAL_TAILS[call]}`);
|
|
13367
13180
|
}
|
|
13368
|
-
function
|
|
13181
|
+
function checkArtifact(call, artifact) {
|
|
13369
13182
|
if (artifact === null || artifact === undefined) {
|
|
13370
13183
|
throw refused(call, "has no artifact, the key a reference is kept under");
|
|
13371
13184
|
}
|
|
@@ -13446,7 +13259,7 @@ function placeRef(db, path2, write, wanted, insertValues) {
|
|
|
13446
13259
|
}
|
|
13447
13260
|
function writeTrackerRef(repoRoot, write, seams = {}) {
|
|
13448
13261
|
checkDispatch("tracker ref", write.dispatch, write.outcome);
|
|
13449
|
-
|
|
13262
|
+
checkArtifact("write", write.artifact);
|
|
13450
13263
|
const wanted = storedRefOf(write.ref);
|
|
13451
13264
|
if (typeof wanted === "string")
|
|
13452
13265
|
throw refused("write", `has a reference ${wanted}`);
|
|
@@ -13471,7 +13284,7 @@ function writeTrackerRef(repoRoot, write, seams = {}) {
|
|
|
13471
13284
|
return { path: path2, action, stored };
|
|
13472
13285
|
}
|
|
13473
13286
|
function readTrackerRef(repoRoot, artifact) {
|
|
13474
|
-
|
|
13287
|
+
checkArtifact("read", artifact);
|
|
13475
13288
|
const path2 = sqliteStorePath(repoRoot);
|
|
13476
13289
|
if (!existsSync19(path2))
|
|
13477
13290
|
return null;
|
|
@@ -13519,7 +13332,7 @@ var FOREIGN_PROGRAMS = new Set([
|
|
|
13519
13332
|
"yarn"
|
|
13520
13333
|
]);
|
|
13521
13334
|
var CONFIG_SECTIONS = new Set(Object.values(SETTINGS).map((setting) => setting.key).filter((key) => key.includes(".")).map((key) => key.slice(0, key.indexOf("."))));
|
|
13522
|
-
var
|
|
13335
|
+
var FENCE_LINE5 = /^ {0,3}(`{3,}|~{3,})(.*)$/u;
|
|
13523
13336
|
var QUOTE_LINE = /^ {0,3}>/u;
|
|
13524
13337
|
var CODE_SPAN4 = /(?<!`)(`+)(?!`)(.+?)(?<!`)\1(?!`)/gu;
|
|
13525
13338
|
var CROSS_ISSUE = /(?<![\w./-])[A-Za-z0-9][A-Za-z0-9-]*\/[A-Za-z0-9._-]+#\d+(?!\w)/gu;
|
|
@@ -13532,7 +13345,7 @@ var KEY_SHAPE = /^[a-z]+\.[a-z][A-Za-z0-9]*$/u;
|
|
|
13532
13345
|
var IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/u;
|
|
13533
13346
|
var UPPER_SNAKE = /^[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+$/u;
|
|
13534
13347
|
function openingFence(line) {
|
|
13535
|
-
const found =
|
|
13348
|
+
const found = FENCE_LINE5.exec(line);
|
|
13536
13349
|
if (found === null)
|
|
13537
13350
|
return null;
|
|
13538
13351
|
const run = found[1] ?? "";
|
|
@@ -13542,7 +13355,7 @@ function openingFence(line) {
|
|
|
13542
13355
|
return { char: run.charAt(0), length: run.length };
|
|
13543
13356
|
}
|
|
13544
13357
|
function closesFence3(line, fence) {
|
|
13545
|
-
const found =
|
|
13358
|
+
const found = FENCE_LINE5.exec(line);
|
|
13546
13359
|
if (found === null)
|
|
13547
13360
|
return false;
|
|
13548
13361
|
const run = found[1] ?? "";
|
|
@@ -13968,7 +13781,7 @@ function createArtifactRefsVerifier(repoRoot, env) {
|
|
|
13968
13781
|
}
|
|
13969
13782
|
|
|
13970
13783
|
// src/triage/triage.ts
|
|
13971
|
-
var PRIVATE_TRIAGE_DIR =
|
|
13784
|
+
var PRIVATE_TRIAGE_DIR = join16(".rafa", "triage", "private");
|
|
13972
13785
|
var SECRET_ENV_NAMES = ["GITHUB_TOKEN", "ANTHROPIC_API_KEY", "LINEAR_API_KEY"];
|
|
13973
13786
|
var TRIAGE_MODULE = "unassigned";
|
|
13974
13787
|
var BLOCKER_SEPARATOR = "; ";
|
|
@@ -13979,7 +13792,7 @@ var MIN_FENCE_LENGTH = 3;
|
|
|
13979
13792
|
var ISSUE_OPENING = "An out-of-scope bug a rafa task session reported. The loop filed it and" + " dispatches no task for it: a plan that wants it fixed declares a task.";
|
|
13980
13793
|
var COMMENT_OPENING = "Reported again by a rafa task session.";
|
|
13981
13794
|
function privateTriageDir(repoRoot) {
|
|
13982
|
-
return
|
|
13795
|
+
return join16(repoRoot, PRIVATE_TRIAGE_DIR);
|
|
13983
13796
|
}
|
|
13984
13797
|
function createPrivateTriageTracker(repoRoot, options = {}) {
|
|
13985
13798
|
return createLocalTracker({
|
|
@@ -14062,10 +13875,10 @@ function issueTitle(redactedWhat) {
|
|
|
14062
13875
|
return `${kept.trimEnd()}${TITLE_CUT}`;
|
|
14063
13876
|
}
|
|
14064
13877
|
function section(heading, value, absent, redact) {
|
|
14065
|
-
const
|
|
13878
|
+
const shown = value === null ? absent : fenced(redact(value));
|
|
14066
13879
|
return `## ${heading}
|
|
14067
13880
|
|
|
14068
|
-
${
|
|
13881
|
+
${shown}`;
|
|
14069
13882
|
}
|
|
14070
13883
|
function issueText(opening, values, redact) {
|
|
14071
13884
|
return [
|
|
@@ -14262,10 +14075,10 @@ function bugLines(bug) {
|
|
|
14262
14075
|
const warning = bug.ref === null ? `${name2}: not filed on ${where}: ${bug.problem}` : `${name2}: recurs in ${issueName2(bug.ref)} on ${where}, not commented on: ${bug.problem}`;
|
|
14263
14076
|
return { notes: [], warnings: [warning] };
|
|
14264
14077
|
}
|
|
14265
|
-
const
|
|
14266
|
-
const note = bug.action === "filed" ? `${name2}: filed on ${where} as ${
|
|
14078
|
+
const issue3 = bug.ref === null ? "an issue" : issueName2(bug.ref);
|
|
14079
|
+
const note = bug.action === "filed" ? `${name2}: filed on ${where} as ${issue3}.` : `${name2}: recurs in ${issue3} on ${where}, found by ${FOUND_BY[bug.foundBy ?? "find"]}; commented on it.`;
|
|
14267
14080
|
const problem = bug.problem === null ? [] : [`${name2}: ${bug.problem}`];
|
|
14268
|
-
const conflict = bug.stored === "conflict" ? [`${name2}: the store keeps another reference for its artifact, in place of ${
|
|
14081
|
+
const conflict = bug.stored === "conflict" ? [`${name2}: the store keeps another reference for its artifact, in place of ${issue3}.`] : [];
|
|
14269
14082
|
return { notes: [note], warnings: [...problem, ...conflict] };
|
|
14270
14083
|
}
|
|
14271
14084
|
function describeStartTriage(result) {
|
|
@@ -14351,14 +14164,231 @@ function mechanicalConflictBullet() {
|
|
|
14351
14164
|
return `* ${MECHANICAL_CONFLICT_SENTENCE}`;
|
|
14352
14165
|
}
|
|
14353
14166
|
|
|
14167
|
+
// src/start/promoted-check.ts
|
|
14168
|
+
import { isAbsolute as isAbsolute6, posix as posix2, relative as relative7 } from "path";
|
|
14169
|
+
|
|
14170
|
+
// src/start/promoted.ts
|
|
14171
|
+
var PROMOTED_BLOCK_KIND = "promoted";
|
|
14172
|
+
var ARROW = /\u2192|->/;
|
|
14173
|
+
var SKIPPED = /^skipped:/i;
|
|
14174
|
+
var LIST_MARKER = /^[-*]\s+/;
|
|
14175
|
+
var WHITESPACE2 = /\s/;
|
|
14176
|
+
function unquoted(text) {
|
|
14177
|
+
const trimmed = text.trim();
|
|
14178
|
+
return trimmed.length >= 2 && trimmed.startsWith("`") && trimmed.endsWith("`") ? trimmed.slice(1, -1).trim() : trimmed;
|
|
14179
|
+
}
|
|
14180
|
+
function wordProblem(word, what) {
|
|
14181
|
+
if (word === "")
|
|
14182
|
+
return `it names no ${what}`;
|
|
14183
|
+
if (WHITESPACE2.test(word))
|
|
14184
|
+
return `its ${what} \`${word}\` holds whitespace`;
|
|
14185
|
+
return null;
|
|
14186
|
+
}
|
|
14187
|
+
function readLine2(text, line) {
|
|
14188
|
+
const content = text.trim().replace(LIST_MARKER, "");
|
|
14189
|
+
const arrow = ARROW.exec(content);
|
|
14190
|
+
if (arrow === null)
|
|
14191
|
+
return { reason: "it holds no `\u2192` or `->`" };
|
|
14192
|
+
const id = unquoted(content.slice(0, arrow.index));
|
|
14193
|
+
const idProblem = wordProblem(id, "id");
|
|
14194
|
+
if (idProblem !== null)
|
|
14195
|
+
return { reason: idProblem };
|
|
14196
|
+
const right = content.slice(arrow.index + arrow[0].length).trim();
|
|
14197
|
+
if (SKIPPED.test(right)) {
|
|
14198
|
+
const reason = right.replace(SKIPPED, "").trim();
|
|
14199
|
+
return reason === "" ? { reason: `it skips \`${id}\` without a reason` } : { answer: { kind: "skipped", id, reason, line } };
|
|
14200
|
+
}
|
|
14201
|
+
const path2 = unquoted(right);
|
|
14202
|
+
const pathProblem = wordProblem(path2, "path");
|
|
14203
|
+
return pathProblem === null ? { answer: { kind: "promoted", id, path: path2, line } } : { reason: pathProblem };
|
|
14204
|
+
}
|
|
14205
|
+
function parsePromoted(output) {
|
|
14206
|
+
const block = readRafaBlocks(output).filter((found) => found.kind === PROMOTED_BLOCK_KIND).at(-1);
|
|
14207
|
+
if (block === undefined)
|
|
14208
|
+
return { status: "absent", answers: [], unreadable: [] };
|
|
14209
|
+
if (!block.closed)
|
|
14210
|
+
return { status: "unclosed", answers: [], unreadable: [] };
|
|
14211
|
+
const answers = [];
|
|
14212
|
+
const unreadable = [];
|
|
14213
|
+
const firstLine2 = new Map;
|
|
14214
|
+
block.body.split(`
|
|
14215
|
+
`).forEach((text, index) => {
|
|
14216
|
+
if (text.trim() === "")
|
|
14217
|
+
return;
|
|
14218
|
+
const line = block.span.first + 1 + index;
|
|
14219
|
+
const reading2 = readLine2(text, line);
|
|
14220
|
+
if ("reason" in reading2) {
|
|
14221
|
+
unreadable.push({ line, text, reason: reading2.reason });
|
|
14222
|
+
return;
|
|
14223
|
+
}
|
|
14224
|
+
const { id } = reading2.answer;
|
|
14225
|
+
const earlier = firstLine2.get(id);
|
|
14226
|
+
if (earlier !== undefined) {
|
|
14227
|
+
const reason = `\`${id}\` was already answered at line ${String(earlier)}`;
|
|
14228
|
+
unreadable.push({ line, text, reason });
|
|
14229
|
+
return;
|
|
14230
|
+
}
|
|
14231
|
+
firstLine2.set(id, line);
|
|
14232
|
+
answers.push(reading2.answer);
|
|
14233
|
+
});
|
|
14234
|
+
return { status: "read", answers, unreadable };
|
|
14235
|
+
}
|
|
14236
|
+
|
|
14237
|
+
// src/start/promoted-check.ts
|
|
14238
|
+
function saidClause2(result) {
|
|
14239
|
+
const said = gitSaid(result).split(`
|
|
14240
|
+
`)[0] ?? "";
|
|
14241
|
+
return said === "" ? "and git wrote nothing" : `git said: ${said}`;
|
|
14242
|
+
}
|
|
14243
|
+
function readHead(git) {
|
|
14244
|
+
const result = git(["rev-parse", "--verify", "HEAD"]);
|
|
14245
|
+
const head = result.stdout.trim();
|
|
14246
|
+
return result.ok && head !== "" ? head : null;
|
|
14247
|
+
}
|
|
14248
|
+
function changedSince(git, head) {
|
|
14249
|
+
if (head === null)
|
|
14250
|
+
return { ok: false, reason: "the HEAD before the session could not be read" };
|
|
14251
|
+
const result = git(["diff", "--name-only", "-z", head, "--"]);
|
|
14252
|
+
if (!result.ok) {
|
|
14253
|
+
return { ok: false, reason: `the changes since ${head.slice(0, 7)} could not be read, ${saidClause2(result)}` };
|
|
14254
|
+
}
|
|
14255
|
+
const paths = result.stdout.split("\x00").filter((path2) => path2 !== "");
|
|
14256
|
+
return { ok: true, head, paths: new Set(paths) };
|
|
14257
|
+
}
|
|
14258
|
+
function repoPath(path2, repoRoot) {
|
|
14259
|
+
const inside = isAbsolute6(path2) ? relative7(repoRoot, path2) : path2;
|
|
14260
|
+
const normal = posix2.normalize(inside.replaceAll("\\", "/"));
|
|
14261
|
+
if (normal === "." || normal === ".." || normal.startsWith("../") || isAbsolute6(normal))
|
|
14262
|
+
return null;
|
|
14263
|
+
return normal.replace(/^(\.\/)+/, "");
|
|
14264
|
+
}
|
|
14265
|
+
function promotedOutcome(lesson, named, changed, repoRoot) {
|
|
14266
|
+
if (!changed.ok)
|
|
14267
|
+
return { kind: "unchecked", id: lesson.id, path: named, reason: changed.reason };
|
|
14268
|
+
const path2 = repoPath(named, repoRoot);
|
|
14269
|
+
return path2 !== null && changed.paths.has(path2) ? { lesson, path: path2 } : { kind: "unchanged", id: lesson.id, path: named };
|
|
14270
|
+
}
|
|
14271
|
+
function checkPromoted(lessons, reading2, changed, repoRoot) {
|
|
14272
|
+
const answers = new Map(reading2.answers.map((answer2) => [answer2.id, answer2]));
|
|
14273
|
+
const listed = new Set(lessons.map((lesson) => lesson.id));
|
|
14274
|
+
const promoted = [];
|
|
14275
|
+
const skipped2 = [];
|
|
14276
|
+
const unpromoted = [];
|
|
14277
|
+
for (const lesson of lessons) {
|
|
14278
|
+
const answer2 = answers.get(lesson.id);
|
|
14279
|
+
if (answer2 === undefined) {
|
|
14280
|
+
unpromoted.push({ kind: "unanswered", id: lesson.id });
|
|
14281
|
+
} else if (answer2.kind === "skipped") {
|
|
14282
|
+
skipped2.push(answer2);
|
|
14283
|
+
} else {
|
|
14284
|
+
const outcome = promotedOutcome(lesson, answer2.path, changed, repoRoot);
|
|
14285
|
+
if ("lesson" in outcome)
|
|
14286
|
+
promoted.push(outcome);
|
|
14287
|
+
else
|
|
14288
|
+
unpromoted.push(outcome);
|
|
14289
|
+
}
|
|
14290
|
+
}
|
|
14291
|
+
const unlisted = reading2.answers.map((answer2) => answer2.id).filter((id) => !listed.has(id));
|
|
14292
|
+
return { promoted, skipped: skipped2, unpromoted, unlisted };
|
|
14293
|
+
}
|
|
14294
|
+
function unpromotedPhrase(lesson, head) {
|
|
14295
|
+
if (lesson.kind === "unanswered")
|
|
14296
|
+
return `\`${lesson.id}\` (no answer in the \`rafa:promoted\` block)`;
|
|
14297
|
+
if (lesson.kind === "unchecked")
|
|
14298
|
+
return `\`${lesson.id}\` (names \`${lesson.path}\`, which could not be checked: ${lesson.reason})`;
|
|
14299
|
+
const since = head === null ? "before the session" : head.slice(0, 7);
|
|
14300
|
+
return `\`${lesson.id}\` (names \`${lesson.path}\`, which has not changed since ${since})`;
|
|
14301
|
+
}
|
|
14302
|
+
function unpromotedLine(check, head) {
|
|
14303
|
+
if (check.unpromoted.length === 0)
|
|
14304
|
+
return null;
|
|
14305
|
+
const named = check.unpromoted.map((lesson) => unpromotedPhrase(lesson, head)).join("; ");
|
|
14306
|
+
return `Lessons listed for promotion that this pull request does not carry: ${named}.`;
|
|
14307
|
+
}
|
|
14308
|
+
async function recordPromotions(learning, promoted) {
|
|
14309
|
+
const problems = [];
|
|
14310
|
+
for (const { lesson, path: path2 } of promoted) {
|
|
14311
|
+
const sourceId = lesson.sources?.[0] ?? lesson.id;
|
|
14312
|
+
try {
|
|
14313
|
+
await learning.push({ source_id: sourceId, instincts: [{ ...lesson, promoted_to: path2 }] });
|
|
14314
|
+
} catch (error) {
|
|
14315
|
+
problems.push(`\`${lesson.id}\` is in ${path2}, but its promoted_to could not be recorded: ${messageOf(error)}`);
|
|
14316
|
+
}
|
|
14317
|
+
}
|
|
14318
|
+
return problems;
|
|
14319
|
+
}
|
|
14320
|
+
async function reportUnpromoted(pulls, branch, line) {
|
|
14321
|
+
try {
|
|
14322
|
+
const found = await pulls.findOpen(branch);
|
|
14323
|
+
if (found === null)
|
|
14324
|
+
return { written: false, problem: `no open pull request was found for ${branch}` };
|
|
14325
|
+
const detail = await pulls.get(found.number);
|
|
14326
|
+
if (detail === null)
|
|
14327
|
+
return { written: false, problem: `pull request #${found.number} could not be read back` };
|
|
14328
|
+
if (detail.body.includes(line))
|
|
14329
|
+
return { written: true, number: found.number, already: true };
|
|
14330
|
+
await pulls.editBody(found.number, bodyWithSentence(detail.body, line));
|
|
14331
|
+
return { written: true, number: found.number, already: false };
|
|
14332
|
+
} catch (error) {
|
|
14333
|
+
return { written: false, problem: `the pull request body could not be written: ${messageOf(error)}` };
|
|
14334
|
+
}
|
|
14335
|
+
}
|
|
14336
|
+
function warnAboutBlock(reading2, check) {
|
|
14337
|
+
const out = activeOutput();
|
|
14338
|
+
if (reading2.status === "absent")
|
|
14339
|
+
out.warn(" The wrap-up session wrote no `rafa:promoted` block.");
|
|
14340
|
+
if (reading2.status === "unclosed")
|
|
14341
|
+
out.warn(" The wrap-up session's `rafa:promoted` block is never closed, so none of it was read.");
|
|
14342
|
+
for (const line of reading2.unreadable)
|
|
14343
|
+
out.warn(` \`rafa:promoted\` line ${line.line} was not read: ${line.reason}.`);
|
|
14344
|
+
for (const id of check.unlisted)
|
|
14345
|
+
out.warn(` The \`rafa:promoted\` block answers \`${id}\`, which the wrap-up did not list.`);
|
|
14346
|
+
}
|
|
14347
|
+
async function recordConfirmed(input, check) {
|
|
14348
|
+
const out = activeOutput();
|
|
14349
|
+
if (check.promoted.length === 0)
|
|
14350
|
+
return;
|
|
14351
|
+
let learning;
|
|
14352
|
+
try {
|
|
14353
|
+
learning = input.learning();
|
|
14354
|
+
} catch (error) {
|
|
14355
|
+
out.warn(` No lesson's promoted_to was recorded: the learning adapter could not be made: ${messageOf(error)}`);
|
|
14356
|
+
return;
|
|
14357
|
+
}
|
|
14358
|
+
for (const problem of await recordPromotions(learning, check.promoted))
|
|
14359
|
+
out.warn(` ${problem}`);
|
|
14360
|
+
for (const { lesson, path: path2 } of check.promoted)
|
|
14361
|
+
out.info(` Lesson \`${lesson.id}\` promoted to ${path2}.`);
|
|
14362
|
+
}
|
|
14363
|
+
async function checkWrapUpAnswer(input) {
|
|
14364
|
+
const out = activeOutput();
|
|
14365
|
+
const reading2 = parsePromoted(input.output);
|
|
14366
|
+
const changed = changedSince(input.git, input.head);
|
|
14367
|
+
const check = checkPromoted(input.lessons, reading2, changed, input.repoRoot);
|
|
14368
|
+
warnAboutBlock(reading2, check);
|
|
14369
|
+
if (!changed.ok && reading2.answers.some((answer2) => answer2.kind === "promoted")) {
|
|
14370
|
+
out.warn(` No promoted path could be checked: ${changed.reason}.`);
|
|
14371
|
+
}
|
|
14372
|
+
await recordConfirmed(input, check);
|
|
14373
|
+
const line = unpromotedLine(check, input.head);
|
|
14374
|
+
if (line === null)
|
|
14375
|
+
return check;
|
|
14376
|
+
const report2 = await reportUnpromoted(input.pulls, input.branch, line);
|
|
14377
|
+
if (!report2.written) {
|
|
14378
|
+
out.warn(` This line is not in the pull request body, as ${report2.problem}: ${line}`);
|
|
14379
|
+
} else if (report2.already) {
|
|
14380
|
+
out.info(` Pull request #${report2.number} already names the lessons it does not carry.`);
|
|
14381
|
+
} else {
|
|
14382
|
+
out.info(` Pull request #${report2.number} now names the lessons it does not carry.`);
|
|
14383
|
+
}
|
|
14384
|
+
return check;
|
|
14385
|
+
}
|
|
14386
|
+
|
|
14354
14387
|
// src/start/wrap-up.ts
|
|
14355
|
-
function buildWrapUpPrompt(branch, planContent, openPullRequest = null, release = null) {
|
|
14388
|
+
function buildWrapUpPrompt(branch, planContent, openPullRequest = null, release = null, lessons = []) {
|
|
14356
14389
|
return [
|
|
14357
14390
|
"* Read `@progress.txt` in full.",
|
|
14358
14391
|
"* `@progress.txt` above names the file `progress.txt` at the repo root; the `@` is a reference marker, never part of a path. Do not create or write a file whose name starts with `@`.",
|
|
14359
|
-
"* If there's anything worth keeping, take what's generally relevant from that file into the `context/` page that owns its subject, `README.md` or a pertinent skill under `.claude/skills/`. The root `AGENTS.md` is a capped map read into every turn of every session: point at the page from there if a new one is needed, never inline the finding itself.",
|
|
14360
|
-
"* Promote a finding ONLY when all three hold, and delete or keep it rather than promoting it when any one fails. It is PROJECT-SPECIFIC \u2014 a fact about THIS tree (its layout, its gates, its conventions, what a command here actually answers) and not a general technique, which belongs in a skill and not in this repo's docs. It is NOT ALREADY COVERED by a skill under `.claude/skills/` \u2014 read the skill that matches the finding's subject before writing anything, and extend that skill in place rather than restating it in a second document. And it NAMES WHAT IT REPLACES \u2014 the sentence, bullet or table row it supersedes, deleted in the SAME edit \u2014 or, when it replaces nothing, says so. A promotion landing beside the claim it should have replaced leaves two authorities on one subject, and nothing here compares two documents, so the stale one is never reported again.",
|
|
14361
|
-
"* If a learn/learn-eval skill is available in this session, invoke it now so reusable patterns from this run are persisted as skills.",
|
|
14362
14392
|
"* Find the plan's issue number `<n>`: the plan below carries it in its `rafa:plan` block as `issue: <n>`.",
|
|
14363
14393
|
`* If the plan carries no \`issue:\` field, read the number from the branch name (${branch}), which is spelled \`feat/rafa-<n>-<slug>\`.`,
|
|
14364
14394
|
'* Title the PR `rafa-<n>: <title>`, taking `<title>` from the plan title, e.g. "rafa-20: Add pull-request commands". Open the PR body with `Closes #<n>` \u2014 the GitHub issue number on its own, never `#rafa-<n>`, since the `rafa-` prefix is this project\'s naming convention and not a GitHub alias. If no number was found, title the PR with the plan title alone and write no closing line rather than inventing one.',
|
|
@@ -14370,6 +14400,7 @@ function buildWrapUpPrompt(branch, planContent, openPullRequest = null, release
|
|
|
14370
14400
|
`* Commit these changes and push them to the CURRENT branch (${branch}). Never create a branch here: the work under review is this branch's, and a second branch splits one plan across two reviews.`,
|
|
14371
14401
|
pullRequestStep(branch, openPullRequest),
|
|
14372
14402
|
"* Do not include Claude attribution in the commit or PR message.",
|
|
14403
|
+
...lessonsSection(lessons),
|
|
14373
14404
|
"",
|
|
14374
14405
|
"The plan this run executed follows, in full.",
|
|
14375
14406
|
"",
|
|
@@ -14377,6 +14408,39 @@ function buildWrapUpPrompt(branch, planContent, openPullRequest = null, release
|
|
|
14377
14408
|
].join(`
|
|
14378
14409
|
`);
|
|
14379
14410
|
}
|
|
14411
|
+
function oneLine4(text) {
|
|
14412
|
+
return text.trim().replace(/\s+/g, " ");
|
|
14413
|
+
}
|
|
14414
|
+
function lessonLines(lesson) {
|
|
14415
|
+
const artifact = lesson.artifact === undefined || oneLine4(lesson.artifact) === "" ? "none" : oneLine4(lesson.artifact);
|
|
14416
|
+
return [
|
|
14417
|
+
`- \`${lesson.id}\``,
|
|
14418
|
+
` - trigger: ${oneLine4(lesson.trigger)}`,
|
|
14419
|
+
` - action: ${oneLine4(lesson.action)}`,
|
|
14420
|
+
` - artifact: ${artifact}`
|
|
14421
|
+
];
|
|
14422
|
+
}
|
|
14423
|
+
function lessonsSection(lessons) {
|
|
14424
|
+
if (lessons.length === 0)
|
|
14425
|
+
return [];
|
|
14426
|
+
return [
|
|
14427
|
+
"",
|
|
14428
|
+
"## Lessons to promote",
|
|
14429
|
+
"",
|
|
14430
|
+
"These lessons recurred across enough task sessions of this project, at a high enough confidence, to belong in its tracked docs. Handle them BEFORE the commit and push above, so the pages you write are in that commit.",
|
|
14431
|
+
"",
|
|
14432
|
+
...lessons.flatMap(lessonLines),
|
|
14433
|
+
"",
|
|
14434
|
+
"For each lesson, EITHER write it into the tracked page that owns its subject \u2014 a `context/` page, `README.md`, or a skill under `.claude/skills/` \u2014 replacing in the same edit any sentence, bullet or table row it supersedes, OR leave it out and say why. The root `AGENTS.md` is a capped map read into every turn of every session: point at a new page from there if one is needed, never inline the lesson itself.",
|
|
14435
|
+
"",
|
|
14436
|
+
"Then answer every lesson listed above, and no other, in ONE `rafa:promoted` block in your final message: one line per lesson, its id, an arrow, and either the path you changed or `skipped:` and the reason. A promoted path must be a file this session changed, and a lesson the block does not answer is reported in the pull request body.",
|
|
14437
|
+
"",
|
|
14438
|
+
"```rafa:promoted",
|
|
14439
|
+
"<id> \u2192 <path>",
|
|
14440
|
+
"<id> \u2192 skipped: <reason>",
|
|
14441
|
+
"```"
|
|
14442
|
+
];
|
|
14443
|
+
}
|
|
14380
14444
|
function pullRequestStep(branch, openPullRequest) {
|
|
14381
14445
|
return openPullRequest === null ? `* No open PR was found for ${branch}: open one with \`gh pr create\`. If it answers that a PR already exists, push to that PR and update its body with \`gh pr edit\`; never open a second PR from a new branch.` : `* PR #${openPullRequest} is already open for ${branch}, likely opened by the plan's close-out: push to it and update its body with \`gh pr edit ${openPullRequest}\` so the description covers the promotions this session committed.`;
|
|
14382
14446
|
}
|
|
@@ -14421,20 +14485,61 @@ async function openPullRequestNumber(branch) {
|
|
|
14421
14485
|
return null;
|
|
14422
14486
|
}
|
|
14423
14487
|
}
|
|
14424
|
-
|
|
14488
|
+
function messageOf4(error) {
|
|
14489
|
+
return error instanceof Error ? error.message : String(error);
|
|
14490
|
+
}
|
|
14491
|
+
function wrapUpAdapter(learning) {
|
|
14492
|
+
const registry = learning.registry ?? CORE_ADAPTER_REGISTRY;
|
|
14493
|
+
return registry.resolve("learning", learning.kind).create({
|
|
14494
|
+
repoRoot: learning.repoRoot,
|
|
14495
|
+
home: learning.home,
|
|
14496
|
+
learningBlessMinConfidence: learning.blessMinConfidence
|
|
14497
|
+
});
|
|
14498
|
+
}
|
|
14499
|
+
async function lessonsToPromote(learning) {
|
|
14500
|
+
if (learning === null)
|
|
14501
|
+
return [];
|
|
14502
|
+
try {
|
|
14503
|
+
const bundle = await wrapUpAdapter(learning).pullBlessed();
|
|
14504
|
+
return promotable(bundle.instincts, {
|
|
14505
|
+
after: learning.promoteAfter,
|
|
14506
|
+
minConfidence: learning.promoteMinConfidence
|
|
14507
|
+
});
|
|
14508
|
+
} catch (error) {
|
|
14509
|
+
activeOutput().warn(` The wrap-up lists no lesson to promote: the \`${learning.kind}\` learning adapter answered no blessed set: ${messageOf4(error)}`);
|
|
14510
|
+
return [];
|
|
14511
|
+
}
|
|
14512
|
+
}
|
|
14513
|
+
async function preserveProgress(planContent, settingSources, release, serving, learning) {
|
|
14425
14514
|
const branch = getCurrentBranch();
|
|
14426
|
-
const
|
|
14515
|
+
const openPullRequest = await openPullRequestNumber(branch);
|
|
14516
|
+
const lessons = await lessonsToPromote(learning);
|
|
14517
|
+
const prompt = buildWrapUpPrompt(branch, planContent, openPullRequest, release, lessons);
|
|
14427
14518
|
const served = serving === null ? null : serveSession(serving);
|
|
14428
14519
|
for (const skipped2 of served?.skipped ?? [])
|
|
14429
14520
|
activeOutput().warn(` ${skipped2.message}`);
|
|
14430
|
-
const
|
|
14431
|
-
|
|
14521
|
+
const git = learning === null || lessons.length === 0 ? null : createGitRunner(learning.repoRoot);
|
|
14522
|
+
const head = git === null ? null : readHead(git);
|
|
14523
|
+
const session = await runClaudeCaptured(withStamp(prompt), settingSources, [], undefined, served?.flags ?? []);
|
|
14524
|
+
if (session.exitCode !== 0) {
|
|
14432
14525
|
activeOutput().error(`
|
|
14433
|
-
\u274C Failed to preserve progress (exit ${exitCode}). Please try again.`);
|
|
14434
|
-
|
|
14435
|
-
activeOutput().info(`
|
|
14436
|
-
\u2705 Progress preserved; PR opened or updated on this branch.`);
|
|
14526
|
+
\u274C Failed to preserve progress (exit ${session.exitCode}). Please try again.`);
|
|
14527
|
+
return;
|
|
14437
14528
|
}
|
|
14529
|
+
activeOutput().info(`
|
|
14530
|
+
\u2705 Progress preserved; PR opened or updated on this branch.`);
|
|
14531
|
+
if (learning === null || git === null)
|
|
14532
|
+
return;
|
|
14533
|
+
await checkWrapUpAnswer({
|
|
14534
|
+
lessons,
|
|
14535
|
+
output: session.stdout,
|
|
14536
|
+
head,
|
|
14537
|
+
repoRoot: learning.repoRoot,
|
|
14538
|
+
branch,
|
|
14539
|
+
git,
|
|
14540
|
+
pulls: ghPullRequestsIn(process.cwd()),
|
|
14541
|
+
learning: () => wrapUpAdapter(learning)
|
|
14542
|
+
});
|
|
14438
14543
|
}
|
|
14439
14544
|
|
|
14440
14545
|
// src/utils/schedule.ts
|
|
@@ -14593,6 +14698,17 @@ async function start(args, repoRoot) {
|
|
|
14593
14698
|
}
|
|
14594
14699
|
});
|
|
14595
14700
|
const serving = { root: repoRoot, run: session.id, home: homedir6(), settings: runConfig.config };
|
|
14701
|
+
const learning = {
|
|
14702
|
+
kind: runConfig.config.learningAdapter,
|
|
14703
|
+
home: homedir6(),
|
|
14704
|
+
blessMinConfidence: runConfig.config.learningBlessMinConfidence
|
|
14705
|
+
};
|
|
14706
|
+
const wrapUpLearning = {
|
|
14707
|
+
...learning,
|
|
14708
|
+
repoRoot,
|
|
14709
|
+
promoteAfter: runConfig.config.learningPromoteAfter,
|
|
14710
|
+
promoteMinConfidence: runConfig.config.learningPromoteMinConfidence
|
|
14711
|
+
};
|
|
14596
14712
|
const triageTask = createStartTriage({ repoRoot, config: runConfig.config });
|
|
14597
14713
|
if (!fs.existsSync(trackerPath)) {
|
|
14598
14714
|
activeOutput().info(`\uD83D\uDCCB Creating new plan tracker at ${path2.basename(trackerPath)}...`);
|
|
@@ -14626,7 +14742,7 @@ async function start(args, repoRoot) {
|
|
|
14626
14742
|
planStub: planStub2,
|
|
14627
14743
|
planContent
|
|
14628
14744
|
});
|
|
14629
|
-
await preserveProgress(planContent, settingSources, release, serving);
|
|
14745
|
+
await preserveProgress(planContent, settingSources, release, serving, wrapUpLearning);
|
|
14630
14746
|
await finishRelease({ repoRoot, preparation: release }, {
|
|
14631
14747
|
readProvider: () => resolvePrProvider({
|
|
14632
14748
|
configured: runConfig.config.prProvider ?? null,
|
|
@@ -14658,7 +14774,7 @@ async function start(args, repoRoot) {
|
|
|
14658
14774
|
});
|
|
14659
14775
|
const { exitCode } = dispatch;
|
|
14660
14776
|
const storeReport = async (outcome) => {
|
|
14661
|
-
const stored2 = storeTaskReport({ repoRoot, planStub: planStub2, dispatch, outcome });
|
|
14777
|
+
const stored2 = await storeTaskReport({ repoRoot, planStub: planStub2, dispatch, outcome, learning });
|
|
14662
14778
|
await triageTask({ trackerPath, lineNum: taskInfo.lineNum, planStub: planStub2, dispatch, outcome });
|
|
14663
14779
|
return stored2;
|
|
14664
14780
|
};
|
|
@@ -14766,9 +14882,9 @@ function recordMissingReview(repoRoot, planPath) {
|
|
|
14766
14882
|
return;
|
|
14767
14883
|
activeOutput().info(`\uD83D\uDD0D ${planPath} records ${REVIEW_MISSING_LINE}: the session returned no readable rafa:spec-review block.`);
|
|
14768
14884
|
}
|
|
14769
|
-
function recordPlanIssue(repoRoot, planPath,
|
|
14770
|
-
const line = issueFieldLine(
|
|
14771
|
-
if (!recordInPlan(repoRoot, planPath, (written) => stampPlanIssue(written,
|
|
14885
|
+
function recordPlanIssue(repoRoot, planPath, issue3) {
|
|
14886
|
+
const line = issueFieldLine(issue3);
|
|
14887
|
+
if (!recordInPlan(repoRoot, planPath, (written) => stampPlanIssue(written, issue3), line))
|
|
14772
14888
|
return;
|
|
14773
14889
|
activeOutput().info(`\uD83D\uDD16 ${planPath} records ${line}, the issue it was planned from.`);
|
|
14774
14890
|
}
|
|
@@ -14781,8 +14897,8 @@ import { readFileSync as readFileSync23 } from "fs";
|
|
|
14781
14897
|
|
|
14782
14898
|
// src/refs/reading.ts
|
|
14783
14899
|
import { readFileSync as readFileSync22, writeFileSync as writeFileSync11 } from "fs";
|
|
14784
|
-
function unblockCommand(
|
|
14785
|
-
return `rafa issue unblock ${String(
|
|
14900
|
+
function unblockCommand(issue3) {
|
|
14901
|
+
return `rafa issue unblock ${String(issue3)}`;
|
|
14786
14902
|
}
|
|
14787
14903
|
function firstStamp(live) {
|
|
14788
14904
|
return live.kind === "unreadable" || live.kind === "absent" ? null : live;
|
|
@@ -14793,7 +14909,7 @@ async function liveReadings(body, verify) {
|
|
|
14793
14909
|
read.push([ref, await verify(ref)]);
|
|
14794
14910
|
return read;
|
|
14795
14911
|
}
|
|
14796
|
-
function rowOf(ref, live, stamp, heldTo,
|
|
14912
|
+
function rowOf(ref, live, stamp, heldTo, issue3) {
|
|
14797
14913
|
const { state, changedHeadings: changedHeadings2 } = compareToStamp({ live, stamp, blocker: ref.blocker });
|
|
14798
14914
|
return Object.freeze({
|
|
14799
14915
|
kind: ref.kind,
|
|
@@ -14803,7 +14919,7 @@ function rowOf(ref, live, stamp, heldTo, issue4) {
|
|
|
14803
14919
|
fingerprint: live,
|
|
14804
14920
|
stamp: heldTo,
|
|
14805
14921
|
changedHeadings: changedHeadings2,
|
|
14806
|
-
unblock: state === "resolved" ? unblockCommand(
|
|
14922
|
+
unblock: state === "resolved" ? unblockCommand(issue3) : null
|
|
14807
14923
|
});
|
|
14808
14924
|
}
|
|
14809
14925
|
function answer2(rows, copy, body, stamps, write) {
|
|
@@ -14957,8 +15073,8 @@ async function enforceRefsGate(options) {
|
|
|
14957
15073
|
// src/commands/plan/refs-check.ts
|
|
14958
15074
|
async function coreRoster() {
|
|
14959
15075
|
const [{ CORE_REGISTRY }, { describeRegistry }] = await Promise.all([
|
|
14960
|
-
import("./index-
|
|
14961
|
-
import("./describe-
|
|
15076
|
+
import("./index-mh5hyw8g.js"),
|
|
15077
|
+
import("./describe-cenk70n5.js")
|
|
14962
15078
|
]);
|
|
14963
15079
|
return describeRegistry(CORE_REGISTRY, version);
|
|
14964
15080
|
}
|
|
@@ -15056,8 +15172,8 @@ function createPlanBlockedOffer(seams = DEFAULT_BLOCKED_SEAMS) {
|
|
|
15056
15172
|
|
|
15057
15173
|
// src/commands/plan/ready-offer.ts
|
|
15058
15174
|
var DEFAULT_OFFER_SEAMS = Object.freeze({});
|
|
15059
|
-
function offerHeadLine(
|
|
15060
|
-
const number = String(
|
|
15175
|
+
function offerHeadLine(issue3) {
|
|
15176
|
+
const number = String(issue3);
|
|
15061
15177
|
return `\uD83C\uDFF7 issue #${number} is not marked ${SPEC_READY_LABEL} \u2014 reading it as rafa issue ready ${number} would.`;
|
|
15062
15178
|
}
|
|
15063
15179
|
function writeOffer(request, report2) {
|
|
@@ -15072,16 +15188,16 @@ function createPlanReadyOffer(seams = DEFAULT_OFFER_SEAMS) {
|
|
|
15072
15188
|
const openPrompter = seams.openPrompter ?? (() => createLinePrompter(process.stdin, process.stderr));
|
|
15073
15189
|
const run = seams.run ?? runIssueReady;
|
|
15074
15190
|
return async (request) => {
|
|
15075
|
-
const { issue:
|
|
15076
|
-
request.output.info(offerHeadLine(
|
|
15191
|
+
const { issue: issue3 } = request;
|
|
15192
|
+
request.output.info(offerHeadLine(issue3.number));
|
|
15077
15193
|
const prompter = lazyPrompter(openPrompter);
|
|
15078
15194
|
try {
|
|
15079
15195
|
const report2 = await run({
|
|
15080
15196
|
gh: request.gh,
|
|
15081
|
-
issue:
|
|
15197
|
+
issue: issue3.number,
|
|
15082
15198
|
trust: request.trust,
|
|
15083
15199
|
ask: prompter.ask,
|
|
15084
|
-
readIssue: () => Promise.resolve(
|
|
15200
|
+
readIssue: () => Promise.resolve(issue3)
|
|
15085
15201
|
});
|
|
15086
15202
|
writeOffer(request, report2);
|
|
15087
15203
|
return report2.status === "marked";
|
|
@@ -15338,4 +15454,4 @@ async function plan(args, repoRoot, registry = CORE_ADAPTER_REGISTRY) {
|
|
|
15338
15454
|
}
|
|
15339
15455
|
}
|
|
15340
15456
|
|
|
15341
|
-
export { readConfigFile, loadConfig, planStubsFromFileNames, resolvePlanStub, sessionLogDir, collectSessionRow, collect, report, name, version, MANIFEST_VERSION, FEATURE_TYPES, OUTPUT_CHANNELS, RUNNING_MANIFEST_SEAMS, validateManifest, plansDirAt, requireProject, resolvePlansDir, resolveProjectConfig, countTasks, formatCounts, checkbox, planFileName, stubOfPlanFile, isFile, issueLine, plural, expectNoArgument, expectOneArgument, expectAtMostOneArgument, readSwitch,
|
|
15457
|
+
export { readConfigFile, loadConfig, planStubsFromFileNames, resolvePlanStub, sessionLogDir, collectSessionRow, collect, report, name, version, MANIFEST_VERSION, FEATURE_TYPES, OUTPUT_CHANNELS, RUNNING_MANIFEST_SEAMS, validateManifest, plansDirAt, requireProject, resolvePlansDir, resolveProjectConfig, countTasks, formatCounts, checkbox, planFileName, stubOfPlanFile, isFile, issueLine, plural, expectNoArgument, expectOneArgument, expectAtMostOneArgument, readSwitch, PROVENANCE_FIELD, isStackList, stackPaths, stacksFromEccSkillName, DESCRIPTION_LIMIT, LISTING_LIMIT, SKILL_SIGNALS, countCharacters, toolOf, pathDirectories, checkReferences, checkFile, checkDirectory, AGENT_DEFINITION_DIR, readInventoryFrontmatter, bundledAgentsDirectory, sourceItem, byName, skillItem, readTrees, SCOPE_DIR, INIT_COMMAND, DISK_FILE_SYSTEM, ScopeError, scopeAt, initHint, selfAndAncestors, resolveScope, SessionRecordError, runsDir, errorCode, readSessions, readSession, SERVE_CLI_VERSION, SKILL_FILE2 as SKILL_FILE, provenanceBlock, serveVerdict, VENDOR_COMMAND, readAgentDirectory, resolveAgentRoster, missingPlanAgents, validate_default, SPEC_READY_LABEL, TEMPLATE_HEADINGS, findReadinessGaps, hasSpecReadyLabel, createGhPermissions, readAuthorTrust, trustRefusalClause, requireTrustedAuthor, SPEC_NEEDS_WORK_LABEL, SPEC_BLOCKED_LABEL, hasSpecBlockedLabel, readBlockedBy, blockedFaultMessage, UNREADABLE, RefsBlockError, fingerprintText, readRefsText, readCopyRefs, restampCopyRefs, createGitRunner, gitSaid, RefVerifyError, ghIssueReader, tsSymbolsOutliner, createRefVerifier, createGhIssueBoard, ID_PREFIX, BRANCH_PREFIX, SPEC_EXTENSION, boardId, notesFileName, PREVIOUS_COPY_WARN_ABOVE, previousDir, countPreviousCopies, SPEC_LABEL, createGhSpecIssueReader, ROADMAP_TITLE, ROADMAP_SETTING, ROADMAP_REFUSAL_EXIT, parseRoadmapBody, createGhRoadmapSearch, severalRoadmapsMessage, resolveRoadmapIssue, createGhOpenPullRequests, closedIssuesIn, branchClaims, scanClaimBranches, blockedLineSentence, memoiseVerifier, LEARNED_DIRECTORY, ORIGIN_FIELD, DEMOTION_VERDICTS, WHEN_TO_USE_HEADING, bodySections, classifySkill, firstSentence, convertToInstinct, readPlanChanges, shellQuote, ghMissingMessage, ghUnauthenticatedMessage, ghHostOf, ghPreflightItems, resolvePrProvider, requireGhProvider, isFirstDispatch, SESSION_ID_FLAG2 as SESSION_ID_FLAG, resolveLoopSeams, sessionIdFlag, refusal2 as refusal, isLive, planLabel, sessionLine, projectRoot, readRecords, pickSession, writeSession, readSessionChecklist, checkboxAt, estimateEta, readSessionFinishes, etaLine, verdictOf, failingRows, formatRows, waitForChecks, parseWorkingTree, parseWorktrees, readMergeRefusal, cleanUpSteps, commandLine, remainingFrom, createGhPullRequests, ghPullRequestsIn, DEFAULT_BASE_BRANCH, ghNextBoard, openNextSources, createLinePrompter, REMOTE, parseBaseStanding, hasDiverged, list_default, readBranch, readPlans, readOpenPull, readBranchPlan, readNextState, commandWords, nextQuestion, NEXT_COMMAND_ACTIONS, actionInvocation, runAction, HINT_FLAG_SPEC, actionOutput, endWithNextStep, endingWith, DEFAULT_ISSUE_SEAMS, lineRefusal2 as lineRefusal, expectTwoArguments, readTextFlag, readNonBlankFlag, readRequiredFlag, readChoice, readChoiceFlag, issueProject, issueSubjectConfig, resolveIssueTracker, issueRef, issueName, urlLines, onTracker, lazyPrompter, ready_default, versionLine, DEFAULT_PLAN_FILE, resolvePlanPath, assessPlanRisk, renderRiskText, announceRiskTotal, resolveCreateSpec, resolveReleaseEnabled, TRIAGE_MODULE, DEFAULT_CI_TIMEOUT_MIN, CI_POLL_INTERVAL_MS, DEFAULT_CI_ATTEMPTS, RUNTIME_SUBDIR, RUNTIME_ENTRY, VERSION_SHAPE2 as VERSION_SHAPE, refuseMisplacedRuntime, groupChangeNotes, renderNoteLines, highestChangeLevel, parseSemanticVersion, readManifestVersion, MECHANICAL_CONFLICT_SENTENCE, start, usage, coreRoster, createPlanRefsVerifier, plan };
|