Skip to content

chore(ci): initialize runtime_ci_tooling for dynamic_library#8

Merged
tsavo-at-pieces merged 4 commits intomainfrom
chore/runtime-ci-tooling-setup
Mar 2, 2026
Merged

chore(ci): initialize runtime_ci_tooling for dynamic_library#8
tsavo-at-pieces merged 4 commits intomainfrom
chore/runtime-ci-tooling-setup

Conversation

@tsavo-at-pieces
Copy link
Contributor

Summary

  • Add runtime_ci_tooling to dev_dependencies and initialize repository CI/CD scaffolding.
  • Generate runtime CI assets (.runtime_ci, .gemini) plus CI/release/issue-triage workflows under .github/workflows.
  • Configure autodoc coverage for the package core and add local prompt generators so autodoc runs in this repository's release pipeline.

Test plan

  • dart pub get
  • dart run runtime_ci_tooling:manage_cicd init
  • dart run runtime_ci_tooling:manage_cicd update
  • dart run runtime_ci_tooling:manage_cicd update --workflows
  • dart run runtime_ci_tooling:manage_cicd autodoc --dry-run
  • dart run scripts/prompts/autodoc_quickstart_prompt.dart \"Dynamic Library Core\" \"lib\" \"lib\"
  • dart run scripts/prompts/autodoc_api_reference_prompt.dart \"Dynamic Library Core\" \"lib\" \"lib\"
  • dart run scripts/prompts/autodoc_examples_prompt.dart \"Dynamic Library Core\" \"lib\" \"lib\"

Set up runtime_ci_tooling with generated CI/release/triage workflows and repo-local autodoc configuration so docs automation runs reliably in this package.
Copilot AI review requested due to automatic review settings March 2, 2026 19:18
Copy link

@mack-at-pieces mack-at-pieces left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary

This PR bootstraps CI/CD infrastructure via runtime_ci_tooling for the dynamic_library package. The scaffolding covers CI (format, analyze, 6-platform test matrix), issue triage (Gemini-powered), and a sophisticated multi-stage release pipeline (version detection, triage, explore, compose, autodoc, release notes, GitHub release creation). The Dart source changes are formatting-only.

Overall the generated scaffolding is well-structured with good patterns (bot-commit detection, concurrency groups, artifact passing between jobs, fallback artifacts when Gemini is unavailable). I left inline comments on a few items that deserve attention — mostly around permissions, a missing conditional guard, and a token expression that looks like a copy-paste artifact.

Key Findings

Security / Permissions

  • ci.yaml is missing a top-level permissions block (CodeQL flagged this). The auto-format job correctly declares contents: write, but analyze and test inherit the default broad token scope. Suggest adding a restrictive top-level permissions: { contents: read } and letting auto-format override.
  • Redundant token fallback expression secrets.GITHUB_TOKEN || secrets.GITHUB_TOKEN appears in ci.yaml — both sides reference the same secret.

Correctness

  • issue-triage.yaml line 82: npm install -g @google/gemini-cli@latest is missing the if: steps.trigger.outputs.run == 'true' guard that every other step in that job has. This means Gemini CLI is installed even when triage is skipped.

Release Pipeline

  • The autodoc job pushes directly to main using a personal access token. This is documented behavior but worth calling out — it bypasses PR review and could conflict with branch protection rules or trigger unexpected CI runs (mitigated by [skip ci] in the commit message).
  • Job numbering comments in release.yaml have a duplicate: both "Release Notes Author" and "Create Release" are labeled "Job 6".

Minor / Style

  • ci.yaml user hook comment blocks (# --- BEGIN USER: pre-test ---) are at column 0 while surrounding steps are indented. YAML parses fine, but it's visually confusing.
  • auto-format only formats lib/. New scripts/prompts/*.dart files won't be auto-formatted on future changes.
  • The three autodoc prompt scripts shell out to find, cat, grep via Process.runSync('sh', ...) — works on CI (ubuntu) but not portable to Windows. Fine for the intended use case.

Dart Source Changes

  • lib/src/frb_init.dart and lib/src/loader.dart changes are purely auto-formatting adjustments (line-length 120). No behavioral changes. ✅

@@ -0,0 +1,202 @@
# Generated by runtime_ci_tooling v0.14.1
# Configured via .runtime_ci/config.json — run 'dart run runtime_ci_tooling:manage_cicd update --workflows' to regenerate.
name: CI

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CodeQL flagged this (and I agree): the workflow is missing a top-level permissions block. The auto-format job correctly scopes its own permissions, but pre-check, analyze, and test inherit the default (broad) GITHUB_TOKEN scope.

Suggested fix — add between name: and on::

permissions:
  contents: read
  packages: read

This lets auto-format keep its own contents: write override while everything else runs with least privilege.

- name: Configure Git for HTTPS with Token
shell: bash
run: |
TOKEN="${{ secrets.GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a template bug — both sides of the || reference the same secret:

TOKEN="${{ secrets.GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"

Was this intended to be secrets.SOME_PAT || secrets.GITHUB_TOKEN (falling back to the default token if a PAT isn't configured)? Same pattern appears in the test job at line 156.

with:
node-version: "22"

- run: npm install -g @google/gemini-cli@latest

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This step is missing the if: steps.trigger.outputs.run == 'true' guard that all other steps in this job have. The Gemini CLI will be installed globally even when the triage trigger check determined the job should be skipped.

Should be:

      - run: npm install -g @google/gemini-cli@latest
        if: steps.trigger.outputs.run == 'true'

git add docs/ .runtime_ci/autodoc.json || true
if ! git diff --cached --quiet; then
git commit -m "bot(autodocs): update generated documentation [skip ci]"
git push origin main

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The autodoc job pushes directly to main using a personal access token, bypassing PR review. The [skip ci] tag prevents infinite loops, but a few considerations:

  1. If branch protection requires PR reviews, this push will fail unless the PAT owner has bypass permissions.
  2. Concurrent release runs could cause push conflicts if autodoc changes overlap.
  3. If the [skip ci] tag is ever removed from the commit message format, this creates an infinite CI loop.

This is a known trade-off for automated doc generation — just flagging for awareness.


# ============================================================================
# Job 6: Create Release
# ============================================================================

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this comment says "Job 6" but the previous section (Release Notes Author at line ~512) is also labeled "Job 6". This should be "Job 7" to match the sequence. (Post-Release Triage below is labeled "Job 7" as well, creating a second conflict.)

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR bootstraps CI/CD and developer-tooling infrastructure for the dynamic_library package. It integrates runtime_ci_tooling as a dev dependency, generating GitHub Actions workflows for CI, automated releases, and issue triage; it also adds a Gemini AI configuration and autodoc prompt generators for LLM-driven documentation generation.

Changes:

  • Add runtime_ci_tooling dev dependency with git+tag_pattern resolution, and add .runtime_ci/ configuration files (config.json, autodoc.json, template_versions.json) that drive the tooling.
  • Add three GitHub Actions workflow files (ci.yaml, release.yaml, issue-triage.yaml) for CI, release automation, and issue triage.
  • Add three Dart prompt-generator scripts under scripts/prompts/ and Gemini CLI configuration under .gemini/ for LLM-driven autodoc.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
pubspec.yaml Adds runtime_ci_tooling as a git dev dependency
.runtime_ci/config.json Main CI/CD and tooling configuration
.runtime_ci/autodoc.json Autodoc module configuration (paths, generators, models)
.runtime_ci/template_versions.json Tracks generated template hashes/versions
.github/workflows/ci.yaml New CI workflow (lint, format, analyze, multi-platform test)
.github/workflows/release.yaml New multi-stage automated release pipeline
.github/workflows/issue-triage.yaml New AI-powered issue triage workflow
scripts/prompts/autodoc_quickstart_prompt.dart LLM prompt generator for QUICKSTART.md
scripts/prompts/autodoc_api_reference_prompt.dart LLM prompt generator for API_REFERENCE.md
scripts/prompts/autodoc_examples_prompt.dart LLM prompt generator for EXAMPLES.md
.gemini/settings.json Gemini CLI tool configuration and MCP servers
.gemini/commands/triage.toml Gemini /triage command definition
.gemini/commands/release-notes.toml Gemini /release-notes command definition
.gemini/commands/changelog.toml Gemini /changelog command definition
.gitignore Adds .runtime_ci/runs/ to ignore list

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

workingDirectory: Directory.current.path,
);
if (result.exitCode == 0) return (result.stdout as String).trim();
return '(command failed)';
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _runSync function in this file returns '(command failed)' when the shell command exits with a non-zero exit code (line 128), while the same function in autodoc_examples_prompt.dart and autodoc_api_reference_prompt.dart returns an empty string '' in the same situation. This inconsistency means failed commands will inject the literal text "(command failed)" into the LLM prompt output in this file, while the other files silently produce empty sections. All three scripts should use the same behavior for consistency.

Suggested change
return '(command failed)';
return '';

Copilot uses AI. Check for mistakes.
Comment on lines +8 to +12
/// dart run scripts/prompts/autodoc_api_reference_prompt.dart <module_name> <source_dir> [lib_dir]
void main(List<String> args) {
if (args.length < 2) {
stderr.writeln(
'Usage: autodoc_api_reference_prompt.dart <module_name> <source_dir> [lib_dir]',
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The docstring (line 8) and usage error message (line 12) both mention [lib_dir] as an accepted third argument, but the function body only reads args[0] and args[1]. The lib_dir argument (if provided) is silently ignored. Either remove [lib_dir] from the usage documentation, or add final libDir = args.length > 2 ? args[2] : ''; and use it to filter the search scope as done in the other two prompt scripts.

Suggested change
/// dart run scripts/prompts/autodoc_api_reference_prompt.dart <module_name> <source_dir> [lib_dir]
void main(List<String> args) {
if (args.length < 2) {
stderr.writeln(
'Usage: autodoc_api_reference_prompt.dart <module_name> <source_dir> [lib_dir]',
/// dart run scripts/prompts/autodoc_api_reference_prompt.dart <module_name> <source_dir>
void main(List<String> args) {
if (args.length < 2) {
stderr.writeln(
'Usage: autodoc_api_reference_prompt.dart <module_name> <source_dir>',

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,65 @@
{
"_comment": "Copy this file to your package's .gemini/settings.json and customize as needed.",
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _comment field still reads "Copy this file to your package's .gemini/settings.json and customize as needed." Since this file already is the package's .gemini/settings.json, this comment is stale template text and should be removed or updated to reflect its current role.

Suggested change
"_comment": "Copy this file to your package's .gemini/settings.json and customize as needed.",
"_comment": "Gemini configuration for this package.",

Copilot uses AI. Check for mistakes.
- name: Configure Git for HTTPS with Token
shell: bash
run: |
TOKEN="${{ secrets.GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the "Configure Git for HTTPS with Token" steps in both the analyze job and the test job, the TOKEN variable is set with secrets.GITHUB_TOKEN || secrets.GITHUB_TOKEN — both sides of the OR are identical. All other workflow files (release.yaml, issue-triage.yaml) use secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN as the fallback pattern for this purpose. The duplication means the PAT is never used and only the standard GITHUB_TOKEN (with potentially limited permissions) will be used for git operations, which may fail to fetch private dependencies from git sources.

Suggested change
TOKEN="${{ secrets.GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"
TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}"

Copilot uses AI. Check for mistakes.
- name: Configure Git for HTTPS with Token
shell: bash
run: |
TOKEN="${{ secrets.GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: the TOKEN fallback in the test job uses secrets.GITHUB_TOKEN || secrets.GITHUB_TOKEN (both sides identical). The intent — matching all other workflows — is secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN.

Suggested change
TOKEN="${{ secrets.GITHUB_TOKEN || secrets.GITHUB_TOKEN }}"
TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}"

Copilot uses AI. Check for mistakes.
with:
node-version: "22"

- run: npm install -g @google/gemini-cli@latest
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The npm install -g @google/gemini-cli@latest step on line 82 is missing the if: steps.trigger.outputs.run == 'true' guard that every other conditional step in this job has (checkout, git config, dart setup, cache, dart pub get, setup-node, cache go modules, and run triage all have this condition). Without the guard, npm install will always run even when the triage trigger check determined the job should be skipped, wasting time and potentially causing errors if the prior steps were skipped.

Suggested change
- run: npm install -g @google/gemini-cli@latest
- run: npm install -g @google/gemini-cli@latest
if: steps.trigger.outputs.run == 'true'

Copilot uses AI. Check for mistakes.
retention-days: 1

# ============================================================================
# Job 6: Create Release
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two jobs are both labeled "Job 6" in the section header comments — "Job 6: Stage 3 -- Release Notes Author" at line 512 and "Job 6: Create Release" at line 649. The create-release job should be labeled "Job 7" and post-release-triage should be "Job 8".

Suggested change
# Job 6: Create Release
# Job 7: Create Release

Copilot uses AI. Check for mistakes.
Comment on lines +120 to +124
String _runSync(String command) {
try {
final result = Process.runSync(
'sh',
['-c', command],
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _runSync helper executes arbitrary shell commands via sh -c where the command string is built from CLI arguments like sourceDir and libDir without any quoting or escaping. An attacker who can control these arguments (e.g., through configuration or CI parameters) can inject shell metacharacters into the interpolated command (such as in tree $sourceDir or grep ... $sourceDir) and achieve command execution with the script's privileges. Refactor _runSync and its callers to avoid sh -c and instead use Process.runSync with argument lists or otherwise ensure all user-controlled values are safely escaped before being incorporated into shell commands.

Suggested change
String _runSync(String command) {
try {
final result = Process.runSync(
'sh',
['-c', command],
List<String> _splitCommandLine(String command) {
final args = <String>[];
final buffer = StringBuffer();
bool inSingleQuotes = false;
bool inDoubleQuotes = false;
bool isEscaped = false;
for (var i = 0; i < command.length; i++) {
final char = command[i];
if (isEscaped) {
buffer.write(char);
isEscaped = false;
continue;
}
if (char == r'\\') {
isEscaped = true;
continue;
}
if (char == "'" && !inDoubleQuotes) {
inSingleQuotes = !inSingleQuotes;
continue;
}
if (char == '"' && !inSingleQuotes) {
inDoubleQuotes = !inDoubleQuotes;
continue;
}
final isWhitespace = char.trim().isEmpty;
if (!inSingleQuotes && !inDoubleQuotes && isWhitespace) {
if (buffer.isNotEmpty) {
args.add(buffer.toString());
buffer.clear();
}
continue;
}
buffer.write(char);
}
if (buffer.isNotEmpty) {
args.add(buffer.toString());
}
return args;
}
String _runSync(String command) {
try {
final parts = _splitCommandLine(command);
if (parts.isEmpty) {
return '';
}
final executable = parts.first;
final arguments = parts.sublist(1);
final result = Process.runSync(
executable,
arguments,

Copilot uses AI. Check for mistakes.
Comment on lines +100 to +105
String _runSync(String command) {
try {
final result = Process.runSync(
'sh',
['-c', command],
workingDirectory: Directory.current.path,
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _runSync helper runs shell commands with sh -c using a command string that is constructed from unescaped CLI arguments such as sourceDir and testDir. Because these arguments are interpolated directly into commands like grep ... $sourceDir and find $testDir ..., an attacker who can influence them can inject shell syntax (e.g., ; or &&) and execute arbitrary commands under the script's privileges. Update _runSync and its callers to avoid sh -c in favor of Process.runSync with explicit argument arrays or to robustly quote/escape any user-controlled input before it reaches the shell.

Copilot uses AI. Check for mistakes.
Comment on lines +78 to +83
String _runSync(String command) {
try {
final result = Process.runSync(
'sh',
['-c', command],
workingDirectory: Directory.current.path,
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The _runSync function invokes Process.runSync('sh', ['-c', command], ...) where command is composed from CLI-controlled sourceDir and file paths without shell escaping. This allows an attacker who can supply these arguments (or influence how they are derived) to inject additional shell commands into constructs like find $sourceDir ... and gain arbitrary command execution. Change _runSync and its call sites to avoid passing untrusted data through sh -c, using Process.runSync with separate arguments or rigorous quoting/escaping for any user-controlled values.

Copilot uses AI. Check for mistakes.
mark-at-pieces
mark-at-pieces previously approved these changes Mar 2, 2026
Copy link

@mark-at-pieces mark-at-pieces left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM; solid tooling adjustment, none of the code changed from a functionality perspective that will adjust the way we are loading dynamic libs, so we are gucci

Add explicit least-privilege permissions to CI, correct the token fallback expression for git HTTPS rewrites, and guard Gemini CLI installation in issue triage so it only runs when triage is triggered.
Route ubuntu-x64 and windows-x64 matrix entries to GitHub-hosted runner labels so required CI jobs can start under current runner availability.
@tsavo-at-pieces tsavo-at-pieces merged commit a0a2f59 into main Mar 2, 2026
13 checks passed
@tsavo-at-pieces tsavo-at-pieces deleted the chore/runtime-ci-tooling-setup branch March 2, 2026 19:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants