copilot-session-usage 0.8.1__tar.gz → 0.8.4__tar.gz
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.
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/skills/gh-release-notes/SKILL.md +129 -61
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/skills/gh-release-notes/scripts/generate_release_notes.py +138 -32
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/workflows/release.yml +3 -12
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/CONTRIBUTING.md +11 -1
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/PKG-INFO +1 -1
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/src/copilot_session_usage/data/models-and-pricing.lock +3 -3
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/src/copilot_session_usage/data/models-and-pricing.yml +104 -0
- copilot_session_usage-0.8.4/tests/test_generate_release_notes.py +631 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/tests/test_release_notes.py +36 -0
- copilot_session_usage-0.8.1/tests/test_generate_release_notes.py +0 -333
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.editorconfig +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.gitattributes +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/changes/requests/skill-breakdown/01-request.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/guidelines/git-commit-message.guideline.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/guidelines/knowledge-base.guidelines.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/pull_request_template.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/skills/consolidate-knowledge-base/SKILL.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/skills/gh-commit-changes/SKILL.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/skills/gh-create-pull-request/SKILL.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/skills/record-finding/SKILL.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/workflows/ci.yml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/workflows/publish.yml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/workflows/refresh-pricing.yml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/workflows/release-notes.yml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.gitignore +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.readthedocs.yaml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/AGENTS.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/CHANGELOG.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/CONSTITUTION.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/LICENSE +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/README.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/articles/presentation.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/internal/automated_release_proces.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/_static/changelog.js +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/_static/custom.css +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/changelog.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/conf.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/explanation/how-cost-estimation-works.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/explanation/index.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/how-to/add-commit-trailer.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/how-to/analyze-specific-session.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/how-to/batch-and-spending.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/how-to/copilot-cli-provider.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/how-to/export-json.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/how-to/index.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/how-to/wsl2.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/index.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/installation.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/reference/api.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/reference/cli.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/reference/index.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/reference/pricing.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/tutorials/getting-started.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/docs/source/tutorials/index.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/guidelines.yml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/justfile +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/_schema/Base.schema.yaml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/_schema/Concept.schema.yaml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/_schema/Experiment.schema.yaml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/_schema/Finding.schema.yaml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/_schema/Hypothesis.schema.yaml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/_schema/Outcome.schema.yaml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/_schema/Playbook.schema.yaml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/_schema/Principle.schema.yaml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/_schema/Reference.schema.yaml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/_schema/Structure.schema.yaml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/concepts/copilot-cli.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/concepts/index.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/concepts/overview.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/concepts/session-cost-analysis.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/concepts/threshold-based-pricing.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/experiments/index.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/experiments/verify-subagent-cost-attribution.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/findings/2026.07.02-00.00-subagent-logs-runsubagent-prefix.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/findings/2026.07.02-22.00-title-generation-not-counted-as-model-turn.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/findings/2026.07.02-23.00-cache-write-approximation.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/findings/2026.07.13-14.27-release-notes-overreported-maintainer-changes.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/findings/2026.07.24-15.01-actions-token-cannot-create-pr.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/findings/2026.08.05-16.24-utility-models-appear-in-logs-but-are-not-billed.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/findings/index.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/guides/automation-scripts.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/guides/cost-optimization.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/guides/index.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/guides/wsl2-setup.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/ideas/index.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/ideas/multi-session-efficiency-analytics.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/index.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/log.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/principles/findings-are-immutable.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/principles/index.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/reference/debug-log-format.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/reference/index.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/reference/pricing-formats.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/structures/cache-cost-approximation.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/structures/index.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/structures/knowledge-base-information-types.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/structures/session-discovery-algorithm.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/structures/subagent-cost-tracking.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/knowledge/structures/vscode-copilot-extension.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/pyproject.toml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/scripts/refresh_pricing.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/skills/copilot-session-usage/SKILL.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/skills/copilot-session-usage/references/span-analysis-template.md +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/src/copilot_session_usage/__init__.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/src/copilot_session_usage/_internal/__init__.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/src/copilot_session_usage/_internal/copilot_cli.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/src/copilot_session_usage/_internal/core.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/src/copilot_session_usage/_internal/git.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/src/copilot_session_usage/_internal/vscode.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/src/copilot_session_usage/api.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/src/copilot_session_usage/cli.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/src/copilot_session_usage/data/__init__.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/src/copilot_session_usage/data/custom-models-pricing.yml +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/tests/conftest.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/tests/test_api.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/tests/test_cli.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/tests/test_copilot_cli.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/tests/test_core.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/tests/test_coverage_gaps.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/tests/test_git.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/tests/test_pricing_runtime.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/tests/test_rendering.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/tests/test_vscode.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/tests/test_vscode_platform.py +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/uv.lock +0 -0
- {copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/uv.toml +0 -0
{copilot_session_usage-0.8.1 → copilot_session_usage-0.8.4}/.github/skills/gh-release-notes/SKILL.md
RENAMED
|
@@ -10,6 +10,20 @@ user-invocable: true
|
|
|
10
10
|
Generate **end-user-friendly, user-impact-only** release notes by analyzing the actual changes between releases.
|
|
11
11
|
Interactive use requires no script; automated generation and validation use the bundled script described below.
|
|
12
12
|
|
|
13
|
+
## Portability
|
|
14
|
+
|
|
15
|
+
This skill works with any Git repository, regardless of language, framework,
|
|
16
|
+
directory layout, documentation host, or release workflow. Discover the target
|
|
17
|
+
product and its public documentation from the requested repository; do not assume
|
|
18
|
+
the skill's own repository is the product being released.
|
|
19
|
+
|
|
20
|
+
The bundled script uses only the Python standard library (Python 3.10+), Git,
|
|
21
|
+
and `gh copilot`. Install this skill where Copilot can discover it in the target
|
|
22
|
+
repository or user-level skill directory. The script may live outside the target
|
|
23
|
+
repository; select that repository with `--repo`. No project package, task runner,
|
|
24
|
+
configuration file, or particular CI workflow is required. Documentation links
|
|
25
|
+
come from repository evidence, not a built-in domain allowlist.
|
|
26
|
+
|
|
13
27
|
The output is meant to be **copy-pasted into a GitHub Release**. It must contain
|
|
14
28
|
release-note sections only: never add a document title, version heading, preamble,
|
|
15
29
|
file summary, commit summary, or closing separator. GitHub already displays the
|
|
@@ -18,13 +32,22 @@ Markdown.
|
|
|
18
32
|
|
|
19
33
|
## Non-negotiable output contract
|
|
20
34
|
|
|
35
|
+
- Describe each user outcome once, in one concise bullet. End that same bullet
|
|
36
|
+
with the closest verified documentation link when available. Do not collect
|
|
37
|
+
those links at the bottom or repeat the change in Documentation or Examples.
|
|
38
|
+
- Scale notes to the actual change: one pricing refresh or small fix normally
|
|
39
|
+
needs one bullet; a large feature release needs distinct outcomes, not a
|
|
40
|
+
bullet for every commit, field, edge case, or implementation detail.
|
|
21
41
|
- Render every public documentation URL as an inline Markdown link with concise
|
|
22
42
|
descriptive text: `See the [pricing reference for details](https://example.com/pricing)`.
|
|
23
43
|
- Never expose a documentation URL as plain text, after a colon, in parentheses,
|
|
24
44
|
or on its own line. A bare `https://...` URL is invalid release-note output.
|
|
25
45
|
- Never include CI, release automation, Git evidence, generator internals,
|
|
26
|
-
governance, contributor or agent guidance, tests, repository housekeeping, or
|
|
46
|
+
internal governance, contributor or agent guidance, tests, repository housekeeping, or
|
|
27
47
|
maintainer process.
|
|
48
|
+
- Apply internal-material exclusions by audience, not filename. If the target
|
|
49
|
+
repository publishes skills, documentation, or test utilities as its product,
|
|
50
|
+
describe evidenced changes for that product's users.
|
|
28
51
|
- Never include `## Maintenance` together with any user-facing section. When a
|
|
29
52
|
user-facing change qualifies, omit Maintenance and discard all internal-only
|
|
30
53
|
candidates.
|
|
@@ -44,20 +67,31 @@ The skill includes `scripts/generate_release_notes.py`, a standalone Python scri
|
|
|
44
67
|
- writes the deterministic `## Maintenance` output without invoking Copilot when the
|
|
45
68
|
requested Git range contains no commits or the caller explicitly requests
|
|
46
69
|
`--maintenance-only`;
|
|
47
|
-
- precomputes the commit log and
|
|
70
|
+
- precomputes the commit log, complete diff summary, and full diff locally so generation also works
|
|
48
71
|
when the Copilot CLI cannot inspect Git history inside its tool environment;
|
|
49
72
|
- invokes the Copilot CLI with `/gh-release-notes`;
|
|
50
|
-
-
|
|
51
|
-
|
|
73
|
+
- exposes Copilot's `view`, `glob`, and `grep` tools for reading and finding
|
|
74
|
+
skill instructions and exact documentation pages (`read` is a permission
|
|
75
|
+
category, not a builtin tool name);
|
|
76
|
+
- invokes Copilot with `--output-format json`, reads JSONL events, and selects
|
|
77
|
+
only the completed `assistant.message` with `phase: final_answer`; commentary,
|
|
78
|
+
streaming deltas, tool results, and telemetry never become release notes;
|
|
79
|
+
- validates the final Markdown and writes the requested file itself, without
|
|
80
|
+
depending on the model's file-writing tools;
|
|
81
|
+
- retries generation up to three times with validation feedback, with a five-minute
|
|
82
|
+
timeout per request, clearing rejected output before each retry;
|
|
83
|
+
- refuses publication after exhausted attempts, never substituting Maintenance
|
|
84
|
+
for a failed request; and
|
|
52
85
|
- verifies that the requested output file follows the release-note output contract. The
|
|
53
86
|
script does not normalize Markdown or decide user impact, categorize changes,
|
|
54
87
|
discover documentation, infer breaking changes, or require examples; those
|
|
55
88
|
decisions belong to this skill.
|
|
56
89
|
|
|
57
|
-
Generate notes for an exact range
|
|
90
|
+
Generate notes for an exact range using the script's installed path:
|
|
58
91
|
|
|
59
92
|
```bash
|
|
60
|
-
python
|
|
93
|
+
python /path/to/gh-release-notes/scripts/generate_release_notes.py \
|
|
94
|
+
--repo /path/to/repo \
|
|
61
95
|
--from-ref v1.0.0 \
|
|
62
96
|
--to-ref v1.1.0 \
|
|
63
97
|
--output release-notes.md
|
|
@@ -67,21 +101,28 @@ For an intentional maintenance-only release whose range contains internal commit
|
|
|
67
101
|
pass `--maintenance-only` to write the deterministic Maintenance section without
|
|
68
102
|
invoking Copilot.
|
|
69
103
|
|
|
70
|
-
`release-notes.md` is the
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
104
|
+
`release-notes.md` is the default output filename, relative to the target repository.
|
|
105
|
+
An explicitly supplied `--output` path is honored exactly; absolute output paths
|
|
106
|
+
are also supported. A release workflow can consume the validated file through
|
|
107
|
+
`gh release --notes-file` or another publishing tool. This skill does not require
|
|
108
|
+
or create that workflow and does not publish releases itself.
|
|
109
|
+
For a non-empty range, the generator clears stale output
|
|
110
|
+
before invoking Copilot and persists only a validated structured final answer.
|
|
111
|
+
Copilot has reading and Git tools, but no file-writing tools. The CLI must support
|
|
112
|
+
JSONL output with final-answer phases and a successful terminal `result` event.
|
|
113
|
+
Malformed or incomplete streams fail validation; no Markdown is scraped from
|
|
114
|
+
surrounding chatter or tool traces.
|
|
115
|
+
An empty range produces the valid maintenance output
|
|
77
116
|
directly and does not require Copilot authentication or skill discovery.
|
|
78
117
|
|
|
79
118
|
The range is Git's two-dot range, `from_ref..to_ref`: `from_ref` itself is excluded
|
|
80
119
|
and `to_ref` is included. Both refs may be tags, branches, or commit IDs.
|
|
81
120
|
|
|
82
121
|
Set `COPILOT_MODEL` or pass `--model` to make model selection explicit. The script also
|
|
83
|
-
supports `--validate FILE`
|
|
84
|
-
|
|
122
|
+
supports `--validate FILE` to enforce section structure, flat non-empty bullets,
|
|
123
|
+
HTTPS Markdown links, exact duplicate rejection, and Maintenance exclusivity
|
|
124
|
+
without invoking Copilot. Semantic accuracy and paraphrased repetition still
|
|
125
|
+
require the skill's audience review.
|
|
85
126
|
|
|
86
127
|
---
|
|
87
128
|
|
|
@@ -107,7 +148,7 @@ What changed since the last release tag?
|
|
|
107
148
|
2. **Applies a strict user-impact gate** — includes a change only when an end user can do, observe, configure, rely on, or learn something different
|
|
108
149
|
3. **Interprets for end-users** — no technical jargon, functions, variable names, file paths, or implementation summaries
|
|
109
150
|
4. **Categorizes intelligently** — Features, Enhancements, Bug Fixes, Breaking Changes, Examples, Documentation, and Maintenance
|
|
110
|
-
5. **Adds
|
|
151
|
+
5. **Adds examples when useful** — includes short inline syntax only when it helps users act
|
|
111
152
|
6. **Links to relevant public docs** — links each user-facing change to the closest published documentation page with concise inline Markdown link text when one exists, even if that page was not changed in the release
|
|
112
153
|
7. **Consolidates related changes** — groups related diffs and eliminates back-and-forth noise
|
|
113
154
|
8. **Outputs clean markdown** — ready to paste into a GitHub Release note
|
|
@@ -169,9 +210,12 @@ Gather all commits in the specified range with their messages.
|
|
|
169
210
|
|
|
170
211
|
### Step 2: Examine relevant diffs
|
|
171
212
|
```bash
|
|
172
|
-
git diff v1.0.0..v1.1.0
|
|
213
|
+
git diff --no-ext-diff v1.0.0..v1.1.0
|
|
173
214
|
```
|
|
174
|
-
Read actual
|
|
215
|
+
Read actual changes to understand behavior. Determine which files form the
|
|
216
|
+
published product before narrowing the analysis. Do not exclude directories by
|
|
217
|
+
name: a skill catalog, documentation site, or testing library may publish skills,
|
|
218
|
+
guidelines, knowledge, or test utilities as its actual product.
|
|
175
219
|
|
|
176
220
|
Treat these as non-release content, and exclude them entirely unless the diff
|
|
177
221
|
also proves a direct change to the published user experience:
|
|
@@ -186,7 +230,9 @@ also proves a direct change to the published user experience:
|
|
|
186
230
|
|
|
187
231
|
These exclusions can be overridden only when the diff proves a direct user
|
|
188
232
|
impact, such as a packaging change that changes the installable artifact or a
|
|
189
|
-
security fix that changes behavior for users.
|
|
233
|
+
security fix that changes behavior for users. Published skills, guidelines, or
|
|
234
|
+
test utilities also qualify when they are the requested repository's product,
|
|
235
|
+
rather than internal support material. In that case, describe the user
|
|
190
236
|
outcome, not the internal mechanism or workflow that enabled it.
|
|
191
237
|
|
|
192
238
|
After identifying a qualifying change, inspect the repository's user-facing
|
|
@@ -223,6 +269,21 @@ Do not wait for the documentation page itself to be modified. For example, a
|
|
|
223
269
|
pricing or model-data change should link to the project's pricing/model reference
|
|
224
270
|
page if that page explains the affected behavior.
|
|
225
271
|
|
|
272
|
+
Keep the link at the end of the bullet that describes the change, before starting
|
|
273
|
+
the next item. For a fictional reporting application, for example:
|
|
274
|
+
|
|
275
|
+
```markdown
|
|
276
|
+
## New Features
|
|
277
|
+
- **PDF report exports** with `report export --format pdf`. [Export guide](https://docs.example.com/reports/export).
|
|
278
|
+
- **Scheduled reports** with configurable delivery times. [Scheduling guide](https://docs.example.com/reports/scheduling).
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
These are layout examples, not claims about the requested range. Verify syntax
|
|
282
|
+
and URLs from the checked-out product documentation. Use a section fragment only
|
|
283
|
+
when its anchor is verified; do not guess one from a heading. Do not add a
|
|
284
|
+
Documentation section linking these guides again. A guide modified alongside its
|
|
285
|
+
feature belongs with the feature, not in a second bullet saying the guide changed.
|
|
286
|
+
|
|
226
287
|
If no trustworthy public documentation URL can be established, omit the
|
|
227
288
|
Documentation section and documentation bullets rather than writing a text-only
|
|
228
289
|
documentation entry. Never emit `## Documentation` unless its body contains at
|
|
@@ -247,6 +308,14 @@ Breaking changes come from:
|
|
|
247
308
|
- If a feature was added then removed → don't mention it
|
|
248
309
|
- If something changed multiple times → only note the final state
|
|
249
310
|
- If multiple commits fix the same issue → merge into one bullet
|
|
311
|
+
- Assign each outcome to exactly one section. A breaking change belongs in
|
|
312
|
+
Breaking Changes, not again in New Features or Enhancements.
|
|
313
|
+
- Put useful command syntax inline in the outcome's bullet. Add Examples only
|
|
314
|
+
for a distinct workflow that cannot be explained by that short syntax; do not
|
|
315
|
+
restate the same capability under a second heading.
|
|
316
|
+
- Delete unchanged defaults, generic benefits, "also updated documentation",
|
|
317
|
+
"improved reliability", and "no breaking changes" unless they convey a specific
|
|
318
|
+
evidenced difference users need to know.
|
|
250
319
|
|
|
251
320
|
### Step 6: Categorize & Format
|
|
252
321
|
|
|
@@ -257,8 +326,9 @@ Fixes`, `## Breaking Changes`, `## Examples`, `## Documentation`, and
|
|
|
257
326
|
`## Maintenance`. Do not add headings such as `Internal`, `User impact`, `Upgrade
|
|
258
327
|
notes`, or `Summary`; fold useful user impact into the permitted sections instead.
|
|
259
328
|
|
|
260
|
-
|
|
261
|
-
|
|
329
|
+
Reserve Documentation for documentation-only changes that help users operate the
|
|
330
|
+
product and are not already covered by another bullet. Never use it as a link
|
|
331
|
+
collection for features or fixes. Make every Documentation
|
|
262
332
|
bullet an inline Markdown link with descriptive link text and an absolute HTTPS
|
|
263
333
|
target. If no such URL was found, omit the entire Documentation section. Do not
|
|
264
334
|
replace the missing URL with a repository path, an unlinked description, or a
|
|
@@ -279,8 +349,8 @@ Documentation bullet remains, remove `## Maintenance` and all of its bullets.
|
|
|
279
349
|
|
|
280
350
|
```markdown
|
|
281
351
|
## New Features
|
|
282
|
-
- Added dark mode toggle in settings
|
|
283
|
-
-
|
|
352
|
+
- Added a dark mode toggle in settings. [Appearance guide](https://docs.example.com/settings#dark-mode).
|
|
353
|
+
- Export reports as PDF from the File menu. [Export guide](https://docs.example.com/export).
|
|
284
354
|
|
|
285
355
|
## Enhancements
|
|
286
356
|
- Improved search performance (now supports partial matches)
|
|
@@ -291,20 +361,19 @@ Documentation bullet remains, remove `## Maintenance` and all of its bullets.
|
|
|
291
361
|
- Resolved crash when uploading 10MB+ files
|
|
292
362
|
|
|
293
363
|
## Breaking Changes
|
|
294
|
-
-
|
|
295
|
-
- CSV export removed; use Excel or PDF instead
|
|
296
|
-
|
|
297
|
-
## Examples
|
|
298
|
-
- Dark mode can be enabled in Settings → Appearance → Theme
|
|
299
|
-
- CSV export is no longer available; choose Excel or PDF from Export menu
|
|
364
|
+
- Run the database migration before upgrading. [Migration guide](https://docs.example.com/upgrade#database).
|
|
365
|
+
- CSV export removed; use Excel or PDF instead. [Export guide](https://docs.example.com/export).
|
|
300
366
|
|
|
301
367
|
## Documentation
|
|
302
|
-
- [
|
|
303
|
-
|
|
368
|
+
- New [account recovery guide](https://docs.example.com/accounts/recovery).
|
|
369
|
+
```
|
|
304
370
|
|
|
371
|
+
The example above illustrates a user-facing release. A maintenance-only release
|
|
372
|
+
instead contains only:
|
|
373
|
+
|
|
374
|
+
```markdown
|
|
305
375
|
## Maintenance
|
|
306
|
-
This release contains maintenance and internal improvements. No user-facing behavior
|
|
307
|
-
changed.
|
|
376
|
+
This release contains maintenance and internal improvements. No user-facing behavior changed.
|
|
308
377
|
```
|
|
309
378
|
|
|
310
379
|
Omit `## Breaking Changes` completely when the actual range contains no breaking
|
|
@@ -321,7 +390,7 @@ directly observe a supported behavior that the mechanism enables.
|
|
|
321
390
|
1. **Parse input** — extract `from_ref`, `to_ref`, `repo_path`, and optional filters
|
|
322
391
|
2. **Discover public documentation** — inspect the repository's available user-facing documentation surfaces and any published links they expose. Do not assume a particular language, documentation generator, directory layout, metadata file, or URL naming scheme. Prefer the closest trustworthy public page for each qualifying change.
|
|
323
392
|
For hosting services like readthedocs that propose latest/stable URLs, prefer the stable version.
|
|
324
|
-
|
|
393
|
+
Use fragment identifiers only when their anchors can be verified.
|
|
325
394
|
Use absolute HTTPS URLs, and omit a documentation link when no public page can be established rather than inventing one.
|
|
326
395
|
3. **Fetch commits** — run `git log` with range, collect hashes and messages
|
|
327
396
|
4. **Read diffs per file** — use `git show <hash>` for each relevant commit and examine changed behavior, not just filenames
|
|
@@ -329,13 +398,13 @@ Use absolute HTTPS URLs, and omit a documentation link when no public page can b
|
|
|
329
398
|
6. **Interpret impact** — state what users can do, observe, configure, rely on, or learn differently
|
|
330
399
|
7. **Detect breaking changes** — scan for BREAKING markers, public API removals, format changes, migrations, and changed defaults
|
|
331
400
|
8. **Group by category** — assign each qualifying change to **New Features**, **Enhancements**, **Bug Fixes**, **Breaking Changes**, **Examples**, **Documentation**, or **Maintenance**
|
|
332
|
-
9. **
|
|
401
|
+
9. **Add useful syntax** — use public docs or CLI help to verify short inline command/API examples; add an Examples section only when a distinct workflow needs it
|
|
333
402
|
10. **Consolidate** — merge related items, remove duplicates and flip-flops
|
|
334
403
|
11. **Handle an empty range** — if no product change qualifies, generate exactly one concise `## Maintenance` section
|
|
335
404
|
12. **Format markdown** — generate clean section headings and bullets with no title, preamble, footer, file summary, or commit summary
|
|
336
|
-
13. **
|
|
405
|
+
13. **Attach documentation locally** — end each outcome's bullet with its closest verified public HTTPS Markdown link. Do not repeat it in Documentation; that section is only for independent documentation-only changes. Do not invent URLs or fragments.
|
|
337
406
|
14. **Run the final audience review** — inspect every bullet and remove anything about CI, workflows, release tooling, Git evidence, validation, governance, contributor or agent guidance, repository housekeeping, changed files, or maintainer process. Remove the entire section if that leaves it empty. Also remove Documentation bullets whose links target contributor, governance, engineering, or maintainer material.
|
|
338
|
-
15. **Run the final format review** — ensure every `https://` occurrence is inside `[visible text](https://target)`, with no bare URL in prose, and remove `## Maintenance` whenever another qualifying section exists.
|
|
407
|
+
15. **Run the final format review** — ensure every `https://` occurrence is inside `[visible text](https://target)`, with no bare URL in prose, remove duplicate outcomes and empty sections, and remove `## Maintenance` whenever another qualifying section exists.
|
|
339
408
|
|
|
340
409
|
---
|
|
341
410
|
|
|
@@ -357,8 +426,8 @@ Start directly with the release-note section instead:
|
|
|
357
426
|
|
|
358
427
|
```markdown
|
|
359
428
|
## New Features
|
|
360
|
-
- Added dark mode toggle in settings
|
|
361
|
-
-
|
|
429
|
+
- Added a dark mode toggle in settings. [Appearance guide](https://docs.example.com/settings#dark-mode).
|
|
430
|
+
- Export reports as PDF from the File menu. [Export guide](https://docs.example.com/export).
|
|
362
431
|
|
|
363
432
|
## Enhancements
|
|
364
433
|
- Improved search performance (supports partial matches)
|
|
@@ -369,15 +438,10 @@ Start directly with the release-note section instead:
|
|
|
369
438
|
- Resolved crash when uploading 10MB+ files
|
|
370
439
|
|
|
371
440
|
## Breaking Changes
|
|
372
|
-
-
|
|
373
|
-
|
|
374
|
-
## Examples
|
|
375
|
-
- Enable dark mode from Settings → Appearance → Theme
|
|
376
|
-
- Export a report as PDF from the File → Export menu
|
|
441
|
+
- Run the database migration before upgrading. [Migration guide](https://docs.example.com/upgrade#database).
|
|
377
442
|
|
|
378
443
|
## Documentation
|
|
379
|
-
- [
|
|
380
|
-
- [Upgrade instructions](https://docs.example.com/upgrade#database)
|
|
444
|
+
- New [account recovery guide](https://docs.example.com/accounts/recovery).
|
|
381
445
|
```
|
|
382
446
|
|
|
383
447
|
**Key Rules:**
|
|
@@ -391,9 +455,9 @@ Start directly with the release-note section instead:
|
|
|
391
455
|
for maintenance-only releases
|
|
392
456
|
- Use public documentation URLs; never use repo-relative paths like `docs/...` or `README.md`
|
|
393
457
|
- For each user-facing bullet, search existing documentation for the closest page about the impacted behavior and link it inline with descriptive Markdown text when available, even when the documentation file was unchanged
|
|
394
|
-
- Use fragment identifiers (`#section-name`)
|
|
395
|
-
-
|
|
396
|
-
-
|
|
458
|
+
- Use fragment identifiers (`#section-name`) only for verified section anchors
|
|
459
|
+
- Prefer short inline examples in the relevant bullet; use **Examples** only for a distinct useful workflow, never to repeat a feature
|
|
460
|
+
- Use **Documentation** only for independent documentation-only changes with a verified HTTPS link; never repeat links or outcomes already covered elsewhere
|
|
397
461
|
- Prefer concise inline Markdown links in every section, such as `See the [pricing reference for details](https://example.com/pricing)`, rather than exposing a full URL after a colon or in parentheses
|
|
398
462
|
- Multiple links OK if they point to different topics
|
|
399
463
|
- Omit any section that has no bullets
|
|
@@ -409,18 +473,22 @@ Start directly with the release-note section instead:
|
|
|
409
473
|
|
|
410
474
|
## Non-interactive automation mode
|
|
411
475
|
|
|
412
|
-
When
|
|
476
|
+
When invoked by the bundled generator or a CI job using structured output:
|
|
413
477
|
|
|
414
478
|
- Honor the requested tag range and repository path exactly.
|
|
415
|
-
-
|
|
416
|
-
|
|
417
|
-
-
|
|
418
|
-
-
|
|
419
|
-
-
|
|
479
|
+
- Return only the complete release-note Markdown in the final answer. The Python
|
|
480
|
+
caller selects its structured event, validates it, and writes the requested file.
|
|
481
|
+
- Do not create, edit, commit, or push any repository files. There is no model-side
|
|
482
|
+
output-file handoff or file verification step.
|
|
483
|
+
- Use `view`, `glob`, `grep`, and read-only Git commands to inspect the evidence,
|
|
484
|
+
skill instructions, public syntax, and documentation destinations as needed.
|
|
485
|
+
- The final answer must contain only release-note Markdown, without an
|
|
486
|
+
explanation, title heading, or code fence.
|
|
420
487
|
- The first line must be exactly one of: `## New Features`, `## Enhancements`, `## Bug Fixes`, `## Breaking Changes`, `## Examples`, `## Documentation`, or `## Maintenance`.
|
|
421
488
|
- Do not write a preamble, tool-call transcript, title, code fence, or explanatory text before the first release-note section.
|
|
422
|
-
-
|
|
423
|
-
-
|
|
489
|
+
- Do not add progress prose, an explanation, or a tool trace to the final answer.
|
|
490
|
+
- Never infer "no user-facing changes" because writing, authentication, or
|
|
491
|
+
generation failed. Maintenance requires evidence about the requested range.
|
|
424
492
|
- Preserve the user-impact categories, concrete examples, evidence-based breaking-change detection, and repository-derived public documentation links described above.
|
|
425
493
|
|
|
426
494
|
## How to Use This Skill in a Session
|
|
@@ -451,9 +519,9 @@ Generate release notes from v2.1.0 to v2.2.0 for /path/to/my-app
|
|
|
451
519
|
- Include breaking changes prominently
|
|
452
520
|
- Omit the entire Breaking Changes section when no breaking change is evidenced
|
|
453
521
|
- Group related changes
|
|
454
|
-
- Add
|
|
522
|
+
- Add verified inline syntax only when useful; do not require an Examples section or repeat the same outcome
|
|
455
523
|
- Discover and link to the project's public documentation when a trustworthy page is available
|
|
456
|
-
-
|
|
524
|
+
- End each qualifying change's bullet with its closest verified public documentation link when available; never move these links into a bottom-of-notes collection
|
|
457
525
|
- Omit empty sections
|
|
458
526
|
- If a candidate cannot pass the normal-user audience test, omit it rather than placing it under Maintenance or Documentation
|
|
459
527
|
|
|
@@ -494,8 +562,8 @@ Generate release notes from v2.1.0 to v2.2.0 for /path/to/my-app
|
|
|
494
562
|
|
|
495
563
|
## Limitations
|
|
496
564
|
|
|
497
|
-
- Requires a
|
|
565
|
+
- Requires a Git repository with available ancestor refs; tags are optional
|
|
498
566
|
- Complex changes may need human interpretation
|
|
499
567
|
- Very large diffs should be reduced to their evidenced user impact; never summarize them by file count
|
|
500
|
-
-
|
|
568
|
+
- Accepts tags, branches, or commit IDs; no version naming convention is required
|
|
501
569
|
- Needs meaningful commit messages for best results, but commit messages alone are never evidence of user impact
|
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
from __future__ import annotations
|
|
5
5
|
|
|
6
6
|
import argparse
|
|
7
|
+
import json
|
|
7
8
|
import os
|
|
9
|
+
import re
|
|
8
10
|
import subprocess
|
|
9
11
|
import sys
|
|
10
12
|
from collections.abc import Sequence
|
|
@@ -13,6 +15,9 @@ from pathlib import Path
|
|
|
13
15
|
SKILL_NAME = "gh-release-notes"
|
|
14
16
|
DEFAULT_OUTPUT = Path("release-notes.md")
|
|
15
17
|
MAX_GIT_CONTEXT_LENGTH = 60_000
|
|
18
|
+
MAX_GENERATION_ATTEMPTS = 3
|
|
19
|
+
COPILOT_TIMEOUT_SECONDS = 300
|
|
20
|
+
DOCUMENTATION_LINK = re.compile(r"\[[^\]\n]+\]\(https://[^\s)]+\)")
|
|
16
21
|
PERMITTED_HEADINGS = frozenset(
|
|
17
22
|
{
|
|
18
23
|
"## New Features",
|
|
@@ -95,9 +100,10 @@ def build_git_context(repo: Path, from_ref: str, to_ref: str) -> str:
|
|
|
95
100
|
diff = git_output(repo, "diff", "diff", "--no-ext-diff", "--unified=3", f"{from_ref}..{to_ref}")
|
|
96
101
|
context = (
|
|
97
102
|
f"Precomputed Git evidence for {from_ref}..{to_ref}:\n\n"
|
|
103
|
+
f"Complete diff (the skill determines user impact, not directory names):\n"
|
|
104
|
+
f"{diff or '(empty)'}\n\n"
|
|
98
105
|
f"Commit log:\n{log or '(no commits)'}\n\n"
|
|
99
|
-
f"
|
|
100
|
-
f"Diff:\n{diff or '(empty)'}"
|
|
106
|
+
f"Complete diff summary:\n{diff_stat or '(empty)'}"
|
|
101
107
|
)
|
|
102
108
|
if len(context) <= MAX_GIT_CONTEXT_LENGTH:
|
|
103
109
|
return context
|
|
@@ -105,7 +111,9 @@ def build_git_context(repo: Path, from_ref: str, to_ref: str) -> str:
|
|
|
105
111
|
truncated = context[:MAX_GIT_CONTEXT_LENGTH]
|
|
106
112
|
return (
|
|
107
113
|
f"{truncated}\n\n[Git evidence truncated at {MAX_GIT_CONTEXT_LENGTH} characters; "
|
|
108
|
-
"
|
|
114
|
+
"this excerpt is incomplete. Inspect the remaining changes with read/git tools "
|
|
115
|
+
"before deciding release coverage.]\n\n"
|
|
116
|
+
f"Complete diff summary:\n{diff_stat}"
|
|
109
117
|
)
|
|
110
118
|
|
|
111
119
|
|
|
@@ -125,18 +133,19 @@ def build_prompt(
|
|
|
125
133
|
prompt = (
|
|
126
134
|
f"Use the /{SKILL_NAME} skill. Generate release notes for the exact Git range "
|
|
127
135
|
f"{from_ref}..{to_ref} in {repo}. The skill is authoritative for analysis, "
|
|
128
|
-
f"classification, wording, documentation, and Markdown format.
|
|
129
|
-
f"release-note Markdown
|
|
130
|
-
f"
|
|
131
|
-
"
|
|
132
|
-
"
|
|
133
|
-
"response, and do not modify any other files. "
|
|
136
|
+
f"classification, wording, documentation, and Markdown format. Return ONLY the "
|
|
137
|
+
f"complete release-note Markdown in your final answer, without commentary, "
|
|
138
|
+
f"title, or fences. The Python caller extracts the structured final_answer "
|
|
139
|
+
f"message, validates it, and writes file {output_reference}; its resolved path "
|
|
140
|
+
f"is {output}. Do not create or edit any files. "
|
|
134
141
|
"Before writing, enforce the skill's final output contract: render every "
|
|
135
142
|
"documentation URL as concise inline Markdown such as "
|
|
136
143
|
"See the [pricing reference for details](https://example.com/pricing), never as a "
|
|
137
144
|
"bare URL; exclude all internal CI, release automation, governance, "
|
|
138
145
|
"contributor, agent, generator, Git-evidence, and maintainer content; and "
|
|
139
|
-
"omit Maintenance whenever any user-facing section remains."
|
|
146
|
+
"omit Maintenance whenever any user-facing section remains. "
|
|
147
|
+
"End each change's bullet with its closest verified documentation link. "
|
|
148
|
+
"State each outcome once; do not repeat it in Documentation or Examples."
|
|
140
149
|
)
|
|
141
150
|
if git_context:
|
|
142
151
|
prompt += (
|
|
@@ -163,14 +172,11 @@ def build_copilot_command(
|
|
|
163
172
|
"--no-auto-update",
|
|
164
173
|
"--no-color",
|
|
165
174
|
"--output-format",
|
|
166
|
-
"
|
|
175
|
+
"json",
|
|
167
176
|
"--disable-builtin-mcps",
|
|
168
|
-
"--available-tools=
|
|
177
|
+
"--available-tools=view,glob,grep,bash,skill",
|
|
169
178
|
"--allow-tool=read",
|
|
170
|
-
"--allow-tool=write",
|
|
171
179
|
"--allow-tool=shell(git:*)",
|
|
172
|
-
"--allow-url=https://github.com",
|
|
173
|
-
"--allow-url=https://copilot-session-usage.readthedocs.io",
|
|
174
180
|
]
|
|
175
181
|
if model:
|
|
176
182
|
command.extend(["--model", model])
|
|
@@ -255,18 +261,60 @@ def run_copilot(
|
|
|
255
261
|
repo: Path,
|
|
256
262
|
prompt: str,
|
|
257
263
|
model: str | None,
|
|
258
|
-
) ->
|
|
259
|
-
"""Run Copilot CLI and
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
264
|
+
) -> str:
|
|
265
|
+
"""Run Copilot CLI and return only its structured final-answer message."""
|
|
266
|
+
try:
|
|
267
|
+
result = subprocess.run(
|
|
268
|
+
build_copilot_command(prompt, model),
|
|
269
|
+
cwd=repo,
|
|
270
|
+
check=False,
|
|
271
|
+
capture_output=True,
|
|
272
|
+
text=True,
|
|
273
|
+
timeout=COPILOT_TIMEOUT_SECONDS,
|
|
274
|
+
)
|
|
275
|
+
except subprocess.TimeoutExpired as error:
|
|
276
|
+
raise RuntimeError(
|
|
277
|
+
f"Copilot CLI timed out after {COPILOT_TIMEOUT_SECONDS} seconds."
|
|
278
|
+
) from error
|
|
267
279
|
if result.returncode != 0:
|
|
268
280
|
output = f"{result.stdout}\n{result.stderr}".strip()
|
|
269
281
|
raise RuntimeError(f"Copilot CLI failed with exit code {result.returncode}:\n{output}")
|
|
282
|
+
return parse_copilot_response(result.stdout)
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def parse_copilot_response(response: str) -> str:
|
|
286
|
+
"""Select final Markdown from JSONL events, never from progress or tool output."""
|
|
287
|
+
final_answer: str | None = None
|
|
288
|
+
completed = False
|
|
289
|
+
for line in response.splitlines():
|
|
290
|
+
if not line.strip():
|
|
291
|
+
continue
|
|
292
|
+
try:
|
|
293
|
+
event = json.loads(line)
|
|
294
|
+
except json.JSONDecodeError as error:
|
|
295
|
+
raise RuntimeError("Copilot returned invalid JSONL output.") from error
|
|
296
|
+
if not isinstance(event, dict):
|
|
297
|
+
raise RuntimeError("Copilot returned a non-object JSONL event.")
|
|
298
|
+
data = event.get("data", {})
|
|
299
|
+
if event.get("type") == "result":
|
|
300
|
+
if event.get("exitCode") != 0:
|
|
301
|
+
raise RuntimeError("Copilot reported an unsuccessful result event.")
|
|
302
|
+
completed = True
|
|
303
|
+
elif (
|
|
304
|
+
event.get("type") == "assistant.message"
|
|
305
|
+
and isinstance(data, dict)
|
|
306
|
+
and data.get("phase") == "final_answer"
|
|
307
|
+
and not data.get("toolRequests")
|
|
308
|
+
):
|
|
309
|
+
content = data.get("content")
|
|
310
|
+
if not isinstance(content, str):
|
|
311
|
+
raise RuntimeError("Copilot final-answer content must be a string.")
|
|
312
|
+
final_answer = content
|
|
313
|
+
if not completed:
|
|
314
|
+
raise RuntimeError("Copilot JSONL output has no successful result event.")
|
|
315
|
+
if final_answer is None:
|
|
316
|
+
raise RuntimeError("Copilot JSONL output has no final-answer message.")
|
|
317
|
+
return final_answer
|
|
270
318
|
|
|
271
319
|
|
|
272
320
|
def validate_output(output: Path) -> None:
|
|
@@ -275,6 +323,12 @@ def validate_output(output: Path) -> None:
|
|
|
275
323
|
content = output.read_text(encoding="utf-8")
|
|
276
324
|
except OSError as error:
|
|
277
325
|
raise RuntimeError(f"Unable to read release-note output {output}: {error}") from error
|
|
326
|
+
validate_content(content, output)
|
|
327
|
+
print("Release-note output file verified.")
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
def validate_content(content: str, output: Path) -> None:
|
|
331
|
+
"""Validate complete Markdown without extracting notes from an agent transcript."""
|
|
278
332
|
if not content.strip():
|
|
279
333
|
raise RuntimeError(f"Copilot created an empty release-note file: {output}")
|
|
280
334
|
|
|
@@ -302,7 +356,41 @@ def validate_output(output: Path) -> None:
|
|
|
302
356
|
f"Release-note output contains an invalid section heading "
|
|
303
357
|
f"{invalid_headings[0]!r}: {output}"
|
|
304
358
|
)
|
|
305
|
-
|
|
359
|
+
if len(headings) != len(set(headings)):
|
|
360
|
+
raise RuntimeError(f"Release-note output contains repeated section headings: {output}")
|
|
361
|
+
if "## Maintenance" in headings and len(headings) != 1:
|
|
362
|
+
raise RuntimeError(f"Maintenance must not accompany user-facing sections: {output}")
|
|
363
|
+
without_links = DOCUMENTATION_LINK.sub("", content)
|
|
364
|
+
if re.search(r"https?://", without_links):
|
|
365
|
+
raise RuntimeError(f"Release-note output contains a bare or invalid URL: {output}")
|
|
366
|
+
|
|
367
|
+
section = ""
|
|
368
|
+
section_has_content = False
|
|
369
|
+
seen_bullets: set[str] = set()
|
|
370
|
+
for line in content.splitlines():
|
|
371
|
+
if not line.strip():
|
|
372
|
+
continue
|
|
373
|
+
if line in PERMITTED_HEADINGS:
|
|
374
|
+
if section and not section_has_content:
|
|
375
|
+
raise RuntimeError(f"Release-note output contains an empty section: {output}")
|
|
376
|
+
section = line
|
|
377
|
+
section_has_content = False
|
|
378
|
+
continue
|
|
379
|
+
section_has_content = True
|
|
380
|
+
if section == "## Maintenance":
|
|
381
|
+
continue
|
|
382
|
+
if not line.startswith("- ") or not line[2:].strip():
|
|
383
|
+
raise RuntimeError(f"Release-note sections must contain concise flat bullets: {output}")
|
|
384
|
+
bullet = " ".join(line[2:].casefold().split()).rstrip(".")
|
|
385
|
+
if bullet in {"none", "n/a", "no breaking changes"}:
|
|
386
|
+
raise RuntimeError(f"Release-note output contains a placeholder bullet: {output}")
|
|
387
|
+
if bullet in seen_bullets:
|
|
388
|
+
raise RuntimeError(f"Release-note output contains a duplicate bullet: {output}")
|
|
389
|
+
seen_bullets.add(bullet)
|
|
390
|
+
if section == "## Documentation" and not DOCUMENTATION_LINK.search(line):
|
|
391
|
+
raise RuntimeError(f"Documentation bullets require an inline HTTPS link: {output}")
|
|
392
|
+
if not section_has_content:
|
|
393
|
+
raise RuntimeError(f"Release-note output contains an empty section: {output}")
|
|
306
394
|
|
|
307
395
|
|
|
308
396
|
def generate_release_notes(
|
|
@@ -331,13 +419,31 @@ def generate_release_notes(
|
|
|
331
419
|
require_copilot_token()
|
|
332
420
|
run_skill_check(repo)
|
|
333
421
|
git_context = build_git_context(repo, from_ref, to_ref)
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
422
|
+
prompt = build_prompt(from_ref, to_ref, repo, output, git_context)
|
|
423
|
+
feedback = ""
|
|
424
|
+
for attempt in range(1, MAX_GENERATION_ATTEMPTS + 1):
|
|
425
|
+
output.write_text("", encoding="utf-8")
|
|
426
|
+
try:
|
|
427
|
+
response = run_copilot(repo, prompt + feedback, model)
|
|
428
|
+
validate_content(response, output)
|
|
429
|
+
output.write_text(response, encoding="utf-8")
|
|
430
|
+
validate_output(output)
|
|
431
|
+
break
|
|
432
|
+
except RuntimeError as error:
|
|
433
|
+
print(f"Release-note attempt {attempt}/{MAX_GENERATION_ATTEMPTS} failed: {error}")
|
|
434
|
+
if attempt == MAX_GENERATION_ATTEMPTS:
|
|
435
|
+
output.write_text("", encoding="utf-8")
|
|
436
|
+
raise RuntimeError(
|
|
437
|
+
f"Release-note generation failed after {attempt} attempts; "
|
|
438
|
+
f"no notes are safe to publish. Last error: {error}"
|
|
439
|
+
) from error
|
|
440
|
+
feedback = (
|
|
441
|
+
f"\nThe previous attempt failed validation: {error}\n"
|
|
442
|
+
"Return ONLY the complete release-note Markdown in your final answer, "
|
|
443
|
+
"starting with a permitted ## heading, with no commentary or tool traces. "
|
|
444
|
+
"The Python caller writes the file; do not attempt file-writing tools. "
|
|
445
|
+
"Do not infer Maintenance from a generation failure."
|
|
446
|
+
)
|
|
341
447
|
print(f"Release notes written to {output}")
|
|
342
448
|
|
|
343
449
|
|