vouchington-tooling 0.22.1 → 0.23.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 CHANGED
@@ -338,6 +338,32 @@ shape, including short table delimiters. `extractLooseMarkdownTableRows` is inte
338
338
  recovery for malformed pipe rows; callers supply table positions to exclude and retain ownership of
339
339
  their policy interpretation.
340
340
 
341
+ `parseMarkdownSections` returns visible root H2 sections, their exact original `content`,
342
+ `startOffset`/`endOffset` source ranges, heading line, and `hasVisibleContent`. Heading text is
343
+ normalized by collapsing whitespace and removing Markdown formatting/comments and collapsed text; matching remains
344
+ case-sensitive. Source offsets are JavaScript string offsets, so `source.slice(startOffset,
345
+ endOffset)` preserves Unicode and CRLF exactly. Sections end at the next visible root H2 or EOF;
346
+ fenced examples, quoted headings, and headings inside `<details>` do not create boundaries.
347
+
348
+ ```ts
349
+ import { parseMarkdownSections, validateMarkdownSections } from 'vouchington-tooling/markdown'
350
+
351
+ const document = parseMarkdownSections(body)
352
+ const diagnostics = validateMarkdownSections(document, {
353
+ requiredHeadings: ['Summary', 'Impact'],
354
+ })
355
+ const relatedIssues = document.sections.find((section) => section.heading === 'Related issues')
356
+ ```
357
+
358
+ The caller owns required headings. Validation returns diagnostics with `code`, `message`, and
359
+ optional `heading`/`line`, for `missing-heading`, `duplicate-heading`, `empty-section`, or
360
+ `malformed-details`. Nonempty visible paragraphs, lists, and populated table bodies satisfy the
361
+ content check; comments, fenced/indented code, images, disclosure labels, and collapsed-only content
362
+ do not. The parser tracks balanced nested details/summary tags in GFM HTML nodes, accepting optional
363
+ summaries and quoted attributes; it is not a general HTML validator or sanitizer. It never rewrites
364
+ the input or infers semantic relevance, cost, schema conformance, or failure causes. The existing
365
+ `markdownSectionBetweenHeadings` API retains its original boundary behavior.
366
+
341
367
  `agent-harness-config` merges classifier-auto and sandbox keys into Claude, Codex, Grok, and Cursor
342
368
  config files. See [docs/agent-harness-config.md](./docs/agent-harness-config.md). `--global` updates
343
369
  home-directory configs; `--repo` updates a checkout. It does not copy allowlists, hooks, or plugins.
@@ -0,0 +1,13 @@
1
+ import type { MarkdownSectionsDiagnostic } from './document-types.mts';
2
+ /** Tracks disclosure tags from HTML nodes, never from fenced code or Markdown comments. */
3
+ export declare class DetailsScope {
4
+ private readonly markdown;
5
+ readonly diagnostics: MarkdownSectionsDiagnostic[];
6
+ private readonly frames;
7
+ constructor(markdown: string);
8
+ get visible(): boolean;
9
+ acceptHtml(html: string, startOffset: number): void;
10
+ finish(): void;
11
+ private acceptTag;
12
+ private problem;
13
+ }
@@ -0,0 +1,73 @@
1
+ /** Tracks disclosure tags from HTML nodes, never from fenced code or Markdown comments. */
2
+ export class DetailsScope {
3
+ markdown;
4
+ diagnostics = [];
5
+ frames = [];
6
+ constructor(markdown) {
7
+ this.markdown = markdown;
8
+ }
9
+ get visible() {
10
+ return this.frames.length === 0;
11
+ }
12
+ acceptHtml(html, startOffset) {
13
+ const tokens = /<!--[\s\S]*?(?:-->|$)|<\/?[a-z][a-z\d:-]*(?:[^"'<>]|"[^"]*"|'[^']*')*>/giu;
14
+ for (const token of html.matchAll(tokens)) {
15
+ if (token[0].startsWith('<!--'))
16
+ continue;
17
+ const tag = /^<(\/?)([a-z][a-z\d:-]*)/iu.exec(token[0]);
18
+ const name = tag[2].toLowerCase();
19
+ if (name !== 'details' && name !== 'summary')
20
+ continue;
21
+ const line = this.markdown.slice(0, startOffset + token.index).split('\n').length;
22
+ if (/\/\s*>$/u.test(token[0])) {
23
+ this.problem(line, `${tag[2]} cannot be self-closing`);
24
+ continue;
25
+ }
26
+ this.acceptTag(name, tag[1] === '/', line);
27
+ }
28
+ }
29
+ finish() {
30
+ for (const frame of this.frames)
31
+ this.problem(frame.line, 'details has no closing tag');
32
+ }
33
+ acceptTag(name, closing, line) {
34
+ if (name === 'details') {
35
+ if (!closing) {
36
+ if (this.frames.at(-1)?.summary === 'open') {
37
+ this.problem(line, 'details cannot open inside summary');
38
+ }
39
+ this.frames.push({ line, summary: 'absent' });
40
+ return;
41
+ }
42
+ const frame = this.frames.pop();
43
+ if (!frame)
44
+ this.problem(line, 'details closes without an opening tag');
45
+ else if (frame.summary === 'open')
46
+ this.problem(line, 'summary has no closing tag');
47
+ return;
48
+ }
49
+ const frame = this.frames.at(-1);
50
+ if (!frame) {
51
+ this.problem(line, 'summary must be inside details');
52
+ return;
53
+ }
54
+ if (closing) {
55
+ if (frame.summary !== 'open')
56
+ this.problem(line, 'summary closes without an opening tag');
57
+ else
58
+ frame.summary = 'closed';
59
+ }
60
+ else if (frame.summary !== 'absent') {
61
+ this.problem(line, 'details contains more than one summary or a nested summary');
62
+ }
63
+ else
64
+ frame.summary = 'open';
65
+ }
66
+ problem(line, reason) {
67
+ this.diagnostics.push({
68
+ code: 'malformed-details',
69
+ line,
70
+ message: `Malformed details: ${reason}.`,
71
+ });
72
+ }
73
+ }
@@ -0,0 +1,3 @@
1
+ import type { MarkdownSectionsDocument } from './document-types.mts';
2
+ /** Parses visible root H2 sections while retaining exact source ranges and disclosure scope. */
3
+ export declare function parseMarkdownSections(markdown: string): MarkdownSectionsDocument;
@@ -0,0 +1,64 @@
1
+ import { parseGfmMarkdown } from './ast.mjs';
2
+ import { DetailsScope } from './details-scope.mjs';
3
+ /** Parses visible root H2 sections while retaining exact source ranges and disclosure scope. */
4
+ export function parseMarkdownSections(markdown) {
5
+ const sections = [];
6
+ const scope = new DetailsScope(markdown);
7
+ let current;
8
+ const visit = (node, collectText) => {
9
+ if (node.type === 'html') {
10
+ scope.acceptHtml(node.value, node.position.start.offset);
11
+ return;
12
+ }
13
+ if (node.type === 'code' || node.type === 'image')
14
+ return;
15
+ if (collectText &&
16
+ scope.visible &&
17
+ current &&
18
+ (node.type === 'text' || node.type === 'inlineCode') &&
19
+ node.value.trim()) {
20
+ current.hasVisibleContent = true;
21
+ }
22
+ if (!('children' in node))
23
+ return;
24
+ for (const [index, child] of node.children.entries()) {
25
+ visit(child, collectText && node.type !== 'heading' && (node.type !== 'table' || index > 0));
26
+ }
27
+ };
28
+ for (const node of parseGfmMarkdown(markdown).children) {
29
+ if (node.type === 'heading') {
30
+ const wasVisible = scope.visible;
31
+ const heading = headingText(node, scope).replace(/\s+/gu, ' ').trim();
32
+ if (node.depth === 2 && wasVisible && heading) {
33
+ if (current)
34
+ current.endOffset = node.position.start.offset;
35
+ current = {
36
+ heading,
37
+ content: '',
38
+ startOffset: node.position.end.offset,
39
+ endOffset: markdown.length,
40
+ line: node.position.start.line,
41
+ hasVisibleContent: false,
42
+ };
43
+ sections.push(current);
44
+ }
45
+ continue;
46
+ }
47
+ visit(node, node.type === 'paragraph' || node.type === 'list' || node.type === 'table');
48
+ }
49
+ scope.finish();
50
+ for (const section of sections)
51
+ section.content = markdown.slice(section.startOffset, section.endOffset);
52
+ return { sections, diagnostics: scope.diagnostics };
53
+ }
54
+ function headingText(node, scope) {
55
+ if (node.type === 'html') {
56
+ scope.acceptHtml(node.value, node.position.start.offset);
57
+ return '';
58
+ }
59
+ if (node.type === 'text' || node.type === 'inlineCode')
60
+ return scope.visible ? node.value : '';
61
+ if (!('children' in node))
62
+ return '';
63
+ return node.children.map((child) => headingText(child, scope)).join('');
64
+ }
@@ -0,0 +1,21 @@
1
+ export type MarkdownVisibleSection = {
2
+ heading: string;
3
+ content: string;
4
+ startOffset: number;
5
+ endOffset: number;
6
+ line: number;
7
+ hasVisibleContent: boolean;
8
+ };
9
+ export type MarkdownSectionsDiagnostic = {
10
+ code: 'missing-heading' | 'duplicate-heading' | 'empty-section' | 'malformed-details';
11
+ message: string;
12
+ heading?: string;
13
+ line?: number;
14
+ };
15
+ export type MarkdownSectionsDocument = {
16
+ sections: MarkdownVisibleSection[];
17
+ diagnostics: MarkdownSectionsDiagnostic[];
18
+ };
19
+ export type ValidateMarkdownSectionsOptions = {
20
+ requiredHeadings: readonly string[];
21
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,8 @@
1
1
  export { findMarkdownNode, findMarkdownNodes, parseGfmMarkdown, walkMarkdown } from './ast.mts';
2
2
  export { extractLooseMarkdownTableRows } from './loose-pipe-rows.mts';
3
+ export { parseMarkdownSections } from './document-sections.mts';
4
+ export { validateMarkdownSections } from './validate-sections.mts';
5
+ export type { MarkdownVisibleSection, MarkdownSectionsDiagnostic, MarkdownSectionsDocument, ValidateMarkdownSectionsOptions, } from './document-types.mts';
3
6
  export { markdownSectionBetweenHeadings } from './sections.mts';
4
7
  export { extractMarkdownTables, parseMarkdownTables } from './tables.mts';
5
8
  export { markdownNodeText } from './text.mts';
@@ -1,5 +1,7 @@
1
1
  export { findMarkdownNode, findMarkdownNodes, parseGfmMarkdown, walkMarkdown } from './ast.mjs';
2
2
  export { extractLooseMarkdownTableRows } from './loose-pipe-rows.mjs';
3
+ export { parseMarkdownSections } from './document-sections.mjs';
4
+ export { validateMarkdownSections } from './validate-sections.mjs';
3
5
  export { markdownSectionBetweenHeadings } from './sections.mjs';
4
6
  export { extractMarkdownTables, parseMarkdownTables } from './tables.mjs';
5
7
  export { markdownNodeText } from './text.mjs';
@@ -0,0 +1,3 @@
1
+ import type { MarkdownSectionsDiagnostic, MarkdownSectionsDocument, ValidateMarkdownSectionsOptions } from './document-types.mts';
2
+ /** Validates caller-owned headings and visible core content without inferring semantic relevance. */
3
+ export declare function validateMarkdownSections(document: MarkdownSectionsDocument, options: ValidateMarkdownSectionsOptions): MarkdownSectionsDiagnostic[];
@@ -0,0 +1,34 @@
1
+ /** Validates caller-owned headings and visible core content without inferring semantic relevance. */
2
+ export function validateMarkdownSections(document, options) {
3
+ const diagnostics = [...document.diagnostics];
4
+ for (const heading of options.requiredHeadings) {
5
+ const sections = document.sections.filter((section) => section.heading === heading);
6
+ if (sections.length === 0) {
7
+ diagnostics.push({
8
+ code: 'missing-heading',
9
+ heading,
10
+ message: `Missing required section: ${heading}.`,
11
+ });
12
+ continue;
13
+ }
14
+ if (sections.length > 1) {
15
+ diagnostics.push({
16
+ code: 'duplicate-heading',
17
+ heading,
18
+ line: sections[1].line,
19
+ message: `Duplicate required section: ${heading}.`,
20
+ });
21
+ }
22
+ for (const section of sections) {
23
+ if (!section.hasVisibleContent) {
24
+ diagnostics.push({
25
+ code: 'empty-section',
26
+ heading,
27
+ line: section.line,
28
+ message: `Required section has no visible prose, list, or table content: ${heading}.`,
29
+ });
30
+ }
31
+ }
32
+ }
33
+ return diagnostics;
34
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vouchington-tooling",
3
- "version": "0.22.1",
3
+ "version": "0.23.0",
4
4
  "description": "Vouchington CLI and extractable tooling libraries.",
5
5
  "homepage": "https://github.com/vouchington/vouchington-tooling/tree/main/packages/vouchington-tooling#readme",
6
6
  "bugs": {
@@ -13,6 +13,11 @@ Use before implementation. Inspect checkout status without discarding work; foll
13
13
  worktree, contribution, and CI policy. Read every applicable `AGENTS.md` from the repository root
14
14
  through changed files, plus relevant documentation and tests. Local instructions govern this skill.
15
15
 
16
+ Use [PR descriptions](../pr-description/SKILL.md) before creating or updating a pull request.
17
+ Diagnose failed CI with [CI-log review](../review-ci-logs/SKILL.md), then update confirmed harness
18
+ gaps before handoff. Keep those records after checks pass; routine local successes stay in the
19
+ evidence record.
20
+
16
21
  - During implementation, use [implementation](references/implementation.md).
17
22
  - Before handoff, use [evidence sweep](references/evidence-sweep.md).
18
23
  - For review, use [review](references/review.md).
@@ -5,3 +5,6 @@ contract compatibility, failure paths, authorization, untrusted input, secrets,
5
5
  boundaries. Remove dead paths and stale documentation rather than labeling them as legacy.
6
6
 
7
7
  Record unresolved risk as an explicit accepted decision or follow-up, never as an unnoticed gap.
8
+ Check the pull-request description against the final behavior and audience impact. After CI
9
+ diagnosis, ensure confirmed local-verification gaps or local/CI mismatches and their correction
10
+ remain disclosed; passing checks do not erase the gap discovered earlier.
@@ -4,9 +4,9 @@
4
4
  {
5
5
  "name": "agent-workflow",
6
6
  "plugin": "vouchington-workflow",
7
- "pluginVersion": "0.8.0",
7
+ "pluginVersion": "0.9.0",
8
8
  "path": "agent-workflow/SKILL.md",
9
- "prerequisites": ["github-issue"]
9
+ "prerequisites": ["github-issue", "pr-description", "review-ci-logs"]
10
10
  },
11
11
  {
12
12
  "name": "backend-vitest-test-authoring",
@@ -18,13 +18,13 @@
18
18
  {
19
19
  "name": "blackboard",
20
20
  "plugin": "vouchington-workflow",
21
- "pluginVersion": "0.8.0",
21
+ "pluginVersion": "0.9.0",
22
22
  "path": "blackboard/SKILL.md"
23
23
  },
24
24
  {
25
25
  "name": "dependabot",
26
26
  "plugin": "vouchington-workflow",
27
- "pluginVersion": "0.8.0",
27
+ "pluginVersion": "0.9.0",
28
28
  "path": "dependabot/SKILL.md",
29
29
  "prerequisites": ["github-actions-checklist"]
30
30
  },
@@ -38,26 +38,26 @@
38
38
  {
39
39
  "name": "git-commit-checklist",
40
40
  "plugin": "vouchington-workflow",
41
- "pluginVersion": "0.8.0",
41
+ "pluginVersion": "0.9.0",
42
42
  "path": "git-commit-checklist/SKILL.md"
43
43
  },
44
44
  {
45
45
  "name": "github-actions-authoring",
46
46
  "plugin": "vouchington-workflow",
47
- "pluginVersion": "0.8.0",
47
+ "pluginVersion": "0.9.0",
48
48
  "path": "github-actions-authoring/SKILL.md",
49
49
  "prerequisites": ["github-actions-checklist"]
50
50
  },
51
51
  {
52
52
  "name": "github-actions-checklist",
53
53
  "plugin": "vouchington-workflow",
54
- "pluginVersion": "0.8.0",
54
+ "pluginVersion": "0.9.0",
55
55
  "path": "github-actions-checklist/SKILL.md"
56
56
  },
57
57
  {
58
58
  "name": "github-issue",
59
59
  "plugin": "vouchington-workflow",
60
- "pluginVersion": "0.8.0",
60
+ "pluginVersion": "0.9.0",
61
61
  "path": "github-issue/SKILL.md"
62
62
  },
63
63
  {
@@ -70,26 +70,26 @@
70
70
  {
71
71
  "name": "npm-publishing",
72
72
  "plugin": "vouchington-workflow",
73
- "pluginVersion": "0.8.0",
73
+ "pluginVersion": "0.9.0",
74
74
  "path": "npm-publishing/SKILL.md"
75
75
  },
76
76
  {
77
77
  "name": "organize-github-issues",
78
78
  "plugin": "vouchington-workflow",
79
- "pluginVersion": "0.8.0",
79
+ "pluginVersion": "0.9.0",
80
80
  "path": "organize-github-issues/SKILL.md",
81
81
  "prerequisites": ["github-issue"]
82
82
  },
83
83
  {
84
84
  "name": "package-json-checklist",
85
85
  "plugin": "vouchington-workflow",
86
- "pluginVersion": "0.8.0",
86
+ "pluginVersion": "0.9.0",
87
87
  "path": "package-json-checklist/SKILL.md"
88
88
  },
89
89
  {
90
90
  "name": "planning",
91
91
  "plugin": "vouchington-workflow",
92
- "pluginVersion": "0.8.0",
92
+ "pluginVersion": "0.9.0",
93
93
  "path": "planning/SKILL.md",
94
94
  "prerequisites": ["github-issue"]
95
95
  },
@@ -115,53 +115,53 @@
115
115
  {
116
116
  "name": "pr-description",
117
117
  "plugin": "vouchington-workflow",
118
- "pluginVersion": "0.8.0",
118
+ "pluginVersion": "0.9.0",
119
119
  "path": "pr-description/SKILL.md"
120
120
  },
121
121
  {
122
122
  "name": "retrospective",
123
123
  "plugin": "vouchington-workflow",
124
- "pluginVersion": "0.8.0",
124
+ "pluginVersion": "0.9.0",
125
125
  "path": "retrospective/SKILL.md"
126
126
  },
127
127
  {
128
128
  "name": "retrospective-distill",
129
129
  "plugin": "vouchington-workflow",
130
- "pluginVersion": "0.8.0",
130
+ "pluginVersion": "0.9.0",
131
131
  "path": "retrospective-distill/SKILL.md",
132
132
  "prerequisites": ["github-issue"]
133
133
  },
134
134
  {
135
135
  "name": "review-ci-logs",
136
136
  "plugin": "vouchington-workflow",
137
- "pluginVersion": "0.8.0",
137
+ "pluginVersion": "0.9.0",
138
138
  "path": "review-ci-logs/SKILL.md",
139
- "prerequisites": ["github-actions-checklist"]
139
+ "prerequisites": ["github-actions-checklist", "pr-description"]
140
140
  },
141
141
  {
142
142
  "name": "review-github-issue-taxonomy",
143
143
  "plugin": "vouchington-workflow",
144
- "pluginVersion": "0.8.0",
144
+ "pluginVersion": "0.9.0",
145
145
  "path": "review-github-issue-taxonomy/SKILL.md",
146
146
  "prerequisites": ["github-issue"]
147
147
  },
148
148
  {
149
149
  "name": "revisit-followups",
150
150
  "plugin": "vouchington-workflow",
151
- "pluginVersion": "0.8.0",
151
+ "pluginVersion": "0.9.0",
152
152
  "path": "revisit-followups/SKILL.md",
153
153
  "prerequisites": ["github-issue"]
154
154
  },
155
155
  {
156
156
  "name": "stacked-prs",
157
157
  "plugin": "vouchington-workflow",
158
- "pluginVersion": "0.8.0",
158
+ "pluginVersion": "0.9.0",
159
159
  "path": "stacked-prs/SKILL.md"
160
160
  },
161
161
  {
162
162
  "name": "static-analysis-checklist",
163
163
  "plugin": "vouchington-workflow",
164
- "pluginVersion": "0.8.0",
164
+ "pluginVersion": "0.9.0",
165
165
  "path": "static-analysis-checklist/SKILL.md",
166
166
  "prerequisites": ["github-actions-checklist"]
167
167
  },
@@ -9,20 +9,54 @@ Read applicable `AGENTS.md` and their declared adapter alias, otherwise
9
9
  `.agents/skills/pr-description/SKILL.md` from the repository root if present.
10
10
  Local policy overrides these defaults; do not reload an already-read adapter or canonical skill.
11
11
 
12
- Use before opening or updating a pull request, or when reviewing PR hand-off quality. Read local
12
+ Use before opening or updating a pull request, after diagnosing a CI failure, or at handoff. Read local
13
13
  `AGENTS.md`, pull-request template, and issue-linking policy first.
14
14
 
15
- Include enough context for a reviewer with no prior conversation:
15
+ Keep the description understandable without the conversation. Its short visible core explains:
16
16
 
17
- - A concise summary of what changed and why.
18
- - The underlying root cause for a fix, not only its visible symptom.
19
- - Validation performed and any intentionally skipped checks with reasons.
20
- - Rollout, compatibility, operational safety, and follow-up context when the change affects a live
21
- or independently deployed surface.
22
- - Correct issue relationships: close only issues fully resolved and explain any non-closing links.
23
- - A compact diagram only when it materially clarifies a multi-component flow or lifecycle.
17
+ - **Summary:** what changed, why, and the concrete before/after behavior. For a fix, explain its
18
+ root cause as well as the symptom. A reader should understand the outcome before implementation
19
+ details.
20
+ - **Impact:** who or what is affected and how their behavior changes. Name the actual audience,
21
+ such as end users, staff, API clients, operators, or contributors. Keep material risks and
22
+ limitations visible. For internal work, describe the affected development or operational flow.
24
23
 
25
- Use the repository's approved PR tooling and preserve sections managed by its review automation.
24
+ Add detail only where the change warrants it:
25
+
26
+ - For cost changes, state the direction and scope. Label figures as measured or estimated, name the
27
+ workload and assumptions, and link evidence. Say unmeasured when no defensible figure exists;
28
+ do not invent savings or claim a neutral cost without evidence. Follow the consumer's privacy
29
+ policy before reporting actual spending, and use approved aggregated or redacted evidence.
30
+ - For database changes, inventory added, removed, and changed tables, columns, constraints, and
31
+ indexes. Explain the relationships and conformance to the consumer's schema rules, including
32
+ producer/consumer changes and any justified exceptions. Link the policy and concrete evidence.
33
+ - For API, workflow, or independently deployed changes, explain the affected contract, lifecycle,
34
+ or operational safety. Follow the consumer's compatibility and deployment policy.
35
+ - Use a before/after table or meaningful Mermaid diagram when it makes behavior, relationships,
36
+ workflow topology, or lifecycle easier to understand. Prose is sufficient for a simple change.
37
+
38
+ Put long comparisons, inventories, diagrams, calculations, and failure evidence in collapsed
39
+ `<details>` sections. Keep the short conclusions and material risks outside them. Keep the
40
+ consumer's required visible core headings outside disclosure containers. Put opening/closing tags
41
+ on separate lines, with a blank line after `</summary>` and before `</details>` so Markdown renders.
42
+ Read [description examples](references/examples.md) when choosing a comparison, diagram, or failure
43
+ record.
44
+
45
+ Do not fill the description with routine local successful-test lists. This is a reporting rule,
46
+ not permission to skip checks: execute required validation and retain its revision, exact scope,
47
+ result, and any omission reason in the work's evidence record.
48
+
49
+ Include **Harness gaps** only after CI diagnosis establishes an omitted local verification or a
50
+ proven local/CI mismatch. Link the first failing run/job and its exact head revision, compare the actual local and
51
+ CI scope, explain the omission or difference, and record the durable correction or authorized
52
+ linked follow-up. Distinguish an omitted check from one that passed locally. Unknown coverage is
53
+ not evidence of a miss; classify genuine CI-only or external infrastructure failures separately.
54
+ Retain confirmed gap records after CI turns green. Diagnose the failure through the repository's
55
+ CI investigation workflow; a failed status alone does not establish a gap.
56
+
57
+ Use the repository's approved PR tooling and preserve sections managed by its review automation
58
+ verbatim. Close only issues fully resolved and explain non-closing issue links. Apply this guidance
59
+ to descriptions being created or updated, not a bulk rewrite of historical pull requests.
26
60
 
27
61
  Consumer wrapper owns: required headings, source-issue rule, merge policy, repository command, and
28
62
  hosted review system.
@@ -0,0 +1,136 @@
1
+ # Description examples
2
+
3
+ These illustrative descriptions show how to keep conclusions visible while disclosing longer
4
+ evidence. Use the consumer's actual headings, policy links, issue relationships, and run evidence.
5
+ Do not copy a claim or figure unless the current change supports it.
6
+
7
+ ## Small behavior fix
8
+
9
+ ```markdown
10
+ ## Summary
11
+
12
+ Reopening the review dialog now cancels its pending reset. Previously, the reset cleared the
13
+ new selection because the timer outlived the closed state.
14
+
15
+ ## Impact
16
+
17
+ Staff can close and reopen a review without losing the new selection. Closing normally still
18
+ clears the old selection after the exit animation.
19
+ ```
20
+
21
+ No diagram, cost calculation, database inventory, or local successful-test list is needed.
22
+
23
+ ## Database relationship change
24
+
25
+ ```markdown
26
+ ## Summary
27
+
28
+ Recovery attempts now reference retained account identities through a foreign key. Previously,
29
+ an unvalidated identifier could refer to an account that no longer existed.
30
+
31
+ ## Impact
32
+
33
+ Operators can trace each attempt to a retained identity, and deletion cannot orphan recovery
34
+ history. The API's recovery response is unchanged. Storage cost is unmeasured; retaining an
35
+ identity row per deleted account adds storage proportional to account deletions.
36
+
37
+ <details>
38
+ <summary>Schema inventory and conformance</summary>
39
+
40
+ | Object | Before | After |
41
+ | --------------------------------------------- | ----------------------- | --------------------------------- |
42
+ | `account_identity` table | Absent | Retains each account's identity |
43
+ | `recovery_attempt.account_id` column | Unvalidated UUID | Removed |
44
+ | `recovery_attempt.account_identity_id` column | Absent | Required foreign key |
45
+ | Foreign-key constraint | Absent | References `account_identity(id)` |
46
+ | Lookup index | Uses the removed column | Uses `account_identity_id` |
47
+
48
+ The relationship uses a typed column and concrete foreign key, as required by the repository's
49
+ relational-storage policy. The current schema, recovery writer, account-deletion path, and
50
+ operator query change together. There is no dual reader or writer.
51
+
52
+ </details>
53
+ ```
54
+
55
+ In a real description, link the consumer's policy and implementation evidence. Do not prescribe
56
+ this consumer-specific retention or deployment policy for other repositories.
57
+
58
+ ## Workflow and cost change
59
+
60
+ ````markdown
61
+ ## Summary
62
+
63
+ Test jobs now run once per revision. Previously, changing a pull request's draft status started
64
+ a duplicate test run even though no test job consumed draft state.
65
+
66
+ ## Impact
67
+
68
+ Contributors keep the checks already attached to their revision when marking it ready. Estimated
69
+ runner use falls by one duplicate suite per draft-to-ready transition; the estimate assumes
70
+ one transition and a 12-minute suite. Aggregate savings are unmeasured.
71
+
72
+ <details>
73
+ <summary>Before and after event flow</summary>
74
+
75
+ ```mermaid
76
+ flowchart LR
77
+ subgraph Before
78
+ B1[Revision pushed] --> B3[Test suite]
79
+ B2[Draft marked ready] --> B4[Duplicate test suite]
80
+ end
81
+ subgraph After
82
+ A1[Revision pushed] --> A3[Test suite]
83
+ A2[Draft marked ready] --> A4[Merge eligibility only]
84
+ end
85
+ ```
86
+
87
+ </details>
88
+ ````
89
+
90
+ The diagram explains event ownership; a file list or arrows between the Summary and Impact sections
91
+ would not clarify the behavior. Link the run measurements supporting a real estimate.
92
+
93
+ ## Harness gap: omitted local verification
94
+
95
+ ```markdown
96
+ ## Harness gaps
97
+
98
+ CI found that the local pre-push command omitted the compiler-contract suite. The harness now
99
+ includes that suite, and the missing public export is fixed.
100
+
101
+ <details>
102
+ <summary>Failure and durable correction</summary>
103
+
104
+ The failed compiler-contract job on the first pushed revision reported the missing export.
105
+ The pre-push record ran the component project only; the compiler-contract project was omitted
106
+ because the command catalog did not include it. The catalog and pre-push selection now include
107
+ the project, and a public-import regression covers this export.
108
+
109
+ </details>
110
+ ```
111
+
112
+ The actual record must link the failing run/job and exact head SHA, plus the local scope and
113
+ correction evidence. Retain it when the corrected revision passes CI.
114
+
115
+ ## Harness gap: passed locally, failed in CI
116
+
117
+ ```markdown
118
+ ## Harness gaps
119
+
120
+ The contract suite passed locally but failed in CI because the local runner selected the host
121
+ architecture instead of the deployment target. Local validation now selects that target explicitly.
122
+
123
+ <details>
124
+ <summary>Local and CI scope difference</summary>
125
+
126
+ The pre-push record and failing job used the same suite on the same revision. Their target
127
+ configurations differed: local selected the host target, while CI compiled the deployment target.
128
+ The local command now uses the deployment target, and a compiler regression covers the rejected
129
+ cross-target import. This was a configuration mismatch, not an omitted suite.
130
+
131
+ </details>
132
+ ```
133
+
134
+ Link the concrete revision, failed job, local evidence, and correction in the actual record.
135
+ An external service outage, unavailable CI-only credentials, or unknown local execution history
136
+ does not support this claim. Explain such limitations separately when they affect the outcome.
@@ -18,6 +18,11 @@ Use when investigating a CI failure, repeated workflow noise, or misleading diag
18
18
  failed steps and representative large entries, then remove the temporary artifacts.
19
19
  3. Classify each finding as a real error, misleading output, a downstream cascade, a necessary
20
20
  diagnostic, or a volume-only concern. Identify the first repository-owned root cause.
21
+ Compare the first failing run/job, its exact head revision, and scope with the pre-push verification record.
22
+ Establish whether the relevant local verification was omitted, passed under a different scope
23
+ or environment, genuinely CI-only, or unknown. A shared command name does not prove shared
24
+ project, target, fixtures, or configuration. Do not infer an omission from missing evidence or
25
+ label an external infrastructure failure a harness miss.
21
26
  4. For a persistent-workspace failure, name the producer of sparse state or unsafe ownership before
22
27
  proposing cleanup. Reject an unconditional pre-checkout workspace traversal. A compliant repair
23
28
  is the one in [github-actions-checklist](../github-actions-checklist/SKILL.md).
@@ -27,6 +32,11 @@ Use when investigating a CI failure, repeated workflow noise, or misleading diag
27
32
  6. Add focused regression evidence, run local workflow validation, and compare before and after
28
33
  output where that comparison is meaningful. Report deferred findings. Do not create issues unless
29
34
  authorized.
35
+ 7. Update [the PR description](../pr-description/SKILL.md) before handoff with confirmed omitted
36
+ verification or proven local/CI mismatch. Link the first failed run/job and its exact revision, the actual local
37
+ scope, the reason it missed the failure, and the durable harness correction or authorized
38
+ follow-up. Keep the distinction between omitted and locally passed checks explicit, and retain
39
+ confirmed gap records after CI turns green. Classify CI-only and unknown coverage separately.
30
40
 
31
41
  Consumer wrapper owns: workflow names, log retention, the CI provider command, retry policy, and
32
42
  scheduled automation.