chore(ci): initialize runtime_ci_tooling for dynamic_library#8
chore(ci): initialize runtime_ci_tooling for dynamic_library#8tsavo-at-pieces merged 4 commits intomainfrom
Conversation
Set up runtime_ci_tooling with generated CI/release/triage workflows and repo-local autodoc configuration so docs automation runs reliably in this package.
mack-at-pieces
left a comment
There was a problem hiding this comment.
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.yamlis missing a top-levelpermissionsblock (CodeQL flagged this). Theauto-formatjob correctly declarescontents: write, butanalyzeandtestinherit the default broad token scope. Suggest adding a restrictive top-levelpermissions: { contents: read }and lettingauto-formatoverride.- Redundant token fallback expression
secrets.GITHUB_TOKEN || secrets.GITHUB_TOKENappears inci.yaml— both sides reference the same secret.
Correctness
issue-triage.yamlline 82:npm install -g @google/gemini-cli@latestis missing theif: 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
autodocjob pushes directly tomainusing 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.yamlhave a duplicate: both "Release Notes Author" and "Create Release" are labeled "Job 6".
Minor / Style
ci.yamluser 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-formatonly formatslib/. Newscripts/prompts/*.dartfiles won't be auto-formatted on future changes.- The three autodoc prompt scripts shell out to
find,cat,grepviaProcess.runSync('sh', ...)— works on CI (ubuntu) but not portable to Windows. Fine for the intended use case.
Dart Source Changes
lib/src/frb_init.dartandlib/src/loader.dartchanges 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 | |||
There was a problem hiding this comment.
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: readThis lets auto-format keep its own contents: write override while everything else runs with least privilege.
.github/workflows/ci.yaml
Outdated
| - name: Configure Git for HTTPS with Token | ||
| shell: bash | ||
| run: | | ||
| TOKEN="${{ secrets.GITHUB_TOKEN || secrets.GITHUB_TOKEN }}" |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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:
- If branch protection requires PR reviews, this push will fail unless the PAT owner has bypass permissions.
- Concurrent release runs could cause push conflicts if autodoc changes overlap.
- 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 | ||
| # ============================================================================ |
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
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_toolingdev dependency with git+tag_patternresolution, 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)'; |
There was a problem hiding this comment.
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.
| return '(command failed)'; | |
| return ''; |
| /// 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]', |
There was a problem hiding this comment.
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.
| /// 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>', |
| @@ -0,0 +1,65 @@ | |||
| { | |||
| "_comment": "Copy this file to your package's .gemini/settings.json and customize as needed.", | |||
There was a problem hiding this comment.
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.
| "_comment": "Copy this file to your package's .gemini/settings.json and customize as needed.", | |
| "_comment": "Gemini configuration for this package.", |
.github/workflows/ci.yaml
Outdated
| - name: Configure Git for HTTPS with Token | ||
| shell: bash | ||
| run: | | ||
| TOKEN="${{ secrets.GITHUB_TOKEN || secrets.GITHUB_TOKEN }}" |
There was a problem hiding this comment.
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.
| TOKEN="${{ secrets.GITHUB_TOKEN || secrets.GITHUB_TOKEN }}" | |
| TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}" |
.github/workflows/ci.yaml
Outdated
| - name: Configure Git for HTTPS with Token | ||
| shell: bash | ||
| run: | | ||
| TOKEN="${{ secrets.GITHUB_TOKEN || secrets.GITHUB_TOKEN }}" |
There was a problem hiding this comment.
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.
| TOKEN="${{ secrets.GITHUB_TOKEN || secrets.GITHUB_TOKEN }}" | |
| TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}" |
| with: | ||
| node-version: "22" | ||
|
|
||
| - run: npm install -g @google/gemini-cli@latest |
There was a problem hiding this comment.
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.
| - run: npm install -g @google/gemini-cli@latest | |
| - run: npm install -g @google/gemini-cli@latest | |
| if: steps.trigger.outputs.run == 'true' |
| retention-days: 1 | ||
|
|
||
| # ============================================================================ | ||
| # Job 6: Create Release |
There was a problem hiding this comment.
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".
| # Job 6: Create Release | |
| # Job 7: Create Release |
| String _runSync(String command) { | ||
| try { | ||
| final result = Process.runSync( | ||
| 'sh', | ||
| ['-c', command], |
There was a problem hiding this comment.
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.
| 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, |
| String _runSync(String command) { | ||
| try { | ||
| final result = Process.runSync( | ||
| 'sh', | ||
| ['-c', command], | ||
| workingDirectory: Directory.current.path, |
There was a problem hiding this comment.
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.
| String _runSync(String command) { | ||
| try { | ||
| final result = Process.runSync( | ||
| 'sh', | ||
| ['-c', command], | ||
| workingDirectory: Directory.current.path, |
There was a problem hiding this comment.
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.
mark-at-pieces
left a comment
There was a problem hiding this comment.
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.
Summary
runtime_ci_toolingtodev_dependenciesand initialize repository CI/CD scaffolding..runtime_ci,.gemini) plus CI/release/issue-triage workflows under.github/workflows.Test plan
dart pub getdart run runtime_ci_tooling:manage_cicd initdart run runtime_ci_tooling:manage_cicd updatedart run runtime_ci_tooling:manage_cicd update --workflowsdart run runtime_ci_tooling:manage_cicd autodoc --dry-rundart 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\"