copilot-session-usage 0.7.7__tar.gz → 0.8.1__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.7.7 → copilot_session_usage-0.8.1}/.github/skills/gh-release-notes/SKILL.md +21 -4
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/skills/gh-release-notes/scripts/generate_release_notes.py +91 -6
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/workflows/ci.yml +9 -0
- copilot_session_usage-0.8.1/.github/workflows/publish.yml +98 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/workflows/release-notes.yml +44 -8
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/workflows/release.yml +76 -16
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/AGENTS.md +2 -2
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/CONSTITUTION.md +2 -2
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/CONTRIBUTING.md +9 -5
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/PKG-INFO +95 -7
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/README.md +94 -6
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/articles/presentation.md +17 -6
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/internal/automated_release_proces.md +55 -32
- copilot_session_usage-0.8.1/docs/source/explanation/how-cost-estimation-works.md +309 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/how-to/analyze-specific-session.md +34 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/how-to/batch-and-spending.md +36 -18
- copilot_session_usage-0.8.1/docs/source/how-to/copilot-cli-provider.md +146 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/how-to/index.md +1 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/index.md +12 -9
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/tutorials/getting-started.md +29 -0
- copilot_session_usage-0.8.1/guidelines.yml +6 -0
- copilot_session_usage-0.8.1/knowledge/concepts/copilot-cli.md +51 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/concepts/index.md +1 -1
- copilot_session_usage-0.8.1/knowledge/log.md +9 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/skills/copilot-session-usage/SKILL.md +77 -7
- copilot_session_usage-0.8.1/skills/copilot-session-usage/references/span-analysis-template.md +105 -0
- copilot_session_usage-0.8.1/src/copilot_session_usage/_internal/copilot_cli.py +963 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/src/copilot_session_usage/_internal/core.py +379 -91
- copilot_session_usage-0.8.1/src/copilot_session_usage/api.py +457 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/src/copilot_session_usage/cli.py +350 -88
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/src/copilot_session_usage/data/models-and-pricing.lock +3 -3
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/src/copilot_session_usage/data/models-and-pricing.yml +50 -74
- copilot_session_usage-0.8.1/tests/conftest.py +175 -0
- copilot_session_usage-0.8.1/tests/test_api.py +587 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/tests/test_cli.py +371 -7
- copilot_session_usage-0.8.1/tests/test_copilot_cli.py +1016 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/tests/test_core.py +270 -0
- copilot_session_usage-0.8.1/tests/test_generate_release_notes.py +333 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/tests/test_release_notes.py +14 -3
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/tests/test_rendering.py +157 -0
- copilot_session_usage-0.7.7/.github/workflows/publish.yml +0 -35
- copilot_session_usage-0.7.7/docs/source/explanation/how-cost-estimation-works.md +0 -167
- copilot_session_usage-0.7.7/knowledge/concepts/copilot-cli.md +0 -51
- copilot_session_usage-0.7.7/knowledge/log.md +0 -3
- copilot_session_usage-0.7.7/src/copilot_session_usage/_internal/copilot_cli.py +0 -18
- copilot_session_usage-0.7.7/src/copilot_session_usage/api.py +0 -286
- copilot_session_usage-0.7.7/tests/conftest.py +0 -24
- copilot_session_usage-0.7.7/tests/test_api.py +0 -256
- copilot_session_usage-0.7.7/tests/test_generate_release_notes.py +0 -176
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.editorconfig +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.gitattributes +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/changes/requests/skill-breakdown/01-request.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/guidelines/git-commit-message.guideline.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/guidelines/knowledge-base.guidelines.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/pull_request_template.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/skills/consolidate-knowledge-base/SKILL.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/skills/gh-commit-changes/SKILL.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/skills/gh-create-pull-request/SKILL.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/skills/record-finding/SKILL.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/workflows/refresh-pricing.yml +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.gitignore +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.readthedocs.yaml +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/CHANGELOG.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/LICENSE +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/_static/changelog.js +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/_static/custom.css +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/changelog.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/conf.py +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/explanation/index.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/how-to/add-commit-trailer.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/how-to/export-json.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/how-to/wsl2.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/installation.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/reference/api.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/reference/cli.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/reference/index.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/reference/pricing.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/docs/source/tutorials/index.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/justfile +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/_schema/Base.schema.yaml +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/_schema/Concept.schema.yaml +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/_schema/Experiment.schema.yaml +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/_schema/Finding.schema.yaml +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/_schema/Hypothesis.schema.yaml +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/_schema/Outcome.schema.yaml +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/_schema/Playbook.schema.yaml +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/_schema/Principle.schema.yaml +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/_schema/Reference.schema.yaml +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/_schema/Structure.schema.yaml +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/concepts/overview.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/concepts/session-cost-analysis.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/concepts/threshold-based-pricing.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/experiments/index.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/experiments/verify-subagent-cost-attribution.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/findings/2026.07.02-00.00-subagent-logs-runsubagent-prefix.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/findings/2026.07.02-22.00-title-generation-not-counted-as-model-turn.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/findings/2026.07.02-23.00-cache-write-approximation.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/findings/2026.07.13-14.27-release-notes-overreported-maintainer-changes.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/findings/2026.07.24-15.01-actions-token-cannot-create-pr.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/findings/2026.08.05-16.24-utility-models-appear-in-logs-but-are-not-billed.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/findings/index.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/guides/automation-scripts.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/guides/cost-optimization.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/guides/index.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/guides/wsl2-setup.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/ideas/index.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/ideas/multi-session-efficiency-analytics.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/index.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/principles/findings-are-immutable.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/principles/index.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/reference/debug-log-format.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/reference/index.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/reference/pricing-formats.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/structures/cache-cost-approximation.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/structures/index.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/structures/knowledge-base-information-types.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/structures/session-discovery-algorithm.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/structures/subagent-cost-tracking.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/knowledge/structures/vscode-copilot-extension.md +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/pyproject.toml +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/scripts/refresh_pricing.py +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/src/copilot_session_usage/__init__.py +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/src/copilot_session_usage/_internal/__init__.py +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/src/copilot_session_usage/_internal/git.py +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/src/copilot_session_usage/_internal/vscode.py +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/src/copilot_session_usage/data/__init__.py +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/src/copilot_session_usage/data/custom-models-pricing.yml +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/tests/test_coverage_gaps.py +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/tests/test_git.py +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/tests/test_pricing_runtime.py +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/tests/test_vscode.py +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/tests/test_vscode_platform.py +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/uv.lock +0 -0
- {copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/uv.toml +0 -0
{copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/skills/gh-release-notes/SKILL.md
RENAMED
|
@@ -41,11 +41,15 @@ Markdown.
|
|
|
41
41
|
The skill includes `scripts/generate_release_notes.py`, a standalone Python script that:
|
|
42
42
|
|
|
43
43
|
- verifies the requested Git range and Copilot skill availability;
|
|
44
|
+
- writes the deterministic `## Maintenance` output without invoking Copilot when the
|
|
45
|
+
requested Git range contains no commits or the caller explicitly requests
|
|
46
|
+
`--maintenance-only`;
|
|
44
47
|
- precomputes the commit log and complete diff locally so generation also works
|
|
45
48
|
when the Copilot CLI cannot inspect Git history inside its tool environment;
|
|
46
49
|
- invokes the Copilot CLI with `/gh-release-notes`;
|
|
47
|
-
-
|
|
48
|
-
|
|
50
|
+
- gives the skill scoped file-writing tools so it writes the final Markdown to the
|
|
51
|
+
requested output file while the response stream is discarded; and
|
|
52
|
+
- verifies that the requested output file follows the release-note output contract. The
|
|
49
53
|
script does not normalize Markdown or decide user impact, categorize changes,
|
|
50
54
|
discover documentation, infer breaking changes, or require examples; those
|
|
51
55
|
decisions belong to this skill.
|
|
@@ -59,6 +63,19 @@ python .github/skills/gh-release-notes/scripts/generate_release_notes.py \
|
|
|
59
63
|
--output release-notes.md
|
|
60
64
|
```
|
|
61
65
|
|
|
66
|
+
For an intentional maintenance-only release whose range contains internal commits,
|
|
67
|
+
pass `--maintenance-only` to write the deterministic Maintenance section without
|
|
68
|
+
invoking Copilot.
|
|
69
|
+
|
|
70
|
+
`release-notes.md` is the canonical shared artifact filename. The generator, both
|
|
71
|
+
release workflows, and `gh release --notes-file` use this same file so the release
|
|
72
|
+
body never depends on Copilot's response stream. An explicitly supplied `--output`
|
|
73
|
+
path is honored exactly. For a non-empty range, the generator creates the empty
|
|
74
|
+
handoff file before invoking Copilot so the skill can edit the known
|
|
75
|
+
repository-relative target; the file is accepted only after the skill has replaced
|
|
76
|
+
it with valid Markdown. An empty range produces the valid maintenance output
|
|
77
|
+
directly and does not require Copilot authentication or skill discovery.
|
|
78
|
+
|
|
62
79
|
The range is Git's two-dot range, `from_ref..to_ref`: `from_ref` itself is excluded
|
|
63
80
|
and `to_ref` is included. Both refs may be tags, branches, or commit IDs.
|
|
64
81
|
|
|
@@ -396,12 +413,12 @@ When this skill is invoked by a CI job with an explicit output-file request:
|
|
|
396
413
|
|
|
397
414
|
- Honor the requested tag range and repository path exactly.
|
|
398
415
|
- Treat the requested output file as mandatory. Writing it is the only successful completion condition.
|
|
399
|
-
-
|
|
416
|
+
- Replace the pre-created handoff file with the final Markdown using the `edit` file tool.
|
|
400
417
|
- After writing, use the `read` file tool to verify that the requested file exists and contains the final release-note Markdown.
|
|
401
418
|
- Do not modify, commit, or push any other repository files.
|
|
402
419
|
- The output file must contain only the final release-note Markdown, without an explanation, title heading, or code fence.
|
|
403
420
|
- The first line must be exactly one of: `## New Features`, `## Enhancements`, `## Bug Fixes`, `## Breaking Changes`, `## Examples`, `## Documentation`, or `## Maintenance`.
|
|
404
|
-
- Do not write a preamble, title, code fence, or explanatory text before the first release-note section.
|
|
421
|
+
- Do not write a preamble, tool-call transcript, title, code fence, or explanatory text before the first release-note section.
|
|
405
422
|
- Never use the Copilot response stream as output. The caller may discard it after the file is written.
|
|
406
423
|
- Do not report the release notes only in the response. If the file cannot be written or verified, the task has failed.
|
|
407
424
|
- Preserve the user-impact categories, concrete examples, evidence-based breaking-change detection, and repository-derived public documentation links described above.
|
|
@@ -13,6 +13,31 @@ from pathlib import Path
|
|
|
13
13
|
SKILL_NAME = "gh-release-notes"
|
|
14
14
|
DEFAULT_OUTPUT = Path("release-notes.md")
|
|
15
15
|
MAX_GIT_CONTEXT_LENGTH = 60_000
|
|
16
|
+
PERMITTED_HEADINGS = frozenset(
|
|
17
|
+
{
|
|
18
|
+
"## New Features",
|
|
19
|
+
"## Enhancements",
|
|
20
|
+
"## Bug Fixes",
|
|
21
|
+
"## Breaking Changes",
|
|
22
|
+
"## Examples",
|
|
23
|
+
"## Documentation",
|
|
24
|
+
"## Maintenance",
|
|
25
|
+
}
|
|
26
|
+
)
|
|
27
|
+
TRACE_MARKERS = (
|
|
28
|
+
"<function_call",
|
|
29
|
+
"<thinking>",
|
|
30
|
+
"<system_notification>",
|
|
31
|
+
"assistant.reasoning",
|
|
32
|
+
"function_calls",
|
|
33
|
+
"to=bash.exec",
|
|
34
|
+
"to=functions.exec",
|
|
35
|
+
)
|
|
36
|
+
MAINTENANCE_NOTES = (
|
|
37
|
+
"## Maintenance\n\n"
|
|
38
|
+
"This release contains maintenance and internal improvements. "
|
|
39
|
+
"No user-facing behavior changed.\n"
|
|
40
|
+
)
|
|
16
41
|
|
|
17
42
|
|
|
18
43
|
def build_parser() -> argparse.ArgumentParser:
|
|
@@ -40,6 +65,11 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
40
65
|
default=DEFAULT_OUTPUT,
|
|
41
66
|
help="Release-note output path (default: release-notes.md).",
|
|
42
67
|
)
|
|
68
|
+
parser.add_argument(
|
|
69
|
+
"--maintenance-only",
|
|
70
|
+
action="store_true",
|
|
71
|
+
help="Write deterministic maintenance notes without invoking Copilot.",
|
|
72
|
+
)
|
|
43
73
|
parser.add_argument(
|
|
44
74
|
"--model",
|
|
45
75
|
default=None,
|
|
@@ -49,7 +79,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|
|
49
79
|
"--validate",
|
|
50
80
|
type=Path,
|
|
51
81
|
metavar="FILE",
|
|
52
|
-
help="Verify that an existing release-note file
|
|
82
|
+
help="Verify that an existing release-note file follows the output contract.",
|
|
53
83
|
)
|
|
54
84
|
return parser
|
|
55
85
|
|
|
@@ -87,11 +117,20 @@ def build_prompt(
|
|
|
87
117
|
git_context: str | None = None,
|
|
88
118
|
) -> str:
|
|
89
119
|
"""Build the small orchestration prompt; the skill owns release-note policy."""
|
|
120
|
+
try:
|
|
121
|
+
output_reference = output.relative_to(repo).as_posix()
|
|
122
|
+
except ValueError:
|
|
123
|
+
output_reference = str(output)
|
|
124
|
+
|
|
90
125
|
prompt = (
|
|
91
126
|
f"Use the /{SKILL_NAME} skill. Generate release notes for the exact Git range "
|
|
92
127
|
f"{from_ref}..{to_ref} in {repo}. The skill is authoritative for analysis, "
|
|
93
|
-
f"classification, wording, documentation, and Markdown format. Write
|
|
94
|
-
f"
|
|
128
|
+
f"classification, wording, documentation, and Markdown format. Write the final "
|
|
129
|
+
f"release-note Markdown directly to the shared repository file "
|
|
130
|
+
f"{output_reference} using the file-writing tools. Its resolved path is {output}. "
|
|
131
|
+
"The release workflow reads this exact file as its notes input; the Copilot "
|
|
132
|
+
"response stream is discarded. Do not put release notes or a summary in your "
|
|
133
|
+
"response, and do not modify any other files. "
|
|
95
134
|
"Before writing, enforce the skill's final output contract: render every "
|
|
96
135
|
"documentation URL as concise inline Markdown such as "
|
|
97
136
|
"See the [pricing reference for details](https://example.com/pricing), never as a "
|
|
@@ -130,6 +169,8 @@ def build_copilot_command(
|
|
|
130
169
|
"--allow-tool=read",
|
|
131
170
|
"--allow-tool=write",
|
|
132
171
|
"--allow-tool=shell(git:*)",
|
|
172
|
+
"--allow-url=https://github.com",
|
|
173
|
+
"--allow-url=https://copilot-session-usage.readthedocs.io",
|
|
133
174
|
]
|
|
134
175
|
if model:
|
|
135
176
|
command.extend(["--model", model])
|
|
@@ -177,6 +218,17 @@ def validate_range(repo: Path, from_ref: str, to_ref: str) -> None:
|
|
|
177
218
|
raise ValueError(f"Git ref {from_ref!r} is not an ancestor of {to_ref!r}.")
|
|
178
219
|
|
|
179
220
|
|
|
221
|
+
def range_has_commits(repo: Path, from_ref: str, to_ref: str) -> bool:
|
|
222
|
+
"""Return whether the requested Git range contains at least one commit."""
|
|
223
|
+
count = git_output(repo, "commit count", "rev-list", "--count", f"{from_ref}..{to_ref}")
|
|
224
|
+
return count != "0"
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def write_maintenance_notes(output: Path) -> None:
|
|
228
|
+
"""Write the deterministic notes required for a maintenance-only release."""
|
|
229
|
+
output.write_text(MAINTENANCE_NOTES, encoding="utf-8")
|
|
230
|
+
|
|
231
|
+
|
|
180
232
|
def run_skill_check(repo: Path) -> None:
|
|
181
233
|
"""Ensure the release-note skill is installed in the Copilot CLI."""
|
|
182
234
|
result = subprocess.run(
|
|
@@ -204,7 +256,7 @@ def run_copilot(
|
|
|
204
256
|
prompt: str,
|
|
205
257
|
model: str | None,
|
|
206
258
|
) -> None:
|
|
207
|
-
"""Run Copilot CLI and
|
|
259
|
+
"""Run Copilot CLI and leave the generated Markdown in the shared output file."""
|
|
208
260
|
result = subprocess.run(
|
|
209
261
|
build_copilot_command(prompt, model),
|
|
210
262
|
cwd=repo,
|
|
@@ -218,13 +270,38 @@ def run_copilot(
|
|
|
218
270
|
|
|
219
271
|
|
|
220
272
|
def validate_output(output: Path) -> None:
|
|
221
|
-
"""Verify that
|
|
273
|
+
"""Verify that generated release notes follow the file output contract."""
|
|
222
274
|
try:
|
|
223
275
|
content = output.read_text(encoding="utf-8")
|
|
224
276
|
except OSError as error:
|
|
225
277
|
raise RuntimeError(f"Unable to read release-note output {output}: {error}") from error
|
|
226
278
|
if not content.strip():
|
|
227
279
|
raise RuntimeError(f"Copilot created an empty release-note file: {output}")
|
|
280
|
+
|
|
281
|
+
first_line = content.splitlines()[0] if content.splitlines() else ""
|
|
282
|
+
if first_line not in PERMITTED_HEADINGS:
|
|
283
|
+
raise RuntimeError(
|
|
284
|
+
f"Release-note output must start with one of the permitted section headings: {output}"
|
|
285
|
+
)
|
|
286
|
+
|
|
287
|
+
lowered = content.lower()
|
|
288
|
+
leaked_markers = [marker for marker in TRACE_MARKERS if marker.lower() in lowered]
|
|
289
|
+
if leaked_markers:
|
|
290
|
+
markers = ", ".join(leaked_markers)
|
|
291
|
+
raise RuntimeError(
|
|
292
|
+
f"Release-note output contains Copilot trace markers ({markers}): {output}"
|
|
293
|
+
)
|
|
294
|
+
if "```" in content:
|
|
295
|
+
raise RuntimeError(f"Release-note output must not contain a code fence: {output}")
|
|
296
|
+
if any(line.startswith("# ") for line in content.splitlines()):
|
|
297
|
+
raise RuntimeError(f"Release-note output must not contain a title heading: {output}")
|
|
298
|
+
headings = [line for line in content.splitlines() if line.startswith("## ")]
|
|
299
|
+
invalid_headings = [heading for heading in headings if heading not in PERMITTED_HEADINGS]
|
|
300
|
+
if invalid_headings:
|
|
301
|
+
raise RuntimeError(
|
|
302
|
+
f"Release-note output contains an invalid section heading "
|
|
303
|
+
f"{invalid_headings[0]!r}: {output}"
|
|
304
|
+
)
|
|
228
305
|
print("Release-note output file verified.")
|
|
229
306
|
|
|
230
307
|
|
|
@@ -234,16 +311,23 @@ def generate_release_notes(
|
|
|
234
311
|
to_ref: str,
|
|
235
312
|
output: Path,
|
|
236
313
|
model: str | None,
|
|
314
|
+
maintenance_only: bool = False,
|
|
237
315
|
) -> None:
|
|
238
316
|
"""Generate release notes, then validate the resulting Markdown file."""
|
|
239
317
|
repo = repo.resolve()
|
|
240
318
|
output = resolve_path(output, repo).resolve()
|
|
241
319
|
validate_range(repo, from_ref, to_ref)
|
|
242
320
|
output.parent.mkdir(parents=True, exist_ok=True)
|
|
243
|
-
output.
|
|
321
|
+
output.write_text("", encoding="utf-8")
|
|
244
322
|
|
|
245
323
|
print(f"Generating release notes from {from_ref} (exclusive) to {to_ref} (inclusive).")
|
|
246
324
|
print(f"Copilot model: {model or 'CLI default'}")
|
|
325
|
+
if maintenance_only or not range_has_commits(repo, from_ref, to_ref):
|
|
326
|
+
write_maintenance_notes(output)
|
|
327
|
+
validate_output(output)
|
|
328
|
+
print(f"Release notes written to {output}")
|
|
329
|
+
return
|
|
330
|
+
|
|
247
331
|
require_copilot_token()
|
|
248
332
|
run_skill_check(repo)
|
|
249
333
|
git_context = build_git_context(repo, from_ref, to_ref)
|
|
@@ -285,6 +369,7 @@ def main(argv: Sequence[str] | None = None) -> int:
|
|
|
285
369
|
to_ref=args.to_ref,
|
|
286
370
|
output=args.output,
|
|
287
371
|
model=args.model or os.environ.get("COPILOT_MODEL"),
|
|
372
|
+
maintenance_only=args.maintenance_only,
|
|
288
373
|
)
|
|
289
374
|
except (OSError, RuntimeError, ValueError) as error:
|
|
290
375
|
print(f"error: {error}", file=sys.stderr)
|
|
@@ -6,6 +6,11 @@ on:
|
|
|
6
6
|
pull_request:
|
|
7
7
|
branches: [main, master]
|
|
8
8
|
workflow_call:
|
|
9
|
+
inputs:
|
|
10
|
+
ref:
|
|
11
|
+
description: "Commit to validate"
|
|
12
|
+
required: false
|
|
13
|
+
type: string
|
|
9
14
|
|
|
10
15
|
jobs:
|
|
11
16
|
test:
|
|
@@ -17,6 +22,8 @@ jobs:
|
|
|
17
22
|
|
|
18
23
|
steps:
|
|
19
24
|
- uses: actions/checkout@v4
|
|
25
|
+
with:
|
|
26
|
+
ref: ${{ inputs.ref || github.sha }}
|
|
20
27
|
|
|
21
28
|
- name: Install uv
|
|
22
29
|
uses: astral-sh/setup-uv@v5
|
|
@@ -49,6 +56,8 @@ jobs:
|
|
|
49
56
|
|
|
50
57
|
steps:
|
|
51
58
|
- uses: actions/checkout@v4
|
|
59
|
+
with:
|
|
60
|
+
ref: ${{ inputs.ref || github.sha }}
|
|
52
61
|
|
|
53
62
|
- name: Install uv
|
|
54
63
|
uses: astral-sh/setup-uv@v5
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
name: Publish existing tag to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
tag:
|
|
7
|
+
description: "Existing version tag to publish, for example v0.7.1"
|
|
8
|
+
required: true
|
|
9
|
+
type: string
|
|
10
|
+
|
|
11
|
+
concurrency:
|
|
12
|
+
group: publish-${{ github.ref }}-${{ inputs.tag }}
|
|
13
|
+
cancel-in-progress: false
|
|
14
|
+
|
|
15
|
+
jobs:
|
|
16
|
+
publish:
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
environment:
|
|
19
|
+
name: pypi
|
|
20
|
+
url: https://pypi.org/p/copilot-session-usage
|
|
21
|
+
permissions:
|
|
22
|
+
contents: read
|
|
23
|
+
id-token: write
|
|
24
|
+
|
|
25
|
+
steps:
|
|
26
|
+
- name: Require the default branch
|
|
27
|
+
env:
|
|
28
|
+
WORKFLOW_REF: ${{ github.ref }}
|
|
29
|
+
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
30
|
+
shell: bash
|
|
31
|
+
run: |
|
|
32
|
+
set -euo pipefail
|
|
33
|
+
expected_ref="refs/heads/$DEFAULT_BRANCH"
|
|
34
|
+
if [[ "$WORKFLOW_REF" != "$expected_ref" ]]; then
|
|
35
|
+
echo "::error::PyPI publication must be dispatched from the default branch ($DEFAULT_BRANCH)."
|
|
36
|
+
echo "::error::Received workflow ref: $WORKFLOW_REF"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
- name: Check out repository history
|
|
41
|
+
uses: actions/checkout@v4
|
|
42
|
+
with:
|
|
43
|
+
fetch-depth: 0
|
|
44
|
+
ref: ${{ github.sha }}
|
|
45
|
+
|
|
46
|
+
- name: Validate and check out the requested tag
|
|
47
|
+
env:
|
|
48
|
+
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
49
|
+
TARGET_TAG: ${{ inputs.tag }}
|
|
50
|
+
shell: bash
|
|
51
|
+
run: |
|
|
52
|
+
set -euo pipefail
|
|
53
|
+
|
|
54
|
+
if [[ ! "$TARGET_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
55
|
+
echo "::error::Expected a semantic-version tag such as v0.7.1, got '$TARGET_TAG'."
|
|
56
|
+
exit 1
|
|
57
|
+
fi
|
|
58
|
+
if ! git ls-remote --exit-code --refs origin "refs/tags/$TARGET_TAG" >/dev/null; then
|
|
59
|
+
echo "::error::Remote tag $TARGET_TAG does not exist."
|
|
60
|
+
exit 1
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
git fetch --force origin "refs/tags/$TARGET_TAG:refs/tags/$TARGET_TAG"
|
|
64
|
+
git fetch --force origin \
|
|
65
|
+
"refs/heads/$DEFAULT_BRANCH:refs/remotes/origin/$DEFAULT_BRANCH"
|
|
66
|
+
target_commit="$(git rev-parse --verify "refs/tags/$TARGET_TAG^{commit}")"
|
|
67
|
+
if ! git merge-base --is-ancestor "$target_commit" "refs/remotes/origin/$DEFAULT_BRANCH"; then
|
|
68
|
+
echo "::error::Release tag $TARGET_TAG is not reachable from the default branch."
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
git checkout --detach "$target_commit"
|
|
72
|
+
|
|
73
|
+
- name: Install uv
|
|
74
|
+
uses: astral-sh/setup-uv@v5
|
|
75
|
+
|
|
76
|
+
- name: Build package
|
|
77
|
+
run: uv build
|
|
78
|
+
|
|
79
|
+
- name: Verify package version
|
|
80
|
+
env:
|
|
81
|
+
TARGET_TAG: ${{ inputs.tag }}
|
|
82
|
+
shell: bash
|
|
83
|
+
run: |
|
|
84
|
+
set -euo pipefail
|
|
85
|
+
expected_version="${TARGET_TAG#v}"
|
|
86
|
+
shopt -s nullglob
|
|
87
|
+
artifacts=(
|
|
88
|
+
dist/copilot_session_usage-"$expected_version"-*.whl
|
|
89
|
+
dist/copilot_session_usage-"$expected_version".tar.gz
|
|
90
|
+
)
|
|
91
|
+
if (( ${#artifacts[@]} != 2 )); then
|
|
92
|
+
echo "::error::Built package artifacts do not match $TARGET_TAG."
|
|
93
|
+
printf '%s\n' dist/*
|
|
94
|
+
exit 1
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
- name: Publish to PyPI
|
|
98
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
{copilot_session_usage-0.7.7 → copilot_session_usage-0.8.1}/.github/workflows/release-notes.yml
RENAMED
|
@@ -20,11 +20,52 @@ jobs:
|
|
|
20
20
|
runs-on: ubuntu-latest
|
|
21
21
|
|
|
22
22
|
steps:
|
|
23
|
-
- name:
|
|
23
|
+
- name: Require the default branch
|
|
24
|
+
env:
|
|
25
|
+
WORKFLOW_REF: ${{ github.ref }}
|
|
26
|
+
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
27
|
+
shell: bash
|
|
28
|
+
run: |
|
|
29
|
+
set -euo pipefail
|
|
30
|
+
expected_ref="refs/heads/$DEFAULT_BRANCH"
|
|
31
|
+
if [[ "$WORKFLOW_REF" != "$expected_ref" ]]; then
|
|
32
|
+
echo "::error::Release-note recovery must be dispatched from the default branch ($DEFAULT_BRANCH)."
|
|
33
|
+
echo "::error::Received workflow ref: $WORKFLOW_REF"
|
|
34
|
+
exit 1
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
- name: Check out repository history
|
|
24
38
|
uses: actions/checkout@v4
|
|
25
39
|
with:
|
|
26
40
|
fetch-depth: 0
|
|
27
|
-
ref: ${{
|
|
41
|
+
ref: ${{ github.sha }}
|
|
42
|
+
|
|
43
|
+
- name: Validate and check out the existing release tag
|
|
44
|
+
env:
|
|
45
|
+
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
46
|
+
TARGET_TAG: ${{ inputs.tag }}
|
|
47
|
+
shell: bash
|
|
48
|
+
run: |
|
|
49
|
+
set -euo pipefail
|
|
50
|
+
|
|
51
|
+
if [[ ! "$TARGET_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
52
|
+
echo "::error::Expected a semantic-version tag such as v0.7.0, got '$TARGET_TAG'."
|
|
53
|
+
exit 1
|
|
54
|
+
fi
|
|
55
|
+
if ! git ls-remote --exit-code --refs origin "refs/tags/$TARGET_TAG" >/dev/null; then
|
|
56
|
+
echo "::error::Remote tag $TARGET_TAG does not exist."
|
|
57
|
+
exit 1
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
git fetch --force origin "refs/tags/$TARGET_TAG:refs/tags/$TARGET_TAG"
|
|
61
|
+
git fetch --force origin \
|
|
62
|
+
"refs/heads/$DEFAULT_BRANCH:refs/remotes/origin/$DEFAULT_BRANCH"
|
|
63
|
+
target_commit="$(git rev-parse --verify "refs/tags/$TARGET_TAG^{commit}")"
|
|
64
|
+
if ! git merge-base --is-ancestor "$target_commit" "refs/remotes/origin/$DEFAULT_BRANCH"; then
|
|
65
|
+
echo "::error::Release tag $TARGET_TAG is not reachable from the default branch."
|
|
66
|
+
exit 1
|
|
67
|
+
fi
|
|
68
|
+
git checkout --detach "$target_commit"
|
|
28
69
|
|
|
29
70
|
- name: Install uv
|
|
30
71
|
uses: astral-sh/setup-uv@v5
|
|
@@ -36,15 +77,10 @@ jobs:
|
|
|
36
77
|
id: range
|
|
37
78
|
shell: bash
|
|
38
79
|
env:
|
|
39
|
-
TARGET_TAG: ${{ inputs.tag
|
|
80
|
+
TARGET_TAG: ${{ inputs.tag }}
|
|
40
81
|
run: |
|
|
41
82
|
set -euo pipefail
|
|
42
83
|
|
|
43
|
-
if [[ ! "$TARGET_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-].*)?$ ]]; then
|
|
44
|
-
echo "::error::Expected a semantic-version tag such as v0.7.0, got '$TARGET_TAG'."
|
|
45
|
-
exit 1
|
|
46
|
-
fi
|
|
47
|
-
|
|
48
84
|
previous_tag="$(git tag --sort=-v:refname --merged "$TARGET_TAG" \
|
|
49
85
|
| awk -v current="$TARGET_TAG" '$0 != current { print; exit }')"
|
|
50
86
|
|
|
@@ -32,25 +32,44 @@ permissions:
|
|
|
32
32
|
contents: write
|
|
33
33
|
|
|
34
34
|
jobs:
|
|
35
|
+
validate-trigger:
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
|
|
38
|
+
steps:
|
|
39
|
+
- name: Require the default branch
|
|
40
|
+
env:
|
|
41
|
+
WORKFLOW_REF: ${{ github.ref }}
|
|
42
|
+
DEFAULT_BRANCH: ${{ github.event.repository.default_branch }}
|
|
43
|
+
shell: bash
|
|
44
|
+
run: |
|
|
45
|
+
set -euo pipefail
|
|
46
|
+
expected_ref="refs/heads/$DEFAULT_BRANCH"
|
|
47
|
+
if [[ "$WORKFLOW_REF" != "$expected_ref" ]]; then
|
|
48
|
+
echo "::error::Release must be dispatched from the default branch ($DEFAULT_BRANCH)."
|
|
49
|
+
echo "::error::Received workflow ref: $WORKFLOW_REF"
|
|
50
|
+
exit 1
|
|
51
|
+
fi
|
|
52
|
+
|
|
35
53
|
ci:
|
|
54
|
+
needs: validate-trigger
|
|
36
55
|
uses: ./.github/workflows/ci.yml
|
|
56
|
+
with:
|
|
57
|
+
ref: ${{ github.sha }}
|
|
37
58
|
|
|
38
59
|
release:
|
|
39
|
-
needs: ci
|
|
60
|
+
needs: [validate-trigger, ci]
|
|
40
61
|
runs-on: ubuntu-latest
|
|
41
62
|
env:
|
|
42
|
-
GH_TOKEN: ${{ github.token }}
|
|
43
|
-
RELEASE_BRANCH: ${{ github.event.repository.default_branch }}
|
|
44
63
|
INCREMENT: ${{ inputs.increment }}
|
|
45
64
|
DRAFT_RELEASE: ${{ inputs.draft }}
|
|
46
65
|
FORCE_RELEASE: ${{ inputs.force }}
|
|
47
66
|
|
|
48
67
|
steps:
|
|
49
|
-
- name: Check out the
|
|
68
|
+
- name: Check out the validated release commit
|
|
50
69
|
uses: actions/checkout@v4
|
|
51
70
|
with:
|
|
52
71
|
fetch-depth: 0
|
|
53
|
-
ref: ${{ github.
|
|
72
|
+
ref: ${{ github.sha }}
|
|
54
73
|
|
|
55
74
|
- name: Install uv
|
|
56
75
|
uses: astral-sh/setup-uv@v5
|
|
@@ -70,23 +89,36 @@ jobs:
|
|
|
70
89
|
exit 1
|
|
71
90
|
fi
|
|
72
91
|
|
|
73
|
-
git switch --force-create "$RELEASE_BRANCH" "origin/$RELEASE_BRANCH"
|
|
74
|
-
|
|
75
92
|
previous_tag="$(git describe --tags --abbrev=0 HEAD 2>/dev/null || true)"
|
|
76
93
|
if [[ -z "$previous_tag" ]]; then
|
|
77
94
|
echo "::error::Could not find a previous release tag."
|
|
78
95
|
exit 1
|
|
79
96
|
fi
|
|
80
97
|
|
|
98
|
+
maintenance_only=false
|
|
81
99
|
if [[ "$INCREMENT" == "auto" ]]; then
|
|
82
100
|
bump_args=(--get-next --yes)
|
|
83
|
-
if [[ "$FORCE_RELEASE" == "true" ]]; then
|
|
84
|
-
bump_args+=(--allow-no-commit)
|
|
85
|
-
fi
|
|
86
101
|
if ! next_version_output="$(uv run --no-sync cz bump "${bump_args[@]}" 2>&1)"; then
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
102
|
+
if grep -q "NO_COMMITS_TO_BUMP" <<<"$next_version_output"; then
|
|
103
|
+
if [[ "$FORCE_RELEASE" != "true" ]]; then
|
|
104
|
+
echo "::error::No eligible conventional commits were found for an automatic release. You can either wait for a user-impacting feature or bug fix and start a new release, or enable 'Force a patch release when auto finds no eligible commit' when triggering the 'Release' Action."
|
|
105
|
+
printf '%s\n' "$next_version_output"
|
|
106
|
+
exit 1
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
echo "No eligible conventional commits found; forcing a maintenance-only patch release."
|
|
110
|
+
if ! next_version_output="$(uv run --no-sync cz bump --get-next \
|
|
111
|
+
--increment PATCH --yes --allow-no-commit 2>&1)"; then
|
|
112
|
+
echo "::error::Commitizen could not calculate the forced patch release version."
|
|
113
|
+
printf '%s\n' "$next_version_output"
|
|
114
|
+
exit 1
|
|
115
|
+
fi
|
|
116
|
+
maintenance_only=true
|
|
117
|
+
else
|
|
118
|
+
echo "::error::Commitizen could not calculate the requested release version."
|
|
119
|
+
printf '%s\n' "$next_version_output"
|
|
120
|
+
exit 1
|
|
121
|
+
fi
|
|
90
122
|
fi
|
|
91
123
|
else
|
|
92
124
|
echo "Using explicit $INCREMENT bump."
|
|
@@ -102,7 +134,7 @@ jobs:
|
|
|
102
134
|
fi
|
|
103
135
|
|
|
104
136
|
target_tag="v$next_version"
|
|
105
|
-
if [[ ! "$target_tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]
|
|
137
|
+
if [[ ! "$target_tag" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
106
138
|
echo "::error::The calculated version did not create a semantic-version tag."
|
|
107
139
|
exit 1
|
|
108
140
|
fi
|
|
@@ -115,6 +147,8 @@ jobs:
|
|
|
115
147
|
|
|
116
148
|
echo "previous_tag=$previous_tag" >> "$GITHUB_OUTPUT"
|
|
117
149
|
echo "target_tag=$target_tag" >> "$GITHUB_OUTPUT"
|
|
150
|
+
echo "target_sha=$(git rev-parse "$target_tag^{commit}")" >> "$GITHUB_OUTPUT"
|
|
151
|
+
echo "maintenance_only=$maintenance_only" >> "$GITHUB_OUTPUT"
|
|
118
152
|
echo "Prepared local tag $target_tag from $previous_tag without modifying the default branch."
|
|
119
153
|
|
|
120
154
|
- name: Generate release notes
|
|
@@ -122,12 +156,18 @@ jobs:
|
|
|
122
156
|
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
|
123
157
|
PREVIOUS_TAG: ${{ steps.version.outputs.previous_tag }}
|
|
124
158
|
TARGET_TAG: ${{ steps.version.outputs.target_tag }}
|
|
159
|
+
MAINTENANCE_ONLY: ${{ steps.version.outputs.maintenance_only }}
|
|
125
160
|
run: |
|
|
126
161
|
set -euo pipefail
|
|
162
|
+
maintenance_args=()
|
|
163
|
+
if [[ "$MAINTENANCE_ONLY" == "true" ]]; then
|
|
164
|
+
maintenance_args+=(--maintenance-only)
|
|
165
|
+
fi
|
|
127
166
|
uv run -- python .github/skills/gh-release-notes/scripts/generate_release_notes.py \
|
|
128
167
|
--from-ref "$PREVIOUS_TAG" \
|
|
129
168
|
--to-ref "$TARGET_TAG" \
|
|
130
|
-
--output release-notes.md
|
|
169
|
+
--output release-notes.md \
|
|
170
|
+
"${maintenance_args[@]}"
|
|
131
171
|
|
|
132
172
|
- name: Upload generated notes
|
|
133
173
|
uses: actions/upload-artifact@v4
|
|
@@ -137,6 +177,7 @@ jobs:
|
|
|
137
177
|
|
|
138
178
|
- name: Create GitHub release
|
|
139
179
|
env:
|
|
180
|
+
GH_TOKEN: ${{ github.token }}
|
|
140
181
|
TARGET_TAG: ${{ steps.version.outputs.target_tag }}
|
|
141
182
|
shell: bash
|
|
142
183
|
run: |
|
|
@@ -168,6 +209,7 @@ jobs:
|
|
|
168
209
|
|
|
169
210
|
outputs:
|
|
170
211
|
target_tag: ${{ steps.version.outputs.target_tag }}
|
|
212
|
+
target_sha: ${{ steps.version.outputs.target_sha }}
|
|
171
213
|
|
|
172
214
|
publish:
|
|
173
215
|
if: ${{ !inputs.draft }}
|
|
@@ -185,7 +227,7 @@ jobs:
|
|
|
185
227
|
uses: actions/checkout@v4
|
|
186
228
|
with:
|
|
187
229
|
fetch-depth: 0
|
|
188
|
-
ref: ${{ needs.release.outputs.
|
|
230
|
+
ref: ${{ needs.release.outputs.target_sha }}
|
|
189
231
|
|
|
190
232
|
- name: Install uv
|
|
191
233
|
uses: astral-sh/setup-uv@v5
|
|
@@ -193,5 +235,23 @@ jobs:
|
|
|
193
235
|
- name: Build package
|
|
194
236
|
run: uv build
|
|
195
237
|
|
|
238
|
+
- name: Verify package version
|
|
239
|
+
env:
|
|
240
|
+
TARGET_TAG: ${{ needs.release.outputs.target_tag }}
|
|
241
|
+
shell: bash
|
|
242
|
+
run: |
|
|
243
|
+
set -euo pipefail
|
|
244
|
+
expected_version="${TARGET_TAG#v}"
|
|
245
|
+
shopt -s nullglob
|
|
246
|
+
artifacts=(
|
|
247
|
+
dist/copilot_session_usage-"$expected_version"-*.whl
|
|
248
|
+
dist/copilot_session_usage-"$expected_version".tar.gz
|
|
249
|
+
)
|
|
250
|
+
if (( ${#artifacts[@]} != 2 )); then
|
|
251
|
+
echo "::error::Built package artifacts do not match $TARGET_TAG."
|
|
252
|
+
printf '%s\n' dist/*
|
|
253
|
+
exit 1
|
|
254
|
+
fi
|
|
255
|
+
|
|
196
256
|
- name: Publish to PyPI
|
|
197
257
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# AGENTS.md — copilot-session-usage
|
|
2
2
|
|
|
3
3
|
`copilot-session-usage` is a PyPI-installable Python package that extracts usage and
|
|
4
|
-
cost analytics from local VS Code Copilot and Copilot
|
|
4
|
+
cost analytics from local VS Code Copilot, Copilot CLI, and Copilot App session logs.
|
|
5
5
|
|
|
6
6
|
Read [`CONSTITUTION.md`](CONSTITUTION.md) before changing code, tests, packaging,
|
|
7
7
|
release automation, or knowledge. It contains the durable project rules. This file
|
|
@@ -37,7 +37,7 @@ src/copilot_session_usage/
|
|
|
37
37
|
├── cli.py Click CLI entry point
|
|
38
38
|
├── _internal/core.py Session parsing, cost analysis, and shaping
|
|
39
39
|
├── _internal/vscode.py VS Code workspace discovery
|
|
40
|
-
├── _internal/copilot_cli.py
|
|
40
|
+
├── _internal/copilot_cli.py Copilot CLI/App discovery and event parsing
|
|
41
41
|
└── data/ Bundled model and pricing data
|
|
42
42
|
tests/ Pytest suite
|
|
43
43
|
scripts/ Maintenance scripts, including pricing refresh
|
|
@@ -24,8 +24,8 @@ Python package, usable from automation, and honest about unavailable evidence.
|
|
|
24
24
|
- Public API belongs in `src/copilot_session_usage/api.py`; internal implementation
|
|
25
25
|
belongs under `src/copilot_session_usage/_internal/`.
|
|
26
26
|
- Public API functions retain the optional `agent` parameter used for provider
|
|
27
|
-
routing. The supported provider values are `vscode` and
|
|
28
|
-
|
|
27
|
+
routing. The supported provider values are `vscode`, `cli`, and the explicit
|
|
28
|
+
combined mode `all`.
|
|
29
29
|
- Keep provider discovery separate from parsing, pricing, shaping, and presentation.
|
|
30
30
|
- Do not expose internal modules as public API merely to avoid a proper API change.
|
|
31
31
|
|