@oss-autopilot/core 1.1.0 → 1.3.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 +64 -2
- package/dist/cli.bundle.cjs +61 -59
- package/dist/commands/config.js +9 -0
- package/dist/commands/setup.d.ts +1 -0
- package/dist/commands/setup.js +24 -2
- package/dist/commands/vet-list.d.ts +24 -0
- package/dist/commands/vet-list.js +112 -0
- package/dist/core/daily-logic.js +16 -2
- package/dist/core/state-schema.d.ts +2 -0
- package/dist/core/state-schema.js +1 -0
- package/dist/formatters/json.d.ts +57 -0
- package/dist/formatters/json.js +29 -0
- package/package.json +1 -1
package/dist/commands/config.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Shows or updates configuration
|
|
4
4
|
*/
|
|
5
5
|
import { getStateManager } from '../core/index.js';
|
|
6
|
+
import { ValidationError } from '../core/errors.js';
|
|
6
7
|
import { ISSUE_SCOPES } from '../core/types.js';
|
|
7
8
|
import { validateGitHubUsername } from './validation.js';
|
|
8
9
|
function validateScope(value) {
|
|
@@ -105,6 +106,14 @@ export async function runConfig(options) {
|
|
|
105
106
|
case 'issueListPath':
|
|
106
107
|
stateManager.updateConfig({ issueListPath: value || undefined });
|
|
107
108
|
break;
|
|
109
|
+
case 'scoreThreshold': {
|
|
110
|
+
const threshold = Number(value);
|
|
111
|
+
if (!Number.isInteger(threshold) || threshold < 1 || threshold > 10) {
|
|
112
|
+
throw new ValidationError(`Invalid value for scoreThreshold: "${value}". Must be an integer between 1 and 10.`);
|
|
113
|
+
}
|
|
114
|
+
stateManager.updateConfig({ scoreThreshold: threshold });
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
108
117
|
default:
|
|
109
118
|
throw new Error(`Unknown config key: ${options.key}`);
|
|
110
119
|
}
|
package/dist/commands/setup.d.ts
CHANGED
package/dist/commands/setup.js
CHANGED
|
@@ -9,11 +9,19 @@ import { PROJECT_CATEGORIES, ISSUE_SCOPES } from '../core/types.js';
|
|
|
9
9
|
/** Parse and validate a positive integer setting value. */
|
|
10
10
|
function parsePositiveInt(value, settingName) {
|
|
11
11
|
const parsed = Number(value);
|
|
12
|
-
if (!Number.
|
|
12
|
+
if (!Number.isInteger(parsed) || parsed < 1) {
|
|
13
13
|
throw new ValidationError(`Invalid value for ${settingName}: "${value}". Must be a positive integer.`);
|
|
14
14
|
}
|
|
15
15
|
return parsed;
|
|
16
16
|
}
|
|
17
|
+
/** Parse and validate an integer within a specific range [min, max]. */
|
|
18
|
+
function parseBoundedInt(value, settingName, min, max) {
|
|
19
|
+
const parsed = Number(value);
|
|
20
|
+
if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
|
|
21
|
+
throw new ValidationError(`Invalid value for ${settingName}: "${value}". Must be an integer between ${min} and ${max}.`);
|
|
22
|
+
}
|
|
23
|
+
return parsed;
|
|
24
|
+
}
|
|
17
25
|
/**
|
|
18
26
|
* Interactive setup wizard or direct setting application.
|
|
19
27
|
*
|
|
@@ -84,9 +92,15 @@ export async function runSetup(options) {
|
|
|
84
92
|
results[key] = value !== 'false' ? 'true' : 'false';
|
|
85
93
|
}
|
|
86
94
|
break;
|
|
95
|
+
case 'scoreThreshold': {
|
|
96
|
+
const threshold = parseBoundedInt(value, 'scoreThreshold', 1, 10);
|
|
97
|
+
stateManager.updateConfig({ scoreThreshold: threshold });
|
|
98
|
+
results[key] = String(threshold);
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
87
101
|
case 'minStars': {
|
|
88
102
|
const stars = Number(value);
|
|
89
|
-
if (!Number.
|
|
103
|
+
if (!Number.isInteger(stars) || stars < 0) {
|
|
90
104
|
throw new ValidationError(`Invalid value for minStars: "${value}". Must be a non-negative integer.`);
|
|
91
105
|
}
|
|
92
106
|
stateManager.updateConfig({ minStars: stars });
|
|
@@ -225,6 +239,7 @@ export async function runSetup(options) {
|
|
|
225
239
|
projectCategories: config.projectCategories ?? [],
|
|
226
240
|
preferredOrgs: config.preferredOrgs ?? [],
|
|
227
241
|
scope: config.scope ?? [],
|
|
242
|
+
scoreThreshold: config.scoreThreshold,
|
|
228
243
|
},
|
|
229
244
|
};
|
|
230
245
|
}
|
|
@@ -281,6 +296,13 @@ export async function runSetup(options) {
|
|
|
281
296
|
default: [],
|
|
282
297
|
type: 'list',
|
|
283
298
|
},
|
|
299
|
+
{
|
|
300
|
+
setting: 'scoreThreshold',
|
|
301
|
+
prompt: 'Minimum vet score (1-10) for issues to keep after vetting? Issues below this are auto-filtered.',
|
|
302
|
+
current: config.scoreThreshold,
|
|
303
|
+
default: 6,
|
|
304
|
+
type: 'number',
|
|
305
|
+
},
|
|
284
306
|
{
|
|
285
307
|
setting: 'aiPolicyBlocklist',
|
|
286
308
|
prompt: 'Repos with anti-AI contribution policies to block (owner/repo, comma-separated)?',
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vet-list command (#764)
|
|
3
|
+
* Re-vets all available issues in a curated issue list file.
|
|
4
|
+
*/
|
|
5
|
+
import { type VetListOutput, type VetOutput, type VetListItemStatus } from '../formatters/json.js';
|
|
6
|
+
interface VetListOptions {
|
|
7
|
+
issueListPath?: string;
|
|
8
|
+
concurrency?: number;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Determine the list status from vetting results.
|
|
12
|
+
* Maps vetting recommendation + reasons to a list-level status.
|
|
13
|
+
*/
|
|
14
|
+
export declare function classifyListStatus(vetResult: VetOutput): VetListItemStatus;
|
|
15
|
+
/**
|
|
16
|
+
* Re-vet all available issues in a curated issue list.
|
|
17
|
+
* Reads the list file, extracts available (non-done) issues,
|
|
18
|
+
* and vets each in parallel with concurrency control.
|
|
19
|
+
*
|
|
20
|
+
* @param options - Vet-list options
|
|
21
|
+
* @returns Consolidated vetting results with list status for each issue
|
|
22
|
+
*/
|
|
23
|
+
export declare function runVetList(options?: VetListOptions): Promise<VetListOutput>;
|
|
24
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Vet-list command (#764)
|
|
3
|
+
* Re-vets all available issues in a curated issue list file.
|
|
4
|
+
*/
|
|
5
|
+
import { IssueDiscovery, requireGitHubToken } from '../core/index.js';
|
|
6
|
+
import { detectIssueList } from './startup.js';
|
|
7
|
+
/**
|
|
8
|
+
* Determine the list status from vetting results.
|
|
9
|
+
* Maps vetting recommendation + reasons to a list-level status.
|
|
10
|
+
*/
|
|
11
|
+
export function classifyListStatus(vetResult) {
|
|
12
|
+
const skipReasons = vetResult.reasonsToSkip.map((r) => r.toLowerCase());
|
|
13
|
+
if (skipReasons.some((r) => r.includes('closed')))
|
|
14
|
+
return 'closed';
|
|
15
|
+
if (skipReasons.some((r) => r.includes('claimed') || r.includes('assigned')))
|
|
16
|
+
return 'claimed';
|
|
17
|
+
if (skipReasons.some((r) => r.includes('existing pr') || r.includes('linked pr') || r.includes('pull request')))
|
|
18
|
+
return 'has_pr';
|
|
19
|
+
if (vetResult.recommendation === 'approve' || vetResult.recommendation === 'needs_review') {
|
|
20
|
+
return 'still_available';
|
|
21
|
+
}
|
|
22
|
+
// Default: if skipped for other reasons, still mark as available
|
|
23
|
+
// (the vetting result will show why it's not recommended)
|
|
24
|
+
return 'still_available';
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Re-vet all available issues in a curated issue list.
|
|
28
|
+
* Reads the list file, extracts available (non-done) issues,
|
|
29
|
+
* and vets each in parallel with concurrency control.
|
|
30
|
+
*
|
|
31
|
+
* @param options - Vet-list options
|
|
32
|
+
* @returns Consolidated vetting results with list status for each issue
|
|
33
|
+
*/
|
|
34
|
+
export async function runVetList(options = {}) {
|
|
35
|
+
const token = requireGitHubToken();
|
|
36
|
+
const concurrency = options.concurrency ?? 5;
|
|
37
|
+
// 1. Find and parse the issue list
|
|
38
|
+
let issueListPath = options.issueListPath;
|
|
39
|
+
if (!issueListPath) {
|
|
40
|
+
const detected = detectIssueList();
|
|
41
|
+
if (!detected) {
|
|
42
|
+
throw new Error('No issue list found. Provide a path with --path or configure issueListPath in settings.');
|
|
43
|
+
}
|
|
44
|
+
issueListPath = detected.path;
|
|
45
|
+
}
|
|
46
|
+
const { runParseList } = await import('./parse-list.js');
|
|
47
|
+
const parsed = await runParseList({ filePath: issueListPath });
|
|
48
|
+
if (parsed.available.length === 0) {
|
|
49
|
+
return {
|
|
50
|
+
results: [],
|
|
51
|
+
summary: { total: 0, stillAvailable: 0, claimed: 0, closed: 0, hasPR: 0, errors: 0 },
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
// 2. Vet each available issue in parallel with concurrency limit
|
|
55
|
+
const discovery = new IssueDiscovery(token);
|
|
56
|
+
const results = [];
|
|
57
|
+
// Simple concurrency limiter
|
|
58
|
+
const items = parsed.available;
|
|
59
|
+
let index = 0;
|
|
60
|
+
async function processNext() {
|
|
61
|
+
while (index < items.length) {
|
|
62
|
+
const item = items[index++];
|
|
63
|
+
try {
|
|
64
|
+
const candidate = await discovery.vetIssue(item.url);
|
|
65
|
+
const vetResult = {
|
|
66
|
+
issue: {
|
|
67
|
+
repo: candidate.issue.repo,
|
|
68
|
+
number: candidate.issue.number,
|
|
69
|
+
title: candidate.issue.title,
|
|
70
|
+
url: candidate.issue.url,
|
|
71
|
+
labels: candidate.issue.labels,
|
|
72
|
+
},
|
|
73
|
+
recommendation: candidate.recommendation,
|
|
74
|
+
reasonsToApprove: candidate.reasonsToApprove,
|
|
75
|
+
reasonsToSkip: candidate.reasonsToSkip,
|
|
76
|
+
projectHealth: candidate.projectHealth,
|
|
77
|
+
vettingResult: candidate.vettingResult,
|
|
78
|
+
};
|
|
79
|
+
results.push({
|
|
80
|
+
...vetResult,
|
|
81
|
+
listStatus: classifyListStatus(vetResult),
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
// Per-issue errors don't fail the batch
|
|
86
|
+
results.push({
|
|
87
|
+
issue: { repo: item.repo, number: item.number, title: item.title, url: item.url, labels: [] },
|
|
88
|
+
recommendation: 'skip',
|
|
89
|
+
reasonsToApprove: [],
|
|
90
|
+
reasonsToSkip: [`Error: ${error instanceof Error ? error.message : String(error)}`],
|
|
91
|
+
projectHealth: {},
|
|
92
|
+
vettingResult: {},
|
|
93
|
+
listStatus: 'error',
|
|
94
|
+
errorMessage: error instanceof Error ? error.message : String(error),
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// Start `concurrency` workers
|
|
100
|
+
const workers = Array.from({ length: Math.min(concurrency, items.length) }, () => processNext());
|
|
101
|
+
await Promise.all(workers);
|
|
102
|
+
// 3. Compute summary
|
|
103
|
+
const summary = {
|
|
104
|
+
total: results.length,
|
|
105
|
+
stillAvailable: results.filter((r) => r.listStatus === 'still_available').length,
|
|
106
|
+
claimed: results.filter((r) => r.listStatus === 'claimed').length,
|
|
107
|
+
closed: results.filter((r) => r.listStatus === 'closed').length,
|
|
108
|
+
hasPR: results.filter((r) => r.listStatus === 'has_pr').length,
|
|
109
|
+
errors: results.filter((r) => r.listStatus === 'error').length,
|
|
110
|
+
};
|
|
111
|
+
return { results, summary };
|
|
112
|
+
}
|
package/dist/core/daily-logic.js
CHANGED
|
@@ -333,11 +333,25 @@ export function computeActionMenu(actionableIssues, capacity, commentedIssues =
|
|
|
333
333
|
}
|
|
334
334
|
// The orchestration layer (commands/oss.md Action Menu section) may insert issue-list
|
|
335
335
|
// options before the search item when a curated list is available.
|
|
336
|
-
|
|
336
|
+
const searchItem = {
|
|
337
337
|
key: 'search',
|
|
338
338
|
label: 'Search for new issues',
|
|
339
339
|
description: 'Look for new contribution opportunities',
|
|
340
|
-
}
|
|
340
|
+
};
|
|
341
|
+
if (!capacity.hasCapacity) {
|
|
342
|
+
const atLimit = capacity.activePRCount >= capacity.maxActivePRs;
|
|
343
|
+
const hasCritical = capacity.criticalIssueCount > 0;
|
|
344
|
+
if (atLimit && hasCritical) {
|
|
345
|
+
searchItem.capacityWarning = `You're at ${capacity.activePRCount}/${capacity.maxActivePRs} active PRs and have ${capacity.criticalIssueCount} critical issue(s). Resolve existing work before claiming new issues.`;
|
|
346
|
+
}
|
|
347
|
+
else if (atLimit) {
|
|
348
|
+
searchItem.capacityWarning = `You're at ${capacity.activePRCount}/${capacity.maxActivePRs} active PRs. Claiming a new issue will exceed your limit.`;
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
searchItem.capacityWarning = `You have ${capacity.criticalIssueCount} critical issue(s) needing attention. Resolve them before claiming new issues.`;
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
items.push(searchItem);
|
|
341
355
|
items.push({
|
|
342
356
|
key: 'done',
|
|
343
357
|
label: 'Done for now',
|
|
@@ -209,6 +209,7 @@ export declare const AgentConfigSchema: z.ZodObject<{
|
|
|
209
209
|
trustedProjects: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
210
210
|
githubUsername: z.ZodDefault<z.ZodString>;
|
|
211
211
|
minRepoScoreThreshold: z.ZodDefault<z.ZodNumber>;
|
|
212
|
+
scoreThreshold: z.ZodDefault<z.ZodNumber>;
|
|
212
213
|
starredRepos: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
213
214
|
starredReposLastFetched: z.ZodOptional<z.ZodString>;
|
|
214
215
|
showHealthCheck: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -354,6 +355,7 @@ export declare const AgentStateSchema: z.ZodObject<{
|
|
|
354
355
|
trustedProjects: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
355
356
|
githubUsername: z.ZodDefault<z.ZodString>;
|
|
356
357
|
minRepoScoreThreshold: z.ZodDefault<z.ZodNumber>;
|
|
358
|
+
scoreThreshold: z.ZodDefault<z.ZodNumber>;
|
|
357
359
|
starredRepos: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
358
360
|
starredReposLastFetched: z.ZodOptional<z.ZodString>;
|
|
359
361
|
showHealthCheck: z.ZodOptional<z.ZodBoolean>;
|
|
@@ -135,6 +135,7 @@ export const AgentConfigSchema = z.object({
|
|
|
135
135
|
trustedProjects: z.array(z.string()).default([]),
|
|
136
136
|
githubUsername: z.string().default(''),
|
|
137
137
|
minRepoScoreThreshold: z.number().default(4),
|
|
138
|
+
scoreThreshold: z.number().int().min(1).max(10).default(6),
|
|
138
139
|
starredRepos: z.array(z.string()).default([]),
|
|
139
140
|
starredReposLastFetched: z.string().optional(),
|
|
140
141
|
showHealthCheck: z.boolean().optional(),
|
|
@@ -53,6 +53,8 @@ export interface ActionMenuItem {
|
|
|
53
53
|
label: string;
|
|
54
54
|
/** Explanation shown below the label. */
|
|
55
55
|
description: string;
|
|
56
|
+
/** Present when the action would exceed the user's PR capacity limit (#765). */
|
|
57
|
+
capacityWarning?: string;
|
|
56
58
|
}
|
|
57
59
|
/**
|
|
58
60
|
* Pre-computed action menu for the orchestration layer.
|
|
@@ -111,6 +113,31 @@ export interface DailyOutput {
|
|
|
111
113
|
repoGroups: CompactRepoGroup[];
|
|
112
114
|
failures: PRCheckFailure[];
|
|
113
115
|
}
|
|
116
|
+
/**
|
|
117
|
+
* Compact version of DailyOutput for reduced JSON payload size (#763).
|
|
118
|
+
* Omits `summary` (pre-rendered markdown ~8KB that duplicates structured fields),
|
|
119
|
+
* `repoGroups` (derivable from digest.openPRs), and full `failures` array.
|
|
120
|
+
* Retains `commentedIssues` because downstream workflows (review-issue-replies.md,
|
|
121
|
+
* action-menu.md) consume it directly.
|
|
122
|
+
* Includes `failureCount` so consumers can detect partial fetch failures without
|
|
123
|
+
* carrying the full error payload.
|
|
124
|
+
*/
|
|
125
|
+
export interface CompactDailyOutput {
|
|
126
|
+
digest: DailyDigestCompact;
|
|
127
|
+
capacity: CapacityAssessment;
|
|
128
|
+
briefSummary: string;
|
|
129
|
+
actionableIssues: CompactActionableIssue[];
|
|
130
|
+
actionMenu: ActionMenu;
|
|
131
|
+
commentedIssues: CommentedIssue[];
|
|
132
|
+
/** Number of PRs that failed to fetch. Non-zero indicates partial results. */
|
|
133
|
+
failureCount: number;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Strip a full DailyOutput down to the compact subset (#763).
|
|
137
|
+
* Omits summary, repoGroups, and full failures array. Retains a failureCount
|
|
138
|
+
* so consumers can detect partial fetch failures.
|
|
139
|
+
*/
|
|
140
|
+
export declare function toCompactDailyOutput(output: DailyOutput): CompactDailyOutput;
|
|
114
141
|
/**
|
|
115
142
|
* Convert a full DailyDigest to the compact format for JSON output (#287).
|
|
116
143
|
* Category arrays become PR URL arrays; full objects stay only in openPRs.
|
|
@@ -207,6 +234,19 @@ export interface StartupOutput {
|
|
|
207
234
|
dashboardUrl?: string;
|
|
208
235
|
issueList?: IssueListInfo;
|
|
209
236
|
}
|
|
237
|
+
/**
|
|
238
|
+
* Compact version of StartupOutput for reduced JSON payload (#763).
|
|
239
|
+
* Derived from StartupOutput with CompactDailyOutput substituted for DailyOutput.
|
|
240
|
+
* Using Omit ensures new fields added to StartupOutput are automatically included.
|
|
241
|
+
*/
|
|
242
|
+
export type CompactStartupOutput = Omit<StartupOutput, 'daily'> & {
|
|
243
|
+
daily?: CompactDailyOutput;
|
|
244
|
+
};
|
|
245
|
+
/**
|
|
246
|
+
* Convert a full StartupOutput to the compact format (#763).
|
|
247
|
+
* Uses destructuring to auto-forward any new fields added to StartupOutput.
|
|
248
|
+
*/
|
|
249
|
+
export declare function toCompactStartupOutput(output: StartupOutput): CompactStartupOutput;
|
|
210
250
|
/** A single parsed issue from a markdown list (#82) */
|
|
211
251
|
export interface ParsedIssueItem {
|
|
212
252
|
repo: string;
|
|
@@ -234,6 +274,23 @@ export interface CheckIntegrationOutput {
|
|
|
234
274
|
newFiles: NewFileInfo[];
|
|
235
275
|
unreferencedCount: number;
|
|
236
276
|
}
|
|
277
|
+
/** Status of a re-vetted issue from the curated list (#764). */
|
|
278
|
+
export type VetListItemStatus = 'still_available' | 'claimed' | 'closed' | 'has_pr' | 'error';
|
|
279
|
+
/** Output of the vet-list command (#764). */
|
|
280
|
+
export interface VetListOutput {
|
|
281
|
+
results: Array<VetOutput & {
|
|
282
|
+
listStatus: VetListItemStatus;
|
|
283
|
+
errorMessage?: string;
|
|
284
|
+
}>;
|
|
285
|
+
summary: {
|
|
286
|
+
total: number;
|
|
287
|
+
stillAvailable: number;
|
|
288
|
+
claimed: number;
|
|
289
|
+
closed: number;
|
|
290
|
+
hasPR: number;
|
|
291
|
+
errors: number;
|
|
292
|
+
};
|
|
293
|
+
}
|
|
237
294
|
/** Output of the vet command */
|
|
238
295
|
export interface VetOutput {
|
|
239
296
|
issue: {
|
package/dist/formatters/json.js
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
* JSON output formatter for CLI --json mode
|
|
3
3
|
* Provides structured output that can be consumed by scripts and plugins
|
|
4
4
|
*/
|
|
5
|
+
/**
|
|
6
|
+
* Strip a full DailyOutput down to the compact subset (#763).
|
|
7
|
+
* Omits summary, repoGroups, and full failures array. Retains a failureCount
|
|
8
|
+
* so consumers can detect partial fetch failures.
|
|
9
|
+
*/
|
|
10
|
+
export function toCompactDailyOutput(output) {
|
|
11
|
+
return {
|
|
12
|
+
digest: output.digest,
|
|
13
|
+
capacity: output.capacity,
|
|
14
|
+
briefSummary: output.briefSummary,
|
|
15
|
+
actionableIssues: output.actionableIssues,
|
|
16
|
+
actionMenu: output.actionMenu,
|
|
17
|
+
commentedIssues: output.commentedIssues,
|
|
18
|
+
failureCount: output.failures.length,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
5
21
|
/**
|
|
6
22
|
* Convert a full DailyDigest to the compact format for JSON output (#287).
|
|
7
23
|
* Category arrays become PR URL arrays; full objects stay only in openPRs.
|
|
@@ -43,6 +59,19 @@ export function compactRepoGroups(groups) {
|
|
|
43
59
|
prUrls: group.prs.map((pr) => pr.url),
|
|
44
60
|
}));
|
|
45
61
|
}
|
|
62
|
+
/**
|
|
63
|
+
* Convert a full StartupOutput to the compact format (#763).
|
|
64
|
+
* Uses destructuring to auto-forward any new fields added to StartupOutput.
|
|
65
|
+
*/
|
|
66
|
+
export function toCompactStartupOutput(output) {
|
|
67
|
+
const { daily, ...rest } = output;
|
|
68
|
+
return {
|
|
69
|
+
...rest,
|
|
70
|
+
daily: daily ? toCompactDailyOutput(daily) : undefined,
|
|
71
|
+
dashboardUrl: output.dashboardUrl,
|
|
72
|
+
issueList: output.issueList,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
46
75
|
/**
|
|
47
76
|
* Wrap data in a standard JSON output envelope
|
|
48
77
|
*/
|