@open-tomato/rafa 0.7.1

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.
@@ -0,0 +1,866 @@
1
+ // @bun
2
+ // src/pr/types.ts
3
+ var MERGE_METHODS = Object.freeze([
4
+ "squash",
5
+ "merge",
6
+ "rebase"
7
+ ]);
8
+ function isMergeMethod(value) {
9
+ return typeof value === "string" && MERGE_METHODS.includes(value);
10
+ }
11
+
12
+ // src/utils/declaration.ts
13
+ var DECLARATION_KEYS = [
14
+ "agent",
15
+ "model",
16
+ "effort",
17
+ "budget",
18
+ "tools",
19
+ "skills"
20
+ ];
21
+ var GRANULAR_KEYS = ["model", "effort", "tools"];
22
+ var AGENT_OWNED_KEYS = ["model", "tools"];
23
+ var MODEL_ALIASES = ["opus", "sonnet", "haiku", "fable"];
24
+ var EFFORT_LEVELS = [
25
+ "low",
26
+ "medium",
27
+ "high",
28
+ "xhigh",
29
+ "max"
30
+ ];
31
+ var DECLARATION_BLOCK = /\{([^{}]*)\}$/;
32
+ var ENTRY_TOKEN = /^([A-Za-z_][A-Za-z0-9_-]*)=(.*)$/;
33
+ var FULL_MODEL_NAME = /^claude-[a-z0-9][a-z0-9.-]*$/;
34
+ var AGENT_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
35
+ function isAgentName(value) {
36
+ return AGENT_NAME.test(value);
37
+ }
38
+ var TOOL_NAME = /^[A-Za-z][A-Za-z0-9_-]*$/;
39
+ var SKILL_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
40
+ var BUDGET_FLAG = "--max-budget-usd";
41
+ var BUDGET_VALUE = /^(?:0|[1-9]\d{0,5})(?:\.\d{1,6})?$/;
42
+ function parseBudgetUsd(value) {
43
+ if (!BUDGET_VALUE.test(value))
44
+ return null;
45
+ const usd = Number(value);
46
+ return usd > 0 ? usd : null;
47
+ }
48
+ function isEffortLevel(value) {
49
+ return EFFORT_LEVELS.includes(value);
50
+ }
51
+ function isModelValue(value) {
52
+ if (MODEL_ALIASES.includes(value))
53
+ return true;
54
+ return FULL_MODEL_NAME.test(value);
55
+ }
56
+ function parseToolList(value) {
57
+ const parts = value.split(",");
58
+ if (parts.length === 0)
59
+ return null;
60
+ const tools = [];
61
+ for (const part of parts) {
62
+ if (!TOOL_NAME.test(part))
63
+ return null;
64
+ if (!tools.includes(part))
65
+ tools.push(part);
66
+ }
67
+ return tools;
68
+ }
69
+ function parseSkillList(value) {
70
+ const parts = value.split(",");
71
+ if (parts.length === 0)
72
+ return null;
73
+ const skills = [];
74
+ for (const part of parts) {
75
+ if (!SKILL_NAME.test(part))
76
+ return null;
77
+ if (!skills.includes(part))
78
+ skills.push(part);
79
+ }
80
+ return skills;
81
+ }
82
+ function unusableValue(key, token) {
83
+ return { reason: "unusable-value", key, text: token };
84
+ }
85
+ function isDeclarationKey(key) {
86
+ return DECLARATION_KEYS.includes(key);
87
+ }
88
+ function tokenise(body) {
89
+ const trimmed = body.trim();
90
+ if (trimmed.length === 0)
91
+ return [];
92
+ return trimmed.split(/\s+/);
93
+ }
94
+ function readBlock(raw, body) {
95
+ const entries = [];
96
+ const extras = [];
97
+ const issues = [];
98
+ const seen = new Set;
99
+ let agent = null;
100
+ let model = null;
101
+ let effort = null;
102
+ let budget = null;
103
+ let tools = null;
104
+ let skills = null;
105
+ for (const token of tokenise(body)) {
106
+ const match = token.match(ENTRY_TOKEN);
107
+ const key = match?.[1];
108
+ const value = match?.[2];
109
+ if (key === undefined || value === undefined) {
110
+ issues.push({ reason: "stray-token", key: "", text: token });
111
+ continue;
112
+ }
113
+ const entry = { key, value };
114
+ entries.push(entry);
115
+ if (!isDeclarationKey(key)) {
116
+ extras.push(entry);
117
+ continue;
118
+ }
119
+ if (seen.has(key)) {
120
+ issues.push({ reason: "duplicate-key", key, text: token });
121
+ continue;
122
+ }
123
+ seen.add(key);
124
+ if (key === "agent") {
125
+ if (AGENT_NAME.test(value))
126
+ agent = value;
127
+ else
128
+ issues.push(unusableValue(key, token));
129
+ continue;
130
+ }
131
+ if (key === "model") {
132
+ if (isModelValue(value))
133
+ model = value;
134
+ else
135
+ issues.push(unusableValue(key, token));
136
+ continue;
137
+ }
138
+ if (key === "effort") {
139
+ if (isEffortLevel(value))
140
+ effort = value;
141
+ else
142
+ issues.push(unusableValue(key, token));
143
+ continue;
144
+ }
145
+ if (key === "budget") {
146
+ const usd = parseBudgetUsd(value);
147
+ if (usd === null)
148
+ issues.push(unusableValue(key, token));
149
+ else
150
+ budget = usd;
151
+ continue;
152
+ }
153
+ if (key === "tools") {
154
+ const parsed = parseToolList(value);
155
+ if (parsed === null)
156
+ issues.push(unusableValue(key, token));
157
+ else
158
+ tools = parsed;
159
+ continue;
160
+ }
161
+ const named = parseSkillList(value);
162
+ if (named === null)
163
+ issues.push(unusableValue(key, token));
164
+ else
165
+ skills = named;
166
+ }
167
+ if (seen.size === 0)
168
+ return null;
169
+ return {
170
+ raw,
171
+ entries,
172
+ extras,
173
+ issues,
174
+ agent,
175
+ model,
176
+ effort,
177
+ budget,
178
+ tools,
179
+ skills
180
+ };
181
+ }
182
+ function parseTaskDeclaration(taskText) {
183
+ const trimmed = taskText.trimEnd();
184
+ const match = trimmed.match(DECLARATION_BLOCK);
185
+ const raw = match?.[0];
186
+ const body = match?.[1];
187
+ if (raw === undefined || body === undefined) {
188
+ return { text: taskText, declaration: null };
189
+ }
190
+ const before = trimmed.slice(0, trimmed.length - raw.length).trimEnd();
191
+ if (before.length === 0)
192
+ return { text: taskText, declaration: null };
193
+ const declaration = readBlock(raw, body);
194
+ if (declaration === null)
195
+ return { text: taskText, declaration: null };
196
+ return { text: before, declaration };
197
+ }
198
+ function stripTaskDeclaration(taskText) {
199
+ return parseTaskDeclaration(taskText).text;
200
+ }
201
+ function budgetArgs(budget) {
202
+ return budget === null ? [] : [BUDGET_FLAG, String(budget)];
203
+ }
204
+ function resolveDeclarationFlags(declaration, agentDeclaresEffort) {
205
+ if (declaration === null)
206
+ return { args: [], suppressed: [] };
207
+ const { agent, effort } = declaration;
208
+ const isPresent = (key) => declaration[key] !== null;
209
+ const budget = budgetArgs(declaration.budget);
210
+ if (agent !== null) {
211
+ if (effort === null || agentDeclaresEffort(agent)) {
212
+ return { args: ["--agent", agent, ...budget], suppressed: GRANULAR_KEYS.filter(isPresent) };
213
+ }
214
+ return {
215
+ args: ["--agent", agent, "--effort", effort, ...budget],
216
+ suppressed: AGENT_OWNED_KEYS.filter(isPresent)
217
+ };
218
+ }
219
+ const args = [];
220
+ if (declaration.model !== null)
221
+ args.push("--model", declaration.model);
222
+ if (declaration.effort !== null)
223
+ args.push("--effort", declaration.effort);
224
+ args.push(...budget);
225
+ if (declaration.tools !== null) {
226
+ args.push("--tools", declaration.tools.join(","));
227
+ }
228
+ return { args, suppressed: [] };
229
+ }
230
+
231
+ // src/config-sections.ts
232
+ function isMapping(value) {
233
+ return typeof value === "object" && value !== null && !Array.isArray(value);
234
+ }
235
+ function describeValue(value) {
236
+ if (typeof value === "string")
237
+ return JSON.stringify(value);
238
+ if (value === null)
239
+ return "null";
240
+ if (Array.isArray(value))
241
+ return "a list";
242
+ if (typeof value === "object")
243
+ return "a mapping";
244
+ return String(value);
245
+ }
246
+ function messageOf(error) {
247
+ return error instanceof Error ? error.message : String(error);
248
+ }
249
+ var CONFIG_VERSIONS = [1];
250
+ var STORE_BACKENDS = ["sqlite", "ndjson"];
251
+ var INJECT_MODES = ["full", "stage", "task"];
252
+ var OUTPUT_MODES = ["text", "json"];
253
+ var CLAUDE_SETTING_SOURCES = ["user", "project", "local"];
254
+ var PR_PROVIDERS = ["gh", "none"];
255
+ var RELEASE_AUTO = "auto";
256
+ function accepted(value) {
257
+ return { value, problems: [], extras: [] };
258
+ }
259
+ function refused(at, raw, expected) {
260
+ const problem = `${at.label} is ${describeValue(raw)}, expected ${expected}`;
261
+ return { value: undefined, problems: [problem], extras: [] };
262
+ }
263
+ function refusedWith(problems) {
264
+ return { value: undefined, problems, extras: [] };
265
+ }
266
+ function mapReading(reading, map) {
267
+ return reading.value === undefined ? { ...reading, value: undefined } : { ...reading, value: map(reading.value) };
268
+ }
269
+ function below(at, step) {
270
+ return { label: `${at.label}${step}`, key: `${at.key}${step}` };
271
+ }
272
+ function oneOf(values) {
273
+ const expected = `one of: ${values.join(", ")}`;
274
+ return (raw, at) => {
275
+ const hit = values.find((value) => value === raw);
276
+ return hit === undefined ? refused(at, raw, expected) : accepted(hit);
277
+ };
278
+ }
279
+ function text(expected) {
280
+ return (raw, at) => typeof raw === "string" && raw.trim() !== "" ? accepted(raw) : refused(at, raw, expected);
281
+ }
282
+ var flag = (raw, at) => typeof raw === "boolean" ? accepted(raw) : refused(at, raw, "true or false");
283
+ var mergeMethod = oneOf(MERGE_METHODS);
284
+ var usdAmount = (raw, at) => {
285
+ const usd = typeof raw === "number" ? parseBudgetUsd(String(raw)) : null;
286
+ return usd === null ? refused(at, raw, "a number of US dollars above zero, at most six digits either side of the point") : accepted(usd);
287
+ };
288
+ var issueNumber = (raw, at) => typeof raw === "number" && Number.isSafeInteger(raw) && raw > 0 ? accepted(raw) : refused(at, raw, "an issue number, a whole number above zero");
289
+ var releaseEnabled = (raw, at) => typeof raw === "boolean" || raw === RELEASE_AUTO ? accepted(raw) : refused(at, raw, `true, false or ${RELEASE_AUTO}`);
290
+ var GITHUB_LOGIN = /^[A-Za-z0-9][A-Za-z0-9-]*(?:\[bot\])?$/;
291
+ function isGitHubLogin(value) {
292
+ return typeof value === "string" && GITHUB_LOGIN.test(value);
293
+ }
294
+ var githubLogin = (raw, at) => isGitHubLogin(raw) ? accepted(raw) : refused(at, raw, "a GitHub login");
295
+ function listOf(item, expected) {
296
+ return (raw, at) => {
297
+ if (!Array.isArray(raw))
298
+ return refused(at, raw, `a list of ${expected}`);
299
+ const readings = raw.map((entry, index) => item(entry, below(at, `[${index}]`)));
300
+ const problems = readings.flatMap((reading) => reading.problems);
301
+ const extras = readings.flatMap((reading) => reading.extras);
302
+ if (problems.length > 0)
303
+ return { value: undefined, problems, extras };
304
+ const values = readings.flatMap((reading) => reading.value === undefined ? [] : [reading.value]);
305
+ return { value: Object.freeze(values), problems, extras };
306
+ };
307
+ }
308
+ function subsetOf(values) {
309
+ const expected = `a comma-separated subset of: ${values.join(", ")}`;
310
+ return (raw, at) => {
311
+ if (typeof raw !== "string")
312
+ return refused(at, raw, expected);
313
+ const named = [];
314
+ for (const entry of raw.split(",")) {
315
+ const hit = values.find((value) => value === entry.trim());
316
+ if (hit === undefined || named.includes(hit))
317
+ return refused(at, raw, expected);
318
+ named.push(hit);
319
+ }
320
+ return accepted(Object.freeze(named));
321
+ };
322
+ }
323
+ var itemText = text("a non-empty string");
324
+ function readKeyedItem(shape, raw, at) {
325
+ const choices = shape.kinds.join(", ");
326
+ if (!isMapping(raw))
327
+ return refused(at, raw, `a mapping naming one of: ${choices}`);
328
+ const entries = Object.entries(raw);
329
+ const kinds = shape.kinds.filter((kind2) => entries.some(([name]) => name === kind2));
330
+ const [kind] = kinds;
331
+ if (kind === undefined)
332
+ return refusedWith([`${at.label} names none of: ${choices}`]);
333
+ if (kinds.length > 1) {
334
+ const named2 = kinds.join(" and ");
335
+ return refusedWith([`${at.label} names ${named2}, expected exactly one of: ${choices}`]);
336
+ }
337
+ const optionalKeys = shape.optionalFor(kind);
338
+ const problems = [];
339
+ const extras = [];
340
+ const optional = new Map;
341
+ let named;
342
+ for (const [name, value] of entries) {
343
+ if (name !== kind && !optionalKeys.includes(name)) {
344
+ extras.push({ key: `${at.key}.${name}`, value });
345
+ continue;
346
+ }
347
+ if (name !== kind && value === null)
348
+ continue;
349
+ const reading = itemText(value, below(at, `.${name}`));
350
+ problems.push(...reading.problems);
351
+ if (reading.value === undefined)
352
+ continue;
353
+ if (name === kind)
354
+ named = reading.value;
355
+ else
356
+ optional.set(name, reading.value);
357
+ }
358
+ return problems.length > 0 || named === undefined ? { value: undefined, problems, extras } : { value: { kind, named, optional }, problems, extras };
359
+ }
360
+ var PREREQUISITE_KINDS = ["tool", "env", "service", "lsp"];
361
+ var REQUIRED_ITEM_KEYS = [...PREREQUISITE_KINDS, "probe"];
362
+ var OPTIONAL_ITEM_KEYS = [...REQUIRED_ITEM_KEYS, "reason"];
363
+ var requiredPrerequisite = (raw, at) => {
364
+ const shape = { kinds: PREREQUISITE_KINDS, optionalFor: () => ["probe"] };
365
+ return mapReading(readKeyedItem(shape, raw, at), (item) => Object.freeze({
366
+ kind: item.kind,
367
+ name: item.named,
368
+ probe: item.optional.get("probe") ?? null
369
+ }));
370
+ };
371
+ var optionalPrerequisite = (raw, at) => {
372
+ const shape = { kinds: PREREQUISITE_KINDS, optionalFor: () => ["probe", "reason"] };
373
+ return mapReading(readKeyedItem(shape, raw, at), (item) => Object.freeze({
374
+ kind: item.kind,
375
+ name: item.named,
376
+ probe: item.optional.get("probe") ?? null,
377
+ reason: item.optional.get("reason") ?? null
378
+ }));
379
+ };
380
+ var MODULE_SOURCE_KINDS = ["npm", "github", "path"];
381
+ var MODULE_SOURCE_KEYS = [...MODULE_SOURCE_KINDS, "ref"];
382
+ var moduleSource = (raw, at) => {
383
+ const shape = {
384
+ kinds: MODULE_SOURCE_KINDS,
385
+ optionalFor: (kind) => kind === "github" ? ["ref"] : []
386
+ };
387
+ return mapReading(readKeyedItem(shape, raw, at), (item) => Object.freeze({
388
+ kind: item.kind,
389
+ location: item.named,
390
+ ref: item.optional.get("ref") ?? null
391
+ }));
392
+ };
393
+
394
+ // src/config-schema.ts
395
+ import { join } from "path";
396
+ var CONFIG_FILE = join(".rafa", "config.yaml");
397
+ var CONFIG_DEFAULTS = Object.freeze({
398
+ version: 1,
399
+ store: "sqlite",
400
+ inject: "stage",
401
+ planDir: join(".rafa", "plans"),
402
+ specsDir: join(".rafa", "specs"),
403
+ trackerDefault: "github",
404
+ trackerFallback: Object.freeze(["local"]),
405
+ learningAdapter: "local",
406
+ outputMode: "text",
407
+ prerequisitesRequired: Object.freeze([]),
408
+ prerequisitesOptional: Object.freeze([]),
409
+ trackingSpecs: false,
410
+ trackingPlans: false,
411
+ trackingAll: false,
412
+ modules: Object.freeze([]),
413
+ allowList: Object.freeze([]),
414
+ settingSources: Object.freeze(["project", "local"]),
415
+ prProvider: null,
416
+ prMergeMethod: "squash",
417
+ prBase: null,
418
+ prResolveBudget: 2,
419
+ boardTrustedAuthors: Object.freeze([]),
420
+ roadmapIssue: null,
421
+ releaseEnabled: RELEASE_AUTO,
422
+ releaseVersionFile: "package.json",
423
+ releaseChangelog: "CHANGELOG.md",
424
+ releaseHeading: "## {version} \u2014 {date}, {title}"
425
+ });
426
+ var directory = text("a directory path");
427
+ var trackerKind = text("a tracker kind name");
428
+ var releaseFile = text("a file path");
429
+ var SETTINGS = {
430
+ version: { key: "version", read: oneOf(CONFIG_VERSIONS), cli: false },
431
+ store: { key: "store", read: oneOf(STORE_BACKENDS), cli: true },
432
+ inject: { key: "plan.inject", read: oneOf(INJECT_MODES), cli: true },
433
+ planDir: { key: "plan.dir", read: directory, cli: true },
434
+ specsDir: { key: "specs.dir", read: directory, cli: true },
435
+ trackerDefault: { key: "tracker.default", read: trackerKind, cli: true },
436
+ trackerFallback: {
437
+ key: "tracker.fallback",
438
+ read: listOf(trackerKind, "tracker kind names"),
439
+ cli: false
440
+ },
441
+ learningAdapter: {
442
+ key: "learning.adapter",
443
+ read: text("a learning adapter name"),
444
+ cli: true
445
+ },
446
+ outputMode: { key: "output.mode", read: oneOf(OUTPUT_MODES), cli: true },
447
+ prerequisitesRequired: {
448
+ key: "prerequisites.required",
449
+ read: listOf(requiredPrerequisite, "prerequisite items"),
450
+ cli: false,
451
+ itemKeys: REQUIRED_ITEM_KEYS
452
+ },
453
+ prerequisitesOptional: {
454
+ key: "prerequisites.optional",
455
+ read: listOf(optionalPrerequisite, "prerequisite items"),
456
+ cli: false,
457
+ itemKeys: OPTIONAL_ITEM_KEYS
458
+ },
459
+ trackingSpecs: { key: "tracking.specs", read: flag, cli: false },
460
+ trackingPlans: { key: "tracking.plans", read: flag, cli: false },
461
+ trackingAll: { key: "tracking.all", read: flag, cli: false },
462
+ modules: {
463
+ key: "modules",
464
+ read: listOf(moduleSource, "module sources"),
465
+ cli: false,
466
+ itemKeys: MODULE_SOURCE_KEYS
467
+ },
468
+ allowList: {
469
+ key: "allowList",
470
+ read: listOf(text("a module name"), "module names"),
471
+ cli: false
472
+ },
473
+ settingSources: {
474
+ key: "loop.settingSources",
475
+ read: subsetOf(CLAUDE_SETTING_SOURCES),
476
+ cli: true
477
+ },
478
+ prProvider: { key: "pr.provider", read: oneOf(PR_PROVIDERS), cli: false },
479
+ prMergeMethod: { key: "pr.mergeMethod", read: mergeMethod, cli: false },
480
+ prBase: { key: "pr.base", read: text("a branch name"), cli: false },
481
+ prResolveBudget: { key: "pr.resolveBudget", read: usdAmount, cli: false },
482
+ boardTrustedAuthors: {
483
+ key: "board.trustedAuthors",
484
+ read: listOf(githubLogin, "GitHub logins"),
485
+ cli: false
486
+ },
487
+ roadmapIssue: { key: "roadmap.issue", read: issueNumber, cli: false },
488
+ releaseEnabled: { key: "release.enabled", read: releaseEnabled, cli: false },
489
+ releaseVersionFile: {
490
+ key: "release.versionFile",
491
+ read: releaseFile,
492
+ cli: false
493
+ },
494
+ releaseChangelog: { key: "release.changelog", read: releaseFile, cli: false },
495
+ releaseHeading: {
496
+ key: "release.heading",
497
+ read: text("a changelog heading template"),
498
+ cli: false
499
+ }
500
+ };
501
+ var SETTING_NAMES = Object.keys(SETTINGS);
502
+ var SETTING_BY_KEY = new Map(SETTING_NAMES.map((setting) => [
503
+ SETTINGS[setting].key,
504
+ setting
505
+ ]));
506
+ function isCommandLineSetting(setting) {
507
+ return SETTINGS[setting].cli;
508
+ }
509
+ function sectionsOf(key) {
510
+ const parts = key.split(".");
511
+ return parts.slice(1).map((_, index) => parts.slice(0, index + 1).join("."));
512
+ }
513
+ var SECTIONS = new Set([...SETTING_BY_KEY.keys()].flatMap(sectionsOf));
514
+ function parentOf(key) {
515
+ const cut = key.lastIndexOf(".");
516
+ return cut < 0 ? "" : key.slice(0, cut);
517
+ }
518
+ var TRAILING_INDEX = /\[\d+\]$/;
519
+ var KNOWN_UNDER = (() => {
520
+ const known = new Map;
521
+ const add = (parent, name) => {
522
+ const names = known.get(parent) ?? [];
523
+ if (!names.includes(name))
524
+ known.set(parent, [...names, name]);
525
+ };
526
+ for (const setting of SETTING_NAMES) {
527
+ const { key, itemKeys } = SETTINGS[setting];
528
+ const parts = key.split(".");
529
+ parts.forEach((part, index) => add(parts.slice(0, index).join("."), part));
530
+ for (const name of itemKeys ?? [])
531
+ add(`${key}[]`, name);
532
+ }
533
+ return known;
534
+ })();
535
+ function knownKeysAbove(key) {
536
+ for (let parent = parentOf(key);parent !== ""; parent = parentOf(parent)) {
537
+ const names = KNOWN_UNDER.get(parent.replace(TRAILING_INDEX, "[]"));
538
+ if (names !== undefined)
539
+ return [parent, names];
540
+ }
541
+ return ["", KNOWN_UNDER.get("") ?? []];
542
+ }
543
+
544
+ // src/config.ts
545
+ import { join as join2 } from "path";
546
+ class ConfigError extends Error {
547
+ problems;
548
+ constructor(problems, options) {
549
+ super(`rafa config: ${problems.join("; ")}`, options);
550
+ this.name = "ConfigError";
551
+ this.problems = problems;
552
+ }
553
+ }
554
+ function readLayer(rawOf, atOf) {
555
+ const problems = [];
556
+ const extras = [];
557
+ const read = (setting) => {
558
+ const raw = rawOf(setting);
559
+ if (raw === undefined)
560
+ return;
561
+ const reading = SETTINGS[setting].read(raw, atOf(setting));
562
+ problems.push(...reading.problems);
563
+ extras.push(...reading.extras);
564
+ return reading.value;
565
+ };
566
+ const layer = {
567
+ version: read("version"),
568
+ store: read("store"),
569
+ inject: read("inject"),
570
+ planDir: read("planDir"),
571
+ specsDir: read("specsDir"),
572
+ trackerDefault: read("trackerDefault"),
573
+ trackerFallback: read("trackerFallback"),
574
+ learningAdapter: read("learningAdapter"),
575
+ outputMode: read("outputMode"),
576
+ prerequisitesRequired: read("prerequisitesRequired"),
577
+ prerequisitesOptional: read("prerequisitesOptional"),
578
+ trackingSpecs: read("trackingSpecs"),
579
+ trackingPlans: read("trackingPlans"),
580
+ trackingAll: read("trackingAll"),
581
+ modules: read("modules"),
582
+ allowList: read("allowList"),
583
+ settingSources: read("settingSources"),
584
+ prProvider: read("prProvider"),
585
+ prMergeMethod: read("prMergeMethod"),
586
+ prBase: read("prBase"),
587
+ prResolveBudget: read("prResolveBudget"),
588
+ boardTrustedAuthors: read("boardTrustedAuthors"),
589
+ roadmapIssue: read("roadmapIssue"),
590
+ releaseEnabled: read("releaseEnabled"),
591
+ releaseVersionFile: read("releaseVersionFile"),
592
+ releaseChangelog: read("releaseChangelog"),
593
+ releaseHeading: read("releaseHeading")
594
+ };
595
+ return { layer, problems, extras };
596
+ }
597
+ function* entriesOf(mapping, prefix) {
598
+ for (const [name, value] of Object.entries(mapping)) {
599
+ const key = prefix === "" ? name : `${prefix}.${name}`;
600
+ if (SECTIONS.has(key) && isMapping(value))
601
+ yield* entriesOf(value, key);
602
+ else
603
+ yield { key, value };
604
+ }
605
+ }
606
+ function sortKeys(document, path) {
607
+ const named = new Map;
608
+ const seen = new Set;
609
+ const extras = [];
610
+ const problems = [];
611
+ for (const entry of entriesOf(document, "")) {
612
+ const setting = SETTING_BY_KEY.get(entry.key);
613
+ if (setting !== undefined) {
614
+ if (seen.has(setting)) {
615
+ problems.push(`${path}: ${entry.key} is given more than once`);
616
+ } else if (entry.value !== null) {
617
+ named.set(setting, entry.value);
618
+ }
619
+ seen.add(setting);
620
+ continue;
621
+ }
622
+ if (!SECTIONS.has(entry.key)) {
623
+ extras.push(entry);
624
+ continue;
625
+ }
626
+ if (entry.value !== null) {
627
+ const found = describeValue(entry.value);
628
+ problems.push(`${path}: ${entry.key} must be a mapping, found ${found}`);
629
+ }
630
+ }
631
+ return { named, extras, problems };
632
+ }
633
+ function parseYaml(text2, path) {
634
+ try {
635
+ return Bun.YAML.parse(text2);
636
+ } catch (error) {
637
+ const problem = `${path}: not valid YAML (${messageOf(error)})`;
638
+ throw new ConfigError([problem], { cause: error });
639
+ }
640
+ }
641
+ function configFilePath(root) {
642
+ return join2(root, CONFIG_FILE);
643
+ }
644
+ function parseConfigText(text2, path) {
645
+ const document = parseYaml(text2, path) ?? {};
646
+ if (!isMapping(document)) {
647
+ const found = describeValue(document);
648
+ throw new ConfigError([
649
+ `${path}: expected a mapping at the top level, found ${found}`
650
+ ]);
651
+ }
652
+ const sorted = sortKeys(document, path);
653
+ const read = readLayer((setting) => sorted.named.get(setting), (setting) => ({
654
+ label: `${path}: ${SETTINGS[setting].key}`,
655
+ key: SETTINGS[setting].key
656
+ }));
657
+ const all = [...sorted.problems, ...read.problems];
658
+ if (all.length > 0)
659
+ throw new ConfigError(all);
660
+ return { path, values: read.layer, extras: [...sorted.extras, ...read.extras] };
661
+ }
662
+ function unknownKeyWarning(key, path) {
663
+ const quoted = JSON.stringify(key);
664
+ const [under, names] = knownKeysAbove(key);
665
+ const where = under === "" ? "" : ` under ${under}`;
666
+ return `rafa config: unknown key ${quoted} in ${path} has no effect ` + `in this version (known keys${where}: ${names.join(", ")})`;
667
+ }
668
+ function resolveConfig(layers = {}) {
669
+ const file = layers.file ?? null;
670
+ const user = layers.user ?? null;
671
+ const overrides = layers.cli ?? {};
672
+ const { layer: cli, problems } = readLayer((setting) => isCommandLineSetting(setting) ? overrides[setting] : undefined, (setting) => ({ label: `command line: ${setting}`, key: setting }));
673
+ if (problems.length > 0)
674
+ throw new ConfigError(problems);
675
+ const rank = (setting) => {
676
+ const fromCli = cli[setting];
677
+ if (fromCli !== undefined)
678
+ return { value: fromCli, source: "cli" };
679
+ const fromFile = file?.values[setting];
680
+ if (fromFile !== undefined)
681
+ return { value: fromFile, source: "file" };
682
+ const fromUser = user?.values[setting];
683
+ if (fromUser !== undefined)
684
+ return { value: fromUser, source: "user" };
685
+ return { value: CONFIG_DEFAULTS[setting], source: "default" };
686
+ };
687
+ const ranked = SETTING_NAMES.map((setting) => [setting, rank(setting)]);
688
+ const warnings = [user, file].flatMap((layer) => layer === null ? [] : layer.extras.map((extra) => unknownKeyWarning(extra.key, layer.path)));
689
+ return {
690
+ config: Object.fromEntries(ranked.map(([setting, { value }]) => [setting, value])),
691
+ sources: Object.fromEntries(ranked.map(([setting, { source }]) => [setting, source])),
692
+ path: file?.path ?? null,
693
+ userPath: user?.path ?? null,
694
+ extras: file?.extras ?? [],
695
+ userExtras: user?.extras ?? [],
696
+ warnings
697
+ };
698
+ }
699
+
700
+ // src/plan/blocks.ts
701
+ var RAFA_BLOCK_KINDS = [
702
+ "plan",
703
+ "context",
704
+ "stage-context",
705
+ "report"
706
+ ];
707
+ var RAFA_PREFIX = "rafa:";
708
+ var FENCE_RUN = /^( {0,3})(`{3,}|~{3,})/;
709
+ var CLOSING_FENCE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
710
+ function isRafaBlockKind(kind) {
711
+ return RAFA_BLOCK_KINDS.includes(kind);
712
+ }
713
+ function linesOf(markdown) {
714
+ const lines = markdown.split(`
715
+ `);
716
+ if (lines[lines.length - 1] === "")
717
+ lines.pop();
718
+ return lines.map((line) => line.endsWith("\r") ? line.slice(0, -1) : line);
719
+ }
720
+ function kindOf(info) {
721
+ const word = info.trim().split(/\s+/)[0] ?? "";
722
+ return word.startsWith(RAFA_PREFIX) ? word.slice(RAFA_PREFIX.length) : null;
723
+ }
724
+ function openingFence(line) {
725
+ const match = FENCE_RUN.exec(line);
726
+ const opener = match?.[0];
727
+ const indent = match?.[1];
728
+ const run = match?.[2];
729
+ if (opener === undefined || indent === undefined || run === undefined) {
730
+ return null;
731
+ }
732
+ const marker = run.charAt(0);
733
+ const info = line.slice(opener.length);
734
+ if (marker === "`" && info.includes("`"))
735
+ return null;
736
+ return { marker, length: run.length, indent: indent.length, kind: kindOf(info) };
737
+ }
738
+ function closes(line, fence) {
739
+ const run = CLOSING_FENCE.exec(line)?.[1];
740
+ return run !== undefined && run.charAt(0) === fence.marker && run.length >= fence.length;
741
+ }
742
+ function closingIndex(lines, from, fence) {
743
+ for (let index = from;index < lines.length; index += 1) {
744
+ if (closes(lines[index] ?? "", fence))
745
+ return index;
746
+ }
747
+ return lines.length;
748
+ }
749
+ function dedent(line, indent) {
750
+ let cut = 0;
751
+ while (cut < indent && line.charAt(cut) === " ")
752
+ cut += 1;
753
+ return line.slice(cut);
754
+ }
755
+ function readRafaBlocks(markdown) {
756
+ const lines = linesOf(markdown);
757
+ const blocks = [];
758
+ let index = 0;
759
+ while (index < lines.length) {
760
+ const fence = openingFence(lines[index] ?? "");
761
+ if (fence === null) {
762
+ index += 1;
763
+ continue;
764
+ }
765
+ const closing = closingIndex(lines, index + 1, fence);
766
+ const closed = closing < lines.length;
767
+ if (fence.kind !== null) {
768
+ const body = lines.slice(index + 1, closing).map((line) => dedent(line, fence.indent)).join(`
769
+ `);
770
+ const last = closed ? closing + 1 : lines.length;
771
+ blocks.push({ kind: fence.kind, body, span: { first: index + 1, last }, closed });
772
+ }
773
+ index = closing + 1;
774
+ }
775
+ return blocks;
776
+ }
777
+
778
+ // src/adapters/output/text.ts
779
+ var DEBUG_VERBOSITY = 2;
780
+ function formatPayload(payload) {
781
+ return typeof payload === "string" ? payload : JSON.stringify(payload);
782
+ }
783
+ function resultLine(event) {
784
+ if (event.ok) {
785
+ return event.data === undefined ? "result: ok" : `result: ok ${formatPayload(event.data)}`;
786
+ }
787
+ const code = event.error?.code ?? "unknown";
788
+ const message = event.error?.message ?? "";
789
+ return message === "" ? `result: error ${code}` : `result: error ${code}: ${message}`;
790
+ }
791
+ function createTextOutput({ verbosity, stream }) {
792
+ const writeLine = (line) => {
793
+ stream.write(`${line}
794
+ `);
795
+ };
796
+ const debug = (message) => {
797
+ if (verbosity >= DEBUG_VERBOSITY)
798
+ writeLine(`debug: ${message}`);
799
+ };
800
+ const info = (message) => {
801
+ writeLine(message);
802
+ };
803
+ const warn = (message) => {
804
+ writeLine(`warn: ${message}`);
805
+ };
806
+ const error = (message) => {
807
+ writeLine(`error: ${message}`);
808
+ };
809
+ const result = (payload) => {
810
+ writeLine(`result: ${formatPayload(payload)}`);
811
+ };
812
+ const writeLog = (event) => {
813
+ switch (event.level) {
814
+ case "debug": {
815
+ debug(event.message);
816
+ return;
817
+ }
818
+ case "info": {
819
+ info(event.message);
820
+ return;
821
+ }
822
+ case "warn": {
823
+ warn(event.message);
824
+ return;
825
+ }
826
+ case "error": {
827
+ error(event.message);
828
+ return;
829
+ }
830
+ }
831
+ };
832
+ const emit = (event) => {
833
+ switch (event.type) {
834
+ case "start": {
835
+ writeLine(`start: ${event.command}`);
836
+ return;
837
+ }
838
+ case "step": {
839
+ writeLine(`step: ${event.name}`);
840
+ return;
841
+ }
842
+ case "log": {
843
+ writeLog(event);
844
+ return;
845
+ }
846
+ case "result": {
847
+ writeLine(resultLine(event));
848
+ return;
849
+ }
850
+ }
851
+ };
852
+ return Object.freeze({ info, warn, error, debug, emit, result });
853
+ }
854
+
855
+ // src/adapters/output/active.ts
856
+ var DEFAULT_OUTPUT = createTextOutput({ verbosity: 0, stream: process.stdout });
857
+ var active = DEFAULT_OUTPUT;
858
+ var activeMode = "text";
859
+ function activeOutput() {
860
+ return active;
861
+ }
862
+ function activeOutputMode() {
863
+ return activeMode;
864
+ }
865
+
866
+ export { MERGE_METHODS, isMergeMethod, isAgentName, BUDGET_FLAG, parseTaskDeclaration, stripTaskDeclaration, resolveDeclarationFlags, isMapping, describeValue, messageOf, CONFIG_VERSIONS, STORE_BACKENDS, INJECT_MODES, OUTPUT_MODES, CLAUDE_SETTING_SOURCES, PR_PROVIDERS, RELEASE_AUTO, listOf, PREREQUISITE_KINDS, requiredPrerequisite, optionalPrerequisite, MODULE_SOURCE_KINDS, CONFIG_FILE, CONFIG_DEFAULTS, ConfigError, configFilePath, parseConfigText, resolveConfig, createTextOutput, activeOutput, activeOutputMode, RAFA_BLOCK_KINDS, isRafaBlockKind, readRafaBlocks };