@glrs-dev/cli 2.4.0 → 2.6.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.
Files changed (49) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/dist/{chunk-HQUCVJ4G.js → chunk-FBXSGZAA.js} +4 -0
  3. package/dist/chunk-J3FXSHMA.js +263 -0
  4. package/dist/{chunk-5ZVUFNCP.js → chunk-S6N5E2GG.js} +8 -1
  5. package/dist/{chunk-2VMFXAJH.js → chunk-UO7WHIKY.js} +18 -5
  6. package/dist/cli.js +10 -3
  7. package/dist/commands/autopilot-tui.d.ts +11 -1
  8. package/dist/commands/autopilot-tui.js +2 -1
  9. package/dist/commands/autopilot.d.ts +2 -0
  10. package/dist/commands/autopilot.js +62 -21
  11. package/dist/commands/debrief.d.ts +2 -0
  12. package/dist/commands/debrief.js +1 -1
  13. package/dist/commands/loop.d.ts +2 -0
  14. package/dist/commands/loop.js +33 -12
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.js +1 -1
  17. package/dist/node_modules/@glrs-dev/adapter-opencode/dist/index.d.ts +270 -0
  18. package/dist/node_modules/@glrs-dev/adapter-opencode/dist/index.js +506 -0
  19. package/dist/node_modules/@glrs-dev/adapter-opencode/package.json +8 -0
  20. package/dist/node_modules/@glrs-dev/autopilot/dist/auto-ship-EVLBKHUZ.js +7 -0
  21. package/dist/node_modules/@glrs-dev/autopilot/dist/changeset-generator-HAHYSSUR.js +15 -0
  22. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-2X3CWH47.js +3288 -0
  23. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-2ZQ6SBV3.js +70 -0
  24. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-6JZQLIRP.js +781 -0
  25. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-AWRK6S6G.js +91 -0
  26. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-BLEIZHET.js +101 -0
  27. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-GXXCEGDD.js +251 -0
  28. package/dist/node_modules/@glrs-dev/autopilot/dist/chunk-S34HOCZ4.js +44 -0
  29. package/dist/node_modules/@glrs-dev/autopilot/dist/index.d.ts +1915 -0
  30. package/dist/node_modules/@glrs-dev/autopilot/dist/index.js +768 -0
  31. package/dist/node_modules/@glrs-dev/autopilot/dist/logger-3XLFMXLN.js +8 -0
  32. package/dist/node_modules/@glrs-dev/autopilot/dist/loop-session-YLCVJGPV.js +9 -0
  33. package/dist/node_modules/@glrs-dev/autopilot/dist/plan-enrichment-4SQYV5FC.js +17 -0
  34. package/dist/node_modules/@glrs-dev/autopilot/package.json +8 -0
  35. package/dist/vendor/harness-opencode/dist/agents/prompts/agents-md-writer.md +1 -1
  36. package/dist/vendor/harness-opencode/dist/agents/prompts/architecture-advisor.md +1 -1
  37. package/dist/vendor/harness-opencode/dist/agents/prompts/code-searcher.md +1 -1
  38. package/dist/vendor/harness-opencode/dist/agents/prompts/docs-maintainer.md +0 -8
  39. package/dist/vendor/harness-opencode/dist/agents/prompts/gap-analyzer.md +1 -3
  40. package/dist/vendor/harness-opencode/dist/agents/prompts/lib-reader.md +1 -1
  41. package/dist/vendor/harness-opencode/dist/agents/prompts/plan-reviewer.md +0 -2
  42. package/dist/vendor/harness-opencode/dist/agents/prompts/plan.md +1 -1
  43. package/dist/vendor/harness-opencode/dist/agents/prompts/prime.md +78 -262
  44. package/dist/vendor/harness-opencode/dist/agents/prompts/research.md +5 -14
  45. package/dist/vendor/harness-opencode/dist/agents/prompts/scoper.md +7 -2
  46. package/dist/vendor/harness-opencode/dist/autopilot/strategies/default.md +29 -0
  47. package/dist/vendor/harness-opencode/dist/index.js +112 -82
  48. package/dist/vendor/harness-opencode/package.json +1 -1
  49. package/package.json +9 -7
@@ -0,0 +1,506 @@
1
+ // src/opencode-adapter.ts
2
+ import { execFile } from "child_process";
3
+ import { promisify } from "util";
4
+ import * as fs from "fs";
5
+ import {
6
+ createOpencode
7
+ } from "@opencode-ai/sdk";
8
+ var execFileP = promisify(execFile);
9
+ var DEFAULT_STARTUP_TIMEOUT_MS = 3e4;
10
+ async function ensureOpencodeOnPath() {
11
+ try {
12
+ await execFileP("opencode", ["--version"]);
13
+ } catch {
14
+ throw new Error(
15
+ "opencode CLI not found on PATH.\n Install: https://opencode.ai\n Or: bunx opencode upgrade"
16
+ );
17
+ }
18
+ }
19
+ async function startServer(opts) {
20
+ await ensureOpencodeOnPath();
21
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_STARTUP_TIMEOUT_MS;
22
+ const port = opts.port ?? 0;
23
+ const priorEnvValue = process.env["GLRS_AGENT_OVERRIDES"];
24
+ try {
25
+ if (opts.agentOverrides) {
26
+ process.env["GLRS_AGENT_OVERRIDES"] = JSON.stringify(opts.agentOverrides);
27
+ }
28
+ const { client, server } = await createOpencode({
29
+ port,
30
+ timeout: timeoutMs,
31
+ hostname: "127.0.0.1"
32
+ });
33
+ let shutdownCalled = false;
34
+ const shutdown = async () => {
35
+ if (shutdownCalled) return;
36
+ shutdownCalled = true;
37
+ try {
38
+ server.close();
39
+ } catch {
40
+ }
41
+ if (priorEnvValue === void 0) {
42
+ delete process.env["GLRS_AGENT_OVERRIDES"];
43
+ } else {
44
+ process.env["GLRS_AGENT_OVERRIDES"] = priorEnvValue;
45
+ }
46
+ };
47
+ return { url: server.url, client, shutdown };
48
+ } catch (err) {
49
+ if (priorEnvValue === void 0) {
50
+ delete process.env["GLRS_AGENT_OVERRIDES"];
51
+ } else {
52
+ process.env["GLRS_AGENT_OVERRIDES"] = priorEnvValue;
53
+ }
54
+ throw err;
55
+ }
56
+ }
57
+ async function selfTest(client) {
58
+ try {
59
+ await client.session.list();
60
+ } catch (err) {
61
+ throw new Error(
62
+ `OpenCode server self-test failed \u2014 the server started but isn't responding to API calls.
63
+ Error: ${err instanceof Error ? err.message : String(err)}
64
+ Run \`opencode --version\` to verify your installation.`
65
+ );
66
+ }
67
+ }
68
+ async function createSession(client, opts) {
69
+ const result = await client.session.create({
70
+ query: { directory: opts.cwd },
71
+ body: {}
72
+ });
73
+ if (!result.data) {
74
+ throw new Error("session.create returned no data");
75
+ }
76
+ return result.data.id;
77
+ }
78
+ async function sendAndWait(client, opts) {
79
+ const stallMs = opts.stallMs ?? 60 * 60 * 1e3;
80
+ const idlePromise = waitForIdle(client, {
81
+ sessionId: opts.sessionId,
82
+ stallMs,
83
+ abortSignal: opts.abortSignal,
84
+ onToolCall: opts.onToolCall,
85
+ onTextDelta: opts.onTextDelta,
86
+ onCostUpdate: opts.onCostUpdate,
87
+ autoRejectPermissions: opts.autoRejectPermissions,
88
+ serverUrl: opts.serverUrl,
89
+ onPermissionRejected: opts.onPermissionRejected
90
+ });
91
+ await client.session.prompt({
92
+ path: { id: opts.sessionId },
93
+ body: {
94
+ parts: [{ type: "text", text: opts.message }],
95
+ ...opts.agentName ? { agent: opts.agentName } : {}
96
+ }
97
+ });
98
+ return idlePromise;
99
+ }
100
+ async function waitForIdle(client, opts) {
101
+ const stallMs = opts.stallMs ?? 60 * 60 * 1e3;
102
+ const sse = await client.event.subscribe();
103
+ const reportedToolCalls = /* @__PURE__ */ new Set();
104
+ let sseFd = null;
105
+ if (process.env["GLRS_DEBUG_SSE"] === "1") {
106
+ try {
107
+ sseFd = fs.openSync("/tmp/glrs-sse.log", "a");
108
+ } catch {
109
+ }
110
+ }
111
+ const sseLog = (msg) => {
112
+ if (sseFd !== null) {
113
+ try {
114
+ fs.writeSync(sseFd, `${(/* @__PURE__ */ new Date()).toISOString()} ${msg}
115
+ `);
116
+ } catch {
117
+ }
118
+ }
119
+ };
120
+ return new Promise((resolve) => {
121
+ let stallTimer = null;
122
+ let settled = false;
123
+ let pollerRunning = true;
124
+ const settle = (result) => {
125
+ if (settled) return;
126
+ settled = true;
127
+ pollerRunning = false;
128
+ if (stallTimer) clearTimeout(stallTimer);
129
+ if (sseFd !== null) {
130
+ try {
131
+ fs.closeSync(sseFd);
132
+ } catch {
133
+ }
134
+ }
135
+ resolve(result);
136
+ };
137
+ const resetStall = () => {
138
+ if (stallTimer) clearTimeout(stallTimer);
139
+ stallTimer = setTimeout(() => settle({ kind: "stall", stallMs }), stallMs);
140
+ };
141
+ if (opts.abortSignal) {
142
+ if (opts.abortSignal.aborted) {
143
+ settle({ kind: "abort" });
144
+ return;
145
+ }
146
+ opts.abortSignal.addEventListener("abort", () => settle({ kind: "abort" }), { once: true });
147
+ }
148
+ resetStall();
149
+ if (opts.onToolCall) {
150
+ (async () => {
151
+ while (pollerRunning && !settled) {
152
+ await new Promise((r) => setTimeout(r, 2e3));
153
+ if (!pollerRunning || settled) break;
154
+ try {
155
+ const msgs = await client.session.messages({ path: { id: opts.sessionId } });
156
+ const messages = msgs?.data ?? msgs;
157
+ if (!Array.isArray(messages)) continue;
158
+ for (const msg of messages) {
159
+ if (msg?.info?.role !== "assistant") continue;
160
+ for (const part of msg?.parts ?? []) {
161
+ if (part?.type !== "tool") continue;
162
+ const callID = part.callID ?? part.id ?? "";
163
+ const status = part.state?.status ?? "";
164
+ const key = `${callID}:${status}`;
165
+ if (reportedToolCalls.has(key)) continue;
166
+ if (status !== "running" && status !== "completed" && status !== "error") continue;
167
+ reportedToolCalls.add(key);
168
+ resetStall();
169
+ const input = part.state?.input;
170
+ let firstArg;
171
+ if (input && typeof input === "object") {
172
+ for (const k of ["filePath", "file_path", "path", "command", "pattern", "query"]) {
173
+ const val = input[k];
174
+ if (typeof val === "string" && val.length > 0) {
175
+ firstArg = val.length > 80 ? val.slice(0, 77) + "..." : val;
176
+ break;
177
+ }
178
+ }
179
+ }
180
+ try {
181
+ opts.onToolCall(part.tool ?? "unknown", firstArg);
182
+ } catch {
183
+ }
184
+ }
185
+ if (opts.onCostUpdate && msg?.info?.cost > 0) {
186
+ const costKey = `cost:${msg.info.id}`;
187
+ if (!reportedToolCalls.has(costKey)) {
188
+ reportedToolCalls.add(costKey);
189
+ }
190
+ }
191
+ }
192
+ if (opts.onCostUpdate) {
193
+ let totalCost = 0;
194
+ let totalIn = 0;
195
+ let totalOut = 0;
196
+ for (const m of messages) {
197
+ if (m?.info?.role === "assistant" && typeof m.info.cost === "number") {
198
+ totalCost += m.info.cost;
199
+ const t = m.info.tokens ?? {};
200
+ totalIn += t.input ?? 0;
201
+ totalOut += t.output ?? 0;
202
+ }
203
+ }
204
+ if (totalCost > 0) {
205
+ const costKey = `cumcost:${totalCost.toFixed(6)}`;
206
+ if (!reportedToolCalls.has(costKey)) {
207
+ reportedToolCalls.add(costKey);
208
+ try {
209
+ opts.onCostUpdate(totalCost, { input: totalIn, output: totalOut });
210
+ } catch {
211
+ }
212
+ }
213
+ }
214
+ }
215
+ } catch {
216
+ }
217
+ }
218
+ })();
219
+ }
220
+ (async () => {
221
+ try {
222
+ for await (const event of sse.stream) {
223
+ if (settled) break;
224
+ const ev = event;
225
+ const props = ev.properties ?? {};
226
+ const type = ev.type ?? "";
227
+ sseLog(`[SSE] type=${type}`);
228
+ if (opts.onCostUpdate && type === "message.updated") {
229
+ const info = props["info"];
230
+ if (info && info.role === "assistant" && typeof info.cost === "number") {
231
+ resetStall();
232
+ try {
233
+ opts.onCostUpdate(
234
+ info.cost,
235
+ {
236
+ input: info.tokens?.input ?? 0,
237
+ output: info.tokens?.output ?? 0
238
+ }
239
+ );
240
+ } catch {
241
+ }
242
+ }
243
+ }
244
+ if (opts.onTextDelta && (type === "message.part.delta" || type === "message.part.updated")) {
245
+ const delta = props["delta"];
246
+ if (typeof delta === "string" && delta.length > 0) {
247
+ resetStall();
248
+ try {
249
+ opts.onTextDelta(delta.length);
250
+ } catch {
251
+ }
252
+ }
253
+ }
254
+ if (opts.onToolCall && (type === "message.part.updated" || type === "message.part.delta")) {
255
+ const part = props["part"];
256
+ if (part && part.type === "tool" && part.sessionID === opts.sessionId) {
257
+ const status = part.state?.status;
258
+ const hasCallId = !!part.callID;
259
+ const hasTool = !!part.tool;
260
+ const shouldFire = hasCallId && hasTool && !reportedToolCalls.has(part.callID) && (status === "completed" || // A: tool finished
261
+ status === "running" || // B: tool started
262
+ status === void 0);
263
+ sseLog(`[TOOL] type=${part.type} status=${status} tool=${part.tool} sessionMatch=${part.sessionID === opts.sessionId} callID=${part.callID} shouldFire=${shouldFire}`);
264
+ if (shouldFire) {
265
+ reportedToolCalls.add(part.callID);
266
+ resetStall();
267
+ let firstArg;
268
+ const input = part.state?.input;
269
+ if (input) {
270
+ const argKeys = ["filePath", "file_path", "path", "command", "pattern", "query"];
271
+ for (const key of argKeys) {
272
+ const val = input[key];
273
+ if (typeof val === "string" && val.length > 0) {
274
+ firstArg = val.length > 80 ? val.slice(0, 77) + "..." : val;
275
+ break;
276
+ }
277
+ }
278
+ }
279
+ try {
280
+ opts.onToolCall(part.tool ?? "unknown", firstArg);
281
+ } catch {
282
+ }
283
+ continue;
284
+ }
285
+ }
286
+ }
287
+ const eventSessionId = props["sessionID"];
288
+ if (opts.autoRejectPermissions && (type === "permission.updated" || type === "question.asked")) {
289
+ const permissionId = props["id"];
290
+ const permissionType = type === "question.asked" ? "question" : props["type"] ?? "unknown";
291
+ const permissionTitle = props["title"] ?? "";
292
+ if (opts.onPermissionRejected) {
293
+ try {
294
+ opts.onPermissionRejected({
295
+ id: permissionId ?? "unknown",
296
+ type: permissionType,
297
+ title: permissionTitle
298
+ });
299
+ } catch {
300
+ }
301
+ }
302
+ if (type === "permission.updated" && permissionId) {
303
+ (async () => {
304
+ try {
305
+ await client.postSessionIdPermissionsPermissionId({
306
+ path: { id: opts.sessionId, permissionID: permissionId },
307
+ body: { response: "reject" }
308
+ });
309
+ } catch {
310
+ }
311
+ })();
312
+ continue;
313
+ }
314
+ if (type === "question.asked") {
315
+ if (opts.serverUrl && permissionId) {
316
+ (async () => {
317
+ try {
318
+ await fetch(`${opts.serverUrl}/question/${permissionId}/reject`, {
319
+ method: "POST"
320
+ });
321
+ } catch {
322
+ }
323
+ })();
324
+ }
325
+ settle({
326
+ kind: "question_rejected",
327
+ title: permissionTitle
328
+ });
329
+ break;
330
+ }
331
+ }
332
+ if (eventSessionId !== opts.sessionId) continue;
333
+ resetStall();
334
+ if (type === "session.idle") {
335
+ settle({ kind: "idle" });
336
+ break;
337
+ }
338
+ if (type === "session.error") {
339
+ const msg = props["message"] ?? "session error";
340
+ sseLog(`[ERROR] session.error: ${msg} props=${JSON.stringify(props)}`);
341
+ settle({ kind: "error", message: msg });
342
+ break;
343
+ }
344
+ }
345
+ } catch (err) {
346
+ if (!settled) {
347
+ settle({ kind: "error", message: err instanceof Error ? err.message : String(err) });
348
+ }
349
+ }
350
+ })();
351
+ });
352
+ }
353
+ async function getSessionCost(client, sessionId) {
354
+ const stats = await getSessionStats(client, sessionId);
355
+ return stats.cost;
356
+ }
357
+ async function getSessionStats(client, sessionId) {
358
+ try {
359
+ const result = await client.session.messages({ path: { id: sessionId } });
360
+ if (!result.data) {
361
+ return { cost: 0, tokensIn: 0, tokensOut: 0 };
362
+ }
363
+ const messages = result.data;
364
+ let cost = 0;
365
+ let tokensIn = 0;
366
+ let tokensOut = 0;
367
+ for (const m of messages) {
368
+ if (m.info.role === "assistant") {
369
+ if (typeof m.info.cost === "number") cost += m.info.cost;
370
+ if (m.info.tokens) {
371
+ tokensIn += m.info.tokens.input ?? 0;
372
+ tokensOut += m.info.tokens.output ?? 0;
373
+ }
374
+ }
375
+ }
376
+ return { cost, tokensIn, tokensOut };
377
+ } catch {
378
+ return { cost: 0, tokensIn: 0, tokensOut: 0 };
379
+ }
380
+ }
381
+ async function getLastAssistantMessage(client, sessionId) {
382
+ try {
383
+ const result = await client.session.messages({ path: { id: sessionId } });
384
+ if (!result.data) return "";
385
+ const messages = result.data;
386
+ const assistantMessages = messages.filter((m) => m.info.role === "assistant");
387
+ if (assistantMessages.length === 0) return "";
388
+ const last = assistantMessages[assistantMessages.length - 1];
389
+ if (!last) return "";
390
+ return last.parts.filter((p) => p.type === "text" && typeof p.text === "string").map((p) => p.text).join("");
391
+ } catch {
392
+ return "";
393
+ }
394
+ }
395
+
396
+ // src/server-error-extractor.ts
397
+ import { readdirSync, readFileSync } from "fs";
398
+ import { join } from "path";
399
+ import { homedir } from "os";
400
+ var OPENCODE_LOG_DIR = join(homedir(), ".local", "share", "opencode", "log");
401
+ function extractServerError() {
402
+ try {
403
+ const entries = readdirSync(OPENCODE_LOG_DIR).filter((f) => f.endsWith(".log")).sort().reverse();
404
+ if (entries.length === 0) return null;
405
+ const logPath = join(OPENCODE_LOG_DIR, entries[0]);
406
+ const content = readFileSync(logPath, "utf-8");
407
+ const lines = content.split("\n").reverse();
408
+ for (const line of lines) {
409
+ if (line.includes("service=session.processor") && line.includes("error=")) {
410
+ const match = line.match(/error=(.+?)(?:\s+stack=|$)/);
411
+ if (match) return match[1].trim();
412
+ }
413
+ if (line.includes("service=llm") && line.includes("error=")) {
414
+ if (line.includes('error={"error":{}}')) continue;
415
+ const match = line.match(/error=(.+?)(?:\s+stream|$)/);
416
+ if (match) return match[1].trim();
417
+ }
418
+ }
419
+ return null;
420
+ } catch {
421
+ return null;
422
+ }
423
+ }
424
+ function enhanceSessionError(genericMessage) {
425
+ if (genericMessage !== "session error" && genericMessage.length > 20) {
426
+ return genericMessage;
427
+ }
428
+ const detail = extractServerError();
429
+ if (detail) return detail;
430
+ return genericMessage;
431
+ }
432
+
433
+ // src/opencode-adapter-class.ts
434
+ var OpenCodeHandle = class {
435
+ id;
436
+ server;
437
+ cwd;
438
+ constructor(server, cwd) {
439
+ this.server = server;
440
+ this.cwd = cwd;
441
+ this.id = server.url;
442
+ }
443
+ };
444
+ var OpenCodeAdapter = class {
445
+ name = "opencode";
446
+ async start(opts) {
447
+ const server = await startServer({ cwd: opts.cwd, agentOverrides: opts.agents });
448
+ return new OpenCodeHandle(server, opts.cwd);
449
+ }
450
+ async createSession(handle, opts) {
451
+ const h = handle;
452
+ return createSession(h.server.client, {
453
+ cwd: h.cwd,
454
+ agentName: opts.agentName
455
+ });
456
+ }
457
+ async sendAndWait(handle, opts) {
458
+ const h = handle;
459
+ const result = await sendAndWait(h.server.client, {
460
+ sessionId: opts.sessionId,
461
+ message: opts.message,
462
+ stallMs: opts.stallMs,
463
+ abortSignal: opts.abortSignal,
464
+ onToolCall: opts.onToolCall,
465
+ onTextDelta: opts.onTextDelta,
466
+ onCostUpdate: opts.onCostUpdate,
467
+ autoRejectPermissions: true,
468
+ serverUrl: h.server.url
469
+ });
470
+ return result;
471
+ }
472
+ async getLastResponse(handle, sessionId) {
473
+ const h = handle;
474
+ return getLastAssistantMessage(h.server.client, sessionId);
475
+ }
476
+ async getSessionCost(handle, sessionId) {
477
+ const h = handle;
478
+ return getSessionCost(h.server.client, sessionId);
479
+ }
480
+ async getSessionStats(handle, sessionId) {
481
+ const h = handle;
482
+ return getSessionStats(h.server.client, sessionId);
483
+ }
484
+ async shutdown(handle) {
485
+ const h = handle;
486
+ await h.server.shutdown();
487
+ }
488
+ async enhanceError(message) {
489
+ return enhanceSessionError(message);
490
+ }
491
+ };
492
+ export {
493
+ DEFAULT_STARTUP_TIMEOUT_MS,
494
+ OpenCodeAdapter,
495
+ createSession,
496
+ enhanceSessionError,
497
+ execFileP,
498
+ extractServerError,
499
+ getLastAssistantMessage,
500
+ getSessionCost,
501
+ getSessionStats,
502
+ selfTest,
503
+ sendAndWait,
504
+ startServer,
505
+ waitForIdle
506
+ };
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "@glrs-dev/adapter-opencode",
3
+ "version": "0.1.1",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts"
8
+ }
@@ -0,0 +1,7 @@
1
+ import {
2
+ autoShip
3
+ } from "./chunk-BLEIZHET.js";
4
+ import "./chunk-GXXCEGDD.js";
5
+ export {
6
+ autoShip
7
+ };
@@ -0,0 +1,15 @@
1
+ import {
2
+ generateChangeset,
3
+ inferBumpLevel,
4
+ readPlanGoal,
5
+ readPlanTitle,
6
+ slugifyTitle
7
+ } from "./chunk-AWRK6S6G.js";
8
+ import "./chunk-GXXCEGDD.js";
9
+ export {
10
+ generateChangeset,
11
+ inferBumpLevel,
12
+ readPlanGoal,
13
+ readPlanTitle,
14
+ slugifyTitle
15
+ };