@open-tomato/rafa 0.41.0 → 0.43.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.
@@ -1,48 +1,451 @@
1
1
  // @bun
2
2
  import {
3
- BOARD_PROJECT_TEMPLATE_DEFAULT,
4
- BOARD_RELATIONSHIP_MODES,
5
- CLAUDE_SETTING_SOURCES,
6
- CONFIG_VERSIONS,
7
- INJECT_MODES,
8
- OUTPUT_MODES,
9
- PR_PROVIDERS,
10
- RELEASE_AUTO,
11
- SKILL_TIERS,
12
- STORE_BACKENDS,
13
- SYNC_STRATEGIES,
14
- below,
15
- busyTimeoutMs,
16
- claimsAhead,
17
- claimsStaleAfter,
18
- confidence,
19
- dayCount,
20
- describeValue,
21
- flag,
22
- githubLogin,
23
- isMapping,
24
- isSkillTier,
25
- issueNumber,
26
- lessonSwitch,
27
- listOf,
28
- mapReading,
29
- mergeMethod,
30
- messageOf,
31
- oneOf,
32
- projectNumber,
33
- projectUrl,
34
- recurrenceCount,
35
- refused,
36
- refusedWith,
37
- releaseEnabled,
38
- routeTarget,
39
- skillResolverName,
40
- subsetOf,
41
- text,
42
- tierPin,
43
- tierSwitch,
44
- usdAmount
45
- } from "./index-m2ahxjw8.js";
3
+ CONFIDENCE_MAX,
4
+ CONFIDENCE_MIN
5
+ } from "./index-hf2k497d.js";
6
+
7
+ // src/pr/types.ts
8
+ var MERGE_METHODS = Object.freeze([
9
+ "squash",
10
+ "merge",
11
+ "rebase"
12
+ ]);
13
+ function isMergeMethod(value) {
14
+ return typeof value === "string" && MERGE_METHODS.includes(value);
15
+ }
16
+
17
+ // src/schema/tiers.ts
18
+ import { existsSync, realpathSync } from "fs";
19
+ import { dirname, join } from "path";
20
+ var SKILL_TIERS = ["project", "rafa", "user"];
21
+ var INSTINCT_SCOPES = ["project", "user"];
22
+ var CLAUDE_SKILLS_PATH = join(".claude", "skills");
23
+ var RAFA_INSTINCTS_PATH = join(".rafa", "instincts");
24
+ var BUNDLED_SKILLS_DIR = join("bundled", "skills");
25
+ var BUNDLED_BIN_DIR = join("bundled", "bin");
26
+ function isSkillTier(value) {
27
+ return SKILL_TIERS.includes(value);
28
+ }
29
+ function realEntry(entry) {
30
+ try {
31
+ return realpathSync(entry);
32
+ } catch {
33
+ return entry;
34
+ }
35
+ }
36
+ function bundledSkillsDirectory(entry) {
37
+ return join(dirname(realEntry(entry ?? Bun.main)), BUNDLED_SKILLS_DIR);
38
+ }
39
+ function bundledBinDirectory(entry) {
40
+ return join(dirname(realEntry(entry ?? Bun.main)), BUNDLED_BIN_DIR);
41
+ }
42
+ function skillTierDirectory(tier, seams) {
43
+ if (tier === "user")
44
+ return join(seams.home, CLAUDE_SKILLS_PATH);
45
+ if (tier === "rafa")
46
+ return bundledSkillsDirectory(seams.entry);
47
+ return seams.projectRoot === null ? null : join(seams.projectRoot, CLAUDE_SKILLS_PATH);
48
+ }
49
+ function resolveSkillTiers(seams) {
50
+ const located = [];
51
+ for (const tier of SKILL_TIERS) {
52
+ const dir = skillTierDirectory(tier, seams);
53
+ if (dir !== null)
54
+ located.push({ tier, dir });
55
+ }
56
+ return located;
57
+ }
58
+ function instinctScopeDirectory(scope, seams) {
59
+ if (scope === "user")
60
+ return join(seams.home, RAFA_INSTINCTS_PATH);
61
+ return seams.projectRoot === null ? null : join(seams.projectRoot, RAFA_INSTINCTS_PATH);
62
+ }
63
+ function resolveInstinctScopes(seams) {
64
+ const located = [];
65
+ for (const scope of INSTINCT_SCOPES) {
66
+ const dir = instinctScopeDirectory(scope, seams);
67
+ if (dir !== null)
68
+ located.push({ scope, dir });
69
+ }
70
+ return located;
71
+ }
72
+ function tierExists(dir) {
73
+ return existsSync(dir);
74
+ }
75
+
76
+ // src/utils/declaration.ts
77
+ var DECLARATION_KEYS = [
78
+ "agent",
79
+ "model",
80
+ "effort",
81
+ "budget",
82
+ "tools",
83
+ "skills",
84
+ "tests"
85
+ ];
86
+ var GRANULAR_KEYS = ["model", "effort", "tools"];
87
+ var AGENT_OWNED_KEYS = ["model", "tools"];
88
+ var MODEL_ALIASES = ["opus", "sonnet", "haiku", "fable"];
89
+ var EFFORT_LEVELS = [
90
+ "low",
91
+ "medium",
92
+ "high",
93
+ "xhigh",
94
+ "max"
95
+ ];
96
+ var TEST_SCOPES = ["affected", "module", "full"];
97
+ var DEFAULT_TEST_SCOPE = "affected";
98
+ var DECLARATION_BLOCK = /\{([^{}]*)\}$/;
99
+ var ENTRY_TOKEN = /^([A-Za-z_][A-Za-z0-9_-]*)=(.*)$/;
100
+ var FULL_MODEL_NAME = /^claude-[a-z0-9][a-z0-9.-]*$/;
101
+ var AGENT_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
102
+ function isAgentName(value) {
103
+ return AGENT_NAME.test(value);
104
+ }
105
+ var TOOL_NAME = /^[A-Za-z][A-Za-z0-9_-]*$/;
106
+ var SKILL_NAME = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
107
+ var BUDGET_FLAG = "--max-budget-usd";
108
+ var BUDGET_VALUE = /^(?:0|[1-9]\d{0,5})(?:\.\d{1,6})?$/;
109
+ function parseBudgetUsd(value) {
110
+ if (!BUDGET_VALUE.test(value))
111
+ return null;
112
+ const usd = Number(value);
113
+ return usd > 0 ? usd : null;
114
+ }
115
+ function isEffortLevel(value) {
116
+ return EFFORT_LEVELS.includes(value);
117
+ }
118
+ function isTestScope(value) {
119
+ return TEST_SCOPES.includes(value);
120
+ }
121
+ function readTestScope(declaration) {
122
+ return declaration?.tests ?? DEFAULT_TEST_SCOPE;
123
+ }
124
+ function isModelValue(value) {
125
+ if (MODEL_ALIASES.includes(value))
126
+ return true;
127
+ return FULL_MODEL_NAME.test(value);
128
+ }
129
+ function parseToolList(value) {
130
+ const parts = value.split(",");
131
+ if (parts.length === 0)
132
+ return null;
133
+ const tools = [];
134
+ for (const part of parts) {
135
+ if (!TOOL_NAME.test(part))
136
+ return null;
137
+ if (!tools.includes(part))
138
+ tools.push(part);
139
+ }
140
+ return tools;
141
+ }
142
+ function parseSkillList(value) {
143
+ const parts = value.split(",");
144
+ if (parts.length === 0)
145
+ return null;
146
+ const skills = [];
147
+ for (const part of parts) {
148
+ if (!SKILL_NAME.test(part))
149
+ return null;
150
+ if (!skills.includes(part))
151
+ skills.push(part);
152
+ }
153
+ return skills;
154
+ }
155
+ function unusableValue(key, token) {
156
+ return { reason: "unusable-value", key, text: token };
157
+ }
158
+ function isDeclarationKey(key) {
159
+ return DECLARATION_KEYS.includes(key);
160
+ }
161
+ function tokenise(body) {
162
+ const trimmed = body.trim();
163
+ if (trimmed.length === 0)
164
+ return [];
165
+ return trimmed.split(/\s+/);
166
+ }
167
+ function readBlock(raw, body) {
168
+ const entries = [];
169
+ const extras = [];
170
+ const issues = [];
171
+ const seen = new Set;
172
+ let agent = null;
173
+ let model = null;
174
+ let effort = null;
175
+ let budget = null;
176
+ let tools = null;
177
+ let skills = null;
178
+ let tests = null;
179
+ for (const token of tokenise(body)) {
180
+ const match = token.match(ENTRY_TOKEN);
181
+ const key = match?.[1];
182
+ const value = match?.[2];
183
+ if (key === undefined || value === undefined) {
184
+ issues.push({ reason: "stray-token", key: "", text: token });
185
+ continue;
186
+ }
187
+ const entry = { key, value };
188
+ entries.push(entry);
189
+ if (!isDeclarationKey(key)) {
190
+ extras.push(entry);
191
+ continue;
192
+ }
193
+ if (seen.has(key)) {
194
+ issues.push({ reason: "duplicate-key", key, text: token });
195
+ continue;
196
+ }
197
+ seen.add(key);
198
+ if (key === "agent") {
199
+ if (AGENT_NAME.test(value))
200
+ agent = value;
201
+ else
202
+ issues.push(unusableValue(key, token));
203
+ continue;
204
+ }
205
+ if (key === "model") {
206
+ if (isModelValue(value))
207
+ model = value;
208
+ else
209
+ issues.push(unusableValue(key, token));
210
+ continue;
211
+ }
212
+ if (key === "effort") {
213
+ if (isEffortLevel(value))
214
+ effort = value;
215
+ else
216
+ issues.push(unusableValue(key, token));
217
+ continue;
218
+ }
219
+ if (key === "budget") {
220
+ const usd = parseBudgetUsd(value);
221
+ if (usd === null)
222
+ issues.push(unusableValue(key, token));
223
+ else
224
+ budget = usd;
225
+ continue;
226
+ }
227
+ if (key === "tools") {
228
+ const parsed = parseToolList(value);
229
+ if (parsed === null)
230
+ issues.push(unusableValue(key, token));
231
+ else
232
+ tools = parsed;
233
+ continue;
234
+ }
235
+ if (key === "tests") {
236
+ if (isTestScope(value))
237
+ tests = value;
238
+ else
239
+ issues.push(unusableValue(key, token));
240
+ continue;
241
+ }
242
+ const named = parseSkillList(value);
243
+ if (named === null)
244
+ issues.push(unusableValue(key, token));
245
+ else
246
+ skills = named;
247
+ }
248
+ if (seen.size === 0)
249
+ return null;
250
+ return {
251
+ raw,
252
+ entries,
253
+ extras,
254
+ issues,
255
+ agent,
256
+ model,
257
+ effort,
258
+ budget,
259
+ tools,
260
+ skills,
261
+ tests
262
+ };
263
+ }
264
+ function parseTaskDeclaration(taskText) {
265
+ const trimmed = taskText.trimEnd();
266
+ const match = trimmed.match(DECLARATION_BLOCK);
267
+ const raw = match?.[0];
268
+ const body = match?.[1];
269
+ if (raw === undefined || body === undefined) {
270
+ return { text: taskText, declaration: null };
271
+ }
272
+ const before = trimmed.slice(0, trimmed.length - raw.length).trimEnd();
273
+ if (before.length === 0)
274
+ return { text: taskText, declaration: null };
275
+ const declaration = readBlock(raw, body);
276
+ if (declaration === null)
277
+ return { text: taskText, declaration: null };
278
+ return { text: before, declaration };
279
+ }
280
+ function stripTaskDeclaration(taskText) {
281
+ return parseTaskDeclaration(taskText).text;
282
+ }
283
+ function budgetArgs(budget) {
284
+ return budget === null ? [] : [BUDGET_FLAG, String(budget)];
285
+ }
286
+ function resolveDeclarationFlags(declaration, agentDeclaresEffort) {
287
+ if (declaration === null)
288
+ return { args: [], suppressed: [] };
289
+ const { agent, effort } = declaration;
290
+ const isPresent = (key) => declaration[key] !== null;
291
+ const budget = budgetArgs(declaration.budget);
292
+ if (agent !== null) {
293
+ if (effort === null || agentDeclaresEffort(agent)) {
294
+ return { args: ["--agent", agent, ...budget], suppressed: GRANULAR_KEYS.filter(isPresent) };
295
+ }
296
+ return {
297
+ args: ["--agent", agent, "--effort", effort, ...budget],
298
+ suppressed: AGENT_OWNED_KEYS.filter(isPresent)
299
+ };
300
+ }
301
+ const args = [];
302
+ if (declaration.model !== null)
303
+ args.push("--model", declaration.model);
304
+ if (declaration.effort !== null)
305
+ args.push("--effort", declaration.effort);
306
+ args.push(...budget);
307
+ if (declaration.tools !== null) {
308
+ args.push("--tools", declaration.tools.join(","));
309
+ }
310
+ return { args, suppressed: [] };
311
+ }
312
+
313
+ // src/config-sections.ts
314
+ function isMapping(value) {
315
+ return typeof value === "object" && value !== null && !Array.isArray(value);
316
+ }
317
+ function describeValue(value) {
318
+ if (typeof value === "string")
319
+ return JSON.stringify(value);
320
+ if (value === null)
321
+ return "null";
322
+ if (Array.isArray(value))
323
+ return "a list";
324
+ if (typeof value === "object")
325
+ return "a mapping";
326
+ return String(value);
327
+ }
328
+ function messageOf(error) {
329
+ return error instanceof Error ? error.message : String(error);
330
+ }
331
+ var CONFIG_VERSIONS = [1];
332
+ var STORE_BACKENDS = ["sqlite", "ndjson"];
333
+ var SYNC_STRATEGIES = ["local", "file", "git", "service", "p2p"];
334
+ var BOARD_RELATIONSHIP_MODES = ["labels", "native"];
335
+ var INJECT_MODES = ["full", "stage", "task"];
336
+ var OUTPUT_MODES = ["text", "json", "events"];
337
+ var CLAUDE_SETTING_SOURCES = ["user", "project", "local"];
338
+ var PR_PROVIDERS = ["gh", "none"];
339
+ var RELEASE_AUTO = "auto";
340
+ var TIER_SWITCHES = ["on", "off"];
341
+ var SKILL_RESOLVERS = ["planner", "tag", "none"];
342
+ var LESSON_SWITCHES = ["on", "off"];
343
+ var CLAIMS_STALE_DISABLED = "disabled";
344
+ var CLAIMS_AHEAD = ["off", "allow"];
345
+ function accepted(value) {
346
+ return { value, problems: [], extras: [] };
347
+ }
348
+ function refused(at, raw, expected) {
349
+ const problem = `${at.label} is ${describeValue(raw)}, expected ${expected}`;
350
+ return { value: undefined, problems: [problem], extras: [] };
351
+ }
352
+ function refusedWith(problems) {
353
+ return { value: undefined, problems, extras: [] };
354
+ }
355
+ function mapReading(reading, map) {
356
+ return reading.value === undefined ? { ...reading, value: undefined } : { ...reading, value: map(reading.value) };
357
+ }
358
+ function below(at, step) {
359
+ return { label: `${at.label}${step}`, key: `${at.key}${step}` };
360
+ }
361
+ function oneOf(values) {
362
+ const expected = `one of: ${values.join(", ")}`;
363
+ return (raw, at) => {
364
+ const hit = values.find((value) => value === raw);
365
+ return hit === undefined ? refused(at, raw, expected) : accepted(hit);
366
+ };
367
+ }
368
+ function text(expected) {
369
+ return (raw, at) => typeof raw === "string" && raw.trim() !== "" ? accepted(raw) : refused(at, raw, expected);
370
+ }
371
+ var flag = (raw, at) => typeof raw === "boolean" ? accepted(raw) : refused(at, raw, "true or false");
372
+ var mergeMethod = oneOf(MERGE_METHODS);
373
+ var usdAmount = (raw, at) => {
374
+ const usd = typeof raw === "number" ? parseBudgetUsd(String(raw)) : null;
375
+ return usd === null ? refused(at, raw, "a number of US dollars above zero, at most six digits either side of the point") : accepted(usd);
376
+ };
377
+ 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");
378
+ var dayCount = (raw, at) => typeof raw === "number" && Number.isSafeInteger(raw) && raw > 0 ? accepted(raw) : refused(at, raw, "a number of days, a whole number above zero");
379
+ var BUSY_TIMEOUT_MIN_MS = 1;
380
+ var BUSY_TIMEOUT_MAX_MS = 60000;
381
+ function isBusyTimeoutMs(raw) {
382
+ return typeof raw === "number" && Number.isSafeInteger(raw) && raw >= BUSY_TIMEOUT_MIN_MS && raw <= BUSY_TIMEOUT_MAX_MS;
383
+ }
384
+ var busyTimeoutMs = (raw, at) => isBusyTimeoutMs(raw) ? accepted(raw) : refused(at, raw, `a lock wait in milliseconds, a whole number from ${String(BUSY_TIMEOUT_MIN_MS)} to ${String(BUSY_TIMEOUT_MAX_MS)}`);
385
+ var confidence = (raw, at) => typeof raw === "number" && Number.isFinite(raw) && toHundredths(raw) >= toHundredths(CONFIDENCE_MIN) && toHundredths(raw) <= toHundredths(CONFIDENCE_MAX) ? accepted(raw) : refused(at, raw, `a confidence from ${String(CONFIDENCE_MIN)} to ${String(CONFIDENCE_MAX)}`);
386
+ function toHundredths(value) {
387
+ return Math.round(value * 100);
388
+ }
389
+ var recurrenceCount = (raw, at) => typeof raw === "number" && Number.isSafeInteger(raw) && raw >= 1 ? accepted(raw) : refused(at, raw, "a count, a whole number of 1 or more");
390
+ var releaseEnabled = (raw, at) => typeof raw === "boolean" || raw === RELEASE_AUTO ? accepted(raw) : refused(at, raw, `true, false or ${RELEASE_AUTO}`);
391
+ var tierSwitch = oneOf(TIER_SWITCHES);
392
+ var tierPin = (raw, at) => {
393
+ if (raw === false)
394
+ return accepted(false);
395
+ const tier = SKILL_TIERS.find((name) => name === raw);
396
+ return tier === undefined ? refused(at, raw, `false or one of: ${SKILL_TIERS.join(", ")}`) : accepted(tier);
397
+ };
398
+ var routeTarget = (raw, at) => raw === false || typeof raw === "string" && raw.trim() !== "" ? accepted(raw) : refused(at, raw, "false or an agent name");
399
+ var skillResolverName = oneOf(SKILL_RESOLVERS);
400
+ var lessonSwitch = oneOf(LESSON_SWITCHES);
401
+ var CLAIM_DURATION = /^(\d+)([hd])$/;
402
+ var HOURS_PER_DAY = 24;
403
+ function claimDurationHours(raw) {
404
+ const match = typeof raw === "string" ? CLAIM_DURATION.exec(raw) : null;
405
+ if (match === null)
406
+ return null;
407
+ const hours = Number(match[1]) * (match[2] === "d" ? HOURS_PER_DAY : 1);
408
+ return Number.isSafeInteger(hours) && hours > 0 ? hours : null;
409
+ }
410
+ var claimsStaleAfter = (raw, at) => raw === CLAIMS_STALE_DISABLED || claimDurationHours(raw) !== null ? accepted(raw) : refused(at, raw, `a duration of whole hours or days above zero, such as 36h or 3d, or ${CLAIMS_STALE_DISABLED}`);
411
+ var claimsAhead = oneOf(CLAIMS_AHEAD);
412
+ var GITHUB_LOGIN = /^[A-Za-z0-9][A-Za-z0-9-]*(?:\[bot\])?$/;
413
+ function isGitHubLogin(value) {
414
+ return typeof value === "string" && GITHUB_LOGIN.test(value);
415
+ }
416
+ var githubLogin = (raw, at) => isGitHubLogin(raw) ? accepted(raw) : refused(at, raw, "a GitHub login");
417
+ var BOARD_PROJECT_TEMPLATE_DEFAULT = "https://github.com/orgs/open-tomato/projects/6";
418
+ var PROJECT_URL = /^https:\/\/github\.com\/(?:orgs|users)\/[A-Za-z0-9][A-Za-z0-9-]*\/projects\/[1-9]\d*$/;
419
+ var projectUrl = (raw, at) => typeof raw === "string" && PROJECT_URL.test(raw) ? accepted(raw) : refused(at, raw, "a GitHub project URL, https://github.com/orgs/<owner>/projects/<number> or /users/<owner>/\u2026");
420
+ var projectNumber = (raw, at) => typeof raw === "number" && Number.isSafeInteger(raw) && raw > 0 ? accepted(raw) : refused(at, raw, "a project number, a whole number above zero");
421
+ function listOf(item, expected) {
422
+ return (raw, at) => {
423
+ if (!Array.isArray(raw))
424
+ return refused(at, raw, `a list of ${expected}`);
425
+ const readings = raw.map((entry, index) => item(entry, below(at, `[${index}]`)));
426
+ const problems = readings.flatMap((reading) => reading.problems);
427
+ const extras = readings.flatMap((reading) => reading.extras);
428
+ if (problems.length > 0)
429
+ return { value: undefined, problems, extras };
430
+ const values = readings.flatMap((reading) => reading.value === undefined ? [] : [reading.value]);
431
+ return { value: Object.freeze(values), problems, extras };
432
+ };
433
+ }
434
+ function subsetOf(values) {
435
+ const expected = `a comma-separated subset of: ${values.join(", ")}`;
436
+ return (raw, at) => {
437
+ if (typeof raw !== "string")
438
+ return refused(at, raw, expected);
439
+ const named = [];
440
+ for (const entry of raw.split(",")) {
441
+ const hit = values.find((value) => value === entry.trim());
442
+ if (hit === undefined || named.includes(hit))
443
+ return refused(at, raw, expected);
444
+ named.push(hit);
445
+ }
446
+ return accepted(Object.freeze(named));
447
+ };
448
+ }
46
449
 
47
450
  // src/config-items.ts
48
451
  var itemText = text("a non-empty string");
@@ -117,7 +520,7 @@ var moduleSource = (raw, at) => {
117
520
  };
118
521
 
119
522
  // src/config-schema.ts
120
- import { join as join2 } from "path";
523
+ import { join as join3 } from "path";
121
524
 
122
525
  // src/config-readers.ts
123
526
  import { isAbsolute, normalize } from "path";
@@ -234,6 +637,163 @@ var BOARD_PROJECT_LIMIT_SETTINGS = {
234
637
  }
235
638
  };
236
639
 
640
+ // src/errors/codes.ts
641
+ var ERROR_LEVELS = Object.freeze(["error", "warn"]);
642
+ var CODE_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*:[a-z0-9]+(?:-[a-z0-9]+)*$/;
643
+ var NEW_CONTEXT_LEAF = "new-context";
644
+ var UNKNOWN_FAMILY = "unknown";
645
+ var TEXT_FIELDS = ["description", "hint", "since"];
646
+ var PREFIX = "error codes";
647
+ function familyOf(code) {
648
+ return code.slice(0, code.indexOf(":"));
649
+ }
650
+ function codeProblem(code) {
651
+ if (!CODE_PATTERN.test(code))
652
+ return `${JSON.stringify(code)} is not <family>:<leaf> in kebab-case`;
653
+ const leaf = code.slice(code.indexOf(":") + 1);
654
+ if (leaf === NEW_CONTEXT_LEAF || leaf === UNKNOWN_FAMILY) {
655
+ return `${JSON.stringify(code)} uses the reserved leaf ${leaf}`;
656
+ }
657
+ if (familyOf(code) === UNKNOWN_FAMILY)
658
+ return `${JSON.stringify(code)} uses the reserved family ${UNKNOWN_FAMILY}`;
659
+ return null;
660
+ }
661
+ function entryProblem(entry, index) {
662
+ const codeFault = codeProblem(entry.code);
663
+ if (codeFault !== null)
664
+ return `[${index}].code ${codeFault}`;
665
+ const empty = TEXT_FIELDS.find((field) => (entry[field] ?? "").trim() === "");
666
+ if (empty !== undefined)
667
+ return `[${index}].${empty} is empty`;
668
+ if (!ERROR_LEVELS.includes(entry.level)) {
669
+ return `[${index}].level is ${JSON.stringify(entry.level)}, expected ${ERROR_LEVELS.join(" or ")}`;
670
+ }
671
+ return null;
672
+ }
673
+ function defineErrorCodes(entries) {
674
+ entries.forEach((entry, index) => {
675
+ const fault = entryProblem(entry, index);
676
+ if (fault !== null)
677
+ throw new TypeError(`${PREFIX}: ${fault}`);
678
+ const first = entries.findIndex((other) => other.code === entry.code);
679
+ if (first < index) {
680
+ throw new TypeError(`${PREFIX}: ${JSON.stringify(entry.code)} is declared at [${first}] and [${index}]`);
681
+ }
682
+ });
683
+ return Object.freeze(entries.map((entry) => Object.freeze({ ...entry })));
684
+ }
685
+
686
+ // src/errors/rafa-codes.ts
687
+ var SEED = "#949";
688
+ var FAMILY_DESCRIPTIONS = Object.freeze({
689
+ api: "a call to a remote service",
690
+ cli: "routing a command line and running the command",
691
+ config: "reading the project or user config",
692
+ fs: "reading or writing files",
693
+ git: "running git",
694
+ input: "a value given on the command line or in a file",
695
+ skill: "a skill file's frontmatter",
696
+ spawn: "starting a child process",
697
+ tracker: "the issue tracker",
698
+ tsc: "type-checking"
699
+ });
700
+ var RAFA_CODES = defineErrorCodes([
701
+ { code: "cli:unknown-subject", description: "the first word names no subject", hint: "run rafa --help for the subjects", level: "error", since: SEED, legacy: "unknown_subject" },
702
+ { code: "cli:missing-action", description: "a subject is named with no action after it", hint: "run rafa <subject> --help for its actions", level: "error", since: SEED, legacy: "missing_action" },
703
+ { code: "cli:unknown-action", description: "the action names nothing the subject holds", hint: "run rafa <subject> --help for its actions", level: "error", since: SEED, legacy: "unknown_action" },
704
+ { code: "cli:unknown-module", description: "a module command names a module that is not mounted", hint: "run rafa module list for the modules that are mounted", level: "error", since: SEED, legacy: "unknown_module" },
705
+ { code: "cli:unexpected-version", description: "--version is typed beside a command, where it is read alone", hint: "run rafa --version alone, with no command beside it", level: "error", since: SEED, legacy: "unexpected_version" },
706
+ { code: "cli:invalid-spec", description: "a command declares a shape the dispatcher refuses", hint: "fix the command declaration the message names", level: "error", since: SEED, legacy: "invalid_spec" },
707
+ { code: "cli:no-project", description: "a command that needs a project ran outside one", hint: "run it inside a project, or run rafa init", level: "error", since: SEED, legacy: "no_project" },
708
+ { code: "cli:command-exit", description: "a command refused and set its own exit code", hint: "read the message above it", level: "error", since: SEED, legacy: "command_exit" },
709
+ { code: "cli:command-error", description: "a command threw an error it did not handle", hint: "file a bug with the stack", level: "error", since: SEED, legacy: "command_error" },
710
+ { code: "cli:result-unwritable", description: "the command result cannot be written, as with data JSON refuses", hint: "file a bug: the command answered data that cannot be written as JSON", level: "error", since: SEED, legacy: "result_unwritable" },
711
+ { code: "skill:missing-field", description: "a required frontmatter field is absent or empty", hint: "add the field the message names", level: "error", since: SEED, legacy: "missing-field" },
712
+ { code: "skill:wrong-type", description: "a frontmatter field has a type the schema cannot read", hint: "write the field as the schema expects", level: "error", since: SEED, legacy: "wrong-type" },
713
+ { code: "skill:invalid-name", description: "a skill name is not kebab-case", hint: "rename the skill in kebab-case", level: "error", since: SEED, legacy: "invalid-name" },
714
+ { code: "skill:description-too-long", description: "the description is at or past its limit", hint: "shorten the description", level: "error", since: SEED, legacy: "description-too-long" },
715
+ { code: "skill:listing-too-long", description: "description and when_to_use together pass the listing limit", hint: "shorten when_to_use", level: "error", since: SEED, legacy: "listing-too-long" },
716
+ { code: "skill:unknown-stack", description: "a stack entry is outside the vocabulary", hint: "use a stack the schema lists", level: "error", since: SEED, legacy: "unknown-stack" },
717
+ { code: "skill:unpaired-prevents", description: "prevents is set without signal, or signal without prevents", hint: "set both or neither", level: "error", since: SEED, legacy: "unpaired-prevents" },
718
+ { code: "skill:unknown-signal", description: "signal holds a value the schema does not list", hint: "use a listed signal", level: "error", since: SEED, legacy: "unknown-signal" },
719
+ { code: "skill:unusable-glob", description: "a paths glob gates on nothing it seems to gate on", hint: "rewrite the glob", level: "error", since: SEED, legacy: "unusable-glob" },
720
+ { code: "skill:forbidden-field", description: "an instinct field appears on a skill", hint: "remove the field", level: "error", since: SEED, legacy: "forbidden-field" },
721
+ { code: "skill:unknown-provenance", description: "provenance names neither first-party nor a known mapping", hint: "use first-party or a known mapping", level: "error", since: SEED, legacy: "unknown-provenance" },
722
+ { code: "skill:invalid-reviewed", description: "provenance.reviewed is not who and a date", hint: "write <who> <YYYY-MM-DD>", level: "error", since: SEED, legacy: "invalid-reviewed" },
723
+ { code: "skill:empty-failure-string", description: "a failure_strings entry is blank", hint: "remove the blank entry", level: "error", since: SEED, legacy: "empty-failure-string" },
724
+ { code: "skill:short-failure-string", description: "a failure_strings entry is too short to name one failure", hint: "lengthen the entry", level: "warn", since: SEED, legacy: "short-failure-string" },
725
+ { code: "git:no-identity", description: "a git commit runs with no author identity set, often in a scratch home", hint: "set user.name and user.email where the commit runs", level: "error", since: SEED },
726
+ { code: "git:filesystem-boundary", description: "git prints an extra line when run outside a repository", hint: "run inside a repository, or read only the first line", level: "warn", since: SEED },
727
+ { code: "tsc:tests-excluded", description: "type errors in test files stay hidden because tsc excludes tests", hint: "type-check the test files", level: "error", since: SEED },
728
+ { code: "fs:gitignored-fixture", description: "a test reads a gitignored file a fresh checkout lacks", hint: "plant the file in the test, never read the live one", level: "error", since: SEED },
729
+ { code: "fs:live-home", description: "a test reads the live home directory instead of a planted one", hint: "point HOME at a scratch folder in the test", level: "error", since: SEED },
730
+ { code: "fs:not-found", description: "a file or folder the step needs is missing", hint: "create it, or fix the path", level: "error", since: SEED },
731
+ { code: "input:missing-param", description: "a required value was not given", hint: "pass the value the message names", level: "error", since: SEED },
732
+ { code: "input:invalid-value", description: "a given value is outside what the step accepts", hint: "pass one of the values the message lists", level: "error", since: SEED },
733
+ { code: "api:http-status", description: "a remote service answered with an error status", hint: "read the status, then retry or fix the request", level: "error", since: SEED },
734
+ { code: "config:invalid-value", description: "a config key holds a value its reader refuses", hint: "fix the key the message names", level: "error", since: SEED },
735
+ { code: "tracker:unreachable", description: "the issue tracker could not be reached", hint: "check the network and gh auth status", level: "error", since: SEED },
736
+ { code: "tracker:unavailable", description: "a tracker kind could not be made or failed its preflight, and the chain passed it over", hint: "read the reason given, or leave the fallback tracker in use", level: "warn", since: "#950" },
737
+ { code: "spawn:nonzero-exit", description: "a child process exited nonzero", hint: "read the child's stderr above", level: "error", since: SEED }
738
+ ]);
739
+
740
+ // src/config-schema-errors.ts
741
+ var ERROR_CODE_KEYS = ["code", "description", "hint", "level", "since"];
742
+ var KNOWN_KEYS = new Set(ERROR_CODE_KEYS);
743
+ var ERRORS_DEFAULTS = Object.freeze({ errorsCodes: Object.freeze([]) });
744
+ var codeText = (raw, at) => {
745
+ const read = text("a code, <family>:<leaf>")(raw, at);
746
+ if (read.value === undefined)
747
+ return read;
748
+ const fault = codeProblem(read.value);
749
+ return fault === null ? read : refused(at, raw, `a code: ${fault}`);
750
+ };
751
+ var EXPECTED = {
752
+ description: "a one-line description, a non-empty string",
753
+ hint: "the next action, a non-empty string",
754
+ since: "the version or issue that added it, a non-empty string"
755
+ };
756
+ var NO_SINCE = { value: undefined, problems: [], extras: [] };
757
+ var errorCodeEntry = (raw, at) => {
758
+ if (!isMapping(raw))
759
+ return refused(at, raw, `a mapping of: ${ERROR_CODE_KEYS.join(", ")}`);
760
+ const code = codeText(raw.code, below(at, ".code"));
761
+ const description = text(EXPECTED.description)(raw.description, below(at, ".description"));
762
+ const hint = text(EXPECTED.hint)(raw.hint, below(at, ".hint"));
763
+ const level = oneOf(ERROR_LEVELS)(raw.level, below(at, ".level"));
764
+ const since = raw.since === undefined || raw.since === null ? NO_SINCE : text(EXPECTED.since)(raw.since, below(at, ".since"));
765
+ const problems = [code, description, hint, level, since].flatMap((reading) => reading.problems);
766
+ const extras = Object.entries(raw).filter(([key]) => !KNOWN_KEYS.has(key)).map(([key, value]) => ({ key: `${at.key}.${key}`, value }));
767
+ if (code.value === undefined || description.value === undefined || hint.value === undefined || level.value === undefined || problems.length > 0)
768
+ return { value: undefined, problems, extras };
769
+ const entry = {
770
+ code: code.value,
771
+ description: description.value,
772
+ hint: hint.value,
773
+ level: level.value,
774
+ ...since.value === undefined ? {} : { since: since.value }
775
+ };
776
+ return { value: Object.freeze(entry), problems: [], extras };
777
+ };
778
+ var errorCodes = (raw, at) => {
779
+ const read = listOf(errorCodeEntry, "error code entries")(raw, at);
780
+ const entries = read.value;
781
+ if (entries === undefined)
782
+ return read;
783
+ const problems = entries.flatMap((entry, index) => {
784
+ const label = `${below(at, `[${index}]`).label}.code is ${JSON.stringify(entry.code)}`;
785
+ if (RAFA_CODES.some((own) => own.code === entry.code)) {
786
+ return [`${label}, which rafa already declares; add a leaf of your own instead`];
787
+ }
788
+ const first = entries.findIndex((other) => other.code === entry.code);
789
+ return first < index ? [`${label}, already given at [${first}]`] : [];
790
+ });
791
+ return problems.length === 0 ? read : { ...refusedWith(problems), extras: read.extras };
792
+ };
793
+ var ERRORS_SETTINGS = {
794
+ errorsCodes: { key: "errors.codes", read: errorCodes, cli: false, itemKeys: ERROR_CODE_KEYS }
795
+ };
796
+
237
797
  // src/config-schema-hub.ts
238
798
  var HUB_TIMEOUT_MIN_SECONDS = 1;
239
799
  var HUB_TIMEOUT_MAX_SECONDS = 30;
@@ -289,8 +849,302 @@ function hubContextOf(config) {
289
849
  return Object.freeze({ url, tokenSecret: config.hubTokenSecret ?? null, timeoutMs });
290
850
  }
291
851
 
852
+ // src/bugs/places.ts
853
+ var BUILT_IN_ENVIRONMENTS = ["local", "ci", "prod"];
854
+
855
+ // src/bugs/title.ts
856
+ var TITLE_VARIABLES = ["type", "project", "projectType", "module", "action", "summary"];
857
+ var UNKNOWN_VALUE = "unknown";
858
+ var DEFAULT_BUG_TITLE = "?(${module}?( - ${action}): )${summary}";
859
+ var MODULE_SHAPE = "[a-z0-9][a-z0-9._-]*";
860
+ var ACTION_SHAPE = "[a-z0-9][a-z0-9._ -]*";
861
+ var SUMMARY = "summary";
862
+ var VARIABLE_SHAPES = { module: MODULE_SHAPE, action: ACTION_SHAPE };
863
+ var FREE_SHAPE = ".+";
864
+ var GROUP_OPEN = "?(";
865
+ var GROUP_CLOSE = ")";
866
+ var VARIABLE_OPEN = "${";
867
+ var VARIABLE_CLOSE = "}";
868
+
869
+ class TitleTemplateError extends Error {
870
+ constructor(message) {
871
+ super(message);
872
+ this.name = "TitleTemplateError";
873
+ }
874
+ }
875
+ function isPrintable(value) {
876
+ return typeof value === "string" && value.trim() !== "" && value.trim() !== UNKNOWN_VALUE;
877
+ }
878
+ function isTitleVariable(name) {
879
+ return TITLE_VARIABLES.includes(name);
880
+ }
881
+ function readVariable(template, at) {
882
+ const close = template.indexOf(VARIABLE_CLOSE, at);
883
+ if (close === -1)
884
+ throw new TitleTemplateError(`"${VARIABLE_OPEN}" at ${at} is never closed`);
885
+ const name = template.slice(at + VARIABLE_OPEN.length, close);
886
+ if (!isTitleVariable(name)) {
887
+ throw new TitleTemplateError(`\${${name}} names no variable, expected one of: ${TITLE_VARIABLES.join(", ")}`);
888
+ }
889
+ return { node: { kind: "variable", name }, next: close + VARIABLE_CLOSE.length };
890
+ }
891
+ function readNodes(template, start, depth) {
892
+ const nodes = [];
893
+ let text2 = "";
894
+ let at = start;
895
+ const flush = () => {
896
+ if (text2 !== "")
897
+ nodes.push({ kind: "text", text: text2 });
898
+ text2 = "";
899
+ };
900
+ while (at < template.length) {
901
+ if (template.startsWith(GROUP_OPEN, at)) {
902
+ flush();
903
+ const inner = readNodes(template, at + GROUP_OPEN.length, depth + 1);
904
+ nodes.push({ kind: "group", nodes: inner.nodes });
905
+ at = inner.next;
906
+ } else if (depth > 0 && template[at] === GROUP_CLOSE) {
907
+ flush();
908
+ return { nodes, next: at + GROUP_CLOSE.length };
909
+ } else if (template.startsWith(VARIABLE_OPEN, at)) {
910
+ flush();
911
+ const variable = readVariable(template, at);
912
+ nodes.push(variable.node);
913
+ at = variable.next;
914
+ } else {
915
+ text2 += template[at];
916
+ at += 1;
917
+ }
918
+ }
919
+ if (depth > 0)
920
+ throw new TitleTemplateError(`a "${GROUP_OPEN}" group is never closed`);
921
+ flush();
922
+ return { nodes, next: at };
923
+ }
924
+ function parseTitleTemplate(template) {
925
+ return readNodes(template, 0, 0).nodes;
926
+ }
927
+ function variablesOf(nodes) {
928
+ return nodes.flatMap((node) => {
929
+ if (node.kind === "variable")
930
+ return [node.name];
931
+ return node.kind === "group" ? variablesOf(node.nodes) : [];
932
+ });
933
+ }
934
+ function groupShows(nodes, values) {
935
+ return nodes.every((node) => node.kind !== "variable" || isPrintable(values[node.name]));
936
+ }
937
+ function printed(values, name) {
938
+ const value = values[name];
939
+ return isPrintable(value) ? value.trim() : "";
940
+ }
941
+ function shownValues(nodes, values) {
942
+ return Object.fromEntries(nodes.flatMap((node) => {
943
+ if (node.kind === "text")
944
+ return [];
945
+ if (node.kind === "variable") {
946
+ return printed(values, node.name) === "" ? [] : [[node.name, printed(values, node.name)]];
947
+ }
948
+ return groupShows(node.nodes, values) ? Object.entries(shownValues(node.nodes, values)) : [];
949
+ }));
950
+ }
951
+ function renderNodes(nodes, values) {
952
+ return nodes.map((node) => {
953
+ if (node.kind === "text")
954
+ return node.text;
955
+ if (node.kind === "variable")
956
+ return printed(values, node.name);
957
+ return groupShows(node.nodes, values) ? renderNodes(node.nodes, values) : "";
958
+ }).join("");
959
+ }
960
+ function renderTitle(template, values) {
961
+ return renderNodes(parseTitleTemplate(template), values).trim();
962
+ }
963
+ function escapeLiteral(text2) {
964
+ return text2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
965
+ }
966
+ function patternOf(nodes, last) {
967
+ return nodes.map((node) => {
968
+ if (node.kind === "text")
969
+ return escapeLiteral(node.text);
970
+ if (node.kind === "group")
971
+ return `(?:${patternOf(node.nodes, last)})?`;
972
+ const shape = node.name === last ? FREE_SHAPE : `${VARIABLE_SHAPES[node.name] ?? FREE_SHAPE}?`;
973
+ return `(?<${node.name}>${shape})`;
974
+ }).join("");
975
+ }
976
+ function parseTitle(template, title) {
977
+ const nodes = parseTitleTemplate(template);
978
+ const names = variablesOf(nodes);
979
+ const match = new RegExp(`^${patternOf(nodes, names.at(-1))}$`, "u").exec(title.trim());
980
+ if (match === null)
981
+ return null;
982
+ return Object.fromEntries(names.flatMap((name) => {
983
+ const value = match.groups?.[name];
984
+ return value === undefined ? [] : [[name, value]];
985
+ }));
986
+ }
987
+ function titleRoundTrips(template, values) {
988
+ const nodes = parseTitleTemplate(template);
989
+ const title = renderNodes(nodes, values).trim();
990
+ if (title === "")
991
+ return false;
992
+ const read = parseTitle(template, title) ?? {};
993
+ const shown = shownValues(nodes, values);
994
+ return variablesOf(nodes).every((name) => (read[name] ?? null) === (shown[name] ?? null));
995
+ }
996
+ var SAMPLE = {
997
+ type: "bug",
998
+ project: "api",
999
+ projectType: "cli",
1000
+ module: "loop",
1001
+ action: "start",
1002
+ summary: "Does not find the branch: again"
1003
+ };
1004
+ function samplesOf(names) {
1005
+ const optional = names.filter((name) => name !== SUMMARY);
1006
+ const subsets = optional.reduce((found, name) => [...found, ...found.map((subset) => [...subset, name])], [[]]);
1007
+ return subsets.map((subset) => Object.fromEntries([...subset, SUMMARY].map((name) => [name, SAMPLE[name]])));
1008
+ }
1009
+ function nodesProblem(template, nodes) {
1010
+ const names = variablesOf(nodes);
1011
+ const twice = names.find((name, index) => names.indexOf(name) !== index);
1012
+ if (twice !== undefined)
1013
+ return `\${${twice}} appears twice`;
1014
+ if (names.at(-1) !== SUMMARY)
1015
+ return `\${${SUMMARY}} must be the last variable`;
1016
+ if (!nodes.some((node) => node.kind === "variable" && node.name === SUMMARY)) {
1017
+ return `\${${SUMMARY}} must stand outside every ?( ) group`;
1018
+ }
1019
+ const failed = samplesOf(names).find((sample) => !titleRoundTrips(template, sample));
1020
+ return failed === undefined ? null : `the title ${JSON.stringify(renderTitle(template, failed))} does not read back as the fields it was rendered from`;
1021
+ }
1022
+ function templateProblem(template) {
1023
+ try {
1024
+ return nodesProblem(template, parseTitleTemplate(template));
1025
+ } catch (error) {
1026
+ if (error instanceof TitleTemplateError)
1027
+ return error.message;
1028
+ throw error;
1029
+ }
1030
+ }
1031
+
1032
+ // src/config-schema-issues.ts
1033
+ var SUMMARY_MAX_DEFAULT = 80;
1034
+ var SUMMARY_MAX_FLOOR = 20;
1035
+ var SUMMARY_MAX_CEILING = 120;
1036
+ var TEMPLATE_EXPECTED = "a title template";
1037
+ var PLACE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
1038
+ var MODULE = new RegExp(`^${MODULE_SHAPE}$`);
1039
+ var ACTION = new RegExp(`^${ACTION_SHAPE}$`);
1040
+ function accepted2(value) {
1041
+ return { value, problems: [], extras: [] };
1042
+ }
1043
+ var titleTemplate = (raw, at) => {
1044
+ if (typeof raw !== "string" || raw.trim() === "")
1045
+ return refused(at, raw, TEMPLATE_EXPECTED);
1046
+ const problem = templateProblem(raw);
1047
+ return problem === null ? accepted2(raw) : refused(at, raw, `${TEMPLATE_EXPECTED}: ${problem}`);
1048
+ };
1049
+ var summaryMax = (raw, at) => typeof raw === "number" && Number.isSafeInteger(raw) && raw >= SUMMARY_MAX_FLOOR && raw <= SUMMARY_MAX_CEILING ? accepted2(raw) : refused(at, raw, `a whole number from ${SUMMARY_MAX_FLOOR} to ${SUMMARY_MAX_CEILING}`);
1050
+ function nameOf(shape, reserved, expected) {
1051
+ return (raw, at) => typeof raw === "string" && shape.test(raw) && !reserved.includes(raw) ? accepted2(raw) : refused(at, raw, expected);
1052
+ }
1053
+ var place = nameOf(PLACE, [...BUILT_IN_ENVIRONMENTS, UNKNOWN_VALUE], `a place name in lower-case kebab-case, other than ${BUILT_IN_ENVIRONMENTS.join(", ")} and ${UNKNOWN_VALUE}`);
1054
+ var moduleName = nameOf(MODULE, [UNKNOWN_VALUE], `a module name in lower-case letters, digits, ".", "_" and "-", other than ${UNKNOWN_VALUE}`);
1055
+ var actionName = nameOf(ACTION, [UNKNOWN_VALUE], `an action in lower-case letters, digits, ".", "_", "-" and spaces, other than ${UNKNOWN_VALUE}`);
1056
+ var ISSUES_DEFAULTS = Object.freeze({
1057
+ issuesTitleBug: DEFAULT_BUG_TITLE,
1058
+ issuesSummaryMax: SUMMARY_MAX_DEFAULT,
1059
+ issuesEnvironments: Object.freeze([]),
1060
+ issuesVocabularyModules: Object.freeze([]),
1061
+ issuesVocabularyActions: Object.freeze([])
1062
+ });
1063
+ var ISSUES_SETTINGS = {
1064
+ issuesTitleBug: { key: "issues.titles.bug", read: titleTemplate, cli: false },
1065
+ issuesSummaryMax: { key: "issues.titles.summaryMax", read: summaryMax, cli: false },
1066
+ issuesEnvironments: { key: "issues.environments", read: listOf(place, "place names"), cli: false },
1067
+ issuesVocabularyModules: { key: "issues.vocabulary.modules", read: listOf(moduleName, "module names"), cli: false },
1068
+ issuesVocabularyActions: { key: "issues.vocabulary.actions", read: listOf(actionName, "actions"), cli: false }
1069
+ };
1070
+
1071
+ // src/adapters/logger/settings.ts
1072
+ import { AsyncLocalStorage } from "async_hooks";
1073
+ var LOG_LEVELS = Object.freeze(["error", "warn", "debug"]);
1074
+ var LOGGER_THEMES = Object.freeze(["default", "plain"]);
1075
+ var DEFAULT_LOGGER_KIND = "console";
1076
+ var DEBUG_VERBOSITY = 2;
1077
+ var API_VERBOSITY = 3;
1078
+ var DEFAULT_LOGGER_SETTINGS = Object.freeze({
1079
+ kind: DEFAULT_LOGGER_KIND,
1080
+ level: "warn",
1081
+ theme: "default",
1082
+ modules: new Map,
1083
+ callSite: false,
1084
+ api: false
1085
+ });
1086
+ var scopes = new AsyncLocalStorage;
1087
+ var moduleLevel = { settings: DEFAULT_LOGGER_SETTINGS };
1088
+ function settingsOf(settings) {
1089
+ return settings === null ? DEFAULT_LOGGER_SETTINGS : Object.freeze({ ...settings, modules: new Map(settings.modules) });
1090
+ }
1091
+ function cellHere() {
1092
+ return scopes.getStore() ?? moduleLevel;
1093
+ }
1094
+ function setActiveLoggerSettings(settings) {
1095
+ cellHere().settings = settingsOf(settings);
1096
+ }
1097
+ function activeLoggerSettings() {
1098
+ return cellHere().settings;
1099
+ }
1100
+ function levelEnabled(level, module, settings, verbosity) {
1101
+ if (level === "api")
1102
+ return settings.api || verbosity >= API_VERBOSITY;
1103
+ if (level === "debug" && verbosity >= DEBUG_VERBOSITY)
1104
+ return true;
1105
+ const threshold = (module === undefined ? undefined : settings.modules.get(module)) ?? settings.level;
1106
+ return LOG_LEVELS.indexOf(level) <= LOG_LEVELS.indexOf(threshold);
1107
+ }
1108
+
1109
+ // src/config-schema-logger.ts
1110
+ var MODULE_LEVEL_KEY = "level";
1111
+ var MODULE_EXPECTED = `a mapping holding ${MODULE_LEVEL_KEY}`;
1112
+ var moduleLevel2 = (raw, at) => {
1113
+ if (!isMapping(raw))
1114
+ return refused(at, raw, MODULE_EXPECTED);
1115
+ const level = oneOf(LOG_LEVELS)(raw[MODULE_LEVEL_KEY], below(at, `.${MODULE_LEVEL_KEY}`));
1116
+ const extras = Object.entries(raw).filter(([key]) => key !== MODULE_LEVEL_KEY).map(([key, value]) => ({ key: `${at.key}.${key}`, value }));
1117
+ return { ...level, extras };
1118
+ };
1119
+ var LOGGER_DEFAULTS = Object.freeze({
1120
+ loggerKind: DEFAULT_LOGGER_KIND,
1121
+ loggerLevel: "warn",
1122
+ loggerTheme: "default",
1123
+ loggerModules: new Map,
1124
+ loggerCallSite: false,
1125
+ loggerApi: false
1126
+ });
1127
+ var LOGGER_SETTINGS = {
1128
+ loggerKind: { key: "logger.kind", read: text("an adapter kind, such as console"), cli: false },
1129
+ loggerLevel: { key: "logger.level", read: oneOf(LOG_LEVELS), cli: false },
1130
+ loggerTheme: { key: "logger.theme", read: oneOf(LOGGER_THEMES), cli: false },
1131
+ loggerModules: { key: "logger.modules", read: mapOf(moduleLevel2, MODULE_EXPECTED), cli: false },
1132
+ loggerCallSite: { key: "logger.callSite", read: flag, cli: false },
1133
+ loggerApi: { key: "logger.api", read: flag, cli: false }
1134
+ };
1135
+ function loggerSettingsOf(config) {
1136
+ return {
1137
+ kind: config.loggerKind,
1138
+ level: config.loggerLevel,
1139
+ theme: config.loggerTheme,
1140
+ modules: config.loggerModules,
1141
+ callSite: config.loggerCallSite,
1142
+ api: config.loggerApi
1143
+ };
1144
+ }
1145
+
292
1146
  // src/config-schema-loop-continue.ts
293
- import { join } from "path";
1147
+ import { join as join2 } from "path";
294
1148
  var RETRIES_ON_CONTINUE_MIN = 1;
295
1149
  var RETRIES_ON_CONTINUE_MAX = 3;
296
1150
  var FORCE_WRAP_UP_MAX_NEW_FAILURES_MIN = 1;
@@ -306,7 +1160,7 @@ var continueCriteria = text("a file path");
306
1160
  var continueCriteriaMode = oneOf(CONTINUE_CRITERIA_MODES);
307
1161
  var LOOP_CONTINUE_DEFAULTS = Object.freeze({
308
1162
  loopRetriesOnContinue: 1,
309
- loopContinueCriteria: join(".rafa", "continue-criteria.md"),
1163
+ loopContinueCriteria: join2(".rafa", "continue-criteria.md"),
310
1164
  loopContinueCriteriaMode: "extend",
311
1165
  loopForceWrapUpMaxNewFailures: false
312
1166
  });
@@ -422,7 +1276,8 @@ var TESTS_DEFAULTS = Object.freeze({
422
1276
  "**/*-spawned*.test.ts",
423
1277
  "**/*-cli.test.ts"
424
1278
  ]),
425
- testsAlwaysRun: Object.freeze(["src/**/*.sweep.test.ts"])
1279
+ testsAlwaysRun: Object.freeze(["src/**/*.sweep.test.ts"]),
1280
+ testsRetakeRedAlone: true
426
1281
  });
427
1282
  var TESTS_SETTINGS = {
428
1283
  testsFullSuiteTriggers: {
@@ -431,7 +1286,8 @@ var TESTS_SETTINGS = {
431
1286
  cli: false
432
1287
  },
433
1288
  testsIntegration: { key: "tests.integration", read: globList, cli: false },
434
- testsAlwaysRun: { key: "tests.alwaysRun", read: globList, cli: false }
1289
+ testsAlwaysRun: { key: "tests.alwaysRun", read: globList, cli: false },
1290
+ testsRetakeRedAlone: { key: "tests.retakeRedAlone", read: flag, cli: false }
435
1291
  };
436
1292
 
437
1293
  // src/config-schema-triage.ts
@@ -642,7 +1498,7 @@ var DEFAULT_ROUTES = Object.freeze([
642
1498
  var DEFAULT_ROUTING = new Map(DEFAULT_ROUTES);
643
1499
 
644
1500
  // src/config-schema.ts
645
- var CONFIG_FILE = join2(".rafa", "config.yaml");
1501
+ var CONFIG_FILE = join3(".rafa", "config.yaml");
646
1502
  var CONFIG_DEFAULTS = Object.freeze({
647
1503
  version: 1,
648
1504
  store: "sqlite",
@@ -650,8 +1506,8 @@ var CONFIG_DEFAULTS = Object.freeze({
650
1506
  effortSync: "local",
651
1507
  ...HUB_DEFAULTS,
652
1508
  inject: "stage",
653
- planDir: join2(".rafa", "plans"),
654
- specsDir: join2(".rafa", "specs"),
1509
+ planDir: join3(".rafa", "plans"),
1510
+ specsDir: join3(".rafa", "specs"),
655
1511
  trackerDefault: "github",
656
1512
  trackerFallback: Object.freeze(["local"]),
657
1513
  learningAdapter: "local",
@@ -667,7 +1523,7 @@ var CONFIG_DEFAULTS = Object.freeze({
667
1523
  modules: Object.freeze([]),
668
1524
  allowList: Object.freeze([]),
669
1525
  settingSources: Object.freeze(["project", "local"]),
670
- loopWorktreeDir: join2(".rafa", "worktrees"),
1526
+ loopWorktreeDir: join3(".rafa", "worktrees"),
671
1527
  ...LOOP_RETRIES_DEFAULTS,
672
1528
  ...LOOP_CONTINUE_DEFAULTS,
673
1529
  ...WRAP_UP_DEFAULTS,
@@ -682,6 +1538,9 @@ var CONFIG_DEFAULTS = Object.freeze({
682
1538
  claimsAhead: "off",
683
1539
  ...TRIAGE_DEFAULTS,
684
1540
  ...RELEASE_DEFAULTS,
1541
+ ...ERRORS_DEFAULTS,
1542
+ ...LOGGER_DEFAULTS,
1543
+ ...ISSUES_DEFAULTS,
685
1544
  cleanupStaleDays: 30,
686
1545
  cleanupWorktreeIdleDays: 7,
687
1546
  cleanupKeep: Object.freeze([]),
@@ -775,6 +1634,9 @@ var SETTINGS = {
775
1634
  claimsAhead: { key: "claims.ahead", read: claimsAhead, cli: false },
776
1635
  ...TRIAGE_SETTINGS,
777
1636
  ...RELEASE_SETTINGS,
1637
+ ...ERRORS_SETTINGS,
1638
+ ...LOGGER_SETTINGS,
1639
+ ...ISSUES_SETTINGS,
778
1640
  cleanupStaleDays: { key: "cleanup.staleDays", read: dayCount, cli: false },
779
1641
  cleanupWorktreeIdleDays: {
780
1642
  key: "cleanup.worktreeIdleDays",
@@ -850,7 +1712,7 @@ function knownKeysAbove(key) {
850
1712
  }
851
1713
 
852
1714
  // src/config.ts
853
- import { join as join3 } from "path";
1715
+ import { join as join4 } from "path";
854
1716
  class ConfigError extends Error {
855
1717
  problems;
856
1718
  constructor(problems, options) {
@@ -932,6 +1794,18 @@ function readLayer(rawOf, atOf) {
932
1794
  releaseSettle: read("releaseSettle"),
933
1795
  releaseTag: read("releaseTag"),
934
1796
  releasePublishCommand: read("releasePublishCommand"),
1797
+ errorsCodes: read("errorsCodes"),
1798
+ loggerKind: read("loggerKind"),
1799
+ loggerLevel: read("loggerLevel"),
1800
+ loggerTheme: read("loggerTheme"),
1801
+ loggerModules: read("loggerModules"),
1802
+ loggerCallSite: read("loggerCallSite"),
1803
+ loggerApi: read("loggerApi"),
1804
+ issuesTitleBug: read("issuesTitleBug"),
1805
+ issuesSummaryMax: read("issuesSummaryMax"),
1806
+ issuesEnvironments: read("issuesEnvironments"),
1807
+ issuesVocabularyModules: read("issuesVocabularyModules"),
1808
+ issuesVocabularyActions: read("issuesVocabularyActions"),
935
1809
  cleanupStaleDays: read("cleanupStaleDays"),
936
1810
  cleanupWorktreeIdleDays: read("cleanupWorktreeIdleDays"),
937
1811
  cleanupKeep: read("cleanupKeep"),
@@ -947,7 +1821,8 @@ function readLayer(rawOf, atOf) {
947
1821
  taskLessons: read("taskLessons"),
948
1822
  testsFullSuiteTriggers: read("testsFullSuiteTriggers"),
949
1823
  testsIntegration: read("testsIntegration"),
950
- testsAlwaysRun: read("testsAlwaysRun")
1824
+ testsAlwaysRun: read("testsAlwaysRun"),
1825
+ testsRetakeRedAlone: read("testsRetakeRedAlone")
951
1826
  };
952
1827
  return { layer, problems, extras };
953
1828
  }
@@ -1003,7 +1878,7 @@ function parseYaml(text2, path) {
1003
1878
  }
1004
1879
  }
1005
1880
  function configFilePath(root) {
1006
- return join3(root, CONFIG_FILE);
1881
+ return join4(root, CONFIG_FILE);
1007
1882
  }
1008
1883
  function parseConfigText(text2, path) {
1009
1884
  const document = parseYaml(text2, path) ?? {};
@@ -1026,7 +1901,8 @@ function parseConfigText(text2, path) {
1026
1901
  var MAP_SETTINGS = new Set([
1027
1902
  "tiersSkills",
1028
1903
  "tiersAgents",
1029
- "routing"
1904
+ "routing",
1905
+ "loggerModules"
1030
1906
  ]);
1031
1907
  function mergeMaps(...layers) {
1032
1908
  const merged = new Map;
@@ -1443,7 +2319,7 @@ function planStubFromPath(planPath) {
1443
2319
  }
1444
2320
 
1445
2321
  // src/adapters/output/text.ts
1446
- var DEBUG_VERBOSITY = 2;
2322
+ var DEBUG_VERBOSITY2 = 2;
1447
2323
  function formatPayload(payload) {
1448
2324
  return typeof payload === "string" ? payload : JSON.stringify(payload);
1449
2325
  }
@@ -1461,7 +2337,7 @@ function createTextOutput({ verbosity, stream }) {
1461
2337
  `);
1462
2338
  };
1463
2339
  const debug = (message) => {
1464
- if (verbosity >= DEBUG_VERBOSITY)
2340
+ if (verbosity >= DEBUG_VERBOSITY2)
1465
2341
  writeLine(`debug: ${message}`);
1466
2342
  };
1467
2343
  const info = (message) => {
@@ -1523,14 +2399,16 @@ function createTextOutput({ verbosity, stream }) {
1523
2399
  }
1524
2400
 
1525
2401
  // src/adapters/output/active.ts
2402
+ import { AsyncLocalStorage as AsyncLocalStorage2 } from "async_hooks";
2403
+ var scopes2 = new AsyncLocalStorage2;
1526
2404
  var DEFAULT_OUTPUT = createTextOutput({ verbosity: 0, stream: process.stdout });
1527
2405
  var active = DEFAULT_OUTPUT;
1528
2406
  var activeMode = "text";
1529
2407
  function activeOutput() {
1530
- return active;
2408
+ return scopes2.getStore()?.output ?? active;
1531
2409
  }
1532
2410
  function activeOutputMode() {
1533
- return activeMode;
2411
+ return scopes2.getStore()?.mode ?? activeMode;
1534
2412
  }
1535
2413
 
1536
- export { hubContextOf, PREREQUISITE_KINDS, requiredPrerequisite, optionalPrerequisite, MODULE_SOURCE_KINDS, isLoopRetryCount, RELEASE_DEFAULTS, pinKey, pinLine, collisionMessage, readItemBytes, resolveTiers, findTierItem, DEFAULT_ROUTES, DEFAULT_ROUTING, CONFIG_FILE, CONFIG_DEFAULTS, SETTINGS, SETTING_NAMES, SETTING_BY_KEY, SECTIONS, knownKeysAbove, ConfigError, configFilePath, parseConfigText, resolveConfig, isStampableStub, planStubFromPrompt, stampPrompt, planStubFromPath, createTextOutput, activeOutput, activeOutputMode, RAFA_BLOCK_KINDS, isRafaBlockKind, readRafaBlocks, REPORT_STATUSES, FINDING_KINDS, FINDING_SIGNALS, FINDING_DOMAINS, CHANGE_LEVELS, parseReport };
2414
+ export { MERGE_METHODS, isMergeMethod, SKILL_TIERS, RAFA_INSTINCTS_PATH, BUNDLED_SKILLS_DIR, isSkillTier, realEntry, bundledBinDirectory, skillTierDirectory, resolveSkillTiers, resolveInstinctScopes, tierExists, TEST_SCOPES, isAgentName, BUDGET_FLAG, readTestScope, parseTaskDeclaration, stripTaskDeclaration, resolveDeclarationFlags, isMapping, describeValue, messageOf, CONFIG_VERSIONS, STORE_BACKENDS, SYNC_STRATEGIES, BOARD_RELATIONSHIP_MODES, INJECT_MODES, OUTPUT_MODES, CLAUDE_SETTING_SOURCES, PR_PROVIDERS, RELEASE_AUTO, SKILL_RESOLVERS, CLAIMS_STALE_DISABLED, BUSY_TIMEOUT_MIN_MS, BUSY_TIMEOUT_MAX_MS, isBusyTimeoutMs, claimDurationHours, isGitHubLogin, BOARD_PROJECT_TEMPLATE_DEFAULT, listOf, hubContextOf, PREREQUISITE_KINDS, requiredPrerequisite, optionalPrerequisite, MODULE_SOURCE_KINDS, DEFAULT_LOGGER_KIND, setActiveLoggerSettings, activeLoggerSettings, levelEnabled, loggerSettingsOf, isLoopRetryCount, RELEASE_DEFAULTS, pinKey, pinLine, collisionMessage, readItemBytes, resolveTiers, findTierItem, DEFAULT_ROUTING, CONFIG_FILE, CONFIG_DEFAULTS, SETTINGS, ConfigError, configFilePath, parseConfigText, resolveConfig, isStampableStub, planStubFromPrompt, stampPrompt, planStubFromPath, RAFA_BLOCK_KINDS, isRafaBlockKind, readRafaBlocks, REPORT_STATUSES, FINDING_KINDS, FINDING_SIGNALS, FINDING_DOMAINS, CHANGE_LEVELS, parseReport, createTextOutput, activeOutput, activeOutputMode };