From 078876de15bbc9e35f8d877bde184b25b172b51a Mon Sep 17 00:00:00 2001 From: Steve Landey Date: Mon, 19 Oct 2026 14:26:48 -0700 Subject: [PATCH 1/4] Fix four small issue-description bugs The schema that reads dependencies.json back dropped the provider fields the templates use, so issues said to edit `undefined`. A Snyk advisory could list the same fix release twice. The rationale sentence joined a lowercase clause after a period. The owner line in the summary had no label. Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 4 +++ src/core/schema.test.ts | 26 +++++++++++++++++++ src/core/schema.ts | 8 ++++++ .../templates/issue-description.hbs | 2 +- src/linear/issueDescriptions.test.ts | 2 +- src/linear/templates/issue-description.hbs | 2 +- src/security/SecurityPlugin.test.ts | 4 +-- src/security/sources/OsvSource.ts | 2 +- src/security/sources/SnykSource.test.ts | 15 +++++++++++ src/security/sources/SnykSource.ts | 4 +-- 10 files changed, 61 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d9420f3..b562c2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,10 @@ ### Fixed +- Issue descriptions read the provider's template text again. `update` wrote `catalogFile`, `patchHint` and the `update*` strings to `dependencies.json`, but reading the file back dropped them, so a Linear or GitHub issue said to edit `undefined` to add a dependency to the catalog. +- An advisory's fix list names each release once. Snyk can repeat a release across an advisory's coordinates, which showed as `fixed in 15.5.27, 16.3.8, 16.3.8`. +- The "Why this matters" sentence starts each clause with a capital, so it no longer reads `4 Snyk advisories. fix available in a newer version.` +- The owner line in an issue's summary is labelled, like the lines around it. - Links from a grouping page to a dependency's page work when the grouping spans ecosystems. Those pages sit at the site root, so the `../details/…` path a plugin builds leads nowhere, which broke `BasicCompliancePlugin`'s flagged entries. `GroupingFlag.detailLink` is optional now and Dependicus resolves it, and `GroupingDetailContext.detailLinkFor` resolves one for a link inside a section's own HTML. - The due date was wrong on an issue that covers two versions of one dependency. Say `react` 17 needs 17.0.5 with a 28-day threshold and `react` 18 needs 18.2.1 with a 10-day threshold. The issue asked for 18.2.1 but added the 28 days to its release date. It now uses the threshold that belongs to the version it asks for. diff --git a/src/core/schema.test.ts b/src/core/schema.test.ts index 28c0876..0737c7d 100644 --- a/src/core/schema.test.ts +++ b/src/core/schema.test.ts @@ -52,6 +52,32 @@ describe('schema', () => { expect(result.facts.dependency.react).toBeDefined(); }); + it('keeps the provider fields the issue templates read', () => { + const provider = { + ...validInput.providers[0], + installCommand: 'pnpm install', + catalogFile: 'pnpm-workspace.yaml', + updatePrefix: 'Update the version in:', + updateSuffix: 'Then run `pnpm install`.', + updateInstructions: 'Bump each package.', + patchHint: 'Check the patches in `patches/` still apply.', + }; + const result = parseDependicusOutput({ ...validInput, providers: [provider] }); + const { dependencies: _deps, ...kept } = result.providers[0]!; + expect(kept).toEqual({ + name: 'pnpm', + ecosystem: 'npm', + supportsCatalog: true, + installCommand: 'pnpm install', + urlPatterns: {}, + catalogFile: 'pnpm-workspace.yaml', + updatePrefix: 'Update the version in:', + updateSuffix: 'Then run `pnpm install`.', + updateInstructions: 'Bump each package.', + patchHint: 'Check the patches in `patches/` still apply.', + }); + }); + it('accepts input with empty facts', () => { const minimal = { metadata: { diff --git a/src/core/schema.ts b/src/core/schema.ts index 2d1645b..3e92d3a 100644 --- a/src/core/schema.ts +++ b/src/core/schema.ts @@ -22,6 +22,14 @@ const providerOutputSchema = z.object({ supportsCatalog: z.boolean(), installCommand: z.string().default('install'), urlPatterns: z.record(z.string(), z.string()).default({}), + // The provider's issue-template text. Zod drops keys it doesn't know, so + // each field a provider can set has to be named here or `update` writes + // it and `make-linear-issues` reads it back as undefined. + updatePrefix: z.string().optional(), + updateSuffix: z.string().optional(), + updateInstructions: z.string().optional(), + catalogFile: z.string().optional(), + patchHint: z.string().optional(), dependencies: z.array(directDependencySchema), }); diff --git a/src/github-issues/templates/issue-description.hbs b/src/github-issues/templates/issue-description.hbs index e98e0bd..ed54e8c 100644 --- a/src/github-issues/templates/issue-description.hbs +++ b/src/github-issues/templates/issue-description.hbs @@ -5,7 +5,7 @@ ## Summary {{#if ownerLabel}} -- {{ownerLabel}} +- **Owner:** {{ownerLabel}} {{/if}} {{#if multiVersion}} - **Current versions in monorepo:** diff --git a/src/linear/issueDescriptions.test.ts b/src/linear/issueDescriptions.test.ts index e7ed60c..0559b75 100644 --- a/src/linear/issueDescriptions.test.ts +++ b/src/linear/issueDescriptions.test.ts @@ -278,7 +278,7 @@ describe('buildIssueDescription', () => { getDetailUrl: testGetDetailUrl, providerInfo: npmProviderInfo, }); - expect(result).toContain('- Web App (Frontend)'); + expect(result).toContain('- **Owner:** Web App (Frontend)'); }); it('shows catalog How to Update', () => { diff --git a/src/linear/templates/issue-description.hbs b/src/linear/templates/issue-description.hbs index 46e15e9..de41c31 100644 --- a/src/linear/templates/issue-description.hbs +++ b/src/linear/templates/issue-description.hbs @@ -5,7 +5,7 @@ ## Summary {{#if ownerLabel}} -- {{ownerLabel}} +- **Owner:** {{ownerLabel}} {{/if}} {{#if multiVersion}} - **Current versions in monorepo:** diff --git a/src/security/SecurityPlugin.test.ts b/src/security/SecurityPlugin.test.ts index 132595d..2e36c07 100644 --- a/src/security/SecurityPlugin.test.ts +++ b/src/security/SecurityPlugin.test.ts @@ -137,7 +137,7 @@ describe('SecurityPlugin', () => { severity: 'high', advisoryCount: 2, fixAvailable: true, - rationale: ['2 advisories', 'fix available in a newer version'], + rationale: ['2 advisories', 'Fix available in a newer version'], sourceLinks: [{ label: 'GHSA-1234', url: 'https://osv.dev/vulnerability/GHSA-1234' }], }; scoped.setVersionFact('react', '1.0.0', SECURITY_FINDINGS_KEY, [finding]); @@ -155,7 +155,7 @@ describe('SecurityPlugin', () => { const whyValue = whyCol.getValue(ctx); expect(whyValue).toContain(' { diff --git a/src/security/sources/OsvSource.ts b/src/security/sources/OsvSource.ts index 7fc18b0..e235322 100644 --- a/src/security/sources/OsvSource.ts +++ b/src/security/sources/OsvSource.ts @@ -241,7 +241,7 @@ export class OsvSource implements DataSource { rationale.push(`${vulns.length} advisories`); } if (anyFixAvailable) { - rationale.push('fix available in a newer version'); + rationale.push('Fix available in a newer version'); } const sourceLinks = vulns.map((v) => ({ diff --git a/src/security/sources/SnykSource.test.ts b/src/security/sources/SnykSource.test.ts index 0b3bc4a..14a1ef9 100644 --- a/src/security/sources/SnykSource.test.ts +++ b/src/security/sources/SnykSource.test.ts @@ -114,6 +114,21 @@ describe('toFinding', () => { expect(toFinding([issue])?.fixAvailable).toBe(true); }); + it('lists a fix version once when Snyk repeats it', () => { + // Snyk can name the same release from two coordinates of one advisory. + const issue: SnykIssue = { + id: 'SNYK-JS-THING-2', + attributes: { + effective_severity_level: 'medium', + coordinates: [ + { remedies: [{ details: { upgrade_package: '15.5.27,16.3.8' } }] }, + { remedies: [{ details: { upgrade_package: '16.3.8' } }] }, + ], + }, + }; + expect(toFinding([issue])?.advisories?.[0]?.fixVersions).toEqual(['15.5.27', '16.3.8']); + }); + it('links each advisory to its Snyk page', () => { expect(toFinding(gin)?.sourceLinks?.[0]?.url).toMatch( /^https:\/\/security\.snyk\.io\/vuln\/SNYK-GOLANG-/, diff --git a/src/security/sources/SnykSource.ts b/src/security/sources/SnykSource.ts index d77d44c..60426fa 100644 --- a/src/security/sources/SnykSource.ts +++ b/src/security/sources/SnykSource.ts @@ -143,7 +143,7 @@ function fixVersions(issue: SnykIssue): string[] { for (const remedy of coordinate.remedies ?? []) { for (const part of remedy.details?.upgrade_package?.split(',') ?? []) { const trimmed = part.trim(); - if (trimmed) versions.push(trimmed); + if (trimmed && !versions.includes(trimmed)) versions.push(trimmed); } } } @@ -300,7 +300,7 @@ export function toFinding( : `${ordered.length} Snyk advisories`, ]; if (maturity) rationale.push(`Snyk exploit maturity: ${maturity}`); - if (fixAvailable) rationale.push('fix available in a newer version'); + if (fixAvailable) rationale.push('Fix available in a newer version'); return { source: 'snyk', From a68e5d5d11351bd737960f17ec8c0e188c4dade2 Mon Sep 17 00:00:00 2001 From: Steve Landey Date: Mon, 19 Oct 2026 14:29:56 -0700 Subject: [PATCH 2/4] Shorten the changelog sentences Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b562c2c..f82033c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,10 +57,10 @@ ### Fixed -- Issue descriptions read the provider's template text again. `update` wrote `catalogFile`, `patchHint` and the `update*` strings to `dependencies.json`, but reading the file back dropped them, so a Linear or GitHub issue said to edit `undefined` to add a dependency to the catalog. -- An advisory's fix list names each release once. Snyk can repeat a release across an advisory's coordinates, which showed as `fixed in 15.5.27, 16.3.8, 16.3.8`. -- The "Why this matters" sentence starts each clause with a capital, so it no longer reads `4 Snyk advisories. fix available in a newer version.` -- The owner line in an issue's summary is labelled, like the lines around it. +- Issue descriptions get the provider's template text again. `update` wrote `catalogFile`, `patchHint` and the `update*` strings to `dependencies.json`. Reading the file back removed them. A Linear or GitHub issue then said to edit `undefined` to add a dependency to the catalog. +- An advisory's fix list gives each release one time. Snyk can repeat a release in one advisory. The list showed `fixed in 15.5.27, 16.3.8, 16.3.8`. +- Each clause of the "Why this matters" sentence starts with a capital letter. It read `4 Snyk advisories. fix available in a newer version.` +- The owner line in an issue's summary has a label, like the lines around it. - Links from a grouping page to a dependency's page work when the grouping spans ecosystems. Those pages sit at the site root, so the `../details/…` path a plugin builds leads nowhere, which broke `BasicCompliancePlugin`'s flagged entries. `GroupingFlag.detailLink` is optional now and Dependicus resolves it, and `GroupingDetailContext.detailLinkFor` resolves one for a link inside a section's own HTML. - The due date was wrong on an issue that covers two versions of one dependency. Say `react` 17 needs 17.0.5 with a 28-day threshold and `react` 18 needs 18.2.1 with a 10-day threshold. The issue asked for 18.2.1 but added the 28 days to its release date. It now uses the threshold that belongs to the version it asks for. From e52d6b3d3eb5d68f4a98c13519f1a9c2c76d9e58 Mon Sep 17 00:00:00 2001 From: Steve Landey Date: Mon, 19 Oct 2026 14:31:16 -0700 Subject: [PATCH 3/4] Drop a comment that restates the diff Co-Authored-By: Claude Fable 5.1 --- src/core/schema.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/core/schema.ts b/src/core/schema.ts index 3e92d3a..4146415 100644 --- a/src/core/schema.ts +++ b/src/core/schema.ts @@ -22,9 +22,6 @@ const providerOutputSchema = z.object({ supportsCatalog: z.boolean(), installCommand: z.string().default('install'), urlPatterns: z.record(z.string(), z.string()).default({}), - // The provider's issue-template text. Zod drops keys it doesn't know, so - // each field a provider can set has to be named here or `update` writes - // it and `make-linear-issues` reads it back as undefined. updatePrefix: z.string().optional(), updateSuffix: z.string().optional(), updateInstructions: z.string().optional(), From bfb802dfc72fb38cb4c3875628b178d5961e50dc Mon Sep 17 00:00:00 2001 From: Steve Landey Date: Mon, 19 Oct 2026 14:32:25 -0700 Subject: [PATCH 4/4] Drop the changelog line for a fix to code 0.2.1 did not have Co-Authored-By: Claude Fable 5.1 --- CHANGELOG.md | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f82033c..807be33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -58,7 +58,6 @@ ### Fixed - Issue descriptions get the provider's template text again. `update` wrote `catalogFile`, `patchHint` and the `update*` strings to `dependencies.json`. Reading the file back removed them. A Linear or GitHub issue then said to edit `undefined` to add a dependency to the catalog. -- An advisory's fix list gives each release one time. Snyk can repeat a release in one advisory. The list showed `fixed in 15.5.27, 16.3.8, 16.3.8`. - Each clause of the "Why this matters" sentence starts with a capital letter. It read `4 Snyk advisories. fix available in a newer version.` - The owner line in an issue's summary has a label, like the lines around it. - Links from a grouping page to a dependency's page work when the grouping spans ecosystems. Those pages sit at the site root, so the `../details/…` path a plugin builds leads nowhere, which broke `BasicCompliancePlugin`'s flagged entries. `GroupingFlag.detailLink` is optional now and Dependicus resolves it, and `GroupingDetailContext.detailLinkFor` resolves one for a link inside a section's own HTML.