@oss-scout/core 0.5.0 → 0.7.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/dist/cli.bundle.cjs +40 -39
- package/dist/core/anti-llm-policy.d.ts +50 -0
- package/dist/core/anti-llm-policy.js +207 -0
- package/dist/core/issue-eligibility.d.ts +6 -2
- package/dist/core/issue-eligibility.js +61 -6
- package/dist/core/issue-vetting.d.ts +9 -0
- package/dist/core/issue-vetting.js +28 -0
- package/dist/core/repo-health.js +15 -6
- package/dist/core/schemas.d.ts +35 -0
- package/dist/core/schemas.js +21 -0
- package/dist/core/slm-triage.d.ts +72 -0
- package/dist/core/slm-triage.js +135 -0
- package/dist/core/types.d.ts +24 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Optional SLM (small language model) pre-triage pass for vetted issues
|
|
3
|
+
* (oss-autopilot#1122).
|
|
4
|
+
*
|
|
5
|
+
* When the user has an Ollama instance running locally and a model
|
|
6
|
+
* configured via `slmTriageModel`, vetting can call out to that model
|
|
7
|
+
* for a structured classification of each candidate issue. The result
|
|
8
|
+
* is surfaced on `IssueCandidate.slmTriage` so consumers (autopilot
|
|
9
|
+
* agents, dashboard, vet-list output) can show the call up-front and
|
|
10
|
+
* skip the cost of reading every issue body manually.
|
|
11
|
+
*
|
|
12
|
+
* Design highlights:
|
|
13
|
+
* - **Fail open.** Any failure (no model configured, Ollama down,
|
|
14
|
+
* timeout, malformed JSON, schema mismatch) returns `null`. Triage
|
|
15
|
+
* must never block the rest of the vetting pipeline.
|
|
16
|
+
* - **Schema-enforced JSON.** Uses Ollama's `format` parameter so the
|
|
17
|
+
* decoder produces JSON conformant to a fixed schema; eliminates the
|
|
18
|
+
* "model returned partial JSON" failure mode that plagues prompt-only
|
|
19
|
+
* structured-output schemes.
|
|
20
|
+
* - **15s timeout.** Local SLMs vary widely in latency; 15s covers
|
|
21
|
+
* small-to-mid models on consumer hardware (Gemma 4 e4b, Qwen 3 4b).
|
|
22
|
+
* Slower models simply produce `null` and don't block vetting.
|
|
23
|
+
*/
|
|
24
|
+
import type { TrackedIssue, LinkedPR } from "./schemas.js";
|
|
25
|
+
/**
|
|
26
|
+
* Result of an SLM triage call. The same shape Ollama is constrained to
|
|
27
|
+
* produce via `format` schema enforcement.
|
|
28
|
+
*/
|
|
29
|
+
export interface SLMTriageResult {
|
|
30
|
+
/** Three buckets that match human triage decisions. */
|
|
31
|
+
decision: "pursue" | "investigate" | "skip";
|
|
32
|
+
/** How sure the model is. Surface in UI; don't gate on it server-side. */
|
|
33
|
+
confidence: "high" | "medium" | "low";
|
|
34
|
+
/** Short phrases (not sentences) explaining the decision. 1–3 entries. */
|
|
35
|
+
reasons: string[];
|
|
36
|
+
/** Model id that produced this result. Useful when comparing runs. */
|
|
37
|
+
modelVersion: string;
|
|
38
|
+
}
|
|
39
|
+
/** Inputs to a triage call. */
|
|
40
|
+
export interface SLMTriageInput {
|
|
41
|
+
issue: Pick<TrackedIssue, "title" | "labels"> & {
|
|
42
|
+
body?: string;
|
|
43
|
+
};
|
|
44
|
+
linkedPRExists: boolean;
|
|
45
|
+
}
|
|
46
|
+
/** Runtime options for `triageWithSLM`. */
|
|
47
|
+
export interface SLMTriageOptions {
|
|
48
|
+
/** Model id (e.g. `gemma4:e4b`). Empty/unset disables triage. */
|
|
49
|
+
model: string;
|
|
50
|
+
/** Override Ollama base URL. Defaults to `http://127.0.0.1:11434`. */
|
|
51
|
+
host?: string;
|
|
52
|
+
/** Override request timeout. */
|
|
53
|
+
timeoutMs?: number;
|
|
54
|
+
/** Override fetch implementation (for tests). */
|
|
55
|
+
fetchImpl?: typeof fetch;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Run an SLM triage classification. Returns `null` on any failure path
|
|
59
|
+
* — caller treats `null` as "no SLM signal available".
|
|
60
|
+
*/
|
|
61
|
+
export declare function triageWithSLM(input: SLMTriageInput, options: SLMTriageOptions): Promise<SLMTriageResult | null>;
|
|
62
|
+
/**
|
|
63
|
+
* Adapter: build an `SLMTriageInput` from the standard scout types we
|
|
64
|
+
* carry through `vetIssue`. Centralizes the mapping so callers don't
|
|
65
|
+
* have to know about the prompt internals.
|
|
66
|
+
*/
|
|
67
|
+
export declare function buildTriageInput(args: {
|
|
68
|
+
issue: TrackedIssue & {
|
|
69
|
+
body?: string;
|
|
70
|
+
};
|
|
71
|
+
linkedPR: LinkedPR | null | undefined;
|
|
72
|
+
}): SLMTriageInput;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/** Default Ollama HTTP endpoint when not overridden. */
|
|
2
|
+
const DEFAULT_OLLAMA_HOST = "http://127.0.0.1:11434";
|
|
3
|
+
/** Default per-call timeout. */
|
|
4
|
+
const DEFAULT_TIMEOUT_MS = 15_000;
|
|
5
|
+
/** Hard cap on issue body length we send to the model. */
|
|
6
|
+
const MAX_BODY_CHARS = 2000;
|
|
7
|
+
/** JSON schema enforced server-side by Ollama's `format` parameter. */
|
|
8
|
+
const SLM_TRIAGE_SCHEMA = {
|
|
9
|
+
type: "object",
|
|
10
|
+
properties: {
|
|
11
|
+
decision: { type: "string", enum: ["pursue", "investigate", "skip"] },
|
|
12
|
+
confidence: { type: "string", enum: ["high", "medium", "low"] },
|
|
13
|
+
reasons: {
|
|
14
|
+
type: "array",
|
|
15
|
+
items: { type: "string" },
|
|
16
|
+
minItems: 1,
|
|
17
|
+
maxItems: 3,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
required: ["decision", "confidence", "reasons"],
|
|
21
|
+
};
|
|
22
|
+
/** Build the user-message prompt from an issue. */
|
|
23
|
+
function buildPrompt(input) {
|
|
24
|
+
const { issue, linkedPRExists } = input;
|
|
25
|
+
const body = (issue.body ?? "").slice(0, MAX_BODY_CHARS);
|
|
26
|
+
return [
|
|
27
|
+
"You triage open-source issues for an autonomous contribution agent.",
|
|
28
|
+
"Classify the issue into exactly one bucket:",
|
|
29
|
+
"- pursue: small, concrete bug or feature with clear acceptance; safe for an autonomous agent to attempt without further design input",
|
|
30
|
+
"- investigate: tractable but needs human reading first (ambiguous scope, design questions, recently-touched files)",
|
|
31
|
+
"- skip: not actionable autonomously (epic, creative, blocked, requires upstream change, requires infra)",
|
|
32
|
+
"",
|
|
33
|
+
"Return JSON only matching the provided schema. Reasons must be short phrases, not sentences. 1-3 reasons total.",
|
|
34
|
+
"",
|
|
35
|
+
"Issue:",
|
|
36
|
+
`Title: ${issue.title}`,
|
|
37
|
+
`Body: ${body}`,
|
|
38
|
+
`Labels: ${issue.labels.join(", ")}`,
|
|
39
|
+
`Linked PR exists: ${linkedPRExists}`,
|
|
40
|
+
].join("\n");
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Run an SLM triage classification. Returns `null` on any failure path
|
|
44
|
+
* — caller treats `null` as "no SLM signal available".
|
|
45
|
+
*/
|
|
46
|
+
export async function triageWithSLM(input, options) {
|
|
47
|
+
if (!options.model)
|
|
48
|
+
return null;
|
|
49
|
+
const host = options.host ?? DEFAULT_OLLAMA_HOST;
|
|
50
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
51
|
+
const fetchFn = options.fetchImpl ?? fetch;
|
|
52
|
+
let response;
|
|
53
|
+
try {
|
|
54
|
+
response = await fetchFn(`${host}/api/chat`, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: { "Content-Type": "application/json" },
|
|
57
|
+
body: JSON.stringify({
|
|
58
|
+
model: options.model,
|
|
59
|
+
messages: [{ role: "user", content: buildPrompt(input) }],
|
|
60
|
+
stream: false,
|
|
61
|
+
format: SLM_TRIAGE_SCHEMA,
|
|
62
|
+
options: { temperature: 0.1 },
|
|
63
|
+
}),
|
|
64
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
// Connection refused, timeout, DNS error, etc.
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
if (!response.ok)
|
|
72
|
+
return null;
|
|
73
|
+
let payload;
|
|
74
|
+
try {
|
|
75
|
+
payload = await response.json();
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
// Ollama `chat` returns { message: { content: string }, ... }.
|
|
81
|
+
const content = payload?.message
|
|
82
|
+
?.content;
|
|
83
|
+
if (typeof content !== "string")
|
|
84
|
+
return null;
|
|
85
|
+
let parsed;
|
|
86
|
+
try {
|
|
87
|
+
parsed = JSON.parse(content);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
if (!isValidTriageShape(parsed))
|
|
93
|
+
return null;
|
|
94
|
+
return {
|
|
95
|
+
decision: parsed.decision,
|
|
96
|
+
confidence: parsed.confidence,
|
|
97
|
+
reasons: parsed.reasons,
|
|
98
|
+
modelVersion: options.model,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Adapter: build an `SLMTriageInput` from the standard scout types we
|
|
103
|
+
* carry through `vetIssue`. Centralizes the mapping so callers don't
|
|
104
|
+
* have to know about the prompt internals.
|
|
105
|
+
*/
|
|
106
|
+
export function buildTriageInput(args) {
|
|
107
|
+
return {
|
|
108
|
+
issue: {
|
|
109
|
+
title: args.issue.title,
|
|
110
|
+
labels: args.issue.labels,
|
|
111
|
+
body: args.issue.body,
|
|
112
|
+
},
|
|
113
|
+
linkedPRExists: !!args.linkedPR,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
function isValidTriageShape(value) {
|
|
117
|
+
if (typeof value !== "object" || value === null)
|
|
118
|
+
return false;
|
|
119
|
+
const v = value;
|
|
120
|
+
if (v.decision !== "pursue" &&
|
|
121
|
+
v.decision !== "investigate" &&
|
|
122
|
+
v.decision !== "skip")
|
|
123
|
+
return false;
|
|
124
|
+
if (v.confidence !== "high" &&
|
|
125
|
+
v.confidence !== "medium" &&
|
|
126
|
+
v.confidence !== "low")
|
|
127
|
+
return false;
|
|
128
|
+
if (!Array.isArray(v.reasons) ||
|
|
129
|
+
v.reasons.length === 0 ||
|
|
130
|
+
v.reasons.length > 3)
|
|
131
|
+
return false;
|
|
132
|
+
if (!v.reasons.every((r) => typeof r === "string"))
|
|
133
|
+
return false;
|
|
134
|
+
return true;
|
|
135
|
+
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Core types for oss-scout — ephemeral types that are never persisted.
|
|
3
3
|
*/
|
|
4
4
|
import type { RepoSignals, TrackedIssue, IssueVettingResult, IssueScope, ScoutState, SearchStrategy } from "./schemas.js";
|
|
5
|
-
export type { ProjectCategory, IssueScope, RepoSignals, RepoScore, StoredMergedPR, StoredClosedPR, ContributionGuidelines, IssueVettingResult, TrackedIssue, ScoutPreferences, SavedCandidate, ScoutState, SearchStrategy, } from "./schemas.js";
|
|
5
|
+
export type { ProjectCategory, IssueScope, RepoSignals, RepoScore, StoredMergedPR, StoredClosedPR, ContributionGuidelines, IssueVettingResult, LinkedPR, TrackedIssue, ScoutPreferences, SavedCandidate, ScoutState, SearchStrategy, } from "./schemas.js";
|
|
6
6
|
/** Health snapshot of a GitHub repository. */
|
|
7
7
|
export interface ProjectHealth {
|
|
8
8
|
repo: string;
|
|
@@ -20,11 +20,34 @@ export interface ProjectHealth {
|
|
|
20
20
|
}
|
|
21
21
|
/** Priority tier for issue search results. */
|
|
22
22
|
export type SearchPriority = "merged_pr" | "starred" | "normal";
|
|
23
|
+
/** Source file the anti-LLM policy match came from, or null when no file matched. */
|
|
24
|
+
export type AntiLLMPolicySourceFile = "CONTRIBUTING.md" | "CODE_OF_CONDUCT.md" | "README.md";
|
|
25
|
+
/** Result of scanning a repo's policy docs for anti-LLM/AI keywords. */
|
|
26
|
+
export interface AntiLLMPolicyResult {
|
|
27
|
+
matched: boolean;
|
|
28
|
+
matchedKeywords: string[];
|
|
29
|
+
sourceFile: AntiLLMPolicySourceFile | null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Optional SLM (small language model) pre-triage classification for an
|
|
33
|
+
* issue (oss-autopilot#1122). Populated when the user has configured
|
|
34
|
+
* `slmTriageModel` and a local Ollama instance is reachable. Always
|
|
35
|
+
* fail-open: any error path leaves this `null`.
|
|
36
|
+
*/
|
|
37
|
+
export interface SLMTriageSummary {
|
|
38
|
+
decision: "pursue" | "investigate" | "skip";
|
|
39
|
+
confidence: "high" | "medium" | "low";
|
|
40
|
+
reasons: string[];
|
|
41
|
+
modelVersion: string;
|
|
42
|
+
}
|
|
23
43
|
/** A fully vetted issue candidate with scoring. */
|
|
24
44
|
export interface IssueCandidate {
|
|
25
45
|
issue: TrackedIssue;
|
|
26
46
|
vettingResult: IssueVettingResult;
|
|
27
47
|
projectHealth: ProjectHealth;
|
|
48
|
+
antiLLMPolicy: AntiLLMPolicyResult;
|
|
49
|
+
/** SLM pre-triage result, or `null` when not configured / unavailable. */
|
|
50
|
+
slmTriage: SLMTriageSummary | null;
|
|
28
51
|
recommendation: "approve" | "skip" | "needs_review";
|
|
29
52
|
reasonsToSkip: string[];
|
|
30
53
|
reasonsToApprove: string[];
|
package/dist/index.d.ts
CHANGED
|
@@ -15,9 +15,10 @@
|
|
|
15
15
|
* @packageDocumentation
|
|
16
16
|
*/
|
|
17
17
|
export { createScout, OssScout } from "./scout.js";
|
|
18
|
-
export type { ScoutConfig, SearchOptions, SearchResult, IssueCandidate, MergedPRRecord, ClosedPRRecord, OpenPRRecord, RepoScoreUpdate, ProjectHealth, SearchPriority, CheckResult, VetListOptions, VetListResult, VetListEntry, VetListSummary, } from "./core/types.js";
|
|
19
|
-
export type { ScoutState, ScoutPreferences, RepoScore, RepoSignals, IssueVettingResult, ContributionGuidelines, TrackedIssue, IssueScope, ProjectCategory, StoredMergedPR, StoredClosedPR, StoredOpenPR, SearchStrategy, SkippedIssue, } from "./core/schemas.js";
|
|
18
|
+
export type { ScoutConfig, SearchOptions, SearchResult, IssueCandidate, MergedPRRecord, ClosedPRRecord, OpenPRRecord, RepoScoreUpdate, ProjectHealth, SearchPriority, CheckResult, AntiLLMPolicyResult, AntiLLMPolicySourceFile, VetListOptions, VetListResult, VetListEntry, VetListSummary, } from "./core/types.js";
|
|
19
|
+
export type { ScoutState, ScoutPreferences, RepoScore, RepoSignals, IssueVettingResult, LinkedPR, ContributionGuidelines, TrackedIssue, IssueScope, ProjectCategory, StoredMergedPR, StoredClosedPR, StoredOpenPR, SearchStrategy, SkippedIssue, } from "./core/schemas.js";
|
|
20
20
|
export { ScoutStateSchema, ScoutPreferencesSchema, RepoScoreSchema, IssueScopeSchema, ProjectCategorySchema, SearchStrategySchema, SkippedIssueSchema, } from "./core/schemas.js";
|
|
21
21
|
export { requireGitHubToken, getGitHubToken } from "./core/utils.js";
|
|
22
22
|
export { IssueDiscovery } from "./core/issue-discovery.js";
|
|
23
23
|
export { IssueVetter, type ScoutStateReader } from "./core/issue-vetting.js";
|
|
24
|
+
export { scanForAntiLLMPolicy, ANTI_LLM_KEYWORDS, } from "./core/anti-llm-policy.js";
|
package/dist/index.js
CHANGED
|
@@ -23,3 +23,4 @@ export { requireGitHubToken, getGitHubToken } from "./core/utils.js";
|
|
|
23
23
|
// Internal classes (for advanced use)
|
|
24
24
|
export { IssueDiscovery } from "./core/issue-discovery.js";
|
|
25
25
|
export { IssueVetter } from "./core/issue-vetting.js";
|
|
26
|
+
export { scanForAntiLLMPolicy, ANTI_LLM_KEYWORDS, } from "./core/anti-llm-policy.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oss-scout/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Personalized GitHub issue finder with multi-strategy search, deep vetting, and viability scoring — CLI, library, MCP server, and Claude Code plugin",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|