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.
- package/dist/api/plugins/rate-limit.js +22 -0
- package/dist/api/routes/releases.js +181 -0
- package/dist/app.js +3 -0
- package/dist/coding/story-build/base-refs.js +10 -2
- package/dist/coding/story-build/loop/driver.js +1 -1
- package/dist/coding/story-build/loop/routes.js +9 -1
- package/dist/coding/story-build/persist.js +53 -7
- package/dist/coding/story-build/plan-persist.js +1 -1
- package/dist/coding/story-build/push-hotfix.js +293 -0
- package/dist/coding/story-build/push.js +5 -0
- package/dist/coding/story-build/routes-build.js +25 -3
- package/dist/db/erase-account.js +14 -0
- package/dist/db/hotfix-bases.js +31 -0
- package/dist/db/migrations/0195_releases.sql +128 -0
- package/dist/db/migrations/0196_story_build_hotfix.sql +54 -0
- package/dist/db/migrations/0197_release_items_live_tag.sql +11 -0
- package/dist/db/migrations/0198_story_build_port_report.sql +7 -0
- package/dist/db/migrations/meta/_journal.json +28 -0
- package/dist/db/queries.js +21 -0
- package/dist/db/release-check.js +378 -0
- package/dist/db/release-settings.js +94 -0
- package/dist/db/repo-tags.js +82 -0
- package/dist/db/schema.sqlite.js +246 -0
- package/dist/github/mutations.js +67 -0
- package/dist/github/queries.js +5 -0
- package/dist/github/settle-query.js +11 -1
- package/dist/github/tag-queries.js +101 -0
- package/dist/releases/create.js +539 -0
- package/dist/releases/github.js +78 -0
- package/dist/releases/notes.js +152 -0
- package/dist/releases/refresh.js +43 -0
- package/dist/sandbox/session/manager.js +207 -12
- package/dist/sandbox/session/plan.js +19 -7
- package/dist/sandbox/session/routes.js +12 -2
- package/dist/sandbox/workspace/hotfix.js +30 -0
- package/dist/sandbox/workspace/index.js +38 -2
- package/dist/sandbox/workspace/port-report.js +137 -0
- package/dist/sandbox/workspace/seed.js +40 -0
- package/dist/shared/hotfix.js +165 -0
- package/dist/shared/index.js +4 -0
- package/dist/shared/release-check.js +528 -0
- package/dist/shared/release-write.js +264 -0
- package/dist/shared/releases.js +89 -0
- package/dist/shared/story-build.js +2 -0
- package/dist/sync/repo-tags.js +368 -0
- package/dist/sync/settle-sweep.js +47 -2
- package/dist/sync/sync-repo.js +12 -0
- package/dist/sync/tag-ranges.js +350 -0
- package/dist/sync/tags-step.js +77 -0
- package/dist/sync/upsert.js +23 -0
- package/package.json +5 -2
- package/public/assets/{AiFixTab-CVIZl9I8.js → AiFixTab-BC3tDZ3z.js} +1 -1
- package/public/assets/{PrBotBehaviourTab-q0M7RU1e.js → PrBotBehaviourTab-C5j5uhLn.js} +1 -1
- package/public/assets/index-BrjUDY3j.js +138 -0
- package/public/assets/index-D1zhsqSF.css +1 -0
- package/public/index.html +2 -2
- package/dist/ai/check-tool.js +0 -6
- package/dist/api/routes/auth.js +0 -194
- package/dist/api/routes/billing.js +0 -209
- package/dist/api/routes/claude-review.js +0 -522
- package/dist/api/routes/contact.js +0 -318
- package/dist/api/routes/feed.js +0 -21
- package/dist/api/routes/inbox.js +0 -45
- package/dist/api/routes/mentions.js +0 -22
- package/dist/api/routes/teams.js +0 -150
- package/dist/api/routes/webhooks.js +0 -181
- package/dist/coding/activity.js +0 -80
- package/dist/coding/agent.js +0 -147
- package/dist/coding/ai-fix/checks-preview.js +0 -58
- package/dist/coding/ai-fix/comment-seed.js +0 -519
- package/dist/coding/ai-fix/prompts.js +0 -283
- package/dist/coding/ai-resolve/prompt.js +0 -161
- package/dist/coding/ai-resolve/routes.js +0 -97
- package/dist/coding/ai-resolve/run.js +0 -343
- package/dist/coding/ai-resolve/schema.js +0 -26
- package/dist/coding/ai-resolve/validate.js +0 -136
- package/dist/coding/checks/deny-scan.js +0 -75
- package/dist/coding/checks/discover.js +0 -541
- package/dist/coding/checks/docker.js +0 -38
- package/dist/coding/checks/env.js +0 -88
- package/dist/coding/checks/hooks.js +0 -91
- package/dist/coding/checks/index.js +0 -8
- package/dist/coding/checks/local.js +0 -40
- package/dist/coding/checks/sandbox.js +0 -206
- package/dist/coding/checks/session.js +0 -518
- package/dist/coding/checks/spawn.js +0 -123
- package/dist/coding/checks/spec.js +0 -140
- package/dist/coding/checks/types.js +0 -6
- package/dist/coding/partial-spend.js +0 -19
- package/dist/coding/safe-path.js +0 -75
- package/dist/coding/story-build/build-prompts.js +0 -178
- package/dist/coding/story-build/build.js +0 -332
- package/dist/coding/story-build/checkouts.js +0 -66
- package/dist/coding/story-build/docs-runner.js +0 -178
- package/dist/coding/story-build/docs.js +0 -334
- package/dist/coding/story-build/manager.js +0 -999
- package/dist/coding/story-build/plan-agent.js +0 -190
- package/dist/coding/story-build/plan.js +0 -338
- package/dist/coding/story-build/prompts.js +0 -147
- package/dist/coding/story-build/routes-docs.js +0 -113
- package/dist/coding/story-build/routes.js +0 -295
- package/dist/coding/story-build/schema.js +0 -53
- package/dist/db/automation-output.js +0 -349
- package/dist/db/credits.js +0 -79
- package/dist/db/schema.js +0 -321
- package/dist/db/schema.pg.js +0 -2197
- package/dist/db/sprint-repo-suggest.js +0 -257
- package/dist/db/sprint-stories.js +0 -42
- package/dist/db/sprint-story.js +0 -505
- package/dist/db/sprint-ticket-repos.js +0 -195
- package/dist/db/story-selection.js +0 -270
- package/dist/db/team-comparison.js +0 -124
- package/dist/db/workspace-comparison.js +0 -98
- package/dist/feed/fyi-provider.js +0 -10
- package/dist/github/local-user.js +0 -92
- package/dist/github/repo-files.js +0 -26
- package/dist/ml/worker.js +0 -31
- package/dist/platform/advisor/discovery/tier2.js +0 -88
- package/dist/platform/advisor/discovery/tier3.js +0 -57
- package/dist/platform/advisor/emitters/adapter.js +0 -5
- package/dist/platform/advisor/emitters/coderabbit.js +0 -175
- package/dist/platform/advisor/emitters/copilot-prose.js +0 -118
- package/dist/platform/advisor/emitters/greptile.js +0 -176
- package/dist/platform/advisor/emitters/lint.js +0 -37
- package/dist/platform/advisor/emitters/manifest.js +0 -239
- package/dist/platform/advisor/emitters/prose.js +0 -183
- package/dist/platform/advisor/emitters/registry.js +0 -20
- package/dist/platform/advisor/emitters/yaml-doc.js +0 -95
- package/dist/platform/advisor/evidence.js +0 -79
- package/dist/platform/advisor/findings.js +0 -15
- package/dist/platform/advisor/intents.js +0 -269
- package/dist/platform/advisor/measure/effect.js +0 -18
- package/dist/platform/advisor/outputs/brief.js +0 -68
- package/dist/platform/advisor/outputs/config-pr.js +0 -156
- package/dist/platform/advisor/refine/diff-guard.js +0 -38
- package/dist/platform/advisor/refine/refine.js +0 -35
- package/dist/platform/advisor/retro-check.js +0 -98
- package/dist/platform/advisor/routes.js +0 -648
- package/dist/platform/advisor/store.js +0 -321
- package/dist/platform/advisor/taxonomy.js +0 -67
- package/dist/platform/annotations/prompts.js +0 -84
- package/dist/platform/annotations/routes.js +0 -86
- package/dist/platform/annotations/runner.js +0 -234
- package/dist/platform/annotations/targets.js +0 -203
- package/dist/platform/insights/grounded-figures.js +0 -162
- package/dist/platform/insights/period-questions.js +0 -235
- package/dist/platform/work-plan/hash.js +0 -99
- package/dist/platform/work-plan/prompt.js +0 -209
- package/dist/platform/work-plan/routes.js +0 -350
- package/dist/pro/bind.js +0 -309
- package/dist/pro/contract.js +0 -36
- package/dist/pro/event-hooks.js +0 -37
- package/dist/pro/migrate.js +0 -64
- package/dist/review/events.js +0 -24
- package/dist/review/local-settings.js +0 -78
- package/dist/review/memory/capture.js +0 -0
- package/dist/review/memory/retrieval.js +0 -272
- package/dist/review/memory/routes.js +0 -78
- package/dist/review/persist.js +0 -203
- package/dist/review/plugin-providers.js +0 -12
- package/dist/review/prompt.js +0 -264
- package/dist/review/review-manager.js +0 -259
- package/dist/review/routing.js +0 -164
- package/dist/review/sprint-assess/agent.js +0 -145
- package/dist/review/sprint-assess/manager.js +0 -382
- package/dist/review/sprint-assess/prompts.js +0 -99
- package/dist/review/sprint-assess/routes.js +0 -109
- package/dist/review/sprint-assess/schema.js +0 -70
- package/dist/review/sprint-assess/validate.js +0 -180
- package/dist/review/sprint-story/manager.js +0 -98
- package/dist/review/sprint-story/prompt.js +0 -45
- package/dist/review/sprint-story/validate.js +0 -127
- package/dist/review/story-docs/build-prompts.js +0 -153
- package/dist/review/story-docs/build-validate.js +0 -162
- package/dist/review/story-report/agent.js +0 -203
- package/dist/review/story-report/fingerprint.js +0 -45
- package/dist/review/story-report/manager.js +0 -287
- package/dist/review/story-report/routes.js +0 -154
- package/dist/review/story-report/story-detail.js +0 -148
- package/dist/review/story-report/validate.js +0 -105
- package/dist/review/ticket-review/sweep-waits.js +0 -20
- package/dist/review/write-deny.js +0 -170
- package/dist/shared/conflict-ai.js +0 -24
- package/dist/sync/benchmark-rollup.js +0 -50
- package/public/assets/index-CbEfmqOD.css +0 -1
- 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
|