From 07602569fd9d734144193939da4423dedb740610 Mon Sep 17 00:00:00 2001 From: William Thorsen Date: Sat, 9 May 2026 13:42:30 -0700 Subject: [PATCH 1/2] agents|feat: Add work-type emojis and breaking tag to PR descriptions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PR descriptions now use canonical work-type emojis as section headers (e.g., ๐ŸŽ‰ Features, ๐Ÿ› Bug fixes, โ™ป๏ธ Refactoring, ๐Ÿงช Tests, ๐Ÿ“ฆ Dependencies). Breaking-change entries are tagged inline with `๐Ÿšจ **Breaking:**` within their work-type section, mirroring release-notes rendering rather than being collected into a separate section. Several work-type labels and emojis are refreshed to align with release-notes conventions: "Fixes" โ†’ "Bug fixes", "Internal functionality" โ†’ "Internal features", "AI" โ†’ "Agentic support", "Removals" โ†’ "Removed", "Deprecations" โ†’ "Deprecated". Emojis for Removed, Deprecated, Internal features, Tooling, CI, Agentic support, and Formatting are also updated. PR descriptions and changelog entries written from this commit forward reflect the new values; existing artifacts continue to read with their historical labels. --- .../content/skills/_data/work-types.json | 126 +++++++++--------- .../content/skills/summarize-change/SKILL.md | 14 +- .../agents/schemas/work-types.schema.json | 118 +++++++++------- .../lib/__tests__/work-types-schema.test.ts | 91 +++++++++++-- 4 files changed, 226 insertions(+), 123 deletions(-) diff --git a/packages/agents/content/skills/_data/work-types.json b/packages/agents/content/skills/_data/work-types.json index 340dc40f..e08f9670 100644 --- a/packages/agents/content/skills/_data/work-types.json +++ b/packages/agents/content/skills/_data/work-types.json @@ -1,127 +1,133 @@ { - "version": "1.0.0", + "markers": { + "breaking": { + "emoji": "๐Ÿšจ", + "label": "Breaking" + } + }, "tiers": ["public", "internal", "process"], "types": [ { - "key": "feat", "aliases": ["feature"], - "tier": "public", + "breakingPolicy": "optional", "emoji": "๐ŸŽ‰", + "key": "feat", "label": "Features", - "breakingPolicy": "optional" + "tier": "public" }, { - "key": "drop", "aliases": [], - "tier": "public", - "emoji": "๐Ÿ—‘๏ธ", - "label": "Removals", - "breakingPolicy": "required" + "breakingPolicy": "required", + "emoji": "๐Ÿชฆ", + "key": "drop", + "label": "Removed", + "tier": "public" }, { - "key": "deprecate", "aliases": [], - "tier": "public", - "emoji": "โš ๏ธ", - "label": "Deprecations", - "breakingPolicy": "forbidden" + "breakingPolicy": "forbidden", + "emoji": "๐Ÿ—‘๏ธ", + "key": "deprecate", + "label": "Deprecated", + "tier": "public" }, { - "key": "fix", "aliases": ["bugfix"], - "tier": "public", + "breakingPolicy": "forbidden", "emoji": "๐Ÿ›", - "label": "Fixes", - "breakingPolicy": "forbidden" + "key": "fix", + "label": "Bug fixes", + "tier": "public" }, { - "key": "sec", "aliases": ["security"], - "tier": "public", + "breakingPolicy": "optional", "emoji": "๐Ÿ”’", + "key": "sec", "label": "Security", - "breakingPolicy": "optional" + "tier": "public" }, { - "key": "perf", "aliases": ["performance"], - "tier": "public", + "breakingPolicy": "forbidden", "emoji": "โšก", + "key": "perf", "label": "Performance", - "breakingPolicy": "forbidden" + "tier": "public" }, { - "key": "internal", "aliases": ["utility"], - "tier": "internal", - "emoji": "๐Ÿ”ง", - "label": "Internal functionality", - "breakingPolicy": "forbidden" + "breakingPolicy": "forbidden", + "emoji": "๐Ÿ—๏ธ", + "key": "internal", + "label": "Internal features", + "tier": "internal" }, { - "key": "refactor", "aliases": [], - "tier": "internal", + "breakingPolicy": "forbidden", "emoji": "โ™ป๏ธ", + "key": "refactor", "label": "Refactoring", - "breakingPolicy": "forbidden" + "tier": "internal" }, { - "key": "tests", "aliases": ["test"], - "tier": "internal", + "breakingPolicy": "forbidden", "emoji": "๐Ÿงช", + "key": "tests", "label": "Tests", - "breakingPolicy": "forbidden" + "tier": "internal" }, { - "key": "tooling", "aliases": [], - "tier": "process", - "emoji": "๐Ÿ› ๏ธ", + "breakingPolicy": "forbidden", + "emoji": "โš™๏ธ", + "key": "tooling", "label": "Tooling", - "breakingPolicy": "forbidden" + "tier": "process" }, { - "key": "ci", "aliases": [], - "tier": "process", - "emoji": "๐Ÿค–", + "breakingPolicy": "forbidden", + "emoji": "๐Ÿ‘ท", + "key": "ci", "label": "CI", - "breakingPolicy": "forbidden" + "tier": "process" }, { - "key": "deps", "aliases": ["dep"], - "tier": "process", + "breakingPolicy": "forbidden", "emoji": "๐Ÿ“ฆ", + "key": "deps", "label": "Dependencies", - "breakingPolicy": "forbidden" + "tier": "process" }, { - "key": "ai", "aliases": [], - "tier": "process", - "emoji": "๐Ÿง ", - "label": "AI", - "breakingPolicy": "forbidden" + "breakingPolicy": "forbidden", + "emoji": "๐Ÿค–", + "key": "ai", + "label": "Agentic support", + "tier": "process" }, { - "key": "docs", "aliases": ["doc"], - "tier": "process", + "breakingPolicy": "forbidden", "emoji": "๐Ÿ“š", + "key": "docs", "label": "Documentation", - "breakingPolicy": "forbidden" + "tier": "process" }, { - "key": "fmt", "aliases": [], - "tier": "process", - "emoji": "๐Ÿ’…", - "label": "Formatting", "breakingPolicy": "forbidden", - "excludedFromChangelog": true + "emoji": "๐ŸŽจ", + "excludedFromChangelog": true, + "key": "fmt", + "label": "Formatting", + "tier": "process" } - ] + ], + "version": "1.0.0" } diff --git a/packages/agents/content/skills/summarize-change/SKILL.md b/packages/agents/content/skills/summarize-change/SKILL.md index 2f5db85e..e5a73639 100644 --- a/packages/agents/content/skills/summarize-change/SKILL.md +++ b/packages/agents/content/skills/summarize-change/SKILL.md @@ -61,23 +61,23 @@ Good: "Heavy-upload sessions were intermittently failing as users hit the upstre ## Details -### Features +### ๐ŸŽ‰ Features {Only if applicable} -### Fixes +### ๐Ÿ› Bug fixes {Only if applicable} -### Refactoring +### โ™ป๏ธ Refactoring {Only if applicable} -### Tests +### ๐Ÿงช Tests {Only if applicable} -### Dependencies +### ๐Ÿ“ฆ Dependencies {Only if applicable} ``` @@ -88,7 +88,9 @@ Good: "Heavy-upload sessions were intermittently failing as users hit the upstre - The change summary follows **newspaper style** โ€” progressive disclosure from most to least essential: `## What` is the headline (outcome in plain language), `## Why` is the context (motivation and background), `## Details` is the full story (implementation mechanics) - Ignore auto-formatter and lint-fix changes - Omit inapplicable Details subsections -- Order Details subsections per `work-types.json` tier order: public โ†’ internal โ†’ process +- Subsection headings use `{emoji} {label}` from the matching [work-types.json](../_data/work-types.json) `types[]` entry. For any subsection not enumerated in the example template above, look up the entry by work-type key and use its `emoji` and `label`. +- Order Details subsections per `work-types.json` tier order: public โ†’ internal โ†’ process. +- Prefix any individual `## Details` entry that describes a breaking change with `๐Ÿšจ **Breaking:** ` (drawn from `markers.breaking` in [work-types.json](../_data/work-types.json), rendered as `{emoji} **{label}:** `). Trigger conditions: a commit with the `!` breaking marker (e.g., `feat!`) or a `BREAKING CHANGE:` footer. The entry stays under its work-type subsection โ€” the prefix tags it inline rather than relocating it to a separate section. - `## What` and `## Why` are required; Details subsections are optional - Never list automated checks (formatting, linting, typechecking, unit tests) in a test plan. They run automatically in CI. diff --git a/packages/agents/schemas/work-types.schema.json b/packages/agents/schemas/work-types.schema.json index 81e291df..b5c9ebb8 100644 --- a/packages/agents/schemas/work-types.schema.json +++ b/packages/agents/schemas/work-types.schema.json @@ -1,77 +1,103 @@ { - "$schema": "https://json-schema.org/draft/2020-12/schema", "$id": "https://github.com/williamthorsen/codeassembly/raw/agents-v0.1.0/packages/agents/schemas/work-types.schema.json", - "title": "Work types", - "description": "Canonical work-types taxonomy consumed by skill content and downstream changelog/release-notes tooling. Defines the work-type vocabulary, tier grouping, breaking-change policy, and changelog-eligibility flags.", - "type": "object", + "$schema": "https://json-schema.org/draft/2020-12/schema", "additionalProperties": false, - "required": ["version", "tiers", "types"], + "description": "Canonical work-types taxonomy consumed by skill content and downstream changelog/release-notes tooling. Defines the work-type vocabulary, tier grouping, breaking-change policy, changelog-eligibility flags, and cross-cutting section markers (e.g., the breaking-changes indicator).", "properties": { - "version": { - "type": "string", - "description": "Semantic version of the data shape. Bump when the structure changes in a way that breaks downstream consumers.", - "pattern": "^\\d+\\.\\d+\\.\\d+$" + "markers": { + "additionalProperties": false, + "description": "Cross-cutting section markers used in PR descriptions and release-notes rendering. Orthogonal to `types[]` โ€” these are not work types but visual indicators of cross-cutting concerns (e.g., the breaking-changes section).", + "properties": { + "breaking": { + "additionalProperties": false, + "description": "Marker for the breaking-changes section. Consumed by `summarize-change` to render the `๐Ÿšจ Breaking changes` subsection of `## Details` when the branch contains breaking commits.", + "properties": { + "emoji": { + "description": "Emoji rendered as the prefix of the breaking-changes section heading.", + "minLength": 1, + "type": "string" + }, + "label": { + "description": "Human-readable label rendered as the breaking-changes section heading.", + "minLength": 1, + "type": "string" + } + }, + "required": ["emoji", "label"], + "type": "object" + } + }, + "required": ["breaking"], + "type": "object" }, "tiers": { - "type": "array", "description": "Canonical tier vocabulary in precedence order (higher tier wins ties). Mirrored at `types[].tier`. Each position is pinned via `prefixItems` so that order is enforced structurally.", + "items": false, + "maxItems": 3, + "minItems": 3, "prefixItems": [ - { "type": "string", "const": "public" }, - { "type": "string", "const": "internal" }, - { "type": "string", "const": "process" } + { "const": "public", "type": "string" }, + { "const": "internal", "type": "string" }, + { "const": "process", "type": "string" } ], - "minItems": 3, - "maxItems": 3, - "items": false + "type": "array" }, "types": { - "type": "array", "description": "The work-type records, in canonical render order. Order is meaningful: when ranking applicable types within a tier, earlier entries win.", "items": { - "type": "object", "additionalProperties": false, - "required": ["key", "aliases", "tier", "emoji", "label", "breakingPolicy"], "properties": { - "key": { - "type": "string", - "description": "Canonical short identifier used in commit messages (e.g., `feat`, `fix`).", - "pattern": "^[a-z][a-z0-9-]*$" - }, "aliases": { - "type": "array", "description": "Alternative identifiers that resolve to the same `key`. Globally unique across all types (asserted in test).", "items": { - "type": "string", - "pattern": "^[a-z][a-z0-9-]*$" + "pattern": "^[a-z][a-z0-9-]*$", + "type": "string" }, + "type": "array", "uniqueItems": true }, - "tier": { - "type": "string", - "description": "Which tier this type belongs to. Drives precedence and grouping in skill content.", - "enum": ["public", "internal", "process"] + "breakingPolicy": { + "description": "Whether a breaking-change marker (`!`) is forbidden, optional, or required for this type.", + "enum": ["forbidden", "optional", "required"], + "type": "string" }, "emoji": { - "type": "string", "description": "Single-character (or short) emoji used in section headers and changelog rendering.", - "minLength": 1 + "minLength": 1, + "type": "string" + }, + "excludedFromChangelog": { + "description": "When true, commits of this type are omitted from the generated changelog (e.g., `fmt`).", + "type": "boolean" + }, + "key": { + "description": "Canonical short identifier used in commit messages (e.g., `feat`, `fix`).", + "pattern": "^[a-z][a-z0-9-]*$", + "type": "string" }, "label": { - "type": "string", "description": "Human-readable category label used in section headings (e.g., `Features`, `Fixes`).", - "minLength": 1 - }, - "breakingPolicy": { - "type": "string", - "description": "Whether a breaking-change marker (`!`) is forbidden, optional, or required for this type.", - "enum": ["forbidden", "optional", "required"] + "minLength": 1, + "type": "string" }, - "excludedFromChangelog": { - "type": "boolean", - "description": "When true, commits of this type are omitted from the generated changelog (e.g., `fmt`)." + "tier": { + "description": "Which tier this type belongs to. Drives precedence and grouping in skill content.", + "enum": ["public", "internal", "process"], + "type": "string" } - } - } + }, + "required": ["aliases", "breakingPolicy", "emoji", "key", "label", "tier"], + "type": "object" + }, + "type": "array" + }, + "version": { + "description": "Semantic version of the data shape. Bump when the structure changes in a way that breaks downstream consumers.", + "pattern": "^\\d+\\.\\d+\\.\\d+$", + "type": "string" } - } + }, + "required": ["markers", "tiers", "types", "version"], + "title": "Work types", + "type": "object" } diff --git a/packages/agents/src/lib/__tests__/work-types-schema.test.ts b/packages/agents/src/lib/__tests__/work-types-schema.test.ts index c57363e4..8899ac98 100644 --- a/packages/agents/src/lib/__tests__/work-types-schema.test.ts +++ b/packages/agents/src/lib/__tests__/work-types-schema.test.ts @@ -15,20 +15,32 @@ type JsonValue = string | number | boolean | null | { [key: string]: JsonValue } /** Shape of a single record under `types[]` โ€” used to type the live JSON for cross-element checks. */ interface WorkTypeRecord { - key: string; aliases: string[]; + breakingPolicy: string; + emoji: string; + excludedFromChangelog?: boolean; + key: string; + label: string; tier: string; +} + +/** Shape of a single marker record (cross-cutting section indicator). */ +interface MarkerRecord { emoji: string; label: string; - breakingPolicy: string; - excludedFromChangelog?: boolean; +} + +/** Shape of the top-level `markers` block. */ +interface MarkersBlock { + breaking: MarkerRecord; } /** Shape of the live `work-types.json` document โ€” used to type the live JSON for cross-element checks. */ interface WorkTypesDocument { - version: string; + markers: MarkersBlock; tiers: string[]; types: WorkTypeRecord[]; + version: string; } /** @@ -95,16 +107,16 @@ describe('work-types.schema.json', () => { }, { description: 'rejects a type record missing a required field', - // Guards `types[].required: ["key", "aliases", "tier", "emoji", "label", "breakingPolicy"]`. + // Guards `types[].required: ["aliases", "breakingPolicy", "emoji", "key", "label", "tier"]`. // Builds a record without `breakingPolicy`. input: buildMinimalDoc({ types: [ { - key: 'feat', aliases: ['feature'], - tier: 'public', emoji: '๐ŸŽ‰', + key: 'feat', label: 'Features', + tier: 'public', }, ], }), @@ -142,6 +154,48 @@ describe('work-types.schema.json', () => { // catch; if the constraint were widened, this rejection test would fail loudly. input: buildMinimalDoc({ version: 'v1.0.0' }), }, + { + description: 'rejects a document missing the `markers` top-level field', + // Guards top-level `required: ["markers", "tiers", "types", "version"]`. Constructed inline + // because `buildMinimalDoc` always supplies `markers`. + input: { + tiers: ['public', 'internal', 'process'], + types: [], + version: '1.0.0', + }, + }, + { + description: 'rejects a `markers` block missing the `breaking` field', + // Guards `markers.required: ["breaking"]`. + input: buildMinimalDoc({ markers: {} }), + }, + { + description: 'rejects a `markers.breaking` record missing the `emoji` field', + // Guards `markers.breaking.required: ["emoji", "label"]`. + input: buildMinimalDoc({ markers: { breaking: { label: 'Breaking' } } }), + }, + { + description: 'rejects a `markers.breaking` record missing the `label` field', + // Guards `markers.breaking.required: ["emoji", "label"]`. + input: buildMinimalDoc({ markers: { breaking: { emoji: '๐Ÿšจ' } } }), + }, + { + description: 'rejects a `markers.breaking.emoji` that is an empty string', + // Guards `markers.breaking.properties.emoji.minLength: 1`. + input: buildMinimalDoc({ markers: { breaking: { emoji: '', label: 'Breaking' } } }), + }, + { + description: 'rejects a `markers.breaking.label` that is an empty string', + // Guards `markers.breaking.properties.label.minLength: 1`. + input: buildMinimalDoc({ markers: { breaking: { emoji: '๐Ÿšจ', label: '' } } }), + }, + { + description: 'rejects an unknown field on `markers.breaking`', + // Guards `markers.breaking.additionalProperties: false`. + input: buildMinimalDoc({ + markers: { breaking: { emoji: '๐Ÿšจ', extra: 'nope', label: 'Breaking' } }, + }), + }, ])('$description', async ({ input }) => { const output = await validate(schemaId, input, FLAG); expect(output).toMatchObject({ valid: false }); @@ -198,6 +252,15 @@ describe('work-types.schema.json', () => { // schema is ever weakened, this assertion still catches a misordered live file. expect(liveData.tiers).toEqual(['public', 'internal', 'process']); }); + + it('exposes `markers.breaking` with the canonical glyph and label', () => { + // The `summarize-change` skill template prefixes breaking-change entries with + // `{emoji} **{label}:**` (e.g., `๐Ÿšจ **Breaking:**`) using these values directly. + // A silent rename of either field would still pass schema validation but break the + // downstream consumer; assert the values explicitly. + expect(liveData.markers.breaking.emoji).toBe('๐Ÿšจ'); + expect(liveData.markers.breaking.label).toBe('Breaking'); + }); }); // region | Helpers @@ -209,9 +272,15 @@ describe('work-types.schema.json', () => { */ function buildMinimalDoc(overrides: Record = {}): JsonValue { return { - version: '1.0.0', + markers: { + breaking: { + emoji: '๐Ÿšจ', + label: 'Breaking', + }, + }, tiers: ['public', 'internal', 'process'], types: [], + version: '1.0.0', ...overrides, }; } @@ -222,12 +291,12 @@ function buildMinimalDoc(overrides: Record = {}): JsonValue { */ function buildTypeRecord(overrides: Record = {}): JsonValue { return { - key: 'feat', aliases: [], - tier: 'public', + breakingPolicy: 'optional', emoji: '๐ŸŽ‰', + key: 'feat', label: 'Features', - breakingPolicy: 'optional', + tier: 'public', ...overrides, }; } From 955ba7acb542b73449c4790f0821ec3a31c70bbf Mon Sep 17 00:00:00 2001 From: William Thorsen Date: Sat, 9 May 2026 14:42:30 -0700 Subject: [PATCH 2/2] agents|docs: Align markers schema descriptions with inline prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Description text on the `markers` schema previously described a dedicated `### ๐Ÿšจ Breaking changes` subsection rather than the inline-prefix design that ships (`๐Ÿšจ **Breaking:**` applied to individual entries within their work-type subsection). The descriptions now match shipped behavior, including the construction pattern (`{emoji} **{label}:**`) and the punctuation contract (the colon belongs to the rendering template, not the data). A one-line growth-path note added to the `markers` description explains how to add future markers (sibling keys with the same `{emoji, label}` shape), so a future contributor doesn't have to reverse-engineer the convention from one example. --- packages/agents/schemas/work-types.schema.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/agents/schemas/work-types.schema.json b/packages/agents/schemas/work-types.schema.json index b5c9ebb8..9b1a2d31 100644 --- a/packages/agents/schemas/work-types.schema.json +++ b/packages/agents/schemas/work-types.schema.json @@ -6,19 +6,19 @@ "properties": { "markers": { "additionalProperties": false, - "description": "Cross-cutting section markers used in PR descriptions and release-notes rendering. Orthogonal to `types[]` โ€” these are not work types but visual indicators of cross-cutting concerns (e.g., the breaking-changes section).", + "description": "Cross-cutting section markers used in PR descriptions and release-notes rendering. Orthogonal to `types[]` โ€” these are not work types but visual indicators applied inline to individual entries within their work-type subsection. Add new markers as sibling keys under `markers`; each follows the same `{emoji, label}` shape.", "properties": { "breaking": { "additionalProperties": false, - "description": "Marker for the breaking-changes section. Consumed by `summarize-change` to render the `๐Ÿšจ Breaking changes` subsection of `## Details` when the branch contains breaking commits.", + "description": "Marker for breaking-change entries. Consumed by `summarize-change` as an inline prefix (`{emoji} **{label}:**`, e.g., `๐Ÿšจ **Breaking:**`) on individual `## Details` entries that describe a breaking change; the entry stays under its work-type subsection.", "properties": { "emoji": { - "description": "Emoji rendered as the prefix of the breaking-changes section heading.", + "description": "Glyph rendered at the start of the inline breaking-change prefix.", "minLength": 1, "type": "string" }, "label": { - "description": "Human-readable label rendered as the breaking-changes section heading.", + "description": "Bare tag word rendered inside the inline prefix's bold construction (e.g., `**Breaking:**`). Singular โ€” punctuation belongs to the rendering template.", "minLength": 1, "type": "string" }