@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.
- package/README.md +36 -14
- package/dist/PROMPT.md +2 -1
- package/dist/bundled/skills/dev-planner/SKILL.md +9 -0
- package/dist/cli.js +3937 -1705
- package/dist/{describe-vq037dyt.js → describe-0tefrkk7.js} +4 -3
- package/dist/effort/store/index.js +6 -4
- package/dist/{index-6vkd153j.js → index-02fx0mzf.js} +4713 -3848
- package/dist/{index-0pdsq2hy.js → index-21qkxk63.js} +11 -224
- package/dist/{index-ddf85qac.js → index-28jzpyks.js} +1 -1
- package/dist/{index-kvnggvyk.js → index-4a45ffqh.js} +2 -2
- package/dist/{index-at9f1z2z.js → index-afersvp3.js} +1028 -51
- package/dist/index-d6d0942q.js +218 -0
- package/dist/{index-944pncd8.js → index-pmkgvtfe.js} +14 -2
- package/dist/{index-jva32qe6.js → index-twbp38kz.js} +290 -39
- package/dist/index-wp8c28fd.js +18 -0
- package/dist/{index-cvpdvmtg.js → index-zhh1h3h9.js} +1023 -935
- package/dist/index.js +13 -11
- package/dist/learning/index.js +25 -0
- package/dist/plan/index.js +8 -7
- package/dist/plan-prompt.md +2 -0
- package/package.json +4 -3
|
@@ -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
|
-
|
|
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 {
|
|
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-
|
|
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
|
-
|
|
367
|
-
|
|
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,
|
|
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 };
|