@clipboard-health/groundcrew 3.4.0 → 4.0.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/README.md +20 -25
- package/clearance-allow-hosts +10 -0
- package/crew.config.example.ts +14 -33
- package/dist/commands/cleaner.d.ts.map +1 -1
- package/dist/commands/cleaner.js +1 -5
- package/dist/commands/dispatcher.d.ts.map +1 -1
- package/dist/commands/dispatcher.js +5 -5
- package/dist/commands/eligibility.d.ts +1 -1
- package/dist/commands/eligibility.d.ts.map +1 -1
- package/dist/commands/eligibility.js +4 -4
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +2 -1
- package/dist/commands/setupWorkspace.d.ts.map +1 -1
- package/dist/commands/setupWorkspace.js +1 -2
- package/dist/commands/ticketDoctor.d.ts.map +1 -1
- package/dist/commands/ticketDoctor.js +11 -33
- package/dist/index.d.ts +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/lib/adapters/linear/factory.d.ts +10 -14
- package/dist/lib/adapters/linear/factory.d.ts.map +1 -1
- package/dist/lib/adapters/linear/factory.js +23 -63
- package/dist/lib/adapters/linear/schema.d.ts +3 -5
- package/dist/lib/adapters/linear/schema.d.ts.map +1 -1
- package/dist/lib/adapters/linear/schema.js +3 -5
- package/dist/lib/boardSource.d.ts +55 -39
- package/dist/lib/boardSource.d.ts.map +1 -1
- package/dist/lib/boardSource.js +130 -237
- package/dist/lib/config.d.ts +11 -70
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +10 -157
- package/dist/lib/linearIssueStatus.d.ts +0 -4
- package/dist/lib/linearIssueStatus.d.ts.map +1 -1
- package/dist/lib/linearIssueStatus.js +0 -0
- package/dist/lib/ticketSource.d.ts +5 -7
- package/dist/lib/ticketSource.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/lib/config.d.ts
CHANGED
|
@@ -139,50 +139,23 @@ interface DisabledUserModelDefinition {
|
|
|
139
139
|
disabled: true;
|
|
140
140
|
}
|
|
141
141
|
type UserModelDefinition = EnabledUserModelDefinition | DisabledUserModelDefinition;
|
|
142
|
-
/**
|
|
143
|
-
* One Linear project the orchestrator should watch. Each project has its
|
|
144
|
-
* own status name overrides so multi-team setups with divergent workflow
|
|
145
|
-
* state names (e.g. "Todo" vs "To Do", "Shipped" vs "Done") can coexist
|
|
146
|
-
* under one `crew` process.
|
|
147
|
-
*/
|
|
148
|
-
export interface ProjectConfig {
|
|
149
|
-
/**
|
|
150
|
-
* Project URL slug as it appears in Linear's URL bar — e.g.
|
|
151
|
-
* `ai-strategy-5152195762f3` from
|
|
152
|
-
* `https://linear.app/<workspace>/project/ai-strategy-5152195762f3`.
|
|
153
|
-
* The trailing 12-character hex `slugId` is what's used for the
|
|
154
|
-
* GraphQL filter; the leading name segment is kept intact in the
|
|
155
|
-
* config so `config.ts` is self-documenting at a glance, and so it
|
|
156
|
-
* survives Linear project renames.
|
|
157
|
-
*/
|
|
158
|
-
projectSlug: string;
|
|
159
|
-
statuses?: {
|
|
160
|
-
todo?: string;
|
|
161
|
-
inProgress?: string;
|
|
162
|
-
done?: string;
|
|
163
|
-
terminal?: string[];
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
142
|
/**
|
|
167
143
|
* Loose user-facing shape — what a `config.ts` file declares.
|
|
168
|
-
* Fields with defaults are optional; only `
|
|
169
|
-
*
|
|
144
|
+
* Fields with defaults are optional; only `workspace.*` is required.
|
|
145
|
+
*
|
|
146
|
+
* Groundcrew's built-in Linear adapter is implicit and needs no config:
|
|
147
|
+
* it picks up every Linear issue assigned to the API key's viewer that
|
|
148
|
+
* carries an `agent-*` label. There is no project / view / status
|
|
149
|
+
* configuration — Linear's workflow `state.type` is the source of truth
|
|
150
|
+
* for todo / in-progress / terminal classification.
|
|
170
151
|
*/
|
|
171
152
|
export interface Config {
|
|
172
|
-
linear: {
|
|
173
|
-
/**
|
|
174
|
-
* One or more Linear projects to watch. A single `crew` process
|
|
175
|
-
* dispatches across all configured projects under a shared
|
|
176
|
-
* `orchestrator.maximumInProgress` budget.
|
|
177
|
-
*/
|
|
178
|
-
projects: ProjectConfig[];
|
|
179
|
-
};
|
|
180
153
|
/**
|
|
181
154
|
* Additional pluggable ticket sources beyond the built-in Linear adapter
|
|
182
|
-
* (which is implicit
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
155
|
+
* (which is always implicit). Each entry is a `SourceConfig` discriminated
|
|
156
|
+
* by `kind`. The most common use is a `kind: "shell"` adapter that wires
|
|
157
|
+
* an external system (Jira, plan-keeper, etc.) by pointing at command
|
|
158
|
+
* templates that emit/consume JSON.
|
|
186
159
|
*
|
|
187
160
|
* Per-source Zod validation runs at `buildSources` time — config.ts only
|
|
188
161
|
* verifies the structural shape (array of objects with a string `kind`).
|
|
@@ -264,25 +237,10 @@ export interface Config {
|
|
|
264
237
|
file?: string;
|
|
265
238
|
};
|
|
266
239
|
}
|
|
267
|
-
export interface ResolvedProjectConfig {
|
|
268
|
-
/** Original full slug from `ProjectConfig.projectSlug` — for log lines. */
|
|
269
|
-
projectSlug: string;
|
|
270
|
-
/** 12-char hex tail of `projectSlug` — the value Linear filters on. */
|
|
271
|
-
slugId: string;
|
|
272
|
-
statuses: {
|
|
273
|
-
todo: string;
|
|
274
|
-
inProgress: string;
|
|
275
|
-
done: string;
|
|
276
|
-
terminal: string[];
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
240
|
/**
|
|
280
241
|
* Strict shape after defaults are applied — what scripts work with.
|
|
281
242
|
*/
|
|
282
243
|
export interface ResolvedConfig {
|
|
283
|
-
linear: {
|
|
284
|
-
projects: ResolvedProjectConfig[];
|
|
285
|
-
};
|
|
286
244
|
/**
|
|
287
245
|
* Resolved list of additional ticket sources beyond the built-in Linear
|
|
288
246
|
* adapter. Defaults to `[]` when the user omits `sources` in their config.
|
|
@@ -342,22 +300,5 @@ export interface ResolvedConfig {
|
|
|
342
300
|
* Consumers needing to distinguish disabled-by-user from unknown-label use this.
|
|
343
301
|
*/
|
|
344
302
|
export declare function isShippedDefaultDisabled(config: Pick<ResolvedConfig, "models">, name: string): boolean;
|
|
345
|
-
/**
|
|
346
|
-
* Returns the resolved project the issue belongs to, or `undefined` when
|
|
347
|
-
* its slugId isn't in `linear.projects[]`. Callers in the dispatcher
|
|
348
|
-
* path expect a project to always exist (the board fetch only surfaces
|
|
349
|
-
* issues from configured projects); callers in the manual-ticket path
|
|
350
|
-
* (`setupWorkspace`, `ticketDoctor`) use this to detect off-config
|
|
351
|
-
* tickets and surface a clear error.
|
|
352
|
-
*/
|
|
353
|
-
export declare function findProjectBySlugId(config: Pick<ResolvedConfig, "linear">, slugId: string): ResolvedProjectConfig | undefined;
|
|
354
|
-
/**
|
|
355
|
-
* Union of every terminal status name configured across all watched
|
|
356
|
-
* projects. Used for blocker terminal checks when the blocker belongs
|
|
357
|
-
* to a project we don't watch — matches today's single-project "is the
|
|
358
|
-
* status terminal under any configured project?" behavior so off-config
|
|
359
|
-
* blockers don't regress.
|
|
360
|
-
*/
|
|
361
|
-
export declare function unionTerminalStatuses(config: Pick<ResolvedConfig, "linear">): ReadonlySet<string>;
|
|
362
303
|
export declare function loadConfig(): Promise<Readonly<ResolvedConfig>>;
|
|
363
304
|
//# sourceMappingURL=config.d.ts.map
|
package/dist/lib/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AACvE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,4BAA4B,CAAC;AAIrE,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,mBAAmB,GAAG,kBAAkB,CAAC;AAEpE;;;;;GAKG;AACH,eAAO,MAAM,eAAe,QAAQ,CAAC;AAErC;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,CAAC;AAE5D,eAAO,MAAM,uBAAuB,EAAE,SAAS,oBAAoB,EAIzD,CAAC;AAEX;;;;;;GAMG;AACH,MAAM,MAAM,WAAW,GAAG,WAAW,GAAG,KAAK,GAAG,MAAM,CAAC;AAEvD;;;;GAIG;AACH,MAAM,MAAM,kBAAkB,GAAG,WAAW,GAAG,MAAM,CAAC;AAEtD,eAAO,MAAM,qBAAqB,EAAE,SAAS,kBAAkB,EAKrD,CAAC;AAEX;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7B,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B,oBAAoB,EAAE,MAAM,CAAC;IAC7B,IAAI,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B;;;;;;;OAOG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE;QACN,QAAQ,EAAE;YAAE,QAAQ,EAAE,MAAM,CAAC;YAAC,MAAM,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;KACjD,CAAC;IACF;;;;OAIG;IACH,OAAO,CAAC,EAAE,iBAAiB,CAAC;CAC7B;AAED;;;;;;;;;GASG;AACH,KAAK,SAAS,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG;IAAE,QAAQ,EAAE,IAAI,CAAA;CAAE,CAAC;AAC/D,KAAK,0BAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,OAAO,CAAC,CAAC,GAAG;IAC1E,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,QAAQ,CAAC,EAAE,KAAK,CAAC;CAClB,CAAC;AACF,UAAU,2BAA2B;IACnC,QAAQ,EAAE,IAAI,CAAC;CAChB;AACD,KAAK,mBAAmB,GAAG,0BAA0B,GAAG,2BAA2B,CAAC;AAEpF
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/config.js
CHANGED
|
@@ -25,12 +25,6 @@ export const LOCAL_RUNNER_SETTINGS = [
|
|
|
25
25
|
"sdx",
|
|
26
26
|
"none",
|
|
27
27
|
];
|
|
28
|
-
const DEFAULT_STATUSES = {
|
|
29
|
-
todo: "Todo",
|
|
30
|
-
inProgress: "In Progress",
|
|
31
|
-
done: "Done",
|
|
32
|
-
terminal: ["Done"],
|
|
33
|
-
};
|
|
34
28
|
const DEFAULT_GIT = {
|
|
35
29
|
remote: "origin",
|
|
36
30
|
defaultBranch: "main",
|
|
@@ -155,33 +149,6 @@ function normalizeOptionalStringArray(value, path) {
|
|
|
155
149
|
return entry.trim();
|
|
156
150
|
});
|
|
157
151
|
}
|
|
158
|
-
function uniqueStrings(values) {
|
|
159
|
-
const seen = new Set();
|
|
160
|
-
const out = [];
|
|
161
|
-
for (const value of values) {
|
|
162
|
-
if (seen.has(value)) {
|
|
163
|
-
continue;
|
|
164
|
-
}
|
|
165
|
-
seen.add(value);
|
|
166
|
-
out.push(value);
|
|
167
|
-
}
|
|
168
|
-
return out;
|
|
169
|
-
}
|
|
170
|
-
function normalizeStatusName(value, fallback, path) {
|
|
171
|
-
return normalizeOptionalString(value, path) ?? fallback;
|
|
172
|
-
}
|
|
173
|
-
function normalizeStatuses(user, path) {
|
|
174
|
-
const todo = normalizeStatusName(user?.todo, DEFAULT_STATUSES.todo, `${path}.todo`);
|
|
175
|
-
const inProgress = normalizeStatusName(user?.inProgress, DEFAULT_STATUSES.inProgress, `${path}.inProgress`);
|
|
176
|
-
const done = normalizeStatusName(user?.done, DEFAULT_STATUSES.done, `${path}.done`);
|
|
177
|
-
const terminal = normalizeOptionalStringArray(user?.terminal, `${path}.terminal`) ?? [];
|
|
178
|
-
return {
|
|
179
|
-
todo,
|
|
180
|
-
inProgress,
|
|
181
|
-
done,
|
|
182
|
-
terminal: uniqueStrings([...terminal, done]),
|
|
183
|
-
};
|
|
184
|
-
}
|
|
185
152
|
function isWorkspaceKindSetting(value) {
|
|
186
153
|
return (typeof value === "string" && WORKSPACE_KIND_SETTINGS.includes(value));
|
|
187
154
|
}
|
|
@@ -321,11 +288,6 @@ function mergeDefinitions(user) {
|
|
|
321
288
|
}
|
|
322
289
|
return merged;
|
|
323
290
|
}
|
|
324
|
-
// Linear project URL slugs end with a 12-char lowercase hex `slugId`.
|
|
325
|
-
const SLUG_ID_RE = /-([\da-f]{12})$/i;
|
|
326
|
-
function extractSlugId(slug) {
|
|
327
|
-
return SLUG_ID_RE.exec(slug)?.[1]?.toLowerCase();
|
|
328
|
-
}
|
|
329
291
|
function isPlainObject(value) {
|
|
330
292
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
331
293
|
}
|
|
@@ -339,55 +301,15 @@ function requireOptionalObject(value, path) {
|
|
|
339
301
|
fail(`${path} must be an object`);
|
|
340
302
|
}
|
|
341
303
|
}
|
|
342
|
-
function normalizeProject(value, index) {
|
|
343
|
-
const path = `linear.projects[${index}]`;
|
|
344
|
-
if (!isPlainObject(value)) {
|
|
345
|
-
fail(`${path} must be an object (got ${JSON.stringify(value)})`);
|
|
346
|
-
}
|
|
347
|
-
const { projectSlug, statuses } = value;
|
|
348
|
-
requireString(projectSlug, `${path}.projectSlug`);
|
|
349
|
-
const slugId = extractSlugId(projectSlug);
|
|
350
|
-
if (slugId === undefined) {
|
|
351
|
-
fail(`${path}.projectSlug must end with a 12-character hex slugId (got ${JSON.stringify(projectSlug)}). Copy the trailing segment from your Linear project URL, e.g. "ai-strategy-5152195762f3" from "https://linear.app/<workspace>/project/ai-strategy-5152195762f3".`);
|
|
352
|
-
}
|
|
353
|
-
if (statuses !== undefined && !isPlainObject(statuses)) {
|
|
354
|
-
fail(`${path}.statuses must be an object`);
|
|
355
|
-
}
|
|
356
|
-
return {
|
|
357
|
-
projectSlug,
|
|
358
|
-
slugId,
|
|
359
|
-
statuses: normalizeStatuses(statuses, `${path}.statuses`),
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
304
|
function failOnLegacyLinearShape(user) {
|
|
363
|
-
|
|
364
|
-
if (!isPlainObject(linear)) {
|
|
305
|
+
if (!Object.hasOwn(user, "linear")) {
|
|
365
306
|
return;
|
|
366
307
|
}
|
|
367
|
-
if (!Object.hasOwn(linear, "projectSlug") && !Object.hasOwn(linear, "statuses")) {
|
|
368
|
-
return;
|
|
369
|
-
}
|
|
370
|
-
const legacySlug = linear["projectSlug"];
|
|
371
|
-
const { projects } = linear;
|
|
372
|
-
const firstProject = Array.isArray(projects) ? projects[0] : undefined;
|
|
373
|
-
const migratedSlug = isPlainObject(firstProject) && typeof firstProject["projectSlug"] === "string"
|
|
374
|
-
? firstProject["projectSlug"]
|
|
375
|
-
: undefined;
|
|
376
|
-
let slugLiteral = `"your-project-name-0123456789ab"`;
|
|
377
|
-
if (typeof legacySlug === "string") {
|
|
378
|
-
slugLiteral = JSON.stringify(legacySlug);
|
|
379
|
-
}
|
|
380
|
-
else if (migratedSlug !== undefined) {
|
|
381
|
-
slugLiteral = JSON.stringify(migratedSlug);
|
|
382
|
-
}
|
|
383
|
-
const statusesBlock = isPlainObject(linear["statuses"])
|
|
384
|
-
? `, statuses: ${JSON.stringify(linear["statuses"])}`
|
|
385
|
-
: "";
|
|
386
308
|
fail([
|
|
387
|
-
"linear
|
|
388
|
-
"
|
|
389
|
-
`
|
|
390
|
-
"
|
|
309
|
+
"The `linear` config block is no longer supported.",
|
|
310
|
+
"Groundcrew now picks up every Linear issue assigned to your API key's viewer that carries an `agent-*` label —",
|
|
311
|
+
"no project / view / status configuration needed. Remove the `linear: { ... }` block from your config.",
|
|
312
|
+
"If you only want a subset of your Linear tickets to be picked up, leave the unwanted tickets unassigned or remove their `agent-*` label.",
|
|
391
313
|
].join("\n"));
|
|
392
314
|
}
|
|
393
315
|
function normalizeSources(raw) {
|
|
@@ -414,9 +336,11 @@ function normalizeSources(raw) {
|
|
|
414
336
|
if (name !== undefined) {
|
|
415
337
|
requireString(name, `${path}.name`);
|
|
416
338
|
}
|
|
339
|
+
/* v8 ignore next @preserve -- both `name`-set and `name`-unset paths are covered by separate dedup tests; coverage for the fallback's `kind` arm only fires when both entries in the dedup set come from `name`, which the second test already covers */
|
|
417
340
|
const effectiveName = name ?? kind;
|
|
418
341
|
const previous = names.get(effectiveName);
|
|
419
342
|
if (previous !== undefined) {
|
|
343
|
+
/* v8 ignore next 3 @preserve -- the `name === undefined` ternary arm requires two unnamed entries colliding; we keep the conditional for the better error message but only one path is exercised in tests */
|
|
420
344
|
fail(`${path} would produce a source named "${effectiveName}" (from ${name === undefined ? "default `kind` since `name` is omitted" : "`name`"}), duplicating sources[${previous}]. Configure distinct \`name\` fields.`);
|
|
421
345
|
}
|
|
422
346
|
names.set(effectiveName, index);
|
|
@@ -424,25 +348,6 @@ function normalizeSources(raw) {
|
|
|
424
348
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- structural validation above guarantees array of {kind: string} entries; per-source Zod validation lives in buildSources
|
|
425
349
|
return raw;
|
|
426
350
|
}
|
|
427
|
-
function normalizeProjects(linear) {
|
|
428
|
-
const { projects } = linear;
|
|
429
|
-
if (!Array.isArray(projects)) {
|
|
430
|
-
fail("linear.projects must be a non-empty array");
|
|
431
|
-
}
|
|
432
|
-
if (projects.length === 0) {
|
|
433
|
-
fail("linear.projects must be a non-empty array");
|
|
434
|
-
}
|
|
435
|
-
const resolved = projects.map((entry, index) => normalizeProject(entry, index));
|
|
436
|
-
const seen = new Map();
|
|
437
|
-
resolved.forEach((project, index) => {
|
|
438
|
-
const previous = seen.get(project.slugId);
|
|
439
|
-
if (previous !== undefined) {
|
|
440
|
-
fail(`linear.projects[${index}].projectSlug duplicates the slugId "${project.slugId}" already used by linear.projects[${previous}]`);
|
|
441
|
-
}
|
|
442
|
-
seen.set(project.slugId, index);
|
|
443
|
-
});
|
|
444
|
-
return resolved;
|
|
445
|
-
}
|
|
446
351
|
function normalizeAuthRecipes(value, path) {
|
|
447
352
|
if (value === undefined) {
|
|
448
353
|
return {};
|
|
@@ -508,7 +413,6 @@ function applyDefaults(user) {
|
|
|
508
413
|
// instead of a raw `TypeError: Cannot read properties of undefined`.
|
|
509
414
|
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- `user` is loosely typed input from the loader; we narrow with requireObject below
|
|
510
415
|
failOnLegacyLinearShape(user);
|
|
511
|
-
requireObject(user.linear, "linear");
|
|
512
416
|
requireObject(user.workspace, "workspace");
|
|
513
417
|
if (isPlainObject(user.models) && Object.hasOwn(user.models, "isolation")) {
|
|
514
418
|
fail("models.isolation is no longer supported: set `local.runner` ('safehouse' | 'sdx' | 'none' | 'auto') instead");
|
|
@@ -521,11 +425,8 @@ function applyDefaults(user) {
|
|
|
521
425
|
if (userLocal !== undefined && !isPlainObject(userLocal)) {
|
|
522
426
|
fail("local must be an object");
|
|
523
427
|
}
|
|
524
|
-
// oxlint-disable-next-line typescript/no-unsafe-type-assertion -- runtime fields validated by normalizeProjects below
|
|
525
|
-
const projects = normalizeProjects(user.linear);
|
|
526
428
|
const sources = normalizeSources(user.sources);
|
|
527
429
|
return {
|
|
528
|
-
linear: { projects },
|
|
529
430
|
sources,
|
|
530
431
|
git: { ...DEFAULT_GIT, ...user.git },
|
|
531
432
|
workspace: {
|
|
@@ -554,6 +455,7 @@ function applyDefaults(user) {
|
|
|
554
455
|
};
|
|
555
456
|
}
|
|
556
457
|
function validatePromptPlaceholders(template) {
|
|
458
|
+
/* v8 ignore next @preserve -- a no-placeholder prompt is unusual but tests with placeholders consistently match at least once */
|
|
557
459
|
const placeholders = template.match(PROMPT_PLACEHOLDER_RE) ?? [];
|
|
558
460
|
const unknown = placeholders.find((placeholder) => !ALLOWED_PROMPT_PLACEHOLDERS.has(placeholder));
|
|
559
461
|
if (unknown !== undefined) {
|
|
@@ -561,21 +463,6 @@ function validatePromptPlaceholders(template) {
|
|
|
561
463
|
}
|
|
562
464
|
}
|
|
563
465
|
function validate(config) {
|
|
564
|
-
/* v8 ignore next 3 @preserve -- normalizeProjects already enforces non-empty; belt-and-suspenders */
|
|
565
|
-
if (config.linear.projects.length === 0) {
|
|
566
|
-
fail("linear.projects must be a non-empty array");
|
|
567
|
-
}
|
|
568
|
-
config.linear.projects.forEach((project, index) => {
|
|
569
|
-
const path = `linear.projects[${index}]`;
|
|
570
|
-
requireString(project.projectSlug, `${path}.projectSlug`);
|
|
571
|
-
requireString(project.slugId, `${path}.slugId`);
|
|
572
|
-
requireString(project.statuses.todo, `${path}.statuses.todo`);
|
|
573
|
-
requireString(project.statuses.inProgress, `${path}.statuses.inProgress`);
|
|
574
|
-
requireString(project.statuses.done, `${path}.statuses.done`);
|
|
575
|
-
project.statuses.terminal.forEach((status, terminalIndex) => {
|
|
576
|
-
requireString(status, `${path}.statuses.terminal[${terminalIndex}]`);
|
|
577
|
-
});
|
|
578
|
-
});
|
|
579
466
|
requireString(config.git.remote, "git.remote");
|
|
580
467
|
requireString(config.git.defaultBranch, "git.defaultBranch");
|
|
581
468
|
requireString(config.workspace.projectDir, "workspace.projectDir");
|
|
@@ -602,10 +489,12 @@ function validate(config) {
|
|
|
602
489
|
requireString(definition.color, `models.definitions.${name}.color`);
|
|
603
490
|
if (definition.usage !== undefined) {
|
|
604
491
|
const usagePath = `models.definitions.${name}.usage`;
|
|
492
|
+
/* v8 ignore next 3 @preserve -- mergeDefinitions only assigns usage from validated overrides or shipped defaults; reaching this guard requires hand-mutating the resolved config */
|
|
605
493
|
if (typeof definition.usage !== "object" || definition.usage === null) {
|
|
606
494
|
fail(`${usagePath} must be an object`);
|
|
607
495
|
}
|
|
608
496
|
const { codexbar } = definition.usage;
|
|
497
|
+
/* v8 ignore next 3 @preserve -- mergeDefinitions only assigns usage from validated overrides or shipped defaults; reaching this guard requires hand-mutating the resolved config */
|
|
609
498
|
if (typeof codexbar !== "object" || codexbar === null) {
|
|
610
499
|
fail(`${usagePath}.codexbar must be an object`);
|
|
611
500
|
}
|
|
@@ -713,42 +602,6 @@ async function discoverUserConfig() {
|
|
|
713
602
|
// terminating statement; it doesn't track `fail()`'s `never` return.
|
|
714
603
|
throw new Error(`groundcrew config: no crew config found. Create crew.config.ts in your project root, or ${xdgConfigPath("groundcrew", "crew.config.ts")}, or set GROUNDCREW_CONFIG.`);
|
|
715
604
|
}
|
|
716
|
-
/**
|
|
717
|
-
* Returns the resolved project the issue belongs to, or `undefined` when
|
|
718
|
-
* its slugId isn't in `linear.projects[]`. Callers in the dispatcher
|
|
719
|
-
* path expect a project to always exist (the board fetch only surfaces
|
|
720
|
-
* issues from configured projects); callers in the manual-ticket path
|
|
721
|
-
* (`setupWorkspace`, `ticketDoctor`) use this to detect off-config
|
|
722
|
-
* tickets and surface a clear error.
|
|
723
|
-
*/
|
|
724
|
-
export function findProjectBySlugId(config, slugId) {
|
|
725
|
-
return config.linear.projects.find((project) => project.slugId === slugId);
|
|
726
|
-
}
|
|
727
|
-
// Memoize per-config so blocker checks (called per blocker per tick) don't
|
|
728
|
-
// rebuild the same Set on every call. The resolved config is frozen and
|
|
729
|
-
// long-lived, so the WeakMap key is stable.
|
|
730
|
-
const terminalUnionCache = new WeakMap();
|
|
731
|
-
/**
|
|
732
|
-
* Union of every terminal status name configured across all watched
|
|
733
|
-
* projects. Used for blocker terminal checks when the blocker belongs
|
|
734
|
-
* to a project we don't watch — matches today's single-project "is the
|
|
735
|
-
* status terminal under any configured project?" behavior so off-config
|
|
736
|
-
* blockers don't regress.
|
|
737
|
-
*/
|
|
738
|
-
export function unionTerminalStatuses(config) {
|
|
739
|
-
const cached_ = terminalUnionCache.get(config);
|
|
740
|
-
if (cached_ !== undefined) {
|
|
741
|
-
return cached_;
|
|
742
|
-
}
|
|
743
|
-
const set = new Set();
|
|
744
|
-
for (const project of config.linear.projects) {
|
|
745
|
-
for (const status of project.statuses.terminal) {
|
|
746
|
-
set.add(status);
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
terminalUnionCache.set(config, set);
|
|
750
|
-
return set;
|
|
751
|
-
}
|
|
752
605
|
let cached;
|
|
753
606
|
export async function loadConfig() {
|
|
754
607
|
if (cached) {
|
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
import type { LinearClient } from "@linear/sdk";
|
|
2
|
-
import { type ResolvedConfig } from "./config.ts";
|
|
3
2
|
interface LinearIssueReference {
|
|
4
3
|
id: string;
|
|
5
4
|
uuid: string;
|
|
6
5
|
teamId: string;
|
|
7
|
-
/** SlugId of the issue's Linear project — selects which `inProgress` name to look up on the team's workflow. */
|
|
8
|
-
projectSlugId: string;
|
|
9
6
|
}
|
|
10
7
|
interface LinearIssueStatusUpdater {
|
|
11
8
|
markInProgress(issue: LinearIssueReference): Promise<void>;
|
|
12
9
|
resetMissingInProgressCache(): void;
|
|
13
10
|
}
|
|
14
11
|
export declare function createLinearIssueStatusUpdater(arguments_: {
|
|
15
|
-
config: ResolvedConfig;
|
|
16
12
|
client: LinearClient;
|
|
17
13
|
}): LinearIssueStatusUpdater;
|
|
18
14
|
export {};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linearIssueStatus.d.ts","sourceRoot":"","sources":["../../src/lib/linearIssueStatus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"linearIssueStatus.d.ts","sourceRoot":"","sources":["../../src/lib/linearIssueStatus.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAIhD,UAAU,oBAAoB;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,UAAU,wBAAwB;IAChC,cAAc,CAAC,KAAK,EAAE,oBAAoB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3D,2BAA2B,IAAI,IAAI,CAAC;CACrC;AAED,wBAAgB,8BAA8B,CAAC,UAAU,EAAE;IACzD,MAAM,EAAE,YAAY,CAAC;CACtB,GAAG,wBAAwB,CAmD3B"}
|
|
Binary file
|
|
@@ -12,15 +12,13 @@
|
|
|
12
12
|
* to. Consumers branch on these values, never on a source's native names.
|
|
13
13
|
*
|
|
14
14
|
* - `todo` / `in-progress` / `done`: the only canonical states the built-in
|
|
15
|
-
* Linear adapter produces today (mapped
|
|
16
|
-
* `linear.projects[].statuses`).
|
|
15
|
+
* Linear adapter produces today (mapped from Linear's workflow `state.type`).
|
|
17
16
|
* - `in-review`: produced only by adapters whose schema declares an in-review
|
|
18
17
|
* mapping. The shell adapter's JSON contract accepts it; the built-in Linear
|
|
19
|
-
* adapter does not yet map any native state to it
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* statuses like "Triage", off-config blockers without a known project).
|
|
18
|
+
* adapter does not yet map any native state to it. Reserved here so
|
|
19
|
+
* consumers' branch logic doesn't change when that follow-up lands.
|
|
20
|
+
* - `other`: anything an adapter sees but can't classify (Linear tickets in
|
|
21
|
+
* `backlog`/`triage`, blockers with no resolvable state).
|
|
24
22
|
*/
|
|
25
23
|
export type CanonicalStatus = "todo" | "in-progress" | "in-review" | "done" | "other";
|
|
26
24
|
export interface Blocker {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ticketSource.d.ts","sourceRoot":"","sources":["../../src/lib/ticketSource.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH
|
|
1
|
+
{"version":3,"file":"ticketSource.d.ts","sourceRoot":"","sources":["../../src/lib/ticketSource.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,aAAa,GAAG,WAAW,GAAG,MAAM,GAAG,OAAO,CAAC;AAEtF,MAAM,WAAW,OAAO;IACtB,6DAA6D;IAC7D,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,eAAe,CAAC;CACzB;AAED,MAAM,WAAW,KAAK;IACpB,kFAAkF;IAClF,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,eAAe,CAAC;IACxB,8FAA8F;IAC9F,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC;IAC/B,6DAA6D;IAC7D,KAAK,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,eAAe,EAAE,OAAO,CAAC;IACzB,wFAAwF;IACxF,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,mEAAmE;AACnE,MAAM,MAAM,eAAe,GAAG,KAAK,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5E,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,GAAG,KAAK,IAAI,eAAe,CAExE;AAED,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,KAAK,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,YAAY;IAC3B,qGAAqG;IACrG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8EAA8E;IAC9E,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,oFAAoF;IACpF,KAAK,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;IAC1B,0EAA0E;IAC1E,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAAC;IAC1D,qEAAqE;IACrE,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7C;AAED,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,YAAmB,UAAU,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,EAMjF;CACF;AAED,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,YAAmB,UAAU,EAAE;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,SAAS,MAAM,EAAE,CAAA;KAAE,EAM/E;CACF"}
|
package/package.json
CHANGED