@open-tomato/rafa 0.18.0 → 0.20.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.
@@ -0,0 +1,218 @@
1
+ // @bun
2
+ import {
3
+ CONFIDENCE_MAX,
4
+ CONFIDENCE_MIN,
5
+ GAP,
6
+ SOURCE_STEP,
7
+ triggerKey
8
+ } from "./index-wp8c28fd.js";
9
+
10
+ // src/learning/bless.ts
11
+ import { createHash } from "crypto";
12
+ var HUNDREDTHS = 100;
13
+ function hundredths(confidence) {
14
+ return Math.round(confidence * HUNDREDTHS);
15
+ }
16
+ function compareText(left, right) {
17
+ if (left < right) {
18
+ return -1;
19
+ }
20
+ if (left > right) {
21
+ return 1;
22
+ }
23
+ return 0;
24
+ }
25
+ function byTrust(left, right) {
26
+ return hundredths(right.confidence) - hundredths(left.confidence) || right.usage_count - left.usage_count || compareText(left.id, right.id);
27
+ }
28
+ function blessedRecords(held, minConfidence) {
29
+ const floor = hundredths(minConfidence);
30
+ return held.filter((each) => each.status === "active" && each.promoted_to === undefined && hundredths(each.confidence) >= floor).sort(byTrust);
31
+ }
32
+ function versionOf(instincts) {
33
+ return createHash("sha256").update(JSON.stringify(instincts)).digest("hex");
34
+ }
35
+ function bless(held, options) {
36
+ const instincts = blessedRecords(held, options.minConfidence);
37
+ return { version: versionOf(instincts), instincts };
38
+ }
39
+ function promotable(held, options) {
40
+ return blessedRecords(held, options.minConfidence).filter((each) => each.usage_count >= options.after);
41
+ }
42
+ // src/learning/merge.ts
43
+ var HUNDREDTHS2 = 100;
44
+ var GAP_H = Math.round(GAP * HUNDREDTHS2);
45
+ var STEP_H = Math.round(SOURCE_STEP * HUNDREDTHS2);
46
+ var MIN_H = Math.round(CONFIDENCE_MIN * HUNDREDTHS2);
47
+ var MAX_H = Math.round(CONFIDENCE_MAX * HUNDREDTHS2);
48
+ function hundredths2(confidence) {
49
+ return Math.round(confidence * HUNDREDTHS2);
50
+ }
51
+ function compareText2(left, right) {
52
+ if (left < right) {
53
+ return -1;
54
+ }
55
+ if (left > right) {
56
+ return 1;
57
+ }
58
+ return 0;
59
+ }
60
+ function byAge(left, right) {
61
+ return compareText2(left.created_at, right.created_at) || compareText2(left.id, right.id);
62
+ }
63
+ function byConfidence(left, right) {
64
+ return hundredths2(right.confidence) - hundredths2(left.confidence) || compareText2(left.id, right.id);
65
+ }
66
+ function sourcesOf(record) {
67
+ return record.sources !== undefined && record.sources.length > 0 ? [...new Set(record.sources)] : [record.id];
68
+ }
69
+ function withSource(record, sourceId) {
70
+ const sources = [...new Set([...record.sources ?? [], sourceId])].sort();
71
+ return { ...record, sources, usage_count: sources.length };
72
+ }
73
+ function firstOptional(ordered, key) {
74
+ const found = ordered.find((record) => record[key] !== undefined);
75
+ return found === undefined ? {} : { [key]: found[key] };
76
+ }
77
+ function collapse(group) {
78
+ const ordered = [...[...group.held].sort(byAge), ...[...group.incoming].sort(byAge)];
79
+ if (ordered.length === 1) {
80
+ return ordered[0];
81
+ }
82
+ const seen = new Set;
83
+ let weighted = 0;
84
+ for (const member of ordered) {
85
+ const own = sourcesOf(member);
86
+ const fresh = own.filter((source) => !seen.has(source));
87
+ const base = hundredths2(member.confidence) - STEP_H * (own.length - 1);
88
+ weighted += base * fresh.length;
89
+ for (const source of fresh) {
90
+ seen.add(source);
91
+ }
92
+ }
93
+ const sources = [...seen].sort();
94
+ const stepped = Math.round(weighted / sources.length + STEP_H * (sources.length - 1));
95
+ const confidence = Math.min(MAX_H, Math.max(MIN_H, stepped)) / HUNDREDTHS2;
96
+ const byAgeAll = [...ordered].sort(byAge);
97
+ const lead = byAgeAll[0];
98
+ const rest = {};
99
+ for (const [key, value] of Object.entries(lead)) {
100
+ if (key !== "artifact" && key !== "promoted_to") {
101
+ Object.assign(rest, { [key]: value });
102
+ }
103
+ }
104
+ return {
105
+ ...rest,
106
+ ...firstOptional(byAgeAll, "artifact"),
107
+ ...firstOptional(byAgeAll, "promoted_to"),
108
+ confidence,
109
+ usage_count: sources.length,
110
+ sources
111
+ };
112
+ }
113
+ function sameButStamp(left, right) {
114
+ const keys = new Set([...Object.keys(left), ...Object.keys(right)]);
115
+ keys.delete("updated_at");
116
+ return [...keys].every((key) => {
117
+ const field = key;
118
+ return JSON.stringify(left[field]) === JSON.stringify(right[field]);
119
+ });
120
+ }
121
+ function stamp(record, held, now) {
122
+ const before = held.find((candidate) => candidate.id === record.id);
123
+ if (before !== undefined && sameButStamp(before, record)) {
124
+ return before;
125
+ }
126
+ return { ...record, updated_at: now };
127
+ }
128
+ function withStatus(record, status) {
129
+ return record.status === status ? record : { ...record, status };
130
+ }
131
+ function groupByAction(held, incoming) {
132
+ const groups = new Map;
133
+ const groupOf = (hash) => {
134
+ const found = groups.get(hash);
135
+ if (found !== undefined) {
136
+ return found;
137
+ }
138
+ const made = { held: [], incoming: [] };
139
+ groups.set(hash, made);
140
+ return made;
141
+ };
142
+ for (const record of held) {
143
+ groupOf(record.action_hash).held.push(record);
144
+ }
145
+ for (const record of incoming) {
146
+ groupOf(record.action_hash).incoming.push(record);
147
+ }
148
+ return groups;
149
+ }
150
+ function settleOne(hash, group, held, now) {
151
+ if (group.held.length === 0 && group.incoming.length === 1) {
152
+ return {
153
+ produced: [group.incoming[0]],
154
+ discarded: [],
155
+ rules: new Map([[hash, "new-trigger"]])
156
+ };
157
+ }
158
+ const record = stamp(withStatus(collapse(group), "active"), held, now);
159
+ return { produced: [record], discarded: [], rules: new Map([[hash, "same-action"]]) };
160
+ }
161
+ function settleMany(collapsed, held, now) {
162
+ const leaderH = Math.max(...[...collapsed.values()].map((record) => hundredths2(record.confidence)));
163
+ const kept = new Map;
164
+ const discarded = [];
165
+ for (const [hash, record] of collapsed) {
166
+ if (leaderH - hundredths2(record.confidence) > GAP_H) {
167
+ discarded.push(record);
168
+ } else {
169
+ kept.set(hash, record);
170
+ }
171
+ }
172
+ const status = kept.size > 1 ? "flagged" : "active";
173
+ const rule = kept.size > 1 ? "flagged" : "higher-confidence";
174
+ const rules = new Map;
175
+ for (const hash of collapsed.keys()) {
176
+ rules.set(hash, kept.has(hash) ? rule : "higher-confidence");
177
+ }
178
+ const produced = [...kept.values()].map((record) => stamp(withStatus(record, status), held, now));
179
+ return {
180
+ produced: produced.sort(byConfidence),
181
+ discarded: discarded.sort(byConfidence),
182
+ rules
183
+ };
184
+ }
185
+ function settleTrigger(held, incoming, now) {
186
+ const groups = groupByAction(held, incoming);
187
+ if (groups.size === 1) {
188
+ const [hash, group] = [...groups.entries()][0];
189
+ return settleOne(hash, group, held, now);
190
+ }
191
+ const collapsed = new Map;
192
+ for (const [hash, group] of groups) {
193
+ collapsed.set(hash, collapse(group));
194
+ }
195
+ return settleMany(collapsed, held, now);
196
+ }
197
+ function merge(held, payload, now) {
198
+ const incoming = payload.instincts.map((record) => withSource(record, payload.source_id));
199
+ const keys = [...new Set(incoming.map((record) => triggerKey(record.trigger)))];
200
+ const outcomes = new Map;
201
+ for (const key of keys) {
202
+ const onKey = (record) => triggerKey(record.trigger) === key;
203
+ outcomes.set(key, settleTrigger(held.filter(onKey), incoming.filter(onKey), now));
204
+ }
205
+ const decisions = payload.instincts.map((record) => {
206
+ const outcome = outcomes.get(triggerKey(record.trigger));
207
+ return {
208
+ incoming: record,
209
+ rule: outcome.rules.get(record.action_hash),
210
+ produced: [...outcome.produced]
211
+ };
212
+ });
213
+ return {
214
+ decisions,
215
+ discarded: keys.flatMap((key) => outcomes.get(key).discarded)
216
+ };
217
+ }
218
+ export { bless, promotable, merge };
@@ -1,5 +1,8 @@
1
1
  // @bun
2
- var __require = import.meta.require;
2
+ import {
3
+ CONFIDENCE_MAX,
4
+ CONFIDENCE_MIN
5
+ } from "./index-wp8c28fd.js";
3
6
 
4
7
  // src/pr/types.ts
5
8
  var MERGE_METHODS = Object.freeze([
@@ -315,6 +318,8 @@ var CLAUDE_SETTING_SOURCES = ["user", "project", "local"];
315
318
  var PR_PROVIDERS = ["gh", "none"];
316
319
  var RELEASE_AUTO = "auto";
317
320
  var TIER_SWITCHES = ["on", "off"];
321
+ var SKILL_RESOLVERS = ["planner", "tag", "none"];
322
+ var LESSON_SWITCHES = ["on", "off"];
318
323
  function accepted(value) {
319
324
  return { value, problems: [], extras: [] };
320
325
  }
@@ -349,6 +354,11 @@ var usdAmount = (raw, at) => {
349
354
  };
350
355
  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");
351
356
  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");
357
+ 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)}`);
358
+ function toHundredths(value) {
359
+ return Math.round(value * 100);
360
+ }
361
+ 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");
352
362
  var releaseEnabled = (raw, at) => typeof raw === "boolean" || raw === RELEASE_AUTO ? accepted(raw) : refused(at, raw, `true, false or ${RELEASE_AUTO}`);
353
363
  var tierSwitch = oneOf(TIER_SWITCHES);
354
364
  var tierPin = (raw, at) => {
@@ -358,6 +368,8 @@ var tierPin = (raw, at) => {
358
368
  return tier === undefined ? refused(at, raw, `false or one of: ${SKILL_TIERS.join(", ")}`) : accepted(tier);
359
369
  };
360
370
  var routeTarget = (raw, at) => raw === false || typeof raw === "string" && raw.trim() !== "" ? accepted(raw) : refused(at, raw, "false or an agent name");
371
+ var skillResolverName = oneOf(SKILL_RESOLVERS);
372
+ var lessonSwitch = oneOf(LESSON_SWITCHES);
361
373
  var GITHUB_LOGIN = /^[A-Za-z0-9][A-Za-z0-9-]*(?:\[bot\])?$/;
362
374
  function isGitHubLogin(value) {
363
375
  return typeof value === "string" && GITHUB_LOGIN.test(value);
@@ -462,4 +474,4 @@ var moduleSource = (raw, at) => {
462
474
  }));
463
475
  };
464
476
 
465
- export { __require, MERGE_METHODS, isMergeMethod, SKILL_TIERS, BUNDLED_SKILLS_DIR, isSkillTier, realEntry, bundledBinDirectory, skillTierDirectory, resolveSkillTiers, resolveInstinctScopes, tierExists, isAgentName, BUDGET_FLAG, parseBudgetUsd, parseTaskDeclaration, stripTaskDeclaration, resolveDeclarationFlags, isMapping, describeValue, messageOf, CONFIG_VERSIONS, STORE_BACKENDS, INJECT_MODES, OUTPUT_MODES, CLAUDE_SETTING_SOURCES, PR_PROVIDERS, RELEASE_AUTO, oneOf, text, flag, mergeMethod, usdAmount, issueNumber, dayCount, releaseEnabled, tierSwitch, tierPin, routeTarget, isGitHubLogin, githubLogin, listOf, subsetOf, PREREQUISITE_KINDS, REQUIRED_ITEM_KEYS, OPTIONAL_ITEM_KEYS, requiredPrerequisite, optionalPrerequisite, MODULE_SOURCE_KINDS, MODULE_SOURCE_KEYS, moduleSource };
477
+ 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, INJECT_MODES, OUTPUT_MODES, CLAUDE_SETTING_SOURCES, PR_PROVIDERS, RELEASE_AUTO, SKILL_RESOLVERS, oneOf, text, flag, mergeMethod, usdAmount, issueNumber, dayCount, 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 };
@@ -11,6 +11,7 @@ import {
11
11
  REQUIRED_ITEM_KEYS,
12
12
  SKILL_TIERS,
13
13
  STORE_BACKENDS,
14
+ confidence,
14
15
  dayCount,
15
16
  describeValue,
16
17
  flag,
@@ -18,25 +19,61 @@ import {
18
19
  isMapping,
19
20
  isSkillTier,
20
21
  issueNumber,
22
+ lessonSwitch,
21
23
  listOf,
22
24
  mergeMethod,
23
25
  messageOf,
24
26
  moduleSource,
25
27
  oneOf,
26
28
  optionalPrerequisite,
29
+ recurrenceCount,
27
30
  releaseEnabled,
28
31
  requiredPrerequisite,
29
32
  routeTarget,
33
+ skillResolverName,
30
34
  subsetOf,
31
35
  text,
32
36
  tierPin,
33
37
  tierSwitch,
34
38
  usdAmount
35
- } from "./index-944pncd8.js";
39
+ } from "./index-pmkgvtfe.js";
36
40
 
37
41
  // src/config-schema.ts
38
42
  import { join } from "path";
39
43
 
44
+ // src/config-readers.ts
45
+ function mapOf(value, expected) {
46
+ return (raw, at) => {
47
+ if (!isMapping(raw)) {
48
+ const problem = `${at.label} is ${describeValue(raw)}, expected a mapping of names to ${expected}`;
49
+ return { value: undefined, problems: [problem], extras: [] };
50
+ }
51
+ const readings = Object.entries(raw).map(([name, entry]) => [
52
+ name,
53
+ name.trim() === "" ? {
54
+ value: undefined,
55
+ problems: [`${at.label} names ${JSON.stringify(name)}, expected a non-empty name`],
56
+ extras: []
57
+ } : value(entry, { label: `${at.label}.${name}`, key: `${at.key}.${name}` })
58
+ ]);
59
+ const problems = readings.flatMap(([, reading]) => reading.problems);
60
+ const extras = readings.flatMap(([, reading]) => reading.extras);
61
+ if (problems.length > 0)
62
+ return { value: undefined, problems, extras };
63
+ const read = new Map;
64
+ for (const [name, reading] of readings) {
65
+ if (reading.value !== undefined)
66
+ read.set(name, reading.value);
67
+ }
68
+ return { value: read, problems, extras };
69
+ };
70
+ }
71
+ var directory = text("a directory path");
72
+ var trackerKind = text("a tracker kind name");
73
+ var releaseFile = text("a file path");
74
+ var tierPins = mapOf(tierPin, "false or a tier");
75
+ var routeTable = mapOf(routeTarget, "false or an agent name");
76
+
40
77
  // src/tiers/resolve.ts
41
78
  import { readFileSync } from "fs";
42
79
  var VENDORED_HEADER = /^<!-- vendored by rafa from .* on \d{4}-\d{2}-\d{2} -->$/;
@@ -203,32 +240,6 @@ var DEFAULT_ROUTING = new Map(DEFAULT_ROUTES);
203
240
 
204
241
  // src/config-schema.ts
205
242
  var CONFIG_FILE = join(".rafa", "config.yaml");
206
- function mapOf(value, expected) {
207
- return (raw, at) => {
208
- if (!isMapping(raw)) {
209
- const problem = `${at.label} is ${describeValue(raw)}, expected a mapping of names to ${expected}`;
210
- return { value: undefined, problems: [problem], extras: [] };
211
- }
212
- const readings = Object.entries(raw).map(([name, entry]) => [
213
- name,
214
- name.trim() === "" ? {
215
- value: undefined,
216
- problems: [`${at.label} names ${JSON.stringify(name)}, expected a non-empty name`],
217
- extras: []
218
- } : value(entry, { label: `${at.label}.${name}`, key: `${at.key}.${name}` })
219
- ]);
220
- const problems = readings.flatMap(([, reading]) => reading.problems);
221
- const extras = readings.flatMap(([, reading]) => reading.extras);
222
- if (problems.length > 0)
223
- return { value: undefined, problems, extras };
224
- const read = new Map;
225
- for (const [name, reading] of readings) {
226
- if (reading.value !== undefined)
227
- read.set(name, reading.value);
228
- }
229
- return { value: read, problems, extras };
230
- };
231
- }
232
243
  var CONFIG_DEFAULTS = Object.freeze({
233
244
  version: 1,
234
245
  store: "sqlite",
@@ -238,6 +249,9 @@ var CONFIG_DEFAULTS = Object.freeze({
238
249
  trackerDefault: "github",
239
250
  trackerFallback: Object.freeze(["local"]),
240
251
  learningAdapter: "local",
252
+ learningBlessMinConfidence: 0.5,
253
+ learningPromoteAfter: 3,
254
+ learningPromoteMinConfidence: 0.7,
241
255
  outputMode: "text",
242
256
  prerequisitesRequired: Object.freeze([]),
243
257
  prerequisitesOptional: Object.freeze([]),
@@ -265,12 +279,10 @@ var CONFIG_DEFAULTS = Object.freeze({
265
279
  tiersRafa: "on",
266
280
  tiersSkills: new Map,
267
281
  tiersAgents: new Map,
268
- routing: DEFAULT_ROUTING
282
+ routing: DEFAULT_ROUTING,
283
+ taskSkills: "planner",
284
+ taskLessons: "on"
269
285
  });
270
- var directory = text("a directory path");
271
- var trackerKind = text("a tracker kind name");
272
- var releaseFile = text("a file path");
273
- var tierPins = mapOf(tierPin, "false or a tier");
274
286
  var SETTINGS = {
275
287
  version: { key: "version", read: oneOf(CONFIG_VERSIONS), cli: false },
276
288
  store: { key: "store", read: oneOf(STORE_BACKENDS), cli: true },
@@ -288,6 +300,9 @@ var SETTINGS = {
288
300
  read: text("a learning adapter name"),
289
301
  cli: true
290
302
  },
303
+ learningBlessMinConfidence: { key: "learning.bless.minConfidence", read: confidence, cli: false },
304
+ learningPromoteAfter: { key: "learning.promote.after", read: recurrenceCount, cli: false },
305
+ learningPromoteMinConfidence: { key: "learning.promote.minConfidence", read: confidence, cli: false },
291
306
  outputMode: { key: "output.mode", read: oneOf(OUTPUT_MODES), cli: true },
292
307
  prerequisitesRequired: {
293
308
  key: "prerequisites.required",
@@ -362,11 +377,9 @@ var SETTINGS = {
362
377
  tiersRafa: { key: "tiers.rafa", read: tierSwitch, cli: false },
363
378
  tiersSkills: { key: "tiers.skills", read: tierPins, cli: false },
364
379
  tiersAgents: { key: "tiers.agents", read: tierPins, cli: false },
365
- routing: {
366
- key: "routing",
367
- read: mapOf(routeTarget, "false or an agent name"),
368
- cli: false
369
- }
380
+ routing: { key: "routing", read: routeTable, cli: false },
381
+ taskSkills: { key: "task.skills", read: skillResolverName, cli: true },
382
+ taskLessons: { key: "task.lessons", read: lessonSwitch, cli: false }
370
383
  };
371
384
  var SETTING_NAMES = Object.keys(SETTINGS);
372
385
  var SETTING_BY_KEY = new Map(SETTING_NAMES.map((setting) => [
@@ -442,6 +455,9 @@ function readLayer(rawOf, atOf) {
442
455
  trackerDefault: read("trackerDefault"),
443
456
  trackerFallback: read("trackerFallback"),
444
457
  learningAdapter: read("learningAdapter"),
458
+ learningBlessMinConfidence: read("learningBlessMinConfidence"),
459
+ learningPromoteAfter: read("learningPromoteAfter"),
460
+ learningPromoteMinConfidence: read("learningPromoteMinConfidence"),
445
461
  outputMode: read("outputMode"),
446
462
  prerequisitesRequired: read("prerequisitesRequired"),
447
463
  prerequisitesOptional: read("prerequisitesOptional"),
@@ -469,7 +485,9 @@ function readLayer(rawOf, atOf) {
469
485
  tiersRafa: read("tiersRafa"),
470
486
  tiersSkills: read("tiersSkills"),
471
487
  tiersAgents: read("tiersAgents"),
472
- routing: read("routing")
488
+ routing: read("routing"),
489
+ taskSkills: read("taskSkills"),
490
+ taskLessons: read("taskLessons")
473
491
  };
474
492
  return { layer, problems, extras };
475
493
  }
@@ -685,6 +703,239 @@ function readRafaBlocks(markdown) {
685
703
  return blocks;
686
704
  }
687
705
 
706
+ // src/report/parse.ts
707
+ var REPORT_STATUSES = ["done", "blocked"];
708
+ var FINDING_KINDS = ["gotcha", "pattern", "location", "skill-suggestion"];
709
+ var FINDING_SIGNALS = ["loud", "silent"];
710
+ var FINDING_DOMAINS = [
711
+ "code-style",
712
+ "testing",
713
+ "git",
714
+ "debugging",
715
+ "workflow",
716
+ "security",
717
+ "general-best-practices"
718
+ ];
719
+ var CHANGE_LEVELS = ["patch", "minor", "major", "none"];
720
+ var REPORT_KIND = "report";
721
+ var REQUIRED = true;
722
+ var OPTIONAL = false;
723
+ var REPORT_KEYS = [
724
+ "status",
725
+ "feedback",
726
+ "findings",
727
+ "skills_used",
728
+ "blockers",
729
+ "out_of_scope_bugs",
730
+ "changes"
731
+ ];
732
+ var FINDING_KEYS = [
733
+ "trigger",
734
+ "kind",
735
+ "what",
736
+ "cause",
737
+ "resolution",
738
+ "artifact",
739
+ "signal",
740
+ "domain"
741
+ ];
742
+ var BLOCKER_KEYS = ["what", "artifact"];
743
+ var BUG_KEYS = ["what", "artifact", "security"];
744
+ var CHANGE_KEYS = ["level", "area", "summary"];
745
+ function isMapping2(value) {
746
+ return typeof value === "object" && value !== null && !Array.isArray(value);
747
+ }
748
+ function isOneOf(choices, value) {
749
+ return choices.includes(value);
750
+ }
751
+ function describeValue2(value) {
752
+ if (typeof value === "string")
753
+ return JSON.stringify(value);
754
+ if (value === null)
755
+ return "nothing";
756
+ if (Array.isArray(value))
757
+ return "a list";
758
+ if (typeof value === "object")
759
+ return "a mapping";
760
+ return `the ${typeof value} ${String(value)}`;
761
+ }
762
+ function messageOf2(error) {
763
+ return error instanceof Error ? error.message : String(error);
764
+ }
765
+ function reportIssue(reason, field, text2) {
766
+ return { reason, field, text: text2 };
767
+ }
768
+ function absent(reason, block, text2) {
769
+ return { present: false, reason, block, text: text2 };
770
+ }
771
+ function scopeOf(mapping, keys, prefix, issues) {
772
+ const fields = new Map;
773
+ const extras = [];
774
+ for (const [key, value] of Object.entries(mapping)) {
775
+ if (keys.includes(key))
776
+ fields.set(key, value);
777
+ else
778
+ extras.push({ key, value });
779
+ }
780
+ return { fields, extras, prefix, issues };
781
+ }
782
+ function valueOf(scope, key, required) {
783
+ const value = scope.fields.get(key) ?? null;
784
+ if (value === null && required) {
785
+ const field = `${scope.prefix}${key}`;
786
+ scope.issues.push(reportIssue("missing-field", field, `${field} is missing`));
787
+ }
788
+ return value;
789
+ }
790
+ function unusable(scope, key, why) {
791
+ const field = `${scope.prefix}${key}`;
792
+ scope.issues.push(reportIssue("unusable-field", field, `${field} ${why}`));
793
+ return null;
794
+ }
795
+ function stringProblem(value) {
796
+ if (typeof value !== "string")
797
+ return `is ${describeValue2(value)}, not a string; quote it`;
798
+ if (value.trim().length === 0)
799
+ return "is blank";
800
+ return null;
801
+ }
802
+ function stringField(scope, key, required) {
803
+ const value = valueOf(scope, key, required);
804
+ if (value === null)
805
+ return null;
806
+ const problem = stringProblem(value);
807
+ return problem === null ? value : unusable(scope, key, problem);
808
+ }
809
+ function choiceField(scope, key, choices, required) {
810
+ const value = valueOf(scope, key, required);
811
+ if (value === null || isOneOf(choices, value))
812
+ return value;
813
+ return unusable(scope, key, `is ${describeValue2(value)}, not one of ${choices.join(", ")}`);
814
+ }
815
+ function booleanField(scope, key, required) {
816
+ const value = valueOf(scope, key, required);
817
+ if (value === null || typeof value === "boolean")
818
+ return value;
819
+ return unusable(scope, key, `is ${describeValue2(value)}, not true or false; write it unquoted`);
820
+ }
821
+ function listField(scope, key, readEntry) {
822
+ const value = valueOf(scope, key, OPTIONAL);
823
+ if (value === null)
824
+ return [];
825
+ if (!Array.isArray(value)) {
826
+ unusable(scope, key, `is ${describeValue2(value)}, not a list`);
827
+ return [];
828
+ }
829
+ const entries = [];
830
+ const items = value;
831
+ for (const [index, item] of items.entries()) {
832
+ const entry = readEntry(item, `${scope.prefix}${key}[${index}]`, scope.issues);
833
+ if (entry !== null)
834
+ entries.push(entry);
835
+ }
836
+ return entries;
837
+ }
838
+ function entryScope(item, field, keys, issues) {
839
+ if (isMapping2(item))
840
+ return scopeOf(item, keys, `${field}.`, issues);
841
+ const text2 = `${field} is ${describeValue2(item)}, not a mapping of ${keys.join(", ")}; dropped`;
842
+ issues.push(reportIssue("unusable-field", field, text2));
843
+ return null;
844
+ }
845
+ function readSkill(item, field, issues) {
846
+ const problem = stringProblem(item);
847
+ if (problem === null)
848
+ return item;
849
+ issues.push(reportIssue("unusable-field", field, `${field} ${problem}; dropped`));
850
+ return null;
851
+ }
852
+ function readFinding(item, field, issues) {
853
+ const scope = entryScope(item, field, FINDING_KEYS, issues);
854
+ if (scope === null)
855
+ return null;
856
+ return {
857
+ trigger: stringField(scope, "trigger", REQUIRED),
858
+ kind: choiceField(scope, "kind", FINDING_KINDS, REQUIRED),
859
+ what: stringField(scope, "what", REQUIRED),
860
+ cause: stringField(scope, "cause", OPTIONAL),
861
+ resolution: stringField(scope, "resolution", OPTIONAL),
862
+ artifact: stringField(scope, "artifact", OPTIONAL),
863
+ signal: choiceField(scope, "signal", FINDING_SIGNALS, REQUIRED),
864
+ domain: choiceField(scope, "domain", FINDING_DOMAINS, OPTIONAL),
865
+ extras: scope.extras
866
+ };
867
+ }
868
+ function readBlocker(item, field, issues) {
869
+ const scope = entryScope(item, field, BLOCKER_KEYS, issues);
870
+ if (scope === null)
871
+ return null;
872
+ return {
873
+ what: stringField(scope, "what", REQUIRED),
874
+ artifact: stringField(scope, "artifact", OPTIONAL),
875
+ extras: scope.extras
876
+ };
877
+ }
878
+ function readBug(item, field, issues) {
879
+ const scope = entryScope(item, field, BUG_KEYS, issues);
880
+ if (scope === null)
881
+ return null;
882
+ return {
883
+ what: stringField(scope, "what", REQUIRED),
884
+ artifact: stringField(scope, "artifact", OPTIONAL),
885
+ security: booleanField(scope, "security", REQUIRED),
886
+ extras: scope.extras
887
+ };
888
+ }
889
+ function readChange(item, field, issues) {
890
+ const scope = entryScope(item, field, CHANGE_KEYS, issues);
891
+ if (scope === null)
892
+ return null;
893
+ return {
894
+ level: choiceField(scope, "level", CHANGE_LEVELS, REQUIRED),
895
+ area: stringField(scope, "area", OPTIONAL),
896
+ summary: stringField(scope, "summary", REQUIRED),
897
+ extras: scope.extras
898
+ };
899
+ }
900
+ function readReport(document, issues) {
901
+ const scope = scopeOf(document, REPORT_KEYS, "", issues);
902
+ return {
903
+ status: choiceField(scope, "status", REPORT_STATUSES, REQUIRED),
904
+ feedback: stringField(scope, "feedback", OPTIONAL),
905
+ findings: listField(scope, "findings", readFinding),
906
+ skillsUsed: listField(scope, "skills_used", readSkill),
907
+ blockers: listField(scope, "blockers", readBlocker),
908
+ outOfScopeBugs: listField(scope, "out_of_scope_bugs", readBug),
909
+ changes: listField(scope, "changes", readChange),
910
+ extras: scope.extras
911
+ };
912
+ }
913
+ function parseReport(output) {
914
+ const reports = readRafaBlocks(output).filter((block2) => block2.kind === REPORT_KIND);
915
+ const block = reports.at(-1);
916
+ if (block === undefined) {
917
+ return absent("no-block", null, "the session output holds no rafa:report block");
918
+ }
919
+ const line = block.span.first;
920
+ if (!block.closed) {
921
+ return absent("unclosed-block", block, `rafa:report block at line ${line} is never closed, so its body may have been cut short ` + "and is not read");
922
+ }
923
+ let document;
924
+ try {
925
+ document = Bun.YAML.parse(block.body);
926
+ } catch (error) {
927
+ const text2 = `rafa:report block at line ${line} is not valid YAML (${messageOf2(error)})`;
928
+ return absent("malformed-block", block, text2);
929
+ }
930
+ if (!isMapping2(document)) {
931
+ const text2 = `rafa:report block at line ${line} holds ${describeValue2(document)}, ` + "not a mapping of report fields";
932
+ return absent("malformed-block", block, text2);
933
+ }
934
+ const issues = [];
935
+ const report = readReport(document, issues);
936
+ return { present: true, report, block, issues };
937
+ }
938
+
688
939
  // src/adapters/output/text.ts
689
940
  var DEBUG_VERBOSITY = 2;
690
941
  function formatPayload(payload) {
@@ -773,4 +1024,4 @@ function activeOutputMode() {
773
1024
  return activeMode;
774
1025
  }
775
1026
 
776
- export { pinKey, pinLine, collisionMessage, readItemBytes, resolveTiers, findTierItem, DEFAULT_ROUTES, DEFAULT_ROUTING, CONFIG_FILE, CONFIG_DEFAULTS, SETTINGS, ConfigError, configFilePath, parseConfigText, resolveConfig, createTextOutput, activeOutput, activeOutputMode, RAFA_BLOCK_KINDS, isRafaBlockKind, readRafaBlocks };
1027
+ export { pinKey, pinLine, collisionMessage, readItemBytes, resolveTiers, findTierItem, DEFAULT_ROUTES, DEFAULT_ROUTING, CONFIG_FILE, CONFIG_DEFAULTS, SETTINGS, ConfigError, configFilePath, parseConfigText, resolveConfig, RAFA_BLOCK_KINDS, isRafaBlockKind, readRafaBlocks, REPORT_STATUSES, FINDING_KINDS, FINDING_SIGNALS, FINDING_DOMAINS, CHANGE_LEVELS, parseReport, createTextOutput, activeOutput, activeOutputMode };