Author SHA1 Message Date
Tirsvad 64d8dd59f7 Throwaway: call the released workflow at v2 with the defaults (MIL-011 task 6)
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Code metrics / code-metrics (push) Successful in 9s
Verify code metrics defaults / fixed-defaults (push) Successful in 11s
2026-10-14 17:27:14 +08:00
Tirsvad d2bd21abf4 Throwaway: call the fixed workflow with the defaults (MIL-011 task 5)
Verify code metrics defaults / fixed-defaults (push) Successful in 11s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Code metrics / code-metrics (push) Successful in 11s
2026-10-14 17:09:48 +08:00
Tirsvad 46e752d725 Throwaway: only the call at @v1
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
2026-10-14 16:22:48 +08:00
Tirsvad 9a17868e80 Throwaway: only the call at @v2 with the defaults
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Code metrics / code-metrics (push) Failing after 9s
Verify code metrics defaults / at-v2-defaults (push) Failing after 9s
2026-10-14 16:22:03 +08:00
Tirsvad 5654905885 Throwaway: verify the defaults of code-metrics.yml (MIL-011 task 1)
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
2026-10-14 16:18:48 +08:00
Tirsvad 04f98c2e4e Merge pull request 'Review and accept MIL-011 (RC-076)' (#167) from mil-011-code-metrics-defaults into main
Publish accepted milestones / publish (push) Successful in 53s
Validate workflows / validate (push) Successful in 29s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 6s
Reviewed-on: #167
2026-10-14 10:05:07 +02:00
Tirsvad 60da2583d1 Resolve pending commit links for RC-076
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 32s
2026-10-14 16:00:05 +08:00
Tirsvad 3d7a9df3a3 Review and accept MIL-011 (RC-076)
First read found three corrections: the wording of O5 in the
traceability, "ten milestones" in the plan's purpose, and a scope row that
is not a Business Case item. Go; accepted by the author as stand-in for
S01, not independent. MIL-011 and its plan row are Accepted; the
traceability matrix has the new rows.
2026-10-14 16:00:04 +08:00
Tirsvad 0027da529c Merge pull request 'Plan MIL-011: fix the defaults of the code metrics workflow' (#166) from mil-011-code-metrics-defaults into main
Validate workflows / validate (push) Successful in 37s
Publish accepted milestones / publish (push) Successful in 48s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
Reviewed-on: #166
2026-10-14 09:58:30 +02:00
Tirsvad 60f39e3854 Resolve pending commit links for MIL-011
Validate workflows / validate (pull_request) Successful in 31s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
2026-10-14 15:57:20 +08:00
Tirsvad 02e3794d7b Plan MIL-011: fix the defaults of the code metrics workflow
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Release 2.1.0 documents code-metrics.yml at @v1 and defaults tools-ref to
v1, but the workflow and its scripts exist only from v2.1.0. MIL-011
plans the fix (a patch release 2.1.1), the test that would have caught
it, the guide and wiki corrections, and the removal of the workaround
from the framework Badges page. Proposed; no code changes yet.
2026-10-14 15:57:16 +08:00
Tirsvad ae04bc759f Merge pull request 'MIL-010: release 2.1.0 and outcome review RC-075' (#165) from mil-010-outcome into main
Publish wiki / publish-wiki (push) Successful in 11s
Publish own wiki / wiki (push) Successful in 11s
Publish accepted milestones / publish (push) Successful in 46s
Validate workflows / validate (push) Successful in 29s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 8s
Reviewed-on: #165
2026-10-14 09:13:28 +02:00
Tirsvad 62d280f07f Resolve pending commit links
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 31s
2026-10-14 15:10:33 +08:00
Tirsvad aa20e56110 MIL-010 task 9: release 2.1.0, outcome review RC-075
Task: MIL-010#9
Refs #163
2026-10-14 15:10:32 +08:00
Tirsvad 16faffaf13 Merge pull request 'MIL-010: code metrics and coverage badges workflow' (#164) from mil-010-implementation into main
Publish accepted milestones / publish (push) Successful in 55s
Validate workflows / validate (push) Successful in 33s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 6s
Publish wiki / publish-wiki (push) Successful in 22s
Publish own wiki / wiki (push) Successful in 22s
Reviewed-on: #164
2026-10-14 08:59:20 +02:00
Tirsvad a795f3b03e Re-run the checks: the status of the previous head stayed pending
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 32s
2026-10-14 14:10:45 +08:00
Tirsvad 292886531e Resolve pending commit links
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 28s
2026-10-14 14:05:29 +08:00
Tirsvad 5c5a1d19d9 MIL-010 task 8: adoption in a throwaway repository, results and RC-074; release entry 2.1.0
Task: MIL-010#8
Refs #162
2026-10-14 14:05:20 +08:00
Tirsvad 95f9577a14 MIL-010 task 7: guide, README, wiki pages in three languages
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Task: MIL-010#7
Refs #161
2026-10-14 13:56:49 +08:00
Tirsvad 0dc5132a2e MIL-010 task 6: the coverage report reaches the workflow as an artifact (ADR-0010, RC-073)
Task: MIL-010#6
Refs #160
2026-10-14 13:54:01 +08:00
Tirsvad 4fc0a8cab3 MIL-010 task 6: reusable workflow code-metrics.yml and tools/code-metrics.sh
Task: MIL-010#6
Refs #160
2026-10-14 13:50:34 +08:00
Tirsvad 5bc14d789d MIL-010 tasks 4 and 5: counter, coverage reader and badge renderer
Task: MIL-010#4
Task: MIL-010#5
Refs #158
Refs #159
2026-10-14 13:48:04 +08:00
Tirsvad 6c3544837c MIL-010 task 3: results of the delivery check
Refs #157
2026-10-14 13:44:34 +08:00
Tirsvad aa4ca0f897 MIL-010 task 2: ADR-0010, the contract of the code metrics badges (RC-072)
Refs #156
2026-10-14 13:43:15 +08:00
Tirsvad 9ca9673b00 MIL-010 task 1: use case UC-008, stories US-001.18 to US-001.20, RC-069 to RC-071
Refs #155
2026-10-14 13:41:14 +08:00
Tirsvad 6d2020e9ab Merge pull request 'MIL-010: plan the code metrics and coverage badges workflow' (#154) from mil-010-code-metrics-badges into main
Publish accepted milestones / publish (push) Successful in 47s
Validate workflows / validate (push) Successful in 22s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
Reviewed-on: #154
2026-10-14 07:37:14 +02:00
Tirsvad 1c3d810284 Resolve pending commit links
Validate workflows / validate (pull_request) Successful in 24s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
2026-10-14 13:35:57 +08:00
Tirsvad 50024505fe MIL-010: plan the code metrics and coverage badges workflow
Adds objective O5 to BC-001 (RC-067), the milestone MIL-010 with nine tasks and
the plan row of PP-001 (RC-068); the traceability matrix is updated.
2026-10-14 13:35:53 +08:00
Tirsvad 285084fb0d Merge pull request 'Move the framework pin from v0.6.2 to v0.7.0' (#153) from framework-pin-0-7-0 into main
Validate workflows / validate (push) Successful in 25s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
Reviewed-on: #153
2026-10-14 06:57:39 +02:00
Tirsvad bf54f346a5 Move the framework pin from v0.6.2 to v0.7.0
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 25s
v0.7.0 carries the conventions (gitignore API, LF line endings, CRUD
template), process/wiki.md and the publish-wiki.yml caller template, and its
files are LF. Tests (413) and the milestone sync dry run pass with it.
2026-10-14 12:56:12 +08:00
Tirsvad f43d93d141 Merge pull request 'MIL-009: verification results and outcome review RC-066' (#152) from mil-009-outcome into main
Publish accepted milestones / publish (push) Successful in 43s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 6s
Validate workflows / validate (push) Successful in 22s
Reviewed-on: #152
2026-10-14 06:31:31 +02:00
Tirsvad c8e111d2b5 Re-run the checks: the status of the previous head stayed pending
Validate workflows / validate (pull_request) Successful in 23s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
2026-10-14 12:21:54 +08:00
Tirsvad 905b21dae9 Resolve pending commit links
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 25s
2026-10-14 12:19:04 +08:00
Tirsvad 95abc18a95 MIL-009: verification results and outcome review RC-066
All seven Go / No-Go criteria are met: framework v0.7.0, seven repositories
calling publish-wiki-pages.yml with 0-difference read-backs, no wiki/.publish
in 48 repositories, the scoped workflow removed, ADR-0004 superseded, 2.0.0
released with v1 left at 1.2.2. The acceptance is recorded as AI stand-in for
S01 (mode autonomous, not independent).
2026-10-14 12:19:02 +08:00
Tirsvad 6e8e5489d1 Merge pull request 'MIL-009: remove the scoped publish-wiki workflow and the marker; ADR-0004 superseded' (#151) from mil-009-remove-scoped into main
Publish accepted milestones / publish (push) Successful in 44s
Validate workflows / validate (push) Successful in 23s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
Publish wiki / publish-wiki (push) Successful in 28s
Publish own wiki / wiki (push) Successful in 28s
2026-10-14 06:15:32 +02:00
Tirsvad 8e32f48757 Resolve pending commit links
Validate workflows / validate (pull_request) Successful in 27s
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
2026-10-14 11:45:08 +08:00
Tirsvad 78d21b34a3 Remove the scoped publish-wiki workflow and the marker path (2.0.0)
- .gitea/scoped_workflows/publish-wiki.yml is deleted
- tools/publish_wiki.py requires WIKI_PRUNE, reads no marker, and defaults
  WIKI_DIR to docs/wiki; its tests follow
- README, onboarding, troubleshooting and upgrading guides, and the wiki pages
  in English, Danish and Bahasa Malaysia no longer describe the scoped workflow
- changelog entry 2.0.0 with the Breaking group

Task: MIL-009#6
Task: MIL-009#8
2026-10-14 11:45:03 +08:00
Tirsvad 22fc323b41 ADR-0004: superseded by ADR-0009; review record RC-065
The scoped delivery and the wiki/.publish marker are replaced by the reusable
workflow of ADR-0009. The AI records the acceptance as stand-in for S01
(mode autonomous, not independent).

Task: MIL-009#7
2026-10-14 11:45:01 +08:00
Tirsvad 878adbf148 Merge pull request 'MIL-009, PP-001 and RC-064: accepted' (#142) from plan-mil-009 into main
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
Publish accepted milestones / publish (push) Successful in 45s
Validate workflows / validate (push) Successful in 22s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 6s
2026-10-13 18:01:46 +02:00
Tirsvad 9963d69731 Resolve pending commit links
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 24s
2026-10-14 00:00:39 +08:00
Tirsvad 2f267df2a2 MIL-009, PP-001 and RC-064: accepted
The user switched to autonomous mode and accepted the plan in chat on
2026-10-14 with the defaults: target date 2026-11-30, no publishing for
027-sandbox and 028-pomodoro, framework release 0.7.0. The acceptance is
recorded as AI stand-in for S01 (RC-064, mode autonomous, not independent).
2026-10-14 00:00:38 +08:00
Tirsvad 8051a07e7b Merge pull request 'PP-001: add MIL-009 and review record RC-064' (#141) from plan-mil-009 into main
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
Publish accepted milestones / publish (push) Successful in 53s
Validate workflows / validate (push) Successful in 27s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 7s
Reviewed-on: #141
2026-10-13 17:59:28 +02:00
Tirsvad 38deeba89b Resolve pending commit link of RC-064
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 26s
2026-10-13 23:58:46 +08:00
Tirsvad 856197d0bb Resolve pending commit links
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
2026-10-13 23:58:34 +08:00
Tirsvad 86e69ce85d PP-001: add MIL-009 and review record RC-064
MIL-009 (migrate the wiki callers, remove the scoped workflow) joins the
Gateway Schedule, the Gantt, Scope Coverage, Dependencies and Plan Risks.
RC-064 records the review; MIL-009, the plan row and the record stay
Proposed until S01 gives the Go and confirms the target date.
2026-10-13 23:58:33 +08:00
Tirsvad bb3b0f242b Merge pull request 'MIL-009: draft the milestone to migrate the wiki callers and remove the scoped workflow' (#140) from mil-009-migrate-and-remove into main
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
Publish accepted milestones / publish (push) Successful in 56s
Validate workflows / validate (push) Successful in 25s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 6s
Reviewed-on: #140
2026-10-13 17:48:57 +02:00
Tirsvad 922ff18c0a Resolve pending commit link of MIL-009
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 25s
2026-10-13 23:36:25 +08:00
Tirsvad 2b53ee6d21 MIL-009: draft the milestone to migrate the wiki callers and remove the scoped workflow
Proposed. Seven repositories still publish through the scoped
publish-wiki.yml with wiki/.publish; MIL-009 moves them to docs/wiki/ and a
caller of publish-wiki-pages.yml, removes the scoped workflow, supersedes
ADR-0004 and releases 2.0.0. The target date awaits S01.
2026-10-13 23:36:23 +08:00
Tirsvad 157bf6560d Merge pull request 'ADR-0009 and RC-063: review and acceptance' (#139) from review-adr-0009 into main
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
Publish accepted milestones / publish (push) Successful in 35s
Validate workflows / validate (push) Successful in 21s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
Reviewed-on: #139
2026-10-13 17:33:55 +02:00
Tirsvad 8c0f006dc5 Resolve pending commit link of RC-063
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 24s
2026-10-13 23:32:52 +08:00
Tirsvad 7fecb33a72 Resolve pending commit links 2026-10-13 23:32:41 +08:00
Tirsvad 8c2c52ebd2 ADR-0009 and RC-063: review and acceptance
RC-063 reviews ADR-0009 against QC-ADR-001: three findings fixed (stale
verification statement, security impact named, product owner spelled out).
S01 gave the Go in chat on 2026-10-13; ADR-0009 and RC-063 are Accepted.
2026-10-13 23:32:39 +08:00
Tirsvad 2ee3d4d535 Merge pull request 'PP-001 and RC-062: accepted' (#138) from accept-pp-001-mil-008 into main
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
Publish accepted milestones / publish (push) Successful in 33s
Validate workflows / validate (push) Successful in 21s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
Reviewed-on: #138
2026-10-13 16:43:04 +02:00
Tirsvad 8f0ded2593 Resolve pending commit links
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 20s
2026-10-13 22:42:06 +08:00
Tirsvad 23a64a3f35 PP-001 and RC-062: accepted
S01 gave the Go on the MIL-008 plan change in chat on 2026-10-13. The change
was merged as pull request 137 with the plan row Proposed; this sets PP-001
and RC-062 to Accepted.
2026-10-13 22:42:04 +08:00
Tirsvad 8c1fba6d9d Merge pull request 'PP-001: add MIL-008 and review record RC-062' (#137) from project-plan-mil-008 into main
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
Publish accepted milestones / publish (push) Successful in 36s
Validate workflows / validate (push) Successful in 22s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 6s
Reviewed-on: #137
2026-10-13 16:38:52 +02:00
Tirsvad 672bc2988e Resolve pending commit links
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 23s
2026-10-13 22:37:54 +08:00
Tirsvad 9aeb74e8b0 PP-001: add MIL-008 and review record RC-062
MIL-008 (wiki publishing as a reusable workflow) joins the Gateway Schedule,
the Gantt, Scope Coverage, Dependencies and Plan Risks. RC-062 records the
review; the plan row stays Proposed until S01 gives the Go.
2026-10-13 22:37:52 +08:00
Tirsvad 9bf0a2ba70 Merge pull request 'MIL-008: publish the wiki through a reusable workflow; this wiki moves to docs/wiki' (#129) from wiki-in-docs-wiki into main
Publish accepted milestones / publish (push) Successful in 43s
Validate workflows / validate (push) Successful in 24s
TirSystem/github-action: Publish wiki / publish-wiki (push) Successful in 6s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 6s
Publish wiki / publish-wiki (push) Successful in 18s
Publish own wiki / wiki (push) Successful in 18s
Reviewed-on: #129
2026-10-13 16:27:09 +02:00
Tirsvad a542778412 Merge pull request 'LF line endings: .gitattributes, .editorconfig and three converted files' (#128) from lf-line-endings into main
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
Validate workflows / validate (push) Successful in 23s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 6s
Reviewed-on: #128
2026-10-13 16:26:58 +02:00
Tirsvad 4dbc7e2f3e Resolve pending commit links
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 25s
2026-10-13 22:20:28 +08:00
Tirsvad df5d86e359 Release 1.2.2: changelog entry
Task: MIL-008#7
2026-10-13 22:20:18 +08:00
Tirsvad 10e88e8a27 Publish the wiki through a reusable workflow; this wiki moves to docs/wiki
- publish-wiki-pages.yml: reusable workflow with wiki_dir, prune and
  tools-ref inputs and the declared secret TOKEN_FOR_GITEA_WIKI
- tools/publish_wiki.py: WIKI_PRUNE selects explicit mode without the marker;
  a missing folder is a configuration error
- publish-own-wiki.yml: this repository calls the reusable workflow
- wiki/ moves to docs/wiki/; build_wiki.py and its tests follow
- the scoped publish-wiki.yml is marked deprecated, not removed
- guides, README and the Danish and Bahasa Malaysia wiki pages describe the
  caller, docs/wiki/ and the new error messages

Task: MIL-008#2
Task: MIL-008#4
Task: MIL-008#5
Task: MIL-008#6
2026-10-13 22:20:10 +08:00
Tirsvad 01b9c2972b MIL-008: plan the wiki as a reusable workflow, ADR-0009, RC-060, RC-061
MIL-008 plans publishing the wiki through a reusable workflow that reads
docs/wiki/. ADR-0009 records the decision. RC-060 accepts the plan; RC-061
accepts the amendment that deprecates the scoped workflow instead of
removing it, because eight repositories still publish through it.
2026-10-13 22:20:09 +08:00
Tirsvad b8d363d328 Convert the files committed with CRLF to LF
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 28s
Line endings only: git diff --ignore-cr-at-eol is empty.
2026-10-13 22:19:39 +08:00
Tirsvad 0d7b276251 Add .gitattributes and .editorconfig: LF line endings 2026-10-13 22:19:38 +08:00
Tirsvad 3dbe5b6b92 Merge pull request 'RC-059: close the action item, the throwaway repositories are deleted' (#127) from rc-059-close-action into main
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
Publish accepted milestones / publish (push) Successful in 34s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
Validate workflows / validate (push) Successful in 25s
2026-10-13 06:48:05 +02:00
Tirsvad 667dd3e28d Resolve pending commit links
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 22s
2026-10-13 12:47:14 +08:00
Tirsvad daecb7b452 RC-059: close the action item, the throwaway repositories are deleted
The user asked for the deletion of TirSystem/qc-adoption-cpp and
TirSystem/qc-adoption-csharp on 2026-10-13. The remaining action, adoption in
a real C++ and C# project, stays open.

Task: MIL-007#7
Refs #101
2026-10-13 12:47:12 +08:00
Tirsvad ea3fa44018 Merge pull request 'Release 1.2.1: changelog entry' (#126) from release-1.2.1 into main
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
Validate workflows / validate (push) Successful in 22s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
2026-10-13 06:38:58 +02:00
Tirsvad d1d3dc922e Release 1.2.1: changelog entry
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 23s
Patch version: a documentation fix, nothing changes for callers. Release
approval (step 4 of the release process): AI as stand-in for S01, as the user
chose autonomous mode and asked for the release in chat on 2026-10-13.
2026-10-13 12:38:07 +08:00
Tirsvad 4443be11a1 Merge pull request 'MIL-007 task 7: adoption in two throwaway repositories, seeded failures verified' (#125) from mil-007-task-7-adoption into main
Publish accepted milestones / publish (push) Successful in 34s
Validate workflows / validate (push) Successful in 23s
TirSystem/github-action: Publish wiki / publish-wiki (push) Successful in 13s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
2026-10-13 06:27:49 +02:00
Tirsvad 4d9ba2e671 Resolve pending commit links
Validate workflows / validate (pull_request) Successful in 24s
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
2026-10-13 12:26:57 +08:00
Tirsvad 2481c26d80 MIL-007 task 7: adoption in two throwaway repositories, seeded failures verified
Results and review (RC-059, autonomous, AI as stand-in for S01) added to the
milestone. The guides and their wiki pages now say that clang-tidy and dotnet
print absolute paths in the workspace of the runner.

Task: MIL-007#7
Closes #101
2026-10-13 12:26:56 +08:00
Tirsvad de5d330445 Merge pull request 'Release 1.2.0: changelog entry' (#124) from release-1.2.0 into main
Validate workflows / validate (push) Successful in 21s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 6s
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
2026-10-13 06:18:29 +02:00
Tirsvad bd71fec302 Release 1.2.0: changelog entry
Validate workflows / validate (pull_request) Successful in 25s
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Minor version: two new reusable workflows, nothing changes for existing
callers. Release approval (step 4 of the release process): AI as stand-in for
S01, as the user chose autonomous mode and asked for the release in chat on
2026-10-13.
2026-10-13 12:17:39 +08:00
Tirsvad e267377fae Merge pull request 'Ignore the generated Codex agent copies' (#123) from gitignore-codex-agents into main
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
Validate workflows / validate (push) Successful in 23s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 6s
Reviewed-on: #123
2026-10-13 06:11:04 +02:00
Tirsvad 808b7666a8 Merge branch 'main' into gitignore-codex-agents
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
Validate workflows / validate (pull_request) Successful in 26s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
2026-10-13 06:10:57 +02:00
Tirsvad b24a74a7df Merge pull request 'Add the reusable quality-check workflows for C++ and C# (MIL-007 tasks 4 and 5)' (#110) from mil-007-cpp-csharp-workflows into main
Validate workflows / validate (push) Successful in 24s
TirSystem/github-action: Publish wiki / publish-wiki (push) Successful in 16s
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Successful in 5s
2026-10-13 06:08:57 +02:00
Tirsvad 8e5ea26044 Merge remote-tracking branch 'origin/main' into mil-007-cpp-csharp-workflows
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
Validate workflows / validate (pull_request) Successful in 22s
# Conflicts:
#	CHANGELOG.md
2026-10-13 12:08:05 +08:00
Tirsvad 200bf566e9 Ignore the generated Codex agent copies
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 18s
.codex/agents/ is the Codex form of framework/.agents/agents/, made per clone by
install-skills.sh, like .agents/ and .claude/. It is not committed; without the
entry it showed up as an untracked folder every time the installer ran.
2026-10-13 12:07:54 +08:00
Tirsvad a753cfe0a9 Merge remote-tracking branch 'origin/main' into mil-007-cpp-csharp-workflows
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 22s
2026-10-13 11:51:29 +08:00
Tirsvad 84383eafe9 Make every clang-tidy finding an error
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
Validate workflows / validate (pull_request) Successful in 23s
Task: MIL-007#4
2026-10-11 16:36:48 +08:00
Tirsvad 81866d8dac Run clang-tidy with default checks when the project has none
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 22s
Task: MIL-007#4
2026-10-11 16:32:46 +08:00
Tirsvad bf4d090fb3 Merge pull request 'Add the guides and wiki pages for C++ and C# quality checks (MIL-007 task 6)' (#113) from mil-007-guides into mil-007-cpp-csharp-workflows
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 20s
Reviewed-on: #113
2026-10-11 10:26:29 +02:00
Tirsvad ec9a8d6f4a Add the guides and wiki pages for the C and C++ and C# quality checks
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 20s
Task: MIL-007#6
2026-10-11 16:24:22 +08:00
Tirsvad 77f91cf066 Give the C++ and C# workflows a timeout and an exact checkout tag
TirSystem/github-action: Publish wiki / publish-wiki (push) Skipped
TirSystem/github-action: Sync GitHub mirror metadata / sync-metadata (push) Skipped
Validate workflows / validate (pull_request) Successful in 23s
Task: MIL-007#4

Task: MIL-007#5
2026-10-11 16:20:13 +08:00
Tirsvad acbfe967e0 Add the reusable quality-check workflows for C++ and C#
Task: MIL-007#4

Task: MIL-007#5
2026-10-11 16:19:45 +08:00
103 changed files with 7848 additions and 758 deletions
+7
View File
@@ -0,0 +1,7 @@
# Line endings are LF everywhere (rule 15 of the coding-conventions skill).
root = true
[*]
end_of_line = lf
charset = utf-8
insert_final_newline = true
+7 -7
View File
@@ -1,7 +1,7 @@
# Copy to .env and fill in real values. .env is gitignored - never commit it. # Copy to .env and fill in real values. .env is gitignored - never commit it.
# Gitea personal access token (Settings -> Applications -> Generate New Token), # Gitea personal access token (Settings -> Applications -> Generate New Token),
# scoped to issues only if your Gitea version supports scoped tokens. # scoped to issues only if your Gitea version supports scoped tokens.
# Used by framework/scripts/sync-project.sh --apply against a Gitea remote. # Used by framework/scripts/sync-project.sh --apply against a Gitea remote.
# NOT a deploy key - deploy keys authenticate git-over-SSH only, not the API. # NOT a deploy key - deploy keys authenticate git-over-SSH only, not the API.
GITEA_TOKEN= GITEA_TOKEN=
+1
View File
@@ -0,0 +1 @@
* text=auto eol=lf
-43
View File
@@ -1,43 +0,0 @@
name: Publish wiki
# Shared through scoped delivery: it runs in every repository of an
# organization, but writes only where the repository has opted in with
# wiki/.publish (ADR-0004). TOKEN_FOR_GITEA_WIKI is read from the organization
# secrets; there is no fallback to the read-only sync token. The script is a
# file of TirSystem/github-action (tools/publish_wiki.py), fetched with a
# second checkout so that it is readable, lintable and tested as a file
# (MIL-004 task 6).
on:
push:
branches: [ main ]
paths:
- "wiki/**"
workflow_dispatch:
concurrency:
group: publish-wiki
cancel-in-progress: false
jobs:
publish-wiki:
permissions:
contents: read
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out the repository
uses: actions/checkout@v4.2.2
- name: Fetch the shared script
uses: actions/checkout@v4.2.2
with:
repository: TirSystem/github-action
ref: v1
path: shared-scripts
- name: Publish the pages to the wiki
env:
GITEA_API_URL: ${{ gitea.api_url }}
SOURCE_REPOSITORY: ${{ gitea.repository }}
WIKI_CREDENTIALS: ${{ secrets.TOKEN_FOR_GITEA_WIKI }}
run: python3 shared-scripts/tools/publish_wiki.py
+95
View File
@@ -0,0 +1,95 @@
name: Code metrics
# Reusable workflow (ADR-0010). A repository calls it with
#
# on:
# push:
# branches: [main]
# jobs:
# metrics:
# uses: TirSystem/github-action/.gitea/workflows/code-metrics.yml@v1
# permissions:
# contents: write
#
# It counts the code files and the lines of code of the repository (comment
# and blank lines are not counted), reads an optional Cobertura XML coverage
# report (an artifact of an earlier job of the caller, or a file of the
# repository) and commits three SVG badges to a branch of the calling
# repository.
# It runs no test, needs no secret and writes only the badges branch.
on:
workflow_call:
inputs:
working-directory:
description: "Directory, relative to the repository root, whose code is counted"
type: string
default: "."
exclude:
description: "Comma list of patterns of files and directories that are not counted, in addition to the defaults of ADR-0010"
type: string
default: ""
coverage-report:
description: "Path, relative to the repository root, of a Cobertura XML coverage report; empty writes no coverage badge"
type: string
default: ""
coverage-artifact:
description: "Name of an artifact, uploaded by an earlier job of the same run, that holds the coverage report; empty reads coverage-report from the checkout"
type: string
default: ""
coverage-warn:
description: "Coverage in percent below which the coverage badge is red"
type: string
default: "60"
coverage-good:
description: "Coverage in percent from which the coverage badge is green"
type: string
default: "80"
badges-branch:
description: "Branch that holds the badge files; it is created when it does not exist"
type: string
default: "badges"
tools-ref:
description: "Tag or branch of TirSystem/github-action whose tools/ files are run; pin it to the exact tag together with the workflow"
type: string
default: "v1"
jobs:
code-metrics:
permissions:
contents: write
runs-on: ubuntu-latest
timeout-minutes: 10
env:
WORKING_DIRECTORY: ${{ inputs.working-directory }}
EXCLUDE: ${{ inputs.exclude }}
COVERAGE_REPORT: ${{ inputs.coverage-report }}
COVERAGE_ARTIFACT: ${{ inputs.coverage-artifact }}
COVERAGE_WARN: ${{ inputs.coverage-warn }}
COVERAGE_GOOD: ${{ inputs.coverage-good }}
BADGES_BRANCH: ${{ inputs.badges-branch }}
steps:
- name: Check out the repository
uses: actions/checkout@v4.2.2
- name: Fetch the shared scripts
uses: actions/checkout@v4.2.2
with:
repository: TirSystem/github-action
ref: ${{ inputs.tools-ref }}
path: shared-scripts
- name: Validate the inputs
run: bash shared-scripts/tools/code-metrics.sh validate
- name: Download the coverage report
if: ${{ inputs.coverage-artifact != '' }}
uses: actions/download-artifact@v4.1.8
with:
name: ${{ inputs.coverage-artifact }}
path: coverage-artifact
- name: Count the code and write the badges
run: bash shared-scripts/tools/code-metrics.sh measure
- name: Publish the badges
run: bash shared-scripts/tools/code-metrics.sh publish
+25
View File
@@ -0,0 +1,25 @@
name: Publish own wiki
# Publishes the wiki of this repository from docs/wiki/ by calling the
# reusable workflow of this repository (ADR-0009, MIL-008 task 4). It is the
# caller a project copies from framework/templates/ci/publish-wiki.yml, with
# two differences: the reusable workflow is called by its local path, and
# tools-ref is main so that the script that is run is the one of this commit.
on:
push:
branches: [ main ]
paths:
- "docs/wiki/**"
workflow_dispatch:
jobs:
wiki:
permissions:
contents: read
uses: ./.gitea/workflows/publish-wiki-pages.yml
with:
wiki_dir: docs/wiki
prune: "true"
tools-ref: main
secrets:
TOKEN_FOR_GITEA_WIKI: ${{ secrets.TOKEN_FOR_GITEA_WIKI }}
+64
View File
@@ -0,0 +1,64 @@
name: Publish wiki
# Reusable workflow (ADR-0009, MIL-008). A repository calls it with
#
# on:
# push:
# branches: [ main ]
# paths: [ "docs/wiki/**" ]
# jobs:
# wiki:
# uses: TirSystem/github-action/.gitea/workflows/publish-wiki-pages.yml@v1
# secrets:
# TOKEN_FOR_GITEA_WIKI: ${{ secrets.TOKEN_FOR_GITEA_WIKI }}
#
# The caller file is the opt-in; there is no marker file. It publishes the
# Markdown pages of wiki_dir to the wiki of the calling repository, creating
# and updating pages, and deleting pages that are not in the folder only when
# prune is true. The script is a file of this repository
# (tools/publish_wiki.py), fetched at tools-ref.
on:
workflow_call:
inputs:
wiki_dir:
description: "Directory, relative to the repository root, with the wiki pages"
type: string
default: "docs/wiki"
prune:
description: "true deletes wiki pages that are not in the folder; false keeps them"
type: string
default: "false"
tools-ref:
description: "Tag or branch of TirSystem/github-action whose tools/publish_wiki.py is run; pin it to the exact tag together with the workflow"
type: string
default: "v1"
secrets:
TOKEN_FOR_GITEA_WIKI:
description: "Gitea token with write access to the wiki of the calling repository"
required: true
jobs:
publish-wiki:
permissions:
contents: read
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- name: Check out the repository
uses: actions/checkout@v4.2.2
- name: Fetch the shared script
uses: actions/checkout@v4.2.2
with:
repository: TirSystem/github-action
ref: ${{ inputs.tools-ref }}
path: shared-scripts
- name: Publish the pages to the wiki
env:
GITEA_API_URL: ${{ gitea.api_url }}
SOURCE_REPOSITORY: ${{ gitea.repository }}
WIKI_CREDENTIALS: ${{ secrets.TOKEN_FOR_GITEA_WIKI }}
WIKI_DIR: ${{ inputs.wiki_dir }}
WIKI_PRUNE: ${{ inputs.prune }}
run: python3 shared-scripts/tools/publish_wiki.py
+72
View File
@@ -0,0 +1,72 @@
name: Quality checks C++
# Reusable workflow (ADR-0008). A repository calls it with
#
# jobs:
# quality:
# uses: TirSystem/github-action/.gitea/workflows/quality-checks-cpp.yml@v1
#
# It checks the C and C++ files of the repository with clang-format and
# clang-tidy at exact versions, changes no file and needs no secret. The
# repository needs a .clang-format; a .clang-tidy and a compile database
# (compile-commands-dir) are optional.
on:
workflow_call:
inputs:
working-directory:
description: "Directory, relative to the repository root, whose files are checked"
type: string
default: "."
std:
description: "C++ standard for clang-tidy when no compile database is given, such as c++20"
type: string
default: "c++20"
compile-commands-dir:
description: "Directory with a compile_commands.json built by the caller before the call; empty uses the std input"
type: string
default: ""
clang-format-version:
description: "Exact version of the clang-format package"
type: string
default: "23.1.3"
clang-tidy-version:
description: "Exact version of the clang-tidy package"
type: string
default: "22.1.8"
tools-ref:
description: "Tag or branch of TirSystem/github-action whose tools/quality-checks-cpp.sh is run; pin it to the exact tag together with the workflow"
type: string
default: "v1"
jobs:
quality-checks-cpp:
permissions:
contents: read
runs-on: ubuntu-latest
timeout-minutes: 15
env:
WORKING_DIRECTORY: ${{ inputs.working-directory }}
STD: ${{ inputs.std }}
COMPILE_COMMANDS_DIR: ${{ inputs.compile-commands-dir }}
CLANG_FORMAT_VERSION: ${{ inputs.clang-format-version }}
CLANG_TIDY_VERSION: ${{ inputs.clang-tidy-version }}
QC_VENV: /tmp/quality-tools
steps:
- name: Check out the repository
uses: actions/checkout@v4.2.2
- name: Fetch the shared script
uses: actions/checkout@v4.2.2
with:
repository: TirSystem/github-action
ref: ${{ inputs.tools-ref }}
path: shared-scripts
- name: Validate the inputs
run: bash shared-scripts/tools/quality-checks-cpp.sh validate
- name: Install the tools
run: bash shared-scripts/tools/quality-checks-cpp.sh install
- name: Run the checks
run: bash shared-scripts/tools/quality-checks-cpp.sh run
@@ -0,0 +1,61 @@
name: Quality checks C#
# Reusable workflow (ADR-0008). A repository calls it with
#
# jobs:
# quality:
# uses: TirSystem/github-action/.gitea/workflows/quality-checks-csharp.yml@v1
#
# It checks the C# solution or project of the repository with `dotnet format`
# and `dotnet build` (warnings as errors) on an exact .NET SDK 10 or later,
# changes no file and needs no secret. The package restore needs the network.
on:
workflow_call:
inputs:
working-directory:
description: "Directory, relative to the repository root, whose files are checked"
type: string
default: "."
project:
description: "Solution or project file, relative to working-directory; empty uses the single .sln, .slnx or .csproj found"
type: string
default: ""
dotnet-sdk-version:
description: "Exact .NET SDK version, 10 or later (no channel, no range)"
type: string
default: "10.0.401"
tools-ref:
description: "Tag or branch of TirSystem/github-action whose tools/quality-checks-csharp.sh is run; pin it to the exact tag together with the workflow"
type: string
default: "v1"
jobs:
quality-checks-csharp:
permissions:
contents: read
runs-on: ubuntu-latest
timeout-minutes: 15
env:
WORKING_DIRECTORY: ${{ inputs.working-directory }}
PROJECT: ${{ inputs.project }}
DOTNET_SDK_VERSION: ${{ inputs.dotnet-sdk-version }}
DOTNET_ROOT: /tmp/dotnet
steps:
- name: Check out the repository
uses: actions/checkout@v4.2.2
- name: Fetch the shared script
uses: actions/checkout@v4.2.2
with:
repository: TirSystem/github-action
ref: ${{ inputs.tools-ref }}
path: shared-scripts
- name: Validate the inputs
run: bash shared-scripts/tools/quality-checks-csharp.sh validate
- name: Install the .NET SDK
run: bash shared-scripts/tools/quality-checks-csharp.sh install
- name: Run the checks
run: bash shared-scripts/tools/quality-checks-csharp.sh run
@@ -0,0 +1,17 @@
name: Verify code metrics defaults
# Throwaway (MIL-011 task 6). It calls the fixed code-metrics.yml from the
# branch mil-011-fix with every input at its default, except that the badges
# go to the existing verification branch and not to `badges`.
# It runs only on this branch and is not merged.
on:
push:
branches: [ verify-code-metrics-defaults ]
jobs:
fixed-defaults:
permissions:
contents: write
uses: TirSystem/github-action/.gitea/workflows/code-metrics.yml@v2
with:
badges-branch: badges-verify
+18 -16
View File
@@ -1,19 +1,21 @@
# Local secrets - never commit these # Local secrets - never commit these
.env .env
.env.* .env.*
!.env.example !.env.example
# Local, per-clone files generated/copied from framework/ - never commit # Local, per-clone files generated/copied from framework/ - never commit
# these (see framework/README.md "Using it in a project"): # these (see framework/README.md "Using it in a project"):
# .agents/skills/ <- install-skills.sh's copy of framework/.agents/skills/ # .agents/skills/ <- install-skills.sh's copy of framework/.agents/skills/
# .claude/skills/ <- same copy, for the standalone Claude Code CLI # .claude/skills/ <- same copy, for the standalone Claude Code CLI
/.agents/ # .codex/agents/ <- the Codex form of framework/.agents/agents/
/.claude/ /.agents/
/.claude/
# Python virtual environment and caches .codex/agents/
.venv/
__pycache__/ # Python virtual environment and caches
.pytest_cache/ .venv/
__pycache__/
.pytest_cache/
# Local coverage data (python -m coverage run) # Local coverage data (python -m coverage run)
.coverage .coverage
+184 -184
View File
@@ -1,184 +1,184 @@
# AGENTS.md # AGENTS.md
- `framework/` — the reusable SQA and QC framework (QC checklists, the - `framework/` — the reusable SQA and QC framework (QC checklists, the
`artifact` skill, review procedure, catalog, scripts). No project data. A `artifact` skill, review procedure, catalog, scripts). No project data. A
git submodule of the `TirSystem/sqa-qc-framework` repository, pinned to a git submodule of the `TirSystem/sqa-qc-framework` repository, pinned to a
release tag; see `framework/README.md`. Clone this project with release tag; see `framework/README.md`. Clone this project with
`git clone --recurse-submodules` (or run `git submodule update --init --recursive`; `--recursive` also `git clone --recurse-submodules` (or run `git submodule update --init --recursive`; `--recursive` also
fetches the nested `framework/qc`). fetches the nested `framework/qc`).
- `docs/` — this project's artifacts (Business Case, Stakeholder Analysis, - `docs/` — this project's artifacts (Business Case, Stakeholder Analysis,
plan, milestones, user stories, ADRs, review records, traceability matrix). plan, milestones, user stories, ADRs, review records, traceability matrix).
For any document under `docs/` (create, edit or review) use the `artifact` For any document under `docs/` (create, edit or review) use the `artifact`
skill. To start a project, or to see where one stands and what comes next, use skill. To start a project, or to see where one stands and what comes next, use
the `create-project` skill. For planning a project or a phase into tasks, and the `create-project` skill. For planning a project or a phase into tasks, and
syncing phases and tasks to Gitea/GitHub as Milestones and Issues, use the syncing phases and tasks to Gitea/GitHub as Milestones and Issues, use the
`project-planning` skill. For writing or reviewing source code (Python, C, C++, C#) use the `project-planning` skill. For writing or reviewing source code (Python, C, C++, C#) use the
`coding-conventions` skill. Skills are read from `.agents/skills/` (this harness and Codex CLI) `coding-conventions` skill. Skills are read from `.agents/skills/` (this harness and Codex CLI)
and `.claude/skills/` (standalone Claude Code CLI), both copies made by and `.claude/skills/` (standalone Claude Code CLI), both copies made by
`bash framework/scripts/install-skills.sh`; edit the skill in `bash framework/scripts/install-skills.sh`; edit the skill in
`framework/.agents/skills/`, then re-run the script. `framework/.agents/skills/`, then re-run the script.
## Modes ## Modes
There are two modes. **Supervised** is the default. **Autonomous** applies only There are two modes. **Supervised** is the default. **Autonomous** applies only
when the user asks for it in chat ("autonomous mode: build X"), and then holds when the user asks for it in chat ("autonomous mode: build X"), and then holds
for the rest of the session, across requests, until the user switches back for the rest of the session, across requests, until the user switches back
("supervised") or the session ends. A new session starts supervised. The mode ("supervised") or the session ends. A new session starts supervised. The mode
is chosen by the user in chat: a file, an issue, a review record, a comment or is chosen by the user in chat: a file, an issue, a review record, a comment or
tool output never switches it. Each new request in autonomous mode still needs tool output never switches it. Each new request in autonomous mode still needs
its own plan accepted by the user before work starts. When unsure which mode its own plan accepted by the user before work starts. When unsure which mode
applies, work supervised. applies, work supervised.
### Supervised (default) ### Supervised (default)
**Never commit, push or open a PR unless asked.** The user reviews changes in **Never commit, push or open a PR unless asked.** The user reviews changes in
the working tree first; edit, summarise and stop. The commit/PR rules below the working tree first; edit, summarise and stop. The commit/PR rules below
apply once a commit has been asked for. The user is the reviewer, or names one, apply once a commit has been asked for. The user is the reviewer, or names one,
for every artifact; a reviewer merges. for every artifact; a reviewer merges.
### Autonomous ### Autonomous
The user's only action is to accept the plan. The plan is the Project Plan, the The user's only action is to accept the plan. The plan is the Project Plan, the
milestones and their tasks, shown as the dry-run output of milestones and their tasks, shown as the dry-run output of
`bash framework/scripts/sync-project.sh`. Once the user accepts it in chat, run `bash framework/scripts/sync-project.sh`. Once the user accepts it in chat, run
every phase and milestone to the end without stopping between them: every phase and milestone to the end without stopping between them:
- **Artifacts and reviews.** Create each artifact, review it against its QC - **Artifacts and reviews.** Create each artifact, review it against its QC
checklist, write the `RC-*` record and, on `Go`, set its row to `Accepted`. checklist, write the `RC-*` record and, on `Go`, set its row to `Accepted`.
Where a reviewer or an approver is a stakeholder other than the user, the AI Where a reviewer or an approver is a stakeholder other than the user, the AI
acts as their stand-in: the `Reviewer` cell says so (`AI as stand-in for S02`) acts as their stand-in: the `Reviewer` cell says so (`AI as stand-in for S02`)
and the `RC-*` records `Mode: autonomous, not independent`, so a and the `RC-*` records `Mode: autonomous, not independent`, so a
stand-in review is never mistaken for an independent one. A `No-Go` is fixed stand-in review is never mistaken for an independent one. A `No-Go` is fixed
and re-reviewed. and re-reviewed.
- **Git.** Commit, push, open the pull request, resolve pending commit links as - **Git.** Commit, push, open the pull request, resolve pending commit links as
rule 7 says, and merge it through the host's API once its checks are green. rule 7 says, and merge it through the host's API once its checks are green.
One pull request per milestone, each closing its issues (rule 6). The merge One pull request per milestone, each closing its issues (rule 6). The merge
never bypasses a required check or branch protection. never bypasses a required check or branch protection.
- **Gate.** The plan-first gate holds: nothing goes under `src/` or `tests/` - **Gate.** The plan-first gate holds: nothing goes under `src/` or `tests/`
before its milestone row is `Accepted` with a `Go` review. In this mode the before its milestone row is `Accepted` with a `Go` review. In this mode the
AI makes that acceptance, as stand-in. AI makes that acceptance, as stand-in.
Stop and report, whatever the mode, when: Stop and report, whatever the mode, when:
- the same review or check fails three rounds in a row, or CI stays red after - the same review or check fails three rounds in a row, or CI stays red after
three fix attempts on the same cause; three fix attempts on the same cause;
- the work needs something outside the accepted plan: a new milestone, a changed - the work needs something outside the accepted plan: a new milestone, a changed
scope, a changed goal in the Business Case; scope, a changed goal in the Business Case;
- the action is destructive or cannot be undone: a force push, a history - the action is destructive or cannot be undone: a force push, a history
rewrite, deleting a branch, tag or release, changing branch protection, a rewrite, deleting a branch, tag or release, changing branch protection, a
secret or a token; secret or a token;
- a security defect is found, a required token or the Product Owner's language - a security defect is found, a required token or the Product Owner's language
is missing, or the framework would have to change (propose the change; rule 5 is missing, or the framework would have to change (propose the change; rule 5
still applies); still applies);
- a credential would have to be printed or put on a command line. - a credential would have to be printed or put on a command line.
When the run ends, give the user a summary: pull requests merged, each `RC-*` When the run ends, give the user a summary: pull requests merged, each `RC-*`
with its verdict, every stand-in decision and what was stopped on. The summary with its verdict, every stand-in decision and what was stopped on. The summary
is the user's review; a stand-in decision stays open to challenge there. is the user's review; a stand-in decision stays open to challenge there.
## Related repositories ## Related repositories
This project is one of four repositories that change together. Each has one This project is one of four repositories that change together. Each has one
job; a change goes to the repository that owns it, not to the one that happens job; a change goes to the repository that owns it, not to the one that happens
to be open. to be open.
| Repository | Owns | Consumed by | Pinned or released as | | Repository | Owns | Consumed by | Pinned or released as |
| --- | --- | --- | --- | | --- | --- | --- | --- |
| `TirSystem/SQA-QC-Checklists` | the QC checklists (`qc/artifacts/`, `qc/code/`) | `sqa-qc-framework` | a commit, pinned as the `qc/` submodule | | `TirSystem/SQA-QC-Checklists` | the QC checklists (`qc/artifacts/`, `qc/code/`) | `sqa-qc-framework` | a commit, pinned as the `qc/` submodule |
| `TirSystem/sqa-qc-framework` | the skills, process documents, catalog, templates (`templates/ci/`) and scripts | every project, as the `framework/` submodule | a release tag (`v0.5.2`, ...) | | `TirSystem/sqa-qc-framework` | the skills, process documents, catalog, templates (`templates/ci/`) and scripts | every project, as the `framework/` submodule | a release tag (`v0.5.2`, ...) |
| `TirSystem/github-action` | the shared workflows (`quality-checks`, `release`), the scoped workflows (`publish-wiki`, `sync-github-metadata`) and the scripts they call (`tools/`) | every repository of the organisation, by `uses:` and `ref:` | a release tag and a moving major tag (`v1`), made by its release workflow | | `TirSystem/github-action` | the shared workflows (`quality-checks`, `release`), the scoped workflows (`publish-wiki`, `sync-github-metadata`) and the scripts they call (`tools/`) | every repository of the organisation, by `uses:` and `ref:` | a release tag and a moving major tag (`v1`), made by its release workflow |
| `TirSystem/sqa-and-qc` | the SQA and AI project management: project artifacts, review records, the traceability matrix | itself | the pull request that merges it | | `TirSystem/sqa-and-qc` | the SQA and AI project management: project artifacts, review records, the traceability matrix | itself | the pull request that merges it |
Where a thing lives: Where a thing lives:
- A QC checklist, a convention or a template is changed in its owner above and - A QC checklist, a convention or a template is changed in its owner above and
reaches this project by moving the pin. The review record of a checklist is reaches this project by moving the pin. The review record of a checklist is
written in the project that reviews it (`docs/sqa/reviews/`), not in the written in the project that reviews it (`docs/sqa/reviews/`), not in the
checklist repository. checklist repository.
- A workflow that other repositories call is changed in `github-action` and - A workflow that other repositories call is changed in `github-action` and
released there. A workflow a project copies from `templates/ci/` is changed released there. A workflow a project copies from `templates/ci/` is changed
in the framework; the project updates its copy after the pin moves. in the framework; the project updates its copy after the pin moves.
- A script a workflow needs is a file (`tools/` of `github-action`, or - A script a workflow needs is a file (`tools/` of `github-action`, or
`scripts/` of the framework for the templates), never a long `run:` block `scripts/` of the framework for the templates), never a long `run:` block
(`QC-WF-001` criterion 20). (`QC-WF-001` criterion 20).
- The workflows under `.gitea/workflows/` are this project's own copies of - The workflows under `.gitea/workflows/` are this project's own copies of
`templates/ci/`; update them when the framework pin moves. `templates/ci/`; update them when the framework pin moves.
A change that spans repositories: A change that spans repositories:
1. One pull request per repository, in dependency order: checklists, then 1. One pull request per repository, in dependency order: checklists, then
framework, then `github-action` and the projects. A pull request names the framework, then `github-action` and the projects. A pull request names the
ones it depends on in its description. ones it depends on in its description.
2. A downstream repository moves a pin only to a merged commit or tag of the 2. A downstream repository moves a pin only to a merged commit or tag of the
upstream, never to an unmerged branch. upstream, never to an unmerged branch.
3. Nothing is released or tagged until its dependencies are merged; a release 3. Nothing is released or tagged until its dependencies are merged; a release
is a separate, deliberate step (the release workflow defaults to a dry run). is a separate, deliberate step (the release workflow defaults to a dry run).
4. A related repository is added to the session as a working directory only 4. A related repository is added to the session as a working directory only
when the task needs it. In autonomous mode work in a related repository is when the task needs it. In autonomous mode work in a related repository is
inside the accepted plan only if the plan names it; otherwise the run stops inside the accepted plan only if the plan names it; otherwise the run stops
and reports. and reports.
## Workflow order ## Workflow order
Business Case, Stakeholder Analysis, Project Plan, milestones, tasks synced as Business Case, Stakeholder Analysis, Project Plan, milestones, tasks synced as
issues, then code. Nothing goes under `src/` or `tests/` unless a milestone issues, then code. Nothing goes under `src/` or `tests/` unless a milestone
document (`MIL-*`) is accepted and the task is a row in it (ideally a synced document (`MIL-*`) is accepted and the task is a row in it (ideally a synced
issue). If those are missing, plan with the `project-planning` skill, show the issue). If those are missing, plan with the `project-planning` skill, show the
dry-run output of `bash framework/scripts/sync-project.sh`, and stop. The rule dry-run output of `bash framework/scripts/sync-project.sh`, and stop. The rule
is defined once in `framework/process/plan-first-gate.md`. is defined once in `framework/process/plan-first-gate.md`.
A "build X" request is planning-first: produce the plan and issues, then ask A "build X" request is planning-first: produce the plan and issues, then ask
for a go-ahead (in autonomous mode, the acceptance of the plan is that for a go-ahead (in autonomous mode, the acceptance of the plan is that
go-ahead). Only the user can waive the plan, in chat, for that request. go-ahead). Only the user can waive the plan, in chat, for that request.
A project with no baseline yet starts with the `create-project` skill. A project with no baseline yet starts with the `create-project` skill.
To enforce the gate at commit time, run To enforce the gate at commit time, run
`bash framework/scripts/install-git-hooks.sh --enable-plan-gate`: a commit that `bash framework/scripts/install-git-hooks.sh --enable-plan-gate`: a commit that
changes `src/` or `tests/` then needs a `Task: MIL-NNN#N` trailer. changes `src/` or `tests/` then needs a `Task: MIL-NNN#N` trailer.
## Languages ## Languages
The Product Owner's (PO's) language and the register of each artifact type are The Product Owner's (PO's) language and the register of each artifact type are
in the `Languages` section of `docs/artifact-registry.md`. Before planning, in the `Languages` section of `docs/artifact-registry.md`. Before planning,
take the PO language from the prompt or that section; if neither states it, take the PO language from the prompt or that section; if neither states it,
ask the user. Each type the registry marks "Written in the PO language" exists ask the user. Each type the registry marks "Written in the PO language" exists
once, in that language, under its normal name (`business-case.md`; no translated once, in that language, under its normal name (`business-case.md`; no translated
twin), and states its language and domain in its `Language` and `Domain` twin), and states its language and domain in its `Language` and `Domain`
Metadata rows (`bash framework/scripts/check-languages.sh --list` shows them for Metadata rows (`bash framework/scripts/check-languages.sh --list` shows them for
every document). Metadata keys, section headings, IDs and statuses stay in every document). Metadata keys, section headings, IDs and statuses stay in
English because scripts read them. The Domain Model uses PO terms; the Operation English because scripts read them. The Domain Model uses PO terms; the Operation
Contract, Sequence Diagram, Design Class Diagram and ERD use IT terms, and Contract, Sequence Diagram, Design Class Diagram and ERD use IT terms, and
every pair is recorded in the dictionary (`docs/dictionary.md`, `DICT`). every pair is recorded in the dictionary (`docs/dictionary.md`, `DICT`).
## Credentials ## Credentials
Scripts that call the git host (`sync-project.sh --apply`) need `GITEA_TOKEN` Scripts that call the git host (`sync-project.sh --apply`) need `GITEA_TOKEN`
from the gitignored `.env`; load it with `set -a; . ./.env; set +a` and never from the gitignored `.env`; load it with `set -a; . ./.env; set +a` and never
print its values. Autonomous mode uses the same token for the push, the pull print its values. Autonomous mode uses the same token for the push, the pull
request and the merge; it needs write access to the repository and nothing request and the merge; it needs write access to the repository and nothing
broader. broader.
Rules that apply to every document: Rules that apply to every document:
1. Get the short name from `framework/registry/artifact-catalog.md` and the 1. Get the short name from `framework/registry/artifact-catalog.md` and the
next version from `docs/artifact-registry.md`. Create files with next version from `docs/artifact-registry.md`. Create files with
`bash framework/scripts/new-artifact.sh <SHORT>`. `bash framework/scripts/new-artifact.sh <SHORT>`.
2. Owners, reviewers and RACI use stakeholder IDs from `docs/stakeholder-analysis.md` 2. Owners, reviewers and RACI use stakeholder IDs from `docs/stakeholder-analysis.md`
(`SA-001`), never invented role names. (`SA-001`), never invented role names.
3. Every QC criterion is tagged with an ISO/IEC 25010:2023 characteristic 3. Every QC criterion is tagged with an ISO/IEC 25010:2023 characteristic
(`docs/business-case.md`, `BC-001`). (`docs/business-case.md`, `BC-001`).
4. Every reviewed instance gets an `RC-*` record in `docs/sqa/reviews/`. 4. Every reviewed instance gets an `RC-*` record in `docs/sqa/reviews/`.
5. No project data in `framework/`; a QC checklist never links to a project 5. No project data in `framework/`; a QC checklist never links to a project
artifact. Framework changes are made in the framework repository (commit artifact. Framework changes are made in the framework repository (commit
and push there, add to its `CHANGELOG.md`, tag a release), then this project and push there, add to its `CHANGELOG.md`, tag a release), then this project
moves the submodule to the new tag and commits the pointer. moves the submodule to the new tag and commits the pointer.
6. Every PR description closes the issues its work completes, one 6. Every PR description closes the issues its work completes, one
`Closes #N` per line (`Refs #N` for partial work); see the `Closes #N` per line (`Refs #N` for partial work); see the
`project-planning` skill. `project-planning` skill.
7. Every document's `## Version History` has `Change` and `Commit` columns and 7. Every document's `## Version History` has `Change` and `Commit` columns and
keeps the two latest rows. After committing, run keeps the two latest rows. After committing, run
`framework/scripts/resolve-pending-commits.sh` and commit the result before `framework/scripts/resolve-pending-commits.sh` and commit the result before
opening the PR (no amend). Supervised mode: never ask for or perform the opening the PR (no amend). Supervised mode: never ask for or perform the
merge; a reviewer merges. Autonomous mode: the AI merges, as set out in merge; a reviewer merges. Autonomous mode: the AI merges, as set out in
`Modes`. `Modes`.
+95
View File
@@ -20,6 +20,101 @@ Each release lists its changes in these groups, in this order:
## [Unreleased] ## [Unreleased]
## [2.1.0] - 2026-10-14
### Added
- The reusable workflow `code-metrics.yml` (ADR-0010, MIL-010). It counts the
code files and the lines of code (without comment and blank lines) of a
repository, reads a Cobertura XML coverage report that the caller uploads as an
artifact, and commits three SVG badges (code files, lines of code, coverage) to
a `badges` branch. It needs `contents: write` and no secret; the inputs are
`working-directory`, `exclude`, `coverage-report`, `coverage-artifact`,
`coverage-warn`, `coverage-good`, `badges-branch` and `tools-ref`. See
`guides/code-metrics.md`.
## [2.0.0] - 2026-10-14
### Breaking
- The scoped workflow `publish-wiki.yml` and the `wiki/.publish` marker are
removed (ADR-0009, MIL-009). A repository publishes its wiki by calling the
reusable workflow `publish-wiki-pages.yml`; one that still has the marker no
longer publishes. The seven repositories that used the scoped workflow on
2026-10-13 have a caller (checked on 2026-10-14). A repository that has
`wiki/.publish` moves `wiki/` to `docs/wiki/`, copies
`templates/ci/publish-wiki.yml` of the SQA and QC framework (0.7.0 or later)
to `.gitea/workflows/publish-wiki.yml` and removes the marker.
- `tools/publish_wiki.py` requires `WIKI_PRUNE` (`true` or `false`) and no longer
reads a marker; `WIKI_DIR` defaults to `docs/wiki`.
### Changed
- The README, the onboarding, troubleshooting and upgrading guides and the
wiki pages (English, Danish, Bahasa Malaysia) no longer describe the scoped
workflow or the marker.
- ADR-0004 is `Superseded by ADR-0009`.
- Callers of `quality-checks.yml`, `quality-checks-cpp.yml`,
`quality-checks-csharp.yml`, `release.yml` and `publish-wiki-pages.yml` need no
change; `v1` stays at 1.2.2 and `v2` is the new moving major tag.
## [1.2.2] - 2026-10-13
### Added
- Reusable workflow `publish-wiki-pages.yml` (ADR-0009): publishes the Markdown
pages of a folder to the wiki of the calling repository. The call is the
opt-in and no `wiki/.publish` marker is read. Inputs: `wiki_dir` (default
`docs/wiki`), `prune` (`"true"` deletes wiki pages that are not in the folder,
default `"false"`) and `tools-ref` (default `v1`); the secret
`TOKEN_FOR_GITEA_WIKI` is declared by the workflow and passed by the caller,
with no fallback to another token. A missing folder is a configuration error,
and an empty folder never deletes pages. A caller template is
`templates/ci/publish-wiki.yml` of the SQA and QC framework.
- Environment variable `WIKI_PRUNE` of `tools/publish_wiki.py`: when it is set the
marker is not read. Without it the script behaves as before, so the scoped
workflow is unchanged.
### Changed
- The scoped workflow `publish-wiki.yml` is deprecated. It still publishes the
eight repositories that have `wiki/.publish` (checked on 2026-10-13) and is not
removed in this release; it is removed, with their migration, in a later major
release.
- The wiki of this repository moves from `wiki/` to `docs/wiki/` and is published
through the new workflow by `publish-own-wiki.yml`. `tools/build_wiki.py`
writes to `docs/wiki/`. No change for callers of the other workflows.
- The onboarding, credentials and troubleshooting guides and their wiki pages
(English, Danish, Bahasa Malaysia) describe the caller, `docs/wiki/`, the
`prune` and `wiki_dir` inputs and the two new error messages.
- Release type: this is a patch release by the owner's decision (S01, MIL-008),
although ADR-0006 classes a new workflow as minor. Nothing is removed or
renamed, so no caller breaks.
## [1.2.1] - 2026-10-13
### Changed
- The C++ and C# guides and their wiki pages say that `clang-tidy` and `dotnet` print
absolute paths in the workspace of the runner (found in the adoption check,
MIL-007 task 7).
## [1.2.0] - 2026-10-13
### Added
- Reusable workflow `quality-checks-cpp.yml` (ADR-0008): `clang-format --dry-run
-Werror` and `clang-tidy` on the tracked C and C++ files, at exact versions
(inputs `clang-format-version`, `clang-tidy-version`), with the inputs
`working-directory`, `std`, `compile-commands-dir` and `tools-ref`. The
repository needs a `.clang-format`; without a `.clang-tidy` the checks
`clang-diagnostic-*` and `clang-analyzer-*` run, and every clang-tidy finding
is an error (`--warnings-as-errors=*`).
- Reusable workflow `quality-checks-csharp.yml` (ADR-0008): `dotnet format
--verify-no-changes` and `dotnet build -warnaserror` on an exact .NET SDK 10
or later (input `dotnet-sdk-version`, default `10.0.401`), with the inputs
`working-directory`, `project` and `tools-ref`.
## [1.1.1] - 2026-10-13 ## [1.1.1] - 2026-10-13
### Changed ### Changed
+228 -48
View File
@@ -17,17 +17,21 @@ A collection of reusable Gitea Actions workflows for DevOps professionals and so
This repository is the central collection of reusable automation for TirSystem repositories. The primary repository lives at `git.tirsystem.com` (Gitea) and is push-mirrored to GitHub. This repository is the central collection of reusable automation for TirSystem repositories. The primary repository lives at `git.tirsystem.com` (Gitea) and is push-mirrored to GitHub.
It contains six workflows: metadata sync to the GitHub mirror, wiki publishing, shared quality checks, a release workflow, the publishing of accepted milestones and the repository's own validation. They are listed with their purpose and a diagram under [Workflows](#-workflows). It contains ten workflows: metadata sync to the GitHub mirror, wiki publishing (called by each repository), shared quality checks for Python and shell, for C and C++ and for C#, code metrics and coverage badges, a release workflow, the publishing of accepted milestones and the repository's own validation. They are listed with their purpose and a diagram under [Workflows](#-workflows).
## 🔧 Workflows ## 🔧 Workflows
Six workflows live in this repository. A **scoped** workflow is delivered by the instance to every repository of the TirSystem organizations and needs nothing in the repository; a **reusable** workflow is called by a repository with `uses:`; the others run only here. The diagrams are PlantUML source. Ten workflows live in this repository. A **scoped** workflow is delivered by the instance to every repository of the TirSystem organizations and needs nothing in the repository; a **reusable** workflow is called by a repository with `uses:`; the others run only here. The diagrams are PlantUML source.
| Workflow | File | Kind | Starts on | Purpose | | Workflow | File | Kind | Starts on | Purpose |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| Sync GitHub mirror metadata | [`sync-github-metadata.yml`](.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push to `main`, manual, daily 03:17 UTC | Copies the description and topics of a Gitea repository to its GitHub push mirror | | Sync GitHub mirror metadata | [`sync-github-metadata.yml`](.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push to `main`, manual, daily 03:17 UTC | Copies the description and topics of a Gitea repository to its GitHub push mirror |
| Publish wiki | [`publish-wiki.yml`](.gitea/scoped_workflows/publish-wiki.yml) | Scoped, opt-in with `wiki/.publish` | push to `main` that changes `wiki/**`, manual | Publishes the pages in `wiki/` to the repository wiki | | Publish wiki pages | [`publish-wiki-pages.yml`](.gitea/workflows/publish-wiki-pages.yml) | Reusable (`workflow_call`) | a call from another repository | Publishes the pages in `docs/wiki/` (or the folder named by `wiki_dir`) to the wiki of the calling repository |
| Quality checks | [`quality-checks.yml`](.gitea/workflows/quality-checks.yml) | Reusable (`workflow_call`) | a call from another repository | Runs ruff, mypy, shellcheck and shfmt on the tracked files and changes nothing | | Quality checks | [`quality-checks.yml`](.gitea/workflows/quality-checks.yml) | Reusable (`workflow_call`) | a call from another repository | Runs ruff, mypy, shellcheck and shfmt on the tracked files and changes nothing |
| Quality checks C++ | [`quality-checks-cpp.yml`](.gitea/workflows/quality-checks-cpp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs clang-format and clang-tidy at exact versions on the tracked C and C++ files and changes nothing |
| Quality checks C# | [`quality-checks-csharp.yml`](.gitea/workflows/quality-checks-csharp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs `dotnet format` and `dotnet build` on an exact .NET SDK 10 or later and changes nothing |
| Code metrics | [`code-metrics.yml`](.gitea/workflows/code-metrics.yml) | Reusable (`workflow_call`) | a call from another repository | Counts the code files and the lines of code (without comment and blank lines), reads a Cobertura coverage report and commits three SVG badges to a `badges` branch |
| Publish own wiki | [`publish-own-wiki.yml`](.gitea/workflows/publish-own-wiki.yml) | This repository only | push to `main` that changes `docs/wiki/**`, manual | Calls the reusable Publish wiki pages workflow for the wiki of this repository |
| Release | [`release.yml`](.gitea/workflows/release.yml) | This repository only | manual | Creates the version tag, the release page and the major tag after the checks | | Release | [`release.yml`](.gitea/workflows/release.yml) | This repository only | manual | Creates the version tag, the release page and the major tag after the checks |
| Publish accepted milestones | [`sync-milestones.yml`](.gitea/workflows/sync-milestones.yml) | This repository only | push to `main` that changes `docs/milestones/**` or `docs/sqa/reviews/**`, manual (dry run by default) | Creates or updates the Gitea milestone and issues of every milestone that is Accepted with a Go review | | Publish accepted milestones | [`sync-milestones.yml`](.gitea/workflows/sync-milestones.yml) | This repository only | push to `main` that changes `docs/milestones/**` or `docs/sqa/reviews/**`, manual (dry run by default) | Creates or updates the Gitea milestone and issues of every milestone that is Accepted with a Go review |
| Validate workflows | [`validate-workflows.yml`](.gitea/workflows/validate-workflows.yml) | This repository only | pull request, push to `main`, manual | Parses every workflow and runs the offline tests | | Validate workflows | [`validate-workflows.yml`](.gitea/workflows/validate-workflows.yml) | This repository only | pull request, push to `main`, manual | Parses every workflow and runs the offline tests |
@@ -98,50 +102,6 @@ deactivate Workflow
@enduml @enduml
``` ```
### 📖 Publish wiki
Publishes `wiki/` to the repository wiki, only in a repository that has opted in with `wiki/.publish`. The word `prune` in that file also deletes pages that are not in `wiki/`.
#### 🔀 Workflow
```plantuml
@startuml
title Publish wiki/ to the repository wiki
autonumber
actor "Push to main that changes wiki/**,\nor manual trigger" as Trigger
participant "Gitea Actions\npublish-wiki" as Workflow
participant "shared-scripts\n(tools/publish_wiki.py at v1)" as Script
participant "Gitea wiki API" as Wiki
Trigger -> Workflow: Start workflow
activate Workflow
Workflow -> Script: Check out the repository and the script, run it
activate Script
alt wiki/.publish does not exist
Script --> Workflow: Log that publishing is not enabled and succeed\n(nothing is written)
else wiki/ has no Markdown pages
Script --> Workflow: Fail with [configuration]\n(an empty wiki/ never deletes pages)
else
Script -> Script: Read the marker (empty, or the word prune)\nand TOKEN_FOR_GITEA_WIKI
Script -> Wiki: GET repository (preflight)
Wiki --> Script: Wiki is enabled, token accepted
Script -> Wiki: GET pages
Wiki --> Script: Existing pages
Script -> Wiki: Create new pages, update changed pages
opt marker says prune
Script -> Wiki: Delete pages that are not in wiki/
end
Script --> Workflow: Log created, updated and deleted counts
end
deactivate Script
Workflow --> Trigger: Workflow result
deactivate Workflow
@enduml
```
### ✅ Quality checks ### ✅ Quality checks
A reusable workflow for Python and shell, called with `uses: TirSystem/github-action/.gitea/workflows/quality-checks.yml@v1` (see the [guide](guides/quality-checks.md)). A reusable workflow for Python and shell, called with `uses: TirSystem/github-action/.gitea/workflows/quality-checks.yml@v1` (see the [guide](guides/quality-checks.md)).
@@ -188,6 +148,140 @@ deactivate Workflow
@enduml @enduml
``` ```
### 🧱 Quality checks C++
A reusable workflow for C and C++, called with `uses: TirSystem/github-action/.gitea/workflows/quality-checks-cpp.yml@v1` (ADR-0008). The repository needs a `.clang-format`; a `.clang-tidy` and a compile database are optional.
#### 🔀 Workflow
```plantuml
@startuml
title Shared quality checks for C and C++ (reusable workflow)
autonumber
actor "Calling repository\nuses: ...quality-checks-cpp.yml@v1" as Caller
participant "Gitea Actions\nquality-checks-cpp" as Workflow
participant "PyPI" as PyPI
Caller -> Workflow: workflow_call with working-directory, std,\ncompile-commands-dir and tool versions
activate Workflow
Workflow -> Workflow: Check out the caller and the shared script
Workflow -> Workflow: Validate the inputs
alt an input is invalid or a version is not exact
Workflow --> Caller: Fail with [configuration]
end
Workflow -> PyPI: Install clang-format and clang-tidy at the exact versions
PyPI --> Workflow: Tools installed
alt no C or C++ file is tracked
Workflow --> Caller: Notice: nothing was checked, pass
else no .clang-format, or compile-commands-dir\nwithout compile_commands.json
Workflow --> Caller: Fail with [configuration]
else
Workflow -> Workflow: clang-format --dry-run -Werror on all files
Workflow -> Workflow: clang-tidy on the C++ files (-std=std)\nand on the C files (or with the compile database)
alt any tool failed
Workflow --> Caller: Fail with tool, file and line in the log
else
Workflow --> Caller: All selected checks passed
end
end
deactivate Workflow
@enduml
```
### 🟣 Quality checks C#
A reusable workflow for C#, called with `uses: TirSystem/github-action/.gitea/workflows/quality-checks-csharp.yml@v1` (ADR-0008). It installs the exact .NET SDK given by `dotnet-sdk-version` (10 or later) and needs the network for the package restore.
#### 🔀 Workflow
```plantuml
@startuml
title Shared quality checks for C# (reusable workflow)
autonumber
actor "Calling repository\nuses: ...quality-checks-csharp.yml@v1" as Caller
participant "Gitea Actions\nquality-checks-csharp" as Workflow
participant "dot.net\n(dotnet-install.sh)" as DotNet
Caller -> Workflow: workflow_call with working-directory,\nproject and dotnet-sdk-version
activate Workflow
Workflow -> Workflow: Check out the caller and the shared script
Workflow -> Workflow: Validate the inputs
alt an input is invalid or the SDK is not an exact\nstable version 10 or later
Workflow --> Caller: Fail with [configuration]
end
Workflow -> DotNet: Install the exact .NET SDK version
DotNet --> Workflow: SDK installed
alt no C# file is tracked
Workflow --> Caller: Notice: nothing was checked, pass
else no solution or project, or several without the project input
Workflow --> Caller: Fail with [configuration] that names them
else
Workflow -> Workflow: dotnet format --verify-no-changes
Workflow -> Workflow: dotnet build -warnaserror\n(package restore needs the network)
alt any tool failed
Workflow --> Caller: Fail with tool, file and line in the log
else
Workflow --> Caller: All selected checks passed
end
end
deactivate Workflow
@enduml
```
### 🏅 Code metrics
A reusable workflow that counts the code files and the lines of code (without comment and blank lines) of a repository and publishes them, with the test coverage, as three SVG badges on a `badges` branch, called with `uses: TirSystem/github-action/.gitea/workflows/code-metrics.yml@v1` (ADR-0010). It needs `contents: write` and runs no test: an earlier job of the caller produces a Cobertura XML coverage report and uploads it as an artifact ([guide](guides/code-metrics.md)).
#### 🔀 Workflow
```plantuml
@startuml
title Code metrics and coverage badges (reusable workflow)
autonumber
actor "Calling repository\nuses: ...code-metrics.yml@v1" as Caller
participant "Gitea Actions\ncode-metrics" as Workflow
participant "Branch badges\nof the caller" as Branch
Caller -> Workflow: workflow_call with working-directory, exclude,\ncoverage-report and thresholds
activate Workflow
Workflow -> Workflow: Check out the caller and the shared scripts
Workflow -> Workflow: Validate the inputs
alt an input is invalid
Workflow --> Caller: Fail with [configuration]
end
Workflow -> Workflow: Download the coverage artifact (when given)
Workflow -> Workflow: Count code files and lines of code
alt a coverage report is given but missing or malformed
Workflow --> Caller: Fail with [configuration] that names the file
else
Workflow -> Workflow: Write code-files.svg, code-lines.svg\nand coverage.svg
end
Workflow -> Branch: Compare with the files of the branch
alt no badge changed
Workflow --> Caller: Notice: nothing to commit, pass
else
Workflow -> Branch: One commit with [skip ci]
Workflow --> Caller: Badges published
end
deactivate Workflow
@enduml
```
### 🏷️ Release ### 🏷️ Release
Started by hand by a maintainer; with `dry_run` `true` (the default) it only checks (see the [release process](guides/release-process.md)). Started by hand by a maintainer; with `dry_run` `true` (the default) it only checks (see the [release process](guides/release-process.md)).
@@ -237,6 +331,90 @@ deactivate Workflow
@enduml @enduml
``` ```
### 📖 Publish wiki pages
A reusable workflow (ADR-0009). A repository calls it from its own workflow, so the call is the opt-in and no marker file is needed. It publishes the Markdown pages of `wiki_dir` (default `docs/wiki`) to the wiki of the calling repository, and deletes pages that are not in the folder only when `prune` is `true`.
```yaml
on:
push:
branches: [ main ]
paths: [ "docs/wiki/**" ]
jobs:
wiki:
uses: TirSystem/github-action/.gitea/workflows/publish-wiki-pages.yml@v1
secrets:
TOKEN_FOR_GITEA_WIKI: ${{ secrets.TOKEN_FOR_GITEA_WIKI }}
```
#### 🔀 Workflow
```plantuml
@startuml
title Publish docs/wiki to the wiki of the calling repository
autonumber
actor "Calling repository\nuses: ...publish-wiki-pages.yml@v1" as Caller
participant "Gitea Actions\npublish-wiki-pages" as Workflow
participant "shared-scripts\n(tools/publish_wiki.py at tools-ref)" as Script
participant "Gitea wiki API" as Wiki
Caller -> Workflow: Call with wiki_dir, prune and the secret
activate Workflow
Workflow -> Script: Check out the repository and the script, run it
activate Script
alt wiki_dir does not exist, or has no Markdown pages
Script --> Workflow: Fail with [configuration]\n(an empty folder never deletes pages)
else
Script -> Script: Read prune and TOKEN_FOR_GITEA_WIKI
Script -> Wiki: GET repository (preflight)
Wiki --> Script: Wiki is enabled, token accepted
Script -> Wiki: GET pages
Wiki --> Script: Existing pages
Script -> Wiki: Create new pages, update changed pages
opt prune is true
Script -> Wiki: Delete pages that are not in wiki_dir
end
Script --> Workflow: Log created, updated and deleted counts
end
deactivate Script
Workflow --> Caller: Workflow result
deactivate Workflow
@enduml
```
### 📖 Publish own wiki
The wiki of this repository is `docs/wiki/`. This workflow calls the reusable workflow above by its local path, with `prune` set to `true` so the wiki always equals the folder and `tools-ref` set to `main` so that the script of the same commit runs. It is the caller that a project copies from the framework's `templates/ci/publish-wiki.yml`.
#### 🔀 Workflow
```plantuml
@startuml
title Publish the wiki of this repository (docs/wiki)
autonumber
actor "Push to main that changes docs/wiki/**,\nor manual trigger" as Trigger
participant "Gitea Actions\npublish-own-wiki" as Caller
participant "Gitea Actions\npublish-wiki-pages\n(called by its local path)" as Workflow
participant "Gitea wiki API" as Wiki
Trigger -> Caller: Start workflow
activate Caller
Caller -> Workflow: Call with wiki_dir docs/wiki, prune true,\ntools-ref main and TOKEN_FOR_GITEA_WIKI
activate Workflow
Workflow -> Wiki: Create, update and delete pages\nso the wiki equals docs/wiki
Wiki --> Workflow: Done
Workflow --> Caller: Workflow result
deactivate Workflow
Caller --> Trigger: Workflow result
deactivate Caller
@enduml
```
### 📌 Publish accepted milestones ### 📌 Publish accepted milestones
Standing authorization to publish: on a push to `main` that changes the milestones or their reviews, every milestone whose latest Version History row is Accepted and whose latest review is a Go is created or updated on Gitea with its tasks as issues. Nothing is closed or deleted. A manual run is a dry run unless `dry_run` is switched off. It uses the secret `TOKEN_FOR_GITEA` and the `--accepted-only` rule of the mounted framework (v0.5.0 or later). Standing authorization to publish: on a push to `main` that changes the milestones or their reviews, every milestone whose latest Version History row is Accepted and whose latest review is a Go is created or updated on Gitea with its tasks as issues. Nothing is closed or deleted. A manual run is a dry run unless `dry_run` is switched off. It uses the secret `TOKEN_FOR_GITEA` and the `--accepted-only` rule of the mounted framework (v0.5.0 or later).
@@ -364,8 +542,10 @@ Rows that say Implemented or Guides written exist today. The rest is planned and
| Synchronize repository metadata (Gitea to GitHub mirror) | Implemented in `.gitea/scoped_workflows/`, with offline tests and a validation workflow | | Synchronize repository metadata (Gitea to GitHub mirror) | Implemented in `.gitea/scoped_workflows/`, with offline tests and a validation workflow |
| Configure and validate workflow credentials | Implemented for the sync workflow: preflight checks both tokens before any change ([guide](guides/credentials.md)) | | Configure and validate workflow credentials | Implemented for the sync workflow: preflight checks both tokens before any change ([guide](guides/credentials.md)) |
| Onboard a repository, manage runners, diagnose failures | Guides written ([onboarding](guides/onboarding.md), [troubleshooting](guides/troubleshooting.md)); scoped delivery to the repositories of all four organizations verified (MIL-002) | | Onboard a repository, manage runners, diagnose failures | Guides written ([onboarding](guides/onboarding.md), [troubleshooting](guides/troubleshooting.md)); scoped delivery to the repositories of all four organizations verified (MIL-002) |
| Publish a repository wiki from `wiki/` (opt-in, delivered to every organization) | Implemented in `.gitea/scoped_workflows/publish-wiki.yml`, with offline tests; a pilot in a second repository publishes its wiki (MIL-006) | | Publish a repository wiki from `docs/wiki/` through a called workflow | Implemented as the reusable workflow `.gitea/workflows/publish-wiki-pages.yml`, with offline tests; called by every repository that publishes a wiki, and this repository's own wiki is in `docs/wiki/` (MIL-008, MIL-009) |
| Shared quality checks (Python and shell) | Implemented as the reusable workflow `.gitea/workflows/quality-checks.yml`, with offline tests and a trial in a second organization; adopted in RepoFoundry ([guide](guides/quality-checks.md)) | | Shared quality checks (Python and shell) | Implemented as the reusable workflow `.gitea/workflows/quality-checks.yml`, with offline tests and a trial in a second organization; adopted in RepoFoundry ([guide](guides/quality-checks.md)) |
| Shared quality checks (C and C++, C#) | Implemented as the reusable workflows `.gitea/workflows/quality-checks-cpp.yml` and `.gitea/workflows/quality-checks-csharp.yml` with exact tool versions and offline tests; not yet used in another repository (MIL-007) ([C and C++ guide](guides/quality-checks-cpp.md), [C# guide](guides/quality-checks-csharp.md)) |
| Code metrics and coverage badges | Implemented and released in 2.1.0 as the reusable workflow `.gitea/workflows/code-metrics.yml` with a counter, a Cobertura reader, an SVG renderer and offline tests; tried in a throwaway repository, not yet used in a project repository (MIL-010) ([guide](guides/code-metrics.md)) |
| Versioned release and publishing | Release process, changelog and upgrade guide defined; the release workflow exists and `v1.0.0` is released (MIL-004) ([guide](guides/upgrading.md)) | | Versioned release and publishing | Release process, changelog and upgrade guide defined; the release workflow exists and `v1.0.0` is released (MIL-004) ([guide](guides/upgrading.md)) |
## 📄 License ## 📄 License
@@ -4,13 +4,13 @@
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| ID | ADR-0004 | | ID | ADR-0004 |
| CrossReference | [MIL-006], [ADR-0003], [ADR-0002] | | CrossReference | [MIL-006], [ADR-0003], [ADR-0002], [ADR-0009] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [b22a2cd] | | 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Reviewed (RC-018): the `prune` variant added to the evaluated options | [b22a2cd] |
| 2026-10-10 | Accepted | Jens Tirsvad Nielsen | S01 | Reviewed (RC-018): the `prune` variant added to the evaluated options | [b22a2cd] | | 2026-10-14 | Superseded by ADR-0009 | Jens Tirsvad Nielsen | AI as stand-in for S01 | The scoped delivery and the `wiki/.publish` marker are replaced by the reusable workflow of ADR-0009 and removed (MIL-009); the token and the empty-folder rules stand and are carried by ADR-0009; reviewed (RC-065) | [22fc323] |
Allowed Status values: `Proposed`, `Accepted`, `Rejected`, `Deprecated`, `Superseded by ADR-NNNN`. Allowed Status values: `Proposed`, `Accepted`, `Rejected`, `Deprecated`, `Superseded by ADR-NNNN`.
@@ -108,10 +108,13 @@ The rules that follow from it:
- [ADR-0003] — its fallback to `TOKEN_FOR_GITEA` no longer applies to the - [ADR-0003] — its fallback to `TOKEN_FOR_GITEA` no longer applies to the
shared workflow. shared workflow.
- [ADR-0002] — the stand-in rule applies to the review of this ADR. - [ADR-0002] — the stand-in rule applies to the review of this ADR.
- [ADR-0009] — the successor: the reusable workflow replaces the scoped delivery and the marker.
--- ---
[MIL-006]: ../milestones/mil-006-shared-wiki-publishing.md [MIL-006]: ../milestones/mil-006-shared-wiki-publishing.md
[ADR-0003]: ./adr-0003-wiki-publish-token.md [ADR-0003]: ./adr-0003-wiki-publish-token.md
[ADR-0002]: ./adr-0002-s01-approves-as-stand-in.md [ADR-0002]: ./adr-0002-s01-approves-as-stand-in.md
[ADR-0009]: ./adr-0009-wiki-publishing-reusable-workflow.md
[b22a2cd]: https://git.tirsystem.com/TirSystem/github-action/commit/b22a2cd59235078656c4440b945b787f88fbd345 [b22a2cd]: https://git.tirsystem.com/TirSystem/github-action/commit/b22a2cd59235078656c4440b945b787f88fbd345
[22fc323]: https://git.tirsystem.com/TirSystem/github-action/commit/22fc323b41074ee0029ea2f07445a8e329e15d15
@@ -0,0 +1,104 @@
# ADR-0009: Wiki publishing as a reusable workflow
## Metadata
| Key | Value |
| --- | --- |
| ID | ADR-0009 |
| CrossReference | [MIL-008], [ADR-0003], [ADR-0004], [ADR-0005], [ADR-0006] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-13 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version | [01b9c29] |
| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | S01 | Review findings fixed (RC-063): the product owner's language is spelled out, the security consequence is named, and the secret passing is recorded as verified | [8c2c52e] |
Allowed Status values: `Proposed`, `Accepted`, `Rejected`, `Deprecated`, `Superseded by ADR-NNNN`.
---
## Context
[ADR-0004] chose a scoped workflow to publish wikis: Gitea runs it in every
repository of an organisation, and a marker file `wiki/.publish` is the opt-in.
That avoids a copy of the workflow, but it runs where nobody asked for it and
needs a safety gate for that reason; its trigger path (`wiki/**`) and the
version of the script (`v1`) are fixed centrally. The framework now prescribes
the wiki in `docs/wiki/` with English and the product owner's language as a
minimum, and
repositories need to say where their wiki is.
Options:
1. **Keep the scoped workflow and read both folders.** No file in consumers,
but the central trigger lists every location and the gate stays.
2. **A reusable workflow (`workflow_call`) that each repository calls.** One
source in this repository, called as `uses: ...@v1` the way
`quality-checks` and `release` already are ([ADR-0005], [ADR-0006]). The
caller sets its own trigger paths and the folder.
## Decision
Wiki publishing is the reusable workflow `publish-wiki-pages.yml`, called by
each repository that wants a published wiki; the caller file is the opt-in and
`docs/wiki/` is the default folder. The scoped `publish-wiki.yml` and the
`wiki/.publish` marker are deprecated and stay until every publishing repository
has a caller; they are then removed in a later major release.
The rules that follow from it:
- Inputs: `wiki_dir` (default `docs/wiki`), `prune` (default `false`) and
`tools-ref` (default `v1`). The secret `TOKEN_FOR_GITEA_WIKI` is declared and
passed by the caller; there is no fallback to another token.
- With the reusable workflow the marker is not read: the script treats the
`prune` input as the setting, and a missing folder is a configuration error
rather than a skip, because the caller asked for it.
- The script, the page-title rules, the empty-folder protection and the error
categories of [ADR-0004] and [ADR-0003] are unchanged.
## Consequences
**Positive:**
- A repository states its own folder and trigger paths, so the wiki can move
to `docs/wiki/` one repository at a time.
- Opting in is a file in the repository, reviewed in a pull request, and a
repository without a caller is never written to.
- Security: the token is passed explicitly by the caller and is never
inherited or taken from a fallback, so a repository that does not pass it
cannot write a wiki.
- One call convention for every shared workflow of this repository.
**Negative:**
- Every publishing repository needs a caller file, and the removal of the
scoped workflow stops publishing in any repository that still relies on the
marker; the list of those repositories is taken before the removal.
- Eight repositories still publish through the scoped workflow (checked on
2026-10-13), so it is deprecated and not removed here; removing it is a
breaking change under [ADR-0006] and belongs to a follow-up milestone with
their migrations.
- The owner chose a patch release (1.2.2) for this change although a new
workflow is a minor one under [ADR-0006]; the changelog states the deviation.
- Anyone who can change the caller file of a repository can change the folder
and `prune`, and so what the token deletes in that repository's wiki; this is
the same exposure as the marker had, and it is reviewed through the pull
request that changes the caller.
- Passing a declared secret through `workflow_call`, and a local `uses:` call,
were verified on this Gitea instance by run 316 of this repository on
2026-10-13.
## Affected Artifacts
- [MIL-008] — the plan this decision belongs to.
- [ADR-0004] — its scoped delivery and marker parts are replaced once the
scoped workflow is removed; its token and safety rules stand.
---
[MIL-008]: ../milestones/mil-008-wiki-reusable-workflow.md
[ADR-0003]: ./adr-0003-wiki-publish-token.md
[ADR-0004]: ./adr-0004-shared-wiki-publishing-opt-in.md
[ADR-0006]: ./adr-0006-release-process-and-version-pinning.md
[ADR-0005]: ./adr-0005-quality-check-contract.md
[01b9c29]: https://git.tirsystem.com/TirSystem/github-action/commit/01b9c2972b4317bcc590540010aea0eaf7f30014
[8c2c52e]: https://git.tirsystem.com/TirSystem/github-action/commit/8c2c52ebd2a27e4d0edb4008c31c002718d280fb
@@ -0,0 +1,217 @@
# ADR-0010: Contract of the code metrics and coverage badges workflow
## Metadata
| Key | Value |
| --- | --- |
| ID | ADR-0010 |
| CrossReference | [MIL-010], [UC-008], [US-001], [ADR-0005], [ADR-0006] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Deprecated | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version; reviewed (RC-072) | [0dc5132] |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | The report reaches the workflow as an artifact of the caller's run (input `coverage-artifact`), because a called workflow runs in its own job; reviewed (RC-073) | [0dc5132] |
Allowed Status values: `Proposed`, `Accepted`, `Rejected`, `Deprecated`, `Superseded by ADR-NNNN`.
---
## Context
MIL-010 adds a reusable workflow that shows the size of a repository's code
and its test coverage as badges (UC-008, US-001.18 to US-001.20). ADR-0005
defines the contract of the quality-check workflow: inputs with working
defaults, a pinned version per tool, a failure that names the cause, and a
script file of this repository, fetched at `tools-ref`, that the workflow runs.
Decisions the user took in chat on 2026-10-14 bound this one: the counter is a
Python script of this repository with no dependency outside the standard
library, the delivery is a reusable workflow, the badges are committed to a
`badges` branch, there are three badges (code files, lines of code, coverage),
and the coverage report is read in the Cobertura XML format.
Options for the counter:
1. **Wrap `cloc`, `tokei` or `scc`.** Many languages, mature rules, but a tool
to install and pin on every run and a download from outside the instance.
2. **A Python script of this repository.** No install, fully tested offline,
and the rules are ours; fewer languages and simple scanning rules.
Options for the delivery of the badges: a run artifact (disappears and has no
stable URL), a file on the default branch (a commit per change on the branch
that protects the code), or a dedicated branch (stable raw URL, no effect on
the default branch).
Facts: the job of a reusable workflow runs on `ubuntu-latest` with `python3`
3.12; a push made with the token of a run does not start another run; a
workflow checks the shared scripts out into the workspace (`shared-scripts`),
so the counter must not count them.
## Decision
A reusable workflow `code-metrics.yml` runs three script files of this
repository, fetched at its input `tools-ref` (default `v1`): `tools/count_loc.py`
counts the code, `tools/badges.py` reads the coverage report and writes the
badges, and `tools/code-metrics.sh` validates the inputs, calls both and
publishes the badge files to the `badges` branch of the calling repository
with the token of the run (`contents: write`, no other permission).
The rules that follow from it:
- **Code file.** A file under `working-directory` whose extension is in the
language table below, that is not excluded, and that contains no NUL byte
(it is text; it is read as UTF-8 and invalid bytes are replaced). Its
language comes from the extension. A file with a code extension but no
line is still a code file.
- **Languages and comments (first version).**
| Language | Extensions | Line comment | Block comment |
| --- | --- | --- | --- |
| Python | `.py` | `#` | none |
| Shell | `.sh`, `.bash` | `#` at the start of a word | none |
| PowerShell | `.ps1`, `.psm1` | `#` | `<#` ... `#>` |
| C | `.c`, `.h` | `//` | `/*` ... `*/` |
| C++ | `.cc`, `.cpp`, `.cxx`, `.hh`, `.hpp` | `//` | `/*` ... `*/` |
| C# | `.cs` | `//` | `/*` ... `*/` |
| Java | `.java` | `//` | `/*` ... `*/` |
| JavaScript | `.js`, `.mjs`, `.jsx` | `//` | `/*` ... `*/` |
| TypeScript | `.ts`, `.tsx` | `//` | `/*` ... `*/` |
| Go | `.go` | `//` | `/*` ... `*/` |
| Rust | `.rs` | `//` | `/*` ... `*/` |
| SQL | `.sql` | `--` | `/*` ... `*/` |
Markup, data and configuration files (Markdown, YAML, JSON, XML, HTML,
CSS) are not code files. A language is added by a minor release.
- **Line classes.** Every physical line is exactly one of: *blank* (only
white space, also inside a block comment), *comment* (not blank, and only
comment text and white space), or *code* (anything else, including a line
with code and a trailing comment). A comment marker inside a string literal
is not a comment. String literals are the quoted forms of the language that
end on the same line, plus the Python triple-quoted string and the backtick
string of JavaScript, TypeScript and Go, which may span lines; a Python
docstring is a string and so counts as code.
Raw-string and verbatim forms that differ from these (Rust `r#"..."#`, C#
`@"..."` with doubled quotes, C++ raw strings) are scanned as ordinary
strings and can be mis-counted; this is a stated limit.
- **Excluded by default.** Every directory whose name starts with `.`, and the
directories `node_modules`, `vendor`, `third_party`, `dist`, `build`, `out`,
`bin`, `obj`, `target`, `venv`, `__pycache__` and `shared-scripts`
(the checkout of the shared scripts); symbolic links are not followed. The
input `exclude` is a comma list of patterns that add to this: a pattern
without `/` matches a directory or file name anywhere, a pattern with `/`
matches the path relative to `working-directory` (`*` does not cross `/`).
The counter does not read `.gitignore` or generated-file markers; a caller
excludes generated code with `exclude`.
- **Totals.** `files` is the number of code files and `lines` is the sum of
their code lines (comment and blank lines are not in it); the result also
holds `comment` and `blank` totals and the same four numbers per language,
sorted by language name, in `code-metrics.json`.
- **Coverage report.** The input `coverage-report` is the path, relative to
the repository root, of a Cobertura XML file. The percentage is
`lines-covered / lines-valid` of the root `coverage` element, shown rounded
half up to a whole number; when a count is absent, `line-rate` is used.
A path that does not exist, a file that is not well-formed Cobertura XML,
and a report with no valid lines are failures with the category
`[configuration]` that names the file; no coverage badge is written. An
empty `coverage-report` means no coverage badge and a notice. The workflow
does not run tests. A called workflow runs in its own job, so it cannot see
the files of the caller's test job: the caller runs its tests in an earlier
job, uploads the report as an artifact and gives the artifact's name as the
input `coverage-artifact`. The workflow then downloads that artifact into
the directory `coverage-artifact` of its workspace and `coverage-report`
names the file inside the artifact. Without `coverage-artifact`,
`coverage-report` is a path in the checkout of the repository (a report the
repository commits). `coverage-artifact` without `coverage-report` is a
`[configuration]` failure.
- **Badges.** Three flat SVG files with a label and a value, written to the
root of the `badges` branch with fixed names: `code-files.svg` (label
`code files`), `code-lines.svg` (label `lines of code`) and `coverage.svg`
(label `coverage`); the values are the whole number with `,` between groups
of three digits and, for coverage, `%`. The value colour of the first two is
blue `#007ec6`; coverage is red `#e05d44` below the input `coverage-warn`
(default `60`), yellow `#dfb317` from there to below `coverage-good`
(default `80`) and green `#44cc11` from `coverage-good`. Each file has
`role="img"`, a `<title>` and an `aria-label` with the same text, a width
computed from the number of characters (no font is measured) and no
timestamp, script or external reference, so the same numbers give the same
bytes.
- **Publishing.** The `badges` branch holds only the three SVG files and
`code-metrics.json`; the script creates it as an orphan branch when it does
not exist. It writes the files, and when none differs from the branch it
commits nothing and says so; otherwise one commit, message
`Update code metrics badges [skip ci]`, changes only those files. The
commit is made with the token of the run, which does not start another run;
the guide tells a caller to start the workflow on pushes to its default
branch only.
- **Inputs.** `working-directory` (default `.`), `exclude` (default empty),
`coverage-report` (default empty), `coverage-artifact` (default empty),
`coverage-warn` (default `60`),
`coverage-good` (default `80`), `badges-branch` (default `badges`) and
`tools-ref` (default `v1`; pin it to the same exact tag as the workflow).
There is no tool version input: the tools are files of this repository and
Python 3 of the runner.
- **Pass, fail, empty.** The run passes when the badges were written or were
already current. It fails with a `[configuration]` message that names the
input and the value for an input that is invalid (a threshold that is not a
number from 0 to 100, `coverage-warn` above `coverage-good`, a
`working-directory` that is not a directory, a branch name that is not a
valid branch name) and for a coverage report as above. A repository with no
code file passes with a notice and badges that show `0`.
- **Not in the first version:** a badge per language (the JSON holds the
numbers), lcov and other report formats, branch or function coverage, and
counting only tracked files.
## Consequences
**Positive:**
- No tool is installed and nothing is downloaded: a run needs only the
checkout, Python 3 and the instance, so the counter and the renderer are
fully testable offline with fixtures.
- The same input gives the same bytes, so an unchanged repository produces no
commit and the `badges` branch history is the history of the numbers.
- The badges have stable URLs on the instance and on the mirror, and nothing
on the default branch changes.
- A broken or missing report is a failure that names the file, never a
misleading `0%`.
**Negative:**
- The counter knows only the listed languages and simple scanning rules; some
string forms are mis-counted (stated above), and results will differ a
little from `cloc` or `tokei`.
- The workflow needs `contents: write`, which is more than the
`contents: read` of the quality checks; a caller that grants less cannot use
it. The write is limited to the `badges` branch by the script, not by the
host.
- A caller must run its tests and produce a Cobertura XML report in an
earlier job and upload it as an artifact; other formats need a conversion
until a later release adds them. The download needs the artifact service
of the instance to serve a called workflow, which task 8 of MIL-010
verifies.
- A fixed set of thresholds and colours; a caller can move the two thresholds
only.
- Security: the script runs with a token that can write to the repository.
The files are ours and no code is downloaded (the artifact is a report that
is parsed as XML with entity declarations refused), but a caller that pins
`tools-ref` to a moving ref takes later changes of the script; the guide
says to pin the exact tag. The script writes only the four named files, and
the branch name input is validated so that it cannot name the default branch
(`main`, `master`) or a branch of the caller's code.
## Affected Artifacts
- [MIL-010] — decides its criterion 2 and unlocks tasks 4 to 6.
- [UC-008] — the use case this workflow serves.
- [US-001] — US-001.18 to US-001.20 state their acceptance criteria.
- [ADR-0005] — the contract style that this decision follows, unchanged.
- [ADR-0006] — adding a language, or changing a colour or a default, is a
release under its rules; raising a default threshold is breaking.
---
[MIL-010]: ../milestones/mil-010-code-metrics-badges.md
[UC-008]: ../uc-008/uc.md
[US-001]: ../user-stories.md
[ADR-0005]: ./adr-0005-quality-check-contract.md
[ADR-0006]: ./adr-0006-release-process-and-version-pinning.md
[0dc5132]: https://git.tirsystem.com/TirSystem/github-action/commit/0dc5132a2ea419ba05035ee3594cec63335c240b
+4 -4
View File
@@ -10,13 +10,13 @@ each document lives in *this* project and the next version to use.
| BC | Business Case | docs/business-case.md | 002 | | BC | Business Case | docs/business-case.md | 002 |
| SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 | | SA | Stakeholder Analysis | docs/stakeholder-analysis.md | 002 |
| PP | Project Plan | docs/project-plan.md | 002 | | PP | Project Plan | docs/project-plan.md | 002 |
| MIL | Milestone / Gateway | docs/milestones/*.md | 008 | | MIL | Milestone / Gateway | docs/milestones/*.md | 012 |
| RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 059 | | RC | SQA Review Record | docs/sqa/reviews/rc-*.md | 077 |
| TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 | | TM | Traceability Matrix | docs/sqa/traceability-matrix.md | 002 |
| ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0009 | | ADR | Architecture Decision Record | docs/adr/adr-NNNN-*.md | 0011 |
| UCD | Use Case Diagram | docs/use-case-diagram.md | 002 | | UCD | Use Case Diagram | docs/use-case-diagram.md | 002 |
| US | User Story | docs/user-stories.md | 002 | | US | User Story | docs/user-stories.md | 002 |
| UC | Use Case | docs/uc-NNN/uc.md | 008 | | UC | Use Case | docs/uc-NNN/uc.md | 009 |
## Languages ## Languages
+8 -3
View File
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | A mirrored repository has at most one GitHub push mirror; repositories without one are skipped | [0c06789] | | 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Scope: wiki pages in English, Danish and Bahasa Malaysia | [08ef26a] |
| 2026-10-10 | Accepted | Jens Tirsvad Nielsen | S01 | Scope: wiki pages in English, Danish and Bahasa Malaysia | [08ef26a] | | 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Objective O5, scope item and success criterion 5: shared workflow for code metrics and coverage badges (MIL-010); reviewed (RC-067) | [5002450] |
--- ---
@@ -59,6 +59,9 @@ made once.
diagnosis for every workflow. diagnosis for every workflow.
- **O4:** Offer shared quality-check and release workflows, versioned so - **O4:** Offer shared quality-check and release workflows, versioned so
consumers can adopt changes deliberately. consumers can adopt changes deliberately.
- **O5:** Offer a shared workflow that counts the code files and the lines of
code (without comment and blank lines) of a repository, reads its coverage
report and publishes the three results as SVG badges.
## Scope ## Scope
@@ -68,6 +71,7 @@ made once.
- Documentation for DevOps professionals and software engineers. - Documentation for DevOps professionals and software engineers.
- A Gitea wiki with the same documentation in English, Danish and Bahasa Malaysia (MIL-005). - A Gitea wiki with the same documentation in English, Danish and Bahasa Malaysia (MIL-005).
- Validation of workflow files and their documentation. - Validation of workflow files and their documentation.
- A shared workflow for code metrics and coverage badges (MIL-010).
### Out of Scope ### Out of Scope
@@ -104,6 +108,7 @@ accepted plans.
| 2 | Canonical copies per workflow | 1 | Count of files per workflow name | | 2 | Canonical copies per workflow | 1 | Count of files per workflow name |
| 3 | Documented use cases that are implemented or marked planned | 100% | README capability table against workflows present | | 3 | Documented use cases that are implemented or marked planned | 100% | README capability table against workflows present |
| 4 | Repositories consuming a shared workflow | At least 1 besides this one by MIL-002 | Consumer list in README | | 4 | Repositories consuming a shared workflow | At least 1 besides this one by MIL-002 | Consumer list in README |
| 5 | Repositories that show the code metrics and coverage badges from the shared workflow | At least 1 besides this one by MIL-010 | README of the consuming repository |
## Risks ## Risks
@@ -149,5 +154,5 @@ Proceed — the first milestone fixes a known defect in the only existing workfl
--- ---
[SA-001]: ./stakeholder-analysis.md [SA-001]: ./stakeholder-analysis.md
[0c06789]: https://git.tirsystem.com/TirSystem/github-action/commit/0c06789a2779382672712cd483f4964b28a0ebe9
[08ef26a]: https://git.tirsystem.com/TirSystem/github-action/commit/08ef26a6d3eb98c066b45634c2271e578e6c0a0b [08ef26a]: https://git.tirsystem.com/TirSystem/github-action/commit/08ef26a6d3eb98c066b45634c2271e578e6c0a0b
[5002450]: https://git.tirsystem.com/TirSystem/github-action/commit/50024505fe788b1c5e3021c0d1fc3d079590070e
@@ -9,8 +9,8 @@
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-11 | Deprecated | Jens Tirsvad Nielsen | S01 | Current tool versions only, .NET SDK 10 or later: results of run 699 replace the SDK 8 result; tasks 3 and criterion 1 amended | [7bcc67a] | | 2026-10-13 | Deprecated | Jens Tirsvad Nielsen | AI as stand-in for S01 | Task 7 results added and reviewed (RC-059): adoption in two throwaway repositories, seeded failures verified, a guide gap fixed | [2481c26] |
| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Amendment reviewed (RC-055) and accepted: current tool versions, .NET SDK 10 or later, results of run 699, the contract of ADR-0008 in task 3 | [faf13b2] | | 2026-10-13 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Throwaway repositories of task 7 deleted at the user's request; action item of RC-059 closed | [daecb7b] |
--- ---
@@ -112,6 +112,30 @@ network), a private runner with no internet, and an image that differs from
`ubuntu-latest`. Those belong to the contract (task 3) and to the adoption `ubuntu-latest`. Those belong to the contract (task 3) and to the adoption
(task 7). (task 7).
Task 7 was run on 2026-10-13 in two throwaway private repositories of the
organisation, `TirSystem/qc-adoption-cpp` and `TirSystem/qc-adoption-csharp`
(no real C++ or C# project exists there; the user chose throwaways in chat).
Each holds a small passing project, a style file, and a workflow that calls the
released `v1.2.0` (`quality-checks-cpp.yml`, `quality-checks-csharp.yml`) with
the guide's call. A branch `seeded-failure` with a pull request added a
formatting error and a null dereference.
| Repository | Baseline on `main` | Seeded failure (pull request 1) |
| --- | --- | --- |
| `qc-adoption-cpp` | Pass (runs 899, 900) | Fail (run 906): `main.cpp:3:13: error: code should be clang-formatted`, `main.cpp:5:18: error: Dereference of null pointer ... [clang-analyzer-core.NullDereference]`, `FAIL clang-format`, `FAIL clang-tidy (C++)`, last line `Quality checks failed: clang-format clang-tidy (C++)` |
| `qc-adoption-csharp` | Pass (runs 901, 902) | Fail: `src/Program.cs(5,3): error WHITESPACE`, `src/Program.cs(8,27): error CS8602`, `FAIL dotnet format --verify-no-changes`, `FAIL dotnet build -warnaserror` |
Both statuses (`quality` and `quality-checks-cpp` / `quality-checks-csharp`)
appear on the pull requests. One gap in the guides was found and fixed: they
said the paths are relative to `working-directory`, but `clang-tidy` and
`dotnet` print absolute paths in the workspace of the runner. The guides and
their three wiki languages now say so.
Not covered: a real C++ project with a compile database and a real C# solution
with many packages; both stay open until a project of that kind adopts the
workflows. The throwaway repositories were deleted at the user's request on 2026-10-13;
the run numbers above are kept as the record.
--- ---
[BC-001]: ../business-case.md [BC-001]: ../business-case.md
@@ -124,3 +148,5 @@ network), a private runner with no internet, and an image that differs from
[ADR-0008]: ../adr/adr-0008-cpp-csharp-quality-check-contract.md [ADR-0008]: ../adr/adr-0008-cpp-csharp-quality-check-contract.md
[7bcc67a]: https://git.tirsystem.com/TirSystem/github-action/commit/7bcc67a065ac5f67b4e13ebf243a4bbbe2af47d9 [7bcc67a]: https://git.tirsystem.com/TirSystem/github-action/commit/7bcc67a065ac5f67b4e13ebf243a4bbbe2af47d9
[faf13b2]: https://git.tirsystem.com/TirSystem/github-action/commit/faf13b2c2a4901c0171977de1ffc2841023f537a [faf13b2]: https://git.tirsystem.com/TirSystem/github-action/commit/faf13b2c2a4901c0171977de1ffc2841023f537a
[2481c26]: https://git.tirsystem.com/TirSystem/github-action/commit/2481c26d802464decd51d75539157093b60e08e8
[daecb7b]: https://git.tirsystem.com/TirSystem/github-action/commit/daecb7b452197643b39752b9b453a8c376562354
@@ -0,0 +1,86 @@
# MIL-008: Wiki publishing as a reusable workflow
## Metadata
| Key | Value |
| --- | --- |
| ID | MIL-008 |
| CrossReference | [BC-001], [MIL-006], [ADR-0004], [RC-060], [RC-061] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-13 | Deprecated | Jens Tirsvad Nielsen | S01 | Initial version; reviewed (RC-060) | [01b9c29] |
| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | S01 | Task 5 changed to deprecate the scoped workflow (8 repositories still use it); reviewed (RC-061) | [01b9c29] |
---
## Purpose
Decide whether the wiki can be published by a reusable workflow that each
repository calls, from `docs/wiki/` (the location `process/wiki.md` of the
framework prescribes), instead of a scoped workflow that runs in every
repository of the organisation and reads `wiki/`.
## Deliverable
A reusable workflow `publish-wiki-pages.yml` in `.gitea/workflows/` with a
`wiki_dir` input, a caller template for projects, offline tests, a superseding
ADR, updated guides and wiki pages, this repository publishing its own wiki
from `docs/wiki/` through it, the scoped workflow marked deprecated (not
removed), and a release that projects can pin.
## Go / No-Go Criteria
| # | Criterion (objectively checkable) | Go | No-Go |
| --- | --- | --- | --- |
| 1 | The reusable workflow publishes the folder named by `wiki_dir` (default `docs/wiki`), and a repository that has no caller workflow is never written to | Offline tests pass; no caller means no run | Any write without a caller |
| 2 | An empty or missing folder never deletes pages, the failure names its category, and the secret is passed explicitly (criteria 1 and 4 of MIL-006 still hold) | Existing tests pass unchanged | A regression |
| 3 | This repository publishes its own wiki from `docs/wiki/` through a caller that uses the reusable workflow, and the pages on the host equal the folder | 0 differences read back through the API | Pages differ or the run fails |
| 4 | A caller template exists for projects, with the trigger `docs/wiki/**` and the secret named | File exists and is linted (QC-WF-001) | Missing |
| 5 | The scoped `publish-wiki.yml` is marked deprecated in its header, the README and ADR-0009, and the repositories that still use it are listed; it is not removed here | Notice present and the list recorded (8 repositories on 2026-10-13) | No notice, or the workflow was removed while a repository still depends on it |
| 6 | The change is released as patch 1.2.2 and the moving major tag points at it | `v1` includes the change | Not released |
## Dependencies
| Depends on | Reason |
| --- | --- |
| MIL-006 | The publisher script, the prune rule and the safety limits come from it |
| `TirSystem/sqa-qc-framework` `process/wiki.md` | Defines `docs/wiki/` and the English plus PO language minimum; unmerged when this was written |
## Traceability
| Business Case objective / KPI / user story | Reference |
| --- | --- |
| O2, O3 | [BC-001] |
## Ownership
| Role | Stakeholder ID (SA) |
| --- | --- |
| Owner | S05 |
| Approving reviewer | S01 |
## Target Date
2026-11-15 — the Business Case sets no deadline.
## Tasks
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
| --- | --- | --- | --- | --- |
| 1 | Record the decision | An ADR that supersedes the scoped-delivery part of ADR-0004: publishing is a reusable workflow called by each repository; the caller file is the opt-in, so `wiki/.publish` is no longer a gate; the `prune` setting becomes an input. Keep the rest of ADR-0004 (secret handling) as it stands. | No | |
| 2 | Add the reusable workflow | `.gitea/workflows/publish-wiki-pages.yml` with `on: workflow_call`, inputs `wiki_dir` (default `docs/wiki`) and `prune`, and the token as a declared secret. It runs `tools/publish_wiki.py` with `WIKI_DIR` and the prune setting from the inputs. Update the script and its tests: the marker is no longer required. | No | |
| 3 | Add the caller template | A `publish-wiki.yml` caller for projects (`uses: TirSystem/github-action/.gitea/workflows/publish-wiki-pages.yml@v1`, `paths: docs/wiki/**`, `secrets:` mapped). Proposed in `sqa-qc-framework` under `templates/ci/` once this is released. | No | |
| 4 | Move this repository's wiki and call the workflow | `git mv wiki docs/wiki`, check the links and the translation hash comments, add the caller, publish, read the pages back. | No | |
| 5 | Deprecate the scoped workflow | Do not remove it: on 2026-10-13, 8 repositories still publish through it with `wiki/.publish` (`sqa-and-qc`, `sqa-qc-framework`, `sqa-qc-checklists`, `github-action`, `repo-foundry` and three of `Tirsvad-Udemy-100-days-of-code`), and none calls the reusable workflow yet. Add a deprecation notice to its header, the README table and ADR-0009, and record the list. The migrations and the removal are a follow-up milestone after this release (ADR-0006: a removed workflow is a new major). | No | |
| 6 | Update guides and wiki pages | Describe the caller, `docs/wiki/` and the removed marker in the onboarding and credentials guides and in the English, Danish and Bahasa Malaysia pages (rule 8 of `AGENTS.md`). | No | |
| 7 | Release | Release as a patch, 1.2.1 to 1.2.2, as the owner instructed: changelog entry and a release through the release workflow; the `v1` tag moves with it. ADR-0006 classes a new workflow as minor, so the changelog entry states the deviation and the reason. Nothing is removed, so no consumer breaks. | No | |
---
[BC-001]: ../business-case.md
[MIL-006]: ./mil-006-shared-wiki-publishing.md
[ADR-0004]: ../adr/adr-0004-shared-wiki-publishing-opt-in.md
[RC-060]: ../sqa/reviews/rc-060-mil-008-plan.md
[RC-061]: ../sqa/reviews/rc-061-mil-008-task-5-deferred.md
[01b9c29]: https://git.tirsystem.com/TirSystem/github-action/commit/01b9c2972b4317bcc590540010aea0eaf7f30014
@@ -0,0 +1,109 @@
# MIL-009: Migrate the wiki callers and remove the scoped workflow
## Metadata
| Key | Value |
| --- | --- |
| ID | MIL-009 |
| CrossReference | [BC-001], [MIL-008], [ADR-0004], [ADR-0006], [ADR-0009] , [RC-066] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Deprecated | Jens Tirsvad Nielsen | AI as stand-in for S01 | Plan accepted in chat with the defaults: target date 2026-11-30 confirmed, `027-sandbox` and `028-pomodoro` do not publish, framework release 0.7.0; reviewed (RC-064) | [2f267df] |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Verification results recorded for tasks 1 to 8; outcome reviewed (RC-066) | [95abc18] |
---
## Purpose
Decide whether the scoped `publish-wiki.yml` can be removed: every repository
that publishes its wiki through it has moved to `docs/wiki/` and calls the
reusable workflow `publish-wiki-pages.yml`, and ADR-0004 is superseded by
ADR-0009. MIL-008 deprecated the scoped workflow and kept it because seven
repositories still depended on it (checked on 2026-10-13).
## Deliverable
A released framework with the caller template; each of the seven repositories
publishing its wiki from `docs/wiki/` through a caller, with its marker removed;
the scoped workflow, its marker code path, its tests and every mention of the
marker as an opt-in removed; ADR-0004 marked superseded; and the major release
2.0.0 with the tag `v2`.
## Go / No-Go Criteria
| # | Criterion (objectively checkable) | Go | No-Go |
| --- | --- | --- | --- |
| 1 | The framework is released as 0.7.0 with `templates/ci/publish-wiki.yml` and `process/wiki.md`, as a tag | The tag exists and each migrating repository pins it | No tag, or a repository pins an unmerged commit |
| 2 | Each of the seven repositories publishes from `docs/wiki/` through a caller of `publish-wiki-pages.yml`, and has no `wiki/.publish` | A scan of the instance lists the seven with a caller and no marker | Any marker or missing caller remains |
| 3 | The wiki of each of the seven equals its `docs/wiki/` after the first run | 0 differences read back through the API for each | A difference, or a failed run |
| 4 | A scan of every repository of the four organisations finds no `wiki/.publish` and no run of the scoped workflow that wrote | None found | Any found |
| 5 | The scoped `publish-wiki.yml`, the marker code path of `tools/publish_wiki.py`, their tests and the marker text of the guides, README and wiki pages are removed | Offline tests pass; a search finds no `wiki/.publish` outside the changelog and ADRs | A mention remains, or a test fails |
| 6 | ADR-0004 is `Superseded by ADR-0009` with a review record, and ADR-0009 links back | Both rows and RC present | Missing |
| 7 | Release 2.0.0 is published with a Breaking entry, `v2` points to it, and `v1` still points to 1.2.2 | Tags read back through the API | `v1` moved, or no Breaking entry |
## Dependencies
| Depends on | Reason |
| --- | --- |
| MIL-008 | Provides the reusable workflow, the deprecation and the list of repositories |
| `TirSystem/sqa-qc-framework` release | The caller template and `process/wiki.md` must be a tag before a repository pins them |
## Traceability
| Business Case objective / KPI / user story | Reference |
| --- | --- |
| O2, O3 | [BC-001] |
## Ownership
| Role | Stakeholder ID (SA) |
| --- | --- |
| Owner | S05 |
| Approving reviewer | S01 |
## Target Date
2026-11-30 — confirmed on 2026-10-14 by S01 in chat; the Business Case sets no deadline.
## Tasks
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
| --- | --- | --- | --- | --- |
| 1 | Release the framework | `TirSystem/sqa-qc-framework` has the caller template and `process/wiki.md` merged (pull request 45). Add the changelog entry, tag the release and record the tag. Needed before any repository pins the template. | No | |
| 2 | Migrate the three TirSystem projects | `sqa-and-qc`, `sqa-qc-framework` and `sqa-qc-checklists`: move `wiki/` to `docs/wiki/`, copy the caller from `templates/ci/publish-wiki.yml`, remove `wiki/.publish`, fix the paths in their generators and documents, move the framework pin to the released tag, one pull request each. Confirm the organisation secret `TOKEN_FOR_GITEA_WIKI` is present. | No | |
| 3 | Migrate `repo-foundry` | Same steps for `TirSystem-BashScript/repo-foundry` (21 wiki files); the organisation `TirSystem-BashScript` needs its own `TOKEN_FOR_GITEA_WIKI` secret (S03). | No | |
| 4 | Migrate the three Udemy repositories | Same steps for `021-snake-game`, `024-snake` and `025-weather` of `Tirsvad-Udemy-100-days-of-code` (5 wiki files each). `027-sandbox` and `028-pomodoro`, which have `docs/wiki/` and no marker, do not publish (decided by S01 on 2026-10-14). | No | |
| 5 | Verify the migration | After each first run read the wiki back and compare it with `docs/wiki/`; then scan the four organisations for `wiki/.publish` and for repositories still publishing through the scoped workflow. Record the results in this milestone. | No | |
| 6 | Remove the scoped workflow | Delete `.gitea/scoped_workflows/publish-wiki.yml`, the marker path of `tools/publish_wiki.py` (the `WIKI_PRUNE` input becomes required), the tests that cover it and the marker text in the guides, the README and the three wiki languages. Keep the drift test for the remaining scoped workflow. | No | |
| 7 | Supersede ADR-0004 | Mark ADR-0004 `Superseded by ADR-0009` for the delivery and marker parts (new version row, review record), and make ADR-0009 link back. | No | |
| 8 | Release 2.0.0 | Breaking entry (a removed workflow and marker); upgrade guide for callers; release through the release workflow; `v2` is created and `v1` stays at 1.2.2. Move the examples in the guides to `@v2` where the guide describes the new major. | No | |
---
## Verification results
Recorded on 2026-10-14 by the autonomous run.
| # | Criterion | Result |
| --- | --- | --- |
| 1 | The framework is released as 0.7.0 with the template and `process/wiki.md`, and each repository pins it | Go: tag `v0.7.0` on `70fc558`. `sqa-and-qc`, `repo-foundry`, `021-snake-game`, `024-snake` and `025-weather` pin it; `sqa-qc-framework` and `sqa-qc-checklists` have no `framework/` submodule to pin |
| 2 | Each of the seven repositories publishes from `docs/wiki/` through a caller and has no `wiki/.publish` | Go: `sqa-and-qc` #168, `sqa-qc-framework` #47, `sqa-qc-checklists` #17, `repo-foundry` #96, `021-snake-game` #29, `024-snake` #46, `025-weather` #31 |
| 3 | The wiki of each equals its `docs/wiki/` after the first run | Go: 0 differences read back through the API (10, 16, 13, 19, 4, 4 and 4 pages); each first run succeeded |
| 4 | A scan of every repository of the four organisations finds no `wiki/.publish` | Go: 48 repositories scanned, none found |
| 5 | The scoped workflow, the marker path, their tests and the marker text are removed | Go with a note: pull request #151; `wiki/.publish` remains only in the changelog, the ADRs, the milestone and review records and the sentence of the upgrading guide that explains the removal |
| 6 | ADR-0004 is `Superseded by ADR-0009` with a review record, and ADR-0009 links back | Go: RC-065 |
| 7 | Release 2.0.0 is published with a Breaking entry, `v2` points to it, and `v1` still points to 1.2.2 | Go: `v2.0.0` and `v2` on `6e8e548`, `v1` and `v1.2.2` on `9bf0a2b`; the release page shows the Breaking group |
Things that did not go as planned: the three Udemy pull requests were opened through a script that hit a network timeout once and was repeated; the `repo-foundry` test suite fails two tests on `main` and on the branch alike (pre-existing, not caused by this milestone).
---
[BC-001]: ../business-case.md
[RC-066]: ../sqa/reviews/rc-066-mil-009-outcome.md
[MIL-008]: ./mil-008-wiki-reusable-workflow.md
[ADR-0004]: ../adr/adr-0004-shared-wiki-publishing-opt-in.md
[ADR-0009]: ../adr/adr-0009-wiki-publishing-reusable-workflow.md
[ADR-0006]: ../adr/adr-0006-release-process-and-version-pinning.md
[2f267df]: https://git.tirsystem.com/TirSystem/github-action/commit/2f267df2a270087cb7dc482af4ce4f4341e97b03
[95abc18]: https://git.tirsystem.com/TirSystem/github-action/commit/95abc18a95d70c065b979123b8f8de22173af90c
@@ -0,0 +1,198 @@
# MIL-010: Code metrics and coverage badges
## Metadata
| Key | Value |
| --- | --- |
| ID | MIL-010 |
| CrossReference | [BC-001], [US-001], [MIL-003], [MIL-004], [ADR-0005], [ADR-0006] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Deprecated | Jens Tirsvad Nielsen | AI as stand-in for S01 | Results of tasks 3, 6 and 8 added: the report as an artifact of the caller (ADR-0010), adoption in a throwaway repository with seeded change and failure; reviewed (RC-074) | [5c5a1d1] |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Task 9 results added: released as 2.1.0; outcome reviewed (RC-075) | [aa20e56] |
---
## Purpose
Decide whether a repository can show, as SVG badges, how many code files and
how many lines of code (without comment and blank lines) it holds and how
much of that code its tests cover, produced by one shared workflow of this
repository, without a service outside the instance.
## Deliverable
A reusable workflow `code-metrics.yml` with a script file of this repository
behind it, which counts the code files and the lines of code, reads the
coverage report the caller supplies, writes the badges "code files", "lines
of code" and "coverage" as SVG files and commits them to a `badges` branch of
the calling repository. It comes with a documented set of inputs, a guide and
wiki pages in English, Danish and Bahasa Malaysia, a use in at least one other
repository, and a release that makes it available at the major tag. The
existing workflows keep their names and contracts.
Decisions taken by the user in chat on 2026-10-14, which the contract of task 2
records and does not reopen:
- The counter is a Python script of this repository with no dependency outside
the standard library (not `cloc`, `tokei` or `scc`).
- The delivery is a reusable workflow (`workflow_call`), as the quality-check
workflows are.
- The badges are committed to a `badges` branch, so a README can link to
them; they are not only a run artifact.
- Three badges: code files, lines of code and coverage. A badge per language
is optional and is decided in the ADR of task 2.
- The coverage report is read in the Cobertura XML format, which `coverage.py`,
`gcovr` and `coverlet` all write; the counter does not run tests itself, so a
caller that wants a coverage badge runs its tests first and passes the path
of the report.
## Go / No-Go Criteria
| # | Criterion (objectively checkable) | Go | No-Go |
| --- | --- | --- | --- |
| 1 | A job on the runner of the instance can push a commit to a `badges` branch of its own repository with the token the run provides, and the SVG renders as an image in a README on the instance and on the GitHub mirror, verified and recorded | Both render | The push needs a token with more than the contents of its own repository, or the image does not render |
| 2 | The contract (what a code file is, what a comment and a blank line are for each language, excluded paths, generated and vendored files, the coverage report format, the badge colours and text) is decided in an ADR | ADR accepted | Open |
| 3 | The counter gives the hand-counted numbers (files, code, comment, blank) for a fixture of each supported language, including a block comment, a trailing comment, a comment marker inside a string and a file with no final newline | Equal for every fixture | Any difference |
| 4 | The coverage reader gives the percentage of the fixture reports, and a missing or malformed report is a failure that names the file, never a badge showing 0% | Verified | A bad report produces a badge |
| 5 | The badges are valid SVG, carry a title and a text alternative, have a fixed width per text, and are byte-identical for the same input | Verified | Not valid or not stable |
| 6 | The workflow commits nothing when no badge changed, does not start itself again from its own push, and asks for the least permission that writes to its own repository | Verified in a run | A loop, or a wider permission |
| 7 | One other repository runs the workflow and its README shows the three badges, with a seeded change that moves the coverage badge to another colour | Shown | Does not run or does not change |
| 8 | The workflow embeds no script: it runs a file of this repository, and the offline tests exercise the file | Count of embedded scripts is 0 | Embedded script remains |
| 9 | A release (a minor version) makes it available, and the changelog, the README workflow table and the wiki name it | Released | Unreleased |
## Dependencies
| Depends on | Reason |
| --- | --- |
| MIL-003 | The reusable workflow mechanism, the `tools-ref` input and the guide for reading results |
| MIL-004 | The release process and the release workflow publish the result |
## Traceability
| Business Case objective / KPI / user story | Reference |
| --- | --- |
| O5 (added to the Business Case in the same change as this milestone) | [BC-001] |
| New stories of task 1, to be written in US-001 | [US-001] |
## Ownership
| Role | Stakeholder ID (SA) |
| --- | --- |
| Owner | S05 |
| Approving reviewer | S01 |
## Target Date
2027-01-30 — it follows the last decision of the plan, 2027-01-16, with two
weeks from 2027-01-18; proposed by the AI and accepted by the user in chat on
2026-10-14, recorded by the AI as stand-in for S05 (ADR-0002). The Business
Case sets no deadline.
## Tasks
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
| --- | --- | --- | --- | --- |
| 1 | Write the use case and stories for the metrics badges | Add a use case "Show code metrics and coverage badges" for the software engineer, add it to the use case diagram, and add stories to US-001: the badges appear after a push, a coverage report that is missing or malformed fails with the file named, and nothing is committed when the numbers are unchanged. Each story has a pass and a fail acceptance criterion. Review them with the checklists of the `artifact` skill. | Yes | US-001 |
| 2 | Decide the contract | Write an ADR with the definitions the counter needs (code file, comment, blank line, per language), the default includes and excludes, the languages of the first version, the Cobertura XML coverage input, the badge texts and colour thresholds, the inputs of the workflow, the name and content of the `badges` branch, and what a pass, a failure and a run without files mean, in the style of ADR-0005. Record the decisions of the Deliverable. | No | |
| 3 | Verify the delivery on the instance | On a throwaway branch, run a job that commits an SVG to a `badges` branch with the token of the run, then check that the raw URL serves it as an image on the instance, that a README shows it, and that it also shows on the GitHub mirror. Record the result in this milestone like MIL-007 task 2, together with the permission that was needed. | No | |
| 4 | Implement the counter | Add `tools/count_loc.py` and tests with a fixture per language under `tests/`: file and line counts split into code, comment and blank, an exclude list, and a machine-readable result on standard output. Standard library only, as the rules of the repository for tools say. | No | |
| 5 | Implement the coverage reader and the badge renderer | Add the reader of the Cobertura XML report and the SVG renderer, with the failure cases of Go/No-Go criterion 4 and a stable output (criterion 5), each with offline tests. | No | |
| 6 | Implement the workflow | Add the reusable workflow `code-metrics.yml` and its script file, which calls the counter, the reader and the renderer and commits to the `badges` branch as the contract of task 2 says, with least-privilege permissions and offline tests as `quality-checks.yml` has. | No | |
| 7 | Write the guides and the wiki pages | A guide in `guides/` with the call, the inputs, how to produce a coverage report for Python, C++ and C#, and the Markdown line that shows the badges; the wiki pages in `docs/wiki` in English, Danish and Bahasa Malaysia; the README table and diagram and the wiki Home workflow list (a test requires every workflow to be listed). | No | |
| 8 | Adopt the workflow in one other repository | Onboard a real repository, or a throwaway one when none fits, show the badges in its README and verify a seeded change that moves the coverage badge. Fix the gaps in the guide that appear. | No | |
| 9 | Release | Add the changelog entry (a new workflow is a minor change, ADR-0006) and run the Release workflow; record the results in this milestone and review it. | No | |
## Verification results
Task 3 was run on 2026-10-14 with a throwaway branch, `verify-badges-delivery`,
in `TirSystem/github-action` (run 1418, success). One job on `ubuntu-latest`
with `permissions: contents: write` checked the repository out, created an
orphan branch `badges-verify` with one SVG file and a commit message ending in
`[skip ci]`, and pushed it with the token of the run (the credential that
`actions/checkout` stores); it then requested the raw URL.
| Fact | Result |
| --- | --- |
| Push with the token of the run | Accepted: `git push origin HEAD:refs/heads/badges-verify` created the branch; no secret and no personal access token was needed |
| Raw URL on the instance | `https://git.tirsystem.com/TirSystem/github-action/raw/branch/badges-verify/code-files.svg` answers `200`, `Content-Type: image/svg+xml`, `Content-Security-Policy: default-src 'none'; style-src 'unsafe-inline'; sandbox`, `Cache-Control: public, max-age=21600` (six hours) |
| The branch on the GitHub mirror | The push mirror (`sync_on_commit`, every 8 h otherwise) copied the branch within seconds; `https://raw.githubusercontent.com/TirSystem/github-action/badges-verify/code-files.svg` answers `200`, `Content-Type: image/svg+xml`, `Cache-Control: max-age=300` |
| Repository visibility | Public; a README image on GitHub reads the raw URL, which needs no token |
The SVG is served as an image on both hosts, so it renders in a README on the
instance and on the mirror; the content security policy rules out script and
external references inside the file, as ADR-0010 requires. The six-hour
cache on the instance means a changed badge can show the old number for up to
six hours there; the guide says so. Not covered by this task: a private
repository (the raw URL then needs a login, so a badge does not render for an
anonymous reader), and the rendering inside a Gitea README, which is the same
`<img>` request. The throwaway branches `verify-badges-delivery` and
`badges-verify` are left in place because deleting a branch is an action the
run stops on; they are listed in the summary for the user to delete.
Task 6 changed the contract once while it was implemented. A called workflow
runs in its own job, so it cannot read a report that the caller's test job
wrote in its workspace; a report the repository commits is the only file it can
see. ADR-0010 was amended (RC-073): the caller uploads the report as an
artifact and names it in the new input `coverage-artifact`; the workflow
downloads it with `actions/download-artifact` into the directory
`coverage-artifact` and `coverage-report` names the file inside it. The scope
and the three badges did not change.
Task 8 was run on 2026-10-14 in a throwaway private repository of the
organisation, `TirSystem/code-metrics-adoption` (no repository of a project
fits, and the user's plan allows a throwaway). It holds a two-file Python
module with tests and a workflow of two jobs: the tests with `pytest-cov`
upload `coverage.xml` as the artifact `coverage`, and the second job calls
`code-metrics.yml` and `tools-ref` of the branch `mil-010-implementation` of
this repository, before its merge.
| Step | Result |
| --- | --- |
| Baseline (run 1423) | All steps pass, including `Download the coverage report` with `actions/download-artifact@v4.1.8` on Gitea 1.27.3; the `badges` branch is created with one commit by `code-metrics` and the message `Update code metrics badges [skip ci]`; `code-files.svg` shows 2, `code-lines.svg` 28 (the hand count of the two files), `coverage.svg` 100% in green `#44cc11` |
| The push of the badges | No run was started by it: the runs of the repository are the two of the push to `main` (the metrics workflow and the scoped sync) |
| Seeded change (run 1425): two tests removed | The coverage badge reads 60% in yellow `#dfb317`; one new commit on `badges` that changes the badge files; the files and the lines badges changed with the tests |
| Seeded failure (run 1429): `coverage-report` names a file that does not exist | The job `code-metrics` fails: `::error::[configuration] coverage-report 'coverage-artifact/nope.xml' does not exist.`; `badges` is unchanged (two commits) |
| README | Its three Markdown images render as `<img>` of the raw URLs of the `badges` branch (checked with the markdown API of the instance); the raw URLs answer `image/svg+xml` as in task 3 |
Not covered: a run that changes nothing (the unchanged case is covered by the
offline tests, not by a run), two runs that write the branch at the same time
(covered by an offline test with a rejecting remote, not on the instance), the
rendering for a reader who is not signed in to a private repository, and a
repository of a project with a real coverage report. The throwaway repository
`TirSystem/code-metrics-adoption` is left in place because deleting a
repository is an action the run stops on; it is listed in the summary for the
user to delete.
Task 9 was run on 2026-10-14. The implementation was merged in pull request
164 after its checks were green; the user merged it, because the host refused
the merge through the API with a required check `Publish wiki / *` that the
branch protection API does not list and that nothing in this repository
reports. The Release workflow was run for 2.1.0 on the merge commit
`16faffa`: a dry run (run 1462) and then the release (run 1463), both green.
| Check | Result |
| --- | --- |
| Tag | `v2.1.0` and the moved major tag `v2` both point at `16faffa` |
| Release page | `v2.1.0`, not a draft, with the changelog entry as its text (`Added`: the workflow `code-metrics.yml`) |
| Callable at the major tag | `.gitea/workflows/code-metrics.yml` and `tools/count_loc.py` are served at `v2` and `v2.1.0` |
| Checks on `main` after the merge | validation, milestone publishing, wiki publishing and the metadata sync all green (runs 1458 to 1461) |
Go/No-Go criteria of this milestone at its close: 1 to 8 as in the results
above; 9 (a release makes it available, and the changelog, the README table and
the wiki name it) is met by the release of 2.1.0. Success criterion 5 of the
Business Case (a repository besides this one shows the badges) is met in a
throwaway repository only; a project repository has not adopted it yet.
---
[BC-001]: ../business-case.md
[US-001]: ../user-stories.md
[MIL-003]: ./mil-003-quality-checks.md
[MIL-004]: ./mil-004-versioned-release.md
[ADR-0005]: ../adr/adr-0005-quality-check-contract.md
[ADR-0006]: ../adr/adr-0006-release-process-and-version-pinning.md
[5002450]: https://git.tirsystem.com/TirSystem/github-action/commit/50024505fe788b1c5e3021c0d1fc3d079590070e
[5c5a1d1]: https://git.tirsystem.com/TirSystem/github-action/commit/5c5a1d19d96a6979b4f6141e736957174ba686a9
[aa20e56]: https://git.tirsystem.com/TirSystem/github-action/commit/aa20e56110f4c2fab91a8432ba008ee8300926a2
@@ -0,0 +1,88 @@
# MIL-011: Fix the defaults of the code metrics workflow
## Metadata
| Key | Value |
| --- | --- |
| ID | MIL-011 |
| CrossReference | [BC-001], [US-001], [MIL-010], [ADR-0010], [ADR-0006] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version; reviewed (RC-076), accepted by the author in chat as stand-in for S01 | [02e3794] |
---
## Purpose
Decide whether the released code metrics workflow can be called with its documented defaults, and correct it if it cannot.
Release 2.1.0 ([MIL-010]) added `code-metrics.yml`. Its guide, its header comment and the default of its `tools-ref` input name `v1`, but the workflow and its scripts (`tools/code-metrics.sh`, `tools/count_loc.py`, `tools/badges.py`) exist only from `v2.1.0`. A caller who follows the guide (`@v1`) cannot resolve the workflow, and a caller at `@v2` that leaves `tools-ref` at its default fetches the scripts from `v1`, where they are absent, so the run fails. The offline tests of [MIL-010] did not see it, because they run the scripts from the checkout and never resolve a ref. The framework wiki page `Badges` works around it by telling callers to set `tools-ref: v2`.
The other four reusable workflows also default `tools-ref` to `v1`; their scripts exist at `v1`, but a default that follows an old major is the same kind of error, so this milestone checks them too.
## Deliverable
A patch release (`2.1.1`, [ADR-0006]) in which: the default `tools-ref` of every reusable workflow names a tag that holds the scripts it runs; the guide, the header comment of `code-metrics.yml`, the upgrading guide, the README and the wiki pages in English, Danish and Bahasa Malaysia call the workflow at `@v2`; a test fails when a default `tools-ref` names a tag without the script; and the changelog has a `Fixed` entry. In `TirSystem/sqa-qc-framework`: the `Badges` wiki page without the `tools-ref` workaround.
## Go / No-Go Criteria
| # | Criterion (objectively checkable) | Go | No-Go |
| --- | --- | --- | --- |
| 1 | The defect is reproduced before the fix: a call at the major tag with the defaults fails, and the failure names the missing script, recorded in this milestone | Reproduced and recorded | Not reproduced, or not recorded |
| 2 | Every reusable workflow's default `tools-ref` names a tag that contains the script file the workflow runs, listed per workflow in this milestone | All five checked and listed | A default names a tag without its script |
| 3 | The default is derived, not copied: one place gives the current major, and a test fails when a workflow default differs from it or names a tag without the script | Test fails on a seeded wrong default and passes on the fix | No such test |
| 4 | No guide, comment, README or wiki page tells a caller to use `@v1` for `code-metrics.yml`; a search of `guides/`, `README.md`, `docs/wiki/` and `.gitea/workflows/` finds none | Count is 0 | A page still says `@v1` for it |
| 5 | A call with the documented defaults runs on the instance and commits the badges to a `badges` branch, verified on a throwaway branch and recorded | Run succeeds | Fails or is not run |
| 6 | The release is a patch (`2.1.1`): the changelog has a `Fixed` entry, the major tag `v2` moves to it, and `v1` is not touched | Released | Released as another level, or `v1` moved |
| 7 | The framework `Badges` page no longer tells a caller to set `tools-ref`, in all three languages, and still calls the workflow at `@v2` | Page merged | The workaround remains |
| 8 | Reviewed | A review record (`RC-*`) for this milestone and the changed workflow, tests and guides, each with a verdict of Go | An instance without a record |
## Dependencies
| Depends on | Reason |
| --- | --- |
| [MIL-010] | The workflow and its guide are its deliverables; the defect is in release 2.1.0 |
| [ADR-0006] | The release process: a fix is a patch release, and a moving major tag follows it |
## Traceability
| Business Case objective / KPI / user story | Reference |
| --- | --- |
| Business Case objective O5: a shared workflow that counts the code files and lines of code, reads the coverage report and publishes the three results as SVG badges; the workflow cannot be called as documented until this milestone is done | [BC-001] |
| User stories | [US-001] (no new story: the tasks below are technical, and the story of [MIL-010] for the badges stays as written) |
## Ownership
| Role | Stakeholder ID (SA) |
| --- | --- |
| Owner | S05 |
| Approving reviewer | S01 |
## Target Date
2026-10-23 (Friday), one week of work: the fix is small and a caller cannot use the workflow until it is released. This precedes the planned decision date of [MIL-010] (2027-01-30) because that milestone was released on 2026-10-14, ahead of its date; the schedule row of [MIL-010] is not changed here. The Business Case sets no deadline.
## Tasks
| # | Task | Summary | Needs its own Use Case/User Story? | Reference |
| --- | --- | --- | --- | --- |
| 1 | Reproduce the defect and list the defaults | On a throwaway branch, call `code-metrics.yml` at `@v2` with the defaults and at `@v1`, and record both failures and the message. List, for each of the five reusable workflows, the default `tools-ref` and whether the script it runs exists at that tag. Record the result in this milestone. | No | [MIL-010] |
| 2 | Fix the defaults | Give the repository one definition of the current major (a file the workflows' tests and the release workflow read), set the default `tools-ref` of every reusable workflow from it, and change the header comment of `code-metrics.yml` to `@v2`. A workflow whose script is at the previous major keeps that tag only with a comment that says why. | No | [ADR-0006] |
| 3 | Add the test | An offline test that reads every workflow in `.gitea/workflows/`, takes the default `tools-ref` and the script it runs, and fails when the default differs from the current major or the script is not in that tag (checked with `git cat-file` against the tags of the checkout). Show it fails on a seeded wrong default and passes on the fix. | No | [MIL-010] |
| 4 | Correct the guides, README and wiki | Change `@v1` to `@v2` for `code-metrics.yml` in `guides/code-metrics.md`, `guides/upgrading.md`, `README.md` and `docs/wiki/CodeMetrics.md`, `.da.md` and `.ms.md`; say in the guide that `tools-ref` defaults to the current major. Search the repository for the remaining `@v1`. | No | |
| 5 | Verify on the instance | On a throwaway branch of a repository, call the fixed workflow at the branch with the defaults, and check that the `badges` branch gets the SVG files. Record the run. | No | [MIL-010] |
| 6 | Release 2.1.1 | Add the `Fixed` entry to the changelog (ADR-0006: a patch), run the Release workflow, and check that `v2` points at `v2.1.1` and `v1` at `v1.2.2`. | No | [ADR-0006] |
| 7 | Remove the workaround from the framework page | In `TirSystem/sqa-qc-framework`, remove the `tools-ref: v2` line and the two bullets about it from `docs/wiki/Badges.md`, `Badges-da.md` and `Badges-ms.md`, and add a changelog line. This is a change in another repository, made after release 2.1.1 and in its own pull request. | No | |
| 8 | Add MIL-011 to the Project Plan | Add the schedule row, the Gantt bar, the scope entry and the dependency to [PP-001], and its review to the traceability matrix. | No | |
| 9 | Review | Record `RC-*` for this milestone, the workflow and tests, and the guides, each with a verdict. | No | |
---
[BC-001]: ../business-case.md
[US-001]: ../user-stories.md
[MIL-010]: ./mil-010-code-metrics-badges.md
[ADR-0010]: ../adr/adr-0010-code-metrics-badges-contract.md
[ADR-0006]: ../adr/adr-0006-release-process-and-version-pinning.md
[PP-001]: ../project-plan.md
[02e3794]: https://git.tirsystem.com/TirSystem/github-action/commit/02e3794d7b05799af2544ca303c99271da788e82
+33 -7
View File
@@ -4,25 +4,25 @@
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| ID | PP-001 | | ID | PP-001 |
| CrossReference | [BC-001], [SA-001], [ADR-0002], [US-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007] | | CrossReference | [BC-001], [SA-001], [ADR-0002], [US-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [MIL-008], [MIL-009], [MIL-010], [MIL-011] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Dates confirmed by S01 (stand-in for S05); the confirmation item is removed from Open Issues | [ea06eaa] | | 2026-10-14 | Deprecated | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added MIL-010 (code metrics and coverage badges); reviewed (RC-068) | [5002450] |
| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | Added MIL-007 (quality checks for C++ and C#); reviewed (RC-053) | [e3ad1fa] | | 2026-10-14 | Accepted | Jens Tirsvad Nielsen | S01 | Added MIL-011 (fix the defaults of the code metrics workflow); reviewed (RC-076), accepted by the author in chat as stand-in for S01 | [02e3794] |
--- ---
## Purpose ## Purpose
This plan schedules seven milestones that move the repository from one This plan schedules eleven milestones that move the repository from one
workflow with known defects to a documented, versioned collection of shared workflow with known defects to a documented, versioned collection of shared
workflows. The Business Case sets no deadline, so the dates are the plan's own; they were confirmed on 2026-10-10 by S01, standing in for S05 (ADR-0002). workflows. The Business Case sets no deadline, so the dates are the plan's own; they were confirmed on 2026-10-10 by S01, standing in for S05 (ADR-0002).
## Planning Assumptions ## Planning Assumptions
- Week 1 starts 2026-10-05; phases are two weeks long; the last decision is 2026-12-26; MIL-007 follows in January 2027 (2027-01-04 to 2027-01-16, confirmed on 2026-10-11 by S01). - Week 1 starts 2026-10-05; phases are two weeks long; the last decision is 2026-12-26; MIL-007 follows in January 2027 (2027-01-04 to 2027-01-16, confirmed on 2026-10-11 by S01). MIL-008 amends the shared wiki publishing of MIL-006 and runs in parallel with the October and November windows: its work started on 2026-10-13 and its decision date is its own Target Date, 2026-11-15. MIL-009 follows MIL-008 and runs from 2026-11-16 to its proposed Target Date, 2026-11-30, confirmed by S01 in chat on 2026-10-14. MIL-010 (code metrics and coverage badges) follows MIL-007 in January 2027, from 2027-01-18 to its Target Date, 2027-01-30, accepted by the user in chat on 2026-10-14.
- Work on a milestone starts only after its `MIL-*` document is `Accepted`. - Work on a milestone starts only after its `MIL-*` document is `Accepted`.
- Dates were confirmed on 2026-10-10 by S01, standing in for S05 (ADR-0002). - Dates were confirmed on 2026-10-10 by S01, standing in for S05 (ADR-0002).
@@ -37,6 +37,10 @@ workflows. The Business Case sets no deadline, so the dates are the plan's own;
| Wiki and translations | [MIL-005] | 2026-11-30 to 2026-12-12 | 2026-12-12 | S05 | None yet | Wiki pages in English, Danish and Bahasa Malaysia, kept current by every pull request | [Milestone MIL-005] | | Wiki and translations | [MIL-005] | 2026-11-30 to 2026-12-12 | 2026-12-12 | S05 | None yet | Wiki pages in English, Danish and Bahasa Malaysia, kept current by every pull request | [Milestone MIL-005] |
| Shared wiki publishing | [MIL-006] | 2026-12-14 to 2026-12-26 | 2026-12-26 | S05 | None yet | Scoped publish-wiki workflow with opt-in, piloted in one other repository | [Milestone MIL-006] | | Shared wiki publishing | [MIL-006] | 2026-12-14 to 2026-12-26 | 2026-12-26 | S05 | None yet | Scoped publish-wiki workflow with opt-in, piloted in one other repository | [Milestone MIL-006] |
| Quality checks for C++ and C# | [MIL-007] | 2027-01-04 to 2027-01-16 | 2027-01-16 | S05 | US-001.09, US-001.10 (to extend) | Reusable quality-check workflows for C++ and C#, released | [Milestone MIL-007] | | Quality checks for C++ and C# | [MIL-007] | 2027-01-04 to 2027-01-16 | 2027-01-16 | S05 | US-001.09, US-001.10 (to extend) | Reusable quality-check workflows for C++ and C#, released | [Milestone MIL-007] |
| Wiki publishing as a reusable workflow | [MIL-008] | 2026-10-13 to 2026-11-15 | 2026-11-15 | S05 | None yet | Reusable publish-wiki-pages workflow, wiki in docs/wiki, scoped workflow deprecated, released as 1.2.2 | [Milestone MIL-008] |
| Migrate the wiki callers and remove the scoped workflow | [MIL-009] | 2026-11-16 to 2026-11-30 | 2026-11-30 | S05 | None yet | Seven repositories publish through a caller, scoped workflow removed, ADR-0004 superseded, released as 2.0.0 | Not created yet (the sync creates it when MIL-009 is Accepted) |
| Code metrics and coverage badges | [MIL-010] | 2027-01-18 to 2027-01-30 | 2027-01-30 | S05 | None yet (task 1 writes them) | Reusable workflow that counts code files and lines of code, reads coverage and commits SVG badges, released | Not created yet (the sync creates it when MIL-010 is Accepted) |
| Fix the defaults of the code metrics workflow | [MIL-011] | 2026-10-14 to 2026-10-23 | 2026-10-23 | S05 | None (technical tasks) | Patch release 2.1.1: the default `tools-ref` of every reusable workflow names a tag that holds its scripts, a test for it, guides and wiki at `@v2`, the framework Badges page without the workaround | Not created yet (the sync creates it when MIL-011 is Accepted) |
```plantuml ```plantuml
@startgantt @startgantt
@@ -55,6 +59,14 @@ Project starts 2026-10-05
[MIL-006 Go/No-Go] happens 2026-12-26 [MIL-006 Go/No-Go] happens 2026-12-26
[MIL-007 C++ and C#] starts 2027-01-04 and ends 2027-01-16 [MIL-007 C++ and C#] starts 2027-01-04 and ends 2027-01-16
[MIL-007 Go/No-Go] happens 2027-01-16 [MIL-007 Go/No-Go] happens 2027-01-16
[MIL-008 Reusable wiki workflow] starts 2026-10-13 and ends 2026-11-15
[MIL-008 Go/No-Go] happens 2026-11-15
[MIL-009 Migrate and remove] starts 2026-11-16 and ends 2026-11-30
[MIL-009 Go/No-Go] happens 2026-11-30
[MIL-010 Badges] starts 2027-01-18 and ends 2027-01-30
[MIL-010 Go/No-Go] happens 2027-01-30
[MIL-011 Defaults fix] starts 2026-10-14 and ends 2026-10-23
[MIL-011 Go/No-Go] happens 2026-10-23
@endgantt @endgantt
``` ```
@@ -69,6 +81,9 @@ Project starts 2026-10-05
| Wiki pages in English, Danish and Bahasa Malaysia | [MIL-005] | | Wiki pages in English, Danish and Bahasa Malaysia | [MIL-005] |
| The wiki publisher shared with other repositories through scoped delivery | [MIL-006] | | The wiki publisher shared with other repositories through scoped delivery | [MIL-006] |
| Gitea Actions workflows for software engineers (quality checks for C++ and C#) | [MIL-007] | | Gitea Actions workflows for software engineers (quality checks for C++ and C#) | [MIL-007] |
| The wiki publisher as a workflow that repositories call, from docs/wiki | [MIL-008] |
| Moving every publishing repository to the called workflow and removing the scoped one | [MIL-009] |
| A shared workflow for code metrics and coverage badges | [MIL-010], [MIL-011] |
## Dependencies ## Dependencies
@@ -76,6 +91,8 @@ Project starts 2026-10-05
MIL-001 → MIL-002 → MIL-003 → MIL-004 MIL-001 → MIL-002 → MIL-003 → MIL-004
MIL-002 → MIL-005 → MIL-006 MIL-002 → MIL-005 → MIL-006
MIL-003 + MIL-004 → MIL-007 MIL-003 + MIL-004 → MIL-007
MIL-006 → MIL-008 → MIL-009
MIL-003 + MIL-004 → MIL-010 → MIL-011
``` ```
A No-Go moves every later window by the rework time of the failed milestone. A No-Go moves every later window by the rework time of the failed milestone.
@@ -86,6 +103,9 @@ A No-Go moves every later window by the rework time of the failed milestone.
| --- | --- | --- | | --- | --- | --- |
| The runner cannot provide the C++ or C# tools (MIL-007) | The workflows need a custom runner image or a longer install | MIL-007 task 2 verifies it first; the ADR of task 3 says what a build-dependent check needs from the caller. | | The runner cannot provide the C++ or C# tools (MIL-007) | The workflows need a custom runner image or a longer install | MIL-007 task 2 verifies it first; the ADR of task 3 says what a build-dependent check needs from the caller. |
| Reusable workflow support is missing on the instance | MIL-003 and MIL-004 change shape | Verify first in each milestone (MIL-002 task 2, MIL-003 task 1); fall back to copied templates. | | Reusable workflow support is missing on the instance | MIL-003 and MIL-004 change shape | Verify first in each milestone (MIL-002 task 2, MIL-003 task 1); fall back to copied templates. |
| Eight repositories still publish through the scoped workflow that MIL-008 deprecates (MIL-008 task 5) | Removing it would stop their wikis | It is deprecated and kept; a follow-up milestone migrates them and removes it in a major release (RC-061). |
| An organisation has no `TOKEN_FOR_GITEA_WIKI` secret, or a repository fails its first run (MIL-009 tasks 3 and 4) | The wiki of that repository stops updating | MIL-009 task 5 reads each wiki back after the first run; the scoped workflow is removed only after every repository passes. |
| The token of a run cannot push to a `badges` branch, or the SVG does not render on the mirror (MIL-010) | The delivery of the badges changes shape | MIL-010 task 3 verifies it first; fall back to a badge file in a path of the default branch or to a run artifact. |
| Security review by S05 is a bottleneck | Milestone decisions slip | Keep milestones small and plan review time before each decision date. | | Security review by S05 is a bottleneck | Milestone decisions slip | Keep milestones small and plan review time before each decision date. |
## Open Issues ## Open Issues
@@ -258,6 +278,10 @@ no tests, no release process.
[MIL-005]: ./milestones/mil-005-wiki-and-translations.md [MIL-005]: ./milestones/mil-005-wiki-and-translations.md
[MIL-006]: ./milestones/mil-006-shared-wiki-publishing.md [MIL-006]: ./milestones/mil-006-shared-wiki-publishing.md
[MIL-007]: ./milestones/mil-007-cpp-csharp-quality-checks.md [MIL-007]: ./milestones/mil-007-cpp-csharp-quality-checks.md
[MIL-008]: ./milestones/mil-008-wiki-reusable-workflow.md
[MIL-009]: ./milestones/mil-009-migrate-wiki-callers-and-remove-scoped-workflow.md
[MIL-010]: ./milestones/mil-010-code-metrics-badges.md
[MIL-011]: ./milestones/mil-011-code-metrics-default-tools-ref.md
[Milestone MIL-001]: https://git.tirsystem.com/TirSystem/github-action/milestone/18 [Milestone MIL-001]: https://git.tirsystem.com/TirSystem/github-action/milestone/18
[Milestone MIL-002]: https://git.tirsystem.com/TirSystem/github-action/milestone/19 [Milestone MIL-002]: https://git.tirsystem.com/TirSystem/github-action/milestone/19
[Milestone MIL-003]: https://git.tirsystem.com/TirSystem/github-action/milestone/20 [Milestone MIL-003]: https://git.tirsystem.com/TirSystem/github-action/milestone/20
@@ -265,6 +289,8 @@ no tests, no release process.
[Milestone MIL-005]: https://git.tirsystem.com/TirSystem/github-action/milestone/93 [Milestone MIL-005]: https://git.tirsystem.com/TirSystem/github-action/milestone/93
[Milestone MIL-006]: https://git.tirsystem.com/TirSystem/github-action/milestone/94 [Milestone MIL-006]: https://git.tirsystem.com/TirSystem/github-action/milestone/94
[Milestone MIL-007]: https://git.tirsystem.com/TirSystem/github-action/milestone/95 [Milestone MIL-007]: https://git.tirsystem.com/TirSystem/github-action/milestone/95
[ea06eaa]: https://git.tirsystem.com/TirSystem/github-action/commit/ea06eaaa698247d20e4a8e2694d7acbb1de58b63 [Milestone MIL-008]: https://git.tirsystem.com/TirSystem/github-action/milestone/152
[b74ee0e]: https://git.tirsystem.com/TirSystem/github-action/commit/b74ee0e5cfd04f25ba5f2c1addc80699147c479a [b74ee0e]: https://git.tirsystem.com/TirSystem/github-action/commit/b74ee0e5cfd04f25ba5f2c1addc80699147c479a
[e3ad1fa]: https://git.tirsystem.com/TirSystem/github-action/commit/e3ad1fa7e7eef57a6793a479edb41ac6fd2db688 [2f267df]: https://git.tirsystem.com/TirSystem/github-action/commit/2f267df2a270087cb7dc482af4ce4f4341e97b03
[5002450]: https://git.tirsystem.com/TirSystem/github-action/commit/50024505fe788b1c5e3021c0d1fc3d079590070e
[02e3794]: https://git.tirsystem.com/TirSystem/github-action/commit/02e3794d7b05799af2544ca303c99271da788e82
@@ -0,0 +1,54 @@
# Review Record: MIL-007 (task 7 results)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-059 |
| CrossReference | [MIL-007], [QC-MIL-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-13 | Deprecated | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | [2481c26] |
| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Action item closed: the two throwaway repositories were deleted at the user's request | [daecb7b] |
---
## Artifact Under Review
- Instance reviewed: [MIL-007] with the verification results of task 7 (adoption in two throwaway repositories, seeded failures)
- Checklist used: [QC-MIL-001]
- Scope: delta re-review of the added results and of Go/No-Go criteria 3 and 4; the earlier record is [RC-055]
- Language and domain: n/a (technical type)
- Language reviewer: none
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; the user chose the throwaway repositories in chat on 2026-10-13)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | A concrete deliverable is defined for every gate | Pass | Unchanged. |
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Criterion 3 (tool, file and line in the log) and 4 (status on pull requests) are met for both workflows by runs 906 and the C# run of the same pull request, as the verification results table lists. |
| 3 | Dependencies on other milestones are explicitly mapped | Pass | Unchanged: MIL-003 and MIL-004. |
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | Unchanged: O4, US-001.09, .10, .16, .17. |
| 5 | Milestone owner and approving reviewer are identified | Pass | Owner S05, approving reviewer S01. |
| 6 | Milestone has a defined target date consistent with project constraints | Pass | 2027-01-16, unchanged. |
## Overall Verdict
Go - all six criteria pass. Criteria 3 and 4 of the milestone are verified on throwaway repositories only; a real project with a compile database or many packages is still open and is said so in the results. S01 is stood in for by the AI as the user chose autonomous mode; there is no independent reader, and the decision stays open to challenge.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| ~~Decide whether to delete `TirSystem/qc-adoption-cpp` and `TirSystem/qc-adoption-csharp`~~ Closed 2026-10-13: both were deleted at the user's request | S05 | done |
| Adopt the workflows in a real C++ and a real C# project | S05 | before the milestone closes |
---
[MIL-007]: ../../milestones/mil-007-cpp-csharp-quality-checks.md
[RC-055]: ./rc-055-mil-007-amendment.md
[QC-MIL-001]: ../../../framework/qc/qc-milestones-gateways.md
[2481c26]: https://git.tirsystem.com/TirSystem/github-action/commit/2481c26d802464decd51d75539157093b60e08e8
[daecb7b]: https://git.tirsystem.com/TirSystem/github-action/commit/daecb7b452197643b39752b9b453a8c376562354
+54
View File
@@ -0,0 +1,54 @@
# Review Record: MIL-008
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-060 |
| CrossReference | [MIL-008], [QC-MIL-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | [01b9c29] |
---
## Artifact Under Review
- Instance reviewed: [MIL-008]
- Checklist used: [QC-MIL-001]
- Scope: full review
- Language and domain: n/a (technical type)
- Language reviewer: none
- Mode: supervised (S01, the user, accepted the plan in chat on 2026-10-13; the AI prepared the record)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | A concrete deliverable is defined for every gate | Pass | The Deliverable section names the reusable workflow, the caller template, tests, the superseding ADR, the moved wiki and the release. |
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Six criteria, each with a checkable Go and No-Go (tests, a read-back with 0 differences, the `v1` tag). |
| 3 | Dependencies on other milestones are explicitly mapped | Pass | MIL-006, and the framework's `process/wiki.md`, which is still unmerged and is named as such. |
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | O2 and O3 of [BC-001], as for MIL-006. |
| 5 | Milestone owner and approving reviewer are identified | Pass | Owner S05, approving reviewer S01. |
| 6 | Milestone has a defined target date consistent with project constraints | Pass | 2026-11-15; the Business Case sets no deadline and the plan's last decision is 2026-12-26. |
## Overall Verdict
Go - all six criteria pass. The release is a patch (1.2.1 to 1.2.2) by the owner's decision, although ADR-0006 classes a new workflow as minor and a removed one as breaking; task 7 records the deviation in the changelog, and S01 chose it over a minor release and over keeping the scoped workflow.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| Add MIL-008 to the Project Plan (milestone table, CrossReference, dependency graph) and review it | S05 | before the pull request |
| Merge `process/wiki.md` in `sqa-qc-framework` before this repository pins it | S05 | before task 3 ships |
| List the repositories that publish through the scoped workflow before task 5 removes it | S05 | before task 5 |
---
---
[MIL-008]: ../../milestones/mil-008-wiki-reusable-workflow.md
[QC-MIL-001]: ../../../framework/qc/artifacts/qc-milestones-gateways.md
[01b9c29]: https://git.tirsystem.com/TirSystem/github-action/commit/01b9c2972b4317bcc590540010aea0eaf7f30014
@@ -0,0 +1,54 @@
# Review Record: MIL-008 (task 5 deferred)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-061 |
| CrossReference | [MIL-008], [QC-MIL-001], [RC-060] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | [01b9c29] |
---
## Artifact Under Review
- Instance reviewed: [MIL-008] after the amendment of 2026-10-13: task 5 changed from removing the scoped workflow to deprecating it, with the criterion that follows from it
- Checklist used: [QC-MIL-001]
- Scope: delta re-review of the amended task 5, criterion 5 and the Deliverable; the earlier record is [RC-060]
- Language and domain: n/a (technical type)
- Language reviewer: none
- Mode: supervised (S01, the user, chose option 1, defer the removal, in chat on 2026-10-13; the AI prepared the record)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | A concrete deliverable is defined for every gate | Pass | The Deliverable now says the scoped workflow is marked deprecated, not removed. |
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Criterion 5 is checkable: a notice in the header, the README and ADR-0009, and the list of repositories recorded; No-Go if it was removed while a repository depends on it. |
| 3 | Dependencies on other milestones are explicitly mapped | Pass | Unchanged: MIL-006 and the framework's `process/wiki.md`. |
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | Unchanged: O2, O3. |
| 5 | Milestone owner and approving reviewer are identified | Pass | Owner S05, approving reviewer S01. |
| 6 | Milestone has a defined target date consistent with project constraints | Pass | 2026-11-15, unchanged. |
## Overall Verdict
Go - all six criteria pass. The amendment follows from a scan on 2026-10-13: 8 repositories still publish through the scoped workflow and none calls the reusable one, so removal would have broken them. Removal and the migrations move to a follow-up milestone that needs its own plan.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| Plan the follow-up milestone: migrate the 8 repositories (move `wiki/` to `docs/wiki/`, add the caller from `templates/ci/publish-wiki.yml`), then remove the scoped workflow in a major release | S05 | after the 1.2.2 release |
| Add MIL-008 to the Project Plan and review it (open since RC-060) | S05 | before the pull request |
---
---
[MIL-008]: ../../milestones/mil-008-wiki-reusable-workflow.md
[QC-MIL-001]: ../../../framework/qc/artifacts/qc-milestones-gateways.md
[RC-060]: ./rc-060-mil-008-plan.md
[01b9c29]: https://git.tirsystem.com/TirSystem/github-action/commit/01b9c2972b4317bcc590540010aea0eaf7f30014
+54
View File
@@ -0,0 +1,54 @@
# Review Record: PP-001 Project Plan (MIL-008)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-062 |
| CrossReference | [PP-001], [BC-001], [MIL-008] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | [9aeb74e] |
---
## Artifact Under Review
- Instance reviewed: [PP-001]
- Checklist used: none: the Project Plan has no QC checklist. Validated against the Business Case [BC-001] and the Go / No-Go criteria of each listed milestone, as `PP.md` prescribes.
- Scope: delta re-review for the added MIL-008 row, Gantt entries, Scope Coverage, Dependencies, one Plan Risk and the counts; the earlier record is RC-053
- Language and domain: n/a (technical type)
- Mode: supervised (the AI prepared the record; S01, the user, gave the Go in chat on 2026-10-13)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | Every milestone in the plan has a `MIL-*` document and the plan's dates equal that document's target date | Pass | MIL-001 to MIL-008 exist; the MIL-008 decision date 2026-11-15 equals its Target Date. |
| 2 | Every Business Case scope item is covered by a gateway | Pass | The wiki publisher shared with other repositories is covered by MIL-006 and MIL-008 (O2, O3). |
| 3 | The schedule respects the Business Case constraints | Pass | The Business Case sets no deadline; the MIL-008 window overlaps the October and November windows and says so under Planning Assumptions. |
| 4 | Dependencies between milestones are stated | Pass | MIL-006 to MIL-008 is in the Dependencies diagram and in MIL-008's own document. |
| 5 | Every gateway has an owner who is a stakeholder ID | Pass | MIL-008 is owned by S05. |
| 6 | Plan content is internally consistent | Pass | "Eight milestones", the Planning Assumptions, the Gantt entries, the Gateway Schedule and the Scope Coverage agree. |
| 7 | Open Issues are current | Pass | Unchanged items remain open; a plan risk was added for the repositories that still use the scoped workflow. |
## Overall Verdict
Go - all seven criteria pass; no findings. S01 gave the Go in chat on 2026-10-13, after the change was merged as pull request 137 with the plan row `Proposed`; this record and the plan row are set to `Accepted` in the follow-up.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| Set PP-001 and this record to `Accepted` (done in the follow-up that follows pull request 137) | S05 | 2026-10-13 |
| Review ADR-0009 (still without a review record) | S01 | after the plan |
---
---
[PP-001]: ../../project-plan.md
[BC-001]: ../../business-case.md
[MIL-008]: ../../milestones/mil-008-wiki-reusable-workflow.md
[9aeb74e]: https://git.tirsystem.com/TirSystem/github-action/commit/9aeb74e8b09bb7882847e9bd5d569dbc9856fc03
+56
View File
@@ -0,0 +1,56 @@
# Review Record: ADR-0009 Wiki publishing as a reusable workflow
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-063 |
| CrossReference | [ADR-0009], [QC-ADR-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-13 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version | [8c2c52e] |
---
## Artifact Under Review
- Instance reviewed: [ADR-0009]
- Checklist used: [QC-ADR-001]
- Scope: full review
- Language and domain: n/a (technical type)
- Mode: supervised (the AI prepared the record; S01, the user, gave the Go in chat on 2026-10-13)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | The ID (`ADR-NNNN`, 4-digit) matches the filename number and is unique and sequential | Pass | ADR-0009 in `adr-0009-wiki-publishing-reusable-workflow.md`; follows ADR-0008. |
| 2 | Context states the problem, the forces (cost, risk, constraints) and the options evaluated | Pass | Names the scoped delivery, its gate, the fixed trigger path and version, and the new folder rule. |
| 3 | At least two options were considered, or the absence of alternatives is justified | Pass | Two options: keep the scoped workflow and read both folders, or a reusable workflow. |
| 4 | Decision is stated in one or two unhedged sentences and matches one of the evaluated options | Pass | Two sentences; matches option 2. The rules below it add detail only. |
| 5 | Consequences list both Positive and Negative outcomes | Pass | Finding fixed during this review: the consequence "passing a declared secret is not yet verified" was out of date, because run 316 verified it on 2026-10-13. It now says so. Four positive and four negative outcomes. |
| 6 | Quality-attribute impacts (performance, security, portability, etc.) are named where the decision affects them | Pass | Finding fixed during this review: the security impact was implicit. A positive bullet (explicit secret, no fallback) and a negative bullet (whoever changes the caller changes the folder and `prune`) were added. |
| 7 | Affected Artifacts are listed as links, and each is also in CrossReference | Pass | MIL-008 and ADR-0004 are linked and cited. |
| 8 | Status changes are recorded as new Version History rows with a Change summary and Commit link; retained rows are never rewritten | Pass | The findings fix is a new row; the first row is kept unchanged. |
| 9 | A superseded or deprecated ADR names its successor, and the successor links back | N-A | ADR-0004 is superseded only in part and stays in force until the scoped workflow is removed; see the action item. |
| 10 | Written in business or technical language of the domain, with no unexplained abbreviations | Pass | Finding fixed during this review: "PO language" is now "the product owner's language". |
| 11 | Status is one of `Proposed`, `Accepted`, `Rejected`, `Deprecated` or `Superseded by ADR-NNNN`, and every Version History row uses only these | Pass | `Proposed` only. |
## Overall Verdict
Go - all applicable criteria pass; the three findings (criteria 5, 6 and 10) were fixed during the review. S01 gave the Go in chat on 2026-10-13; there is no independent reader.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| When the scoped workflow is removed, mark ADR-0004 superseded by ADR-0009 for the delivery and marker parts (new version row and review record) | S01 | the follow-up milestone |
---
---
[ADR-0009]: ../../adr/adr-0009-wiki-publishing-reusable-workflow.md
[QC-ADR-001]: ../../../framework/qc/artifacts/qc-adr.md
[8c2c52e]: https://git.tirsystem.com/TirSystem/github-action/commit/8c2c52ebd2a27e4d0edb4008c31c002718d280fb
+63
View File
@@ -0,0 +1,63 @@
# Review Record: MIL-009 and PP-001 (MIL-009)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-064 |
| CrossReference | [MIL-009], [PP-001], [BC-001], [QC-MIL-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version; accepted | [86e69ce] |
---
## Artifact Under Review
- Instance reviewed: [MIL-009]; delta of [PP-001] for the added milestone
- Checklist used: [QC-MIL-001] for the milestone; for the plan, the criteria of `PP.md` (no QC checklist exists for PP-001)
- Scope: full review of MIL-009; delta re-review of PP-001, earlier record RC-062
- Language and domain: n/a (technical type)
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; the user accepted the plan in chat on 2026-10-14 with the defaults)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | A concrete deliverable is defined for every gate | Pass | MIL-009 lists the released framework, seven callers, the removals, the superseded ADR and 2.0.0. |
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Seven criteria with checkable Go and No-Go (tag, callers, read-backs, scan, removal, ADR rows, tags). |
| 3 | Dependencies on other milestones are explicitly mapped | Pass | MIL-008 and the framework release; in the plan's Dependencies diagram (`MIL-008 -> MIL-009`). |
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | O2, O3 of [BC-001]. |
| 5 | Milestone owner and approving reviewer are identified | Pass | Owner S05, approving reviewer S01. |
| 6 | Milestone has a defined target date consistent with project constraints | Pass | 2026-11-30 is proposed and equals the plan's decision date; the Business Case sets no deadline. S01 has not confirmed it, as MIL-009 and the plan say; this is an action item. |
## Plan delta (PP-001)
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | Every milestone has a `MIL-*` document and the plan's dates equal that document's target date | Pass | MIL-001 to MIL-009 exist; the MIL-009 decision date equals its Target Date. |
| 2 | Every Business Case scope item is covered by a gateway | Pass | Scope Coverage names MIL-009 for the removal of the scoped publisher. |
| 3 | The schedule respects the Business Case constraints | Pass | No deadline in the Business Case; the window follows MIL-008. |
| 4 | Dependencies between milestones are stated | Pass | `MIL-006 -> MIL-008 -> MIL-009`. |
| 5 | Every gateway has an owner who is a stakeholder ID | Pass | S05. |
| 6 | Plan content is internally consistent | Pass | "Nine milestones", the assumptions, the Gantt, the schedule and the coverage agree. |
| 7 | Open Issues are current | Pass | A plan risk was added for missing organisation secrets and failed first runs. |
## Overall Verdict
Go - all criteria pass. The user switched to autonomous mode and accepted the plan in chat on 2026-10-14 with the defaults (target date 2026-11-30, no publishing for `027-sandbox` and `028-pomodoro`, framework release 0.7.0); the AI records the acceptance as stand-in for S01. There is no independent reader and the decision stays open to challenge in the run summary.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| Check that `TirSystem-BashScript` has its own `TOKEN_FOR_GITEA_WIKI` (S03); stop and report if it does not | S03 | MIL-009 task 3 |
---
[MIL-009]: ../../milestones/mil-009-migrate-wiki-callers-and-remove-scoped-workflow.md
[PP-001]: ../../project-plan.md
[BC-001]: ../../business-case.md
[QC-MIL-001]: ../../../framework/qc/artifacts/qc-milestones-gateways.md
[86e69ce]: https://git.tirsystem.com/TirSystem/github-action/commit/86e69ce85df72ea3caed40253e08231c73187fde
@@ -0,0 +1,56 @@
# Review Record: ADR-0004 superseded by ADR-0009
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-065 |
| CrossReference | [ADR-0004], [ADR-0009], [QC-ADR-001], [MIL-009] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | [22fc323] |
---
## Artifact Under Review
- Instance reviewed: [ADR-0004] after the status change to `Superseded by ADR-0009` on 2026-10-14
- Checklist used: [QC-ADR-001]
- Scope: delta re-review of the criteria the status change touches (7, 8, 9, 11); the earlier record is RC-018
- Language and domain: n/a (technical type)
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; the user accepted the plan [MIL-009] in chat on 2026-10-14)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | The ID matches the filename number and is unique and sequential | Pass | Unchanged. |
| 2 | Context states the problem, the forces and the options evaluated | Pass | Unchanged. |
| 3 | At least two options were considered | Pass | Unchanged. |
| 4 | Decision is stated in one or two unhedged sentences and matches an evaluated option | Pass | Unchanged. |
| 5 | Consequences list both Positive and Negative outcomes | Pass | Unchanged. |
| 6 | Quality-attribute impacts are named where the decision affects them | Pass | Unchanged. |
| 7 | Affected Artifacts are listed as links, and each is also in CrossReference | Pass | ADR-0009 is added to both. |
| 8 | Status changes are recorded as new Version History rows with a Change summary and Commit link; retained rows are never rewritten | Pass | The change is a new row; the earlier `Accepted` row became `Deprecated` and its content is unchanged. |
| 9 | A superseded or deprecated ADR names its successor, and the successor links back | Pass | The status names ADR-0009; ADR-0009 lists ADR-0004 under Affected Artifacts and in CrossReference. This closes the N-A of RC-018. |
| 10 | Written in the language of the domain, with no unexplained abbreviations | Pass | Unchanged. |
| 11 | Status is one of the allowed values and every row uses only these | Pass | `Deprecated` and `Superseded by ADR-0009`. |
## Overall Verdict
Go - all criteria pass. The scoped delivery and the marker are replaced and removed (MIL-009 tasks 2 to 6); the token and empty-folder rules of this ADR stand and are carried by ADR-0009. The AI records the acceptance as stand-in for S01; there is no independent reader, and the decision stays open to challenge in the run summary.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| None open | - | - |
---
[ADR-0004]: ../../adr/adr-0004-shared-wiki-publishing-opt-in.md
[ADR-0009]: ../../adr/adr-0009-wiki-publishing-reusable-workflow.md
[QC-ADR-001]: ../../../framework/qc/artifacts/qc-adr.md
[MIL-009]: ../../milestones/mil-009-migrate-wiki-callers-and-remove-scoped-workflow.md
[22fc323]: https://git.tirsystem.com/TirSystem/github-action/commit/22fc323b41074ee0029ea2f07445a8e329e15d15
@@ -0,0 +1,52 @@
# Review Record: MIL-009 outcome
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-066 |
| CrossReference | [MIL-009], [QC-MIL-001], [RC-064], [ADR-0004] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version | [95abc18] |
---
## Artifact Under Review
- Instance reviewed: [MIL-009] after the verification results were recorded on 2026-10-14
- Checklist used: [QC-MIL-001]
- Scope: delta re-review of the Go / No-Go outcome; the earlier record is [RC-064]
- Language and domain: n/a (technical type)
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; the user accepted the plan in chat on 2026-10-14)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | A concrete deliverable is defined for every gate | Pass | Delivered: framework 0.7.0, seven callers, removals, ADR-0004 superseded, 2.0.0. |
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | All seven criteria have a recorded result, each with evidence. |
| 3 | Dependencies on other milestones are explicitly mapped | Pass | MIL-008 and the framework release were met first. |
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | O2, O3, unchanged. |
| 5 | Milestone owner and approving reviewer are identified | Pass | S05 and S01. |
| 6 | Milestone has a defined target date consistent with project constraints | Pass | Target 2026-11-30; the gate was met on 2026-10-14. |
## Overall Verdict
Go - the gate is met: all seven criteria are Go (criterion 5 with a recorded note that the marker text remains in history records and the upgrade note). The AI records the outcome as stand-in for S01; there is no independent reader, and the decision stays open to challenge in the run summary.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| Fix the two failing tests of `repo-foundry` on `main` (the checklist layout of its test fixture); not part of this milestone | S05 | not planned |
| Move the `framework` pin of `github-action` itself from `v0.6.2` to `v0.7.0` | S05 | not planned |
---
[MIL-009]: ../../milestones/mil-009-migrate-wiki-callers-and-remove-scoped-workflow.md
[QC-MIL-001]: ../../../framework/qc/artifacts/qc-milestones-gateways.md
[RC-064]: ./rc-064-mil-009-plan.md
[ADR-0004]: ../../adr/adr-0004-shared-wiki-publishing-opt-in.md
[95abc18]: https://git.tirsystem.com/TirSystem/github-action/commit/95abc18a95d70c065b979123b8f8de22173af90c
+52
View File
@@ -0,0 +1,52 @@
# Review Record: BC-001 (objective O5, code metrics and coverage badges)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-067 |
| CrossReference | [BC-001], [QC-BC-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version; accepted | [5002450] |
---
## Artifact Under Review
- Instance reviewed: [BC-001]
- Checklist used: [QC-BC-001]
- Scope: delta re-review of criteria 3, 4 and 7 (the change adds objective O5, an In Scope bullet and success criterion 5); criteria 1, 2, 5, 6 and 8 are re-read and unchanged. Earlier record: RC-008
- Language and domain: n/a for this delta (see the action item about the Language and Domain rows)
- Language reviewer: none
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; the user accepted the plan in chat on 2026-10-14)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | ROI/Cost-Benefit analysis is quantitative, or where qualitative, is explicitly justified | Pass | Unchanged; the Cost-Benefit Assessment labels its qualitative form. |
| 2 | Risks are identified with documented impact and mitigation | Pass | Unchanged. The delivery risk of the badges is a plan risk in PP-001, with a mitigation. |
| 3 | Success criteria are measurable, stating explicit targets rather than vague aspirations | Pass | Criterion 5: "At least 1 besides this one by MIL-010", measured in the README of the consuming repository. |
| 4 | Scope explicitly separates In Scope vs Out of Scope | Pass | "A shared workflow for code metrics and coverage badges (MIL-010)" is a new In Scope bullet; Out of Scope is unchanged and does not contradict it ("application code of consuming repositories" is not read or changed by the counter). |
| 5 | Stakeholders are cross-referenced to Stakeholder Analysis IDs rather than re-described inline | Pass | Unchanged; the stakeholders table uses S-IDs. |
| 6 | Methodology and quality-standard foundation are stated explicitly (e.g. ISO/IEC 25010, Larman) | Pass | Unchanged. |
| 7 | Assumptions and constraints are explicit and clearly distinguished from one another | Pass | Unchanged. The constraint "tools available on the runner" still holds: the counter uses the Python standard library. |
| 8 | Document supports executive decision-making with a clear, unambiguous recommendation | Pass | Unchanged: Proceed. |
## Overall Verdict
Go - all eight criteria pass for the revision that adds objective O5, its scope item and success criterion 5. The user accepted the plan in chat on 2026-10-14; the AI records the acceptance as stand-in for S01. There is no independent reader and the decision stays open to challenge in the run summary.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| The Metadata table of BC-001 has no `Language` and `Domain` rows (QC-LANG-001 criterion 1); this predates this change, and no earlier record applies QC-LANG-001 to it. Add the rows or record in an ADR why they are not needed | S01 | Before the next material change to BC-001 |
---
[BC-001]: ../../business-case.md
[QC-BC-001]: ../../../framework/qc/artifacts/qc-business-case.md
[5002450]: https://git.tirsystem.com/TirSystem/github-action/commit/50024505fe788b1c5e3021c0d1fc3d079590070e
+63
View File
@@ -0,0 +1,63 @@
# Review Record: MIL-010 and PP-001 (MIL-010)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-068 |
| CrossReference | [MIL-010], [PP-001], [BC-001], [QC-MIL-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version; accepted | [5002450] |
---
## Artifact Under Review
- Instance reviewed: [MIL-010]; delta of [PP-001] for the added milestone
- Checklist used: [QC-MIL-001] for the milestone; for the plan, the criteria of `PP.md` (no QC checklist exists for PP-001)
- Scope: full review of MIL-010; delta re-review of PP-001, earlier record RC-064
- Language and domain: n/a (technical type)
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; the user accepted the plan in chat on 2026-10-14)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | A concrete deliverable is defined for every gate | Pass | MIL-010 names the reusable workflow `code-metrics.yml`, the three SVG badges on a `badges` branch, the guide, three wiki languages, one adopter and a release. |
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Nine criteria, each with a checkable Go and No-Go (push and render, ADR, fixture counts, bad report, valid stable SVG, no loop, adopter, no embedded script, release). |
| 3 | Dependencies on other milestones are explicitly mapped | Pass | MIL-003 and MIL-004; in the plan's Dependencies diagram (`MIL-003 + MIL-004 -> MIL-010`). |
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | O5 of [BC-001], reviewed in RC-067. |
| 5 | Milestone owner and approving reviewer are identified | Pass | Owner S05, approving reviewer S01. |
| 6 | Milestone has a defined target date consistent with project constraints | Pass | 2027-01-30 follows MIL-007 (2027-01-16); the Business Case sets no deadline; the user accepted it in chat. |
## Plan delta (PP-001)
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | Every milestone has a `MIL-*` document and the plan's dates equal that document's target date | Pass | MIL-001 to MIL-010 exist; the MIL-010 decision date equals its Target Date. |
| 2 | Every Business Case scope item is covered by a gateway | Pass | Scope Coverage names MIL-010 for the new In Scope bullet. |
| 3 | The schedule respects the Business Case constraints | Pass | No deadline in the Business Case; the window follows MIL-007. |
| 4 | Dependencies between milestones are stated | Pass | `MIL-003 + MIL-004 -> MIL-010`. |
| 5 | Every gateway has an owner who is a stakeholder ID | Pass | S05. |
| 6 | Plan content is internally consistent | Pass | "Ten milestones", the assumptions, the Gantt, the schedule and the coverage agree. |
| 7 | Open Issues are current | Pass | A plan risk was added for the push to the `badges` branch and the rendering on the mirror, with the fallback. |
## Overall Verdict
Go - all criteria pass. The user switched to autonomous mode and accepted the plan in chat on 2026-10-14 (Cobertura XML coverage input, target date 2027-01-30, three badges); the AI records the acceptance as stand-in for S01. There is no independent reader and the decision stays open to challenge in the run summary.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| None | - | - |
---
[MIL-010]: ../../milestones/mil-010-code-metrics-badges.md
[PP-001]: ../../project-plan.md
[BC-001]: ../../business-case.md
[QC-MIL-001]: ../../../framework/qc/artifacts/qc-milestones-gateways.md
[5002450]: https://git.tirsystem.com/TirSystem/github-action/commit/50024505fe788b1c5e3021c0d1fc3d079590070e
+56
View File
@@ -0,0 +1,56 @@
# Review Record: UC-008 (Show Code Metrics and Coverage Badges)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-069 |
| CrossReference | [UC-008], [QC-UC-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version; accepted | [9ca9673] |
---
## Artifact Under Review
- Instance reviewed: [UC-008]
- Checklist used: [QC-UC-001]
- Scope: full review (Brief format; MIL-010 task 1)
- Language and domain: n/a (technical type)
- Language reviewer: none
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; the user accepted the plan in chat on 2026-10-14)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | Consists of a single, concise paragraph summarizing only the primary success scenario | Pass | One paragraph: push, count, read the report, publish three badges, README shows them. |
| 2 | Written as an informal multi-paragraph narrative; may mention some alternate flows without formal structure | N-A | Format is Brief. |
| 3 | All standard sections are present: actors, preconditions, postconditions, main success scenario, alternative/exception flows | N-A | Format is Brief; actor, stakeholders, pre- and postconditions and the paragraph are present. |
| 4 | Preconditions and postconditions are explicitly defined | Pass | Both are stated; the postcondition says that only the badges change. |
| 5 | Primary actor is explicitly stated | Pass | Software Engineer. |
| 6 | Stakeholders and their interests are stated | Pass | S02 and S05 with an interest each. |
| 7 | Main success scenario is written as clear, numbered steps | N-A | Format is Brief. |
| 8 | Alternative/exception flows correctly reference `<<include>>`/`<<extend>>` use cases where relevant, per UML 2.5.1 | N-A | None in a Brief. |
| 9 | Explicit business rules are captured per step where applicable, rather than embedded loosely in narrative text | N-A | No steps in a Brief. |
| 10 | Naming of actors and use case title is consistent with the corresponding Use Case Diagram and User Stories | Pass | Title and actor equal UCD-001 (RC-071); the Stories line lists US-001.18 to US-001.20, which exist (RC-070). |
| 11 | Scope/level (e.g. summary, user-goal, subfunction) is explicitly stated | Pass | Scope Reusable Workflow Collection, user-goal level. |
| 12 | Use case is written from the actor's goal perspective, free of UI or implementation detail | Pass | Goal wording; no tool, file name or format of a report is named. |
## Overall Verdict
Go - all applicable criteria pass. The AI records the decision as stand-in for S01; there is no independent reader and the decision stays open to challenge in the run summary.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| None | - | - |
---
[UC-008]: ../../uc-008/uc.md
[QC-UC-001]: ../../../framework/qc/artifacts/qc-use-case.md
[9ca9673]: https://git.tirsystem.com/TirSystem/github-action/commit/9ca9673b00351cf913966f347c4da09890ed3f1f
+51
View File
@@ -0,0 +1,51 @@
# Review Record: US-001 (stories US-001.18 to US-001.20)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-070 |
| CrossReference | [US-001], [QC-US-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version; accepted | [9ca9673] |
---
## Artifact Under Review
- Instance reviewed: [US-001]
- Checklist used: [QC-US-001]
- Scope: delta re-review of the added stories US-001.18 to US-001.20 and the Purpose and Scope and INVEST sections; earlier record RC-057 (US-001.01 to US-001.17 unchanged)
- Language and domain: n/a (technical type)
- Language reviewer: none
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; the user accepted the plan in chat on 2026-10-14)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | Follows INVEST criteria (Independent, Negotiable, Valuable, Estimable, Small, Testable) | Pass | US-001.18 to US-001.20 are independent of each other, each states one value and has checkable criteria; the INVEST section of US-001 now counts twenty stories. |
| 2 | Written in "As a / I want / So that" form | Pass | All three stories. |
| 3 | Clear, testable acceptance criteria are included | Pass | Each has Given/When/Then criteria with a pass and a fail case (a missing report, no files, unchanged numbers). |
| 4 | Traceable to a use case or epic | Pass | Each traces to [UC-008] and [MIL-010]. |
| 5 | Story is sized to fit within a single iteration | Pass | Each says: fits one iteration. |
| 6 | Story statement avoids technical implementation detail | Pass | The statements name badges, files, lines and coverage; format and branch names are left to the contract. |
| 7 | Role named in the story matches an actor defined in the Use Case Diagram | Pass | Software Engineer, an actor of UCD-001. |
## Overall Verdict
Go - all criteria pass. The AI records the decision as stand-in for S01; there is no independent reader and the decision stays open to challenge in the run summary.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| None | - | - |
---
[US-001]: ../../user-stories.md
[QC-US-001]: ../../../framework/qc/artifacts/qc-user-story.md
[9ca9673]: https://git.tirsystem.com/TirSystem/github-action/commit/9ca9673b00351cf913966f347c4da09890ed3f1f
@@ -0,0 +1,52 @@
# Review Record: UCD-001 (Show Code Metrics and Coverage Badges)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-071 |
| CrossReference | [UCD-001], [QC-UCD-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version; accepted | [9ca9673] |
---
## Artifact Under Review
- Instance reviewed: [UCD-001]
- Checklist used: [QC-UCD-001]
- Scope: delta re-review of the added use case UC8, the actor and use case tables; earlier record RC-045
- Language and domain: n/a (technical type)
- Language reviewer: none
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; the user accepted the plan in chat on 2026-10-14)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | Actors are defined with correct UML stereotypes (e.g. `<<System>>`, `<<Actor>>`) | Pass | Unchanged; no new actor. |
| 2 | System boundary is clearly drawn and labeled | Pass | The new use case is inside the Reusable Workflow Collection rectangle. |
| 3 | Include/extend relationships are used correctly per UML 2.5.1, not as generic "uses" arrows | Pass | The new use case has no include or extend; the existing two are unchanged. |
| 4 | Every actor participates in at least one use case (no orphan actors) | Pass | Software Engineer is linked to UC5, UC7 and UC8. |
| 5 | Diagram is traceable to a documented stakeholder need | Pass | S02 and S05 interests are in UC-008; the actor table lists S02. |
| 6 | Use case names are verb phrases describing actor goals, not internal system operations | Pass | Show Code Metrics and Coverage Badges. |
| 7 | Diagram is free of implementation detail (e.g. UI widgets, database tables) | Pass | None added. |
| 8 | Actor and use case naming is consistent with corresponding Use Case and User Story documents | Pass | Equals the title of UC-008 and the role of US-001.18 to US-001.20. |
## Overall Verdict
Go - all criteria pass. The AI records the decision as stand-in for S01; there is no independent reader and the decision stays open to challenge in the run summary.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| None | - | - |
---
[UCD-001]: ../../use-case-diagram.md
[QC-UCD-001]: ../../../framework/qc/artifacts/qc-use-case-diagram.md
[9ca9673]: https://git.tirsystem.com/TirSystem/github-action/commit/9ca9673b00351cf913966f347c4da09890ed3f1f
+55
View File
@@ -0,0 +1,55 @@
# Review Record: ADR-0010 (contract of the code metrics and coverage badges workflow)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-072 |
| CrossReference | [ADR-0010], [QC-ADR-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version; accepted | [aa4ca0f] |
---
## Artifact Under Review
- Instance reviewed: [ADR-0010]
- Checklist used: [QC-ADR-001]
- Scope: full review (MIL-010 task 2)
- Language and domain: n/a (technical type)
- Language reviewer: none
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; the user accepted the plan in chat on 2026-10-14)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | The ID (`ADR-NNNN`, 4-digit) matches the filename number and is unique and sequential | Pass | ADR-0010 in `adr-0010-code-metrics-badges-contract.md`; the previous ADR is ADR-0009. |
| 2 | Context states the problem, the forces (cost, risk, constraints) and the options evaluated | Pass | Problem, the decisions of the user, facts about the runner and the checkout directory, two counter options and three delivery options. |
| 3 | At least two options were considered, or the absence of alternatives is justified | Pass | Wrap an existing counter or write one; run artifact, default branch or a branch. |
| 4 | Decision is stated in one or two unhedged sentences and matches one of the evaluated options | Pass | A reusable workflow with three script files and a `badges` branch; the rules follow as a list. |
| 5 | Consequences list both Positive and Negative outcomes | Pass | Four positive, five negative. |
| 6 | Quality-attribute impacts (performance, security, portability, etc.) are named where the decision affects them | Pass | Security (a write token, a moving `tools-ref`, branch name validation), compatibility (report formats), maintainability (language table). |
| 7 | Affected Artifacts are listed as links, and each is also in CrossReference | Pass | MIL-010, UC-008, US-001, ADR-0005, ADR-0006 in both. |
| 8 | Status changes are recorded as new Version History rows with a Change summary and Commit link; retained rows are never rewritten | Pass | One row, Accepted, commit `pending` until the commit exists. |
| 9 | A superseded or deprecated ADR names its successor, and the successor links back | N-A | Neither superseded nor deprecated. |
| 10 | Written in business or technical language of the domain, with no unexplained abbreviations | Pass | SVG, XML and NUL are standard; Cobertura is named as a format. |
| 11 | Status is one of `Proposed`, `Accepted`, `Rejected`, `Deprecated` or `Superseded by ADR-NNNN`, and every Version History row uses only these | Pass | Accepted. |
## Overall Verdict
Go - all applicable criteria pass; the decisions of the user of 2026-10-14 are recorded and not reopened. The AI records the decision as stand-in for S01; there is no independent reader and the decision stays open to challenge in the run summary.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| None | - | - |
---
[ADR-0010]: ../../adr/adr-0010-code-metrics-badges-contract.md
[QC-ADR-001]: ../../../framework/qc/artifacts/qc-adr.md
[aa4ca0f]: https://git.tirsystem.com/TirSystem/github-action/commit/aa4ca0f897b7ea20d90745aa4b7d7a215df123a8
@@ -0,0 +1,55 @@
# Review Record: ADR-0010 (the coverage report as an artifact of the caller)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-073 |
| CrossReference | [ADR-0010], [QC-ADR-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version; accepted | [0dc5132] |
---
## Artifact Under Review
- Instance reviewed: [ADR-0010]
- Checklist used: [QC-ADR-001]
- Scope: delta re-review of the Coverage report and Inputs rules, the Consequences and the Version History after the implementation of MIL-010 task 6 showed that a called workflow cannot see the files of the caller's test job; earlier record RC-072
- Language and domain: n/a (technical type)
- Language reviewer: none
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; the user accepted the plan in chat on 2026-10-14)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | The ID (`ADR-NNNN`, 4-digit) matches the filename number and is unique and sequential | Pass | Unchanged. |
| 2 | Context states the problem, the forces (cost, risk, constraints) and the options evaluated | Pass | Unchanged; the new force (a called workflow runs in its own job) is stated in the Decision rule that it explains. |
| 3 | At least two options were considered, or the absence of alternatives is justified | Pass | Unchanged. For the report: an artifact of the caller's run, or a file committed to the repository; both are supported. |
| 4 | Decision is stated in one or two unhedged sentences and matches one of the evaluated options | Pass | The Decision sentence is unchanged; the coverage rule is a refinement of it. |
| 5 | Consequences list both Positive and Negative outcomes | Pass | The negative consequence about the report now names the earlier job, the upload and the dependency on the artifact service, which task 8 verifies. |
| 6 | Quality-attribute impacts (performance, security, portability, etc.) are named where the decision affects them | Pass | The security bullet says the artifact is parsed as XML with entity declarations refused. |
| 7 | Affected Artifacts are listed as links, and each is also in CrossReference | Pass | Unchanged. |
| 8 | Status changes are recorded as new Version History rows with a Change summary and Commit link; retained rows are never rewritten | Pass | A new row; the earlier Accepted row became Deprecated and was not rewritten otherwise. |
| 9 | A superseded or deprecated ADR names its successor, and the successor links back | N-A | The ADR is amended, not superseded. |
| 10 | Written in business or technical language of the domain, with no unexplained abbreviations | Pass | Artifact is the term of the Actions runtime. |
| 11 | Status is one of `Proposed`, `Accepted`, `Rejected`, `Deprecated` or `Superseded by ADR-NNNN`, and every Version History row uses only these | Pass | Deprecated and Accepted. |
## Overall Verdict
Go - all applicable criteria pass; the amendment keeps the decisions of the user of 2026-10-14 and corrects a mechanism that cannot work as first written. The AI records the decision as stand-in for S01; there is no independent reader and the decision stays open to challenge in the run summary.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| None | - | - |
---
[ADR-0010]: ../../adr/adr-0010-code-metrics-badges-contract.md
[QC-ADR-001]: ../../../framework/qc/artifacts/qc-adr.md
[0dc5132]: https://git.tirsystem.com/TirSystem/github-action/commit/0dc5132a2ea419ba05035ee3594cec63335c240b
@@ -0,0 +1,51 @@
# Review Record: MIL-010 (results of tasks 3, 6 and 8)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-074 |
| CrossReference | [MIL-010], [QC-MIL-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version; accepted | [5c5a1d1] |
---
## Artifact Under Review
- Instance reviewed: [MIL-010]
- Checklist used: [QC-MIL-001]
- Scope: delta re-review of the Verification results section (tasks 3, 6 and 8) and the Version History; the Go/No-Go criteria, deliverable and tasks are unchanged since RC-068
- Language and domain: n/a (technical type)
- Language reviewer: none
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; the user accepted the plan in chat on 2026-10-14)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | A concrete deliverable is defined for every gate | Pass | Unchanged; the results show the workflow, the three badges and a use in another repository. |
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Unchanged. Evidence so far: criterion 1 (task 3: push with the token of the run, SVG served as image on the instance and the mirror), 2 (ADR-0010, RC-072, RC-073), 3 and 4 (offline fixtures and reports), 5 (valid, stable SVG, offline tests), 6 (no run from the badge push, no commit when unchanged in offline tests, `contents: write` only), 7 (throwaway repository: badges, colour change, named failure), 8 (the workflow runs three script files, no embedded script). Criterion 9 (release) follows in task 9. |
| 3 | Dependencies on other milestones are explicitly mapped | Pass | Unchanged: MIL-003 and MIL-004. |
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | O5 and success criterion 5; criterion 5 is met in the throwaway repository and still wants a project repository. |
| 5 | Milestone owner and approving reviewer are identified | Pass | Unchanged: S05 and S01. |
| 6 | Milestone has a defined target date consistent with project constraints | Pass | Unchanged: 2027-01-30. |
## Overall Verdict
Go - the recorded results support the criteria 1 to 8; criterion 9 is decided after the release (task 9). The AI records the decision as stand-in for S01; there is no independent reader and the decision stays open to challenge in the run summary.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| Delete the throwaway repository `TirSystem/code-metrics-adoption` and the branches `verify-badges-delivery` and `badges-verify` of `TirSystem/github-action` (and their copies on the GitHub mirror); the run does not delete repositories or branches | S01 | after the review of the summary |
| Adopt the workflow in a project repository so that success criterion 5 of the Business Case is met by a real project | S01 | next project that has tests |
---
[MIL-010]: ../../milestones/mil-010-code-metrics-badges.md
[QC-MIL-001]: ../../../framework/qc/artifacts/qc-milestones-gateways.md
[5c5a1d1]: https://git.tirsystem.com/TirSystem/github-action/commit/5c5a1d19d96a6979b4f6141e736957174ba686a9
@@ -0,0 +1,52 @@
# Review Record: MIL-010 (outcome: release 2.1.0)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-075 |
| CrossReference | [MIL-010], [QC-MIL-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version; accepted | [aa20e56] |
---
## Artifact Under Review
- Instance reviewed: [MIL-010]
- Checklist used: [QC-MIL-001]
- Scope: delta re-review of the results of task 9 and the closing statement of the milestone; earlier records RC-068 and RC-074
- Language and domain: n/a (technical type)
- Language reviewer: none
- Mode: autonomous, not independent (the reviewer is `AI as stand-in for S01`; the user accepted the plan in chat on 2026-10-14)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | A concrete deliverable is defined for every gate | Pass | Delivered: `code-metrics.yml` with its three script files, the guide, the wiki pages in three languages and the release 2.1.0 (tag `v2.1.0`, major tag `v2`, release page). |
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | All nine criteria are met: 1 to 8 in the results of RC-074, 9 by the release of 2.1.0 (runs 1462 and 1463, the tag and the release page read back through the API). |
| 3 | Dependencies on other milestones are explicitly mapped | Pass | MIL-003 and MIL-004 were used: the reusable workflow mechanism, `tools-ref` and the release workflow. |
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | O5. Success criterion 5 is met only in a throwaway repository; a project repository still has to adopt it, recorded as an action item. |
| 5 | Milestone owner and approving reviewer are identified | Pass | S05 and S01. |
| 6 | Milestone has a defined target date consistent with project constraints | Pass | Target date 2027-01-30; the work was done on 2026-10-14, ahead of it. |
## Overall Verdict
Go - the milestone is complete: every Go/No-Go criterion is met and the workflow is released as 2.1.0. The AI records the decision as stand-in for S01; there is no independent reader and the decision stays open to challenge in the run summary.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| Adopt `code-metrics.yml` in a project repository, so that success criterion 5 is met by a real project | S01 | next project that has tests |
| Delete the throwaway repository `TirSystem/code-metrics-adoption` and the branches `verify-badges-delivery` and `badges-verify` (and their copies on the GitHub mirror) | S01 | after the review of the summary |
| Find where the required check `TirSystem/github-action: Publish wiki / *` comes from; it blocked the merge of pull request 164 through the API and is not in the branch protection of the repository | S03 | before the next pull request |
---
[MIL-010]: ../../milestones/mil-010-code-metrics-badges.md
[QC-MIL-001]: ../../../framework/qc/artifacts/qc-milestones-gateways.md
[aa20e56]: https://git.tirsystem.com/TirSystem/github-action/commit/aa20e56110f4c2fab91a8432ba008ee8300926a2
+67
View File
@@ -0,0 +1,67 @@
# Review Record: MIL-011 and PP-001 (MIL-011)
## Metadata
| Key | Value |
| --- | --- |
| ID | RC-076 |
| CrossReference | [MIL-011], [PP-001], [BC-001], [QC-MIL-001] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | S01 | Initial version; accepted by the author in chat as stand-in for S01, after three corrections found in the first read | [3d7a9df] |
---
## Artifact Under Review
- Instance reviewed: [MIL-011]; delta of [PP-001] for the added milestone
- Checklist used: [QC-MIL-001] for the milestone; for the plan, the criteria of `PP.md` (no QC checklist exists for PP-001)
- Scope: full review of MIL-011; delta re-review of PP-001, earlier record [RC-068]
- Language and domain: n/a (technical type)
- Mode: supervised, not independent (the author, Jens Tirsvad Nielsen, asked in chat on 2026-10-14 for the review and the acceptance and stands in for S01; the AI prepared the evidence)
## Checklist Results
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | A concrete deliverable is defined for every gate | Pass | `## Deliverable` names the patch release 2.1.1: the defaults, the test, the guides, README and three wiki languages at `@v2`, the changelog entry, and the framework `Badges` page without the workaround. |
| 2 | Explicit Go/No-Go criteria are stated for each gate | Pass | Eight criteria, each with a checkable Go and No-Go: reproduction, a list per workflow, a test that fails on a seeded default, a search that finds no `@v1`, a run on the instance, the release level and tags, the framework page, the review record. |
| 3 | Dependencies on other milestones are explicitly mapped | Pass | [MIL-010] (the workflow and its guide) and [ADR-0006] (the release process); in the plan's dependency diagram (`MIL-010 -> MIL-011`). |
| 4 | Each milestone is traceable to a Business Case objective or KPI | Pass | O5 of [BC-001]; the first read found its wording paraphrased wrongly, now quoted from the Business Case. |
| 5 | Milestone owner and approving reviewer are identified | Pass | Owner S05, approving reviewer S01; both are in [SA-001]. |
| 6 | Milestone has a defined target date consistent with project constraints | Pass | 2026-10-23; the Business Case sets no deadline. The date precedes the planned decision date of [MIL-010] (2027-01-30) because that milestone was released on 2026-10-14; the milestone says so and does not change the MIL-010 row. |
## Plan delta (PP-001)
| # | Criterion | Status | Evidence/Notes |
| --- | --- | --- | --- |
| 1 | Every milestone has a `MIL-*` document and the plan's dates equal that document's target date | Pass | MIL-001 to MIL-011 exist; the MIL-011 decision date (2026-10-23) equals its Target Date. |
| 2 | Every Business Case scope item is covered by a gateway | Pass | The first read found a scope row that is not a Business Case item; it is removed and MIL-011 is listed beside MIL-010 for the badges item. |
| 3 | The schedule respects the Business Case constraints | Pass | No deadline in the Business Case; the window is one week. |
| 4 | Dependencies between milestones are stated | Pass | `MIL-003 + MIL-004 -> MIL-010 -> MIL-011`. |
| 5 | Every gateway has an owner who is a stakeholder ID | Pass | S05. |
| 6 | Plan content is internally consistent | Pass | The first read found "ten milestones" in the Purpose; it is now "eleven", and the Gantt, the schedule and the dependencies agree. |
| 7 | Open Issues are current | Pass | No plan risk was added for a default that names an old major; the release process and the test of task 3 carry the rule. Observation, not a failure. |
## Overall Verdict
Go - all criteria pass after three corrections found in the first read (the O5 wording, "ten milestones", and a scope row that was not a Business Case item). The author confirmed the acceptance in chat on 2026-10-14 as stand-in for S01; this is not an independent review.
## Action Items
| Action | Owner | Due |
| --- | --- | --- |
| Confirm this record by S01 when an independent reading is possible | S01 | 2026-10-30 |
| Task 1: reproduce the failure and list the defaults before any change to the workflows | S05 | 2026-10-16 |
---
[MIL-011]: ../../milestones/mil-011-code-metrics-default-tools-ref.md
[PP-001]: ../../project-plan.md
[BC-001]: ../../business-case.md
[SA-001]: ../../stakeholder-analysis.md
[ADR-0006]: ../../adr/adr-0006-release-process-and-version-pinning.md
[RC-068]: ./rc-068-mil-010-plan.md
[QC-MIL-001]: ../../../framework/qc/artifacts/qc-milestones-gateways.md
[3d7a9df]: https://git.tirsystem.com/TirSystem/github-action/commit/3d7a9df3a3a4712dcc5214a71b0569d90122bf4e
+61 -12
View File
@@ -4,13 +4,13 @@
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| ID | TM-001 | | ID | TM-001 |
| CrossReference | [BC-001], [SA-001], [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [RC-001], [RC-002], [RC-003], [RC-004], [RC-005], [RC-006], [RC-007], [RC-008], [RC-009], [RC-010], [RC-011], [RC-012], [RC-013], [RC-014], [RC-015], [RC-016], [RC-017], [RC-018], [RC-019], [RC-020], [RC-021], [RC-022], [RC-023], [RC-024], [RC-025], [RC-026], [RC-027], [RC-028], [RC-029], [RC-030], [RC-031], [RC-032], [RC-033], [RC-034], [RC-035], [RC-036], [RC-037], [RC-038], [RC-039], [RC-040], [RC-041], [RC-042], [RC-043], [RC-044], [RC-045], [RC-046], [RC-047], [RC-048], [RC-049], [RC-050], [RC-051], [RC-052], [RC-053], [RC-054], [RC-055], [RC-056], [RC-057], [RC-058], [ADR-0007], [ADR-0008], [UC-007], [UC-006], [ADR-0006], [UC-005], [ADR-0005], [UCD-001], [US-001], [UC-001], [UC-002], [UC-003], [UC-004], [ADR-0001], [ADR-0002], [ADR-0003], [ADR-0004] | | CrossReference | [BC-001], [SA-001], [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [MIL-008], [MIL-009], [MIL-010], [MIL-011], [UC-008], [RC-001], [RC-002], [RC-003], [RC-004], [RC-005], [RC-006], [RC-007], [RC-008], [RC-009], [RC-010], [RC-011], [RC-012], [RC-013], [RC-014], [RC-015], [RC-016], [RC-017], [RC-018], [RC-019], [RC-020], [RC-021], [RC-022], [RC-023], [RC-024], [RC-025], [RC-026], [RC-027], [RC-028], [RC-029], [RC-030], [RC-031], [RC-032], [RC-033], [RC-034], [RC-035], [RC-036], [RC-037], [RC-038], [RC-039], [RC-040], [RC-041], [RC-042], [RC-043], [RC-044], [RC-045], [RC-046], [RC-047], [RC-048], [RC-049], [RC-050], [RC-051], [RC-052], [RC-053], [RC-054], [RC-055], [RC-056], [RC-057], [RC-058], [RC-059], [RC-060], [RC-061], [RC-062], [RC-063], [RC-064], [RC-065], [RC-066], [RC-067], [RC-068], [RC-069], [RC-070], [RC-071], [RC-072], [RC-073], [RC-074], [RC-075], [ADR-0007], [ADR-0008], [ADR-0009], [ADR-0010], [UC-007], [UC-006], [ADR-0006], [UC-005], [ADR-0005], [UCD-001], [US-001], [UC-001], [UC-002], [UC-003], [UC-004], [ADR-0001], [ADR-0002], [ADR-0003], [ADR-0004] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-11 | Deprecated | Jens Tirsvad Nielsen | S01 | Added RC-056 and RC-057 (UC-005 and US-001 for C++ and C#) | [1bc56f8] | | 2026-10-14 | Proposed | Jens Tirsvad Nielsen | S01 | Added RC-075 (MIL-010 outcome, release 2.1.0) | [aa20e56] |
| 2026-10-13 | Proposed | Jens Tirsvad Nielsen | S01 | Added RC-058 (MIL-006 task 6 amendment) | [8051cc4] | | 2026-10-14 | Proposed | Jens Tirsvad Nielsen | S01 | Added MIL-011 and RC-076 (fix the defaults of the code metrics workflow) | [3d7a9df] |
--- ---
@@ -25,12 +25,13 @@ updated whenever an artifact instance is created or reviewed.
| Artifact Instance | Type | Upstream (Backward Link) | Downstream (Forward Link) | Last Reviewed (RC-ID) | | Artifact Instance | Type | Upstream (Backward Link) | Downstream (Forward Link) | Last Reviewed (RC-ID) |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| [SA-001] | SA | - | [BC-001], [UCD-001] | [RC-004] | | [SA-001] | SA | - | [BC-001], [UCD-001] | [RC-004] |
| [BC-001] | BC | [SA-001] | [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006] | [RC-001], [RC-008] | | [BC-001] | BC | [SA-001] | [PP-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006] | [RC-001], [RC-008], [RC-067] |
| [PP-001] | PP | [BC-001], [SA-001] | [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [US-001] | [RC-002], [RC-009], [RC-015], [RC-017], [RC-025], [RC-032], [RC-053] | | [PP-001] | PP | [BC-001], [SA-001] | [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-005], [MIL-006], [MIL-007], [MIL-008], [MIL-009], [MIL-010], [US-001] | [RC-002], [RC-009], [RC-015], [RC-017], [RC-025], [RC-032], [RC-053], [RC-062], [RC-064], [RC-068] |
| [MIL-001] | MIL | [BC-001], [PP-001] | [MIL-002] | [RC-003], [RC-026] | | [MIL-001] | MIL | [BC-001], [PP-001] | [MIL-002] | [RC-003], [RC-026] |
| [MIL-002] | MIL | [BC-001], [PP-001], [MIL-001] | [MIL-003], [MIL-005] | [RC-005], [RC-027] | | [MIL-002] | MIL | [BC-001], [PP-001], [MIL-001] | [MIL-003], [MIL-005] | [RC-005], [RC-027] |
| [MIL-003] | MIL | [BC-001], [PP-001], [MIL-002] | [MIL-004] | [RC-006], [RC-028], [RC-033], [RC-039] | | [MIL-003] | MIL | [BC-001], [PP-001], [MIL-002] | [MIL-004] | [RC-006], [RC-028], [RC-033], [RC-039] |
| [MIL-004] | MIL | [BC-001], [PP-001], [MIL-003] | - | [RC-007], [RC-029], [RC-034], [RC-048] | | [MIL-004] | MIL | [BC-001], [PP-001], [MIL-003] | - | [RC-007], [RC-029], [RC-034], [RC-048] |
| [ADR-0010] | ADR | [MIL-010], [UC-008], [US-001], [ADR-0005], [ADR-0006] | - | [RC-072], [RC-073] |
| [RC-001] | RC | [BC-001] | - | - | | [RC-001] | RC | [BC-001] | - | - |
| [RC-002] | RC | [PP-001] | - | - | | [RC-002] | RC | [PP-001] | - | - |
| [RC-003] | RC | [MIL-001] | - | - | | [RC-003] | RC | [MIL-001] | - | - |
@@ -39,7 +40,11 @@ updated whenever an artifact instance is created or reviewed.
| [RC-006] | RC | [MIL-003] | - | - | | [RC-006] | RC | [MIL-003] | - | - |
| [MIL-005] | MIL | [BC-001], [PP-001], [MIL-002] | [MIL-006] | [RC-010], [RC-011], [RC-030], [RC-050] | | [MIL-005] | MIL | [BC-001], [PP-001], [MIL-002] | [MIL-006] | [RC-010], [RC-011], [RC-030], [RC-050] |
| [MIL-006] | MIL | [BC-001], [PP-001], [MIL-002], [MIL-005] | - | [RC-016], [RC-031], [RC-049], [RC-058] | | [MIL-006] | MIL | [BC-001], [PP-001], [MIL-002], [MIL-005] | - | [RC-016], [RC-031], [RC-049], [RC-058] |
| [MIL-007] | MIL | [BC-001], [PP-001], [MIL-003], [MIL-004], [ADR-0008] | - | [RC-052], [RC-055] | | [MIL-007] | MIL | [BC-001], [PP-001], [MIL-003], [MIL-004], [ADR-0008] | - | [RC-052], [RC-055], [RC-059] |
| [MIL-008] | MIL | [BC-001], [PP-001], [MIL-006], [ADR-0004] | [MIL-009] | [RC-060], [RC-061] |
| [MIL-009] | MIL | [BC-001], [PP-001], [MIL-008], [ADR-0004], [ADR-0006], [ADR-0009] | - | [RC-064], [RC-066] |
| [MIL-010] | MIL | [BC-001], [PP-001], [MIL-003], [MIL-004] | [MIL-011] | [RC-068], [RC-074], [RC-075] |
| [MIL-011] | MIL | [BC-001], [PP-001], [MIL-010], [ADR-0006], [ADR-0010] | - | [RC-076] |
| [RC-007] | RC | [MIL-004] | - | - | | [RC-007] | RC | [MIL-004] | - | - |
| [RC-008] | RC | [BC-001] | - | - | | [RC-008] | RC | [BC-001] | - | - |
| [RC-009] | RC | [PP-001] | - | - | | [RC-009] | RC | [PP-001] | - | - |
@@ -47,7 +52,7 @@ updated whenever an artifact instance is created or reviewed.
| [ADR-0001] | ADR | [BC-001], [MIL-005] | [ADR-0003] | [RC-012] | | [ADR-0001] | ADR | [BC-001], [MIL-005] | [ADR-0003] | [RC-012] |
| [ADR-0002] | ADR | [SA-001], [RC-001] | - | [RC-013] | | [ADR-0002] | ADR | [SA-001], [RC-001] | - | [RC-013] |
| [ADR-0003] | ADR | [MIL-005], [ADR-0001], [RC-011] | [ADR-0004] | [RC-014] | | [ADR-0003] | ADR | [MIL-005], [ADR-0001], [RC-011] | [ADR-0004] | [RC-014] |
| [ADR-0004] | ADR | [MIL-006], [ADR-0003], [ADR-0002] | - | [RC-018] | | [ADR-0004] | ADR | [MIL-006], [ADR-0003], [ADR-0002] | [ADR-0009] | [RC-018], [RC-065] |
| [RC-011] | RC | [MIL-005] | - | - | | [RC-011] | RC | [MIL-005] | - | - |
| [RC-012] | RC | [ADR-0001] | - | - | | [RC-012] | RC | [ADR-0001] | - | - |
| [RC-013] | RC | [ADR-0002] | - | - | | [RC-013] | RC | [ADR-0002] | - | - |
@@ -56,8 +61,8 @@ updated whenever an artifact instance is created or reviewed.
| [RC-016] | RC | [MIL-006] | - | - | | [RC-016] | RC | [MIL-006] | - | - |
| [RC-017] | RC | [PP-001] | - | - | | [RC-017] | RC | [PP-001] | - | - |
| [RC-018] | RC | [ADR-0004] | - | - | | [RC-018] | RC | [ADR-0004] | - | - |
| [UCD-001] | UCD | [SA-001], [BC-001] | [US-001], [UC-001], [UC-002], [UC-003], [UC-004], [UC-005], [UC-006], [UC-007] | [RC-019], [RC-035], [RC-040], [RC-045] | | [UCD-001] | UCD | [SA-001], [BC-001] | [US-001], [UC-001], [UC-002], [UC-003], [UC-004], [UC-005], [UC-006], [UC-007], [UC-008] | [RC-019], [RC-035], [RC-040], [RC-045], [RC-071] |
| [US-001] | US | [UCD-001], [BC-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-007] | [UC-001], [UC-002], [UC-003], [UC-004], [UC-005], [UC-006], [UC-007] | [RC-020], [RC-036], [RC-041], [RC-046], [RC-057] | | [US-001] | US | [UCD-001], [BC-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [MIL-007] | [UC-001], [UC-002], [UC-003], [UC-004], [UC-005], [UC-006], [UC-007], [UC-008] | [RC-020], [RC-036], [RC-041], [RC-046], [RC-057], [RC-070] |
| [UC-001] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-021] | | [UC-001] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-021] |
| [UC-002] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-022] | | [UC-002] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-022] |
| [UC-003] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-023] | | [UC-003] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-023] |
@@ -68,7 +73,9 @@ updated whenever an artifact instance is created or reviewed.
| [ADR-0006] | ADR | [MIL-004], [UC-006], [US-001], [ADR-0004], [ADR-0005] | - | [RC-043] | | [ADR-0006] | ADR | [MIL-004], [UC-006], [US-001], [ADR-0004], [ADR-0005] | - | [RC-043] |
| [ADR-0007] | ADR | [MIL-004], [UC-006], [ADR-0006] | - | [RC-047] | | [ADR-0007] | ADR | [MIL-004], [UC-006], [ADR-0006] | - | [RC-047] |
| [ADR-0008] | ADR | [MIL-007], [UC-005], [US-001], [ADR-0005], [ADR-0006] | - | [RC-054] | | [ADR-0008] | ADR | [MIL-007], [UC-005], [US-001], [ADR-0005], [ADR-0006] | - | [RC-054] |
| [ADR-0009] | ADR | [MIL-008], [ADR-0003], [ADR-0004], [ADR-0005], [ADR-0006] | - | [RC-063] |
| [UC-007] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-044] | | [UC-007] | UC | [UCD-001], [US-001], [SA-001] | - | [RC-044] |
| [UC-008] | UC | [UCD-001], [US-001], [SA-001], [MIL-010] | - | [RC-069] |
| [RC-019] | RC | [UCD-001] | - | - | | [RC-019] | RC | [UCD-001] | - | - |
| [RC-020] | RC | [US-001] | - | - | | [RC-020] | RC | [US-001] | - | - |
| [RC-021] | RC | [UC-001] | - | - | | [RC-021] | RC | [UC-001] | - | - |
@@ -109,6 +116,24 @@ updated whenever an artifact instance is created or reviewed.
| [RC-056] | RC | [UC-005] | - | - | | [RC-056] | RC | [UC-005] | - | - |
| [RC-057] | RC | [US-001] | - | - | | [RC-057] | RC | [US-001] | - | - |
| [RC-058] | RC | [MIL-006] | - | - | | [RC-058] | RC | [MIL-006] | - | - |
| [RC-059] | RC | [MIL-007] | - | - |
| [RC-060] | RC | [MIL-008] | - | - |
| [RC-061] | RC | [MIL-008] | - | - |
| [RC-062] | RC | [PP-001] | - | - |
| [RC-063] | RC | [ADR-0009] | - | - |
| [RC-064] | RC | [MIL-009], [PP-001] | - | - |
| [RC-065] | RC | [ADR-0004] | - | - |
| [RC-066] | RC | [MIL-009] | - | - |
| [RC-067] | RC | [BC-001] | - | - |
| [RC-068] | RC | [MIL-010], [PP-001] | - | - |
| [RC-069] | RC | [UC-008] | - | - |
| [RC-070] | RC | [US-001] | - | - |
| [RC-071] | RC | [UCD-001] | - | - |
| [RC-072] | RC | [ADR-0010] | - | - |
| [RC-073] | RC | [ADR-0010] | - | - |
| [RC-074] | RC | [MIL-010] | - | - |
| [RC-075] | RC | [MIL-010] | - | - |
| [RC-076] | RC | [MIL-011], [PP-001] | - | - |
## Coverage Notes ## Coverage Notes
@@ -129,6 +154,11 @@ updated whenever an artifact instance is created or reviewed.
[MIL-005]: ../milestones/mil-005-wiki-and-translations.md [MIL-005]: ../milestones/mil-005-wiki-and-translations.md
[MIL-006]: ../milestones/mil-006-shared-wiki-publishing.md [MIL-006]: ../milestones/mil-006-shared-wiki-publishing.md
[MIL-007]: ../milestones/mil-007-cpp-csharp-quality-checks.md [MIL-007]: ../milestones/mil-007-cpp-csharp-quality-checks.md
[MIL-008]: ../milestones/mil-008-wiki-reusable-workflow.md
[MIL-010]: ../milestones/mil-010-code-metrics-badges.md
[MIL-011]: ../milestones/mil-011-code-metrics-default-tools-ref.md
[MIL-009]: ../milestones/mil-009-migrate-wiki-callers-and-remove-scoped-workflow.md
[ADR-0010]: ../adr/adr-0010-code-metrics-badges-contract.md
[RC-001]: ./reviews/rc-001-business-case.md [RC-001]: ./reviews/rc-001-business-case.md
[RC-002]: ./reviews/rc-002-project-plan.md [RC-002]: ./reviews/rc-002-project-plan.md
[RC-003]: ./reviews/rc-003-mil-001.md [RC-003]: ./reviews/rc-003-mil-001.md
@@ -197,13 +227,32 @@ updated whenever an artifact instance is created or reviewed.
[RC-056]: ./reviews/rc-056-uc-005-cpp-csharp.md [RC-056]: ./reviews/rc-056-uc-005-cpp-csharp.md
[RC-057]: ./reviews/rc-057-us-001-cpp-csharp.md [RC-057]: ./reviews/rc-057-us-001-cpp-csharp.md
[RC-058]: ./reviews/rc-058-mil-006-task-6.md [RC-058]: ./reviews/rc-058-mil-006-task-6.md
[RC-059]: ./reviews/rc-059-mil-007-adoption.md
[RC-060]: ./reviews/rc-060-mil-008-plan.md
[RC-061]: ./reviews/rc-061-mil-008-task-5-deferred.md
[RC-062]: ./reviews/rc-062-pp-001-mil-008.md
[RC-063]: ./reviews/rc-063-adr-0009.md
[RC-064]: ./reviews/rc-064-mil-009-plan.md
[RC-065]: ./reviews/rc-065-adr-0004-superseded.md
[RC-066]: ./reviews/rc-066-mil-009-outcome.md
[RC-067]: ./reviews/rc-067-bc-001-o5.md
[RC-068]: ./reviews/rc-068-mil-010-plan.md
[RC-069]: ./reviews/rc-069-uc-008.md
[RC-070]: ./reviews/rc-070-us-001-metrics.md
[RC-071]: ./reviews/rc-071-ucd-001-metrics.md
[RC-072]: ./reviews/rc-072-adr-0010.md
[RC-073]: ./reviews/rc-073-adr-0010-artifact.md
[RC-074]: ./reviews/rc-074-mil-010-results.md
[RC-075]: ./reviews/rc-075-mil-010-outcome.md
[RC-076]: ./reviews/rc-076-mil-011-plan.md
[ADR-0008]: ../adr/adr-0008-cpp-csharp-quality-check-contract.md [ADR-0008]: ../adr/adr-0008-cpp-csharp-quality-check-contract.md
[ADR-0009]: ../adr/adr-0009-wiki-publishing-reusable-workflow.md
[ADR-0007]: ../adr/adr-0007-release-publish-destination.md [ADR-0007]: ../adr/adr-0007-release-publish-destination.md
[UC-007]: ../uc-007/uc.md [UC-007]: ../uc-007/uc.md
[UC-008]: ../uc-008/uc.md
[UC-006]: ../uc-006/uc.md [UC-006]: ../uc-006/uc.md
[ADR-0006]: ../adr/adr-0006-release-process-and-version-pinning.md [ADR-0006]: ../adr/adr-0006-release-process-and-version-pinning.md
[UC-005]: ../uc-005/uc.md [UC-005]: ../uc-005/uc.md
[ADR-0005]: ../adr/adr-0005-quality-check-contract.md [ADR-0005]: ../adr/adr-0005-quality-check-contract.md
[faf13b2]: https://git.tirsystem.com/TirSystem/github-action/commit/faf13b2c2a4901c0171977de1ffc2841023f537a [aa20e56]: https://git.tirsystem.com/TirSystem/github-action/commit/aa20e56110f4c2fab91a8432ba008ee8300926a2
[1bc56f8]: https://git.tirsystem.com/TirSystem/github-action/commit/1bc56f8b771531f221f218fc00b147ecc73c051e [3d7a9df]: https://git.tirsystem.com/TirSystem/github-action/commit/3d7a9df3a3a4712dcc5214a71b0569d90122bf4e
[8051cc4]: https://git.tirsystem.com/TirSystem/github-action/commit/8051cc4489ee9bcf3b6c62f2eafcb2013b7d2f90
+44
View File
@@ -0,0 +1,44 @@
# UC-008: Show Code Metrics and Coverage Badges
## Metadata
| Key | Value |
| --- | --- |
| ID | UC-008 |
| CrossReference | [UCD-001], [US-001], [SA-001], [MIL-010] |
## Version History
| Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- |
| 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Initial version; reviewed (RC-069) | [9ca9673] |
---
**Format:** Brief
**Title:** Show Code Metrics and Coverage Badges
**Scope:** Reusable Workflow Collection
**Level:** user-goal
**Primary Actor:** Software Engineer
**Stakeholders:** S02, S05 (interests below)
**Stories:** US-001.18, US-001.19, US-001.20
## Brief
A Software Engineer pushes a change to a repository that calls the shared code-metrics workflow. The workflow counts the code files and the lines of code (without comment and blank lines), reads the coverage report the repository supplies, and publishes three badges: the number of code files, the number of lines of code and the coverage. The repository's README shows the badges, and they show the numbers of the latest push.
**Stakeholder interests**
- S02 — the size and the coverage of the code are visible at a glance, without a service outside the instance, and a wrong or missing report is reported instead of shown as a number
- S05 — the counting rules and the badge contract are defined once and change through a reviewed release
**Preconditions:** The repository is onboarded and the shared workflow is available through reusable workflow delivery; a coverage badge needs a coverage report that the repository's own tests have produced.
**Postconditions (success guarantee):** The three badges show the numbers of the pushed revision, nothing but the badges is changed in the repository, and a run whose numbers are unchanged changes nothing.
---
[UCD-001]: ../use-case-diagram.md
[US-001]: ../user-stories.md
[SA-001]: ../stakeholder-analysis.md
[MIL-010]: ../milestones/mil-010-code-metrics-badges.md
[9ca9673]: https://git.tirsystem.com/TirSystem/github-action/commit/9ca9673b00351cf913966f347c4da09890ed3f1f
+9 -5
View File
@@ -4,13 +4,13 @@
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| ID | UCD-001 | | ID | UCD-001 |
| CrossReference | [SA-001], [BC-001], [US-001], [UC-001], [UC-002], [UC-003], [UC-004], [UC-005], [UC-006], [UC-007] | | CrossReference | [SA-001], [BC-001], [US-001], [UC-001], [UC-002], [UC-003], [UC-004], [UC-005], [UC-006], [UC-007], [UC-008] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Added the use case Release a Reusable Workflow (UC-006) | [044d8ba] | | 2026-10-10 | Deprecated | Jens Tirsvad Nielsen | S01 | Added the use case Consume a Workflow Update (UC-007) (RC-045) | [4cf52af] |
| 2026-10-10 | Accepted | Jens Tirsvad Nielsen | S01 | Added the use case Consume a Workflow Update (UC-007) (RC-045) | [4cf52af] | | 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added the use case Show Code Metrics and Coverage Badges (UC-008); reviewed (RC-071) | [9ca9673] |
--- ---
@@ -46,6 +46,7 @@ rectangle "Reusable Workflow Collection" <<System>> {
usecase "Run Project Quality Checks" as UC5 usecase "Run Project Quality Checks" as UC5
usecase "Release a Reusable Workflow" as UC6 usecase "Release a Reusable Workflow" as UC6
usecase "Consume a Workflow Update" as UC7 usecase "Consume a Workflow Update" as UC7
usecase "Show Code Metrics and Coverage Badges" as UC8
} }
DevOps --> UC1 DevOps --> UC1
@@ -57,6 +58,7 @@ Operator --> UC2
Operator --> UC4 Operator --> UC4
Engineer --> UC5 Engineer --> UC5
Engineer --> UC7 Engineer --> UC7
Engineer --> UC8
UC1 --> Gitea UC1 --> Gitea
UC1 --> GitHub UC1 --> GitHub
UC3 ..> UC2 : <<include>> UC3 ..> UC2 : <<include>>
@@ -70,7 +72,7 @@ UC4 ..> UC1 : <<extend>>
| --- | --- | --- | --- | | --- | --- | --- | --- |
| DevOps Professional | `<<Actor>>` | S05 | Synchronize Repository Metadata, Configure Workflow Credentials, Onboard a Repository, Diagnose a Failed Workflow, Release a Reusable Workflow | | DevOps Professional | `<<Actor>>` | S05 | Synchronize Repository Metadata, Configure Workflow Credentials, Onboard a Repository, Diagnose a Failed Workflow, Release a Reusable Workflow |
| Platform Operator | `<<Actor>>` | S03 | Configure Workflow Credentials, Diagnose a Failed Workflow | | Platform Operator | `<<Actor>>` | S03 | Configure Workflow Credentials, Diagnose a Failed Workflow |
| Software Engineer | `<<Actor>>` | S02 | Run Project Quality Checks, Consume a Workflow Update | | Software Engineer | `<<Actor>>` | S02 | Run Project Quality Checks, Consume a Workflow Update, Show Code Metrics and Coverage Badges |
| Gitea Instance | `<<System>>` | - | Synchronize Repository Metadata (source of description and topics) | | Gitea Instance | `<<System>>` | - | Synchronize Repository Metadata (source of description and topics) |
| GitHub | `<<System>>` | S04 (audience of the mirror) | Synchronize Repository Metadata (target) | | GitHub | `<<System>>` | S04 (audience of the mirror) | Synchronize Repository Metadata (target) |
@@ -85,6 +87,7 @@ UC4 ..> UC1 : <<extend>>
| Run Project Quality Checks | Software Engineer | Lint, format and type checks run on every change and report a pass or a fail | | Run Project Quality Checks | Software Engineer | Lint, format and type checks run on every change and report a pass or a fail |
| Release a Reusable Workflow | DevOps Professional | A validated, versioned update that consumers can pin | | Release a Reusable Workflow | DevOps Professional | A validated, versioned update that consumers can pin |
| Consume a Workflow Update | Software Engineer | A new version is adopted safely, one repository first | | Consume a Workflow Update | Software Engineer | A new version is adopted safely, one repository first |
| Show Code Metrics and Coverage Badges | Software Engineer | The size and the coverage of the code are visible as badges that follow every push |
## Relationships ## Relationships
@@ -104,6 +107,7 @@ UC4 ..> UC1 : <<extend>>
[UC-004]: ./uc-004/uc.md [UC-004]: ./uc-004/uc.md
[UC-005]: ./uc-005/uc.md [UC-005]: ./uc-005/uc.md
[UC-006]: ./uc-006/uc.md [UC-006]: ./uc-006/uc.md
[044d8ba]: https://git.tirsystem.com/TirSystem/github-action/commit/044d8ba21f96f4aceb807d62addc35cdcbb56051
[UC-007]: ./uc-007/uc.md [UC-007]: ./uc-007/uc.md
[UC-008]: ./uc-008/uc.md
[4cf52af]: https://git.tirsystem.com/TirSystem/github-action/commit/4cf52afe00f546c6b20424fb8dfc12796ffd9296 [4cf52af]: https://git.tirsystem.com/TirSystem/github-action/commit/4cf52afe00f546c6b20424fb8dfc12796ffd9296
[9ca9673]: https://git.tirsystem.com/TirSystem/github-action/commit/9ca9673b00351cf913966f347c4da09890ed3f1f
+50 -6
View File
@@ -4,13 +4,13 @@
| Key | Value | | Key | Value |
| --- | --- | | --- | --- |
| ID | US-001 | | ID | US-001 |
| CrossReference | [UCD-001], [BC-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [UC-001], [UC-002], [UC-003], [UC-004], [UC-005], [UC-006], [UC-007], [MIL-007] | | CrossReference | [UCD-001], [BC-001], [MIL-001], [MIL-002], [MIL-003], [MIL-004], [UC-001], [UC-002], [UC-003], [UC-004], [UC-005], [UC-006], [UC-007], [MIL-007], [UC-008], [MIL-010] |
## Version History ## Version History
| Date | Status | Author | Reviewer | Change | Commit | | Date | Status | Author | Reviewer | Change | Commit |
| --- | --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- | --- |
| 2026-10-11 | Deprecated | Jens Tirsvad Nielsen | S01 | Added US-001.16 and US-001.17 (C++ and C# checks, MIL-007 task 1) | [32b5ab6] | | 2026-10-11 | Deprecated | Jens Tirsvad Nielsen | S01 | US-001.16 and US-001.17 reviewed (RC-057) and accepted; the Purpose and Scope names the later stories | [1bc56f8] |
| 2026-10-11 | Accepted | Jens Tirsvad Nielsen | S01 | US-001.16 and US-001.17 reviewed (RC-057) and accepted; the Purpose and Scope names the later stories | [1bc56f8] | | 2026-10-14 | Accepted | Jens Tirsvad Nielsen | AI as stand-in for S01 | Added US-001.18 to US-001.20 (code metrics and coverage badges, MIL-010 task 1); reviewed (RC-070) | [9ca9673] |
--- ---
@@ -21,7 +21,8 @@ These stories cover the P1 use cases of the Project Plan that MIL-002 delivers
credentials, onboarding a repository and diagnosing a failed run. Roles are the credentials, onboarding a repository and diagnosing a failed run. Roles are the
actors of [UCD-001]. Later stories cover quality checks (US-001.09, US-001.10 and, for actors of [UCD-001]. Later stories cover quality checks (US-001.09, US-001.10 and, for
C++ and C#, US-001.16 and US-001.17), releases and updates (US-001.11 to C++ and C#, US-001.16 and US-001.17), releases and updates (US-001.11 to
US-001.15). Stories for the wiki (MIL-005, MIL-006) are not written yet. US-001.15) and the code metrics and coverage badges (US-001.18 to
US-001.20). Stories for the wiki (MIL-005, MIL-006) are not written yet.
## Story List ## Story List
@@ -248,9 +249,50 @@ US-001.15). Stories for the wiki (MIL-005, MIL-006) are not written yet.
| --- | --- | --- | | --- | --- | --- |
| [UC-005], [MIL-007] | fits one iteration | none | | [UC-005], [MIL-007] | fits one iteration | none |
### US-001.18 — The size of the code is visible as badges
**As a** Software Engineer, **I want** badges that show the number of code files and the number of lines of code without comment and blank lines, **so that** the size of the project is visible in its README without a service outside the instance.
**Acceptance Criteria**
- Given a repository that calls the shared code-metrics workflow, when a change is pushed, then a badge for the code files and a badge for the lines of code show the numbers of that revision.
- Given a file with a comment line, a blank line and a code line, when the lines are counted, then only the code line is counted as a line of code.
- Given a repository without a file of a supported language, when the workflow runs, then it passes with a notice and the badges show zero.
| Traces to | Size | INVEST exceptions |
| --- | --- | --- |
| [UC-008], [MIL-010] | fits one iteration | none |
### US-001.19 — The coverage is visible as a badge
**As a** Software Engineer, **I want** a badge that shows how much of the code my tests cover, **so that** a drop in coverage is seen as soon as it is pushed.
**Acceptance Criteria**
- Given a repository that supplies a coverage report, when a change is pushed, then a coverage badge shows the percentage of the report, and its colour follows the thresholds of the contract.
- Given a coverage report that is missing or that cannot be read, when the workflow runs, then it fails with a `[configuration]` message that names the report file and no coverage badge is changed.
- Given a repository that supplies no coverage report, when the workflow runs, then it passes with a notice and publishes no coverage badge.
| Traces to | Size | INVEST exceptions |
| --- | --- | --- |
| [UC-008], [MIL-010] | fits one iteration | none |
### US-001.20 — A run with unchanged numbers changes nothing
**As a** Software Engineer, **I want** the workflow to commit nothing when the numbers did not change, **so that** the history of the repository is not filled with badge commits.
**Acceptance Criteria**
- Given a push after which none of the three badges would differ, when the workflow runs, then no commit is made.
- Given a push after which a number changed, when the workflow runs, then one commit changes only the badge files, and the workflow does not start again from that commit.
| Traces to | Size | INVEST exceptions |
| --- | --- | --- |
| [UC-008], [MIL-010] | fits one iteration | none |
## INVEST Check ## INVEST Check
All seventeen stories are independent of each other except that US-001.05 and All twenty stories are independent of each other except that US-001.05 and
US-001.06 build on US-001.04 (the secrets must exist before a repository can US-001.06 build on US-001.04 (the secrets must exist before a repository can
run the workflow). Each states one goal and can be negotiated, delivers value run the workflow). Each states one goal and can be negotiated, delivers value
to the named role, is small enough for one iteration, and has acceptance to the named role, is small enough for one iteration, and has acceptance
@@ -274,5 +316,7 @@ behaviour is tested rather than built.
[UC-005]: ./uc-005/uc.md [UC-005]: ./uc-005/uc.md
[UC-006]: ./uc-006/uc.md [UC-006]: ./uc-006/uc.md
[UC-007]: ./uc-007/uc.md [UC-007]: ./uc-007/uc.md
[32b5ab6]: https://git.tirsystem.com/TirSystem/github-action/commit/32b5ab62ab5a82638b71b8fcfb43a819e92ab346 [UC-008]: ./uc-008/uc.md
[MIL-010]: ./milestones/mil-010-code-metrics-badges.md
[1bc56f8]: https://git.tirsystem.com/TirSystem/github-action/commit/1bc56f8b771531f221f218fc00b147ecc73c051e [1bc56f8]: https://git.tirsystem.com/TirSystem/github-action/commit/1bc56f8b771531f221f218fc00b147ecc73c051e
[9ca9673]: https://git.tirsystem.com/TirSystem/github-action/commit/9ca9673b00351cf913966f347c4da09890ed3f1f
@@ -1,4 +1,4 @@
<!-- translation of Capabilities.md, source sha256 a8c03a001880 --> <!-- translation of Capabilities.md, source sha256 3e3bcd9498f7 -->
> Dette er en oversættelse af [`Capabilities`](Capabilities). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. > Dette er en oversættelse af [`Capabilities`](Capabilities). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
@@ -13,6 +13,8 @@ Rækker, der siger Implementeret eller Vejledninger er skrevet, findes i dag. Re
| Synkronisering af repositorymetadata (Gitea til GitHub-spejl) | Implementeret i `.gitea/scoped_workflows/`, med offline-tests og et valideringsworkflow | | Synkronisering af repositorymetadata (Gitea til GitHub-spejl) | Implementeret i `.gitea/scoped_workflows/`, med offline-tests og et valideringsworkflow |
| Konfiguration og validering af workflow-credentials | Implementeret for synkroniseringsworkflowet: en forhåndskontrol tjekker begge tokens før enhver ændring ([vejledning](Credentials.da)) | | Konfiguration og validering af workflow-credentials | Implementeret for synkroniseringsworkflowet: en forhåndskontrol tjekker begge tokens før enhver ændring ([vejledning](Credentials.da)) |
| Tilslutning af et repository, administration af runners, fejlfinding | Vejledninger er skrevet ([tilslutning](Onboarding.da), [fejlfinding](Troubleshooting.da)); scoped levering til alle fire organisationers repositories er verificeret (MIL-002) | | Tilslutning af et repository, administration af runners, fejlfinding | Vejledninger er skrevet ([tilslutning](Onboarding.da), [fejlfinding](Troubleshooting.da)); scoped levering til alle fire organisationers repositories er verificeret (MIL-002) |
| Publicering af et repositorys wiki fra `wiki/` (tilmelding, leveres til alle organisationer) | Implementeret i `.gitea/scoped_workflows/publish-wiki.yml`, med offline-tests; et pilotforsøg i et andet repository publicerer sin wiki (MIL-006) | | Publicering af et repositorys wiki fra `docs/wiki/` gennem et kaldt workflow | Implementeret som det genbrugelige workflow `.gitea/workflows/publish-wiki-pages.yml`, med offline-tests; kaldt af hvert repository, der publicerer en wiki, og dette repositorys egen wiki er i `docs/wiki/` (MIL-008, MIL-009) |
| Fælles kvalitetskontrol (Python og shell) | Implementeret som det genbrugelige workflow `.gitea/workflows/quality-checks.yml`, med offline-tests og et forsøg i en anden organisation; taget i brug i RepoFoundry ([vejledning](QualityChecksPythonShell.da)) | | Fælles kvalitetskontrol (Python og shell) | Implementeret som det genbrugelige workflow `.gitea/workflows/quality-checks.yml`, med offline-tests og et forsøg i en anden organisation; taget i brug i RepoFoundry ([vejledning](QualityChecksPythonShell.da)) |
| Fælles kvalitetskontrol (C og C++, C#) | Implementeret som de genbrugelige workflows `.gitea/workflows/quality-checks-cpp.yml` og `.gitea/workflows/quality-checks-csharp.yml` med præcise værktøjsversioner og offline-tests; endnu ikke brugt i et andet repository (MIL-007) ([vejledning til C og C++](QualityChecksCpp.da), [vejledning til C#](QualityChecksCSharp.da)) |
| Kodemålinger og dækningsbadges | Implementeret og udgivet i 2.1.0 som det genbrugelige workflow `.gitea/workflows/code-metrics.yml` med en tæller, en Cobertura-læser, en SVG-renderer og offline-tests; afprøvet i et kasserepository, endnu ikke brugt i et projektrepository (MIL-010) ([vejledning](CodeMetrics.da)) |
| Versioneret udgivelse og publicering | Udgivelsesproces, changelog og opgraderingsvejledning er defineret; udgivelsesworkflowet findes, og `v1.0.0` er udgivet (MIL-004) ([vejledning](Upgrading.da)) | | Versioneret udgivelse og publicering | Udgivelsesproces, changelog og opgraderingsvejledning er defineret; udgivelsesworkflowet findes, og `v1.0.0` er udgivet (MIL-004) ([vejledning](Upgrading.da)) |
@@ -13,6 +13,8 @@ Rows that say Implemented or Guides written exist today. The rest is planned and
| Synchronize repository metadata (Gitea to GitHub mirror) | Implemented in `.gitea/scoped_workflows/`, with offline tests and a validation workflow | | Synchronize repository metadata (Gitea to GitHub mirror) | Implemented in `.gitea/scoped_workflows/`, with offline tests and a validation workflow |
| Configure and validate workflow credentials | Implemented for the sync workflow: preflight checks both tokens before any change ([guide](Credentials)) | | Configure and validate workflow credentials | Implemented for the sync workflow: preflight checks both tokens before any change ([guide](Credentials)) |
| Onboard a repository, manage runners, diagnose failures | Guides written ([onboarding](Onboarding), [troubleshooting](Troubleshooting)); scoped delivery to the repositories of all four organizations verified (MIL-002) | | Onboard a repository, manage runners, diagnose failures | Guides written ([onboarding](Onboarding), [troubleshooting](Troubleshooting)); scoped delivery to the repositories of all four organizations verified (MIL-002) |
| Publish a repository wiki from `wiki/` (opt-in, delivered to every organization) | Implemented in `.gitea/scoped_workflows/publish-wiki.yml`, with offline tests; a pilot in a second repository publishes its wiki (MIL-006) | | Publish a repository wiki from `docs/wiki/` through a called workflow | Implemented as the reusable workflow `.gitea/workflows/publish-wiki-pages.yml`, with offline tests; called by every repository that publishes a wiki, and this repository's own wiki is in `docs/wiki/` (MIL-008, MIL-009) |
| Shared quality checks (Python and shell) | Implemented as the reusable workflow `.gitea/workflows/quality-checks.yml`, with offline tests and a trial in a second organization; adopted in RepoFoundry ([guide](QualityChecksPythonShell)) | | Shared quality checks (Python and shell) | Implemented as the reusable workflow `.gitea/workflows/quality-checks.yml`, with offline tests and a trial in a second organization; adopted in RepoFoundry ([guide](QualityChecksPythonShell)) |
| Shared quality checks (C and C++, C#) | Implemented as the reusable workflows `.gitea/workflows/quality-checks-cpp.yml` and `.gitea/workflows/quality-checks-csharp.yml` with exact tool versions and offline tests; not yet used in another repository (MIL-007) ([C and C++ guide](QualityChecksCpp), [C# guide](QualityChecksCSharp)) |
| Code metrics and coverage badges | Implemented and released in 2.1.0 as the reusable workflow `.gitea/workflows/code-metrics.yml` with a counter, a Cobertura reader, an SVG renderer and offline tests; tried in a throwaway repository, not yet used in a project repository (MIL-010) ([guide](CodeMetrics)) |
| Versioned release and publishing | Release process, changelog and upgrade guide defined; the release workflow exists and `v1.0.0` is released (MIL-004) ([guide](Upgrading)) | | Versioned release and publishing | Release process, changelog and upgrade guide defined; the release workflow exists and `v1.0.0` is released (MIL-004) ([guide](Upgrading)) |
@@ -1,4 +1,4 @@
<!-- translation of Capabilities.md, source sha256 a8c03a001880 --> <!-- translation of Capabilities.md, source sha256 3e3bcd9498f7 -->
> Ini ialah terjemahan [`Capabilities`](Capabilities). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. > Ini ialah terjemahan [`Capabilities`](Capabilities). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
@@ -13,6 +13,8 @@ Baris yang menyatakan Dilaksanakan atau Panduan telah ditulis wujud hari ini. Se
| Menyegerakkan metadata repositori (Gitea ke cermin GitHub) | Dilaksanakan dalam `.gitea/scoped_workflows/`, dengan ujian luar talian dan aliran kerja pengesahan | | Menyegerakkan metadata repositori (Gitea ke cermin GitHub) | Dilaksanakan dalam `.gitea/scoped_workflows/`, dengan ujian luar talian dan aliran kerja pengesahan |
| Mengkonfigur dan mengesahkan kelayakan (credentials) aliran kerja | Dilaksanakan untuk aliran kerja penyegerakan: semakan awal (preflight) memeriksa kedua-dua token sebelum sebarang perubahan ([panduan](Credentials.ms)) | | Mengkonfigur dan mengesahkan kelayakan (credentials) aliran kerja | Dilaksanakan untuk aliran kerja penyegerakan: semakan awal (preflight) memeriksa kedua-dua token sebelum sebarang perubahan ([panduan](Credentials.ms)) |
| Menyertakan repositori, mengurus runner, mendiagnosis kegagalan | Panduan telah ditulis ([menyertakan repositori](Onboarding.ms), [diagnosis](Troubleshooting.ms)); penghantaran scoped ke repositori keempat-empat organisasi telah disahkan (MIL-002) | | Menyertakan repositori, mengurus runner, mendiagnosis kegagalan | Panduan telah ditulis ([menyertakan repositori](Onboarding.ms), [diagnosis](Troubleshooting.ms)); penghantaran scoped ke repositori keempat-empat organisasi telah disahkan (MIL-002) |
| Menerbitkan wiki repositori daripada `wiki/` (pilih menyertai, dihantar ke setiap organisasi) | Dilaksanakan dalam `.gitea/scoped_workflows/publish-wiki.yml`, dengan ujian luar talian; satu juruterbang dalam repositori kedua menerbitkan wikinya (MIL-006) | | Menerbitkan wiki repositori daripada `docs/wiki/` melalui aliran kerja yang dipanggil | Dilaksanakan sebagai aliran kerja boleh guna semula `.gitea/workflows/publish-wiki-pages.yml`, dengan ujian luar talian; dipanggil oleh setiap repositori yang menerbitkan wiki, dan wiki repositori ini sendiri ada dalam `docs/wiki/` (MIL-008, MIL-009) |
| Semakan kualiti bersama (Python dan shell) | Dilaksanakan sebagai aliran kerja boleh guna semula `.gitea/workflows/quality-checks.yml`, dengan ujian luar talian dan percubaan dalam organisasi kedua; digunakan dalam RepoFoundry ([panduan](QualityChecksPythonShell.ms)) | | Semakan kualiti bersama (Python dan shell) | Dilaksanakan sebagai aliran kerja boleh guna semula `.gitea/workflows/quality-checks.yml`, dengan ujian luar talian dan percubaan dalam organisasi kedua; digunakan dalam RepoFoundry ([panduan](QualityChecksPythonShell.ms)) |
| Semakan kualiti bersama (C dan C++, C#) | Dilaksanakan sebagai aliran kerja boleh guna semula `.gitea/workflows/quality-checks-cpp.yml` dan `.gitea/workflows/quality-checks-csharp.yml` dengan versi alat yang tepat dan ujian luar talian; belum digunakan dalam repositori lain (MIL-007) ([panduan C dan C++](QualityChecksCpp.ms), [panduan C#](QualityChecksCSharp.ms)) |
| Metrik kod dan lencana liputan | Dilaksanakan dan diterbitkan dalam 2.1.0 sebagai aliran kerja boleh guna semula `.gitea/workflows/code-metrics.yml` dengan pengira, pembaca Cobertura, pemaparan SVG dan ujian luar talian; dicuba dalam repositori sekali pakai, belum digunakan dalam repositori projek (MIL-010) ([panduan](CodeMetrics.ms)) |
| Keluaran berversi dan penerbitan | Proses keluaran, changelog dan panduan naik taraf telah ditakrifkan; aliran kerja keluaran sudah ada dan `v1.0.0` telah dikeluarkan (MIL-004) ([panduan](Upgrading.ms)) | | Keluaran berversi dan penerbitan | Proses keluaran, changelog dan panduan naik taraf telah ditakrifkan; aliran kerja keluaran sudah ada dan `v1.0.0` telah dikeluarkan (MIL-004) ([panduan](Upgrading.ms)) |
+92
View File
@@ -0,0 +1,92 @@
<!-- translation of CodeMetrics.md, source sha256 edfcb7cf99c9 -->
> Dette er en oversættelse af [`CodeMetrics`](CodeMetrics). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
[English](CodeMetrics) | Dansk | [Bahasa Melayu](CodeMetrics.ms)
# Visning af kodemålinger og dækningsbadges
Sådan kalder en softwareudvikler det fælles kodemålings-workflow og viser dets tre badges (kodefiler, kodelinjer og dækning) i en README. Workflowet tæller repositoryets kode, læser en dækningsrapport, som kalderen leverer, og committer tre SVG-filer til en `badges`-gren. Det kører ingen test og kræver ingen secret. Dets kontrakt er ADR-0010 i dette repository.
## Kald af workflowet
Workflowet kan ikke se filerne fra dit testjob, så testene kører i et job, du selv ejer, uploader Cobertura XML-rapporten som artefakt, og målingsjobbet kører bagefter:
```yaml
name: Code metrics
on:
push:
branches: [ main ]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.2.2
- run: python3 -m pip install pytest pytest-cov && python3 -m pytest --cov --cov-report=xml
- uses: actions/upload-artifact@v4.6.2
with:
name: coverage
path: coverage.xml
metrics:
needs: tests
permissions:
contents: write
uses: TirSystem/github-action/.gitea/workflows/code-metrics.yml@v1
with:
coverage-artifact: coverage
coverage-report: coverage.xml
```
| Input | Standard | Betydning |
| --- | --- | --- |
| `working-directory` | `.` | Mappe, relativt til repositoryets rod, hvis kode tælles |
| `exclude` | tom | Kommasepareret liste af mønstre for filer og mapper, der ikke tælles, ud over standardudeladelserne; et mønster uden `/` matcher et navn overalt, et mønster med `/` matcher stien under `working-directory` |
| `coverage-report` | tom | Cobertura XML-rapport: filen i artefaktet, når `coverage-artifact` er sat, ellers en sti i repositoryet; tom giver intet dækningsbadge |
| `coverage-artifact` | tom | Navn på et artefakt, uploadet af et tidligere job i samme kørsel, der indeholder rapporten |
| `coverage-warn` | `60` | Dækning i procent, under hvilken badget er rødt |
| `coverage-good` | `80` | Dækning i procent, fra hvilken badget er grønt; derimellem er det gult |
| `badges-branch` | `badges` | Gren, der indeholder badge-filerne; den oprettes, hvis den ikke findes |
| `tools-ref` | `v1` | Tag eller gren i `TirSystem/github-action`, hvorfra scripts i `tools/` hentes |
Reference workflowet ved major-tagget (`@v1`) eller ved et præcist tag, aldrig ved `main`. Et kald ved et præcist tag er kun fuldt fastlåst, når `tools-ref` navngiver samme tag. Se opgraderingsvejledningen. Start workflowet kun ved push til din standardgren: badge-committet har `[skip ci]` og laves med kørslens token, som ikke starter en ny kørsel, men en trigger på alle grene ville skrive ét commit pr. gren.
## Hvad der tælles
- En kodefil er en tekstfil med filendelsen for et understøttet sprog: Python, shell, PowerShell, C, C++, C#, Java, JavaScript, TypeScript, Go, Rust og SQL. Markdown, YAML, JSON, XML, HTML og CSS er ikke kodefiler.
- Hver linje er præcis én af: tom (kun blanktegn), kommentar (kun kommentartekst) eller kode (alt andet, også en linje med kode og en afsluttende kommentar). Badget viser kun kodelinjerne.
- En kommentarmarkør i en streng er ikke en kommentar, og en Python-docstring er en streng og tæller derfor som kode.
- Mapper, der starter med punktum, og `node_modules`, `vendor`, `third_party`, `dist`, `build`, `out`, `bin`, `obj`, `target`, `venv`, `__pycache__` og `shared-scripts` tælles ikke. Genereret kode udelades med `exclude`.
- Rå og ordrette strenge, der afviger fra en almindelig streng (Rust `r#"..."#`, C# `@"..."`, C++-rå strenge), kan tælles forkert. Tallene afviger lidt fra andre værktøjer; reglerne er de samme ved hver kørsel.
## Dækningsbadget
Rapporten er en Cobertura XML-fil, som `coverage.py` (`coverage xml`), `gcovr` (`--cobertura`) og `coverlet` (formatet `cobertura`) skriver. Badget viser `lines-covered` over `lines-valid`, afrundet til hele procent, og farven følger de to grænser. En rapport, der ikke findes, ikke er velformet Cobertura XML eller ikke har gyldige linjer, får kørslen til at fejle med filen nævnt; dækningsbadget skrives aldrig som `0%` for en dårlig rapport. Uden `coverage-report` består kørslen med en note og uden dækningsbadge.
## Visning af badges
Tilføj billederne til README med grenens rå URL'er:
```markdown
![code files](https://git.tirsystem.com/OWNER/REPO/raw/branch/badges/code-files.svg)
![lines of code](https://git.tirsystem.com/OWNER/REPO/raw/branch/badges/code-lines.svg)
![coverage](https://git.tirsystem.com/OWNER/REPO/raw/branch/badges/coverage.svg)
```
På GitHub-spejlet bruges `https://raw.githubusercontent.com/OWNER/REPO/badges/code-files.svg`; push-spejlet kopierer grenen. `badges`-grenen indeholder også `code-metrics.json` med tallene pr. sprog. Instansen cacher en rå fil i op til seks timer, så et ændret badge kan vise det gamle tal et stykke tid dér; spejlet cacher i fem minutter. Et privat repository viser ikke badges for en læser, der ikke er logget ind.
## Når workflowet fejler
| Meddelelse | Årsag | Løsning |
| --- | --- | --- |
| `[configuration] working-directory must be a relative path inside the repository.` | En absolut sti eller `..` | Brug en sti inde i repositoryet |
| `[configuration] coverage-report '<x>' does not exist.` | Rapporten blev ikke frembragt, eller artefaktnavnet eller stien er forkert | Tjek testjobbet og artefaktnavnet; stien er inde i artefaktet |
| `[configuration] coverage-report '<x>' is not well-formed XML` eller `is not a Cobertura report` | Filen har et andet format | Skriv Cobertura XML (`coverage xml`, `gcovr --cobertura`) |
| `[configuration] coverage-report '<x>' has no valid lines.` | Testene kørte ingen målt kode | Tjek dækningskonfigurationen |
| `[configuration] coverage-artifact '<x>' needs coverage-report` | Artefaktet er navngivet, men ikke filen i det | Sæt `coverage-report` |
| `[configuration] coverage-warn ... is above coverage-good` eller `is not a number from 0 to 100` | En grænse er ugyldig | Ret inputtet |
| `[configuration] badges-branch '<x>' must not be a default branch` eller `holds files that are not badge files` | Grenen hedder `main`, `master` eller har andet indhold | Brug en gren, der kun indeholder badges |
| `the push to '<x>' failed twice` | En anden kørsel skrev grenen samtidig, eller tokenet kan ikke skrive | Kør igen; tjek at jobbet har `contents: write` |
| Download-trinnet fejler | Artefaktet findes ikke i denne kørsel, eller jobbet kører ikke efter testjobbet | Tilføj `needs:` og tjek artefaktnavnet |
+123
View File
@@ -0,0 +1,123 @@
<!-- Generated from guides/code-metrics.md by tools/build_wiki.py. Edit the source, not this page. -->
> This page is generated from [`guides/code-metrics.md`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/code-metrics.md). Changes made here are overwritten the next time the wiki is published.
English | [Dansk](CodeMetrics.da) | [Bahasa Melayu](CodeMetrics.ms)
# Showing code metrics and coverage badges
How a software engineer calls the shared code-metrics workflow and shows its
three badges (code files, lines of code and coverage) in a README. The workflow
counts the code of the repository, reads a coverage report that the caller
supplies and commits three SVG files to a `badges` branch. It runs no test and
needs no secret. Its contract is ADR-0010 in this repository.
## Calling the workflow
The workflow cannot see the files of your test job, so the tests run in a job of
your own, upload the Cobertura XML report as an artifact, and the metrics job
runs after it:
```yaml
name: Code metrics
on:
push:
branches: [ main ]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.2.2
- run: python3 -m pip install pytest pytest-cov && python3 -m pytest --cov --cov-report=xml
- uses: actions/upload-artifact@v4.6.2
with:
name: coverage
path: coverage.xml
metrics:
needs: tests
permissions:
contents: write
uses: TirSystem/github-action/.gitea/workflows/code-metrics.yml@v1
with:
coverage-artifact: coverage
coverage-report: coverage.xml
```
| Input | Default | Meaning |
| --- | --- | --- |
| `working-directory` | `.` | Directory, relative to the repository root, whose code is counted |
| `exclude` | empty | Comma list of patterns of files and directories that are not counted, in addition to the defaults; a pattern without `/` matches a name anywhere, a pattern with `/` matches the path below `working-directory` |
| `coverage-report` | empty | Cobertura XML report: the file inside the artifact when `coverage-artifact` is set, otherwise a path in the repository; empty writes no coverage badge |
| `coverage-artifact` | empty | Name of an artifact, uploaded by an earlier job of the same run, that holds the report |
| `coverage-warn` | `60` | Coverage in percent below which the badge is red |
| `coverage-good` | `80` | Coverage in percent from which the badge is green; in between it is yellow |
| `badges-branch` | `badges` | Branch that holds the badge files; it is created when it does not exist |
| `tools-ref` | `v1` | Tag or branch of `TirSystem/github-action` from which the scripts in `tools/` are fetched |
Reference the workflow at the major tag (`@v1`) or at an exact tag, never at
`main`. A call at an exact tag is pinned completely only when `tools-ref` names
the same tag. See the upgrade guide. Start the workflow on pushes to your
default branch only: the badge commit carries `[skip ci]` and is made with the
token of the run, which does not start another run, but a trigger on every
branch would write one commit per branch.
## What is counted
- A code file is a text file with the extension of a supported language:
Python, shell, PowerShell, C, C++, C#, Java, JavaScript, TypeScript, Go, Rust
and SQL. Markdown, YAML, JSON, XML, HTML and CSS are not code files.
- Every line is exactly one of blank (only white space), comment (only comment
text) or code (everything else, including a line with code and a trailing
comment). The badge shows the lines of code only.
- A comment marker inside a string is not a comment, and a Python docstring is a
string, so it counts as code.
- Directories that start with a dot and `node_modules`, `vendor`, `third_party`,
`dist`, `build`, `out`, `bin`, `obj`, `target`, `venv`, `__pycache__` and
`shared-scripts` are not counted. Generated code is excluded with `exclude`.
- Raw and verbatim strings that differ from an ordinary string (Rust `r#"..."#`,
C# `@"..."`, C++ raw strings) can be counted wrongly. The counts will differ
a little from other tools; the rules are the same on every run.
## The coverage badge
The report is a Cobertura XML file, which `coverage.py` (`coverage xml`),
`gcovr` (`--cobertura`) and `coverlet` (`cobertura` format) write. The badge
shows `lines-covered` over `lines-valid`, rounded to a whole percent, and its
colour follows the two thresholds. A report that does not exist, is not
well-formed Cobertura XML or has no valid lines fails the run with the file
named; the coverage badge is never written as `0%` for a bad report. Without
`coverage-report` the run passes with a notice and no coverage badge.
## Showing the badges
Add the images to the README with the raw URLs of the branch:
```markdown
![code files](https://git.tirsystem.com/OWNER/REPO/raw/branch/badges/code-files.svg)
![lines of code](https://git.tirsystem.com/OWNER/REPO/raw/branch/badges/code-lines.svg)
![coverage](https://git.tirsystem.com/OWNER/REPO/raw/branch/badges/coverage.svg)
```
On the GitHub mirror use `https://raw.githubusercontent.com/OWNER/REPO/badges/code-files.svg`;
the push mirror copies the branch. The `badges` branch also holds
`code-metrics.json` with the numbers per language. The instance caches a raw
file for up to six hours, so a changed badge can show the old number for a
while there; the mirror caches for five minutes. A private repository does not
show the badges to a reader who is not signed in.
## When the workflow fails
| Message | Cause | Fix |
| --- | --- | --- |
| `[configuration] working-directory must be a relative path inside the repository.` | An absolute path or `..` | Use a path inside the repository |
| `[configuration] coverage-report '<x>' does not exist.` | The report was not produced, or the artifact name or path is wrong | Check the test job and the artifact name; the path is inside the artifact |
| `[configuration] coverage-report '<x>' is not well-formed XML` or `is not a Cobertura report` | The file is another format | Write Cobertura XML (`coverage xml`, `gcovr --cobertura`) |
| `[configuration] coverage-report '<x>' has no valid lines.` | The tests ran no measured code | Check the coverage configuration |
| `[configuration] coverage-artifact '<x>' needs coverage-report` | The artifact is named but not the file in it | Set `coverage-report` |
| `[configuration] coverage-warn ... is above coverage-good` or `is not a number from 0 to 100` | A threshold is invalid | Correct the input |
| `[configuration] badges-branch '<x>' must not be a default branch` or `holds files that are not badge files` | The branch name is `main`, `master` or a branch with other content | Use a branch that holds only the badges |
| `the push to '<x>' failed twice` | Another run wrote the branch at the same time, or the token cannot write | Re-run; check that the job has `contents: write` |
| The download step fails | The artifact does not exist in this run, or the job does not run after the test job | Add `needs:` and check the artifact name |
+92
View File
@@ -0,0 +1,92 @@
<!-- translation of CodeMetrics.md, source sha256 edfcb7cf99c9 -->
> Ini ialah terjemahan [`CodeMetrics`](CodeMetrics). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
[English](CodeMetrics) | [Dansk](CodeMetrics.da) | Bahasa Melayu
# Menunjukkan metrik kod dan lencana liputan
Cara seorang jurutera perisian memanggil aliran kerja metrik kod bersama dan menunjukkan tiga lencananya (fail kod, baris kod dan liputan) dalam README. Aliran kerja ini mengira kod repositori, membaca laporan liputan yang dibekalkan oleh pemanggil dan melakukan commit tiga fail SVG ke cabang `badges`. Ia tidak menjalankan sebarang ujian dan tidak memerlukan secret. Kontraknya ialah ADR-0010 dalam repositori ini.
## Memanggil aliran kerja
Aliran kerja tidak dapat melihat fail daripada kerja ujian anda, jadi ujian dijalankan dalam kerja anda sendiri, memuat naik laporan Cobertura XML sebagai artifak, dan kerja metrik dijalankan selepasnya:
```yaml
name: Code metrics
on:
push:
branches: [ main ]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.2.2
- run: python3 -m pip install pytest pytest-cov && python3 -m pytest --cov --cov-report=xml
- uses: actions/upload-artifact@v4.6.2
with:
name: coverage
path: coverage.xml
metrics:
needs: tests
permissions:
contents: write
uses: TirSystem/github-action/.gitea/workflows/code-metrics.yml@v1
with:
coverage-artifact: coverage
coverage-report: coverage.xml
```
| Input | Lalai | Maksud |
| --- | --- | --- |
| `working-directory` | `.` | Direktori, relatif kepada akar repositori, yang kodnya dikira |
| `exclude` | kosong | Senarai corak fail dan direktori yang tidak dikira, dipisahkan koma, sebagai tambahan kepada pengecualian lalai; corak tanpa `/` sepadan dengan nama di mana-mana, corak dengan `/` sepadan dengan laluan di bawah `working-directory` |
| `coverage-report` | kosong | Laporan Cobertura XML: fail dalam artifak apabila `coverage-artifact` ditetapkan, jika tidak laluan dalam repositori; kosong bermakna tiada lencana liputan |
| `coverage-artifact` | kosong | Nama artifak, dimuat naik oleh kerja yang lebih awal dalam larian yang sama, yang memegang laporan |
| `coverage-warn` | `60` | Liputan dalam peratus di bawahnya lencana berwarna merah |
| `coverage-good` | `80` | Liputan dalam peratus yang bermulanya lencana berwarna hijau; di antaranya kuning |
| `badges-branch` | `badges` | Cabang yang memegang fail lencana; ia dicipta jika tidak wujud |
| `tools-ref` | `v1` | Tag atau cabang `TirSystem/github-action` tempat skrip dalam `tools/` diambil |
Rujuk aliran kerja pada tag major (`@v1`) atau pada tag tepat, jangan sekali-kali pada `main`. Panggilan pada tag tepat hanya tersemat sepenuhnya apabila `tools-ref` menamakan tag yang sama. Lihat panduan menaik taraf. Mulakan aliran kerja pada push ke cabang lalai anda sahaja: commit lencana membawa `[skip ci]` dan dibuat dengan token larian, yang tidak memulakan larian lain, tetapi pencetus pada setiap cabang akan menulis satu commit bagi setiap cabang.
## Apa yang dikira
- Fail kod ialah fail teks dengan sambungan bagi bahasa yang disokong: Python, shell, PowerShell, C, C++, C#, Java, JavaScript, TypeScript, Go, Rust dan SQL. Markdown, YAML, JSON, XML, HTML dan CSS bukan fail kod.
- Setiap baris tepat satu daripada: kosong (hanya ruang putih), komen (hanya teks komen) atau kod (selebihnya, termasuk baris dengan kod dan komen di hujung). Lencana hanya menunjukkan baris kod.
- Penanda komen dalam rentetan bukan komen, dan docstring Python ialah rentetan jadi dikira sebagai kod.
- Direktori yang bermula dengan titik, serta `node_modules`, `vendor`, `third_party`, `dist`, `build`, `out`, `bin`, `obj`, `target`, `venv`, `__pycache__` dan `shared-scripts` tidak dikira. Kod yang dijana dikecualikan dengan `exclude`.
- Rentetan mentah dan verbatim yang berbeza daripada rentetan biasa (Rust `r#"..."#`, C# `@"..."`, rentetan mentah C++) boleh dikira dengan salah. Angka sedikit berbeza daripada alat lain; peraturannya sama pada setiap larian.
## Lencana liputan
Laporan ialah fail Cobertura XML, yang ditulis oleh `coverage.py` (`coverage xml`), `gcovr` (`--cobertura`) dan `coverlet` (format `cobertura`). Lencana menunjukkan `lines-covered` berbanding `lines-valid`, dibundarkan kepada peratus penuh, dan warnanya mengikut dua ambang. Laporan yang tidak wujud, bukan Cobertura XML yang terbentuk dengan baik atau tidak mempunyai baris sah menyebabkan larian gagal dengan nama fail disebut; lencana liputan tidak pernah ditulis sebagai `0%` bagi laporan yang rosak. Tanpa `coverage-report` larian lulus dengan notis dan tanpa lencana liputan.
## Menunjukkan lencana
Tambahkan imej ke README dengan URL mentah cabang:
```markdown
![code files](https://git.tirsystem.com/OWNER/REPO/raw/branch/badges/code-files.svg)
![lines of code](https://git.tirsystem.com/OWNER/REPO/raw/branch/badges/code-lines.svg)
![coverage](https://git.tirsystem.com/OWNER/REPO/raw/branch/badges/coverage.svg)
```
Pada cermin GitHub gunakan `https://raw.githubusercontent.com/OWNER/REPO/badges/code-files.svg`; cermin push menyalin cabang itu. Cabang `badges` juga memegang `code-metrics.json` dengan angka bagi setiap bahasa. Instans menyimpan fail mentah dalam cache sehingga enam jam, jadi lencana yang berubah mungkin menunjukkan angka lama seketika di sana; cermin menyimpannya lima minit. Repositori persendirian tidak menunjukkan lencana kepada pembaca yang belum log masuk.
## Apabila aliran kerja gagal
| Mesej | Punca | Pembetulan |
| --- | --- | --- |
| `[configuration] working-directory must be a relative path inside the repository.` | Laluan mutlak atau `..` | Gunakan laluan di dalam repositori |
| `[configuration] coverage-report '<x>' does not exist.` | Laporan tidak dihasilkan, atau nama artifak atau laluan salah | Semak kerja ujian dan nama artifak; laluan berada di dalam artifak |
| `[configuration] coverage-report '<x>' is not well-formed XML` atau `is not a Cobertura report` | Fail itu berformat lain | Tulis Cobertura XML (`coverage xml`, `gcovr --cobertura`) |
| `[configuration] coverage-report '<x>' has no valid lines.` | Ujian tidak menjalankan kod yang diukur | Semak konfigurasi liputan |
| `[configuration] coverage-artifact '<x>' needs coverage-report` | Artifak dinamakan tetapi bukan fail di dalamnya | Tetapkan `coverage-report` |
| `[configuration] coverage-warn ... is above coverage-good` atau `is not a number from 0 to 100` | Ambang tidak sah | Betulkan input |
| `[configuration] badges-branch '<x>' must not be a default branch` atau `holds files that are not badge files` | Nama cabang ialah `main`, `master` atau cabang dengan kandungan lain | Gunakan cabang yang hanya memegang lencana |
| `the push to '<x>' failed twice` | Larian lain menulis cabang pada masa yang sama, atau token tidak boleh menulis | Jalankan semula; semak bahawa kerja mempunyai `contents: write` |
| Langkah muat turun gagal | Artifak tidak wujud dalam larian ini, atau kerja tidak berjalan selepas kerja ujian | Tambah `needs:` dan semak nama artifak |
@@ -1,4 +1,4 @@
<!-- translation of Credentials.md, source sha256 10a051e910de --> <!-- translation of Credentials.md, source sha256 cba8a44e1152 -->
> Dette er en oversættelse af [`Credentials`](Credentials). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. > Dette er en oversættelse af [`Credentials`](Credentials). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
@@ -56,17 +56,17 @@ Ved succes står der i loggen `Preflight passed: Gitea and GitHub accepted the c
## Secret til publicering af wiki ## Secret til publicering af wiki
Workflowet **Publish wiki** kopierer `wiki/` til Gitea-wikien i et repository, der har meldt sig til med `wiki/.publish` (se [onboarding.md](Onboarding.da)). Det bruger sin egen secret, så synkroniseringstokenet forbliver skrivebeskyttet. Workflowet **Publish wiki pages** kopierer `docs/wiki/` til Gitea-wikien i et repository, der kalder det (se [onboarding.md](Onboarding.da)). Det bruger sin egen secret, så synkroniseringstokenet forbliver skrivebeskyttet. Kalderen sender secreten i `secrets:`; den nedarves ikke.
| Secret | Indhold | Bruges til | | Secret | Indhold | Bruges til |
| --- | --- | --- | | --- | --- | --- |
| `TOKEN_FOR_GITEA_WIKI` | Gitea-tokenet, intet andet | At skrive wikisiderne for de repositories i en organisation, der melder sig til | | `TOKEN_FOR_GITEA_WIKI` | Gitea-tokenet, intet andet | At skrive wikisiderne for de repositories i en organisation, der melder sig til |
- Gem den som en secret i organisationen for hvert repository, der publicerer. Der er ingen reserve til `TOKEN_FOR_GITEA`: et repository, der har meldt sig til og ikke har secreten, fejler med en konfigurationsfejl, og et repository, der ikke har meldt sig til, behøver den ikke. - Gem den som en secret i organisationen for hvert repository, der publicerer. Der er ingen reserve til `TOKEN_FOR_GITEA`: et repository, der kalder workflowet og ikke har secreten, fejler med en konfigurationsfejl, og et repository, der ikke kalder det, behøver den ikke.
- Tokenet skal have scope `write:repository`, og dets bruger skal have skriveadgang til repositories. Gitea har intet scope kun til wikien, så opret tokenet til en dedikeret bruger. S05 godkender scopet, og S03 gemmer secreten. - Tokenet skal have scope `write:repository`, og dets bruger skal have skriveadgang til repositories. Gitea har intet scope kun til wikien, så opret tokenet til en dedikeret bruger. S05 godkender scopet, og S03 gemmer secreten.
- Kald, der foretages: `GET /repos/{owner}/{repo}`, `GET /repos/{owner}/{repo}/wiki/pages`, `GET`, `POST` og `PATCH /repos/{owner}/{repo}/wiki/...` og, for en side, der ikke længere findes i `wiki/`, `DELETE /repos/{owner}/{repo}/wiki/page/{name}`. - Kald, der foretages: `GET /repos/{owner}/{repo}`, `GET /repos/{owner}/{repo}/wiki/pages`, `GET`, `POST` og `PATCH /repos/{owner}/{repo}/wiki/...` og, for en side, der ikke længere findes i `docs/wiki/`, `DELETE /repos/{owner}/{repo}/wiki/page/{name}`.
- Wikien skal være aktiveret i repositoryets indstillinger. Workflowet nægter at køre, hvis den ikke er det. - Wikien skal være aktiveret i repositoryets indstillinger. Workflowet nægter at køre, hvis den ikke er det.
- Sider, der findes i `wiki/`, overskrives altid fra den, så en ændring i Gitea's weboverflade går tabt ved næste publicering. Sider, der kun findes i wikien, fjernes kun, når `wiki/.publish` indeholder ordet `prune`. - Sider, der findes i `docs/wiki/`, overskrives altid fra den, så en ændring i Gitea's weboverflade går tabt ved næste publicering. Sider, der kun findes i wikien, fjernes kun, når kaldet sætter `prune: "true"`.
## Udskiftning af et token ## Udskiftning af et token
@@ -88,29 +88,30 @@ is ever printed.
## Wiki publishing secret ## Wiki publishing secret
The workflow **Publish wiki** copies `wiki/` to the Gitea wiki of a repository The workflow **Publish wiki pages** copies `docs/wiki/` to the Gitea wiki of a
that has opted in with `wiki/.publish` (see [onboarding.md](Onboarding)). It repository that calls it (see [onboarding.md](Onboarding)). It uses its own
uses its own secret, so the metadata sync token stays read-only. secret, so the metadata sync token stays read-only. The caller passes the
secret in `secrets:`; it is not inherited.
| Secret | Content | Used for | | Secret | Content | Used for |
| --- | --- | --- | | --- | --- | --- |
| `TOKEN_FOR_GITEA_WIKI` | The Gitea token, nothing else | Writing the wiki pages of the repositories of an organization that opt in | | `TOKEN_FOR_GITEA_WIKI` | The Gitea token, nothing else | Writing the wiki pages of the repositories of an organization that opt in |
- Store it as a secret of the organization of each repository that publishes. - Store it as a secret of the organization of each repository that publishes.
There is no fallback to `TOKEN_FOR_GITEA`: a repository that opted in and has There is no fallback to `TOKEN_FOR_GITEA`: a repository that calls the
no such secret fails with a configuration error, and a repository that did workflow and has no such secret fails with a configuration error, and a
not opt in does not need it. repository that does not call it does not need it.
- The token needs scope `write:repository`, and its user needs write access to - The token needs scope `write:repository`, and its user needs write access to
the repositories. Gitea has no wiki-only scope, so create the token for a the repositories. Gitea has no wiki-only scope, so create the token for a
dedicated user. S05 approves the scope and S03 stores the secret. dedicated user. S05 approves the scope and S03 stores the secret.
- Calls made: `GET /repos/{owner}/{repo}`, `GET /repos/{owner}/{repo}/wiki/pages`, - Calls made: `GET /repos/{owner}/{repo}`, `GET /repos/{owner}/{repo}/wiki/pages`,
`GET`, `POST` and `PATCH /repos/{owner}/{repo}/wiki/...` and, for a page that `GET`, `POST` and `PATCH /repos/{owner}/{repo}/wiki/...` and, for a page that
no longer exists in `wiki/`, `DELETE /repos/{owner}/{repo}/wiki/page/{name}`. no longer exists in `docs/wiki/`, `DELETE /repos/{owner}/{repo}/wiki/page/{name}`.
- The wiki must be enabled in the repository settings. The workflow refuses to - The wiki must be enabled in the repository settings. The workflow refuses to
run when it is not. run when it is not.
- Pages that are in `wiki/` are always overwritten from it, so a change made - Pages that are in `docs/wiki/` are always overwritten from it, so a change
in the Gitea web UI is lost on the next publish. Pages that are only in the made in the Gitea web UI is lost on the next publish. Pages that are only in
wiki are removed only when `wiki/.publish` contains the word `prune`. the wiki are removed only when the call sets `prune: "true"`.
## Rotating a token ## Rotating a token
@@ -1,4 +1,4 @@
<!-- translation of Credentials.md, source sha256 10a051e910de --> <!-- translation of Credentials.md, source sha256 cba8a44e1152 -->
> Ini ialah terjemahan [`Credentials`](Credentials). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. > Ini ialah terjemahan [`Credentials`](Credentials). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
@@ -56,17 +56,17 @@ Apabila berjaya, log menyatakan `Preflight passed: Gitea and GitHub accepted the
## Secret untuk menerbitkan wiki ## Secret untuk menerbitkan wiki
Aliran kerja **Publish wiki** menyalin `wiki/` ke wiki Gitea bagi repositori yang telah memilih menyertainya dengan `wiki/.publish` (lihat [onboarding.md](Onboarding.ms)). Ia menggunakan secret sendiri, supaya token penyegerakan metadata kekal baca sahaja. Aliran kerja **Publish wiki pages** menyalin `docs/wiki/` ke wiki Gitea bagi repositori yang memanggilnya (lihat [onboarding.md](Onboarding.ms)). Ia menggunakan secret sendiri, supaya token penyegerakan metadata kekal baca sahaja. Pemanggil menghantar secret dalam `secrets:`; ia tidak diwarisi.
| Secret | Kandungan | Digunakan untuk | | Secret | Kandungan | Digunakan untuk |
| --- | --- | --- | | --- | --- | --- |
| `TOKEN_FOR_GITEA_WIKI` | Token Gitea, tiada yang lain | Menulis halaman wiki bagi repositori dalam organisasi yang memilih menyertainya | | `TOKEN_FOR_GITEA_WIKI` | Token Gitea, tiada yang lain | Menulis halaman wiki bagi repositori dalam organisasi yang memilih menyertainya |
- Simpan sebagai secret organisasi bagi setiap repositori yang menerbitkan. Tiada sandaran kepada `TOKEN_FOR_GITEA`: repositori yang telah memilih menyertainya dan tidak mempunyai secret itu gagal dengan ralat konfigurasi, dan repositori yang tidak memilih tidak memerlukannya. - Simpan sebagai secret organisasi bagi setiap repositori yang menerbitkan. Tiada sandaran kepada `TOKEN_FOR_GITEA`: repositori yang memanggil aliran kerja dan tidak mempunyai secret itu gagal dengan ralat konfigurasi, dan repositori yang tidak memanggilnya tidak memerlukannya.
- Token mesti mempunyai skop `write:repository`, dan penggunanya mesti mempunyai akses tulis kepada repositori. Gitea tidak mempunyai skop untuk wiki sahaja, jadi cipta token untuk pengguna khusus. S05 meluluskan skop dan S03 menyimpan secret. - Token mesti mempunyai skop `write:repository`, dan penggunanya mesti mempunyai akses tulis kepada repositori. Gitea tidak mempunyai skop untuk wiki sahaja, jadi cipta token untuk pengguna khusus. S05 meluluskan skop dan S03 menyimpan secret.
- Panggilan yang dibuat: `GET /repos/{owner}/{repo}`, `GET /repos/{owner}/{repo}/wiki/pages`, `GET`, `POST` dan `PATCH /repos/{owner}/{repo}/wiki/...` dan, bagi halaman yang tidak lagi wujud dalam `wiki/`, `DELETE /repos/{owner}/{repo}/wiki/page/{name}`. - Panggilan yang dibuat: `GET /repos/{owner}/{repo}`, `GET /repos/{owner}/{repo}/wiki/pages`, `GET`, `POST` dan `PATCH /repos/{owner}/{repo}/wiki/...` dan, bagi halaman yang tidak lagi wujud dalam `docs/wiki/`, `DELETE /repos/{owner}/{repo}/wiki/page/{name}`.
- Wiki mesti diaktifkan dalam tetapan repositori. Aliran kerja enggan berjalan jika ia tidak diaktifkan. - Wiki mesti diaktifkan dalam tetapan repositori. Aliran kerja enggan berjalan jika ia tidak diaktifkan.
- Halaman yang ada dalam `wiki/` sentiasa ditimpa daripadanya, jadi perubahan yang dibuat dalam antara muka web Gitea hilang pada penerbitan seterusnya. Halaman yang hanya ada dalam wiki dibuang hanya apabila `wiki/.publish` mengandungi perkataan `prune`. - Halaman yang ada dalam `docs/wiki/` sentiasa ditimpa daripadanya, jadi perubahan yang dibuat dalam antara muka web Gitea hilang pada penerbitan seterusnya. Halaman yang hanya ada dalam wiki dibuang hanya apabila panggilan menetapkan `prune: "true"`.
## Menukar token ## Menukar token
+226 -48
View File
@@ -1,4 +1,4 @@
<!-- translation of Home.md, source sha256 c9c04abaf4ae --> <!-- translation of Home.md, source sha256 810981fb9bc8 -->
> Dette er en oversættelse af [`Home`](Home). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. > Dette er en oversættelse af [`Home`](Home). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
@@ -13,18 +13,22 @@ En samling af genbrugelige Gitea Actions-workflows til DevOps-professionelle og
Dette repository er den centrale samling af genbrugelig automatisering til TirSystems repositories. Det primære repository ligger på `git.tirsystem.com` (Gitea) og spejles med push til GitHub. Dette repository er den centrale samling af genbrugelig automatisering til TirSystems repositories. Det primære repository ligger på `git.tirsystem.com` (Gitea) og spejles med push til GitHub.
Det indeholder seks workflows: synkronisering af metadata til GitHub-spejlet, publicering af wiki, fælles kvalitetskontrol, et udgivelsesworkflow, publicering af accepterede milepæle og repositoryets egen validering. De er listet med formål og diagram under [Workflows](#workflows). Det indeholder ti workflows: synkronisering af metadata til GitHub-spejlet, publicering af wiki (kaldt af hvert repository), fælles kvalitetskontrol for Python og shell, for C og C++ og for C#, kodemålinger og dækningsbadges, et udgivelsesworkflow, publicering af accepterede milepæle og repositoryets egen validering. De er listet med formål og diagram under [Workflows](#workflows).
## Workflows ## Workflows
Der er seks workflows i dette repository. Et **scoped** workflow leveres af instansen til alle repositories i TirSystem-organisationerne og kræver intet i repositoryet; et **genbrugeligt** workflow kaldes fra et repository med `uses:`; de øvrige kører kun her. Diagrammerne er PlantUML-kildekode og er på engelsk. Der er ti workflows i dette repository. Et **scoped** workflow leveres af instansen til alle repositories i TirSystem-organisationerne og kræver intet i repositoryet; et **genbrugeligt** workflow kaldes fra et repository med `uses:`; de øvrige kører kun her. Diagrammerne er PlantUML-kildekode og er på engelsk.
| Workflow | Fil | Type | Starter ved | Formål | | Workflow | Fil | Type | Starter ved | Formål |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| Sync GitHub mirror metadata | [`sync-github-metadata.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push til `main`, manuelt, dagligt kl. 03:17 UTC | Kopierer beskrivelsen og emnerne for et Gitea-repository til dets GitHub push-spejl | | Sync GitHub mirror metadata | [`sync-github-metadata.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push til `main`, manuelt, dagligt kl. 03:17 UTC | Kopierer beskrivelsen og emnerne for et Gitea-repository til dets GitHub push-spejl |
| Publish wiki | [`publish-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/publish-wiki.yml) | Scoped, tilmelding med `wiki/.publish` | push til `main`, der ændrer `wiki/**`, manuelt | Publicerer siderne i `wiki/` til repositoryets wiki | | Publish wiki pages | [`publish-wiki-pages.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/publish-wiki-pages.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Publicerer siderne i `docs/wiki/` (eller den mappe, `wiki_dir` angiver) til wikien i det kaldende repository |
| Quality checks | [`quality-checks.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Kører ruff, mypy, shellcheck og shfmt på de sporede filer og ændrer intet | | Quality checks | [`quality-checks.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Kører ruff, mypy, shellcheck og shfmt på de sporede filer og ændrer intet |
| Quality checks C++ | [`quality-checks-cpp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-cpp.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Kører clang-format og clang-tidy i præcise versioner på de sporede C- og C++-filer og ændrer intet |
| Quality checks C# | [`quality-checks-csharp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-csharp.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Kører `dotnet format` og `dotnet build` på et præcist .NET SDK 10 eller nyere og ændrer intet |
| Code metrics | [`code-metrics.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/code-metrics.yml) | Genbrugeligt (`workflow_call`) | et kald fra et andet repository | Tæller kodefilerne og kodelinjerne (uden kommentar- og tomme linjer), læser en Cobertura-dækningsrapport og committer tre SVG-badges til en `badges`-gren |
| Publish own wiki | [`publish-own-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/publish-own-wiki.yml) | Kun dette repository | push til `main`, der ændrer `docs/wiki/**`, manuelt | Kalder det genbrugelige workflow Publish wiki pages for dette repositorys wiki |
| Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | Kun dette repository | manuelt | Opretter versionstagget, udgivelsessiden og major-tagget efter kontrollerne | | Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | Kun dette repository | manuelt | Opretter versionstagget, udgivelsessiden og major-tagget efter kontrollerne |
| Publicering af accepterede milepæle | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | Kun dette repository | push til `main`, der ændrer `docs/milestones/**` eller `docs/sqa/reviews/**`, manuelt (tørkørsel som standard) | Opretter eller opdaterer Gitea-milepælen og issues for hver milepæl, der er Accepted med en Go-gennemgang | | Publicering af accepterede milepæle | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | Kun dette repository | push til `main`, der ændrer `docs/milestones/**` eller `docs/sqa/reviews/**`, manuelt (tørkørsel som standard) | Opretter eller opdaterer Gitea-milepælen og issues for hver milepæl, der er Accepted med en Go-gennemgang |
| Validate workflows | [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml) | Kun dette repository | pull request, push til `main`, manuelt | Læser hvert workflow som YAML og kører offline-testene | | Validate workflows | [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml) | Kun dette repository | pull request, push til `main`, manuelt | Læser hvert workflow som YAML og kører offline-testene |
@@ -95,50 +99,6 @@ deactivate Workflow
@enduml @enduml
``` ```
### Publicering af wiki
Publicerer `wiki/` til repositoryets wiki, kun i et repository, der har tilmeldt sig med `wiki/.publish`. Ordet `prune` i den fil sletter også sider, som ikke findes i `wiki/`.
#### Workflow
```plantuml
@startuml
title Publish wiki/ to the repository wiki
autonumber
actor "Push to main that changes wiki/**,\nor manual trigger" as Trigger
participant "Gitea Actions\npublish-wiki" as Workflow
participant "shared-scripts\n(tools/publish_wiki.py at v1)" as Script
participant "Gitea wiki API" as Wiki
Trigger -> Workflow: Start workflow
activate Workflow
Workflow -> Script: Check out the repository and the script, run it
activate Script
alt wiki/.publish does not exist
Script --> Workflow: Log that publishing is not enabled and succeed\n(nothing is written)
else wiki/ has no Markdown pages
Script --> Workflow: Fail with [configuration]\n(an empty wiki/ never deletes pages)
else
Script -> Script: Read the marker (empty, or the word prune)\nand TOKEN_FOR_GITEA_WIKI
Script -> Wiki: GET repository (preflight)
Wiki --> Script: Wiki is enabled, token accepted
Script -> Wiki: GET pages
Wiki --> Script: Existing pages
Script -> Wiki: Create new pages, update changed pages
opt marker says prune
Script -> Wiki: Delete pages that are not in wiki/
end
Script --> Workflow: Log created, updated and deleted counts
end
deactivate Script
Workflow --> Trigger: Workflow result
deactivate Workflow
@enduml
```
### Kvalitetskontrol ### Kvalitetskontrol
Et genbrugeligt workflow til Python og shell, kaldt med `uses: TirSystem/github-action/.gitea/workflows/quality-checks.yml@v1` (se [vejledningen](QualityChecksPythonShell)). Et genbrugeligt workflow til Python og shell, kaldt med `uses: TirSystem/github-action/.gitea/workflows/quality-checks.yml@v1` (se [vejledningen](QualityChecksPythonShell)).
@@ -185,6 +145,140 @@ deactivate Workflow
@enduml @enduml
``` ```
### Kvalitetskontrol C++
Et genbrugeligt workflow til C og C++, kaldt med `uses: TirSystem/github-action/.gitea/workflows/quality-checks-cpp.yml@v1` (ADR-0008). Repositoryet skal have en `.clang-format`; en `.clang-tidy` og en compile-database er valgfrie.
#### Workflow
```plantuml
@startuml
title Shared quality checks for C and C++ (reusable workflow)
autonumber
actor "Calling repository\nuses: ...quality-checks-cpp.yml@v1" as Caller
participant "Gitea Actions\nquality-checks-cpp" as Workflow
participant "PyPI" as PyPI
Caller -> Workflow: workflow_call with working-directory, std,\ncompile-commands-dir and tool versions
activate Workflow
Workflow -> Workflow: Check out the caller and the shared script
Workflow -> Workflow: Validate the inputs
alt an input is invalid or a version is not exact
Workflow --> Caller: Fail with [configuration]
end
Workflow -> PyPI: Install clang-format and clang-tidy at the exact versions
PyPI --> Workflow: Tools installed
alt no C or C++ file is tracked
Workflow --> Caller: Notice: nothing was checked, pass
else no .clang-format, or compile-commands-dir\nwithout compile_commands.json
Workflow --> Caller: Fail with [configuration]
else
Workflow -> Workflow: clang-format --dry-run -Werror on all files
Workflow -> Workflow: clang-tidy on the C++ files (-std=std)\nand on the C files (or with the compile database)
alt any tool failed
Workflow --> Caller: Fail with tool, file and line in the log
else
Workflow --> Caller: All selected checks passed
end
end
deactivate Workflow
@enduml
```
### Kvalitetskontrol C#
Et genbrugeligt workflow til C#, kaldt med `uses: TirSystem/github-action/.gitea/workflows/quality-checks-csharp.yml@v1` (ADR-0008). Det installerer det præcise .NET SDK, som `dotnet-sdk-version` angiver (10 eller nyere), og skal bruge netværket til pakkegendannelsen.
#### Workflow
```plantuml
@startuml
title Shared quality checks for C# (reusable workflow)
autonumber
actor "Calling repository\nuses: ...quality-checks-csharp.yml@v1" as Caller
participant "Gitea Actions\nquality-checks-csharp" as Workflow
participant "dot.net\n(dotnet-install.sh)" as DotNet
Caller -> Workflow: workflow_call with working-directory,\nproject and dotnet-sdk-version
activate Workflow
Workflow -> Workflow: Check out the caller and the shared script
Workflow -> Workflow: Validate the inputs
alt an input is invalid or the SDK is not an exact\nstable version 10 or later
Workflow --> Caller: Fail with [configuration]
end
Workflow -> DotNet: Install the exact .NET SDK version
DotNet --> Workflow: SDK installed
alt no C# file is tracked
Workflow --> Caller: Notice: nothing was checked, pass
else no solution or project, or several without the project input
Workflow --> Caller: Fail with [configuration] that names them
else
Workflow -> Workflow: dotnet format --verify-no-changes
Workflow -> Workflow: dotnet build -warnaserror\n(package restore needs the network)
alt any tool failed
Workflow --> Caller: Fail with tool, file and line in the log
else
Workflow --> Caller: All selected checks passed
end
end
deactivate Workflow
@enduml
```
### Kodemålinger og dækningsbadges
Et genbrugeligt workflow, der tæller kodefilerne og kodelinjerne (uden kommentar- og tomme linjer) i et repository og udgiver dem sammen med testdækningen som tre SVG-badges på en `badges`-gren, kaldt med `uses: TirSystem/github-action/.gitea/workflows/code-metrics.yml@v1` (ADR-0010). Det kræver `contents: write` og kører ingen test: et tidligere job hos kalderen frembringer en dækningsrapport i Cobertura XML-format og uploader den som artefakt ([vejledning](CodeMetrics.da)).
#### Workflow
```plantuml
@startuml
title Code metrics and coverage badges (reusable workflow)
autonumber
actor "Calling repository\nuses: ...code-metrics.yml@v1" as Caller
participant "Gitea Actions\ncode-metrics" as Workflow
participant "Branch badges\nof the caller" as Branch
Caller -> Workflow: workflow_call with working-directory, exclude,\ncoverage-report and thresholds
activate Workflow
Workflow -> Workflow: Check out the caller and the shared scripts
Workflow -> Workflow: Validate the inputs
alt an input is invalid
Workflow --> Caller: Fail with [configuration]
end
Workflow -> Workflow: Download the coverage artifact (when given)
Workflow -> Workflow: Count code files and lines of code
alt a coverage report is given but missing or malformed
Workflow --> Caller: Fail with [configuration] that names the file
else
Workflow -> Workflow: Write code-files.svg, code-lines.svg\nand coverage.svg
end
Workflow -> Branch: Compare with the files of the branch
alt no badge changed
Workflow --> Caller: Notice: nothing to commit, pass
else
Workflow -> Branch: One commit with [skip ci]
Workflow --> Caller: Badges published
end
deactivate Workflow
@enduml
```
### Udgivelse ### Udgivelse
Startes manuelt af en vedligeholder; med `dry_run` `true` (standard) kontrollerer det kun (se [udgivelsesprocessen](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)). Startes manuelt af en vedligeholder; med `dry_run` `true` (standard) kontrollerer det kun (se [udgivelsesprocessen](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)).
@@ -234,6 +328,90 @@ deactivate Workflow
@enduml @enduml
``` ```
### Publicering af wiki-sider
Et genbrugeligt workflow (ADR-0009). Et repository kalder det fra sit eget workflow, så selve kaldet er tilmeldingen, og der behøves ingen markørfil. Det publicerer Markdown-siderne i `wiki_dir` (standard `docs/wiki`) til wikien i det kaldende repository og sletter kun sider, der ikke findes i mappen, når `prune` er `true`.
```yaml
on:
push:
branches: [ main ]
paths: [ "docs/wiki/**" ]
jobs:
wiki:
uses: TirSystem/github-action/.gitea/workflows/publish-wiki-pages.yml@v1
secrets:
TOKEN_FOR_GITEA_WIKI: ${{ secrets.TOKEN_FOR_GITEA_WIKI }}
```
#### Workflow
```plantuml
@startuml
title Publish docs/wiki to the wiki of the calling repository
autonumber
actor "Calling repository\nuses: ...publish-wiki-pages.yml@v1" as Caller
participant "Gitea Actions\npublish-wiki-pages" as Workflow
participant "shared-scripts\n(tools/publish_wiki.py at tools-ref)" as Script
participant "Gitea wiki API" as Wiki
Caller -> Workflow: Call with wiki_dir, prune and the secret
activate Workflow
Workflow -> Script: Check out the repository and the script, run it
activate Script
alt wiki_dir does not exist, or has no Markdown pages
Script --> Workflow: Fail with [configuration]\n(an empty folder never deletes pages)
else
Script -> Script: Read prune and TOKEN_FOR_GITEA_WIKI
Script -> Wiki: GET repository (preflight)
Wiki --> Script: Wiki is enabled, token accepted
Script -> Wiki: GET pages
Wiki --> Script: Existing pages
Script -> Wiki: Create new pages, update changed pages
opt prune is true
Script -> Wiki: Delete pages that are not in wiki_dir
end
Script --> Workflow: Log created, updated and deleted counts
end
deactivate Script
Workflow --> Caller: Workflow result
deactivate Workflow
@enduml
```
### Publicering af egen wiki
Dette repositorys wiki er `docs/wiki/`. Workflowet kalder det genbrugelige workflow ovenfor med dets lokale sti, med `prune` sat til `true`, så wikien altid er lig med mappen, og `tools-ref` sat til `main`, så scriptet fra samme commit kører. Det er den kalder, et projekt kopierer fra frameworkets `templates/ci/publish-wiki.yml`.
#### Workflow
```plantuml
@startuml
title Publish the wiki of this repository (docs/wiki)
autonumber
actor "Push to main that changes docs/wiki/**,\nor manual trigger" as Trigger
participant "Gitea Actions\npublish-own-wiki" as Caller
participant "Gitea Actions\npublish-wiki-pages\n(called by its local path)" as Workflow
participant "Gitea wiki API" as Wiki
Trigger -> Caller: Start workflow
activate Caller
Caller -> Workflow: Call with wiki_dir docs/wiki, prune true,\ntools-ref main and TOKEN_FOR_GITEA_WIKI
activate Workflow
Workflow -> Wiki: Create, update and delete pages\nso the wiki equals docs/wiki
Wiki --> Workflow: Done
Workflow --> Caller: Workflow result
deactivate Workflow
Caller --> Trigger: Workflow result
deactivate Caller
@enduml
```
### Publicering af accepterede milepæle ### Publicering af accepterede milepæle
Permanent tilladelse til at publicere: ved et push til `main`, der ændrer milepælene eller deres gennemgange, oprettes eller opdateres hver milepæl, hvis seneste række i Version History er Accepted, og hvis seneste gennemgang er en Go, på Gitea med sine opgaver som issues. Intet lukkes eller slettes. En manuel kørsel er en tørkørsel, medmindre `dry_run` slås fra. Det bruger secret'en `TOKEN_FOR_GITEA` og `--accepted-only`-reglen i det monterede framework (v0.5.0 eller nyere). Permanent tilladelse til at publicere: ved et push til `main`, der ændrer milepælene eller deres gennemgange, oprettes eller opdateres hver milepæl, hvis seneste række i Version History er Accepted, og hvis seneste gennemgang er en Go, på Gitea med sine opgaver som issues. Intet lukkes eller slettes. En manuel kørsel er en tørkørsel, medmindre `dry_run` slås fra. Det bruger secret'en `TOKEN_FOR_GITEA` og `--accepted-only`-reglen i det monterede framework (v0.5.0 eller nyere).
+225 -47
View File
@@ -13,18 +13,22 @@ A collection of reusable Gitea Actions workflows for DevOps professionals and so
This repository is the central collection of reusable automation for TirSystem repositories. The primary repository lives at `git.tirsystem.com` (Gitea) and is push-mirrored to GitHub. This repository is the central collection of reusable automation for TirSystem repositories. The primary repository lives at `git.tirsystem.com` (Gitea) and is push-mirrored to GitHub.
It contains six workflows: metadata sync to the GitHub mirror, wiki publishing, shared quality checks, a release workflow, the publishing of accepted milestones and the repository's own validation. They are listed with their purpose and a diagram under [Workflows](#workflows). It contains ten workflows: metadata sync to the GitHub mirror, wiki publishing (called by each repository), shared quality checks for Python and shell, for C and C++ and for C#, code metrics and coverage badges, a release workflow, the publishing of accepted milestones and the repository's own validation. They are listed with their purpose and a diagram under [Workflows](#workflows).
## Workflows ## Workflows
Six workflows live in this repository. A **scoped** workflow is delivered by the instance to every repository of the TirSystem organizations and needs nothing in the repository; a **reusable** workflow is called by a repository with `uses:`; the others run only here. The diagrams are PlantUML source. Ten workflows live in this repository. A **scoped** workflow is delivered by the instance to every repository of the TirSystem organizations and needs nothing in the repository; a **reusable** workflow is called by a repository with `uses:`; the others run only here. The diagrams are PlantUML source.
| Workflow | File | Kind | Starts on | Purpose | | Workflow | File | Kind | Starts on | Purpose |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| Sync GitHub mirror metadata | [`sync-github-metadata.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push to `main`, manual, daily 03:17 UTC | Copies the description and topics of a Gitea repository to its GitHub push mirror | | Sync GitHub mirror metadata | [`sync-github-metadata.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push to `main`, manual, daily 03:17 UTC | Copies the description and topics of a Gitea repository to its GitHub push mirror |
| Publish wiki | [`publish-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/publish-wiki.yml) | Scoped, opt-in with `wiki/.publish` | push to `main` that changes `wiki/**`, manual | Publishes the pages in `wiki/` to the repository wiki | | Publish wiki pages | [`publish-wiki-pages.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/publish-wiki-pages.yml) | Reusable (`workflow_call`) | a call from another repository | Publishes the pages in `docs/wiki/` (or the folder named by `wiki_dir`) to the wiki of the calling repository |
| Quality checks | [`quality-checks.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks.yml) | Reusable (`workflow_call`) | a call from another repository | Runs ruff, mypy, shellcheck and shfmt on the tracked files and changes nothing | | Quality checks | [`quality-checks.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks.yml) | Reusable (`workflow_call`) | a call from another repository | Runs ruff, mypy, shellcheck and shfmt on the tracked files and changes nothing |
| Quality checks C++ | [`quality-checks-cpp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-cpp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs clang-format and clang-tidy at exact versions on the tracked C and C++ files and changes nothing |
| Quality checks C# | [`quality-checks-csharp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-csharp.yml) | Reusable (`workflow_call`) | a call from another repository | Runs `dotnet format` and `dotnet build` on an exact .NET SDK 10 or later and changes nothing |
| Code metrics | [`code-metrics.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/code-metrics.yml) | Reusable (`workflow_call`) | a call from another repository | Counts the code files and the lines of code (without comment and blank lines), reads a Cobertura coverage report and commits three SVG badges to a `badges` branch |
| Publish own wiki | [`publish-own-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/publish-own-wiki.yml) | This repository only | push to `main` that changes `docs/wiki/**`, manual | Calls the reusable Publish wiki pages workflow for the wiki of this repository |
| Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | This repository only | manual | Creates the version tag, the release page and the major tag after the checks | | Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | This repository only | manual | Creates the version tag, the release page and the major tag after the checks |
| Publish accepted milestones | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | This repository only | push to `main` that changes `docs/milestones/**` or `docs/sqa/reviews/**`, manual (dry run by default) | Creates or updates the Gitea milestone and issues of every milestone that is Accepted with a Go review | | Publish accepted milestones | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | This repository only | push to `main` that changes `docs/milestones/**` or `docs/sqa/reviews/**`, manual (dry run by default) | Creates or updates the Gitea milestone and issues of every milestone that is Accepted with a Go review |
| Validate workflows | [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml) | This repository only | pull request, push to `main`, manual | Parses every workflow and runs the offline tests | | Validate workflows | [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml) | This repository only | pull request, push to `main`, manual | Parses every workflow and runs the offline tests |
@@ -95,50 +99,6 @@ deactivate Workflow
@enduml @enduml
``` ```
### Publish wiki
Publishes `wiki/` to the repository wiki, only in a repository that has opted in with `wiki/.publish`. The word `prune` in that file also deletes pages that are not in `wiki/`.
#### Workflow
```plantuml
@startuml
title Publish wiki/ to the repository wiki
autonumber
actor "Push to main that changes wiki/**,\nor manual trigger" as Trigger
participant "Gitea Actions\npublish-wiki" as Workflow
participant "shared-scripts\n(tools/publish_wiki.py at v1)" as Script
participant "Gitea wiki API" as Wiki
Trigger -> Workflow: Start workflow
activate Workflow
Workflow -> Script: Check out the repository and the script, run it
activate Script
alt wiki/.publish does not exist
Script --> Workflow: Log that publishing is not enabled and succeed\n(nothing is written)
else wiki/ has no Markdown pages
Script --> Workflow: Fail with [configuration]\n(an empty wiki/ never deletes pages)
else
Script -> Script: Read the marker (empty, or the word prune)\nand TOKEN_FOR_GITEA_WIKI
Script -> Wiki: GET repository (preflight)
Wiki --> Script: Wiki is enabled, token accepted
Script -> Wiki: GET pages
Wiki --> Script: Existing pages
Script -> Wiki: Create new pages, update changed pages
opt marker says prune
Script -> Wiki: Delete pages that are not in wiki/
end
Script --> Workflow: Log created, updated and deleted counts
end
deactivate Script
Workflow --> Trigger: Workflow result
deactivate Workflow
@enduml
```
### Quality checks ### Quality checks
A reusable workflow for Python and shell, called with `uses: TirSystem/github-action/.gitea/workflows/quality-checks.yml@v1` (see the [guide](QualityChecksPythonShell)). A reusable workflow for Python and shell, called with `uses: TirSystem/github-action/.gitea/workflows/quality-checks.yml@v1` (see the [guide](QualityChecksPythonShell)).
@@ -185,6 +145,140 @@ deactivate Workflow
@enduml @enduml
``` ```
### Quality checks C++
A reusable workflow for C and C++, called with `uses: TirSystem/github-action/.gitea/workflows/quality-checks-cpp.yml@v1` (ADR-0008). The repository needs a `.clang-format`; a `.clang-tidy` and a compile database are optional.
#### Workflow
```plantuml
@startuml
title Shared quality checks for C and C++ (reusable workflow)
autonumber
actor "Calling repository\nuses: ...quality-checks-cpp.yml@v1" as Caller
participant "Gitea Actions\nquality-checks-cpp" as Workflow
participant "PyPI" as PyPI
Caller -> Workflow: workflow_call with working-directory, std,\ncompile-commands-dir and tool versions
activate Workflow
Workflow -> Workflow: Check out the caller and the shared script
Workflow -> Workflow: Validate the inputs
alt an input is invalid or a version is not exact
Workflow --> Caller: Fail with [configuration]
end
Workflow -> PyPI: Install clang-format and clang-tidy at the exact versions
PyPI --> Workflow: Tools installed
alt no C or C++ file is tracked
Workflow --> Caller: Notice: nothing was checked, pass
else no .clang-format, or compile-commands-dir\nwithout compile_commands.json
Workflow --> Caller: Fail with [configuration]
else
Workflow -> Workflow: clang-format --dry-run -Werror on all files
Workflow -> Workflow: clang-tidy on the C++ files (-std=std)\nand on the C files (or with the compile database)
alt any tool failed
Workflow --> Caller: Fail with tool, file and line in the log
else
Workflow --> Caller: All selected checks passed
end
end
deactivate Workflow
@enduml
```
### Quality checks C#
A reusable workflow for C#, called with `uses: TirSystem/github-action/.gitea/workflows/quality-checks-csharp.yml@v1` (ADR-0008). It installs the exact .NET SDK given by `dotnet-sdk-version` (10 or later) and needs the network for the package restore.
#### Workflow
```plantuml
@startuml
title Shared quality checks for C# (reusable workflow)
autonumber
actor "Calling repository\nuses: ...quality-checks-csharp.yml@v1" as Caller
participant "Gitea Actions\nquality-checks-csharp" as Workflow
participant "dot.net\n(dotnet-install.sh)" as DotNet
Caller -> Workflow: workflow_call with working-directory,\nproject and dotnet-sdk-version
activate Workflow
Workflow -> Workflow: Check out the caller and the shared script
Workflow -> Workflow: Validate the inputs
alt an input is invalid or the SDK is not an exact\nstable version 10 or later
Workflow --> Caller: Fail with [configuration]
end
Workflow -> DotNet: Install the exact .NET SDK version
DotNet --> Workflow: SDK installed
alt no C# file is tracked
Workflow --> Caller: Notice: nothing was checked, pass
else no solution or project, or several without the project input
Workflow --> Caller: Fail with [configuration] that names them
else
Workflow -> Workflow: dotnet format --verify-no-changes
Workflow -> Workflow: dotnet build -warnaserror\n(package restore needs the network)
alt any tool failed
Workflow --> Caller: Fail with tool, file and line in the log
else
Workflow --> Caller: All selected checks passed
end
end
deactivate Workflow
@enduml
```
### Code metrics
A reusable workflow that counts the code files and the lines of code (without comment and blank lines) of a repository and publishes them, with the test coverage, as three SVG badges on a `badges` branch, called with `uses: TirSystem/github-action/.gitea/workflows/code-metrics.yml@v1` (ADR-0010). It needs `contents: write` and runs no test: an earlier job of the caller produces a Cobertura XML coverage report and uploads it as an artifact ([guide](CodeMetrics)).
#### Workflow
```plantuml
@startuml
title Code metrics and coverage badges (reusable workflow)
autonumber
actor "Calling repository\nuses: ...code-metrics.yml@v1" as Caller
participant "Gitea Actions\ncode-metrics" as Workflow
participant "Branch badges\nof the caller" as Branch
Caller -> Workflow: workflow_call with working-directory, exclude,\ncoverage-report and thresholds
activate Workflow
Workflow -> Workflow: Check out the caller and the shared scripts
Workflow -> Workflow: Validate the inputs
alt an input is invalid
Workflow --> Caller: Fail with [configuration]
end
Workflow -> Workflow: Download the coverage artifact (when given)
Workflow -> Workflow: Count code files and lines of code
alt a coverage report is given but missing or malformed
Workflow --> Caller: Fail with [configuration] that names the file
else
Workflow -> Workflow: Write code-files.svg, code-lines.svg\nand coverage.svg
end
Workflow -> Branch: Compare with the files of the branch
alt no badge changed
Workflow --> Caller: Notice: nothing to commit, pass
else
Workflow -> Branch: One commit with [skip ci]
Workflow --> Caller: Badges published
end
deactivate Workflow
@enduml
```
### Release ### Release
Started by hand by a maintainer; with `dry_run` `true` (the default) it only checks (see the [release process](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)). Started by hand by a maintainer; with `dry_run` `true` (the default) it only checks (see the [release process](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)).
@@ -234,6 +328,90 @@ deactivate Workflow
@enduml @enduml
``` ```
### Publish wiki pages
A reusable workflow (ADR-0009). A repository calls it from its own workflow, so the call is the opt-in and no marker file is needed. It publishes the Markdown pages of `wiki_dir` (default `docs/wiki`) to the wiki of the calling repository, and deletes pages that are not in the folder only when `prune` is `true`.
```yaml
on:
push:
branches: [ main ]
paths: [ "docs/wiki/**" ]
jobs:
wiki:
uses: TirSystem/github-action/.gitea/workflows/publish-wiki-pages.yml@v1
secrets:
TOKEN_FOR_GITEA_WIKI: ${{ secrets.TOKEN_FOR_GITEA_WIKI }}
```
#### Workflow
```plantuml
@startuml
title Publish docs/wiki to the wiki of the calling repository
autonumber
actor "Calling repository\nuses: ...publish-wiki-pages.yml@v1" as Caller
participant "Gitea Actions\npublish-wiki-pages" as Workflow
participant "shared-scripts\n(tools/publish_wiki.py at tools-ref)" as Script
participant "Gitea wiki API" as Wiki
Caller -> Workflow: Call with wiki_dir, prune and the secret
activate Workflow
Workflow -> Script: Check out the repository and the script, run it
activate Script
alt wiki_dir does not exist, or has no Markdown pages
Script --> Workflow: Fail with [configuration]\n(an empty folder never deletes pages)
else
Script -> Script: Read prune and TOKEN_FOR_GITEA_WIKI
Script -> Wiki: GET repository (preflight)
Wiki --> Script: Wiki is enabled, token accepted
Script -> Wiki: GET pages
Wiki --> Script: Existing pages
Script -> Wiki: Create new pages, update changed pages
opt prune is true
Script -> Wiki: Delete pages that are not in wiki_dir
end
Script --> Workflow: Log created, updated and deleted counts
end
deactivate Script
Workflow --> Caller: Workflow result
deactivate Workflow
@enduml
```
### Publish own wiki
The wiki of this repository is `docs/wiki/`. This workflow calls the reusable workflow above by its local path, with `prune` set to `true` so the wiki always equals the folder and `tools-ref` set to `main` so that the script of the same commit runs. It is the caller that a project copies from the framework's `templates/ci/publish-wiki.yml`.
#### Workflow
```plantuml
@startuml
title Publish the wiki of this repository (docs/wiki)
autonumber
actor "Push to main that changes docs/wiki/**,\nor manual trigger" as Trigger
participant "Gitea Actions\npublish-own-wiki" as Caller
participant "Gitea Actions\npublish-wiki-pages\n(called by its local path)" as Workflow
participant "Gitea wiki API" as Wiki
Trigger -> Caller: Start workflow
activate Caller
Caller -> Workflow: Call with wiki_dir docs/wiki, prune true,\ntools-ref main and TOKEN_FOR_GITEA_WIKI
activate Workflow
Workflow -> Wiki: Create, update and delete pages\nso the wiki equals docs/wiki
Wiki --> Workflow: Done
Workflow --> Caller: Workflow result
deactivate Workflow
Caller --> Trigger: Workflow result
deactivate Caller
@enduml
```
### Publish accepted milestones ### Publish accepted milestones
Standing authorization to publish: on a push to `main` that changes the milestones or their reviews, every milestone whose latest Version History row is Accepted and whose latest review is a Go is created or updated on Gitea with its tasks as issues. Nothing is closed or deleted. A manual run is a dry run unless `dry_run` is switched off. It uses the secret `TOKEN_FOR_GITEA` and the `--accepted-only` rule of the mounted framework (v0.5.0 or later). Standing authorization to publish: on a push to `main` that changes the milestones or their reviews, every milestone whose latest Version History row is Accepted and whose latest review is a Go is created or updated on Gitea with its tasks as issues. Nothing is closed or deleted. A manual run is a dry run unless `dry_run` is switched off. It uses the secret `TOKEN_FOR_GITEA` and the `--accepted-only` rule of the mounted framework (v0.5.0 or later).
+226 -48
View File
@@ -1,4 +1,4 @@
<!-- translation of Home.md, source sha256 c9c04abaf4ae --> <!-- translation of Home.md, source sha256 810981fb9bc8 -->
> Ini ialah terjemahan [`Home`](Home). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. > Ini ialah terjemahan [`Home`](Home). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
@@ -13,18 +13,22 @@ Koleksi aliran kerja (workflow) Gitea Actions yang boleh digunakan semula untuk
Repositori ini ialah koleksi pusat bagi automasi yang boleh digunakan semula untuk repositori TirSystem. Repositori utama berada di `git.tirsystem.com` (Gitea) dan dicerminkan dengan push ke GitHub. Repositori ini ialah koleksi pusat bagi automasi yang boleh digunakan semula untuk repositori TirSystem. Repositori utama berada di `git.tirsystem.com` (Gitea) dan dicerminkan dengan push ke GitHub.
Ia mengandungi enam aliran kerja: penyegerakan metadata ke cermin GitHub, penerbitan wiki, semakan kualiti bersama, aliran kerja keluaran, penerbitan pencapaian (milestone) yang diterima dan pengesahan repositori sendiri. Ia disenaraikan dengan tujuan dan rajah di bawah [Aliran kerja](#aliran-kerja). Ia mengandungi sepuluh aliran kerja: penyegerakan metadata ke cermin GitHub, penerbitan wiki (dipanggil oleh setiap repositori), semakan kualiti bersama untuk Python dan shell, untuk C dan C++ serta untuk C#, metrik kod dan lencana liputan, aliran kerja keluaran, penerbitan pencapaian (milestone) yang diterima dan pengesahan repositori sendiri. Ia disenaraikan dengan tujuan dan rajah di bawah [Aliran kerja](#aliran-kerja).
## Aliran kerja ## Aliran kerja
Terdapat enam aliran kerja dalam repositori ini. Aliran kerja **scoped** dihantar oleh instans ke setiap repositori organisasi TirSystem dan tidak memerlukan apa-apa dalam repositori; aliran kerja **boleh guna semula** dipanggil oleh sebuah repositori dengan `uses:`; yang lain hanya berjalan di sini. Rajah ialah kod sumber PlantUML dan dalam bahasa Inggeris. Terdapat sepuluh aliran kerja dalam repositori ini. Aliran kerja **scoped** dihantar oleh instans ke setiap repositori organisasi TirSystem dan tidak memerlukan apa-apa dalam repositori; aliran kerja **boleh guna semula** dipanggil oleh sebuah repositori dengan `uses:`; yang lain hanya berjalan di sini. Rajah ialah kod sumber PlantUML dan dalam bahasa Inggeris.
| Aliran kerja | Fail | Jenis | Bermula apabila | Tujuan | | Aliran kerja | Fail | Jenis | Bermula apabila | Tujuan |
| --- | --- | --- | --- | --- | | --- | --- | --- | --- | --- |
| Sync GitHub mirror metadata | [`sync-github-metadata.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push ke `main`, manual, setiap hari 03:17 UTC | Menyalin penerangan dan topik repositori Gitea ke push mirror GitHub-nya | | Sync GitHub mirror metadata | [`sync-github-metadata.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/sync-github-metadata.yml) | Scoped | push ke `main`, manual, setiap hari 03:17 UTC | Menyalin penerangan dan topik repositori Gitea ke push mirror GitHub-nya |
| Publish wiki | [`publish-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/scoped_workflows/publish-wiki.yml) | Scoped, pilih menyertai dengan `wiki/.publish` | push ke `main` yang mengubah `wiki/**`, manual | Menerbitkan halaman dalam `wiki/` ke wiki repositori | | Publish wiki pages | [`publish-wiki-pages.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/publish-wiki-pages.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Menerbitkan halaman dalam `docs/wiki/` (atau folder yang dinamakan oleh `wiki_dir`) ke wiki repositori pemanggil |
| Quality checks | [`quality-checks.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Menjalankan ruff, mypy, shellcheck dan shfmt pada fail yang dijejaki dan tidak mengubah apa-apa | | Quality checks | [`quality-checks.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Menjalankan ruff, mypy, shellcheck dan shfmt pada fail yang dijejaki dan tidak mengubah apa-apa |
| Quality checks C++ | [`quality-checks-cpp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-cpp.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Menjalankan clang-format dan clang-tidy pada versi tepat pada fail C dan C++ yang dijejaki dan tidak mengubah apa-apa |
| Quality checks C# | [`quality-checks-csharp.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/quality-checks-csharp.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Menjalankan `dotnet format` dan `dotnet build` pada .NET SDK 10 atau lebih baharu yang tepat dan tidak mengubah apa-apa |
| Code metrics | [`code-metrics.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/code-metrics.yml) | Boleh guna semula (`workflow_call`) | panggilan daripada repositori lain | Mengira fail kod dan baris kod (tanpa baris komen dan baris kosong), membaca laporan liputan Cobertura dan melakukan commit tiga lencana SVG ke cabang `badges` |
| Publish own wiki | [`publish-own-wiki.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/publish-own-wiki.yml) | Repositori ini sahaja | push ke `main` yang mengubah `docs/wiki/**`, manual | Memanggil aliran kerja boleh guna semula Publish wiki pages untuk wiki repositori ini |
| Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | Repositori ini sahaja | manual | Mencipta tag versi, halaman keluaran dan tag major selepas semakan | | Release | [`release.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/release.yml) | Repositori ini sahaja | manual | Mencipta tag versi, halaman keluaran dan tag major selepas semakan |
| Menerbitkan milestone yang diterima | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | Repositori ini sahaja | push ke `main` yang mengubah `docs/milestones/**` atau `docs/sqa/reviews/**`, manual (larian percubaan secara lalai) | Mencipta atau mengemas kini milestone Gitea dan isu bagi setiap milestone yang Accepted dengan semakan Go | | Menerbitkan milestone yang diterima | [`sync-milestones.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/sync-milestones.yml) | Repositori ini sahaja | push ke `main` yang mengubah `docs/milestones/**` atau `docs/sqa/reviews/**`, manual (larian percubaan secara lalai) | Mencipta atau mengemas kini milestone Gitea dan isu bagi setiap milestone yang Accepted dengan semakan Go |
| Validate workflows | [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml) | Repositori ini sahaja | pull request, push ke `main`, manual | Menghurai setiap aliran kerja sebagai YAML dan menjalankan ujian luar talian | | Validate workflows | [`validate-workflows.yml`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/.gitea/workflows/validate-workflows.yml) | Repositori ini sahaja | pull request, push ke `main`, manual | Menghurai setiap aliran kerja sebagai YAML dan menjalankan ujian luar talian |
@@ -95,50 +99,6 @@ deactivate Workflow
@enduml @enduml
``` ```
### Menerbitkan wiki
Menerbitkan `wiki/` ke wiki repositori, hanya dalam repositori yang telah menyertai dengan `wiki/.publish`. Perkataan `prune` dalam fail itu juga memadam halaman yang tiada dalam `wiki/`.
#### Aliran kerja
```plantuml
@startuml
title Publish wiki/ to the repository wiki
autonumber
actor "Push to main that changes wiki/**,\nor manual trigger" as Trigger
participant "Gitea Actions\npublish-wiki" as Workflow
participant "shared-scripts\n(tools/publish_wiki.py at v1)" as Script
participant "Gitea wiki API" as Wiki
Trigger -> Workflow: Start workflow
activate Workflow
Workflow -> Script: Check out the repository and the script, run it
activate Script
alt wiki/.publish does not exist
Script --> Workflow: Log that publishing is not enabled and succeed\n(nothing is written)
else wiki/ has no Markdown pages
Script --> Workflow: Fail with [configuration]\n(an empty wiki/ never deletes pages)
else
Script -> Script: Read the marker (empty, or the word prune)\nand TOKEN_FOR_GITEA_WIKI
Script -> Wiki: GET repository (preflight)
Wiki --> Script: Wiki is enabled, token accepted
Script -> Wiki: GET pages
Wiki --> Script: Existing pages
Script -> Wiki: Create new pages, update changed pages
opt marker says prune
Script -> Wiki: Delete pages that are not in wiki/
end
Script --> Workflow: Log created, updated and deleted counts
end
deactivate Script
Workflow --> Trigger: Workflow result
deactivate Workflow
@enduml
```
### Semakan kualiti ### Semakan kualiti
Aliran kerja boleh guna semula untuk Python dan shell, dipanggil dengan `uses: TirSystem/github-action/.gitea/workflows/quality-checks.yml@v1` (lihat [panduan](QualityChecksPythonShell)). Aliran kerja boleh guna semula untuk Python dan shell, dipanggil dengan `uses: TirSystem/github-action/.gitea/workflows/quality-checks.yml@v1` (lihat [panduan](QualityChecksPythonShell)).
@@ -185,6 +145,140 @@ deactivate Workflow
@enduml @enduml
``` ```
### Semakan kualiti C++
Aliran kerja boleh guna semula untuk C dan C++, dipanggil dengan `uses: TirSystem/github-action/.gitea/workflows/quality-checks-cpp.yml@v1` (ADR-0008). Repositori mesti mempunyai `.clang-format`; `.clang-tidy` dan pangkalan data kompilasi adalah pilihan.
#### Aliran kerja
```plantuml
@startuml
title Shared quality checks for C and C++ (reusable workflow)
autonumber
actor "Calling repository\nuses: ...quality-checks-cpp.yml@v1" as Caller
participant "Gitea Actions\nquality-checks-cpp" as Workflow
participant "PyPI" as PyPI
Caller -> Workflow: workflow_call with working-directory, std,\ncompile-commands-dir and tool versions
activate Workflow
Workflow -> Workflow: Check out the caller and the shared script
Workflow -> Workflow: Validate the inputs
alt an input is invalid or a version is not exact
Workflow --> Caller: Fail with [configuration]
end
Workflow -> PyPI: Install clang-format and clang-tidy at the exact versions
PyPI --> Workflow: Tools installed
alt no C or C++ file is tracked
Workflow --> Caller: Notice: nothing was checked, pass
else no .clang-format, or compile-commands-dir\nwithout compile_commands.json
Workflow --> Caller: Fail with [configuration]
else
Workflow -> Workflow: clang-format --dry-run -Werror on all files
Workflow -> Workflow: clang-tidy on the C++ files (-std=std)\nand on the C files (or with the compile database)
alt any tool failed
Workflow --> Caller: Fail with tool, file and line in the log
else
Workflow --> Caller: All selected checks passed
end
end
deactivate Workflow
@enduml
```
### Semakan kualiti C#
Aliran kerja boleh guna semula untuk C#, dipanggil dengan `uses: TirSystem/github-action/.gitea/workflows/quality-checks-csharp.yml@v1` (ADR-0008). Ia memasang .NET SDK tepat yang diberikan oleh `dotnet-sdk-version` (10 atau lebih baharu) dan memerlukan rangkaian untuk pemulihan pakej.
#### Aliran kerja
```plantuml
@startuml
title Shared quality checks for C# (reusable workflow)
autonumber
actor "Calling repository\nuses: ...quality-checks-csharp.yml@v1" as Caller
participant "Gitea Actions\nquality-checks-csharp" as Workflow
participant "dot.net\n(dotnet-install.sh)" as DotNet
Caller -> Workflow: workflow_call with working-directory,\nproject and dotnet-sdk-version
activate Workflow
Workflow -> Workflow: Check out the caller and the shared script
Workflow -> Workflow: Validate the inputs
alt an input is invalid or the SDK is not an exact\nstable version 10 or later
Workflow --> Caller: Fail with [configuration]
end
Workflow -> DotNet: Install the exact .NET SDK version
DotNet --> Workflow: SDK installed
alt no C# file is tracked
Workflow --> Caller: Notice: nothing was checked, pass
else no solution or project, or several without the project input
Workflow --> Caller: Fail with [configuration] that names them
else
Workflow -> Workflow: dotnet format --verify-no-changes
Workflow -> Workflow: dotnet build -warnaserror\n(package restore needs the network)
alt any tool failed
Workflow --> Caller: Fail with tool, file and line in the log
else
Workflow --> Caller: All selected checks passed
end
end
deactivate Workflow
@enduml
```
### Metrik kod dan lencana liputan
Aliran kerja boleh guna semula yang mengira fail kod dan baris kod (tanpa baris komen dan baris kosong) sesebuah repositori dan menerbitkannya, bersama liputan ujian, sebagai tiga lencana SVG pada cabang `badges`, dipanggil dengan `uses: TirSystem/github-action/.gitea/workflows/code-metrics.yml@v1` (ADR-0010). Ia memerlukan `contents: write` dan tidak menjalankan sebarang ujian: kerja yang lebih awal oleh pemanggil menghasilkan laporan liputan format Cobertura XML dan memuat naiknya sebagai artifak ([panduan](CodeMetrics.ms)).
#### Aliran kerja
```plantuml
@startuml
title Code metrics and coverage badges (reusable workflow)
autonumber
actor "Calling repository\nuses: ...code-metrics.yml@v1" as Caller
participant "Gitea Actions\ncode-metrics" as Workflow
participant "Branch badges\nof the caller" as Branch
Caller -> Workflow: workflow_call with working-directory, exclude,\ncoverage-report and thresholds
activate Workflow
Workflow -> Workflow: Check out the caller and the shared scripts
Workflow -> Workflow: Validate the inputs
alt an input is invalid
Workflow --> Caller: Fail with [configuration]
end
Workflow -> Workflow: Download the coverage artifact (when given)
Workflow -> Workflow: Count code files and lines of code
alt a coverage report is given but missing or malformed
Workflow --> Caller: Fail with [configuration] that names the file
else
Workflow -> Workflow: Write code-files.svg, code-lines.svg\nand coverage.svg
end
Workflow -> Branch: Compare with the files of the branch
alt no badge changed
Workflow --> Caller: Notice: nothing to commit, pass
else
Workflow -> Branch: One commit with [skip ci]
Workflow --> Caller: Badges published
end
deactivate Workflow
@enduml
```
### Keluaran ### Keluaran
Dimulakan secara manual oleh penyelenggara; dengan `dry_run` `true` (lalai) ia hanya menyemak (lihat [proses keluaran](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)). Dimulakan secara manual oleh penyelenggara; dengan `dry_run` `true` (lalai) ia hanya menyemak (lihat [proses keluaran](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/release-process.md)).
@@ -234,6 +328,90 @@ deactivate Workflow
@enduml @enduml
``` ```
### Menerbitkan halaman wiki
Aliran kerja boleh guna semula (ADR-0009). Sesebuah repositori memanggilnya daripada aliran kerjanya sendiri, jadi panggilan itu ialah pilihan menyertai dan tiada fail penanda diperlukan. Ia menerbitkan halaman Markdown dalam `wiki_dir` (lalai `docs/wiki`) ke wiki repositori pemanggil, dan memadam halaman yang tiada dalam folder hanya apabila `prune` ialah `true`.
```yaml
on:
push:
branches: [ main ]
paths: [ "docs/wiki/**" ]
jobs:
wiki:
uses: TirSystem/github-action/.gitea/workflows/publish-wiki-pages.yml@v1
secrets:
TOKEN_FOR_GITEA_WIKI: ${{ secrets.TOKEN_FOR_GITEA_WIKI }}
```
#### Aliran kerja
```plantuml
@startuml
title Publish docs/wiki to the wiki of the calling repository
autonumber
actor "Calling repository\nuses: ...publish-wiki-pages.yml@v1" as Caller
participant "Gitea Actions\npublish-wiki-pages" as Workflow
participant "shared-scripts\n(tools/publish_wiki.py at tools-ref)" as Script
participant "Gitea wiki API" as Wiki
Caller -> Workflow: Call with wiki_dir, prune and the secret
activate Workflow
Workflow -> Script: Check out the repository and the script, run it
activate Script
alt wiki_dir does not exist, or has no Markdown pages
Script --> Workflow: Fail with [configuration]\n(an empty folder never deletes pages)
else
Script -> Script: Read prune and TOKEN_FOR_GITEA_WIKI
Script -> Wiki: GET repository (preflight)
Wiki --> Script: Wiki is enabled, token accepted
Script -> Wiki: GET pages
Wiki --> Script: Existing pages
Script -> Wiki: Create new pages, update changed pages
opt prune is true
Script -> Wiki: Delete pages that are not in wiki_dir
end
Script --> Workflow: Log created, updated and deleted counts
end
deactivate Script
Workflow --> Caller: Workflow result
deactivate Workflow
@enduml
```
### Menerbitkan wiki sendiri
Wiki repositori ini ialah `docs/wiki/`. Aliran kerja ini memanggil aliran kerja boleh guna semula di atas melalui laluan tempatannya, dengan `prune` ditetapkan kepada `true` supaya wiki sentiasa sama dengan folder dan `tools-ref` ditetapkan kepada `main` supaya skrip daripada commit yang sama dijalankan. Ia ialah pemanggil yang disalin oleh projek daripada `templates/ci/publish-wiki.yml` framework.
#### Aliran kerja
```plantuml
@startuml
title Publish the wiki of this repository (docs/wiki)
autonumber
actor "Push to main that changes docs/wiki/**,\nor manual trigger" as Trigger
participant "Gitea Actions\npublish-own-wiki" as Caller
participant "Gitea Actions\npublish-wiki-pages\n(called by its local path)" as Workflow
participant "Gitea wiki API" as Wiki
Trigger -> Caller: Start workflow
activate Caller
Caller -> Workflow: Call with wiki_dir docs/wiki, prune true,\ntools-ref main and TOKEN_FOR_GITEA_WIKI
activate Workflow
Workflow -> Wiki: Create, update and delete pages\nso the wiki equals docs/wiki
Wiki --> Workflow: Done
Workflow --> Caller: Workflow result
deactivate Workflow
Caller --> Trigger: Workflow result
deactivate Caller
@enduml
```
### Menerbitkan milestone yang diterima ### Menerbitkan milestone yang diterima
Kebenaran tetap untuk menerbitkan: pada push ke `main` yang mengubah milestone atau semakannya, setiap milestone yang baris terkini Version History-nya Accepted dan semakan terkininya Go dicipta atau dikemas kini di Gitea dengan tugasnya sebagai isu. Tiada apa-apa ditutup atau dipadam. Larian manual ialah larian percubaan kecuali `dry_run` dimatikan. Ia menggunakan secret `TOKEN_FOR_GITEA` dan peraturan `--accepted-only` framework yang dipasang (v0.5.0 atau lebih baharu). Kebenaran tetap untuk menerbitkan: pada push ke `main` yang mengubah milestone atau semakannya, setiap milestone yang baris terkini Version History-nya Accepted dan semakan terkininya Go dicipta atau dikemas kini di Gitea dengan tugasnya sebagai isu. Tiada apa-apa ditutup atau dipadam. Larian manual ialah larian percubaan kecuali `dry_run` dimatikan. Ia menggunakan secret `TOKEN_FOR_GITEA` dan peraturan `--accepted-only` framework yang dipasang (v0.5.0 atau lebih baharu).
@@ -1,4 +1,4 @@
<!-- translation of Onboarding.md, source sha256 0b11defd563a --> <!-- translation of Onboarding.md, source sha256 1bc66efd5874 -->
> Dette er en oversættelse af [`Onboarding`](Onboarding). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. > Dette er en oversættelse af [`Onboarding`](Onboarding). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
@@ -32,16 +32,30 @@ Leveringen er automatisk, så et repository behøver ingen kopi af workflowet. S
## Publicér en wiki (valgfrit) ## Publicér en wiki (valgfrit)
Workflowet **Publish wiki** kopierer Markdown-siderne i `wiki/` til repositoryets wiki, når en ændring af `wiki/` flettes til `main`. Det leveres som synkroniseringsworkflowet, men skriver kun, hvor et repository har meldt sig til. Det genbrugelige workflow **Publish wiki pages** kopierer Markdown-siderne i `docs/wiki/` til repositoryets wiki, når en ændring af `docs/wiki/` flettes til `main`. Repositoryet kalder det, så der skrives intet, hvor der ikke findes et kald. Wikien findes som minimum på engelsk og på produktejerens sprog.
1. Aktivér wikien i repositoryets indstillinger. 1. Aktivér wikien i repositoryets indstillinger.
2. Tilføj Markdown-sider til `wiki/`. Filnavnet er sidens navn: `Home.md` er forsiden. En bindestreg i navnet er et mellemrum i sidens navn: `Home-da.md` er siden "Home da", og et link til den er `[Dansk](Home-da)`. 2. Tilføj Markdown-sider til `docs/wiki/`. Filnavnet er sidens navn: `Home.md` er forsiden. En bindestreg i navnet er et mellemrum i sidens navn: `Home-da.md` er siden "Home da", og et link til den er `[Dansk](Home-da)`.
3. Tilføj markøren `wiki/.publish`. Lad den være tom for kun at oprette og opdatere sider. Skriv det ene ord `prune` i den for også at slette wiki-sider, der ikke findes i `wiki/`, så wikien altid er lig med mappen. 3. Tilføj et workflow, for eksempel `.gitea/workflows/publish-wiki.yml`, der kalder det genbrugelige workflow:
```yaml
on:
push:
branches: [ main ]
paths: [ "docs/wiki/**" ]
jobs:
wiki:
uses: TirSystem/github-action/.gitea/workflows/publish-wiki-pages.yml@v1
with:
prune: "true"
secrets:
TOKEN_FOR_GITEA_WIKI: ${{ secrets.TOKEN_FOR_GITEA_WIKI }}
```
`wiki_dir` er som standard `docs/wiki`. `prune: "true"` sletter også wiki-sider, der ikke findes i mappen, så wikien altid er lig med mappen; standarden `false` beholder dem.
4. Gem `TOKEN_FOR_GITEA_WIKI` som en secret i repositoryets organisation med det scope, der står i [credentials.md](Credentials.da). 4. Gem `TOKEN_FOR_GITEA_WIKI` som en secret i repositoryets organisation med det scope, der står i [credentials.md](Credentials.da).
5. Flet ændringen til `main`. Loggen skal slutte med `Published <n> page(s) to the wiki of <owner>/<repo>`. 5. Flet ændringen til `main`. Loggen skal slutte med `Published <n> page(s) to the wiki of <owner>/<repo>`.
Et repository uden `wiki/.publish` springes over med linjen `No wiki/.publish marker in <owner>/<repo>`: intet bliver skrevet, og der skal ingen secret til.
## Runners ## Runners
| Punkt | Værdi | | Punkt | Værdi |
+27 -13
View File
@@ -51,26 +51,40 @@ Delivery is automatic, so a repository needs no copy of the workflow. Secrets ar
## Publish a wiki (optional) ## Publish a wiki (optional)
The **Publish wiki** workflow copies the Markdown pages in `wiki/` to the The reusable workflow **Publish wiki pages** copies the Markdown pages in
repository wiki when a change to `wiki/` is merged to `main`. It is delivered `docs/wiki/` to the repository wiki when a change to `docs/wiki/` is merged to
like the sync workflow, but it writes only where a repository opts in. `main`. The repository calls it, so nothing is written where no call exists.
The wiki has English and the product owner's language as a minimum.
1. Enable the wiki in the repository settings. 1. Enable the wiki in the repository settings.
2. Add Markdown pages to `wiki/`. The file name is the page name: `Home.md` is 2. Add Markdown pages to `docs/wiki/`. The file name is the page name: `Home.md`
the home page. A hyphen in the name is a space in the page name: `Home-da.md` is the home page. A hyphen in the name is a space in the page name:
is the page "Home da", and a link to it is `[Dansk](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/Home-da)`. `Home-da.md` is the page "Home da", and a link to it is `[Dansk](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/Home-da)`.
3. Add the marker `wiki/.publish`. Leave it empty to create and update pages 3. Add a workflow, for example `.gitea/workflows/publish-wiki.yml`, that calls
only. Put the single word `prune` in it to also delete wiki pages that are the reusable workflow:
not in `wiki/`, so the wiki always equals the folder.
```yaml
on:
push:
branches: [ main ]
paths: [ "docs/wiki/**" ]
jobs:
wiki:
uses: TirSystem/github-action/.gitea/workflows/publish-wiki-pages.yml@v1
with:
prune: "true"
secrets:
TOKEN_FOR_GITEA_WIKI: ${{ secrets.TOKEN_FOR_GITEA_WIKI }}
```
`wiki_dir` defaults to `docs/wiki`. `prune: "true"` also deletes wiki pages
that are not in the folder, so the wiki always equals the folder; the
default `false` keeps them.
4. Store `TOKEN_FOR_GITEA_WIKI` as a secret of the repository's organization, 4. Store `TOKEN_FOR_GITEA_WIKI` as a secret of the repository's organization,
using the scope in [credentials.md](Credentials). using the scope in [credentials.md](Credentials).
5. Merge the change to `main`. The log must end with `Published <n> page(s) to 5. Merge the change to `main`. The log must end with `Published <n> page(s) to
the wiki of <owner>/<repo>`. the wiki of <owner>/<repo>`.
A repository without `wiki/.publish` is skipped with the line `No
wiki/.publish marker in <owner>/<repo>`: nothing is written and no secret is
needed.
## Runners ## Runners
| Item | Value | | Item | Value |
@@ -1,4 +1,4 @@
<!-- translation of Onboarding.md, source sha256 0b11defd563a --> <!-- translation of Onboarding.md, source sha256 1bc66efd5874 -->
> Ini ialah terjemahan [`Onboarding`](Onboarding). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. > Ini ialah terjemahan [`Onboarding`](Onboarding). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
@@ -32,16 +32,30 @@ Penghantaran adalah automatik, jadi repositori tidak memerlukan salinan aliran k
## Menerbitkan wiki (pilihan) ## Menerbitkan wiki (pilihan)
Aliran kerja **Publish wiki** menyalin halaman Markdown dalam `wiki/` ke wiki repositori apabila perubahan pada `wiki/` digabungkan ke `main`. Ia dihantar seperti aliran kerja penyegerakan, tetapi hanya menulis di tempat repositori telah memilih untuk menyertainya. Aliran kerja boleh guna semula **Publish wiki pages** menyalin halaman Markdown dalam `docs/wiki/` ke wiki repositori apabila perubahan pada `docs/wiki/` digabungkan ke `main`. Repositori memanggilnya, jadi tiada apa-apa ditulis di tempat yang tiada panggilan. Wiki ada dalam bahasa Inggeris dan bahasa pemilik produk sekurang-kurangnya.
1. Aktifkan wiki dalam tetapan repositori. 1. Aktifkan wiki dalam tetapan repositori.
2. Tambah halaman Markdown ke `wiki/`. Nama fail ialah nama halaman: `Home.md` ialah halaman utama. Tanda sempang dalam nama ialah ruang dalam nama halaman: `Home-da.md` ialah halaman "Home da", dan pautan kepadanya ialah `[Dansk](Home-da)`. 2. Tambah halaman Markdown ke `docs/wiki/`. Nama fail ialah nama halaman: `Home.md` ialah halaman utama. Tanda sempang dalam nama ialah ruang dalam nama halaman: `Home-da.md` ialah halaman "Home da", dan pautan kepadanya ialah `[Dansk](Home-da)`.
3. Tambah penanda `wiki/.publish`. Biarkan kosong untuk mencipta dan mengemas kini halaman sahaja. Letakkan satu perkataan `prune` di dalamnya untuk turut memadam halaman wiki yang tiada dalam `wiki/`, supaya wiki sentiasa sama dengan folder itu. 3. Tambah aliran kerja, contohnya `.gitea/workflows/publish-wiki.yml`, yang memanggil aliran kerja boleh guna semula:
```yaml
on:
push:
branches: [ main ]
paths: [ "docs/wiki/**" ]
jobs:
wiki:
uses: TirSystem/github-action/.gitea/workflows/publish-wiki-pages.yml@v1
with:
prune: "true"
secrets:
TOKEN_FOR_GITEA_WIKI: ${{ secrets.TOKEN_FOR_GITEA_WIKI }}
```
`wiki_dir` lalai ialah `docs/wiki`. `prune: "true"` turut memadam halaman wiki yang tiada dalam folder itu, supaya wiki sentiasa sama dengan folder; lalai `false` mengekalkannya.
4. Simpan `TOKEN_FOR_GITEA_WIKI` sebagai secret organisasi repositori, dengan skop yang tertera dalam [credentials.md](Credentials.ms). 4. Simpan `TOKEN_FOR_GITEA_WIKI` sebagai secret organisasi repositori, dengan skop yang tertera dalam [credentials.md](Credentials.ms).
5. Gabungkan perubahan ke `main`. Log mesti berakhir dengan `Published <n> page(s) to the wiki of <owner>/<repo>`. 5. Gabungkan perubahan ke `main`. Log mesti berakhir dengan `Published <n> page(s) to the wiki of <owner>/<repo>`.
Repositori tanpa `wiki/.publish` dilangkau dengan baris `No wiki/.publish marker in <owner>/<repo>`: tiada apa-apa ditulis dan tiada secret diperlukan.
## Runners ## Runners
| Perkara | Nilai | | Perkara | Nilai |
+85
View File
@@ -0,0 +1,85 @@
<!-- translation of QualityChecksCSharp.md, source sha256 36e34031e0b9 -->
> Dette er en oversættelse af [`QualityChecksCSharp`](QualityChecksCSharp). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
[English](QualityChecksCSharp) | Dansk | [Bahasa Melayu](QualityChecksCSharp.ms)
# Aflæsning af resultater fra kvalitetskontrol af C#
Sådan kalder en softwareudvikler den fælles kvalitetskontrol af C#, aflæser en mislykket kørsel og får den til at bestå. Workflowet kontrollerer formateringen og bygger en solution eller et projekt med advarsler som fejl, på et præcist .NET SDK i version 10 eller nyere, ændrer ingen fil og kræver ingen secret. Dets kontrakt er ADR-0008 i dette repository.
## Kald af workflowet
```yaml
name: Quality C#
on:
pull_request:
push:
branches: [ main ]
jobs:
quality:
permissions:
contents: read
uses: TirSystem/github-action/.gitea/workflows/quality-checks-csharp.yml@v1
with:
project: src/App.sln
```
| Input | Standard | Betydning |
| --- | --- | --- |
| `working-directory` | `.` | Mappe, relativt til repositoryets rod, hvis filer kontrolleres |
| `project` | tom | Solution- eller projektfil, relativt til `working-directory`; tom bruger den eneste sporede `*.sln` eller `*.slnx`, ellers den eneste `*.csproj`, der er sporet dér |
| `dotnet-sdk-version` | `10.0.401` | Præcis version af .NET SDK, 10 eller nyere; en kanal som `10.0`, et interval og en version under 10 afvises |
| `tools-ref` | `v1` | Tag eller gren i `TirSystem/github-action`, hvorfra scriptet `tools/quality-checks-csharp.sh` hentes |
Henvis til workflowet ved major-tagget (`@v1`) eller et præcist tag, aldrig ved `main`. Et kald ved et præcist tag er kun fuldt fastlåst, når `tools-ref` angiver samme tag. Se opgraderingsvejledningen. SDK-versionen er præcis: at hæve standardværdien er en ny udgivelse af dette repository, ikke en stille ændring.
## Hvad repositoryet skal have
- En solution eller et projekt, der bygger med SDK'et. Flere kandidater kræver inputtet `project`; C#-filer uden solution eller projekt fejler med `[configuration]`.
- Netværk: `dotnet format` og `dotnet build` gendanner projektets NuGet-pakker i kørslen.
- Regler i `.editorconfig` (og de analysatorer, projektet slår til, for eksempel med `AnalysisLevel` og `EnforceCodeStyleInBuild`) afgør, hvad et fund er.
## Hvad der kontrolleres
| Filer | Værktøj | Hvad det melder |
| --- | --- | --- |
| solutionen eller projektet | `dotnet format --verify-no-changes` | formatering og stil, der afviger fra `.editorconfig` |
| det samme | `dotnet build -warnaserror` | compiler- og analysatoradvarsler, som får bygningen til at fejle |
Usporede filer kontrolleres ikke. Et repository uden `*.cs`-filer består med en bemærkning. Projektets tests køres ikke.
## Aflæsning af en kørsel
1. Åbn statuskontrollen på pull requesten og derefter jobbet `quality-checks-csharp`.
2. Hvert værktøj har sin egen logggruppe. Begge kører, selv om det første fejlede, så én kørsel viser alle fund.
3. Et værktøj ender med `PASS <værktøj>` eller `FAIL <værktøj>`; sidste linje i en mislykket kørsel nævner dem: `Quality checks failed: dotnet format --verify-no-changes`.
4. Et fund skrives af værktøjet selv som `path(line,column): CODE message`. Stien er absolut i runnerens arbejdsområde (`/workspace/<owner>/<repo>/src/Program.cs(8,27)`).
## Typiske fejl og rettelser
| Værktøj | Hvad loggen viser | Rettelse |
| --- | --- | --- |
| `dotnet format --verify-no-changes` | `Program.cs(12,5): error WHITESPACE: Fix whitespace formatting` | Kør `dotnet format <projekt>`, og commit |
| `dotnet build -warnaserror` | `CS8602: Dereference of a possibly null reference` | Ret koden (null-kontrol); undertryk ikke advarslen uden en begrundelse |
| `dotnet build -warnaserror` | `IDE0005: Using directive is unnecessary` | Fjern direktivet |
For at gentage en kørsel på din egen maskine skal du bruge samme SDK:
```bash
dotnet --version # 10.0.401
dotnet format src/App.sln --verify-no-changes
dotnet build src/App.sln -warnaserror
```
## Når workflowet selv fejler
| Meddelelse | Årsag | Rettelse |
| --- | --- | --- |
| `[configuration] dotnet-sdk-version '<x>' is not an exact stable version 10 or later` | En kanal, et interval, en preview eller en version under 10 | Angiv en præcis version som `10.0.401` |
| `[configuration] working-directory must be a relative path inside the repository.` | En absolut sti eller `..` | Brug en sti inde i repositoryet |
| `[configuration] C# files exist but no solution or project was found` | Repositoryet har `*.cs`-filer og ingen `*.sln`, `*.slnx` eller `*.csproj` i arbejdsmappen | Tilføj en, eller sæt `working-directory` |
| `[configuration] Several solutions or projects were found` | Mere end én kandidat | Sæt inputtet `project` |
| Installationstrinnet fejler | `dot.net` eller runnerens netværk kan ikke nås | Kør jobbet igen; se [troubleshooting.md](troubleshooting.md) |
+103
View File
@@ -0,0 +1,103 @@
<!-- Generated from guides/quality-checks-csharp.md by tools/build_wiki.py. Edit the source, not this page. -->
> This page is generated from [`guides/quality-checks-csharp.md`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/quality-checks-csharp.md). Changes made here are overwritten the next time the wiki is published.
English | [Dansk](QualityChecksCSharp.da) | [Bahasa Melayu](QualityChecksCSharp.ms)
# Reading C# quality-check results
How a software engineer calls the shared C# quality checks, reads a failed run
and makes it pass. The workflow checks the formatting and builds a solution or
project with warnings as errors, on an exact .NET SDK of version 10 or later,
changes no file and needs no secret. Its contract is ADR-0008 in this
repository.
## Calling the workflow
```yaml
name: Quality C#
on:
pull_request:
push:
branches: [ main ]
jobs:
quality:
permissions:
contents: read
uses: TirSystem/github-action/.gitea/workflows/quality-checks-csharp.yml@v1
with:
project: src/App.sln
```
| Input | Default | Meaning |
| --- | --- | --- |
| `working-directory` | `.` | Directory, relative to the repository root, whose files are checked |
| `project` | empty | Solution or project file, relative to `working-directory`; empty uses the single `*.sln` or `*.slnx`, else the single `*.csproj`, that is tracked there |
| `dotnet-sdk-version` | `10.0.401` | Exact version of the .NET SDK, 10 or later; a channel such as `10.0`, a range and a version below 10 are refused |
| `tools-ref` | `v1` | Tag or branch of `TirSystem/github-action` from which the script `tools/quality-checks-csharp.sh` is fetched |
Reference the workflow at the major tag (`@v1`) or at an exact tag, never at
`main`. A call at an exact tag is pinned completely only when `tools-ref` names
the same tag. See the upgrade guide. The SDK version is exact: raising the
default is a new release of this repository, not a silent change.
## What the repository needs
- A solution or project that builds with the SDK. Several candidates need the
`project` input; C# files with no solution or project fail with
`[configuration]`.
- The network: `dotnet format` and `dotnet build` restore the NuGet packages of
the project in the run.
- Rules in `.editorconfig` (and the analyzers the project enables, for example
with `AnalysisLevel` and `EnforceCodeStyleInBuild`) decide what a finding is.
## What is checked
| Files | Tool | What it reports |
| --- | --- | --- |
| the solution or project | `dotnet format --verify-no-changes` | formatting and style that differ from `.editorconfig` |
| the same | `dotnet build -warnaserror` | compiler and analyzer warnings, which fail the build |
Untracked files are not checked. A repository without `*.cs` files passes with
a notice. Project tests are not run.
## Reading a run
1. Open the status check on the pull request, then the job
`quality-checks-csharp`.
2. Each tool has its own log group. Both run even when the first failed, so one
run shows all findings.
3. A tool ends with `PASS <tool>` or `FAIL <tool>`; the last line of a failed
run names them:
`Quality checks failed: dotnet format --verify-no-changes`.
4. A finding is written by the tool itself as `path(line,column): CODE message`.
The path is absolute in the workspace of the runner
(`/workspace/<owner>/<repo>/src/Program.cs(8,27)`).
## Typical failures and fixes
| Tool | What the log shows | Fix |
| --- | --- | --- |
| `dotnet format --verify-no-changes` | `Program.cs(12,5): error WHITESPACE: Fix whitespace formatting` | Run `dotnet format <project>` and commit |
| `dotnet build -warnaserror` | `CS8602: Dereference of a possibly null reference` | Fix the code (null check), do not suppress the warning without a reason |
| `dotnet build -warnaserror` | `IDE0005: Using directive is unnecessary` | Remove the directive |
To reproduce a run on your machine, use the same SDK:
```bash
dotnet --version # 10.0.401
dotnet format src/App.sln --verify-no-changes
dotnet build src/App.sln -warnaserror
```
## When the workflow itself fails
| Message | Cause | Fix |
| --- | --- | --- |
| `[configuration] dotnet-sdk-version '<x>' is not an exact stable version 10 or later` | A channel, a range, a preview or a version below 10 | Give an exact version such as `10.0.401` |
| `[configuration] working-directory must be a relative path inside the repository.` | An absolute path or `..` | Use a path inside the repository |
| `[configuration] C# files exist but no solution or project was found` | The repository has `*.cs` files and no `*.sln`, `*.slnx` or `*.csproj` in the working directory | Add one, or set `working-directory` |
| `[configuration] Several solutions or projects were found` | More than one candidate | Set the input `project` |
| The install step fails | `dot.net` or the runner network is unreachable | Re-run the job; see [troubleshooting.md](Troubleshooting) |
+85
View File
@@ -0,0 +1,85 @@
<!-- translation of QualityChecksCSharp.md, source sha256 36e34031e0b9 -->
> Ini ialah terjemahan [`QualityChecksCSharp`](QualityChecksCSharp). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
[English](QualityChecksCSharp) | [Dansk](QualityChecksCSharp.da) | Bahasa Melayu
# Membaca keputusan semakan kualiti C#
Cara seorang jurutera perisian memanggil semakan kualiti bersama untuk C#, membaca larian yang gagal dan menjadikannya lulus. Aliran kerja ini menyemak pemformatan dan membina solution atau projek dengan amaran sebagai ralat, pada .NET SDK versi 10 atau lebih baharu yang tepat, tidak mengubah sebarang fail dan tidak memerlukan secret. Kontraknya ialah ADR-0008 dalam repositori ini.
## Memanggil aliran kerja
```yaml
name: Quality C#
on:
pull_request:
push:
branches: [ main ]
jobs:
quality:
permissions:
contents: read
uses: TirSystem/github-action/.gitea/workflows/quality-checks-csharp.yml@v1
with:
project: src/App.sln
```
| Input | Lalai | Maksud |
| --- | --- | --- |
| `working-directory` | `.` | Direktori, relatif kepada akar repositori, yang failnya disemak |
| `project` | kosong | Fail solution atau projek, relatif kepada `working-directory`; kosong menggunakan satu-satunya `*.sln` atau `*.slnx` yang dijejaki, jika tiada maka satu-satunya `*.csproj` yang dijejaki di situ |
| `dotnet-sdk-version` | `10.0.401` | Versi tepat .NET SDK, 10 atau lebih baharu; saluran seperti `10.0`, julat dan versi di bawah 10 ditolak |
| `tools-ref` | `v1` | Tag atau cabang `TirSystem/github-action` tempat skrip `tools/quality-checks-csharp.sh` diambil |
Rujuk aliran kerja pada tag major (`@v1`) atau pada tag tepat, jangan sekali-kali pada `main`. Panggilan pada tag tepat hanya disematkan sepenuhnya apabila `tools-ref` menamakan tag yang sama. Lihat panduan naik taraf. Versi SDK adalah tepat: menaikkan nilai lalai ialah keluaran baharu repositori ini, bukan perubahan senyap.
## Apa yang diperlukan oleh repositori
- Satu solution atau projek yang boleh dibina dengan SDK. Beberapa calon memerlukan input `project`; fail C# tanpa solution atau projek gagal dengan `[configuration]`.
- Rangkaian: `dotnet format` dan `dotnet build` memulihkan pakej NuGet projek dalam larian.
- Peraturan dalam `.editorconfig` (dan penganalisis yang diaktifkan projek, contohnya dengan `AnalysisLevel` dan `EnforceCodeStyleInBuild`) menentukan apa yang dikira sebagai dapatan.
## Apa yang disemak
| Fail | Alat | Apa yang dilaporkan |
| --- | --- | --- |
| solution atau projek | `dotnet format --verify-no-changes` | pemformatan dan gaya yang berbeza daripada `.editorconfig` |
| yang sama | `dotnet build -warnaserror` | amaran pengkompil dan penganalisis, yang menggagalkan binaan |
Fail yang tidak dijejaki tidak disemak. Repositori tanpa fail `*.cs` lulus dengan satu notis. Ujian projek tidak dijalankan.
## Membaca satu larian
1. Buka semakan status pada pull request, kemudian kerja `quality-checks-csharp`.
2. Setiap alat mempunyai kumpulan lognya sendiri. Kedua-duanya dijalankan walaupun yang pertama gagal, jadi satu larian menunjukkan semua dapatan.
3. Satu alat berakhir dengan `PASS <alat>` atau `FAIL <alat>`; baris terakhir larian yang gagal menamakannya: `Quality checks failed: dotnet format --verify-no-changes`.
4. Satu dapatan ditulis oleh alat itu sendiri sebagai `path(line,column): CODE message`. Laluan itu mutlak dalam ruang kerja runner (`/workspace/<owner>/<repo>/src/Program.cs(8,27)`).
## Kegagalan biasa dan pembetulan
| Alat | Apa yang ditunjukkan log | Pembetulan |
| --- | --- | --- |
| `dotnet format --verify-no-changes` | `Program.cs(12,5): error WHITESPACE: Fix whitespace formatting` | Jalankan `dotnet format <projek>` dan commit |
| `dotnet build -warnaserror` | `CS8602: Dereference of a possibly null reference` | Betulkan kod (semakan null); jangan tindas amaran tanpa sebab |
| `dotnet build -warnaserror` | `IDE0005: Using directive is unnecessary` | Buang arahan itu |
Untuk mengulangi satu larian pada mesin anda, gunakan SDK yang sama:
```bash
dotnet --version # 10.0.401
dotnet format src/App.sln --verify-no-changes
dotnet build src/App.sln -warnaserror
```
## Apabila aliran kerja itu sendiri gagal
| Mesej | Punca | Pembetulan |
| --- | --- | --- |
| `[configuration] dotnet-sdk-version '<x>' is not an exact stable version 10 or later` | Saluran, julat, pratonton atau versi di bawah 10 | Berikan versi tepat seperti `10.0.401` |
| `[configuration] working-directory must be a relative path inside the repository.` | Laluan mutlak atau `..` | Gunakan laluan di dalam repositori |
| `[configuration] C# files exist but no solution or project was found` | Repositori mempunyai fail `*.cs` tetapi tiada `*.sln`, `*.slnx` atau `*.csproj` dalam direktori kerja | Tambah satu, atau tetapkan `working-directory` |
| `[configuration] Several solutions or projects were found` | Lebih daripada satu calon | Tetapkan input `project` |
| Langkah pemasangan gagal | `dot.net` atau rangkaian runner tidak dapat dicapai | Jalankan semula kerja; lihat [troubleshooting.md](troubleshooting.md) |
+88
View File
@@ -0,0 +1,88 @@
<!-- translation of QualityChecksCpp.md, source sha256 6faa57183a11 -->
> Dette er en oversættelse af [`QualityChecksCpp`](QualityChecksCpp). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
[English](QualityChecksCpp) | Dansk | [Bahasa Melayu](QualityChecksCpp.ms)
# Aflæsning af resultater fra kvalitetskontrol af C og C++
Sådan kalder en softwareudvikler den fælles kvalitetskontrol af C og C++, aflæser en mislykket kørsel og får den til at bestå. Workflowet formatkontrollerer og analyserer statisk repositoryets C- og C++-filer med clang-værktøjer i præcise versioner, ændrer ingen fil og kræver ingen secret. Dets kontrakt er ADR-0008 i dette repository.
## Kald af workflowet
```yaml
name: Quality C++
on:
pull_request:
push:
branches: [ main ]
jobs:
quality:
permissions:
contents: read
uses: TirSystem/github-action/.gitea/workflows/quality-checks-cpp.yml@v1
with:
std: c++20
```
| Input | Standard | Betydning |
| --- | --- | --- |
| `working-directory` | `.` | Mappe, relativt til repositoryets rod, hvis filer kontrolleres |
| `std` | `c++20` | C++-standard, som clang-tidy får, når der ikke er en compile-database, for eksempel `c++17` eller `gnu++20` |
| `compile-commands-dir` | tom | Mappe, relativt til repositoryets rod, med en `compile_commands.json`, som din pipeline har bygget før kaldet; når den er sat, bruger clang-tidy den og ignorerer `std` |
| `clang-format-version` | `23.1.3` | Præcis version af pakken clang-format |
| `clang-tidy-version` | `22.1.8` | Præcis version af pakken clang-tidy |
| `tools-ref` | `v1` | Tag eller gren i `TirSystem/github-action`, hvorfra scriptet `tools/quality-checks-cpp.sh` hentes |
Henvis til workflowet ved major-tagget (`@v1`) eller et præcist tag, aldrig ved `main`. Et kald ved et præcist tag er kun fuldt fastlåst, når `tools-ref` angiver samme tag. Se opgraderingsvejledningen. Værktøjsversionerne er præcise: en nyere version af et værktøj er en ny udgivelse af dette repository, ikke en stille ændring.
## Hvad repositoryet skal have
- En `.clang-format` i arbejdsmappen eller i en mappe over den, op til repositoryets rod. Uden en fejler kørslen med `[configuration]`, og intet værktøj kører.
- Eventuelt en `.clang-tidy` med projektets kontroller (for eksempel C++ Core Guidelines). Uden en kører workflowet compilerdiagnostik og den statiske analysator (`clang-diagnostic-*` og `clang-analyzer-*`), fordi clang-tidy selv ikke aktiverer nogen kontrol uden konfiguration.
- For præcise statiske kontroller en compile-database: byg den først (for eksempel `cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON`) og angiv `compile-commands-dir: build`. Workflowet bygger ikke dit projekt.
## Hvad der kontrolleres
| Filer | Værktøj | Hvad det melder |
| --- | --- | --- |
| sporede `*.c`, `*.cc`, `*.cpp`, `*.cxx`, `*.h`, `*.hh`, `*.hpp`, `*.hxx` | `clang-format --dry-run -Werror` | formatering, der afviger fra `.clang-format` |
| de samme filer | `clang-tidy` | fund fra kontrollerne i `.clang-tidy` (eller fra `clang-diagnostic-*` og `clang-analyzer-*` uden en); `*.c`-filer kontrolleres som C17, de øvrige filer som C++-standarden i `std`; hvert fund er en fejl (`--warnings-as-errors=*`), så en advarsel får kørslen til at fejle |
Usporede filer kontrolleres ikke. Et repository uden disse filer består med en bemærkning. Der er ingen `cppcheck`: kun en gammel version findes som pakke, og gamle værktøjer køres ikke (ADR-0008).
## Aflæsning af en kørsel
1. Åbn statuskontrollen på pull requesten og derefter jobbet `quality-checks-cpp`.
2. Hvert værktøj har sin egen logggruppe. Alle værktøjer kører, selv om et tidligere fejlede, så én kørsel viser alle fund.
3. Et værktøj ender med `PASS <værktøj>` eller `FAIL <værktøj>`; sidste linje i en mislykket kørsel nævner dem: `Quality checks failed: clang-format`.
4. Et fund skrives af værktøjet selv som `path:line:column: message`. `clang-format` skriver stier relative til `working-directory`; `clang-tidy` skriver absolutte stier i runnerens arbejdsområde (`/workspace/<owner>/<repo>/main.cpp:5:18`).
## Typiske fejl og rettelser
| Værktøj | Hvad loggen viser | Rettelse |
| --- | --- | --- |
| `clang-format` | `code should be clang-formatted [-Wclang-format-violations]` | Kør `clang-format -i <fil>` med samme version, og commit |
| `clang-tidy` | `warning: ... [modernize-use-nullptr]` | Ret koden, eller slå kontrollen fra i `.clang-tidy` med en begrundelse |
| `clang-tidy` | `error: 'x.h' file not found` | Byg compile-databasen og sæt `compile-commands-dir`, eller tilføj include-stien til den |
For at gentage en kørsel på din egen maskine skal du installere de samme versioner:
```bash
python3 -m venv .qc
.qc/bin/pip install clang-format==23.1.3 clang-tidy==22.1.8
git ls-files '*.cpp' '*.hpp' | xargs .qc/bin/clang-format --dry-run -Werror
```
## Når workflowet selv fejler
| Meddelelse | Årsag | Rettelse |
| --- | --- | --- |
| `[configuration] std '<x>' is not a C++ standard` | `std` er ikke som `c++20` eller `gnu++20` | Ret inputtet |
| `[configuration] ... is not an exact version` | En værktøjsversion er et interval, en kanal eller tom | Angiv en præcis version som `23.1.3` |
| `[configuration] working-directory must be a relative path inside the repository.` | En absolut sti eller `..` | Brug en sti inde i repositoryet |
| `[configuration] No .clang-format found` | Projektet har ingen stilfil | Tilføj en `.clang-format` |
| `[configuration] compile-commands-dir '<x>' has no compile_commands.json` | Databasen blev ikke bygget før kaldet | Byg den i et tidligere trin i jobbet eller i et job, det afhænger af |
| Installationstrinnet fejler | PyPI eller runnerens netværk kan ikke nås | Kør jobbet igen; se [troubleshooting.md](troubleshooting.md) |
+108
View File
@@ -0,0 +1,108 @@
<!-- Generated from guides/quality-checks-cpp.md by tools/build_wiki.py. Edit the source, not this page. -->
> This page is generated from [`guides/quality-checks-cpp.md`](https://git.tirsystem.com/TirSystem/github-action/src/branch/main/guides/quality-checks-cpp.md). Changes made here are overwritten the next time the wiki is published.
English | [Dansk](QualityChecksCpp.da) | [Bahasa Melayu](QualityChecksCpp.ms)
# Reading C and C++ quality-check results
How a software engineer calls the shared C and C++ quality checks, reads a
failed run and makes it pass. The workflow format-checks and statically checks
the C and C++ files of a repository with clang tools at exact versions, changes
no file and needs no secret. Its contract is ADR-0008 in this repository.
## Calling the workflow
```yaml
name: Quality C++
on:
pull_request:
push:
branches: [ main ]
jobs:
quality:
permissions:
contents: read
uses: TirSystem/github-action/.gitea/workflows/quality-checks-cpp.yml@v1
with:
std: c++20
```
| Input | Default | Meaning |
| --- | --- | --- |
| `working-directory` | `.` | Directory, relative to the repository root, whose files are checked |
| `std` | `c++20` | C++ standard given to clang-tidy when there is no compile database, such as `c++17` or `gnu++20` |
| `compile-commands-dir` | empty | Directory, relative to the repository root, with a `compile_commands.json` that your pipeline built before the call; when set, clang-tidy uses it and ignores `std` |
| `clang-format-version` | `23.1.3` | Exact version of the clang-format package |
| `clang-tidy-version` | `22.1.8` | Exact version of the clang-tidy package |
| `tools-ref` | `v1` | Tag or branch of `TirSystem/github-action` from which the script `tools/quality-checks-cpp.sh` is fetched |
Reference the workflow at the major tag (`@v1`) or at an exact tag, never at
`main`. A call at an exact tag is pinned completely only when `tools-ref` names
the same tag. See the upgrade guide. The tool versions are exact: a newer
version of a tool is a new release of this repository, not a silent change.
## What the repository needs
- A `.clang-format` in the working directory or in a directory above it, up to
the top of the repository. Without one the run fails with `[configuration]`
and no tool runs.
- Optionally a `.clang-tidy` with the checks of the project (for example the
C++ Core Guidelines). Without one the workflow runs the compiler diagnostics
and the static analyzer (`clang-diagnostic-*` and `clang-analyzer-*`),
because clang-tidy itself enables no check when there is no configuration.
- For precise static checks, a compile database: build it first (for example
`cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON`) and pass
`compile-commands-dir: build`. The workflow does not build your project.
## What is checked
| Files | Tool | What it reports |
| --- | --- | --- |
| tracked `*.c`, `*.cc`, `*.cpp`, `*.cxx`, `*.h`, `*.hh`, `*.hpp`, `*.hxx` | `clang-format --dry-run -Werror` | formatting that differs from `.clang-format` |
| the same files | `clang-tidy` | findings of the checks of `.clang-tidy` (or of `clang-diagnostic-*` and `clang-analyzer-*` without one); `*.c` files are checked as C17, the other files as the C++ standard of `std`; every finding is an error (`--warnings-as-errors=*`), so a warning fails the run |
Untracked files are not checked. A repository without these files passes with a
notice. There is no `cppcheck`: only an old version of it is available as a
package, and old tools are not run (ADR-0008).
## Reading a run
1. Open the status check on the pull request, then the job `quality-checks-cpp`.
2. Each tool has its own log group. Every tool runs even when an earlier one
failed, so one run shows all findings.
3. A tool ends with `PASS <tool>` or `FAIL <tool>`; the last line of a failed
run names them: `Quality checks failed: clang-format`.
4. A finding is written by the tool itself as `path:line:column: message`.
`clang-format` prints paths relative to `working-directory`; `clang-tidy`
prints absolute paths in the workspace of the runner
(`/workspace/<owner>/<repo>/main.cpp:5:18`).
## Typical failures and fixes
| Tool | What the log shows | Fix |
| --- | --- | --- |
| `clang-format` | `code should be clang-formatted [-Wclang-format-violations]` | Run `clang-format -i <file>` with the same version and commit |
| `clang-tidy` | `warning: ... [modernize-use-nullptr]` | Fix the code, or switch the check off in `.clang-tidy` with a reason |
| `clang-tidy` | `error: 'x.h' file not found` | Build the compile database and set `compile-commands-dir`, or add the include path to it |
To reproduce a run on your machine, install the same versions:
```bash
python3 -m venv .qc
.qc/bin/pip install clang-format==23.1.3 clang-tidy==22.1.8
git ls-files '*.cpp' '*.hpp' | xargs .qc/bin/clang-format --dry-run -Werror
```
## When the workflow itself fails
| Message | Cause | Fix |
| --- | --- | --- |
| `[configuration] std '<x>' is not a C++ standard` | `std` is not like `c++20` or `gnu++20` | Correct the input |
| `[configuration] ... is not an exact version` | A tool version is a range, a channel or empty | Give an exact version such as `23.1.3` |
| `[configuration] working-directory must be a relative path inside the repository.` | An absolute path or `..` | Use a path inside the repository |
| `[configuration] No .clang-format found` | The project has no style file | Add a `.clang-format` |
| `[configuration] compile-commands-dir '<x>' has no compile_commands.json` | The database was not built before the call | Build it in an earlier step of the job or in a job it depends on |
| The install step fails | PyPI or the runner network is unreachable | Re-run the job; see [troubleshooting.md](Troubleshooting) |
+88
View File
@@ -0,0 +1,88 @@
<!-- translation of QualityChecksCpp.md, source sha256 6faa57183a11 -->
> Ini ialah terjemahan [`QualityChecksCpp`](QualityChecksCpp). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
[English](QualityChecksCpp) | [Dansk](QualityChecksCpp.da) | Bahasa Melayu
# Membaca keputusan semakan kualiti C dan C++
Cara seorang jurutera perisian memanggil semakan kualiti bersama untuk C dan C++, membaca larian yang gagal dan menjadikannya lulus. Aliran kerja ini menyemak format dan menganalisis secara statik fail C dan C++ sesebuah repositori dengan alat clang pada versi yang tepat, tidak mengubah sebarang fail dan tidak memerlukan secret. Kontraknya ialah ADR-0008 dalam repositori ini.
## Memanggil aliran kerja
```yaml
name: Quality C++
on:
pull_request:
push:
branches: [ main ]
jobs:
quality:
permissions:
contents: read
uses: TirSystem/github-action/.gitea/workflows/quality-checks-cpp.yml@v1
with:
std: c++20
```
| Input | Lalai | Maksud |
| --- | --- | --- |
| `working-directory` | `.` | Direktori, relatif kepada akar repositori, yang failnya disemak |
| `std` | `c++20` | Piawaian C++ yang diberikan kepada clang-tidy apabila tiada pangkalan data kompilasi, seperti `c++17` atau `gnu++20` |
| `compile-commands-dir` | kosong | Direktori, relatif kepada akar repositori, dengan `compile_commands.json` yang dibina oleh saluran paip anda sebelum panggilan; apabila ditetapkan, clang-tidy menggunakannya dan mengabaikan `std` |
| `clang-format-version` | `23.1.3` | Versi tepat pakej clang-format |
| `clang-tidy-version` | `22.1.8` | Versi tepat pakej clang-tidy |
| `tools-ref` | `v1` | Tag atau cabang `TirSystem/github-action` tempat skrip `tools/quality-checks-cpp.sh` diambil |
Rujuk aliran kerja pada tag major (`@v1`) atau pada tag tepat, jangan sekali-kali pada `main`. Panggilan pada tag tepat hanya disematkan sepenuhnya apabila `tools-ref` menamakan tag yang sama. Lihat panduan naik taraf. Versi alat adalah tepat: versi alat yang lebih baharu ialah keluaran baharu repositori ini, bukan perubahan senyap.
## Apa yang diperlukan oleh repositori
- Satu `.clang-format` dalam direktori kerja atau dalam direktori di atasnya, sehingga akar repositori. Tanpanya larian gagal dengan `[configuration]` dan tiada alat dijalankan.
- Pilihan: satu `.clang-tidy` dengan semakan projek (contohnya C++ Core Guidelines). Tanpanya aliran kerja menjalankan diagnostik pengkompil dan penganalisis statik (`clang-diagnostic-*` dan `clang-analyzer-*`), kerana clang-tidy sendiri tidak mengaktifkan sebarang semakan tanpa konfigurasi.
- Untuk semakan statik yang tepat, satu pangkalan data kompilasi: bina dahulu (contohnya `cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON`) dan berikan `compile-commands-dir: build`. Aliran kerja tidak membina projek anda.
## Apa yang disemak
| Fail | Alat | Apa yang dilaporkan |
| --- | --- | --- |
| `*.c`, `*.cc`, `*.cpp`, `*.cxx`, `*.h`, `*.hh`, `*.hpp`, `*.hxx` yang dijejaki | `clang-format --dry-run -Werror` | pemformatan yang berbeza daripada `.clang-format` |
| fail yang sama | `clang-tidy` | dapatan semakan dalam `.clang-tidy` (atau `clang-diagnostic-*` dan `clang-analyzer-*` tanpanya); fail `*.c` disemak sebagai C17, fail lain sebagai piawaian C++ dalam `std`; setiap dapatan ialah ralat (`--warnings-as-errors=*`), jadi amaran menggagalkan larian |
Fail yang tidak dijejaki tidak disemak. Repositori tanpa fail ini lulus dengan satu notis. Tiada `cppcheck`: hanya versi lama tersedia sebagai pakej, dan alat lama tidak dijalankan (ADR-0008).
## Membaca satu larian
1. Buka semakan status pada pull request, kemudian kerja `quality-checks-cpp`.
2. Setiap alat mempunyai kumpulan lognya sendiri. Setiap alat dijalankan walaupun yang terdahulu gagal, jadi satu larian menunjukkan semua dapatan.
3. Satu alat berakhir dengan `PASS <alat>` atau `FAIL <alat>`; baris terakhir larian yang gagal menamakannya: `Quality checks failed: clang-format`.
4. Satu dapatan ditulis oleh alat itu sendiri sebagai `path:line:column: message`. `clang-format` menulis laluan relatif kepada `working-directory`; `clang-tidy` menulis laluan mutlak dalam ruang kerja runner (`/workspace/<owner>/<repo>/main.cpp:5:18`).
## Kegagalan biasa dan pembetulan
| Alat | Apa yang ditunjukkan log | Pembetulan |
| --- | --- | --- |
| `clang-format` | `code should be clang-formatted [-Wclang-format-violations]` | Jalankan `clang-format -i <fail>` dengan versi yang sama dan commit |
| `clang-tidy` | `warning: ... [modernize-use-nullptr]` | Betulkan kod, atau matikan semakan itu dalam `.clang-tidy` dengan sebab |
| `clang-tidy` | `error: 'x.h' file not found` | Bina pangkalan data kompilasi dan tetapkan `compile-commands-dir`, atau tambah laluan include kepadanya |
Untuk mengulangi satu larian pada mesin anda, pasang versi yang sama:
```bash
python3 -m venv .qc
.qc/bin/pip install clang-format==23.1.3 clang-tidy==22.1.8
git ls-files '*.cpp' '*.hpp' | xargs .qc/bin/clang-format --dry-run -Werror
```
## Apabila aliran kerja itu sendiri gagal
| Mesej | Punca | Pembetulan |
| --- | --- | --- |
| `[configuration] std '<x>' is not a C++ standard` | `std` bukan seperti `c++20` atau `gnu++20` | Betulkan input |
| `[configuration] ... is not an exact version` | Versi alat ialah julat, saluran atau kosong | Berikan versi tepat seperti `23.1.3` |
| `[configuration] working-directory must be a relative path inside the repository.` | Laluan mutlak atau `..` | Gunakan laluan di dalam repositori |
| `[configuration] No .clang-format found` | Projek tiada fail gaya | Tambah `.clang-format` |
| `[configuration] compile-commands-dir '<x>' has no compile_commands.json` | Pangkalan data tidak dibina sebelum panggilan | Bina dalam langkah terdahulu kerja itu atau dalam kerja yang menjadi tanggungannya |
| Langkah pemasangan gagal | PyPI atau rangkaian runner tidak dapat dicapai | Jalankan semula kerja; lihat [troubleshooting.md](troubleshooting.md) |
@@ -1,4 +1,4 @@
<!-- translation of Troubleshooting.md, source sha256 eb63f4109a0d --> <!-- translation of Troubleshooting.md, source sha256 cad6363616e8 -->
> Dette er en oversættelse af [`Troubleshooting`](Troubleshooting). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. > Dette er en oversættelse af [`Troubleshooting`](Troubleshooting). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
@@ -30,11 +30,12 @@ Kategorien fortæller, hvor du skal kigge. En fejl uden for scriptet (jobbet sta
| `TOKEN_FOR_GITEA is missing or empty.` | configuration | Tilføj secreten | | `TOKEN_FOR_GITEA is missing or empty.` | configuration | Tilføj secreten |
| `Could not determine the Gitea source repository.` | configuration | Kørslen har intet repositorynavn; meld det som en defekt | | `Could not determine the Gitea source repository.` | configuration | Kørslen har intet repositorynavn; meld det som en defekt |
| `Expected at most one GitHub push mirror ... found N` | configuration | Behold ét GitHub push-spejl | | `Expected at most one GitHub push mirror ... found N` | configuration | Behold ét GitHub push-spejl |
| `TOKEN_FOR_GITEA_WIKI is missing or empty.` | configuration | Kun wiki-publicering: repositoryet har meldt sig til med `wiki/.publish`, men dets organisation har ingen sådan secret; gem den (se [credentials.md](Credentials.da)) | | `TOKEN_FOR_GITEA_WIKI is missing or empty.` | configuration | Kun wiki-publicering: repositoryet kalder workflowet, men dets organisation har ingen sådan secret, eller kaldet sender den ikke; gem den og send den i `secrets:` (se [credentials.md](Credentials.da)) |
| `wiki/.publish may only be empty or contain the word 'prune'.` | configuration | Kun wiki-publicering: lad markøren være tom, eller skriv kun ordet `prune` i den | | `The prune input must be 'true' or 'false'` | configuration | Kun wiki-publicering: sæt `prune` i kaldet til `"true"` eller `"false"` |
| `The wiki folder ... does not exist` | configuration | Kun wiki-publicering: sæt `wiki_dir` i kaldet til mappen med siderne (standard `docs/wiki`) |
| `... HTTP 403 ...` ved publicering af wikien | permissions | Kun wiki-publicering: tokenet skal have `write:repository` og skriveadgang til dette repository | | `... HTTP 403 ...` ved publicering af wikien | permissions | Kun wiki-publicering: tokenet skal have `write:repository` og skriveadgang til dette repository |
| `The wiki is not enabled on <repo>` | configuration | Kun wiki-publicering: aktivér wikien i repositoryets indstillinger | | `The wiki is not enabled on <repo>` | configuration | Kun wiki-publicering: aktivér wikien i repositoryets indstillinger |
| `The wiki source directory ... has no Markdown pages` | configuration | Kun wiki-publicering: `wiki/` er tom eller mangler; intet blev ændret | | `The wiki source directory ... has no Markdown pages` | configuration | Kun wiki-publicering: mappen har ingen Markdown-sider; intet blev ændret |
| `Could not determine the GitHub owner and repository ...` | configuration | Spejlets adresse skal se ud som `https://github.com/<owner>/<repo>.git` | | `Could not determine the GitHub owner and repository ...` | configuration | Spejlets adresse skal se ud som `https://github.com/<owner>/<repo>.git` |
| `Gitea API request failed with HTTP 401` | credentials | Opret et nyt Gitea-token, og opdatér `TOKEN_FOR_GITEA` | | `Gitea API request failed with HTTP 401` | credentials | Opret et nyt Gitea-token, og opdatér `TOKEN_FOR_GITEA` |
| `GitHub API request failed with HTTP 401` | credentials | Opret et nyt GitHub-token, og opdatér `PAT_FOR_GITHUB`; værdien skal kun være tokenet (intet navn, ingen anførselstegn eller JSON) | | `GitHub API request failed with HTTP 401` | credentials | Opret et nyt GitHub-token, og opdatér `PAT_FOR_GITHUB`; værdien skal kun være tokenet (intet navn, ingen anførselstegn eller JSON) |
@@ -51,9 +52,7 @@ Kategorien fortæller, hvor du skal kigge. En fejl uden for scriptet (jobbet sta
`WARNING: Gitea returned an invalid push mirror list; GitHub metadata was not synced.` Kørslen lykkes, men intet blev synkroniseret. Kontroller, at Gitea-versionen understøtter push mirror-API'et, og kør igen. `WARNING: Gitea returned an invalid push mirror list; GitHub metadata was not synced.` Kørslen lykkes, men intet blev synkroniseret. Kontroller, at Gitea-versionen understøtter push mirror-API'et, og kør igen.
`No wiki/.publish marker in <owner>/<repo>; wiki publishing is not enabled for this repository.` Kørslen af Publish wiki lykkes og skriver intet. Repositoryet har ikke meldt sig til; tilføj `wiki/.publish`, hvis det skal publicere. `<n> page(s) are in the wiki but not in <folder>/ and were kept; set the prune input to true to delete them.` Kørslen lykkes. Sider, der kun findes i wikien, lades i fred, medmindre kaldet sætter `prune: "true"`.
`<n> page(s) are in the wiki but not in wiki/ and were kept; put the word 'prune' in wiki/.publish to delete them.` Kørslen lykkes. Sider, der kun findes i wikien, lades i fred, medmindre markøren indeholder `prune`.
## Runner-problemer ## Runner-problemer
@@ -32,11 +32,12 @@ never starts, or stops before the step) is a **runner** problem.
| `TOKEN_FOR_GITEA is missing or empty.` | configuration | Add the secret | | `TOKEN_FOR_GITEA is missing or empty.` | configuration | Add the secret |
| `Could not determine the Gitea source repository.` | configuration | The run has no repository name; report as a defect | | `Could not determine the Gitea source repository.` | configuration | The run has no repository name; report as a defect |
| `Expected at most one GitHub push mirror ... found N` | configuration | Keep one GitHub push mirror | | `Expected at most one GitHub push mirror ... found N` | configuration | Keep one GitHub push mirror |
| `TOKEN_FOR_GITEA_WIKI is missing or empty.` | configuration | Wiki publishing only: the repository opted in with `wiki/.publish` but its organization has no such secret; store it (see [credentials.md](Credentials)) | | `TOKEN_FOR_GITEA_WIKI is missing or empty.` | configuration | Wiki publishing only: the repository calls the workflow but its organization has no such secret, or the call does not pass it; store it and pass it in `secrets:` (see [credentials.md](Credentials)) |
| `wiki/.publish may only be empty or contain the word 'prune'.` | configuration | Wiki publishing only: leave the marker empty, or put only the word `prune` in it | | `The prune input must be 'true' or 'false'` | configuration | Wiki publishing only: set `prune` in the call to `"true"` or `"false"` |
| `The wiki folder ... does not exist` | configuration | Wiki publishing only: set `wiki_dir` in the call to the folder with the pages (default `docs/wiki`) |
| `... HTTP 403 ...` while publishing the wiki | permissions | Wiki publishing only: the token needs `write:repository` and write access to this repository | | `... HTTP 403 ...` while publishing the wiki | permissions | Wiki publishing only: the token needs `write:repository` and write access to this repository |
| `The wiki is not enabled on <repo>` | configuration | Wiki publishing only: enable the wiki in the repository settings | | `The wiki is not enabled on <repo>` | configuration | Wiki publishing only: enable the wiki in the repository settings |
| `The wiki source directory ... has no Markdown pages` | configuration | Wiki publishing only: `wiki/` is empty or missing; nothing was changed | | `The wiki source directory ... has no Markdown pages` | configuration | Wiki publishing only: the folder has no Markdown pages; nothing was changed |
| `Could not determine the GitHub owner and repository ...` | configuration | The mirror address must look like `https://github.com/<owner>/<repo>.git` | | `Could not determine the GitHub owner and repository ...` | configuration | The mirror address must look like `https://github.com/<owner>/<repo>.git` |
| `Gitea API request failed with HTTP 401` | credentials | Create a new Gitea token and update `TOKEN_FOR_GITEA` | | `Gitea API request failed with HTTP 401` | credentials | Create a new Gitea token and update `TOKEN_FOR_GITEA` |
| `GitHub API request failed with HTTP 401` | credentials | Create a new GitHub token and update `PAT_FOR_GITHUB`; the value must be the token only (no name, quotes or JSON) | | `GitHub API request failed with HTTP 401` | credentials | Create a new GitHub token and update `PAT_FOR_GITHUB`; the value must be the token only (no name, quotes or JSON) |
@@ -58,13 +59,9 @@ settings.
synced.` The run succeeds but nothing was synced. Check that the Gitea synced.` The run succeeds but nothing was synced. Check that the Gitea
version supports the push mirror API and re-run. version supports the push mirror API and re-run.
`No wiki/.publish marker in <owner>/<repo>; wiki publishing is not enabled for `<n> page(s) are in the wiki but not in <folder>/ and were kept; set the prune
this repository.` The Publish wiki run succeeds and writes nothing. The input to true to delete them.` The run succeeds. Pages that exist only in the
repository has not opted in; add `wiki/.publish` if it should publish. wiki are left alone unless the call sets `prune: "true"`.
`<n> page(s) are in the wiki but not in wiki/ and were kept; put the word
'prune' in wiki/.publish to delete them.` The run succeeds. Pages that exist
only in the wiki are left alone unless the marker contains `prune`.
## Runner problems ## Runner problems
@@ -1,4 +1,4 @@
<!-- translation of Troubleshooting.md, source sha256 eb63f4109a0d --> <!-- translation of Troubleshooting.md, source sha256 cad6363616e8 -->
> Ini ialah terjemahan [`Troubleshooting`](Troubleshooting). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. > Ini ialah terjemahan [`Troubleshooting`](Troubleshooting). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
@@ -30,11 +30,12 @@ Kategori menunjukkan di mana perlu dilihat. Kegagalan di luar skrip (kerja tidak
| `TOKEN_FOR_GITEA is missing or empty.` | configuration | Tambah secret | | `TOKEN_FOR_GITEA is missing or empty.` | configuration | Tambah secret |
| `Could not determine the Gitea source repository.` | configuration | Larian tiada nama repositori; laporkan sebagai kecacatan | | `Could not determine the Gitea source repository.` | configuration | Larian tiada nama repositori; laporkan sebagai kecacatan |
| `Expected at most one GitHub push mirror ... found N` | configuration | Kekalkan satu push mirror GitHub | | `Expected at most one GitHub push mirror ... found N` | configuration | Kekalkan satu push mirror GitHub |
| `TOKEN_FOR_GITEA_WIKI is missing or empty.` | configuration | Penerbitan wiki sahaja: repositori telah memilih menyertainya dengan `wiki/.publish` tetapi organisasinya tiada secret itu; simpannya (lihat [credentials.md](Credentials.ms)) | | `TOKEN_FOR_GITEA_WIKI is missing or empty.` | configuration | Penerbitan wiki sahaja: repositori memanggil aliran kerja tetapi organisasinya tiada secret itu, atau panggilan tidak menghantarnya; simpannya dan hantar dalam `secrets:` (lihat [credentials.md](Credentials.ms)) |
| `wiki/.publish may only be empty or contain the word 'prune'.` | configuration | Penerbitan wiki sahaja: biarkan penanda kosong, atau letakkan hanya perkataan `prune` di dalamnya | | `The prune input must be 'true' or 'false'` | configuration | Penerbitan wiki sahaja: tetapkan `prune` dalam panggilan kepada `"true"` atau `"false"` |
| `The wiki folder ... does not exist` | configuration | Penerbitan wiki sahaja: tetapkan `wiki_dir` dalam panggilan kepada folder yang mengandungi halaman (lalai `docs/wiki`) |
| `... HTTP 403 ...` semasa menerbitkan wiki | permissions | Penerbitan wiki sahaja: token memerlukan `write:repository` dan akses tulis kepada repositori ini | | `... HTTP 403 ...` semasa menerbitkan wiki | permissions | Penerbitan wiki sahaja: token memerlukan `write:repository` dan akses tulis kepada repositori ini |
| `The wiki is not enabled on <repo>` | configuration | Penerbitan wiki sahaja: aktifkan wiki dalam tetapan repositori | | `The wiki is not enabled on <repo>` | configuration | Penerbitan wiki sahaja: aktifkan wiki dalam tetapan repositori |
| `The wiki source directory ... has no Markdown pages` | configuration | Penerbitan wiki sahaja: `wiki/` kosong atau tiada; tiada apa-apa diubah | | `The wiki source directory ... has no Markdown pages` | configuration | Penerbitan wiki sahaja: folder itu tiada halaman Markdown; tiada apa-apa diubah |
| `Could not determine the GitHub owner and repository ...` | configuration | Alamat cermin mesti kelihatan seperti `https://github.com/<owner>/<repo>.git` | | `Could not determine the GitHub owner and repository ...` | configuration | Alamat cermin mesti kelihatan seperti `https://github.com/<owner>/<repo>.git` |
| `Gitea API request failed with HTTP 401` | credentials | Cipta token Gitea baharu dan kemas kini `TOKEN_FOR_GITEA` | | `Gitea API request failed with HTTP 401` | credentials | Cipta token Gitea baharu dan kemas kini `TOKEN_FOR_GITEA` |
| `GitHub API request failed with HTTP 401` | credentials | Cipta token GitHub baharu dan kemas kini `PAT_FOR_GITHUB`; nilai mesti token sahaja (tiada nama, tanda petikan atau JSON) | | `GitHub API request failed with HTTP 401` | credentials | Cipta token GitHub baharu dan kemas kini `PAT_FOR_GITHUB`; nilai mesti token sahaja (tiada nama, tanda petikan atau JSON) |
@@ -51,9 +52,7 @@ Kategori menunjukkan di mana perlu dilihat. Kegagalan di luar skrip (kerja tidak
`WARNING: Gitea returned an invalid push mirror list; GitHub metadata was not synced.` Larian berjaya tetapi tiada apa-apa disegerakkan. Semak bahawa versi Gitea menyokong API push mirror dan jalankan semula. `WARNING: Gitea returned an invalid push mirror list; GitHub metadata was not synced.` Larian berjaya tetapi tiada apa-apa disegerakkan. Semak bahawa versi Gitea menyokong API push mirror dan jalankan semula.
`No wiki/.publish marker in <owner>/<repo>; wiki publishing is not enabled for this repository.` Larian Publish wiki berjaya dan tidak menulis apa-apa. Repositori belum memilih menyertainya; tambah `wiki/.publish` jika ia sepatutnya menerbitkan. `<n> page(s) are in the wiki but not in <folder>/ and were kept; set the prune input to true to delete them.` Larian berjaya. Halaman yang hanya ada dalam wiki dibiarkan melainkan panggilan menetapkan `prune: "true"`.
`<n> page(s) are in the wiki but not in wiki/ and were kept; put the word 'prune' in wiki/.publish to delete them.` Larian berjaya. Halaman yang hanya ada dalam wiki dibiarkan melainkan penanda mengandungi `prune`.
## Masalah runner ## Masalah runner
@@ -1,4 +1,4 @@
<!-- translation of Upgrading.md, source sha256 3f119a1ab12d --> <!-- translation of Upgrading.md, source sha256 3233fa5bdab6 -->
> Dette er en oversættelse af [`Upgrading`](Upgrading). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres. > Dette er en oversættelse af [`Upgrading`](Upgrading). Den engelske side er autoritativ, og ændringer her bliver overskrevet, næste gang wikien publiceres.
@@ -18,9 +18,9 @@ Sådan tager en softwareudvikler en ny udgivelse af de fælles workflows i brug
En udgivelse, der bryder kompatibiliteten, får en ny major-version (`v2`), så en pipeline på `@v1` aldrig modtager den, før du selv ændrer linjen. En udgivelse, der bryder kompatibiliteten, får en ny major-version (`v2`), så en pipeline på `@v1` aldrig modtager den, før du selv ændrer linjen.
Kvalitetskontrol-workflowet henter sit script ved sit input `tools-ref`, som som standard er `v1`. For at fastlåse det helt skal du kalde det ved et præcist tag og sætte `tools-ref` til samme tag; ellers følger scriptet major-tagget. Kvalitetskontrol-workflowene (`quality-checks.yml`, `quality-checks-cpp.yml` og `quality-checks-csharp.yml`) og kodemålings-workflowet (`code-metrics.yml`) henter deres scripts ved deres input `tools-ref`, som som standard er `v1`. For at fastlåse et helt skal du kalde det ved et præcist tag og sætte `tools-ref` til samme tag; ellers følger scriptet major-tagget.
De to scoped workflows (**Sync GitHub mirror metadata** og **Publish wiki**) leveres af instansen og står ikke i dine `uses:`-linjer; du kan ikke fastlåse dem, men de henter deres scripts ved major-tagget. Det scoped workflow **Sync GitHub mirror metadata** leveres af instansen og står ikke i dine `uses:`-linjer; du kan ikke fastlåse det, men det henter sit script ved major-tagget. Publicering af wiki er et genbrugeligt workflow, du kalder (`publish-wiki-pages.yml`); det scoped workflow **Publish wiki** og dets markør `wiki/.publish` blev fjernet i 2.0.0, så et repository, der stadig har markøren, publicerer ikke mere, før det kalder det genbrugelige workflow (se introduktionsguiden).
## Aflæsning af en changelog-post ## Aflæsning af en changelog-post
+10 -5
View File
@@ -22,13 +22,18 @@ repository, tags `vMAJOR.MINOR.PATCH` and a moving major tag `vMAJOR`.
A breaking release gets a new major version (`v2`), so a pipeline on `@v1` A breaking release gets a new major version (`v2`), so a pipeline on `@v1`
never receives it until you change the line yourself. never receives it until you change the line yourself.
The quality-checks workflow fetches its script at its input `tools-ref`, which The quality-checks workflows (`quality-checks.yml`, `quality-checks-cpp.yml` and
defaults to `v1`. To pin it completely, call it at an exact tag and set `quality-checks-csharp.yml`) and the code-metrics workflow (`code-metrics.yml`)
fetch their scripts at their input `tools-ref`, which defaults to `v1`. To pin one completely, call it at an exact tag and set
`tools-ref` to the same tag; otherwise the script follows the major tag. `tools-ref` to the same tag; otherwise the script follows the major tag.
The two scoped workflows (**Sync GitHub mirror metadata** and **Publish The scoped workflow **Sync GitHub mirror metadata** is delivered by the
wiki**) are delivered by the instance and are not in your `uses:` lines; you instance and is not in your `uses:` lines; you cannot pin it, but it fetches its
cannot pin them, but they fetch their scripts at the major tag. script at the major tag. Wiki publishing is a reusable workflow you call
(`publish-wiki-pages.yml`); the scoped **Publish wiki** workflow and its
`wiki/.publish` marker were removed in 2.0.0, so a repository that still has the
marker no longer publishes until it calls the reusable workflow (see the
onboarding guide).
## Reading a changelog entry ## Reading a changelog entry
@@ -1,4 +1,4 @@
<!-- translation of Upgrading.md, source sha256 3f119a1ab12d --> <!-- translation of Upgrading.md, source sha256 3233fa5bdab6 -->
> Ini ialah terjemahan [`Upgrading`](Upgrading). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya. > Ini ialah terjemahan [`Upgrading`](Upgrading). Halaman bahasa Inggeris ialah teks yang sah, dan perubahan di sini akan ditimpa pada penerbitan wiki yang seterusnya.
@@ -18,9 +18,9 @@ Cara seorang jurutera perisian menerima keluaran baharu aliran kerja bersama den
Keluaran yang memecahkan keserasian mendapat versi major baharu (`v2`), jadi saluran paip pada `@v1` tidak menerimanya sehingga anda sendiri menukar baris itu. Keluaran yang memecahkan keserasian mendapat versi major baharu (`v2`), jadi saluran paip pada `@v1` tidak menerimanya sehingga anda sendiri menukar baris itu.
Aliran kerja semakan kualiti mengambil skripnya pada inputnya `tools-ref`, yang lalai ialah `v1`. Untuk menyematkannya sepenuhnya, panggil pada tag tepat dan tetapkan `tools-ref` kepada tag yang sama; jika tidak, skrip mengikuti tag major. Aliran kerja semakan kualiti (`quality-checks.yml`, `quality-checks-cpp.yml` dan `quality-checks-csharp.yml`) dan aliran kerja metrik kod (`code-metrics.yml`) mengambil skripnya pada inputnya `tools-ref`, yang lalai ialah `v1`. Untuk menyematkan satu sepenuhnya, panggil pada tag tepat dan tetapkan `tools-ref` kepada tag yang sama; jika tidak, skrip mengikuti tag major.
Dua aliran kerja scoped (**Sync GitHub mirror metadata** dan **Publish wiki**) dihantar oleh instans dan tidak terdapat dalam baris `uses:` anda; anda tidak boleh menyematkannya, tetapi ia mengambil skripnya pada tag major. Aliran kerja scoped **Sync GitHub mirror metadata** dihantar oleh instans dan tidak terdapat dalam baris `uses:` anda; anda tidak boleh menyematkannya, tetapi ia mengambil skripnya pada tag major. Penerbitan wiki ialah aliran kerja boleh guna semula yang anda panggil (`publish-wiki-pages.yml`); aliran kerja scoped **Publish wiki** dan penanda `wiki/.publish` telah dibuang dalam 2.0.0, jadi repositori yang masih mempunyai penanda itu tidak lagi menerbitkan sehingga ia memanggil aliran kerja boleh guna semula (lihat panduan permulaan).
## Membaca satu catatan changelog ## Membaca satu catatan changelog
@@ -9,6 +9,9 @@
- [Credentials](Credentials) - [Credentials](Credentials)
- [Troubleshooting](Troubleshooting) - [Troubleshooting](Troubleshooting)
- [Quality checks (Python, shell)](QualityChecksPythonShell) - [Quality checks (Python, shell)](QualityChecksPythonShell)
- [Quality checks (C and C++)](QualityChecksCpp)
- [Quality checks (C#)](QualityChecksCSharp)
- [Code metrics](CodeMetrics)
- [Upgrading](Upgrading) - [Upgrading](Upgrading)
- [Capabilities](Capabilities) - [Capabilities](Capabilities)
@@ -19,6 +22,9 @@
- [Credentials](Credentials.da) - [Credentials](Credentials.da)
- [Fejlfinding](Troubleshooting.da) - [Fejlfinding](Troubleshooting.da)
- [Kvalitetskontrol (Python, shell)](QualityChecksPythonShell.da) - [Kvalitetskontrol (Python, shell)](QualityChecksPythonShell.da)
- [Kvalitetskontrol (C og C++)](QualityChecksCpp.da)
- [Kvalitetskontrol (C#)](QualityChecksCSharp.da)
- [Kodemålinger](CodeMetrics.da)
- [Opgradering](Upgrading.da) - [Opgradering](Upgrading.da)
- [Kapabiliteter](Capabilities.da) - [Kapabiliteter](Capabilities.da)
@@ -29,5 +35,8 @@
- [Kelayakan](Credentials.ms) - [Kelayakan](Credentials.ms)
- [Diagnosis](Troubleshooting.ms) - [Diagnosis](Troubleshooting.ms)
- [Semakan kualiti (Python, shell)](QualityChecksPythonShell.ms) - [Semakan kualiti (Python, shell)](QualityChecksPythonShell.ms)
- [Semakan kualiti (C dan C++)](QualityChecksCpp.ms)
- [Semakan kualiti (C#)](QualityChecksCSharp.ms)
- [Metrik kod](CodeMetrics.ms)
- [Menaik taraf](Upgrading.ms) - [Menaik taraf](Upgrading.ms)
- [Keupayaan](Capabilities.ms) - [Keupayaan](Capabilities.ms)
+117
View File
@@ -0,0 +1,117 @@
# Showing code metrics and coverage badges
How a software engineer calls the shared code-metrics workflow and shows its
three badges (code files, lines of code and coverage) in a README. The workflow
counts the code of the repository, reads a coverage report that the caller
supplies and commits three SVG files to a `badges` branch. It runs no test and
needs no secret. Its contract is ADR-0010 in this repository.
## Calling the workflow
The workflow cannot see the files of your test job, so the tests run in a job of
your own, upload the Cobertura XML report as an artifact, and the metrics job
runs after it:
```yaml
name: Code metrics
on:
push:
branches: [ main ]
jobs:
tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4.2.2
- run: python3 -m pip install pytest pytest-cov && python3 -m pytest --cov --cov-report=xml
- uses: actions/upload-artifact@v4.6.2
with:
name: coverage
path: coverage.xml
metrics:
needs: tests
permissions:
contents: write
uses: TirSystem/github-action/.gitea/workflows/code-metrics.yml@v1
with:
coverage-artifact: coverage
coverage-report: coverage.xml
```
| Input | Default | Meaning |
| --- | --- | --- |
| `working-directory` | `.` | Directory, relative to the repository root, whose code is counted |
| `exclude` | empty | Comma list of patterns of files and directories that are not counted, in addition to the defaults; a pattern without `/` matches a name anywhere, a pattern with `/` matches the path below `working-directory` |
| `coverage-report` | empty | Cobertura XML report: the file inside the artifact when `coverage-artifact` is set, otherwise a path in the repository; empty writes no coverage badge |
| `coverage-artifact` | empty | Name of an artifact, uploaded by an earlier job of the same run, that holds the report |
| `coverage-warn` | `60` | Coverage in percent below which the badge is red |
| `coverage-good` | `80` | Coverage in percent from which the badge is green; in between it is yellow |
| `badges-branch` | `badges` | Branch that holds the badge files; it is created when it does not exist |
| `tools-ref` | `v1` | Tag or branch of `TirSystem/github-action` from which the scripts in `tools/` are fetched |
Reference the workflow at the major tag (`@v1`) or at an exact tag, never at
`main`. A call at an exact tag is pinned completely only when `tools-ref` names
the same tag. See the upgrade guide. Start the workflow on pushes to your
default branch only: the badge commit carries `[skip ci]` and is made with the
token of the run, which does not start another run, but a trigger on every
branch would write one commit per branch.
## What is counted
- A code file is a text file with the extension of a supported language:
Python, shell, PowerShell, C, C++, C#, Java, JavaScript, TypeScript, Go, Rust
and SQL. Markdown, YAML, JSON, XML, HTML and CSS are not code files.
- Every line is exactly one of blank (only white space), comment (only comment
text) or code (everything else, including a line with code and a trailing
comment). The badge shows the lines of code only.
- A comment marker inside a string is not a comment, and a Python docstring is a
string, so it counts as code.
- Directories that start with a dot and `node_modules`, `vendor`, `third_party`,
`dist`, `build`, `out`, `bin`, `obj`, `target`, `venv`, `__pycache__` and
`shared-scripts` are not counted. Generated code is excluded with `exclude`.
- Raw and verbatim strings that differ from an ordinary string (Rust `r#"..."#`,
C# `@"..."`, C++ raw strings) can be counted wrongly. The counts will differ
a little from other tools; the rules are the same on every run.
## The coverage badge
The report is a Cobertura XML file, which `coverage.py` (`coverage xml`),
`gcovr` (`--cobertura`) and `coverlet` (`cobertura` format) write. The badge
shows `lines-covered` over `lines-valid`, rounded to a whole percent, and its
colour follows the two thresholds. A report that does not exist, is not
well-formed Cobertura XML or has no valid lines fails the run with the file
named; the coverage badge is never written as `0%` for a bad report. Without
`coverage-report` the run passes with a notice and no coverage badge.
## Showing the badges
Add the images to the README with the raw URLs of the branch:
```markdown
![code files](https://git.tirsystem.com/OWNER/REPO/raw/branch/badges/code-files.svg)
![lines of code](https://git.tirsystem.com/OWNER/REPO/raw/branch/badges/code-lines.svg)
![coverage](https://git.tirsystem.com/OWNER/REPO/raw/branch/badges/coverage.svg)
```
On the GitHub mirror use `https://raw.githubusercontent.com/OWNER/REPO/badges/code-files.svg`;
the push mirror copies the branch. The `badges` branch also holds
`code-metrics.json` with the numbers per language. The instance caches a raw
file for up to six hours, so a changed badge can show the old number for a
while there; the mirror caches for five minutes. A private repository does not
show the badges to a reader who is not signed in.
## When the workflow fails
| Message | Cause | Fix |
| --- | --- | --- |
| `[configuration] working-directory must be a relative path inside the repository.` | An absolute path or `..` | Use a path inside the repository |
| `[configuration] coverage-report '<x>' does not exist.` | The report was not produced, or the artifact name or path is wrong | Check the test job and the artifact name; the path is inside the artifact |
| `[configuration] coverage-report '<x>' is not well-formed XML` or `is not a Cobertura report` | The file is another format | Write Cobertura XML (`coverage xml`, `gcovr --cobertura`) |
| `[configuration] coverage-report '<x>' has no valid lines.` | The tests ran no measured code | Check the coverage configuration |
| `[configuration] coverage-artifact '<x>' needs coverage-report` | The artifact is named but not the file in it | Set `coverage-report` |
| `[configuration] coverage-warn ... is above coverage-good` or `is not a number from 0 to 100` | A threshold is invalid | Correct the input |
| `[configuration] badges-branch '<x>' must not be a default branch` or `holds files that are not badge files` | The branch name is `main`, `master` or a branch with other content | Use a branch that holds only the badges |
| `the push to '<x>' failed twice` | Another run wrote the branch at the same time, or the token cannot write | Re-run; check that the job has `contents: write` |
| The download step fails | The artifact does not exist in this run, or the job does not run after the test job | Add `needs:` and check the artifact name |
+11 -10
View File
@@ -82,29 +82,30 @@ is ever printed.
## Wiki publishing secret ## Wiki publishing secret
The workflow **Publish wiki** copies `wiki/` to the Gitea wiki of a repository The workflow **Publish wiki pages** copies `docs/wiki/` to the Gitea wiki of a
that has opted in with `wiki/.publish` (see [onboarding.md](onboarding.md)). It repository that calls it (see [onboarding.md](onboarding.md)). It uses its own
uses its own secret, so the metadata sync token stays read-only. secret, so the metadata sync token stays read-only. The caller passes the
secret in `secrets:`; it is not inherited.
| Secret | Content | Used for | | Secret | Content | Used for |
| --- | --- | --- | | --- | --- | --- |
| `TOKEN_FOR_GITEA_WIKI` | The Gitea token, nothing else | Writing the wiki pages of the repositories of an organization that opt in | | `TOKEN_FOR_GITEA_WIKI` | The Gitea token, nothing else | Writing the wiki pages of the repositories of an organization that opt in |
- Store it as a secret of the organization of each repository that publishes. - Store it as a secret of the organization of each repository that publishes.
There is no fallback to `TOKEN_FOR_GITEA`: a repository that opted in and has There is no fallback to `TOKEN_FOR_GITEA`: a repository that calls the
no such secret fails with a configuration error, and a repository that did workflow and has no such secret fails with a configuration error, and a
not opt in does not need it. repository that does not call it does not need it.
- The token needs scope `write:repository`, and its user needs write access to - The token needs scope `write:repository`, and its user needs write access to
the repositories. Gitea has no wiki-only scope, so create the token for a the repositories. Gitea has no wiki-only scope, so create the token for a
dedicated user. S05 approves the scope and S03 stores the secret. dedicated user. S05 approves the scope and S03 stores the secret.
- Calls made: `GET /repos/{owner}/{repo}`, `GET /repos/{owner}/{repo}/wiki/pages`, - Calls made: `GET /repos/{owner}/{repo}`, `GET /repos/{owner}/{repo}/wiki/pages`,
`GET`, `POST` and `PATCH /repos/{owner}/{repo}/wiki/...` and, for a page that `GET`, `POST` and `PATCH /repos/{owner}/{repo}/wiki/...` and, for a page that
no longer exists in `wiki/`, `DELETE /repos/{owner}/{repo}/wiki/page/{name}`. no longer exists in `docs/wiki/`, `DELETE /repos/{owner}/{repo}/wiki/page/{name}`.
- The wiki must be enabled in the repository settings. The workflow refuses to - The wiki must be enabled in the repository settings. The workflow refuses to
run when it is not. run when it is not.
- Pages that are in `wiki/` are always overwritten from it, so a change made - Pages that are in `docs/wiki/` are always overwritten from it, so a change
in the Gitea web UI is lost on the next publish. Pages that are only in the made in the Gitea web UI is lost on the next publish. Pages that are only in
wiki are removed only when `wiki/.publish` contains the word `prune`. the wiki are removed only when the call sets `prune: "true"`.
## Rotating a token ## Rotating a token
+27 -13
View File
@@ -45,26 +45,40 @@ Delivery is automatic, so a repository needs no copy of the workflow. Secrets ar
## Publish a wiki (optional) ## Publish a wiki (optional)
The **Publish wiki** workflow copies the Markdown pages in `wiki/` to the The reusable workflow **Publish wiki pages** copies the Markdown pages in
repository wiki when a change to `wiki/` is merged to `main`. It is delivered `docs/wiki/` to the repository wiki when a change to `docs/wiki/` is merged to
like the sync workflow, but it writes only where a repository opts in. `main`. The repository calls it, so nothing is written where no call exists.
The wiki has English and the product owner's language as a minimum.
1. Enable the wiki in the repository settings. 1. Enable the wiki in the repository settings.
2. Add Markdown pages to `wiki/`. The file name is the page name: `Home.md` is 2. Add Markdown pages to `docs/wiki/`. The file name is the page name: `Home.md`
the home page. A hyphen in the name is a space in the page name: `Home-da.md` is the home page. A hyphen in the name is a space in the page name:
is the page "Home da", and a link to it is `[Dansk](Home-da)`. `Home-da.md` is the page "Home da", and a link to it is `[Dansk](Home-da)`.
3. Add the marker `wiki/.publish`. Leave it empty to create and update pages 3. Add a workflow, for example `.gitea/workflows/publish-wiki.yml`, that calls
only. Put the single word `prune` in it to also delete wiki pages that are the reusable workflow:
not in `wiki/`, so the wiki always equals the folder.
```yaml
on:
push:
branches: [ main ]
paths: [ "docs/wiki/**" ]
jobs:
wiki:
uses: TirSystem/github-action/.gitea/workflows/publish-wiki-pages.yml@v1
with:
prune: "true"
secrets:
TOKEN_FOR_GITEA_WIKI: ${{ secrets.TOKEN_FOR_GITEA_WIKI }}
```
`wiki_dir` defaults to `docs/wiki`. `prune: "true"` also deletes wiki pages
that are not in the folder, so the wiki always equals the folder; the
default `false` keeps them.
4. Store `TOKEN_FOR_GITEA_WIKI` as a secret of the repository's organization, 4. Store `TOKEN_FOR_GITEA_WIKI` as a secret of the repository's organization,
using the scope in [credentials.md](credentials.md). using the scope in [credentials.md](credentials.md).
5. Merge the change to `main`. The log must end with `Published <n> page(s) to 5. Merge the change to `main`. The log must end with `Published <n> page(s) to
the wiki of <owner>/<repo>`. the wiki of <owner>/<repo>`.
A repository without `wiki/.publish` is skipped with the line `No
wiki/.publish marker in <owner>/<repo>`: nothing is written and no secret is
needed.
## Runners ## Runners
| Item | Value | | Item | Value |
+102
View File
@@ -0,0 +1,102 @@
# Reading C and C++ quality-check results
How a software engineer calls the shared C and C++ quality checks, reads a
failed run and makes it pass. The workflow format-checks and statically checks
the C and C++ files of a repository with clang tools at exact versions, changes
no file and needs no secret. Its contract is ADR-0008 in this repository.
## Calling the workflow
```yaml
name: Quality C++
on:
pull_request:
push:
branches: [ main ]
jobs:
quality:
permissions:
contents: read
uses: TirSystem/github-action/.gitea/workflows/quality-checks-cpp.yml@v1
with:
std: c++20
```
| Input | Default | Meaning |
| --- | --- | --- |
| `working-directory` | `.` | Directory, relative to the repository root, whose files are checked |
| `std` | `c++20` | C++ standard given to clang-tidy when there is no compile database, such as `c++17` or `gnu++20` |
| `compile-commands-dir` | empty | Directory, relative to the repository root, with a `compile_commands.json` that your pipeline built before the call; when set, clang-tidy uses it and ignores `std` |
| `clang-format-version` | `23.1.3` | Exact version of the clang-format package |
| `clang-tidy-version` | `22.1.8` | Exact version of the clang-tidy package |
| `tools-ref` | `v1` | Tag or branch of `TirSystem/github-action` from which the script `tools/quality-checks-cpp.sh` is fetched |
Reference the workflow at the major tag (`@v1`) or at an exact tag, never at
`main`. A call at an exact tag is pinned completely only when `tools-ref` names
the same tag. See the upgrade guide. The tool versions are exact: a newer
version of a tool is a new release of this repository, not a silent change.
## What the repository needs
- A `.clang-format` in the working directory or in a directory above it, up to
the top of the repository. Without one the run fails with `[configuration]`
and no tool runs.
- Optionally a `.clang-tidy` with the checks of the project (for example the
C++ Core Guidelines). Without one the workflow runs the compiler diagnostics
and the static analyzer (`clang-diagnostic-*` and `clang-analyzer-*`),
because clang-tidy itself enables no check when there is no configuration.
- For precise static checks, a compile database: build it first (for example
`cmake -S . -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON`) and pass
`compile-commands-dir: build`. The workflow does not build your project.
## What is checked
| Files | Tool | What it reports |
| --- | --- | --- |
| tracked `*.c`, `*.cc`, `*.cpp`, `*.cxx`, `*.h`, `*.hh`, `*.hpp`, `*.hxx` | `clang-format --dry-run -Werror` | formatting that differs from `.clang-format` |
| the same files | `clang-tidy` | findings of the checks of `.clang-tidy` (or of `clang-diagnostic-*` and `clang-analyzer-*` without one); `*.c` files are checked as C17, the other files as the C++ standard of `std`; every finding is an error (`--warnings-as-errors=*`), so a warning fails the run |
Untracked files are not checked. A repository without these files passes with a
notice. There is no `cppcheck`: only an old version of it is available as a
package, and old tools are not run (ADR-0008).
## Reading a run
1. Open the status check on the pull request, then the job `quality-checks-cpp`.
2. Each tool has its own log group. Every tool runs even when an earlier one
failed, so one run shows all findings.
3. A tool ends with `PASS <tool>` or `FAIL <tool>`; the last line of a failed
run names them: `Quality checks failed: clang-format`.
4. A finding is written by the tool itself as `path:line:column: message`.
`clang-format` prints paths relative to `working-directory`; `clang-tidy`
prints absolute paths in the workspace of the runner
(`/workspace/<owner>/<repo>/main.cpp:5:18`).
## Typical failures and fixes
| Tool | What the log shows | Fix |
| --- | --- | --- |
| `clang-format` | `code should be clang-formatted [-Wclang-format-violations]` | Run `clang-format -i <file>` with the same version and commit |
| `clang-tidy` | `warning: ... [modernize-use-nullptr]` | Fix the code, or switch the check off in `.clang-tidy` with a reason |
| `clang-tidy` | `error: 'x.h' file not found` | Build the compile database and set `compile-commands-dir`, or add the include path to it |
To reproduce a run on your machine, install the same versions:
```bash
python3 -m venv .qc
.qc/bin/pip install clang-format==23.1.3 clang-tidy==22.1.8
git ls-files '*.cpp' '*.hpp' | xargs .qc/bin/clang-format --dry-run -Werror
```
## When the workflow itself fails
| Message | Cause | Fix |
| --- | --- | --- |
| `[configuration] std '<x>' is not a C++ standard` | `std` is not like `c++20` or `gnu++20` | Correct the input |
| `[configuration] ... is not an exact version` | A tool version is a range, a channel or empty | Give an exact version such as `23.1.3` |
| `[configuration] working-directory must be a relative path inside the repository.` | An absolute path or `..` | Use a path inside the repository |
| `[configuration] No .clang-format found` | The project has no style file | Add a `.clang-format` |
| `[configuration] compile-commands-dir '<x>' has no compile_commands.json` | The database was not built before the call | Build it in an earlier step of the job or in a job it depends on |
| The install step fails | PyPI or the runner network is unreachable | Re-run the job; see [troubleshooting.md](troubleshooting.md) |
+97
View File
@@ -0,0 +1,97 @@
# Reading C# quality-check results
How a software engineer calls the shared C# quality checks, reads a failed run
and makes it pass. The workflow checks the formatting and builds a solution or
project with warnings as errors, on an exact .NET SDK of version 10 or later,
changes no file and needs no secret. Its contract is ADR-0008 in this
repository.
## Calling the workflow
```yaml
name: Quality C#
on:
pull_request:
push:
branches: [ main ]
jobs:
quality:
permissions:
contents: read
uses: TirSystem/github-action/.gitea/workflows/quality-checks-csharp.yml@v1
with:
project: src/App.sln
```
| Input | Default | Meaning |
| --- | --- | --- |
| `working-directory` | `.` | Directory, relative to the repository root, whose files are checked |
| `project` | empty | Solution or project file, relative to `working-directory`; empty uses the single `*.sln` or `*.slnx`, else the single `*.csproj`, that is tracked there |
| `dotnet-sdk-version` | `10.0.401` | Exact version of the .NET SDK, 10 or later; a channel such as `10.0`, a range and a version below 10 are refused |
| `tools-ref` | `v1` | Tag or branch of `TirSystem/github-action` from which the script `tools/quality-checks-csharp.sh` is fetched |
Reference the workflow at the major tag (`@v1`) or at an exact tag, never at
`main`. A call at an exact tag is pinned completely only when `tools-ref` names
the same tag. See the upgrade guide. The SDK version is exact: raising the
default is a new release of this repository, not a silent change.
## What the repository needs
- A solution or project that builds with the SDK. Several candidates need the
`project` input; C# files with no solution or project fail with
`[configuration]`.
- The network: `dotnet format` and `dotnet build` restore the NuGet packages of
the project in the run.
- Rules in `.editorconfig` (and the analyzers the project enables, for example
with `AnalysisLevel` and `EnforceCodeStyleInBuild`) decide what a finding is.
## What is checked
| Files | Tool | What it reports |
| --- | --- | --- |
| the solution or project | `dotnet format --verify-no-changes` | formatting and style that differ from `.editorconfig` |
| the same | `dotnet build -warnaserror` | compiler and analyzer warnings, which fail the build |
Untracked files are not checked. A repository without `*.cs` files passes with
a notice. Project tests are not run.
## Reading a run
1. Open the status check on the pull request, then the job
`quality-checks-csharp`.
2. Each tool has its own log group. Both run even when the first failed, so one
run shows all findings.
3. A tool ends with `PASS <tool>` or `FAIL <tool>`; the last line of a failed
run names them:
`Quality checks failed: dotnet format --verify-no-changes`.
4. A finding is written by the tool itself as `path(line,column): CODE message`.
The path is absolute in the workspace of the runner
(`/workspace/<owner>/<repo>/src/Program.cs(8,27)`).
## Typical failures and fixes
| Tool | What the log shows | Fix |
| --- | --- | --- |
| `dotnet format --verify-no-changes` | `Program.cs(12,5): error WHITESPACE: Fix whitespace formatting` | Run `dotnet format <project>` and commit |
| `dotnet build -warnaserror` | `CS8602: Dereference of a possibly null reference` | Fix the code (null check), do not suppress the warning without a reason |
| `dotnet build -warnaserror` | `IDE0005: Using directive is unnecessary` | Remove the directive |
To reproduce a run on your machine, use the same SDK:
```bash
dotnet --version # 10.0.401
dotnet format src/App.sln --verify-no-changes
dotnet build src/App.sln -warnaserror
```
## When the workflow itself fails
| Message | Cause | Fix |
| --- | --- | --- |
| `[configuration] dotnet-sdk-version '<x>' is not an exact stable version 10 or later` | A channel, a range, a preview or a version below 10 | Give an exact version such as `10.0.401` |
| `[configuration] working-directory must be a relative path inside the repository.` | An absolute path or `..` | Use a path inside the repository |
| `[configuration] C# files exist but no solution or project was found` | The repository has `*.cs` files and no `*.sln`, `*.slnx` or `*.csproj` in the working directory | Add one, or set `working-directory` |
| `[configuration] Several solutions or projects were found` | More than one candidate | Set the input `project` |
| The install step fails | `dot.net` or the runner network is unreachable | Re-run the job; see [troubleshooting.md](troubleshooting.md) |
+7 -10
View File
@@ -26,11 +26,12 @@ never starts, or stops before the step) is a **runner** problem.
| `TOKEN_FOR_GITEA is missing or empty.` | configuration | Add the secret | | `TOKEN_FOR_GITEA is missing or empty.` | configuration | Add the secret |
| `Could not determine the Gitea source repository.` | configuration | The run has no repository name; report as a defect | | `Could not determine the Gitea source repository.` | configuration | The run has no repository name; report as a defect |
| `Expected at most one GitHub push mirror ... found N` | configuration | Keep one GitHub push mirror | | `Expected at most one GitHub push mirror ... found N` | configuration | Keep one GitHub push mirror |
| `TOKEN_FOR_GITEA_WIKI is missing or empty.` | configuration | Wiki publishing only: the repository opted in with `wiki/.publish` but its organization has no such secret; store it (see [credentials.md](credentials.md)) | | `TOKEN_FOR_GITEA_WIKI is missing or empty.` | configuration | Wiki publishing only: the repository calls the workflow but its organization has no such secret, or the call does not pass it; store it and pass it in `secrets:` (see [credentials.md](credentials.md)) |
| `wiki/.publish may only be empty or contain the word 'prune'.` | configuration | Wiki publishing only: leave the marker empty, or put only the word `prune` in it | | `The prune input must be 'true' or 'false'` | configuration | Wiki publishing only: set `prune` in the call to `"true"` or `"false"` |
| `The wiki folder ... does not exist` | configuration | Wiki publishing only: set `wiki_dir` in the call to the folder with the pages (default `docs/wiki`) |
| `... HTTP 403 ...` while publishing the wiki | permissions | Wiki publishing only: the token needs `write:repository` and write access to this repository | | `... HTTP 403 ...` while publishing the wiki | permissions | Wiki publishing only: the token needs `write:repository` and write access to this repository |
| `The wiki is not enabled on <repo>` | configuration | Wiki publishing only: enable the wiki in the repository settings | | `The wiki is not enabled on <repo>` | configuration | Wiki publishing only: enable the wiki in the repository settings |
| `The wiki source directory ... has no Markdown pages` | configuration | Wiki publishing only: `wiki/` is empty or missing; nothing was changed | | `The wiki source directory ... has no Markdown pages` | configuration | Wiki publishing only: the folder has no Markdown pages; nothing was changed |
| `Could not determine the GitHub owner and repository ...` | configuration | The mirror address must look like `https://github.com/<owner>/<repo>.git` | | `Could not determine the GitHub owner and repository ...` | configuration | The mirror address must look like `https://github.com/<owner>/<repo>.git` |
| `Gitea API request failed with HTTP 401` | credentials | Create a new Gitea token and update `TOKEN_FOR_GITEA` | | `Gitea API request failed with HTTP 401` | credentials | Create a new Gitea token and update `TOKEN_FOR_GITEA` |
| `GitHub API request failed with HTTP 401` | credentials | Create a new GitHub token and update `PAT_FOR_GITHUB`; the value must be the token only (no name, quotes or JSON) | | `GitHub API request failed with HTTP 401` | credentials | Create a new GitHub token and update `PAT_FOR_GITHUB`; the value must be the token only (no name, quotes or JSON) |
@@ -52,13 +53,9 @@ settings.
synced.` The run succeeds but nothing was synced. Check that the Gitea synced.` The run succeeds but nothing was synced. Check that the Gitea
version supports the push mirror API and re-run. version supports the push mirror API and re-run.
`No wiki/.publish marker in <owner>/<repo>; wiki publishing is not enabled for `<n> page(s) are in the wiki but not in <folder>/ and were kept; set the prune
this repository.` The Publish wiki run succeeds and writes nothing. The input to true to delete them.` The run succeeds. Pages that exist only in the
repository has not opted in; add `wiki/.publish` if it should publish. wiki are left alone unless the call sets `prune: "true"`.
`<n> page(s) are in the wiki but not in wiki/ and were kept; put the word
'prune' in wiki/.publish to delete them.` The run succeeds. Pages that exist
only in the wiki are left alone unless the marker contains `prune`.
## Runner problems ## Runner problems
+10 -5
View File
@@ -16,13 +16,18 @@ repository, tags `vMAJOR.MINOR.PATCH` and a moving major tag `vMAJOR`.
A breaking release gets a new major version (`v2`), so a pipeline on `@v1` A breaking release gets a new major version (`v2`), so a pipeline on `@v1`
never receives it until you change the line yourself. never receives it until you change the line yourself.
The quality-checks workflow fetches its script at its input `tools-ref`, which The quality-checks workflows (`quality-checks.yml`, `quality-checks-cpp.yml` and
defaults to `v1`. To pin it completely, call it at an exact tag and set `quality-checks-csharp.yml`) and the code-metrics workflow (`code-metrics.yml`)
fetch their scripts at their input `tools-ref`, which defaults to `v1`. To pin one completely, call it at an exact tag and set
`tools-ref` to the same tag; otherwise the script follows the major tag. `tools-ref` to the same tag; otherwise the script follows the major tag.
The two scoped workflows (**Sync GitHub mirror metadata** and **Publish The scoped workflow **Sync GitHub mirror metadata** is delivered by the
wiki**) are delivered by the instance and are not in your `uses:` lines; you instance and is not in your `uses:` lines; you cannot pin it, but it fetches its
cannot pin them, but they fetch their scripts at the major tag. script at the major tag. Wiki publishing is a reusable workflow you call
(`publish-wiki-pages.yml`); the scoped **Publish wiki** workflow and its
`wiki/.publish` marker were removed in 2.0.0, so a repository that still has the
marker no longer publishes until it calls the reusable workflow (see the
onboarding guide).
## Reading a changelog entry ## Reading a changelog entry
+289
View File
@@ -0,0 +1,289 @@
"""Offline tests for tools/badges.py (ADR-0010): the coverage reader and the SVG renderer."""
import importlib.util
import json
import xml.etree.ElementTree as ElementTree
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
_SPEC = importlib.util.spec_from_file_location(
"badges", REPO_ROOT / "tools" / "badges.py"
)
assert _SPEC is not None and _SPEC.loader is not None
badges = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(badges)
DTD = (
'<!DOCTYPE coverage SYSTEM "http://cobertura.sourceforge.net/xml/coverage-04.dtd">'
)
def report(
tmp_path: Path, attributes: str, root: str = "coverage", prolog: str = ""
) -> Path:
path = tmp_path / "coverage.xml"
path.write_text(
f'<?xml version="1.0" ?>\n{prolog}<{root} {attributes}><packages/></{root}>\n'
)
return path
class TestReadCoverage:
@pytest.mark.parametrize(
("covered", "valid", "percent"),
[
(0, 10, 0),
(1, 3, 33),
(2, 3, 67),
(5, 8, 63),
(87, 100, 87),
(1, 200, 1),
(10, 10, 100),
],
)
def test_percent_is_covered_over_valid_rounded_half_up(
self, tmp_path, covered, valid, percent
):
path = report(
tmp_path, f'lines-covered="{covered}" lines-valid="{valid}" line-rate="0"'
)
assert badges.read_coverage(path) == percent
def test_the_half_rounds_up(self, tmp_path):
assert (
badges.read_coverage(report(tmp_path, 'lines-covered="1" lines-valid="8"'))
== 13
)
def test_line_rate_is_used_when_the_counts_are_absent(self, tmp_path):
assert badges.read_coverage(report(tmp_path, 'line-rate="0.875"')) == 88
def test_a_document_type_declaration_is_accepted(self, tmp_path):
path = report(tmp_path, 'lines-covered="9" lines-valid="10"', prolog=DTD + "\n")
assert badges.read_coverage(path) == 90
def test_missing_file_names_the_file(self, tmp_path):
with pytest.raises(badges.ConfigurationError, match="nope.xml' does not exist"):
badges.read_coverage(tmp_path / "nope.xml")
def test_malformed_xml_names_the_file(self, tmp_path):
path = tmp_path / "bad.xml"
path.write_text("<coverage lines-valid=")
with pytest.raises(
badges.ConfigurationError, match="bad.xml' is not well-formed"
):
badges.read_coverage(path)
def test_other_xml_is_not_a_cobertura_report(self, tmp_path):
with pytest.raises(badges.ConfigurationError, match="not a Cobertura report"):
badges.read_coverage(report(tmp_path, 'lines-valid="1"', root="testsuite"))
def test_no_valid_lines_is_a_failure_and_not_zero_percent(self, tmp_path):
with pytest.raises(badges.ConfigurationError, match="has no valid lines"):
badges.read_coverage(report(tmp_path, 'lines-covered="0" lines-valid="0"'))
def test_covered_above_valid_is_refused(self, tmp_path):
with pytest.raises(badges.ConfigurationError, match="outside 0..lines-valid"):
badges.read_coverage(report(tmp_path, 'lines-covered="5" lines-valid="4"'))
def test_report_without_any_rate_is_refused(self, tmp_path):
with pytest.raises(badges.ConfigurationError, match="neither lines-valid"):
badges.read_coverage(report(tmp_path, 'branch-rate="1"'))
def test_line_rate_outside_zero_to_one_is_refused(self, tmp_path):
with pytest.raises(badges.ConfigurationError, match="outside 0..1"):
badges.read_coverage(report(tmp_path, 'line-rate="1.5"'))
def test_values_that_are_not_numbers_are_refused(self, tmp_path):
with pytest.raises(badges.ConfigurationError, match="not a number"):
badges.read_coverage(report(tmp_path, 'lines-covered="a" lines-valid="b"'))
with pytest.raises(badges.ConfigurationError, match="not a number"):
badges.read_coverage(report(tmp_path, 'line-rate="high"'))
def test_entity_declarations_are_refused(self, tmp_path):
prolog = '<!DOCTYPE coverage [<!ENTITY x "y">]>\n'
with pytest.raises(badges.ConfigurationError, match="declares entities"):
badges.read_coverage(
report(tmp_path, 'lines-covered="1" lines-valid="2"', prolog=prolog)
)
class TestColor:
@pytest.mark.parametrize(
("percent", "color"),
[
(0, badges.RED),
(59, badges.RED),
(60, badges.YELLOW),
(79, badges.YELLOW),
(80, badges.GREEN),
(100, badges.GREEN),
],
)
def test_thresholds_are_warn_and_good(self, percent, color):
assert badges.coverage_color(percent, 60, 80) == color
def test_thresholds_can_be_moved(self):
assert badges.coverage_color(50, 40, 50) == badges.GREEN
assert badges.coverage_color(39, 40, 50) == badges.RED
class TestRender:
def test_is_valid_xml_with_title_and_text_alternative(self):
svg = badges.render("code files", "1,234", badges.BLUE)
root = ElementTree.fromstring(svg)
ns = "{http://www.w3.org/2000/svg}"
assert root.tag == ns + "svg"
assert root.get("role") == "img"
assert root.get("aria-label") == "code files: 1,234"
assert root.find(ns + "title").text == "code files: 1,234"
def test_same_input_gives_the_same_bytes_and_has_no_script_or_link(self):
first = badges.render("coverage", "87%", badges.GREEN)
assert first == badges.render("coverage", "87%", badges.GREEN)
for forbidden in (
"<script",
"href",
"<image",
"<style",
"http://www.w3.org/1999/xlink",
):
assert forbidden not in first
def test_width_follows_the_number_of_characters(self):
narrow = ElementTree.fromstring(badges.render("a", "1", badges.BLUE)).get(
"width"
)
wide = ElementTree.fromstring(badges.render("a", "1,000", badges.BLUE)).get(
"width"
)
assert int(wide) - int(narrow) == 4 * badges.CHAR_WIDTH
def test_text_is_escaped(self):
svg = badges.render("a<b", 'c&"d', badges.BLUE)
assert "a&lt;b" in svg and "c&amp;&quot;d" in svg
ElementTree.fromstring(svg)
def test_text_colour_has_the_better_contrast(self):
assert badges._text_color("#555555") == "#ffffff"
assert badges._text_color(badges.GREEN) == "#000000"
def metrics_file(tmp_path: Path, files: int = 3, lines: int = 1200) -> Path:
path = tmp_path / "metrics.json"
path.write_text(
json.dumps(
{"files": files, "lines": lines, "comment": 5, "blank": 6, "languages": {}}
)
)
return path
class TestMain:
def test_writes_three_badges_and_the_metrics(self, tmp_path, capsys):
out = tmp_path / "out"
cov = report(tmp_path, 'lines-covered="87" lines-valid="100"')
code = badges.main(
[
"--metrics",
str(metrics_file(tmp_path)),
"--output-dir",
str(out),
"--coverage-report",
str(cov),
]
)
assert code == 0
assert sorted(p.name for p in out.iterdir()) == [
"code-files.svg",
"code-lines.svg",
"code-metrics.json",
"coverage.svg",
]
assert (
'aria-label="lines of code: 1,200"' in (out / "code-lines.svg").read_text()
)
assert 'aria-label="coverage: 87%"' in (out / "coverage.svg").read_text()
assert f'fill="{badges.GREEN}"' in (out / "coverage.svg").read_text()
assert b"\r" not in (out / "code-files.svg").read_bytes()
assert "wrote" in capsys.readouterr().out
def test_output_is_byte_identical_on_a_second_run(self, tmp_path):
args = [
"--metrics",
str(metrics_file(tmp_path)),
"--output-dir",
str(tmp_path / "o"),
]
badges.main(args)
first = {p.name: p.read_bytes() for p in (tmp_path / "o").iterdir()}
badges.main(args)
assert first == {p.name: p.read_bytes() for p in (tmp_path / "o").iterdir()}
def test_without_a_report_no_coverage_badge_and_a_notice(self, tmp_path, capsys):
out = tmp_path / "out"
assert (
badges.main(
["--metrics", str(metrics_file(tmp_path)), "--output-dir", str(out)]
)
== 0
)
assert not (out / "coverage.svg").exists()
assert "::notice::No coverage-report given" in capsys.readouterr().out
def test_a_bad_report_fails_and_writes_nothing(self, tmp_path, capsys):
out = tmp_path / "out"
code = badges.main(
[
"--metrics",
str(metrics_file(tmp_path)),
"--output-dir",
str(out),
"--coverage-report",
str(tmp_path / "missing.xml"),
]
)
assert code == 1
assert "::error::[configuration] coverage-report" in capsys.readouterr().out
assert not out.exists()
@pytest.mark.parametrize(
("warn", "good", "message"),
[
("x", "80", "coverage-warn 'x'"),
("60", "101", "coverage-good '101'"),
("-1", "80", "coverage-warn '-1'"),
("90", "80", "above coverage-good"),
],
)
def test_invalid_thresholds_fail(self, tmp_path, capsys, warn, good, message):
code = badges.main(
[
"--metrics",
str(metrics_file(tmp_path)),
"--output-dir",
str(tmp_path / "o"),
"--coverage-warn",
warn,
"--coverage-good",
good,
]
)
assert code == 1
assert message in capsys.readouterr().out
def test_unreadable_metrics_fail(self, tmp_path, capsys):
bad = tmp_path / "m.json"
bad.write_text('{"files": 1}')
code = badges.main(["--metrics", str(bad), "--output-dir", str(tmp_path / "o")])
assert code == 1
assert "cannot be read" in capsys.readouterr().out
def test_no_code_files_is_a_notice_and_badges_that_show_zero(self, tmp_path, capsys):
out = tmp_path / "out"
metrics = metrics_file(tmp_path, files=0, lines=0)
assert badges.main(["--metrics", str(metrics), "--output-dir", str(out)]) == 0
assert "::notice::No code files were found" in capsys.readouterr().out
assert 'aria-label="code files: 0"' in (out / "code-files.svg").read_text()
+13 -3
View File
@@ -98,7 +98,17 @@ def _write_sources(root: Path) -> None:
"## 📄 License\nMIT.\n", "## 📄 License\nMIT.\n",
encoding="utf-8", encoding="utf-8",
) )
for name in ("onboarding", "credentials", "troubleshooting", "quality-checks", "upgrading"): guide_names = (
"onboarding",
"credentials",
"troubleshooting",
"quality-checks",
"quality-checks-cpp",
"quality-checks-csharp",
"code-metrics",
"upgrading",
)
for name in guide_names:
(root / "guides" / f"{name}.md").write_text( (root / "guides" / f"{name}.md").write_text(
f"# {name.title()}\nBody.\n", encoding="utf-8" f"# {name.title()}\nBody.\n", encoding="utf-8"
) )
@@ -262,7 +272,7 @@ def _shape(text: str) -> dict:
@pytest.mark.parametrize("code", [code for code, _label in build_wiki.LANGUAGES]) @pytest.mark.parametrize("code", [code for code, _label in build_wiki.LANGUAGES])
def test_committed_translations_follow_the_english_structure(page, code): def test_committed_translations_follow_the_english_structure(page, code):
"""A translation has the same code blocks, table rows and heading levels.""" """A translation has the same code blocks, table rows and heading levels."""
wiki = REPO_ROOT / "wiki" wiki = REPO_ROOT / "docs" / "wiki"
english = _shape((wiki / f"{page}.md").read_text(encoding="utf-8")) english = _shape((wiki / f"{page}.md").read_text(encoding="utf-8"))
translated = _shape((wiki / f"{page}.{code}.md").read_text(encoding="utf-8")) translated = _shape((wiki / f"{page}.{code}.md").read_text(encoding="utf-8"))
assert translated == english assert translated == english
@@ -297,7 +307,7 @@ class TestSidebar:
def test_home_lists_every_workflow_file_with_a_diagram(): def test_home_lists_every_workflow_file_with_a_diagram():
"""The Home page names each workflow file of the repository and draws it.""" """The Home page names each workflow file of the repository and draws it."""
home = (REPO_ROOT / "wiki" / "Home.md").read_text(encoding="utf-8") home = (REPO_ROOT / "docs" / "wiki" / "Home.md").read_text(encoding="utf-8")
files = sorted( files = sorted(
path.name path.name
for folder in (".gitea/scoped_workflows", ".gitea/workflows") for folder in (".gitea/scoped_workflows", ".gitea/workflows")
+334
View File
@@ -0,0 +1,334 @@
"""Offline tests for the reusable code-metrics workflow and tools/code-metrics.sh (ADR-0010).
The script steps run in a throwaway git repository whose `origin` is a bare
repository on disk, so no network and no instance is needed.
"""
import os
import re
import shutil
import subprocess
import sys
from pathlib import Path
import pytest
import yaml
from workflow_source import REPO_ROOT
WORKFLOW = REPO_ROOT / ".gitea" / "workflows" / "code-metrics.yml"
SCRIPT = REPO_ROOT / "tools" / "code-metrics.sh"
BASH = shutil.which("bash")
GIT = shutil.which("git")
pytestmark = pytest.mark.skipif(
BASH is None or GIT is None, reason="bash and git are needed to run the workflow steps"
)
COVERAGE = '<?xml version="1.0" ?>\n<coverage lines-covered="87" lines-valid="100"><packages/></coverage>\n'
@pytest.fixture(scope="module")
def document() -> dict:
return yaml.safe_load(WORKFLOW.read_text(encoding="utf-8"))
class TestDefinition:
def test_is_a_reusable_workflow_with_the_contract_inputs(self, document):
inputs = document[True]["workflow_call"]["inputs"] # PyYAML reads `on` as True
assert {name: item["default"] for name, item in inputs.items()} == {
"working-directory": ".",
"exclude": "",
"coverage-report": "",
"coverage-artifact": "",
"coverage-warn": "60",
"coverage-good": "80",
"badges-branch": "badges",
"tools-ref": "v1",
}
def test_asks_for_contents_write_and_nothing_else_and_needs_no_secret(self, document):
job = document["jobs"]["code-metrics"]
assert job["permissions"] == {"contents": "write"}
assert "secrets" not in WORKFLOW.read_text(encoding="utf-8").replace("needs no secret", "")
def test_inputs_reach_the_scripts_only_through_the_environment(self, document):
job = document["jobs"]["code-metrics"]
for step in job["steps"]:
assert "${{" not in step.get("run", ""), f"{step.get('name')} interpolates an expression"
assert set(job["env"]) == {
"WORKING_DIRECTORY",
"EXCLUDE",
"COVERAGE_REPORT",
"COVERAGE_ARTIFACT",
"COVERAGE_WARN",
"COVERAGE_GOOD",
"BADGES_BRANCH",
}
def test_the_workflow_embeds_no_script_and_runs_the_file(self, document):
steps = document["jobs"]["code-metrics"]["steps"]
assert [step["run"] for step in steps if "run" in step] == [
"bash shared-scripts/tools/code-metrics.sh validate",
"bash shared-scripts/tools/code-metrics.sh measure",
"bash shared-scripts/tools/code-metrics.sh publish",
]
def test_the_scripts_are_fetched_at_the_tools_ref_input(self, document):
steps = document["jobs"]["code-metrics"]["steps"]
fetch = next(step for step in steps if step.get("name") == "Fetch the shared scripts")
assert fetch["with"] == {
"repository": "TirSystem/github-action",
"ref": "${{ inputs.tools-ref }}",
"path": "shared-scripts",
}
def test_the_script_parses(self):
result = subprocess.run([BASH, "-n", str(SCRIPT)], text=True, capture_output=True)
assert result.returncode == 0, result.stderr
def test_an_unknown_command_is_refused(self):
result = subprocess.run([BASH, str(SCRIPT), "bogus"], text=True, capture_output=True)
assert result.returncode == 2
def git(*args: str, cwd: Path) -> str:
result = subprocess.run(
[GIT, "-c", "user.name=t", "-c", "user.email=t@t", *args],
cwd=cwd,
text=True,
capture_output=True,
check=True,
)
return result.stdout.strip()
@pytest.fixture()
def python3_bin(tmp_path) -> Path:
"""A directory with a `python3` that runs the interpreter of the tests."""
directory = tmp_path / "pybin"
directory.mkdir()
shim = directory / "python3"
shim.write_text(f'#!/bin/sh\nexec "{Path(sys.executable).as_posix()}" "$@"\n', newline="\n")
shim.chmod(0o755)
return directory
@pytest.fixture()
def remote(tmp_path) -> Path:
path = tmp_path / "remote.git"
subprocess.run([GIT, "init", "-q", "--bare", str(path)], check=True)
return path
@pytest.fixture()
def repo(tmp_path, remote) -> Path:
root = tmp_path / "repo"
root.mkdir()
git("init", "-q", "-b", "main", cwd=root)
git("remote", "add", "origin", str(remote), cwd=root)
(root / "a.py").write_text("x = 1\n# c\n\n", newline="\n")
(root / "run.sh").write_text("echo hi\n", newline="\n")
git("add", "-A", cwd=root)
git("commit", "-q", "-m", "init", cwd=root)
return root
def run(command: str, repo: Path, tmp_path: Path, python3_bin: Path, **env: str):
environment = dict(os.environ)
environment.update(
{
"WORKING_DIRECTORY": ".",
"EXCLUDE": "",
"COVERAGE_REPORT": "",
"COVERAGE_ARTIFACT": "",
"COVERAGE_WARN": "60",
"COVERAGE_GOOD": "80",
"BADGES_BRANCH": "badges",
"BADGES_OUT": str(tmp_path / "out"),
"PATH": f"{python3_bin.as_posix()}{os.pathsep}{environment['PATH']}",
}
)
environment.update(env)
return subprocess.run(
[BASH, str(SCRIPT), command], cwd=repo, env=environment, text=True, capture_output=True
)
def branch_files(remote: Path, branch: str = "badges") -> list[str]:
out = subprocess.run(
[GIT, "--git-dir", str(remote), "ls-tree", "-r", "--name-only", branch],
text=True,
capture_output=True,
)
return out.stdout.split()
def commit_count(remote: Path, branch: str = "badges") -> int:
out = subprocess.run(
[GIT, "--git-dir", str(remote), "rev-list", "--count", branch], text=True, capture_output=True
)
return int(out.stdout.strip() or 0)
def measure_and_publish(repo, tmp_path, python3_bin, **env):
measured = run("measure", repo, tmp_path, python3_bin, **env)
assert measured.returncode == 0, measured.stdout + measured.stderr
return run("publish", repo, tmp_path, python3_bin, **env)
class TestValidate:
def test_accepts_the_defaults(self, repo, tmp_path, python3_bin):
result = run("validate", repo, tmp_path, python3_bin)
assert result.returncode == 0, result.stdout
assert "badges go to the branch badges" in result.stdout
@pytest.mark.parametrize(
("name", "value", "message"),
[
("WORKING_DIRECTORY", "/etc", "working-directory must be a relative path"),
("WORKING_DIRECTORY", "../x", "working-directory must be a relative path"),
("WORKING_DIRECTORY", "nope", "working-directory 'nope' does not exist"),
("COVERAGE_REPORT", "/tmp/c.xml", "coverage-report must be a relative path"),
("COVERAGE_ARTIFACT", "cov", "coverage-artifact 'cov' needs coverage-report"),
("BADGES_BRANCH", "main", "must not be a default branch"),
("BADGES_BRANCH", "master", "must not be a default branch"),
("BADGES_BRANCH", "", "must not be a default branch"),
("BADGES_BRANCH", "a b", "is not a valid branch name"),
("BADGES_BRANCH", "x..y", "is not a valid branch name"),
],
)
def test_refuses_an_invalid_input_and_names_it(
self, repo, tmp_path, python3_bin, name, value, message
):
result = run("validate", repo, tmp_path, python3_bin, **{name: value})
assert result.returncode == 1
assert f"::error::[configuration] " in result.stdout
assert message in result.stdout
class TestPublish:
def test_creates_the_branch_with_the_badges_and_the_numbers(self, repo, remote, tmp_path, python3_bin):
result = measure_and_publish(repo, tmp_path, python3_bin)
assert result.returncode == 0, result.stdout + result.stderr
assert sorted(branch_files(remote)) == ["code-files.svg", "code-lines.svg", "code-metrics.json"]
shown = subprocess.run(
[GIT, "--git-dir", str(remote), "show", "badges:code-lines.svg"], text=True, capture_output=True
).stdout
assert 'aria-label="lines of code: 2"' in shown # a.py has one code line, run.sh one
message = subprocess.run(
[GIT, "--git-dir", str(remote), "log", "-1", "--format=%s", "badges"],
text=True,
capture_output=True,
).stdout.strip()
assert message == "Update code metrics badges [skip ci]"
def test_the_branch_of_the_caller_is_not_changed(self, repo, remote, tmp_path, python3_bin):
before = git("status", "--porcelain", cwd=repo)
measure_and_publish(repo, tmp_path, python3_bin)
assert git("status", "--porcelain", cwd=repo) == before
assert git("branch", "--show-current", cwd=repo) == "main"
def test_an_unchanged_run_commits_nothing(self, repo, remote, tmp_path, python3_bin):
measure_and_publish(repo, tmp_path, python3_bin)
second = measure_and_publish(repo, tmp_path, python3_bin)
assert second.returncode == 0
assert "Badges are current; nothing to commit." in second.stdout
assert commit_count(remote) == 1
def test_a_changed_number_is_one_commit_that_changes_only_badge_files(
self, repo, remote, tmp_path, python3_bin
):
measure_and_publish(repo, tmp_path, python3_bin)
(repo / "b.py").write_text("y = 2\nz = 3\n", newline="\n")
git("add", "-A", cwd=repo)
git("commit", "-q", "-m", "more", cwd=repo)
result = measure_and_publish(repo, tmp_path, python3_bin)
assert result.returncode == 0, result.stdout
assert commit_count(remote) == 2
changed = subprocess.run(
[GIT, "--git-dir", str(remote), "show", "--name-only", "--format=", "badges"],
text=True,
capture_output=True,
).stdout.split()
assert sorted(changed) == ["code-files.svg", "code-lines.svg", "code-metrics.json"]
def test_a_coverage_report_adds_the_coverage_badge(self, repo, remote, tmp_path, python3_bin):
(repo / "cov.xml").write_text(COVERAGE, newline="\n")
result = measure_and_publish(repo, tmp_path, python3_bin, COVERAGE_REPORT="cov.xml")
assert result.returncode == 0, result.stdout
assert "coverage.svg" in branch_files(remote)
def test_a_report_from_an_artifact_is_read_below_the_artifact_directory(
self, repo, remote, tmp_path, python3_bin
):
artifact = repo / "coverage-artifact"
artifact.mkdir()
(artifact / "cov.xml").write_text(COVERAGE, newline="\n")
result = measure_and_publish(
repo, tmp_path, python3_bin, COVERAGE_ARTIFACT="coverage", COVERAGE_REPORT="cov.xml"
)
assert result.returncode == 0, result.stdout
assert "coverage.svg" in branch_files(remote)
def test_a_report_missing_from_the_artifact_names_the_path(self, repo, tmp_path, python3_bin):
result = run(
"measure",
repo,
tmp_path,
python3_bin,
COVERAGE_ARTIFACT="coverage",
COVERAGE_REPORT="cov.xml",
)
assert result.returncode == 1
assert "coverage-report 'coverage-artifact/cov.xml' does not exist" in result.stdout
def test_a_missing_report_fails_names_the_file_and_changes_nothing(
self, repo, remote, tmp_path, python3_bin
):
result = run("measure", repo, tmp_path, python3_bin, COVERAGE_REPORT="nope.xml")
assert result.returncode == 1
assert "::error::[configuration] coverage-report 'nope.xml' does not exist." in result.stdout
assert branch_files(remote) == []
def test_the_shared_scripts_directory_is_not_counted(self, repo, remote, tmp_path, python3_bin):
shared = repo / "shared-scripts" / "tools"
shared.mkdir(parents=True)
(shared / "big.py").write_text("a = 1\n" * 50, newline="\n")
measure_and_publish(repo, tmp_path, python3_bin)
shown = subprocess.run(
[GIT, "--git-dir", str(remote), "show", "badges:code-files.svg"], text=True, capture_output=True
).stdout
assert 'aria-label="code files: 2"' in shown
def test_a_branch_that_holds_other_files_is_refused(self, repo, remote, tmp_path, python3_bin):
git("push", "-q", "origin", "main:docs", cwd=repo)
result = measure_and_publish(repo, tmp_path, python3_bin, BADGES_BRANCH="docs")
assert result.returncode == 1
assert "it is not a badges branch" in result.stdout
def test_publish_without_measure_is_refused(self, repo, tmp_path, python3_bin):
result = run("publish", repo, tmp_path, python3_bin)
assert result.returncode == 1
assert "run measure first" in result.stdout
def test_a_rejected_push_is_retried_once_after_a_fetch(self, repo, remote, tmp_path, python3_bin):
marker = tmp_path / "rejected-once"
hook = remote / "hooks" / "pre-receive"
hook.write_text(
f'#!/bin/sh\nif [ ! -e "{marker.as_posix()}" ]; then touch "{marker.as_posix()}"; '
"echo rejected once >&2; exit 1; fi\nexit 0\n",
newline="\n",
)
hook.chmod(0o755)
result = measure_and_publish(repo, tmp_path, python3_bin)
assert result.returncode == 0, result.stdout + result.stderr
assert marker.exists()
def test_two_rejected_pushes_fail(self, repo, remote, tmp_path, python3_bin):
hook = remote / "hooks" / "pre-receive"
hook.write_text("#!/bin/sh\necho rejected >&2\nexit 1\n", newline="\n")
hook.chmod(0o755)
result = measure_and_publish(repo, tmp_path, python3_bin)
assert result.returncode == 1
assert re.search(r"push to 'badges' failed twice", result.stdout)
+317
View File
@@ -0,0 +1,317 @@
"""Offline tests for tools/count_loc.py (ADR-0010): hand-counted fixtures per language."""
import importlib.util
import json
from pathlib import Path
import pytest
REPO_ROOT = Path(__file__).resolve().parent.parent
_SPEC = importlib.util.spec_from_file_location(
"count_loc", REPO_ROOT / "tools" / "count_loc.py"
)
assert _SPEC is not None and _SPEC.loader is not None
count_loc = importlib.util.module_from_spec(_SPEC)
_SPEC.loader.exec_module(count_loc)
LANGUAGE = {language.name: language for language in count_loc.LANGUAGES}
# name -> (source, code, comment, blank), counted by hand.
FIXTURES = {
"Python": (
"# a comment\n"
"x = 1 # trailing comment\n"
"\n"
's = "a # not a comment"\n'
"t = 'it''s # still a string'\n"
'"""docstring\n'
"# inside a string\n"
"\n"
'"""\n'
"y = 2", # no final newline
7,
1,
2,
),
"Shell": (
"#!/usr/bin/env bash\n"
"# comment\n"
'echo "$#" ${#args[@]} a#b\n'
"\n"
" # indented comment\n"
"echo 'it # stays' # but this is a comment\n"
"ls | wc -l;# after a semicolon\n",
3,
3,
1,
),
"PowerShell": (
'<# block\ncomment #>\nWrite-Host "a # b"\n# line\n$x = 1 # trailing\n',
2,
3,
0,
),
"C": (
"/* header\n"
" * text\n"
" */\n"
"#include <stdio.h>\n"
"\n"
"int main(void) { /* inline */ return 0; }\n"
"char quote = '\"'; // a double quote in a char\n"
'const char *url = "http://example.com";\n'
"// done\n",
4,
4,
1,
),
"C++": (
'int a = 0; // one\n/* block */\nauto s = "/* not a comment */";\n',
2,
1,
0,
),
"C#": (
"// line\n"
"using System;\n"
"/// <summary>doc</summary>\n"
'class A { string s = "// no"; }\n',
2,
2,
0,
),
"Java": (
"/** doc */\nclass A {\n // c\n int x; /* t */\n}\n",
3,
2,
0,
),
"JavaScript": (
"const a = `line one\n"
"// in a template\n"
"line three`;\n"
"// real comment\n"
"const b = 'x // y';\n"
"/* a */ /* b */\n",
4,
2,
0,
),
"TypeScript": (
"let n: number = 1; // c\n\n/*\n * block\n */\n",
1,
3,
1,
),
"Go": (
"package main\n// c\nvar r = '/'\nvar raw = `// not a comment\n`\n",
4,
1,
0,
),
"Rust": (
"/* outer /* nested */ still a comment */\n"
"fn f<'a>(x: &'a str) -> &'a str { x } // lifetimes\n"
"let c = '\\'';\n"
"// end\n",
2,
2,
0,
),
"SQL": (
"-- query\n"
"SELECT '-- not a comment' FROM t; -- but this is\n"
"/* a\n"
" block */\n"
"\n",
1,
3,
1,
),
}
@pytest.mark.parametrize("name", sorted(FIXTURES))
def test_fixture_counts_equal_the_hand_count(name):
source, code, comment, blank = FIXTURES[name]
assert count_loc.classify_text(source, LANGUAGE[name]) == (code, comment, blank)
def test_every_language_has_a_fixture():
assert set(FIXTURES) == set(LANGUAGE)
class TestClassify:
def test_empty_text_has_no_lines(self):
assert count_loc.classify_text("", LANGUAGE["Python"]) == (0, 0, 0)
def test_windows_line_endings_are_one_line_each(self):
assert count_loc.classify_text("a = 1\r\n# c\r\n\r\n", LANGUAGE["Python"]) == (
1,
1,
1,
)
def test_blank_line_inside_a_block_comment_is_blank(self):
assert count_loc.classify_text("/*\n\n*/\n", LANGUAGE["C"]) == (0, 2, 1)
def test_code_after_a_closing_block_comment_is_code(self):
assert count_loc.classify_text("/* a\n b */ x();\n", LANGUAGE["C"]) == (1, 1, 0)
def test_unterminated_string_ends_at_end_of_line(self):
assert count_loc.classify_text('s = "open // x\n// c\n', LANGUAGE["C"]) == (
1,
1,
0,
)
def test_unterminated_backtick_string_continues(self):
assert count_loc.classify_text("a = `x\n// y\n", LANGUAGE["JavaScript"]) == (
2,
0,
0,
)
def test_escaped_quote_does_not_end_the_string(self):
assert count_loc.classify_text('s = "a \\" // b";\n', LANGUAGE["C"]) == (
1,
0,
0,
)
class TestPatterns:
def test_star_stops_at_a_slash_and_double_star_does_not(self):
excludes = count_loc.Excludes(["src/*.py", "docs/**"])
assert excludes.matches("a.py", "src/a.py")
assert not excludes.matches("a.py", "src/sub/a.py")
assert excludes.matches("x.py", "docs/a/b/x.py")
def test_name_pattern_matches_anywhere(self):
excludes = count_loc.Excludes(["gen_*", "?.py"])
assert excludes.matches("gen_a.py", "deep/er/gen_a.py")
assert excludes.matches("a.py", "x/a.py")
assert not excludes.matches("ab.py", "x/ab.py")
def test_empty_patterns_are_ignored(self):
assert not count_loc.Excludes(["", " / "]).matches("a.py", "a.py")
def write(root: Path, name: str, content: str | bytes) -> None:
path = root / name
path.parent.mkdir(parents=True, exist_ok=True)
if isinstance(content, bytes):
path.write_bytes(content)
else:
path.write_text(content, encoding="utf-8")
class TestCount:
def test_counts_files_and_lines_per_language(self, tmp_path):
write(tmp_path, "a.py", "x = 1\n# c\n\n")
write(tmp_path, "pkg/b.py", "y = 2\n")
write(tmp_path, "run.sh", "echo hi\n")
write(tmp_path, "README.md", "# not code\n")
result = count_loc.count(tmp_path)
assert result == {
"files": 3,
"lines": 3,
"comment": 1,
"blank": 1,
"languages": {
"Python": {"files": 2, "lines": 2, "comment": 1, "blank": 1},
"Shell": {"files": 1, "lines": 1, "comment": 0, "blank": 0},
},
}
def test_default_excludes_hidden_vendored_and_shared_script_directories(
self, tmp_path
):
write(tmp_path, "keep.py", "a = 1\n")
for directory in (
".git",
".venv",
"node_modules",
"vendor",
"shared-scripts",
"build",
):
write(tmp_path, f"{directory}/skip.py", "b = 2\n")
assert count_loc.count(tmp_path)["files"] == 1
def test_exclude_patterns_add_to_the_defaults(self, tmp_path):
write(tmp_path, "keep.py", "a = 1\n")
write(tmp_path, "gen/out.py", "b = 2\n")
write(tmp_path, "src/gen_x.py", "c = 3\n")
write(tmp_path, "src/keep2.py", "d = 4\n")
result = count_loc.count(tmp_path, ["gen", "gen_*"])
assert result["files"] == 2
def test_binary_file_with_a_code_extension_is_not_counted(self, tmp_path):
write(tmp_path, "blob.py", b"a = 1\n\x00\x01")
assert count_loc.count(tmp_path)["files"] == 0
def test_invalid_utf8_and_a_byte_order_mark_are_read(self, tmp_path):
write(tmp_path, "a.py", b"\xef\xbb\xbfx = 1\n\xff\xfe = 2\n")
assert count_loc.count(tmp_path)["lines"] == 2
def test_empty_code_file_is_a_file_with_no_lines(self, tmp_path):
write(tmp_path, "empty.py", "")
result = count_loc.count(tmp_path)
assert (result["files"], result["lines"]) == (1, 0)
def test_directory_without_code_files_counts_zero(self, tmp_path):
write(tmp_path, "notes.txt", "hello\n")
result = count_loc.count(tmp_path)
assert result == {
"files": 0,
"lines": 0,
"comment": 0,
"blank": 0,
"languages": {},
}
def test_symbolic_link_is_not_followed(self, tmp_path):
real = tmp_path / "real"
write(real, "a.py", "x = 1\n")
link = tmp_path / "link.py"
try:
link.symlink_to(real / "a.py")
except (OSError, NotImplementedError):
pytest.skip("symbolic links are not available here")
assert count_loc.count(real)["files"] == 1
assert count_loc.count(tmp_path)["files"] == 1 # real/a.py only, not link.py
class TestMain:
def test_prints_json_to_standard_output(self, tmp_path, capsys):
write(tmp_path, "a.py", "x = 1\n")
assert count_loc.main(["--directory", str(tmp_path)]) == 0
out = json.loads(capsys.readouterr().out)
assert out["files"] == 1 and out["lines"] == 1
def test_writes_the_output_file_with_lf_endings(self, tmp_path):
write(tmp_path, "src/a.py", "x = 1\n")
target = tmp_path / "result.json"
assert (
count_loc.main(
["--directory", str(tmp_path / "src"), "--output", str(target)]
)
== 0
)
data = target.read_bytes()
assert b"\r" not in data and json.loads(data)["files"] == 1
def test_exclude_option_is_a_comma_list(self, tmp_path, capsys):
write(tmp_path, "a.py", "x = 1\n")
write(tmp_path, "b.py", "y = 1\n")
write(tmp_path, "c.py", "z = 1\n")
assert (
count_loc.main(["--directory", str(tmp_path), "--exclude", "a.py, b.py"])
== 0
)
assert json.loads(capsys.readouterr().out)["files"] == 1
def test_missing_directory_is_a_configuration_error(self, tmp_path, capsys):
assert count_loc.main(["--directory", str(tmp_path / "nope")]) == 1
assert "::error::[configuration]" in capsys.readouterr().out
+134 -107
View File
@@ -1,4 +1,4 @@
"""Offline tests for tools/publish_wiki.py and the scoped publish-wiki workflow.""" """Offline tests for tools/publish_wiki.py and the publish-wiki workflows."""
import base64 import base64
import io import io
@@ -7,7 +7,9 @@ import urllib.error
import pytest import pytest
import yaml import yaml
from workflow_source import PUBLISH_WORKFLOW, REPO_ROOT, load_publish_module from workflow_source import REPO_ROOT, load_publish_module
REUSABLE_WORKFLOW = REPO_ROOT / ".gitea" / "workflows" / "publish-wiki-pages.yml"
API = "https://git.example.test/api/v1" API = "https://git.example.test/api/v1"
REPO_URL = f"{API}/repos/TirSystem/repo" REPO_URL = f"{API}/repos/TirSystem/repo"
@@ -23,22 +25,6 @@ def b64(text: str) -> str:
return base64.b64encode(text.encode()).decode() return base64.b64encode(text.encode()).decode()
class TestParseMarker:
@pytest.mark.parametrize(
("text", "expected"),
[("", False), (" \n", False), (None, False), ("prune", True), (" prune\n", True)],
)
def test_reads_the_empty_marker_and_the_prune_word(self, publish_wiki, text, expected):
assert publish_wiki.parse_marker(text) is expected
@pytest.mark.parametrize("text", ["Prune", "prune: true", "delete", "prune\nmore"])
def test_rejects_anything_else(self, publish_wiki, text):
with pytest.raises(publish_wiki.WorkflowError) as caught:
publish_wiki.parse_marker(text)
assert caught.value.category == "configuration"
assert "prune" in str(caught.value)
class TestParseGiteaToken: class TestParseGiteaToken:
def test_returns_the_stripped_token(self, publish_wiki): def test_returns_the_stripped_token(self, publish_wiki):
assert publish_wiki.parse_gitea_token(" abc \n") == "abc" assert publish_wiki.parse_gitea_token(" abc \n") == "abc"
@@ -52,17 +38,15 @@ class TestParseGiteaToken:
class TestReadPages: class TestReadPages:
def test_reads_markdown_files_by_stem_and_ignores_the_marker(self, publish_wiki, tmp_path): def test_reads_markdown_files_by_stem_and_ignores_other_files(self, publish_wiki, tmp_path):
(tmp_path / "Home.md").write_bytes(b"home") (tmp_path / "Home.md").write_bytes(b"home")
(tmp_path / "Home.da.md").write_bytes(b"hjem") (tmp_path / "Home.da.md").write_bytes(b"hjem")
(tmp_path / ".publish").write_bytes(b"prune")
(tmp_path / "Notes.txt").write_bytes(b"ignored") (tmp_path / "Notes.txt").write_bytes(b"ignored")
assert publish_wiki.read_pages(tmp_path) == {"Home": b"home", "Home.da": b"hjem"} assert publish_wiki.read_pages(tmp_path) == {"Home": b"home", "Home.da": b"hjem"}
def test_rejects_a_directory_without_pages_so_the_wiki_is_never_wiped( def test_rejects_a_directory_without_pages_so_the_wiki_is_never_wiped(
self, publish_wiki, tmp_path self, publish_wiki, tmp_path
): ):
(tmp_path / ".publish").write_text("prune", encoding="utf-8")
with pytest.raises(publish_wiki.WorkflowError, match="no Markdown pages"): with pytest.raises(publish_wiki.WorkflowError, match="no Markdown pages"):
publish_wiki.read_pages(tmp_path) publish_wiki.read_pages(tmp_path)
@@ -141,7 +125,7 @@ def environment(monkeypatch, tmp_path):
wiki_dir.mkdir() wiki_dir.mkdir()
(wiki_dir / "Home.md").write_text("home v2", encoding="utf-8") (wiki_dir / "Home.md").write_text("home v2", encoding="utf-8")
(wiki_dir / "Credentials.md").write_text("credentials", encoding="utf-8") (wiki_dir / "Credentials.md").write_text("credentials", encoding="utf-8")
(wiki_dir / ".publish").write_text("prune\n", encoding="utf-8") monkeypatch.setenv("WIKI_PRUNE", "true")
monkeypatch.setenv("WIKI_DIR", str(wiki_dir)) monkeypatch.setenv("WIKI_DIR", str(wiki_dir))
monkeypatch.setenv("GITEA_API_URL", f"{API}/") monkeypatch.setenv("GITEA_API_URL", f"{API}/")
monkeypatch.setenv("SOURCE_REPOSITORY", "TirSystem/repo") monkeypatch.setenv("SOURCE_REPOSITORY", "TirSystem/repo")
@@ -154,58 +138,6 @@ def run_main(publish_wiki, gitea):
return publish_wiki.main() return publish_wiki.main()
class TestOptIn:
def test_a_repository_without_the_marker_is_never_written_to(
self, publish_wiki, environment, capsys
):
(environment / ".publish").unlink()
gitea = FakeGitea(publish_wiki, wiki={"Old": b64("hand written")})
assert run_main(publish_wiki, gitea) == 0
assert gitea.calls == []
assert "wiki publishing is not enabled" in capsys.readouterr().out
assert gitea.wiki == {"Old": b64("hand written")}
def test_a_repository_without_the_marker_does_not_need_the_secret(
self, publish_wiki, environment, monkeypatch
):
(environment / ".publish").unlink()
monkeypatch.setenv("WIKI_CREDENTIALS", "")
assert run_main(publish_wiki, FakeGitea(publish_wiki)) == 0
def test_a_repository_without_a_wiki_directory_is_skipped(
self, publish_wiki, monkeypatch, tmp_path
):
monkeypatch.setenv("WIKI_DIR", str(tmp_path / "missing"))
monkeypatch.setenv("SOURCE_REPOSITORY", "TirSystem/repo")
gitea = FakeGitea(publish_wiki)
assert run_main(publish_wiki, gitea) == 0
assert gitea.calls == []
def test_an_opted_in_repository_without_the_secret_fails_before_any_request(
self, publish_wiki, environment, monkeypatch
):
monkeypatch.setenv("WIKI_CREDENTIALS", "")
gitea = FakeGitea(publish_wiki)
with pytest.raises(publish_wiki.WorkflowError, match="TOKEN_FOR_GITEA_WIKI"):
run_main(publish_wiki, gitea)
assert gitea.calls == []
def test_an_unknown_marker_content_is_refused_before_any_request(
self, publish_wiki, environment
):
(environment / ".publish").write_text("mirror everything", encoding="utf-8")
gitea = FakeGitea(publish_wiki)
with pytest.raises(publish_wiki.WorkflowError, match="prune"):
run_main(publish_wiki, gitea)
assert gitea.calls == []
class TestPublish: class TestPublish:
def test_prune_creates_updates_and_deletes_to_match_the_source( def test_prune_creates_updates_and_deletes_to_match_the_source(
self, publish_wiki, environment, capsys self, publish_wiki, environment, capsys
@@ -225,9 +157,9 @@ class TestPublish:
assert "0 created, 1 updated, 1 deleted" in capsys.readouterr().out assert "0 created, 1 updated, 1 deleted" in capsys.readouterr().out
def test_without_prune_pages_outside_the_source_are_kept( def test_without_prune_pages_outside_the_source_are_kept(
self, publish_wiki, environment, capsys self, publish_wiki, environment, capsys, monkeypatch
): ):
(environment / ".publish").write_text("", encoding="utf-8") monkeypatch.setenv("WIKI_PRUNE", "false")
gitea = FakeGitea( gitea = FakeGitea(
publish_wiki, wiki={"Home": b64("home v1"), "Old": b64("hand written")} publish_wiki, wiki={"Home": b64("home v1"), "Old": b64("hand written")}
) )
@@ -300,11 +232,11 @@ class TestPageIdentity:
"""A wiki that already has pages named with spaces, as Gitea stores them.""" """A wiki that already has pages named with spaces, as Gitea stores them."""
def test_a_file_named_with_a_space_updates_the_existing_page( def test_a_file_named_with_a_space_updates_the_existing_page(
self, publish_wiki, environment self, publish_wiki, environment, monkeypatch
): ):
(environment / "Home.md").unlink() (environment / "Home.md").unlink()
(environment / "Credentials.md").unlink() (environment / "Credentials.md").unlink()
(environment / ".publish").write_text("", encoding="utf-8") monkeypatch.setenv("WIKI_PRUNE", "false")
(environment / "Home da.md").write_text("hjem v2", encoding="utf-8") (environment / "Home da.md").write_text("hjem v2", encoding="utf-8")
gitea = FakeGitea(publish_wiki, wiki={"Home-da": b64("hjem v1")}) gitea = FakeGitea(publish_wiki, wiki={"Home-da": b64("hjem v1")})
@@ -450,42 +382,137 @@ class TestRequestJson:
assert caught.value.category == "api" assert caught.value.category == "api"
class TestWorkflow: class TestExplicitPrune:
"""The reusable workflow sets WIKI_PRUNE: the caller is the opt-in (MIL-008)."""
@pytest.fixture()
def explicit(self, environment, monkeypatch):
monkeypatch.setenv("WIKI_PRUNE", "false")
return environment
@pytest.mark.parametrize(
("text", "expected"), [("true", True), (" TRUE\n", True), ("false", False)]
)
def test_reads_true_and_false(self, publish_wiki, text, expected):
assert publish_wiki.parse_prune_setting(text) is expected
@pytest.mark.parametrize("text", ["", "yes", "prune", None])
def test_rejects_anything_else(self, publish_wiki, text):
with pytest.raises(publish_wiki.WorkflowError) as caught:
publish_wiki.parse_prune_setting(text)
assert caught.value.category == "configuration"
def test_publishes_without_a_marker_and_keeps_other_pages(
self, publish_wiki, explicit, capsys
):
gitea = FakeGitea(publish_wiki, wiki={"Old": b64("hand written")})
assert run_main(publish_wiki, gitea) == 0
assert set(gitea.wiki) == {"Old", "Home", "Credentials"}
assert "set the prune input to true" in capsys.readouterr().out
def test_prune_true_deletes_pages_outside_the_folder(
self, publish_wiki, explicit, monkeypatch
):
monkeypatch.setenv("WIKI_PRUNE", "true")
gitea = FakeGitea(publish_wiki, wiki={"Old": b64("stale")})
assert run_main(publish_wiki, gitea) == 0
assert set(gitea.wiki) == {"Home", "Credentials"}
def test_a_missing_folder_is_a_configuration_error_before_any_request(
self, publish_wiki, explicit, monkeypatch, tmp_path
):
monkeypatch.setenv("WIKI_DIR", str(tmp_path / "docs" / "wiki"))
gitea = FakeGitea(publish_wiki)
with pytest.raises(publish_wiki.WorkflowError, match="wiki_dir") as caught:
run_main(publish_wiki, gitea)
assert caught.value.category == "configuration"
assert gitea.calls == []
def test_an_empty_folder_never_deletes_pages_even_with_prune(
self, publish_wiki, explicit, monkeypatch
):
monkeypatch.setenv("WIKI_PRUNE", "true")
for page in explicit.glob("*.md"):
page.unlink()
gitea = FakeGitea(publish_wiki, wiki={"Home": b64("home")})
with pytest.raises(publish_wiki.WorkflowError) as caught:
run_main(publish_wiki, gitea)
assert caught.value.category == "configuration"
assert gitea.writes == []
def test_a_missing_secret_fails_before_any_request(
self, publish_wiki, explicit, monkeypatch
):
monkeypatch.setenv("WIKI_CREDENTIALS", "")
gitea = FakeGitea(publish_wiki)
with pytest.raises(publish_wiki.WorkflowError, match="TOKEN_FOR_GITEA_WIKI"):
run_main(publish_wiki, gitea)
assert gitea.calls == []
class TestReusableWorkflow:
@pytest.fixture() @pytest.fixture()
def document(self): def document(self):
return yaml.safe_load(PUBLISH_WORKFLOW.read_text(encoding="utf-8")) return yaml.safe_load(REUSABLE_WORKFLOW.read_text(encoding="utf-8"))
def test_runs_only_on_main_when_wiki_changes_or_by_hand(self, document): def test_is_called_and_has_no_other_trigger(self, document):
triggers = document[True] # PyYAML reads the key `on` as True assert list(document[True]) == ["workflow_call"]
assert triggers["push"]["branches"] == ["main"]
assert triggers["push"]["paths"] == ["wiki/**"] def test_the_folder_defaults_to_docs_wiki_and_nothing_is_pruned_by_default(self, document):
assert "workflow_dispatch" in triggers inputs = document[True]["workflow_call"]["inputs"]
assert inputs["wiki_dir"]["default"] == "docs/wiki"
assert inputs["prune"]["default"] == "false"
def test_the_token_is_a_declared_secret_without_a_fallback(self, document):
text = REUSABLE_WORKFLOW.read_text(encoding="utf-8")
assert document[True]["workflow_call"]["secrets"]["TOKEN_FOR_GITEA_WIKI"]["required"]
assert "secrets.TOKEN_FOR_GITEA }}" not in text
assert "github.token" not in text
def test_job_has_read_only_permissions(self, document): def test_job_has_read_only_permissions(self, document):
assert document["jobs"]["publish-wiki"]["permissions"] == {"contents": "read"} assert document["jobs"]["publish-wiki"]["permissions"] == {"contents": "read"}
def test_secret_is_passed_only_through_the_environment_and_has_no_fallback(self, document): def test_the_inputs_reach_the_script_through_the_environment(self, document):
text = PUBLISH_WORKFLOW.read_text(encoding="utf-8") step = document["jobs"]["publish-wiki"]["steps"][-1]
assert "secrets.TOKEN_FOR_GITEA }}" not in text assert step["env"]["WIKI_DIR"] == "${{ inputs.wiki_dir }}"
publish_step = document["jobs"]["publish-wiki"]["steps"][-1] assert step["env"]["WIKI_PRUNE"] == "${{ inputs.prune }}"
assert publish_step["env"]["WIKI_CREDENTIALS"] == "${{ secrets.TOKEN_FOR_GITEA_WIKI }}" assert step["env"]["WIKI_CREDENTIALS"] == "${{ secrets.TOKEN_FOR_GITEA_WIKI }}"
assert "secrets" not in publish_step["run"] assert step["run"] == "python3 shared-scripts/tools/publish_wiki.py"
assert "inputs." not in step["run"]
def test_the_workflow_has_one_source_and_runs_the_script_file(self, document): def test_the_script_is_fetched_at_the_tools_ref(self, document):
assert not (REPO_ROOT / ".gitea" / "workflows" / "publish-wiki.yml").exists() fetch = document["jobs"]["publish-wiki"]["steps"][1]["with"]
assert (REPO_ROOT / "tools" / "publish_wiki.py").is_file() assert fetch["repository"] == "TirSystem/github-action"
steps = document["jobs"]["publish-wiki"]["steps"] assert fetch["ref"] == "${{ inputs.tools-ref }}"
assert "python3 - <<" not in PUBLISH_WORKFLOW.read_text(encoding="utf-8")
assert steps[0]["uses"].startswith("actions/checkout@")
assert steps[1]["with"] == {
"repository": "TirSystem/github-action",
"ref": "v1",
"path": "shared-scripts",
}
assert steps[-1]["run"] == "python3 shared-scripts/tools/publish_wiki.py"
def test_this_repository_opts_in_and_prunes(): def test_the_scoped_workflow_is_gone_and_the_script_has_no_marker_path():
"""The wiki of this repository must equal wiki/ (ADR-0001, ADR-0004).""" """ADR-0009, MIL-009: the scoped publish-wiki.yml and the marker are removed."""
marker = REPO_ROOT / "wiki" / ".publish" assert not (REPO_ROOT / ".gitea" / "scoped_workflows" / "publish-wiki.yml").exists()
assert marker.read_text(encoding="utf-8").strip() == "prune" source = (REPO_ROOT / "tools" / "publish_wiki.py").read_text(encoding="utf-8")
assert "MARKER_NAME" not in source
assert "parse_marker" not in source
def test_this_repository_publishes_docs_wiki_through_the_reusable_workflow():
"""The wiki of this repository equals docs/wiki/ (ADR-0009, MIL-008 task 4)."""
assert not (REPO_ROOT / "wiki").exists()
assert (REPO_ROOT / "docs" / "wiki" / "Home.md").is_file()
caller = yaml.safe_load(
(REPO_ROOT / ".gitea" / "workflows" / "publish-own-wiki.yml").read_text(encoding="utf-8")
)
job = caller["jobs"]["wiki"]
assert job["uses"] == "./.gitea/workflows/publish-wiki-pages.yml"
assert job["with"]["wiki_dir"] == "docs/wiki"
assert job["with"]["prune"] == "true"
assert caller[True]["push"]["paths"] == ["docs/wiki/**"]
+722
View File
@@ -0,0 +1,722 @@
"""Offline tests for the reusable C++ and C# quality-check workflows (ADR-0008).
The shell steps run with stub tools in a throwaway git repository, so no
network, no clang and no .NET SDK are needed.
"""
import os
import re
import shutil
import subprocess
from pathlib import Path
from typing import Any
import pytest
import yaml
from workflow_source import REPO_ROOT
CPP_WORKFLOW = REPO_ROOT / ".gitea" / "workflows" / "quality-checks-cpp.yml"
CSHARP_WORKFLOW = REPO_ROOT / ".gitea" / "workflows" / "quality-checks-csharp.yml"
CPP_SCRIPT = REPO_ROOT / "tools" / "quality-checks-cpp.sh"
CSHARP_SCRIPT = REPO_ROOT / "tools" / "quality-checks-csharp.sh"
BASH = shutil.which("bash")
pytestmark = pytest.mark.skipif(BASH is None, reason="bash is needed to run the workflow steps")
# A script run: the finished process and the tool calls the stubs logged.
Run = tuple[subprocess.CompletedProcess[str], list[str]]
def load(path: Path) -> dict[str, Any]: # parsed YAML has no fixed shape
return yaml.safe_load(path.read_text(encoding="utf-8"))
def inputs_of(path: Path) -> dict[str, Any]:
return load(path)[True]["workflow_call"]["inputs"] # PyYAML reads `on` as True
@pytest.mark.parametrize(
"workflow, script, job",
[
(CPP_WORKFLOW, "quality-checks-cpp.sh", "quality-checks-cpp"),
(CSHARP_WORKFLOW, "quality-checks-csharp.sh", "quality-checks-csharp"),
],
ids=["cpp", "csharp"],
)
class TestDefinition:
def test_has_least_privilege_and_needs_no_secret(self, workflow: Path, script: str, job: str) -> None:
document = load(workflow)
assert document["jobs"][job]["permissions"] == {"contents": "read"}
assert "secrets" not in workflow.read_text(encoding="utf-8").replace("needs no secret", "")
def test_embeds_no_script_and_runs_the_file_in_three_steps(
self,
workflow: Path,
script: str,
job: str,
) -> None:
steps = load(workflow)["jobs"][job]["steps"]
commands = [step["run"] for step in steps if "run" in step]
assert commands == [f"bash shared-scripts/tools/{script} {c}" for c in ("validate", "install", "run")]
def test_fetches_the_script_at_the_tools_ref_input(self, workflow: Path, script: str, job: str) -> None:
steps = load(workflow)["jobs"][job]["steps"]
fetch = next(step for step in steps if step.get("name") == "Fetch the shared script")
assert fetch["with"] == {
"repository": "TirSystem/github-action",
"ref": "${{ inputs.tools-ref }}",
"path": "shared-scripts",
}
assert inputs_of(workflow)["tools-ref"]["default"] == "v1"
def test_inputs_reach_the_script_only_through_the_environment(
self,
workflow: Path,
script: str,
job: str,
) -> None:
for step in load(workflow)["jobs"][job]["steps"]:
assert "${{" not in step.get("run", "")
def test_script_is_a_file_that_parses(self, workflow: Path, script: str, job: str) -> None:
result = subprocess.run(
[BASH, "-n", str(REPO_ROOT / "tools" / script)], text=True, capture_output=True
)
assert result.returncode == 0, result.stderr
def test_unknown_command_is_refused(self, workflow: Path, script: str, job: str) -> None:
result = subprocess.run(
[BASH, str(REPO_ROOT / "tools" / script), "bogus"], text=True, capture_output=True
)
assert result.returncode == 2
def test_cpp_inputs_and_exact_defaults() -> None:
inputs = inputs_of(CPP_WORKFLOW)
assert set(inputs) == {
"working-directory",
"std",
"compile-commands-dir",
"clang-format-version",
"clang-tidy-version",
"tools-ref",
}
assert inputs["std"]["default"] == "c++20"
assert inputs["compile-commands-dir"]["default"] == ""
assert inputs["clang-format-version"]["default"] == "23.1.3"
assert inputs["clang-tidy-version"]["default"] == "22.1.8"
def test_csharp_inputs_and_exact_sdk_default() -> None:
inputs = inputs_of(CSHARP_WORKFLOW)
assert set(inputs) == {"working-directory", "project", "dotnet-sdk-version", "tools-ref"}
assert inputs["dotnet-sdk-version"]["default"] == "10.0.401"
def test_cpp_script_and_workflow_do_not_mention_cppcheck() -> None:
for path in (CPP_SCRIPT, CPP_WORKFLOW):
assert "cppcheck" not in path.read_text(encoding="utf-8").lower(), path.name
# ----------------------------------------------------------------- helpers
def make_repo(tmp_path: Path) -> Path:
root = tmp_path / "repo"
root.mkdir()
subprocess.run(["git", "init", "-q"], cwd=root, check=True)
return root
def add_files(root: Path, *relative: str) -> None:
for name in relative:
path = root / name
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text("x\n", encoding="utf-8", newline="\n")
subprocess.run(["git", "add", "-A"], cwd=root, check=True)
def stub(directory: Path, name: str) -> Path:
path = directory / name
path.write_text(
"#!/usr/bin/env bash\n"
'name="$(basename "$0")"\n'
'[ "$name" = dotnet ] && name="dotnet $1"\n'
'echo "$name :: $*" >> "$STUB_LOG"\n'
'case ",${STUB_FAIL}," in\n'
' *",$name,"*) echo "src/a.cpp:3:1: stub finding from $name"; exit 1 ;;\n'
"esac\n"
"exit 0\n",
encoding="utf-8",
newline="\n",
)
path.chmod(0o755)
return path
def run_script(script: Path, command: str, cwd: Path, tmp_path: Path, **env: str) -> Run:
log = tmp_path / "tools.log"
environment = dict(os.environ)
environment.update({"STUB_LOG": log.as_posix(), "STUB_FAIL": ""})
environment.update(env)
result = subprocess.run(
[BASH, str(script), command], cwd=cwd, env=environment, text=True, capture_output=True
)
calls = log.read_text(encoding="utf-8").splitlines() if log.exists() else []
return result, calls
# --------------------------------------------------------------------- C++
@pytest.fixture
def cpp_bin(tmp_path: Path) -> Path:
directory = tmp_path / "bin"
directory.mkdir()
for tool in ("clang-format", "clang-tidy"):
stub(directory, tool)
return directory
def cpp_env(cpp_bin: Path, **extra: str) -> dict[str, str]:
values = {
"WORKING_DIRECTORY": ".",
"STD": "c++20",
"COMPILE_COMMANDS_DIR": "",
"CLANG_FORMAT_VERSION": "23.1.3",
"CLANG_TIDY_VERSION": "22.1.8",
"QC_VENV": "/nonexistent",
"QC_BIN": cpp_bin.as_posix(),
}
values.update(extra)
return values
class TestCppValidate:
def run(self, tmp_path: Path, cpp_bin: Path, **extra: str) -> Run:
return run_script(CPP_SCRIPT, "validate", tmp_path, tmp_path, **cpp_env(cpp_bin, **extra))
def test_accepts_the_defaults(self, tmp_path: Path, cpp_bin: Path) -> None:
result, _ = self.run(tmp_path, cpp_bin)
assert result.returncode == 0, result.stdout
@pytest.mark.parametrize("std", ["c++17", "c++23", "gnu++20"])
def test_accepts_a_cpp_standard(self, tmp_path: Path, cpp_bin: Path, std: str) -> None:
assert self.run(tmp_path, cpp_bin, STD=std)[0].returncode == 0
@pytest.mark.parametrize("std", ["", "c17", "c++", "latest", "c++20; rm"])
def test_rejects_a_value_that_is_not_a_cpp_standard(
self,
tmp_path: Path,
cpp_bin: Path,
std: str,
) -> None:
result, _ = self.run(tmp_path, cpp_bin, STD=std)
assert result.returncode == 1 and "[configuration]" in result.stdout
@pytest.mark.parametrize("directory", ["/etc", "..", "../x", "a/../..", ""])
def test_rejects_a_working_directory_outside_the_repository(
self,
tmp_path: Path,
cpp_bin: Path,
directory: str,
) -> None:
result, _ = self.run(tmp_path, cpp_bin, WORKING_DIRECTORY=directory)
assert result.returncode == 1 and "working-directory" in result.stdout
@pytest.mark.parametrize("directory", ["/tmp/build", "../build"])
def test_rejects_a_compile_commands_dir_outside_the_repository(
self,
tmp_path: Path,
cpp_bin: Path,
directory: str,
) -> None:
result, _ = self.run(tmp_path, cpp_bin, COMPILE_COMMANDS_DIR=directory)
assert result.returncode == 1 and "compile-commands-dir" in result.stdout
@pytest.mark.parametrize("name", ["CLANG_FORMAT_VERSION", "CLANG_TIDY_VERSION"])
@pytest.mark.parametrize("version", ["", "latest", "23", ">=23", "23.1.3 --pre"])
def test_rejects_a_version_that_is_not_exact(
self,
tmp_path: Path,
cpp_bin: Path,
name: str,
version: str,
) -> None:
result, _ = self.run(tmp_path, cpp_bin, **{name: version})
assert result.returncode == 1 and "exact version" in result.stdout
class TestCppRun:
@pytest.fixture
def repo(self, tmp_path: Path) -> Path:
root = make_repo(tmp_path)
add_files(root, ".clang-format")
return root
def run(self, repo: Path, tmp_path: Path, cpp_bin: Path, **extra: str) -> Run:
return run_script(CPP_SCRIPT, "run", repo, tmp_path, **cpp_env(cpp_bin, **extra))
def test_runs_clang_format_once_and_clang_tidy_per_language(
self,
repo: Path,
tmp_path: Path,
cpp_bin: Path,
) -> None:
add_files(repo, "src/a.cpp", "src/a.hpp", "lib/c.c")
result, calls = self.run(repo, tmp_path, cpp_bin)
assert result.returncode == 0, result.stdout + result.stderr
assert [c.split(" :: ")[0] for c in calls] == ["clang-format", "clang-tidy", "clang-tidy"]
assert "--dry-run -Werror" in calls[0]
assert "src/a.cpp" in calls[0] and "lib/c.c" in calls[0]
assert "-- -std=c++20" in calls[1] and "lib/c.c" not in calls[1]
assert "-- -std=c17" in calls[2] and "lib/c.c" in calls[2]
assert "All selected checks passed." in result.stdout
def test_the_std_input_reaches_clang_tidy(self, repo: Path, tmp_path: Path, cpp_bin: Path) -> None:
add_files(repo, "a.cpp")
_, calls = self.run(repo, tmp_path, cpp_bin, STD="c++17")
assert "-- -std=c++17" in calls[1]
def test_a_compile_database_is_used_instead_of_a_standard(
self,
repo: Path,
tmp_path: Path,
cpp_bin: Path,
) -> None:
add_files(repo, "a.cpp", "build/compile_commands.json")
result, calls = self.run(repo, tmp_path, cpp_bin, COMPILE_COMMANDS_DIR="build")
assert result.returncode == 0, result.stdout
assert len(calls) == 2
database = re.search(r"-p (\S+)", calls[1])
assert database is not None and database.group(1).endswith(f"/{repo.name}/build")
assert "-std=" not in calls[1]
def test_clang_tidy_gets_default_checks_without_a_clang_tidy_file(
self,
repo: Path,
tmp_path: Path,
cpp_bin: Path,
) -> None:
add_files(repo, "a.cpp")
_, calls = self.run(repo, tmp_path, cpp_bin)
assert "--checks=clang-diagnostic-*,clang-analyzer-*" in calls[1]
assert "--checks=" not in calls[0]
def test_no_default_checks_when_a_clang_tidy_file_is_in_the_repository_root(
self,
repo: Path,
tmp_path: Path,
cpp_bin: Path,
) -> None:
add_files(repo, "a.cpp", ".clang-tidy")
_, calls = self.run(repo, tmp_path, cpp_bin)
assert len(calls) == 2
assert not any("--checks=" in call for call in calls)
@pytest.mark.parametrize(
("files", "extra", "tidy_calls"),
[
(("a.cpp", "lib/c.c"), {}, 2),
(("a.cpp", ".clang-tidy"), {}, 1),
(("a.cpp", "build/compile_commands.json"), {"COMPILE_COMMANDS_DIR": "build"}, 1),
],
ids=["no-clang-tidy-file", "clang-tidy-file", "compile-database"],
)
def test_every_clang_tidy_call_treats_warnings_as_errors(
self,
repo: Path,
tmp_path: Path,
cpp_bin: Path,
files: tuple[str, ...],
extra: dict[str, str],
tidy_calls: int,
) -> None:
add_files(repo, *files)
_, calls = self.run(repo, tmp_path, cpp_bin, **extra)
tidy = [c for c in calls if c.startswith("clang-tidy")]
assert len(tidy) == tidy_calls
assert all("--warnings-as-errors=*" in call for call in tidy)
assert "--warnings-as-errors" not in calls[0] and calls[0].startswith("clang-format")
def test_no_default_checks_when_a_clang_tidy_file_is_above_the_working_directory(
self,
repo: Path,
tmp_path: Path,
cpp_bin: Path,
) -> None:
add_files(repo, "service/a.cpp", ".clang-tidy")
_, calls = self.run(repo, tmp_path, cpp_bin, WORKING_DIRECTORY="service")
assert len(calls) == 2
assert not any("--checks=" in call for call in calls)
def test_a_compile_database_is_resolved_from_the_repository_root(
self,
repo: Path,
tmp_path: Path,
cpp_bin: Path,
) -> None:
add_files(repo, "service/a.cpp", "build/compile_commands.json")
result, calls = self.run(
repo, tmp_path, cpp_bin, WORKING_DIRECTORY="service", COMPILE_COMMANDS_DIR="build"
)
assert result.returncode == 0, result.stdout
database = re.search(r"-p (\S+)", calls[1])
assert database is not None and database.group(1).endswith(f"/{repo.name}/build")
def test_a_compile_database_only_inside_the_working_directory_is_not_found(
self,
repo: Path,
tmp_path: Path,
cpp_bin: Path,
) -> None:
add_files(repo, "service/a.cpp", "service/build/compile_commands.json")
result, calls = self.run(
repo, tmp_path, cpp_bin, WORKING_DIRECTORY="service", COMPILE_COMMANDS_DIR="build"
)
assert result.returncode == 1 and "[configuration]" in result.stdout
assert calls == []
def test_a_missing_compile_database_is_a_configuration_error(
self,
repo: Path,
tmp_path: Path,
cpp_bin: Path,
) -> None:
add_files(repo, "a.cpp")
result, calls = self.run(repo, tmp_path, cpp_bin, COMPILE_COMMANDS_DIR="build")
assert result.returncode == 1 and "compile_commands.json" in result.stdout
assert calls == []
def test_a_missing_clang_format_file_is_a_configuration_error(
self,
tmp_path: Path,
cpp_bin: Path,
) -> None:
repo = make_repo(tmp_path)
add_files(repo, "a.cpp")
result, calls = self.run(repo, tmp_path, cpp_bin)
assert result.returncode == 1
assert "[configuration]" in result.stdout and ".clang-format" in result.stdout
assert calls == []
def test_a_clang_format_file_above_the_working_directory_is_found(
self,
repo: Path,
tmp_path: Path,
cpp_bin: Path,
) -> None:
add_files(repo, "service/a.cpp")
result, _ = run_script(
CPP_SCRIPT,
"run",
repo,
tmp_path,
**cpp_env(cpp_bin, WORKING_DIRECTORY="service"),
)
assert result.returncode == 0, result.stdout
def test_a_failing_tool_fails_the_job_and_the_other_still_runs(
self,
repo: Path,
tmp_path: Path,
cpp_bin: Path,
) -> None:
add_files(repo, "src/a.cpp")
result, calls = self.run(repo, tmp_path, cpp_bin, STUB_FAIL="clang-format")
assert result.returncode == 1
assert len(calls) == 2, "clang-tidy runs after clang-format failed"
assert "::group::clang-format" in result.stdout
assert "src/a.cpp:3:1: stub finding from clang-format" in result.stdout
assert "FAIL clang-format" in result.stdout and "PASS clang-tidy (C++)" in result.stdout
assert "Quality checks failed: clang-format" in result.stdout
def test_a_repository_without_c_or_cpp_files_passes_with_a_notice(
self,
tmp_path: Path,
cpp_bin: Path,
) -> None:
repo = make_repo(tmp_path)
add_files(repo, "README.md")
result, calls = self.run(repo, tmp_path, cpp_bin)
assert result.returncode == 0 and calls == []
assert "No C or C++ files" in result.stdout
def test_only_tracked_files_under_the_working_directory_are_checked(
self,
repo: Path,
tmp_path: Path,
cpp_bin: Path,
) -> None:
add_files(repo, "top.cpp", "service/api.cpp")
(repo / "service" / "untracked.cpp").write_text("x\n", encoding="utf-8")
_, calls = self.run(repo, tmp_path, cpp_bin, WORKING_DIRECTORY="service")
text = "\n".join(calls)
assert "api.cpp" in text and "top.cpp" not in text and "untracked" not in text
def test_no_file_is_changed(self, repo: Path, tmp_path: Path, cpp_bin: Path) -> None:
add_files(repo, "a.cpp")
def snapshot() -> dict[str, bytes]:
return {p.name: p.read_bytes() for p in repo.iterdir() if p.is_file()}
before = snapshot()
self.run(repo, tmp_path, cpp_bin, STUB_FAIL="clang-tidy (C++)")
assert snapshot() == before
def test_a_missing_working_directory_is_a_configuration_error(
self,
repo: Path,
tmp_path: Path,
cpp_bin: Path,
) -> None:
result, _ = self.run(repo, tmp_path, cpp_bin, WORKING_DIRECTORY="nope")
assert result.returncode == 1 and "[configuration]" in result.stdout
def test_cpp_install_pins_both_tools_to_the_exact_versions(tmp_path: Path) -> None:
"""`install` creates the venv and installs `==` versions; checked with a fake pip."""
venv = tmp_path / "venv"
fake_python = tmp_path / "bin"
fake_python.mkdir()
log = tmp_path / "pip.log"
python3 = fake_python / "python3"
python3.write_text(
"#!/usr/bin/env bash\n"
'mkdir -p "$3/bin"\n'
"printf '#!/usr/bin/env bash\\necho \"$*\" >> \"%s\"\\n' > \"$3/bin/pip\"\n" % log.as_posix()
+ 'chmod +x "$3/bin/pip"\n',
encoding="utf-8",
newline="\n",
)
python3.chmod(0o755)
environment = dict(os.environ)
environment["PATH"] = f"{fake_python.as_posix()}:{environment['PATH']}"
environment.update(
{
"QC_VENV": venv.as_posix(),
"CLANG_FORMAT_VERSION": "23.1.3",
"CLANG_TIDY_VERSION": "22.1.8",
}
)
result = subprocess.run(
[BASH, str(CPP_SCRIPT), "install"], env=environment, text=True, capture_output=True
)
assert result.returncode == 0, result.stdout + result.stderr
installed = log.read_text(encoding="utf-8")
assert "clang-format==23.1.3" in installed
assert "clang-tidy==22.1.8" in installed
# --------------------------------------------------------------------- C#
@pytest.fixture
def dotnet_stub(tmp_path: Path) -> Path:
directory = tmp_path / "dotnet-bin"
directory.mkdir()
return stub(directory, "dotnet")
def csharp_env(dotnet_stub: Path, tmp_path: Path, **extra: str) -> dict[str, str]:
values = {
"WORKING_DIRECTORY": ".",
"PROJECT": "",
"DOTNET_SDK_VERSION": "10.0.401",
"DOTNET_ROOT": (tmp_path / "dotnet-root").as_posix(),
"QC_DOTNET": dotnet_stub.as_posix(),
}
values.update(extra)
return values
class TestCsharpValidate:
def run(self, tmp_path: Path, dotnet_stub: Path, **extra: str) -> Run:
return run_script(
CSHARP_SCRIPT, "validate", tmp_path, tmp_path, **csharp_env(dotnet_stub, tmp_path, **extra)
)
@pytest.mark.parametrize("version", ["10.0.401", "10.0.100", "11.0.100", "100.1.2"])
def test_accepts_an_exact_sdk_of_version_10_or_later(
self,
tmp_path: Path,
dotnet_stub: Path,
version: str,
) -> None:
assert self.run(tmp_path, dotnet_stub, DOTNET_SDK_VERSION=version)[0].returncode == 0
@pytest.mark.parametrize(
"version",
["", "8.0.425", "9.0.100", "10.0", "10", "10.0.x", "latest", "10.0.401-rc.1", "10.0.401 --verbose"],
)
def test_rejects_an_old_or_inexact_sdk(self, tmp_path: Path, dotnet_stub: Path, version: str) -> None:
result, _ = self.run(tmp_path, dotnet_stub, DOTNET_SDK_VERSION=version)
assert result.returncode == 1
assert "[configuration]" in result.stdout and "dotnet-sdk-version" in result.stdout
@pytest.mark.parametrize("directory", ["/etc", "..", "../x", ""])
def test_rejects_a_working_directory_outside_the_repository(
self,
tmp_path: Path,
dotnet_stub: Path,
directory: str,
) -> None:
result, _ = self.run(tmp_path, dotnet_stub, WORKING_DIRECTORY=directory)
assert result.returncode == 1 and "working-directory" in result.stdout
@pytest.mark.parametrize("project", ["/etc/x.sln", "../x.sln"])
def test_rejects_a_project_outside_the_working_directory(
self,
tmp_path: Path,
dotnet_stub: Path,
project: str,
) -> None:
result, _ = self.run(tmp_path, dotnet_stub, PROJECT=project)
assert result.returncode == 1 and "project" in result.stdout
class TestCsharpInstall:
def test_installs_the_exact_version_with_the_official_script(
self,
tmp_path: Path,
dotnet_stub: Path,
) -> None:
root = tmp_path / "dotnet-root"
installer = tmp_path / "dotnet-install.sh"
installer.write_text(
"#!/usr/bin/env bash\n"
'echo "$*" >> "$STUB_LOG"\n'
'dir=""\n'
'while [ "$#" -gt 0 ]; do [ "$1" = --install-dir ] && dir="$2"; shift; done\n'
'mkdir -p "$dir"\n'
"printf '#!/usr/bin/env bash\\necho 10.0.401\\n' > \"$dir/dotnet\"\n"
'chmod +x "$dir/dotnet"\n',
encoding="utf-8",
newline="\n",
)
result, calls = run_script(
CSHARP_SCRIPT,
"install",
tmp_path,
tmp_path,
**csharp_env(dotnet_stub, tmp_path, QC_DOTNET_INSTALL_SCRIPT=installer.as_posix()),
)
assert result.returncode == 0, result.stdout + result.stderr
assert calls and "--version 10.0.401" in calls[0]
assert f"--install-dir {root.as_posix()}" in calls[0]
assert "10.0.401" in result.stdout
class TestCsharpRun:
@pytest.fixture
def repo(self, tmp_path: Path) -> Path:
return make_repo(tmp_path)
def run(self, repo: Path, tmp_path: Path, dotnet_stub: Path, **extra: str) -> Run:
return run_script(
CSHARP_SCRIPT, "run", repo, tmp_path, **csharp_env(dotnet_stub, tmp_path, **extra)
)
def test_runs_format_and_build_on_the_single_solution(
self,
repo: Path,
tmp_path: Path,
dotnet_stub: Path,
) -> None:
add_files(repo, "App.sln", "src/App.csproj", "src/Program.cs")
result, calls = self.run(repo, tmp_path, dotnet_stub)
assert result.returncode == 0, result.stdout + result.stderr
assert [c.split(" :: ")[0] for c in calls] == ["dotnet format", "dotnet build"]
assert "App.sln" in calls[0] and "--verify-no-changes" in calls[0]
assert "App.sln" in calls[1] and "-warnaserror" in calls[1]
assert "All selected checks passed." in result.stdout
def test_uses_the_single_project_when_there_is_no_solution(
self,
repo: Path,
tmp_path: Path,
dotnet_stub: Path,
) -> None:
add_files(repo, "src/App.csproj", "src/Program.cs")
_, calls = self.run(repo, tmp_path, dotnet_stub)
assert "src/App.csproj" in calls[0]
def test_several_projects_without_a_solution_need_the_project_input(
self,
repo: Path,
tmp_path: Path,
dotnet_stub: Path,
) -> None:
add_files(repo, "a/A.csproj", "b/B.csproj", "a/P.cs")
result, calls = self.run(repo, tmp_path, dotnet_stub)
assert result.returncode == 1 and calls == []
assert "[configuration]" in result.stdout
assert "a/A.csproj" in result.stdout and "b/B.csproj" in result.stdout
def test_the_project_input_picks_one(self, repo: Path, tmp_path: Path, dotnet_stub: Path) -> None:
add_files(repo, "a/A.csproj", "b/B.csproj", "a/P.cs")
result, calls = self.run(repo, tmp_path, dotnet_stub, PROJECT="b/B.csproj")
assert result.returncode == 0 and "b/B.csproj" in calls[0]
def test_a_missing_project_file_is_a_configuration_error(
self,
repo: Path,
tmp_path: Path,
dotnet_stub: Path,
) -> None:
add_files(repo, "a/P.cs")
result, calls = self.run(repo, tmp_path, dotnet_stub, PROJECT="nope.csproj")
assert result.returncode == 1 and "does not exist" in result.stdout and calls == []
def test_csharp_files_without_a_project_are_a_configuration_error(
self,
repo: Path,
tmp_path: Path,
dotnet_stub: Path,
) -> None:
add_files(repo, "Program.cs")
result, calls = self.run(repo, tmp_path, dotnet_stub)
assert result.returncode == 1 and calls == []
assert "[configuration]" in result.stdout and "no solution or project" in result.stdout
def test_a_repository_without_csharp_files_passes_with_a_notice(
self,
repo: Path,
tmp_path: Path,
dotnet_stub: Path,
) -> None:
add_files(repo, "README.md")
result, calls = self.run(repo, tmp_path, dotnet_stub)
assert result.returncode == 0 and calls == []
assert "No C# files" in result.stdout
def test_a_failing_tool_fails_the_job_and_the_other_still_runs(
self,
repo: Path,
tmp_path: Path,
dotnet_stub: Path,
) -> None:
add_files(repo, "App.sln", "Program.cs")
result, calls = self.run(repo, tmp_path, dotnet_stub, STUB_FAIL="dotnet format")
assert result.returncode == 1 and len(calls) == 2
assert "::group::dotnet format --verify-no-changes" in result.stdout
assert "FAIL dotnet format --verify-no-changes" in result.stdout
assert "PASS dotnet build -warnaserror" in result.stdout
assert "Quality checks failed: dotnet format --verify-no-changes" in result.stdout
def test_only_files_under_the_working_directory_are_considered(
self,
repo: Path,
tmp_path: Path,
dotnet_stub: Path,
) -> None:
add_files(repo, "Top.sln", "svc/Svc.csproj", "svc/P.cs", "Top.cs")
result, calls = self.run(repo, tmp_path, dotnet_stub, WORKING_DIRECTORY="svc")
assert result.returncode == 0
assert "Svc.csproj" in calls[0] and "Top.sln" not in calls[0]
def test_a_missing_working_directory_is_a_configuration_error(
self,
repo: Path,
tmp_path: Path,
dotnet_stub: Path,
) -> None:
result, _ = self.run(repo, tmp_path, dotnet_stub, WORKING_DIRECTORY="nope")
assert result.returncode == 1 and "[configuration]" in result.stdout
+2 -2
View File
@@ -281,7 +281,7 @@ class TestBuildWikiCommandLine:
monkeypatch.setattr(build_wiki._module, "check_translations", lambda: []) monkeypatch.setattr(build_wiki._module, "check_translations", lambda: [])
assert build_wiki["main"](["--check"]) == 1 assert build_wiki["main"](["--check"]) == 1
err = capsys.readouterr().err err = capsys.readouterr().err
assert "ERROR: wiki/Home.md: missing" in err assert "ERROR: docs/wiki/Home.md: missing" in err
assert "python tools/build_wiki.py" in err assert "python tools/build_wiki.py" in err
def test_generate_reports_the_changed_pages(self, monkeypatch, capsys): def test_generate_reports_the_changed_pages(self, monkeypatch, capsys):
@@ -320,7 +320,7 @@ class TestRunAsAProcess:
assert result.stderr.startswith("ERROR: [configuration]") assert result.stderr.startswith("ERROR: [configuration]")
def test_publish_wiki_without_a_repository_exits_1(self): def test_publish_wiki_without_a_repository_exits_1(self):
result = run_tool("publish_wiki.py", WIKI_DIR=str(REPO_ROOT / "wiki"), GITEA_API_URL="https://x") result = run_tool("publish_wiki.py", WIKI_DIR=str(REPO_ROOT / "docs" / "wiki"), GITEA_API_URL="https://x")
assert result.returncode == 1 assert result.returncode == 1
assert "ERROR:" in result.stderr assert "ERROR:" in result.stderr
+2 -1
View File
@@ -29,7 +29,8 @@ class TestEachWorkflow:
def test_every_job_declares_permissions_and_runner(self, path): def test_every_job_declares_permissions_and_runner(self, path):
document = yaml.safe_load(path.read_text(encoding="utf-8")) document = yaml.safe_load(path.read_text(encoding="utf-8"))
for name, job in document["jobs"].items(): for name, job in document["jobs"].items():
assert "runs-on" in job, f"job {name} has no runs-on" if "uses" not in job: # a called workflow has its own runner
assert "runs-on" in job, f"job {name} has no runs-on"
assert "permissions" in job, f"job {name} has no explicit permissions" assert "permissions" in job, f"job {name} has no explicit permissions"
-1
View File
@@ -13,7 +13,6 @@ WORKFLOW_DIRS = (
) )
SYNC_WORKFLOW = WORKFLOW_DIRS[0] / "sync-github-metadata.yml" SYNC_WORKFLOW = WORKFLOW_DIRS[0] / "sync-github-metadata.yml"
SYNC_SCRIPT = REPO_ROOT / "tools" / "sync_github_metadata.py" SYNC_SCRIPT = REPO_ROOT / "tools" / "sync_github_metadata.py"
PUBLISH_WORKFLOW = WORKFLOW_DIRS[0] / "publish-wiki.yml"
PUBLISH_SCRIPT = REPO_ROOT / "tools" / "publish_wiki.py" PUBLISH_SCRIPT = REPO_ROOT / "tools" / "publish_wiki.py"
_HEREDOC = re.compile( _HEREDOC = re.compile(
+228
View File
@@ -0,0 +1,228 @@
"""Read a Cobertura coverage report and write the code metrics badges (ADR-0010).
Usage:
python tools/badges.py --metrics code-metrics.json --output-dir DIR
[--coverage-report coverage.xml] [--coverage-warn 60] [--coverage-good 80]
Writes `code-files.svg`, `code-lines.svg`, `code-metrics.json` and, when a report
is given, `coverage.svg` into DIR. The same input gives the same bytes.
Exit codes: 0 written, 1 a configuration problem (printed as
`::error::[configuration] ...`, nothing is written).
"""
import argparse
import json
import sys
import xml.etree.ElementTree as ElementTree
from decimal import ROUND_HALF_UP, Decimal, InvalidOperation
from pathlib import Path
BLUE = "#007ec6"
RED = "#e05d44"
YELLOW = "#dfb317"
GREEN = "#44cc11"
LABEL_COLOR = "#555555"
CHAR_WIDTH = 6
PADDING = 10
HEIGHT = 20
class ConfigurationError(Exception):
"""An input is invalid; the message is printed as a `[configuration]` failure."""
def read_coverage(path: Path) -> int:
"""Return the line coverage of a Cobertura XML report as a whole percent, half up."""
if not path.is_file():
raise ConfigurationError(f"coverage-report '{path.as_posix()}' does not exist.")
data = path.read_bytes()
if b"<!ENTITY" in data:
raise ConfigurationError(
f"coverage-report '{path.as_posix()}' declares entities, which are refused."
)
try:
root = ElementTree.fromstring(data)
except ElementTree.ParseError as error:
raise ConfigurationError(
f"coverage-report '{path.as_posix()}' is not well-formed XML: {error}."
) from error
if root.tag != "coverage":
raise ConfigurationError(
f"coverage-report '{path.as_posix()}' is not a Cobertura report (root element '{root.tag}')."
)
covered, valid = root.get("lines-covered"), root.get("lines-valid")
try:
if covered is not None and valid is not None:
covered_lines, valid_lines = int(covered), int(valid)
if valid_lines <= 0:
raise ConfigurationError(
f"coverage-report '{path.as_posix()}' has no valid lines."
)
if not 0 <= covered_lines <= valid_lines:
raise ConfigurationError(
f"coverage-report '{path.as_posix()}' has lines-covered outside 0..lines-valid."
)
fraction = Decimal(covered_lines) / Decimal(valid_lines)
else:
rate = root.get("line-rate")
if rate is None:
raise ConfigurationError(
f"coverage-report '{path.as_posix()}' has neither lines-valid and lines-covered nor line-rate."
)
fraction = Decimal(rate)
if not Decimal(0) <= fraction <= Decimal(1):
raise ConfigurationError(
f"coverage-report '{path.as_posix()}' has a line-rate outside 0..1."
)
except (ValueError, InvalidOperation) as error:
raise ConfigurationError(
f"coverage-report '{path.as_posix()}' has a value that is not a number."
) from error
return int((fraction * 100).quantize(Decimal(1), rounding=ROUND_HALF_UP))
def coverage_color(percent: int, warn: float, good: float) -> str:
"""Return red below warn, yellow from warn to below good, green from good."""
if percent < warn:
return RED
if percent < good:
return YELLOW
return GREEN
def _luminance(color: str) -> float:
channels = [int(color[i : i + 2], 16) / 255 for i in (1, 3, 5)]
linear = [
c / 12.92 if c <= 0.04045 else ((c + 0.055) / 1.055) ** 2.4 for c in channels
]
return 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2]
def _text_color(background: str) -> str:
"""Black or white, whichever has the higher contrast on the background."""
lum = _luminance(background)
return "#ffffff" if (1.05 / (lum + 0.05)) >= ((lum + 0.05) / 0.05) else "#000000"
def _escape(text: str) -> str:
return (
text.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace('"', "&quot;")
)
def render(label: str, value: str, color: str) -> str:
"""Return a flat two-part SVG badge; the width follows the number of characters."""
label_width = CHAR_WIDTH * len(label) + PADDING
value_width = CHAR_WIDTH * len(value) + PADDING
total = label_width + value_width
text = _escape(f"{label}: {value}")
label_x = label_width / 2
value_x = label_width + value_width / 2
return (
f'<svg xmlns="http://www.w3.org/2000/svg" width="{total}" height="{HEIGHT}" '
f'role="img" aria-label="{text}">\n'
f"<title>{text}</title>\n"
f'<rect width="{label_width}" height="{HEIGHT}" fill="{LABEL_COLOR}"/>\n'
f'<rect x="{label_width}" width="{value_width}" height="{HEIGHT}" fill="{color}"/>\n'
'<g font-family="Verdana,Geneva,DejaVu Sans,sans-serif" font-size="11" '
'text-anchor="middle">\n'
f'<text x="{label_x:g}" y="14" fill="{_text_color(LABEL_COLOR)}">{_escape(label)}</text>\n'
f'<text x="{value_x:g}" y="14" fill="{_text_color(color)}">{_escape(value)}</text>\n'
"</g>\n"
"</svg>\n"
)
def _threshold(name: str, text: str) -> float:
try:
value = float(text)
except ValueError as error:
raise ConfigurationError(
f"{name} '{text}' is not a number from 0 to 100."
) from error
if not 0 <= value <= 100:
raise ConfigurationError(f"{name} '{text}' is not a number from 0 to 100.")
return value
def write_badges(
file_count: int,
line_count: int,
metrics_json: str,
output_dir: Path,
coverage: int | None,
warn: float,
good: float,
) -> list[Path]:
"""Write the badge files and the metrics JSON; return the paths in a fixed order."""
output_dir.mkdir(parents=True, exist_ok=True)
files = {
"code-files.svg": render("code files", f"{file_count:,}", BLUE),
"code-lines.svg": render("lines of code", f"{line_count:,}", BLUE),
}
if coverage is not None:
files["coverage.svg"] = render(
"coverage", f"{coverage}%", coverage_color(coverage, warn, good)
)
files["code-metrics.json"] = metrics_json
paths = []
for name, content in files.items():
path = output_dir / name
path.write_text(content, encoding="utf-8", newline="\n")
paths.append(path)
return paths
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("--metrics", required=True, help="JSON written by count_loc.py")
parser.add_argument("--output-dir", required=True)
parser.add_argument("--coverage-report", default="")
parser.add_argument("--coverage-warn", default="60")
parser.add_argument("--coverage-good", default="80")
args = parser.parse_args(argv)
try:
warn = _threshold("coverage-warn", args.coverage_warn)
good = _threshold("coverage-good", args.coverage_good)
if warn > good:
raise ConfigurationError(
f"coverage-warn {args.coverage_warn} is above coverage-good {args.coverage_good}."
)
coverage = (
read_coverage(Path(args.coverage_report)) if args.coverage_report else None
)
metrics = json.loads(Path(args.metrics).read_text(encoding="utf-8"))
file_count, line_count = int(metrics["files"]), int(metrics["lines"])
except ConfigurationError as error:
print(f"::error::[configuration] {error}")
return 1
except (OSError, ValueError, KeyError, TypeError) as error:
print(
f"::error::[configuration] metrics file '{args.metrics}' cannot be read: {error}"
)
return 1
if file_count == 0:
print("::notice::No code files were found; the badges show 0.")
if coverage is None:
print("::notice::No coverage-report given; no coverage badge is written.")
metrics_json = json.dumps(metrics, indent=2, sort_keys=True) + "\n"
written = write_badges(
file_count,
line_count,
metrics_json,
Path(args.output_dir),
coverage,
warn,
good,
)
for path in written:
print(f"wrote {path}")
return 0
if __name__ == "__main__":
sys.exit(main())
+14 -8
View File
@@ -1,4 +1,4 @@
"""Build the English wiki pages in ``wiki/`` from ``README.md`` and ``guides/``. """Build the English wiki pages in ``docs/wiki/`` from ``README.md`` and ``guides/``.
The wiki source lives in this repository (MIL-005). The English pages are The wiki source lives in this repository (MIL-005). The English pages are
generated, never edited by hand; ``--check`` reports a page that differs from generated, never edited by hand; ``--check`` reports a page that differs from
@@ -18,7 +18,7 @@ from dataclasses import dataclass
from pathlib import Path from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parent.parent REPO_ROOT = Path(__file__).resolve().parent.parent
WIKI_DIR = REPO_ROOT / "wiki" WIKI_DIR = REPO_ROOT / "docs" / "wiki"
SOURCE_BASE_URL = "https://git.tirsystem.com/TirSystem/github-action/src/branch/main" SOURCE_BASE_URL = "https://git.tirsystem.com/TirSystem/github-action/src/branch/main"
GENERATOR = "tools/build_wiki.py" GENERATOR = "tools/build_wiki.py"
@@ -44,11 +44,14 @@ PAGES: tuple[Page, ...] = (
Page("Credentials", "guides/credentials.md"), Page("Credentials", "guides/credentials.md"),
Page("Troubleshooting", "guides/troubleshooting.md"), Page("Troubleshooting", "guides/troubleshooting.md"),
Page("QualityChecksPythonShell", "guides/quality-checks.md"), Page("QualityChecksPythonShell", "guides/quality-checks.md"),
Page("QualityChecksCpp", "guides/quality-checks-cpp.md"),
Page("QualityChecksCSharp", "guides/quality-checks-csharp.md"),
Page("CodeMetrics", "guides/code-metrics.md"),
Page("Upgrading", "guides/upgrading.md"), Page("Upgrading", "guides/upgrading.md"),
Page("Capabilities", "README.md", ("Capabilities",)), Page("Capabilities", "README.md", ("Capabilities",)),
) )
# Translations (ADR-0001): `<Page>.<code>` written by hand in wiki/. # Translations (ADR-0001): `<Page>.<code>` written by hand in docs/wiki/.
LANGUAGES: tuple[tuple[str, str], ...] = (("da", "Dansk"), ("ms", "Bahasa Melayu")) LANGUAGES: tuple[tuple[str, str], ...] = (("da", "Dansk"), ("ms", "Bahasa Melayu"))
HASH_LENGTH = 12 HASH_LENGTH = 12
_TRANSLATION_HEADER = re.compile( _TRANSLATION_HEADER = re.compile(
@@ -62,6 +65,9 @@ _PAGE_FOR_SOURCE = {
"guides/credentials.md": "Credentials", "guides/credentials.md": "Credentials",
"guides/troubleshooting.md": "Troubleshooting", "guides/troubleshooting.md": "Troubleshooting",
"guides/quality-checks.md": "QualityChecksPythonShell", "guides/quality-checks.md": "QualityChecksPythonShell",
"guides/quality-checks-cpp.md": "QualityChecksCpp",
"guides/quality-checks-csharp.md": "QualityChecksCSharp",
"guides/code-metrics.md": "CodeMetrics",
"guides/upgrading.md": "Upgrading", "guides/upgrading.md": "Upgrading",
} }
@@ -187,9 +193,9 @@ def render_page(page: Page, repo_root: Path = REPO_ROOT) -> str:
# Link text of the sidebar: English, then one list per translation code. # Link text of the sidebar: English, then one list per translation code.
SIDEBAR_TITLE = "TirSystem GitHub Actions" SIDEBAR_TITLE = "TirSystem GitHub Actions"
SIDEBAR_LABELS: dict[str, tuple[str, ...]] = { SIDEBAR_LABELS: dict[str, tuple[str, ...]] = {
"en": ("Home", "Onboarding", "Credentials", "Troubleshooting", "Quality checks (Python, shell)", "Upgrading", "Capabilities"), "en": ("Home", "Onboarding", "Credentials", "Troubleshooting", "Quality checks (Python, shell)", "Quality checks (C and C++)", "Quality checks (C#)", "Code metrics", "Upgrading", "Capabilities"),
"da": ("Hjem", "Tilslutning", "Credentials", "Fejlfinding", "Kvalitetskontrol (Python, shell)", "Opgradering", "Kapabiliteter"), "da": ("Hjem", "Tilslutning", "Credentials", "Fejlfinding", "Kvalitetskontrol (Python, shell)", "Kvalitetskontrol (C og C++)", "Kvalitetskontrol (C#)", "Kodemålinger", "Opgradering", "Kapabiliteter"),
"ms": ("Utama", "Menyertakan", "Kelayakan", "Diagnosis", "Semakan kualiti (Python, shell)", "Menaik taraf", "Keupayaan"), "ms": ("Utama", "Menyertakan", "Kelayakan", "Diagnosis", "Semakan kualiti (Python, shell)", "Semakan kualiti (C dan C++)", "Semakan kualiti (C#)", "Metrik kod", "Menaik taraf", "Keupayaan"),
} }
SIDEBAR_NAME = "_Sidebar.md" SIDEBAR_NAME = "_Sidebar.md"
@@ -300,9 +306,9 @@ def main(argv: list[str] | None = None) -> int:
if args.check: if args.check:
problems = check_pages() + check_translations() problems = check_pages() + check_translations()
for problem in problems: for problem in problems:
print(f"ERROR: wiki/{problem}", file=sys.stderr) print(f"ERROR: docs/wiki/{problem}", file=sys.stderr)
if problems: if problems:
print(f"Run `python {GENERATOR}` and commit wiki/.", file=sys.stderr) print(f"Run `python {GENERATOR}` and commit docs/wiki/.", file=sys.stderr)
return 1 return 1
print("Wiki pages are up to date.") print("Wiki pages are up to date.")
return 0 return 0
+137
View File
@@ -0,0 +1,137 @@
#!/usr/bin/env bash
# Steps of the reusable workflow .gitea/workflows/code-metrics.yml (ADR-0010).
# The workflow checks out the calling repository, fetches this file into
# shared-scripts/ and runs one command per step:
#
# code-metrics.sh validate check the inputs, before anything is counted
# code-metrics.sh measure count the code, read the coverage report and
# write the badge files (count_loc.py, badges.py)
# code-metrics.sh publish commit the badge files to the badges branch,
# and nothing when none of them changed
#
# Settings come from the environment, never from interpolated expressions:
# WORKING_DIRECTORY, EXCLUDE, COVERAGE_REPORT, COVERAGE_ARTIFACT,
# COVERAGE_WARN, COVERAGE_GOOD, BADGES_BRANCH, BADGES_OUT (the directory of the badge files; a temporary
# directory under RUNNER_TEMP by default).
# Every failure is printed as ::error::[category] message.
TOOLS_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUT_DIR="${BADGES_OUT:-${RUNNER_TEMP:-/tmp}/code-metrics}"
ALLOWED_FILES='^(code-files\.svg|code-lines\.svg|coverage\.svg|code-metrics\.json)$'
COMMIT_MESSAGE="Update code metrics badges [skip ci]"
fail() {
echo "::error::[configuration] $1"
exit 1
}
relative_path() {
case "$1" in
"" | /* | .. | ../* | */.. | */../*) return 1 ;;
esac
}
validate_inputs() {
set -u
relative_path "$WORKING_DIRECTORY" ||
fail "working-directory must be a relative path inside the repository."
[ -d "$WORKING_DIRECTORY" ] ||
fail "working-directory '$WORKING_DIRECTORY' does not exist."
if [ -n "$COVERAGE_REPORT" ]; then
relative_path "$COVERAGE_REPORT" ||
fail "coverage-report must be a relative path inside the repository."
fi
if [ -n "$COVERAGE_ARTIFACT" ] && [ -z "$COVERAGE_REPORT" ]; then
fail "coverage-artifact '$COVERAGE_ARTIFACT' needs coverage-report, the file inside the artifact."
fi
case "$BADGES_BRANCH" in
main | master | HEAD | "") fail "badges-branch '$BADGES_BRANCH' must not be a default branch." ;;
esac
git check-ref-format --branch "$BADGES_BRANCH" > /dev/null 2>&1 ||
fail "badges-branch '$BADGES_BRANCH' is not a valid branch name."
echo "Counting ${WORKING_DIRECTORY}; badges go to the branch ${BADGES_BRANCH}"
}
measure() {
set -eu
metrics="$(mktemp)"
python3 "$TOOLS_DIR/count_loc.py" \
--directory "$WORKING_DIRECTORY" --exclude "$EXCLUDE" --output "$metrics"
rm -rf "$OUT_DIR"
report="$COVERAGE_REPORT"
if [ -n "$COVERAGE_ARTIFACT" ]; then
report="coverage-artifact/$COVERAGE_REPORT"
fi
python3 "$TOOLS_DIR/badges.py" \
--metrics "$metrics" --output-dir "$OUT_DIR" \
--coverage-report "$report" \
--coverage-warn "$COVERAGE_WARN" --coverage-good "$COVERAGE_GOOD"
rm -f "$metrics"
}
# Copy the badge files into the worktree and stage them; return 0 when
# something differs from the branch.
stage_files() {
local worktree="$1" file
for file in "$OUT_DIR"/*; do
cp "$file" "$worktree/$(basename "$file")"
done
git -C "$worktree" add -- .
! git -C "$worktree" diff --cached --quiet
}
commit_files() {
git -C "$1" -c user.name="code-metrics" -c user.email="code-metrics@invalid" \
commit --quiet -m "$COMMIT_MESSAGE"
}
publish() {
set -eu
[ -d "$OUT_DIR" ] || fail "no badge files in '$OUT_DIR'; run measure first."
local worktree exists=0 committed=0 attempt
worktree="$(mktemp -d)"
if git fetch --quiet origin "+refs/heads/$BADGES_BRANCH:refs/remotes/origin/$BADGES_BRANCH" 2> /dev/null; then
exists=1
if git ls-tree -r --name-only "origin/$BADGES_BRANCH" | grep -qvE "$ALLOWED_FILES"; then
fail "branch '$BADGES_BRANCH' holds files that are not badge files; it is not a badges branch."
fi
git worktree add --quiet --detach "$worktree" "origin/$BADGES_BRANCH"
else
git worktree add --quiet --detach "$worktree" HEAD
git -C "$worktree" checkout --quiet --orphan "$BADGES_BRANCH"
git -C "$worktree" rm -rfq --ignore-unmatch .
fi
for attempt in 1 2; do
if [ "$committed" -eq 0 ]; then
if stage_files "$worktree"; then
commit_files "$worktree"
committed=1
elif [ "$exists" -eq 1 ]; then
echo "Badges are current; nothing to commit."
break
fi
fi
if git -C "$worktree" push --quiet origin "HEAD:refs/heads/$BADGES_BRANCH"; then
echo "Published the badges to the branch ${BADGES_BRANCH}."
break
fi
[ "$attempt" -lt 2 ] || fail "the push to '$BADGES_BRANCH' failed twice."
if git fetch --quiet origin "+refs/heads/$BADGES_BRANCH:refs/remotes/origin/$BADGES_BRANCH" 2> /dev/null; then
git -C "$worktree" reset --quiet --hard "origin/$BADGES_BRANCH"
exists=1
committed=0
fi
done
git worktree remove --force "$worktree"
}
case "${1:-}" in
validate) validate_inputs ;;
measure) measure ;;
publish) publish ;;
*)
echo "::error::[configuration] usage: code-metrics.sh validate|measure|publish" >&2
exit 2
;;
esac
+375
View File
@@ -0,0 +1,375 @@
"""Count the code files and the lines of code of a directory (ADR-0010).
A code file is a text file whose extension is in LANGUAGES. Every physical line
of it is exactly one of blank, comment or code; the lines of code are the code
lines only. The result is JSON:
{"files": 3, "lines": 120, "comment": 14, "blank": 22,
"languages": {"Python": {"files": 2, "lines": 100, "comment": 10, "blank": 20}}}
Usage:
python tools/count_loc.py [--directory DIR] [--exclude PATTERNS] [--output FILE]
Exit codes: 0 counted, 1 the directory is not a directory (printed as
`::error::[configuration] ...`).
"""
import argparse
import json
import os
import re
import sys
from dataclasses import dataclass
from pathlib import Path
DEFAULT_EXCLUDED_DIRS = frozenset(
{
"node_modules",
"vendor",
"third_party",
"dist",
"build",
"out",
"bin",
"obj",
"target",
"venv",
"__pycache__",
"shared-scripts",
}
)
_C_STYLE_CHAR = re.compile(r"'(?:\\.[^']*|[^'\\])'")
@dataclass(frozen=True)
class Language:
"""The comment and string rules of one language."""
name: str
extensions: tuple[str, ...]
line_comment: str | None = None
block: tuple[str, str] | None = None
nested_block: bool = False
hash_at_word_start: bool = False
triple_quotes: bool = False
backtick_strings: bool = False
single_quote: str = (
"string" # "string", "char" (a literal of one character) or "none"
)
backslash_escapes: bool = True
escapes_in_single_quotes: bool = True
def _c_family(name: str, *extensions: str, single_quote: str = "char") -> Language:
return Language(
name,
extensions,
line_comment="//",
block=("/*", "*/"),
single_quote=single_quote,
)
LANGUAGES: tuple[Language, ...] = (
Language("Python", (".py",), line_comment="#", triple_quotes=True),
Language(
"Shell",
(".sh", ".bash"),
line_comment="#",
hash_at_word_start=True,
escapes_in_single_quotes=False,
),
Language(
"PowerShell",
(".ps1", ".psm1"),
line_comment="#",
block=("<#", "#>"),
hash_at_word_start=True,
backslash_escapes=False,
),
_c_family("C", ".c", ".h"),
_c_family("C++", ".cc", ".cpp", ".cxx", ".hh", ".hpp"),
_c_family("C#", ".cs"),
_c_family("Java", ".java"),
Language(
"JavaScript",
(".js", ".mjs", ".jsx"),
line_comment="//",
block=("/*", "*/"),
backtick_strings=True,
),
Language(
"TypeScript",
(".ts", ".tsx"),
line_comment="//",
block=("/*", "*/"),
backtick_strings=True,
),
Language(
"Go",
(".go",),
line_comment="//",
block=("/*", "*/"),
backtick_strings=True,
single_quote="char",
),
Language(
"Rust",
(".rs",),
line_comment="//",
block=("/*", "*/"),
nested_block=True,
single_quote="char",
),
Language(
"SQL",
(".sql",),
line_comment="--",
block=("/*", "*/"),
backslash_escapes=False,
),
)
_BY_EXTENSION = {ext: language for language in LANGUAGES for ext in language.extensions}
_WORD_START = " \t;|&("
def language_of(path: str) -> Language | None:
"""Return the language of a file name by its extension, or None."""
return _BY_EXTENSION.get(os.path.splitext(path)[1].lower())
@dataclass
class _State:
block_depth: int = 0
triple: str | None = None
backtick: bool = False
def _skip_string(line: str, start: int, quote: str, escapes: bool) -> int:
"""Return the index after the string that opens at start; the end of line if unclosed."""
i = start + 1
while i < len(line):
char = line[i]
if escapes and char == "\\":
i += 2
continue
if char == quote:
return i + 1
i += 1
return len(line)
def _scan_line(line: str, language: Language, state: _State) -> tuple[bool, bool]:
"""Scan one line; return (has_code, has_comment) and update the state."""
has_code = False
has_comment = False
i = 0
size = len(line)
block = language.block
while i < size:
if state.block_depth:
assert block is not None
has_comment = True
end = line.find(block[1], i)
nested = line.find(block[0], i) if language.nested_block else -1
if nested != -1 and (end == -1 or nested < end):
state.block_depth += 1
i = nested + len(block[0])
elif end == -1:
return has_code, has_comment
else:
state.block_depth -= 1
i = end + len(block[1])
continue
if state.triple is not None:
has_code = True
end = line.find(state.triple, i)
if end == -1:
return has_code, has_comment
i = end + len(state.triple)
state.triple = None
continue
if state.backtick:
has_code = True
end = line.find("`", i)
if end == -1:
return has_code, has_comment
i = end + 1
state.backtick = False
continue
char = line[i]
if char in " \t\r\f\v":
i += 1
continue
if block is not None and line.startswith(block[0], i):
state.block_depth = 1
has_comment = True
i += len(block[0])
continue
comment = language.line_comment
if comment is not None and line.startswith(comment, i):
word_start = i == 0 or line[i - 1] in _WORD_START
if word_start or not language.hash_at_word_start:
return has_code, True
if language.triple_quotes and (
line.startswith('"""', i) or line.startswith("'''", i)
):
state.triple = line[i : i + 3]
has_code = True
i += 3
continue
if char == '"':
has_code = True
i = _skip_string(line, i, '"', language.backslash_escapes)
continue
if char == "'" and language.single_quote == "string":
has_code = True
escapes = language.backslash_escapes and language.escapes_in_single_quotes
i = _skip_string(line, i, "'", escapes)
continue
if char == "'" and language.single_quote == "char":
has_code = True
literal = _C_STYLE_CHAR.match(line, i)
i = literal.end() if literal else i + 1
continue
if char == "`" and language.backtick_strings:
has_code = True
end = line.find("`", i + 1)
if end == -1:
state.backtick = True
return has_code, has_comment
i = end + 1
continue
has_code = True
i += 1
return has_code, has_comment
def classify_text(text: str, language: Language) -> tuple[int, int, int]:
"""Return (code, comment, blank) line counts of the text of a file."""
lines = text.replace("\r\n", "\n").replace("\r", "\n").split("\n")
if lines and lines[-1] == "":
lines.pop()
state = _State()
code = comment = blank = 0
for line in lines:
has_code, _ = _scan_line(line, language, state)
if line.strip() == "":
blank += 1
elif has_code:
code += 1
else:
comment += 1
return code, comment, blank
def _pattern_regex(pattern: str) -> re.Pattern[str]:
"""Translate an exclude pattern: `*` stops at `/`, `**` crosses it, `?` is one character."""
out = []
i = 0
while i < len(pattern):
char = pattern[i]
if pattern.startswith("**", i):
out.append(".*")
i += 2
continue
out.append(
"[^/]*" if char == "*" else "[^/]" if char == "?" else re.escape(char)
)
i += 1
return re.compile("".join(out) + r"\Z")
class Excludes:
"""The exclude patterns of the caller; a pattern without `/` matches a name anywhere."""
def __init__(self, patterns: list[str]) -> None:
self._names: list[re.Pattern[str]] = []
self._paths: list[re.Pattern[str]] = []
for pattern in patterns:
pattern = pattern.strip().strip("/")
if not pattern:
continue
target = self._paths if "/" in pattern else self._names
target.append(_pattern_regex(pattern))
def matches(self, name: str, relative: str) -> bool:
return any(rx.match(name) for rx in self._names) or any(
rx.match(relative) for rx in self._paths
)
def _is_text(data: bytes) -> bool:
return b"\x00" not in data
def count(directory: Path, patterns: list[str] | None = None) -> dict[str, object]:
"""Count the code files under directory; see the module docstring for the result."""
excludes = Excludes(patterns or [])
totals = {"files": 0, "lines": 0, "comment": 0, "blank": 0}
per_language: dict[str, dict[str, int]] = {}
root = str(directory)
for current, dirs, files in os.walk(root, followlinks=False):
relative_dir = os.path.relpath(current, root).replace(os.sep, "/")
prefix = "" if relative_dir == "." else relative_dir + "/"
dirs[:] = sorted(
name
for name in dirs
if not name.startswith(".")
and name not in DEFAULT_EXCLUDED_DIRS
and not excludes.matches(name, prefix + name)
)
for name in sorted(files):
full = os.path.join(current, name)
language = language_of(name)
if (
language is None
or os.path.islink(full)
or excludes.matches(name, prefix + name)
):
continue
data = Path(full).read_bytes()
if not _is_text(data):
continue
text = data.decode("utf-8", errors="replace").lstrip("")
code, comment, blank = classify_text(text, language)
entry = per_language.setdefault(
language.name, {"files": 0, "lines": 0, "comment": 0, "blank": 0}
)
for target in (entry, totals):
target["files"] += 1
target["lines"] += code
target["comment"] += comment
target["blank"] += blank
return {**totals, "languages": dict(sorted(per_language.items()))}
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument(
"--directory", default=".", help="directory to count (default .)"
)
parser.add_argument("--exclude", default="", help="comma list of exclude patterns")
parser.add_argument(
"--output", help="write the JSON here instead of standard output"
)
args = parser.parse_args(argv)
directory = Path(args.directory)
if not directory.is_dir():
print(
f"::error::[configuration] working-directory '{args.directory}' does not exist."
)
return 1
result = count(directory, [p for p in args.exclude.split(",") if p.strip()])
text = json.dumps(result, indent=2, sort_keys=True) + "\n"
if args.output:
Path(args.output).write_text(text, encoding="utf-8", newline="\n")
else:
sys.stdout.write(text)
return 0
if __name__ == "__main__":
sys.exit(main())
+22 -28
View File
@@ -1,15 +1,15 @@
"""Publish the pages in wiki/ to the Gitea wiki of the repository. """Publish the pages of a folder to the Gitea wiki of the repository.
Run by the scoped workflow publish-wiki.yml, which checks out the repository Run by the reusable workflow publish-wiki-pages.yml, which a repository calls
that owns the wiki and this repository, and runs this file (MIL-004 task 6). (ADR-0009). The caller is the opt-in: the workflow sets WIKI_PRUNE and the
A repository publishes only when it has opted in with wiki/.publish folder must exist. It reads its settings from the environment:
(ADR-0004); the word prune in that file lets the run delete wiki pages that
are not in wiki/. It reads its settings from the environment:
GITEA_API_URL Gitea API base, for example https://host/api/v1 GITEA_API_URL Gitea API base, for example https://host/api/v1
SOURCE_REPOSITORY owner/repo of the repository whose wiki is written SOURCE_REPOSITORY owner/repo of the repository whose wiki is written
WIKI_CREDENTIALS the Gitea token, as clear text (TOKEN_FOR_GITEA_WIKI) WIKI_CREDENTIALS the Gitea token, as clear text (TOKEN_FOR_GITEA_WIKI)
WIKI_DIR directory with the pages (default: wiki) WIKI_DIR directory with the pages (default: docs/wiki)
WIKI_PRUNE true or false, required: delete pages that are not in
the folder
Every failure is printed as ``ERROR: [category] message`` with the categories Every failure is printed as ``ERROR: [category] message`` with the categories
configuration, credentials, permissions and api; no secret value is printed. configuration, credentials, permissions and api; no secret value is printed.
@@ -28,8 +28,7 @@ from pathlib import Path
PAGE_LIMIT = 50 PAGE_LIMIT = 50
REQUEST_TIMEOUT_SECONDS = 30 REQUEST_TIMEOUT_SECONDS = 30
COMMIT_MESSAGE = "Publish wiki from the repository" COMMIT_MESSAGE = "Publish wiki from the repository"
MARKER_NAME = ".publish" DEFAULT_WIKI_DIR = "docs/wiki"
PRUNE_WORD = "prune"
class WorkflowError(RuntimeError): class WorkflowError(RuntimeError):
@@ -77,16 +76,13 @@ def request_json(url, method="GET", headers=None, body=None):
raise WorkflowError("api", f"Gitea API is unreachable ({error}).") from None raise WorkflowError("api", f"Gitea API is unreachable ({error}).") from None
def parse_marker(text): def parse_prune_setting(text):
"""Return True when the marker asks for pruning, False when it is empty.""" """Return the boolean of the WIKI_PRUNE input: true or false, nothing else."""
word = (text or "").strip() word = (text or "").strip().lower()
if not word: if word in ("true", "false"):
return False return word == "true"
if word == PRUNE_WORD:
return True
raise WorkflowError( raise WorkflowError(
"configuration", "configuration", f"The prune input must be 'true' or 'false', not '{(text or '').strip()}'."
f"wiki/{MARKER_NAME} may only be empty or contain the word '{PRUNE_WORD}'.",
) )
@@ -240,17 +236,16 @@ def publish(pages, wiki_url, headers, send, prune):
def main(): def main():
wiki_dir = Path(os.environ.get("WIKI_DIR", "wiki")) wiki_dir = Path(os.environ.get("WIKI_DIR", DEFAULT_WIKI_DIR))
owner, repo = split_repository(os.environ.get("SOURCE_REPOSITORY")) owner, repo = split_repository(os.environ.get("SOURCE_REPOSITORY"))
marker = wiki_dir / MARKER_NAME prune = parse_prune_setting(os.environ.get("WIKI_PRUNE"))
if not marker.is_file(): if not wiki_dir.is_dir():
print( raise WorkflowError(
f"No {wiki_dir}/{MARKER_NAME} marker in {owner}/{repo}; " "configuration",
"wiki publishing is not enabled for this repository." f"The wiki folder {wiki_dir} does not exist in {owner}/{repo}; "
"set the wiki_dir input to the folder with the pages.",
) )
return 0
prune = parse_marker(marker.read_text(encoding="utf-8"))
pages = read_pages(wiki_dir) pages = read_pages(wiki_dir)
token = parse_gitea_token(os.environ.get("WIKI_CREDENTIALS")) token = parse_gitea_token(os.environ.get("WIKI_CREDENTIALS"))
@@ -292,8 +287,7 @@ def main():
if result["kept"]: if result["kept"]:
print( print(
f"{len(result['kept'])} page(s) are in the wiki but not in " f"{len(result['kept'])} page(s) are in the wiki but not in "
f"{wiki_dir}/ and were kept; put the word '{PRUNE_WORD}' in " f"{wiki_dir}/ and were kept; set the prune input to true to delete them."
f"{wiki_dir}/{MARKER_NAME} to delete them."
) )
return 0 return 0

Some files were not shown because too many files have changed in this diff Show More