@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,226 @@
|
|
|
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
|
+
/** Minimum tracked PR history before strategy output is meaningful (#1243). */
|
|
17
|
+
export const STRATEGY_MIN_PRS = 10;
|
|
18
|
+
/** How many top-N items each "primary" / "favorite" list returns. */
|
|
19
|
+
const TOP_N = 5;
|
|
20
|
+
/** Days since `mergedAt` after which a PR no longer counts as recent for
|
|
21
|
+
* the active-now PR-type distribution. 90 days matches the issue's
|
|
22
|
+
* proposed trajectory window. */
|
|
23
|
+
const RECENT_WINDOW_DAYS = 90;
|
|
24
|
+
/** Match repo from a GitHub URL: `https://github.com/owner/repo/pull/N`. */
|
|
25
|
+
const REPO_FROM_URL = /https:\/\/github\.com\/([^/]+)\/([^/]+)\/(?:issues|pull)\/\d+/i;
|
|
26
|
+
function parseRepo(url) {
|
|
27
|
+
const m = url.match(REPO_FROM_URL);
|
|
28
|
+
return m ? `${m[1]}/${m[2]}` : null;
|
|
29
|
+
}
|
|
30
|
+
function classifyTitle(title) {
|
|
31
|
+
const t = title.trim().toLowerCase();
|
|
32
|
+
// Match Conventional Commits prefix first; fall back to keyword search
|
|
33
|
+
// in the title body.
|
|
34
|
+
const prefix = t.match(/^(feat|fix|docs|refactor|test|perf|chore|build|ci|style|revert)(?:\([^)]+\))?!?:/);
|
|
35
|
+
const tag = prefix?.[1];
|
|
36
|
+
if (tag === 'docs')
|
|
37
|
+
return 'docs';
|
|
38
|
+
if (tag === 'fix' || tag === 'perf')
|
|
39
|
+
return 'fixes';
|
|
40
|
+
if (tag === 'feat')
|
|
41
|
+
return 'features';
|
|
42
|
+
if (tag === 'refactor')
|
|
43
|
+
return 'refactors';
|
|
44
|
+
if (tag === 'test')
|
|
45
|
+
return 'tests';
|
|
46
|
+
// Heuristic fallbacks for non-conventional titles.
|
|
47
|
+
if (/\b(?:doc|readme|comment|typo)\b/i.test(t))
|
|
48
|
+
return 'docs';
|
|
49
|
+
if (/\b(?:fix|bug|crash|regression|broken)\b/i.test(t))
|
|
50
|
+
return 'fixes';
|
|
51
|
+
if (/\b(?:add|implement|introduce|support|new)\b/i.test(t))
|
|
52
|
+
return 'features';
|
|
53
|
+
if (/\b(?:refactor|cleanup|extract|simplify|rename)\b/i.test(t))
|
|
54
|
+
return 'refactors';
|
|
55
|
+
if (/\btest|spec\b/i.test(t))
|
|
56
|
+
return 'tests';
|
|
57
|
+
return 'other';
|
|
58
|
+
}
|
|
59
|
+
function topNByCount(counts, n) {
|
|
60
|
+
return Object.entries(counts)
|
|
61
|
+
.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]))
|
|
62
|
+
.slice(0, n)
|
|
63
|
+
.map(([name]) => name);
|
|
64
|
+
}
|
|
65
|
+
function determineStyle(_totalPRs, primaryLanguages, favoriteRepos) {
|
|
66
|
+
// `totalPRs` is reserved for future "explorer" classification (low-volume
|
|
67
|
+
// contributors). Today the function is only reachable from `computeStrategy`
|
|
68
|
+
// which gates at `merged.length >= STRATEGY_MIN_PRS` (10), so a `totalPRs < 5`
|
|
69
|
+
// branch could never fire — removed to avoid misleading future readers.
|
|
70
|
+
// Heavy concentration in 1-2 repos → maintainer; spread across many → generalist.
|
|
71
|
+
if (favoriteRepos.length === 1)
|
|
72
|
+
return 'maintainer';
|
|
73
|
+
if (primaryLanguages.length === 1 && favoriteRepos.length >= 2 && favoriteRepos.length <= 3) {
|
|
74
|
+
return 'specialist';
|
|
75
|
+
}
|
|
76
|
+
if (favoriteRepos.length >= 4)
|
|
77
|
+
return 'generalist';
|
|
78
|
+
return 'specialist';
|
|
79
|
+
}
|
|
80
|
+
function determineTrajectory(state) {
|
|
81
|
+
// Compare the last three months' merged PR counts against the prior
|
|
82
|
+
// three months. If each window has at least one merge, ratio >1.2 is
|
|
83
|
+
// growing, <0.8 is declining, otherwise steady.
|
|
84
|
+
const counts = state.monthlyMergedCounts ?? {};
|
|
85
|
+
const months = Object.keys(counts).sort();
|
|
86
|
+
if (months.length < 6)
|
|
87
|
+
return 'steady';
|
|
88
|
+
const recent = months.slice(-3).reduce((sum, m) => sum + (counts[m] ?? 0), 0);
|
|
89
|
+
const prior = months.slice(-6, -3).reduce((sum, m) => sum + (counts[m] ?? 0), 0);
|
|
90
|
+
if (prior === 0)
|
|
91
|
+
return recent > 0 ? 'growing' : 'steady';
|
|
92
|
+
const ratio = recent / prior;
|
|
93
|
+
if (ratio >= 1.2)
|
|
94
|
+
return 'growing';
|
|
95
|
+
if (ratio <= 0.8)
|
|
96
|
+
return 'declining';
|
|
97
|
+
return 'steady';
|
|
98
|
+
}
|
|
99
|
+
function recommendForOverExtension(openPRCount, dormantPRCount, overExtended) {
|
|
100
|
+
if (overExtended)
|
|
101
|
+
return 'follow_up_dormant';
|
|
102
|
+
if (dormantPRCount > 0 && openPRCount > 5)
|
|
103
|
+
return 'wait_on_maintainers';
|
|
104
|
+
if (openPRCount === 0)
|
|
105
|
+
return 'open_more';
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Compute the deterministic strategy signal from agent state. Returns
|
|
110
|
+
* null when state is thinner than {@link STRATEGY_MIN_PRS} merged PRs —
|
|
111
|
+
* the auto-display surface uses this null sentinel as the
|
|
112
|
+
* minimum-data gate (#1243).
|
|
113
|
+
*/
|
|
114
|
+
export function computeStrategy(state) {
|
|
115
|
+
const merged = state.mergedPRs ?? [];
|
|
116
|
+
const closed = state.closedPRs ?? [];
|
|
117
|
+
const totalPRs = merged.length + closed.length;
|
|
118
|
+
if (merged.length < STRATEGY_MIN_PRS)
|
|
119
|
+
return null;
|
|
120
|
+
// Per-repo PR counts (merged). Used both for "favorite repos" and to
|
|
121
|
+
// back-fill primary languages from `repoScores` entries that overlap.
|
|
122
|
+
const mergedByRepo = {};
|
|
123
|
+
for (const pr of merged) {
|
|
124
|
+
const repo = parseRepo(pr.url);
|
|
125
|
+
if (!repo)
|
|
126
|
+
continue;
|
|
127
|
+
mergedByRepo[repo] = (mergedByRepo[repo] ?? 0) + 1;
|
|
128
|
+
}
|
|
129
|
+
const favoriteRepos = topNByCount(mergedByRepo, TOP_N);
|
|
130
|
+
// Languages: weight a repo's language by that repo's merged count so
|
|
131
|
+
// `vercel/next.js` (TypeScript) counts more than a one-off PR to a
|
|
132
|
+
// Lua project even if both repos are in `repoScores`.
|
|
133
|
+
const languageCounts = {};
|
|
134
|
+
for (const [repo, count] of Object.entries(mergedByRepo)) {
|
|
135
|
+
const score = state.repoScores[repo];
|
|
136
|
+
const lang = score?.language;
|
|
137
|
+
if (!lang)
|
|
138
|
+
continue;
|
|
139
|
+
languageCounts[lang] = (languageCounts[lang] ?? 0) + count;
|
|
140
|
+
}
|
|
141
|
+
const primaryLanguages = topNByCount(languageCounts, TOP_N);
|
|
142
|
+
// PR-type distribution over the recent window.
|
|
143
|
+
const distribution = {
|
|
144
|
+
docs: 0,
|
|
145
|
+
fixes: 0,
|
|
146
|
+
features: 0,
|
|
147
|
+
refactors: 0,
|
|
148
|
+
tests: 0,
|
|
149
|
+
other: 0,
|
|
150
|
+
};
|
|
151
|
+
const cutoff = Date.now() - RECENT_WINDOW_DAYS * 86400000;
|
|
152
|
+
for (const pr of merged) {
|
|
153
|
+
const mergedAt = Date.parse(pr.mergedAt);
|
|
154
|
+
if (Number.isNaN(mergedAt) || mergedAt < cutoff)
|
|
155
|
+
continue;
|
|
156
|
+
distribution[classifyTitle(pr.title)] += 1;
|
|
157
|
+
}
|
|
158
|
+
// Capacity: read from the last digest. When no digest exists yet,
|
|
159
|
+
// default to zero (the agent has nothing to recommend without a
|
|
160
|
+
// digest). The DailyDigest schema does not store a `dormantCount`
|
|
161
|
+
// directly — derive it from the `waitingOnMaintainerPRs` array,
|
|
162
|
+
// which is the "PR flagged as awaiting maintainer review" bucket
|
|
163
|
+
// produced by `pr-monitor`.
|
|
164
|
+
const summary = state.lastDigest?.summary;
|
|
165
|
+
const openPRCount = summary?.totalActivePRs ?? 0;
|
|
166
|
+
const waiting = state.lastDigest?.waitingOnMaintainerPRs ?? [];
|
|
167
|
+
const dormantPRCount = waiting.length;
|
|
168
|
+
// Overextended: dormant PRs spread across 2+ repos. Same definition
|
|
169
|
+
// the issue body uses.
|
|
170
|
+
const dormantRepos = new Set(waiting
|
|
171
|
+
.map((pr) => (typeof pr.url === 'string' ? parseRepo(pr.url) : null))
|
|
172
|
+
.filter((r) => r !== null));
|
|
173
|
+
const overExtended = dormantPRCount >= 2 && dormantRepos.size >= 2;
|
|
174
|
+
const profile = {
|
|
175
|
+
style: determineStyle(totalPRs, primaryLanguages, favoriteRepos),
|
|
176
|
+
totalPRs,
|
|
177
|
+
mergedCount: merged.length,
|
|
178
|
+
mergeRate: totalPRs === 0 ? 0 : merged.length / totalPRs,
|
|
179
|
+
primaryLanguages,
|
|
180
|
+
favoriteRepos,
|
|
181
|
+
};
|
|
182
|
+
const capacity = {
|
|
183
|
+
openPRCount,
|
|
184
|
+
dormantPRCount,
|
|
185
|
+
overExtended,
|
|
186
|
+
suggestedAction: recommendForOverExtension(openPRCount, dormantPRCount, overExtended),
|
|
187
|
+
};
|
|
188
|
+
const patterns = {
|
|
189
|
+
prTypeDistribution: distribution,
|
|
190
|
+
trajectoryDirection: determineTrajectory(state),
|
|
191
|
+
// Without per-PR diff size in StoredMergedPR, a true "average PR
|
|
192
|
+
// size" lookup is out of scope until that data is captured (#1243
|
|
193
|
+
// explicitly defers this). Surface 0 so downstream callers know the
|
|
194
|
+
// signal is unavailable rather than fabricating a value.
|
|
195
|
+
averagePRSize: 0,
|
|
196
|
+
};
|
|
197
|
+
const recommendations = {
|
|
198
|
+
// The deterministic recommendations layer reuses the profile
|
|
199
|
+
// signals — the agent's coaching prose layers on top of these.
|
|
200
|
+
languages: primaryLanguages,
|
|
201
|
+
repos: favoriteRepos,
|
|
202
|
+
issueTypes: deriveIssueTypePreferences(distribution),
|
|
203
|
+
avoidPatterns: deriveAvoidPatterns(capacity, patterns),
|
|
204
|
+
};
|
|
205
|
+
return { profile, capacity, patterns, recommendations };
|
|
206
|
+
}
|
|
207
|
+
function deriveIssueTypePreferences(distribution) {
|
|
208
|
+
// Recommend the user's two strongest PR types — they have a track
|
|
209
|
+
// record there, so issues in those buckets are higher-yield.
|
|
210
|
+
const ranked = Object.entries(distribution)
|
|
211
|
+
.filter(([type]) => type !== 'other')
|
|
212
|
+
.sort((a, b) => b[1] - a[1])
|
|
213
|
+
.slice(0, 2)
|
|
214
|
+
.map(([type]) => type);
|
|
215
|
+
return ranked;
|
|
216
|
+
}
|
|
217
|
+
function deriveAvoidPatterns(capacity, patterns) {
|
|
218
|
+
const out = [];
|
|
219
|
+
if (capacity.overExtended) {
|
|
220
|
+
out.push('opening new PRs while dormant ones await review across multiple repos');
|
|
221
|
+
}
|
|
222
|
+
if (patterns.trajectoryDirection === 'declining') {
|
|
223
|
+
out.push('drifting away from a previously productive cadence — capacity may be saturated');
|
|
224
|
+
}
|
|
225
|
+
return out;
|
|
226
|
+
}
|
package/dist/core/types.d.ts
CHANGED
|
@@ -207,6 +207,8 @@ interface CommentedIssueBase {
|
|
|
207
207
|
title: string;
|
|
208
208
|
url: string;
|
|
209
209
|
userLastCommentedAt: string;
|
|
210
|
+
/** User's most recent comment body, truncated to 200 chars (+ "..." suffix when truncated). #1290 */
|
|
211
|
+
userLastCommentBody: string;
|
|
210
212
|
labels: string[];
|
|
211
213
|
daysSinceUserComment: number;
|
|
212
214
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow-state helpers (#1280).
|
|
3
|
+
*
|
|
4
|
+
* Centralizes the read/write logic for `state.workflowState`. Used
|
|
5
|
+
* by `draft-first-workflow.md`, `work-through-issues.md`, and
|
|
6
|
+
* `pre-commit-review.md` to record pause points and offer
|
|
7
|
+
* Resume / Restart / Discard at the next `/oss` invocation.
|
|
8
|
+
*
|
|
9
|
+
* Pure functions — callers manage state I/O. Same architectural
|
|
10
|
+
* shape as the recent #1277 (follow-up-history) helpers.
|
|
11
|
+
*/
|
|
12
|
+
import type { AgentState, WorkflowState } from './state-schema.js';
|
|
13
|
+
export type WorkflowName = WorkflowState['workflowName'];
|
|
14
|
+
/**
|
|
15
|
+
* Snapshot the user's current workflow position. Returns the
|
|
16
|
+
* patched state; callers persist via `StateManager.save()` (or the
|
|
17
|
+
* gist `checkpoint()` path).
|
|
18
|
+
*/
|
|
19
|
+
export declare function recordWorkflowPause(state: AgentState, next: Omit<WorkflowState, 'lastUpdatedAt'>, now?: Date): AgentState;
|
|
20
|
+
/**
|
|
21
|
+
* Read the current workflow snapshot, or null when no pause is
|
|
22
|
+
* recorded.
|
|
23
|
+
*/
|
|
24
|
+
export declare function getWorkflowState(state: AgentState): WorkflowState | null;
|
|
25
|
+
/**
|
|
26
|
+
* Discard the current pause snapshot. Used by:
|
|
27
|
+
* - the workflow completing normally,
|
|
28
|
+
* - the user picking "Discard and start fresh" at the resume
|
|
29
|
+
* prompt,
|
|
30
|
+
* - any consumer that detects the snapshot has gone stale (branch
|
|
31
|
+
* deleted, repo no longer reachable).
|
|
32
|
+
*/
|
|
33
|
+
export declare function clearWorkflowState(state: AgentState): AgentState;
|
|
34
|
+
/**
|
|
35
|
+
* Append a step name to `completedSteps` and update `currentStep`
|
|
36
|
+
* to the supplied next step. Convenience wrapper around the immer-
|
|
37
|
+
* style read/replace pattern. Returns the patched state.
|
|
38
|
+
*/
|
|
39
|
+
export declare function advanceWorkflowStep(state: AgentState, nextStep: string, now?: Date): AgentState;
|
|
40
|
+
/**
|
|
41
|
+
* Merge per-step data into `stepData` without overwriting other
|
|
42
|
+
* keys. Useful when a workflow's resume needs to restore non-trivial
|
|
43
|
+
* context (skipped compliance items, last review pass count, etc.).
|
|
44
|
+
*/
|
|
45
|
+
export declare function setStepData(state: AgentState, step: string, data: unknown, now?: Date): AgentState;
|
|
46
|
+
/**
|
|
47
|
+
* Read step data for a specific step, or undefined when none was
|
|
48
|
+
* stored. Callers typically narrow the unknown via a type guard.
|
|
49
|
+
*/
|
|
50
|
+
export declare function getStepData(state: AgentState, step: string): unknown;
|
|
51
|
+
/**
|
|
52
|
+
* Whether the recorded pause is on the supplied workflow. Useful
|
|
53
|
+
* for the router to decide whether to offer Resume vs ignore the
|
|
54
|
+
* snapshot when the current `/oss` invocation is unrelated.
|
|
55
|
+
*/
|
|
56
|
+
export declare function isPausedIn(state: AgentState, workflowName: WorkflowName): boolean;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow-state helpers (#1280).
|
|
3
|
+
*
|
|
4
|
+
* Centralizes the read/write logic for `state.workflowState`. Used
|
|
5
|
+
* by `draft-first-workflow.md`, `work-through-issues.md`, and
|
|
6
|
+
* `pre-commit-review.md` to record pause points and offer
|
|
7
|
+
* Resume / Restart / Discard at the next `/oss` invocation.
|
|
8
|
+
*
|
|
9
|
+
* Pure functions — callers manage state I/O. Same architectural
|
|
10
|
+
* shape as the recent #1277 (follow-up-history) helpers.
|
|
11
|
+
*/
|
|
12
|
+
/**
|
|
13
|
+
* Snapshot the user's current workflow position. Returns the
|
|
14
|
+
* patched state; callers persist via `StateManager.save()` (or the
|
|
15
|
+
* gist `checkpoint()` path).
|
|
16
|
+
*/
|
|
17
|
+
export function recordWorkflowPause(state, next, now = new Date()) {
|
|
18
|
+
const incoming = {
|
|
19
|
+
...next,
|
|
20
|
+
lastUpdatedAt: now.toISOString(),
|
|
21
|
+
};
|
|
22
|
+
return { ...state, workflowState: incoming };
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Read the current workflow snapshot, or null when no pause is
|
|
26
|
+
* recorded.
|
|
27
|
+
*/
|
|
28
|
+
export function getWorkflowState(state) {
|
|
29
|
+
return state.workflowState ?? null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Discard the current pause snapshot. Used by:
|
|
33
|
+
* - the workflow completing normally,
|
|
34
|
+
* - the user picking "Discard and start fresh" at the resume
|
|
35
|
+
* prompt,
|
|
36
|
+
* - any consumer that detects the snapshot has gone stale (branch
|
|
37
|
+
* deleted, repo no longer reachable).
|
|
38
|
+
*/
|
|
39
|
+
export function clearWorkflowState(state) {
|
|
40
|
+
if (!state.workflowState)
|
|
41
|
+
return state;
|
|
42
|
+
const { workflowState: _drop, ...rest } = state;
|
|
43
|
+
return rest;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Append a step name to `completedSteps` and update `currentStep`
|
|
47
|
+
* to the supplied next step. Convenience wrapper around the immer-
|
|
48
|
+
* style read/replace pattern. Returns the patched state.
|
|
49
|
+
*/
|
|
50
|
+
export function advanceWorkflowStep(state, nextStep, now = new Date()) {
|
|
51
|
+
const ws = state.workflowState;
|
|
52
|
+
if (!ws)
|
|
53
|
+
return state;
|
|
54
|
+
if (ws.currentStep === nextStep)
|
|
55
|
+
return state;
|
|
56
|
+
const completed = [...ws.completedSteps];
|
|
57
|
+
if (!completed.includes(ws.currentStep))
|
|
58
|
+
completed.push(ws.currentStep);
|
|
59
|
+
return {
|
|
60
|
+
...state,
|
|
61
|
+
workflowState: {
|
|
62
|
+
...ws,
|
|
63
|
+
currentStep: nextStep,
|
|
64
|
+
completedSteps: completed,
|
|
65
|
+
lastUpdatedAt: now.toISOString(),
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Merge per-step data into `stepData` without overwriting other
|
|
71
|
+
* keys. Useful when a workflow's resume needs to restore non-trivial
|
|
72
|
+
* context (skipped compliance items, last review pass count, etc.).
|
|
73
|
+
*/
|
|
74
|
+
export function setStepData(state, step, data, now = new Date()) {
|
|
75
|
+
const ws = state.workflowState;
|
|
76
|
+
if (!ws)
|
|
77
|
+
return state;
|
|
78
|
+
return {
|
|
79
|
+
...state,
|
|
80
|
+
workflowState: {
|
|
81
|
+
...ws,
|
|
82
|
+
stepData: { ...ws.stepData, [step]: data },
|
|
83
|
+
lastUpdatedAt: now.toISOString(),
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Read step data for a specific step, or undefined when none was
|
|
89
|
+
* stored. Callers typically narrow the unknown via a type guard.
|
|
90
|
+
*/
|
|
91
|
+
export function getStepData(state, step) {
|
|
92
|
+
return state.workflowState?.stepData[step];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Whether the recorded pause is on the supplied workflow. Useful
|
|
96
|
+
* for the router to decide whether to offer Resume vs ignore the
|
|
97
|
+
* snapshot when the current `/oss` invocation is unrelated.
|
|
98
|
+
*/
|
|
99
|
+
export function isPausedIn(state, workflowName) {
|
|
100
|
+
return state.workflowState?.workflowName === workflowName;
|
|
101
|
+
}
|
|
@@ -534,6 +534,134 @@ export declare const PRTemplateOutputSchema: z.ZodObject<{
|
|
|
534
534
|
source: z.ZodNullable<z.ZodString>;
|
|
535
535
|
error: z.ZodOptional<z.ZodString>;
|
|
536
536
|
}, z.core.$strip>;
|
|
537
|
+
export declare const RepoVetOutputSchema: z.ZodObject<{
|
|
538
|
+
repoSlug: z.ZodString;
|
|
539
|
+
fetchedAt: z.ZodString;
|
|
540
|
+
repoMeta: z.ZodObject<{
|
|
541
|
+
stars: z.ZodNumber;
|
|
542
|
+
forks: z.ZodNumber;
|
|
543
|
+
openIssues: z.ZodNumber;
|
|
544
|
+
watchers: z.ZodNumber;
|
|
545
|
+
isArchived: z.ZodBoolean;
|
|
546
|
+
lastPushed: z.ZodString;
|
|
547
|
+
createdAt: z.ZodString;
|
|
548
|
+
}, z.core.$strip>;
|
|
549
|
+
prMergeTime: z.ZodObject<{
|
|
550
|
+
avgDays: z.ZodNullable<z.ZodNumber>;
|
|
551
|
+
medianDays: z.ZodNullable<z.ZodNumber>;
|
|
552
|
+
sampleSize: z.ZodNumber;
|
|
553
|
+
sourceWindowDays: z.ZodLiteral<90>;
|
|
554
|
+
}, z.core.$strip>;
|
|
555
|
+
mergeRate: z.ZodObject<{
|
|
556
|
+
merged: z.ZodNumber;
|
|
557
|
+
opened: z.ZodNumber;
|
|
558
|
+
percent: z.ZodNullable<z.ZodNumber>;
|
|
559
|
+
windowDays: z.ZodLiteral<90>;
|
|
560
|
+
}, z.core.$strip>;
|
|
561
|
+
maintainerActivity: z.ZodObject<{
|
|
562
|
+
lastCommitISO: z.ZodNullable<z.ZodString>;
|
|
563
|
+
contributorsLast90d: z.ZodNumber;
|
|
564
|
+
lastReleaseISO: z.ZodNullable<z.ZodString>;
|
|
565
|
+
}, z.core.$strip>;
|
|
566
|
+
communityHealth: z.ZodObject<{
|
|
567
|
+
contributing: z.ZodBoolean;
|
|
568
|
+
issueTemplates: z.ZodBoolean;
|
|
569
|
+
prTemplate: z.ZodBoolean;
|
|
570
|
+
codeOfConduct: z.ZodBoolean;
|
|
571
|
+
incomplete: z.ZodOptional<z.ZodBoolean>;
|
|
572
|
+
}, z.core.$strip>;
|
|
573
|
+
rubricScore: z.ZodNumber;
|
|
574
|
+
rubricVerdict: z.ZodEnum<{
|
|
575
|
+
recommended: "recommended";
|
|
576
|
+
proceed_with_caution: "proceed_with_caution";
|
|
577
|
+
avoid: "avoid";
|
|
578
|
+
}>;
|
|
579
|
+
}, z.core.$strip>;
|
|
580
|
+
/**
|
|
581
|
+
* The CLI wrapper renames the core function's `repo` metadata object to
|
|
582
|
+
* `repoMeta` so the top-level slug doesn't collide with it. The TS type
|
|
583
|
+
* is derived from the Zod schema so any drift between schema and type
|
|
584
|
+
* fails at compile time.
|
|
585
|
+
*/
|
|
586
|
+
export type RepoVetOutput = z.infer<typeof RepoVetOutputSchema>;
|
|
587
|
+
export declare const ComplianceScoreOutputSchema: z.ZodObject<{
|
|
588
|
+
pr: z.ZodObject<{
|
|
589
|
+
repo: z.ZodString;
|
|
590
|
+
number: z.ZodNumber;
|
|
591
|
+
title: z.ZodString;
|
|
592
|
+
url: z.ZodString;
|
|
593
|
+
}, z.core.$strip>;
|
|
594
|
+
score: z.ZodNumber;
|
|
595
|
+
rating: z.ZodEnum<{
|
|
596
|
+
ready: "ready";
|
|
597
|
+
minor: "minor";
|
|
598
|
+
fix_first: "fix_first";
|
|
599
|
+
significant_work: "significant_work";
|
|
600
|
+
}>;
|
|
601
|
+
emoji: z.ZodEnum<{
|
|
602
|
+
"\uD83C\uDF1F": "🌟";
|
|
603
|
+
"\u2705": "✅";
|
|
604
|
+
"\u26A0\uFE0F": "⚠️";
|
|
605
|
+
"\u274C": "❌";
|
|
606
|
+
}>;
|
|
607
|
+
checks: z.ZodObject<{
|
|
608
|
+
issueReference: z.ZodObject<{
|
|
609
|
+
status: z.ZodEnum<{
|
|
610
|
+
fail: "fail";
|
|
611
|
+
pass: "pass";
|
|
612
|
+
warn: "warn";
|
|
613
|
+
}>;
|
|
614
|
+
weight: z.ZodNumber;
|
|
615
|
+
detail: z.ZodString;
|
|
616
|
+
}, z.core.$strip>;
|
|
617
|
+
description: z.ZodObject<{
|
|
618
|
+
status: z.ZodEnum<{
|
|
619
|
+
fail: "fail";
|
|
620
|
+
pass: "pass";
|
|
621
|
+
warn: "warn";
|
|
622
|
+
}>;
|
|
623
|
+
weight: z.ZodNumber;
|
|
624
|
+
detail: z.ZodString;
|
|
625
|
+
}, z.core.$strip>;
|
|
626
|
+
focusedChanges: z.ZodObject<{
|
|
627
|
+
status: z.ZodEnum<{
|
|
628
|
+
fail: "fail";
|
|
629
|
+
pass: "pass";
|
|
630
|
+
warn: "warn";
|
|
631
|
+
}>;
|
|
632
|
+
weight: z.ZodNumber;
|
|
633
|
+
detail: z.ZodString;
|
|
634
|
+
}, z.core.$strip>;
|
|
635
|
+
tests: z.ZodObject<{
|
|
636
|
+
status: z.ZodEnum<{
|
|
637
|
+
fail: "fail";
|
|
638
|
+
pass: "pass";
|
|
639
|
+
warn: "warn";
|
|
640
|
+
}>;
|
|
641
|
+
weight: z.ZodNumber;
|
|
642
|
+
detail: z.ZodString;
|
|
643
|
+
}, z.core.$strip>;
|
|
644
|
+
title: z.ZodObject<{
|
|
645
|
+
status: z.ZodEnum<{
|
|
646
|
+
fail: "fail";
|
|
647
|
+
pass: "pass";
|
|
648
|
+
warn: "warn";
|
|
649
|
+
}>;
|
|
650
|
+
weight: z.ZodNumber;
|
|
651
|
+
detail: z.ZodString;
|
|
652
|
+
}, z.core.$strip>;
|
|
653
|
+
branch: z.ZodObject<{
|
|
654
|
+
status: z.ZodEnum<{
|
|
655
|
+
fail: "fail";
|
|
656
|
+
pass: "pass";
|
|
657
|
+
warn: "warn";
|
|
658
|
+
}>;
|
|
659
|
+
weight: z.ZodNumber;
|
|
660
|
+
detail: z.ZodString;
|
|
661
|
+
}, z.core.$strip>;
|
|
662
|
+
}, z.core.$strip>;
|
|
663
|
+
}, z.core.$strip>;
|
|
664
|
+
export type ComplianceScoreOutput = z.infer<typeof ComplianceScoreOutputSchema>;
|
|
537
665
|
export declare const ParseIssueListOutputSchema: z.ZodObject<{
|
|
538
666
|
available: z.ZodArray<z.ZodObject<{
|
|
539
667
|
repo: z.ZodString;
|
|
@@ -696,6 +824,25 @@ export interface StartupOutput {
|
|
|
696
824
|
* a structured signal to surface or recover from the failure.
|
|
697
825
|
*/
|
|
698
826
|
dashboardError?: string;
|
|
827
|
+
/**
|
|
828
|
+
* Status of the dashboard SPA build that ran (or didn't) before this
|
|
829
|
+
* startup invocation (#1293). Populated by the workflow shell via
|
|
830
|
+
* `OSS_DASHBOARD_BUILD_STATUS`; absent when the CLI is invoked outside
|
|
831
|
+
* the plugin workflow:
|
|
832
|
+
* - `'fresh'` — built artifact was up-to-date, no rebuild attempted.
|
|
833
|
+
* - `'rebuilt'` — rebuild ran and succeeded.
|
|
834
|
+
* - `'failed'` — rebuild ran and failed; `dashboardUrl` may serve stale
|
|
835
|
+
* or missing assets until the build is fixed.
|
|
836
|
+
* - `'missing-pnpm'` — rebuild was needed but pnpm (required for the
|
|
837
|
+
* workspace dependency) was unavailable.
|
|
838
|
+
*/
|
|
839
|
+
dashboardBuildStatus?: 'fresh' | 'rebuilt' | 'failed' | 'missing-pnpm';
|
|
840
|
+
/**
|
|
841
|
+
* Last few lines of the dashboard build log when `dashboardBuildStatus`
|
|
842
|
+
* is `'failed'` or `'missing-pnpm'`. Surfaced by the workflow as a
|
|
843
|
+
* one-line warning so the user sees what broke without leaving `/oss`.
|
|
844
|
+
*/
|
|
845
|
+
dashboardBuildErrorTail?: string;
|
|
699
846
|
issueList?: IssueListInfo;
|
|
700
847
|
}
|
|
701
848
|
/**
|
package/dist/formatters/json.js
CHANGED
|
@@ -376,6 +376,85 @@ export const PRTemplateOutputSchema = z.object({
|
|
|
376
376
|
source: z.string().nullable(),
|
|
377
377
|
error: z.string().optional(),
|
|
378
378
|
});
|
|
379
|
+
/**
|
|
380
|
+
* Output of the `repo-vet` CLI command (#1271, follow-up to #1242).
|
|
381
|
+
*
|
|
382
|
+
* Validates the full nested shape so a refactor that drops or
|
|
383
|
+
* mis-types one of these fields trips `outputJsonValidated` rather
|
|
384
|
+
* than silently shipping a broken envelope (#1245 contract pattern,
|
|
385
|
+
* matching `ComplianceScoreOutputSchema` below).
|
|
386
|
+
*/
|
|
387
|
+
const RepoVetMetaSchema = z.object({
|
|
388
|
+
stars: z.number(),
|
|
389
|
+
forks: z.number(),
|
|
390
|
+
openIssues: z.number(),
|
|
391
|
+
watchers: z.number(),
|
|
392
|
+
isArchived: z.boolean(),
|
|
393
|
+
lastPushed: z.string(),
|
|
394
|
+
createdAt: z.string(),
|
|
395
|
+
});
|
|
396
|
+
export const RepoVetOutputSchema = z.object({
|
|
397
|
+
repoSlug: z.string(),
|
|
398
|
+
fetchedAt: z.string(),
|
|
399
|
+
repoMeta: RepoVetMetaSchema,
|
|
400
|
+
prMergeTime: z.object({
|
|
401
|
+
avgDays: z.number().nullable(),
|
|
402
|
+
medianDays: z.number().nullable(),
|
|
403
|
+
sampleSize: z.number().int().nonnegative(),
|
|
404
|
+
sourceWindowDays: z.literal(90),
|
|
405
|
+
}),
|
|
406
|
+
mergeRate: z.object({
|
|
407
|
+
merged: z.number().int().nonnegative(),
|
|
408
|
+
opened: z.number().int().nonnegative(),
|
|
409
|
+
percent: z.number().nullable(),
|
|
410
|
+
windowDays: z.literal(90),
|
|
411
|
+
}),
|
|
412
|
+
maintainerActivity: z.object({
|
|
413
|
+
lastCommitISO: z.string().nullable(),
|
|
414
|
+
contributorsLast90d: z.number().int().nonnegative(),
|
|
415
|
+
lastReleaseISO: z.string().nullable(),
|
|
416
|
+
}),
|
|
417
|
+
communityHealth: z.object({
|
|
418
|
+
contributing: z.boolean(),
|
|
419
|
+
issueTemplates: z.boolean(),
|
|
420
|
+
prTemplate: z.boolean(),
|
|
421
|
+
codeOfConduct: z.boolean(),
|
|
422
|
+
/**
|
|
423
|
+
* True when at least one community-health probe failed for a non-404
|
|
424
|
+
* reason (auth, rate-limit, 5xx). The boolean flags above stay
|
|
425
|
+
* `false` for unprobed paths in that case, so consumers should treat
|
|
426
|
+
* the false flags as "could not determine" rather than "confirmed
|
|
427
|
+
* absent" when this is set.
|
|
428
|
+
*/
|
|
429
|
+
incomplete: z.boolean().optional(),
|
|
430
|
+
}),
|
|
431
|
+
rubricScore: z.number(),
|
|
432
|
+
rubricVerdict: z.enum(['recommended', 'proceed_with_caution', 'avoid']),
|
|
433
|
+
});
|
|
434
|
+
const ComplianceCheckSchema = z.object({
|
|
435
|
+
status: z.enum(['pass', 'warn', 'fail']),
|
|
436
|
+
weight: z.number(),
|
|
437
|
+
detail: z.string(),
|
|
438
|
+
});
|
|
439
|
+
export const ComplianceScoreOutputSchema = z.object({
|
|
440
|
+
pr: z.object({
|
|
441
|
+
repo: z.string(),
|
|
442
|
+
number: z.number().int().nonnegative(),
|
|
443
|
+
title: z.string(),
|
|
444
|
+
url: z.string(),
|
|
445
|
+
}),
|
|
446
|
+
score: z.number().int().min(0).max(100),
|
|
447
|
+
rating: z.enum(['ready', 'minor', 'fix_first', 'significant_work']),
|
|
448
|
+
emoji: z.enum(['🌟', '✅', '⚠️', '❌']),
|
|
449
|
+
checks: z.object({
|
|
450
|
+
issueReference: ComplianceCheckSchema,
|
|
451
|
+
description: ComplianceCheckSchema,
|
|
452
|
+
focusedChanges: ComplianceCheckSchema,
|
|
453
|
+
tests: ComplianceCheckSchema,
|
|
454
|
+
title: ComplianceCheckSchema,
|
|
455
|
+
branch: ComplianceCheckSchema,
|
|
456
|
+
}),
|
|
457
|
+
});
|
|
379
458
|
const ParsedIssueItemSchema = z.object({
|
|
380
459
|
repo: z.string(),
|
|
381
460
|
number: z.number(),
|