@open-tomato/rafa 0.31.0 → 0.33.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,7 +1,7 @@
1
1
  // @bun
2
2
  import {
3
3
  describeValue
4
- } from "./index-dnwcx1cs.js";
4
+ } from "./index-fzhd3d4y.js";
5
5
 
6
6
  // src/cli/spends.ts
7
7
  var SPENDS_GLYPH = "\uD83E\uDE99";
@@ -31,7 +31,7 @@ function spendsProblem(value) {
31
31
  }
32
32
 
33
33
  // src/cli/command.ts
34
- var COMMAND_OUTPUTS = ["text", "json", "tui"];
34
+ var COMMAND_OUTPUTS = ["text", "json", "events", "tui"];
35
35
  var MAX_EXIT_CODE = 255;
36
36
 
37
37
  class CommandExit extends Error {
@@ -1,18 +1,19 @@
1
1
  // @bun
2
2
  import {
3
+ BOARD_RELATIONSHIP_MODES,
3
4
  CLAUDE_SETTING_SOURCES,
4
5
  CONFIG_VERSIONS,
5
6
  INJECT_MODES,
6
- MODULE_SOURCE_KEYS,
7
- OPTIONAL_ITEM_KEYS,
8
7
  OUTPUT_MODES,
9
8
  PR_PROVIDERS,
10
9
  RELEASE_AUTO,
11
- REQUIRED_ITEM_KEYS,
12
10
  SKILL_TIERS,
13
11
  STORE_BACKENDS,
14
12
  SYNC_STRATEGIES,
13
+ below,
15
14
  busyTimeoutMs,
15
+ claimsAhead,
16
+ claimsStaleAfter,
16
17
  confidence,
17
18
  dayCount,
18
19
  describeValue,
@@ -23,14 +24,14 @@ import {
23
24
  issueNumber,
24
25
  lessonSwitch,
25
26
  listOf,
27
+ mapReading,
26
28
  mergeMethod,
27
29
  messageOf,
28
- moduleSource,
29
30
  oneOf,
30
- optionalPrerequisite,
31
31
  recurrenceCount,
32
+ refused,
33
+ refusedWith,
32
34
  releaseEnabled,
33
- requiredPrerequisite,
34
35
  routeTarget,
35
36
  skillResolverName,
36
37
  subsetOf,
@@ -38,7 +39,79 @@ import {
38
39
  tierPin,
39
40
  tierSwitch,
40
41
  usdAmount
41
- } from "./index-dnwcx1cs.js";
42
+ } from "./index-fzhd3d4y.js";
43
+
44
+ // src/config-items.ts
45
+ var itemText = text("a non-empty string");
46
+ function readKeyedItem(shape, raw, at) {
47
+ const choices = shape.kinds.join(", ");
48
+ if (!isMapping(raw))
49
+ return refused(at, raw, `a mapping naming one of: ${choices}`);
50
+ const entries = Object.entries(raw);
51
+ const kinds = shape.kinds.filter((kind2) => entries.some(([name]) => name === kind2));
52
+ const [kind] = kinds;
53
+ if (kind === undefined)
54
+ return refusedWith([`${at.label} names none of: ${choices}`]);
55
+ if (kinds.length > 1) {
56
+ const named2 = kinds.join(" and ");
57
+ return refusedWith([`${at.label} names ${named2}, expected exactly one of: ${choices}`]);
58
+ }
59
+ const optionalKeys = shape.optionalFor(kind);
60
+ const problems = [];
61
+ const extras = [];
62
+ const optional = new Map;
63
+ let named;
64
+ for (const [name, value] of entries) {
65
+ if (name !== kind && !optionalKeys.includes(name)) {
66
+ extras.push({ key: `${at.key}.${name}`, value });
67
+ continue;
68
+ }
69
+ if (name !== kind && value === null)
70
+ continue;
71
+ const reading = itemText(value, below(at, `.${name}`));
72
+ problems.push(...reading.problems);
73
+ if (reading.value === undefined)
74
+ continue;
75
+ if (name === kind)
76
+ named = reading.value;
77
+ else
78
+ optional.set(name, reading.value);
79
+ }
80
+ return problems.length > 0 || named === undefined ? { value: undefined, problems, extras } : { value: { kind, named, optional }, problems, extras };
81
+ }
82
+ var PREREQUISITE_KINDS = ["tool", "env", "service", "lsp"];
83
+ var REQUIRED_ITEM_KEYS = [...PREREQUISITE_KINDS, "probe"];
84
+ var OPTIONAL_ITEM_KEYS = [...REQUIRED_ITEM_KEYS, "reason"];
85
+ var requiredPrerequisite = (raw, at) => {
86
+ const shape = { kinds: PREREQUISITE_KINDS, optionalFor: () => ["probe"] };
87
+ return mapReading(readKeyedItem(shape, raw, at), (item) => Object.freeze({
88
+ kind: item.kind,
89
+ name: item.named,
90
+ probe: item.optional.get("probe") ?? null
91
+ }));
92
+ };
93
+ var optionalPrerequisite = (raw, at) => {
94
+ const shape = { kinds: PREREQUISITE_KINDS, optionalFor: () => ["probe", "reason"] };
95
+ return mapReading(readKeyedItem(shape, raw, at), (item) => Object.freeze({
96
+ kind: item.kind,
97
+ name: item.named,
98
+ probe: item.optional.get("probe") ?? null,
99
+ reason: item.optional.get("reason") ?? null
100
+ }));
101
+ };
102
+ var MODULE_SOURCE_KINDS = ["npm", "github", "path"];
103
+ var MODULE_SOURCE_KEYS = [...MODULE_SOURCE_KINDS, "ref"];
104
+ var moduleSource = (raw, at) => {
105
+ const shape = {
106
+ kinds: MODULE_SOURCE_KINDS,
107
+ optionalFor: (kind) => kind === "github" ? ["ref"] : []
108
+ };
109
+ return mapReading(readKeyedItem(shape, raw, at), (item) => Object.freeze({
110
+ kind: item.kind,
111
+ location: item.named,
112
+ ref: item.optional.get("ref") ?? null
113
+ }));
114
+ };
42
115
 
43
116
  // src/config-schema.ts
44
117
  import { join } from "path";
@@ -99,6 +172,61 @@ var fragmentsDirectory = (raw, at) => {
99
172
  return { value: undefined, problems: [problem], extras: [] };
100
173
  };
101
174
 
175
+ // src/config-schema-hub.ts
176
+ var HUB_TIMEOUT_MIN_SECONDS = 1;
177
+ var HUB_TIMEOUT_MAX_SECONDS = 30;
178
+ var MS_PER_SECOND = 1000;
179
+ var HUB_TIMEOUT = /^(\d+)s$/;
180
+ var HUB_URL_PROTOCOLS = Object.freeze(["http:", "https:"]);
181
+ var HUB_DEFAULTS = Object.freeze({
182
+ hubUrl: null,
183
+ hubTokenSecret: null,
184
+ hubTimeout: "3s"
185
+ });
186
+ function hubTimeoutMs(raw) {
187
+ const match = typeof raw === "string" ? HUB_TIMEOUT.exec(raw) : null;
188
+ if (match === null)
189
+ return null;
190
+ const seconds = Number(match[1]);
191
+ return seconds >= HUB_TIMEOUT_MIN_SECONDS && seconds <= HUB_TIMEOUT_MAX_SECONDS ? seconds * MS_PER_SECOND : null;
192
+ }
193
+ var hubTimeout = (raw, at) => hubTimeoutMs(raw) === null ? refused(at, raw, `a duration of whole seconds from ${String(HUB_TIMEOUT_MIN_SECONDS)}s to ${String(HUB_TIMEOUT_MAX_SECONDS)}s, such as 3s`) : { value: raw, problems: [], extras: [] };
194
+ function isHubUrl(raw) {
195
+ if (typeof raw !== "string" || raw.trim() !== raw)
196
+ return false;
197
+ const url = URL.parse(raw);
198
+ return url !== null && HUB_URL_PROTOCOLS.includes(url.protocol) && url.username === "" && url.password === "";
199
+ }
200
+ var hubUrl = (raw, at) => isHubUrl(raw) ? { value: raw, problems: [], extras: [] } : refused(at, raw, "an http or https URL with no user name or password, such as https://hub.example.org");
201
+ var HUB_SETTINGS = {
202
+ hubUrl: { key: "hub.url", read: hubUrl, cli: false },
203
+ hubTokenSecret: { key: "hub.tokenSecret", read: text("a secret store name"), cli: false },
204
+ hubTimeout: { key: "hub.timeout", read: hubTimeout, cli: false }
205
+ };
206
+ var HUB_STRATEGY = "service";
207
+ function labelOf(input) {
208
+ const path = input.effortSyncSource === "user" ? input.paths.user : input.paths.file;
209
+ return path ?? input.effortSyncSource;
210
+ }
211
+ function hubUrlProblems(input) {
212
+ if (input.effortSync !== HUB_STRATEGY || input.hubUrl !== null)
213
+ return [];
214
+ return [
215
+ `${labelOf(input)}: effort.sync is ${describeValue(HUB_STRATEGY)} and no config file names hub.url;` + " expected hub.url, the address of the hub, such as https://hub.example.org"
216
+ ];
217
+ }
218
+ function hubContextOf(config) {
219
+ const url = config.hubUrl ?? null;
220
+ if (url === null)
221
+ return;
222
+ const timeout = config.hubTimeout ?? HUB_DEFAULTS.hubTimeout;
223
+ const timeoutMs = hubTimeoutMs(timeout);
224
+ if (timeoutMs === null) {
225
+ throw new TypeError(`hub.timeout is ${describeValue(timeout)}, which no config accepts`);
226
+ }
227
+ return Object.freeze({ url, tokenSecret: config.hubTokenSecret ?? null, timeoutMs });
228
+ }
229
+
102
230
  // src/config-schema-release.ts
103
231
  var PR_DEFAULTS = Object.freeze({
104
232
  prProvider: null,
@@ -115,7 +243,8 @@ var RELEASE_DEFAULTS = Object.freeze({
115
243
  releaseFragments: ".changes",
116
244
  releaseStrategy: "semver-by-level",
117
245
  releaseSettle: "push",
118
- releaseTag: "manual"
246
+ releaseTag: "manual",
247
+ releasePublishCommand: "npm publish"
119
248
  });
120
249
  var DANGEROUS_RELEASE_DEFAULTS = Object.freeze({
121
250
  dangerousAcceptVersionCollision: false
@@ -151,7 +280,12 @@ var RELEASE_SETTINGS = {
151
280
  },
152
281
  releaseStrategy: { key: "release.strategy", read: releaseStrategy, cli: false },
153
282
  releaseSettle: { key: "release.settle", read: releaseSettleMode, cli: false },
154
- releaseTag: { key: "release.tag", read: releaseTagMode, cli: false }
283
+ releaseTag: { key: "release.tag", read: releaseTagMode, cli: false },
284
+ releasePublishCommand: {
285
+ key: "release.publishCommand",
286
+ read: text("a publish command"),
287
+ cli: false
288
+ }
155
289
  };
156
290
  var DANGEROUS_RELEASE_SETTINGS = {
157
291
  dangerousAcceptVersionCollision: {
@@ -161,6 +295,49 @@ var DANGEROUS_RELEASE_SETTINGS = {
161
295
  }
162
296
  };
163
297
 
298
+ // src/config-schema-tests.ts
299
+ import { isAbsolute as isAbsolute2 } from "path";
300
+ var GLOB_PATTERN = "a glob pattern relative to the repository root";
301
+ var repoGlob = (raw, at) => typeof raw === "string" && isAbsolute2(raw) ? refused(at, raw, GLOB_PATTERN) : text(GLOB_PATTERN)(raw, at);
302
+ var globList = listOf(repoGlob, "glob patterns");
303
+ var TESTS_DEFAULTS = Object.freeze({
304
+ testsFullSuiteTriggers: Object.freeze([
305
+ "bunfig.toml",
306
+ "tsconfig*.json",
307
+ "package.json",
308
+ "bun.lock",
309
+ "bun.lockb"
310
+ ]),
311
+ testsIntegration: Object.freeze([
312
+ "**/*-integration.test.ts",
313
+ "**/*.integration.test.ts",
314
+ "**/*-spawned*.test.ts",
315
+ "**/*-cli.test.ts"
316
+ ])
317
+ });
318
+ var TESTS_SETTINGS = {
319
+ testsFullSuiteTriggers: {
320
+ key: "tests.fullSuiteTriggers",
321
+ read: globList,
322
+ cli: false
323
+ },
324
+ testsIntegration: { key: "tests.integration", read: globList, cli: false }
325
+ };
326
+
327
+ // src/config-schema-wrap-up.ts
328
+ var WRAP_UP_RETRIES_MIN = 1;
329
+ var WRAP_UP_RETRIES_MAX = 3;
330
+ function isRetryCount(raw) {
331
+ return typeof raw === "number" && Number.isSafeInteger(raw) && raw >= WRAP_UP_RETRIES_MIN && raw <= WRAP_UP_RETRIES_MAX;
332
+ }
333
+ var wrapUpRetries = (raw, at) => raw === false || isRetryCount(raw) ? { value: raw, problems: [], extras: [] } : refused(at, raw, `false or a whole number from ${String(WRAP_UP_RETRIES_MIN)} to ${String(WRAP_UP_RETRIES_MAX)}`);
334
+ var WRAP_UP_DEFAULTS = Object.freeze({
335
+ loopWrapUpRetries: 1
336
+ });
337
+ var WRAP_UP_SETTINGS = {
338
+ loopWrapUpRetries: { key: "loop.wrapUp.retries", read: wrapUpRetries, cli: false }
339
+ };
340
+
164
341
  // src/tiers/resolve.ts
165
342
  import { readFileSync } from "fs";
166
343
  var VENDORED_HEADER = /^<!-- vendored by rafa from .* on \d{4}-\d{2}-\d{2} -->$/;
@@ -332,6 +509,7 @@ var CONFIG_DEFAULTS = Object.freeze({
332
509
  store: "sqlite",
333
510
  effortBusyTimeoutMs: 5000,
334
511
  effortSync: "local",
512
+ ...HUB_DEFAULTS,
335
513
  inject: "stage",
336
514
  planDir: join(".rafa", "plans"),
337
515
  specsDir: join(".rafa", "specs"),
@@ -351,9 +529,13 @@ var CONFIG_DEFAULTS = Object.freeze({
351
529
  allowList: Object.freeze([]),
352
530
  settingSources: Object.freeze(["project", "local"]),
353
531
  loopWorktreeDir: join(".rafa", "worktrees"),
532
+ ...WRAP_UP_DEFAULTS,
354
533
  ...PR_DEFAULTS,
355
534
  boardTrustedAuthors: Object.freeze([]),
535
+ boardRelationships: "labels",
356
536
  roadmapIssue: null,
537
+ claimsStaleAfter: "3d",
538
+ claimsAhead: "off",
357
539
  ...RELEASE_DEFAULTS,
358
540
  cleanupStaleDays: 30,
359
541
  cleanupWorktreeIdleDays: 7,
@@ -367,13 +549,15 @@ var CONFIG_DEFAULTS = Object.freeze({
367
549
  tiersAgents: new Map,
368
550
  routing: DEFAULT_ROUTING,
369
551
  taskSkills: "planner",
370
- taskLessons: "on"
552
+ taskLessons: "on",
553
+ ...TESTS_DEFAULTS
371
554
  });
372
555
  var SETTINGS = {
373
556
  version: { key: "version", read: oneOf(CONFIG_VERSIONS), cli: false },
374
557
  store: { key: "store", read: oneOf(STORE_BACKENDS), cli: true },
375
558
  effortBusyTimeoutMs: { key: "effort.busyTimeoutMs", read: busyTimeoutMs, cli: false },
376
559
  effortSync: { key: "effort.sync", read: oneOf(SYNC_STRATEGIES), cli: false },
560
+ ...HUB_SETTINGS,
377
561
  inject: { key: "plan.inject", read: oneOf(INJECT_MODES), cli: true },
378
562
  planDir: { key: "plan.dir", read: directory, cli: true },
379
563
  specsDir: { key: "specs.dir", read: directory, cli: true },
@@ -424,13 +608,21 @@ var SETTINGS = {
424
608
  cli: true
425
609
  },
426
610
  loopWorktreeDir: { key: "loop.worktreeDir", read: directory, cli: false },
611
+ ...WRAP_UP_SETTINGS,
427
612
  ...PR_SETTINGS,
428
613
  boardTrustedAuthors: {
429
614
  key: "board.trustedAuthors",
430
615
  read: listOf(githubLogin, "GitHub logins"),
431
616
  cli: false
432
617
  },
618
+ boardRelationships: {
619
+ key: "board.relationships",
620
+ read: oneOf(BOARD_RELATIONSHIP_MODES),
621
+ cli: false
622
+ },
433
623
  roadmapIssue: { key: "roadmap.issue", read: issueNumber, cli: false },
624
+ claimsStaleAfter: { key: "claims.staleAfter", read: claimsStaleAfter, cli: false },
625
+ claimsAhead: { key: "claims.ahead", read: claimsAhead, cli: false },
434
626
  ...RELEASE_SETTINGS,
435
627
  cleanupStaleDays: { key: "cleanup.staleDays", read: dayCount, cli: false },
436
628
  cleanupWorktreeIdleDays: {
@@ -460,7 +652,8 @@ var SETTINGS = {
460
652
  tiersAgents: { key: "tiers.agents", read: tierPins, cli: false },
461
653
  routing: { key: "routing", read: routeTable, cli: false },
462
654
  taskSkills: { key: "task.skills", read: skillResolverName, cli: true },
463
- taskLessons: { key: "task.lessons", read: lessonSwitch, cli: false }
655
+ taskLessons: { key: "task.lessons", read: lessonSwitch, cli: false },
656
+ ...TESTS_SETTINGS
464
657
  };
465
658
  var SETTING_NAMES = Object.keys(SETTINGS);
466
659
  var SETTING_BY_KEY = new Map(SETTING_NAMES.map((setting) => [
@@ -532,6 +725,9 @@ function readLayer(rawOf, atOf) {
532
725
  store: read("store"),
533
726
  effortBusyTimeoutMs: read("effortBusyTimeoutMs"),
534
727
  effortSync: read("effortSync"),
728
+ hubUrl: read("hubUrl"),
729
+ hubTokenSecret: read("hubTokenSecret"),
730
+ hubTimeout: read("hubTimeout"),
535
731
  inject: read("inject"),
536
732
  planDir: read("planDir"),
537
733
  specsDir: read("specsDir"),
@@ -551,13 +747,17 @@ function readLayer(rawOf, atOf) {
551
747
  allowList: read("allowList"),
552
748
  settingSources: read("settingSources"),
553
749
  loopWorktreeDir: read("loopWorktreeDir"),
750
+ loopWrapUpRetries: read("loopWrapUpRetries"),
554
751
  prProvider: read("prProvider"),
555
752
  prMergeMethod: read("prMergeMethod"),
556
753
  prBase: read("prBase"),
557
754
  prResolveBudget: read("prResolveBudget"),
558
755
  prVersionCollision: read("prVersionCollision"),
559
756
  boardTrustedAuthors: read("boardTrustedAuthors"),
757
+ boardRelationships: read("boardRelationships"),
560
758
  roadmapIssue: read("roadmapIssue"),
759
+ claimsStaleAfter: read("claimsStaleAfter"),
760
+ claimsAhead: read("claimsAhead"),
561
761
  releaseEnabled: read("releaseEnabled"),
562
762
  releaseVersionFile: read("releaseVersionFile"),
563
763
  releaseChangelog: read("releaseChangelog"),
@@ -566,6 +766,7 @@ function readLayer(rawOf, atOf) {
566
766
  releaseStrategy: read("releaseStrategy"),
567
767
  releaseSettle: read("releaseSettle"),
568
768
  releaseTag: read("releaseTag"),
769
+ releasePublishCommand: read("releasePublishCommand"),
569
770
  cleanupStaleDays: read("cleanupStaleDays"),
570
771
  cleanupWorktreeIdleDays: read("cleanupWorktreeIdleDays"),
571
772
  cleanupKeep: read("cleanupKeep"),
@@ -578,7 +779,9 @@ function readLayer(rawOf, atOf) {
578
779
  tiersAgents: read("tiersAgents"),
579
780
  routing: read("routing"),
580
781
  taskSkills: read("taskSkills"),
581
- taskLessons: read("taskLessons")
782
+ taskLessons: read("taskLessons"),
783
+ testsFullSuiteTriggers: read("testsFullSuiteTriggers"),
784
+ testsIntegration: read("testsIntegration")
582
785
  };
583
786
  return { layer, problems, extras };
584
787
  }
@@ -700,6 +903,15 @@ function resolveConfig(layers = {}) {
700
903
  return { value: CONFIG_DEFAULTS[setting], source: "default" };
701
904
  };
702
905
  const ranked = SETTING_NAMES.map((setting) => [setting, rank(setting)]);
906
+ const effortSync = rank("effortSync");
907
+ const hubProblems = hubUrlProblems({
908
+ effortSync: effortSync.value,
909
+ hubUrl: rank("hubUrl").value,
910
+ effortSyncSource: effortSync.source,
911
+ paths: { file: file?.path ?? null, user: user?.path ?? null }
912
+ });
913
+ if (hubProblems.length > 0)
914
+ throw new ConfigError(hubProblems);
703
915
  const rafaLoaded = ranked.find(([setting]) => setting === "tiersRafa")?.[1].value !== "off";
704
916
  const warnings = [user, file].flatMap((layer) => layer === null ? [] : [
705
917
  ...layer.extras.map((extra) => unknownKeyWarning(extra.key, layer.path)),
@@ -1129,6 +1341,9 @@ function createTextOutput({ verbosity, stream }) {
1129
1341
  writeLine(resultLine(event));
1130
1342
  return;
1131
1343
  }
1344
+ case "event": {
1345
+ return;
1346
+ }
1132
1347
  }
1133
1348
  };
1134
1349
  return Object.freeze({ info, warn, error, debug, emit, result });
@@ -1145,4 +1360,4 @@ function activeOutputMode() {
1145
1360
  return activeMode;
1146
1361
  }
1147
1362
 
1148
- export { pinKey, pinLine, collisionMessage, readItemBytes, resolveTiers, findTierItem, DEFAULT_ROUTES, DEFAULT_ROUTING, CONFIG_FILE, CONFIG_DEFAULTS, SETTINGS, 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 };
1363
+ export { hubContextOf, PREREQUISITE_KINDS, requiredPrerequisite, optionalPrerequisite, MODULE_SOURCE_KINDS, pinKey, pinLine, collisionMessage, readItemBytes, resolveTiers, findTierItem, DEFAULT_ROUTES, DEFAULT_ROUTING, CONFIG_FILE, CONFIG_DEFAULTS, SETTINGS, 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 };
@@ -80,7 +80,8 @@ var DECLARATION_KEYS = [
80
80
  "effort",
81
81
  "budget",
82
82
  "tools",
83
- "skills"
83
+ "skills",
84
+ "tests"
84
85
  ];
85
86
  var GRANULAR_KEYS = ["model", "effort", "tools"];
86
87
  var AGENT_OWNED_KEYS = ["model", "tools"];
@@ -92,6 +93,8 @@ var EFFORT_LEVELS = [
92
93
  "xhigh",
93
94
  "max"
94
95
  ];
96
+ var TEST_SCOPES = ["affected", "module", "full"];
97
+ var DEFAULT_TEST_SCOPE = "affected";
95
98
  var DECLARATION_BLOCK = /\{([^{}]*)\}$/;
96
99
  var ENTRY_TOKEN = /^([A-Za-z_][A-Za-z0-9_-]*)=(.*)$/;
97
100
  var FULL_MODEL_NAME = /^claude-[a-z0-9][a-z0-9.-]*$/;
@@ -112,6 +115,12 @@ function parseBudgetUsd(value) {
112
115
  function isEffortLevel(value) {
113
116
  return EFFORT_LEVELS.includes(value);
114
117
  }
118
+ function isTestScope(value) {
119
+ return TEST_SCOPES.includes(value);
120
+ }
121
+ function readTestScope(declaration) {
122
+ return declaration?.tests ?? DEFAULT_TEST_SCOPE;
123
+ }
115
124
  function isModelValue(value) {
116
125
  if (MODEL_ALIASES.includes(value))
117
126
  return true;
@@ -166,6 +175,7 @@ function readBlock(raw, body) {
166
175
  let budget = null;
167
176
  let tools = null;
168
177
  let skills = null;
178
+ let tests = null;
169
179
  for (const token of tokenise(body)) {
170
180
  const match = token.match(ENTRY_TOKEN);
171
181
  const key = match?.[1];
@@ -222,6 +232,13 @@ function readBlock(raw, body) {
222
232
  tools = parsed;
223
233
  continue;
224
234
  }
235
+ if (key === "tests") {
236
+ if (isTestScope(value))
237
+ tests = value;
238
+ else
239
+ issues.push(unusableValue(key, token));
240
+ continue;
241
+ }
225
242
  const named = parseSkillList(value);
226
243
  if (named === null)
227
244
  issues.push(unusableValue(key, token));
@@ -240,7 +257,8 @@ function readBlock(raw, body) {
240
257
  effort,
241
258
  budget,
242
259
  tools,
243
- skills
260
+ skills,
261
+ tests
244
262
  };
245
263
  }
246
264
  function parseTaskDeclaration(taskText) {
@@ -313,14 +331,17 @@ function messageOf(error) {
313
331
  var CONFIG_VERSIONS = [1];
314
332
  var STORE_BACKENDS = ["sqlite", "ndjson"];
315
333
  var SYNC_STRATEGIES = ["local", "file", "git", "service", "p2p"];
334
+ var BOARD_RELATIONSHIP_MODES = ["labels", "native"];
316
335
  var INJECT_MODES = ["full", "stage", "task"];
317
- var OUTPUT_MODES = ["text", "json"];
336
+ var OUTPUT_MODES = ["text", "json", "events"];
318
337
  var CLAUDE_SETTING_SOURCES = ["user", "project", "local"];
319
338
  var PR_PROVIDERS = ["gh", "none"];
320
339
  var RELEASE_AUTO = "auto";
321
340
  var TIER_SWITCHES = ["on", "off"];
322
341
  var SKILL_RESOLVERS = ["planner", "tag", "none"];
323
342
  var LESSON_SWITCHES = ["on", "off"];
343
+ var CLAIMS_STALE_DISABLED = "disabled";
344
+ var CLAIMS_AHEAD = ["off", "allow"];
324
345
  function accepted(value) {
325
346
  return { value, problems: [], extras: [] };
326
347
  }
@@ -377,6 +398,17 @@ var tierPin = (raw, at) => {
377
398
  var routeTarget = (raw, at) => raw === false || typeof raw === "string" && raw.trim() !== "" ? accepted(raw) : refused(at, raw, "false or an agent name");
378
399
  var skillResolverName = oneOf(SKILL_RESOLVERS);
379
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);
380
412
  var GITHUB_LOGIN = /^[A-Za-z0-9][A-Za-z0-9-]*(?:\[bot\])?$/;
381
413
  function isGitHubLogin(value) {
382
414
  return typeof value === "string" && GITHUB_LOGIN.test(value);
@@ -410,75 +442,5 @@ function subsetOf(values) {
410
442
  return accepted(Object.freeze(named));
411
443
  };
412
444
  }
413
- var itemText = text("a non-empty string");
414
- function readKeyedItem(shape, raw, at) {
415
- const choices = shape.kinds.join(", ");
416
- if (!isMapping(raw))
417
- return refused(at, raw, `a mapping naming one of: ${choices}`);
418
- const entries = Object.entries(raw);
419
- const kinds = shape.kinds.filter((kind2) => entries.some(([name]) => name === kind2));
420
- const [kind] = kinds;
421
- if (kind === undefined)
422
- return refusedWith([`${at.label} names none of: ${choices}`]);
423
- if (kinds.length > 1) {
424
- const named2 = kinds.join(" and ");
425
- return refusedWith([`${at.label} names ${named2}, expected exactly one of: ${choices}`]);
426
- }
427
- const optionalKeys = shape.optionalFor(kind);
428
- const problems = [];
429
- const extras = [];
430
- const optional = new Map;
431
- let named;
432
- for (const [name, value] of entries) {
433
- if (name !== kind && !optionalKeys.includes(name)) {
434
- extras.push({ key: `${at.key}.${name}`, value });
435
- continue;
436
- }
437
- if (name !== kind && value === null)
438
- continue;
439
- const reading = itemText(value, below(at, `.${name}`));
440
- problems.push(...reading.problems);
441
- if (reading.value === undefined)
442
- continue;
443
- if (name === kind)
444
- named = reading.value;
445
- else
446
- optional.set(name, reading.value);
447
- }
448
- return problems.length > 0 || named === undefined ? { value: undefined, problems, extras } : { value: { kind, named, optional }, problems, extras };
449
- }
450
- var PREREQUISITE_KINDS = ["tool", "env", "service", "lsp"];
451
- var REQUIRED_ITEM_KEYS = [...PREREQUISITE_KINDS, "probe"];
452
- var OPTIONAL_ITEM_KEYS = [...REQUIRED_ITEM_KEYS, "reason"];
453
- var requiredPrerequisite = (raw, at) => {
454
- const shape = { kinds: PREREQUISITE_KINDS, optionalFor: () => ["probe"] };
455
- return mapReading(readKeyedItem(shape, raw, at), (item) => Object.freeze({
456
- kind: item.kind,
457
- name: item.named,
458
- probe: item.optional.get("probe") ?? null
459
- }));
460
- };
461
- var optionalPrerequisite = (raw, at) => {
462
- const shape = { kinds: PREREQUISITE_KINDS, optionalFor: () => ["probe", "reason"] };
463
- return mapReading(readKeyedItem(shape, raw, at), (item) => Object.freeze({
464
- kind: item.kind,
465
- name: item.named,
466
- probe: item.optional.get("probe") ?? null,
467
- reason: item.optional.get("reason") ?? null
468
- }));
469
- };
470
- var MODULE_SOURCE_KINDS = ["npm", "github", "path"];
471
- var MODULE_SOURCE_KEYS = [...MODULE_SOURCE_KINDS, "ref"];
472
- var moduleSource = (raw, at) => {
473
- const shape = {
474
- kinds: MODULE_SOURCE_KINDS,
475
- optionalFor: (kind) => kind === "github" ? ["ref"] : []
476
- };
477
- return mapReading(readKeyedItem(shape, raw, at), (item) => Object.freeze({
478
- kind: item.kind,
479
- location: item.named,
480
- ref: item.optional.get("ref") ?? null
481
- }));
482
- };
483
445
 
484
- export { MERGE_METHODS, isMergeMethod, SKILL_TIERS, RAFA_INSTINCTS_PATH, BUNDLED_SKILLS_DIR, isSkillTier, realEntry, bundledBinDirectory, skillTierDirectory, resolveSkillTiers, resolveInstinctScopes, tierExists, isAgentName, BUDGET_FLAG, parseBudgetUsd, parseTaskDeclaration, stripTaskDeclaration, resolveDeclarationFlags, isMapping, describeValue, messageOf, CONFIG_VERSIONS, STORE_BACKENDS, SYNC_STRATEGIES, INJECT_MODES, OUTPUT_MODES, CLAUDE_SETTING_SOURCES, PR_PROVIDERS, RELEASE_AUTO, SKILL_RESOLVERS, oneOf, text, flag, mergeMethod, usdAmount, issueNumber, dayCount, BUSY_TIMEOUT_MIN_MS, BUSY_TIMEOUT_MAX_MS, isBusyTimeoutMs, busyTimeoutMs, confidence, recurrenceCount, releaseEnabled, tierSwitch, tierPin, routeTarget, skillResolverName, lessonSwitch, isGitHubLogin, githubLogin, listOf, subsetOf, PREREQUISITE_KINDS, REQUIRED_ITEM_KEYS, OPTIONAL_ITEM_KEYS, requiredPrerequisite, optionalPrerequisite, MODULE_SOURCE_KINDS, MODULE_SOURCE_KEYS, moduleSource };
446
+ export { MERGE_METHODS, isMergeMethod, SKILL_TIERS, RAFA_INSTINCTS_PATH, BUNDLED_SKILLS_DIR, isSkillTier, realEntry, bundledBinDirectory, skillTierDirectory, resolveSkillTiers, resolveInstinctScopes, tierExists, TEST_SCOPES, isAgentName, BUDGET_FLAG, parseBudgetUsd, 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, refused, refusedWith, mapReading, below, oneOf, text, flag, mergeMethod, usdAmount, issueNumber, dayCount, BUSY_TIMEOUT_MIN_MS, BUSY_TIMEOUT_MAX_MS, isBusyTimeoutMs, busyTimeoutMs, confidence, recurrenceCount, releaseEnabled, tierSwitch, tierPin, routeTarget, skillResolverName, lessonSwitch, claimDurationHours, claimsStaleAfter, claimsAhead, isGitHubLogin, githubLogin, listOf, subsetOf };
@@ -4,10 +4,10 @@ import {
4
4
  commandSpelling,
5
5
  isRoutingWord,
6
6
  isTopLevel
7
- } from "./index-e10rwa4g.js";
7
+ } from "./index-3mzw3kzh.js";
8
8
  import {
9
9
  describeValue
10
- } from "./index-dnwcx1cs.js";
10
+ } from "./index-fzhd3d4y.js";
11
11
 
12
12
  // src/cli/registry.ts
13
13
  var REFUSAL = "command registry";