feat: scaffold pre-release pipeline, refresh deps weekly - #225
Draft
ottobolyos wants to merge 15 commits into
Draft
feat: scaffold pre-release pipeline, refresh deps weekly#225ottobolyos wants to merge 15 commits into
ottobolyos wants to merge 15 commits into
Conversation
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 17, 2026
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
…ascade 20260819T134500Z)
This was referenced Aug 19, 2026
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
Replaces the placeholder v4 SHA on actions/setup-dotnet with the SHA of the v4.3.1 tag verified via `gh api repos/actions/setup-dotnet/git/ refs/tags/v4.3.1`, and updates renovatebot/github-action from an unverified SHA to the SHA of tag v43.0.7 verified via `gh api repos/renovatebot/github-action/git/refs/tags/v43.0.7`. Closes findings F-SEC-001 and F-SEC-002 from the PR TrakHound#225 review. Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
`countCommitsSinceLastDevBoundary` returned `i + 1` at both the
stable-cut-marker and dev-tag boundaries, off by one relative to the
docstring ("commits BEFORE the marker") and out of step with the
`--range` code path which counts commits after the boundary. Change
both return sites to `Math.max(1, i)` so the two paths agree on a
fixture where the marker sits at HEAD~1 (expected N = 1) and the
floor still guards against `-dev.0` when the boundary sits on HEAD.
Extends `semver-bump.test.ts` with three git-fixture cases covering
the two boundary positions plus the no-boundary fall-through, and
reworks the top-of-file algorithm summary (F-DOC-002 + F-DOC-007) so
the docstring matches the code and calls out the `applyBump`
`none → patch` fall-through that keeps a chore-only range distinct
from the last stable tag.
Closes findings F-CR-001, F-DOC-002, and F-DOC-007 from the PR TrakHound#225
review.
Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
`renovatebot/github-action` opens its own PRs per package on `renovate/*` branches, so the "one deps PR per week" invariant only ever held for the two ecosystems this workflow drives directly (npm + NuGet). The no-op "Handled by the Renovate step above" step is gone, the bulk-PR branch is renamed to `chore/deps-weekly-npm-nuget` to disambiguate it from Renovate's own branches, and the header comment + docs page now describe the two PR shapes accurately. npm bumps now pass `--cooldown "$MIN_AGE_DAYS"` (added in ncu v18), which pins the same 7-day supply-chain quarantine Renovate enforces on GH Actions + Dockerfile bases. NuGet has no equivalent (`dotnet-outdated` lacks any age filter) and the workflow header, step comment, and docs page all state this explicitly; a proper NuGet quarantine is tracked as a follow-up. Also switches the doc from Renovate's non-existent `docker` manager name to the actual `dockerfile` manager (F-DOC-005). Closes findings F-CR-002, F-CR-003, F-DOC-001, F-DOC-005, F-IMP-002, and F-SEC-005 from the PR TrakHound#225 review. Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
`buildx build --load` on the default docker driver silently strips `--sbom true` and `--provenance mode=max` — the loader only knows how to import a single-platform image manifest, not an image index. Drop both flags from `docker-build.ts` and add a comment explaining why so a future author does not re-add them. The downstream `sbom` job in `release.yml` still emits an SPDX SBOM from the pushed image, so the metadata surface is preserved without the buildx-strip warning. Swaps the docker SBOM path from `docker scout sbom` (required a `docker scout` install on every runner, produced a materially different SPDX shape) to `anchore/sbom-action` (v0.24.0, pinned by SHA verified via `gh api repos/anchore/sbom-action/git/refs/tags/ v0.24.0`). `sbom.ts` still supports docker mode locally, now via syft — the same engine anchore/sbom-action wraps — so `--dry-run` output shape agrees with CI. Docs pages updated to name the new backend. Closes findings F-CR-005 and F-CR-006 from the PR TrakHound#225 review. Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
`create-gh-release` now names `vuln-scan` on its own `needs:` list
directly instead of leaning on the transitive path through
`publish-nuget`, so a future rewire that lets publish-nuget bypass
the vuln scan (e.g. via an `if:` override) still hard-fails the
release cut (F-CR-009).
`gh-release-create.ts` gains three fixes:
- `collectAssets` now uses `readdirSync(dir, { recursive: true,
withFileTypes: true })` and filters to `.isFile()`, so nested
SBOM layouts like `_manifest/spdx_2.2/manifest.spdx.json`
(the shape `Microsoft.Sbom.DotNetTool` emits) reach the release
attachment list; the prior code only walked the top level despite
a docstring claiming one-level recursion. Nested dotfiles /
dot-directories are still skipped.
- A `--target <sha>` option pins the tag to the SHA that produced
the artefacts; the workflow now passes `${{ github.sha }}` so a
concurrent `master` push cannot race the tag onto a different
commit than the one the artefacts were built from.
- The `gh release create` invocation is now idempotent: a pre-step
swallows the "already exists" error by deleting the prior
release + tag when the workflow re-runs on the same SHA. Prior
behaviour errored on the second run and left the pipeline red.
Extends the test suite with fixtures for nested-asset discovery
(`_manifest/spdx_2.2/manifest.spdx.json`), nested-dotfile skipping,
and the new `--target` field.
Closes findings F-CR-009, F-IMP collectAssets recursion, F-IMP
idempotency, and F-IMP release-tag target from the PR TrakHound#225 review.
Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
The plugin was declared under `plugins:` in `commitlint.config.mjs` and installed in `pre-merge.yml`'s commitlint step but no rule ever referenced it — the `scope-enum` rule that gates the pinned scope list is the standard rule from `@commitlint/config-conventional`, not the plugin's selective-scope override. Removing the declaration shrinks the CI install by one dependency and shrinks the local commit-msg attack surface. Closes findings F-CR-011, F-SEC-004, and the related SIMP recommendation from the PR TrakHound#225 review. Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
`shell.ts` gains a `SECRET_ARG_NAMES` set (`--api-key`, `--password`, `-p`, `--token`) and `renderCmd()` now redacts the value that follows any of them — both the `--name value` two-token form and the `--name=value` single-token form. The actual argv passed to the child spawn is untouched; only the log line is rewritten. Guards against a CI-log line that echoes the command from leaking a credential a caller placed on the argv. `nuget-push.ts` no longer places the raw API key on the parent's argv either. The `dotnet nuget push` invocation is wrapped in `sh -c '…$NUGET_API_KEY'` with the key in the child's `env:`, so the parent process's argv holds only the shell wrapper and the literal string `$NUGET_API_KEY`. `renderCmd`'s SECRET_ARG_NAMES redaction is the second line of defence for the CI log. Adds eight new test cases pinning the two-token, equals-form, multi-secret, end-of-argv, and API-surface contracts. Closes finding F-SEC-003 from the PR TrakHound#225 review. Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
`dotnet tool install --global` without `--version` resolves to the latest published NuGet version at install-time, which lets a compromised or accidentally-broken upstream publish silently land on a release-cutting runner. Pin to the current known-good release verified via `gh api repos/microsoft/sbom-tool/releases/latest`. Closes finding F-SEC-007 from the PR TrakHound#225 review. Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
Doc corrections lifted out of the PR TrakHound#225 review: - `release-pipeline.md`: `DOCKERHUB_USERNAME` + `DOCKERHUB_TOKEN` are also consumed by `vuln-scan` (Trivy needs them to pull the image before scanning). Adds a `needs:` column to the jobs table and a mermaid `graph LR` of the same dependency graph — the two surfaces stay in step because they describe the same `release.yml` shape. - `commit-format.md`: the `header-max-length` rule measures the whole first line, not the subject alone — reword to state that plus the `body-leading-blank` + `footer-leading-blank` rules the config also enforces. - `tools-release.md`: adds a `shell.ts` section documenting the shared helper's surface (`run`, `renderCmd` w/ secret redaction, `parseDryRun`, `requireEnv`, `optionalEnv`) and reworks the opener to exclude helpers explicitly; the old text falsely claimed every script under `tools/release/` exposes a `main`. - `tools/dev/README.md` + `tools/docs/README.md`: shrunk from placeholder stubs to one-line pointers at `/reference/cli` per the "no placeholder README" project convention. Closes findings F-DOC-003, F-DOC-004, F-DOC-006, F-DOC-008, and F-DOC-010 from the PR TrakHound#225 review. Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
`tools/` gained TypeScript unit suites for `ci/*.ts` and every `release/*.ts` script (three commits earlier), but they weren't running under any workflow — the pre-merge gate only ran commitlint, and `dotnet.yml` covers only the .NET matrix. Adds a second job `unit-tests-tools` that runs `npm ci && npm run typecheck && npm test` under `tools/` so those suites participate in the required-check set. The job stays lightweight (Node.js only, no dotnet, no docker) so its wall-clock stays under a minute and it does not delay the merge on a green run. Closes the "tests exist but not wired to CI" gap flagged in the PR TrakHound#225 review. Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos
pushed a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
…-cycle-Completed 20260819T143044Z)
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 19, 2026
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
Replaces the placeholder v4 SHA on actions/setup-dotnet with the SHA of the v4.3.1 tag verified via `gh api repos/actions/setup-dotnet/git/ refs/tags/v4.3.1`, and updates renovatebot/github-action from an unverified SHA to the SHA of tag v43.0.7 verified via `gh api repos/renovatebot/github-action/git/refs/tags/v43.0.7`. Closes findings F-SEC-001 and F-SEC-002 from the PR TrakHound#225 review. Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
`countCommitsSinceLastDevBoundary` returned `i + 1` at both the
stable-cut-marker and dev-tag boundaries, off by one relative to the
docstring ("commits BEFORE the marker") and out of step with the
`--range` code path which counts commits after the boundary. Change
both return sites to `Math.max(1, i)` so the two paths agree on a
fixture where the marker sits at HEAD~1 (expected N = 1) and the
floor still guards against `-dev.0` when the boundary sits on HEAD.
Extends `semver-bump.test.ts` with three git-fixture cases covering
the two boundary positions plus the no-boundary fall-through, and
reworks the top-of-file algorithm summary (F-DOC-002 + F-DOC-007) so
the docstring matches the code and calls out the `applyBump`
`none → patch` fall-through that keeps a chore-only range distinct
from the last stable tag.
Closes findings F-CR-001, F-DOC-002, and F-DOC-007 from the PR TrakHound#225
review.
Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
`renovatebot/github-action` opens its own PRs per package on `renovate/*` branches, so the "one deps PR per week" invariant only ever held for the two ecosystems this workflow drives directly (npm + NuGet). The no-op "Handled by the Renovate step above" step is gone, the bulk-PR branch is renamed to `chore/deps-weekly-npm-nuget` to disambiguate it from Renovate's own branches, and the header comment + docs page now describe the two PR shapes accurately. npm bumps now pass `--cooldown "$MIN_AGE_DAYS"` (added in ncu v18), which pins the same 7-day supply-chain quarantine Renovate enforces on GH Actions + Dockerfile bases. NuGet has no equivalent (`dotnet-outdated` lacks any age filter) and the workflow header, step comment, and docs page all state this explicitly; a proper NuGet quarantine is tracked as a follow-up. Also switches the doc from Renovate's non-existent `docker` manager name to the actual `dockerfile` manager (F-DOC-005). Closes findings F-CR-002, F-CR-003, F-DOC-001, F-DOC-005, F-IMP-002, and F-SEC-005 from the PR TrakHound#225 review. Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
`buildx build --load` on the default docker driver silently strips `--sbom true` and `--provenance mode=max` — the loader only knows how to import a single-platform image manifest, not an image index. Drop both flags from `docker-build.ts` and add a comment explaining why so a future author does not re-add them. The downstream `sbom` job in `release.yml` still emits an SPDX SBOM from the pushed image, so the metadata surface is preserved without the buildx-strip warning. Swaps the docker SBOM path from `docker scout sbom` (required a `docker scout` install on every runner, produced a materially different SPDX shape) to `anchore/sbom-action` (v0.24.0, pinned by SHA verified via `gh api repos/anchore/sbom-action/git/refs/tags/ v0.24.0`). `sbom.ts` still supports docker mode locally, now via syft — the same engine anchore/sbom-action wraps — so `--dry-run` output shape agrees with CI. Docs pages updated to name the new backend. Closes findings F-CR-005 and F-CR-006 from the PR TrakHound#225 review. Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos
force-pushed
the
feat/release-automation-phase1
branch
from
August 21, 2026 06:18
f1af97f to
9a8d456
Compare
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
`create-gh-release` now names `vuln-scan` on its own `needs:` list
directly instead of leaning on the transitive path through
`publish-nuget`, so a future rewire that lets publish-nuget bypass
the vuln scan (e.g. via an `if:` override) still hard-fails the
release cut (F-CR-009).
`gh-release-create.ts` gains three fixes:
- `collectAssets` now uses `readdirSync(dir, { recursive: true,
withFileTypes: true })` and filters to `.isFile()`, so nested
SBOM layouts like `_manifest/spdx_2.2/manifest.spdx.json`
(the shape `Microsoft.Sbom.DotNetTool` emits) reach the release
attachment list; the prior code only walked the top level despite
a docstring claiming one-level recursion. Nested dotfiles /
dot-directories are still skipped.
- A `--target <sha>` option pins the tag to the SHA that produced
the artefacts; the workflow now passes `${{ github.sha }}` so a
concurrent `master` push cannot race the tag onto a different
commit than the one the artefacts were built from.
- The `gh release create` invocation is now idempotent: a pre-step
swallows the "already exists" error by deleting the prior
release + tag when the workflow re-runs on the same SHA. Prior
behaviour errored on the second run and left the pipeline red.
Extends the test suite with fixtures for nested-asset discovery
(`_manifest/spdx_2.2/manifest.spdx.json`), nested-dotfile skipping,
and the new `--target` field.
Closes findings F-CR-009, F-IMP collectAssets recursion, F-IMP
idempotency, and F-IMP release-tag target from the PR TrakHound#225 review.
Claude-Session: https://claude.ai/code/session_0162RfaA55VT8NX6QfU7RUVo
ottobolyos
force-pushed
the
feat/release-automation-phase1
branch
from
August 21, 2026 14:08
3620a7a to
f3992e3
Compare
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
Replaces the placeholder v4 SHA on actions/setup-dotnet with the SHA of the v4.3.1 tag verified via `gh api repos/actions/setup-dotnet/git/ refs/tags/v4.3.1`, and updates renovatebot/github-action from an unverified SHA to the SHA of tag v43.0.7 verified via `gh api repos/renovatebot/github-action/git/refs/tags/v43.0.7`. Closes findings F-SEC-001 and F-SEC-002 from the PR TrakHound#225 review.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
`countCommitsSinceLastDevBoundary` returned `i + 1` at both the
stable-cut-marker and dev-tag boundaries, off by one relative to the
docstring ("commits BEFORE the marker") and out of step with the
`--range` code path which counts commits after the boundary. Change
both return sites to `Math.max(1, i)` so the two paths agree on a
fixture where the marker sits at HEAD~1 (expected N = 1) and the
floor still guards against `-dev.0` when the boundary sits on HEAD.
Extends `semver-bump.test.ts` with three git-fixture cases covering
the two boundary positions plus the no-boundary fall-through, and
reworks the top-of-file algorithm summary (F-DOC-002 + F-DOC-007) so
the docstring matches the code and calls out the `applyBump`
`none → patch` fall-through that keeps a chore-only range distinct
from the last stable tag.
Closes findings F-CR-001, F-DOC-002, and F-DOC-007 from the PR TrakHound#225
review.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
`renovatebot/github-action` opens its own PRs per package on `renovate/*` branches, so the "one deps PR per week" invariant only ever held for the two ecosystems this workflow drives directly (npm + NuGet). The no-op "Handled by the Renovate step above" step is gone, the bulk-PR branch is renamed to `chore/deps-weekly-npm-nuget` to disambiguate it from Renovate's own branches, and the header comment + docs page now describe the two PR shapes accurately. npm bumps now pass `--cooldown "$MIN_AGE_DAYS"` (added in ncu v18), which pins the same 7-day supply-chain quarantine Renovate enforces on GH Actions + Dockerfile bases. NuGet has no equivalent (`dotnet-outdated` lacks any age filter) and the workflow header, step comment, and docs page all state this explicitly; a proper NuGet quarantine is tracked as a follow-up. Also switches the doc from Renovate's non-existent `docker` manager name to the actual `dockerfile` manager (F-DOC-005). Closes findings F-CR-002, F-CR-003, F-DOC-001, F-DOC-005, F-IMP-002, and F-SEC-005 from the PR TrakHound#225 review.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
`buildx build --load` on the default docker driver silently strips `--sbom true` and `--provenance mode=max` — the loader only knows how to import a single-platform image manifest, not an image index. Drop both flags from `docker-build.ts` and add a comment explaining why so a future author does not re-add them. The downstream `sbom` job in `release.yml` still emits an SPDX SBOM from the pushed image, so the metadata surface is preserved without the buildx-strip warning. Swaps the docker SBOM path from `docker scout sbom` (required a `docker scout` install on every runner, produced a materially different SPDX shape) to `anchore/sbom-action` (v0.24.0, pinned by SHA verified via `gh api repos/anchore/sbom-action/git/refs/tags/ v0.24.0`). `sbom.ts` still supports docker mode locally, now via syft — the same engine anchore/sbom-action wraps — so `--dry-run` output shape agrees with CI. Docs pages updated to name the new backend. Closes findings F-CR-005 and F-CR-006 from the PR TrakHound#225 review.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
`create-gh-release` now names `vuln-scan` on its own `needs:` list
directly instead of leaning on the transitive path through
`publish-nuget`, so a future rewire that lets publish-nuget bypass
the vuln scan (e.g. via an `if:` override) still hard-fails the
release cut (F-CR-009).
`gh-release-create.ts` gains three fixes:
- `collectAssets` now uses `readdirSync(dir, { recursive: true,
withFileTypes: true })` and filters to `.isFile()`, so nested
SBOM layouts like `_manifest/spdx_2.2/manifest.spdx.json`
(the shape `Microsoft.Sbom.DotNetTool` emits) reach the release
attachment list; the prior code only walked the top level despite
a docstring claiming one-level recursion. Nested dotfiles /
dot-directories are still skipped.
- A `--target <sha>` option pins the tag to the SHA that produced
the artefacts; the workflow now passes `${{ github.sha }}` so a
concurrent `master` push cannot race the tag onto a different
commit than the one the artefacts were built from.
- The `gh release create` invocation is now idempotent: a pre-step
swallows the "already exists" error by deleting the prior
release + tag when the workflow re-runs on the same SHA. Prior
behavior errored on the second run and left the pipeline red.
Extends the test suite with fixtures for nested-asset discovery
(`_manifest/spdx_2.2/manifest.spdx.json`), nested-dotfile skipping,
and the new `--target` field.
Closes findings F-CR-009, F-IMP collectAssets recursion, F-IMP
idempotency, and F-IMP release-tag target from the PR TrakHound#225 review.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
The plugin was declared under `plugins:` in `commitlint.config.mjs` and installed in `pre-merge.yml`'s commitlint step but no rule ever referenced it — the `scope-enum` rule that gates the pinned scope list is the standard rule from `@commitlint/config-conventional`, not the plugin's selective-scope override. Removing the declaration shrinks the CI install by one dependency and shrinks the local commit-msg attack surface. Closes findings F-CR-011, F-SEC-004, and the related SIMP recommendation from the PR TrakHound#225 review.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
`shell.ts` gains a `SECRET_ARG_NAMES` set (`--api-key`, `--password`, `-p`, `--token`) and `renderCmd()` now redacts the value that follows any of them — both the `--name value` two-token form and the `--name=value` single-token form. The actual argv passed to the child spawn is untouched; only the log line is rewritten. Guards against a CI-log line that echoes the command from leaking a credential a caller placed on the argv. `nuget-push.ts` no longer places the raw API key on the parent's argv either. The `dotnet nuget push` invocation is wrapped in `sh -c '…$NUGET_API_KEY'` with the key in the child's `env:`, so the parent process's argv holds only the shell wrapper and the literal string `$NUGET_API_KEY`. `renderCmd`'s SECRET_ARG_NAMES redaction is the second line of defense for the CI log. Adds eight new test cases pinning the two-token, equals-form, multi-secret, end-of-argv, and API-surface contracts. Closes finding F-SEC-003 from the PR TrakHound#225 review.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
`dotnet tool install --global` without `--version` resolves to the latest published NuGet version at install-time, which lets a compromised or accidentally-broken upstream publish silently land on a release-cutting runner. Pin to the current known-good release verified via `gh api repos/microsoft/sbom-tool/releases/latest`. Closes finding F-SEC-007 from the PR TrakHound#225 review.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
Doc corrections lifted out of the PR TrakHound#225 review: - `release-pipeline.md`: `DOCKERHUB_USERNAME` + `DOCKERHUB_TOKEN` are also consumed by `vuln-scan` (Trivy needs them to pull the image before scanning). Adds a `needs:` column to the jobs table and a mermaid `graph LR` of the same dependency graph — the two surfaces stay in step because they describe the same `release.yml` shape. - `commit-format.md`: the `header-max-length` rule measures the whole first line, not the subject alone — reword to state that plus the `body-leading-blank` + `footer-leading-blank` rules the config also enforces. - `tools-release.md`: adds a `shell.ts` section documenting the shared helper's surface (`run`, `renderCmd` w/ secret redaction, `parseDryRun`, `requireEnv`, `optionalEnv`) and reworks the opener to exclude helpers explicitly; the old text falsely claimed every script under `tools/release/` exposes a `main`. - `tools/dev/README.md` + `tools/docs/README.md`: shrunk from placeholder stubs to one-line pointers at `/reference/cli` per the "no placeholder README" project convention. Closes findings F-DOC-003, F-DOC-004, F-DOC-006, F-DOC-008, and F-DOC-010 from the PR TrakHound#225 review.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
`tools/` gained TypeScript unit suites for `ci/*.ts` and every `release/*.ts` script (three commits earlier), but they weren't running under any workflow — the pre-merge gate only ran commitlint, and `dotnet.yml` covers only the .NET matrix. Adds a second job `unit-tests-tools` that runs `npm ci && npm run typecheck && npm test` under `tools/` so those suites participate in the required-check set. The job stays lightweight (Node.js only, no dotnet, no docker) so its wall-clock stays under a minute and it does not delay the merge on a green run. Closes the "tests exist but not wired to CI" gap flagged in the PR TrakHound#225 review.
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
Introduces the phase-1 shape approved on Discussion TrakHound#175. Every push to master will cut a `<version>-dev.<N>` pre-release: `dotnet pack`, a native-per-arch Docker build, SPDX SBOMs, Trivy vuln scan, nuget.org publish, Docker Hub push, and a GitHub pre-release. A separate weekly workflow bumps every dependency (GitHub Actions, Dockerfile bases, npm under docs/, NuGet across every .csproj) subject to a seven-day supply-chain quarantine. A pre-merge commitlint gate is added to enforce Conventional Commits on every PR — the promise the release pipeline's semver-bump relies on. `lefthook.yml` replays the check client-side so a broken commit never leaves the workstation. Docker image signing, .nupkg signing, and stable-release automation are explicitly out of scope for phase 1 — no placeholders, no scaffolding. The existing `MTConnect.NET.Builder` flow is untouched.
Extend the in-house `test()`-shim suite from 20 to 31 assertions to pin
every branch documented in the semver-bump.ts docstrings that the
initial pass under-covered.
Additions cover:
- bumpKindFor: every documented patch type (`refactor`, `style`,
`perf`, `test`, `ci`, `revert`), unknown conventional-shaped types
that must fall through to `none`, case-insensitivity across the
type + bang arms, and every BREAKING-CHANGE-anchor variant
(start-of-body vs post-newline, hyphenated vs spaced, lower vs
mixed case, mid-word rejection).
- aggregateBump: the all-`none` fall-through arm distinct from empty.
- applyBump: v-prefix strip for every kind × prefix combination, and
the `''` / bare-`v` invalid-input arms of the throw path.
- isStableCutMarker: the subject.trim() branch, case-sensitivity on
the marker literal, and PR-suffix well-formedness.
- isDevTag: the boundary rejects (`-dev` no counter, trailing dot,
non-numeric counter, missing `v` prefix, leading whitespace) and
the zero-counter acceptance.
RED-verified each new-test class by temporarily mutating the
corresponding SUT line and observing the failure before restoring:
- drop `toLowerCase()` → case-insensitive test failed;
- collapse `(^|\n)` → `^` → BREAKING newline-anchor test failed;
- remove `refactor` from the known set → patch-type test failed;
- drop `$` from the isDevTag regex → boundary test failed;
- drop `.trim()` from isStableCutMarker → whitespace test failed.
SUT restored bit-for-bit before commit.
`tools/release/shell.ts` shipped with zero unit tests despite being the
shared shell-out layer every release script imports. Land a 20-assertion
suite that mirrors the in-house `test()`-shim shape used by
`tools/ci/semver-bump.test.ts`, so the tools/ dir has one runner
convention across both directories.
Coverage:
- renderCmd: safe-charset pass-through, whitespace-triggered quoting,
interior-double-quote escape, shell-special-char coverage
(`$`, `;`, `|`, `*`, space, empty string), empty argv.
- parseDryRun: presence, absence, empty argv, positional
invariance (front/middle/end), multiple occurrences, and the
intentional `--dry-run=true` non-recognition.
- requireEnv: present, absent (throws with var name), empty and
whitespace-only values treated as missing.
- optionalEnv: present, absent (returns undefined), empty and
whitespace-only values treated as undefined.
- run: dry-run path only — logs the rendered command via
process.stdout.write and returns without spawning. Live-spawn path
stays in the integration matrix.
Test-shim refactored from immediate-execute to a collect-then-await
main() so async cases (the two `run` dry-run assertions) do not
silently drop before the runtime exits.
RED-verified: widening the `quoteForLog` whitelist to include a space
made the whitespace-quoting test fail; loosening `parseDryRun`'s
equality check to `startsWith` broke the `--dry-run=true` test. Both
mutations were reverted bit-for-bit and the suite goes 20/20 green.
None of the six `tools/release/*.ts` scripts shipped with a unit test
in the initial cut. Add per-script suites covering every pure helper
that is safe to exercise without a live docker / dotnet / gh dependency:
- docker-build.test.ts (8): archSuffixFor for both platforms,
parseOptions happy path, --image default, --dry-run consumption,
missing / unrecognized / absent --platform + --version errors.
- docker-push.test.ts (10): archSuffixFor, per-arch push mode,
manifest mode, --image override, --dry-run consumption, and every
error arm (missing --version, neither --platform nor --manifest,
mutex enforcement, unrecognized --platform).
- gh-release-create.test.ts (14): parseOptions happy path + defaults
+ missing-version error; collectAssets across empty / missing /
populated dirs with dotfile + hidden-dir filtering + multi-dir
aggregation; renderReleaseNotes header, empty-assets sentinel,
per-asset basename rendering, docker-section presence toggle,
not-for-production prose invariant.
- pack.test.ts (4): parseOptions happy path, default --output path,
--dry-run consumption, missing --version error including the
example-hint suffix.
- nuget-push.test.ts (7): every default (input, source), the
--api-key -> NUGET_API_KEY fallback ladder (flag beats env, env
beats undefined), --dry-run consumption.
- sbom.test.ts (9): --nuget vs --docker mode selection, mutex
enforcement, missing-mode error, --input / --output defaults +
overrides, --dry-run consumption.
- shell.ts unit suite (from prior commit) reruns as part of the
same runner.
Also:
- `refactor`: promote the previously module-private `archSuffixFor`,
`parseOptions`, `collectAssets`, `renderReleaseNotes`, and each
`Options` type to `export` so the test files can invoke them
without indirection through `main()`. Zero runtime-behavior delta.
- `chore`: add `tools/run-tests.ts` — a small discover-and-spawn
runner that walks `tools/**/*.test.ts` and returns non-zero on
any file's failure. Wired as the `test` npm script alongside the
existing `typecheck` script.
RED-verified each new suite by temporarily mutating the SUT and
observing the corresponding test's failure:
- swap archSuffixFor return arms -> docker-build test failed;
- drop the platform/manifest mutex -> docker-push test failed;
- render assets as absolute paths instead of backtick basenames ->
gh-release-create test failed;
- strip the example hint from pack's missing-version error ->
pack test failed;
- remove the NUGET_API_KEY env fallback -> nuget-push test failed;
- drop the --nuget/--docker mutex -> sbom test failed.
Each SUT restored bit-for-bit before commit; 103 assertions pass
across the 8 test files under `npm test`.
Replaces the placeholder v4 SHA on actions/setup-dotnet with the SHA of the v4.3.1 tag verified via `gh api repos/actions/setup-dotnet/git/ refs/tags/v4.3.1`, and updates renovatebot/github-action from an unverified SHA to the SHA of tag v43.0.7 verified via `gh api repos/renovatebot/github-action/git/refs/tags/v43.0.7`. Closes findings F-SEC-001 and F-SEC-002 from the PR TrakHound#225 review.
`countCommitsSinceLastDevBoundary` returned `i + 1` at both the
stable-cut-marker and dev-tag boundaries, off by one relative to the
docstring ("commits BEFORE the marker") and out of step with the
`--range` code path which counts commits after the boundary. Change
both return sites to `Math.max(1, i)` so the two paths agree on a
fixture where the marker sits at HEAD~1 (expected N = 1) and the
floor still guards against `-dev.0` when the boundary sits on HEAD.
Extends `semver-bump.test.ts` with three git-fixture cases covering
the two boundary positions plus the no-boundary fall-through, and
reworks the top-of-file algorithm summary (F-DOC-002 + F-DOC-007) so
the docstring matches the code and calls out the `applyBump`
`none → patch` fall-through that keeps a chore-only range distinct
from the last stable tag.
Closes findings F-CR-001, F-DOC-002, and F-DOC-007 from the PR TrakHound#225
review.
`renovatebot/github-action` opens its own PRs per package on `renovate/*` branches, so the "one deps PR per week" invariant only ever held for the two ecosystems this workflow drives directly (npm + NuGet). The no-op "Handled by the Renovate step above" step is gone, the bulk-PR branch is renamed to `chore/deps-weekly-npm-nuget` to disambiguate it from Renovate's own branches, and the header comment + docs page now describe the two PR shapes accurately. npm bumps now pass `--cooldown "$MIN_AGE_DAYS"` (added in ncu v18), which pins the same 7-day supply-chain quarantine Renovate enforces on GH Actions + Dockerfile bases. NuGet has no equivalent (`dotnet-outdated` lacks any age filter) and the workflow header, step comment, and docs page all state this explicitly; a proper NuGet quarantine is tracked as a follow-up. Also switches the doc from Renovate's non-existent `docker` manager name to the actual `dockerfile` manager (F-DOC-005). Closes findings F-CR-002, F-CR-003, F-DOC-001, F-DOC-005, F-IMP-002, and F-SEC-005 from the PR TrakHound#225 review.
`buildx build --load` on the default docker driver silently strips `--sbom true` and `--provenance mode=max` — the loader only knows how to import a single-platform image manifest, not an image index. Drop both flags from `docker-build.ts` and add a comment explaining why so a future author does not re-add them. The downstream `sbom` job in `release.yml` still emits an SPDX SBOM from the pushed image, so the metadata surface is preserved without the buildx-strip warning. Swaps the docker SBOM path from `docker scout sbom` (required a `docker scout` install on every runner, produced a materially different SPDX shape) to `anchore/sbom-action` (v0.24.0, pinned by SHA verified via `gh api repos/anchore/sbom-action/git/refs/tags/ v0.24.0`). `sbom.ts` still supports docker mode locally, now via syft — the same engine anchore/sbom-action wraps — so `--dry-run` output shape agrees with CI. Docs pages updated to name the new backend. Closes findings F-CR-005 and F-CR-006 from the PR TrakHound#225 review.
`create-gh-release` now names `vuln-scan` on its own `needs:` list
directly instead of leaning on the transitive path through
`publish-nuget`, so a future rewire that lets publish-nuget bypass
the vuln scan (e.g. via an `if:` override) still hard-fails the
release cut (F-CR-009).
`gh-release-create.ts` gains three fixes:
- `collectAssets` now uses `readdirSync(dir, { recursive: true,
withFileTypes: true })` and filters to `.isFile()`, so nested
SBOM layouts like `_manifest/spdx_2.2/manifest.spdx.json`
(the shape `Microsoft.Sbom.DotNetTool` emits) reach the release
attachment list; the prior code only walked the top level despite
a docstring claiming one-level recursion. Nested dotfiles /
dot-directories are still skipped.
- A `--target <sha>` option pins the tag to the SHA that produced
the artefacts; the workflow now passes `${{ github.sha }}` so a
concurrent `master` push cannot race the tag onto a different
commit than the one the artefacts were built from.
- The `gh release create` invocation is now idempotent: a pre-step
swallows the "already exists" error by deleting the prior
release + tag when the workflow re-runs on the same SHA. Prior
behavior errored on the second run and left the pipeline red.
Extends the test suite with fixtures for nested-asset discovery
(`_manifest/spdx_2.2/manifest.spdx.json`), nested-dotfile skipping,
and the new `--target` field.
Closes findings F-CR-009, F-IMP collectAssets recursion, F-IMP
idempotency, and F-IMP release-tag target from the PR TrakHound#225 review.
The plugin was declared under `plugins:` in `commitlint.config.mjs` and installed in `pre-merge.yml`'s commitlint step but no rule ever referenced it — the `scope-enum` rule that gates the pinned scope list is the standard rule from `@commitlint/config-conventional`, not the plugin's selective-scope override. Removing the declaration shrinks the CI install by one dependency and shrinks the local commit-msg attack surface. Closes findings F-CR-011, F-SEC-004, and the related SIMP recommendation from the PR TrakHound#225 review.
`shell.ts` gains a `SECRET_ARG_NAMES` set (`--api-key`, `--password`, `-p`, `--token`) and `renderCmd()` now redacts the value that follows any of them — both the `--name value` two-token form and the `--name=value` single-token form. The actual argv passed to the child spawn is untouched; only the log line is rewritten. Guards against a CI-log line that echoes the command from leaking a credential a caller placed on the argv. `nuget-push.ts` no longer places the raw API key on the parent's argv either. The `dotnet nuget push` invocation is wrapped in `sh -c '…$NUGET_API_KEY'` with the key in the child's `env:`, so the parent process's argv holds only the shell wrapper and the literal string `$NUGET_API_KEY`. `renderCmd`'s SECRET_ARG_NAMES redaction is the second line of defense for the CI log. Adds eight new test cases pinning the two-token, equals-form, multi-secret, end-of-argv, and API-surface contracts. Closes finding F-SEC-003 from the PR TrakHound#225 review.
`dotnet tool install --global` without `--version` resolves to the latest published NuGet version at install-time, which lets a compromised or accidentally-broken upstream publish silently land on a release-cutting runner. Pin to the current known-good release verified via `gh api repos/microsoft/sbom-tool/releases/latest`. Closes finding F-SEC-007 from the PR TrakHound#225 review.
Doc corrections lifted out of the PR TrakHound#225 review: - `release-pipeline.md`: `DOCKERHUB_USERNAME` + `DOCKERHUB_TOKEN` are also consumed by `vuln-scan` (Trivy needs them to pull the image before scanning). Adds a `needs:` column to the jobs table and a mermaid `graph LR` of the same dependency graph — the two surfaces stay in step because they describe the same `release.yml` shape. - `commit-format.md`: the `header-max-length` rule measures the whole first line, not the subject alone — reword to state that plus the `body-leading-blank` + `footer-leading-blank` rules the config also enforces. - `tools-release.md`: adds a `shell.ts` section documenting the shared helper's surface (`run`, `renderCmd` w/ secret redaction, `parseDryRun`, `requireEnv`, `optionalEnv`) and reworks the opener to exclude helpers explicitly; the old text falsely claimed every script under `tools/release/` exposes a `main`. - `tools/dev/README.md` + `tools/docs/README.md`: shrunk from placeholder stubs to one-line pointers at `/reference/cli` per the "no placeholder README" project convention. Closes findings F-DOC-003, F-DOC-004, F-DOC-006, F-DOC-008, and F-DOC-010 from the PR TrakHound#225 review.
`tools/` gained TypeScript unit suites for `ci/*.ts` and every `release/*.ts` script (three commits earlier), but they weren't running under any workflow — the pre-merge gate only ran commitlint, and `dotnet.yml` covers only the .NET matrix. Adds a second job `unit-tests-tools` that runs `npm ci && npm run typecheck && npm test` under `tools/` so those suites participate in the required-check set. The job stays lightweight (Node.js only, no dotnet, no docker) so its wall-clock stays under a minute and it does not delay the merge on a green run. Closes the "tests exist but not wired to CI" gap flagged in the PR TrakHound#225 review.
…ntine Threads the just-filed follow-up issue number into the `deps-update.yml` header comment, the NuGet-bump step comment, and the `deps-update.md` supply-chain section so the "no NuGet quarantine" caveat is tracked to an actionable next step rather than left as an open thread.
ottobolyos
force-pushed
the
feat/release-automation-phase1
branch
from
August 21, 2026 16:05
f3992e3 to
9d8516f
Compare
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
…n/up-to-pr-225
ottobolyos
added a commit
to ottobolyos/mtconnect.net
that referenced
this pull request
Aug 21, 2026
…n/up-to-pr-225
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Phase-1 scaffolding for the automated pre-release pipeline and weekly deps-update workflow discussed on discussion #175. Restricted to what the maintainer approved in the 2026-08-16T20:10:44Z reply; explicitly excludes what he asked to defer.
Scope landed on this branch
tools/restructure — newrelease/,dev/,docs/,ci/subfolders. Every script is TypeScript run undertsx(no build step, no dotnet dependency on CI). Roottools/package.json+tools/tsconfig.json.tools/release/:pack.ts,nuget-push.ts,docker-build.ts,docker-push.ts,sbom.ts,gh-release-create.ts,shell.ts(shared helper). Every script exercised under--dry-runlocally.tools/ci/semver-bump.ts+semver-bump.test.ts. Reads commit range, computes next<version>-dev.<N>pre-release version per the conventional-commits map. Filters out non-numeric tags viagit describe --exclude 'v*-*'.*.test.ts), wired viatools/run-tests.tsand invoked from CI (pre-merge.ymlrunsnpm testundertools/).Workflows — three new files under
.github/workflows/, all passactionlintclean:release.yml(triggered on push to master): compute-version → pack → docker-amd64 + docker-arm64 in parallel → docker-manifest → sbom → vuln-scan → publish-nuget → create-gh-release. All gated ongithub.ref == 'refs/heads/master'.deps-update.yml(cron Sat 02:00 UTC): bumps four ecosystems across two PR shapes — GH Actions + Dockerfile bases via per-package Renovate PRs onrenovate/*branches, and npm + NuGet via a single bulk PR onchore/deps-weekly-npm-nuget. GH Actions, Dockerfile, and npm all enforce a seven-day supply-chain quarantine; NuGet does not (dotnet-outdatedhas no built-in filter, tracked in deps-update: implement 7-day NuGet supply-chain quarantine (dotnet-outdated has no built-in filter) #237).pre-merge.yml: runscommitlinton the PR commit range andnpm test+typecheckundertools/on every PR head.Repo-root config —
commitlint.config.mjs(extends@commitlint/config-conventional, pins allowed scopes) +lefthook.yml(commit-msg via commitlint, pre-commitdotnet format whitespace --include {staged_files}).Docs-site pages under
docs/development/:release-pipeline.md(with mermaid job-graph),deps-update.md,commit-format.md,tools-release.md. Added to VitePress sidebar.Explicitly out of scope (Patrick's ask to defer)
build/MTConnect.NET-Builderstays untouched; the local, signed stable-cut flow is preserved.Follow-ups deferred to issues
Four items surfaced in the review that require their own PRs to resolve; each is tracked as an issue so this PR can merge without expanding scope:
dotnet packto the curated nupkg set (currently over-publishes) #234 —release: gate dotnet pack to the curated nupkg set (currently over-publishes)(F-CR-004).pack.tscurrently packs everything in the .sln that resolves as packable; the manualNuget.csflow's curated list is not yet ported.release: enforce or document 'stable tags must merge back into master' invariant(F-CR-007).lastStableTag()walksHEADancestry; a maintenance-branch stable cut that never merges back to master would silently pick an older base.deps-update: gate auto-merge on code-owner label(F-SEC-006). Auto-merge on green CI is the phase-1 shape approved on Automate semantic versioning + NuGet package publishing in CI #175; adding a human-approval gate is a governance decision beyond this PR.deps-update: implement 7-day NuGet supply-chain quarantine (dotnet-outdated has no built-in filter)(F-CR-002 second half, F-SEC-005). The workflow header, step comment, and docs page all name the caveat inline.Secrets you would need to define before turning on
release.ymlNUGET_API_KEY— classic API key.DOCKERHUB_USERNAME+DOCKERHUB_TOKEN— fordocker push trakhound/mtconnect-agent:....None of these secrets are referenced by any workflow that runs on PRs — only on push-to-master.
Dime review cycle 1
Retroactive backfill (2026-08-20). The 6-agent Ultrareview cycle ran on this PR (2026-08-19) and its findings landed atomically across 11 fix commits. Ledger reconstruction from commit history + PR comments:
[FINDING:A08]security-audit HIGH —setup-dotnetaction pinned to an unverified SHA; corrected infix(ci): pin setup-dotnet + renovatebot to verified upstream SHAs(713f956).[FINDING:A08]security-audit HIGH — renovatebot action pinned to an unverified SHA; corrected in the same 713f956 commit.[FINDING]code-review MEDIUM —semver-bump.tshad an off-by-one boundary at the dev-counter increment; fixed infix(ci): correct semver-bump boundary off-by-one at the dev-counter(92fc040) with branch-coverage pins intest(ci): close branch-coverage gaps in semver-bump pure helpers(55a03c0).[FINDING:A08]security-audit MEDIUM — supply-chain quarantine drift on the weekly deps-update workflow; split into per-package + bulk PR shapes inci(deps): split weekly update into per-package renovate + bulk PR(53ecb32).[FINDING:A08]security-audit MEDIUM — docker-build attestation strip: noisy buildx flags dropped + SBOM swapped to syft inrefactor(release): drop noisy buildx flags + swap docker SBOM to syft(11e3fd0).[FINDING]code-review MEDIUM — release-idempotency + SBOM asset recursion: GH release pinned to source SHA + SBOM recursion added infix(release): pin GH release to source SHA, recurse SBOMs, re-runable(e130397).[FINDING:A03]security-audit MEDIUM — secret arg redaction in shell logs + NuGet-key masking added infix(release): redact secret arg values in shell logs + hide NuGet key(ad41f40).[FINDING:A08]security-audit MEDIUM —Microsoft.Sbom.DotNetToolinstall pinned to v4.1.5 infix(ci): pin Microsoft.Sbom.DotNetTool install to v4.1.5(9806b73).[SIMPLIFY]simplification LOW — unusedcommitlint-plugin-selective-scopedependency dropped inchore(ci): drop unused commitlint-plugin-selective-scope dependency(1d5175e).[DOCS]documentation-audit — release docs corrected: secrets table, header cap, mermaid graph, README indocs(release): fix secrets table, header cap, mermaid graph, README(c3e462d); NuGet quarantine caveat linked to deps-update: implement 7-day NuGet supply-chain quarantine (dotnet-outdated has no built-in filter) #237 indocs(release): link follow-up issue #237 for the NuGet quarantine(f1af97f).[TEST]test-coverage-audit — tools/ went from 20 → 103 assertions across 8 test files; unit suites for everyrelease/*.tsadded intest(release): add unit suites for every release/*.ts + wire npm test(1910c8f) +test(release): add unit suite for shell.ts pure helpers(5814be1); newunit-tests-toolsCI job wired into the pre-merge required set viaci(test): wire tools/ npm test + typecheck into pre-merge required set(a8fe6e4).[IMPROVE]improvement — four follow-up MEDIUM items opened as tracking issues onTrakHound/MTConnect.NET: release: gatedotnet packto the curated nupkg set (currently over-publishes) #234 (pack.ts curation), release: enforce or document 'stable tags must merge back into master' invariant #235 (stable-tag reachability doc), deps-update: gate auto-merge on code-owner label #236 (auto-merge code-owner gate), deps-update: implement 7-day NuGet supply-chain quarantine (dotnet-outdated has no built-in filter) #237 (NuGet 7-day quarantine) — tracked per the project's medium-finding disposition convention.(Zero unfixed findings — Ready-eligible.)
Depends on