Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 31 additions & 14 deletions .gemini/commands/triage.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,34 +3,51 @@ description = "Triage a GitHub issue: classify, prioritize, detect duplicates. U
prompt = """
Triage GitHub issue #{{args}} for the !{grep '^name:' pubspec.yaml | sed 's/name: //'} package.

## Issue Details
## Repository Context
```
!{gh issue view {{args}} --json number,title,body,author,labels,createdAt --jq '"#\\(.number): \\(.title)\\nAuthor: @\\(.author.login)\\nLabels: \\([.labels[].name] | join(", "))\\nCreated: \\(.createdAt)\\n\\n\\(.body)"' 2>/dev/null || echo "Could not fetch issue"}
REPO: !{gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null || echo "UNKNOWN"}
```

## CRITICAL SAFETY RULES — READ BEFORE ACTING

1. **ALWAYS use `--repo <owner/repo>` on EVERY `gh` command.** Never rely on git remote resolution.
2. **ONLY operate on repositories owned by: `open-runtime`, `pieces-app`.** If the repo above belongs to a different org (e.g. `grpc`, `niclas-pricken`, etc.), STOP IMMEDIATELY and report: "Refusing to triage — repo belongs to an unauthorized org."
3. **Before posting any comment**, check the existing comments below for duplicates. If a triage comment already exists, do NOT post another one.
4. **Never post, edit, label, or close issues on upstream/parent repos.** This is a fork — only operate on the fork's own issues.

## Issue Details (including existing comments)
```
!{REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null); case "$REPO" in open-runtime/*|pieces-app/*) ;; *) echo "BLOCKED: repo $REPO belongs to unauthorized org — refusing to fetch"; exit 0;; esac; gh issue view {{args}} --repo "$REPO" --json number,title,body,author,labels,createdAt,comments --jq '"#\\(.number): \\(.title)\\nAuthor: @\\(.author.login)\\nLabels: \\([.labels[].name] | join(", "))\\nCreated: \\(.createdAt)\\n\\n\\(.body)\\n\\n--- EXISTING COMMENTS (\\(.comments | length)) ---\\n\\(.comments | map("[\\(.author.login) @ \\(.createdAt)]:\\n\\(.body)") | join("\\n---\\n"))"' 2>/dev/null || echo "Could not fetch issue"}
```

## Package Structure
```
!{tree lib/ -L 2 --dirsfirst -d}
!{tree lib/ -L 2 --dirsfirst -d 2>/dev/null || echo "No lib/ directory"}
```

## Open Issues (for duplicate detection)
```
!{gh issue list --state open --limit 50 --json number,title,labels --jq '.[] | "#\\(.number): \\(.title) [\\([.labels[].name] | join(", "))]"' 2>/dev/null || echo "Could not list issues"}
!{REPO=$(gh repo view --json nameWithOwner -q .nameWithOwner 2>/dev/null); case "$REPO" in open-runtime/*|pieces-app/*) ;; *) echo "BLOCKED: repo $REPO belongs to unauthorized org"; exit 0;; esac; gh issue list --repo "$REPO" --state open --limit 50 --json number,title,labels --jq '.[] | "#\\(.number): \\(.title) [\\([.labels[].name] | join(", "))]"' 2>/dev/null || echo "Could not list issues"}
```

## Triage Tasks

1. **Type**: Classify as one of: bug, feature-request, enhancement, documentation, question
2. **Priority**: Assign P0-critical, P1-high, P2-medium, or P3-low
3. **Area**: Classify area(s): proto, ml-models, core, provisioning, grpc, crypto, googleapis, ci-cd, docs
4. **Duplicates**: Check open issues for duplicates (HIGH/MEDIUM/LOW confidence)
5. **Comment**: Draft a helpful, welcoming comment for the reporter
1. **Org Check**: Verify the repository belongs to `open-runtime` or `pieces-app`. If not, STOP.
2. **Duplicate Check**: Review the EXISTING COMMENTS above. If a triage comment already exists, skip commenting.
3. **Type**: Classify as one of: bug, feature-request, enhancement, documentation, question
4. **Priority**: Assign P0-critical, P1-high, P2-medium, or P3-low
5. **Area**: Classify area(s) based on the package structure above
6. **Duplicates**: Check open issues for duplicates (HIGH/MEDIUM/LOW confidence)
7. **Comment**: Draft a helpful, welcoming comment for the reporter (only if no triage comment exists yet)

## Actions

Apply your triage using gh CLI:
- `gh issue edit {{args}} --add-label "<type>"`
- `gh issue edit {{args}} --add-label "<priority>"`
- `gh issue edit {{args}} --add-label "area/<area>"`
- `gh issue comment {{args}} --body "<comment>"`
IMPORTANT: Replace `OWNER/REPO` below with the actual repo from "Repository Context" above.
IMPORTANT: If any existing comment above already contains triage analysis, do NOT post a duplicate.

Apply your triage using gh CLI (ALWAYS include --repo):
- `gh issue edit {{args}} --repo OWNER/REPO --add-label "<type>"`
- `gh issue edit {{args}} --repo OWNER/REPO --add-label "<priority>"`
- `gh issue edit {{args}} --repo OWNER/REPO --add-label "area/<area>"`
- `gh issue comment {{args}} --repo OWNER/REPO --body "<comment>"`
"""
49 changes: 45 additions & 4 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Generated by runtime_ci_tooling v0.11.3
# Generated by runtime_ci_tooling v0.12.1
# Configured via .runtime_ci/config.json — run 'dart run runtime_ci_tooling:manage_cicd update --workflows' to regenerate.
name: CI

Expand Down Expand Up @@ -77,7 +77,7 @@ jobs:
echo "::notice::Code is already formatted."
fi

analyze-and-test:
analyze:
needs: [pre-check, auto-format]
if: needs.pre-check.outputs.should_run == 'true'
runs-on: ubuntu-latest
Expand All @@ -104,8 +104,8 @@ jobs:
uses: actions/cache@v5.0.3
with:
path: ~/.pub-cache
key: ${{ runner.os }}-dart-pub-${{ hashFiles('**/pubspec.yaml') }}
restore-keys: ${{ runner.os }}-dart-pub-
key: ${{ runner.os }}-${{ runner.arch }}-dart-pub-${{ hashFiles('**/pubspec.yaml') }}
restore-keys: ${{ runner.os }}-${{ runner.arch }}-dart-pub-

- run: dart pub get
env:
Expand All @@ -119,6 +119,44 @@ jobs:
exit 1
fi

test:
needs: [pre-check, analyze, auto-format]
if: needs.pre-check.outputs.should_run == 'true'
runs-on: ${{ matrix.runner }}
strategy:
fail-fast: false
matrix:
include: [{"platform_id":"ubuntu-x64","runner":"ubuntu-latest","os_family":"linux","arch":"x64"},{"platform_id":"ubuntu-arm64","runner":"runtime-ubuntu-24.04-arm64-208gb-64core","os_family":"linux","arch":"arm64"},{"platform_id":"macos-arm64","runner":"macos-latest","os_family":"macos","arch":"arm64"},{"platform_id":"macos-x64","runner":"macos-15-intel","os_family":"macos","arch":"x64"},{"platform_id":"windows-x64","runner":"windows-latest","os_family":"windows","arch":"x64"},{"platform_id":"windows-arm64","runner":"runtime-windows-11-arm64-208gb-64core","os_family":"windows","arch":"arm64"}]
steps:
- uses: actions/checkout@v6.0.2
with:
ref: ${{ needs.auto-format.outputs.sha }}
persist-credentials: false

- name: Configure Git for HTTPS with Token
shell: bash
run: |
TOKEN="${{ secrets.TSAVO_AT_PIECES_PERSONAL_ACCESS_TOKEN || secrets.GITHUB_TOKEN }}"
git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "git@github.com:"
git config --global url."https://x-access-token:${TOKEN}@github.com/".insteadOf "ssh://git@github.com/"
git config --global url."https://x-access-token:${TOKEN}@github.com/open-runtime/".insteadOf "git@github.com:open-runtime/"
git config --global url."https://x-access-token:${TOKEN}@github.com/pieces-app/".insteadOf "git@github.com:pieces-app/"

- uses: dart-lang/setup-dart@v1.7.1
with:
sdk: "3.9.2"

- name: Cache Dart pub dependencies
uses: actions/cache@v5.0.3
with:
path: ~/.pub-cache
key: ${{ runner.os }}-${{ runner.arch }}-dart-pub-${{ hashFiles('**/pubspec.yaml') }}
restore-keys: ${{ runner.os }}-${{ runner.arch }}-dart-pub-

- run: dart pub get
env:
GIT_LFS_SKIP_SMUDGE: "1"

# --- BEGIN USER: pre-test ---
# --- END USER: pre-test ---

Expand All @@ -127,3 +165,6 @@ jobs:

# --- BEGIN USER: post-test ---
# --- END USER: post-test ---

# --- BEGIN USER: extra-jobs ---
# --- END USER: extra-jobs ---
7 changes: 6 additions & 1 deletion .runtime_ci/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,11 @@
"managed_test": false
},
"secrets": {},
"sub_packages": []
"sub_packages": [],
"platforms": ["ubuntu-x64", "ubuntu-arm64", "macos-arm64", "macos-x64", "windows-x64", "windows-arm64"],
"runner_overrides": {
"ubuntu-arm64": "runtime-ubuntu-24.04-arm64-208gb-64core",
"windows-arm64": "runtime-windows-11-arm64-208gb-64core"
}
}
}
10 changes: 5 additions & 5 deletions .runtime_ci/template_versions.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"tooling_version": "0.11.3",
"updated_at": "2026-02-24T00:59:57.805138Z",
"tooling_version": "0.12.1",
"updated_at": "2026-02-24T16:04:43.651023Z",
"templates": {
"gemini_settings": {
"hash": "93983f49dd2f40d2ed245271854946d8916b8f0698ed2cfaf12058305baa0b08",
Expand All @@ -23,9 +23,9 @@
"updated_at": "2026-02-24T00:59:57.620091Z"
},
"workflow_ci": {
"hash": "b88e1af6c7579d24ce0f57a0a483c3f1d7e9c7b8ca5de2ee0fe5c0c49c18bc77",
"consumer_hash": "618010803b11f765c2359fc6769edec4178162b6d54a0061f086ee1a54278c9a",
"updated_at": "2026-02-24T00:59:57.726984Z"
"hash": "92c5c82c94e96022d4c7bd7372b1d04273e927223b9f8726a182871d89d1ef77",
"consumer_hash": "1f33e89a0ccffc4ec34838b4e76f9c4d6d7ab5eefb1d9731b554dfc0ed752696",
"updated_at": "2026-02-24T16:04:43.655468Z"
},
"workflow_release": {
"hash": "326627cf41fdeb6cd61dae2fda98599d5815a34e63e4a8af1aaa8f7ad18435d3",
Expand Down
11 changes: 7 additions & 4 deletions SETUP.md
Original file line number Diff line number Diff line change
Expand Up @@ -309,15 +309,18 @@ The CI workflow (`.github/workflows/ci.yaml`) is generated from your `ci` sectio
| `features.proto` | bool | `false` | Enable protobuf generation step |
| `features.lfs` | bool | `false` | Enable Git LFS checkout |
| `features.format_check` | bool | `true` | Enable `dart format` check |
| `features.analysis_cache` | bool | `true` | Cache analysis results across runs |
| `features.managed_analyze` | bool | `true` | Run `dart analyze` via tooling |
| `features.managed_test` | bool | `true` | Run `dart test` via tooling |
| `features.analysis_cache` | bool | `false` | Cache analysis results across runs |
| `features.managed_analyze` | bool | `false` | Run `dart analyze` via tooling |
| `features.managed_test` | bool | `false` | Run `dart test` via tooling |
| `platforms` | list | `["ubuntu"]` | Platform matrix. If 2+ entries, CI runs `analyze` once then `test` as a matrix. Valid: `ubuntu-x64`, `ubuntu-arm64`, `macos-arm64`, `macos-x64`, `windows-x64`, `windows-arm64` (plus aliases `ubuntu`, `macos`, `windows`). |
| `runner_overrides` | object | `{}` | Override platform IDs to custom `runs-on` labels (e.g. org-managed GitHub-hosted runners). Example: `{ "ubuntu-arm64": "runtime-ubuntu-24.04-arm64-208gb-64core" }` |
| `secrets` | object | `{}` | Additional secrets as `{ "ENV_NAME": "SECRET_NAME" }` |
| `sub_packages` | list | `[]` | Sub-packages as `[{ "name": "...", "path": "..." }]` |

You can add custom steps before/after tests using user-preservable sections in the
generated workflow — look for `# --- BEGIN USER: pre-test ---` and
`# --- END USER: post-test ---` markers.
`# --- END USER: post-test ---` markers. To add additional jobs (including reusable workflow calls),
use `# --- BEGIN USER: extra-jobs ---` / `# --- END USER: extra-jobs ---`.

### Customize the workflows

Expand Down
13 changes: 11 additions & 2 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -1242,8 +1242,17 @@ final exists = await commandExists('git');
**Triggers:** Push to `main`, pull requests targeting `main`

**Jobs:**
1. `pre-check` -- Skip bot commits (author `github-actions[bot]` or `[skip ci]`)
2. `analyze-and-test` -- Verify protos, run analysis, run tests
1. `pre-check` — Skip bot commits (author `github-actions[bot]` or `[skip ci]`)
2. Optional `auto-format` — If `ci.features.format_check=true`, auto-format `lib/` and push `bot(format)` commit
3. **Single-platform mode** (default, `ci.platforms` missing or 1 entry):
- `analyze-and-test` — Verify protos, run analysis, run tests
4. **Multi-platform mode** (`ci.platforms` has 2+ entries):
- `analyze` — Run analysis once (Ubuntu)
- `test` — Run tests as a matrix across OS+arch (`x64` + `arm64`)

**Platform matrix configuration:**
- `ci.platforms`: list of platform IDs (e.g. `["ubuntu-x64","ubuntu-arm64","macos-arm64","macos-x64","windows-x64","windows-arm64"]`)
- `ci.runner_overrides`: optional map to point platform IDs at custom `runs-on` labels (e.g. org-managed GitHub-hosted runners)

**Key steps:**
```yaml
Expand Down
75 changes: 71 additions & 4 deletions lib/src/cli/commands/analyze_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import 'package:args/command_runner.dart';
import '../../triage/utils/config.dart';
import '../utils/logger.dart';
import '../utils/repo_utils.dart';
import '../utils/sub_package_utils.dart';

/// Run dart analyze.
/// Run `dart analyze` on the root package and all configured sub-packages.
class AnalyzeCommand extends Command<void> {
@override
final String name = 'analyze';
Expand All @@ -24,6 +25,8 @@ class AnalyzeCommand extends Command<void> {

Logger.header('Running dart analyze');

final failures = <String>[];

// --fatal-infos is non-negatable (infos are non-fatal by default).
// --[no-]fatal-warnings supports negation; disable it so only errors fail CI.
final result = Process.runSync(Platform.resolvedExecutable, [
Expand All @@ -38,10 +41,74 @@ class AnalyzeCommand extends Command<void> {
if (stderr.isNotEmpty) Logger.error(stderr);

if (result.exitCode != 0) {
Logger.error('Analysis failed with exit code ${result.exitCode}');
exit(result.exitCode);
Logger.error('Root analysis failed with exit code ${result.exitCode}');
failures.add(config.repoName);
} else {
Logger.success('Root analysis complete');
}

// ── Sub-package analysis ──────────────────────────────────────────────
final subPackages = SubPackageUtils.loadSubPackages(repoRoot);
SubPackageUtils.logSubPackages(subPackages);

for (final sp in subPackages) {
final name = sp['name'] as String;
final path = sp['path'] as String;
final dir = '$repoRoot/$path';
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

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

While path normalization removes trailing slashes from config paths, the concatenation '$repoRoot/$path' on line 57 could still produce unexpected results if the path contains leading slashes or is an absolute path. Consider adding validation to ensure paths are relative and don't start with / or contain .. segments, which could escape the repository root.

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not applicable — paths come from a trusted developer-maintained config file.

The path value is read from .runtime_ci/config.json, which is checked into the repo by the developer who runs ci init. This is not untrusted user input — it's a configuration file under version control, equivalent to entries in pubspec.yaml or analysis_options.yaml. Adding path traversal guards here would be security theater against a threat model that doesn't apply (a developer who controls the config file already has full access to the repo).

This matches the existing pattern in the codebase — RepoUtils.findRepoRoot(), WorkflowGenerator.loadCiConfig(), etc. all concatenate paths from config without validation.


Logger.header('Analyzing sub-package: $name ($path)');

if (!Directory(dir).existsSync()) {
Logger.warn(' Directory not found: $dir — skipping');
continue;
}

if (!File('$dir/pubspec.yaml').existsSync()) {
Logger.error(' No pubspec.yaml in $dir — cannot analyze');
failures.add(name);
continue;
}

// Ensure dependencies are resolved (sub-packages have independent
// pubspec.yaml files that the root `dart pub get` may not cover).
final pubGetResult = Process.runSync(
Platform.resolvedExecutable,
['pub', 'get'],
workingDirectory: dir,
environment: {'GIT_LFS_SKIP_SMUDGE': '1'},
);
if (pubGetResult.exitCode != 0) {
final pubGetStderr = (pubGetResult.stderr as String).trim();
if (pubGetStderr.isNotEmpty) Logger.error(pubGetStderr);
Logger.error(' dart pub get failed for $name (exit code ${pubGetResult.exitCode})');
failures.add(name);
continue;
}

final spResult = Process.runSync(Platform.resolvedExecutable, [
'analyze',
'--no-fatal-warnings',
], workingDirectory: dir);

final spStdout = (spResult.stdout as String).trim();
if (spStdout.isNotEmpty) print(spStdout);

final spStderr = (spResult.stderr as String).trim();
if (spStderr.isNotEmpty) Logger.error(spStderr);

if (spResult.exitCode != 0) {
Logger.error('Analysis failed for $name (exit code ${spResult.exitCode})');
failures.add(name);
} else {
Logger.success('Analysis passed for $name');
}
}

if (failures.isNotEmpty) {
Logger.error('Analysis failed for ${failures.length} package(s): ${failures.join(', ')}');
exit(1);
}

Logger.success('Analysis complete');
Logger.success('All analysis complete');
}
}
11 changes: 11 additions & 0 deletions lib/src/cli/commands/compose_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import '../utils/prompt_resolver.dart';
import '../utils/release_utils.dart';
import '../utils/repo_utils.dart';
import '../utils/step_summary.dart';
import '../utils/sub_package_utils.dart';
import '../utils/version_detection.dart';

const String _kGeminiProModel = 'gemini-3.1-pro-preview';
Expand Down Expand Up @@ -79,6 +80,16 @@ class ComposeCommand extends Command<void> {
}
ctx.savePrompt('compose', prompt);

// Enrich prompt with sub-package context for multi-package repos
SubPackageUtils.enrichPromptWithSubPackages(
repoRoot: repoRoot,
prevTag: prevTag,
promptFilePath: ctx.artifactPath('compose', 'prompt.txt'),
buildInstructions: SubPackageUtils.buildHierarchicalChangelogInstructions,
newVersion: newVersion,
verbose: global.verbose,
);

if (global.dryRun) {
Logger.info('[DRY-RUN] Would run Gemini CLI with composer prompt (${prompt.length} chars)');
return;
Expand Down
Loading
Loading