@oss-autopilot/core 3.4.1 → 3.5.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-registry.js +50 -0
- package/dist/cli.bundle.cjs +81 -78
- package/dist/commands/compliance-score.d.ts +21 -0
- package/dist/commands/compliance-score.js +156 -0
- package/dist/commands/index.d.ts +4 -0
- package/dist/commands/index.js +4 -0
- package/dist/commands/list-mark-done.d.ts +48 -0
- package/dist/commands/list-mark-done.js +213 -0
- package/dist/commands/parse-list.js +86 -9
- package/dist/commands/repo-vet.d.ts +21 -0
- package/dist/commands/repo-vet.js +215 -0
- package/dist/commands/startup.js +18 -0
- package/dist/core/ci-enforced-tools.d.ts +35 -0
- package/dist/core/ci-enforced-tools.js +109 -0
- package/dist/core/comment-decision.d.ts +72 -0
- package/dist/core/comment-decision.js +74 -0
- package/dist/core/compliance-score.d.ts +127 -0
- package/dist/core/compliance-score.js +277 -0
- package/dist/core/config-registry.js +12 -0
- package/dist/core/contributing.d.ts +52 -0
- package/dist/core/contributing.js +139 -0
- package/dist/core/extraction-categories.d.ts +55 -0
- package/dist/core/extraction-categories.js +108 -0
- package/dist/core/follow-up-history.d.ts +41 -0
- package/dist/core/follow-up-history.js +71 -0
- package/dist/core/gist-state-store.d.ts +30 -7
- package/dist/core/gist-state-store.js +87 -11
- package/dist/core/issue-conversation.js +1 -0
- package/dist/core/issue-effort.d.ts +29 -0
- package/dist/core/issue-effort.js +41 -0
- package/dist/core/maintainer-hints.d.ts +23 -0
- package/dist/core/maintainer-hints.js +36 -0
- package/dist/core/pr-quality-rubric.d.ts +70 -0
- package/dist/core/pr-quality-rubric.js +121 -0
- package/dist/core/repo-vet.d.ts +90 -0
- package/dist/core/repo-vet.js +178 -0
- package/dist/core/state-schema.d.ts +76 -0
- package/dist/core/state-schema.js +75 -0
- package/dist/core/strategy.d.ts +75 -0
- package/dist/core/strategy.js +226 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/core/workflow-state.d.ts +56 -0
- package/dist/core/workflow-state.js +101 -0
- package/dist/formatters/json.d.ts +147 -0
- package/dist/formatters/json.js +79 -0
- package/package.json +1 -1
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repo health scoring (#1242).
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `agents/repo-evaluator.md`'s in-prompt assembly logic
|
|
5
|
+
* so the rubric weights, sub-factor thresholds, and verdict cutoffs
|
|
6
|
+
* are deterministic, unit-testable, and tunable without editing
|
|
7
|
+
* markdown. Same architectural shape as success-grade (#858),
|
|
8
|
+
* compliance-score (#1245), and strategy (#1243).
|
|
9
|
+
*
|
|
10
|
+
* The function is pure — callers (the MCP tool, the CLI command)
|
|
11
|
+
* supply pre-fetched repo signals so the score is reproducible
|
|
12
|
+
* against fixture data and offline replays.
|
|
13
|
+
*
|
|
14
|
+
* Rubric reference: docs/repo-rubric.md.
|
|
15
|
+
*/
|
|
16
|
+
export interface RepoVetInput {
|
|
17
|
+
/** Star count from the GitHub repo metadata. */
|
|
18
|
+
stars: number;
|
|
19
|
+
forks: number;
|
|
20
|
+
openIssues: number;
|
|
21
|
+
watchers: number;
|
|
22
|
+
isArchived: boolean;
|
|
23
|
+
/** ISO-8601 timestamp of the last push. */
|
|
24
|
+
lastPushed: string;
|
|
25
|
+
/** ISO-8601 timestamp of repo creation. */
|
|
26
|
+
createdAt: string;
|
|
27
|
+
/** Number of commits to the default branch in the last 30 days. */
|
|
28
|
+
commitsLast30Days: number;
|
|
29
|
+
/** Per-PR `createdAt → mergedAt` durations in days, for the last 90
|
|
30
|
+
* days of merged PRs. Used to derive avg / median merge time. */
|
|
31
|
+
prMergeTimesDays: number[];
|
|
32
|
+
/** Count of merges in the last 90 days. */
|
|
33
|
+
mergedCount90Days: number;
|
|
34
|
+
/** Count of opened PRs in the last 90 days. Used for merge rate. */
|
|
35
|
+
openedCount90Days: number;
|
|
36
|
+
/** ISO-8601 timestamp of the most recent commit on the default branch. */
|
|
37
|
+
lastCommitISO: string | null;
|
|
38
|
+
/** Distinct authors who committed in the last 90 days. */
|
|
39
|
+
contributorsLast90d: number;
|
|
40
|
+
/** ISO-8601 timestamp of the most recent published release. */
|
|
41
|
+
lastReleaseISO: string | null;
|
|
42
|
+
hasContributing: boolean;
|
|
43
|
+
hasIssueTemplates: boolean;
|
|
44
|
+
hasPRTemplate: boolean;
|
|
45
|
+
hasCodeOfConduct: boolean;
|
|
46
|
+
}
|
|
47
|
+
export type RepoVetVerdict = 'recommended' | 'proceed_with_caution' | 'avoid';
|
|
48
|
+
export interface RepoVetResult {
|
|
49
|
+
repo: {
|
|
50
|
+
stars: number;
|
|
51
|
+
forks: number;
|
|
52
|
+
openIssues: number;
|
|
53
|
+
watchers: number;
|
|
54
|
+
isArchived: boolean;
|
|
55
|
+
lastPushed: string;
|
|
56
|
+
createdAt: string;
|
|
57
|
+
};
|
|
58
|
+
prMergeTime: {
|
|
59
|
+
avgDays: number | null;
|
|
60
|
+
medianDays: number | null;
|
|
61
|
+
sampleSize: number;
|
|
62
|
+
sourceWindowDays: 90;
|
|
63
|
+
};
|
|
64
|
+
mergeRate: {
|
|
65
|
+
merged: number;
|
|
66
|
+
opened: number;
|
|
67
|
+
percent: number | null;
|
|
68
|
+
windowDays: 90;
|
|
69
|
+
};
|
|
70
|
+
maintainerActivity: {
|
|
71
|
+
lastCommitISO: string | null;
|
|
72
|
+
contributorsLast90d: number;
|
|
73
|
+
lastReleaseISO: string | null;
|
|
74
|
+
};
|
|
75
|
+
communityHealth: {
|
|
76
|
+
contributing: boolean;
|
|
77
|
+
issueTemplates: boolean;
|
|
78
|
+
prTemplate: boolean;
|
|
79
|
+
codeOfConduct: boolean;
|
|
80
|
+
};
|
|
81
|
+
/** Weighted 1-10 score per docs/repo-rubric.md. */
|
|
82
|
+
rubricScore: number;
|
|
83
|
+
/** Top-line verdict derived from the score and red-flag overrides. */
|
|
84
|
+
rubricVerdict: RepoVetVerdict;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Compute repo-health metrics and an overall rubric score from
|
|
88
|
+
* pre-fetched signals (#1242). Pure function — no I/O, no global state.
|
|
89
|
+
*/
|
|
90
|
+
export declare function computeRepoVet(input: RepoVetInput): RepoVetResult;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repo health scoring (#1242).
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `agents/repo-evaluator.md`'s in-prompt assembly logic
|
|
5
|
+
* so the rubric weights, sub-factor thresholds, and verdict cutoffs
|
|
6
|
+
* are deterministic, unit-testable, and tunable without editing
|
|
7
|
+
* markdown. Same architectural shape as success-grade (#858),
|
|
8
|
+
* compliance-score (#1245), and strategy (#1243).
|
|
9
|
+
*
|
|
10
|
+
* The function is pure — callers (the MCP tool, the CLI command)
|
|
11
|
+
* supply pre-fetched repo signals so the score is reproducible
|
|
12
|
+
* against fixture data and offline replays.
|
|
13
|
+
*
|
|
14
|
+
* Rubric reference: docs/repo-rubric.md.
|
|
15
|
+
*/
|
|
16
|
+
const WEIGHTS = {
|
|
17
|
+
activity: 0.25,
|
|
18
|
+
prSpeed: 0.25,
|
|
19
|
+
mergeRate: 0.2,
|
|
20
|
+
guidelines: 0.1,
|
|
21
|
+
stability: 0.05,
|
|
22
|
+
/** Responsiveness (15%) is documented in the rubric but the input
|
|
23
|
+
* shape doesn't surface it — `gh pr list --json` doesn't expose
|
|
24
|
+
* "time to first review". Per the rubric note, omit it rather than
|
|
25
|
+
* fabricate it. The remaining weights total 0.85 and are normalized
|
|
26
|
+
* to a 10-point ceiling at the end. */
|
|
27
|
+
};
|
|
28
|
+
const SUM_OF_AVAILABLE_WEIGHTS = WEIGHTS.activity + WEIGHTS.prSpeed + WEIGHTS.mergeRate + WEIGHTS.guidelines + WEIGHTS.stability;
|
|
29
|
+
const VERDICT_CUTOFFS = {
|
|
30
|
+
recommended: 7.5,
|
|
31
|
+
cautious: 5,
|
|
32
|
+
};
|
|
33
|
+
function median(nums) {
|
|
34
|
+
if (nums.length === 0)
|
|
35
|
+
return 0;
|
|
36
|
+
const sorted = [...nums].sort((a, b) => a - b);
|
|
37
|
+
const mid = Math.floor(sorted.length / 2);
|
|
38
|
+
return sorted.length % 2 === 0 ? (sorted[mid - 1] + sorted[mid]) / 2 : sorted[mid];
|
|
39
|
+
}
|
|
40
|
+
function activitySubScore(input) {
|
|
41
|
+
// 1.0 when commits/30d ≥ 30 (one per day), 0 when 0, linear in between.
|
|
42
|
+
if (input.isArchived)
|
|
43
|
+
return 0;
|
|
44
|
+
return Math.min(1, input.commitsLast30Days / 30);
|
|
45
|
+
}
|
|
46
|
+
function prSpeedSubScore(input) {
|
|
47
|
+
// Rubric: avg PR merge time < 7 days = healthy. Linear: 0d → 1.0,
|
|
48
|
+
// 14d+ → 0.0. Sample of zero merges = 0 score (no signal of speed).
|
|
49
|
+
if (input.prMergeTimesDays.length === 0)
|
|
50
|
+
return 0;
|
|
51
|
+
const avg = input.prMergeTimesDays.reduce((sum, d) => sum + d, 0) / input.prMergeTimesDays.length;
|
|
52
|
+
if (avg <= 0)
|
|
53
|
+
return 1;
|
|
54
|
+
if (avg >= 14)
|
|
55
|
+
return 0;
|
|
56
|
+
return Math.max(0, Math.min(1, (14 - avg) / 14));
|
|
57
|
+
}
|
|
58
|
+
function mergeRateSubScore(input) {
|
|
59
|
+
// Rubric: >70% merged in last 90 days. Linear: 100% → 1.0, 0% → 0.
|
|
60
|
+
if (input.openedCount90Days === 0)
|
|
61
|
+
return 0;
|
|
62
|
+
const rate = input.mergedCount90Days / input.openedCount90Days;
|
|
63
|
+
return Math.max(0, Math.min(1, rate));
|
|
64
|
+
}
|
|
65
|
+
function guidelinesSubScore(input) {
|
|
66
|
+
// Each of the four community-health flags contributes equally.
|
|
67
|
+
let score = 0;
|
|
68
|
+
if (input.hasContributing)
|
|
69
|
+
score += 0.4;
|
|
70
|
+
if (input.hasIssueTemplates)
|
|
71
|
+
score += 0.25;
|
|
72
|
+
if (input.hasPRTemplate)
|
|
73
|
+
score += 0.25;
|
|
74
|
+
if (input.hasCodeOfConduct)
|
|
75
|
+
score += 0.1;
|
|
76
|
+
return Math.min(1, score);
|
|
77
|
+
}
|
|
78
|
+
function stabilitySubScore(input) {
|
|
79
|
+
// Rubric: not archived, regular releases. Half-credit for not-archived,
|
|
80
|
+
// half for a release within the last 6 months.
|
|
81
|
+
if (input.isArchived)
|
|
82
|
+
return 0;
|
|
83
|
+
let score = 0.5;
|
|
84
|
+
if (input.lastReleaseISO) {
|
|
85
|
+
const since = Date.now() - new Date(input.lastReleaseISO).getTime();
|
|
86
|
+
const sixMonths = 1000 * 60 * 60 * 24 * 30 * 6;
|
|
87
|
+
if (since <= sixMonths)
|
|
88
|
+
score += 0.5;
|
|
89
|
+
}
|
|
90
|
+
return score;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Hard red-flag overrides — when one fires, verdict drops to `avoid`
|
|
94
|
+
* regardless of the weighted score. Sourced verbatim from the rubric.
|
|
95
|
+
*/
|
|
96
|
+
function hasRedFlags(input) {
|
|
97
|
+
if (input.isArchived)
|
|
98
|
+
return true;
|
|
99
|
+
if (input.lastCommitISO) {
|
|
100
|
+
const sinceCommit = Date.now() - new Date(input.lastCommitISO).getTime();
|
|
101
|
+
const sixtyDays = 60 * 24 * 60 * 60 * 1000;
|
|
102
|
+
if (sinceCommit > sixtyDays)
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
if (input.openedCount90Days > 0 && input.mergedCount90Days === 0)
|
|
106
|
+
return true;
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
function deriveVerdict(score, redFlags) {
|
|
110
|
+
if (redFlags)
|
|
111
|
+
return 'avoid';
|
|
112
|
+
if (score >= VERDICT_CUTOFFS.recommended)
|
|
113
|
+
return 'recommended';
|
|
114
|
+
if (score >= VERDICT_CUTOFFS.cautious)
|
|
115
|
+
return 'proceed_with_caution';
|
|
116
|
+
return 'avoid';
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Compute repo-health metrics and an overall rubric score from
|
|
120
|
+
* pre-fetched signals (#1242). Pure function — no I/O, no global state.
|
|
121
|
+
*/
|
|
122
|
+
export function computeRepoVet(input) {
|
|
123
|
+
const subScores = {
|
|
124
|
+
activity: activitySubScore(input),
|
|
125
|
+
prSpeed: prSpeedSubScore(input),
|
|
126
|
+
mergeRate: mergeRateSubScore(input),
|
|
127
|
+
guidelines: guidelinesSubScore(input),
|
|
128
|
+
stability: stabilitySubScore(input),
|
|
129
|
+
};
|
|
130
|
+
// Weighted average normalized to the 10-point ceiling.
|
|
131
|
+
const weightedSum = subScores.activity * WEIGHTS.activity +
|
|
132
|
+
subScores.prSpeed * WEIGHTS.prSpeed +
|
|
133
|
+
subScores.mergeRate * WEIGHTS.mergeRate +
|
|
134
|
+
subScores.guidelines * WEIGHTS.guidelines +
|
|
135
|
+
subScores.stability * WEIGHTS.stability;
|
|
136
|
+
const rubricScore = Math.round((weightedSum / SUM_OF_AVAILABLE_WEIGHTS) * 100) / 10;
|
|
137
|
+
const sampleSize = input.prMergeTimesDays.length;
|
|
138
|
+
const avgDays = sampleSize === 0 ? null : input.prMergeTimesDays.reduce((s, d) => s + d, 0) / sampleSize;
|
|
139
|
+
const medianDays = sampleSize === 0 ? null : median(input.prMergeTimesDays);
|
|
140
|
+
// Cap at 100. A repo clearing a backlog can have mergedCount > openedCount
|
|
141
|
+
// because PRs opened before the window can merge inside it — without the
|
|
142
|
+
// cap, the surfaced text would read "Merge rate (90d): 160% (8/5)" which
|
|
143
|
+
// looks like a tool bug. The score itself is fine (mergeRateSubScore
|
|
144
|
+
// clamps separately); this just keeps the display sensible.
|
|
145
|
+
const mergeRatePercent = input.openedCount90Days === 0 ? null : Math.min(100, (input.mergedCount90Days / input.openedCount90Days) * 100);
|
|
146
|
+
const verdict = deriveVerdict(rubricScore, hasRedFlags(input));
|
|
147
|
+
return {
|
|
148
|
+
repo: {
|
|
149
|
+
stars: input.stars,
|
|
150
|
+
forks: input.forks,
|
|
151
|
+
openIssues: input.openIssues,
|
|
152
|
+
watchers: input.watchers,
|
|
153
|
+
isArchived: input.isArchived,
|
|
154
|
+
lastPushed: input.lastPushed,
|
|
155
|
+
createdAt: input.createdAt,
|
|
156
|
+
},
|
|
157
|
+
prMergeTime: { avgDays, medianDays, sampleSize, sourceWindowDays: 90 },
|
|
158
|
+
mergeRate: {
|
|
159
|
+
merged: input.mergedCount90Days,
|
|
160
|
+
opened: input.openedCount90Days,
|
|
161
|
+
percent: mergeRatePercent,
|
|
162
|
+
windowDays: 90,
|
|
163
|
+
},
|
|
164
|
+
maintainerActivity: {
|
|
165
|
+
lastCommitISO: input.lastCommitISO,
|
|
166
|
+
contributorsLast90d: input.contributorsLast90d,
|
|
167
|
+
lastReleaseISO: input.lastReleaseISO,
|
|
168
|
+
},
|
|
169
|
+
communityHealth: {
|
|
170
|
+
contributing: input.hasContributing,
|
|
171
|
+
issueTemplates: input.hasIssueTemplates,
|
|
172
|
+
prTemplate: input.hasPRTemplate,
|
|
173
|
+
codeOfConduct: input.hasCodeOfConduct,
|
|
174
|
+
},
|
|
175
|
+
rubricScore,
|
|
176
|
+
rubricVerdict: verdict,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
@@ -78,6 +78,53 @@ export declare const AnalyzedIssueConversationSchema: z.ZodObject<{
|
|
|
78
78
|
repo: z.ZodString;
|
|
79
79
|
analyzedAt: z.ZodString;
|
|
80
80
|
}, z.core.$strip>;
|
|
81
|
+
/**
|
|
82
|
+
* One entry in a PR's follow-up history (#1277). Tier matches the
|
|
83
|
+
* cadence labels in `skills/pr-etiquette/SKILL.md` (light_check_in
|
|
84
|
+
* for 7-13 days, direct_check_in for 14-29 days, final_check_in for
|
|
85
|
+
* 30+ days). The `draftPath` is optional so an entry recorded from a
|
|
86
|
+
* direct `gh pr comment` (no draft) still validates.
|
|
87
|
+
*/
|
|
88
|
+
export declare const FollowUpEntrySchema: z.ZodObject<{
|
|
89
|
+
tier: z.ZodEnum<{
|
|
90
|
+
light_check_in: "light_check_in";
|
|
91
|
+
direct_check_in: "direct_check_in";
|
|
92
|
+
final_check_in: "final_check_in";
|
|
93
|
+
}>;
|
|
94
|
+
timestamp: z.ZodString;
|
|
95
|
+
draftPath: z.ZodOptional<z.ZodString>;
|
|
96
|
+
}, z.core.$strip>;
|
|
97
|
+
export type FollowUpEntry = z.infer<typeof FollowUpEntrySchema>;
|
|
98
|
+
/**
|
|
99
|
+
* Pause-point snapshot for resumable workflows (#1280). When the user
|
|
100
|
+
* picks "Done for now" inside `draft-first-workflow.md`,
|
|
101
|
+
* `work-through-issues.md`, or `pre-commit-review.md`, the workflow
|
|
102
|
+
* records its position here. The next `/oss` run consults this state
|
|
103
|
+
* and offers "Resume / Restart / Discard" instead of restarting from
|
|
104
|
+
* the beginning.
|
|
105
|
+
*
|
|
106
|
+
* `stepData` is intentionally typed as `Record<string, unknown>` so
|
|
107
|
+
* each workflow can persist whatever per-step context it needs to
|
|
108
|
+
* resume cleanly (compliance-gap skip list, last review pass count,
|
|
109
|
+
* etc.) without forcing a tagged-union schema in shared state.
|
|
110
|
+
*/
|
|
111
|
+
export declare const WorkflowStateSchema: z.ZodObject<{
|
|
112
|
+
workflowName: z.ZodEnum<{
|
|
113
|
+
"draft-first": "draft-first";
|
|
114
|
+
"work-through-issues": "work-through-issues";
|
|
115
|
+
"pre-commit-review": "pre-commit-review";
|
|
116
|
+
}>;
|
|
117
|
+
currentStep: z.ZodString;
|
|
118
|
+
branchName: z.ZodOptional<z.ZodString>;
|
|
119
|
+
issueContext: z.ZodOptional<z.ZodObject<{
|
|
120
|
+
title: z.ZodString;
|
|
121
|
+
url: z.ZodString;
|
|
122
|
+
}, z.core.$strip>>;
|
|
123
|
+
completedSteps: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
124
|
+
stepData: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
125
|
+
lastUpdatedAt: z.ZodString;
|
|
126
|
+
}, z.core.$strip>;
|
|
127
|
+
export type WorkflowState = z.infer<typeof WorkflowStateSchema>;
|
|
81
128
|
export declare const ContributionGuidelinesSchema: z.ZodObject<{
|
|
82
129
|
branchNamingConvention: z.ZodOptional<z.ZodString>;
|
|
83
130
|
commitMessageFormat: z.ZodOptional<z.ZodString>;
|
|
@@ -243,6 +290,8 @@ export declare const AgentConfigSchema: z.ZodObject<{
|
|
|
243
290
|
}>>;
|
|
244
291
|
diffToolCustomCommand: z.ZodOptional<z.ZodString>;
|
|
245
292
|
autoFormatBeforePush: z.ZodDefault<z.ZodBoolean>;
|
|
293
|
+
healthCheckFreshnessMinutes: z.ZodDefault<z.ZodNumber>;
|
|
294
|
+
reviewMaxPasses: z.ZodOptional<z.ZodNumber>;
|
|
246
295
|
slmTriageModel: z.ZodDefault<z.ZodString>;
|
|
247
296
|
slmTriageHost: z.ZodDefault<z.ZodString>;
|
|
248
297
|
}, z.core.$strip>;
|
|
@@ -403,6 +452,8 @@ export declare const AgentStateSchema: z.ZodObject<{
|
|
|
403
452
|
}>>;
|
|
404
453
|
diffToolCustomCommand: z.ZodOptional<z.ZodString>;
|
|
405
454
|
autoFormatBeforePush: z.ZodDefault<z.ZodBoolean>;
|
|
455
|
+
healthCheckFreshnessMinutes: z.ZodDefault<z.ZodNumber>;
|
|
456
|
+
reviewMaxPasses: z.ZodOptional<z.ZodNumber>;
|
|
406
457
|
slmTriageModel: z.ZodDefault<z.ZodString>;
|
|
407
458
|
slmTriageHost: z.ZodDefault<z.ZodString>;
|
|
408
459
|
}, z.core.$strip>>;
|
|
@@ -532,6 +583,31 @@ export declare const AgentStateSchema: z.ZodObject<{
|
|
|
532
583
|
notes: z.ZodArray<z.ZodString>;
|
|
533
584
|
}, z.core.$strip>>;
|
|
534
585
|
}, z.core.$strip>>>;
|
|
586
|
+
prFollowUpHistory: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodObject<{
|
|
587
|
+
tier: z.ZodEnum<{
|
|
588
|
+
light_check_in: "light_check_in";
|
|
589
|
+
direct_check_in: "direct_check_in";
|
|
590
|
+
final_check_in: "final_check_in";
|
|
591
|
+
}>;
|
|
592
|
+
timestamp: z.ZodString;
|
|
593
|
+
draftPath: z.ZodOptional<z.ZodString>;
|
|
594
|
+
}, z.core.$strip>>>>;
|
|
595
|
+
workflowState: z.ZodOptional<z.ZodObject<{
|
|
596
|
+
workflowName: z.ZodEnum<{
|
|
597
|
+
"draft-first": "draft-first";
|
|
598
|
+
"work-through-issues": "work-through-issues";
|
|
599
|
+
"pre-commit-review": "pre-commit-review";
|
|
600
|
+
}>;
|
|
601
|
+
currentStep: z.ZodString;
|
|
602
|
+
branchName: z.ZodOptional<z.ZodString>;
|
|
603
|
+
issueContext: z.ZodOptional<z.ZodObject<{
|
|
604
|
+
title: z.ZodString;
|
|
605
|
+
url: z.ZodString;
|
|
606
|
+
}, z.core.$strip>>;
|
|
607
|
+
completedSteps: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
608
|
+
stepData: z.ZodDefault<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
609
|
+
lastUpdatedAt: z.ZodString;
|
|
610
|
+
}, z.core.$strip>>;
|
|
535
611
|
}, z.core.$strip>;
|
|
536
612
|
export type IssueStatus = z.infer<typeof IssueStatusSchema>;
|
|
537
613
|
export type FetchedPRStatus = z.infer<typeof FetchedPRStatusSchema>;
|
|
@@ -67,6 +67,45 @@ export const AnalyzedIssueConversationSchema = z.object({
|
|
|
67
67
|
repo: z.string(),
|
|
68
68
|
analyzedAt: z.string(),
|
|
69
69
|
});
|
|
70
|
+
/**
|
|
71
|
+
* One entry in a PR's follow-up history (#1277). Tier matches the
|
|
72
|
+
* cadence labels in `skills/pr-etiquette/SKILL.md` (light_check_in
|
|
73
|
+
* for 7-13 days, direct_check_in for 14-29 days, final_check_in for
|
|
74
|
+
* 30+ days). The `draftPath` is optional so an entry recorded from a
|
|
75
|
+
* direct `gh pr comment` (no draft) still validates.
|
|
76
|
+
*/
|
|
77
|
+
export const FollowUpEntrySchema = z.object({
|
|
78
|
+
tier: z.enum(['light_check_in', 'direct_check_in', 'final_check_in']),
|
|
79
|
+
timestamp: z.string().datetime(),
|
|
80
|
+
draftPath: z.string().optional(),
|
|
81
|
+
});
|
|
82
|
+
/**
|
|
83
|
+
* Pause-point snapshot for resumable workflows (#1280). When the user
|
|
84
|
+
* picks "Done for now" inside `draft-first-workflow.md`,
|
|
85
|
+
* `work-through-issues.md`, or `pre-commit-review.md`, the workflow
|
|
86
|
+
* records its position here. The next `/oss` run consults this state
|
|
87
|
+
* and offers "Resume / Restart / Discard" instead of restarting from
|
|
88
|
+
* the beginning.
|
|
89
|
+
*
|
|
90
|
+
* `stepData` is intentionally typed as `Record<string, unknown>` so
|
|
91
|
+
* each workflow can persist whatever per-step context it needs to
|
|
92
|
+
* resume cleanly (compliance-gap skip list, last review pass count,
|
|
93
|
+
* etc.) without forcing a tagged-union schema in shared state.
|
|
94
|
+
*/
|
|
95
|
+
export const WorkflowStateSchema = z.object({
|
|
96
|
+
workflowName: z.enum(['draft-first', 'work-through-issues', 'pre-commit-review']),
|
|
97
|
+
currentStep: z.string(),
|
|
98
|
+
branchName: z.string().optional(),
|
|
99
|
+
issueContext: z
|
|
100
|
+
.object({
|
|
101
|
+
title: z.string(),
|
|
102
|
+
url: z.string(),
|
|
103
|
+
})
|
|
104
|
+
.optional(),
|
|
105
|
+
completedSteps: z.array(z.string()).default([]),
|
|
106
|
+
stepData: z.record(z.string(), z.unknown()).default({}),
|
|
107
|
+
lastUpdatedAt: z.string().datetime(),
|
|
108
|
+
});
|
|
70
109
|
// ── 3. Contribution schemas ──────────────────────────────────────────
|
|
71
110
|
export const ContributionGuidelinesSchema = z.object({
|
|
72
111
|
branchNamingConvention: z.string().optional(),
|
|
@@ -161,6 +200,25 @@ export const AgentConfigSchema = z.object({
|
|
|
161
200
|
* the hook does nothing on every push unless the user explicitly enables it.
|
|
162
201
|
*/
|
|
163
202
|
autoFormatBeforePush: z.boolean().default(false),
|
|
203
|
+
/**
|
|
204
|
+
* Threshold (in minutes) for the SessionStart PR health one-liner (#1255).
|
|
205
|
+
* The cached digest only refreshes when the user runs `/oss`; SessionStart
|
|
206
|
+
* fires every session. Without a freshness gate the line drifts arbitrarily
|
|
207
|
+
* stale between runs. When the cache is older than this many minutes (and
|
|
208
|
+
* not yet 7 days old, which keeps the existing catch-up nudge), the line is
|
|
209
|
+
* suppressed entirely. Default 30 minutes.
|
|
210
|
+
*/
|
|
211
|
+
healthCheckFreshnessMinutes: z.number().int().positive().default(30),
|
|
212
|
+
/**
|
|
213
|
+
* Convergence cap for the multi-agent review loop in
|
|
214
|
+
* `workflows/dispatch-review.md` (#1275). When unset, the workflow
|
|
215
|
+
* falls back to per-mode defaults (5 for diff, 3 for plan). Lower
|
|
216
|
+
* values shorten the loop at the cost of skipping later iterations
|
|
217
|
+
* if findings persist; higher values give the loop more chances to
|
|
218
|
+
* converge before bailing. Optional — leave unset to use the
|
|
219
|
+
* defaults.
|
|
220
|
+
*/
|
|
221
|
+
reviewMaxPasses: z.number().int().positive().optional(),
|
|
164
222
|
/**
|
|
165
223
|
* Optional Ollama model for SLM pre-triage during issue vetting (#1122).
|
|
166
224
|
* Empty disables the feature. Recommended: `gemma4:e4b` (default for
|
|
@@ -236,4 +294,21 @@ export const AgentStateSchema = z.object({
|
|
|
236
294
|
closedPRs: z.array(StoredClosedPRSchema).optional(),
|
|
237
295
|
analyzedIssueConversations: z.array(AnalyzedIssueConversationSchema).optional(),
|
|
238
296
|
activeIssues: z.array(TrackedIssueSchema).default([]),
|
|
297
|
+
/**
|
|
298
|
+
* Per-PR follow-up history (#1277). Keyed by PR URL. Each entry
|
|
299
|
+
* records a tier-bucketed follow-up that the user drafted (and
|
|
300
|
+
* presumably posted via `draft-review-post`). The dormant-pr
|
|
301
|
+
* workflow reads this before drafting to enforce the
|
|
302
|
+
* one-follow-up-per-timeframe rule documented in
|
|
303
|
+
* `skills/pr-etiquette/SKILL.md`.
|
|
304
|
+
*/
|
|
305
|
+
prFollowUpHistory: z.record(z.string(), z.array(FollowUpEntrySchema)).optional(),
|
|
306
|
+
/**
|
|
307
|
+
* Pause-point snapshot for resumable workflows (#1280). Set when
|
|
308
|
+
* the user picks "Done for now" mid-workflow; cleared when the
|
|
309
|
+
* workflow completes or the user explicitly discards. The router
|
|
310
|
+
* reads this on every `/oss` invocation and offers Resume /
|
|
311
|
+
* Restart / Discard when present.
|
|
312
|
+
*/
|
|
313
|
+
workflowState: WorkflowStateSchema.optional(),
|
|
239
314
|
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contribution strategy compute (#1243).
|
|
3
|
+
*
|
|
4
|
+
* Extracts the deterministic compute layer from
|
|
5
|
+
* `agents/contribution-strategist.md` so the categorization,
|
|
6
|
+
* trajectory detection, and capacity overextension rules are typed,
|
|
7
|
+
* unit-testable, and consumable both by the agent (synthesis layer)
|
|
8
|
+
* and a future auto-display in `/oss`.
|
|
9
|
+
*
|
|
10
|
+
* Same architectural shape as success-grade (#858), linked-PR
|
|
11
|
+
* classifier (#910), and compliance-score (#1245). Pure function — no
|
|
12
|
+
* I/O, no global state, no LLM. The interpretive narrative ("you're
|
|
13
|
+
* doing X well, here's a growth opportunity") stays in the agent
|
|
14
|
+
* prompt.
|
|
15
|
+
*/
|
|
16
|
+
import type { AgentState } from './state-schema.js';
|
|
17
|
+
export type ContributorStyle = 'maintainer' | 'explorer' | 'specialist' | 'generalist';
|
|
18
|
+
export type TrajectoryDirection = 'growing' | 'steady' | 'declining';
|
|
19
|
+
export type CapacityAction = 'open_more' | 'follow_up_dormant' | 'wait_on_maintainers' | null;
|
|
20
|
+
export interface StrategyProfile {
|
|
21
|
+
style: ContributorStyle;
|
|
22
|
+
totalPRs: number;
|
|
23
|
+
mergedCount: number;
|
|
24
|
+
/** Merge rate as a 0-1 fraction. `0` when no PRs are tracked. */
|
|
25
|
+
mergeRate: number;
|
|
26
|
+
/** Top languages by repo PR count, descending. Empty when language data
|
|
27
|
+
* is not available in repoScores. */
|
|
28
|
+
primaryLanguages: string[];
|
|
29
|
+
/** Top repos by merged PR count, descending. */
|
|
30
|
+
favoriteRepos: string[];
|
|
31
|
+
}
|
|
32
|
+
export interface StrategyCapacity {
|
|
33
|
+
openPRCount: number;
|
|
34
|
+
dormantPRCount: number;
|
|
35
|
+
/** True when dormant PRs span >=2 distinct repos (a signal that the
|
|
36
|
+
* contributor is awaiting reviews from multiple maintainers, not just
|
|
37
|
+
* one slow project). */
|
|
38
|
+
overExtended: boolean;
|
|
39
|
+
/** The single highest-priority next action — null when state is too
|
|
40
|
+
* thin to recommend anything. */
|
|
41
|
+
suggestedAction: CapacityAction;
|
|
42
|
+
}
|
|
43
|
+
export interface StrategyPatterns {
|
|
44
|
+
prTypeDistribution: {
|
|
45
|
+
docs: number;
|
|
46
|
+
fixes: number;
|
|
47
|
+
features: number;
|
|
48
|
+
refactors: number;
|
|
49
|
+
tests: number;
|
|
50
|
+
other: number;
|
|
51
|
+
};
|
|
52
|
+
trajectoryDirection: TrajectoryDirection;
|
|
53
|
+
averagePRSize: number;
|
|
54
|
+
}
|
|
55
|
+
export interface StrategyRecommendations {
|
|
56
|
+
languages: string[];
|
|
57
|
+
repos: string[];
|
|
58
|
+
issueTypes: string[];
|
|
59
|
+
avoidPatterns: string[];
|
|
60
|
+
}
|
|
61
|
+
export interface StrategyResult {
|
|
62
|
+
profile: StrategyProfile;
|
|
63
|
+
capacity: StrategyCapacity;
|
|
64
|
+
patterns: StrategyPatterns;
|
|
65
|
+
recommendations: StrategyRecommendations;
|
|
66
|
+
}
|
|
67
|
+
/** Minimum tracked PR history before strategy output is meaningful (#1243). */
|
|
68
|
+
export declare const STRATEGY_MIN_PRS = 10;
|
|
69
|
+
/**
|
|
70
|
+
* Compute the deterministic strategy signal from agent state. Returns
|
|
71
|
+
* null when state is thinner than {@link STRATEGY_MIN_PRS} merged PRs —
|
|
72
|
+
* the auto-display surface uses this null sentinel as the
|
|
73
|
+
* minimum-data gate (#1243).
|
|
74
|
+
*/
|
|
75
|
+
export declare function computeStrategy(state: AgentState): StrategyResult | null;
|