ensemble-review 1.0.35 → 1.0.37

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.
Files changed (186) hide show
  1. package/dist/api/plugins/rate-limit.js +22 -0
  2. package/dist/api/routes/releases.js +181 -0
  3. package/dist/app.js +3 -0
  4. package/dist/coding/story-build/base-refs.js +10 -2
  5. package/dist/coding/story-build/loop/driver.js +1 -1
  6. package/dist/coding/story-build/loop/routes.js +9 -1
  7. package/dist/coding/story-build/persist.js +53 -7
  8. package/dist/coding/story-build/plan-persist.js +1 -1
  9. package/dist/coding/story-build/push-hotfix.js +293 -0
  10. package/dist/coding/story-build/push.js +5 -0
  11. package/dist/coding/story-build/routes-build.js +25 -3
  12. package/dist/db/erase-account.js +14 -0
  13. package/dist/db/hotfix-bases.js +31 -0
  14. package/dist/db/migrations/0195_releases.sql +128 -0
  15. package/dist/db/migrations/0196_story_build_hotfix.sql +54 -0
  16. package/dist/db/migrations/0197_release_items_live_tag.sql +11 -0
  17. package/dist/db/migrations/0198_story_build_port_report.sql +7 -0
  18. package/dist/db/migrations/meta/_journal.json +28 -0
  19. package/dist/db/queries.js +21 -0
  20. package/dist/db/release-check.js +378 -0
  21. package/dist/db/release-settings.js +94 -0
  22. package/dist/db/repo-tags.js +82 -0
  23. package/dist/db/schema.sqlite.js +246 -0
  24. package/dist/github/mutations.js +67 -0
  25. package/dist/github/queries.js +5 -0
  26. package/dist/github/settle-query.js +11 -1
  27. package/dist/github/tag-queries.js +101 -0
  28. package/dist/releases/create.js +539 -0
  29. package/dist/releases/github.js +78 -0
  30. package/dist/releases/notes.js +152 -0
  31. package/dist/releases/refresh.js +43 -0
  32. package/dist/sandbox/session/manager.js +207 -12
  33. package/dist/sandbox/session/plan.js +19 -7
  34. package/dist/sandbox/session/routes.js +12 -2
  35. package/dist/sandbox/workspace/hotfix.js +30 -0
  36. package/dist/sandbox/workspace/index.js +38 -2
  37. package/dist/sandbox/workspace/port-report.js +137 -0
  38. package/dist/sandbox/workspace/seed.js +40 -0
  39. package/dist/shared/hotfix.js +165 -0
  40. package/dist/shared/index.js +4 -0
  41. package/dist/shared/release-check.js +528 -0
  42. package/dist/shared/release-write.js +264 -0
  43. package/dist/shared/releases.js +89 -0
  44. package/dist/shared/story-build.js +2 -0
  45. package/dist/sync/repo-tags.js +368 -0
  46. package/dist/sync/settle-sweep.js +47 -2
  47. package/dist/sync/sync-repo.js +12 -0
  48. package/dist/sync/tag-ranges.js +350 -0
  49. package/dist/sync/tags-step.js +77 -0
  50. package/dist/sync/upsert.js +23 -0
  51. package/package.json +5 -2
  52. package/public/assets/{AiFixTab-CVIZl9I8.js → AiFixTab-BC3tDZ3z.js} +1 -1
  53. package/public/assets/{PrBotBehaviourTab-q0M7RU1e.js → PrBotBehaviourTab-C5j5uhLn.js} +1 -1
  54. package/public/assets/index-BrjUDY3j.js +138 -0
  55. package/public/assets/index-D1zhsqSF.css +1 -0
  56. package/public/index.html +2 -2
  57. package/dist/ai/check-tool.js +0 -6
  58. package/dist/api/routes/auth.js +0 -194
  59. package/dist/api/routes/billing.js +0 -209
  60. package/dist/api/routes/claude-review.js +0 -522
  61. package/dist/api/routes/contact.js +0 -318
  62. package/dist/api/routes/feed.js +0 -21
  63. package/dist/api/routes/inbox.js +0 -45
  64. package/dist/api/routes/mentions.js +0 -22
  65. package/dist/api/routes/teams.js +0 -150
  66. package/dist/api/routes/webhooks.js +0 -181
  67. package/dist/coding/activity.js +0 -80
  68. package/dist/coding/agent.js +0 -147
  69. package/dist/coding/ai-fix/checks-preview.js +0 -58
  70. package/dist/coding/ai-fix/comment-seed.js +0 -519
  71. package/dist/coding/ai-fix/prompts.js +0 -283
  72. package/dist/coding/ai-resolve/prompt.js +0 -161
  73. package/dist/coding/ai-resolve/routes.js +0 -97
  74. package/dist/coding/ai-resolve/run.js +0 -343
  75. package/dist/coding/ai-resolve/schema.js +0 -26
  76. package/dist/coding/ai-resolve/validate.js +0 -136
  77. package/dist/coding/checks/deny-scan.js +0 -75
  78. package/dist/coding/checks/discover.js +0 -541
  79. package/dist/coding/checks/docker.js +0 -38
  80. package/dist/coding/checks/env.js +0 -88
  81. package/dist/coding/checks/hooks.js +0 -91
  82. package/dist/coding/checks/index.js +0 -8
  83. package/dist/coding/checks/local.js +0 -40
  84. package/dist/coding/checks/sandbox.js +0 -206
  85. package/dist/coding/checks/session.js +0 -518
  86. package/dist/coding/checks/spawn.js +0 -123
  87. package/dist/coding/checks/spec.js +0 -140
  88. package/dist/coding/checks/types.js +0 -6
  89. package/dist/coding/partial-spend.js +0 -19
  90. package/dist/coding/safe-path.js +0 -75
  91. package/dist/coding/story-build/build-prompts.js +0 -178
  92. package/dist/coding/story-build/build.js +0 -332
  93. package/dist/coding/story-build/checkouts.js +0 -66
  94. package/dist/coding/story-build/docs-runner.js +0 -178
  95. package/dist/coding/story-build/docs.js +0 -334
  96. package/dist/coding/story-build/manager.js +0 -999
  97. package/dist/coding/story-build/plan-agent.js +0 -190
  98. package/dist/coding/story-build/plan.js +0 -338
  99. package/dist/coding/story-build/prompts.js +0 -147
  100. package/dist/coding/story-build/routes-docs.js +0 -113
  101. package/dist/coding/story-build/routes.js +0 -295
  102. package/dist/coding/story-build/schema.js +0 -53
  103. package/dist/db/automation-output.js +0 -349
  104. package/dist/db/credits.js +0 -79
  105. package/dist/db/schema.js +0 -321
  106. package/dist/db/schema.pg.js +0 -2197
  107. package/dist/db/sprint-repo-suggest.js +0 -257
  108. package/dist/db/sprint-stories.js +0 -42
  109. package/dist/db/sprint-story.js +0 -505
  110. package/dist/db/sprint-ticket-repos.js +0 -195
  111. package/dist/db/story-selection.js +0 -270
  112. package/dist/db/team-comparison.js +0 -124
  113. package/dist/db/workspace-comparison.js +0 -98
  114. package/dist/feed/fyi-provider.js +0 -10
  115. package/dist/github/local-user.js +0 -92
  116. package/dist/github/repo-files.js +0 -26
  117. package/dist/ml/worker.js +0 -31
  118. package/dist/platform/advisor/discovery/tier2.js +0 -88
  119. package/dist/platform/advisor/discovery/tier3.js +0 -57
  120. package/dist/platform/advisor/emitters/adapter.js +0 -5
  121. package/dist/platform/advisor/emitters/coderabbit.js +0 -175
  122. package/dist/platform/advisor/emitters/copilot-prose.js +0 -118
  123. package/dist/platform/advisor/emitters/greptile.js +0 -176
  124. package/dist/platform/advisor/emitters/lint.js +0 -37
  125. package/dist/platform/advisor/emitters/manifest.js +0 -239
  126. package/dist/platform/advisor/emitters/prose.js +0 -183
  127. package/dist/platform/advisor/emitters/registry.js +0 -20
  128. package/dist/platform/advisor/emitters/yaml-doc.js +0 -95
  129. package/dist/platform/advisor/evidence.js +0 -79
  130. package/dist/platform/advisor/findings.js +0 -15
  131. package/dist/platform/advisor/intents.js +0 -269
  132. package/dist/platform/advisor/measure/effect.js +0 -18
  133. package/dist/platform/advisor/outputs/brief.js +0 -68
  134. package/dist/platform/advisor/outputs/config-pr.js +0 -156
  135. package/dist/platform/advisor/refine/diff-guard.js +0 -38
  136. package/dist/platform/advisor/refine/refine.js +0 -35
  137. package/dist/platform/advisor/retro-check.js +0 -98
  138. package/dist/platform/advisor/routes.js +0 -648
  139. package/dist/platform/advisor/store.js +0 -321
  140. package/dist/platform/advisor/taxonomy.js +0 -67
  141. package/dist/platform/annotations/prompts.js +0 -84
  142. package/dist/platform/annotations/routes.js +0 -86
  143. package/dist/platform/annotations/runner.js +0 -234
  144. package/dist/platform/annotations/targets.js +0 -203
  145. package/dist/platform/insights/grounded-figures.js +0 -162
  146. package/dist/platform/insights/period-questions.js +0 -235
  147. package/dist/platform/work-plan/hash.js +0 -99
  148. package/dist/platform/work-plan/prompt.js +0 -209
  149. package/dist/platform/work-plan/routes.js +0 -350
  150. package/dist/pro/bind.js +0 -309
  151. package/dist/pro/contract.js +0 -36
  152. package/dist/pro/event-hooks.js +0 -37
  153. package/dist/pro/migrate.js +0 -64
  154. package/dist/review/events.js +0 -24
  155. package/dist/review/local-settings.js +0 -78
  156. package/dist/review/memory/capture.js +0 -0
  157. package/dist/review/memory/retrieval.js +0 -272
  158. package/dist/review/memory/routes.js +0 -78
  159. package/dist/review/persist.js +0 -203
  160. package/dist/review/plugin-providers.js +0 -12
  161. package/dist/review/prompt.js +0 -264
  162. package/dist/review/review-manager.js +0 -259
  163. package/dist/review/routing.js +0 -164
  164. package/dist/review/sprint-assess/agent.js +0 -145
  165. package/dist/review/sprint-assess/manager.js +0 -382
  166. package/dist/review/sprint-assess/prompts.js +0 -99
  167. package/dist/review/sprint-assess/routes.js +0 -109
  168. package/dist/review/sprint-assess/schema.js +0 -70
  169. package/dist/review/sprint-assess/validate.js +0 -180
  170. package/dist/review/sprint-story/manager.js +0 -98
  171. package/dist/review/sprint-story/prompt.js +0 -45
  172. package/dist/review/sprint-story/validate.js +0 -127
  173. package/dist/review/story-docs/build-prompts.js +0 -153
  174. package/dist/review/story-docs/build-validate.js +0 -162
  175. package/dist/review/story-report/agent.js +0 -203
  176. package/dist/review/story-report/fingerprint.js +0 -45
  177. package/dist/review/story-report/manager.js +0 -287
  178. package/dist/review/story-report/routes.js +0 -154
  179. package/dist/review/story-report/story-detail.js +0 -148
  180. package/dist/review/story-report/validate.js +0 -105
  181. package/dist/review/ticket-review/sweep-waits.js +0 -20
  182. package/dist/review/write-deny.js +0 -170
  183. package/dist/shared/conflict-ai.js +0 -24
  184. package/dist/sync/benchmark-rollup.js +0 -50
  185. package/public/assets/index-CbEfmqOD.css +0 -1
  186. package/public/assets/index-DBK4RWQu.js +0 -136
@@ -1,183 +0,0 @@
1
- import { CATEGORY_PHRASING, SEVERITY_FLOOR_PHRASING } from '../taxonomy.js';
2
- import { MAX_CONFIG_BYTES } from './adapter.js';
3
- export const MARKER_START = '<!-- limn:advisor:start -->';
4
- export const MARKER_END = '<!-- limn:advisor:end -->';
5
- export const INTENTS_HEADER_PREFIX = '<!-- limn:advisor:intents:';
6
- // Priority for budget drops: suppressions first (they are why the user is here), the
7
- // severity floor next, then escalations/amplifications, then the pair/lint advisories.
8
- const PRIORITY = [
9
- 'SUPPRESS_PATH',
10
- 'QUIET_PATH_NITS',
11
- 'SUPPRESS_CATEGORY',
12
- 'LOWER_VERBOSITY',
13
- 'ESCALATE',
14
- 'AMPLIFY_PATH',
15
- 'SCOPE_OFF',
16
- 'PROMOTE_TO_LINT',
17
- 'BOOTSTRAP_CONFIG',
18
- ];
19
- export function sentenceFor(intent) {
20
- switch (intent.kind) {
21
- case 'SUPPRESS_PATH':
22
- return `Do not review files under \`${intent.targetKey}\` — findings there consistently go unaddressed here.`;
23
- case 'QUIET_PATH_NITS':
24
- return `In \`${intent.targetKey}\`, do not post nitpick-level comments — raise only findings of minor severity and above there.`;
25
- case 'SUPPRESS_CATEGORY': {
26
- const t = intent.target;
27
- const neg = t.kind === 'category' ? CATEGORY_PHRASING[t.category].negative : undefined;
28
- return neg ?? `Do not raise ${intent.targetKey} findings.`;
29
- }
30
- case 'LOWER_VERBOSITY':
31
- return SEVERITY_FLOOR_PHRASING.negative;
32
- case 'ESCALATE': {
33
- const t = intent.target;
34
- const pos = t.kind === 'category' ? CATEGORY_PHRASING[t.category].positive : undefined;
35
- return pos ?? `Prioritize ${intent.targetKey} findings.`;
36
- }
37
- case 'AMPLIFY_PATH':
38
- return `Review files under \`${intent.targetKey}\` with extra depth — findings there are consistently acted on.`;
39
- case 'SCOPE_OFF': {
40
- const partner = intent.target.kind === 'partner' ? intent.target.partnerLabel : 'another reviewer';
41
- return `Skip findings that ${partner} also raises (overlapping line coverage); focus where you are the only reviewer.`;
42
- }
43
- case 'PROMOTE_TO_LINT':
44
- return `Do not flag "${intent.targetKey}" — it is being promoted to a deterministic lint rule.`;
45
- case 'BOOTSTRAP_CONFIG':
46
- return 'This instruction block was bootstrapped by Limn; tune the sentences above as the evidence changes.';
47
- }
48
- }
49
- const headerLine = (intents) => `${INTENTS_HEADER_PREFIX} ${intents.map((i) => `${i.kind}|${i.targetKey}`).join(' ')} -->`;
50
- const assemble = (intents) => [
51
- MARKER_START,
52
- headerLine(intents),
53
- '',
54
- ...intents.map((i) => `- ${sentenceFor(i)}`),
55
- '',
56
- MARKER_END,
57
- ].join('\n');
58
- // Render the managed block. `budget` is the max length of the BLOCK (the caller subtracts
59
- // whatever else shares the file's budget); over budget ⇒ drop lowest-priority intents one
60
- // at a time, deterministically.
61
- export function renderManagedBlock(intents, budget) {
62
- const ordered = [...intents].sort((a, b) => PRIORITY.indexOf(a.kind) - PRIORITY.indexOf(b.kind) ||
63
- a.targetKey.localeCompare(b.targetKey));
64
- const included = [...ordered];
65
- const dropped = [];
66
- let block = assemble(included);
67
- if (budget != null) {
68
- while (included.length > 0 && block.length > budget) {
69
- dropped.unshift(included.pop());
70
- block = assemble(included);
71
- }
72
- }
73
- return { block, included, dropped };
74
- }
75
- // Replace an existing managed block in `text`, or append one (blank-line separated). Never
76
- // touches a byte outside the markers; a malformed half-block (start without end) is a
77
- // refusal — overwriting it could eat user content.
78
- export function upsertManagedBlock(text, block) {
79
- if (text == null || text.trim() === '')
80
- return { ok: true, content: `${block}\n` };
81
- const start = text.indexOf(MARKER_START);
82
- const end = text.indexOf(MARKER_END);
83
- if (start === -1 && end === -1) {
84
- const sep = text.endsWith('\n') ? '\n' : '\n\n';
85
- return { ok: true, content: `${text}${sep}${block}\n` };
86
- }
87
- if (start === -1 || end === -1 || end < start) {
88
- return { ok: false, reason: 'malformed limn:advisor marker block — refusing to edit' };
89
- }
90
- return {
91
- ok: true,
92
- content: text.slice(0, start) + block + text.slice(end + MARKER_END.length),
93
- };
94
- }
95
- export function extractManagedBlock(text) {
96
- const start = text.indexOf(MARKER_START);
97
- const end = text.indexOf(MARKER_END);
98
- if (start === -1 || end === -1 || end < start)
99
- return null;
100
- return text.slice(start, end + MARKER_END.length);
101
- }
102
- export function genericProseAdapter(configPath, opts) {
103
- const degraded = (alternative) => ({ level: 'degraded', alternative });
104
- const proseNote = 'expressed as a prose instruction in the managed block';
105
- return {
106
- key: `prose:${configPath}`,
107
- displayName: `Instructions (${configPath})`,
108
- capabilities: {
109
- SUPPRESS_PATH: degraded(proseNote),
110
- QUIET_PATH_NITS: { level: 'expressible' },
111
- SUPPRESS_CATEGORY: { level: 'expressible' },
112
- LOWER_VERBOSITY: { level: 'expressible' },
113
- SCOPE_OFF: degraded(proseNote),
114
- AMPLIFY_PATH: { level: 'expressible' },
115
- ESCALATE: { level: 'expressible' },
116
- PROMOTE_TO_LINT: degraded(proseNote),
117
- BOOTSTRAP_CONFIG: { level: 'expressible' },
118
- },
119
- configTargets: [{ path: configPath, format: 'prose' }],
120
- parse(files) {
121
- const file = files.find((f) => f.path === configPath);
122
- const text = file?.text ?? null;
123
- if (text != null && text.length > MAX_CONFIG_BYTES) {
124
- return {
125
- ok: false,
126
- reason: `${configPath} exceeds ${MAX_CONFIG_BYTES} bytes — refusing to edit`,
127
- config: { text: null },
128
- };
129
- }
130
- return { ok: true, config: { text } };
131
- },
132
- plan(intents, current) {
133
- const items = [];
134
- const render = renderManagedBlock(intents, opts?.charBudget);
135
- const upserted = upsertManagedBlock(current.text, render.block);
136
- if (!upserted.ok) {
137
- for (const intent of intents) {
138
- items.push({
139
- intentKind: intent.kind,
140
- targetKey: intent.targetKey,
141
- status: 'unsupported',
142
- note: upserted.reason,
143
- });
144
- }
145
- return { items, state: { files: [], changed: false } };
146
- }
147
- for (const intent of render.included) {
148
- items.push({ intentKind: intent.kind, targetKey: intent.targetKey, status: 'applied' });
149
- }
150
- for (const intent of render.dropped) {
151
- items.push({
152
- intentKind: intent.kind,
153
- targetKey: intent.targetKey,
154
- status: 'unsupported',
155
- note: 'dropped: over the char budget',
156
- });
157
- }
158
- const changed = render.included.length > 0 && upserted.content !== current.text;
159
- return {
160
- items,
161
- state: {
162
- files: changed ? [{ path: configPath, content: upserted.content }] : [],
163
- changed,
164
- },
165
- };
166
- },
167
- serialize(plan) {
168
- return plan.state.changed ? plan.state.files : [];
169
- },
170
- validate(files) {
171
- const errors = [];
172
- if (opts?.charBudget != null) {
173
- for (const f of files) {
174
- if (f.content.length > opts.charBudget) {
175
- errors.push(`${f.path}: over the ${opts.charBudget}-char budget`);
176
- }
177
- }
178
- }
179
- return { ok: errors.length === 0, errors };
180
- },
181
- };
182
- }
183
- //# sourceMappingURL=prose.js.map
@@ -1,20 +0,0 @@
1
- import { coderabbitAdapter } from './coderabbit.js';
2
- import { greptileAdapter } from './greptile.js';
3
- import { copilotProseAdapter } from './copilot-prose.js';
4
- const ADAPTERS = {
5
- coderabbit: coderabbitAdapter,
6
- greptile: greptileAdapter,
7
- copilot: copilotProseAdapter,
8
- };
9
- // `kind` is the AutomatedReviewerKind from the classifier; adapter keys deliberately match
10
- // the kinds they serve so a profile `kindHint` of 'known:coderabbit' round-trips.
11
- export function adapterForKind(kind) {
12
- return ADAPTERS[kind] ?? null;
13
- }
14
- export function adapterByKey(key) {
15
- return ADAPTERS[key] ?? null;
16
- }
17
- export function listAdapters() {
18
- return Object.values(ADAPTERS);
19
- }
20
- //# sourceMappingURL=registry.js.map
@@ -1,95 +0,0 @@
1
- // Shared eemeli `yaml` Document round-trip helpers. The Document API is the load-bearing
2
- // choice: comments, ordering, anchors and odd indentation SURVIVE a parse→edit→stringify
3
- // cycle, which is the hard requirement for editing a user's hand-written config (the
4
- // merge-never-regenerate rule). Every helper is additive: get-or-create a collection node,
5
- // append a scalar if absent — nothing rewrites what the user wrote.
6
- //
7
- // Input is REPO-AUTHORED: size-capped by the caller (MAX_CONFIG_BYTES), parsed with the CORE
8
- // schema only (no custom tag execution — `yaml` core tags are data-only), and a document
9
- // with parse errors is a refusal, never a best-effort edit.
10
- import { Document, isMap, isScalar, isSeq, parseDocument } from 'yaml';
11
- export function parseYamlConfig(text) {
12
- const doc = parseDocument(text, { prettyErrors: false });
13
- if (doc.errors.length > 0) {
14
- return { ok: false, reason: `YAML parse error: ${doc.errors[0].message}` };
15
- }
16
- return { ok: true, doc };
17
- }
18
- export function emptyYamlDoc() {
19
- return new Document({});
20
- }
21
- // Get-or-create the map at a dotted path (['reviews'] → the reviews map), creating plain
22
- // block maps along the way. Refuses (returns null) when an intermediate node exists but is
23
- // not a map — that is the user's structure and not ours to rewrite.
24
- export function ensureMapAt(doc, path) {
25
- if (!doc.contents)
26
- doc.contents = doc.createNode({});
27
- let node = doc.contents;
28
- for (const key of path) {
29
- if (!isMap(node))
30
- return null;
31
- let next = node.get(key, true);
32
- if (next == null) {
33
- const created = doc.createNode({});
34
- node.set(doc.createNode(key), created);
35
- next = created;
36
- }
37
- node = next;
38
- }
39
- return isMap(node) ? node : null;
40
- }
41
- // Get-or-create the sequence at `key` under the map at `path`. Same refusal rule.
42
- export function ensureSeqAt(doc, path, key) {
43
- const map = ensureMapAt(doc, path);
44
- if (!map)
45
- return null;
46
- let seq = map.get(key, true);
47
- if (seq == null) {
48
- const created = doc.createNode([]);
49
- map.set(doc.createNode(key), created);
50
- seq = created;
51
- }
52
- return isSeq(seq) ? seq : null;
53
- }
54
- // Append a string scalar unless an equal scalar is already present. Returns whether the
55
- // value is in the sequence afterwards BECAUSE OF US (false = it was already there — a noop,
56
- // which the plan reports rather than double-appending on a re-run).
57
- export function appendUniqueString(doc, seq, value) {
58
- for (const item of seq.items) {
59
- if (isScalar(item) && item.value === value)
60
- return false;
61
- }
62
- // An EMPTY flow seq (`[]` — ours from a bootstrap template, or a user's placeholder)
63
- // switches to block style before the first entry: multi-entry flow renders as an unreadable
64
- // one-liner, and there are no existing entries whose formatting could be disturbed.
65
- if (seq.items.length === 0)
66
- seq.flow = false;
67
- seq.add(doc.createNode(value));
68
- return true;
69
- }
70
- // Read a scalar at map path (null when absent or not a scalar).
71
- export function scalarAt(doc, path, key) {
72
- let node = doc.contents;
73
- for (const k of path) {
74
- if (!isMap(node))
75
- return null;
76
- node = node.get(k, true);
77
- }
78
- if (!isMap(node))
79
- return null;
80
- const v = node.get(key, true);
81
- return isScalar(v) && (typeof v.value === 'string' || typeof v.value === 'number')
82
- ? String(v.value)
83
- : null;
84
- }
85
- export function setScalar(doc, path, key, value) {
86
- const map = ensureMapAt(doc, path);
87
- if (!map)
88
- return false;
89
- map.set(doc.createNode(key), doc.createNode(value));
90
- return true;
91
- }
92
- export function docToString(doc) {
93
- return doc.toString();
94
- }
95
- //# sourceMappingURL=yaml-doc.js.map
@@ -1,79 +0,0 @@
1
- const pct = (num, den) => den > 0 ? Math.round((num / den) * 100) : null;
2
- export function evidenceFromPathCell(cell, bot, win) {
3
- return {
4
- windowKind: win.kind,
5
- from: win.from,
6
- to: win.to,
7
- volume: cell.volume,
8
- actedOn: cell.actedOn,
9
- actedOnPct: pct(cell.actedOn, cell.volume),
10
- untouched: cell.untouched,
11
- untouchedPct: pct(cell.untouched, cell.volume),
12
- mergedUntouched: cell.mergedUntouched,
13
- overdueUntouched: cell.overdueUntouched,
14
- dissent: cell.dissent,
15
- actedOnHigh: cell.actedOnHigh,
16
- bySeverity: cell.bySeverity,
17
- samplePrIds: cell.samplePrIds,
18
- pathCoveragePct: bot.pathCoveragePct,
19
- };
20
- }
21
- export function evidenceFromCategoryCell(cell, bot, win) {
22
- return {
23
- windowKind: win.kind,
24
- from: win.from,
25
- to: win.to,
26
- volume: cell.findings,
27
- actedOn: cell.actedOn,
28
- // Acted-on facts exist only for the THREAD-LINKED subset — the denominator is disclosed
29
- // via `threadLinked`, never silently swapped for `findings`.
30
- actedOnPct: pct(cell.actedOn, cell.threadLinked),
31
- untouched: cell.untouched,
32
- untouchedPct: pct(cell.untouched, cell.threadLinked),
33
- mergedUntouched: cell.mergedUntouched,
34
- overdueUntouched: cell.overdueUntouched,
35
- dissent: cell.dissent,
36
- actedOnHigh: cell.actedOnHigh,
37
- bySeverity: cell.bySeverity,
38
- threadLinked: cell.threadLinked,
39
- samplePrIds: cell.samplePrIds,
40
- pathCoveragePct: bot.pathCoveragePct,
41
- };
42
- }
43
- export function evidenceFromBotTotals(bot, win) {
44
- return {
45
- windowKind: win.kind,
46
- from: win.from,
47
- to: win.to,
48
- volume: bot.threads,
49
- actedOn: bot.actedOn,
50
- actedOnPct: pct(bot.actedOn, bot.threads),
51
- untouched: bot.untouched,
52
- untouchedPct: pct(bot.untouched, bot.threads),
53
- mergedUntouched: bot.mergedUntouched,
54
- overdueUntouched: bot.overdueUntouched,
55
- dissent: bot.dissent,
56
- actedOnHigh: 0,
57
- samplePrIds: [],
58
- pathCoveragePct: bot.pathCoveragePct,
59
- };
60
- }
61
- export function evidenceFromOverlapCell(cell, bot, win) {
62
- return {
63
- windowKind: win.kind,
64
- from: win.from,
65
- to: win.to,
66
- volume: cell.threads,
67
- actedOn: bot.actedOn,
68
- actedOnPct: pct(bot.actedOn, bot.threads),
69
- untouched: bot.untouched,
70
- untouchedPct: pct(bot.untouched, bot.threads),
71
- mergedUntouched: bot.mergedUntouched,
72
- overdueUntouched: bot.overdueUntouched,
73
- dissent: bot.dissent,
74
- actedOnHigh: 0,
75
- samplePrIds: [],
76
- pathCoveragePct: bot.pathCoveragePct,
77
- };
78
- }
79
- //# sourceMappingURL=evidence.js.map
@@ -1,15 +0,0 @@
1
- export function parseFindingsPayload(raw) {
2
- const p = raw;
3
- if (!p ||
4
- typeof p !== 'object' ||
5
- !Array.isArray(p.bots) ||
6
- !Array.isArray(p.pathCells) ||
7
- !Array.isArray(p.categoryCells) ||
8
- !Array.isArray(p.overlapCells) ||
9
- typeof p.window?.kind !== 'string' ||
10
- typeof p.floors?.minCellThreads !== 'number') {
11
- throw new Error('getAdvisorFindings returned an unrecognized payload (host/plugin contract drift?)');
12
- }
13
- return p;
14
- }
15
- //# sourceMappingURL=findings.js.map
@@ -1,269 +0,0 @@
1
- import { evidenceFromBotTotals, evidenceFromCategoryCell, evidenceFromOverlapCell, evidenceFromPathCell, } from './evidence.js';
2
- import { retroFor } from './retro-check.js';
3
- import { CATEGORY_PHRASING } from './taxonomy.js';
4
- // Suppression: a (bot × path) or thread-linked (bot × category) where ≥70% went untouched —
5
- // the same bar as the free path suggestion (untouchedPct ≥ 70), kept in lockstep on purpose.
6
- export const SUPPRESS_MIN_UNTOUCHED_SHARE = 0.7;
7
- // The suppression VETO: a cell containing even ONE acted-on high-severity finding never
8
- // earns a full suppress — the team demonstrably got value there, and "4 of 5 ignored" is
9
- // not licence to also silence the one that mattered. Vetoed path cells fall through to the
10
- // nit-scoped QUIET_PATH_NITS check below instead.
11
- export const SUPPRESS_MAX_ACTED_HIGH = 0;
12
- // The MERGED-PAST gate (Alex, 2026-08-09): a suppression additionally needs at least one
13
- // untouched thread on a PR that has SINCE MERGED. Silence on an open PR is not final — the
14
- // team may still act; a merge over the bot's open thread is the team's final answer, and
15
- // only that earns a suppress. (Measured on erxes: 39% of merged PRs carry an untouched
16
- // CodeRabbit thread — the signal is real, not vestigial.)
17
- export const SUPPRESS_MIN_MERGED_UNTOUCHED = 1;
18
- // QUIET_PATH_NITS — the noise-focused path intent: keep the path, stop nit-level comments
19
- // there. Fires when the path's LABELLED findings are nit-dominated (the family's 0.7 bar)
20
- // and those nits are mostly ignored; the labelled floor reuses floors.minCellThreads (the
21
- // same ≥5 claim, over the labelled subset).
22
- export const QUIET_PATH_MIN_NIT_SHARE = 0.7;
23
- export const QUIET_PATH_MAX_NIT_ACTED_SHARE = 0.3;
24
- // Amplification: an area where ≥`floors.amplifyMinActedPct` of the bot's findings were acted
25
- // on AND at least this many were high-severity — evidence the bot is genuinely valuable there.
26
- export const AMPLIFY_MIN_ACTED_HIGH = 2;
27
- // Escalation (category): the thread-linked acted-on share that marks a category the team
28
- // consistently acts on — worth asking the bot to rank higher.
29
- export const ESCALATE_MIN_ACTED_SHARE = 0.7;
30
- export const ESCALATE_MIN_THREAD_LINKED = 5;
31
- // LOWER_VERBOSITY nit share (volume bar = floors.minCellFindings — core's ML_NIT pair).
32
- export const LOWER_VERBOSITY_MIN_NIT_SHARE = 0.7;
33
- // SCOPE_OFF mirrors core's overlap-suggestion pair.
34
- export const SCOPE_OFF_MIN_OVERLAP_SHARE = 0.4;
35
- // Bootstrap: an unconfigured bot only earns a config skeleton once it has real volume.
36
- export const BOOTSTRAP_MIN_THREADS = 10;
37
- // PROMOTE_TO_LINT (DeepSource title templates): a rule template must fire this often.
38
- export const PROMOTE_TO_LINT_MIN_FIRES = 20;
39
- const dedupeKeyFor = (kind, botUserId, targetKey, windowKind) => `${kind}|${botUserId}|${targetKey}|${windowKind}`;
40
- export function deriveIntents(payload, opts) {
41
- const win = payload.window;
42
- const botById = new Map(payload.bots.map((b) => [b.botUserId, b]));
43
- const out = [];
44
- const push = (intent) => {
45
- out.push(intent);
46
- };
47
- const botOf = (id) => {
48
- const b = botById.get(id);
49
- // Cells never come from quality checks (core guarantees it), but the guard keeps the
50
- // rule visible: a linter's noise is its job, and no intent may target one.
51
- return b && !b.isQualityCheck ? b : undefined;
52
- };
53
- // ── Path cells → SUPPRESS_PATH / AMPLIFY_PATH / QUIET_PATH_NITS ───────────────────────
54
- // One intent per cell, checked in subsumption order: full suppression (only when the
55
- // veto passes — nothing high-severity was acted on there), then amplification, then the
56
- // nit-scoped quieting. A vetoed suppression cell falls through to the quiet check; a
57
- // vetoed cell that is not nit-dominated produces NOTHING, which is the honest outcome —
58
- // "mostly ignored but the one acted-on finding was a big one" is not a tuning signal.
59
- for (const cell of payload.pathCells) {
60
- const bot = botOf(cell.botUserId);
61
- if (!bot)
62
- continue;
63
- const untouchedShare = cell.volume > 0 ? cell.untouched / cell.volume : 0;
64
- const actedShare = cell.volume > 0 ? cell.actedOn / cell.volume : 0;
65
- const labelled = cell.bySeverity.nit + cell.bySeverity.minor + cell.bySeverity.major + cell.bySeverity.critical;
66
- const nitShare = labelled > 0 ? cell.bySeverity.nit / labelled : 0;
67
- const nitActedShare = cell.bySeverity.nit > 0 ? cell.actedOnNits / cell.bySeverity.nit : 0;
68
- if (untouchedShare >= SUPPRESS_MIN_UNTOUCHED_SHARE &&
69
- cell.actedOnHigh <= SUPPRESS_MAX_ACTED_HIGH &&
70
- cell.mergedUntouched >= SUPPRESS_MIN_MERGED_UNTOUCHED) {
71
- push({
72
- kind: 'SUPPRESS_PATH',
73
- botUserId: bot.botUserId,
74
- botKey: bot.key,
75
- botLabel: bot.label,
76
- botKind: bot.kind,
77
- target: { kind: 'path', pathBucket: cell.pathBucket },
78
- targetKey: cell.pathBucket,
79
- dedupeKey: dedupeKeyFor('SUPPRESS_PATH', bot.botUserId, cell.pathBucket, win.kind),
80
- rationale: `${Math.round(untouchedShare * 100)}% of ${bot.label}'s ${cell.volume} threads in ${cell.pathBucket} went untouched (${cell.mergedUntouched} shipped in PRs that merged anyway), and nothing acted on there was high-severity — suppress this path.`,
81
- evidence: evidenceFromPathCell(cell, bot, win),
82
- retro: retroFor('SUPPRESS_PATH', { pathCell: cell }),
83
- });
84
- }
85
- else if (actedShare * 100 >= payload.floors.amplifyMinActedPct &&
86
- cell.actedOnHigh >= AMPLIFY_MIN_ACTED_HIGH) {
87
- push({
88
- kind: 'AMPLIFY_PATH',
89
- botUserId: bot.botUserId,
90
- botKey: bot.key,
91
- botLabel: bot.label,
92
- botKind: bot.kind,
93
- target: { kind: 'path', pathBucket: cell.pathBucket },
94
- targetKey: cell.pathBucket,
95
- dedupeKey: dedupeKeyFor('AMPLIFY_PATH', bot.botUserId, cell.pathBucket, win.kind),
96
- rationale: `${Math.round(actedShare * 100)}% of ${bot.label}'s ${cell.volume} threads in ${cell.pathBucket} were acted on (${cell.actedOnHigh} high-severity) — ask for deeper coverage here.`,
97
- evidence: evidenceFromPathCell(cell, bot, win),
98
- retro: retroFor('AMPLIFY_PATH', { pathCell: cell }),
99
- });
100
- }
101
- else if (labelled >= payload.floors.minCellThreads &&
102
- nitShare >= QUIET_PATH_MIN_NIT_SHARE &&
103
- nitActedShare <= QUIET_PATH_MAX_NIT_ACTED_SHARE) {
104
- push({
105
- kind: 'QUIET_PATH_NITS',
106
- botUserId: bot.botUserId,
107
- botKey: bot.key,
108
- botLabel: bot.label,
109
- botKind: bot.kind,
110
- target: { kind: 'path', pathBucket: cell.pathBucket },
111
- targetKey: cell.pathBucket,
112
- dedupeKey: dedupeKeyFor('QUIET_PATH_NITS', bot.botUserId, cell.pathBucket, win.kind),
113
- rationale: `${cell.bySeverity.nit} of ${bot.label}'s ${labelled} scored findings in ${cell.pathBucket} are nits and ${cell.bySeverity.nit - cell.actedOnNits} of those went unaddressed — keep the path, stop nit-level comments there.`,
114
- evidence: evidenceFromPathCell(cell, bot, win),
115
- retro: retroFor('QUIET_PATH_NITS', { pathCell: cell }),
116
- });
117
- }
118
- }
119
- // ── Category cells → SUPPRESS_CATEGORY / ESCALATE ─────────────────────────────────────
120
- for (const cell of payload.categoryCells) {
121
- const bot = botOf(cell.botUserId);
122
- if (!bot)
123
- continue;
124
- if (cell.category === 'praise')
125
- continue; // labelled work, never a finding target
126
- const phrasing = CATEGORY_PHRASING[cell.category];
127
- const linkedUntouchedShare = cell.threadLinked > 0 ? cell.untouched / cell.threadLinked : 0;
128
- const linkedActedShare = cell.threadLinked > 0 ? cell.actedOn / cell.threadLinked : 0;
129
- if (cell.threadLinked >= payload.floors.minCellThreads &&
130
- linkedUntouchedShare >= SUPPRESS_MIN_UNTOUCHED_SHARE &&
131
- // The same veto + merged-past gate as SUPPRESS_PATH: an acted-on high-severity finding
132
- // in the category is proof of value, and open-PR silence alone is not final.
133
- cell.actedOnHigh <= SUPPRESS_MAX_ACTED_HIGH &&
134
- cell.mergedUntouched >= SUPPRESS_MIN_MERGED_UNTOUCHED) {
135
- push({
136
- kind: 'SUPPRESS_CATEGORY',
137
- botUserId: bot.botUserId,
138
- botKey: bot.key,
139
- botLabel: bot.label,
140
- botKind: bot.kind,
141
- target: { kind: 'category', category: cell.category },
142
- targetKey: cell.category,
143
- dedupeKey: dedupeKeyFor('SUPPRESS_CATEGORY', bot.botUserId, cell.category, win.kind),
144
- rationale: `${Math.round(linkedUntouchedShare * 100)}% of ${bot.label}'s ${phrasing.label.toLowerCase()} findings went untouched (${cell.threadLinked} thread-linked of ${cell.findings}; ${cell.mergedUntouched} shipped in PRs that merged anyway) — suppress this category.`,
145
- evidence: evidenceFromCategoryCell(cell, bot, win),
146
- retro: retroFor('SUPPRESS_CATEGORY', { categoryCell: cell }),
147
- });
148
- }
149
- else if (cell.threadLinked >= ESCALATE_MIN_THREAD_LINKED &&
150
- linkedActedShare >= ESCALATE_MIN_ACTED_SHARE) {
151
- push({
152
- kind: 'ESCALATE',
153
- botUserId: bot.botUserId,
154
- botKey: bot.key,
155
- botLabel: bot.label,
156
- botKind: bot.kind,
157
- target: { kind: 'category', category: cell.category },
158
- targetKey: cell.category,
159
- dedupeKey: dedupeKeyFor('ESCALATE', bot.botUserId, cell.category, win.kind),
160
- rationale: `${Math.round(linkedActedShare * 100)}% of ${bot.label}'s ${phrasing.label.toLowerCase()} findings were acted on — ask the bot to rank this category higher.`,
161
- evidence: evidenceFromCategoryCell(cell, bot, win),
162
- retro: retroFor('ESCALATE', { categoryCell: cell }),
163
- });
164
- }
165
- }
166
- // ── Bot totals → LOWER_VERBOSITY / BOOTSTRAP_CONFIG ───────────────────────────────────
167
- for (const bot of payload.bots) {
168
- if (bot.isQualityCheck)
169
- continue;
170
- if (bot.mlFindings >= payload.floors.minCellFindings &&
171
- bot.mlNits / bot.mlFindings >= LOWER_VERBOSITY_MIN_NIT_SHARE) {
172
- push({
173
- kind: 'LOWER_VERBOSITY',
174
- botUserId: bot.botUserId,
175
- botKey: bot.key,
176
- botLabel: bot.label,
177
- botKind: bot.kind,
178
- target: { kind: 'severity' },
179
- targetKey: 'nit',
180
- dedupeKey: dedupeKeyFor('LOWER_VERBOSITY', bot.botUserId, 'nit', win.kind),
181
- rationale: `${Math.round((bot.mlNits / bot.mlFindings) * 100)}% of ${bot.label}'s ${bot.mlFindings} scored findings are nits — raise its severity floor.`,
182
- evidence: evidenceFromBotTotals(bot, win),
183
- retro: retroFor('LOWER_VERBOSITY', { bot }),
184
- });
185
- }
186
- if (!opts.botsWithConfigTarget.has(bot.botUserId) &&
187
- bot.threads >= BOOTSTRAP_MIN_THREADS) {
188
- push({
189
- kind: 'BOOTSTRAP_CONFIG',
190
- botUserId: bot.botUserId,
191
- botKey: bot.key,
192
- botLabel: bot.label,
193
- botKind: bot.kind,
194
- target: { kind: 'bootstrap' },
195
- targetKey: 'config',
196
- dedupeKey: dedupeKeyFor('BOOTSTRAP_CONFIG', bot.botUserId, 'config', win.kind),
197
- rationale: `${bot.label} produced ${bot.threads} threads this window with no known configuration — bootstrap a config file so it becomes tunable.`,
198
- evidence: evidenceFromBotTotals(bot, win),
199
- retro: retroFor('BOOTSTRAP_CONFIG', { bot }),
200
- });
201
- }
202
- }
203
- // ── Overlap cells → SCOPE_OFF ─────────────────────────────────────────────────────────
204
- // One intent per bot (its heaviest partner), not one per pair direction — the reader
205
- // decides which of the two to narrow, and both bots may earn one (symmetric, like the
206
- // free overlap suggestion).
207
- const bestPartner = new Map();
208
- for (const cell of payload.overlapCells) {
209
- const cur = bestPartner.get(cell.botUserId);
210
- if (!cur ||
211
- cell.sharedClusters > cur.sharedClusters ||
212
- (cell.sharedClusters === cur.sharedClusters && cell.partnerUserId < cur.partnerUserId))
213
- bestPartner.set(cell.botUserId, cell);
214
- }
215
- for (const cell of bestPartner.values()) {
216
- const bot = botOf(cell.botUserId);
217
- if (!bot)
218
- continue;
219
- const share = cell.threads > 0 ? cell.overlapThreads / cell.threads : 0;
220
- if (share < SCOPE_OFF_MIN_OVERLAP_SHARE)
221
- continue;
222
- const partner = botById.get(cell.partnerUserId);
223
- const partnerLabel = partner?.label ?? `#${cell.partnerUserId}`;
224
- push({
225
- kind: 'SCOPE_OFF',
226
- botUserId: bot.botUserId,
227
- botKey: bot.key,
228
- botLabel: bot.label,
229
- botKind: bot.kind,
230
- target: { kind: 'partner', partnerUserId: cell.partnerUserId, partnerLabel },
231
- targetKey: `u${cell.partnerUserId}`,
232
- dedupeKey: dedupeKeyFor('SCOPE_OFF', bot.botUserId, `u${cell.partnerUserId}`, win.kind),
233
- rationale: `${cell.overlapThreads} of ${bot.label}'s ${cell.threads} threads land on lines other bots also flagged (most often ${partnerLabel}: ${cell.sharedClusters} shared cluster${cell.sharedClusters === 1 ? '' : 's'}) — narrow one of the two.`,
234
- evidence: evidenceFromOverlapCell(cell, bot, win),
235
- retro: retroFor('SCOPE_OFF', {}),
236
- });
237
- }
238
- // ── T4 lint templates → PROMOTE_TO_LINT (DeepSource-only in v1; advisory text) ────────
239
- for (const stat of opts.lintTemplates ?? []) {
240
- if (stat.fires < PROMOTE_TO_LINT_MIN_FIRES)
241
- continue;
242
- const bot = botById.get(stat.botUserId);
243
- if (!bot)
244
- continue;
245
- const actedPct = stat.fires > 0 ? Math.round((stat.actedOn / stat.fires) * 100) : 0;
246
- push({
247
- kind: 'PROMOTE_TO_LINT',
248
- botUserId: bot.botUserId,
249
- botKey: bot.key,
250
- botLabel: bot.label,
251
- botKind: bot.kind,
252
- target: { kind: 'lint', template: stat.template },
253
- targetKey: stat.template,
254
- dedupeKey: dedupeKeyFor('PROMOTE_TO_LINT', bot.botUserId, stat.template, win.kind),
255
- rationale: `"${stat.template}" fired ${stat.fires}× (${actedPct}% acted on) — a deterministic lint rule would catch this earlier and cheaper than a review bot.`,
256
- evidence: evidenceFromBotTotals(bot, win),
257
- retro: retroFor('PROMOTE_TO_LINT', { bot }),
258
- });
259
- }
260
- // Deterministic order: worst evidence first within a bot, bots by thread volume (the
261
- // payload's own order), then intent kind for stability.
262
- const botOrder = new Map(payload.bots.map((b, i) => [b.botUserId, i]));
263
- out.sort((a, b) => (botOrder.get(a.botUserId) ?? 0) - (botOrder.get(b.botUserId) ?? 0) ||
264
- b.evidence.volume - a.evidence.volume ||
265
- a.kind.localeCompare(b.kind) ||
266
- a.targetKey.localeCompare(b.targetKey));
267
- return out;
268
- }
269
- //# sourceMappingURL=intents.js.map