@oss-autopilot/core 3.8.0 → 3.10.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.
@@ -21,6 +21,8 @@ export { executeDailyCheck } from './daily.js';
21
21
  export { runStartup } from './startup.js';
22
22
  /** Return contribution statistics (merge rate, PR counts, repo breakdown) from local state. */
23
23
  export { runStatus } from './status.js';
24
+ /** On-demand strategy snapshot via the typed `computeStrategy` core function (#1243 step 4). */
25
+ export { runStrategy } from './strategy.js';
24
26
  /** Search GitHub for contributable issues using multi-strategy discovery. */
25
27
  export { runSearch, MAX_SEARCH_RESULTS } from './search.js';
26
28
  /** Surface feature-scoped opportunities in repos with 3+ merged PRs (scout 0.9.0). */
@@ -22,6 +22,8 @@ export { executeDailyCheck } from './daily.js';
22
22
  export { runStartup } from './startup.js';
23
23
  /** Return contribution statistics (merge rate, PR counts, repo breakdown) from local state. */
24
24
  export { runStatus } from './status.js';
25
+ /** On-demand strategy snapshot via the typed `computeStrategy` core function (#1243 step 4). */
26
+ export { runStrategy } from './strategy.js';
25
27
  /** Search GitHub for contributable issues using multi-strategy discovery. */
26
28
  export { runSearch, MAX_SEARCH_RESULTS } from './search.js';
27
29
  /** Surface feature-scoped opportunities in repos with 3+ merged PRs (scout 0.9.0). */
@@ -5,7 +5,8 @@
5
5
  import { buildCandidateLinkedPR, createAutopilotScout } from './scout-bridge.js';
6
6
  import { getStateManager } from '../core/index.js';
7
7
  import { gradeFromCandidate } from '../core/issue-grading.js';
8
- import { warn } from '../core/logger.js';
8
+ import { computeStrategy } from '../core/strategy.js';
9
+ import { debug, warn } from '../core/logger.js';
9
10
  const MODULE = 'search';
10
11
  /**
11
12
  * Hard cap on issue-search result count. Shared between CLI (`cli-registry.ts`),
@@ -46,8 +47,23 @@ function sanitizeViabilityScore(raw) {
46
47
  }
47
48
  export async function runSearch(options) {
48
49
  const scout = await createAutopilotScout();
49
- const result = await scout.search({ maxResults: options.maxResults });
50
50
  const stateManager = getStateManager();
51
+ // Derive personalization from local history (#1244). `computeStrategy`
52
+ // returns null below the merged-PR floor — in that case we pass nothing
53
+ // and scout's sort behaves exactly as before. Once strategy data is
54
+ // available, scout boosts language/repo matches into a separate sort
55
+ // tier (still no filtering).
56
+ const strategy = computeStrategy(stateManager.getState());
57
+ const preferLanguages = strategy?.recommendations.languages ?? undefined;
58
+ const preferRepos = strategy?.recommendations.repos ?? undefined;
59
+ if (preferLanguages?.length || preferRepos?.length) {
60
+ debug(MODULE, `Applying strategy bias to search: preferLanguages=${JSON.stringify(preferLanguages ?? [])}, preferRepos=${JSON.stringify(preferRepos ?? [])}`);
61
+ }
62
+ const result = await scout.search({
63
+ maxResults: options.maxResults,
64
+ preferLanguages,
65
+ preferRepos,
66
+ });
51
67
  const searchOutput = {
52
68
  candidates: result.candidates.map((c) => {
53
69
  const repoScoreRecord = stateManager.getRepoScore(c.issue.repo);
@@ -97,6 +113,8 @@ export async function runSearch(options) {
97
113
  }
98
114
  : undefined,
99
115
  ...(linkedPR ? { linkedPR } : {}),
116
+ ...(typeof c.boostScore === 'number' ? { boostScore: c.boostScore } : {}),
117
+ ...(c.boostReasons && c.boostReasons.length > 0 ? { boostReasons: c.boostReasons } : {}),
100
118
  };
101
119
  }),
102
120
  excludedRepos: result.excludedRepos,
@@ -0,0 +1,31 @@
1
+ /**
2
+ * strategy command (#1243 step 4).
3
+ *
4
+ * Runs the typed `computeStrategy` core function against local state on
5
+ * demand. The daily auto-display already emits `strategySummary` under
6
+ * a cadence gate; this command is the *ungated* path used by the
7
+ * `contribution-strategist` agent so a user asking "how am I doing?"
8
+ * always gets an answer (or a clear "not enough data yet" message) even
9
+ * when the daily cadence has not fired.
10
+ *
11
+ * Read-only: no API calls, no state mutation.
12
+ */
13
+ import { type StrategyResult } from '../core/strategy.js';
14
+ /**
15
+ * On-demand strategy snapshot. Mirrors `StrategyOutputSchema` in
16
+ * `formatters/json.ts` structurally — that schema uses `.passthrough()`
17
+ * for forward compatibility on the JSON-validation surface, so its
18
+ * inferred type carries an extra `unknown` index signature that does
19
+ * not match `StrategyResult`. The command's TypeScript shape is the
20
+ * exact structural form below.
21
+ */
22
+ export interface StrategyCommandOutput {
23
+ strategy: StrategyResult | null;
24
+ message?: string;
25
+ }
26
+ /**
27
+ * Compute the on-demand strategy snapshot. Returns `{ strategy: null,
28
+ * message }` when state is below the merged-PR floor, and `{ strategy }`
29
+ * with the full typed result otherwise.
30
+ */
31
+ export declare function runStrategy(): Promise<StrategyCommandOutput>;
@@ -0,0 +1,33 @@
1
+ /**
2
+ * strategy command (#1243 step 4).
3
+ *
4
+ * Runs the typed `computeStrategy` core function against local state on
5
+ * demand. The daily auto-display already emits `strategySummary` under
6
+ * a cadence gate; this command is the *ungated* path used by the
7
+ * `contribution-strategist` agent so a user asking "how am I doing?"
8
+ * always gets an answer (or a clear "not enough data yet" message) even
9
+ * when the daily cadence has not fired.
10
+ *
11
+ * Read-only: no API calls, no state mutation.
12
+ */
13
+ import { getStateManager } from '../core/index.js';
14
+ import { computeStrategy, STRATEGY_MIN_PRS } from '../core/strategy.js';
15
+ /**
16
+ * Compute the on-demand strategy snapshot. Returns `{ strategy: null,
17
+ * message }` when state is below the merged-PR floor, and `{ strategy }`
18
+ * with the full typed result otherwise.
19
+ */
20
+ export async function runStrategy() {
21
+ const stateManager = getStateManager();
22
+ const state = stateManager.getState();
23
+ const strategy = computeStrategy(state);
24
+ if (strategy === null) {
25
+ const merged = state.mergedPRs?.length ?? 0;
26
+ return {
27
+ strategy: null,
28
+ message: `Strategy snapshot needs at least ${STRATEGY_MIN_PRS} merged PRs in local state; ` +
29
+ `currently tracking ${merged}. Keep contributing and the snapshot becomes available automatically.`,
30
+ };
31
+ }
32
+ return { strategy };
33
+ }
@@ -184,6 +184,106 @@ export declare const StatusOutputSchema: z.ZodObject<{
184
184
  }, z.core.$strip>>>;
185
185
  }, z.core.$strip>;
186
186
  export type StatusOutput = z.infer<typeof StatusOutputSchema>;
187
+ export declare const StrategyResultSchema: z.ZodObject<{
188
+ profile: z.ZodObject<{
189
+ style: z.ZodEnum<{
190
+ maintainer: "maintainer";
191
+ explorer: "explorer";
192
+ specialist: "specialist";
193
+ generalist: "generalist";
194
+ }>;
195
+ totalPRs: z.ZodNumber;
196
+ mergedCount: z.ZodNumber;
197
+ mergeRate: z.ZodNumber;
198
+ primaryLanguages: z.ZodArray<z.ZodString>;
199
+ favoriteRepos: z.ZodArray<z.ZodString>;
200
+ }, z.core.$loose>;
201
+ capacity: z.ZodObject<{
202
+ openPRCount: z.ZodNumber;
203
+ dormantPRCount: z.ZodNumber;
204
+ dormantRepoCount: z.ZodNumber;
205
+ overExtended: z.ZodBoolean;
206
+ suggestedAction: z.ZodUnion<readonly [z.ZodLiteral<"open_more">, z.ZodLiteral<"follow_up_dormant">, z.ZodLiteral<"wait_on_maintainers">, z.ZodNull]>;
207
+ }, z.core.$loose>;
208
+ patterns: z.ZodObject<{
209
+ prTypeDistribution: z.ZodObject<{
210
+ docs: z.ZodNumber;
211
+ fixes: z.ZodNumber;
212
+ features: z.ZodNumber;
213
+ refactors: z.ZodNumber;
214
+ tests: z.ZodNumber;
215
+ other: z.ZodNumber;
216
+ }, z.core.$loose>;
217
+ trajectoryDirection: z.ZodEnum<{
218
+ growing: "growing";
219
+ steady: "steady";
220
+ declining: "declining";
221
+ }>;
222
+ averagePRSize: z.ZodNumber;
223
+ }, z.core.$loose>;
224
+ recommendations: z.ZodObject<{
225
+ languages: z.ZodArray<z.ZodString>;
226
+ repos: z.ZodArray<z.ZodString>;
227
+ issueTypes: z.ZodArray<z.ZodString>;
228
+ avoidPatterns: z.ZodArray<z.ZodString>;
229
+ }, z.core.$loose>;
230
+ }, z.core.$loose>;
231
+ /**
232
+ * On-demand strategy snapshot output (#1243 step 4). Wraps the same
233
+ * `StrategyResult` shape consumed by the daily auto-display, but is
234
+ * always available — the cadence gate only governs the auto-display,
235
+ * not direct CLI/MCP calls. `strategy` is null when the minimum-data
236
+ * gate fails (fewer than `STRATEGY_MIN_PRS` merged PRs); `message`
237
+ * carries the human-readable explanation in that case.
238
+ */
239
+ export declare const StrategyOutputSchema: z.ZodObject<{
240
+ strategy: z.ZodNullable<z.ZodObject<{
241
+ profile: z.ZodObject<{
242
+ style: z.ZodEnum<{
243
+ maintainer: "maintainer";
244
+ explorer: "explorer";
245
+ specialist: "specialist";
246
+ generalist: "generalist";
247
+ }>;
248
+ totalPRs: z.ZodNumber;
249
+ mergedCount: z.ZodNumber;
250
+ mergeRate: z.ZodNumber;
251
+ primaryLanguages: z.ZodArray<z.ZodString>;
252
+ favoriteRepos: z.ZodArray<z.ZodString>;
253
+ }, z.core.$loose>;
254
+ capacity: z.ZodObject<{
255
+ openPRCount: z.ZodNumber;
256
+ dormantPRCount: z.ZodNumber;
257
+ dormantRepoCount: z.ZodNumber;
258
+ overExtended: z.ZodBoolean;
259
+ suggestedAction: z.ZodUnion<readonly [z.ZodLiteral<"open_more">, z.ZodLiteral<"follow_up_dormant">, z.ZodLiteral<"wait_on_maintainers">, z.ZodNull]>;
260
+ }, z.core.$loose>;
261
+ patterns: z.ZodObject<{
262
+ prTypeDistribution: z.ZodObject<{
263
+ docs: z.ZodNumber;
264
+ fixes: z.ZodNumber;
265
+ features: z.ZodNumber;
266
+ refactors: z.ZodNumber;
267
+ tests: z.ZodNumber;
268
+ other: z.ZodNumber;
269
+ }, z.core.$loose>;
270
+ trajectoryDirection: z.ZodEnum<{
271
+ growing: "growing";
272
+ steady: "steady";
273
+ declining: "declining";
274
+ }>;
275
+ averagePRSize: z.ZodNumber;
276
+ }, z.core.$loose>;
277
+ recommendations: z.ZodObject<{
278
+ languages: z.ZodArray<z.ZodString>;
279
+ repos: z.ZodArray<z.ZodString>;
280
+ issueTypes: z.ZodArray<z.ZodString>;
281
+ avoidPatterns: z.ZodArray<z.ZodString>;
282
+ }, z.core.$loose>;
283
+ }, z.core.$loose>>;
284
+ message: z.ZodOptional<z.ZodString>;
285
+ }, z.core.$strip>;
286
+ export type StrategyCommandOutput = z.infer<typeof StrategyOutputSchema>;
187
287
  export declare const DailyOutputSchema: z.ZodObject<{
188
288
  digest: z.ZodObject<{
189
289
  generatedAt: z.ZodString;
@@ -520,6 +620,8 @@ export declare const SearchOutputSchema: z.ZodObject<{
520
620
  updatedAt: z.ZodOptional<z.ZodString>;
521
621
  isStalled: z.ZodBoolean;
522
622
  }, z.core.$strip>>;
623
+ boostScore: z.ZodOptional<z.ZodNumber>;
624
+ boostReasons: z.ZodOptional<z.ZodArray<z.ZodString>>;
523
625
  }, z.core.$strip>>;
524
626
  excludedRepos: z.ZodArray<z.ZodString>;
525
627
  aiPolicyBlocklist: z.ZodArray<z.ZodString>;
@@ -576,6 +678,8 @@ export declare const FeaturesOutputSchema: z.ZodObject<{
576
678
  updatedAt: z.ZodOptional<z.ZodString>;
577
679
  isStalled: z.ZodBoolean;
578
680
  }, z.core.$strip>>;
681
+ boostScore: z.ZodOptional<z.ZodNumber>;
682
+ boostReasons: z.ZodOptional<z.ZodArray<z.ZodString>>;
579
683
  horizon: z.ZodEnum<{
580
684
  "quick-win": "quick-win";
581
685
  "bigger-bet": "bigger-bet";
@@ -631,6 +735,8 @@ export declare const FeaturesOutputSchema: z.ZodObject<{
631
735
  updatedAt: z.ZodOptional<z.ZodString>;
632
736
  isStalled: z.ZodBoolean;
633
737
  }, z.core.$strip>>;
738
+ boostScore: z.ZodOptional<z.ZodNumber>;
739
+ boostReasons: z.ZodOptional<z.ZodArray<z.ZodString>>;
634
740
  horizon: z.ZodEnum<{
635
741
  "quick-win": "quick-win";
636
742
  "bigger-bet": "bigger-bet";
@@ -202,7 +202,7 @@ const CompactRepoGroupSchema = z.object({
202
202
  // Mirrors {@link StrategyResult} in core/strategy.ts. Kept passthrough on the
203
203
  // inner objects so additive shape changes there don't break Zod validation
204
204
  // before the schema catches up — drift on required keys still fails.
205
- const StrategyResultSchema = z
205
+ export const StrategyResultSchema = z
206
206
  .object({
207
207
  profile: z
208
208
  .object({
@@ -258,6 +258,18 @@ const StrategyResultSchema = z
258
258
  .passthrough(),
259
259
  })
260
260
  .passthrough();
261
+ /**
262
+ * On-demand strategy snapshot output (#1243 step 4). Wraps the same
263
+ * `StrategyResult` shape consumed by the daily auto-display, but is
264
+ * always available — the cadence gate only governs the auto-display,
265
+ * not direct CLI/MCP calls. `strategy` is null when the minimum-data
266
+ * gate fails (fewer than `STRATEGY_MIN_PRS` merged PRs); `message`
267
+ * carries the human-readable explanation in that case.
268
+ */
269
+ export const StrategyOutputSchema = z.object({
270
+ strategy: StrategyResultSchema.nullable(),
271
+ message: z.string().optional(),
272
+ });
261
273
  export const DailyOutputSchema = z.object({
262
274
  digest: DailyDigestCompactSchema,
263
275
  capacity: CapacityAssessmentSchema,
@@ -323,6 +335,14 @@ const SearchCandidateSchema = z.object({
323
335
  })
324
336
  .optional(),
325
337
  linkedPR: CandidateLinkedPRSchema.optional(),
338
+ /**
339
+ * Personalization sort-tier signal threaded up from oss-scout (#1244).
340
+ * Present only when local strategy data biased the search and this
341
+ * candidate matched. `boostReasons` is the human-readable explanation
342
+ * (e.g. `"repo affinity: vercel/next.js"`, `"language match: TypeScript"`).
343
+ */
344
+ boostScore: z.number().optional(),
345
+ boostReasons: z.array(z.string()).optional(),
326
346
  });
327
347
  export const SearchOutputSchema = z.object({
328
348
  candidates: z.array(SearchCandidateSchema),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oss-autopilot/core",
3
- "version": "3.8.0",
3
+ "version": "3.10.0",
4
4
  "description": "CLI and core library for managing open source contributions",
5
5
  "type": "module",
6
6
  "bin": {
@@ -54,18 +54,18 @@
54
54
  "dependencies": {
55
55
  "@octokit/plugin-throttling": "^11.0.3",
56
56
  "@octokit/rest": "^22.0.1",
57
- "@oss-scout/core": "^0.9.0",
57
+ "@oss-scout/core": "^0.10.0",
58
58
  "commander": "^14.0.3",
59
59
  "zod": "^4.4.3"
60
60
  },
61
61
  "devDependencies": {
62
- "@types/node": "^25.6.0",
63
- "@vitest/coverage-v8": "^4.1.5",
62
+ "@types/node": "^25.7.0",
63
+ "@vitest/coverage-v8": "^4.1.6",
64
64
  "esbuild": "^0.28.0",
65
65
  "tsx": "^4.21.0",
66
66
  "typedoc": "^0.28.19",
67
67
  "typescript": "^5.9.3",
68
- "vitest": "^4.1.5"
68
+ "vitest": "^4.1.6"
69
69
  },
70
70
  "scripts": {
71
71
  "build": "tsc",